diff --git a/.circleci/config.yml b/.circleci/config.yml index 1336ac75fd9..7f2be753823 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,10 @@ version: 2.1 defaults: &defaults working_directory: ~/repo docker: - - image: pyodide/pyodide-env:19 + # Note: when updating the docker image version, + # make sure there are no extra old versions lying around. + # (e.g. `rg -F --hidden `) + - image: pyodide/pyodide-env:20220411-chrome99-firefox98 environment: - EMSDK_NUM_CORES: 3 EMCC_CORES: 3 @@ -13,16 +16,6 @@ defaults: &defaults CCACHE_DIR: /root/.ccache/ jobs: - lint: - <<: *defaults - resource_class: small - steps: - - checkout - - - run: - name: lint - command: make lint - test-docs: <<: *defaults resource_class: small @@ -44,11 +37,13 @@ jobs: - restore_cache: keys: - - -core-{{ checksum "Makefile.envs" }}-{{ checksum "cpython/Makefile" }}-v20210910- + - -core-v20220303-{{ checksum "cpython/Makefile" }}-{{ checksum "Makefile.envs" }} + - -core-v20220303-{{ checksum "cpython/Makefile" }} + - -core-v20220303 - run: name: build emsdk - no_output_timeout: 1200 + no_output_timeout: 20m command: | # This is necessary to use the ccache from emsdk source pyodide_env.sh @@ -62,7 +57,7 @@ jobs: - run: name: build cpython - no_output_timeout: 1200 + no_output_timeout: 20m command: | # This is necessary to use the ccache from emsdk source pyodide_env.sh @@ -73,7 +68,7 @@ jobs: - run: name: build pyodide core - no_output_timeout: 1200 + no_output_timeout: 20m command: | # This is necessary to use the ccache from emsdk source pyodide_env.sh @@ -84,12 +79,12 @@ jobs: - run: name: check-size - command: ls -lh build/ + command: ls -lh dist/ - save_cache: paths: - /root/.ccache - key: -core-{{ checksum "Makefile.envs" }}-{{ checksum "cpython/Makefile" }}-v20210910- + key: -core-v20220303-{{ checksum "cpython/Makefile" }}-{{ checksum "Makefile.envs" }} - run: name: Clean up workspace @@ -100,70 +95,7 @@ jobs: - run: name: Zip build directory command: | - tar cjf pyodide-build.tar.gz build - - - persist_to_workspace: - root: . - paths: - - . - - - store_artifacts: - path: /root/repo/build/ - - - store_artifacts: - path: /root/repo/pyodide-build.tar.gz - - - store_artifacts: - path: /root/repo/packages/build-logs - - build-packages-no-numpy-dependents: - <<: *defaults - resource_class: large - - steps: - - checkout - - - attach_workspace: - at: . - - - restore_cache: - keys: - - -pkg-{{ checksum "Makefile.envs" }}-v20210911- - - - run: - name: build packages - no_output_timeout: 1800 - command: | - source pyodide_env.sh - - # Set mtime for EM_CONFIG to avoid ccache cache misses - touch -m -d '1 Jan 2021 12:00' emsdk/emsdk/.emscripten - - ccache -z - PYODIDE_PACKAGES='*, no-numpy-dependents' make -C packages - ccache -s - environment: - PYODIDE_JOBS: 5 - - - run: - name: check-size - command: ls -lh build/ - - - save_cache: - paths: - - /root/.ccache - key: -pkg-{{ checksum "Makefile.envs" }}-v20210911- - - - persist_to_workspace: - root: . - paths: - - ./packages - - ./build - - - run: - name: Zip build directory - command: | - tar cjf pyodide-build.tar.gz build + tar cjf pyodide-build.tar.gz dist - persist_to_workspace: root: . @@ -171,7 +103,7 @@ jobs: - . - store_artifacts: - path: /root/repo/build/ + path: /root/repo/dist/ - store_artifacts: path: /root/repo/pyodide-build.tar.gz @@ -180,9 +112,12 @@ jobs: path: /root/repo/packages/build-logs build-packages: + parameters: + packages: + description: The packages to be built. + type: string <<: *defaults resource_class: large - steps: - checkout @@ -191,11 +126,12 @@ jobs: - restore_cache: keys: - - -pkg-{{ checksum "Makefile.envs" }}-v20210911- + - -pkg2-v20220303-{{ checksum "Makefile.envs" }} + - -pkg2-v20220303 - run: name: build packages - no_output_timeout: 1800 + no_output_timeout: 60m command: | source pyodide_env.sh @@ -203,39 +139,31 @@ jobs: touch -m -d '1 Jan 2021 12:00' emsdk/emsdk/.emscripten ccache -z - PYODIDE_PACKAGES='*' make -C packages + PYODIDE_PACKAGES='<< parameters.packages >>' make -C packages ccache -s environment: PYODIDE_JOBS: 5 - run: name: check-size - command: ls -lh build/ + command: ls -lh dist/ - save_cache: paths: - /root/.ccache - key: -pkg-{{ checksum "Makefile.envs" }}-v20210911- + key: -pkg2-v20220303-{{ checksum "Makefile.envs" }} - run: name: Zip build directory command: | - tar cjf pyodide-build.tar.gz build - - - persist_to_workspace: - root: . - paths: - - ./packages/.artifacts - - ./build - - - store_artifacts: - path: /root/repo/build/ + tar --exclude="node_modules" -cjf pyodide-build.tar.gz dist + tar cjf build-logs.tar.gz packages/build-logs - store_artifacts: path: /root/repo/pyodide-build.tar.gz - store_artifacts: - path: /root/repo/packages/build-logs + path: /root/repo/build-logs.tar.gz test-main: parameters: @@ -279,7 +207,8 @@ jobs: name: test command: | mkdir test-results - pytest \ + python3 -m pip install -e ./pyodide-build + PYODIDE_ROOT=. pytest \ --junitxml=test-results/junit.xml \ --verbose \ -k 'not (chrome or firefox or node)' \ @@ -298,19 +227,19 @@ jobs: - run: name: test command: | - cd src/js - npx tsd + cd src/test-js npm ci + npm link ../../dist + npm run test-types npm test - - run: - name: check if webpack cli works well with load-pyodide.js - command: | - git clone https://github.com/pyodide/pyodide-webpack-example.git - cd pyodide-webpack-example - npm ci - cp ../src/js/load-pyodide.js node_modules/pyodide/load-pyodide.js - head -20 node_modules/pyodide/load-pyodide.js - npx webpack + # - run: + # name: check if webpack cli works well with load-pyodide.js + # command: | + # git clone https://github.com/pyodide/pyodide-webpack-example.git + # cd pyodide-webpack-example + # npm ci + # cp ../src/js/*.js node_modules/pyodide/ + # npx webpack benchmark: <<: *defaults @@ -319,12 +248,17 @@ jobs: - checkout - attach_workspace: at: . + - run: + name: install requirements + command: | + pip3 install numpy matplotlib - run: name: benchmark command: | - python benchmark/benchmark.py /usr/local/bin/python3 build/benchmarks.json + python benchmark/benchmark.py all --output dist/benchmarks.json + - store_artifacts: - path: /root/repo/build/benchmarks.json + path: /root/repo/dist/benchmarks.json deploy-release: # To reduce chance of deployment issues, try to keep the steps here as @@ -346,7 +280,7 @@ jobs: - run: name: Deploy Github Releases command: | - cp -r build /tmp/pyodide + cp -r dist /tmp/pyodide cd /tmp/ tar cjf pyodide-build-${CIRCLE_TAG}.tar.bz2 pyodide/ ghr -t "${GITHUB_TOKEN}" -u "${CIRCLE_PROJECT_USERNAME}" -r "${CIRCLE_PROJECT_REPONAME}" -c "${CIRCLE_SHA1}" -delete "${CIRCLE_TAG}" ./pyodide-build-${CIRCLE_TAG}.tar.bz2 @@ -357,9 +291,9 @@ jobs: - run: name: Deploy to pyodide-cdn2.iodide.io command: | - find build/ -type f -print0 | xargs -0 -n1 -I@ bash -c "echo \"Compressing @\"; gzip @; mv @.gz @;" - aws s3 sync build/ "s3://pyodide-cdn2.iodide.io/v${CIRCLE_TAG}/full/" --exclude '*.data' --cache-control 'max-age=30758400, immutable, public' --content-encoding 'gzip' # 1 year cache - aws s3 sync build/ "s3://pyodide-cdn2.iodide.io/v${CIRCLE_TAG}/full/" --exclude '*' --include '*.data' --cache-control 'max-age=30758400, immutable, public' --content-type 'application/wasm' --content-encoding 'gzip' # 1 year + find dist/ -type f -print0 | xargs -0 -n1 -I@ bash -c "echo \"Compressing @\"; gzip @; mv @.gz @;" + aws s3 sync dist/ "s3://pyodide-cdn2.iodide.io/v${CIRCLE_TAG}/full/" --exclude '*.data' --cache-control 'max-age=30758400, immutable, public' --content-encoding 'gzip' # 1 year cache + aws s3 sync dist/ "s3://pyodide-cdn2.iodide.io/v${CIRCLE_TAG}/full/" --exclude '*' --include '*.data' --cache-control 'max-age=30758400, immutable, public' --content-type 'application/wasm' --content-encoding 'gzip' # 1 year # update 301 redirect for the /latest/* route. aws s3api put-bucket-website --cli-input-json file://.circleci/s3-website-config.json @@ -386,20 +320,15 @@ jobs: - run: name: Deploy to pyodide-cdn2.iodide.io command: | - find build/ -type f -print0 | xargs -0 -n1 -I@ bash -c "echo \"Compressing @\"; gzip @; mv @.gz @;" + find dist/ -type f -print0 | xargs -0 -n1 -I@ bash -c "echo \"Compressing @\"; gzip @; mv @.gz @;" aws s3 rm --recursive "s3://pyodide-cdn2.iodide.io/dev/full/" - aws s3 sync build/ "s3://pyodide-cdn2.iodide.io/dev/full/" --exclude '*.data' --cache-control 'max-age=3600, public' --content-encoding 'gzip' # 1 hour cache - aws s3 sync build/ "s3://pyodide-cdn2.iodide.io/dev/full/" --exclude '*' --include '*.data' --cache-control 'max-age=3600, public' --content-type 'application/wasm' --content-encoding 'gzip' # 1 hour cache + aws s3 sync dist/ "s3://pyodide-cdn2.iodide.io/dev/full/" --exclude '*.data' --cache-control 'max-age=3600, public' --content-encoding 'gzip' # 1 hour cache + aws s3 sync dist/ "s3://pyodide-cdn2.iodide.io/dev/full/" --exclude '*' --include '*.data' --cache-control 'max-age=3600, public' --content-type 'application/wasm' --content-encoding 'gzip' # 1 hour cache workflows: version: 2 build-and-deploy: jobs: - - lint: - filters: - tags: - only: /.*/ - - test-docs: filters: tags: @@ -415,23 +344,72 @@ workflows: tags: only: /.*/ - - build-packages-no-numpy-dependents: + - build-packages: + name: build-packages-no-numpy-dependents + packages: "*,no-numpy-dependents" requires: - build-core filters: tags: only: /.*/ + post-steps: + - persist_to_workspace: + root: . + paths: + - ./packages + - ./dist - build-packages: + name: build-packages-opencv-python + packages: opencv-python requires: - build-packages-no-numpy-dependents filters: tags: only: /.*/ + post-steps: + - persist_to_workspace: + root: . + paths: + - ./packages/opencv-python/build + - ./packages/opencv-python/dist + - ./packages/build-logs/opencv* + - ./dist/opencv* + + - build-packages: + name: build-packages-numpy-dependents + packages: "*,!opencv-python" + requires: + - build-packages-no-numpy-dependents + filters: + tags: + only: /.*/ + post-steps: + - persist_to_workspace: + root: . + paths: + - ./packages + - ./dist + + - build-packages: + name: build-packages + packages: "*" + requires: + - build-packages-numpy-dependents + - build-packages-opencv-python + filters: + tags: + only: /.*/ + post-steps: + - persist_to_workspace: + root: . + paths: + - ./packages/.artifacts + - ./dist - test-main: name: test-core-chrome - test-params: -k "chrome and not webworker" src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ + test-params: -k "chrome and not webworker" src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/ requires: - build-core filters: @@ -440,7 +418,7 @@ workflows: - test-main: name: test-core-firefox - test-params: -k "firefox and not webworker" src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ + test-params: -k "firefox and not webworker" src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/ requires: - build-core filters: @@ -449,7 +427,7 @@ workflows: - test-main: name: test-core-node - test-params: -k node src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ + test-params: -k node src packages/micropip packages/fpcast-test packages/sharedlib-test-py/ packages/cpp-exceptions-test/ requires: - build-core filters: @@ -494,7 +472,7 @@ workflows: - test-main: name: test-packages-node - test-params: -k "node and not numpy" packages/test* packages/*/test* + test-params: -k node packages/test* packages/*/test* requires: - build-packages filters: @@ -524,7 +502,6 @@ workflows: - deploy-release: requires: - - lint - test-python - test-core-firefox - test-packages-firefox @@ -535,7 +512,6 @@ workflows: only: /^\d+\.\d+\.\w+$/ - deploy-dev: requires: - - lint - test-python - test-core-firefox - test-packages-firefox diff --git a/.clang-format b/.clang-format index dc541884769..0c081366b43 100644 --- a/.clang-format +++ b/.clang-format @@ -107,6 +107,4 @@ SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 8 UseTab: Never ---- -Language: JavaScript ... diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000000..7d8d112cde1 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +extend-ignore = E203, E402, E501, E731, E741, B950 +select = C,E,F,W,B,B9 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 463d16d7bea..46ca0890963 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,6 @@ - - ### Description + remove those checkboxes, or mark them as checked. If you keep unchecked checkboxes, + we will assume that your PR is not ready to be merged --> - [ ] Add a [CHANGELOG](https://github.com/pyodide/pyodide/blob/main/docs/project/changelog.md) entry - [ ] Add / update tests diff --git a/.github/workflows/deploy_release.yml b/.github/workflows/deploy_release.yml index 97099346123..896d9bc5bf5 100644 --- a/.github/workflows/deploy_release.yml +++ b/.github/workflows/deploy_release.yml @@ -14,7 +14,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: "3.9" + python-version: "3.10.2" - name: Install dependencies run: python -m pip install build twine diff --git a/.github/workflows/docker_image.yml b/.github/workflows/docker_image.yml index 869f55e9e33..a71f9bc40be 100644 --- a/.github/workflows/docker_image.yml +++ b/.github/workflows/docker_image.yml @@ -2,9 +2,18 @@ name: pyodide-env-docker on: workflow_dispatch: inputs: - version: - description: "Version of the docker image to build." - required: true + chrome_version: + description: "Version of the chrome browser (e.g. 97, 96, latest)." + required: false + default: "97" + firefox_version: + description: "Version of the firefox browser (e.g. 96, 95, latest)." + required: false + default: "96" + tag: + description: "Tag of the docker image to build (If not set, -chrome[version]-firefox[version] will be used)" + required: false + default: "" env: GHCR_REGISTRY: ghcr.io IMAGE_NAME: pyodide/pyodide-env @@ -25,14 +34,26 @@ jobs: uses: docker/metadata-action@v3 with: images: ${{ env.IMAGE_NAME }} + - name: Set tag + id: tag + run: | + TAG=${{ github.event.inputs.tag }} + if [ -z "$TAG" ]; then + DATE=$(date +'%Y%m%d') + TAG="$DATE-chrome${{ github.event.inputs.chrome_version }}-firefox${{ github.event.inputs.firefox_version }}" + fi + echo "::set-output name=tag::$TAG" - name: Build and push Docker image to Docker Hub id: build uses: docker/build-push-action@v2 with: file: ./Dockerfile push: true - tags: ${{ env.IMAGE_NAME }}:${{ github.event.inputs.version }} + tags: ${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + CHROME_VERSION=${{ github.event.inputs.chrome_version }} + FIREFOX_VERSION=${{ github.event.inputs.firefox_version }} - name: Image digest run: echo ${{ steps.build.outputs.digest }} build_ghcr: @@ -51,13 +72,25 @@ jobs: uses: docker/metadata-action@v3 with: images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }} + - name: Set tag + id: tag + run: | + TAG=${{ github.event.inputs.tag }} + if [ -z "$TAG" ]; then + DATE=$(date +'%Y%m%d') + TAG="$DATE-chrome${{ github.event.inputs.chrome_version }}-firefox${{ github.event.inputs.firefox_version }}" + fi + echo "::set-output name=tag::$TAG" - name: Build and push Docker image to ${{ env.GHCR_REGISTRY }} id: build uses: docker/build-push-action@v2 with: file: ./Dockerfile push: true - tags: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.version }} + tags: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + CHROME_VERSION=${{ github.event.inputs.chrome_version }} + FIREFOX_VERSION=${{ github.event.inputs.firefox_version }} - name: Image digest run: echo ${{ steps.build.outputs.digest }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6ca3845d424..92b59d72947 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,20 @@ on: pull_request: branches: [main] +concurrency: + group: main-${{ github.head_ref }} + cancel-in-progress: true + jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v3 + with: + python-version: 3.10.2 + - uses: pre-commit/action@v2.0.3 + build-core: runs-on: ubuntu-latest env: @@ -28,7 +41,7 @@ jobs: - uses: conda-incubator/setup-miniconda@v2 with: activate-environment: pyodide-env - python-version: 3.9.5 + python-version: 3.10.2 channels: conda-forge - name: Check Python versions @@ -75,13 +88,13 @@ jobs: ccache -s - name: check-size - run: ls -lh build/ + run: ls -lh dist/ - name: Store artifacts build uses: actions/upload-artifact@v2 with: name: core-build - path: ./build/ + path: ./dist/ retention-days: 60 test-core: @@ -89,7 +102,7 @@ jobs: env: DISPLAY: :99 - needs: build-core + needs: [build-core] strategy: matrix: selenium_runner: [firefox] @@ -101,12 +114,12 @@ jobs: uses: actions/download-artifact@v2 with: name: core-build - path: ./build/ + path: ./dist/ - uses: conda-incubator/setup-miniconda@v2 with: activate-environment: pyodide-env - python-version: 3.9.5 + python-version: 3.10.2 channels: conda-forge - name: install test requirements @@ -120,7 +133,7 @@ jobs: shell: bash -l {0} run: | ls -lh - ls -lh build/ + ls -lh dist/ tools/pytest_wrapper.py src packages/micropip/ -v -k "${SELENIUM_RUNNER}" - name: run package tests @@ -129,5 +142,5 @@ jobs: shell: bash -l {0} run: | ls -lh - ls -lh build/ + ls -lh dist/ tools/pytest_wrapper.py packages/test* packages/*/test* -v -k "numpy and not joblib and ${SELENIUM_RUNNER}" diff --git a/.github/workflows/prebuilt_docker_image.yml b/.github/workflows/prebuilt_docker_image.yml index 70fe9b62bda..a7cff71cee1 100644 --- a/.github/workflows/prebuilt_docker_image.yml +++ b/.github/workflows/prebuilt_docker_image.yml @@ -1,13 +1,11 @@ name: prebuilt-docker-image on: - push: - tags: - - "*.*.*" + release: + types: [published] env: GHCR_REGISTRY: ghcr.io IMAGE_NAME: pyodide/pyodide - # Note: the following version needs updating after each pyodide-env image release - PYODIDE_ENV_VERSION: 19 + PYODIDE_ENV_VERSION: 20220411-chrome99-firefox98 jobs: build_docker: runs-on: ubuntu-latest @@ -15,6 +13,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + id: builder + with: + driver-opts: env.BUILDKIT_STEP_LOG_MAX_SIZE=-1 - name: Log into Docker Hub registry uses: docker/login-action@v1 with: @@ -33,8 +36,9 @@ jobs: push: true build-args: | VERSION=${{ env.PYODIDE_ENV_VERSION }} - tags: ${{ env.IMAGE_NAME }}:${{ github.event.inputs.version }} + tags: ${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }} labels: ${{ steps.meta.outputs.labels }} + builder: ${{ steps.builder.outputs.name }} - name: Image digest run: echo ${{ steps.build.outputs.digest }} build_ghcr: @@ -42,6 +46,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + id: builder + with: + driver-opts: env.BUILDKIT_STEP_LOG_MAX_SIZE=-1 - name: Log into registry ${{ env.GHCR_REGISTRY }} uses: docker/login-action@v1 with: @@ -61,7 +70,8 @@ jobs: push: true build-args: | VERSION=${{ env.PYODIDE_ENV_VERSION }} - tags: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.version }} + tags: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }} labels: ${{ steps.meta.outputs.labels }} + builder: ${{ steps.builder.outputs.name }} - name: Image digest run: echo ${{ steps.build.outputs.digest }} diff --git a/.gitignore b/.gitignore index 72eb62e33cd..5e39ec54cd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ *.a *.o *.pyc -src/js/pyproxy.gen.js +src/js/*.gen.* +src/js/*.out.* *.egg-info/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64daf2f65e7..0675ac56bf4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,38 +1,103 @@ -exclude: '^.*patches|.*\.cgi$' +exclude: (^.*patches|.*\.cgi$|^packages/micropip/src/micropip/externals|^benchmark/benchmarks$) default_language_version: - python: "3.9" + python: "3.10" repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: "v4.2.0" hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks - id: check-yaml + exclude: .clang-format + - id: debug-statements - id: end-of-file-fixer + - id: mixed-line-ending - id: trailing-whitespace + + - repo: https://github.com/PyCQA/isort + rev: "5.10.1" + hooks: + - id: isort + + - repo: https://github.com/asottile/pyupgrade + rev: "v2.32.0" + hooks: + - id: pyupgrade + args: ["--py310-plus"] + + - repo: https://github.com/hadialqattan/pycln + rev: "v1.2.5" + hooks: + - id: pycln + args: [--config=pyproject.toml] + stages: [manual] + - repo: https://github.com/psf/black - rev: 20.8b1 + rev: "22.3.0" hooks: - id: black - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.8 + + - repo: https://github.com/pycqa/flake8 + rev: "4.0.1" hooks: - id: flake8 - types: [file, python] - # only check for unused imports, as the rest is done by black - args: [--select=F401] + additional_dependencies: [flake8-bugbear] + - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.812 + rev: "v0.942" hooks: - id: mypy - args: - - --ignore-missing-imports - - repo: local + files: ^(packages/.*/src|src|pyodide-build/pyodide_build) + exclude: (setup.py|^src/tests|conftest.py) + args: [] + additional_dependencies: &mypy-deps + - packaging + - types-docutils + - types-pyyaml + - types-setuptools + - numpy + - id: mypy + name: mypy-tests + args: [--ignore-missing-imports] + files: ^(packages/|docs|conftest.py|src/tests) + exclude: (^packages/.*/setup.py|/src) + additional_dependencies: *mypy-deps + + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: "v13.0.1" hooks: - id: clang-format - name: clang-format-6.0 - language: system - entry: ./tools/clang-format-precommit.sh + types_or: [c++, c, cuda] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v2.3.2" # Use the sha / tag you want to point at + rev: "v2.6.2" hooks: - id: prettier + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: "v1.9.0" + hooks: + - id: python-check-blanket-noqa + - id: python-check-blanket-type-ignore + - id: python-no-log-warn + - id: python-no-eval + exclude: ^(src/py/_pyodide/_base\.py|src/tests/test_typeconversions\.py)$ + - id: python-use-type-annotations + exclude: docs/sphinx_pyodide/tests/test_directives\.py + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: "v0.8.0.4" + hooks: + - id: shellcheck + + - repo: https://github.com/codespell-project/codespell + rev: "v2.1.0" + hooks: + - id: codespell + args: ["-L", "te,slowy,aray,ba,nd,classs,feld"] + exclude: ^benchmark/benchmarks/pystone_benchmarks/pystone\.py$ diff --git a/.prettierignore b/.prettierignore index 5417b44211d..2ac3d558943 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,5 +8,5 @@ emsdk cpython .vscode .pytest_cache -.clang-format +.clang-format packages/CLAPACK/make.inc diff --git a/.readthedocs.yml b/.readthedocs.yml index 9e1f0253407..c5b096593d3 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -10,8 +10,12 @@ sphinx: formats: all -# Optionally set the version of Python and requirements required to build your docs python: - version: 3.8 install: - requirements: docs/requirements-doc.txt + +build: + os: ubuntu-20.04 + tools: + python: "3.10" + nodejs: "14" diff --git a/Dockerfile b/Dockerfile index 840c66b207b..5949a8f7f7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM node:14.16.1-buster-slim AS node-image -FROM python:3.9.5-slim-buster +FROM python:3.10.2-slim-buster RUN apt-get update \ && apt-get install -y --no-install-recommends \ @@ -8,25 +8,78 @@ RUN apt-get update \ patch pkg-config swig unzip wget xz-utils \ autoconf autotools-dev automake texinfo dejagnu \ build-essential prelink autoconf libtool libltdl-dev \ - # testing packages: libgconf-2-4 is necessary for running chromium - libgconf-2-4 "chromium=90.*" \ - && rm -rf /var/lib/apt/lists/* + gnupg2 libdbus-glib-1-2 sudo ADD docs/requirements-doc.txt requirements.txt / RUN pip3 --no-cache-dir install -r /requirements.txt \ && pip3 --no-cache-dir install -r /requirements-doc.txt -# Get firefox 70.0.1 and geckodriver -RUN wget -qO- https://ftp.mozilla.org/pub/firefox/releases/87.0/linux-x86_64/en-US/firefox-87.0.tar.bz2 | tar jx \ - && ln -s $PWD/firefox/firefox /usr/local/bin/firefox \ - && wget -qO- https://github.com/mozilla/geckodriver/releases/download/v0.29.1/geckodriver-v0.29.1-linux64.tar.gz | tar zxC /usr/local/bin/ +# Get Chrome and Firefox (borrowed from https://github.com/SeleniumHQ/docker-selenium) -# Get recent version of chromedriver -RUN wget --quiet https://chromedriver.storage.googleapis.com/90.0.4430.24/chromedriver_linux64.zip \ - && unzip chromedriver_linux64.zip \ - && mv $PWD/chromedriver /usr/local/bin \ - && rm -f chromedriver_linux64.zip +ARG CHROME_VERSION="latest" +ARG FIREFOX_VERSION="latest" +# Note: geckodriver version needs to be updated manually +ARG GECKODRIVER_VERSION="0.30.0" + +#============================================ +# Firefox & gekcodriver +#============================================ +# can specify Firefox version by FIREFOX_VERSION; +# e.g. latest +# 95 +# 96 +# +# can specify Firefox geckodriver version by GECKODRIVER_VERSION; +#============================================ + +RUN if [ $FIREFOX_VERSION = "latest" ] || [ $FIREFOX_VERSION = "nightly-latest" ] || [ $FIREFOX_VERSION = "devedition-latest" ] || [ $FIREFOX_VERSION = "esr-latest" ]; \ + then FIREFOX_DOWNLOAD_URL="https://download.mozilla.org/?product=firefox-$FIREFOX_VERSION-ssl&os=linux64&lang=en-US"; \ + else FIREFOX_VERSION_FULL="${FIREFOX_VERSION}.0" && FIREFOX_DOWNLOAD_URL="https://download-installer.cdn.mozilla.net/pub/firefox/releases/$FIREFOX_VERSION_FULL/linux-x86_64/en-US/firefox-$FIREFOX_VERSION_FULL.tar.bz2"; \ + fi \ + && wget --no-verbose -O /tmp/firefox.tar.bz2 $FIREFOX_DOWNLOAD_URL \ + && tar -C /opt -xjf /tmp/firefox.tar.bz2 \ + && rm /tmp/firefox.tar.bz2 \ + && mv /opt/firefox /opt/firefox-$FIREFOX_VERSION \ + && ln -fs /opt/firefox-$FIREFOX_VERSION/firefox /usr/local/bin/firefox \ + && wget --no-verbose -O /tmp/geckodriver.tar.gz https://github.com/mozilla/geckodriver/releases/download/v$GECKODRIVER_VERSION/geckodriver-v$GECKODRIVER_VERSION-linux64.tar.gz \ + && rm -rf /opt/geckodriver \ + && tar -C /opt -zxf /tmp/geckodriver.tar.gz \ + && rm /tmp/geckodriver.tar.gz \ + && mv /opt/geckodriver /opt/geckodriver-$GECKODRIVER_VERSION \ + && chmod 755 /opt/geckodriver-$GECKODRIVER_VERSION \ + && ln -fs /opt/geckodriver-$GECKODRIVER_VERSION /usr/local/bin/geckodriver \ + && echo "Using Firefox version: $(firefox --version)" \ + && echo "Using GeckoDriver version: "$GECKODRIVER_VERSION + + +#============================================ +# Google Chrome & Chrome webdriver +#============================================ +# can specify Chrome version by CHROME_VERSION; +# e.g. latest +# 96 +# 97 +#============================================ + +RUN if [ $CHROME_VERSION = "latest" ]; \ + then CHROME_VERSION_FULL=$(wget --no-verbose -O - "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"); \ + else CHROME_VERSION_FULL=$(wget --no-verbose -O - "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_${CHROME_VERSION}"); \ + fi \ + && CHROME_DOWNLOAD_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION_FULL}-1_amd64.deb" \ + && wget --no-verbose -O /tmp/google-chrome.deb ${CHROME_DOWNLOAD_URL} \ + && apt install -qqy /tmp/google-chrome.deb \ + && rm -f /tmp/google-chrome.deb \ + && rm -rf /var/lib/apt/lists/* \ + && wget --no-verbose -O /tmp/chromedriver_linux64.zip https://chromedriver.storage.googleapis.com/$CHROME_VERSION_FULL/chromedriver_linux64.zip \ + && rm -rf /opt/selenium/chromedriver \ + && unzip /tmp/chromedriver_linux64.zip -d /opt/selenium \ + && rm /tmp/chromedriver_linux64.zip \ + && mv /opt/selenium/chromedriver /opt/selenium/chromedriver-$CHROME_VERSION_FULL \ + && chmod 755 /opt/selenium/chromedriver-$CHROME_VERSION_FULL \ + && ln -fs /opt/selenium/chromedriver-$CHROME_VERSION_FULL /usr/local/bin/chromedriver \ + && echo "Using Chrome version: $(google-chrome --version)" \ + && echo "Using Chromedriver version: "$CHROME_VERSION_FULL COPY --from=node-image /usr/local/bin/node /usr/local/bin/ COPY --from=node-image /usr/local/lib/node_modules /usr/local/lib/node_modules diff --git a/Dockerfile-prebuilt b/Dockerfile-prebuilt index 93bbe16c3b3..fbc8206b14e 100644 --- a/Dockerfile-prebuilt +++ b/Dockerfile-prebuilt @@ -11,4 +11,4 @@ COPY . pyodide # the rm at the end deletes the build results such that the resulting image can still be used for building pyodide # from source (including partial and customized builds). Due to the previous run of make, builds # executed with this image will be much faster than the ones executed with pyodide-env -RUN cd pyodide && PYODIDE_PACKAGES='*' make && rm -r ./build +RUN cd pyodide && PYODIDE_PACKAGES='*' make && rm -r ./dist diff --git a/Makefile b/Makefile index d4368e0a83c..56cc79d854b 100644 --- a/Makefile +++ b/Makefile @@ -9,55 +9,60 @@ CXX=em++ all: check \ - build/pyodide.asm.js \ - build/pyodide.js \ - build/console.html \ - build/distutils.data \ - build/packages.json \ - build/pyodide_py.tar \ - build/test.data \ - build/test.html \ - build/webworker.js \ - build/webworker_dev.js + dist/pyodide.asm.js \ + dist/pyodide.js \ + dist/pyodide.d.ts \ + dist/package.json \ + npm-link \ + dist/console.html \ + dist/distutils.tar \ + dist/test.tar \ + dist/packages.json \ + dist/pyodide_py.tar \ + dist/test.html \ + dist/module_test.html \ + dist/webworker.js \ + dist/webworker_dev.js \ + dist/module_webworker_dev.js echo -e "\nSUCCESS!" $(CPYTHONLIB)/tzdata : pip install tzdata --target=$(CPYTHONLIB) -build/pyodide_py.tar: $(wildcard src/py/pyodide/*.py) $(wildcard src/py/_pyodide/*.py) - cd src/py && tar --exclude '*__pycache__*' -cvf ../../build/pyodide_py.tar pyodide _pyodide +dist/pyodide_py.tar: $(wildcard src/py/pyodide/*.py) $(wildcard src/py/_pyodide/*.py) + cd src/py && tar --exclude '*__pycache__*' -cf ../../dist/pyodide_py.tar pyodide _pyodide -build/pyodide.asm.js: \ +dist/pyodide.asm.js: \ src/core/docstring.o \ src/core/error_handling.o \ - src/core/numpy_patch.o \ + src/core/error_handling_cpp.o \ src/core/hiwire.o \ src/core/js2python.o \ src/core/jsproxy.o \ - src/core/keyboard_interrupt.o \ src/core/main.o \ src/core/pyproxy.o \ src/core/python2js_buffer.o \ src/core/python2js.o \ + src/js/_pyodide.out.js \ $(wildcard src/py/lib/*.py) \ $(CPYTHONLIB)/tzdata \ $(CPYTHONLIB) date +"[%F %T] Building pyodide.asm.js..." - [ -d build ] || mkdir build - $(CXX) -o build/pyodide.asm.js $(filter %.o,$^) \ + [ -d dist ] || mkdir dist + $(CXX) -o dist/pyodide.asm.js $(filter %.o,$^) \ $(MAIN_MODULE_LDFLAGS) # Strip out C++ symbols which all start __Z. # There are 4821 of these and they have VERY VERY long names. # To show some stats on the symbols you can use the following: - # cat build/pyodide.asm.js | grep -ohE 'var _{0,5}.' | sort | uniq -c | sort -nr | head -n 20 - sed -i -E 's/var __Z[^;]*;//g' build/pyodide.asm.js - sed -i '1i\ - "use strict";\ - let setImmediate = globalThis.setImmediate;\ - let clearImmediate = globalThis.clearImmediate;\ - let baseName, fpcGOT, dyncallGOT, fpVal, dcVal;\ - ' build/pyodide.asm.js - echo "globalThis._createPyodideModule = _createPyodideModule;" >> build/pyodide.asm.js + # cat dist/pyodide.asm.js | grep -ohE 'var _{0,5}.' | sort | uniq -c | sort -nr | head -n 20 + sed -i -E 's/var __Z[^;]*;//g' dist/pyodide.asm.js + sed -i '1i "use strict";' dist/pyodide.asm.js + # Remove last 6 lines of pyodide.asm.js, see issue #2282 + # Hopefully we will remove this after emscripten fixes it, upstream issue + # emscripten-core/emscripten#16518 + # Sed nonsense from https://stackoverflow.com/a/13383331 + sed -i -n -e :a -e '1,6!{P;N;D;};N;ba' dist/pyodide.asm.js + echo "globalThis._createPyodideModule = _createPyodideModule;" >> dist/pyodide.asm.js date +"[%F %T] done building pyodide.asm.js." @@ -70,11 +75,24 @@ node_modules/.installed : src/js/package.json src/js/package-lock.json ln -sfn src/js/node_modules/ node_modules touch node_modules/.installed -build/pyodide.js: src/js/*.js src/js/pyproxy.gen.js node_modules/.installed - npx typescript --project src/js +dist/pyodide.js src/js/_pyodide.out.js: src/js/*.ts src/js/pyproxy.gen.ts src/js/error_handling.gen.ts node_modules/.installed npx rollup -c src/js/rollup.config.js -src/js/pyproxy.gen.js : src/core/pyproxy.* src/core/*.h +dist/package.json : src/js/package.json + cp $< $@ + +.PHONY: npm-link +npm-link: dist/package.json + cd src/test-js && npm ci && npm link ../../dist + +dist/pyodide.d.ts: src/js/*.ts src/js/pyproxy.gen.ts src/js/error_handling.gen.ts + npx dts-bundle-generator src/js/pyodide.ts --export-referenced-types false + mv src/js/pyodide.d.ts dist + +src/js/error_handling.gen.ts : src/core/error_handling.ts + cp $< $@ + +src/js/pyproxy.gen.ts : src/core/pyproxy.* src/core/*.h # We can't input pyproxy.js directly because CC will be unhappy about the file # extension. Instead cat it and have CC read from stdin. # -E : Only apply prepreocessor @@ -82,18 +100,30 @@ src/js/pyproxy.gen.js : src/core/pyproxy.* src/core/*.h # definition files, rollup will strip them out) # -P : Don't put in macro debug info # -imacros pyproxy.c : include all of the macros definitions from pyproxy.c + # + # First we use sed to delete the segments of the file between + # "// pyodide-skip" and "// end-pyodide-skip". This allows us to give + # typescript type declarations for the macros which we need for intellisense + # and documentation generation. The result of processing the type + # declarations with the macro processor is a type error, so we snip them + # out. rm -f $@ - echo "// This file is generated by applying the C preprocessor to core/pyproxy.js" >> $@ + echo "// This file is generated by applying the C preprocessor to core/pyproxy.ts" >> $@ echo "// It uses the macros defined in core/pyproxy.c" >> $@ echo "// Do not edit it directly!" >> $@ - cat src/core/pyproxy.js | $(CC) -E -C -P -imacros src/core/pyproxy.c $(MAIN_MODULE_CFLAGS) - >> $@ + cat src/core/pyproxy.ts | \ + sed '/^\/\/\s*pyodide-skip/,/^\/\/\s*end-pyodide-skip/d' | \ + $(CC) -E -C -P -imacros src/core/pyproxy.c $(MAIN_MODULE_CFLAGS) - \ + >> $@ -build/test.html: src/templates/test.html +dist/test.html: src/templates/test.html cp $< $@ +dist/module_test.html: src/templates/module_test.html + cp $< $@ -.PHONY: build/console.html -build/console.html: src/templates/console.html +.PHONY: dist/console.html +dist/console.html: src/templates/console.html cp $< $@ sed -i -e 's#{{ PYODIDE_BASE_URL }}#$(PYODIDE_BASE_URL)#g' $@ @@ -105,54 +135,39 @@ docs/_build/html/console.html: src/templates/console.html sed -i -e 's#{{ PYODIDE_BASE_URL }}#$(PYODIDE_BASE_URL)#g' $@ -.PHONY: build/webworker.js -build/webworker.js: src/templates/webworker.js +.PHONY: dist/webworker.js +dist/webworker.js: src/templates/webworker.js cp $< $@ sed -i -e 's#{{ PYODIDE_BASE_URL }}#$(PYODIDE_BASE_URL)#g' $@ +.PHONY: dist/module_webworker_dev.js +dist/module_webworker_dev.js: src/templates/module_webworker.js + cp $< $@ + sed -i -e 's#{{ PYODIDE_BASE_URL }}#./#g' $@ -.PHONY: build/webworker_dev.js -build/webworker_dev.js: src/templates/webworker.js +.PHONY: dist/webworker_dev.js +dist/webworker_dev.js: src/templates/webworker.js cp $< $@ sed -i -e 's#{{ PYODIDE_BASE_URL }}#./#g' $@ update_base_url: \ - build/console.html \ - build/webworker.js - - - -lint: node_modules/.installed - # check for unused imports, the rest is done by black - flake8 --select=F401 src tools pyodide-build benchmark conftest.py docs packages/matplotlib/src/ - find src -type f -regex '.*\.\(c\|h\)' \ - | xargs clang-format-6.0 -output-replacements-xml \ - | (! grep ' 1: - error("%d arguments are too many;" % nargs) - elif nargs == 1: - try: - loops = int(sys.argv[1]) - except ValueError: - error("Invalid argument %r;" % sys.argv[1]) - else: - loops = LOOPS - main(loops) +def pystone(): + main(LOOPS) diff --git a/benchmark/plot_benchmark.py b/benchmark/plot_benchmark.py index c42686f0919..3c86b0efc3e 100644 --- a/benchmark/plot_benchmark.py +++ b/benchmark/plot_benchmark.py @@ -1,8 +1,9 @@ -import matplotlib.pyplot as plt -import numpy as np import json import sys +import matplotlib.pyplot as plt +import numpy as np + plt.rcdefaults() fig, ax = plt.subplots(constrained_layout=True, figsize=(8, 8)) diff --git a/conftest.py b/conftest.py index ba4fc2643a0..684cc17eea8 100644 --- a/conftest.py +++ b/conftest.py @@ -1,39 +1,40 @@ """ Various common utilities for testing. """ -import re import contextlib +import functools import json import multiprocessing -import textwrap -import tempfile -import time import os import pathlib -import pexpect import queue -import sys +import re import shutil +import sys +import tempfile +import textwrap +import time +import pexpect import pytest ROOT_PATH = pathlib.Path(__file__).parents[0].resolve() TEST_PATH = ROOT_PATH / "src" / "tests" -BUILD_PATH = ROOT_PATH / "build" +DIST_PATH = ROOT_PATH / "dist" sys.path.append(str(ROOT_PATH / "pyodide-build")) sys.path.append(str(ROOT_PATH / "src" / "py")) -from pyodide_build.testing import set_webdriver_script_timeout, parse_driver_timeout +from pyodide_build.testing import parse_driver_timeout, set_webdriver_script_timeout def pytest_addoption(parser): group = parser.getgroup("general") group.addoption( - "--build-dir", + "--dist-dir", action="store", - default=BUILD_PATH, - help="Path to the build directory", + default=DIST_PATH, + help="Path to the dist directory", ) group.addoption( "--run-xfail", @@ -72,8 +73,17 @@ def pytest_collection_modifyitems(config, items): _maybe_skip_test(item, delayed=True) +@functools.cache +def built_packages() -> list[str]: + """Returns the list of built package names from packages.json""" + packages_json_path = DIST_PATH / "packages.json" + if not packages_json_path.exists(): + return [] + return list(json.loads(packages_json_path.read_text())["packages"].keys()) + + def _package_is_built(package_name: str) -> bool: - return (BUILD_PATH / f"{package_name}.data").exists() + return package_name.lower() in built_packages() class JavascriptException(Exception): @@ -98,42 +108,33 @@ def __init__( server_log=None, load_pyodide=True, script_timeout=20, + script_type="classic", ): self.server_port = server_port self.server_hostname = server_hostname self.base_url = f"http://{self.server_hostname}:{self.server_port}" self.server_log = server_log - self.driver = self.get_driver() + self.script_type = script_type + self.driver = self.get_driver() # type: ignore[attr-defined] self.set_script_timeout(script_timeout) self.script_timeout = script_timeout self.prepare_driver() self.javascript_setup() if load_pyodide: - self.run_js( - """ - let pyodide = await loadPyodide({ indexURL : './', fullStdLib: false, jsglobals : self }); - self.pyodide = pyodide; - globalThis.pyodide = pyodide; - pyodide._module.inTestHoist = true; // improve some error messages for tests - pyodide.globals.get; - pyodide.pyodide_py.eval_code; - pyodide.pyodide_py.eval_code_async; - pyodide.pyodide_py.register_js_module; - pyodide.pyodide_py.unregister_js_module; - pyodide.pyodide_py.find_imports; - pyodide._module._util_module = pyodide.pyimport("pyodide._util"); - pyodide._module._util_module.unpack_buffer_archive; - pyodide._module.importlib.invalidate_caches; - pyodide.runPython(""); - """, - ) + self.load_pyodide() + self.initialize_global_hiwire_objects() self.save_state() self.restore_state() SETUP_CODE = pathlib.Path(ROOT_PATH / "tools/testsetup.js").read_text() def prepare_driver(self): - self.driver.get(f"{self.base_url}/test.html") + if self.script_type == "classic": + self.driver.get(f"{self.base_url}/test.html") + elif self.script_type == "module": + self.driver.get(f"{self.base_url}/module_test.html") + else: + raise Exception("Unknown script type to load!") def set_script_timeout(self, timeout): self.driver.set_script_timeout(timeout) @@ -151,6 +152,37 @@ def javascript_setup(self): pyodide_checks=False, ) + def load_pyodide(self): + self.run_js( + """ + let pyodide = await loadPyodide({ fullStdLib: false, jsglobals : self }); + self.pyodide = pyodide; + globalThis.pyodide = pyodide; + pyodide._api.inTestHoist = true; // improve some error messages for tests + """ + ) + + def initialize_global_hiwire_objects(self): + """ + There are a bunch of global objects that occasionally enter the hiwire cache + but never leave. The refcount checks get angry about them if they aren't preloaded. + We need to go through and touch them all once to keep everything okay. + """ + self.run_js( + """ + pyodide.globals.get; + pyodide.pyodide_py.eval_code; + pyodide.pyodide_py.eval_code_async; + pyodide.pyodide_py.register_js_module; + pyodide.pyodide_py.unregister_js_module; + pyodide.pyodide_py.find_imports; + pyodide._api.importlib.invalidate_caches; + pyodide._api.package_loader.unpack_buffer; + pyodide._api.package_loader.get_dynlibs; + pyodide.runPython(""); + """ + ) + @property def pyodide_loaded(self): return self.run_js("return !!(self.pyodide && self.pyodide.runPython);") @@ -231,7 +263,7 @@ def run_js_inner(self, code, check_code): %s cb([0, result]); } catch (e) { - cb([1, e.toString(), e.stack]); + cb([1, e.toString(), e.stack, e.message]); } })() """ @@ -239,6 +271,7 @@ def run_js_inner(self, code, check_code): if retval[0] == 0: return retval[1] else: + print("JavascriptException message: ", retval[3]) raise JavascriptException(retval[1], retval[2]) def get_num_hiwire_keys(self): @@ -246,19 +279,19 @@ def get_num_hiwire_keys(self): @property def force_test_fail(self) -> bool: - return self.run_js("return !!pyodide._module.fail_test;") + return self.run_js("return !!pyodide._api.fail_test;") def clear_force_test_fail(self): - self.run_js("pyodide._module.fail_test = false;") + self.run_js("pyodide._api.fail_test = false;") def save_state(self): - self.run_js("self.__savedState = pyodide._module.saveState();") + self.run_js("self.__savedState = pyodide._api.saveState();") def restore_state(self): self.run_js( """ if(self.__savedState){ - pyodide._module.restoreState(self.__savedState) + pyodide._api.restoreState(self.__savedState) } """ ) @@ -277,9 +310,15 @@ def run_webworker(self, code): # we have a multiline string, fix indentation code = textwrap.dedent(code) + worker_file = ( + "webworker_dev.js" + if self.script_type == "classic" + else "module_webworker_dev.js" + ) + return self.run_js( """ - let worker = new Worker( '{}' ); + let worker = new Worker('{}', {{ type: '{}' }}); let res = new Promise((res, rej) => {{ worker.onerror = e => rej(e); worker.onmessage = e => {{ @@ -293,14 +332,15 @@ def run_webworker(self, code): }}); return await res """.format( - f"http://{self.server_hostname}:{self.server_port}/webworker_dev.js", + f"http://{self.server_hostname}:{self.server_port}/{worker_file}", + self.script_type, code, ), pyodide_checks=False, ) def load_package(self, packages): - self.run_js("await pyodide.loadPackage({!r})".format(packages)) + self.run_js(f"await pyodide.loadPackage({packages!r})") @property def urls(self): @@ -345,13 +385,20 @@ class NodeWrapper(SeleniumWrapper): browser = "node" def init_node(self): - os.chdir("build") - self.p = pexpect.spawn( - f"node --expose-gc ../tools/node_test_driver.js {self.base_url}", timeout=60 - ) + self.p = pexpect.spawn("/bin/bash", timeout=60) self.p.setecho(False) self.p.delaybeforesend = None - os.chdir("..") + # disable canonical input processing mode to allow sending longer lines + # See: https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.send + self.p.sendline("stty -icanon") + self.p.sendline( + f"node --expose-gc --experimental-wasm-bigint ./src/test-js/node_test_driver.js {self.base_url}", + ) + + try: + self.p.expect_exact("READY!!") + except pexpect.exceptions.EOF: + raise JavascriptException("", self.p.before.decode()) def get_driver(self): self._logs = [] @@ -390,26 +437,29 @@ def clean_logs(self): def run_js_inner(self, code, check_code): check_code = "" wrapped = """ - let result = await (async () => { %s })(); - %s + let result = await (async () => {{ {} }})(); + {} return result; - """ % ( + """.format( code, check_code, ) from uuid import uuid4 cmd_id = str(uuid4()) - self.p.sendline(cmd_id) - self.p.sendline(wrapped) - self.p.sendline(cmd_id) - self.p.expect_exact(f"{cmd_id}:UUID\r\n", timeout=self._timeout) - self.p.expect_exact(f"{cmd_id}:UUID\r\n") - if self.p.before: - self._logs.append(self.p.before.decode()[:-2].replace("\r", "")) - self.p.expect(f"[01]\r\n") - success = int(self.p.match[0].decode()[0]) == 0 - self.p.expect_exact(f"\r\n{cmd_id}:UUID\r\n") + try: + self.p.sendline(cmd_id) + self.p.sendline(wrapped) + self.p.sendline(cmd_id) + self.p.expect_exact(f"{cmd_id}:UUID\r\n", timeout=self._timeout) + self.p.expect_exact(f"{cmd_id}:UUID\r\n") + if self.p.before: + self._logs.append(self.p.before.decode()[:-2].replace("\r", "")) + self.p.expect("[01]\r\n") + success = int(self.p.match[0].decode()[0]) == 0 + self.p.expect_exact(f"\r\n{cmd_id}:UUID\r\n") + except pexpect.exceptions.EOF: + raise JavascriptException("", self.p.before.decode()) if success: return json.loads(self.p.before.decode().replace("undefined", "null")) else: @@ -423,7 +473,7 @@ def pytest_runtest_call(item): not possible to "Fail" a test from a fixture (no matter what you do, pytest sets the test status to "Error"). The approach suggested there is hook pytest_runtest_call as we do here. To get access to the selenium fixture, we - immitate the definition of pytest_pyfunc_call: + imitate the definition of pytest_pyfunc_call: https://github.com/pytest-dev/pytest/blob/6.2.2/src/_pytest/python.py#L177 Pytest issue #5044: @@ -527,7 +577,7 @@ def _maybe_skip_test(item, delayed=False): @contextlib.contextmanager -def selenium_common(request, web_server_main, load_pyodide=True): +def selenium_common(request, web_server_main, load_pyodide=True, script_type="classic"): """Returns an initialized selenium object. If `_should_skip_test` indicate that the test will be skipped, @@ -535,6 +585,7 @@ def selenium_common(request, web_server_main, load_pyodide=True): """ server_hostname, server_port, server_log = web_server_main + cls: type[SeleniumWrapper] if request.param == "firefox": cls = FirefoxWrapper elif request.param == "chrome": @@ -542,12 +593,13 @@ def selenium_common(request, web_server_main, load_pyodide=True): elif request.param == "node": cls = NodeWrapper else: - assert False + raise AssertionError(f"Unknown browser: {request.param}") selenium = cls( server_port=server_port, server_hostname=server_hostname, server_log=server_log, load_pyodide=load_pyodide, + script_type=script_type, ) try: yield selenium @@ -570,12 +622,31 @@ def selenium_standalone(request, web_server_main): print(selenium.logs) +@pytest.fixture(params=["firefox", "chrome", "node"], scope="module") +def selenium_esm(request, web_server_main): + # Avoid loading the fixture if the test is going to be skipped + _maybe_skip_test(request.node) + + with selenium_common( + request, web_server_main, load_pyodide=True, script_type="module" + ) as selenium: + with set_webdriver_script_timeout( + selenium, script_timeout=parse_driver_timeout(request) + ): + try: + yield selenium + finally: + print(selenium.logs) + + @contextlib.contextmanager -def selenium_standalone_noload_common(request, web_server_main): +def selenium_standalone_noload_common(request, web_server_main, script_type="classic"): # Avoid loading the fixture if the test is going to be skipped _maybe_skip_test(request.node) - with selenium_common(request, web_server_main, load_pyodide=False) as selenium: + with selenium_common( + request, web_server_main, load_pyodide=False, script_type=script_type + ) as selenium: with set_webdriver_script_timeout( selenium, script_timeout=parse_driver_timeout(request) ): @@ -586,13 +657,22 @@ def selenium_standalone_noload_common(request, web_server_main): @pytest.fixture(params=["firefox", "chrome"], scope="function") -def selenium_webworker_standalone(request, web_server_main): +def selenium_webworker_standalone(request, web_server_main, script_type): # Avoid loading the fixture if the test is going to be skipped + if request.param == "firefox" and script_type == "module": + pytest.skip("firefox does not support module type web worker") _maybe_skip_test(request.node) - with selenium_standalone_noload_common(request, web_server_main) as selenium: + with selenium_standalone_noload_common( + request, web_server_main, script_type=script_type + ) as selenium: yield selenium +@pytest.fixture(params=["classic", "module"], scope="module") +def script_type(request): + return request.param + + @pytest.fixture(params=["firefox", "chrome", "node"], scope="function") def selenium_standalone_noload(request, web_server_main): """Only difference between this and selenium_webworker_standalone is that @@ -633,15 +713,15 @@ def selenium(request, selenium_module_scope): @pytest.fixture(scope="session") def web_server_main(request): - """Web server that serves files in the build/ directory""" - with spawn_web_server(request.config.option.build_dir) as output: + """Web server that serves files in the dist/ directory""" + with spawn_web_server(request.config.option.dist_dir) as output: yield output @pytest.fixture(scope="session") def web_server_secondary(request): - """Secondary web server that serves files build/ directory""" - with spawn_web_server(request.config.option.build_dir) as output: + """Secondary web server that serves files dist/ directory""" + with spawn_web_server(request.config.option.dist_dir) as output: yield output @@ -653,15 +733,15 @@ def web_server_tst_data(request): @contextlib.contextmanager -def spawn_web_server(build_dir=None): +def spawn_web_server(dist_dir=None): - if build_dir is None: - build_dir = BUILD_PATH + if dist_dir is None: + dist_dir = DIST_PATH tmp_dir = tempfile.mkdtemp() log_path = pathlib.Path(tmp_dir) / "http-server.log" - q = multiprocessing.Queue() - p = multiprocessing.Process(target=run_web_server, args=(q, log_path, build_dir)) + q: multiprocessing.Queue[str] = multiprocessing.Queue() + p = multiprocessing.Process(target=run_web_server, args=(q, log_path, dist_dir)) try: p.start() @@ -679,7 +759,7 @@ def spawn_web_server(build_dir=None): shutil.rmtree(tmp_dir) -def run_web_server(q, log_filepath, build_dir): +def run_web_server(q, log_filepath, dist_dir): """Start the HTTP web server Parameters @@ -692,7 +772,7 @@ def run_web_server(q, log_filepath, build_dir): import http.server import socketserver - os.chdir(build_dir) + os.chdir(dist_dir) log_fh = log_filepath.open("w", buffering=1) sys.stdout = log_fh @@ -713,8 +793,8 @@ def end_headers(self): with socketserver.TCPServer(("", 0), Handler) as httpd: host, port = httpd.server_address print(f"Starting webserver at http://{host}:{port}") - httpd.server_name = "test-server" - httpd.server_port = port + httpd.server_name = "test-server" # type: ignore[attr-defined] + httpd.server_port = port # type: ignore[attr-defined] q.put(port) def service_actions(): @@ -725,7 +805,7 @@ def service_actions(): except queue.Empty: pass - httpd.service_actions = service_actions + httpd.service_actions = service_actions # type: ignore[assignment] httpd.serve_forever() diff --git a/cpython/Makefile b/cpython/Makefile index cbdd45faa44..2cd888b30fe 100644 --- a/cpython/Makefile +++ b/cpython/Makefile @@ -10,12 +10,12 @@ INSTALL=$(ROOT)/installs/python-$(PYVERSION) TARBALL=$(ROOT)/downloads/Python-$(PYVERSION).tgz URL=https://www.python.org/ftp/python/$(PYVERSION)/Python-$(PYVERSION).tgz LIB=libpython$(PYMAJOR).$(PYMINOR).a -SYSCONFIG_NAME=_sysconfigdata__emscripten_ +SYSCONFIG_NAME=_sysconfigdata__emscripten_$(PLATFORM_TRIPLET) -ZLIBVERSION = 1.2.11 +ZLIBVERSION=1.2.12 ZLIBTARBALL=$(ROOT)/downloads/zlib-$(ZLIBVERSION).tar.gz ZLIBBUILD=$(ROOT)/build/zlib-$(ZLIBVERSION) -ZLIBURL=https://zlib.net/zlib-1.2.11.tar.gz +ZLIBURL=https://zlib.net/zlib-$(ZLIBVERSION).tar.gz SQLITETARBALL=$(ROOT)/downloads/sqlite-autoconf-3270200.tar.gz SQLITEBUILD=$(ROOT)/build/sqlite-autoconf-3270200 @@ -27,7 +27,7 @@ BZIP2URL=https://sourceware.org/pub/bzip2/bzip2-1.0.2.tar.gz FFIBUILD=$(ROOT)/build/libffi LIBFFIREPO=https://github.com/hoodmane/libffi-emscripten -LIBFFI_COMMIT=d6666df2a2820e0548a66166021ddae04a11a2db +LIBFFI_COMMIT=008d7aafde297703eb2f259c969aebf301b01a6d all: $(INSTALL)/lib/$(LIB) $(INSTALL)/lib/libffi.a @@ -60,7 +60,7 @@ clean-all: clean $(TARBALL): [ -d $(ROOT)/downloads ] || mkdir $(ROOT)/downloads wget -q -O $@ $(URL) - md5sum --quiet --check checksums || (rm $@; false) + shasum --algorithm 256 --check checksums --quiet || (rm $@; false) $(ZLIBTARBALL): @@ -87,7 +87,7 @@ $(BUILD)/.patched: $(TARBALL) $(ZLIBBUILD)/.configured: $(ZLIBTARBALL) [ -d $(ROOT)/build ] || (mkdir $(ROOT)/build) - tar -C $(ROOT)/build/ -xf $(ROOT)/downloads/zlib-1.2.11.tar.gz + tar -C $(ROOT)/build/ -xf $(ROOT)/downloads/zlib-$(ZLIBVERSION).tar.gz cd $(ZLIBBUILD); emconfigure ./configure touch $@ @@ -140,6 +140,7 @@ $(BUILD)/Makefile: $(BUILD)/.patched $(ZLIBBUILD)/.configured $(INSTALL)/lib/lib ./configure \ CFLAGS="${PYTHON_CFLAGS}" \ CPPFLAGS="-I$(SQLITEBUILD) -I$(BZIP2BUILD) -I$(ZLIBBUILD)" \ + PLATFORM_TRIPLET="$(PLATFORM_TRIPLET)" \ --without-pymalloc \ --disable-shared \ --disable-ipv6 \ diff --git a/cpython/Setup.local b/cpython/Setup.local index 75e2221626c..368c87dfeea 100644 --- a/cpython/Setup.local +++ b/cpython/Setup.local @@ -21,11 +21,9 @@ _csv _csv.c CTYPES_FLAGS=-DHAVE_FFI_PREP_CIF_VAR=1 -DHAVE_FFI_PREP_CLOSURE_LOC=1 -DHAVE_FFI_CLOSURE_ALLOC=1 _ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/cfield.c _ctypes/stgdict.c $(CTYPES_FLAGS) -_ctypes_test _ctypes/_ctypes_test.c unicodedata unicodedata.c _pickle _pickle.c -parser parsermodule.c _socket socketmodule.c select selectmodule.c @@ -71,3 +69,4 @@ _lsprof _lsprof.c rotatingtree.c _decimal _decimal/_decimal.c _decimal/libmpdec/basearith.c _decimal/libmpdec/constants.c _decimal/libmpdec/context.c _decimal/libmpdec/convolute.c _decimal/libmpdec/crt.c _decimal/libmpdec/difradix2.c _decimal/libmpdec/fnt.c _decimal/libmpdec/fourstep.c _decimal/libmpdec/io.c _decimal/libmpdec/mpalloc.c _decimal/libmpdec/mpdecimal.c _decimal/libmpdec/numbertheory.c _decimal/libmpdec/sixstep.c _decimal/libmpdec/transpose.c -I$(srcdir)/Modules/_decimal/libmpdec mmap mmapmodule.c +_xxsubinterpreters _xxsubinterpretersmodule.c diff --git a/cpython/checksums b/cpython/checksums index f74ee6ca510..f0ef77c68d5 100644 --- a/cpython/checksums +++ b/cpython/checksums @@ -1 +1 @@ -364158b3113cf8ac8db7868ce40ebc7b downloads/Python-3.9.5.tgz +3c0ede893011319f9b0a56b44953a3d52c7abf9657c23fb4bc9ced93b86e9c97 downloads/Python-3.10.2.tgz diff --git a/cpython/patches/0001-Interrupt-handling.patch b/cpython/patches/0001-Interrupt-handling.patch new file mode 100644 index 00000000000..b8865adae2c --- /dev/null +++ b/cpython/patches/0001-Interrupt-handling.patch @@ -0,0 +1,121 @@ +From e2e91c8940302f3c431d81c6ea2af2ea0571165d Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Wed, 30 Mar 2022 14:50:30 -0700 +Subject: [PATCH] Interrupt handling + +--- + Modules/signalmodule.c | 38 ++++++++++++++++++++++++++++++++++++++ + Python/ceval.c | 29 +++++++++++++++++++++++++++++ + 2 files changed, 67 insertions(+) + +diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c +index 96881d4a49..1926f650b6 100644 +--- a/Modules/signalmodule.c ++++ b/Modules/signalmodule.c +@@ -1768,11 +1768,49 @@ PyErr_CheckSignals(void) + return _PyErr_CheckSignalsTstate(tstate); + } + ++#if defined(__EMSCRIPTEN__) ++ ++#include ++EM_JS(int, _Py_CheckEmscriptenSignals_Helper, (void), { ++ if (!Module.Py_EmscriptenSignalBuffer) { ++ return 0; ++ } ++ try { ++ let result = Module.Py_EmscriptenSignalBuffer[0]; ++ Module.Py_EmscriptenSignalBuffer[0] = 0; ++ return result; ++ } catch(e) { ++#if !defined(NDEBUG) ++ console.warn("Error occurred while trying to read signal buffer:", e); ++#endif ++ return 0; ++ } ++}); ++ ++EMSCRIPTEN_KEEPALIVE int Py_EMSCRIPTEN_SIGNAL_HANDLING = 0; ++ ++void ++_Py_CheckEmscriptenSignals(void) ++{ ++ if (!Py_EMSCRIPTEN_SIGNAL_HANDLING) { ++ return; ++ } ++ int signal = _Py_CheckEmscriptenSignals_Helper(); ++ if (signal) { ++ PyErr_SetInterruptEx(signal); ++ } ++} ++ ++#endif + + /* Declared in cpython/pyerrors.h */ + int + _PyErr_CheckSignalsTstate(PyThreadState *tstate) + { ++ #if defined(__EMSCRIPTEN__) ++ _Py_CheckEmscriptenSignals(); ++ #endif ++ + if (!_Py_atomic_load(&is_tripped)) { + return 0; + } +diff --git a/Python/ceval.c b/Python/ceval.c +index ab10b4166d..98bc690fae 100644 +--- a/Python/ceval.c ++++ b/Python/ceval.c +@@ -1152,6 +1152,30 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) + return _PyEval_EvalFrame(tstate, f, throwflag); + } + ++#if defined(__EMSCRIPTEN__) ++ ++extern int Py_EMSCRIPTEN_SIGNAL_HANDLING; ++void _Py_CheckEmscriptenSignals(void); ++ ++#define PY_EMSCRIPTEN_SIGNAL_INTERVAL 50 ++ ++static int ++emscripten_signal_clock = PY_EMSCRIPTEN_SIGNAL_INTERVAL; ++ ++static void ++_Py_CheckEmscriptenSignalsPeriodically() ++{ ++ if (!Py_EMSCRIPTEN_SIGNAL_HANDLING) { ++ return; ++ } ++ emscripten_signal_clock--; ++ if (emscripten_signal_clock == 0) { ++ emscripten_signal_clock = PY_EMSCRIPTEN_SIGNAL_INTERVAL; ++ _Py_CheckEmscriptenSignals(); ++ } ++} ++ ++#endif + + /* Handle signals, pending calls, GIL drop request + and asynchronous exception */ +@@ -1313,6 +1337,7 @@ eval_frame_handle_pending(PyThreadState *tstate) + + + #define CHECK_EVAL_BREAKER() \ ++ _Py_CheckEmscriptenSignalsPeriodically(); \ + if (_Py_atomic_load_relaxed(eval_breaker)) { \ + continue; \ + } +@@ -1742,6 +1767,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag) + assert(STACK_LEVEL() <= co->co_stacksize); /* else overflow */ + assert(!_PyErr_Occurred(tstate)); + ++ #if defined(__EMSCRIPTEN__) ++ _Py_CheckEmscriptenSignalsPeriodically(); ++ #endif ++ + /* Do periodic things. Doing this every time through + the loop would add too much overhead, so we do it + only every Nth instruction. We also do it if +-- +2.25.1 + diff --git a/cpython/patches/0001-call-trampolines-to-handle-fpcast-troubles.patch b/cpython/patches/0001-Patch-in-call-trampolines-to-handle-fpcast-troubles.patch similarity index 85% rename from cpython/patches/0001-call-trampolines-to-handle-fpcast-troubles.patch rename to cpython/patches/0001-Patch-in-call-trampolines-to-handle-fpcast-troubles.patch index 538f50cec62..085be938065 100644 --- a/cpython/patches/0001-call-trampolines-to-handle-fpcast-troubles.patch +++ b/cpython/patches/0001-Patch-in-call-trampolines-to-handle-fpcast-troubles.patch @@ -1,7 +1,7 @@ -From a608eb640f151f08ffd810e53eb29f58ef68f493 Mon Sep 17 00:00:00 2001 +From 9e3b5aacb71deb2870ab589ca88aa126e1acc86a Mon Sep 17 00:00:00 2001 From: Hood Chatham -Date: Fri, 3 Dec 2021 20:55:40 -0800 -Subject: [PATCH] call trampolines to handle fpcast troubles +Date: Mon, 28 Feb 2022 00:44:13 -0500 +Subject: [PATCH 01/10] Patch in call trampolines to handle fpcast troubles The wasm call_indirect instruction takes a function signature as an immediate argument (an immediate argument is one which is determined statically at compile @@ -42,7 +42,6 @@ no issue (this mimics Javascript's typical function call semantics). So at the locations where the bad calls occur, we patch in a trampoline which calls out to Javascript to make the call for us. Like magic, the problem is gone. Research shows that the performance cost of this is surprisingly low too. - --- Objects/call.c | 5 ++++- Objects/descrobject.c | 28 +++++++++++++++++++--------- @@ -50,10 +49,10 @@ shows that the performance cost of this is surprisingly low too. 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/Objects/call.c b/Objects/call.c -index 87dc0dbbdb..f8c7811521 100644 +index 960c37e196..98861e8b87 100644 --- a/Objects/call.c +++ b/Objects/call.c -@@ -143,6 +143,9 @@ PyObject_VectorcallDict(PyObject *callable, PyObject *const *args, +@@ -167,6 +167,9 @@ PyObject_VectorcallDict(PyObject *callable, PyObject *const *args, } @@ -63,7 +62,7 @@ index 87dc0dbbdb..f8c7811521 100644 PyObject * _PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable, PyObject *const *args, Py_ssize_t nargs, -@@ -188,7 +191,7 @@ _PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable, +@@ -212,7 +215,7 @@ _PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable, PyObject *result = NULL; if (_Py_EnterRecursiveCall(tstate, " while calling a Python object") == 0) { @@ -73,10 +72,10 @@ index 87dc0dbbdb..f8c7811521 100644 } diff --git a/Objects/descrobject.c b/Objects/descrobject.c -index fce9cdd309..326844f569 100644 +index 97669bef36..2a30c050a9 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c -@@ -174,6 +174,11 @@ member_get(PyMemberDescrObject *descr, PyObject *obj, PyObject *type) +@@ -172,6 +172,11 @@ member_get(PyMemberDescrObject *descr, PyObject *obj, PyObject *type) return PyMember_GetOne((char *)obj, descr->d_member); } @@ -89,15 +88,15 @@ index fce9cdd309..326844f569 100644 getset_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type) { @@ -182,7 +187,7 @@ getset_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type) - if (descr_check((PyDescrObject *)descr, obj, &res)) - return res; + return NULL; + } if (descr->d_getset->get != NULL) - return descr->d_getset->get(obj, descr->d_getset->closure); + return getter_call_trampoline(descr->d_getset->get, obj, descr->d_getset->closure); PyErr_Format(PyExc_AttributeError, "attribute '%V' of '%.100s' objects is not readable", descr_name((PyDescrObject *)descr), "?", -@@ -228,6 +233,10 @@ member_set(PyMemberDescrObject *descr, PyObject *obj, PyObject *value) +@@ -227,6 +232,10 @@ member_set(PyMemberDescrObject *descr, PyObject *obj, PyObject *value) return PyMember_SetOne((char *)obj, descr->d_member, value); } @@ -108,17 +107,17 @@ index fce9cdd309..326844f569 100644 static int getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value) { -@@ -236,8 +245,7 @@ getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value) - if (descr_setcheck((PyDescrObject *)descr, obj, value, &res)) - return res; - if (descr->d_getset->set != NULL) +@@ -234,8 +243,7 @@ getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value) + return -1; + } + if (descr->d_getset->set != NULL) { - return descr->d_getset->set(obj, value, - descr->d_getset->closure); + return setter_call_trampoline(descr->d_getset->set, obj, value, descr->d_getset->closure); + } PyErr_Format(PyExc_AttributeError, "attribute '%V' of '%.100s' objects is not writable", - descr_name((PyDescrObject *)descr), "?", -@@ -291,6 +299,9 @@ method_enter_call(PyThreadState *tstate, PyObject *func) +@@ -289,6 +297,9 @@ method_enter_call(PyThreadState *tstate, PyObject *func) return (funcptr)((PyMethodDescrObject *)func)->d_method->ml_meth; } @@ -128,7 +127,7 @@ index fce9cdd309..326844f569 100644 /* Now the actual vectorcall functions */ static PyObject * method_vectorcall_VARARGS( -@@ -310,7 +321,7 @@ method_vectorcall_VARARGS( +@@ -308,7 +319,7 @@ method_vectorcall_VARARGS( Py_DECREF(argstuple); return NULL; } @@ -137,7 +136,7 @@ index fce9cdd309..326844f569 100644 Py_DECREF(argstuple); _Py_LeaveRecursiveCall(tstate); return result; -@@ -338,12 +349,11 @@ method_vectorcall_VARARGS_KEYWORDS( +@@ -336,12 +347,11 @@ method_vectorcall_VARARGS_KEYWORDS( goto exit; } } @@ -152,7 +151,7 @@ index fce9cdd309..326844f569 100644 _Py_LeaveRecursiveCall(tstate); exit: Py_DECREF(argstuple); -@@ -431,7 +441,7 @@ method_vectorcall_NOARGS( +@@ -429,7 +439,7 @@ method_vectorcall_NOARGS( if (meth == NULL) { return NULL; } @@ -161,7 +160,7 @@ index fce9cdd309..326844f569 100644 _Py_LeaveRecursiveCall(tstate); return result; } -@@ -459,7 +469,7 @@ method_vectorcall_O( +@@ -457,7 +467,7 @@ method_vectorcall_O( if (meth == NULL) { return NULL; } @@ -171,10 +170,10 @@ index fce9cdd309..326844f569 100644 return result; } diff --git a/Objects/methodobject.c b/Objects/methodobject.c -index 7b430416c5..b57d7a37b2 100644 +index 2df63cfdf6..978d62cd67 100644 --- a/Objects/methodobject.c +++ b/Objects/methodobject.c -@@ -460,6 +460,12 @@ cfunction_vectorcall_FASTCALL_KEYWORDS_METHOD( +@@ -464,6 +464,12 @@ cfunction_vectorcall_FASTCALL_KEYWORDS_METHOD( return result; } @@ -187,7 +186,7 @@ index 7b430416c5..b57d7a37b2 100644 static PyObject * cfunction_vectorcall_NOARGS( PyObject *func, PyObject *const *args, size_t nargsf, PyObject *kwnames) -@@ -482,7 +488,7 @@ cfunction_vectorcall_NOARGS( +@@ -486,7 +492,7 @@ cfunction_vectorcall_NOARGS( if (meth == NULL) { return NULL; } @@ -196,7 +195,7 @@ index 7b430416c5..b57d7a37b2 100644 _Py_LeaveRecursiveCall(tstate); return result; } -@@ -536,7 +542,7 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs) +@@ -540,7 +546,7 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs) PyObject *result; if (flags & METH_KEYWORDS) { @@ -205,7 +204,7 @@ index 7b430416c5..b57d7a37b2 100644 } else { if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) { -@@ -545,7 +551,7 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs) +@@ -549,7 +555,7 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs) ((PyCFunctionObject*)func)->m_ml->ml_name); return NULL; } diff --git a/cpython/patches/0002-add-emscripten-host.patch b/cpython/patches/0002-add-emscripten-host.patch new file mode 100644 index 00000000000..ad78d11e66b --- /dev/null +++ b/cpython/patches/0002-add-emscripten-host.patch @@ -0,0 +1,93 @@ +From fb49efa04e2ecf57c68c73e00538b2c13abea503 Mon Sep 17 00:00:00 2001 +From: Michael Droettboom +Date: Mon, 28 Feb 2022 00:50:54 -0500 +Subject: [PATCH 02/10] add emscripten host + +--- + config.sub | 7 ++++++- + configure | 6 ++++++ + configure.ac | 5 +++++ + 3 files changed, 17 insertions(+), 1 deletion(-) + +diff --git a/config.sub b/config.sub +index d74fb6deac..3f5097a1af 100755 +--- a/config.sub ++++ b/config.sub +@@ -145,7 +145,8 @@ case $1 in + nto-qnx* | linux-* | uclinux-uclibc* \ + | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ + | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ +- | storm-chaos* | os2-emx* | rtmk-nova*) ++ | storm-chaos* | os2-emx* | rtmk-nova* | \ ++ emscripten) + basic_machine=$field1 + basic_os=$maybe_os + ;; +@@ -255,6 +256,9 @@ case $1 in + basic_machine=i386-pc + basic_os=aros + ;; ++ wasm32) ++ basic_machine=wasm32-unknown ++ ;; + aux) + basic_machine=m68k-apple + basic_os=aux +@@ -1184,6 +1188,7 @@ case $cpu-$vendor in + | amdgcn \ + | arc | arceb | arc32 | arc64 \ + | arm | arm[lb]e | arme[lb] | armv* \ ++ | wasm32 \ + | avr | avr32 \ + | asmjs \ + | ba \ +diff --git a/configure b/configure +index a7d2975f1f..13aeb01b73 100755 +--- a/configure ++++ b/configure +@@ -3347,6 +3347,9 @@ then + *-*-vxworks*) + ac_sys_system=VxWorks + ;; ++ wasm32-*-*) ++ ac_sys_system=Emscripten ++ ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" +@@ -3397,6 +3400,9 @@ if test "$cross_compiling" = yes; then + *-*-vxworks*) + _host_cpu=$host_cpu + ;; ++ wasm32-*-*) ++ _host_cpu= ++ ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" +diff --git a/configure.ac b/configure.ac +index 5aa91cbad3..87c397e969 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -403,6 +403,9 @@ then + *-*-vxworks*) + ac_sys_system=VxWorks + ;; ++ wasm32-*-*) ++ ac_sys_system=Emscripten ++ ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" +@@ -451,6 +454,8 @@ if test "$cross_compiling" = yes; then + ;; + *-*-vxworks*) + _host_cpu=$host_cpu ++ wasm32-*-*) ++ _host_cpu= + ;; + *) + # for now, limit cross builds to known configurations +-- +2.25.1 + diff --git a/cpython/patches/0002-dont-test-undecodable-filenames.patch b/cpython/patches/0002-dont-test-undecodable-filenames.patch deleted file mode 100644 index 1d216eaa5a0..00000000000 --- a/cpython/patches/0002-dont-test-undecodable-filenames.patch +++ /dev/null @@ -1,25 +0,0 @@ -From ba3bbdfc19c8d1d778c36af0424c56e193460170 Mon Sep 17 00:00:00 2001 -From: Michael Droettboom -Date: Sun, 5 Jul 2020 17:38:21 +0200 -Subject: [PATCH] dont-test-undecodable-filenames - ---- - Lib/test/support/__init__.py | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index aee3737..d0be99f 100644 ---- a/Lib/test/support/__init__.py -+++ b/Lib/test/support/__init__.py -@@ -809,6 +809,8 @@ if os.name == 'nt': - 'Unicode filename tests may not be effective' - % (TESTFN_UNENCODABLE, TESTFN_ENCODING)) - TESTFN_UNENCODABLE = None -+elif sys.platform == 'emscripten': -+ pass - # Mac OS X denies unencodable filenames (invalid utf-8) - elif sys.platform != 'darwin': - try: --- -2.17.1 - diff --git a/cpython/patches/0003-fix-Py_Sigset_Converter.patch b/cpython/patches/0003-fix-Py_Sigset_Converter.patch new file mode 100644 index 00000000000..0a9fb7e5785 --- /dev/null +++ b/cpython/patches/0003-fix-Py_Sigset_Converter.patch @@ -0,0 +1,30 @@ +From 88529d19154fae757a58229e76c0fb849d279725 Mon Sep 17 00:00:00 2001 +From: Roman Yurchak +Date: Mon, 28 Feb 2022 00:53:53 -0500 +Subject: [PATCH 03/10] fix Py_Sigset_Converter + +--- + Modules/posixmodule.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c +index 03de470323..b115552dcd 100644 +--- a/Modules/posixmodule.c ++++ b/Modules/posixmodule.c +@@ -1552,6 +1552,13 @@ _Py_Sigset_Converter(PyObject *obj, void *addr) + Py_DECREF(iterator); + return 0; + } ++#else ++int ++_Py_Sigset_Converter(PyObject *obj, void *addr) ++{ ++ PyErr_SetFromErrno(PyExc_OSError); ++ return 0; ++} + #endif /* HAVE_SIGSET_T */ + + #ifdef MS_WINDOWS +-- +2.25.1 + diff --git a/cpython/patches/0007-testing.patch b/cpython/patches/0004-testing.patch similarity index 80% rename from cpython/patches/0007-testing.patch rename to cpython/patches/0004-testing.patch index 942a1748cc0..7d43f6be5f6 100644 --- a/cpython/patches/0007-testing.patch +++ b/cpython/patches/0004-testing.patch @@ -1,7 +1,7 @@ -From 0eb0ea9aeb5a5cfdceb35547dd1a97d73914338b Mon Sep 17 00:00:00 2001 +From 17c0cf37fa3a23bed2dbe3c365b24c20e7e862f0 Mon Sep 17 00:00:00 2001 From: Michael Droettboom -Date: Sun, 5 Jul 2020 19:56:47 +0200 -Subject: [PATCH] testing +Date: Mon, 28 Feb 2022 00:54:33 -0500 +Subject: [PATCH 04/10] testing --- Lib/platform.py | 2 +- @@ -14,10 +14,10 @@ Subject: [PATCH] testing 7 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py -index 0bce438..8fdfe05 100755 +index e32f9c11cd..69939e527f 100755 --- a/Lib/platform.py +++ b/Lib/platform.py -@@ -614,7 +614,7 @@ def _syscmd_file(target, default=''): +@@ -605,7 +605,7 @@ def _syscmd_file(target, default=''): default in case the command should fail. """ @@ -27,10 +27,10 @@ index 0bce438..8fdfe05 100755 return default diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py -index e20bf9a..18f62f4 100644 +index 52cc065da1..bd217172fb 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py -@@ -639,7 +639,9 @@ class Regrtest: +@@ -659,7 +659,9 @@ def main(self, tests=None, **kwargs): except SystemExit as exc: # bpo-38203: Python can hang at exit in Py_Finalize(), especially # on threading._shutdown() call: put a timeout @@ -42,18 +42,18 @@ index e20bf9a..18f62f4 100644 sys.exit(exc.code) diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py -index 37e576d..d470e4a 100644 +index 6d699c8486..b160d5fe30 100644 --- a/Lib/test/support/script_helper.py +++ b/Lib/test/support/script_helper.py -@@ -9,6 +9,7 @@ import os.path +@@ -9,6 +9,7 @@ import subprocess import py_compile import zipfile +import unittest from importlib.util import source_from_cache - from test.support import make_legacy_pyc -@@ -34,6 +35,8 @@ def interpreter_requires_environment(): + from test import support +@@ -36,6 +37,8 @@ def interpreter_requires_environment(): situation. PYTHONPATH or PYTHONUSERSITE are other common environment variables that might impact whether or not the interpreter can start. """ @@ -62,7 +62,7 @@ index 37e576d..d470e4a 100644 global __cached_interp_requires_environment if __cached_interp_requires_environment is None: # If PYTHONHOME is set, assume that we need it -@@ -171,6 +174,8 @@ def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): +@@ -177,6 +180,8 @@ def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): kw is extra keyword args to pass to subprocess.Popen. Returns a Popen object. """ @@ -72,22 +72,22 @@ index 37e576d..d470e4a 100644 if not interpreter_requires_environment(): cmd_line.append('-E') diff --git a/Lib/test/test_cgi.py b/Lib/test/test_cgi.py -index 4e1506a..8c02cdb 100644 +index c1b893d3fe..cfd4d47fa3 100644 --- a/Lib/test/test_cgi.py +++ b/Lib/test/test_cgi.py -@@ -221,6 +221,7 @@ Content-Length: 3 - self.assertEqual(fs.getvalue(key), expect_val[0]) +@@ -223,6 +223,7 @@ def test_separator(self): + @warnings_helper.ignore_warnings(category=DeprecationWarning) def test_log(self): + raise unittest.SkipTest('known pyodide failure') cgi.log("Testing") cgi.logfp = StringIO() diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py -index 9ab68c6..3e74849 100644 +index fc8c39365f..91b610542e 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py -@@ -155,6 +155,7 @@ class TestFcntl(unittest.TestCase): +@@ -157,6 +157,7 @@ def test_flock(self): @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") def test_lockf_exclusive(self): @@ -95,7 +95,7 @@ index 9ab68c6..3e74849 100644 self.f = open(TESTFN, 'wb+') cmd = fcntl.LOCK_EX | fcntl.LOCK_NB fcntl.lockf(self.f, cmd) -@@ -166,6 +167,7 @@ class TestFcntl(unittest.TestCase): +@@ -168,6 +169,7 @@ def test_lockf_exclusive(self): @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") def test_lockf_share(self): @@ -104,10 +104,10 @@ index 9ab68c6..3e74849 100644 cmd = fcntl.LOCK_SH | fcntl.LOCK_NB fcntl.lockf(self.f, cmd) diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py -index 1af23c6..045b4c7 100644 +index f86e767ac0..a763dbebe0 100644 --- a/Lib/test/test_gzip.py +++ b/Lib/test/test_gzip.py -@@ -743,6 +743,7 @@ class TestCommandLine(unittest.TestCase): +@@ -753,6 +753,7 @@ class TestCommandLine(unittest.TestCase): data = b'This is a simple test with gzip' def test_decompress_stdin_stdout(self): @@ -115,7 +115,7 @@ index 1af23c6..045b4c7 100644 with io.BytesIO() as bytes_io: with gzip.GzipFile(fileobj=bytes_io, mode='wb') as gzip_file: gzip_file.write(self.data) -@@ -779,6 +780,7 @@ class TestCommandLine(unittest.TestCase): +@@ -789,6 +790,7 @@ def test_decompress_infile_outfile_error(self): @create_and_remove_directory(TEMPDIR) def test_compress_stdin_outfile(self): @@ -124,10 +124,10 @@ index 1af23c6..045b4c7 100644 with Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) as proc: out, err = proc.communicate(self.data) diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py -index 7101264..bf359a0 100644 +index a12f6f0b97..acede437c3 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py -@@ -1513,6 +1513,7 @@ class TestBasicOps(unittest.TestCase): +@@ -1533,6 +1533,7 @@ def __next__(self): next(a) def test_tee_concurrent(self): @@ -136,5 +136,5 @@ index 7101264..bf359a0 100644 finish = threading.Event() class I: -- -2.17.1 +2.25.1 diff --git a/cpython/patches/0008-drop-pwd-built-in-module.patch b/cpython/patches/0005-Drop-pwd-built-in-module.patch similarity index 85% rename from cpython/patches/0008-drop-pwd-built-in-module.patch rename to cpython/patches/0005-Drop-pwd-built-in-module.patch index 4d1fe4abba4..bb314ad6596 100644 --- a/cpython/patches/0008-drop-pwd-built-in-module.patch +++ b/cpython/patches/0005-Drop-pwd-built-in-module.patch @@ -1,18 +1,17 @@ -From 5c949af8b513e87fec513f88aa47b61199a53b99 Mon Sep 17 00:00:00 2001 +From edfeabd42ac35f095a550e76084e4a549965f4ab Mon Sep 17 00:00:00 2001 From: ryanking13 Date: Wed, 6 Oct 2021 22:16:32 +0900 -Subject: [PATCH] Drop pwd bulit-in module +Subject: [PATCH 05/10] Drop pwd built-in module This patch drops pwd module support from Cpython. pwd module provides access to the Unix user account and password database, which is not available in browser environment. - --- Modules/Setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Setup b/Modules/Setup -index 5e26472446..3c3421fb8e 100644 +index 87c6a152f8..89bab18530 100644 --- a/Modules/Setup +++ b/Modules/Setup @@ -103,7 +103,7 @@ PYTHONPATH=$(COREPYTHONPATH) @@ -25,5 +24,5 @@ index 5e26472446..3c3421fb8e 100644 _sre -DPy_BUILD_CORE_BUILTIN _sre.c # Fredrik Lundh's new regular expressions _codecs _codecsmodule.c # access to the builtin codecs and codec registry -- -2.29.2 +2.25.1 diff --git a/cpython/patches/0005-add-emscripten-host.patch b/cpython/patches/0005-add-emscripten-host.patch deleted file mode 100644 index 8762b3342e9..00000000000 --- a/cpython/patches/0005-add-emscripten-host.patch +++ /dev/null @@ -1,97 +0,0 @@ ---- - config.sub | 9 ++++++++- - configure | 6 ++++++ - configure.ac | 5 +++++ - 3 files changed, 19 insertions(+), 1 deletion(-) - -diff --git a/config.sub b/config.sub -index ba37cf9..0d22b33 100755 ---- a/config.sub -+++ b/config.sub -@@ -118,7 +118,8 @@ case $maybe_os in - linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ - knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \ - kopensolaris*-gnu* | cloudabi*-eabi* | \ -- storm-chaos* | os2-emx* | rtmk-nova*) -+ storm-chaos* | os2-emx* | rtmk-nova* | \ -+ emscripten) - os=-$maybe_os - basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` - ;; -@@ -377,6 +378,7 @@ case $basic_machine in - | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ - | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ - | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ -+ | wasm32 \ - | avr-* | avr32-* \ - | ba-* \ - | be32-* | be64-* \ -@@ -527,6 +529,9 @@ case $basic_machine in - asmjs) - basic_machine=asmjs-unknown - ;; -+ wasm32) -+ basic_machine=wasm32-unknown -+ ;; - aux) - basic_machine=m68k-apple - os=-aux -@@ -1522,6 +1527,8 @@ case $os in - ;; - esac - ;; -+ -emscripten) -+ ;; - -nacl*) - ;; - -ios) -diff --git a/configure b/configure -index 8dcdbf1..e1195d2 100755 ---- a/configure -+++ b/configure -@@ -3307,6 +3307,9 @@ then - *-*-vxworks*) - ac_sys_system=VxWorks - ;; -+ wasm32-*-*) -+ ac_sys_system=Emscripten -+ ;; - *) - # for now, limit cross builds to known configurations - MACHDEP="unknown" -@@ -3357,6 +3360,9 @@ if test "$cross_compiling" = yes; then - *-*-vxworks*) - _host_cpu=$host_cpu - ;; -+ wasm32-*-*) -+ _host_cpu= -+ ;; - *) - # for now, limit cross builds to known configurations - MACHDEP="unknown" -diff --git a/configure.ac b/configure.ac -index b1e4c6c..dbfae6d 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -403,6 +403,9 @@ then - *-*-vxworks*) - ac_sys_system=VxWorks - ;; -+ wasm32-*-*) -+ ac_sys_system=Emscripten -+ ;; - *) - # for now, limit cross builds to known configurations - MACHDEP="unknown" -@@ -451,6 +454,8 @@ if test "$cross_compiling" = yes; then - ;; - *-*-vxworks*) - _host_cpu=$host_cpu -+ wasm32-*-*) -+ _host_cpu= - ;; - *) - # for now, limit cross builds to known configurations --- -2.17.1 - diff --git a/cpython/patches/0006-fix-Py_Sigset_Converter.patch b/cpython/patches/0006-fix-Py_Sigset_Converter.patch deleted file mode 100644 index 585dc85632f..00000000000 --- a/cpython/patches/0006-fix-Py_Sigset_Converter.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 63fd6ee84261ff357d3d3b56a35b756c4d13ce69 Mon Sep 17 00:00:00 2001 -From: Roman Yurchak -Date: Sun, 5 Jul 2020 21:17:10 +0200 -Subject: [PATCH] fix Py_Sigset_Converter - ---- - Modules/posixmodule.c | 7 +++++++ - Modules/posixmodule.h | 2 -- - 2 files changed, 7 insertions(+), 2 deletions(-) - -diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c -index c984e2e..9d1bbee 100644 ---- a/Modules/posixmodule.c -+++ b/Modules/posixmodule.c -@@ -1486,6 +1486,13 @@ error: - Py_DECREF(iterator); - return 0; - } -+#else -+int -+_Py_Sigset_Converter(PyObject *obj, void *addr) -+{ -+ PyErr_SetFromErrno(PyExc_OSError); -+ return 0; -+} - #endif /* HAVE_SIGSET_T */ - - #ifdef MS_WINDOWS -diff --git a/Modules/posixmodule.h b/Modules/posixmodule.h -index 711ac68..5452ffb 100644 ---- a/Modules/posixmodule.h -+++ b/Modules/posixmodule.h -@@ -23,9 +23,7 @@ PyAPI_FUNC(int) _Py_Gid_Converter(PyObject *, gid_t *); - # define HAVE_SIGSET_T - #endif - --#ifdef HAVE_SIGSET_T - PyAPI_FUNC(int) _Py_Sigset_Converter(PyObject *, void *); --#endif /* HAVE_SIGSET_T */ - #endif /* Py_LIMITED_API */ - - #ifdef __cplusplus --- -2.17.1 - diff --git a/cpython/patches/remove-duplicate-symbols-from-cfield.c.patch b/cpython/patches/0007-Remove-duplicate-symbols-from-cfield.c.patch similarity index 87% rename from cpython/patches/remove-duplicate-symbols-from-cfield.c.patch rename to cpython/patches/0007-Remove-duplicate-symbols-from-cfield.c.patch index cb523c0087e..df95f066167 100644 --- a/cpython/patches/remove-duplicate-symbols-from-cfield.c.patch +++ b/cpython/patches/0007-Remove-duplicate-symbols-from-cfield.c.patch @@ -1,7 +1,7 @@ -From fc3b69f9afa779185a60834cf1817c22706edcd1 Mon Sep 17 00:00:00 2001 +From e053e51c4e4c84423c4389463b12defb5bca7b4d Mon Sep 17 00:00:00 2001 From: Hood Date: Tue, 22 Jun 2021 20:12:45 -0700 -Subject: [PATCH] Remove duplicate symbols from cfield.c +Subject: [PATCH 07/10] Remove duplicate symbols from cfield.c These symbols are already defined by libffi. --- @@ -9,10 +9,10 @@ These symbols are already defined by libffi. 1 file changed, 26 deletions(-) diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c -index 7ebd4ba..7a63ab7 100644 +index ec6feca8b0..244c3399a5 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c -@@ -1635,31 +1635,5 @@ typedef struct _ffi_type +@@ -1599,31 +1599,5 @@ typedef struct _ffi_type } ffi_type; */ @@ -45,5 +45,5 @@ index 7ebd4ba..7a63ab7 100644 /*---------------- EOF ----------------*/ -- -2.17.1 +2.25.1 diff --git a/cpython/patches/xfail-core-ctypes-tests-that-don-t-work.patch b/cpython/patches/0008-xfail-core-ctypes-tests-that-don-t-work.patch similarity index 73% rename from cpython/patches/xfail-core-ctypes-tests-that-don-t-work.patch rename to cpython/patches/0008-xfail-core-ctypes-tests-that-don-t-work.patch index b77c4311fa0..f3f56edd4c6 100644 --- a/cpython/patches/xfail-core-ctypes-tests-that-don-t-work.patch +++ b/cpython/patches/0008-xfail-core-ctypes-tests-that-don-t-work.patch @@ -1,19 +1,18 @@ -From a4aec920c76ebcb8360350ff0046c7a0c74c0cf2 Mon Sep 17 00:00:00 2001 +From fcd212640bb74988c31d3ba434bcac83cbcb087c Mon Sep 17 00:00:00 2001 From: Hood Date: Thu, 24 Jun 2021 14:55:10 -0700 -Subject: [PATCH] xfail core ctypes tests that don't work +Subject: [PATCH 08/10] xfail core ctypes tests that don't work PyCode_SetExtra doesn't work and ctypes doesn't seem to work with variadic functions including PyUnicode_FromFormat/ --- - Lib/test/test_code.py | 7 +++++-- - Lib/test/test_unicode.py | 1 + - 2 files changed, 6 insertions(+), 2 deletions(-) + Lib/test/test_code.py | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py -index ac3dde7..d91a350 100644 +index b9c4f8bdcb..31cb8876c8 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py -@@ -389,6 +389,7 @@ if check_impl_detail(cpython=True) and ctypes is not None: +@@ -396,6 +396,7 @@ def test_bad_index(self): ctypes.c_voidp(100)), 0) def test_free_called(self): @@ -21,7 +20,7 @@ index ac3dde7..d91a350 100644 # Verify that the provided free function gets invoked # when the code object is cleaned up. f = self.get_func() -@@ -398,6 +399,7 @@ if check_impl_detail(cpython=True) and ctypes is not None: +@@ -405,6 +406,7 @@ def test_free_called(self): self.assertEqual(LAST_FREED, 100) def test_get_set(self): @@ -29,7 +28,7 @@ index ac3dde7..d91a350 100644 # Test basic get/set round tripping. f = self.get_func() -@@ -414,6 +416,7 @@ if check_impl_detail(cpython=True) and ctypes is not None: +@@ -421,6 +423,7 @@ def test_get_set(self): del f def test_free_different_thread(self): @@ -37,7 +36,7 @@ index ac3dde7..d91a350 100644 # Freeing a code object on a different thread then # where the co_extra was set should be safe. f = self.get_func() -@@ -438,8 +441,8 @@ def test_main(verbose=None): +@@ -445,8 +448,8 @@ def test_main(verbose=None): from test import test_code run_doctest(test_code, verbose) tests = [CodeTest, CodeConstsTest, CodeWeakRefTest] @@ -48,3 +47,6 @@ index ac3dde7..d91a350 100644 run_unittest(*tests) if __name__ == "__main__": +-- +2.25.1 + diff --git a/cpython/patches/0009-Fix-_PyImport_LoadDynamicModuleWithSpec-fpcast.patch b/cpython/patches/0009-Fix-_PyImport_LoadDynamicModuleWithSpec-fpcast.patch new file mode 100644 index 00000000000..9c01bf15395 --- /dev/null +++ b/cpython/patches/0009-Fix-_PyImport_LoadDynamicModuleWithSpec-fpcast.patch @@ -0,0 +1,41 @@ +From 9a3c6ecd36a405e3d67059436cc54055cdcd465a Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Tue, 1 Mar 2022 17:06:53 -0800 +Subject: [PATCH 09/10] Fix _PyImport_LoadDynamicModuleWithSpec fpcast + +--- + Python/importdl.c | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/Python/importdl.c b/Python/importdl.c +index 6d2554741f..0caafd0e13 100644 +--- a/Python/importdl.c ++++ b/Python/importdl.c +@@ -89,6 +89,15 @@ get_encoded_name(PyObject *name, const char **hook_prefix) { + return NULL; + } + ++#include ++// Sometimes these init functions expect one argument, sometimes zero arguments ++// so they need a trampoline. ++typedef PyObject *(*PyInitFunctionType)(void); ++ ++EM_JS(PyObject*, em_call_init_function, (PyInitFunctionType f), { ++ return Module.wasmTable.get(f)(); ++}); ++ + PyObject * + _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp) + { +@@ -164,7 +173,7 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp) + _Py_PackageContext = oldcontext; + goto error; + } +- m = p0(); ++ m = em_call_init_function(p0); + _Py_PackageContext = oldcontext; + + if (m == NULL) { +-- +2.25.1 + diff --git a/cpython/patches/0010-bpo-47197-Fix-void-return-type-handling-in-ctypes.patch b/cpython/patches/0010-bpo-47197-Fix-void-return-type-handling-in-ctypes.patch new file mode 100644 index 00000000000..d3e3819e424 --- /dev/null +++ b/cpython/patches/0010-bpo-47197-Fix-void-return-type-handling-in-ctypes.patch @@ -0,0 +1,52 @@ +From 7b550d8ce20d48ebb1a4f240d6cd5e8a72a430b5 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Fri, 1 Apr 2022 18:55:11 -0700 +Subject: [PATCH 10/10] bpo-47197: Fix void return type handling in ctypes + +_ctypes_get_ffi_type never returns ffi_type_void. If the +return type is specified as None, we need set the libffi +return type to void, but just taking the output from +_ctypes_get_ffi_type will make the return type be sint. + +This fixes two spots where ctypes accidentally converts +None return type to sint rather than void, causing crashes +on Emscripten targets. +--- + Modules/_ctypes/callbacks.c | 2 +- + Modules/_ctypes/callproc.c | 7 ++++++- + 2 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c +index 5a4d1c543f..ef28b93551 100644 +--- a/Modules/_ctypes/callbacks.c ++++ b/Modules/_ctypes/callbacks.c +@@ -403,7 +403,7 @@ CThunkObject *_ctypes_alloc_callback(PyObject *callable, + #endif + result = ffi_prep_cif(&p->cif, cc, + Py_SAFE_DOWNCAST(nArgs, Py_ssize_t, int), +- _ctypes_get_ffi_type(restype), ++ p->ffi_restype, + &p->atypes[0]); + if (result != FFI_OK) { + PyErr_Format(PyExc_RuntimeError, +diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c +index ddf289e3e8..d6513c0e1f 100644 +--- a/Modules/_ctypes/callproc.c ++++ b/Modules/_ctypes/callproc.c +@@ -1219,7 +1219,12 @@ PyObject *_ctypes_callproc(PPROC pProc, + } + } + +- rtype = _ctypes_get_ffi_type(restype); ++ if (restype == Py_None) { ++ rtype = &ffi_type_void; ++ } else { ++ rtype = _ctypes_get_ffi_type(restype); ++ } ++ + resbuf = alloca(max(rtype->size, sizeof(ffi_arg))); + + #ifdef _Py_MEMORY_SANITIZER +-- +2.25.1 + diff --git a/cpython/patches/ctypes-dont-deref-function-pointer.patch b/cpython/patches/ctypes-dont-deref-function-pointer.patch deleted file mode 100644 index 5c648bba5cf..00000000000 --- a/cpython/patches/ctypes-dont-deref-function-pointer.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 3f11694d7587e782530118d176306eeb6eebf5d1 Mon Sep 17 00:00:00 2001 -From: Hood -Date: Wed, 23 Jun 2021 13:47:30 -0700 -Subject: [PATCH] Don't dereference function pointer - -Ctypes thinks that the result of dlsym is a pointer to the function pointer, so -it should call it like `result = (*f)(args)`. Probably this is true for the -native dlsym, but our dlsym returns an index into the indirect call table -"wasmTable", in particular it isn't even aligned like a pointer should be. -This patch fixes it so that it calls it like `result = f(args)` instead. - ---- - Modules/_ctypes/_ctypes.c | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c -index ceae67e..44f2d76 100644 ---- a/Modules/_ctypes/_ctypes.c -+++ b/Modules/_ctypes/_ctypes.c -@@ -771,7 +771,11 @@ CDataType_in_dll(PyObject *type, PyObject *args) - return NULL; - } - #endif -- return PyCData_AtAddress(type, address); -+ CDataObject *ob = (CDataObject *)GenericPyCData_new(type, NULL, NULL); -+ if (ob == NULL) -+ return NULL; -+ *(void **)ob->b_ptr = address; -+ return (PyObject*)ob; - } - - static const char from_param_doc[] = --- -2.17.1 - diff --git a/cpython/patches/keyboard-interrupt.patch b/cpython/patches/keyboard-interrupt.patch deleted file mode 100644 index e33e7af5102..00000000000 --- a/cpython/patches/keyboard-interrupt.patch +++ /dev/null @@ -1,148 +0,0 @@ -From 320cc07c0a30436759cde8f4c926e76ed8dc918a Mon Sep 17 00:00:00 2001 -From: Hood -Date: Fri, 3 Sep 2021 18:08:26 -0700 -Subject: [PATCH] Patch in keyboard interrupt handling - ---- - Include/cpython/ceval.h | 1 + - Include/cpython/pystate.h | 3 +++ - Modules/signalmodule.c | 3 +++ - Python/ceval.c | 13 ++++++++++++- - Python/pystate.c | 2 ++ - 5 files changed, 21 insertions(+), 1 deletion(-) - -This patch adds a callback called pyodide_callback with signature -`int callback(void)` to the main loop in `ceval.c`. This function gets called once -per opcode except after opcodes `SETUP_FINALLY`, `SETUP_WITH`, `BEFORE_ASYNC_WITH`, -and `YIELD_FROM`. The main loop normally prevents signal handling, etc from happening -after these instructions, so I figured this should apply to my callback too. -(There is an extra layer of protection here though because in the callback we use -`PyErr_SetInterrupt` which simulates a SIGINT signal and triggers the KeyboardInterrupt -via the standard mechanism.) - -Note that we call the callback outside of the normal -`if (_Py_atomic_load_relaxed(eval_breaker))` -block where most of the "periodic things" happen. This is because normally when a -"periodic thing" is queued, the `eval_breaker` flag is set signalling the need for -handling. The whole point of this patch though is that we need to be able to set an -interrupt from a remote thread and the `eval_breaker` flag lives on the WASM heap. -Unless the whole WASM heap is made into a `SharedArrayBuffer` and shared between -webworkers, we have no way to set the `eval_breaker` flag. We still want to skip -the callback after the sensitive opcodes `SETUP_FINALLY`, `SETUP_WITH`, -`BEFORE_ASYNC_WITH`, and `YIELD_FROM`, so we hoisted the check for that condition -out of the `eval_breaker` conditional. - -We also patched the `threadstate` struct to include a `pyodide_callback` field, patch -`new_threadstate` to initialize the `pyodide_callback` field to `NULL`, and add a new -API `PyPyodide_SetPyodideCallback` to set `pyodide_callback`. - -Lastly, we patched PyErr_CheckSignals to check our custom source of keyboard interrupts -so that C extensions that use PyErr_CheckSignals to allow keyboard interrupts will be -interruptable in pyodide too. - -diff --git a/Include/cpython/ceval.h b/Include/cpython/ceval.h -index e1922a6..a4acd9b 100644 ---- a/Include/cpython/ceval.h -+++ b/Include/cpython/ceval.h -@@ -10,6 +10,7 @@ PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *); - PyAPI_DATA(int) _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg); - PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *); - PyAPI_FUNC(int) _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg); -+PyAPI_FUNC(void) PyPyodide_SetPyodideCallback(PyPyodide_callback); - PyAPI_FUNC(int) _PyEval_GetCoroutineOriginTrackingDepth(void); - PyAPI_FUNC(int) _PyEval_SetAsyncGenFirstiter(PyObject *); - PyAPI_FUNC(PyObject *) _PyEval_GetAsyncGenFirstiter(void); -diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h -index f292da1..3577ac8 100644 ---- a/Include/cpython/pystate.h -+++ b/Include/cpython/pystate.h -@@ -17,6 +17,7 @@ PyAPI_FUNC(PyObject *) _PyInterpreterState_GetMainModule(PyInterpreterState *); - - /* Py_tracefunc return -1 when raising an exception, or 0 for success. */ - typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *); -+typedef int (*PyPyodide_callback)(void); - - /* The following values are used for 'what' for tracefunc functions - * -@@ -75,6 +76,8 @@ struct _ts { - PyObject *c_profileobj; - PyObject *c_traceobj; - -+ PyPyodide_callback pyodide_callback; -+ - /* The exception currently being raised */ - PyObject *curexc_type; - PyObject *curexc_value; -diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c -index de564c2..bfe2fd1 100644 ---- a/Modules/signalmodule.c -+++ b/Modules/signalmodule.c -@@ -1683,6 +1683,9 @@ PyErr_CheckSignals(void) - int - _PyErr_CheckSignalsTstate(PyThreadState *tstate) - { -+ if (tstate->pyodide_callback != NULL && tstate->pyodide_callback() == -1) -+ return -1; -+ - if (!_Py_atomic_load(&is_tripped)) { - return 0; - } -diff --git a/Python/ceval.c b/Python/ceval.c -index 91e879e..f8d424d 100644 ---- a/Python/ceval.c -+++ b/Python/ceval.c -@@ -1374,7 +1374,6 @@ main_loop: - async I/O handler); see Py_AddPendingCall() and - Py_MakePendingCalls() above. */ - -- if (_Py_atomic_load_relaxed(eval_breaker)) { - opcode = _Py_OPCODE(*next_instr); - if (opcode == SETUP_FINALLY || - opcode == SETUP_WITH || -@@ -1399,11 +1398,18 @@ main_loop: - goto fast_next_opcode; - } - -+ if (_Py_atomic_load_relaxed(eval_breaker)) { - if (eval_frame_handle_pending(tstate) != 0) { - goto error; - } - } - -+ if(tstate->pyodide_callback != NULL){ -+ if(tstate->pyodide_callback() != 0){ -+ goto error; -+ } -+ } -+ - fast_next_opcode: - f->f_lasti = INSTR_OFFSET(); - -@@ -4816,6 +4822,11 @@ PyEval_SetTrace(Py_tracefunc func, PyObject *arg) - } - } - -+void -+PyPyodide_SetPyodideCallback(PyPyodide_callback pyodide_callback){ -+ PyThreadState *tstate = _PyThreadState_GET(); -+ tstate->pyodide_callback = pyodide_callback; -+} - - void - _PyEval_SetCoroutineOriginTrackingDepth(PyThreadState *tstate, int new_depth) -diff --git a/Python/pystate.c b/Python/pystate.c -index 9beefa8..408b7ce 100644 ---- a/Python/pystate.c -+++ b/Python/pystate.c -@@ -602,6 +602,8 @@ new_threadstate(PyInterpreterState *interp, int init) - tstate->c_profileobj = NULL; - tstate->c_traceobj = NULL; - -+ tstate->pyodide_callback = NULL; -+ - tstate->trash_delete_nesting = 0; - tstate->trash_delete_later = NULL; - tstate->on_delete = NULL; --- -2.17.1 - diff --git a/cpython/pyconfig.undefs.h b/cpython/pyconfig.undefs.h index 1cba442c068..9152fb3fbb7 100644 --- a/cpython/pyconfig.undefs.h +++ b/cpython/pyconfig.undefs.h @@ -21,6 +21,7 @@ #undef HAVE_PWRITEV #undef HAVE_PIPE2 #undef HAVE_NICE +#undef HAVE_EVENTFD /* Syscalls that resulted in a segfault */ #undef HAVE_UTIMENSAT diff --git a/docs/Makefile b/docs/Makefile index 55813caf59c..88d8867d9b3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = python3 -m sphinx +SPHINXBUILD = python3.10 -m sphinx SOURCEDIR = . BUILDDIR = _build diff --git a/docs/conf.py b/docs/conf.py index 6c6664ce4db..05b2a02cdd4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,42 +1,21 @@ -# -*- coding: utf-8 -*- # Configuration file for the Sphinx documentation builder. # -- Path setup -------------------------------------------------------------- +import atexit import os -import sys -from typing import Dict, Any -import pathlib +import shutil import subprocess - -base_dir = pathlib.Path(__file__).resolve().parent.parent -path_dirs = [ - str(base_dir), - str(base_dir / "pyodide-build"), - str(base_dir / "docs/sphinx_pyodide"), - str(base_dir / "src/py"), - str(base_dir / "packages/micropip/src"), -] -sys.path = path_dirs + sys.path +import sys +from pathlib import Path +from typing import Any +from unittest import mock # -- Project information ----------------------------------------------------- project = "Pyodide" copyright = "2019-2021, Pyodide contributors and Mozilla" -import pyodide -import micropip # noqa - -# We hacked it so that autodoc will look for submodules, but only if we import -# them here. TODO: look these up in the source directory? -import pyodide.console -import pyodide.http -import pyodide.webloop - -# The full version, including alpha/beta/rc tags. -release = version = pyodide.__version__ - - # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -53,28 +32,32 @@ "sphinx_panels", "sphinx_pyodide", "sphinx_argparse_cli", - # "versionwarning.extension", + "versionwarning.extension", "sphinx_issues", ] myst_enable_extensions = ["substitution"] -js_source_path = ["../src/js", "../src/core"] -jsdoc_config_path = "./jsdoc_conf.json" +js_language = "typescript" +jsdoc_config_path = "../src/js/tsconfig.json" root_for_relative_js_paths = "../src/" issues_github_path = "pyodide/pyodide" versionwarning_messages = { "latest": ( - "This is the development version of the documentation. ", + "This is the development version of the documentation. " 'See here for latest stable ' "documentation. Please do not use Pyodide with non " - "versioned (`dev`) URLs from the CDN for deployed applications!", + "versioned (`dev`) URLs from the CDN for deployed applications!" ) } +versionwarning_body_selector = "#main-content > div" autosummary_generate = True autodoc_default_flags = ["members", "inherited-members"] +# Add modules to be mocked. +mock_modules = ["ruamel.yaml", "tomli"] + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -101,10 +84,9 @@ # html_theme = "sphinx_book_theme" html_logo = "_static/img/pyodide-logo.png" -html_title = f"Version {version}" # theme-specific options -html_theme_options: Dict[str, Any] = {} +html_theme_options: dict[str, Any] = {} # paths that contain custom static files (such as style sheets) html_static_path = ["_static"] @@ -126,31 +108,92 @@ # A list of files that should not be packed into the epub file. epub_exclude_files = ["search.html"] -if "READTHEDOCS" in os.environ: - env = {"PYODIDE_BASE_URL": "https://cdn.jsdelivr.net/pyodide/v0.19.0/full/"} - os.makedirs("_build/html", exist_ok=True) - res = subprocess.check_output( - ["make", "-C", "..", "docs/_build/html/console.html"], - env=env, - stderr=subprocess.STDOUT, - encoding="utf-8", - ) - print(res) - - -# Prevent API docs for webloop methods: they are the same as for base event loop -# and it clutters api docs too much - def delete_attrs(cls): for name in dir(cls): if not name.startswith("_"): try: delattr(cls, name) - except: + except Exception: pass -delete_attrs(pyodide.webloop.WebLoop) -delete_attrs(pyodide.webloop.WebLoopPolicy) -delete_attrs(pyodide.console.PyodideConsole) +# Try not to cause side effects if we are imported incidentally. + +try: + import sphinx + + IN_SPHINX = hasattr(sphinx, "application") +except ImportError: + IN_SPHINX = False + +IN_READTHEDOCS = "READTHEDOCS" in os.environ + +if IN_READTHEDOCS: + env = {"PYODIDE_BASE_URL": "https://cdn.jsdelivr.net/pyodide/dev/full/"} + os.makedirs("_build/html", exist_ok=True) + res = subprocess.check_output( + ["make", "-C", "..", "docs/_build/html/console.html"], + env=env, + stderr=subprocess.STDOUT, + encoding="utf-8", + ) + print(res) + +if IN_SPHINX: + # Compatibility shims. sphinx-js and sphinxcontrib-napoleon have not been updated for Python 3.10 + import collections + from typing import Callable, Mapping + + collections.Mapping = Mapping # type: ignore[attr-defined] + collections.Callable = Callable # type: ignore[attr-defined] + + base_dir = Path(__file__).resolve().parent.parent + path_dirs = [ + str(base_dir), + str(base_dir / "pyodide-build"), + str(base_dir / "docs/sphinx_pyodide"), + str(base_dir / "src/py"), + str(base_dir / "packages/micropip/src"), + ] + sys.path = path_dirs + sys.path + + import micropip # noqa: F401 + import pyodide + + # We hacked it so that autodoc will look for submodules, but only if we import + # them here. TODO: look these up in the source directory? + import pyodide.console + import pyodide.http + import pyodide.webloop + + # The full version, including alpha/beta/rc tags. + release = version = pyodide.__version__ + html_title = f"Version {version}" + + shutil.copy("../src/core/pyproxy.ts", "../src/js/pyproxy.gen.ts") + shutil.copy("../src/core/error_handling.ts", "../src/js/error_handling.gen.ts") + js_source_path = [str(x) for x in Path("../src/js").glob("*.ts")] + + def remove_pyproxy_gen_ts(): + Path("../src/js/pyproxy.gen.ts").unlink(missing_ok=True) + + atexit.register(remove_pyproxy_gen_ts) + + os.environ["PATH"] += f':{str(Path("../src/js/node_modules/.bin").resolve())}' + print(os.environ["PATH"]) + if IN_READTHEDOCS: + subprocess.run(["npm", "ci"], cwd="../src/js") + elif not shutil.which("typedoc"): + raise Exception( + "Before building the Pyodide docs you must run 'npm install' in 'src/js'." + ) + + # Prevent API docs for webloop methods: they are the same as for base event loop + # and it clutters api docs too much + delete_attrs(pyodide.webloop.WebLoop) + delete_attrs(pyodide.webloop.WebLoopPolicy) + delete_attrs(pyodide.console.PyodideConsole) + + for module in mock_modules: + sys.modules[module] = mock.Mock() diff --git a/docs/development/building-from-sources.md b/docs/development/building-from-sources.md index 8809e094a81..49bab2bfaef 100644 --- a/docs/development/building-from-sources.md +++ b/docs/development/building-from-sources.md @@ -67,8 +67,8 @@ Make sure the prerequisites for build a custom, patched version of emsdk, so there is no need to build it yourself prior. -You would need Python 3.9.5 to run the build scripts. To make sure that the -correct Python is used during build it is recommended to use a [Python virtual +You need Python 3.10.2 to run the build scripts. To make sure that the correct +Python is used during the build it is recommended to use a [virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment), ```{tabbed} Linux @@ -163,7 +163,7 @@ The following environment variables additionally impact the build: - `PYODIDE_BASE_URL`: Base URL where Pyodide packages are deployed. It must end with a trailing `/`. Default: `./` to load Pyodide packages from the same base URL path as where `pyodide.js` is located. Example: - `https://cdn.jsdelivr.net/pyodide/v0.18.0/full/` + `https://cdn.jsdelivr.net/pyodide/v0.20.0/full/` - `EXTRA_CFLAGS` : Add extra compilation flags. - `EXTRA_LDFLAGS` : Add extra linker flags. diff --git a/docs/development/contributing.md b/docs/development/contributing.md index e438a3591c4..ea5df85dd8d 100644 --- a/docs/development/contributing.md +++ b/docs/development/contributing.md @@ -8,17 +8,56 @@ for diving into it. ## Development Workflow -See {ref}`building_from_sources` and {ref}`testing` documentation. +To contribute code, see the following steps, -For code-style the use of [pre-commit](https://pre-commit.com/) is also recommended, +1. Fork the Pyodide repository [https://github.com/pyodide/pyodide](https://github.com/pyodide/pyodide) on Github. +2. If you are on Linux, you can skip this step. On Windows and MacOS you have a + choice. The first option is to manually install Docker: -``` -pip install pre-commit -pre-commit install -``` + - on MacOS follow [these instructions](https://docs.docker.com/desktop/mac/install/) + - on Windows, [install WSL + 2](https://docs.microsoft.com/en-us/windows/wsl/install), then Docker. + Note that Windows filesystem access from WSL2 is very slow and should + be avoided when building Pyodide. + + The second option is to use a service that provides a Linux + development environment, such as + + - [Github Codespaces](https://github.com/features/codespaces) + - [gitpod.io](https://gitpod.io) + - or a remote Linux VM with SSH connection. + +3. Clone your fork of Pyodide + ``` + git clone https://github.com//pyodide.git + ``` + and add the upstream remote, + ``` + git remote add upstream https://github.com/pyodide/pyodide.git + ``` +4. While the build will happen inside Docker you still need a development + environment with Python 3.10 and ideally Node.js. These can be installed + for instance with, + ``` + conda create -c conda-forge -n pyodide-env python=3.10.2 nodejs + conda activate pyodide-env + ``` + or via your system package manager. +5. Install requirements (it's recommended to use a virtualenv or a conda env), + ``` + pip install -r requirements.txt + ``` +6. Enable [pre-commit](https://pre-commit.com/) for code style, + + ``` + pre-commit install + ``` + + This will run a set of linters for each commit. + +7. Follow {ref}`building_from_sources` instructions. -This will run a set of linters at each commit. Currently, it runs yaml syntax -validation and is removing trailing whitespaces. +8. See {ref}`testing` documentation. ## Code of Conduct diff --git a/docs/development/core.md b/docs/development/core.md index c9db84e73e8..b07b55e61f8 100644 --- a/docs/development/core.md +++ b/docs/development/core.md @@ -13,7 +13,7 @@ The primary purpose of `core` is to implement {ref}`type translations /"` (substitute in your favorite port). Make sure to -rebuild CPython with these flags set (it isn't necessary to rebuild emsdk). - -Once you have done this, when you load Pyodide, you will see 404 errors for -requests for the various source maps. In order to load the source maps -correctly, you will need to run a custom server that "fixes" the source map -urls. The following debugging server seems to work for both CPython and numpy. -Run it in the Pyodide root directory. If you need to debug other C extensions, -you will need to update the server. It should be clear what to do based by -looking at the 404s generated by the server and locating those files in the file -tree, perhaps by using `find`. - -```py -import socket -import socketserver -from http.server import SimpleHTTPRequestHandler -import pathlib - -alternative_bases=["cpython/build/Python-3.9.5/","src/", "build/"] -def fixup_url(path): - if pathlib.Path("." + path).exists(): - return path - for base in alternative_bases: - q = pathlib.Path(base + path) - if q.exists(): - return str(q) - # Numpy source maps can be in a bunch of different places inside of the - # directory tree, so we need to glob for them. - dir = list( - pathlib.Path("packages/numpy/build/numpy-1.17.5/").glob("**" + path) - ) - if dir: - return str(dir[0]) - return path - - -class MyTCPServer(socketserver.TCPServer): - def server_bind(self): - """Use socket.SO_REUSEADDR to allow faster restart""" - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.bind(self.server_address) - -class Handler(SimpleHTTPRequestHandler): - def end_headers(self): - # Enable Cross-Origin Resource Sharing (CORS) - self.send_header('Access-Control-Allow-Origin', '*') - super().end_headers() - - def do_GET(self): - self.path = fixup_url(self.path) - super().do_GET() - - -if __name__ == '__main__': - port = 8000 - with MyTCPServer(("", port), Handler) as httpd: - print("Serving at: http://127.0.0.1:{}".format(port)) - httpd.serve_forever() +Also whenever you can reproduce a bug in chromium make sure to use a +chromium-based browser (e.g., chrome) for debugging. They are better at it. + +## Run `prettier` on `pyodide.asm.js` + +Before doing any debugger I strongly recommend running +`npx prettier -w pyodide.asm.js`. This makes everything much easier. + +## Linker error: function signature mismatch + +You may get linker errors as follows: + +``` +wasm-ld: error: function signature mismatch: some_func +>>> defined as (i32, i32) -> i32 in some_static_lib.a(a.o) +>>> defined as (i32) -> i32 in b.o ``` + +This is especially common in Scipy. Oftentimes it isn't too hard to figure out +what is going wrong because it told you the both the symbol name (`some_func`) +and the object files involved (this is much easier than the runtime version of +this error!). If you can't tell what is going on from looking at the source +files, it's time to pull out `wasm-objdump`. In this case `a.o` is part of +`some_static_lib.a` so you first need to get it out with +`ar -x some_static_lib.a a.o`. +Now we can check if `a.o` imports or defines `some_func`. +To check for imports, use `wasm-objdump a.o -j Import -x | grep some_func`. +If `a.o` is importing `some_func` you should see a line like: +`- func[0] sig=1 <- env.some_func` in the output. + +If not, you will see nothing or things like `some_func2`. To check if `a.o` +defines `some_func` (this is a bit redundant because you can conclude whether or +not does from whether it imports it) we can use: +`wasm-objdump a.o -j Function -x | grep some_func`, if +`a.o` defines `some_func` you will see something like: +` - func[0] sig=0 `. + +Now the question is what these signatures mean (though we already know this from +the linker error). To find out what signature 0 is, you can use +`wasm-objdump a.o -j Type -x | grep "type\[0\]"`. + +Using this, we can verify that `a.o` imports `some_func` with signature +`(i32, i32) -> i32` but `b.o` exports it with signature `(i32) -> i32`, +hence the linker error. + +This process works in basically the same way for already-linked `.so` and +`.wasm` files, which can help if you get the load-time version of this linker +error. + +## Misencoded Wasm + +On a very rare occasion you may run into a misencoded object file. This can +cause different tools to crash, `wasm-ld` may panic, etc. `wasm-objdump` will +just generate a useless error message. In this case, I recommend +`wasm-objdump -s --debug 2>&1 | grep -i error -C 20` (or pipe to `less`), which will result in +more diagnostic information. Sometimes the crash happens quite a lot later than the actual error, +look for suspiciously large constants, these are often the first sign of something gone haywire. + +After this, you can get out a hex editor and consult the +[WebAssembly binary specification](https://webassembly.github.io/spec/core/binary/index.html) +Cross reference against the hex addresses appearing in `wasm-objdump --debug`. +With enough diligence you can locate the problem. + +## Debugging RuntimeError: function signature mismatch + +First recompile with `-g2`. `-g2` keeps symbols but won't try to use C source +maps which mostly make our life harder (though it may be helpful to link one +copy with `-g2` and one with `-g3` and run them at the same time cf +{ref}`source-maps`). + +The browser console will show something like the following. Click on the +innermost stack trace: + +![fpcast stack trace](./signature-mismatch1.png "fpcast stack trace") + +Clicking the offset will (hopefully) take you to the corresponding wasm +instruction, which should be a `call_indirect`. If the offset is too large +(somewhere between `0x0200000` and `0x0300000`) you will instead see `;; text is truncated due to size`, see {ref}`text-truncated-due-to-size`. In this example +we see the following: + +![wasm bad call_indirect instruction](./signature-mismatch2.png "wasm bad call_indirect instruction") + +So we think we are calling a function pointer with signature +`(param i32 i32) (result i32)` +meaning that it takes two `i32` inputs and returns one `i32` output. Set a +breakpoint by clicking on the address, then refresh the page and run the +reproduction again. Sometimes these are on really hot code paths (as in the +present example) so you probably only want to set the breakpoint once Pyodide is +finished loading. If your reproduction passes through the breakpoint multiple +times before crashing you can do the usual chore of counting how many times you +have to press "Resume" before the crash. Suppose you've done all this, and we've +got the vm stopped at the bad instruction just before crashing: + +![wasm bad function pointer](./signature-mismatch3.png "wasm bad function pointer") + +The bottom value on the stack is the function pointer. In this case it's the +fourth item on the stack, so you can type the following into the console: + +```js +> pyodide._module.wasmTable.get(stack[4].value) // stack[4].value === 13109 +< ƒ $one() { [native code] } +``` + +So the bad function pointer's symbol is `one`! Now clicking on `$one` brings you +to the source for it: + +![function pointer signature](./signature-mismatch4.png "function pointer signature") + +and we see the function pointer has signature `(param $var0 i32) (result i32)`, +meaning it takes one `i32` input and returns one `i32` output. Note that if the +function had `void` return type it might look like `(param $var0 i32 $var1 i32)` +(with no `result`). Confusion between `i32` and `void` return type is the single +most common cause of this error. + +Now we basically know the cause of the trouble. You can look up `cfunction_call` +in the CPython source code with the help of ripgrep and locate the line that +generates this call, and look up `one` in the appropriate source and find the +signature. Another approach to locate the call site would be to recompile with +`-g3` and use source maps {ref}`source-maps` to locate the problematic source +code. With the same process of reproduce crash ==> click innermost stack frame +==> see source file and line where the error occurs. In this case we see that +the crash is on the line: + +```C +result = _PyCFunction_TrampolineCall(meth, self, args); +``` + +in the file `/src/cpython/build/Python-3.11.0dev0/Objects/methodobject.c`. +Unfortunately, source maps are useless for the harder problem of finding the +callee because compiling with `-g3` increases the number of function pointers so +the function pointer we are calling is in a different spot. I know of no way to +determine the bad function pointer when compiling with `-g3`. + +Sometimes (particularly with Scipy/CLAPACK) the issue will be a mismatch between +`(param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)` and +`(param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)` + +(14 vs 15 parameters) which might be a little hard to discern. I copy the +signature into the Javascript console and run `"i32 ... i32".split(" ").length` +in this case. + +(text-truncated-due-to-size)= + +## Dealing with `;; text is truncated due to size` + +If you are debugging and run into the dreaded `;; text is truncated due to size` +error message, the solution is to compile a modified version of Chrome devtools +with a larger wasm size cap. Surprisingly, this is not actually all that hard. + +These instructions are adapted from here: +https://www.diverto.hr/en/blog/2020-08-15-WebAssembly-limit/ + +In short, + +``` +git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git +./fetch devtools-frontend +cd devtools-frontend +``` + +Apply the following change: + +```patch +--- a/front_end/entrypoints/wasmparser_worker/WasmParserWorker.ts ++++ b/front_end/entrypoints/wasmparser_worker/WasmParserWorker.ts +@@ -55,7 +55,7 @@ export function dissambleWASM( + const lines = []; + const offsets = []; + const functionBodyOffsets = []; +- const MAX_LINES = 1000 * 1000; ++ const MAX_LINES = 12 * 1000 * 1000; + let chunkSize: number = 128 * 1024; + let buffer: Uint8Array = new Uint8Array(chunkSize); + let pendingSize = 0; +``` + +Then build with: + +``` +gn gen out/Default +autoninja -C out/Default +``` + +then + +``` +cd out/Default/resources/inspector_overlay/ +python http.server +``` + +and then you can start a version of chrome using the modified devtools: + +``` +chrome --custom-devtools-frontend=http://localhost:/ +``` + +(source-maps)= + +## Using C source maps + +[Chromium has support for DWARF +info](https://developer.chrome.com/blog/wasm-debugging-2020/) which can be very +helpful for debugging in certain circumstances. + +I haven't used this very much because it is often not very beneficial. The +biggest issue is that I have found no way to toggle between viewing the C source +and the WebAssembly. In particular, if source maps are available, the debugger +gives no way to view the current line in the wasm. What's worse is that even if +it fails to find the source map, it won't fall back to displaying the source +map. To _prevent_ this, relink the code with `-g2`. + +Typically once I have isolated the interesting line of C code, I need to see +what is going on at an instruction-level. This limitation means that it is +generally easier to work directly with instructions. One work around is to load +a copy of Pyodide with the source maps next to one without the source maps. This +situation is rapidly improving both on Emscripten's side and on the browser +side. To build Pyodide with DWARF, you should set `DBGFLAGS="-g3 -gseparate-dwarf`. + +If you are building in the docker image, you will get error 404s when the +browser tries to look up the source maps because the path `/src/cpython/...` +doesn't exist. One dumb solution is `sudo ln -s $(pwd) /src`. It might not be +the best idea to link some random directory into root, if you manage to destroy +your computer with this please don't blame me. In particular, if you later want +to remove this link make sure not to remove `/srv` instead! The correct solution +is to use `--source-map-base`, but I can't seem to get it to work. diff --git a/docs/development/maintainers.md b/docs/development/maintainers.md index 8b05414a979..b4b625e26ad 100644 --- a/docs/development/maintainers.md +++ b/docs/development/maintainers.md @@ -20,17 +20,21 @@ the latest release branch named `stable` (due to ReadTheDocs constraints). 2. Set the version in: - - `docs/project/about.md`, + - `docs/project/about.md` (the Zenodo citation), + - `docs/development/building-from-sources.md`, + - `docs/usage/downloading-and-deploying.md`, - `setup.cfg`, - `src/js/package.json`, - `src/py/pyodide/__init__.py`, - `src/py/setup.cfg`, - `pyodide-build/setup.cfg`, + - ... other places After this, try using `ripgrep` to make sure there are no extra old versions lying around e.g., `rg -F "0.18"`, `rg -F dev0`, `rg -F dev.0`. 3. Make sure the change log is up-to-date. + - Indicate the release date in the change log. - Generate the list of contributors for the release at the end of the changelog entry with, @@ -39,6 +43,7 @@ the latest release branch named `stable` (due to ReadTheDocs constraints). ``` where `LAST_TAG` is the tag for the last release. Merge the PR. + 4. Assuming the upstream `stable` branch exists, rename it to a release branch for the previous major version. For instance if last release was, `0.20.0`, the corresponding release branch would be `0.20.X`, @@ -69,7 +74,7 @@ the latest release branch named `stable` (due to ReadTheDocs constraints). 6. Release the Pyodide JavaScript package: ```bash - cd src/js + cd dist npm publish # Note: use "--tag next" for prereleases npm dist-tag add pyodide@a.b.c next # Label this release as also the latest unstable release ``` @@ -77,6 +82,8 @@ the latest release branch named `stable` (due to ReadTheDocs constraints). 7. Revert Step 1. and increment the version in `src/py/pyodide/__init__.py` to the next version specified by Semantic Versioning. +8. Update this file with any relevant changes. + ### Making a minor release For a minor release, commits need to be added to the `stable` branch, ideally via a PR. @@ -111,7 +118,7 @@ alpha version as the stable release. If you accidentally publish the alpha release over the stable `latest` tag, you can fix it with: `npm dist-tag add pyodide@a.b.c latest` where `a.b.c` should be -the lastest stable version. Then use +the latest stable version. Then use `npm dist-tag add pyodide@a.b.c-alpha.d next` to set the `next` tag to point to the just-published alpha release. diff --git a/docs/development/meta-yaml.md b/docs/development/meta-yaml.md index f68b22308fa..0f5cf1c39f8 100644 --- a/docs/development/meta-yaml.md +++ b/docs/development/meta-yaml.md @@ -9,9 +9,16 @@ though it is much more limited. The most important limitation is that Pyodide assumes there will only be one version of a given library available, whereas Conda allows the user to specify the versions of each package that they want to install. Despite the limitations, it is recommended to use existing conda -package definitions as a starting point to create Pyodide packages. In -general, however, one should not -expect Conda packages to "just work" with Pyodide, see {pr}`795` +package definitions as a starting point to create Pyodide packages. In general, +however, one should not expect Conda packages to "just work" with Pyodide, see +{pr}`795` + +```{admonition} This is unstable +:class: warning + +The Pyodide build system is under fairly active development (as of 2022/03/13). +The next couple of releases are likely to include breaking changes. +``` The supported keys in the `meta.yaml` file are described below. @@ -76,16 +83,6 @@ source tree (the expanded tarball). ## `build` -### `build/skip_host` - -Skip building C extensions for the host environment. Default: `True`. - -Setting this to `False` will result in ~2x slower builds for packages that -include C extensions. It should only be needed when a package is a build -time dependency for other packages. For instance, numpy is imported during -installation of matplotlib, importing numpy also imports included C extensions, -therefore it is built both for host and target. - ### `build/cflags` Extra arguments to pass to the compiler when building for WebAssembly. @@ -153,3 +150,19 @@ A list of required packages. ### `test/imports` List of imports to test after the package is built. + +## Supported Environment Variables + +The following environment variables can be used in the scripts in the meta.yaml +files: + +- PYODIDE_ROOT: The path to the base Pyodide directory +- PYMAJOR: Current major Python version +- PYMINOR: Current minor Python version +- PYMICRO: Current micro Python version +- SIDE_MODULE_CFLAGS: The standard CFLAGS for a side module. Use when compiling + libraries or shared libraries. +- SIDE_MODULE_LDFLAGS: The standard LDFLAGS for a side module. Use when linking + a shared library. +- NUMPY_LIB: Use `-L$NUMPY_LIB` as a ldflag when linking `-lnpymath` or + `-lnpyrandom`. diff --git a/docs/development/new-packages.md b/docs/development/new-packages.md index 39e0a774ace..89fa5d91f40 100644 --- a/docs/development/new-packages.md +++ b/docs/development/new-packages.md @@ -16,14 +16,13 @@ you should start this process with package dependencies. Most pure Python packages can be installed directly from PyPI with {func}`micropip.install` if they have a pure Python wheel. Check if this is the -case by going to the `pypi.org/project/` URL and checking if the -"Download files" tab contains a file that ends with `*py3-none-any.whl`. +case by trying `micropip.install("package-name")`. If the wheel is not on PyPI, but nevertheless you believe there is nothing preventing it (it is a Python package without C extensions): - you can create the wheel yourself by running, - ``` + ```py python -m pip install build python -m build ``` @@ -31,8 +30,8 @@ preventing it (it is a Python package without C extensions): [Python packaging guide](https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives) for more details. Then upload the wheel file somewhere (not to PyPI) and install it with micropip via its URL. -- you can also open an issue in the package repository asking the authors to - upload the wheel. +- please open an issue in the package repository asking the authors to upload + the wheel. If however the package has C extensions or its code requires patching, then continue to the next steps. @@ -46,11 +45,11 @@ any compilation commands. If your package is on PyPI, the easiest place to start is with the {ref}`mkpkg tool `. From the Pyodide root directory, install the -tool with `pip install -e pyodide-build`, then run: +tool with `pip install ./pyodide-build`, then run: -`pyodide-build mkpkg ` +`python -m pyodide_build mkpkg ` -This will generate a `meta.yaml` under `package//` (see +This will generate a `meta.yaml` file under `packages//` (see {ref}`meta-yaml-spec`) that should work out of the box for many simple Python packages. This tool will populate the latest version, download link and sha256 hash by querying PyPI. It doesn't currently handle package dependencies, so you @@ -68,19 +67,17 @@ file. This can be done either by checking if the URL exists, or by searching the [conda-forge GitHub org](https://github.com/conda-forge/) for the package name. -The `meta.yaml` in Pyodide was inspired by the one in conda, however it is +The Pyodide `meta.yaml` file format was inspired by the one in conda, however it is not strictly compatible. ``` ### 3. Building the package and investigating issues -Once the `meta.yaml` is ready, build the package with the following commands -from inside the package directory `packages/` +Once the `meta.yaml` file is ready, build the package with the following +commands from inside the package directory `packages/` -``` -export PYTHONPATH="$PYTHONPATH:/path/to/pyodide/pyodide-build/" +```sh python -m pyodide_build buildpkg meta.yaml -cp build/*.data build/*.js ../../build/ ``` and see if there are any errors. @@ -96,43 +93,77 @@ then restart the build. If the package has a git repository, the easiest way to make a patch is usually: -1. Clone the git repository. You might want to use the options `git clone --depth 1 --branch `. Find the appropriate tag given the version - of the package you are trying to modify. -2. Make whatever changes you want. Commit them. -3. Use `git format-patch HEAD~1 -o /packages//patches/` +1. Clone the git repository of the package. You might want to use the options + `git clone --depth 1 --branch `. Find the appropriate tag given the + version of the package you are trying to modify. +2. Make a new branch with `git checkout -b pyodide-version` (e.g., + `pyodide-1.21.4`). +3. Make whatever changes you want. Commit them. Please split your changes up + into focused commits. Write detailed commit messages! People will read them + in the future, particularly when migrating patches or trying to decide if + they are no longer needed. The first line of each commit message will also be + used in the patch file name. +4. Use `git format-patch -o /packages//patches/` to generate a patch file for your changes and store it directly into the patches folder. -It is also possible to create a patch with `git diff --no-index old_file new_file > my_changes.patch`, but you will probably have to fix up the paths in -the generated diff. If it is taking a large number of attempts, you can use the -flags `--src-prefix` and `--dst-prefix` to try to ensure the patch file is -generated with the correct paths. +#### Migrating Patches + +When you want to upgrade the version of a package, you will need to migrate the +patches. To do this: + +1. Clone the git repository of the package. You might want to use the options + `git clone --depth 1 --branch `. +2. Make a new branch with `git checkout -b pyodide-old-version` (e.g., + `pyodide-1.21.4`). +3. Apply the current patches with `git am /packages//patches/*`. +4. Make a new branch `git checkout -b pyodide-new-version` (e.g., + `pyodide-1.22.0`) +5. Rebase the patches with `git rebase old-version --onto new-version` (e.g., + `git rebase pyodide-1.21.4 --onto pyodide-1.22.0`). Resolve any rebase + conflicts. If a patch has been upstreamed, you can drop it with `git rebase --skip`. +6. Remove old patches with `rm /packages//patches/*`. +7. Use `git format-patch -o /packages//patches/` + to generate new patch files. + +#### Upstream your patches! + +Please create PRs or issues to discuss with the package maintainers to try to +find ways to include your patches into the package. Many package maintainers are +very receptive to including Pyodide-related patches and they reduce future +maintenance work for us. ## The package build pipeline -Pyodide includes a toolchain to make it easier to add new third-party Python -libraries to the build. We automate the following steps: - -- Download a source tarball (usually from PyPI) -- Confirm integrity of the package by comparing it to a checksum -- Apply patches, if any, to the source distribution -- Add extra files, if any, to the source distribution -- If the package includes C/C++/Cython extensions: - - Build the package natively, keeping track of invocations of the native - compiler and linker - - Rebuild the package using emscripten to target WebAssembly -- If the package is pure Python: - - Run the `setup.py` script to get the built package -- Unvendor unit tests included in the installation folder to a separate package - `-tests` -- Package the results into an emscripten virtual filesystem package, which - comprises: - - A `.data` file containing the file contents of the whole package, - concatenated together - - A `.js` file which contains metadata about the files and installs them into - the virtual filesystem. - -Lastly, a `packages.json` file is output containing the dependency tree of all +Pyodide includes a toolchain to add new third-party Python libraries to the +build. We automate the following steps: + +- If source is a url (not in-tree): + - Download a source archive or a pure python wheel (usually from PyPI) + - Confirm integrity of the package by comparing it to a checksum + - If building from source (not from a wheel): + - Apply patches, if any, to the source distribution + - Add extra files, if any, to the source distribution +- If the source is not a wheel (building from a source archive or an in-tree + source): + - Run `build/script` if present + - Modify the `PATH` to point to wrappers for `gfortran`, `gcc`, `g++`, `ar`, + and `ld` that preempt compiler calls, rewrite the arguments, and pass them + to the appropriate emscripten compiler tools. + - Using `pypa/build`: + - Create an isolated build environment. Install symbolic links from this + isolated environment to "host" copies of certain unisolated packages. + - Install the build dependencies requested in the package `build-requires`. + (We ignore all version constraints on the unisolated packages, but version + constraints on other packages are respected. + - Run the PEP 517 build backend associated to the project to generate a wheel. +- Unpack the wheel with `python -m wheel unpack`. +- Run the `build/post` script in the unpacked wheel directory if it's present. +- Unvendor unit tests included in the installation folder to a separate zip file + `-tests.zip` +- Repack the wheel with `python -m wheel pack` + +Lastly, a `packages.json` file is created containing the dependency tree of all packages, so {any}`pyodide.loadPackage` can load a package's dependencies automatically. @@ -150,12 +181,6 @@ build, then when it works, copy the modified sources into your checked out copy of the package source repository and use `git format-patch` to generate the patch. -For very complicated builds, we also have a mechanism for repeating from the -replay stage of the build. If the build is failing during the replay stage, you -will see lines like `[line 766 of 1156]` labeling which command is being -replayed. `--continue=replay` will start over from the begining of the replay -stage, `--continue=replay:766` will start from step 766 of the replay stage. - ## C library dependencies Some Python packages depend on certain C libraries, e.g. `lxml` depends on @@ -174,7 +199,7 @@ package: source: url: - sha265: + sha256: requirements: run: @@ -204,56 +229,18 @@ here.](https://github.com/orgs/emscripten-ports/repositories?type=all) ## Structure of a Pyodide package This section describes the structure of a pure Python package, and how our build -system creates it. In general, it is not recommended to construct these by -hand; instead create a Python wheel and install it with micropip. +system creates it. Pyodide is obtained by compiling CPython into WebAssembly. As such, it loads -packages the same way as CPython --- it looks for relevant files `.py` files in -`/lib/python3.x/`. When creating and loading a package, our job is to put our -`.py` files in the right location in emscripten's virtual filesystem. - -Suppose you have a Python library that consists of a single directory -`/PATH/TO/LIB/` whose contents would go into -`/lib/python3.9/site-packages/PACKAGE_NAME/` under a normal Python installation. - -The simplest version of the corresponding Pyodide package contains two files --- -`PACKAGE_NAME.data` and `PACKAGE_NAME.js`. The first file `PACKAGE_NAME.data` is -a concatenation of all contents of `/PATH/TO/LIB`. When loading the package via -`pyodide.loadPackage`, Pyodide will load and run `PACKAGE_NAME.js`. The script -then fetches `PACKAGE_NAME.data` and extracts the contents to emscripten's -virtual filesystem. Afterwards, since the files are now in `/lib/python3.9/`, -running `import PACKAGE_NAME` in Python will successfully import the module as -usual. - -To construct this bundle, we use the `file_packager.py` script from emscripten. -We invoke it as follows: - -```sh -$ ./tools/file_packager.sh \ - PACKAGE_NAME.data \ - --js-output=PACKAGE_NAME.js \ - --preload /PATH/TO/LIB/@/lib/python3.9/site-packages/PACKAGE_NAME/ -``` - -The arguments can be explained as follows: - -- PACKAGE_NAME.data indicates where to put the data file -- --js-output=PACKAGE_NAME.js indicates where to put the javascript file -- `--preload` instructs the package to look for the file/directory before the - separator `@` (namely `/PATH/TO/LIB/`) and place it at the path after the `@` - in the virtual filesystem (namely - `/lib/python3.9/site-packages/PACKAGE_NAME/`). - -`file_packager.sh` adds the following options: - -- `--lz4` to use LZ4 to compress the files -- `--export-name=globalThis.__pyodide_module` tells `file_packager` where to - find the main Emscripten module for linking. -- `--exclude *__pycache__*` to omit the pycache directories -- `--use-preload-plugins` says to [automatically decode files based on their - extension](https://emscripten.org/docs/porting/files/packaging_files.html#preloading-files) - The preload plugins are important if the package contains any `.so` files - because `WebAssembly.instantiate` is asynchronous. +packages the same way as CPython --- it looks for relevant files `.py` and `.so` +files in the directories in `sys.path`. When installing a package, our job is to +install our `.py` and `.so` files in the right location in emscripten's virtual +filesystem. + +Wheels are just zip archives, and to install them we unzip them into the +`site-packages` directory. If there are any `.so` files, we also need to load +them at install time: WebAssembly must be loaded asynchronously, but Python +imports are synchronous so it is impossible to load `.so` files lazily. ```{eval-rst} .. toctree:: diff --git a/docs/development/signature-mismatch1.png b/docs/development/signature-mismatch1.png new file mode 100644 index 00000000000..4e5708500e1 Binary files /dev/null and b/docs/development/signature-mismatch1.png differ diff --git a/docs/development/signature-mismatch2.png b/docs/development/signature-mismatch2.png new file mode 100644 index 00000000000..2fc0fffdac8 Binary files /dev/null and b/docs/development/signature-mismatch2.png differ diff --git a/docs/development/signature-mismatch3.png b/docs/development/signature-mismatch3.png new file mode 100644 index 00000000000..f3dc76ec1b3 Binary files /dev/null and b/docs/development/signature-mismatch3.png differ diff --git a/docs/development/signature-mismatch4.png b/docs/development/signature-mismatch4.png new file mode 100644 index 00000000000..df735e2a472 Binary files /dev/null and b/docs/development/signature-mismatch4.png differ diff --git a/docs/development/testing.md b/docs/development/testing.md index dc9172b8043..ea9f03aeeaa 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -18,7 +18,7 @@ and check that they are in your `PATH`. ### Running the Python test suite -To run the pytest suite of tests, type on the command line: +To run the pytest suite of tests, from the root directory of Pyodide, type on the command line: ```bash pytest @@ -35,40 +35,29 @@ There are 3 test locations that are collected by pytest, To run tests on the JavaScript Pyodide package using Mocha, run the following commands, -``` +```sh cd src/js npm test ``` To check TypeScript type definitions run, -``` +```sh npx tsd ``` ### Manual interactive testing -To run manual interactive tests, a docker environment and a webserver will be -used. - -1. Bind port 8000 for testing. To automatically bind port 8000 of the docker - environment and the host system, run: `./run_docker` - -2. Now, this can be used to test the Pyodide builds running within the - docker environment using external browser programs on the host system. To do - this, run: `pyodide-build serve` +To run tests manually: -3. This serves the `build` directory of the Pyodide project on port 8000. +1. Build Pyodide, perhaps in the docker image - - To serve a different directory, use the `--build_dir` argument followed - by the path of the directory. - - To serve on a different port, use the `--port` argument followed by the - desired port number. Make sure that the port passed in `--port` argument - is same as the one defined as `DOCKER_PORT` in the `run_docker` script. +2. From outside of the docker image, `cd` into the `dist` directory and run + `python -m http.server`. -4. Once the webserver is running, simple interactive testing can be run by - visiting this URL: - [http://localhost:8000/console.html](http://localhost:8000/console.html) +3. Once the webserver is running, simple interactive testing can be run by + visiting the URL: `http://localhost:/console.html`. It's recommended to + use `pyodide.runPython` in the browser console rather than using the repl. ## Benchmarking @@ -76,7 +65,7 @@ To run common benchmarks to understand Pyodide's performance, begin by installing the same prerequisites as for testing. Then run: ```bash -make benchmark +PYODIDE_PACKAGES="numpy,matplotlib" make benchmark ``` ## Linting diff --git a/docs/jsdoc_conf.json b/docs/jsdoc_conf.json deleted file mode 100644 index ba36c1b12eb..00000000000 --- a/docs/jsdoc_conf.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "comment": "These are the default options except that we added pyodide-jsdoc-plugin.js", - "plugins": ["pyodide-jsdoc-plugin.js"], - "recurseDepth": 10, - "source": { - "includePattern": ".+\\.js(doc|x)?$", - "excludePattern": "((^|\\/|\\\\)_)|(.*gen.*)" - }, - "sourceType": "module", - "tags": { - "allowUnknownTags": true, - "dictionaries": ["jsdoc", "closure"] - }, - "templates": { - "cleverLinks": false, - "monospaceLinks": false - } -} diff --git a/docs/project/about.md b/docs/project/about.md index f26f6014507..d68471d75f2 100644 --- a/docs/project/about.md +++ b/docs/project/about.md @@ -45,7 +45,7 @@ list from there with "The Pyodide development team" like in the example below: month = aug, year = 2021, publisher = {Zenodo}, - version = {0.19.0}, + version = {0.20.0}, doi = {10.5281/zenodo.5156931}, url = {https://doi.org/10.5281/zenodo.5156931} } diff --git a/docs/project/changelog.md b/docs/project/changelog.md index e118bd67d89..af06417454e 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -12,10 +12,222 @@ substitutions: # Change Log +## Unreleased + +- {{ Fix }} We now tell packagers (e.g., Webpack) to ignore npm-specific imports when packing files for the browser. {pr}`2468` + +- {{ Enhancement }} Allow passing `credentials` to `micropip.install()` + {pr}`2458` + +- {{ Enhancement }} Update Typescript target to ES2017 to generate more modern Javascript code. {pr}`2471` + +- {{ Fix }} micropip now correctly handles package names that include dashes + {pr}`2414` + +- {{ Enhancement }} We now put our built files into the `dist` directory rather + than the `build` directory. {pr}`2387` + +- {{ Enhancement }} `loadPyodide` no longer uses any global state, so it can be + used more than once in the same thread. This is recommended if a network + request causes a loading failure, if there is a fatal error, if you damage the + state of the runtime so badly that it is no longer usable, or for certain + testing purposes. It is not recommended for creating multiple execution + environments, for which you should use + `pyodide.runPython(code, { globals : some_dict})`; + {pr}`2391` + +- {{ Bugfix }} The build will error out earlier if `cmake` or `libtool` are not installed. + +- {{ Enhancement }} Add SHA-256 hash of package to entries in `packages.json` + +### Packages + +- {{ Enhancement }} Pillow now supports WEBP image format {pr}`2407`. + +- New packages: opencv-python v4.5.5.64 {pr}`2305`, ffmpeg {pr}`2305`, libwebp {pr}`2305`, + h5py, pkgconfig and libhdf5 {pr}`2411` + +## Version 0.20.0 + +[See the release notes for a summary.](https://blog.pyodide.org/posts/0.20-release/) + +### CPython and stdlib + +- {{ Update }} Pyodide now runs Python 3.10.2. + {pr}`2225` + +- {{ Enhancement }} All + `ctypes` tests pass now except for `test_callback_too_many_args` (and we have + a plan to fix `test_callback_too_many_args` upstream). `libffi-emscripten` + now also passes all libffi tests. + {pr}`2350` + +### Packages + +- {{Fix}} matplotlib now loads multiple fonts correctly {pr}`2271` + +- New packages: boost-histogram {pr}`2174`, cryptography v3.3.2 {pr}`2263`, the + standard library ssl module {pr}`2263`, python-solvespace v3.0.7, + lazy-object-proxy {pr}`2320`. + +- Many more scipy linking errors were fixed, mostly related to the Fortran f2c + ABI for string arguments. There are still some fatal errors in the Scipy test + suite, but none seem to be simple linker errors. + {pr}`2289` + +- Removed pyodide-interrupts. If you were using this for some reason, use + {any}`setInterruptBuffer ` instead. + {pr}`2309` + +- Most included packages were updated to the latest version. See + {ref}`packages-in-pyodide` for a full list. + +### Type translations + +- {{Fix}} Python tracebacks now include Javascript frames when Python calls a + Javascript function. + {pr}`2123` + +- {{Enhancement}} Added a `default_converter` argument to {any}`JsProxy.to_py` + and {any}`pyodide.toPy` which is used to process any object that doesn't have + a built-in conversion to Python. Also added a `default_converter` argument to + {any}`PyProxy.toJs` and {any}`pyodide.to_js` to convert. + {pr}`2170` and {pr}`2208` + +- {{ Enhancement }} Async Python functions called from Javascript now have the + resulting coroutine automatically scheduled. For instance, this makes it + possible to use an async Python function as a Javascript event handler. + {pr}`2319` + +### Javascript package + +- {{Enhancement}} It is no longer necessary to provide `indexURL` to + {any}`loadPyodide `. + {pr}`2292` + +- {{ Breaking }} The `globals` argument to {any}`runPython ` + and {any}`runPythonAsync ` is now passed as a named + argument. The old usage still works with a deprecation warning. + {pr}`2300` + +- {{Enhancement}} The Javascript package was migrated to Typescript. + {pr}`2130` and {pr}`2133` + +- {{Fix}} Fix importing pyodide with ESM syntax in a module type web worker. + {pr}`2220` + +- {{Enhancement}} When Pyodide is loaded as an ES6 module, no global + {any}`loadPyodide ` variable is created (instead, it + should be accessed as an attribute on the module). + {pr}`2249` + +- {{Fix}} The type `Py2JsResult` has been replaced with `any` which is more + accurate. For backwards compatibility, we still export `Py2JsResult` as an + alias for `any`. + {pr}`2277` + +- {{Fix}} Pyodide now loads correctly even if requirejs is included. + {pr}`2283` + +- {{ Enhancement }} Added robust handling for non-`Error` objects thrown by + Javascript code. This mostly should never happen since well behaved Javascript + code ought to throw errors. But it's better not to completely crash if it + throws something else. + {pr}`2294` + +### pyodide_build + +- {{Enhancement}} Pyodide now uses Python wheel files to distribute packages + rather than the emscripten `file_packager.py` format. + {pr}`2027` + +- {{Enhancement}} Pyodide now uses `pypa/build` to build packages. We (mostly) + use build isolation, so we can build packages that require conflicting + versions of setuptools or alternative build backends. + {pr}`2272` + +- {{Enhancement}} Most pure Python packages were switched to use the wheels + directly from PyPI rather than rebuilding them. + {pr}`2126` + +- {{Enhancement}} Added support for C++ exceptions in packages. Now C++ + extensions compiled and linked with `-fexceptions` can catch C++ exceptions. + Furthermore, uncaught C++ exceptions will be formatted in a human-readable + way. + {pr}`2178` + +- {{Breaking}} Removed the `skip-host` key from the `meta.yaml` format. If + needed, install a host copy of the package with pip instead. + {pr}`2256` + +### Uncategorized + +- {{ Enhancement }} The interrupt buffer can be used to raise all 64 signals + now, not just `SIGINT`. Write a number between `1<= signum <= 64` into the + interrupt buffer to trigger the corresponding signal. By default everything + but `SIGINT` will be ignored. Any value written into the interrupt buffer + outside of the range from 1 to 64 will be silently discarded. + {pr}`2301` + +- {{ Enhancement }} Updated to Emscripten 2.0.27. + {pr}`2295` + +- {{ Breaking }} The `extractDir` argument to + {any}`unpackArchive ` is now passed as a named argument. + The old usage still works with a deprecation warning. + {pr}`2300` + +- {{ Enhancement }} Support ANSI escape codes in the Pyodide console. + {pr}`2345` + +- {{ Fix }} `pyodide_build` can now be installed in non-editable ways. + {pr}`2351` + +### List of contributors + +Boris Feld, Christian Staudt, Gabriel Fougeron, Gyeongjae Choi, Henry Schreiner, +Hood Chatham, Jo Bovy, Karthikeyan Singaravelan, Leo Psidom, Liumeo, Luka +Mamukashvili, Madhur Tandon, Paul Korzhyk, Roman Yurchak, Seungmin Kim, Thorsten +Beier, Tom White, and Will Lachance + +## Version 0.19.1 + +_February 19, 2022_ + +### Packages + +- New packages: sqlalchemy {pr}`2112`, pydantic {pr}`2117`, wrapt {pr}`2165` + +- {{ Update }} Upgraded packages: pyb2d (0.7.2), {pr}`2117` + +- {{Fix}} A fatal error in `scipy.stats.binom.ppf` has been fixed. + {pr}`2109` + +- {{Fix}} Type signature mismatches in some numpy comparators have been fixed. + {pr}`2110` + +### Type translations + +- {{Fix}} The "PyProxy has already been destroyed" error message has been + improved with some context information. + {pr}`2121` + +### REPL + +- {{Enhancement}} Pressing TAB in REPL no longer triggers completion when input + is whitespace. {pr}`2125` + +### List of contributors + +Christian Staudt, Gyeongjae Choi, Hood Chatham, Liumeo, Paul Korzhyk, Roman +Yurchak, Seungmin Kim, Thorsten Beier + ## Version 0.19.0 _January 10, 2021_ +[See the release notes for a summary.](https://blog.pyodide.org/posts/0.19-release/) + ### Python package - {{Enhancement}} If `find_imports` is used on code that contains a syntax @@ -445,7 +657,7 @@ _August 3rd, 2021_ ### List of contributors -Albertas Gimbutas, Andreas Klostermann, arfy slowy, daoxian, +Albertas Gimbutas, Andreas Klostermann, Arfy Slowy, daoxian, Devin Neal, fuyutarow, Grimmer, Guido Zuidhof, Gyeongjae Choi, Hood Chatham, Ian Clester, Itay Dafna, Jeremy Tuloup, jmsmdy, LinasNas, Madhur Tandon, Michael Christensen, Nicholas Bollweg, Ondřej Staněk, Paul m. p. P, @@ -574,7 +786,7 @@ See the {ref}`0-17-0-release-notes` for more information. - `eval_code` now accepts separate `globals` and `locals` parameters. {pr}`1083` - Added the `pyodide.setInterruptBuffer` API. This can be used to set a - `SharedArrayBuffer` to be the keyboard interupt buffer. If Pyodide is running + `SharedArrayBuffer` to be the keyboard interrupt buffer. If Pyodide is running on a webworker, the main thread can signal to the webworker that it should raise a `KeyboardInterrupt` by writing to the interrupt buffer. {pr}`1148` and {pr}`1173` @@ -599,7 +811,7 @@ See the {ref}`0-17-0-release-notes` for more information. ### Build system -- {{ Enhancement }} Updated to latest emscripten 2.0.13 with the updstream LLVM backend +- {{ Enhancement }} Updated to latest emscripten 2.0.13 with the upstream LLVM backend {pr}`1102` - {{ API }} Use upstream `file_packager.py`, and stop checking package abi versions. The `PYODIDE_PACKAGE_ABI` environment variable is no longer used, but is @@ -739,7 +951,7 @@ by 0.16.1 with identical contents. ### Other improvements -- Modifiy MEMFS timestamp handling to support better caching. This in +- Modify MEMFS timestamp handling to support better caching. This in particular allows to import newly created Python modules without invalidating import caches {pr}`893` @@ -877,3 +1089,10 @@ _Mar 21, 2019_ have been removed. - The `run_docker` script can now be configured with environment variables. + +```{eval-rst} +.. toctree:: + :hidden: + + deprecation-timeline.md +``` diff --git a/docs/project/deprecation-timeline.md b/docs/project/deprecation-timeline.md new file mode 100644 index 00000000000..b87f01541c3 --- /dev/null +++ b/docs/project/deprecation-timeline.md @@ -0,0 +1,38 @@ +(deprecation-timeline)= + +# Pyodide Deprecation Timeline + +Each Pyodide release may deprecate certain features from previous releases in a +backward incompatible way. If a feature is deprecated, it will continue to work +until its removal, but raise warnings. We try to ensure deprecations are done +over at least two minor(feature) releases, however, as Pyodide is still in beta +state, this list is subject to change and some features can be removed without +deprecation warnings. More details about each item can often be found in the +{ref}`changelog`. + +## 0.21.0 + +- The `globals` argument to `runPython` and `runPythonAsync` will be passed as a + named argument only. +- The `extractDir` argument to `unpackArchive` will be passed as a named + argument only. + +## 0.20.0 + +- The skip-host key will be removed from the meta.yaml format. If needed, + install a host copy of the package with pip instead. +- `pyodide-interrupts` module will be removed. If you were using this for some + reason, use {any}`setInterruptBuffer ` instead. + +## 0.19.0 + +- The default working directory (home directory) inside the Pyodide virtual file + system has been changed from `/` to `/home/pyodide`. To get the previous + behavior, you can + + - call `os.chdir("/")` in Python to change working directory or + - call {any}`loadPyodide ` with the `homedir="/"` + argument + +- When a JavaScript function is called from Python, PyProxy arguments and return + values will be automatically destroyed when the function is finished. diff --git a/docs/project/roadmap.md b/docs/project/roadmap.md index 2e296c5f2b1..abc0a77e86e 100644 --- a/docs/project/roadmap.md +++ b/docs/project/roadmap.md @@ -63,17 +63,6 @@ resolution. See issues {issue}`2045` and {issue}`1100`. -## Switch to using wheels for Python packages - -We are planning to switch from using Emscripten's file packager to packaging -Python packages as wheels. Other shared libraries can be bundled as zip or tar -archives. This makes us more compliant with the general Python ecosystem and -makes the archive files easier to inspect. They could also be used more easily -with systems other than Emscripten. Eventually, it is possible that packages -will be able to upload wheels for Pyodide to PyPi. - -See issue {pr}`655` and PR {pr}`2027`. - ## Find a better way to compile Fortran Currently, we use f2c to cross compile Fortran to C. This does not work very @@ -90,16 +79,6 @@ projects heading in that direction including flang and lfortran. See {issue}`scipy/scipy#15290`. -## Support for Rust packages - -We have promising partial work toward compiling Python packages with Rust extensions for use with -Pyodide. So far we have only compiled small toy examples. Currently the compiled -Rust packages have various functional limitations and come out _very_ large. -Hopefully we can work toward increasing functionality so we can compile real -world Rust packages. It would also be good to reduce the Rust package sizes. - -See {issue}`1973` and {pr}`2081`. - ## Better project sustainability Some of the challenges that Pyodide faces, such as maintaining a collection of diff --git a/docs/requirements-doc.txt b/docs/requirements-doc.txt index 92ada9f31b4..f40999f78d6 100644 --- a/docs/requirements-doc.txt +++ b/docs/requirements-doc.txt @@ -1,12 +1,13 @@ autodocsumm docutils==0.16 -myst-parser==0.13.3 +myst-parser packaging # required by micropip at import time -sphinx +sphinx>=4.4.0 sphinx-argparse-cli~=1.6.0 sphinx_book_theme -sphinxcontrib-napoleon -sphinx-issues +sphinxcontrib-napoleon>=0.7 +sphinx-issues==2.0.0 sphinx-js==3.1 sphinx-version-warning~=1.1.2 sphinx-panels +markupsafe<2.1.0 diff --git a/docs/sphinx_pyodide/README.md b/docs/sphinx_pyodide/README.md new file mode 100644 index 00000000000..cb6d333c89e --- /dev/null +++ b/docs/sphinx_pyodide/README.md @@ -0,0 +1,104 @@ +# Sphinx-Pyodide + +This package is a special sphinx extension for Pyodide's documentation. +The three separate files do three unrelated tasks: + +## packages + +Adds a custom directive which makes a table of the Pyodide packages + +## lexers + +Adds custom pyodide Pygments lexers that provides syntax highlighting for +Javascript with embedded Python code, and for HTML with embedded Javascript that +itself contains embedded Python. + +e.g., in + +```pyodide +await pyodide.loadPackage(numpy); +pyodide.runPython(` + def f(y): + return y + ${x} + f(7) +`); +``` + +the code inside of `pyodide.runPython` is highlighted as Python, the other code +is highlighted as Javascript. Similarly with `html-pyodide`: + +```html-pyodide +
+ +``` + +## jsdoc + +This extends `sphinx-js` for our purposes. This contains a mix of missing +features that could ideally be upstreamed into `sphinx-js` and custom code that +can't be upstreamed. + +### TsAnalyzer monkey patches + +We monkey patch `TsAnalyzer._type_name` to fill in formatting of types that were +left as TODOs by `sphinx-js` (this could probably be upstreamed). + +We monkey patch `TsAnalyzer._convert_node` to fix two crashes (could be +upstreamed) and to turn a destructured object argument into a list of argument docs: + +```js +/** +* @param options +*/ +function f({x , y } : { + /** The x value */ + x : number, + /** The y value */ + y : string +}){ ... } +``` + +should be documented like: + +``` +options.x (number) The x value +options.y (number) The y value +``` + +### `js:function` + +We update the `js:function` directive to display `async` in front of async +functions. + +### `PyodideAnalyzer` + +We compose `TsAnalyzer` into `PyodideAnalyzer`. This prunes out private +functions, marks functions as `async` so we can display `async` in front of +them, and organizes functions into our three categories: + +- `globalThis`: globaly exposed functions +- `pyodide`: pyodide APIs +- `PyProxy`: `PyProxy` APIs + +It also classifies them into three types: function, class, or attribute so we +can separate them out in the summary. + +### js-autodoc + +`sphinx-js` doesn't include full recursive layout features by default. This adds +a `js-doc-summary` directive and a `js-doc-content` directive. `js-doc-summary` +makes a summary table for any category (`globalThis`, `pyodide` or `PyProxy`) +and `js-doc-content` formats the main documentation content. These are used in +`js-api.md`. A lot of this code is similar to the `autosummary` package, but +there are enough differences that it was easier to copy the code in. + +## autodoc_submodules + +This makes autodoc recursively document submodules, mixed in with the top level +docs. For instance, `pyfetch` is defined in `pyodide.html`. This adds an entry +called `http.pyfetch` to the list of documented APIs for `pyodide`. diff --git a/docs/sphinx_pyodide/sphinx_pyodide/__init__.py b/docs/sphinx_pyodide/sphinx_pyodide/__init__.py index 17b7bdb0473..1d1a62de594 100644 --- a/docs/sphinx_pyodide/sphinx_pyodide/__init__.py +++ b/docs/sphinx_pyodide/sphinx_pyodide/__init__.py @@ -1,6 +1,10 @@ -from .jsdoc import PyodideAnalyzer -from .lexers import PyodideLexer, HtmlPyodideLexer -from .jsdoc import get_jsdoc_summary_directive, get_jsdoc_content_directive +from .autodoc_submodules import monkeypatch_module_documenter +from .jsdoc import ( + PyodideAnalyzer, + get_jsdoc_content_directive, + get_jsdoc_summary_directive, +) +from .lexers import HtmlPyodideLexer, PyodideLexer from .packages import get_packages_summary_directive @@ -8,68 +12,8 @@ def wrap_analyzer(app): app._sphinxjs_analyzer = PyodideAnalyzer(app._sphinxjs_analyzer) -from typing import Any, Dict, List, Tuple -from sphinx.util.inspect import safe_getattr -from sphinx.ext.autodoc import ModuleDocumenter, ObjectMember # type: ignore - -# Monkey patch autodoc to include submodules as well. -# We have to import the submodules for it to find them. -def get_module_members(module: Any) -> List[Tuple[str, Any]]: - members = {} # type: Dict[str, Tuple[str, Any]] - for name in dir(module): - try: - value = safe_getattr(module, name, None) - # Before patch this used to always do - # members[name] = (name, value) - # We want to also recursively look up names on submodules. - if type(value).__name__ != "module": - members[name] = (name, value) - continue - if name.startswith("_"): - continue - submodule = value # Rename for clarity - [base, _, rest] = submodule.__name__.partition(".") - if not base == module.__name__: - # Not part of package, don't document - continue - - for (sub_name, sub_val) in get_module_members(submodule): - # Skip names not in __all__ - if hasattr(submodule, "__all__") and sub_name not in submodule.__all__: - continue - qual_name = rest + "." + sub_name - members[qual_name] = (qual_name, sub_val) - continue - except AttributeError: - continue - - return sorted(list(members.values())) - - -# For some reason I was unable to monkey patch get_module_members -# so we monkey patch get_object_members instead... -# This is similar to the original function but I dropped a branch (for brevity only) -def get_object_members(self, want_all: bool): - members = get_module_members(self.object) - if not self.__all__: - # for implicit module members, check __module__ to avoid - # documenting imported objects - return True, members - else: - ret = [] - for name, value in members: - if name in self.__all__ or "." in name: - ret.append(ObjectMember(name, value)) - else: - ret.append(ObjectMember(name, value, skipped=True)) - - return False, ret - - -ModuleDocumenter.get_object_members = get_object_members # type: ignore - - def setup(app): + monkeypatch_module_documenter() app.add_lexer("pyodide", PyodideLexer) app.add_lexer("html-pyodide", HtmlPyodideLexer) app.setup_extension("sphinx_js") diff --git a/docs/sphinx_pyodide/sphinx_pyodide/autodoc_submodules.py b/docs/sphinx_pyodide/sphinx_pyodide/autodoc_submodules.py new file mode 100644 index 00000000000..10d45a192f2 --- /dev/null +++ b/docs/sphinx_pyodide/sphinx_pyodide/autodoc_submodules.py @@ -0,0 +1,71 @@ +""" +Monkey patch autodoc to recursively include submodules as well. We have to +import the submodules for it to find them. +""" + +from typing import Any + +from sphinx.ext.autodoc import ModuleDocumenter, ObjectMember +from sphinx.util.inspect import safe_getattr + +__all__ = ["monkeypatch_module_documenter"] + + +def get_module_members(module: Any) -> list[tuple[str, Any]]: + members: dict[str, tuple[str, Any]] = {} + for name in dir(module): + try: + value = safe_getattr(module, name, None) + # Before patch this used to always do + # members[name] = (name, value) + # We want to also recursively look up names on submodules. + if type(value).__name__ != "module": + members[name] = (name, value) + continue + if name.startswith("_"): + continue + submodule = value # Rename for clarity + [base, _, rest] = submodule.__name__.partition(".") + if not base == module.__name__: + # Not part of package, don't document + continue + + for (sub_name, sub_val) in get_module_members(submodule): + # Skip names not in __all__ + if hasattr(submodule, "__all__") and sub_name not in submodule.__all__: + continue + qual_name = rest + "." + sub_name + members[qual_name] = (qual_name, sub_val) + continue + except AttributeError: + continue + + return sorted(list(members.values())) + + +def get_object_members(self, want_all: bool): + """ + For some reason I was unable to monkey patch get_module_members so we monkey + patch get_object_members to call our updated `get_module_members`. + + This is similar to the original function but I dropped the `want_all` branch + for brevity because we don't use it. + """ + members = get_module_members(self.object) + if not self.__all__: + # for implicit module members, check __module__ to avoid + # documenting imported objects + return True, members + else: + ret = [] + for name, value in members: + if name in self.__all__ or "." in name: + ret.append(ObjectMember(name, value)) + else: + ret.append(ObjectMember(name, value, skipped=True)) + + return False, ret + + +def monkeypatch_module_documenter(): + ModuleDocumenter.get_object_members = get_object_members diff --git a/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py b/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py index 3e08b9262bf..21d6a73f065 100644 --- a/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py +++ b/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py @@ -1,25 +1,200 @@ +from collections import OrderedDict +from typing import Any + +import docutils.parsers.rst.directives as directives from docutils import nodes -from docutils.parsers.rst import Directive, Parser as RstParser, directives +from docutils.parsers.rst import Directive +from docutils.parsers.rst import Parser as RstParser from docutils.statemachine import StringList from docutils.utils import new_document - -from collections import OrderedDict -import re - from sphinx import addnodes +from sphinx.domains.javascript import JavaScriptDomain, JSCallable +from sphinx.ext.autosummary import autosummary_table, extract_summary from sphinx.util import rst from sphinx.util.docutils import switch_source_input -from sphinx.ext.autosummary import autosummary_table, extract_summary -from sphinx.domains.javascript import JSCallable, JavaScriptDomain - -from sphinx_js.jsdoc import Analyzer as JsAnalyzer -from sphinx_js.ir import Class, Function -from sphinx_js.parsers import path_and_formal_params, PathVisitor +from sphinx_js.ir import Class, Function, Interface +from sphinx_js.parsers import PathVisitor, path_and_formal_params from sphinx_js.renderers import ( - AutoFunctionRenderer, AutoAttributeRenderer, AutoClassRenderer, + AutoFunctionRenderer, ) +from sphinx_js.typedoc import Analyzer as TsAnalyzer + +_orig_convert_node = TsAnalyzer._convert_node +_orig_type_name = TsAnalyzer._type_name + + +def destructure_param(param: dict[str, Any]) -> list[dict[str, Any]]: + """We want to document a destructured argument as if it were several + separate arguments. This finds complex inline object types in the arguments + list of a function and "destructures" them into separately documented arguments. + + E.g., a function + + /** + * @param options + */ + function f({x , y } : { + /** The x value */ + x : number, + /** The y value */ + y : string + }){ ... } + + should be documented like: + + options.x (number) The x value + options.y (number) The y value + """ + decl = param["type"]["declaration"] + result = [] + for child in decl["children"]: + child = dict(child) + if "type" not in child: + if "signatures" in child: + child["comment"] = child["signatures"][0]["comment"] + child["type"] = { + "type": "reflection", + "declaration": dict(child), + } + else: + raise AssertionError("Didn't expect to get here...") + child["name"] = param["name"] + "." + child["name"] + result.append(child) + return result + + +def fix_up_inline_object_signature(self: TsAnalyzer, node: dict[str, Any]): + """Calls get_destructured_children on inline object types""" + kind = node.get("kindString") + if kind not in ["Call signature", "Constructor signature"]: + return + params = node.get("parameters", []) + new_params = [] + for param in params: + param_type = param["type"] + if ( + param_type["type"] != "reflection" + or "children" not in param_type["declaration"] + ): + new_params.append(param) + else: + new_params.extend(destructure_param(param)) + node["parameters"] = new_params + + +def _convert_node(self: TsAnalyzer, node: dict[str, Any]): + """Monkey patch for TsAnalyzer._convert_node. + + Fixes two crashes and separates documentation for destructured object + arguments into a series of separate argument entries. + """ + kind = node.get("kindString") + # if a class has no documented constructor, don't crash + if kind in ["Function", "Constructor", "Method"] and not node.get("sources"): + return None, [] + # This fixes a crash, not really sure what it means. + node["extendedTypes"] = [t for t in node.get("extendedTypes", []) if "id" in t] + # See docstring for destructure_param + fix_up_inline_object_signature(self, node) + return _orig_convert_node(self, node) + + +TsAnalyzer._convert_node = _convert_node + + +def object_literal_type_name(self, decl): + """This renders the names of object literal types. + + They have zero or more "children" and zero or one "indexSignatures". + For example: + + { + [key: string]: string, + name : string, + id : string + } + + has children "name" and "id" and an indexSignature "[key: string]: string" + """ + children = [] + if "indexSignature" in decl: + index_sig = decl["indexSignature"] + assert len(index_sig["parameters"]) == 1 + key = index_sig["parameters"][0] + keyname = key["name"] + keytype = self._type_name(key["type"]) + valuetype = self._type_name(index_sig["type"]) + children.append(f"[{keyname}: {keytype}]: {valuetype}") + if "children" in decl: + children.extend( + child["name"] + ": " + self._type_name(child["type"]) + for child in decl["children"] + ) + + return "{" + ", ".join(children) + "}" + + +def reflection_type_name(self, type): + """Fill in the type names for type_of_type == "reflection" + + This is left as a TODO in sphinx-js. + + There are a couple of options: if + + decl["kindString"] == "Type Literal" + + then this is a literal object type. At least we assume it's a literal object + type, maybe there are other ways for that to happen. + + Otherwise, we assume that it's a function type, which we want to format + like: + + (a : string, b : number) => string + """ + decl = type["declaration"] + if decl["kindString"] == "Type literal": + return object_literal_type_name(self, decl) + decl_sig = None + if "signatures" in decl: + decl_sig = decl["signatures"][0] + elif decl["kindString"] == "Call signature": + decl_sig = decl + assert decl_sig + params = [ + f'{ty["name"]}: {self._type_name(ty["type"])}' + for ty in decl_sig.get("parameters", []) + ] + params_str = ", ".join(params) + ret_str = self._type_name(decl_sig["type"]) + return f"({params_str}) => {ret_str}" + + +def _type_name(self, type): + """Monkey patch for sphinx-js type_name + + Rendering various types is left as TODO by _type_name. Fill these in. + """ + res = _orig_type_name(self, type) + if "TODO" not in res: + # _orig_type_name handled it, leave it alone. + return res + type_of_type = type.get("type") + if type_of_type == "predicate": + return f"boolean (typeguard for {self._type_name(type['targetType'])})" + if type_of_type == "reflection": + return reflection_type_name(self, type) + if type_of_type == "named-tuple-member": + name = type["name"] + type = self._type_name(type["element"]) + return f"{name}: {type}" + raise NotImplementedError( + f"Cannot render type name for type_of_type={type_of_type}" + ) + + +TsAnalyzer._type_name = _type_name class JSFuncMaybeAsync(JSCallable): @@ -45,9 +220,9 @@ def flatten_suffix_tree(tree): suffix tree, but the suffix tree is inconveniently shaped. So we flatten it... """ - result = {} - path = [] - iters = [] + result: dict[tuple[str, ...], Any] = {} + path: list[str] = [] + iters: list[Any] = [] cur_iter = iter(tree.items()) while True: try: @@ -78,7 +253,7 @@ class PyodideAnalyzer: we access later. """ - def __init__(self, analyzer: JsAnalyzer) -> None: + def __init__(self, analyzer: TsAnalyzer) -> None: self.inner = analyzer self.create_js_doclets() @@ -92,74 +267,69 @@ def longname_to_path(self, name): """ return PathVisitor().visit(path_and_formal_params["path"].parse(name)) - def get_object_from_json(self, json): - """Look up the JsDoc IR object corresponding to this object. We use the - "kind" field to decide whether the object is a "function" or an - "attribute". We use longname_to_path to convert the path into a list of - path components which JsAnalyzer.get_object requires. - """ - path = self.longname_to_path(json["longname"]) - if json["kind"] == "function": - kind = "function" - elif json["kind"] == "class": - kind = "class" - else: - kind = "attribute" - obj = self.inner.get_object(path, kind) - obj.kind = kind - return obj - def create_js_doclets(self): """Search through the doclets generated by JsDoc and categorize them by summary section. Skip docs labeled as "@private". """ - self.doclets = flatten_suffix_tree(self._doclets_by_path._tree) + self.doclets = flatten_suffix_tree(self._objects_by_path._tree) def get_val(): - return OrderedDict([["attribute", []], ["function", []], ["class", []]]) + return OrderedDict([("attribute", []), ("function", []), ("class", [])]) modules = ["globalThis", "pyodide", "PyProxy"] self.js_docs = {key: get_val() for key in modules} - items = {key: [] for key in modules} + items = {key: list[Any]() for key in modules} for (key, doclet) in self.doclets.items(): - if doclet.value.get("access", None) == "private": + if getattr(doclet.value, "is_private", False): continue # Remove the part of the key corresponding to the file - key = [x for x in key if "/" not in x][1:] - toplevelname = key[0] - doclet.value["name"] = doclet.value["name"].rpartition(".")[2] - if toplevelname == "globalThis.": + key = [x for x in key if "/" not in x] + filename = key[0] + toplevelname = key[1] + if key[-1].startswith("$"): + doclet.value.is_private = True + continue + doclet.value.name = doclet.value.name.rpartition(".")[2] + if filename == "pyodide.": # Might be named globalThis.something or exports.something. # Trim off the prefix. items["globalThis"] += doclet continue + pyproxy_class_endings = ("Methods", "Class") if toplevelname.endswith("#"): # This is a class method. - if toplevelname.startswith("PyProxy"): + if filename == "pyproxy.gen." and toplevelname[:-1].endswith( + pyproxy_class_endings + ): # Merge all of the PyProxy methods into one API items["PyProxy"] += doclet # If it's not part of a PyProxy class, the method will be # documented as part of the class. continue - if toplevelname.startswith("PyProxy"): + if filename == "pyproxy.gen." and toplevelname.endswith( + pyproxy_class_endings + ): + continue + if filename.startswith("PyProxy"): # Skip all PyProxy classes, they are documented as one merged # API. continue items["pyodide"] += doclet - from operator import itemgetter + + from operator import attrgetter for key, value in items.items(): - for json in sorted(value, key=itemgetter("name")): - obj = self.get_object_from_json(json) - obj.async_ = json.get("async", False) + for obj in sorted(value, key=attrgetter("name")): + obj.async_ = False if isinstance(obj, Class): - # sphinx-jsdoc messes up array types. Fix them. - for x in obj.members: - if hasattr(x, "type") and x.type: - x.type = re.sub("Array\.<([a-zA-Z_0-9]*)>", r"\1[]", x.type) - if obj.name == "iterator": - # sphinx-jsdoc messes up Symbol attributes. Fix them. - obj.name = "[Symbol.iterator]" + obj.kind = "class" + elif isinstance(obj, Function): + obj.kind = "function" + obj.async_ = obj.returns and obj.returns[0].type.startswith( + "Promise<" + ) + else: + obj.kind = "attribute" self.js_docs[key][obj.kind].append(obj) @@ -175,12 +345,13 @@ class JsDocContent(Directive): required_arguments = 1 def get_rst(self, obj): - """Grab the appropriate renderer and render us to rst. - JsDoc also has an AutoClassRenderer which may be useful in the future.""" + """Grab the appropriate renderer and render us to rst.""" if isinstance(obj, Function): renderer = AutoFunctionRenderer elif isinstance(obj, Class): renderer = AutoClassRenderer + elif isinstance(obj, Interface): + renderer = AutoClassRenderer else: renderer = AutoAttributeRenderer rst = renderer( @@ -190,12 +361,13 @@ def get_rst(self, obj): rst = self.add_async_option_to_rst(rst) return rst - def add_async_option_to_rst(self, rst): + def add_async_option_to_rst(self, rst: str) -> str: rst_lines = rst.split("\n") - for i, line in enumerate(rst_lines): - if line.startswith(".."): - break - rst_lines.insert(i + 1, " :async:") + try: + index = next(i for i, ln in enumerate(rst_lines) if ln.startswith("..")) + except StopIteration: + index = len(rst_lines) - 1 + rst_lines.insert(index + 1, " :async:") return "\n".join(rst_lines) def get_rst_for_group(self, objects): @@ -343,7 +515,7 @@ def append_row(*column_texts: str) -> None: for prefix, name, sig, summary, real_name in items: qualifier = "any" # <== Only thing changed from autosummary version if "nosignatures" not in self.options: - col1 = "%s:%s:`%s <%s>`\\ %s" % ( + col1 = "{}:{}:`{} <{}>`\\ {}".format( prefix, qualifier, name, @@ -351,7 +523,7 @@ def append_row(*column_texts: str) -> None: rst.escape(sig), ) else: - col1 = "%s:%s:`%s <%s>`" % (prefix, qualifier, name, real_name) + col1 = f"{prefix}:{qualifier}:`{name} <{real_name}>`" col2 = summary append_row(col1, col2) diff --git a/docs/sphinx_pyodide/sphinx_pyodide/lexers.py b/docs/sphinx_pyodide/sphinx_pyodide/lexers.py index 3d2828db026..f49dd4e36cb 100644 --- a/docs/sphinx_pyodide/sphinx_pyodide/lexers.py +++ b/docs/sphinx_pyodide/sphinx_pyodide/lexers.py @@ -1,7 +1,7 @@ -from pygments.lexer import bygroups, inherit, using, default +from pygments.lexer import bygroups, default, inherit, using from pygments.lexers import PythonLexer -from pygments.lexers.javascript import JavascriptLexer from pygments.lexers.html import HtmlLexer +from pygments.lexers.javascript import JavascriptLexer from pygments.token import Name, Punctuation, Text, Token diff --git a/docs/sphinx_pyodide/sphinx_pyodide/packages.py b/docs/sphinx_pyodide/sphinx_pyodide/packages.py index c8114f4b8af..61c06aef4f4 100644 --- a/docs/sphinx_pyodide/sphinx_pyodide/packages.py +++ b/docs/sphinx_pyodide/sphinx_pyodide/packages.py @@ -1,17 +1,19 @@ -from docutils import nodes -from docutils.parsers.rst import Directive -import sys import pathlib -from typing import Dict, Any, Tuple, List +import sys +from typing import Any +from docutils import nodes +from docutils.parsers.rst import Directive from sphinx import addnodes - base_dir = pathlib.Path(__file__).resolve().parents[3] sys.path.append(str(base_dir / "pyodide-build")) from pyodide_build.io import parse_package_config +PYODIDE_TESTONLY = "pyodide.test" +PYODIDE_STDLIB = "pyodide.stdlib" + def get_packages_summary_directive(app): class PyodidePackagesSummary(Directive): @@ -25,12 +27,15 @@ def run(self): packages = {} for package in packages_list: - name, version, is_library = self.parse_package_info(package) + name, version, is_library, tag = self.parse_package_info(package) - # skip libraries (e.g. libxml, libyaml, ...) - if is_library: + # skip libraries (e.g. libxml, libyaml, ...) and test only packages + if is_library or tag == PYODIDE_TESTONLY: continue + if tag == PYODIDE_STDLIB: + version = "Pyodide standard library" + packages[name] = { "name": name, "version": version, @@ -43,18 +48,21 @@ def run(self): return result - def parse_package_info(self, config: pathlib.Path) -> Tuple[str, str, bool]: + def parse_package_info( + self, config: pathlib.Path + ) -> tuple[str, str, bool, str]: yaml_data = parse_package_config(config) name = yaml_data["package"]["name"] version = yaml_data["package"]["version"] + tag = yaml_data["package"].get("_tag", "") is_library = yaml_data.get("build", {}).get("library", False) - return name, version, is_library + return name, version, is_library, tag def get_package_metadata_list( self, directory: pathlib.Path - ) -> List[pathlib.Path]: + ) -> list[pathlib.Path]: """Return metadata files of packages in alphabetical order (case insensitive)""" return sorted( directory.glob("**/meta.yaml"), @@ -62,8 +70,8 @@ def get_package_metadata_list( ) def format_packages_table( - self, packages: Dict[str, Any], columns: Tuple[str] - ) -> List[Any]: + self, packages: dict[str, Any], columns: tuple[str, ...] + ) -> list[Any]: table_spec = addnodes.tabular_col_spec() table_spec["spec"] = r"\X{1}{2}\X{1}{2}" @@ -84,7 +92,7 @@ def format_packages_table( group += thead rows = [] - for name, pkg_info in packages.items(): + for pkg_info in packages.values(): row = nodes.row() rows.append(row) for column in columns: diff --git a/docs/sphinx_pyodide/tests/jsdoc_dump.json.gz b/docs/sphinx_pyodide/tests/jsdoc_dump.json.gz deleted file mode 100644 index 8005b7e9477..00000000000 Binary files a/docs/sphinx_pyodide/tests/jsdoc_dump.json.gz and /dev/null differ diff --git a/docs/sphinx_pyodide/tests/test_directives.py b/docs/sphinx_pyodide/tests/test_directives.py index 1fe9f965909..2ed3cf097b2 100644 --- a/docs/sphinx_pyodide/tests/test_directives.py +++ b/docs/sphinx_pyodide/tests/test_directives.py @@ -1,31 +1,42 @@ +import collections +import gzip +import json import pathlib import sys -import json -import gzip +import types +from typing import Mapping, Union -from docutils.utils import new_document from docutils.frontend import OptionParser -from sphinx_js.jsdoc import Analyzer as JsAnalyzer +from docutils.utils import new_document + +# Shim sphinx-js Python 3.10 compatibility +collections.Mapping = Mapping # type: ignore[attr-defined] +types.Union = Union # type: ignore[attr-defined] from sphinx_js.suffix_tree import SuffixTree +from sphinx_js.typedoc import Analyzer as TsAnalyzer test_directory = pathlib.Path(__file__).resolve().parent sys.path.append(str(test_directory.parent)) -# jsdoc_dump.json.gz is the source file for the test docs. -# It can be updated as follows: -# jsdoc -c ./docs/jsdoc_conf.json -X ./src/js/ ./src/core/ | gzip > docs/sphinx_pyodide/tests/jsdoc_dump.json.gz -with gzip.open(test_directory / "jsdoc_dump.json.gz") as fh: + +# tsdoc_dump.json.gz is the source file for the test docs. It can be updated as follows: +# +# cp src/core/pyproxy.ts src/js/pyproxy.gen.ts +# typedoc src/js/*.ts --tsconfig src/js/tsconfig.json --json docs/sphinx_pyodide/tests/ +# gzip docs/sphinx_pyodide/tests/ +# rm src/js/pyproxy.gen.ts +with gzip.open(test_directory / "tsdoc_dump.json.gz") as fh: jsdoc_json = json.load(fh) settings_json = json.loads((test_directory / "app_settings.json").read_text()) from sphinx_pyodide.jsdoc import ( PyodideAnalyzer, + flatten_suffix_tree, get_jsdoc_content_directive, get_jsdoc_summary_directive, - flatten_suffix_tree, ) -inner_analyzer = JsAnalyzer(jsdoc_json, "/home/hood/pyodide/src") +inner_analyzer = TsAnalyzer(jsdoc_json, "/home/hood/pyodide/src") settings = OptionParser().get_default_values() settings.update(settings_json, OptionParser()) @@ -68,15 +79,17 @@ def test_pyodide_analyzer(): "loadPackage", "runPythonAsync", "loadPackagesFromImports", + "pyimport", "registerJsModule", "isPyProxy", "toPy", "setInterruptBuffer", - "setStandardStreams", "checkInterrupt", + "unpackArchive", "registerComlink", } assert attribute_names == { + "IN_NODE", "FS", "loadedPackages", "globals", @@ -122,7 +135,7 @@ def no_op_parse_rst(rst): rp = results["runPython"] assert rp["directive"] == "function" - assert rp["sig"] == "code, globals)" + assert rp["sig"] == "code, globals=Module.globals)" assert "Runs a string of Python code from JavaScript." in rp["body"] @@ -157,7 +170,7 @@ def test_summary(): assert globals["loadPyodide"] == ( "*async* ", "loadPyodide", - "(config, )", + "(config)", "Load the main Pyodide wasm module and initialize it.", "globalThis.loadPyodide", ) diff --git a/docs/sphinx_pyodide/tests/tsdoc_dump.json.gz b/docs/sphinx_pyodide/tests/tsdoc_dump.json.gz new file mode 100644 index 00000000000..58d954bfb4a Binary files /dev/null and b/docs/sphinx_pyodide/tests/tsdoc_dump.json.gz differ diff --git a/docs/usage/downloading-and-deploying.md b/docs/usage/downloading-and-deploying.md index 0bfe9c24d3f..c28fc6ed43d 100644 --- a/docs/usage/downloading-and-deploying.md +++ b/docs/usage/downloading-and-deploying.md @@ -10,7 +10,7 @@ Pyodide packages, including the `pyodide.js` file, are available from the JsDeli | channel | indexURL | Comments | REPL | | ------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------- | -------------------------------------------------- | -| Latest release | `https://cdn.jsdelivr.net/pyodide/v0.19.0/full/` | Recommended, cached by the browser | [link](https://pyodide.org/en/stable/console.html) | +| Latest release | `https://cdn.jsdelivr.net/pyodide/v0.20.0/full/` | Recommended, cached by the browser | [link](https://pyodide.org/en/stable/console.html) | | Dev (`main` branch) | `https://cdn.jsdelivr.net/pyodide/dev/full/` | Re-deployed for each commit on main, no browser caching, should only be used for testing | [link](https://pyodide.org/en/latest/console.html) | To access a particular file, append the file name to `indexURL`. For instance, diff --git a/docs/usage/faq.md b/docs/usage/faq.md index 321ad691ec4..e6977396b1f 100644 --- a/docs/usage/faq.md +++ b/docs/usage/faq.md @@ -11,7 +11,7 @@ For that purpose, Pyodide provides {any}`pyodide.http.pyfetch`, which is a convenient wrapper of JavaScript `fetch`: ```pyodide -pyodide.runPython(` +await pyodide.runPythonAsync(` from pyodide.http import pyfetch response = await pyfetch("https://some_url/...") if response.status == 200: @@ -82,14 +82,30 @@ and returns a list of packages imported. ## How can I execute code in a custom namespace? -The second argument to {any}`pyodide.eval_code` is a global namespace to execute the code in. -The namespace is a Python dictionary. +The second argument to {any}`pyodide.runPython` is an options object which may +include a `globals` element which is a namespace for code to read from and write +to. The provided namespace must be a Python dictionary. -```javascript -let my_namespace = pyodide.globals.dict(); -pyodide.runPython(`x = 1 + 1`, my_namespace); -pyodide.runPython(`y = x ** x`, my_namespace); -my_namespace.y; // ==> 4 +```pyodide +let my_namespace = pyodide.globals.get("dict")(); +pyodide.runPython(`x = 1 + 1`, { globals: my_namespace }); +pyodide.runPython(`y = x ** x`, { globals: my_namespace }); +my_namespace.get("y"); // ==> 4 +``` + +You can also use this approach to inject variables from JavaScript into the +Python namespace, for example: + +```pyodide +let my_namespace = pyodide.toPy({ x: 2, y: [1, 2, 3] }); +pyodide.runPython( + ` + assert x == y[1] + z = x ** x + `, + { globals: my_namespace } +); +my_namespace.get("z"); // ==> 4 ``` ## How to detect that code is run with Pyodide? @@ -268,12 +284,12 @@ you say ``` loadPyodide({ - ..., stdin: stdin_func, stdout: stdout_func, stderr: stderr_func + stdin: stdin_func, stdout: stdout_func, stderr: stderr_func }); ``` then every time a line is written to `stdout` (resp. `stderr`), `stdout_func` -(resp `stderr_func`) will be called on the line. Everytime `stdin` is read, +(resp `stderr_func`) will be called on the line. Every time `stdin` is read, `stdin_func` will be called with zero arguments. It is expected to return a string which is interpreted as a line of text. @@ -303,7 +319,7 @@ with redirect_stdin(StringIO("\n".join(["eval", "asyncio.ensure_future", "functo it will print: ``` -Welcome to Python 3.9's help utility! +Welcome to Python 3.10's help utility! <...OMITTED LINES> Help on built-in function eval in module builtins: eval(source, globals=None, locals=None, /) diff --git a/docs/usage/index.md b/docs/usage/index.md index af15e14e77c..18358c519ca 100644 --- a/docs/usage/index.md +++ b/docs/usage/index.md @@ -12,14 +12,12 @@ Pyodide with {any}`loadPyodide ` specifying an index URL - + + Pyodide test page
Open your browser console to see Pyodide output + @@ -114,9 +112,7 @@ Create and save a test `index.html` page with the following contents: output.value = "Initializing...\n"; // init Pyodide async function main() { - let pyodide = await loadPyodide({ - indexURL: "https://cdn.jsdelivr.net/pyodide/v0.19.0/full/", - }); + let pyodide = await loadPyodide(); output.value += "Ready!\n"; return pyodide; } diff --git a/docs/usage/type-conversions.md b/docs/usage/type-conversions.md index 0c6d3e99ac9..67bc769a272 100644 --- a/docs/usage/type-conversions.md +++ b/docs/usage/type-conversions.md @@ -278,7 +278,7 @@ proxy.toJs({pyproxies}); // pyproxies contains the list of proxies created by `toJs`. We can destroy them // when we are done with them for(let px of pyproxies){ - px.destory(); + px.destroy(); } proxy.destroy(); ``` @@ -357,7 +357,7 @@ let result_js = result_py.toJs(); result_py.destroy(); ``` -If a function is indended to be used from JavaScript, you can use {any}`to_js ` on the return value. This prevents the return value from +If a function is intended to be used from JavaScript, you can use {any}`to_js ` on the return value. This prevents the return value from leaking without requiring the JavaScript code to explicitly destroy it. This is particularly important for callbacks. @@ -437,7 +437,7 @@ from pyodide import create_proxy from js import document def my_callback(): print("hi") -proxy = document.create_proxy(my_callback) +proxy = pyodide.create_proxy(my_callback) document.body.addEventListener("click", proxy) # ... # make sure to hold on to proxy @@ -498,7 +498,7 @@ JavaScript. The data inside the buffer can be accessed via the API copies the buffer into JavaScript, whereas the `getBuffer` method allows low level access to the WASM memory backing the buffer. The `getBuffer` API is more powerful but requires care to use correctly. For simple use cases the `toJs` API -should be prefered. +should be preferred. If the buffer is zero or one-dimensional, then `toJs` will in most cases convert it to a single `TypedArray`. However, in the case that the format of the buffer @@ -598,7 +598,7 @@ try { ## Importing Objects -It is possible to access objects in one languge from the global scope in the +It is possible to access objects in one language from the global scope in the other language. It is also possible to create custom namespaces and access objects on the custom namespaces. diff --git a/docs/usage/wasm-constraints.md b/docs/usage/wasm-constraints.md index 9a71a22ca4c..890797bb8bb 100644 --- a/docs/usage/wasm-constraints.md +++ b/docs/usage/wasm-constraints.md @@ -5,13 +5,13 @@ Most of the Python standard library is functional, except for the modules listed in the sections below. A large part of the CPython test suite passes except for tests skipped in -[`src/tests/python_tests.txt`](https://github.com/pyodide/pyodide/blob/main/src/tests/python_tests.txt) +[`src/tests/python_tests.yaml`](https://github.com/pyodide/pyodide/blob/main/src/tests/python_tests.yaml) or via [patches](https://github.com/pyodide/pyodide/tree/main/cpython/patches). ### Optional modules The following stdlib modules are included by default, however -they can be excluded with `loadPyodide({..., fullStdLib = false })`. +they can be excluded with `loadPyodide({ fullStdLib : false })`. Individual modules can then be loaded as necessary using {any}`pyodide.loadPackage`, diff --git a/docs/usage/webworker.md b/docs/usage/webworker.md index 23eaac05de8..c3ed281bb48 100644 --- a/docs/usage/webworker.md +++ b/docs/usage/webworker.md @@ -105,12 +105,10 @@ shown below: // Setup your project to serve `py-worker.js`. You should also serve // `pyodide.js`, and all its associated `.asm.js`, `.data`, `.json`, // and `.wasm` files as well: -importScripts("https://cdn.jsdelivr.net/pyodide/v0.19.0/full/pyodide.js"); +importScripts("https://cdn.jsdelivr.net/pyodide/dev/full/pyodide.js"); async function loadPyodideAndPackages() { - self.pyodide = await loadPyodide({ - indexURL: "https://cdn.jsdelivr.net/pyodide/v0.19.0/full/", - }); + self.pyodide = await loadPyodide(); await self.pyodide.loadPackage(["numpy", "pytz"]); } let pyodideReadyPromise = loadPyodideAndPackages(); @@ -145,7 +143,7 @@ You would just need to call `.postMessages()` with the right arguments as this API does. ```js -const pyodideWorker = new Worker("./build/webworker.js"); +const pyodideWorker = new Worker("./dist/webworker.js"); const callbacks = {}; diff --git a/emsdk/Makefile b/emsdk/Makefile index d0944b82202..b3f76e20b4e 100644 --- a/emsdk/Makefile +++ b/emsdk/Makefile @@ -7,7 +7,7 @@ emsdk/.complete: ../Makefile.envs $(wildcard patches/*.patch) if [ -d emsdk ]; then rm -rf emsdk; fi git clone --depth 1 https://github.com/emscripten-core/emsdk.git cd emsdk && ./emsdk install --build=Release $(PYODIDE_EMSCRIPTEN_VERSION) - cat patches/*.patch | patch -p1 + cd emsdk/upstream/emscripten/ && cat ../../../patches/*.patch | patch -p1 --verbose cd emsdk && ./emsdk install --build=Release $(PYODIDE_EMSCRIPTEN_VERSION) ccache-git-emscripten-64bit cd emsdk && ./emsdk activate --embedded --build=Release $(PYODIDE_EMSCRIPTEN_VERSION) touch emsdk/.complete diff --git a/emsdk/patches/0001-Throw-away-errors-in-minify_wasm_js.patch b/emsdk/patches/0001-Throw-away-errors-in-minify_wasm_js.patch index 95f95ca6e9f..15d8c0e4a35 100644 --- a/emsdk/patches/0001-Throw-away-errors-in-minify_wasm_js.patch +++ b/emsdk/patches/0001-Throw-away-errors-in-minify_wasm_js.patch @@ -1,17 +1,17 @@ -From 310269ed630ead73e89e6bfc15e54e4c90959c95 Mon Sep 17 00:00:00 2001 +From e83a295f8e1b8c48d4748d1811a4b22840f25e14 Mon Sep 17 00:00:00 2001 From: Hood Date: Thu, 24 Jun 2021 04:08:02 -0700 -Subject: [PATCH] Throw away errors in minify_wasm_js +Subject: [PATCH 1/4] Throw away errors in minify_wasm_js --- emcc.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) -diff --git a/emsdk/upstream/emscripten/emcc.py b/emsdk/upstream/emscripten/emcc.py -index 839f791b3..5653470dd 100755 ---- a/emsdk/upstream/emscripten/emcc.py -+++ b/emsdk/upstream/emscripten/emcc.py -@@ -2901,11 +2901,14 @@ def do_binaryen(target, options, wasm_target): +diff --git a/emcc.py b/emcc.py +index 78801d2d5..ada67e93f 100755 +--- a/emcc.py ++++ b/emcc.py +@@ -3130,11 +3130,14 @@ def phase_binaryen(target, options, wasm_target): # Closure can print out readable error messages (Closure will then # minify whitespace afterwards) save_intermediate_with_wasm('preclean', wasm_target) @@ -30,7 +30,7 @@ index 839f791b3..5653470dd 100755 + pass save_intermediate_with_wasm('postclean', wasm_target) - if shared.Settings.ASYNCIFY_LAZY_LOAD_CODE: + if settings.ASYNCIFY_LAZY_LOAD_CODE: -- -2.17.1 +2.25.1 diff --git a/emsdk/patches/fix-dup.patch b/emsdk/patches/0002-Fix-dup.patch similarity index 75% rename from emsdk/patches/fix-dup.patch rename to emsdk/patches/0002-Fix-dup.patch index 985b3614243..402ad12a3ea 100644 --- a/emsdk/patches/fix-dup.patch +++ b/emsdk/patches/0002-Fix-dup.patch @@ -1,14 +1,9 @@ -From d9181272105915622a3eb8940e08aaf6e00f385c Mon Sep 17 00:00:00 2001 +From 40956dee436737d9dd40e0b57c6e2ebd26569920 Mon Sep 17 00:00:00 2001 From: Hood Date: Wed, 8 Sep 2021 17:49:15 -0700 -Subject: [PATCH] Fix dup +Subject: [PATCH 2/4] Fix dup ---- - src/library_fs.js | 7 ++++++- - src/library_syscall.js | 12 ++++++------ - 2 files changed, 12 insertions(+), 7 deletions(-) - -This should fix two problems with the `dup` system calls: +This fixes two problems with the `dup` system calls: 1. `dup` expects that every file descriptor has a corresponding file (so pipes and (https://github.com/emscripten-core/emscripten/issues/14640) files that have been unlinked (https://github.com/emscripten-core/emscripten/issues/15012) cannot be duplicated ). 2. The dup'd file descriptor does not share flags and position with the original file desciptor. @@ -16,13 +11,16 @@ This should fix two problems with the `dup` system calls: This is a simplification of an upstream pull request that would fix this problem. https://github.com/emscripten-core/emscripten/pull/9396/files This patch is simpler than the upstream one but leaves NODERAWFS broken. +--- + src/library_fs.js | 7 ++++++- + src/library_syscall.js | 12 ++++++------ + 2 files changed, 12 insertions(+), 7 deletions(-) - -diff --git a/emsdk/upstream/emscripten/src/library_fs.js b/emsdk/upstream/emscripten/src/library_fs.js -index 4ca59469c..519fab972 100644 ---- a/emsdk/upstream/emscripten/src/library_fs.js -+++ b/emsdk/upstream/emscripten/src/library_fs.js -@@ -425,11 +425,16 @@ FS.staticInit();` + +diff --git a/src/library_fs.js b/src/library_fs.js +index 50b06c264..fc8d0193a 100644 +--- a/src/library_fs.js ++++ b/src/library_fs.js +@@ -429,11 +429,16 @@ FS.staticInit();` + }, isAppend: { get: function() { return (this.flags & {{{ cDefine('O_APPEND') }}}); } @@ -40,10 +38,10 @@ index 4ca59469c..519fab972 100644 for (var p in stream) { newStream[p] = stream[p]; } -diff --git a/emsdk/upstream/emscripten/src/library_syscall.js b/emsdk/upstream/emscripten/src/library_syscall.js -index 96d2ec0c3..0001624ec 100644 ---- a/emsdk/upstream/emscripten/src/library_syscall.js -+++ b/emsdk/upstream/emscripten/src/library_syscall.js +diff --git a/src/library_syscall.js b/src/library_syscall.js +index c704a0604..09721d025 100644 +--- a/src/library_syscall.js ++++ b/src/library_syscall.js @@ -137,10 +137,10 @@ var SyscallsLibrary = { } return 0; @@ -57,7 +55,7 @@ index 96d2ec0c3..0001624ec 100644 }, doReadv: function(stream, iov, iovcnt, offset) { var ret = 0; -@@ -380,7 +380,7 @@ var SyscallsLibrary = { +@@ -374,7 +374,7 @@ var SyscallsLibrary = { }, __sys_dup: function(fd) { var old = SYSCALLS.getStreamFromFD(fd); @@ -66,7 +64,7 @@ index 96d2ec0c3..0001624ec 100644 }, __sys_pipe__deps: ['$PIPEFS'], __sys_pipe: function(fdPtr) { -@@ -472,7 +472,7 @@ var SyscallsLibrary = { +@@ -461,7 +461,7 @@ var SyscallsLibrary = { __sys_dup2: function(oldfd, suggestFD) { var old = SYSCALLS.getStreamFromFD(oldfd); if (old.fd === suggestFD) return suggestFD; @@ -75,7 +73,7 @@ index 96d2ec0c3..0001624ec 100644 }, __sys_getppid__nothrow: true, __sys_getppid__proxy: false, -@@ -1167,7 +1167,7 @@ var SyscallsLibrary = { +@@ -1108,7 +1108,7 @@ var SyscallsLibrary = { return -{{{ cDefine('EINVAL') }}}; } var newStream; @@ -84,15 +82,15 @@ index 96d2ec0c3..0001624ec 100644 return newStream.fd; } case {{{ cDefine('F_GETFD') }}}: -@@ -1403,7 +1403,7 @@ var SyscallsLibrary = { +@@ -1330,7 +1330,7 @@ var SyscallsLibrary = { assert(!flags); #endif if (old.fd === suggestFD) return -{{{ cDefine('EINVAL') }}}; - return SYSCALLS.doDup(old.path, old.flags, suggestFD); + return SYSCALLS.doDup(old, suggestFD); }, - __sys_pipe2__nothrow: true, - __sys_pipe2__proxy: false, + + __sys_prlimit64: function(pid, resource, new_limit, old_limit) { -- -2.17.1 +2.25.1 diff --git a/emsdk/patches/0003-Fix-side-module-exception-handling.patch b/emsdk/patches/0003-Fix-side-module-exception-handling.patch new file mode 100644 index 00000000000..c00cd5c38c7 --- /dev/null +++ b/emsdk/patches/0003-Fix-side-module-exception-handling.patch @@ -0,0 +1,34 @@ +From 73b89ee1b5a57c65824baf91b547be32b69decbd Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Tue, 15 Feb 2022 23:27:03 -0500 +Subject: [PATCH 3/4] Fix side module exception handling + +See https://github.com/emscripten-core/emscripten/pull/16309 + +Apparently compiled C++ code tries to call a bunch of functions with names like +`__cxa_find_matching_catch_##` which all mean the same function. Emscripten +statically defines the set of these that are used in the main module, but it is +possible that a side module will need extra ones. This fixes the problem by updating +`resolveGlobalSymbol` so that every `__cxa_find_matching_catch_##` will resolve to +`__cxa_find_matching_catch`. +--- + src/library_dylink.js | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/library_dylink.js b/src/library_dylink.js +index 54a6f190c..1d1613eef 100644 +--- a/src/library_dylink.js ++++ b/src/library_dylink.js +@@ -32,6 +32,9 @@ var LibraryDylink = { + sym = createInvokeFunction(symName.split('_')[1]); + } + ++ if(!sym && symName.startsWith("__cxa_find_matching_catch")){ ++ sym = Module.___cxa_find_matching_catch; ++ } + return sym; + }, + +-- +2.25.1 + diff --git a/emsdk/patches/0004-Fix-lookupPath-when-applied-to-a-symlink-loop.patch b/emsdk/patches/0004-Fix-lookupPath-when-applied-to-a-symlink-loop.patch new file mode 100644 index 00000000000..d66f2d54c36 --- /dev/null +++ b/emsdk/patches/0004-Fix-lookupPath-when-applied-to-a-symlink-loop.patch @@ -0,0 +1,31 @@ +From 8de43377d0e72a4c5794f8494a06d81a9609090f Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Wed, 2 Mar 2022 13:44:14 -0800 +Subject: [PATCH 4/4] Fix lookupPath when applied to a symlink loop + +The following code leads to an infinite loop in lookupPath: + +FS.symlink("linkX/inside","/linkX"); +FS.lookupPath("/linkX", {follow:true}); + +This patch fixes it. +--- + src/library_fs.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/library_fs.js b/src/library_fs.js +index fc8d0193a..294361b09 100644 +--- a/src/library_fs.js ++++ b/src/library_fs.js +@@ -173,7 +173,7 @@ FS.staticInit();` + + var link = FS.readlink(current_path); + current_path = PATH_FS.resolve(PATH.dirname(current_path), link); + +- var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count }); ++ var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count + 1 }); + current = lookup.node; + + if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX). +-- +2.25.1 + diff --git a/emsdk/patches/fix_file_packager_node.patch b/emsdk/patches/fix_file_packager_node.patch deleted file mode 100644 index d60bd461f1d..00000000000 --- a/emsdk/patches/fix_file_packager_node.patch +++ /dev/null @@ -1,47 +0,0 @@ -commit fb4b3c3a955e73fe20f930fb73588c0ad7a446e5 -Author: Sam Clegg -Date: Thu Jun 3 09:42:51 2021 -0700 - - Add support for `--preload-file` in Node.js (#11785) - - Note: this is included in emscripten 2.O.24 - -diff --git a/emsdk/upstream/emscripten/tools/file_packager.py b/emsdk/upstream/emscripten/tools/file_packager.py -index cd70b4b7d..ace6d6b2c 100755 ---- a/emsdk/upstream/emscripten/tools/file_packager.py -+++ b/emsdk/upstream/emscripten/tools/file_packager.py -@@ -525,14 +525,12 @@ def main(): - remote_package_size = os.path.getsize(package_name) - remote_package_name = os.path.basename(package_name) - ret += r''' -- var PACKAGE_PATH; -+ var PACKAGE_PATH = ''; - if (typeof window === 'object') { - PACKAGE_PATH = window['encodeURIComponent'](window.location.pathname.toString().substring(0, window.location.pathname.toString().lastIndexOf('/')) + '/'); -- } else if (typeof location !== 'undefined') { -- // worker -+ } else if (typeof process === 'undefined' && typeof location !== 'undefined') { -+ // web worker - PACKAGE_PATH = encodeURIComponent(location.pathname.toString().substring(0, location.pathname.toString().lastIndexOf('/')) + '/'); -- } else { -- throw 'using preloaded data can only be done on a web page or in a web worker'; - } - var PACKAGE_NAME = '%s'; - var REMOTE_PACKAGE_BASE = '%s'; -@@ -716,6 +714,16 @@ def main(): - - ret += r''' - function fetchRemotePackage(packageName, packageSize, callback, errback) { -+ if (typeof process === 'object') { -+ require('fs').readFile(packageName, function(err, contents) { -+ if (err) { -+ errback(err); -+ } else { -+ callback(contents.buffer); -+ } -+ }); -+ return; -+ } - var xhr = new XMLHttpRequest(); - xhr.open('GET', packageName, true); - xhr.responseType = 'arraybuffer'; diff --git a/emsdk/patches/update-jpeg-url.patch b/emsdk/patches/update-jpeg-url.patch deleted file mode 100644 index 004a8eca07b..00000000000 --- a/emsdk/patches/update-jpeg-url.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 2b49ba503a0fd0be14e71ea831c06503b543ecb0 Mon Sep 17 00:00:00 2001 -From: Sam Clegg -Date: Mon, 12 Apr 2021 09:27:04 -0700 -Subject: [PATCH] Update upstream URL for libjpeg. NFC (#13869) - -The old URL seems to be generating `Forbidden!`. - -I wonder if we should start maintaining our own mirror to avoid -this kind of thing. ---- - tools/ports/libjpeg.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/emsdk/upstream/emscripten/tools/ports/libjpeg.py b/emsdk/upstream/emscripten/tools/ports/libjpeg.py -index f0deb7f4a7d..563ef7fd09f 100644 ---- a/emsdk/upstream/emscripten/tools/ports/libjpeg.py -+++ b/emsdk/upstream/emscripten/tools/ports/libjpeg.py -@@ -8,7 +8,7 @@ - import logging - - VERSION = '9c' --HASH = '2b581c60ae401a79bbbe748ff2deeda5acd50bfd2ea22e5926e36d34b9ebcffb6580b0ff48e972c1441583e30e21e1ea821ca0423f9c67ce08a31dffabdbe6b7' -+HASH = 'b2affe9a1688bd49fc033f4682c4a242d4ee612f1affaef532f5adcb4602efc4433c4a52a4b3d69e7440ff1f6413b1b041b419bc90efd6d697999961a9a6afb7' - - - def needed(settings): -@@ -16,7 +16,7 @@ def needed(settings): - - - def get(ports, settings, shared): -- ports.fetch_project('libjpeg', 'https://dl.bintray.com/homebrew/mirror/jpeg-9c.tar.gz', 'jpeg-9c', sha512hash=HASH) -+ ports.fetch_project('libjpeg', 'https://storage.googleapis.com/webassembly/emscripten-ports/jpegsrc.v9c.tar.gz', 'jpeg-9c', sha512hash=HASH) - - def create(final): - logging.info('building port: libjpeg') diff --git a/packages/CLAPACK/make.inc b/packages/CLAPACK/make.inc index 8ebf0001a6d..90ce6523e58 100644 --- a/packages/CLAPACK/make.inc +++ b/packages/CLAPACK/make.inc @@ -21,7 +21,7 @@ PLAT = _WA # and desired load options for your machine. # ####################################################### -# This is used to compile C libary +# This is used to compile C library #CC = gcc # inherit $CC from emmake # if no wrapping of the blas library is needed, uncomment next line #CC = gcc -DNO_BLAS_WRAP @@ -44,7 +44,7 @@ F2CCFLAGS = $(CFLAGS) # For gfortran compiler: SECOND and DSECND will use a call to the INTERNAL FUNCTION ETIME # TIMER = INT_ETIME # If your Fortran compiler does not provide etime (like Nag Fortran Compiler, etc...) -# SECOND and DSECND will use a call to the Fortran standard INTERNAL FUNCTION CPU_TIME +# SECOND and DSECND will use a call to the Fortran standard INTERNAL FUNCTION CPU_TIME TIMER = INT_CPU_TIME # If neither of this works...you can use the NONE value... In that case, SECOND and DSECND will always return 0 # TIMER = NONE diff --git a/packages/CLAPACK/meta.yaml b/packages/CLAPACK/meta.yaml index 619c5da8306..b85f4b83bb3 100644 --- a/packages/CLAPACK/meta.yaml +++ b/packages/CLAPACK/meta.yaml @@ -8,11 +8,12 @@ source: extract_dir: CLAPACK-3.2.1 patches: - patches/0001-add-missing-import.patch - - patches/0002-fix-arith-h.patch + - patches/0002-fix-arith.h.patch - patches/0003-lapack-install-make.patch - patches/0004-fix-f2clibs-build.patch - patches/0005-remove-redundant-symbols.patch - patches/0006-correct-return-types.patch + - patches/0007-Fix-xerbla-and-ilaenv.patch extras: - [make.inc, make.inc] @@ -32,6 +33,5 @@ build: sed -i 's/^ ld /^ $(LD)/' **/Makefile emmake make -j ${PYODIDE_JOBS:-3} blaslib lapacklib - mkdir -p install/lib - - emcc blas_WA.a lapack_WA.a F2CLIBS/libf2c.a -sSIDE_MODULE -o install/lib/clapack_all.so + mkdir -p dist/lib + emcc blas_WA.a lapack_WA.a F2CLIBS/libf2c.a -sSIDE_MODULE -o dist/lib/clapack_all.so diff --git a/packages/CLAPACK/patches/0001-add-missing-import.patch b/packages/CLAPACK/patches/0001-add-missing-import.patch index 830aac56249..9a78f11d5a5 100644 --- a/packages/CLAPACK/patches/0001-add-missing-import.patch +++ b/packages/CLAPACK/patches/0001-add-missing-import.patch @@ -1,7 +1,18 @@ +From c1c714f1217ab6fba5ae1ceb6d0c4a9469490093 Mon Sep 17 00:00:00 2001 +From: Michael Droettboom +Date: Fri, 18 Mar 2022 19:59:05 -0700 +Subject: [PATCH 1/7] add missing import + The original source files are missing some imports. gcc assumes implicit function declaration, so the code compiles fine, but emscripten sets -Werror=implicit-function-declaration so it is a hard error. +--- + BLAS/SRC/xerbla.c | 2 ++ + INSTALL/tstiee.c | 2 ++ + 2 files changed, 4 insertions(+) +diff --git a/BLAS/SRC/xerbla.c b/BLAS/SRC/xerbla.c +index 2d7baf5..da1d7fd 100644 --- a/BLAS/SRC/xerbla.c +++ b/BLAS/SRC/xerbla.c @@ -10,6 +10,8 @@ @@ -12,7 +23,9 @@ function declaration, so the code compiles fine, but emscripten sets + #include "f2c.h" #include "blaswrap.h" - + +diff --git a/INSTALL/tstiee.c b/INSTALL/tstiee.c +index 2b5426f..89a41b8 100644 --- a/INSTALL/tstiee.c +++ b/INSTALL/tstiee.c @@ -10,6 +10,8 @@ @@ -23,4 +36,7 @@ function declaration, so the code compiles fine, but emscripten sets + #include "f2c.h" #include "blaswrap.h" + #include "string.h" +-- +2.25.1 diff --git a/packages/CLAPACK/patches/0002-fix-arith-h.patch b/packages/CLAPACK/patches/0002-fix-arith.h.patch similarity index 57% rename from packages/CLAPACK/patches/0002-fix-arith-h.patch rename to packages/CLAPACK/patches/0002-fix-arith.h.patch index b3edefe2cc3..9ec143ed842 100644 --- a/packages/CLAPACK/patches/0002-fix-arith-h.patch +++ b/packages/CLAPACK/patches/0002-fix-arith.h.patch @@ -1,10 +1,20 @@ +From 01990867ee7a641078505efba367a413a97f7802 Mon Sep 17 00:00:00 2001 +From: Michael Droettboom +Date: Fri, 18 Mar 2022 19:59:25 -0700 +Subject: [PATCH 2/7] fix arith.h + arith.h is a file generated at build time by compiling and running a C program. Since we use emscripten to build throughout, the C program becomes a wasm file and we call it differently. +--- + F2CLIBS/libf2c/Makefile | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) +diff --git a/F2CLIBS/libf2c/Makefile b/F2CLIBS/libf2c/Makefile +index 0a3ed0d..a473ed8 100644 --- a/F2CLIBS/libf2c/Makefile +++ b/F2CLIBS/libf2c/Makefile -@@ -184,8 +184,8 @@ +@@ -173,8 +173,8 @@ xwsne.o: fmt.h arith.h: arithchk.c $(CC) $(CFLAGS) -DNO_FPINIT arithchk.c -lm ||\ $(CC) -DNO_LONG_LONG $(CFLAGS) -DNO_FPINIT arithchk.c -lm @@ -15,3 +25,6 @@ and we call it differently. check: xsum Notice README abort_.c arithchk.c backspac.c c_abs.c c_cos.c \ +-- +2.25.1 + diff --git a/packages/CLAPACK/patches/0003-lapack-install-make.patch b/packages/CLAPACK/patches/0003-lapack-install-make.patch index d4bf0efc8b2..e3f1a103530 100644 --- a/packages/CLAPACK/patches/0003-lapack-install-make.patch +++ b/packages/CLAPACK/patches/0003-lapack-install-make.patch @@ -1,18 +1,31 @@ +From 7b263aed921afdf92525975befd268b0871b8dc4 Mon Sep 17 00:00:00 2001 +From: Michael Droettboom +Date: Fri, 18 Mar 2022 19:59:46 -0700 +Subject: [PATCH 3/7] lapack install make + lapack_install requires f2clib (see INSTALL/Makefile). Don't run LAPACK_INSTALL tests since they won't actually run on x86 +--- + Makefile | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) +diff --git a/Makefile b/Makefile +index 36c6a0d..4e43b79 100644 --- a/Makefile +++ b/Makefile -@@ -15,9 +15,8 @@ +@@ -15,9 +15,8 @@ lib: f2clib blaslib variants lapacklib tmglib clean: cleanlib cleantesting cleanblas_testing - + -lapack_install: - ( cd INSTALL; $(MAKE); ./testlsame; ./testslamch; \ - ./testdlamch; ./testsecond; ./testdsecnd; ./testversion ) +lapack_install: f2clib + ( cd INSTALL; $(MAKE) ) - + blaslib: ( cd BLAS/SRC; $(MAKE) ) +-- +2.25.1 + diff --git a/packages/CLAPACK/patches/0004-fix-f2clibs-build.patch b/packages/CLAPACK/patches/0004-fix-f2clibs-build.patch index 304e247dfbc..1d1b1e5467a 100644 --- a/packages/CLAPACK/patches/0004-fix-f2clibs-build.patch +++ b/packages/CLAPACK/patches/0004-fix-f2clibs-build.patch @@ -1,13 +1,23 @@ +From d88133066f9f6312145c1186116fdb6446d3f7a5 Mon Sep 17 00:00:00 2001 +From: Michael Droettboom +Date: Fri, 18 Mar 2022 20:00:51 -0700 +Subject: [PATCH 4/7] fix f2clibs build + emscripten produces LLVM bitcode here, not genuine object files, so it doesn't make sense to strip symbols. (It would also fail because emcc uses the file extension to determine what kind of object to output, and .xxx is not a recognized extension; this is the error message you would receive if you try to run the commands) +--- + F2CLIBS/libf2c/Makefile | 2 -- + 1 file changed, 2 deletions(-) +diff --git a/F2CLIBS/libf2c/Makefile b/F2CLIBS/libf2c/Makefile +index a473ed8..e51d826 100644 --- a/F2CLIBS/libf2c/Makefile +++ b/F2CLIBS/libf2c/Makefile -@@ -20,8 +20,6 @@ +@@ -19,8 +19,6 @@ include ../../make.inc # compile, then strip unnecessary symbols .c.o: $(CC) -c -DSkip_f2c_Undefs $(CFLAGS) $*.c @@ -16,3 +26,6 @@ message you would receive if you try to run the commands) ## Under Solaris (and other systems that do not understand ld -x), ## omit -x in the ld line above. ## If your system does not have the ld command, comment out +-- +2.25.1 + diff --git a/packages/CLAPACK/patches/0005-remove-redundant-symbols.patch b/packages/CLAPACK/patches/0005-remove-redundant-symbols.patch index a8e404b530d..25ccf8f4fdc 100644 --- a/packages/CLAPACK/patches/0005-remove-redundant-symbols.patch +++ b/packages/CLAPACK/patches/0005-remove-redundant-symbols.patch @@ -1,14 +1,23 @@ +From 78ff0cec961d9eb4e94193995fe151e1ecdae9df Mon Sep 17 00:00:00 2001 +From: Roman Yurchak +Date: Fri, 18 Mar 2022 20:01:39 -0700 +Subject: [PATCH 5/7] remove redundant symbols + Remove a few symbols from LAPACK that are redundantly defined with BLAS or are ported in scipy. It wouldn't be an issue if we were linking dynamically, but because of static linking otherwise we get errors at link time about symbols defined twice. - Roman Yurchak (https://github.com/pyodide/pyodide/pull/238) -diff --git a/Makefile.orig b/Makefile -index 223f4da..69ac630 100644 +--- + SRC/Makefile | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/SRC/Makefile b/SRC/Makefile +index 5f1eb22..32e669b 100644 --- a/SRC/Makefile +++ b/SRC/Makefile -@@ -50,9 +50,9 @@ include $(TOPDIR)/make.inc +@@ -48,9 +48,9 @@ include ../make.inc # ####################################################################### @@ -20,39 +29,6 @@ index 223f4da..69ac630 100644 ALLXAUX = -@@ -104,7 +104,7 @@ SLASRC = \ - sggrqf.o sggsvd.o sggsvp.o sgtcon.o sgtrfs.o sgtsv.o \ - sgtsvx.o sgttrf.o sgttrs.o sgtts2.o shgeqz.o \ - shsein.o shseqr.o slabrd.o slacon.o slacn2.o \ -- slaein.o slaexc.o slag2.o slags2.o slagtm.o slagv2.o slahqr.o \ -+ slaein.o slaexc.o slag2.o slags2.o slagtm.o slagv2.o \ - slahrd.o slahr2.o slaic1.o slaln2.o slals0.o slalsa.o slalsd.o \ - slangb.o slange.o slangt.o slanhs.o slansb.o slansp.o \ - slansy.o slantb.o slantp.o slantr.o slanv2.o \ -@@ -176,7 +176,7 @@ CLASRC = \ - clacgv.o clacon.o clacn2.o clacp2.o clacpy.o clacrm.o clacrt.o cladiv.o \ - claed0.o claed7.o claed8.o \ - claein.o claesy.o claev2.o clags2.o clagtm.o \ -- clahef.o clahqr.o \ -+ clahef.o \ - clahrd.o clahr2.o claic1.o clals0.o clalsa.o clalsd.o clangb.o clange.o clangt.o \ - clanhb.o clanhe.o \ - clanhp.o clanhs.o clanht.o clansb.o clansp.o clansy.o clantb.o \ -@@ -236,7 +236,7 @@ DLASRC = \ - dggrqf.o dggsvd.o dggsvp.o dgtcon.o dgtrfs.o dgtsv.o \ - dgtsvx.o dgttrf.o dgttrs.o dgtts2.o dhgeqz.o \ - dhsein.o dhseqr.o dlabrd.o dlacon.o dlacn2.o \ -- dlaein.o dlaexc.o dlag2.o dlags2.o dlagtm.o dlagv2.o dlahqr.o \ -+ dlaein.o dlaexc.o dlag2.o dlags2.o dlagtm.o dlagv2.o \ - dlahrd.o dlahr2.o dlaic1.o dlaln2.o dlals0.o dlalsa.o dlalsd.o \ - dlangb.o dlange.o dlangt.o dlanhs.o dlansb.o dlansp.o \ - dlansy.o dlantb.o dlantp.o dlantr.o dlanv2.o \ -@@ -310,7 +310,7 @@ ZLASRC = \ - zlacgv.o zlacon.o zlacn2.o zlacp2.o zlacpy.o zlacrm.o zlacrt.o zladiv.o \ - zlaed0.o zlaed7.o zlaed8.o \ - zlaein.o zlaesy.o zlaev2.o zlags2.o zlagtm.o \ -- zlahef.o zlahqr.o \ -+ zlahef.o \ - zlahrd.o zlahr2.o zlaic1.o zlals0.o zlalsa.o zlalsd.o zlangb.o zlange.o \ - zlangt.o zlanhb.o \ - zlanhe.o \ +-- +2.25.1 + diff --git a/packages/CLAPACK/patches/0006-correct-return-types.patch b/packages/CLAPACK/patches/0006-correct-return-types.patch index 40450916be8..726664c4c1d 100644 --- a/packages/CLAPACK/patches/0006-correct-return-types.patch +++ b/packages/CLAPACK/patches/0006-correct-return-types.patch @@ -1,5 +1,17 @@ +From 572a3e20ba040b4f29bbef97a9db6658c10077d3 Mon Sep 17 00:00:00 2001 +From: Joe Marshall +Date: Fri, 18 Mar 2022 20:02:42 -0700 +Subject: [PATCH 6/7] correct return types + Make return types to fortran subroutines consistently be int. Some functions are defined within clapack as variously void and int return. Normal C compilers don't care, but emscripten is strict about return values. +--- + F2CLIBS/libf2c/ef1asc_.c | 2 +- + F2CLIBS/libf2c/f2ch.add | 4 ++-- + F2CLIBS/libf2c/s_cat.c | 6 +++--- + F2CLIBS/libf2c/s_copy.c | 4 ++-- + 4 files changed, 8 insertions(+), 8 deletions(-) + diff --git a/F2CLIBS/libf2c/ef1asc_.c b/F2CLIBS/libf2c/ef1asc_.c index 70be0bc..b2a82a2 100644 --- a/F2CLIBS/libf2c/ef1asc_.c @@ -64,3 +76,6 @@ index 9dacfc7..8d8963f 100644 #endif { register char *aend, *bend; +-- +2.25.1 + diff --git a/packages/CLAPACK/patches/0007-Fix-xerbla-and-ilaenv.patch b/packages/CLAPACK/patches/0007-Fix-xerbla-and-ilaenv.patch new file mode 100644 index 00000000000..47863c73cdd --- /dev/null +++ b/packages/CLAPACK/patches/0007-Fix-xerbla-and-ilaenv.patch @@ -0,0 +1,52 @@ +From c0d2a69ac4e4b63c4f28fadb896581164a2e29d3 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Fri, 18 Mar 2022 22:07:00 -0700 +Subject: [PATCH 7/7] Fix xerbla and ilaenv + +These functions take actual C strings as arguments. It is annoying +to patch fortran to pass C strings, so this adds a wrapper that +'converts' from a Fortran string. +--- + BLAS/SRC/xerbla.c | 6 +++++- + SRC/ilaenv.c | 6 ++++++ + 2 files changed, 11 insertions(+), 1 deletion(-) + +diff --git a/BLAS/SRC/xerbla.c b/BLAS/SRC/xerbla.c +index da1d7fd..a6b5ca4 100644 +--- a/BLAS/SRC/xerbla.c ++++ b/BLAS/SRC/xerbla.c +@@ -68,7 +68,7 @@ static integer c__1 = 1; + /* .. */ + /* .. Executable Statements .. */ + +- printf("** On entry to %6s, parameter number %2i had an illegal value\n", ++ printf("** On entry to %s, parameter number %2i had an illegal value\n", + srname, *info); + + +@@ -76,3 +76,7 @@ static integer c__1 = 1; + + return 0; + } /* xerbla_ */ ++ ++int xerblaf2py_(char *srname, integer *info, integer srnamelen){ ++ return xerbla_(srname, info); ++} +diff --git a/SRC/ilaenv.c b/SRC/ilaenv.c +index 9565433..b8d28a8 100644 +--- a/SRC/ilaenv.c ++++ b/SRC/ilaenv.c +@@ -652,3 +652,9 @@ L160: + /* End of ILAENV */ + + } /* ilaenv_ */ ++ ++integer ilaenvf2py_(integer *ispec, char *name__, char *opts, integer *n1, ++ integer *n2, integer *n3, integer *n4, int name_len, int opts_len) ++{ ++ return ilaenv_(ispec, name__, opts, n1, n2, n3, n4); ++} +\ No newline at end of file +-- +2.25.1 + diff --git a/packages/Jinja2/meta.yaml b/packages/Jinja2/meta.yaml index e5d33eea920..bd59fcd7a0f 100644 --- a/packages/Jinja2/meta.yaml +++ b/packages/Jinja2/meta.yaml @@ -1,12 +1,12 @@ package: name: Jinja2 - version: 3.0.3 + version: 3.1.1 requirements: run: - MarkupSafe source: - sha256: 611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7 - url: https://files.pythonhosted.org/packages/91/a5/429efc6246119e1e3fbf562c00187d04e83e54619249eb732bb423efa6c6/Jinja2-3.0.3.tar.gz + sha256: 539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119 + url: https://files.pythonhosted.org/packages/76/02/af4045156cde8feeefa30cb1c051e10321d4960c418bd52346a497feb302/Jinja2-3.1.1-py3-none-any.whl test: imports: - jinja2 diff --git a/packages/Makefile b/packages/Makefile index 1d5cc4d533a..e88a5df3c04 100644 --- a/packages/Makefile +++ b/packages/Makefile @@ -1,3 +1,5 @@ +.PHONY=pyodide-build + export PYODIDE_ROOT=$(abspath ..) include ../Makefile.envs @@ -6,22 +8,20 @@ else ONLY_PACKAGES=--only "$(PYODIDE_PACKAGES)" endif -all: .artifacts/bin/pyodide-build - mkdir -p build-logs - echo PYTHONPATH="$(HOSTINSTALLDIR)/lib/python:$(PYODIDE_ROOT)/pyodide-build/" - PYTHONPATH="$(HOSTINSTALLDIR)/lib/python:$(PYODIDE_ROOT)/pyodide-build/" python -m pyodide_build buildall . ../build \ - $(ONLY_PACKAGES) --n-jobs $${PYODIDE_JOBS:-4} \ - --log-dir=build-logs - -.artifacts/bin/pyodide-build: ../pyodide-build/pyodide_build/** +all: pyodide-build mkdir -p $(HOSTINSTALLDIR) - $(HOSTPYTHON) -m pip install -e ../pyodide-build --no-deps --prefix $(HOSTINSTALLDIR) + PYODIDE_ROOT=$(PYODIDE_ROOT) python -m pyodide_build buildall . $(PYODIDE_ROOT)/dist \ + $(ONLY_PACKAGES) --n-jobs $${PYODIDE_JOBS:-4} \ + --log-dir=./build-logs + +pyodide-build: ../pyodide-build/pyodide_build/** + $(HOSTPYTHON) -m pip install -e ../pyodide-build update-all: for pkg in $$(find . -maxdepth 1 ! -name ".*" -type d -exec basename {} \; | tail -n +2); do \ - python -m pyodide_build mkpkg "$${pkg}" --update; \ + PYODIDE_ROOT=$(PYODIDE_ROOT) python -m pyodide_build mkpkg "$${pkg}" --update; \ done clean: - rm -rf ./*/build ./*/build.log + rm -rf ./*/build ./*/build.log ./*/dist rm -rf ./.artifacts diff --git a/packages/MarkupSafe/meta.yaml b/packages/MarkupSafe/meta.yaml index 0922fed8398..65066b38f26 100644 --- a/packages/MarkupSafe/meta.yaml +++ b/packages/MarkupSafe/meta.yaml @@ -1,9 +1,9 @@ package: name: MarkupSafe - version: 2.0.1 + version: 2.1.1 source: - sha256: 594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a - url: https://files.pythonhosted.org/packages/bf/10/ff66fea6d1788c458663a84d88787bae15d45daa16f6b3ef33322a51fc7e/MarkupSafe-2.0.1.tar.gz + sha256: 7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b + url: https://files.pythonhosted.org/packages/1d/97/2288fe498044284f39ab8950703e88abbac2abbdf65524d576157af70556/MarkupSafe-2.1.1.tar.gz test: imports: - markupsafe diff --git a/packages/Pillow/meta.yaml b/packages/Pillow/meta.yaml new file mode 100644 index 00000000000..7d972b551bc --- /dev/null +++ b/packages/Pillow/meta.yaml @@ -0,0 +1,27 @@ +package: + name: Pillow + version: 9.1.0 +source: + sha256: f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97 + url: https://files.pythonhosted.org/packages/4b/83/090146d7871d90a2643d469c319c1d014e41b315ab5cf0f8b4b6a764ef31/Pillow-9.1.0.tar.gz + patches: + - patches/0001-Enable-image-formats.patch + extras: + - - src/setup.cfg + - ./setup.cfg +build: + cflags: | + -s USE_ZLIB=1 + -s USE_LIBJPEG=1 + -s USE_FREETYPE=1 + -s SIDE_MODULE=1 + -I$(PYODIDE_ROOT)/packages/libwebp/build/libwebp-1.2.2/build/lib/include + ldflags: | + -ljpeg + -L$(PYODIDE_ROOT)/packages/libwebp/build/libwebp-1.2.2/build/lib/lib/ +requirements: + run: + - libwebp +test: + imports: + - PIL diff --git a/packages/pillow/patches/setitup.patch b/packages/Pillow/patches/0001-Enable-image-formats.patch similarity index 60% rename from packages/pillow/patches/setitup.patch rename to packages/Pillow/patches/0001-Enable-image-formats.patch index 32fd6f9e99b..7a7da44770b 100644 --- a/packages/pillow/patches/setitup.patch +++ b/packages/Pillow/patches/0001-Enable-image-formats.patch @@ -1,6 +1,17 @@ ---- a/setup.py 2020-10-22 16:27:16.000000000 +0200 -+++ b/setup.py 2020-12-15 08:43:30.000000000 +0100 -@@ -557,7 +557,9 @@ +From 0c4d089d29d7b12de69b1725a65ad1d3d3fa82ec Mon Sep 17 00:00:00 2001 +From: ryanking13 +Date: Tue, 19 Apr 2022 07:18:31 +0000 +Subject: [PATCH] Enable image formats + +--- + setup.py | 19 ++++++++++++++----- + 1 file changed, 14 insertions(+), 5 deletions(-) + +diff --git a/setup.py b/setup.py +index 3468b260..da2a2067 100755 +--- a/setup.py ++++ b/setup.py +@@ -630,7 +630,9 @@ class pil_build_ext(build_ext): if feature.want("zlib"): _dbg("Looking for zlib") @@ -11,7 +22,7 @@ if _find_library_file(self, "z"): feature.zlib = "z" elif sys.platform == "win32" and _find_library_file(self, "zlib"): -@@ -565,7 +567,9 @@ +@@ -638,7 +640,9 @@ class pil_build_ext(build_ext): if feature.want("jpeg"): _dbg("Looking for jpeg") @@ -22,7 +33,7 @@ if _find_library_file(self, "jpeg"): feature.jpeg = "jpeg" elif sys.platform == "win32" and _find_library_file(self, "libjpeg"): -@@ -631,7 +635,9 @@ +@@ -704,7 +708,9 @@ class pil_build_ext(build_ext): if feature.want("freetype"): _dbg("Looking for freetype") @@ -33,7 +44,18 @@ # look for freetype2 include files freetype_version = 0 for subdir in self.compiler.include_dirs: -@@ -704,7 +710,8 @@ +@@ -769,7 +775,9 @@ class pil_build_ext(build_ext): + + if feature.want("webp"): + _dbg("Looking for webp") +- if _find_include_file(self, "webp/encode.h") and _find_include_file( ++ if "PYODIDE" in os.environ: ++ feature.webp = "webp" ++ elif _find_include_file(self, "webp/encode.h") and _find_include_file( + self, "webp/decode.h" + ): + # In Google's precompiled zip it is call "libwebp": +@@ -810,7 +818,8 @@ class pil_build_ext(build_ext): libs = self.add_imaging_libs.split() defs = [] if feature.jpeg: @@ -43,3 +65,6 @@ defs.append(("HAVE_LIBJPEG", None)) if feature.jpeg2000: libs.append(feature.jpeg2000) +-- +2.35.1 + diff --git a/packages/Pillow/src/setup.cfg b/packages/Pillow/src/setup.cfg new file mode 100644 index 00000000000..7f97664cec2 --- /dev/null +++ b/packages/Pillow/src/setup.cfg @@ -0,0 +1,12 @@ +[build_ext] +disable_platform_guessing=True +enable_zlib=True +enable_jpeg=True +enable_freetype=True +enable_webp=True +disable_lcms=True +disable_tiff=True +disable_xcb=True +disable_webpmux=True +disable_jpeg2000=True +disable_imagequant=True diff --git a/packages/Pillow/test_pillow.py b/packages/Pillow/test_pillow.py new file mode 100644 index 00000000000..c15b89f85e4 --- /dev/null +++ b/packages/Pillow/test_pillow.py @@ -0,0 +1,51 @@ +from pyodide_build.testing import run_in_pyodide + + +@run_in_pyodide( + packages=["Pillow"], +) +def test_pillow(): + import io + + from PIL import Image, ImageDraw, ImageOps + + img = Image.new("RGB", (4, 4), color=(0, 0, 0)) + ctx = ImageDraw.Draw(img) + ctx.line([0, 0, 3, 0, 3, 3, 0, 3, 0, 0], (255, 0, 0), 1) + img.putpixel((1, 1), (0, 255, 0)) + img.putpixel((2, 2), (0, 0, 255)) + img = ImageOps.flip(img) + + img_bytes = b"\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00" + assert img.tobytes() == img_bytes + + extensions = { + "jpeg": b"\xff\xd8\xff\xe0", + "png": b"\x89PNG", + "webp": b"RIFF", + } + + for ext, signature in extensions.items(): + with io.BytesIO() as imgfile: + img.save(imgfile, format=ext.upper()) + _img = Image.open(imgfile) + assert _img + assert ( + imgfile.getvalue()[:4] == signature + ), f"Wrong signature on image format: {ext}" + + +@run_in_pyodide( + packages=["Pillow"], +) +def test_jpeg_modes(): + from PIL import Image + + rgb = Image.new("RGB", (4, 4)) + rgb.save("rgb.jpg") + + gray = Image.new("L", (4, 4)) + gray.save("gray.jpg") + + bw = Image.new("1", (4, 4)) + bw.save("bw.jpg") diff --git a/packages/Pygments/meta.yaml b/packages/Pygments/meta.yaml index 58adcfc5189..76fcfd90f22 100644 --- a/packages/Pygments/meta.yaml +++ b/packages/Pygments/meta.yaml @@ -1,9 +1,9 @@ package: name: Pygments - version: 2.9.0 + version: 2.11.2 source: - sha256: a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f - url: https://files.pythonhosted.org/packages/ba/6e/7a7c13c21d8a4a7f82ccbfe257a045890d4dbf18c023f985f565f97393e3/Pygments-2.9.0.tar.gz + sha256: 44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65 + url: https://files.pythonhosted.org/packages/1d/17/ed4d2df187995561b28f1073df24137cb750e12f9879d291cc8ab67c65d2/Pygments-2.11.2-py3-none-any.whl test: imports: - pygments diff --git a/packages/astropy/meta.yaml b/packages/astropy/meta.yaml index d8f02e2b25d..ac579a9c9f1 100644 --- a/packages/astropy/meta.yaml +++ b/packages/astropy/meta.yaml @@ -1,12 +1,10 @@ package: name: astropy - version: "5.0" + version: 5.0.4 source: - url: https://files.pythonhosted.org/packages/82/f0/fc3f6f250bb9d5e5aaa890469fb27c4edee66cf0e2ea4f8b0b287260c6f8/astropy-5.0.tar.gz - sha256: 70203e151e13292586a817b4069ce1aad4643567aff38b1d191c173bc54f3927 + url: https://files.pythonhosted.org/packages/a3/d6/b36be0232fa0761f4f3484eb5907224feebf0b359fde2218b01e782649c6/astropy-5.0.4.tar.gz + sha256: 001184f1a9c3f526a363883ce28efb9cbf076df3d151ca3e131509a248f0dfb9 build: - script: | - pip install extension-helpers # The test module is imported from the top level `__init__.py` # so it cannot be unvendored unvendor-tests: false @@ -16,6 +14,26 @@ requirements: - packaging - numpy - pyerfa + - pyyaml test: imports: - astropy + - astropy.config + - astropy.constants + - astropy.convolution + - astropy.coordinates + - astropy.cosmology + - astropy.extern + - astropy.io + - astropy.modeling + - astropy.nddata + - astropy.samp + - astropy.stats + - astropy.table + - astropy.time + - astropy.timeseries + - astropy.uncertainty + - astropy.units + - astropy.utils + - astropy.visualization + - astropy.wcs diff --git a/packages/atomicwrites/meta.yaml b/packages/atomicwrites/meta.yaml index 36a08ec6a8a..d6f5bab8823 100644 --- a/packages/atomicwrites/meta.yaml +++ b/packages/atomicwrites/meta.yaml @@ -2,8 +2,8 @@ package: name: atomicwrites version: 1.4.0 source: - sha256: ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a - url: https://files.pythonhosted.org/packages/55/8d/74a75635f2c3c914ab5b3850112fd4b0c8039975ecb320e4449aa363ba54/atomicwrites-1.4.0.tar.gz + sha256: 6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197 + url: https://files.pythonhosted.org/packages/2c/a0/da5f49008ec6e9a658dbf5d7310a4debd397bce0b4db03cf8a410066bb87/atomicwrites-1.4.0-py2.py3-none-any.whl test: imports: - atomicwrites diff --git a/packages/attrs/meta.yaml b/packages/attrs/meta.yaml index 799cb5f7edc..c895e1e6a34 100644 --- a/packages/attrs/meta.yaml +++ b/packages/attrs/meta.yaml @@ -2,8 +2,8 @@ package: name: attrs version: 21.4.0 source: - sha256: 626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd - url: https://files.pythonhosted.org/packages/d7/77/ebb15fc26d0f815839ecd897b919ed6d85c050feeb83e100e020df9153d2/attrs-21.4.0.tar.gz + sha256: 2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4 + url: https://files.pythonhosted.org/packages/be/be/7abce643bfdf8ca01c48afa2ddf8308c2308b0c3b239a44e57d020afa0ef/attrs-21.4.0-py2.py3-none-any.whl requirements: run: - six diff --git a/packages/autograd/meta.yaml b/packages/autograd/meta.yaml index dc7ed9011fb..8e4c552873a 100644 --- a/packages/autograd/meta.yaml +++ b/packages/autograd/meta.yaml @@ -1,9 +1,9 @@ package: name: autograd - version: "1.3" + version: "1.4" source: - sha256: a15d147577e10de037de3740ca93bfa3b5a7cdfbc34cfb9105429c3580a33ec4 - url: https://files.pythonhosted.org/packages/23/12/b58522dc2cbbd7ab939c7b8e5542c441c9a06a8eccb00b3ecac04a739896/autograd-1.3.tar.gz + sha256: 383de0f537ef2e38b85ff9692593b0cfae8958c9b3bd451b52c255fd9171ffce + url: https://files.pythonhosted.org/packages/04/6d/63beeb8c1f1964aa7d84e59657c7f5b85f6ae009ed80f51e0d3ec78e0f97/autograd-1.4.tar.gz test: imports: - autograd diff --git a/packages/beautifulsoup4/meta.yaml b/packages/beautifulsoup4/meta.yaml index 7acb49534c7..ca1c56c3ec6 100644 --- a/packages/beautifulsoup4/meta.yaml +++ b/packages/beautifulsoup4/meta.yaml @@ -1,12 +1,12 @@ package: name: beautifulsoup4 - version: 4.9.3 + version: 4.11.0 requirements: run: - soupsieve source: - sha256: 84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25 - url: https://files.pythonhosted.org/packages/6b/c3/d31704ae558dcca862e4ee8e8388f357af6c9d9acb0cad4ba0fbbd350d9a/beautifulsoup4-4.9.3.tar.gz + sha256: 577b9e1c36d2ada780d807c5622e889d43172466658c2eb239e97296965cdddb + url: https://files.pythonhosted.org/packages/85/f6/fcd36aaa9697fa4d84a21c3e9d121fc2cb000ac952d08c606409e671319b/beautifulsoup4-4.11.0-py3-none-any.whl test: imports: - bs4 diff --git a/packages/bleach/meta.yaml b/packages/bleach/meta.yaml index 1d2a29a88f2..50a4dc205ac 100644 --- a/packages/bleach/meta.yaml +++ b/packages/bleach/meta.yaml @@ -1,14 +1,14 @@ package: name: bleach - version: 4.1.0 + version: 5.0.0 requirements: run: - webencodings - packaging - six source: - sha256: 0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da - url: https://files.pythonhosted.org/packages/6a/a3/217842324374fd3fb33db0eb4c2909ccf3ecc5a94f458088ac68581f8314/bleach-4.1.0.tar.gz + sha256: 08a1fe86d253b5c88c92cc3d810fd8048a16d15762e1e5b74d502256e5926aa1 + url: https://files.pythonhosted.org/packages/35/8b/bad37525e7ef0669f013059affb3bbcd9311a24d63eda96723339ee0aab7/bleach-5.0.0-py3-none-any.whl test: imports: - bleach diff --git a/packages/bokeh/meta.yaml b/packages/bokeh/meta.yaml index 408f3d72fcd..0f13c7711c1 100644 --- a/packages/bokeh/meta.yaml +++ b/packages/bokeh/meta.yaml @@ -6,14 +6,14 @@ requirements: - distutils - numpy - Jinja2 - - pillow + - Pillow - python-dateutil - six - typing-extensions - pyyaml source: - sha256: f0a4b53364ed3b7eb936c5cb1a4f4132369e394c7ae0a8ef420459410958033d - url: https://files.pythonhosted.org/packages/8d/45/723c63620c66e953046d351c599ea14fae6f4d3a5c3af0c952846197b019/bokeh-2.4.2.tar.gz + sha256: 2a842d717feeee802e668054277c09054b6f1561557a16dddaf5f7c452f2728c + url: https://files.pythonhosted.org/packages/87/6c/c2b2911555d34c9fa24ad0f41cf42ba6887e279a521bca0cb3d091d77c17/bokeh-2.4.2-py3-none-any.whl test: imports: - bokeh diff --git a/packages/boost-histogram/meta.yaml b/packages/boost-histogram/meta.yaml new file mode 100644 index 00000000000..acdf2d886d6 --- /dev/null +++ b/packages/boost-histogram/meta.yaml @@ -0,0 +1,20 @@ +package: + name: boost-histogram + version: 1.3.1 +source: + url: https://files.pythonhosted.org/packages/fa/2d/79cad54cc2579836d048809e6f29fc294f9b4f7c4eedee6fda4425daaf30/boost_histogram-1.3.1.tar.gz + sha256: 31cd396656f3a37834e07d304cdb84d9906bc2172626a3d92fe577d08bcf410f +requirements: + run: + - numpy # runtime only +build: + cxxflags: -fexceptions + ldflags: -fexceptions +test: + imports: + - boost_histogram +about: + home: https://github.com/scikit-hep/boost-histogram + PyPI: https://pypi.org/project/boost-histogram + summary: The Boost::Histogram Python wrapper. + license: BSD-3-Clause diff --git a/packages/boost-histogram/test_boost_histogram.py b/packages/boost-histogram/test_boost_histogram.py new file mode 100644 index 00000000000..2605194d5bd --- /dev/null +++ b/packages/boost-histogram/test_boost_histogram.py @@ -0,0 +1,43 @@ +from pyodide_build.testing import run_in_pyodide + + +@run_in_pyodide(packages=["boost-histogram"]) +def test_boost_histogram(): + import unittest + + import boost_histogram as bh + + h = bh.Histogram(bh.axis.Integer(0, 10)) + h.fill([1, 1, 1, 14]) + assert h[bh.underflow] == 0 + assert h[bh.loc(0)] == 0 + assert h[bh.loc(1)] == 3 + assert h[bh.overflow] == 1 + + assert h.sum() == 3 + assert h.sum(flow=True) == 4 + assert h[sum] == 4 + + h = bh.Histogram(bh.axis.Regular(10, 0, 10), bh.axis.Boolean()) + assert len(h.axes[0]) == 10 + assert len(h.axes[1]) == 2 + + h.fill([0.5, 0.5, 3.5], [True, False, True]) + assert h[sum, bh.loc(True)] == 2 + assert h[sum, bh.loc(False)] == 1 + assert h[0, sum] == 2 + assert h[0, bh.loc(True)] == 1 + + h = bh.Histogram(bh.axis.StrCategory([], growth=True)) + h.fill("fear leads to anger anger leads to hate hate leads to suffering".split()) + + assert h[bh.loc("fear")] == 1 + assert h[bh.loc("anger")] == 2 + assert h[bh.loc("hate")] == 2 + assert h[bh.loc("to")] == 3 + + # Test exception handling + mean = bh.accumulators.Mean() + + with unittest.TestCase().assertRaises(KeyError): + mean["invalid"] diff --git a/packages/certifi/meta.yaml b/packages/certifi/meta.yaml new file mode 100644 index 00000000000..2f8bd95bb1c --- /dev/null +++ b/packages/certifi/meta.yaml @@ -0,0 +1,7 @@ +package: + name: certifi + version: 2022.5.18 + +source: + sha256: 6ae10321df3e464305a46e997da41ea56c1d311fb9ff1dd4e04d6f14653ec63a + url: https://files.pythonhosted.org/packages/c5/63/a8e4b0c24e8538cec5579edbb2df4ed1f980d5f186878905317139d83b61/certifi-2022.5.18.tar.gz diff --git a/packages/cffi/meta.yaml b/packages/cffi/meta.yaml index 1912462ee4f..b3a68d6aae6 100644 --- a/packages/cffi/meta.yaml +++ b/packages/cffi/meta.yaml @@ -1,14 +1,17 @@ package: name: cffi - version: 1.14.6 + version: 1.15.0 requirements: run: - pycparser source: - url: https://files.pythonhosted.org/packages/2e/92/87bb61538d7e60da8a7ec247dc048f7671afe17016cd0008b3b710012804/cffi-1.14.6.tar.gz - sha256: c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd + url: https://files.pythonhosted.org/packages/00/9e/92de7e1217ccc3d5f352ba21e52398372525765b2e0c4530e6eb2ba9282a/cffi-1.15.0.tar.gz + sha256: 920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954 patches: - patches/libffi-config.patch +build: + script: | + pip install -t $HOSTSITEPACKAGES cffi==$PKG_VERSION test: imports: - cffi diff --git a/packages/cffi_example/test_cffi_example.py b/packages/cffi_example/test_cffi_example.py index 6809383ae68..987ff1be509 100644 --- a/packages/cffi_example/test_cffi_example.py +++ b/packages/cffi_example/test_cffi_example.py @@ -1,6 +1,6 @@ import pytest -from pyodide_build.testing import run_in_pyodide +from pyodide_build.testing import run_in_pyodide CHROME_FAIL_v90_MSG = ( "Doesn't work in chrome v89 or v90, I think because of " diff --git a/packages/charset-normalizer/meta.yaml b/packages/charset-normalizer/meta.yaml new file mode 100644 index 00000000000..79519b1787e --- /dev/null +++ b/packages/charset-normalizer/meta.yaml @@ -0,0 +1,7 @@ +package: + name: charset-normalizer + version: 2.0.7 + +source: + sha256: e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0 + url: https://files.pythonhosted.org/packages/9f/c5/334c019f92c26e59637bb42bd14a190428874b2b2de75a355da394cf16c1/charset-normalizer-2.0.7.tar.gz diff --git a/packages/cloudpickle/meta.yaml b/packages/cloudpickle/meta.yaml index 1d0250f1305..0166b7efb43 100644 --- a/packages/cloudpickle/meta.yaml +++ b/packages/cloudpickle/meta.yaml @@ -2,8 +2,8 @@ package: name: cloudpickle version: 2.0.0 source: - sha256: 5cd02f3b417a783ba84a4ec3e290ff7929009fe51f6405423cfccfadd43ba4a4 - url: https://files.pythonhosted.org/packages/25/c4/29b4f944e8709c4cddfdb93f0278f57ba097ed46845d9bdf57178db31c64/cloudpickle-2.0.0.tar.gz + sha256: 6b2df9741d06f43839a3275c4e6632f7df6487a1f181f5f46a052d3c917c3d11 + url: https://files.pythonhosted.org/packages/07/3c/bf72ebd3e78eb1ef773f4f0650ecdc29c6454aeafe9c08f6da3f227dd2bc/cloudpickle-2.0.0-py3-none-any.whl test: imports: - cloudpickle diff --git a/packages/cloudpickle/test_cloudpickle.py b/packages/cloudpickle/test_cloudpickle.py index af988fc2a33..e60a16050ba 100644 --- a/packages/cloudpickle/test_cloudpickle.py +++ b/packages/cloudpickle/test_cloudpickle.py @@ -5,7 +5,7 @@ def test_cloudpickle(): import cloudpickle - squared = lambda x: x ** 2 + squared = lambda x: x**2 pickled_lambda = cloudpickle.dumps(squared) import pickle diff --git a/packages/cmyt/meta.yaml b/packages/cmyt/meta.yaml new file mode 100644 index 00000000000..a7328082afc --- /dev/null +++ b/packages/cmyt/meta.yaml @@ -0,0 +1,20 @@ +package: + name: cmyt + version: 1.0.4 +source: + url: https://files.pythonhosted.org/packages/2e/b1/270a024f723fda150c1fcd441d0764760593db4115d09a462bce20ed5c13/cmyt-1.0.4-py3-none-any.whl + sha256: aed700aa071fa216c548e3dbc0090c69d7b0dd6d528345f5f286cb0357f063cf +requirements: + run: + - colorspacious + - matplotlib + - more-itertools + - numpy +test: + imports: + - cmyt +about: + home: https://github.com/yt-project/yt + PyPI: https://pypi.org/project/cmyt + summary: A collection of Matplotlib colormaps from the yt project + license: BSD 3-Clause diff --git a/packages/colorspacious/meta.yaml b/packages/colorspacious/meta.yaml new file mode 100644 index 00000000000..4fee7a0b9f2 --- /dev/null +++ b/packages/colorspacious/meta.yaml @@ -0,0 +1,19 @@ +package: + name: colorspacious + version: 1.1.2 +source: + url: https://files.pythonhosted.org/packages/ab/a1/318b9aeca7b9856410ededa4f52d6f82174d1a41e64bdd70d951e532675a/colorspacious-1.1.2-py2.py3-none-any.whl + sha256: c78befa603cea5dccb332464e7dd29e96469eebf6cd5133029153d1e69e3fd6f +requirements: + run: + - numpy +test: + imports: + - colorspacious +about: + home: https://github.com/njsmith/colorspacious + PyPI: https://pypi.org/project/colorspacious + summary: + A powerful, accurate, and easy-to-use Python library for doing colorspace + conversions + license: MIT diff --git a/packages/cpp-exceptions-test/meta.yaml b/packages/cpp-exceptions-test/meta.yaml new file mode 100644 index 00000000000..b2035ce1c61 --- /dev/null +++ b/packages/cpp-exceptions-test/meta.yaml @@ -0,0 +1,14 @@ +package: + name: cpp-exceptions-test + version: "0.1" + _tag: pyodide.test +source: + path: src +build: + sharedlibrary: true + script: | + em++ -c throw.cpp -o throw.o -fPIC -fexceptions -O2 + em++ -c catch.cpp -o catch.o -fPIC -fexceptions -O2 + mkdir dist + em++ throw.o -sSIDE_MODULE -o dist/cpp-exceptions-test-throw.so -fexceptions + em++ catch.o -sSIDE_MODULE -o dist/cpp-exceptions-test-catch.so -fexceptions diff --git a/packages/cpp-exceptions-test/src/catch.cpp b/packages/cpp-exceptions-test/src/catch.cpp new file mode 100644 index 00000000000..4624fe92e50 --- /dev/null +++ b/packages/cpp-exceptions-test/src/catch.cpp @@ -0,0 +1,26 @@ +#include +using namespace std; +#include +#include + +extern "C" char* +throw_exc(int x); + +extern "C" char* +catch_exc(int x) +{ + char* msg; + try { + char* res = throw_exc(x); + asprintf(&msg, "result was: %s", res); + } catch (int param) { + asprintf(&msg, "caught int %d", param); + } catch (char param) { + asprintf(&msg, "caught char %d", param); + } catch (runtime_error& e) { + asprintf(&msg, "caught runtime_error %s", e.what()); + } catch (...) { + asprintf(&msg, "caught ????"); + } + return msg; +} diff --git a/packages/cpp-exceptions-test/src/throw.cpp b/packages/cpp-exceptions-test/src/throw.cpp new file mode 100644 index 00000000000..bfbf1dddd4b --- /dev/null +++ b/packages/cpp-exceptions-test/src/throw.cpp @@ -0,0 +1,24 @@ +#include +#include +using namespace std; + +class myexception : public exception +{ + virtual const char* what() const throw() { return "My exception happened"; } +} myex; + +extern "C" char* +throw_exc(int x) +{ + if (x == 1) { + throw 1000; + } else if (x == 2) { + throw 'c'; + } else if (x == 3) { + throw runtime_error("abc"); + } else if (x == 4) { + throw myex; + } else { + throw "abc"; + } +} diff --git a/packages/cpp-exceptions-test/test_cpp_exceptions.py b/packages/cpp-exceptions-test/test_cpp_exceptions.py new file mode 100644 index 00000000000..3a27e205adc --- /dev/null +++ b/packages/cpp-exceptions-test/test_cpp_exceptions.py @@ -0,0 +1,64 @@ +def test_uncaught_cpp_exceptions(selenium): + assert ( + selenium.run_js( + """ + await pyodide.loadPackage("cpp-exceptions-test"); + const Tests = pyodide._api.tests; + const sitePackages = pyodide.runPython("import site; site.getsitepackages()[0]") + const idx = pyodide._module.LDSO.loadedLibNames[sitePackages + "/cpp-exceptions-test-throw.so"] + const throwlib = pyodide._module.LDSO.loadedLibs[idx].module; + """ + """\ + function t(x){ + try { + throwlib.throw_exc(x); + } catch(e){ + let errString = Tests.convertCppException(e).toString(); + errString = errString.replace(/[0-9]+/, "xxx"); + return errString; + } + } + return [t(1), t(2), t(3), t(4), t(5)]; + """ + ) + == [ + "CppException int: The exception is an object of type int at address xxx " + "which does not inherit from std::exception", + "CppException char: The exception is an object of type char at address xxx " + "which does not inherit from std::exception", + "CppException std::runtime_error: abc", + "CppException myexception: My exception happened", + "CppException char const*: The exception is an object of type char const* at " + "address xxx which does not inherit from std::exception", + ] + ) + + +def test_cpp_exception_catching(selenium): + assert ( + selenium.run_js( + """ + await pyodide.loadPackage("cpp-exceptions-test"); + const Module = pyodide._module; + const sitePackages = pyodide.runPython("import site; site.getsitepackages()[0]") + const idx = Module.LDSO.loadedLibNames[sitePackages + "/cpp-exceptions-test-catch.so"] + const catchlib = Module.LDSO.loadedLibs[idx].module; + """ + """\ + function t(x){ + const ptr = catchlib.catch_exc(x); + const res = Module.UTF8ToString(ptr); + Module._free(ptr); + return res; + } + + return [t(1), t(2), t(3), t(5)]; + """ + ) + == [ + "caught int 1000", + "caught char 99", + "caught runtime_error abc", + "caught ????", + ] + ) diff --git a/packages/cryptography/meta.yaml b/packages/cryptography/meta.yaml new file mode 100644 index 00000000000..464bc2bed40 --- /dev/null +++ b/packages/cryptography/meta.yaml @@ -0,0 +1,27 @@ +package: + name: cryptography + version: 3.4.8 + +source: + url: https://files.pythonhosted.org/packages/cc/98/8a258ab4787e6f835d350639792527d2eb7946ff9fc0caca9c3f4cf5dcfe/cryptography-3.4.8.tar.gz + sha256: 94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c +build: + cflags: | + -I$(OPEN_SSL_ROOT)/include/ + -Wno-implicit-function-declaration + ldflags: | + -L$(OPEN_SSL_ROOT)/dist/ + script: | + export CRYPTOGRAPHY_DONT_BUILD_RUST=1 +requirements: + run: + - openssl + - six + - cffi +test: + imports: + - cryptography + - cryptography.fernet + - cryptography.hazmat + - cryptography.utils + - cryptography.x509 diff --git a/packages/cryptography/test_cryptography.py b/packages/cryptography/test_cryptography.py new file mode 100644 index 00000000000..5415bcb1225 --- /dev/null +++ b/packages/cryptography/test_cryptography.py @@ -0,0 +1,224 @@ +from hypothesis import HealthCheck, given, settings +from hypothesis.strategies import binary, integers + +from conftest import selenium_context_manager +from pyodide_build.testing import run_in_pyodide + + +@run_in_pyodide(packages=["cryptography"]) +def test_cryptography(): + import base64 + + from cryptography.fernet import Fernet, MultiFernet + + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32)) + f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32)) + f = MultiFernet([f1, f2]) + + assert f1.decrypt(f.encrypt(b"abc")) == b"abc" + + +@run_in_pyodide(packages=["cryptography", "pytest"]) +def test_der_reader_basic(): + import pytest + from cryptography.hazmat._der import DERReader + + reader = DERReader(b"123456789") + assert reader.read_byte() == ord(b"1") + assert reader.read_bytes(1).tobytes() == b"2" + assert reader.read_bytes(4).tobytes() == b"3456" + + with pytest.raises(ValueError): + reader.read_bytes(4) + + assert reader.read_bytes(3).tobytes() == b"789" + + # The input is now empty. + with pytest.raises(ValueError): + reader.read_bytes(1) + with pytest.raises(ValueError): + reader.read_byte() + + +@run_in_pyodide(packages=["cryptography", "pytest"]) +def test_der(): + import pytest + from cryptography.hazmat._der import ( + INTEGER, + NULL, + OCTET_STRING, + SEQUENCE, + DERReader, + encode_der, + encode_der_integer, + ) + + # This input is the following structure, using + # https://github.com/google/der-ascii + # + # SEQUENCE { + # SEQUENCE { + # NULL {} + # INTEGER { 42 } + # OCTET_STRING { "hello" } + # } + # } + der = b"\x30\x0e\x30\x0c\x05\x00\x02\x01\x2a\x04\x05\x68\x65\x6c\x6c\x6f" + reader = DERReader(der) + with pytest.raises(ValueError): + reader.check_empty() + + with pytest.raises(ValueError): + with reader: + pass + + with pytest.raises(ZeroDivisionError): + with DERReader(der): + raise ZeroDivisionError + + # Parse the outer element. + outer = reader.read_element(SEQUENCE) # type: ignore[unreachable] + reader.check_empty() + assert outer.data.tobytes() == der[2:] + + # Parse the outer element with read_any_element. + reader = DERReader(der) + tag, outer2 = reader.read_any_element() + reader.check_empty() + assert tag == SEQUENCE + assert outer2.data.tobytes() == der[2:] + + # Parse the outer element with read_single_element. + outer3 = DERReader(der).read_single_element(SEQUENCE) + assert outer3.data.tobytes() == der[2:] + + # read_single_element rejects trailing data. + with pytest.raises(ValueError): + DERReader(der + der).read_single_element(SEQUENCE) + + # Continue parsing the structure. + inner = outer.read_element(SEQUENCE) + outer.check_empty() + + # Parsing a missing optional element should work. + assert inner.read_optional_element(INTEGER) is None + + null = inner.read_element(NULL) + null.check_empty() + + # Parsing a present optional element should work. + integer = inner.read_optional_element(INTEGER) + assert integer.as_integer() == 42 + + octet_string = inner.read_element(OCTET_STRING) + assert octet_string.data.tobytes() == b"hello" + + # Parsing a missing optional element should work when the input is empty. + inner.check_empty() + assert inner.read_optional_element(INTEGER) is None + + # Re-encode the same structure. + der2 = encode_der( + SEQUENCE, + encode_der( + SEQUENCE, + encode_der(NULL), + encode_der(INTEGER, encode_der_integer(42)), + encode_der(OCTET_STRING, b"hello"), + ), + ) + assert der2 == der + + +@run_in_pyodide(packages=["cryptography"]) +def test_der_lengths(): + + from cryptography.hazmat._der import OCTET_STRING, DERReader, encode_der + + for [length, header] in [ + # Single-byte lengths. + (0, b"\x04\x00"), + (1, b"\x04\x01"), + (2, b"\x04\x02"), + (127, b"\x04\x7f"), + # Long-form lengths. + (128, b"\x04\x81\x80"), + (129, b"\x04\x81\x81"), + (255, b"\x04\x81\xff"), + (0x100, b"\x04\x82\x01\x00"), + (0x101, b"\x04\x82\x01\x01"), + (0xFFFF, b"\x04\x82\xff\xff"), + (0x10000, b"\x04\x83\x01\x00\x00"), + ]: + body = length * b"a" + der = header + body + + reader = DERReader(der) + element = reader.read_element(OCTET_STRING) + reader.check_empty() + assert element.data.tobytes() == body + + assert encode_der(OCTET_STRING, body) == der + + +@settings(suppress_health_check=[HealthCheck.too_slow], deadline=None) +@given(data=binary()) +def test_fernet(selenium_module_scope, data): + sbytes = list(data) + with selenium_context_manager(selenium_module_scope) as selenium: + selenium.load_package("cryptography") + selenium.run( + f""" + from cryptography.fernet import Fernet + data = bytes({sbytes}) + f = Fernet(Fernet.generate_key()) + ct = f.encrypt(data) + assert f.decrypt(ct) == data + """ + ) + + +@settings(suppress_health_check=[HealthCheck.too_slow], deadline=None) +@given(block_size=integers(min_value=1, max_value=255), data=binary()) +def test_pkcs7(selenium_module_scope, block_size, data): + sbytes = list(data) + with selenium_context_manager(selenium_module_scope) as selenium: + selenium.load_package("cryptography") + selenium.run( + f""" + from cryptography.hazmat.primitives.padding import ANSIX923, PKCS7 + block_size = {block_size} + data = bytes({sbytes}) + # Generate in [1, 31] so we can easily get block_size in bits by + # multiplying by 8. + p = PKCS7(block_size=block_size * 8) + padder = p.padder() + unpadder = p.unpadder() + + padded = padder.update(data) + padder.finalize() + + assert unpadder.update(padded) + unpadder.finalize() == data + """ + ) + + +@settings(suppress_health_check=[HealthCheck.too_slow], deadline=None) +@given(block_size=integers(min_value=1, max_value=255), data=binary()) +def test_ansix923(selenium_module_scope, block_size, data): + sbytes = list(data) + with selenium_context_manager(selenium_module_scope) as selenium: + selenium.load_package("cryptography") + selenium.run( + f""" + from cryptography.hazmat.primitives.padding import ANSIX923, PKCS7 + block_size = {block_size} + data = bytes({sbytes}) + a = ANSIX923(block_size=block_size * 8) + padder = a.padder() + unpadder = a.unpadder() + + padded = padder.update(data) + padder.finalize() + + assert unpadder.update(padded) + unpadder.finalize() == data + """ + ) diff --git a/packages/cssselect/meta.yaml b/packages/cssselect/meta.yaml index 5e6f5725403..ff1bdef38a7 100644 --- a/packages/cssselect/meta.yaml +++ b/packages/cssselect/meta.yaml @@ -2,8 +2,8 @@ package: name: cssselect version: 1.1.0 source: - sha256: f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc - url: https://files.pythonhosted.org/packages/70/54/37630f6eb2c214cdee2ae56b7287394c8aa2f3bafb8b4eb8c3791aae7a14/cssselect-1.1.0.tar.gz + sha256: f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf + url: https://files.pythonhosted.org/packages/3b/d4/3b5c17f00cce85b9a1e6f91096e1cc8e8ede2e1be8e96b87ce1ed09e92c5/cssselect-1.1.0-py2.py3-none-any.whl test: imports: - cssselect diff --git a/packages/cycler/meta.yaml b/packages/cycler/meta.yaml index 79659d72607..de97156833f 100644 --- a/packages/cycler/meta.yaml +++ b/packages/cycler/meta.yaml @@ -2,8 +2,8 @@ package: name: cycler version: 0.11.0 source: - url: https://files.pythonhosted.org/packages/34/45/a7caaacbfc2fa60bee42effc4bcc7d7c6dbe9c349500e04f65a861c15eb9/cycler-0.11.0.tar.gz - sha256: 9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f + url: https://files.pythonhosted.org/packages/5c/f9/695d6bedebd747e5eb0fe8fad57b72fdf25411273a39791cde838d5a8f51/cycler-0.11.0-py3-none-any.whl + sha256: 3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3 test: imports: - cycler diff --git a/packages/decorator/meta.yaml b/packages/decorator/meta.yaml index 567e1c9bd0f..eccbce92735 100644 --- a/packages/decorator/meta.yaml +++ b/packages/decorator/meta.yaml @@ -2,8 +2,13 @@ package: name: decorator version: 5.1.1 source: - sha256: 637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 - url: https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz + url: https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl + sha256: b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186 test: imports: - decorator +about: + home: https://github.com/micheles/decorator + PyPI: https://pypi.org/project/decorator + summary: Decorators for Humans + license: new BSD License diff --git a/packages/distlib/meta.yaml b/packages/distlib/meta.yaml index 20700149d2f..1f2b1da36bc 100644 --- a/packages/distlib/meta.yaml +++ b/packages/distlib/meta.yaml @@ -1,11 +1,13 @@ package: name: distlib - version: 0.3.1 + version: 0.3.4 source: - sha256: edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1 - url: https://files.pythonhosted.org/packages/2f/83/1eba07997b8ba58d92b3e51445d5bf36f9fba9cb8166bcae99b9c3464841/distlib-0.3.1.zip + sha256: 6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b + url: https://files.pythonhosted.org/packages/ac/a3/8ee4f54d5f12e16eeeda6b7df3dfdbda24e6cc572c86ff959a4ce110391b/distlib-0.3.4-py2.py3-none-any.whl build: - script: "find . -name *.exe -delete" + post: | + find build/distlib-0.3.4/dist -name '*.exe' -delete + test: imports: - distlib diff --git a/packages/docutils/meta.yaml b/packages/docutils/meta.yaml index 2cf050a93e1..3aa98534233 100644 --- a/packages/docutils/meta.yaml +++ b/packages/docutils/meta.yaml @@ -2,8 +2,8 @@ package: name: docutils version: 0.18.1 source: - sha256: 679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06 - url: https://files.pythonhosted.org/packages/57/b1/b880503681ea1b64df05106fc7e3c4e3801736cf63deffc6fa7fc5404cf5/docutils-0.18.1.tar.gz + sha256: 23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c + url: https://files.pythonhosted.org/packages/8d/14/69b4bad34e3f250afe29a854da03acb6747711f3df06c359fa053fae4e76/docutils-0.18.1-py2.py3-none-any.whl test: imports: - docutils diff --git a/packages/ffmpeg/meta.yaml b/packages/ffmpeg/meta.yaml new file mode 100644 index 00000000000..6ebe4f23de1 --- /dev/null +++ b/packages/ffmpeg/meta.yaml @@ -0,0 +1,25 @@ +package: + name: ffmpeg + version: "4.4.1" + +source: + url: https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n4.4.1.tar.gz + sha256: 82b43cc67296bcd01a59ae6b327cdb50121d3a9e35f41a30de1edd71bb4a6666 + extract_dir: FFmpeg-n4.4.1 +build: + library: true + script: | + emconfigure ./configure \ + --extra-cflags="-fPIC" \ + --disable-x86asm \ + --disable-inline-asm \ + --disable-doc \ + --disable-stripping \ + --disable-programs \ + --disable-pthreads \ + --nm="$PYODIDE_ROOT/emsdk/emsdk/upstream/bin/llvm-nm -g" \ + --ar=emar --cc=emcc --cxx=em++ --objcc=emcc --dep-cc=emcc --ranlib=emranlib \ + --prefix=./lib + + emmake make -j${PYODIDE_JOBS:-3} + emmake make install diff --git a/packages/fonttools/meta.yaml b/packages/fonttools/meta.yaml new file mode 100644 index 00000000000..21d83d64d8a --- /dev/null +++ b/packages/fonttools/meta.yaml @@ -0,0 +1,14 @@ +package: + name: fonttools + version: 4.32.0 +source: + url: https://files.pythonhosted.org/packages/bc/83/43991c6f0dfb395cc9ccf5c19fd51fc6068cb3919cee4b78eddd4b16efd1/fonttools-4.32.0-py3-none-any.whl + sha256: b038d1a0dee0079de7ade57071e2e2aced6e35bd697de244ac62938b2b1628c1 +test: + imports: + - fontTools +about: + home: http://github.com/fonttools/fonttools + PyPI: https://pypi.org/project/fontTools + summary: Tools to manipulate font files + license: MIT diff --git a/packages/fpcast-test/fpcast-test/fpcast-test.c b/packages/fpcast-test/fpcast-test/fpcast-test.c index 99f1e790588..807042b69c5 100644 --- a/packages/fpcast-test/fpcast-test/fpcast-test.c +++ b/packages/fpcast-test/fpcast-test/fpcast-test.c @@ -1,69 +1,78 @@ #define PY_SSIZE_T_CLEAN #include - -static PyObject *zero(void){ - Py_RETURN_NONE; +static PyObject* +zero(void) +{ + Py_RETURN_NONE; } -static PyObject *one(PyObject *self){ - Py_RETURN_NONE; +static PyObject* +one(PyObject* self) +{ + Py_RETURN_NONE; } -static PyObject *two(PyObject *self, PyObject *args){ - Py_RETURN_NONE; +static PyObject* +two(PyObject* self, PyObject* args) +{ + Py_RETURN_NONE; } -static PyObject *three(PyObject *self, PyObject *args, PyObject *kwargs){ - Py_RETURN_NONE; +static PyObject* +three(PyObject* self, PyObject* args, PyObject* kwargs) +{ + Py_RETURN_NONE; } -static int set_two(PyObject* self, PyObject* value){ - return 0; +static int +set_two(PyObject* self, PyObject* value) +{ + return 0; } // These two structs are the same but it's important that they have to be // duplicated here or else we miss test coverage. static PyMethodDef Test_Functions[] = { - {"noargs0",(PyCFunction)zero, METH_NOARGS}, - {"noargs1", (PyCFunction)one, METH_NOARGS}, - {"noargs2", (PyCFunction)two, METH_NOARGS}, - {"noargs3", (PyCFunction)three, METH_NOARGS}, - - {"varargs0",(PyCFunction)zero, METH_VARARGS}, - {"varargs1", (PyCFunction)one, METH_VARARGS}, - {"varargs2", (PyCFunction)two, METH_VARARGS}, - {"varargs3", (PyCFunction)three, METH_VARARGS}, - - {"kwargs0", (PyCFunction)zero, METH_VARARGS | METH_KEYWORDS}, - {"kwargs1", (PyCFunction)one, METH_VARARGS | METH_KEYWORDS}, - {"kwargs2", (PyCFunction)two, METH_VARARGS | METH_KEYWORDS}, - {"kwargs3", (PyCFunction)three, METH_VARARGS | METH_KEYWORDS}, - {NULL, NULL, 0, NULL} + { "noargs0", (PyCFunction)zero, METH_NOARGS }, + { "noargs1", (PyCFunction)one, METH_NOARGS }, + { "noargs2", (PyCFunction)two, METH_NOARGS }, + { "noargs3", (PyCFunction)three, METH_NOARGS }, + + { "varargs0", (PyCFunction)zero, METH_VARARGS }, + { "varargs1", (PyCFunction)one, METH_VARARGS }, + { "varargs2", (PyCFunction)two, METH_VARARGS }, + { "varargs3", (PyCFunction)three, METH_VARARGS }, + + { "kwargs0", (PyCFunction)zero, METH_VARARGS | METH_KEYWORDS }, + { "kwargs1", (PyCFunction)one, METH_VARARGS | METH_KEYWORDS }, + { "kwargs2", (PyCFunction)two, METH_VARARGS | METH_KEYWORDS }, + { "kwargs3", (PyCFunction)three, METH_VARARGS | METH_KEYWORDS }, + { NULL, NULL, 0, NULL } }; static PyMethodDef Test_Methods[] = { - {"noargs0",(PyCFunction)zero, METH_NOARGS}, - {"noargs1", (PyCFunction)one, METH_NOARGS}, - {"noargs2", (PyCFunction)two, METH_NOARGS}, - {"noargs3", (PyCFunction)three, METH_NOARGS}, - - {"varargs0",(PyCFunction)zero, METH_VARARGS}, - {"varargs1", (PyCFunction)one, METH_VARARGS}, - {"varargs2", (PyCFunction)two, METH_VARARGS}, - {"varargs3", (PyCFunction)three, METH_VARARGS}, - - {"kwargs0", (PyCFunction)zero, METH_VARARGS | METH_KEYWORDS}, - {"kwargs1", (PyCFunction)one, METH_VARARGS | METH_KEYWORDS}, - {"kwargs2", (PyCFunction)two, METH_VARARGS | METH_KEYWORDS}, - {"kwargs3", (PyCFunction)three, METH_VARARGS | METH_KEYWORDS}, - {NULL, NULL, 0, NULL} + { "noargs0", (PyCFunction)zero, METH_NOARGS }, + { "noargs1", (PyCFunction)one, METH_NOARGS }, + { "noargs2", (PyCFunction)two, METH_NOARGS }, + { "noargs3", (PyCFunction)three, METH_NOARGS }, + + { "varargs0", (PyCFunction)zero, METH_VARARGS }, + { "varargs1", (PyCFunction)one, METH_VARARGS }, + { "varargs2", (PyCFunction)two, METH_VARARGS }, + { "varargs3", (PyCFunction)three, METH_VARARGS }, + + { "kwargs0", (PyCFunction)zero, METH_VARARGS | METH_KEYWORDS }, + { "kwargs1", (PyCFunction)one, METH_VARARGS | METH_KEYWORDS }, + { "kwargs2", (PyCFunction)two, METH_VARARGS | METH_KEYWORDS }, + { "kwargs3", (PyCFunction)three, METH_VARARGS | METH_KEYWORDS }, + { NULL, NULL, 0, NULL } }; static PyGetSetDef Test_GetSet[] = { - { "getset0", .get = (getter)zero }, - { "getset1", .get = (getter)one, .set = (setter)set_two }, - { NULL } + { "getset0", .get = (getter)zero }, + { "getset1", .get = (getter)one, .set = (setter)set_two }, + { NULL } }; static PyTypeObject TestType = { @@ -113,22 +122,22 @@ static PyTypeObject Callable3 = { }; static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "fpcast_test", /* name of module */ - "Tests for the fpcast handling", /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module, - or -1 if the module keeps state in global variables. */ - Test_Methods + PyModuleDef_HEAD_INIT, + "fpcast_test", /* name of module */ + "Tests for the fpcast handling", /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + Test_Methods }; - -PyMODINIT_FUNC PyInit_fpcast_test(void) +PyMODINIT_FUNC +PyInit_fpcast_test(void) { - PyObject* module_object = PyModule_Create(&module); - PyModule_AddType(module_object, &TestType); - PyModule_AddType(module_object, &Callable0); - PyModule_AddType(module_object, &Callable1); - PyModule_AddType(module_object, &Callable2); - PyModule_AddType(module_object, &Callable3); - return module_object; + PyObject* module_object = PyModule_Create(&module); + PyModule_AddType(module_object, &TestType); + PyModule_AddType(module_object, &Callable0); + PyModule_AddType(module_object, &Callable1); + PyModule_AddType(module_object, &Callable2); + PyModule_AddType(module_object, &Callable3); + return module_object; } diff --git a/packages/fpcast-test/fpcast-test/setup.py b/packages/fpcast-test/fpcast-test/setup.py index 1e75c6d1edb..537d10aabcf 100644 --- a/packages/fpcast-test/fpcast-test/setup.py +++ b/packages/fpcast-test/fpcast-test/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, Extension +from setuptools import Extension, setup setup( name="fpcast-test", diff --git a/packages/fpcast-test/meta.yaml b/packages/fpcast-test/meta.yaml index c580db420f2..040a4300a0d 100644 --- a/packages/fpcast-test/meta.yaml +++ b/packages/fpcast-test/meta.yaml @@ -1,6 +1,7 @@ package: name: fpcast-test version: "0.1" + _tag: pyodide.test source: path: fpcast-test test: diff --git a/packages/h5py/meta.yaml b/packages/h5py/meta.yaml new file mode 100644 index 00000000000..407c47cefa5 --- /dev/null +++ b/packages/h5py/meta.yaml @@ -0,0 +1,28 @@ +package: + name: h5py + version: 3.6.0 + +source: + url: https://github.com/h5py/h5py/archive/3.6.0.tar.gz + sha256: 840fca0787bca8ad2729b9c20e06a16f219e7a24ccc59fdf5177a05ec8472b2d + patches: + - patches/configure.patch + - patches/build.patch + +requirements: + run: + - numpy + - libhdf5 + - pkgconfig + +test: + imports: + - h5py + +build: + script: | + export HDF5_MPI=OFF + export H5PY_SETUP_REQUIRES="0" + export HDF5_DIR=$PYODIDE_ROOT/packages/libhdf5/build/hdf5 + export PKG_CONFIG_PATH=${HDF5_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH} + export HDF5_VERSION=1.13.1 diff --git a/packages/h5py/patches/build.patch b/packages/h5py/patches/build.patch new file mode 100644 index 00000000000..d5932940b55 --- /dev/null +++ b/packages/h5py/patches/build.patch @@ -0,0 +1,13 @@ +diff --git a/setup_build.py b/setup_build.py +index 6b66789..448774a 100644 +--- a/setup_build.py ++++ b/setup_build.py +@@ -104,7 +104,7 @@ class h5py_build_ext(build_ext): + settings['include_dirs'] += [mpi4py.get_include()] + + # TODO: should this only be done on UNIX? +- if os.name != 'nt': ++ if False: + settings['runtime_library_dirs'] = settings['library_dirs'] + + def make_extension(module): diff --git a/packages/h5py/patches/configure.patch b/packages/h5py/patches/configure.patch new file mode 100644 index 00000000000..7afbadc0392 --- /dev/null +++ b/packages/h5py/patches/configure.patch @@ -0,0 +1,48 @@ +diff --git a/setup_configure.py b/setup_configure.py +index 16c355b..85a4f90 100644 +--- a/setup_configure.py ++++ b/setup_configure.py +@@ -183,7 +183,7 @@ class BuildConfig: + class HDF5LibWrapper: + + def __init__(self, libdirs): +- self._load_hdf5_lib(libdirs) ++ pass + + def _load_hdf5_lib(self, libdirs): + """ +@@ -245,23 +245,9 @@ class HDF5LibWrapper: + def autodetect_version(self): + """ + Detect the current version of HDF5, and return X.Y.Z version string. +- +- Raises an exception if anything goes wrong. + """ +- import ctypes +- from ctypes import byref +- +- major = ctypes.c_uint() +- minor = ctypes.c_uint() +- release = ctypes.c_uint() +- +- try: +- self._lib.H5get_libversion(byref(major), byref(minor), byref(release)) +- except Exception: +- print("error: Unable to find HDF5 version") +- raise + +- return int(major.value), int(minor.value), int(release.value) ++ return (1, 13, 1) + + def load_function(self, func_name): + try: +@@ -277,7 +263,7 @@ class HDF5LibWrapper: + return True + + def has_mpi_support(self): +- return self.has_functions("H5Pget_fapl_mpio", "H5Pset_fapl_mpio") ++ return False + + def has_ros3_support(self): +- return self.has_functions("H5Pget_fapl_ros3", "H5Pset_fapl_ros3") ++ return False diff --git a/packages/html5lib/meta.yaml b/packages/html5lib/meta.yaml index f1c837fa0d8..a6377a9e234 100644 --- a/packages/html5lib/meta.yaml +++ b/packages/html5lib/meta.yaml @@ -6,8 +6,8 @@ requirements: - webencodings - six source: - sha256: b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f - url: https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz + sha256: 0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d + url: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl test: imports: - html5lib diff --git a/packages/idna/meta.yaml b/packages/idna/meta.yaml new file mode 100644 index 00000000000..63c9e75319a --- /dev/null +++ b/packages/idna/meta.yaml @@ -0,0 +1,7 @@ +package: + name: idna + version: "3.3" + +source: + sha256: 9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d + url: https://files.pythonhosted.org/packages/62/08/e3fc7c8161090f742f504f40b1bccbfc544d4a4e09eb774bf40aafce5436/idna-3.3.tar.gz diff --git a/packages/imageio/meta.yaml b/packages/imageio/meta.yaml index 4fc89d448c7..def71561e7c 100644 --- a/packages/imageio/meta.yaml +++ b/packages/imageio/meta.yaml @@ -1,13 +1,13 @@ package: name: imageio - version: 2.9.0 + version: 2.16.1 source: - sha256: 52ddbaeca2dccf53ba2d6dec5676ca7bc3b2403ef8b37f7da78b7654bb3e10f0 - url: https://files.pythonhosted.org/packages/c3/73/f37f428748c4f10a7991ac5bff00f113a34bcc0d0a78957d6e1cdc29a94e/imageio-2.9.0.tar.gz + sha256: d8d17c59b6f5f3b350bbbe346e7cb7dda0399b1881d93ad01cb29b5acdb24c42 + url: https://files.pythonhosted.org/packages/29/24/a3a7aa7f1e7f1c3a5c9fe2ff3fec8d9d17e10741eafb710f06705744b35f/imageio-2.16.1-py3-none-any.whl requirements: run: - numpy - - pillow + - Pillow test: imports: - imageio diff --git a/packages/imageio/test_imageio.py b/packages/imageio/test_imageio.py index 53eeab0047f..1cde0bcf622 100644 --- a/packages/imageio/test_imageio.py +++ b/packages/imageio/test_imageio.py @@ -3,8 +3,8 @@ @run_in_pyodide(standalone=True, packages=["numpy", "imageio"]) def test_imageio(): - import numpy as np import imageio + import numpy as np filename = "/tmp/foo.tif" image_in = np.random.randint(0, 65535, size=(100, 36), dtype=np.uint16) @@ -16,8 +16,8 @@ def test_imageio(): @run_in_pyodide(packages=["numpy", "imageio"]) def test_jpg(): - import numpy as np import imageio + import numpy as np img = np.zeros((5, 5), dtype=np.uint8) imageio.imsave("img.jpg", img) diff --git a/packages/iniconfig/meta.yaml b/packages/iniconfig/meta.yaml index 32eb17f12d6..720819d1b56 100644 --- a/packages/iniconfig/meta.yaml +++ b/packages/iniconfig/meta.yaml @@ -2,8 +2,8 @@ package: name: iniconfig version: 1.1.1 source: - sha256: bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 - url: https://files.pythonhosted.org/packages/23/a2/97899f6bd0e873fed3a7e67ae8d3a08b21799430fb4da15cfedf10d6e2c2/iniconfig-1.1.1.tar.gz + sha256: 011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 + url: https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl test: imports: - iniconfig diff --git a/packages/jedi/meta.yaml b/packages/jedi/meta.yaml index edbedd303c4..580698b1c48 100644 --- a/packages/jedi/meta.yaml +++ b/packages/jedi/meta.yaml @@ -2,8 +2,8 @@ package: name: jedi version: 0.18.1 source: - sha256: 74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab - url: https://files.pythonhosted.org/packages/c2/25/273288df952e07e3190446efbbb30b0e4871a0d63b4246475f3019d4f55e/jedi-0.18.1.tar.gz + sha256: 637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d + url: https://files.pythonhosted.org/packages/b3/0e/836f12ec50075161e365131f13f5758451645af75c2becf61c6351ecec39/jedi-0.18.1-py2.py3-none-any.whl requirements: run: - parso diff --git a/packages/joblib/meta.yaml b/packages/joblib/meta.yaml index f805bae1bf5..474168162c6 100644 --- a/packages/joblib/meta.yaml +++ b/packages/joblib/meta.yaml @@ -1,17 +1,16 @@ package: name: joblib - version: "0.11" + version: 1.1.0 requirements: run: - distutils source: - url: https://pypi.io/packages/source/j/joblib/joblib-0.11.tar.gz - sha256: 7b8fd56df36d9731a83729395ccb85a3b401f62a96255deb1a77220c00ed4085 - + url: https://files.pythonhosted.org/packages/92/b9/9e3616e7e00c8165fb25175c53444533bdde05f3e974d45d9fcbbe451ee6/joblib-1.1.0.tar.gz + sha256: 4158fcecd13733f8be669be0683b96ebdbbd38d23559f54dca7205aea1bf1e35 patches: - - patches/use-setuptools.patch + - patches/0001-No-multiprocessing-1256.patch test: imports: diff --git a/packages/joblib/patches/0001-No-multiprocessing-1256.patch b/packages/joblib/patches/0001-No-multiprocessing-1256.patch new file mode 100644 index 00000000000..f980fdfb425 --- /dev/null +++ b/packages/joblib/patches/0001-No-multiprocessing-1256.patch @@ -0,0 +1,501 @@ +From b285003a08d44ff85327367f37a61489a53217a3 Mon Sep 17 00:00:00 2001 +From: Gael Varoquaux +Date: Tue, 8 Feb 2022 09:20:22 +0100 +Subject: [PATCH] No multiprocessing (#1256) + +Co-authored-by: Hood Chatham +Co-authored-by: Olivier Grisel +--- + doc/parallel.rst | 7 ++ + joblib/__init__.py | 3 +- + joblib/_cloudpickle_wrapper.py | 17 +++++ + joblib/_multiprocessing_helpers.py | 1 + + joblib/parallel.py | 55 ++++++++++++--- + joblib/test/test_cloudpickle_wrapper.py | 27 ++++++++ + joblib/test/test_memmapping.py | 5 +- + joblib/test/test_missing_multiprocessing.py | 32 +++++++++ + joblib/test/test_module.py | 5 +- + joblib/test/test_parallel.py | 75 +++++++++++++++------ + 10 files changed, 194 insertions(+), 33 deletions(-) + create mode 100644 joblib/_cloudpickle_wrapper.py + create mode 100644 joblib/test/test_cloudpickle_wrapper.py + create mode 100644 joblib/test/test_missing_multiprocessing.py + +diff --git a/doc/parallel.rst b/doc/parallel.rst +index 466d613..8514e2d 100644 +--- a/doc/parallel.rst ++++ b/doc/parallel.rst +@@ -71,6 +71,13 @@ call to :class:`joblib.Parallel` but this is now considered a bad pattern + (when done in a library) as it does not make it possible to override that + choice with the ``parallel_backend`` context manager. + ++ ++.. topic:: The loky backend may not always be available ++ ++ Some rare systems do not support multiprocessing (for instance ++ Pyodide). In this case the loky backend is not available and the ++ default backend falls back to threading. ++ + Besides builtin joblib backends, we can use + `Joblib Apache Spark Backend `_ + to distribute joblib tasks on a Spark cluster. +diff --git a/joblib/__init__.py b/joblib/__init__.py +index 4255c86..9863998 100644 +--- a/joblib/__init__.py ++++ b/joblib/__init__.py +@@ -123,8 +123,7 @@ from .parallel import cpu_count + from .parallel import register_parallel_backend + from .parallel import parallel_backend + from .parallel import effective_n_jobs +- +-from .externals.loky import wrap_non_picklable_objects ++from ._cloudpickle_wrapper import wrap_non_picklable_objects + + + __all__ = ['Memory', 'MemorizedResult', 'PrintTime', 'Logger', 'hash', 'dump', +diff --git a/joblib/_cloudpickle_wrapper.py b/joblib/_cloudpickle_wrapper.py +new file mode 100644 +index 0000000..3dbe3ae +--- /dev/null ++++ b/joblib/_cloudpickle_wrapper.py +@@ -0,0 +1,17 @@ ++""" ++Small shim of loky's cloudpickle_wrapper to avoid failure when ++multiprocessing is not available. ++""" ++ ++ ++from ._multiprocessing_helpers import mp ++ ++ ++def my_wrap_non_picklable_objects(obj, keep_wrapper=True): ++ return obj ++ ++ ++if mp is None: ++ wrap_non_picklable_objects = my_wrap_non_picklable_objects ++else: ++ from .externals.loky import wrap_non_picklable_objects # noqa +diff --git a/joblib/_multiprocessing_helpers.py b/joblib/_multiprocessing_helpers.py +index 1c5de2f..bde4bc1 100644 +--- a/joblib/_multiprocessing_helpers.py ++++ b/joblib/_multiprocessing_helpers.py +@@ -14,6 +14,7 @@ mp = int(os.environ.get('JOBLIB_MULTIPROCESSING', 1)) or None + if mp: + try: + import multiprocessing as mp ++ import _multiprocessing # noqa + except ImportError: + mp = None + +diff --git a/joblib/parallel.py b/joblib/parallel.py +index 687557e..9eb6308 100644 +--- a/joblib/parallel.py ++++ b/joblib/parallel.py +@@ -27,7 +27,6 @@ from ._parallel_backends import (FallbackToBackend, MultiprocessingBackend, + ThreadingBackend, SequentialBackend, + LokyBackend) + from .externals.cloudpickle import dumps, loads +-from .externals import loky + + # Make sure that those two classes are part of the public joblib.parallel API + # so that 3rd party backend implementers can import them from here. +@@ -36,15 +35,28 @@ from ._parallel_backends import ParallelBackendBase # noqa + + + BACKENDS = { +- 'multiprocessing': MultiprocessingBackend, + 'threading': ThreadingBackend, + 'sequential': SequentialBackend, +- 'loky': LokyBackend, + } + # name of the backend used by default by Parallel outside of any context + # managed by ``parallel_backend``. +-DEFAULT_BACKEND = 'loky' ++ ++# threading is the only backend that is always everywhere ++DEFAULT_BACKEND = 'threading' ++ + DEFAULT_N_JOBS = 1 ++ ++MAYBE_AVAILABLE_BACKENDS = {'multiprocessing', 'loky'} ++ ++# if multiprocessing is available, so is loky, we set it as the default ++# backend ++if mp is not None: ++ BACKENDS['multiprocessing'] = MultiprocessingBackend ++ from .externals import loky ++ BACKENDS['loky'] = LokyBackend ++ DEFAULT_BACKEND = 'loky' ++ ++ + DEFAULT_THREAD_BACKEND = 'threading' + + # Thread local value that can be overridden by the ``parallel_backend`` context +@@ -135,7 +147,9 @@ class parallel_backend(object): + 'threading' is a low-overhead alternative that is most efficient for + functions that release the Global Interpreter Lock: e.g. I/O-bound code or + CPU-bound code in a few calls to native code that explicitly releases the +- GIL. ++ GIL. Note that on some rare systems (such as pyiodine), ++ multiprocessing and loky may not be available, in which case joblib ++ defaults to threading. + + In addition, if the `dask` and `distributed` Python packages are installed, + it is possible to use the 'dask' backend for better scheduling of nested +@@ -184,9 +198,20 @@ class parallel_backend(object): + def __init__(self, backend, n_jobs=-1, inner_max_num_threads=None, + **backend_params): + if isinstance(backend, str): +- if backend not in BACKENDS and backend in EXTERNAL_BACKENDS: +- register = EXTERNAL_BACKENDS[backend] +- register() ++ if backend not in BACKENDS: ++ if backend in EXTERNAL_BACKENDS: ++ register = EXTERNAL_BACKENDS[backend] ++ register() ++ elif backend in MAYBE_AVAILABLE_BACKENDS: ++ warnings.warn( ++ f"joblib backend '{backend}' is not available on " ++ f"your system, falling back to {DEFAULT_BACKEND}.", ++ UserWarning, ++ stacklevel=2) ++ BACKENDS[backend] = BACKENDS[DEFAULT_BACKEND] ++ else: ++ raise ValueError("Invalid backend: %s, expected one of %r" ++ % (backend, sorted(BACKENDS.keys()))) + + backend = BACKENDS[backend](**backend_params) + +@@ -436,7 +461,9 @@ class Parallel(Logger): + + - "loky" used by default, can induce some + communication and memory overhead when exchanging input and +- output data with the worker Python processes. ++ output data with the worker Python processes. On some rare ++ systems (such as Pyiodide), the loky backend may not be ++ available. + - "multiprocessing" previous process-based backend based on + `multiprocessing.Pool`. Less robust than `loky`. + - "threading" is a very low-overhead backend but it suffers +@@ -690,6 +717,16 @@ class Parallel(Logger): + # preload modules on the forkserver helper process. + self._backend_args['context'] = backend + backend = MultiprocessingBackend(nesting_level=nesting_level) ++ ++ elif backend not in BACKENDS and backend in MAYBE_AVAILABLE_BACKENDS: ++ warnings.warn( ++ f"joblib backend '{backend}' is not available on " ++ f"your system, falling back to {DEFAULT_BACKEND}.", ++ UserWarning, ++ stacklevel=2) ++ BACKENDS[backend] = BACKENDS[DEFAULT_BACKEND] ++ backend = BACKENDS[DEFAULT_BACKEND](nesting_level=nesting_level) ++ + else: + try: + backend_factory = BACKENDS[backend] +diff --git a/joblib/test/test_cloudpickle_wrapper.py b/joblib/test/test_cloudpickle_wrapper.py +new file mode 100644 +index 0000000..733f51c +--- /dev/null ++++ b/joblib/test/test_cloudpickle_wrapper.py +@@ -0,0 +1,27 @@ ++""" ++Test that our implementation of wrap_non_picklable_objects mimics ++properly the loky implementation. ++""" ++ ++from .._cloudpickle_wrapper import wrap_non_picklable_objects ++from .._cloudpickle_wrapper import my_wrap_non_picklable_objects ++ ++ ++def a_function(x): ++ return x ++ ++ ++class AClass(object): ++ ++ def __call__(self, x): ++ return x ++ ++ ++def test_wrap_non_picklable_objects(): ++ # Mostly a smoke test: test that we can use callable in the same way ++ # with both our implementation of wrap_non_picklable_objects and the ++ # upstream one ++ for obj in (a_function, AClass()): ++ wrapped_obj = wrap_non_picklable_objects(obj) ++ my_wrapped_obj = my_wrap_non_picklable_objects(obj) ++ assert wrapped_obj(1) == my_wrapped_obj(1) +diff --git a/joblib/test/test_memmapping.py b/joblib/test/test_memmapping.py +index dc40d23..67ddaef 100644 +--- a/joblib/test/test_memmapping.py ++++ b/joblib/test/test_memmapping.py +@@ -146,7 +146,8 @@ def test_memmap_based_array_reducing(tmpdir): + assert_array_equal(b3_reconstructed, b3) + + +-@skipif(sys.platform != "win32", ++@with_multiprocessing ++@skipif((sys.platform != "win32") or (), + reason="PermissionError only easily triggerable on Windows") + def test_resource_tracker_retries_when_permissionerror(tmpdir): + # Test resource_tracker retry mechanism when unlinking memmaps. See more +@@ -355,6 +356,7 @@ def test_pool_with_memmap_array_view(factory, tmpdir): + + + @with_numpy ++@with_multiprocessing + @parametrize("backend", ["multiprocessing", "loky"]) + def test_permission_error_windows_reference_cycle(backend): + # Non regression test for: +@@ -389,6 +391,7 @@ def test_permission_error_windows_reference_cycle(backend): + + + @with_numpy ++@with_multiprocessing + @parametrize("backend", ["multiprocessing", "loky"]) + def test_permission_error_windows_memmap_sent_to_parent(backend): + # Second non-regression test for: +diff --git a/joblib/test/test_missing_multiprocessing.py b/joblib/test/test_missing_multiprocessing.py +new file mode 100644 +index 0000000..251925c +--- /dev/null ++++ b/joblib/test/test_missing_multiprocessing.py +@@ -0,0 +1,32 @@ ++""" ++Pyodide and other single-threaded Python builds will be missing the ++_multiprocessing module. Test that joblib still works in this environment. ++""" ++ ++import os ++import subprocess ++import sys ++ ++ ++def test_missing_multiprocessing(tmp_path): ++ """ ++ Test that import joblib works even if _multiprocessing is missing. ++ ++ pytest has already imported everything from joblib. The most reasonable way ++ to test importing joblib with modified environment is to invoke a separate ++ Python process. This also ensures that we don't break other tests by ++ importing a bad `_multiprocessing` module. ++ """ ++ (tmp_path / "_multiprocessing.py").write_text( ++ 'raise ImportError("No _multiprocessing module!")' ++ ) ++ env = dict(os.environ) ++ # For subprocess, use current sys.path with our custom version of ++ # multiprocessing inserted. ++ env["PYTHONPATH"] = ":".join([str(tmp_path)] + sys.path) ++ subprocess.check_call( ++ [sys.executable, "-c", ++ "import joblib, math; " ++ "joblib.Parallel(n_jobs=1)(" ++ "joblib.delayed(math.sqrt)(i**2) for i in range(10))" ++ ], env=env) +diff --git a/joblib/test/test_module.py b/joblib/test/test_module.py +index 9c3b12b..a2257a4 100644 +--- a/joblib/test/test_module.py ++++ b/joblib/test/test_module.py +@@ -1,7 +1,7 @@ + import sys + import joblib +-import pytest + from joblib.testing import check_subprocess_call ++from joblib.test.common import with_multiprocessing + + + def test_version(): +@@ -9,6 +9,7 @@ def test_version(): + "There are no __version__ argument on the joblib module") + + ++@with_multiprocessing + def test_no_start_method_side_effect_on_import(): + # check that importing joblib does not implicitly set the global + # start_method for multiprocessing. +@@ -22,6 +23,7 @@ def test_no_start_method_side_effect_on_import(): + check_subprocess_call([sys.executable, '-c', code]) + + ++@with_multiprocessing + def test_no_semaphore_tracker_on_import(): + # check that importing joblib does not implicitly spawn a resource tracker + # or a semaphore tracker +@@ -38,6 +40,7 @@ def test_no_semaphore_tracker_on_import(): + check_subprocess_call([sys.executable, '-c', code]) + + ++@with_multiprocessing + def test_no_resource_tracker_on_import(): + code = """if True: + import joblib +diff --git a/joblib/test/test_parallel.py b/joblib/test/test_parallel.py +index 7edeb85..4c321e5 100644 +--- a/joblib/test/test_parallel.py ++++ b/joblib/test/test_parallel.py +@@ -24,14 +24,17 @@ from importlib import reload + import joblib + from joblib import parallel + from joblib import dump, load +-from joblib.externals.loky import get_reusable_executor ++ ++from joblib._multiprocessing_helpers import mp + + from joblib.test.common import np, with_numpy + from joblib.test.common import with_multiprocessing + from joblib.testing import (parametrize, raises, check_subprocess_call, + skipif, SkipTest, warns) + +-from joblib.externals.loky.process_executor import TerminatedWorkerError ++if mp is not None: ++ # Loky is not available if multiprocessing is not ++ from joblib.externals.loky import get_reusable_executor + + from queue import Queue + +@@ -69,7 +72,10 @@ from joblib.my_exceptions import WorkerInterrupt + ALL_VALID_BACKENDS = [None] + sorted(BACKENDS.keys()) + # Add instances of backend classes deriving from ParallelBackendBase + ALL_VALID_BACKENDS += [BACKENDS[backend_str]() for backend_str in BACKENDS] +-PROCESS_BACKENDS = ['multiprocessing', 'loky'] ++if mp is None: ++ PROCESS_BACKENDS = [] ++else: ++ PROCESS_BACKENDS = ['multiprocessing', 'loky'] + PARALLEL_BACKENDS = PROCESS_BACKENDS + ['threading'] + + if hasattr(mp, 'get_context'): +@@ -269,6 +275,7 @@ def raise_exception(backend): + raise ValueError + + ++@with_multiprocessing + def test_nested_loop_with_exception_with_loky(): + with raises(ValueError): + with Parallel(n_jobs=2, backend="loky") as parallel: +@@ -568,8 +575,14 @@ class FakeParallelBackend(SequentialBackend): + + + def test_invalid_backend(): +- with raises(ValueError): ++ with raises(ValueError) as excinfo: + Parallel(backend='unit-testing') ++ assert "Invalid backend:" in str(excinfo.value) ++ ++ with raises(ValueError) as excinfo: ++ with parallel_backend('unit-testing'): ++ pass ++ assert "Invalid backend:" in str(excinfo.value) + + + @parametrize('backend', ALL_VALID_BACKENDS) +@@ -600,6 +613,17 @@ def test_overwrite_default_backend(): + assert _active_backend_type() == DefaultBackend + + ++@skipif(mp is not None, reason="Only without multiprocessing") ++def test_backend_no_multiprocessing(): ++ with warns(UserWarning, ++ match="joblib backend '.*' is not available on.*"): ++ Parallel(backend='loky')(delayed(square)(i) for i in range(3)) ++ ++ # The below should now work without problems ++ with parallel_backend('loky'): ++ Parallel()(delayed(square)(i) for i in range(3)) ++ ++ + def check_backend_context_manager(backend_name): + with parallel_backend(backend_name, n_jobs=3): + active_backend, active_n_jobs = parallel.get_active_backend() +@@ -1207,7 +1231,10 @@ def test_memmapping_leaks(backend, tmpdir): + raise AssertionError('temporary directory of Parallel was not removed') + + +-@parametrize('backend', [None, 'loky', 'threading']) ++@parametrize('backend', ++ ([None, 'threading'] if mp is None ++ else [None, 'loky', 'threading']) ++ ) + def test_lambda_expression(backend): + # cloudpickle is used to pickle delayed callables + results = Parallel(n_jobs=2, backend=backend)( +@@ -1237,6 +1264,7 @@ def test_backend_batch_statistics_reset(backend): + p._backend._DEFAULT_SMOOTHED_BATCH_DURATION) + + ++@with_multiprocessing + def test_backend_hinting_and_constraints(): + for n_jobs in [1, 2, -1]: + assert type(Parallel(n_jobs=n_jobs)._backend) == LokyBackend +@@ -1347,12 +1375,13 @@ def test_invalid_backend_hinting_and_constraints(): + # requiring shared memory semantics. + Parallel(prefer='processes', require='sharedmem') + +- # It is inconsistent to ask explicitly for a process-based parallelism +- # while requiring shared memory semantics. +- with raises(ValueError): +- Parallel(backend='loky', require='sharedmem') +- with raises(ValueError): +- Parallel(backend='multiprocessing', require='sharedmem') ++ if mp is not None: ++ # It is inconsistent to ask explicitly for a process-based ++ # parallelism while requiring shared memory semantics. ++ with raises(ValueError): ++ Parallel(backend='loky', require='sharedmem') ++ with raises(ValueError): ++ Parallel(backend='multiprocessing', require='sharedmem') + + + def test_global_parallel_backend(): +@@ -1437,7 +1466,8 @@ def _recursive_parallel(nesting_limit=None): + return Parallel()(delayed(_recursive_parallel)() for i in range(2)) + + +-@parametrize('backend', ['loky', 'threading']) ++@parametrize('backend', ++ (['threading'] if mp is None else ['loky', 'threading'])) + def test_thread_bomb_mitigation(backend): + # Test that recursive parallelism raises a recursion rather than + # saturating the operating system resources by creating a unbounded number +@@ -1446,13 +1476,18 @@ def test_thread_bomb_mitigation(backend): + with raises(BaseException) as excinfo: + _recursive_parallel() + exc = excinfo.value +- if backend == "loky" and isinstance(exc, TerminatedWorkerError): +- # The recursion exception can itself cause an error when pickling it to +- # be send back to the parent process. In this case the worker crashes +- # but the original traceback is still printed on stderr. This could be +- # improved but does not seem simple to do and this is is not critical +- # for users (as long as there is no process or thread bomb happening). +- pytest.xfail("Loky worker crash when serializing RecursionError") ++ if backend == "loky": ++ # Local import because loky may not be importable for lack of ++ # multiprocessing ++ from joblib.externals.loky.process_executor import TerminatedWorkerError # noqa ++ if isinstance(exc, TerminatedWorkerError): ++ # The recursion exception can itself cause an error when ++ # pickling it to be send back to the parent process. In this ++ # case the worker crashes but the original traceback is still ++ # printed on stderr. This could be improved but does not seem ++ # simple to do and this is is not critical for users (as long ++ # as there is no process or thread bomb happening). ++ pytest.xfail("Loky worker crash when serializing RecursionError") + else: + assert isinstance(exc, RecursionError) + +@@ -1466,7 +1501,7 @@ def _run_parallel_sum(): + return env_vars, parallel_sum(100) + + +-@parametrize("backend", [None, 'loky']) ++@parametrize("backend", ([None, 'loky'] if mp is not None else [None])) + @skipif(parallel_sum is None, reason="Need OpenMP helper compiled") + def test_parallel_thread_limit(backend): + results = Parallel(n_jobs=2, backend=backend)( +-- +2.25.1 + diff --git a/packages/joblib/patches/use-setuptools.patch b/packages/joblib/patches/use-setuptools.patch deleted file mode 100644 index 0ada7851491..00000000000 --- a/packages/joblib/patches/use-setuptools.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/setup.py b/setup.py -index 134db18..f705b28 100755 ---- a/setup.py -+++ b/setup.py -@@ -1,6 +1,6 @@ - #!/usr/bin/env python - --from distutils.core import setup -+from setuptools import setup - import sys - - import joblib diff --git a/packages/joblib/test_joblib.py b/packages/joblib/test_joblib.py index 3cdfb8365ce..107299ea107 100644 --- a/packages/joblib/test_joblib.py +++ b/packages/joblib/test_joblib.py @@ -1,6 +1,3 @@ -import pytest - - def test_joblib_numpy_pickle(selenium, request): selenium.load_package(["numpy", "joblib"]) selenium.run( diff --git a/packages/kiwisolver/meta.yaml b/packages/kiwisolver/meta.yaml index d8585aafdfe..acff5304b08 100644 --- a/packages/kiwisolver/meta.yaml +++ b/packages/kiwisolver/meta.yaml @@ -1,9 +1,9 @@ package: name: kiwisolver - version: 1.3.2 + version: 1.4.2 source: - sha256: fc4453705b81d03568d5b808ad8f09c77c47534f6ac2e72e733f9ca4714aa75c - url: https://files.pythonhosted.org/packages/8e/87/259fde8cf07d06677f0a749cb157d079ebd00d40fe52faaab1a882a66159/kiwisolver-1.3.2.tar.gz + sha256: 7f606d91b8a8816be476513a77fd30abe66227039bd6f8b406c348cb0247dcc9 + url: https://files.pythonhosted.org/packages/2b/65/9eb6841880f6214f70e891a97ac945137bb6b2dd65ac35da219a752255fe/kiwisolver-1.4.2.tar.gz test: imports: - kiwisolver diff --git a/packages/lazy-object-proxy/meta.yaml b/packages/lazy-object-proxy/meta.yaml new file mode 100644 index 00000000000..fc40ed5559f --- /dev/null +++ b/packages/lazy-object-proxy/meta.yaml @@ -0,0 +1,14 @@ +package: + name: lazy-object-proxy + version: 1.7.1 +source: + url: https://files.pythonhosted.org/packages/75/93/3fc1cc28f71dd10b87a53b9d809602d7730e84cc4705a062def286232a9c/lazy-object-proxy-1.7.1.tar.gz + sha256: d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4 +test: + imports: + - lazy_object_proxy +about: + home: https://github.com/ionelmc/python-lazy-object-proxy + PyPI: https://pypi.org/project/lazy-object-proxy + summary: A fast and thorough lazy object proxy. + license: BSD-2-Clause diff --git a/packages/lazy-object-proxy/test_lazy_object_proxy.py b/packages/lazy-object-proxy/test_lazy_object_proxy.py new file mode 100644 index 00000000000..61a1ee92b06 --- /dev/null +++ b/packages/lazy-object-proxy/test_lazy_object_proxy.py @@ -0,0 +1,22 @@ +from pyodide_build.testing import run_in_pyodide + + +@run_in_pyodide(packages=["lazy-object-proxy"]) +def test_lazy_object_proxy(): + import lazy_object_proxy + + def expensive_func(): + from time import sleep + + print("starting calculation") + # just as example for a slow computation + sleep(0.1) + print("finished calculation") + # return the result of the calculation + return 10 + + obj = lazy_object_proxy.Proxy(expensive_func) + # function is called only when object is actually used + assert obj == 10 # now expensive_func is called + + assert obj == 10 # the result without calling the expensive_func diff --git a/packages/libhdf5/meta.yaml b/packages/libhdf5/meta.yaml new file mode 100644 index 00000000000..d16d2b4c958 --- /dev/null +++ b/packages/libhdf5/meta.yaml @@ -0,0 +1,31 @@ +package: + name: libhdf5 + version: 1.13.1 + +source: + sha256: 92552458f35c7e58128ce1bfc2831abf901cc142ea0fdd2b056311e4452db7bf + url: https://github.com/HDFGroup/hdf5/archive/refs/tags/hdf5-1_13_1.tar.gz + +requirements: + run: + - zlib + +build: + library: true + script: | + mkdir -p build; + cd build \ + && LDFLAGS="-s NODERAWFS=1 -sUSE_ZLIB=1 -sFORCE_FILESYSTEM=1" emcmake cmake ../ \ + -DCMAKE_INSTALL_PREFIX=../../hdf5 \ + -DH5_HAVE_GETPWUID=0 \ + -DH5_HAVE_SIGNAL=0 \ + -DBUILD_SHARED_LIBS=0 \ + -DBUILD_STATIC_LIBS=1 \ + -DBUILD_TESTING=0 \ + -DCMAKE_C_FLAGS="-Wno-incompatible-pointer-types-discards-qualifiers" \ + -DHDF5_BUILD_EXAMPLES=0 \ + -DHDF5_BUILD_TOOLS=0 \ + -DHDF5_BUILD_UTILS=0 \ + -DHDF5_ENABLE_Z_LIB_SUPPORT=1 \ + -DHDF5_ENABLE_ROS3_VFD=0; + emmake make -j ${PYODIDE_JOBS:-3} install; diff --git a/packages/libwebp/meta.yaml b/packages/libwebp/meta.yaml new file mode 100644 index 00000000000..9c52897dd64 --- /dev/null +++ b/packages/libwebp/meta.yaml @@ -0,0 +1,14 @@ +package: + name: libwebp + version: 1.2.2 + +source: + url: https://github.com/webmproject/libwebp/archive/refs/tags/v1.2.2.tar.gz + sha256: 51e9297aadb7d9eb99129fe0050f53a11fcce38a0848fb2b0389e385ad93695e + +build: + library: true + script: | + mkdir build && cd build && emcmake cmake -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_INSTALL_PREFIX=./lib ../ + emmake make -j ${PYODIDE_JOBS:-3} + emmake make install diff --git a/packages/logbook/meta.yaml b/packages/logbook/meta.yaml index 1f2d5050053..59e152d2597 100644 --- a/packages/logbook/meta.yaml +++ b/packages/logbook/meta.yaml @@ -1,10 +1,10 @@ package: name: logbook - version: 1.5.2 + version: 1.5.3 source: - url: https://github.com/getlogbook/logbook/archive/refs/tags/1.5.2.tar.gz - sha256: 3eebcb13a41636004a2d03d875bc1c2ccc4afb02dc13f45320b147fee79c9739 + url: https://files.pythonhosted.org/packages/2f/d9/16ac346f7c0102835814cc9e5b684aaadea101560bb932a2403bd26b2320/Logbook-1.5.3.tar.gz + sha256: 66f454ada0f56eae43066f604a222b09893f98c1adc18df169710761b8f32fe8 requirements: run: diff --git a/packages/lxml/meta.yaml b/packages/lxml/meta.yaml index a4969e66b17..2780fb87c6d 100644 --- a/packages/lxml/meta.yaml +++ b/packages/lxml/meta.yaml @@ -1,9 +1,9 @@ package: name: lxml - version: 4.4.1 + version: 4.8.0 source: - sha256: c81cb40bff373ab7a7446d6bbca0190bccc5be3448b47b51d729e37799bb5692 - url: https://files.pythonhosted.org/packages/c4/43/3f1e7d742e2a7925be180b6af5e0f67d38de2f37560365ac1a0b9a04c015/lxml-4.4.1.tar.gz + sha256: f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23 + url: https://files.pythonhosted.org/packages/3b/94/e2b1b3bad91d15526c7e38918795883cee18b93f6785ea8ecf13f8ffa01e/lxml-4.8.0.tar.gz build: script: | export WITH_XML2_CONFIG=$PYODIDE_ROOT/packages/libxml/build/libxml-2.9.10/xml2-config diff --git a/packages/matplotlib/meta.yaml b/packages/matplotlib/meta.yaml index 25cffa2b9fe..1d9b4f2e1a3 100644 --- a/packages/matplotlib/meta.yaml +++ b/packages/matplotlib/meta.yaml @@ -1,14 +1,13 @@ package: name: matplotlib - version: 3.3.3 + version: 3.5.1 source: - url: https://files.pythonhosted.org/packages/7b/b3/7c48f648bf83f39d4385e0169d1b68218b838e185047f7f613b1cfc57947/matplotlib-3.3.3.tar.gz - sha256: b1b60c6476c4cfe9e5cf8ab0d3127476fd3d5f05de0f343a452badaad0e4bdec + url: https://files.pythonhosted.org/packages/8a/46/425a44ab9a71afd2f2c8a78b039c1af8ec21e370047f0ad6e43ca819788e/matplotlib-3.5.1.tar.gz + sha256: b2e9810e09c3a47b73ce9cab5a72243a1258f61e7900969097a817232246ce1c patches: - patches/disable_macosx_backend.patch - - patches/fix-freetype.patch - patches/hardcoded-font-cache.patch - patches/fix-threading.patch @@ -20,7 +19,7 @@ source: - - src/html5_canvas_backend.py - lib/matplotlib/backends/html5_canvas_backend.py - - src/setup.cfg - - ./setup.cfg + - ./mplsetup.cfg build: cflags: -s USE_FREETYPE=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 @@ -28,21 +27,24 @@ build: replace-libs: - png16=png post: | - wget -O $SITEPACKAGES/matplotlib/mpl-data/fonts/ttf/Humor-Sans.ttf http://antiyawn.com/uploads/Humor-Sans-1.0.ttf - rm -rf $SITEPACKAGES/matplotlib/backends/qt_editor - rm -rf $SITEPACKAGES/matplotlib/backends/web_backend - rm -rf $SITEPACKAGES/sphinxext - cp $PKGDIR/src/fontlist.json $SITEPACKAGES/matplotlib - mkdir -p $PKGDIR/../../build/fonts - cp $SITEPACKAGES/matplotlib/mpl-data/fonts/ttf/* $PKGDIR/../../build/fonts/ + cd build/matplotlib-3.5.1/dist/matplotlib-3.5.1/ + rm -rf matplotlib/backends/qt_editor + rm -rf matplotlib/backends/web_backend + rm -rf sphinxext + cp $PKGDIR/src/fontlist.json matplotlib + cp $PKGDIR/src/Humor-Sans-1.0.ttf matplotlib/mpl-data/fonts/ttf/Humor-Sans.ttf + mkdir -p $PKGDIR/../../dist/fonts + cp matplotlib/mpl-data/fonts/ttf/* $PKGDIR/../../dist/fonts/ requirements: run: - - distutils - cycler + - distutils + - fonttools - kiwisolver - numpy - - pillow + - packaging + - Pillow - pyparsing - python-dateutil - pytz diff --git a/packages/matplotlib/patches/disable_macosx_backend.patch b/packages/matplotlib/patches/disable_macosx_backend.patch index ad80979567e..455d571a2e9 100644 --- a/packages/matplotlib/patches/disable_macosx_backend.patch +++ b/packages/matplotlib/patches/disable_macosx_backend.patch @@ -1,9 +1,10 @@ This comes from #541. We let it as is in case it helps building on Mac. diff --git a/setupext.py b/setupext.py +index e41ab98fe1..d0e26d484d 100644 --- a/setupext.py +++ b/setupext.py -@@ -665,6 +665,7 @@ class BackendMacOSX(OptionalPackage): +@@ -724,6 +724,7 @@ class BackendMacOSX(OptionalPackage): name = 'macosx' def check(self): @@ -12,4 +13,5 @@ diff --git a/setupext.py b/setupext.py raise Skipped("Mac OS-X only") return super().check() -- -2.20.1 +2.35.1 + diff --git a/packages/matplotlib/patches/fix-freetype.patch b/packages/matplotlib/patches/fix-freetype.patch deleted file mode 100644 index 01fb44ce015..00000000000 --- a/packages/matplotlib/patches/fix-freetype.patch +++ /dev/null @@ -1,18 +0,0 @@ -Proper Freetype linking. - -diff --git a/setupext.py b/setupext.py ---- a/setupext.py -+++ b/setupext.py -@@ -522,6 +522,9 @@ class FreeType(SetupPackage): - name = "freetype" - - def add_flags(self, ext): -+ if "PYODIDE" in os.environ: -+ ext.libraries.append('freetype') -+ return - ext.sources.insert(0, 'src/checkdep_freetype2.c') - if options.get('system_freetype'): - pkg_config_setup_extension( --- -2.20.1 - diff --git a/packages/matplotlib/patches/fix-threading.patch b/packages/matplotlib/patches/fix-threading.patch index 689b42b1195..a26f6f0d21d 100644 --- a/packages/matplotlib/patches/fix-threading.patch +++ b/packages/matplotlib/patches/fix-threading.patch @@ -1,9 +1,10 @@ threading.Timer is not yet supported in Pyodide. diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py +index 9d45575eb1..e8fb0ca322 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py -@@ -1063,10 +1063,8 @@ class FontManager: +@@ -1056,10 +1056,8 @@ class FontManager: self.afmlist = [] self.ttflist = [] @@ -16,7 +17,7 @@ diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py try: for fontext in ["afm", "ttf"]: for path in [*findSystemFonts(paths, fontext=fontext), -@@ -1079,7 +1077,7 @@ class FontManager: +@@ -1072,7 +1070,7 @@ class FontManager: _log.info("Failed to extract font properties from %s: " "%s", path, exc) finally: @@ -26,4 +27,5 @@ diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py def addfont(self, path): """ -- -2.20.1 +2.35.1 + diff --git a/packages/matplotlib/patches/hardcoded-font-cache.patch b/packages/matplotlib/patches/hardcoded-font-cache.patch index 86757c567f3..1b6a002e55a 100644 --- a/packages/matplotlib/patches/hardcoded-font-cache.patch +++ b/packages/matplotlib/patches/hardcoded-font-cache.patch @@ -3,18 +3,27 @@ is manually pushed in the matplotlib's directory (see meta.yaml) so we teach `font_manager.py` where to find it. diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py +index e8fb0ca322..fa9a6c4ea7 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py -@@ -1394,8 +1392,7 @@ def is_opentype_cff_font(filename): - return False +@@ -1403,7 +1403,6 @@ def _get_font(filename, hinting_factor, *, _kerning_factor, thread_id): + return ft2font.FT2Font( + filename, hinting_factor, _kerning_factor=_kerning_factor) - --_fmcache = os.path.join( -- mpl.get_cachedir(), 'fontlist-v{}.json'.format(FontManager.__version__)) -+_fmcache = os.path.join(os.path.dirname(__file__), "fontlist.json") - fontManager = None +- + # FT2Font objects cannot be used across fork()s because they reference the same + # FT_Library object. While invalidating *all* existing FT2Fonts after a fork + # would be too complicated to be worth it, the main way FT2Fonts get reused is +@@ -1425,8 +1424,7 @@ def get_font(filename, hinting_factor=None): + def _load_fontmanager(*, try_read_cache=True): +- fm_path = Path( +- mpl.get_cachedir(), f"fontlist-v{FontManager.__version__}.json") ++ fm_path = Path(__file__).parent.joinpath("fontlist.json") + if try_read_cache: + try: + fm = json_load(fm_path) -- -2.20.1 +2.35.1 diff --git a/packages/matplotlib/reference-images/canvas-math-text-chrome.png b/packages/matplotlib/reference-images/canvas-math-text-chrome.png index 758cbaffe2e..0c39f6cce0b 100644 Binary files a/packages/matplotlib/reference-images/canvas-math-text-chrome.png and b/packages/matplotlib/reference-images/canvas-math-text-chrome.png differ diff --git a/packages/matplotlib/reference-images/canvas-math-text-firefox.png b/packages/matplotlib/reference-images/canvas-math-text-firefox.png index 2575da50a99..8b3c5d34a67 100644 Binary files a/packages/matplotlib/reference-images/canvas-math-text-firefox.png and b/packages/matplotlib/reference-images/canvas-math-text-firefox.png differ diff --git a/packages/matplotlib/src/Humor-Sans-1.0.ttf b/packages/matplotlib/src/Humor-Sans-1.0.ttf new file mode 100644 index 00000000000..d642b12b821 Binary files /dev/null and b/packages/matplotlib/src/Humor-Sans-1.0.ttf differ diff --git a/packages/matplotlib/src/browser_backend.py b/packages/matplotlib/src/browser_backend.py index 22f35f10682..beb084d188f 100644 --- a/packages/matplotlib/src/browser_backend.py +++ b/packages/matplotlib/src/browser_backend.py @@ -1,9 +1,74 @@ +import math +from typing import Any, Callable + from matplotlib.backend_bases import FigureCanvasBase, NavigationToolbar2, TimerBase -from js import document -from js import window +from js import clearInterval, clearTimeout, document, setInterval, setTimeout +from pyodide import JsProxy, create_once_callable, create_proxy -import math +try: + from js import devicePixelRatio as DEVICE_PIXEL_RATIO +except ImportError: + DEVICE_PIXEL_RATIO = 1 + + +EVENT_LISTENERS: dict[tuple[JsProxy, str, JsProxy], JsProxy] = {} + + +def _add_event_listener(elt: JsProxy, event: str, listener: Callable[[Any], None]): + proxy = create_proxy(listener) + EVENT_LISTENERS[(id(elt), event, listener)] = proxy + elt.addEventListener(event, proxy) + + +def _remove_event_listener(elt: JsProxy, event: str, listener: Callable[[Any], None]): + proxy = EVENT_LISTENERS[(id(elt), event, listener)] + elt.removeEventListener(event, proxy) + proxy.destroy() + + +class Destroyable: + def destroy(self): + pass + + +TIMEOUTS: dict[int, Destroyable] = {} + + +def _set_timeout(callback: Callable[[], None], timeout: int) -> int: + id = -1 + + def wrapper(): + nonlocal id + callback() + TIMEOUTS.pop(id, None) + + id = setTimeout(create_once_callable(wrapper), timeout) + return id + + +# An object with a no-op destroy method so we can do +# +# TIMEOUTS.pop(id, DUMMY_DESTROYABLE).destroy() +# +# and either it gets a real object and calls the real destroy method or it gets +# the fake which does nothing. This is to handle the case where clear_timeout is +# called after the timeout executes. +DUMMY_DESTROYABLE = Destroyable() + + +def _clear_timeout(id: int): + clearTimeout(id) + TIMEOUTS.pop(id, DUMMY_DESTROYABLE).destroy() + + +def _set_interval(callback: Callable[[], None], interval: int) -> int: + return setInterval(create_once_callable(callback), interval) + + +def _clear_interval(id: int): + clearInterval(id) + TIMEOUTS.pop(id).destroy() class FigureCanvasWasm(FigureCanvasBase): @@ -77,7 +142,7 @@ def get_dpi_ratio(self, context): or getattr(context, "backendStorePixelRatio", 0) or 1 ) - return (getattr(window, "devicePixelRatio", 0) or 1) / backing_store + return DEVICE_PIXEL_RATIO / backing_store def create_root_element(self): # Designed to be overridden by subclasses @@ -108,11 +173,10 @@ def ignore(event): width *= self._ratio height *= self._ratio div = self.create_root_element() - div.addEventListener("contextmenu", ignore) + _add_event_listener(div, "contextmenu", ignore) div.setAttribute( "style", - "margin: 0 auto; text-align: center;" - + "width: {}px".format(width / self._ratio), + "margin: 0 auto; text-align: center;" + f"width: {width / self._ratio}px", ) div.id = self._id @@ -158,13 +222,13 @@ def ignore(event): rubberband.setAttribute("tabindex", "0") # Event handlers are added to the canvas "on top", even though most of # the activity happens in the canvas below. - rubberband.addEventListener("mousemove", self.onmousemove) - rubberband.addEventListener("mouseup", self.onmouseup) - rubberband.addEventListener("mousedown", self.onmousedown) - rubberband.addEventListener("mouseenter", self.onmouseenter) - rubberband.addEventListener("mouseleave", self.onmouseleave) - rubberband.addEventListener("keyup", self.onkeyup) - rubberband.addEventListener("keydown", self.onkeydown) + _add_event_listener(rubberband, "mousemove", self.onmousemove) + _add_event_listener(rubberband, "mouseup", self.onmouseup) + _add_event_listener(rubberband, "mousedown", self.onmousedown) + _add_event_listener(rubberband, "mouseenter", self.onmouseenter) + _add_event_listener(rubberband, "mouseleave", self.onmouseleave) + _add_event_listener(rubberband, "keyup", self.onkeyup) + _add_event_listener(rubberband, "keydown", self.onkeydown) context = rubberband.getContext("2d") context.strokeStyle = "#000000" context.setLineDash([2, 2]) @@ -190,7 +254,7 @@ def draw(self): def draw_idle(self): if not self._idle_scheduled: self._idle_scheduled = True - window.setTimeout(self.draw, 1) + _set_timeout(self.draw, 1) def set_message(self, message): message_display = self.get_element("message") @@ -432,7 +496,7 @@ def add_spacer(): span.textContent = "\u00a0" div.appendChild(span) - for text, tooltip_text, image_file, name_of_method in self.toolitems: + for _text, _tooltip_text, image_file, name_of_method in self.toolitems: if image_file in _FONTAWESOME_ICONS: if image_file is None: add_spacer() @@ -441,16 +505,16 @@ def add_spacer(): button.classList.add("fa") button.classList.add(_FONTAWESOME_ICONS[image_file]) button.classList.add("matplotlib-toolbar-button") - button.addEventListener("click", getattr(self, name_of_method)) + _add_event_listener(button, "click", getattr(self, name_of_method)) div.appendChild(button) - for format, mimetype in sorted(list(FILE_TYPES.items())): + for format, _mimetype in sorted(list(FILE_TYPES.items())): button = document.createElement("button") button.classList.add("fa") button.textContent = format button.classList.add("matplotlib-toolbar-button") button.id = "text" - button.addEventListener("click", self.ondownload) + _add_event_listener(button, "click", self.ondownload) div.appendChild(button) return div @@ -479,18 +543,18 @@ class TimerWasm(TimerBase): def _timer_start(self): self._timer_stop() if self._single: - self._timer = window.setTimeout(self._on_timer, self.interval) + self._timer: int | None = _set_timeout(self._on_timer, self.interval) else: - self._timer = window.setInterval(self._on_timer, self.interval) + self._timer = _set_interval(self._on_timer, self.interval) def _timer_stop(self): if self._timer is None: return elif self._single: - window.clearTimeout(self._timer) + _clear_timeout(self._timer) self._timer = None else: - window.clearInterval(self._timer) + _clear_interval(self._timer) self._timer = None def _timer_set_interval(self): diff --git a/packages/matplotlib/src/html5_canvas_backend.py b/packages/matplotlib/src/html5_canvas_backend.py index c9bcafb760c..18efe65776c 100644 --- a/packages/matplotlib/src/html5_canvas_backend.py +++ b/packages/matplotlib/src/html5_canvas_backend.py @@ -1,43 +1,40 @@ +import base64 +import io +import math + import numpy as np -from matplotlib.backends.browser_backend import FigureCanvasWasm, NavigationToolbar2Wasm +from matplotlib import __version__, interactive from matplotlib.backend_bases import ( + FigureManagerBase, GraphicsContextBase, RendererBase, - FigureManagerBase, _Backend, ) - -from PIL import Image -from PIL.PngImagePlugin import PngInfo - -from matplotlib import __version__ -from matplotlib.colors import colorConverter, rgb2hex -from matplotlib.transforms import Affine2D -from matplotlib.path import Path -from matplotlib import interactive - +from matplotlib.backends.browser_backend import FigureCanvasWasm, NavigationToolbar2Wasm from matplotlib.cbook import maxdict +from matplotlib.colors import colorConverter, rgb2hex from matplotlib.font_manager import findfont -from matplotlib.ft2font import FT2Font, LOAD_NO_HINTING +from matplotlib.ft2font import LOAD_NO_HINTING, FT2Font from matplotlib.mathtext import MathTextParser +from matplotlib.path import Path +from matplotlib.transforms import Affine2D +from PIL import Image +from PIL.PngImagePlugin import PngInfo +try: + from js import FontFace, ImageData, document +except ImportError: + raise ImportError( + "html5_canvas_backend is only supported in the browser in the main thread" + ) from pyodide import create_proxy -from js import document, window, XMLHttpRequest, ImageData, FontFace - -import base64 -import io -import math - _capstyle_d = {"projecting": "square", "butt": "butt", "round": "round"} # The URLs of fonts that have already been loaded into the browser _font_set = set() -if hasattr(window, "testing"): - _base_fonts_url = "/fonts/" -else: - _base_fonts_url = "/pyodide/fonts/" +_base_fonts_url = "/fonts/" interactive(True) @@ -46,20 +43,11 @@ class FigureCanvasHTMLCanvas(FigureCanvasWasm): def __init__(self, *args, **kwargs): FigureCanvasWasm.__init__(self, *args, **kwargs) - # A count of the fonts loaded. To support testing - window.font_counter = 0 - def create_root_element(self): root_element = document.createElement("div") document.body.appendChild(root_element) return root_element - def get_dpi_ratio(self, context): - if hasattr(window, "testing"): - return 2.0 - else: - return super().get_dpi_ratio(context) - def draw(self): # Render the figure using custom renderer self._idle_scheduled = True @@ -74,6 +62,8 @@ def draw(self): ctx = canvas.getContext("2d") renderer = RendererHTMLCanvas(ctx, width, height, self.figure.dpi, self) self.figure.draw(renderer) + except Exception as e: + raise RuntimeError("Rendering failed") from e finally: self.figure.dpi = orig_dpi self._idle_scheduled = False @@ -92,28 +82,6 @@ def get_pixel_data(self): canvas_base64 = base64.b64decode(img_URL) return np.asarray(Image.open(io.BytesIO(canvas_base64))) - def compare_reference_image(self, url, threshold): - canvas_data = self.get_pixel_data() - - def _get_url_async(url, threshold): - req = XMLHttpRequest.new() - req.open("GET", url, True) - req.responseType = "arraybuffer" - - def callback(e): - if req.readyState == 4: - ref_data = np.asarray(Image.open(io.BytesIO(req.response.to_py()))) - mean_deviation = np.mean(np.abs(canvas_data - ref_data)) - window.deviation = mean_deviation - - # converts a `numpy._bool` type explicitly to `bool` - window.result = bool(mean_deviation <= threshold) - - req.onreadystatechange = callback - req.send(None) - - _get_url_async(url, threshold) - def print_png( self, filename_or_obj, *args, metadata=None, pil_kwargs=None, **kwargs ): @@ -163,7 +131,7 @@ def download(self, format, mimetype): mimetype, base64.b64encode(data.getvalue()).decode("ascii") ), ) - element.setAttribute("download", "plot.{}".format(format)) + element.setAttribute("download", f"plot.{format}") element.style.display = "none" document.body.appendChild(element) @@ -185,7 +153,7 @@ def set_capstyle(self, cs): self._capstyle = cs self.renderer.ctx.lineCap = _capstyle_d[cs] else: - raise ValueError("Unrecognized cap style. Found {0}".format(cs)) + raise ValueError(f"Unrecognized cap style. Found {cs}") def set_clip_rectangle(self, rectangle): self.renderer.ctx.save() @@ -214,8 +182,8 @@ def set_dashes(self, dash_offset, dash_list): if dash_list is None: self.renderer.ctx.setLineDash([]) else: - dl = np.asarray(dash_list) - dl = list(self.renderer.points_to_pixels(dl)) + dln = np.asarray(dash_list) + dl = list(self.renderer.points_to_pixels(dln)) self.renderer.ctx.setLineDash(dl) def set_joinstyle(self, js): @@ -223,7 +191,7 @@ def set_joinstyle(self, js): self._joinstyle = js self.renderer.ctx.lineJoin = js else: - raise ValueError("Unrecognized join style. Found {0}".format(js)) + raise ValueError(f"Unrecognized join style. Found {js}") def set_linewidth(self, w): self.stroke = w != 0 @@ -244,6 +212,9 @@ def __init__(self, ctx, width, height, dpi, fig): self.fontd = maxdict(50) self.mathtext_parser = MathTextParser("bitmap") + # Keep the state of fontfaces that are loading + self.fonts_loading = {} + def new_gc(self): return GraphicsContextHTMLCanvas(renderer=self) @@ -266,11 +237,9 @@ def _matplotlib_color_to_CSS(self, color, alpha, alpha_overrides, is_RGB=True): G = int(color[1] * 255) B = int(color[2] * 255) if len(color) == 3 or alpha_overrides: - CSS_color = """rgba({0:d}, {1:d}, {2:d}, {3:.3g})""".format( - R, G, B, alpha - ) + CSS_color = f"""rgba({R:d}, {G:d}, {B:d}, {alpha:.3g})""" else: - CSS_color = """rgba({0:d}, {1:d}, {2:d}, {3:.3g})""".format( + CSS_color = """rgba({:d}, {:d}, {:d}, {:.3g})""".format( R, G, B, color[3] ) @@ -363,9 +332,12 @@ def _get_font(self, prop): return font, font_file_name def get_text_width_height_descent(self, s, prop, ismath): + w: float + h: float if ismath: image, d = self.mathtext_parser.parse(s, self.dpi, prop) - w, h = image.get_width(), image.get_height() + image_arr = np.asarray(image) + h, w = image_arr.shape else: font, _ = self._get_font(prop) font.set_text(s, 0.0, flags=LOAD_NO_HINTING) @@ -390,15 +362,20 @@ def _draw_math_text(self, gc, x, y, s, prop, angle): if angle != 0: self.ctx.restore() - def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): - def _load_font_into_web(loaded_face): - document.fonts.add(loaded_face) - window.font_counter += 1 - self.fig.draw_idle() + def load_font_into_web(self, loaded_face, font_url): + fontface = loaded_face.result() + document.fonts.add(fontface) + self.fonts_loading.pop(font_url, None) + # Redraw figure after font has loaded + self.fig.draw() + return fontface + + def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): if ismath: self._draw_math_text(gc, x, y, s, prop, angle) return + angle = math.radians(angle) width, height, descent = self.get_text_width_height_descent(s, prop, ismath) x -= math.sin(angle) * descent @@ -409,7 +386,7 @@ def _load_font_into_web(loaded_face): font_face_arguments = ( prop.get_name(), - "url({0})".format(_base_fonts_url + font_file_name), + f"url({_base_fonts_url + font_file_name})", ) # The following snippet loads a font into the browser's @@ -421,9 +398,14 @@ def _load_font_into_web(loaded_face): if font_face_arguments not in _font_set: _font_set.add(font_face_arguments) f = FontFace.new(*font_face_arguments) - f.load().then(_load_font_into_web) - font_property_string = "{0} {1} {2:.3g}px {3}, {4}".format( + font_url = font_face_arguments[1] + self.fonts_loading[font_url] = f + f.load().add_done_callback( + lambda result: self.load_font_into_web(result, font_url) + ) + + font_property_string = "{} {} {:.3g}px {}, {}".format( prop.get_style(), prop.get_weight(), font_size, diff --git a/packages/matplotlib/src/wasm_backend.py b/packages/matplotlib/src/wasm_backend.py index ddf403ee8e9..7ae6c62cd44 100644 --- a/packages/matplotlib/src/wasm_backend.py +++ b/packages/matplotlib/src/wasm_backend.py @@ -12,18 +12,15 @@ # TODO: Figure resizing support -from matplotlib.backends.browser_backend import FigureCanvasWasm, NavigationToolbar2Wasm - import base64 import io -from matplotlib.backends import backend_agg -from matplotlib.backend_bases import _Backend, FigureManagerBase from matplotlib import interactive +from matplotlib.backend_bases import FigureManagerBase, _Backend +from matplotlib.backends import backend_agg +from matplotlib.backends.browser_backend import FigureCanvasWasm, NavigationToolbar2Wasm -from js import document -from js import ImageData - +from js import ImageData, document interactive(True) @@ -74,7 +71,7 @@ def download(self, format, mimetype): data = io.BytesIO() try: self.canvas.figure.savefig(data, format=format) - except Exception as e: + except Exception: raise element.setAttribute( "href", @@ -82,7 +79,7 @@ def download(self, format, mimetype): mimetype, base64.b64encode(data.getvalue()).decode("ascii") ), ) - element.setAttribute("download", "plot.{}".format(format)) + element.setAttribute("download", f"plot.{format}") element.style.display = "none" document.body.appendChild(element) element.click() diff --git a/packages/matplotlib/test_matplotlib.py b/packages/matplotlib/test_matplotlib.py index d4a09780923..e982b8c7315 100644 --- a/packages/matplotlib/test_matplotlib.py +++ b/packages/matplotlib/test_matplotlib.py @@ -1,59 +1,83 @@ -import pytest -import os +import base64 import pathlib -from selenium.webdriver.support.wait import WebDriverWait +import textwrap -ROOT_PATH = pathlib.Path(__file__).resolve().parents[2] +import pytest -TEST_PATH = ROOT_PATH / "packages" / "matplotlib" / "reference-images" -TARGET_PATH = ROOT_PATH / "build" / "matplotlib-test" +REFERENCE_IMAGES_PATH = pathlib.Path(__file__).parent / "reference-images" -def get_backend(selenium_standalone): - selenium = selenium_standalone - return selenium.run( - """ - import matplotlib - matplotlib.get_backend() +def run_with_resolve(selenium, code): + selenium.run_js( + f""" + try {{ + let promise = new Promise((resolve) => self.resolve = resolve); + pyodide.runPython({code!r}); + await promise; + }} finally {{ + delete self.resolve; + }} """ ) -def get_canvas_data(selenium, prefix): - import base64 +def patch_font_loading_and_dpi(target_font=""): + """Monkey-patches font loading and dpi to allow testing""" + return textwrap.dedent( + f"""from matplotlib.backends.html5_canvas_backend import RendererHTMLCanvas + from matplotlib.backends.html5_canvas_backend import FigureCanvasHTMLCanvas + FigureCanvasHTMLCanvas.get_dpi_ratio = lambda self, context: 2.0 + load_font_into_web = RendererHTMLCanvas.load_font_into_web + def load_font_into_web_wrapper(self, loaded_font, font_url, orig_function=load_font_into_web): + fontface = orig_function(self, loaded_font, font_url) + + target_font = {target_font!r} + if not target_font or target_font == fontface.family: + try: + from js import resolve + resolve() + except Exception as e: + raise ValueError("unable to resolve") from e + + RendererHTMLCanvas.load_font_into_web = load_font_into_web_wrapper + """ + ) - canvas_tag_property = "//canvas[starts-with(@id, 'matplotlib')]" - canvas_element = selenium.driver.find_element_by_xpath(canvas_tag_property) - img_script = "return arguments[0].toDataURL('image/png').substring(21)" - canvas_base64 = selenium.driver.execute_script(img_script, canvas_element) - canvas_png = base64.b64decode(canvas_base64) - with open( - r"{0}/{1}-{2}.png".format(TEST_PATH, prefix, selenium.browser), "wb" - ) as f: - f.write(canvas_png) +def save_canvas_data(selenium, output_path): + canvas_data = selenium.run( + """ + import base64 + canvas = plt.gcf().canvas.get_element("canvas") + canvas_data = canvas.toDataURL("image/png")[21:] + canvas_data + """ + ) -def check_comparison(selenium, prefix, num_fonts): - font_wait = WebDriverWait(selenium.driver, timeout=70) - font_wait.until(FontsLoaded(num_fonts)) + canvas_png = base64.b64decode(canvas_data) + output_path.write_bytes(canvas_png) - # If we don't have a reference image, write one to disk - if not os.path.isfile( - "{0}/{1}-{2}.png".format(TEST_PATH, prefix, selenium.browser) - ): - get_canvas_data(selenium, prefix) - selenium.run( +def compare_with_reference_image(selenium, reference_image): + reference_image_encoded = base64.b64encode(reference_image.read_bytes()) + deviation = selenium.run( f""" - url = 'http://{selenium.server_hostname}:{selenium.server_port}/matplotlib-test/{prefix}-{selenium.browser}.png' - threshold = 0 - plt.gcf().canvas.compare_reference_image(url, threshold) - """ + import io + import base64 + import numpy as np + from PIL import Image + canvas_data = plt.gcf().canvas.get_pixel_data() + ref_data = np.asarray(Image.open(io.BytesIO(base64.b64decode({reference_image_encoded!r})))) + + deviation = np.mean(np.abs(canvas_data - ref_data)) + float(deviation) + """ ) - wait = WebDriverWait(selenium.driver, timeout=70) - wait.until(ResultLoaded()) - assert selenium.run("window.deviation") == 0 - assert selenium.run("window.result") is True + + # Note: uncomment this line if you want to save the output canvas image (for comparison). + # save_canvas_data(selenium, reference_image.with_name(f"output-{reference_image.name}")) + + return deviation == 0.0 @pytest.mark.skip_refcount_check @@ -78,14 +102,18 @@ def test_svg(selenium): if selenium.browser == "node": pytest.xfail("No supported matplotlib backends on node") selenium.load_package("matplotlib") - selenium.run("from matplotlib import pyplot as plt") - selenium.run("plt.figure(); pass") - selenium.run("x = plt.plot([1,2,3])") - selenium.run("import io") - selenium.run("fd = io.BytesIO()") - selenium.run("plt.savefig(fd, format='svg')") - content = selenium.run("fd.getvalue().decode('utf8')") - assert len(content) == 16283 + content = selenium.run( + """ + from matplotlib import pyplot as plt + import io + plt.figure() + x = plt.plot([1,2,3]) + fd = io.BytesIO() + plt.savefig(fd, format='svg') + fd.getvalue().decode('utf8') + """ + ) + assert len(content) == 14998 assert content.startswith(" \beta_i,\ " - r"\alpha_{i+1}^j = {\rm sin}(2\pi f_j t_i) e^{-5 t_i/\tau},\ " - r"\ldots$", - - 2: r"$\frac{3}{4},\ \binom{3}{4},\ \genfrac{}{}{0}{}{3}{4},\ " - r"\left(\frac{5 - \frac{1}{x}}{4}\right),\ \ldots$", - - 3: r"$\sqrt{2},\ \sqrt[3]{x},\ \ldots$", - - 4: r"$\mathrm{Roman}\ , \ \mathit{Italic}\ , \ \mathtt{Typewriter} \ " - r"\mathrm{or}\ \mathcal{CALLIGRAPHY}$", - - 5: r"$\acute a,\ \bar a,\ \breve a,\ \dot a,\ \ddot a, \ \grave a, \ " - r"\hat a,\ \tilde a,\ \vec a,\ \widehat{xyz},\ \widetilde{xyz},\ " - r"\ldots$", - - 6: r"$\alpha,\ \beta,\ \chi,\ \delta,\ \lambda,\ \mu,\ " - r"\Delta,\ \Gamma,\ \Omega,\ \Phi,\ \Pi,\ \Upsilon,\ \nabla,\ " - r"\aleph,\ \beth,\ \daleth,\ \gimel,\ \ldots$", - - 7: r"$\coprod,\ \int,\ \oint,\ \prod,\ \sum,\ " - r"\log,\ \sin,\ \approx,\ \oplus,\ \star,\ \varpropto,\ " - r"\infty,\ \partial,\ \Re,\ \leftrightsquigarrow, \ \ldots$"} - - - def doall(): - # Colors used in mpl online documentation. - mpl_blue_rvb = (191. / 255., 209. / 256., 212. / 255.) - mpl_orange_rvb = (202. / 255., 121. / 256., 0. / 255.) - mpl_grey_rvb = (51. / 255., 51. / 255., 51. / 255.) - - # Creating figure and axis. - plt.figure(figsize=(6, 7)) - plt.axes([0.01, 0.01, 0.98, 0.90], facecolor="white", frameon=True) - plt.gca().set_xlim(0., 1.) - plt.gca().set_ylim(0., 1.) - plt.gca().set_title("Matplotlib's math rendering engine", - color=mpl_grey_rvb, fontsize=14, weight='bold') - plt.gca().set_xticklabels("", visible=False) - plt.gca().set_yticklabels("", visible=False) - - # Gap between lines in axes coords - line_axesfrac = (1. / (n_lines)) - - # Plotting header demonstration formula - full_demo = mathext_demos[0] - plt.annotate(full_demo, - xy=(0.5, 1. - 0.59 * line_axesfrac), - color=mpl_orange_rvb, ha='center', fontsize=20) - - # Plotting features demonstration formulae - for i_line in range(1, n_lines): - baseline = 1 - (i_line) * line_axesfrac - baseline_next = baseline - line_axesfrac - title = mathtext_titles[i_line] + ":" - fill_color = ['white', mpl_blue_rvb][i_line % 2] - plt.fill_between([0., 1.], [baseline, baseline], - [baseline_next, baseline_next], - color=fill_color, alpha=0.5) - plt.annotate(title, - xy=(0.07, baseline - 0.3 * line_axesfrac), - color=mpl_grey_rvb, weight='bold') - demo = mathext_demos[i_line] - plt.annotate(demo, - xy=(0.05, baseline - 0.75 * line_axesfrac), - color=mpl_grey_rvb, fontsize=16) - - for i in range(n_lines): - s = mathext_demos[i] - print(i, s) - plt.show() - doall() - """ - ) - - check_comparison(selenium, "canvas-math-text", 1) - finally: - TARGET_PATH.unlink() + selenium.set_script_timeout(60) + run_with_resolve( + selenium, + f""" + {patch_font_loading_and_dpi()} + """ + + r""" + import os + os.environ["TESTING_MATPLOTLIB"] = "1" + + import matplotlib + matplotlib.use("module://matplotlib.backends.html5_canvas_backend") + from js import window + window.testingMatplotlib = True + import matplotlib.pyplot as plt + import sys + import re + + # Selection of features following + # "Writing mathematical expressions" tutorial + mathtext_titles = { + 0: "Header demo", + 1: "Subscripts and superscripts", + 2: "Fractions, binomials and stacked numbers", + 3: "Radicals", + 4: "Fonts", + 5: "Accents", + 6: "Greek, Hebrew", + 7: "Delimiters, functions and Symbols"} + n_lines = len(mathtext_titles) + + # Randomly picked examples + mathext_demos = { + 0: r"$W^{3\beta}_{\delta_1 \rho_1 \sigma_2} = " + r"U^{3\beta}_{\delta_1 \rho_1} + \frac{1}{8 \pi 2} " + r"\int^{\alpha_2}_{\alpha_2} d \alpha^\prime_2 \left[\frac{ " + r"U^{2\beta}_{\delta_1 \rho_1} - \alpha^\prime_2U^{1\beta}_" + r"{\rho_1 \sigma_2} }{U^{0\beta}_{\rho_1 \sigma_2}}\right]$", + + 1: r"$\alpha_i > \beta_i,\ " + r"\alpha_{i+1}^j = {\rm sin}(2\pi f_j t_i) e^{-5 t_i/\tau},\ " + r"\ldots$", + + 2: r"$\frac{3}{4},\ \binom{3}{4},\ \genfrac{}{}{0}{}{3}{4},\ " + r"\left(\frac{5 - \frac{1}{x}}{4}\right),\ \ldots$", + + 3: r"$\sqrt{2},\ \sqrt[3]{x},\ \ldots$", + + 4: r"$\mathrm{Roman}\ , \ \mathit{Italic}\ , \ \mathtt{Typewriter} \ " + r"\mathrm{or}\ \mathcal{CALLIGRAPHY}$", + + 5: r"$\acute a,\ \bar a,\ \breve a,\ \dot a,\ \ddot a, \ \grave a, \ " + r"\hat a,\ \tilde a,\ \vec a,\ \widehat{xyz},\ \widetilde{xyz},\ " + r"\ldots$", + + 6: r"$\alpha,\ \beta,\ \chi,\ \delta,\ \lambda,\ \mu,\ " + r"\Delta,\ \Gamma,\ \Omega,\ \Phi,\ \Pi,\ \Upsilon,\ \nabla,\ " + r"\aleph,\ \beth,\ \daleth,\ \gimel,\ \ldots$", + + 7: r"$\coprod,\ \int,\ \oint,\ \prod,\ \sum,\ " + r"\log,\ \sin,\ \approx,\ \oplus,\ \star,\ \varpropto,\ " + r"\infty,\ \partial,\ \Re,\ \leftrightsquigarrow, \ \ldots$"} + + + def doall(): + # Colors used in mpl online documentation. + mpl_blue_rvb = (191. / 255., 209. / 256., 212. / 255.) + mpl_orange_rvb = (202. / 255., 121. / 256., 0. / 255.) + mpl_grey_rvb = (51. / 255., 51. / 255., 51. / 255.) + + # Creating figure and axis. + plt.figure(figsize=(6, 7)) + plt.axes([0.01, 0.01, 0.98, 0.90], facecolor="white", frameon=True) + plt.gca().set_xlim(0., 1.) + plt.gca().set_ylim(0., 1.) + plt.gca().set_title("Matplotlib's math rendering engine", + color=mpl_grey_rvb, fontsize=14, weight='bold') + plt.gca().set_xticklabels("", visible=False) + plt.gca().set_yticklabels("", visible=False) + + # Gap between lines in axes coords + line_axesfrac = (1. / (n_lines)) + + # Plotting header demonstration formula + full_demo = mathext_demos[0] + plt.annotate(full_demo, + xy=(0.5, 1. - 0.59 * line_axesfrac), + color=mpl_orange_rvb, ha='center', fontsize=20) + + # Plotting features demonstration formulae + for i_line in range(1, n_lines): + baseline = 1 - (i_line) * line_axesfrac + baseline_next = baseline - line_axesfrac + title = mathtext_titles[i_line] + ":" + fill_color = ['white', mpl_blue_rvb][i_line % 2] + plt.fill_between([0., 1.], [baseline, baseline], + [baseline_next, baseline_next], + color=fill_color, alpha=0.5) + plt.annotate(title, + xy=(0.07, baseline - 0.3 * line_axesfrac), + color=mpl_grey_rvb, weight='bold') + demo = mathext_demos[i_line] + plt.annotate(demo, + xy=(0.05, baseline - 0.75 * line_axesfrac), + color=mpl_grey_rvb, fontsize=16) + + for i in range(n_lines): + s = mathext_demos[i] + print(i, s) + plt.show() + doall() + """, + ) + + assert compare_with_reference_image( + selenium, REFERENCE_IMAGES_PATH / f"canvas-math-text-{selenium.browser}.png" + ) @pytest.mark.skip_refcount_check @pytest.mark.skip_pyproxy_check -def test_custom_font_text(selenium): +def test_custom_font_text(selenium_standalone): + selenium = selenium_standalone if selenium.browser == "node": pytest.xfail("No supported matplotlib backends on node") + selenium.load_package("matplotlib") - if get_backend(selenium) == "module://matplotlib.backends.wasm_backend": - pytest.skip( - "test supported only for html5 canvas backend. wasm backend is currently used" - ) - else: - TARGET_PATH.symlink_to(TEST_PATH, True) - try: - selenium.run( - """ - from js import window - window.testing = True - import matplotlib.pyplot as plt - import numpy as np - - f = {'fontname': 'cmsy10'} - - t = np.arange(0.0, 2.0, 0.01) - s = 1 + np.sin(2 * np.pi * t) - plt.figure() - plt.title('A simple Sine Curve', **f) - plt.plot(t, s, linewidth=1.0, marker=11) - plt.plot(t, t) - plt.grid(True) - plt.show() - """ - ) - - check_comparison(selenium, "canvas-custom-font-text", 2) - finally: - TARGET_PATH.unlink() + selenium.set_script_timeout(60) + run_with_resolve( + selenium, + f""" + {patch_font_loading_and_dpi(target_font='cmsy10')} + import matplotlib + matplotlib.use("module://matplotlib.backends.html5_canvas_backend") + import matplotlib.pyplot as plt + import numpy as np + + f = {{'fontname': 'cmsy10'}} + + t = np.arange(0.0, 2.0, 0.01) + s = 1 + np.sin(2 * np.pi * t) + plt.figure() + plt.title('A simple Sine Curve', **f) + plt.plot(t, s, linewidth=1.0, marker=11) + plt.plot(t, t) + plt.grid(True) + plt.show() + """, + ) + + assert compare_with_reference_image( + selenium, + REFERENCE_IMAGES_PATH / f"canvas-custom-font-text-{selenium.browser}.png", + ) @pytest.mark.skip_refcount_check @pytest.mark.skip_pyproxy_check -def test_zoom_on_polar_plot(selenium): +def test_zoom_on_polar_plot(selenium_standalone): + selenium = selenium_standalone if selenium.browser == "node": pytest.xfail("No supported matplotlib backends on node") + selenium.load_package("matplotlib") - if get_backend(selenium) == "module://matplotlib.backends.wasm_backend": - pytest.skip( - "test supported only for html5 canvas backend. wasm backend is currently used" - ) - else: - TARGET_PATH.symlink_to(TEST_PATH, True) - try: - selenium.run( - """ - from js import window - window.testing = True - - import numpy as np - import matplotlib.pyplot as plt - np.random.seed(42) - - # Compute pie slices - N = 20 - theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False) - radii = 10 * np.random.rand(N) - width = np.pi / 4 * np.random.rand(N) - - ax = plt.subplot(111, projection='polar') - bars = ax.bar(theta, radii, width=width, bottom=0.0) - - # Use custom colors and opacity - for r, bar in zip(radii, bars): - bar.set_facecolor(plt.cm.viridis(r / 10.)) - bar.set_alpha(0.5) - - ax.set_rlim([0,5]) - plt.show() - """ - ) - - check_comparison(selenium, "canvas-polar-zoom", 1) - finally: - TARGET_PATH.unlink() + selenium.set_script_timeout(60) + run_with_resolve( + selenium, + f""" + {patch_font_loading_and_dpi()} + import matplotlib + matplotlib.use("module://matplotlib.backends.html5_canvas_backend") + import numpy as np + import matplotlib.pyplot as plt + np.random.seed(42) + + # Compute pie slices + N = 20 + theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False) + radii = 10 * np.random.rand(N) + width = np.pi / 4 * np.random.rand(N) + + ax = plt.subplot(111, projection='polar') + bars = ax.bar(theta, radii, width=width, bottom=0.0) + + # Use custom colors and opacity + for r, bar in zip(radii, bars): + bar.set_facecolor(plt.cm.viridis(r / 10.)) + bar.set_alpha(0.5) + + ax.set_rlim([0,5]) + plt.show() + """, + ) + + assert compare_with_reference_image( + selenium, REFERENCE_IMAGES_PATH / f"canvas-polar-zoom-{selenium.browser}.png" + ) @pytest.mark.skip_refcount_check @pytest.mark.skip_pyproxy_check -def test_transparency(selenium): +def test_transparency(selenium_standalone): + selenium = selenium_standalone if selenium.browser == "node": pytest.xfail("No supported matplotlib backends on node") - selenium.load_package("matplotlib") - if get_backend(selenium) == "module://matplotlib.backends.wasm_backend": - pytest.skip( - "test supported only for html5 canvas backend. wasm backend is currently used" - ) - else: - TARGET_PATH.symlink_to(TEST_PATH, True) - try: - selenium.run( - """ - from js import window - window.testing = True - - import numpy as np - np.random.seed(19680801) - import matplotlib.pyplot as plt - - fig, ax = plt.subplots() - for color in ['tab:blue', 'tab:orange', 'tab:green']: - n = 100 - x, y = np.random.rand(2, n) - scale = 200.0 * np.random.rand(n) - ax.scatter(x, y, c=color, s=scale, label=color, - alpha=0.3, edgecolors='none') - - ax.legend() - ax.grid(True) - - plt.show() - """ - ) - - check_comparison(selenium, "canvas-transparency", 1) - finally: - TARGET_PATH.unlink() - - -class ResultLoaded: - def __call__(self, driver): - inited = driver.execute_script("return window.result") - return inited is not None + selenium.load_package("matplotlib") + selenium.set_script_timeout(60) + run_with_resolve( + selenium, + f""" + {patch_font_loading_and_dpi()} + import matplotlib + matplotlib.use("module://matplotlib.backends.html5_canvas_backend") + import numpy as np + np.random.seed(19680801) + import matplotlib.pyplot as plt + + fig, ax = plt.subplots() + for color in ['tab:blue', 'tab:orange', 'tab:green']: + n = 100 + x, y = np.random.rand(2, n) + scale = 200.0 * np.random.rand(n) + ax.scatter(x, y, c=color, s=scale, label=color, + alpha=0.3, edgecolors='none') + + ax.legend() + ax.grid(True) -class FontsLoaded: - def __init__(self, num_fonts): - self.num_fonts = num_fonts + plt.show() + """, + ) - def __call__(self, driver): - font_inited = driver.execute_script("return window.font_counter") - return font_inited is not None and font_inited == self.num_fonts + assert compare_with_reference_image( + selenium, REFERENCE_IMAGES_PATH / f"canvas-transparency-{selenium.browser}.png" + ) diff --git a/packages/micropip/meta.yaml b/packages/micropip/meta.yaml index e25cd7fbbff..923eb6ee9cc 100644 --- a/packages/micropip/meta.yaml +++ b/packages/micropip/meta.yaml @@ -1,6 +1,7 @@ package: name: micropip version: "0.1" + _tag: pyodide.stdlib requirements: run: - pyparsing diff --git a/packages/micropip/src/micropip/__init__.py b/packages/micropip/src/micropip/__init__.py index 3e63b06ef07..7b845e6c29d 100644 --- a/packages/micropip/src/micropip/__init__.py +++ b/packages/micropip/src/micropip/__init__.py @@ -1,3 +1,4 @@ -from ._micropip import install, _list as list +from ._micropip import _list as list +from ._micropip import install __all__ = ["install", "list"] diff --git a/packages/micropip/src/micropip/_micropip.py b/packages/micropip/src/micropip/_micropip.py index 1a8a842ffba..2b6512d246b 100644 --- a/packages/micropip/src/micropip/_micropip.py +++ b/packages/micropip/src/micropip/_micropip.py @@ -6,20 +6,18 @@ import io import json import tempfile -from importlib.metadata import version as get_version +from pathlib import Path +from typing import Any +from zipfile import ZipFile +from packaging.markers import default_environment from packaging.requirements import Requirement from packaging.version import Version -from packaging.markers import default_environment -from pathlib import Path -from typing import Dict, Any, Union, List, Tuple, Optional -from zipfile import ZipFile +from pyodide import IN_BROWSER, to_js from .externals.pip._internal.utils.wheel import pkg_resources_distribution_for_wheel -from .package import PackageDict, PackageMetadata - -from pyodide import IN_BROWSER, to_js +from .package import PackageDict, PackageMetadata, normalize_package_name # Provide stubs for testing in native python if IN_BROWSER: @@ -36,7 +34,7 @@ WHEEL_BASE = Path(tempfile.mkdtemp()) if IN_BROWSER: - BUILTIN_PACKAGES = pyodide_js._module.packages.to_py() + BUILTIN_PACKAGES = pyodide_js._api.packages.to_py() else: BUILTIN_PACKAGES = {} @@ -44,7 +42,7 @@ from pyodide_js import loadedPackages else: - class loadedPackages: # type: ignore + class loadedPackages: # type: ignore[no-redef] @staticmethod def to_py(): return {} @@ -60,7 +58,7 @@ async def fetch_string(url: str, **kwargs) -> str: return await (await pyfetch(url, **kwargs)).string() else: - from urllib.request import urlopen, Request + from urllib.request import Request, urlopen async def fetch_bytes(url: str, **kwargs) -> bytes: return urlopen(Request(url, headers=kwargs)).read() @@ -76,23 +74,23 @@ async def fetch_string(url: str, **kwargs) -> str: # we want to avoid using the event loop at all. Instead just run the # coroutines in sequence. # TODO: Use an asyncio testing framework to avoid this - async def gather(*coroutines): # type: ignore + async def gather(*coroutines): # type: ignore[no-redef] result = [] for coroutine in coroutines: result.append(await coroutine) return result -async def _get_pypi_json(pkgname): +async def _get_pypi_json(pkgname, **fetch_extra_kwargs): url = f"https://pypi.org/pypi/{pkgname}/json" - return json.loads(await fetch_string(url)) + return json.loads(await fetch_string(url, **fetch_extra_kwargs)) def _is_pure_python_wheel(filename: str): return filename.endswith("py3-none-any.whl") -def _parse_wheel_url(url: str) -> Tuple[str, Dict[str, Any], str]: +def _parse_wheel_url(url: str) -> tuple[str, dict[str, Any], str]: """Parse wheels URL and extract available metadata See https://www.python.org/dev/peps/pep-0427/#file-name-convention @@ -150,16 +148,17 @@ def __init__(self): async def gather_requirements( self, - requirements: Union[str, List[str]], + requirements: str | list[str], ctx=None, keep_going: bool = False, + **kwargs, ): ctx = ctx or default_environment() ctx.setdefault("extra", None) if isinstance(requirements, str): requirements = [requirements] - transaction: Dict[str, Any] = { + transaction: dict[str, Any] = { "wheels": [], "pyodide_packages": [], "locked": copy.deepcopy(self.installed_packages), @@ -169,20 +168,30 @@ async def gather_requirements( requirement_promises = [] for requirement in requirements: requirement_promises.append( - self.add_requirement(requirement, ctx, transaction) + self.add_requirement(requirement, ctx, transaction, **kwargs) ) await gather(*requirement_promises) return transaction async def install( - self, requirements: Union[str, List[str]], ctx=None, keep_going: bool = False + self, + requirements: str | list[str], + ctx=None, + keep_going: bool = False, + credentials: str | None = None, ): async def _install(install_func, done_callback): await install_func done_callback() - transaction = await self.gather_requirements(requirements, ctx, keep_going) + fetch_extra_kwargs = dict() + + if credentials: + fetch_extra_kwargs["credentials"] = credentials + transaction = await self.gather_requirements( + requirements, ctx, keep_going, **fetch_extra_kwargs + ) if transaction["failed"]: failed_requirements = ", ".join( @@ -207,7 +216,10 @@ async def _install(install_func, done_callback): ), functools.partial( self.installed_packages.update, - {pkg.name: pkg for pkg in pyodide_packages}, + { + normalize_package_name(pkg.name): pkg + for pkg in pyodide_packages + }, ), ) ) @@ -222,7 +234,11 @@ async def _install(install_func, done_callback): _install_wheel(name, wheel), functools.partial( self.installed_packages.update, - {name: PackageMetadata(name, str(ver), wheel_source)}, + { + normalize_package_name(name): PackageMetadata( + name, str(ver), wheel_source + ) + }, ), ) ) @@ -230,7 +246,11 @@ async def _install(install_func, done_callback): await gather(*wheel_promises) async def add_requirement( - self, requirement: Union[str, Requirement], ctx, transaction + self, + requirement: str | Requirement, + ctx, + transaction, + **fetch_extra_kwargs, ): """Add a requirement to the transaction. @@ -246,7 +266,9 @@ async def add_requirement( if not _is_pure_python_wheel(wheel["filename"]): raise ValueError(f"'{wheel['filename']}' is not a pure Python 3 wheel") - await self.add_wheel(name, wheel, version, (), ctx, transaction) + await self.add_wheel( + name, wheel, version, (), ctx, transaction, **fetch_extra_kwargs + ) return else: req = Requirement(requirement) @@ -281,9 +303,9 @@ async def add_requirement( f"Requested '{requirement}', " f"but {req.name}=={ver} is already installed" ) - metadata = await _get_pypi_json(req.name) - wheel, ver = self.find_wheel(metadata, req) - if wheel is None and ver is None: + metadata = await _get_pypi_json(req.name, **fetch_extra_kwargs) + maybe_wheel, maybe_ver = self.find_wheel(metadata, req) + if maybe_wheel is None or maybe_ver is None: if transaction["keep_going"]: transaction["failed"].append(req) else: @@ -292,23 +314,55 @@ async def add_requirement( "You can use `micropip.install(..., keep_going=True)` to get a list of all packages with missing wheels." ) else: - await self.add_wheel(req.name, wheel, ver, req.extras, ctx, transaction) + await self.add_wheel( + req.name, + maybe_wheel, + maybe_ver, + req.extras, + ctx, + transaction, + **fetch_extra_kwargs, + ) + + async def add_wheel( + self, name, wheel, version, extras, ctx, transaction, **fetch_extra_kwargs + ): + normalized_name = normalize_package_name(name) + transaction["locked"][normalized_name] = PackageMetadata( + name=name, + version=version, + ) + + try: + wheel_bytes = await fetch_bytes(wheel["url"], **fetch_extra_kwargs) + except Exception as e: + if wheel["url"].startswith("https://files.pythonhosted.org/"): + raise e + else: + raise ValueError( + f"Couldn't fetch wheel from '{wheel['url']}'." + "One common reason for this is when the server blocks " + "Cross-Origin Resource Sharing (CORS)." + "Check if the server is sending the correct 'Access-Control-Allow-Origin' header." + ) from e - async def add_wheel(self, name, wheel, version, extras, ctx, transaction): - transaction["locked"][name] = PackageMetadata(name=name, version=version) - wheel_bytes = await fetch_bytes(wheel["url"]) wheel["wheel_bytes"] = wheel_bytes - with ZipFile(io.BytesIO(wheel_bytes)) as zip_file: # type: ignore + with ZipFile(io.BytesIO(wheel_bytes)) as zip_file: dist = pkg_resources_distribution_for_wheel(zip_file, name, "???") + + project_name = dist.project_name + if project_name == "UNKNOWN": + project_name = name + for recurs_req in dist.requires(extras): await self.add_requirement(recurs_req, ctx, transaction) - transaction["wheels"].append((name, wheel, version)) + transaction["wheels"].append((project_name, wheel, version)) def find_wheel( - self, metadata: Dict[str, Any], req: Requirement - ) -> Tuple[Any, Optional[Version]]: + self, metadata: dict[str, Any], req: Requirement + ) -> tuple[Any | None, Version | None]: """Parse metadata to find the latest version of pure python wheel. Parameters @@ -327,7 +381,7 @@ def find_wheel( """ releases = metadata.get("releases", {}) candidate_versions = sorted( - (Version(v) for v in req.specifier.filter(releases)), # type: ignore + (Version(v) for v in req.specifier.filter(releases)), reverse=True, ) for ver in candidate_versions: @@ -344,7 +398,11 @@ def find_wheel( del _PackageManager -def install(requirements: Union[str, List[str]], keep_going: bool = False): +def install( + requirements: str | list[str], + keep_going: bool = False, + credentials: str | None = None, +): """Install the given package and all of its dependencies. See :ref:`loading packages ` for more information. @@ -383,6 +441,15 @@ def install(requirements: Union[str, List[str]], keep_going: bool = False): - If ``True``, the micropip will keep going after the first error, and report a list of errors at the end. + credentials : ``Optional[str]`` + + This parameter specifies the value of ``credentials`` when calling the + `fetch() `__ function + which is used to download the package. + + When not specified, ``fetch()`` is called without ``credentials``. + + Returns ------- ``Future`` @@ -392,7 +459,9 @@ def install(requirements: Union[str, List[str]], keep_going: bool = False): """ importlib.invalidate_caches() return asyncio.ensure_future( - PACKAGE_MANAGER.install(requirements, keep_going=keep_going) + PACKAGE_MANAGER.install( + requirements, keep_going=keep_going, credentials=credentials + ) ) diff --git a/packages/micropip/src/micropip/package.py b/packages/micropip/src/micropip/package.py index 042c2c59151..72573df1023 100644 --- a/packages/micropip/src/micropip/package.py +++ b/packages/micropip/src/micropip/package.py @@ -1,27 +1,25 @@ +import re from collections import UserDict -from dataclasses import dataclass, field, astuple -from pathlib import Path -from typing import List, Dict, Iterable +from dataclasses import astuple, dataclass +from typing import Iterable __all__ = ["PackageDict"] -def _format_table(headers: List[str], table: List[Iterable]) -> str: - # fmt: off +def _format_table(headers: list[str], table: list[Iterable]) -> str: """ Returns a minimal formatted table >>> print(_format_table(["Header1", "Header2"], [["val1", "val2"], ["val3", "val4"]])) Header1 | Header2 ------- | ------- - val1 | val2 - val3 | val4 + val1 | val2 + val3 | val4 """ - # fmt: on def format_row(values, widths, filler=""): row = " | ".join(f"{x:{filler}<{w}}" for x, w in zip(values, widths)) - return row + return row.rstrip() col_width = [max(len(x) for x in col) for col in zip(headers, *table)] rows = [] @@ -34,6 +32,10 @@ def format_row(values, widths, filler=""): return "\n".join(rows) +def normalize_package_name(pkgname: str): + return re.sub(r"[^\w\d.]+", "_", pkgname, re.UNICODE).lower() + + @dataclass class PackageMetadata: name: str @@ -57,6 +59,14 @@ class PackageDict(UserDict): def __repr__(self): return self._tabularize() + def __getitem__(self, key): + normalized_key = normalize_package_name(key) + return super().__getitem__(normalized_key) + + def __contains__(self, key): + normalized_key = normalize_package_name(key) + return super().__contains__(normalized_key) + def _tabularize(self): headers = [key.capitalize() for key in PackageMetadata.keys()] table = list(self.values()) diff --git a/packages/micropip/src/setup.py b/packages/micropip/src/setup.py index d17e0ecdb9a..1ac878f009a 100755 --- a/packages/micropip/src/setup.py +++ b/packages/micropip/src/setup.py @@ -2,7 +2,6 @@ from setuptools import find_packages, setup - setup( name="micropip", version="0.1", diff --git a/packages/micropip/test_micropip.py b/packages/micropip/test_micropip.py index 6dd8da4e575..c585d8740ef 100644 --- a/packages/micropip/test_micropip.py +++ b/packages/micropip/test_micropip.py @@ -1,11 +1,14 @@ import asyncio import io import sys -from pathlib import Path import zipfile +from pathlib import Path +from typing import Any import pytest +from pyodide_build.testing import run_in_pyodide + sys.path.append(str(Path(__file__).resolve().parent / "src")) @@ -26,7 +29,7 @@ def mock_get_pypi_json(pkg_map): A mock function of ``_get_pypi_json`` which returns dummy JSON data of PyPI API. """ - class Wildcard(object): + class Wildcard: def __eq__(self, other): return True @@ -163,7 +166,7 @@ def test_install_custom_url(selenium_standalone_micropip, base_url): root = Path(__file__).resolve().parents[2] src = root / "src" / "tests" / "data" - target = root / "build" / "test_data" + target = root / "dist" / "test_data" target.symlink_to(src, True) path = "/test_data/snowballstemmer-2.0.0-py2.py3-none-any.whl" try: @@ -190,7 +193,10 @@ def test_add_requirement(web_server_tst_data): base_url = f"http://{server_hostname}:{server_port}/" url = base_url + "snowballstemmer-2.0.0-py2.py3-none-any.whl" - transaction = {"wheels": [], "locked": {}} + transaction: dict[str, Any] = { + "wheels": [], + "locked": {}, + } asyncio.get_event_loop().run_until_complete( _micropip.PACKAGE_MANAGER.add_requirement(url, {}, transaction) ) @@ -230,9 +236,10 @@ def test_add_requirement_marker(): def test_last_version_from_pypi(): pytest.importorskip("packaging") - from micropip import _micropip from packaging.requirements import Requirement + from micropip import _micropip + requirement = Requirement("dummy_module") versions = ["0.0.1", "0.15.5", "0.9.1"] @@ -257,7 +264,7 @@ def test_install_non_pure_python_wheel(): msg = "not a pure Python 3 wheel" with pytest.raises(ValueError, match=msg): url = "http://scikit_learn-0.22.2.post1-cp35-cp35m-macosx_10_9_intel.whl" - transaction = {"wheels": [], "locked": {}} + transaction = {"wheels": list[Any](), "locked": dict[str, Any]()} asyncio.get_event_loop().run_until_complete( _micropip.PACKAGE_MANAGER.add_requirement(url, {}, transaction) ) @@ -345,6 +352,22 @@ def test_install_keep_going(monkeypatch): ) +def test_fetch_wheel_fail(monkeypatch): + pytest.importorskip("packaging") + from micropip import _micropip + + def _mock_fetch_bytes(*args, **kwargs): + raise Exception("Failed to fetch") + + monkeypatch.setattr(_micropip, "fetch_bytes", _mock_fetch_bytes) + + msg = "Access-Control-Allow-Origin" + with pytest.raises(ValueError, match=msg): + asyncio.get_event_loop().run_until_complete( + _micropip.install("htps://x.com/xxx-1.0.0-py3-none-any.whl") + ) + + def test_list_pypi_package(monkeypatch): pytest.importorskip("packaging") from micropip import _micropip @@ -380,6 +403,27 @@ def test_list_wheel_package(monkeypatch): assert "dummy" in pkg_list and pkg_list["dummy"].source.lower() == dummy_url +def test_list_wheel_name_mismatch(monkeypatch): + pytest.importorskip("packaging") + from micropip import _micropip + + dummy_pkg_name = "dummy-Dummy" + normalized_pkg_name = dummy_pkg_name.replace("-", "_").lower() + dummy_url = f"https://dummy.com/{normalized_pkg_name}-1.0.0-py3-none-any.whl" + _mock_fetch_bytes = mock_fetch_bytes(dummy_pkg_name, f"Name: {dummy_pkg_name}") + + monkeypatch.setattr(_micropip, "fetch_bytes", _mock_fetch_bytes) + + asyncio.get_event_loop().run_until_complete(_micropip.install(dummy_url)) + + pkg_list = _micropip._list() + assert ( + dummy_pkg_name in pkg_list + and pkg_list[dummy_pkg_name].source.lower() == dummy_url + and pkg_list[dummy_pkg_name].name == dummy_pkg_name + ) + + def test_list_pyodide_package(selenium_standalone_micropip): selenium = selenium_standalone_micropip selenium.run_js( @@ -415,3 +459,33 @@ def test_list_loaded_from_js(selenium_standalone_micropip): `); """ ) + + +@pytest.mark.skip_refcount_check +@run_in_pyodide(packages=["micropip"]) +async def test_install_with_credentials(): + import json + from unittest.mock import MagicMock, patch + + import micropip + + fetch_response_mock = MagicMock() + + async def myfunc(): + return json.dumps(dict()) + + fetch_response_mock.string.side_effect = myfunc + + @patch("micropip._micropip.pyfetch", return_value=fetch_response_mock) + async def call_micropip_install(pyfetch_mock): + try: + await micropip.install("pyodide-micropip-test", credentials="include") + except BaseException: + # The above will raise an exception as the mock data is garbage + # but it is sufficient for this test + pass + pyfetch_mock.assert_called_with( + "https://pypi.org/pypi/pyodide-micropip-test/json", credentials="include" + ) + + await call_micropip_install() diff --git a/packages/mne/meta.yaml b/packages/mne/meta.yaml index 156589f40c8..c23a8589fc2 100644 --- a/packages/mne/meta.yaml +++ b/packages/mne/meta.yaml @@ -1,15 +1,16 @@ package: name: mne - version: 0.24.1 + version: 1.0.0 source: - sha256: 38cbffd03a6ad0e83ef4a964ac9910a37d164c37fcc84894e39ed0cdf805300d - url: https://files.pythonhosted.org/packages/b0/64/8cd2716407139822b268ed65662ec8ef0880aa0cd86c715b698b49a4c7e7/mne-0.24.1.tar.gz + sha256: fe754938aac35a4a1c3d321e6a584f100bfdbaa0a380de9f395cb95c48d1f9a7 + url: https://files.pythonhosted.org/packages/cd/3c/223fd3fd4529c3d9f4786a9a8e6873adaaeff2c5fa873e233f50cfdeba44/mne-1.0.0-py3-none-any.whl requirements: run: - distutils - numpy - scipy - setuptools + - decorator test: imports: - mne diff --git a/packages/more-itertools/meta.yaml b/packages/more-itertools/meta.yaml index f0013ce0d96..258a21bcf5b 100644 --- a/packages/more-itertools/meta.yaml +++ b/packages/more-itertools/meta.yaml @@ -1,9 +1,9 @@ package: name: more-itertools - version: 8.8.0 + version: 8.12.0 source: - sha256: 83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a - url: https://files.pythonhosted.org/packages/03/0a/4769cc0557fbe690f86bc4c183faa116f1050e5c9296f1b7b5806f063cdb/more-itertools-8.8.0.tar.gz + sha256: 43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b + url: https://files.pythonhosted.org/packages/e5/c3/48e2c81038f57e8caab9a6e6fb6c2fc23536c59b092abefc447e6b5d1903/more_itertools-8.12.0-py3-none-any.whl test: imports: - more_itertools diff --git a/packages/mpmath/meta.yaml b/packages/mpmath/meta.yaml index baf72b2afd9..b0a1d65ad96 100644 --- a/packages/mpmath/meta.yaml +++ b/packages/mpmath/meta.yaml @@ -2,8 +2,8 @@ package: name: mpmath version: 1.2.1 source: - sha256: 79ffb45cf9f4b101a807595bcb3e72e0396202e0b1d25d689134b48c4216a81a - url: https://files.pythonhosted.org/packages/95/ba/7384cb4db4ed474d4582944053549e02ec25da630810e4a23454bc9fa617/mpmath-1.2.1.tar.gz + sha256: 604bc21bd22d2322a177c73bdb573994ef76e62edd595d17e00aff24b0667e5c + url: https://files.pythonhosted.org/packages/d4/cf/3965bddbb4f1a61c49aacae0e78fd1fe36b5dc36c797b31f30cf07dcbbb7/mpmath-1.2.1-py3-none-any.whl test: imports: - mpmath diff --git a/packages/msgpack/test_pack.py b/packages/msgpack/test_pack.py index 3cb98f9e6d7..bd2967f9d8c 100644 --- a/packages/msgpack/test_pack.py +++ b/packages/msgpack/test_pack.py @@ -3,7 +3,7 @@ @run_in_pyodide(standalone=True, packages=["msgpack"]) def test_pack(): - from msgpack import packb, unpackb, Unpacker, Packer, pack + from msgpack import Packer, Unpacker, pack, packb, unpackb # noqa: F401 def check(data, use_list=False): re = unpackb(packb(data), use_list=use_list, strict_map_key=False) diff --git a/packages/networkx/meta.yaml b/packages/networkx/meta.yaml index b48d8089b7f..aee7bdc01ae 100644 --- a/packages/networkx/meta.yaml +++ b/packages/networkx/meta.yaml @@ -1,6 +1,6 @@ package: name: networkx - version: 2.6.3 + version: 2.7.1 requirements: run: - decorator @@ -8,8 +8,8 @@ requirements: - matplotlib - numpy source: - sha256: c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51 - url: https://files.pythonhosted.org/packages/97/ae/7497bc5e1c84af95e585e3f98585c9f06c627fac6340984c4243053e8f44/networkx-2.6.3.tar.gz + sha256: 011e85d277c89681e8fa661cf5ff0743443445049b0b68789ad55ef09340c6e0 + url: https://files.pythonhosted.org/packages/f6/34/4913f651b8e178dde5abcf8d62495e4dcd0757a9a6840f1b1f7a290afaea/networkx-2.7.1-py3-none-any.whl test: imports: - networkx diff --git a/packages/nlopt/src/setup.py b/packages/nlopt/src/setup.py index 3ec9b902672..13be314bbc5 100644 --- a/packages/nlopt/src/setup.py +++ b/packages/nlopt/src/setup.py @@ -3,9 +3,10 @@ import re from pathlib import Path from subprocess import check_call -from setuptools import setup, Extension -from setuptools.command.build_py import build_py + from numpy import get_include +from setuptools import Extension, setup +from setuptools.command.build_py import build_py def create_pkg_directory(): diff --git a/packages/nlopt/test_nlopt.py b/packages/nlopt/test_nlopt.py index b9bf24dc0cc..a5e690195b0 100644 --- a/packages/nlopt/test_nlopt.py +++ b/packages/nlopt/test_nlopt.py @@ -8,8 +8,8 @@ }, ) def test_nlopt(): - import numpy as np import nlopt + import numpy as np # objective function def f(x, grad): @@ -19,9 +19,9 @@ def f(x, grad): 67.8306620138889 - 13.5689721666667 * x0 - 3.83269458333333 * x1 - + 0.720841066666667 * x0 ** 2 + + 0.720841066666667 * x0**2 + 0.3427605 * x0 * x1 - + 0.0640322916666664 * x1 ** 2 + + 0.0640322916666664 * x1**2 ) grad[0] = 1.44168213333333 * x0 + 0.3427605 * x1 - 13.5689721666667 @@ -37,9 +37,9 @@ def h(x, grad): -3.72589930555515 + 128.965158333333 * x0 + 0.341479166666643 * x1 - - 0.19642666666667 * x0 ** 2 + - 0.19642666666667 * x0**2 + 2.78692500000002 * x0 * x1 - - 0.0000104166666686543 * x1 ** 2 + - 0.0000104166666686543 * x1**2 - 468.897287036862 ) diff --git a/packages/nltk/meta.yaml b/packages/nltk/meta.yaml index 089da7591d3..d7628d7d565 100644 --- a/packages/nltk/meta.yaml +++ b/packages/nltk/meta.yaml @@ -1,9 +1,9 @@ package: name: nltk - version: 3.6.7 + version: "3.7" source: - sha256: 51bf1aef5304740a708be7c8e683f7798f03dc5c7a7e7feb758be9e95f4585e3 - url: https://files.pythonhosted.org/packages/62/8c/066d49386d048e9e8b580f4aff143b45ceddbf25ce429ea68f9c6ae54308/nltk-3.6.7.zip + sha256: ba3de02490308b248f9b94c8bc1ac0683e9aa2ec49ee78536d8667afb5e3eec8 + url: https://files.pythonhosted.org/packages/43/0b/8298798bc5a9a007b7cae3f846a3d9a325953e0f9c238affa478b4d59324/nltk-3.7-py3-none-any.whl requirements: run: - regex diff --git a/packages/nose/meta.yaml b/packages/nose/meta.yaml index 508a28b9619..e3aaff0bc3c 100644 --- a/packages/nose/meta.yaml +++ b/packages/nose/meta.yaml @@ -3,8 +3,8 @@ package: version: 1.3.7 source: - url: https://pypi.io/packages/source/n/nose/nose-1.3.7.tar.gz - sha256: f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98 + url: https://files.pythonhosted.org/packages/15/d8/dd071918c040f50fa1cf80da16423af51ff8ce4a0f2399b7bf8de45ac3d9/nose-1.3.7-py3-none-any.whl + sha256: 9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac requirements: run: diff --git a/packages/numcodecs/meta.yaml b/packages/numcodecs/meta.yaml index 8e02c6f64a0..0da4097c493 100644 --- a/packages/numcodecs/meta.yaml +++ b/packages/numcodecs/meta.yaml @@ -8,6 +8,10 @@ source: - patches/fixblosc.patch - patches/fixsetup.patch - patches/fixzlib.patch +build: + script: | + rm numcodecs/blosc.c + pip install -t $HOSTSITEPACKAGES cython requirements: run: - numpy diff --git a/packages/numcodecs/test_numcodecs.py b/packages/numcodecs/test_numcodecs.py index b652a778cd5..a4bc2b1bdad 100644 --- a/packages/numcodecs/test_numcodecs.py +++ b/packages/numcodecs/test_numcodecs.py @@ -9,13 +9,14 @@ }, ) def test_blosc(): + import array + import numpy as np from numcodecs.blosc import Blosc - from numcodecs.zstd import Zstd - from numcodecs.lz4 import LZ4 from numcodecs.compat import ensure_bytes, ensure_ndarray + from numcodecs.lz4 import LZ4 + from numcodecs.zstd import Zstd from numpy.testing import assert_array_almost_equal, assert_array_equal - import array def compare_arrays(arr, res, precision=None): # ensure numpy array with matching dtype @@ -113,20 +114,20 @@ def check_encode_decode(arr, codec, precision=None): np.random.normal(loc=1000, scale=1, size=(100, 10)), np.random.randint(0, 2, size=1000, dtype=bool).reshape(100, 10, order="F"), np.random.choice([b"a", b"bb", b"ccc"], size=1000).reshape(10, 10, 10), - np.random.randint(0, 2 ** 60, size=1000, dtype="u8").view("M8[ns]"), - np.random.randint(0, 2 ** 60, size=1000, dtype="u8").view("m8[ns]"), - np.random.randint(0, 2 ** 25, size=1000, dtype="u8").view("M8[m]"), - np.random.randint(0, 2 ** 25, size=1000, dtype="u8").view("m8[m]"), - np.random.randint(-(2 ** 63), -(2 ** 63) + 20, size=1000, dtype="i8").view( + np.random.randint(0, 2**60, size=1000, dtype="u8").view("M8[ns]"), + np.random.randint(0, 2**60, size=1000, dtype="u8").view("m8[ns]"), + np.random.randint(0, 2**25, size=1000, dtype="u8").view("M8[m]"), + np.random.randint(0, 2**25, size=1000, dtype="u8").view("m8[m]"), + np.random.randint(-(2**63), -(2**63) + 20, size=1000, dtype="i8").view( "M8[ns]" ), - np.random.randint(-(2 ** 63), -(2 ** 63) + 20, size=1000, dtype="i8").view( + np.random.randint(-(2**63), -(2**63) + 20, size=1000, dtype="i8").view( "m8[ns]" ), - np.random.randint(-(2 ** 63), -(2 ** 63) + 20, size=1000, dtype="i8").view( + np.random.randint(-(2**63), -(2**63) + 20, size=1000, dtype="i8").view( "M8[m]" ), - np.random.randint(-(2 ** 63), -(2 ** 63) + 20, size=1000, dtype="i8").view( + np.random.randint(-(2**63), -(2**63) + 20, size=1000, dtype="i8").view( "m8[m]" ), ] @@ -148,8 +149,8 @@ def check_encode_decode(arr, codec, precision=None): Blosc(cname="zstd", clevel=1, shuffle=1), Blosc(cname="blosclz", clevel=1, shuffle=2), Blosc(shuffle=Blosc.SHUFFLE, blocksize=0), - Blosc(shuffle=Blosc.SHUFFLE, blocksize=2 ** 8), - Blosc(cname="lz4", clevel=1, shuffle=Blosc.NOSHUFFLE, blocksize=2 ** 8), + Blosc(shuffle=Blosc.SHUFFLE, blocksize=2**8), + Blosc(cname="lz4", clevel=1, shuffle=Blosc.NOSHUFFLE, blocksize=2**8), ] for codec in codecs: for arr in arrays: diff --git a/packages/numpy/config/_numpyconfig.h b/packages/numpy/config/_numpyconfig.h index d4087bde14a..7bb42603f2a 100644 --- a/packages/numpy/config/_numpyconfig.h +++ b/packages/numpy/config/_numpyconfig.h @@ -24,8 +24,6 @@ #define NPY_RELAXED_STRIDES_CHECKING 1 #define NPY_USE_C99_FORMATS 1 #define NPY_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) -#define NPY_ABI_VERSION 0x01000009 -#define NPY_API_VERSION 0x0000000C #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS 1 diff --git a/packages/numpy/config/config.h b/packages/numpy/config/config.h deleted file mode 100644 index d2bafb9ebb9..00000000000 --- a/packages/numpy/config/config.h +++ /dev/null @@ -1,212 +0,0 @@ -#define HAVE_ENDIAN_H 1 -#define SIZEOF_PY_INTPTR_T 4 -#define SIZEOF_OFF_T 8 -#define SIZEOF_PY_LONG_LONG 8 -#define MATHLIB m -#define HAVE_SIN 1 -#define HAVE_COS 1 -#define HAVE_TAN 1 -#define HAVE_SINH 1 -#define HAVE_COSH 1 -#define HAVE_TANH 1 -#define HAVE_FABS 1 -#define HAVE_FLOOR 1 -#define HAVE_CEIL 1 -#define HAVE_SQRT 1 -#define HAVE_LOG10 1 -#define HAVE_LOG 1 -#define HAVE_EXP 1 -#define HAVE_ASIN 1 -#define HAVE_ACOS 1 -#define HAVE_ATAN 1 -#define HAVE_FMOD 1 -#define HAVE_MODF 1 -#define HAVE_FREXP 1 -#define HAVE_LDEXP 1 -#define HAVE_RINT 1 -#define HAVE_TRUNC 1 -#define HAVE_EXP2 1 -#define HAVE_LOG2 1 -#define HAVE_ATAN2 1 -#define HAVE_POW 1 -#define HAVE_NEXTAFTER 1 -#define HAVE_STRTOLL 1 -#define HAVE_STRTOULL 1 -#define HAVE_CBRT 1 -#define HAVE_STRTOLD_L 1 -#define HAVE_FALLOCATE 1 -#undef HAVE_BACKTRACE -#undef HAVE_XMMINTRIN_H -#undef HAVE_EMMINTRIN_H -#define HAVE_FEATURES_H 1 -#define HAVE_DLFCN_H 1 -#define HAVE___BUILTIN_ISNAN 1 -#define HAVE___BUILTIN_ISINF 1 -#define HAVE___BUILTIN_ISFINITE 1 -#define HAVE___BUILTIN_BSWAP32 1 -#define HAVE___BUILTIN_BSWAP64 1 -#define HAVE___BUILTIN_EXPECT 1 -#define HAVE___BUILTIN_MUL_OVERFLOW 1 -#undef HAVE___BUILTIN_CPU_SUPPORTS -#define HAVE__M_FROM_INT64 1 -#define HAVE__MM_LOAD_PS 1 -#define HAVE__MM_PREFETCH 1 -#define HAVE__MM_LOAD_PD 1 -#define HAVE___BUILTIN_PREFETCH 1 -#define HAVE_LINK_AVX 1 -#define HAVE_LINK_AVX2 1 -#undef HAVE_ATTRIBUTE_OPTIMIZE_UNROLL_LOOPS -#undef HAVE_ATTRIBUTE_OPTIMIZE_OPT_3 -#define HAVE_ATTRIBUTE_NONNULL 1 -#define HAVE_ATTRIBUTE_TARGET_AVX 1 -#define HAVE_ATTRIBUTE_TARGET_AVX2 1 -#define HAVE___THREAD 1 -#define HAVE_SINF 1 -#define HAVE_COSF 1 -#define HAVE_TANF 1 -#define HAVE_SINHF 1 -#define HAVE_COSHF 1 -#define HAVE_TANHF 1 -#define HAVE_FABSF 1 -#define HAVE_FLOORF 1 -#define HAVE_CEILF 1 -#define HAVE_RINTF 1 -#define HAVE_TRUNCF 1 -#define HAVE_SQRTF 1 -#define HAVE_LOG10F 1 -#define HAVE_LOGF 1 -#define HAVE_LOG1PF 1 -#define HAVE_EXPF 1 -#define HAVE_EXPM1F 1 -#define HAVE_ASINF 1 -#define HAVE_ACOSF 1 -#define HAVE_ATANF 1 -#define HAVE_ASINHF 1 -#define HAVE_ACOSHF 1 -#define HAVE_ATANHF 1 -#define HAVE_HYPOTF 1 -#define HAVE_ATAN2F 1 -#define HAVE_POWF 1 -#define HAVE_FMODF 1 -#define HAVE_MODFF 1 -#define HAVE_FREXPF 1 -#define HAVE_LDEXPF 1 -#define HAVE_EXP2F 1 -#define HAVE_LOG2F 1 -#define HAVE_COPYSIGNF 1 -#define HAVE_NEXTAFTERF 1 -#define HAVE_CBRTF 1 -#undef HAVE_SINL -#undef HAVE_COSL -#undef HAVE_TANL -#undef HAVE_SINHL -#undef HAVE_COSHL -#undef HAVE_TANHL -#undef HAVE_FABSL -#undef HAVE_FLOORL -#undef HAVE_CEILL -#undef HAVE_RINTL -#undef HAVE_TRUNCL -#undef HAVE_SQRTL -#undef HAVE_LOG10L -#undef HAVE_LOGL -#undef HAVE_LOG1PL -#undef HAVE_EXPL -#undef HAVE_EXPM1L -#undef HAVE_ASINL -#undef HAVE_ACOSL -#undef HAVE_ATANL -#undef HAVE_ASINHL -#undef HAVE_ACOSHL -#undef HAVE_ATANHL -#undef HAVE_HYPOTL -#undef HAVE_ATAN2L -#undef HAVE_POWL -#undef HAVE_FMODL -#undef HAVE_MODFL -#undef HAVE_FREXPL -#undef HAVE_LDEXPL -#undef HAVE_EXP2L -#undef HAVE_LOG2L -#undef HAVE_COPYSIGNL -#undef HAVE_NEXTAFTERL -#undef HAVE_CBRTL -#define HAVE_DECL_SIGNBIT -#undef HAVE_COMPLEX_H -#define HAVE_CABS 1 -#define HAVE_CACOS 1 -#define HAVE_CACOSH 1 -#define HAVE_CARG 1 -#define HAVE_CASIN 1 -#define HAVE_CASINH 1 -#define HAVE_CATAN 1 -#define HAVE_CATANH 1 -#define HAVE_CCOS 1 -#define HAVE_CCOSH 1 -#define HAVE_CEXP 1 -#define HAVE_CIMAG 1 -#define HAVE_CLOG 1 -#define HAVE_CONJ 1 -#define HAVE_CPOW 1 -#define HAVE_CPROJ 1 -#define HAVE_CREAL 1 -#define HAVE_CSIN 1 -#define HAVE_CSINH 1 -#define HAVE_CSQRT 1 -#define HAVE_CTAN 1 -#define HAVE_CTANH 1 -#define HAVE_CABSF 1 -#define HAVE_CACOSF 1 -#define HAVE_CACOSHF 1 -#define HAVE_CARGF 1 -#define HAVE_CASINF 1 -#define HAVE_CASINHF 1 -#define HAVE_CATANF 1 -#define HAVE_CATANHF 1 -#define HAVE_CCOSF 1 -#define HAVE_CCOSHF 1 -#define HAVE_CEXPF 1 -#define HAVE_CIMAGF 1 -#define HAVE_CLOGF 1 -#define HAVE_CONJF 1 -#define HAVE_CPOWF 1 -#define HAVE_CPROJF 1 -#define HAVE_CREALF 1 -#define HAVE_CSINF 1 -#define HAVE_CSINHF 1 -#define HAVE_CSQRTF 1 -#define HAVE_CTANF 1 -#define HAVE_CTANHF 1 -#undef HAVE_CABSL -#undef HAVE_CACOSL -#undef HAVE_CACOSHL -#undef HAVE_CARGL -#undef HAVE_CASINL -#undef HAVE_CASINHL -#undef HAVE_CATANL -#undef HAVE_CATANHL -#undef HAVE_CCOSL -#undef HAVE_CCOSHL -#undef HAVE_CEXPL -#undef HAVE_CIMAGL -#undef HAVE_CLOGL -#undef HAVE_CONJL -#undef HAVE_CPOWL -#undef HAVE_CPROJL -#undef HAVE_CREALL -#undef HAVE_CSINL -#undef HAVE_CSINHL -#undef HAVE_CSQRTL -#undef HAVE_CTANL -#undef HAVE_CTANHL -#define NPY_RESTRICT restrict -#define NPY_RELAXED_STRIDES_CHECKING 1 -#define HAVE_LDOUBLE_IEEE_DOUBLE_LE 1 -#define NPY_PY3K 1 -#ifndef __cplusplus -/* #undef inline */ -#endif - -#ifndef _NPY_NPY_CONFIG_H_ -#error config.h should never be included directly, include npy_config.h instead -#endif diff --git a/packages/numpy/meta.yaml b/packages/numpy/meta.yaml index eeebdb0f0a4..ffb80300787 100644 --- a/packages/numpy/meta.yaml +++ b/packages/numpy/meta.yaml @@ -1,47 +1,41 @@ package: name: numpy - version: 1.21.4 + version: 1.22.3 source: - url: https://files.pythonhosted.org/packages/fb/48/b0708ebd7718a8933f0d3937513ef8ef2f4f04529f1f66ca86d873043921/numpy-1.21.4.zip - sha256: e6c76a87633aa3fa16614b61ccedfae45b91df2767cf097aa9c933932a7ed1e0 + url: https://files.pythonhosted.org/packages/64/4a/b008d1f8a7b9f5206ecf70a53f84e654707e7616a771d84c05151a4713e9/numpy-1.22.3.zip + sha256: dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18 patches: - - patches/segfault-PyArray_Broadcast.patch - - patches/segfault-PyArray_PyIntAsIntp.patch - - patches/disable-maybe-uninitialized.patch - - patches/dont-include-execinfo.patch - - patches/fix-longdouble.patch - - patches/fix-static-init-of-nditer-pywrap.patch - - patches/init-alloc-cache.patch - - patches/use-local-blas-lapack.patch - - patches/fix-install-with-skip-build.patch - - patches/make-int-return-values.patch - - patches/fix-ieee754.patch - - patches/disable-optimization.patch - - patches/not-build-lapack-lite-as-64-bit.patch - - patches/fix-invalid-asm-instruction.patch - - patches/fix-removed-decorators-module.patch + - patches/0001-disable-maybe-uninitialized.patch + - patches/0002-disable-optimization.patch + - patches/0003-dont-include-execinfo.patch + - patches/0004-Fix-ieee754.patch + - patches/0005-fix-longdouble.patch + - patches/0006-MAINT-BLD-Fix-math-feature-detection-for-wasm.patch + - patches/0007-BUG-Fix-the-return-type-of-random_float_fill.patch + - patches/0008-fix-static-init-of-nditer-pywrap.patch + - patches/0009-init-alloc-cache.patch + - patches/0010-make-int-return-values.patch + - patches/0011-not-build-lapack-lite-as-64-bit.patch + - patches/0012-use-local-blas-lapack.patch + - patches/0013-fix-invalid-asm-instruction.patch + - patches/0014-disable-svml.patch build: - skip_host: False - # set linker and C flags to error on anything to do with function declarations being wrong. - # In webassembly, any conflicts mean that a randomly selected 50% of calls to the function - # will fail. Better to fail at compile or link time. cflags: | - -include math.h - -I $(PYODIDE_ROOT)/packages/numpy/config - -Werror=implicit-function-declaration - -Werror=mismatched-parameter-types - -Werror=mismatched-return-types - ldflags: | - -Wl,--fatal-warnings + -Wno-return-type post: | + pip install -t $HOSTSITEPACKAGES numpy==$PKG_VERSION # copy the correct numpy config into the build artifacts. Otherwise scipy will try to build with the config # from the build computer and bad things will happen - cp config/* $PKGDIR/../.artifacts/lib/python/numpy-1.21.4-py3.9-linux-x86_64.egg/numpy/core/include/numpy - cp -r $PKGDIR/../.artifacts/lib/python/numpy-1.21.4-py3.9-linux-x86_64.egg/numpy $PKGDIR/../.artifacts/lib/python - + export NUMPY_CORE_INCLUDE=numpy/core/include/numpy + cp \ + $PKG_BUILD_DIR/build/src.emscripten_wasm32-$PYMAJOR.$PYMINOR/$NUMPY_CORE_INCLUDE/*config.h \ + $HOSTSITEPACKAGES/$NUMPY_CORE_INCLUDE + # static libraries for other packages to link + mkdir -p $NUMPY_LIB + find $SRC_DIR -name '*.a' | sed "s:.*:cp \0 $NUMPY_LIB:g" | bash test: imports: - numpy diff --git a/packages/numpy/patches/0001-disable-maybe-uninitialized.patch b/packages/numpy/patches/0001-disable-maybe-uninitialized.patch new file mode 100644 index 00000000000..72a53f9348b --- /dev/null +++ b/packages/numpy/patches/0001-disable-maybe-uninitialized.patch @@ -0,0 +1,24 @@ +From 61f19d3c2da35468d1ae45b432d2ff37b1e89fcb Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:17:17 -0700 +Subject: [PATCH 01/14] disable maybe uninitialized + +--- + numpy/linalg/umath_linalg.c.src | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/numpy/linalg/umath_linalg.c.src b/numpy/linalg/umath_linalg.c.src +index f8a154445..ab4d8369a 100644 +--- a/numpy/linalg/umath_linalg.c.src ++++ b/numpy/linalg/umath_linalg.c.src +@@ -750,7 +750,6 @@ update_pointers(npy_uint8** bases, ptrdiff_t* offsets, size_t count) + positives with this warning + */ + #pragma GCC diagnostic push +-#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + + /* + ***************************************************************************** +-- +2.25.1 + diff --git a/packages/numpy/patches/disable-optimization.patch b/packages/numpy/patches/0002-disable-optimization.patch similarity index 70% rename from packages/numpy/patches/disable-optimization.patch rename to packages/numpy/patches/0002-disable-optimization.patch index abb5c5c635e..28549017d8c 100644 --- a/packages/numpy/patches/disable-optimization.patch +++ b/packages/numpy/patches/0002-disable-optimization.patch @@ -1,5 +1,12 @@ -This is to disable SIMD optimization until SAFARI has good support for it. +From 84d145e2d49f2ec659a30e3aa6268fce77e4dde7 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:17:30 -0700 +Subject: [PATCH 02/14] disable optimization +This is to disable SIMD optimization until SAFARI has good support for it. +--- + numpy/distutils/command/build.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/distutils/command/build.py b/numpy/distutils/command/build.py index a4fda537d..11fffe282 100644 @@ -14,3 +21,6 @@ index a4fda537d..11fffe282 100644 """ the '_simd' module is a very large. Adding more dispatched features will increase binary size and compile time. By default we minimize +-- +2.25.1 + diff --git a/packages/numpy/patches/dont-include-execinfo.patch b/packages/numpy/patches/0003-dont-include-execinfo.patch similarity index 50% rename from packages/numpy/patches/dont-include-execinfo.patch rename to packages/numpy/patches/0003-dont-include-execinfo.patch index 5370f5ff6d0..5156780bf39 100644 --- a/packages/numpy/patches/dont-include-execinfo.patch +++ b/packages/numpy/patches/0003-dont-include-execinfo.patch @@ -1,8 +1,17 @@ +From 90b3929743198f7c7bd19bc9f81bdbd7da0bf972 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:18:12 -0700 +Subject: [PATCH 03/14] dont include execinfo + +--- + numpy/core/src/multiarray/temp_elide.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + diff --git a/numpy/core/src/multiarray/temp_elide.c b/numpy/core/src/multiarray/temp_elide.c -index e5175f162..13850b18b 100644 +index f615aa336..6d24e95b5 100644 --- a/numpy/core/src/multiarray/temp_elide.c +++ b/numpy/core/src/multiarray/temp_elide.c -@@ -82,9 +82,9 @@ +@@ -80,9 +80,9 @@ * during in-place operations */ #define NPY_MIN_ELIDE_BYTES (32) @@ -13,3 +22,6 @@ index e5175f162..13850b18b 100644 /* * linear search pointer in table +-- +2.25.1 + diff --git a/packages/numpy/patches/fix-ieee754.patch b/packages/numpy/patches/0004-Fix-ieee754.patch similarity index 73% rename from packages/numpy/patches/fix-ieee754.patch rename to packages/numpy/patches/0004-Fix-ieee754.patch index 332d64471fe..0c4e46e3637 100644 --- a/packages/numpy/patches/fix-ieee754.patch +++ b/packages/numpy/patches/0004-Fix-ieee754.patch @@ -1,16 +1,20 @@ -commit 12b131883a0505987b052cb58ee1320aed24aeb4 -Author: Roman Yurchak -Date: Mon Mar 22 21:33:26 2021 +0100 +From 77cdba9a810726e01542ab330ee7ec49f446a50e Mon Sep 17 00:00:00 2001 +From: Roman Yurchak +Date: Thu, 31 Mar 2022 17:19:07 -0700 +Subject: [PATCH 04/14] Fix ieee754 - Use float error status without external includes in numpy - - A better solution would be to rely on FE_DIVBYZERO, FE_DIVBYZERO etc - defined in musl's fenv.h (https://github.com/numpy/numpy/pull/12124) - however these are not valid for emscripten - (https://github.com/emscripten-core/emscripten/issues/13678) +Use float error status without external includes in numpy + +A better solution would be to rely on FE_DIVBYZERO, FE_DIVBYZERO etc +defined in musl's fenv.h (https://github.com/numpy/numpy/pull/12124) +however these are not valid for emscripten +(https://github.com/emscripten-core/emscripten/issues/13678) +--- + numpy/core/src/npymath/ieee754.c.src | 30 ++++++++-------------------- + 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/numpy/core/src/npymath/ieee754.c.src b/numpy/core/src/npymath/ieee754.c.src -index 3f66b24a4..6beb63edc 100644 +index 4e6ddb712..6aecb7577 100644 --- a/numpy/core/src/npymath/ieee754.c.src +++ b/numpy/core/src/npymath/ieee754.c.src @@ -682,7 +682,8 @@ void npy_set_floatstatus_invalid(void) @@ -67,3 +71,6 @@ index 3f66b24a4..6beb63edc 100644 return fpstatus; } +-- +2.25.1 + diff --git a/packages/numpy/patches/fix-longdouble.patch b/packages/numpy/patches/0005-fix-longdouble.patch similarity index 60% rename from packages/numpy/patches/fix-longdouble.patch rename to packages/numpy/patches/0005-fix-longdouble.patch index 1f53b53d520..f1d59b5f835 100644 --- a/packages/numpy/patches/fix-longdouble.patch +++ b/packages/numpy/patches/0005-fix-longdouble.patch @@ -1,8 +1,17 @@ +From 69030ca31273be35847e677a800651be7b7e3106 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:20:58 -0700 +Subject: [PATCH 05/14] fix longdouble + +--- + numpy/core/src/npymath/npy_math_complex.c.src | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + diff --git a/numpy/core/src/npymath/npy_math_complex.c.src b/numpy/core/src/npymath/npy_math_complex.c.src -index cf427dad8..6c9f1c37e 100644 +index 8c432e483..19c136b43 100644 --- a/numpy/core/src/npymath/npy_math_complex.c.src +++ b/numpy/core/src/npymath/npy_math_complex.c.src -@@ -1522,10 +1522,9 @@ _sum_squares@c@(@type@ x, @type@ y) +@@ -1485,10 +1485,9 @@ _sum_squares@c@(@type@ x, @type@ y) #if @precision@ == 1 const npy_float SQRT_MIN = 1.0842022e-19f; #endif @@ -15,3 +24,6 @@ index cf427dad8..6c9f1c37e 100644 #if NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE const npy_longdouble SQRT_MIN = 1.4916681462400413e-154; /* sqrt(DBL_MIN) */ #else +-- +2.25.1 + diff --git a/packages/numpy/patches/0006-MAINT-BLD-Fix-math-feature-detection-for-wasm.patch b/packages/numpy/patches/0006-MAINT-BLD-Fix-math-feature-detection-for-wasm.patch new file mode 100644 index 00000000000..b9f5cc12312 --- /dev/null +++ b/packages/numpy/patches/0006-MAINT-BLD-Fix-math-feature-detection-for-wasm.patch @@ -0,0 +1,383 @@ +From 2895d82a329e356d94c3f00762804f5fa23745fa Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Sat, 5 Mar 2022 16:00:54 -0800 +Subject: [PATCH 06/14] MAINT, BLD Fix math feature detection for wasm + +The web assembly linker is strict about function types, it is unwilling +to link a function defined with signature say `double f(double, double)` +to an invocation with signature `void f(void)`. This causes trouble in +`numpy/core/setup.py` in the functions `check_math_capabilities` and +`check_mathlib`. + +This patch fixes the problem by giving config.try_link the correct +function signatures for these functions. In particular I added a separate +header file with all the math declarations. It would be be possible to +just include 'math.h' but we also need to parse the declarations to figure +out how to call the functions. If f has arguments type1, type2, type3, we +want to call it like f((type1)0, (type2)0, (type3)0). Anyways it is easier +to parse the arguments out of our feature_detection_math.h than to worry +about the possibility that someone will have a differently formatted system +math.h. We do a test where we include both math.h and feature_detection_math.h +to ensure consistency between the signatures. + +I also separated out the fcntl functions, backtrace and madvise, and strtold_l. +This is because they require separate headers. strtold_l requires testing both +with the locale.h header and with the xlocale.h header (this seems to vary even +among different linuxes). All of the functions I moved out of OPTIONAL_STDFUNCS +are absent on windows and some are absent on OSX so separating them out of the +OPTIONAL_STDFUNCS should mildly improve the speed of feature detection on mac +and windows (since they have all the functions remaining in OPTIONAL_STDFUNCS). +--- + numpy/core/feature_detection_locale.h | 1 + + numpy/core/feature_detection_math.h | 107 ++++++++++++++++++++++++++ + numpy/core/feature_detection_misc.h | 4 + + numpy/core/feature_detection_stdio.h | 6 ++ + numpy/core/setup.py | 76 ++++++++++++++---- + numpy/core/setup_common.py | 39 ++++++++-- + 6 files changed, 211 insertions(+), 22 deletions(-) + create mode 100644 numpy/core/feature_detection_locale.h + create mode 100644 numpy/core/feature_detection_math.h + create mode 100644 numpy/core/feature_detection_misc.h + create mode 100644 numpy/core/feature_detection_stdio.h + +diff --git a/numpy/core/feature_detection_locale.h b/numpy/core/feature_detection_locale.h +new file mode 100644 +index 000000000..0af1d6e7e +--- /dev/null ++++ b/numpy/core/feature_detection_locale.h +@@ -0,0 +1 @@ ++long double strtold_l(const char*, char**, locale_t); +diff --git a/numpy/core/feature_detection_math.h b/numpy/core/feature_detection_math.h +new file mode 100644 +index 000000000..27ad7bcf0 +--- /dev/null ++++ b/numpy/core/feature_detection_math.h +@@ -0,0 +1,107 @@ ++double expm1(double); ++double log1p(double); ++double acosh(double); ++double asinh(double); ++double atanh(double); ++double rint(double); ++double trunc(double); ++double exp2(double); ++double log2(double); ++double hypot(double, double); ++double atan2(double, double); ++double pow(double, double); ++double copysign(double, double); ++double nextafter(double, double); ++long long strtoll(const char*, char**, int); ++unsigned long long strtoull(const char*, char**, int); ++double cbrt(double); ++long double sinl(long double); ++long double cosl(long double); ++long double tanl(long double); ++long double sinhl(long double); ++long double coshl(long double); ++long double tanhl(long double); ++long double fabsl(long double); ++long double floorl(long double); ++long double ceill(long double); ++long double rintl(long double); ++long double truncl(long double); ++long double sqrtl(long double); ++long double log10l(long double); ++long double logl(long double); ++long double log1pl(long double); ++long double expl(long double); ++long double expm1l(long double); ++long double asinl(long double); ++long double acosl(long double); ++long double atanl(long double); ++long double asinhl(long double); ++long double acoshl(long double); ++long double atanhl(long double); ++long double hypotl(long double, long double); ++long double atan2l(long double, long double); ++long double powl(long double, long double); ++long double fmodl(long double, long double); ++long double modfl(long double, long double*); ++long double frexpl(long double, int*); ++long double ldexpl(long double, int); ++long double exp2l(long double); ++long double log2l(long double); ++long double copysignl(long double, long double); ++long double nextafterl(long double, long double); ++long double cbrtl(long double); ++float sinf(float); ++float cosf(float); ++float tanf(float); ++float sinhf(float); ++float coshf(float); ++float tanhf(float); ++float fabsf(float); ++float floorf(float); ++float ceilf(float); ++float rintf(float); ++float truncf(float); ++float sqrtf(float); ++float log10f(float); ++float logf(float); ++float log1pf(float); ++float expf(float); ++float expm1f(float); ++float asinf(float); ++float acosf(float); ++float atanf(float); ++float asinhf(float); ++float acoshf(float); ++float atanhf(float); ++float hypotf(float, float); ++float atan2f(float, float); ++float powf(float, float); ++float fmodf(float, float); ++float modff(float, float*); ++float frexpf(float, int*); ++float ldexpf(float, int); ++float exp2f(float); ++float log2f(float); ++float copysignf(float, float); ++float nextafterf(float, float); ++float cbrtf(float); ++double sin(double); ++double cos(double); ++double tan(double); ++double sinh(double); ++double cosh(double); ++double tanh(double); ++double fabs(double); ++double floor(double); ++double ceil(double); ++double sqrt(double); ++double log10(double); ++double log(double); ++double exp(double); ++double asin(double); ++double acos(double); ++double atan(double); ++double fmod(double, double); ++double modf(double, double*); ++double frexp(double, int*); ++double ldexp(double, int); +diff --git a/numpy/core/feature_detection_misc.h b/numpy/core/feature_detection_misc.h +new file mode 100644 +index 000000000..f58cf4b62 +--- /dev/null ++++ b/numpy/core/feature_detection_misc.h +@@ -0,0 +1,4 @@ ++#include ++ ++int backtrace(void **, int); ++int madvise(void *, size_t, int); +diff --git a/numpy/core/feature_detection_stdio.h b/numpy/core/feature_detection_stdio.h +new file mode 100644 +index 000000000..bc14d16d0 +--- /dev/null ++++ b/numpy/core/feature_detection_stdio.h +@@ -0,0 +1,6 @@ ++#include ++#include ++ ++off_t ftello(FILE *stream); ++int fseeko(FILE *stream, off_t offset, int whence); ++int fallocate(int, int, off_t, off_t); +diff --git a/numpy/core/setup.py b/numpy/core/setup.py +index 7eb08132e..cb31162d1 100644 +--- a/numpy/core/setup.py ++++ b/numpy/core/setup.py +@@ -125,32 +125,48 @@ def win32_checks(deflist): + deflist.append('FORCE_NO_LONG_DOUBLE_FORMATTING') + + def check_math_capabilities(config, ext, moredefs, mathlibs): +- def check_func(func_name): +- return config.check_func(func_name, libraries=mathlibs, +- decl=True, call=True) +- +- def check_funcs_once(funcs_name): +- decl = dict([(f, True) for f in funcs_name]) +- st = config.check_funcs_once(funcs_name, libraries=mathlibs, +- decl=decl, call=decl) ++ def check_func( ++ func_name, ++ decl=False, ++ headers=["feature_detection_math.h"], ++ ): ++ return config.check_func( ++ func_name, ++ libraries=mathlibs, ++ decl=decl, ++ call=True, ++ call_args=FUNC_CALL_ARGS[func_name], ++ headers=headers, ++ ) ++ ++ def check_funcs_once(funcs_name, headers=["feature_detection_math.h"]): ++ call = dict([(f, True) for f in funcs_name]) ++ call_args = dict([(f, FUNC_CALL_ARGS[f]) for f in funcs_name]) ++ st = config.check_funcs_once( ++ funcs_name, ++ libraries=mathlibs, ++ decl=False, ++ call=call, ++ call_args=call_args, ++ headers=headers, ++ ) + if st: + moredefs.extend([(fname2def(f), 1) for f in funcs_name]) + return st + +- def check_funcs(funcs_name): ++ def check_funcs(funcs_name, headers=["feature_detection_math.h"]): + # Use check_funcs_once first, and if it does not work, test func per + # func. Return success only if all the functions are available +- if not check_funcs_once(funcs_name): ++ if not check_funcs_once(funcs_name, headers=headers): + # Global check failed, check func per func + for f in funcs_name: +- if check_func(f): ++ if check_func(f, headers=headers): + moredefs.append((fname2def(f), 1)) + return 0 + else: + return 1 + + #use_msvc = config.check_decl("_MSC_VER") +- + if not check_funcs_once(MANDATORY_FUNCS): + raise SystemError("One of the required function to build numpy is not" + " available (the list is %s)." % str(MANDATORY_FUNCS)) +@@ -165,15 +181,34 @@ def check_funcs(funcs_name): + for f in OPTIONAL_STDFUNCS_MAYBE: + if config.check_decl(fname2def(f), + headers=["Python.h", "math.h"]): +- OPTIONAL_STDFUNCS.remove(f) ++ if f in OPTIONAL_STDFUNCS: ++ OPTIONAL_STDFUNCS.remove(f) ++ else: ++ OPTIONAL_FILE_FUNCS.remove(f) ++ + + check_funcs(OPTIONAL_STDFUNCS) ++ check_funcs(OPTIONAL_FILE_FUNCS, headers=["feature_detection_stdio.h"]) ++ check_funcs(OPTIONAL_MISC_FUNCS, headers=["feature_detection_misc.h"]) ++ ++ + + for h in OPTIONAL_HEADERS: + if config.check_func("", decl=False, call=False, headers=[h]): + h = h.replace(".", "_").replace(os.path.sep, "_") + moredefs.append((fname2def(h), 1)) + ++ # Try with both "locale.h" and "xlocale.h" ++ locale_headers = [ ++ "stdlib.h", ++ "xlocale.h", ++ "feature_detection_locale.h", ++ ] ++ if not check_funcs(OPTIONAL_LOCALE_FUNCS, headers=locale_headers): ++ # It didn't work with xlocale.h, maybe it will work with locale.h? ++ locale_headers[1] = "locale.h" ++ check_funcs(OPTIONAL_LOCALE_FUNCS, headers=locale_headers) ++ + for tup in OPTIONAL_INTRINSICS: + headers = None + if len(tup) == 2: +@@ -394,12 +429,18 @@ def check_types(config_cmd, ext, build_dir): + def check_mathlib(config_cmd): + # Testing the C math library + mathlibs = [] +- mathlibs_choices = [[], ['m'], ['cpml']] +- mathlib = os.environ.get('MATHLIB') ++ mathlibs_choices = [[], ["m"], ["cpml"]] ++ mathlib = os.environ.get("MATHLIB") + if mathlib: +- mathlibs_choices.insert(0, mathlib.split(',')) ++ mathlibs_choices.insert(0, mathlib.split(",")) + for libs in mathlibs_choices: +- if config_cmd.check_func("exp", libraries=libs, decl=True, call=True): ++ if config_cmd.check_func( ++ "log", ++ libraries=libs, ++ call_args="0", ++ decl="double log(double);", ++ call=True ++ ): + mathlibs = libs + break + else: +@@ -408,6 +449,7 @@ def check_mathlib(config_cmd): + "MATHLIB env variable") + return mathlibs + ++ + def visibility_define(config): + """Return the define value to use for NPY_VISIBILITY_HIDDEN (may be empty + string).""" +diff --git a/numpy/core/setup_common.py b/numpy/core/setup_common.py +index 70e8fc897..181c58fb1 100644 +--- a/numpy/core/setup_common.py ++++ b/numpy/core/setup_common.py +@@ -1,8 +1,9 @@ + # Code common to build tools +-import sys +-import warnings + import copy ++import pathlib ++import sys + import textwrap ++import warnings + + from numpy.distutils.misc_util import mingw32 + +@@ -101,6 +102,32 @@ def check_api_version(apiversion, codegen_dir): + warnings.warn(msg % (apiversion, curapi_hash, apiversion, api_hash, + __file__), + MismatchCAPIWarning, stacklevel=2) ++ ++ ++FUNC_CALL_ARGS = {} ++ ++def set_sig(sig): ++ prefix, _, args = sig.partition("(") ++ args = args.rpartition(")")[0] ++ funcname = prefix.rpartition(" ")[-1] ++ args = [arg.strip() for arg in args.split(",")] ++ FUNC_CALL_ARGS[funcname] = ", ".join("(%s) 0" % arg for arg in args) ++ ++ ++for file in [ ++ "feature_detection_locale.h", ++ "feature_detection_math.h", ++ "feature_detection_misc.h", ++ "feature_detection_stdio.h", ++]: ++ with open(pathlib.Path(__file__).parent / file) as f: ++ for line in f: ++ if line.startswith("#"): ++ continue ++ if not line.strip(): ++ continue ++ set_sig(line) ++ + # Mandatory functions: if not found, fail the build + MANDATORY_FUNCS = ["sin", "cos", "tan", "sinh", "cosh", "tanh", "fabs", + "floor", "ceil", "sqrt", "log10", "log", "exp", "asin", +@@ -110,9 +137,11 @@ def check_api_version(apiversion, codegen_dir): + # replacement implementation. Note that some of these are C99 functions. + OPTIONAL_STDFUNCS = ["expm1", "log1p", "acosh", "asinh", "atanh", + "rint", "trunc", "exp2", "log2", "hypot", "atan2", "pow", +- "copysign", "nextafter", "ftello", "fseeko", +- "strtoll", "strtoull", "cbrt", "strtold_l", "fallocate", +- "backtrace", "madvise"] ++ "copysign", "nextafter", "strtoll", "strtoull", "cbrt"] ++ ++OPTIONAL_LOCALE_FUNCS = ["strtold_l"] ++OPTIONAL_FILE_FUNCS = ["ftello", "fseeko", "fallocate"] ++OPTIONAL_MISC_FUNCS = ["backtrace", "madvise"] + + + OPTIONAL_HEADERS = [ +-- +2.25.1 + diff --git a/packages/numpy/patches/0007-BUG-Fix-the-return-type-of-random_float_fill.patch b/packages/numpy/patches/0007-BUG-Fix-the-return-type-of-random_float_fill.patch new file mode 100644 index 00000000000..db6fd80aa72 --- /dev/null +++ b/packages/numpy/patches/0007-BUG-Fix-the-return-type-of-random_float_fill.patch @@ -0,0 +1,33 @@ +From b250b95b9740015179b72b0af9271a1c6ac5fe6b Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Wed, 26 Jan 2022 18:59:47 -0800 +Subject: [PATCH 07/14] BUG: Fix the return type of random_float_fill + +The `random_float_fill` function type is declared with return type `double` but +I think this is a typo. The actual implementation of `random_float_fill` is +`random_standard_uniform_fill_f` which has return type `void`: + +void random_standard_uniform_fill_f(bitgen_t *bitgen_state, npy_intp cnt, float *out) + +Also, `random_double_fill` is declared with return type `void`. This fixes the +return type of `random_float_fill` to match. +--- + numpy/random/_common.pxd | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/numpy/random/_common.pxd b/numpy/random/_common.pxd +index 9f2e8c3ca..3625634cd 100644 +--- a/numpy/random/_common.pxd ++++ b/numpy/random/_common.pxd +@@ -45,7 +45,7 @@ + ctypedef double (*random_double_2)(void *state, double a, double b) nogil + ctypedef double (*random_double_3)(void *state, double a, double b, double c) nogil + +-ctypedef double (*random_float_fill)(bitgen_t *state, np.npy_intp count, float* out) nogil ++ctypedef void (*random_float_fill)(bitgen_t *state, np.npy_intp count, float* out) nogil + ctypedef float (*random_float_0)(bitgen_t *state) nogil + ctypedef float (*random_float_1)(bitgen_t *state, float a) nogil + +-- +2.25.1 + diff --git a/packages/numpy/patches/fix-static-init-of-nditer-pywrap.patch b/packages/numpy/patches/0008-fix-static-init-of-nditer-pywrap.patch similarity index 52% rename from packages/numpy/patches/fix-static-init-of-nditer-pywrap.patch rename to packages/numpy/patches/0008-fix-static-init-of-nditer-pywrap.patch index 3f456df63c9..13bacd00bb5 100644 --- a/packages/numpy/patches/fix-static-init-of-nditer-pywrap.patch +++ b/packages/numpy/patches/0008-fix-static-init-of-nditer-pywrap.patch @@ -1,8 +1,17 @@ +From 04399d268103b821fe5e3124a7742a1f8aa86155 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:22:18 -0700 +Subject: [PATCH 08/14] fix static init of nditer pywrap + +--- + numpy/core/src/multiarray/nditer_pywrap.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + diff --git a/numpy/core/src/multiarray/nditer_pywrap.c b/numpy/core/src/multiarray/nditer_pywrap.c -index 8e072d5f4..897950c7c 100644 +index 2675496ab..cea617b6f 100644 --- a/numpy/core/src/multiarray/nditer_pywrap.c +++ b/numpy/core/src/multiarray/nditer_pywrap.c -@@ -2477,7 +2477,7 @@ NPY_NO_EXPORT PyTypeObject NpyIter_Type = { +@@ -2479,7 +2479,7 @@ NPY_NO_EXPORT PyTypeObject NpyIter_Type = { .tp_flags = Py_TPFLAGS_DEFAULT, .tp_iternext = (iternextfunc)npyiter_next, .tp_methods = npyiter_methods, @@ -11,3 +20,6 @@ index 8e072d5f4..897950c7c 100644 .tp_getset = npyiter_getsets, .tp_init = (initproc)npyiter_init, .tp_new = npyiter_new, +-- +2.25.1 + diff --git a/packages/numpy/patches/init-alloc-cache.patch b/packages/numpy/patches/0009-init-alloc-cache.patch similarity index 50% rename from packages/numpy/patches/init-alloc-cache.patch rename to packages/numpy/patches/0009-init-alloc-cache.patch index ca2f741a054..561aaa50cb4 100644 --- a/packages/numpy/patches/init-alloc-cache.patch +++ b/packages/numpy/patches/0009-init-alloc-cache.patch @@ -1,10 +1,21 @@ +From fc9fff66fba0258cf4fd782ff6e55c52a15833eb Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:22:42 -0700 +Subject: [PATCH 09/14] init alloc cache + +--- + numpy/core/src/multiarray/alloc.c | 6 ++++++ + numpy/core/src/multiarray/alloc.h | 3 +++ + numpy/core/src/multiarray/multiarraymodule.c | 2 ++ + 3 files changed, 11 insertions(+) + diff --git a/numpy/core/src/multiarray/alloc.c b/numpy/core/src/multiarray/alloc.c -index f8305d115..369a14012 100644 +index 0a694cf62..07feba641 100644 --- a/numpy/core/src/multiarray/alloc.c +++ b/numpy/core/src/multiarray/alloc.c -@@ -277,3 +277,9 @@ PyDataMem_RENEW(void *ptr, size_t size) - } - return result; +@@ -658,3 +658,9 @@ get_handler_version(PyObject *NPY_UNUSED(self), PyObject *args) + Py_DECREF(mem_handler); + return version; } + +NPY_NO_EXPORT void npy_init_cache() { @@ -13,22 +24,24 @@ index f8305d115..369a14012 100644 + _PyDataMem_eventhook = NULL; +} diff --git a/numpy/core/src/multiarray/alloc.h b/numpy/core/src/multiarray/alloc.h -index 2b69efc35..10069503d 100644 +index 13c828458..a81e9b20f 100644 --- a/numpy/core/src/multiarray/alloc.h +++ b/numpy/core/src/multiarray/alloc.h -@@ -33,4 +33,7 @@ npy_free_cache_dim_array(PyArrayObject * arr) - npy_free_cache_dim(PyArray_DIMS(arr), PyArray_NDIM(arr)); - } +@@ -45,6 +45,9 @@ extern PyDataMem_Handler default_handler; + extern PyObject *current_handler; /* PyContextVar/PyCapsule */ + #endif +NPY_NO_EXPORT void +npy_init_cache(void); + - #endif + NPY_NO_EXPORT PyObject * + get_handler_name(PyObject *NPY_UNUSED(self), PyObject *obj); + NPY_NO_EXPORT PyObject * diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c -index 8e7352e4f..a713679e8 100644 +index 576c39f5d..066297a6d 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c -@@ -4747,6 +4747,8 @@ PyMODINIT_FUNC initmultiarray(void) { +@@ -4723,6 +4723,8 @@ PyMODINIT_FUNC PyInit__multiarray_umath(void) { goto err; } @@ -37,3 +50,6 @@ index 8e7352e4f..a713679e8 100644 #if defined(MS_WIN64) && defined(__GNUC__) PyErr_WarnEx(PyExc_Warning, "Numpy built with MINGW-W64 on Windows 64 bits is experimental, " \ +-- +2.25.1 + diff --git a/packages/numpy/patches/make-int-return-values.patch b/packages/numpy/patches/0010-make-int-return-values.patch similarity index 76% rename from packages/numpy/patches/make-int-return-values.patch rename to packages/numpy/patches/0010-make-int-return-values.patch index 5ba80f66185..64737dba3d0 100644 --- a/packages/numpy/patches/make-int-return-values.patch +++ b/packages/numpy/patches/0010-make-int-return-values.patch @@ -1,3 +1,8 @@ +From 1760850bf0f763b19ea5cd9de8f2e578bcc08a98 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:24:13 -0700 +Subject: [PATCH 10/14] make int return values + Make return values to fortran subroutines be consistently int. This is because f2py defaults to making subroutines return void, whereas @@ -5,29 +10,20 @@ f2c makes them int. On most systems this doesn't matter, but on WASM, strict return type checking is used which makes things fail when f2py code calls subroutines that were generated from fortran with f2c. To fix this we make everything, including f2c, return int from subroutines. +--- + numpy/f2py/common_rules.py | 6 +++--- + numpy/f2py/rules.py | 6 +++--- + numpy/f2py/src/fortranobject.c | 7 +++++-- + numpy/f2py/src/fortranobject.h | 6 +++--- + numpy/linalg/lapack_lite/f2c.c | 11 ++++++----- + numpy/linalg/lapack_lite/f2c.h | 10 +++++----- + 6 files changed, 25 insertions(+), 21 deletions(-) -diff --git a/numpy/f2py/src/fortranobject.h b/numpy/f2py/src/fortranobject.h -index 5d0dcf6..d5f77b3 100644 ---- a/numpy/f2py/src/fortranobject.h -+++ b/numpy/f2py/src/fortranobject.h -@@ -67,9 +67,9 @@ Author: Pearu Peterson - - #define F2PY_MAX_DIMS 40 - --typedef void (*f2py_set_data_func)(char*,npy_intp*); --typedef void (*f2py_void_func)(void); --typedef void (*f2py_init_func)(int*,npy_intp*,f2py_set_data_func,int*); -+typedef int (*f2py_set_data_func)(char*,npy_intp*); -+typedef int (*f2py_void_func)(void); -+typedef int (*f2py_init_func)(int*,npy_intp*,f2py_set_data_func,int*); - - /*typedef void* (*f2py_c_func)(void*,...);*/ - diff --git a/numpy/f2py/common_rules.py b/numpy/f2py/common_rules.py -index 62c1ba2..5893683 100644 +index 937d8bc72..1bc01ecd1 100644 --- a/numpy/f2py/common_rules.py +++ b/numpy/f2py/common_rules.py -@@ -108,7 +108,7 @@ def buildhooks(m): +@@ -104,7 +104,7 @@ def dadd(line, s=doc): cadd('\t{NULL}\n};') inames1 = rmbadname(inames) inames1_tps = ','.join(['char *' + s for s in inames1]) @@ -36,7 +32,7 @@ index 62c1ba2..5893683 100644 cadd('\tint i_f2py=0;') for n in inames1: cadd('\tf2py_%s_def[i_f2py++].data = %s;' % (name, n)) -@@ -117,10 +117,10 @@ def buildhooks(m): +@@ -113,10 +113,10 @@ def dadd(line, s=doc): F_FUNC = 'F_FUNC_US' else: F_FUNC = 'F_FUNC' @@ -50,10 +46,10 @@ index 62c1ba2..5893683 100644 % (F_FUNC, lower_name, name.upper(), name)) cadd('}\n') diff --git a/numpy/f2py/rules.py b/numpy/f2py/rules.py -index 1b41498ea..9419aeb16 100755 +index 78810a0a7..24cb06432 100755 --- a/numpy/f2py/rules.py +++ b/numpy/f2py/rules.py -@@ -399,9 +399,9 @@ rout_rules = [ +@@ -394,9 +394,9 @@ 'decl': '', '_check': ismoduleroutine }, { # Subroutine @@ -67,32 +63,49 @@ index 1b41498ea..9419aeb16 100755 isdummyroutine: '' }, diff --git a/numpy/f2py/src/fortranobject.c b/numpy/f2py/src/fortranobject.c -index b55385b50..232a7f90d 100644 +index c96378170..d7b9536d8 100644 --- a/numpy/f2py/src/fortranobject.c +++ b/numpy/f2py/src/fortranobject.c -@@ -308,6 +308,11 @@ fortran_getattr(PyFortranObject *fp, char *name) { +@@ -397,6 +397,10 @@ fortran_getattr(PyFortranObject *fp, char *name) return s; } - if ((strcmp(name,"_cpointer")==0) && (fp->len==1)) { -+ if(fp->defs[0].data==NULL) + if ((strcmp(name, "_cpointer") == 0) && (fp->len == 1)) { ++ if(fp->defs[0].data == NULL) + { -+ PyErr_Format(PyExc_AttributeError,"missing function pointer %s",fp->defs[0].name); -+ ++ PyErr_Format(PyExc_AttributeError, "missing function pointer %s", fp->defs[0].name); + } - PyObject *cobj = F2PyCapsule_FromVoidPtr((void *)(fp->defs[0].data),NULL); + PyObject *cobj = + F2PyCapsule_FromVoidPtr((void *)(fp->defs[0].data), NULL); if (PyDict_SetItemString(fp->dict, name, cobj)) - return NULL; -@@ -382,7 +387,7 @@ fortran_setattr(PyFortranObject *fp, char *name, PyObject *v) { +@@ -483,8 +487,7 @@ fortran_setattr(PyFortranObject *fp, char *name, PyObject *v) if (v == NULL) { int rv = PyDict_DelItemString(fp->dict, name); if (rv < 0) -- PyErr_SetString(PyExc_AttributeError,"delete non-existing fortran attribute"); +- PyErr_SetString(PyExc_AttributeError, +- "delete non-existing fortran attribute"); + PyErr_Format(PyExc_AttributeError,"delete non-existing fortran attribute %s",name); return rv; } else +diff --git a/numpy/f2py/src/fortranobject.h b/numpy/f2py/src/fortranobject.h +index a1e9fdbdf..a55335a8d 100644 +--- a/numpy/f2py/src/fortranobject.h ++++ b/numpy/f2py/src/fortranobject.h +@@ -45,9 +45,9 @@ Author: Pearu Peterson + + #define F2PY_MAX_DIMS 40 + +-typedef void (*f2py_set_data_func)(char *, npy_intp *); +-typedef void (*f2py_void_func)(void); +-typedef void (*f2py_init_func)(int *, npy_intp *, f2py_set_data_func, int *); ++typedef int (*f2py_set_data_func)(char*,npy_intp*); ++typedef int (*f2py_void_func)(void); ++typedef int (*f2py_init_func)(int*,npy_intp*,f2py_set_data_func,int*); + + /*typedef void* (*f2py_c_func)(void*,...);*/ + diff --git a/numpy/linalg/lapack_lite/f2c.c b/numpy/linalg/lapack_lite/f2c.c -index 1114bef3b..905beaa0d 100644 +index 9a1e9cec1..f94cd5736 100644 --- a/numpy/linalg/lapack_lite/f2c.c +++ b/numpy/linalg/lapack_lite/f2c.c @@ -14,9 +14,9 @@ @@ -135,10 +148,10 @@ index 1114bef3b..905beaa0d 100644 diff --git a/numpy/linalg/lapack_lite/f2c.h b/numpy/linalg/lapack_lite/f2c.h -index 80f1a12b1..25204797b 100644 +index d3fbfc177..b44aaac44 100644 --- a/numpy/linalg/lapack_lite/f2c.h +++ b/numpy/linalg/lapack_lite/f2c.h -@@ -259,7 +259,7 @@ extern double d_tan(double *); +@@ -263,7 +263,7 @@ extern double d_tan(double *); extern double d_tanh(double *); extern double derf_(double *); extern double derfc_(double *); @@ -147,7 +160,7 @@ index 80f1a12b1..25204797b 100644 extern integer do_lio(ftnint *, ftnint *, char *, ftnlen); extern integer do_uio(ftnint *, char *, ftnlen); extern integer e_rdfe(void); -@@ -271,7 +271,7 @@ extern integer e_rsli(void); +@@ -275,7 +275,7 @@ extern integer e_rsli(void); extern integer e_rsue(void); extern integer e_wdfe(void); extern integer e_wdue(void); @@ -156,7 +169,7 @@ index 80f1a12b1..25204797b 100644 extern integer e_wsfi(void); extern integer e_wsle(void); extern integer e_wsli(void); -@@ -346,9 +346,9 @@ extern double r_sinh(float *); +@@ -350,9 +350,9 @@ extern double r_sinh(float *); extern double r_sqrt(float *); extern double r_tan(float *); extern double r_tanh(float *); @@ -168,7 +181,7 @@ index 80f1a12b1..25204797b 100644 extern int s_paus(char *, ftnlen); extern integer s_rdfe(cilist *); extern integer s_rdue(cilist *); -@@ -363,7 +363,7 @@ extern integer s_rsue(cilist *); +@@ -367,7 +367,7 @@ extern integer s_rsue(cilist *); extern int s_stop(char *, ftnlen); extern integer s_wdfe(cilist *); extern integer s_wdue(cilist *); @@ -177,3 +190,6 @@ index 80f1a12b1..25204797b 100644 extern integer s_wsfi(icilist *); extern integer s_wsle(cilist *); extern integer s_wsli(icilist *); +-- +2.25.1 + diff --git a/packages/numpy/patches/not-build-lapack-lite-as-64-bit.patch b/packages/numpy/patches/0011-not-build-lapack-lite-as-64-bit.patch similarity index 79% rename from packages/numpy/patches/not-build-lapack-lite-as-64-bit.patch rename to packages/numpy/patches/0011-not-build-lapack-lite-as-64-bit.patch index cd2484fe97b..71566f20687 100644 --- a/packages/numpy/patches/not-build-lapack-lite-as-64-bit.patch +++ b/packages/numpy/patches/0011-not-build-lapack-lite-as-64-bit.patch @@ -1,4 +1,12 @@ +From d2fccf7fc5611cfd60fbe7a8e0bd35d665ac00f7 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:24:56 -0700 +Subject: [PATCH 11/14] not build lapack lite as 64-bit + This patch is to avoid adding macros which will build lapack-lite in 64 bit integer mode, which is not compatible with wasm's 32 bit pointers. +--- + numpy/linalg/setup.py | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/numpy/linalg/setup.py b/numpy/linalg/setup.py index 94536bb2c..df6c61b9f 100644 @@ -24,3 +32,6 @@ index 94536bb2c..df6c61b9f 100644 self.set_info(**info) lapack_info = numpy_linalg_lapack_lite().get_info(2) +-- +2.25.1 + diff --git a/packages/numpy/patches/use-local-blas-lapack.patch b/packages/numpy/patches/0012-use-local-blas-lapack.patch similarity index 64% rename from packages/numpy/patches/use-local-blas-lapack.patch rename to packages/numpy/patches/0012-use-local-blas-lapack.patch index f605b62cf68..038b3903f1d 100644 --- a/packages/numpy/patches/use-local-blas-lapack.patch +++ b/packages/numpy/patches/0012-use-local-blas-lapack.patch @@ -1,8 +1,17 @@ +From 12917e4c1b9d64f3896989d767a4cec6a15684d6 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 17:25:44 -0700 +Subject: [PATCH 12/14] use local blas lapack + +--- + numpy/distutils/system_info.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py -index 7f41bb07e..c12d9b196 100644 +index d5a1687da..1a20b59c7 100644 --- a/numpy/distutils/system_info.py +++ b/numpy/distutils/system_info.py -@@ -549,11 +549,11 @@ def get_info(name, notfound_action=0): +@@ -553,11 +553,11 @@ def get_info(name, notfound_action=0): 'numeric': Numeric_info, 'numarray': numarray_info, 'numerix': numerix_info, @@ -16,3 +25,6 @@ index 7f41bb07e..c12d9b196 100644 'blas_ilp64_opt': blas_ilp64_opt_info, 'blas_ilp64_plain_opt': blas_ilp64_plain_opt_info, 'blas64__opt': blas64__opt_info, +-- +2.25.1 + diff --git a/packages/numpy/patches/0013-fix-invalid-asm-instruction.patch b/packages/numpy/patches/0013-fix-invalid-asm-instruction.patch new file mode 100644 index 00000000000..70bb2cd9292 --- /dev/null +++ b/packages/numpy/patches/0013-fix-invalid-asm-instruction.patch @@ -0,0 +1,30 @@ +From 7ce33c4a000682040b484e08a438a2cab2f10bb1 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 18:19:39 -0700 +Subject: [PATCH 13/14] fix invalid asm instruction + +This asm instruction is invalid during compilation. Proabably due to no support from emscripten yet. Comment out for now. +--- + numpy/core/src/common/simd/intdiv.h | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/numpy/core/src/common/simd/intdiv.h b/numpy/core/src/common/simd/intdiv.h +index a7a461721..61fb4fb52 100644 +--- a/numpy/core/src/common/simd/intdiv.h ++++ b/numpy/core/src/common/simd/intdiv.h +@@ -92,8 +92,10 @@ NPY_FINLINE unsigned npyv__bitscan_revnz_u32(npy_uint32 a) + unsigned long rl; + (void)_BitScanReverse(&rl, (unsigned long)a); + r = (unsigned)rl; +-#elif defined(NPY_HAVE_SSE2) && (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) +- __asm__("bsr %1, %0" : "=r" (r) : "r"(a)); ++/** ++ *#elif defined(NPY_HAVE_SSE2) && (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) ++ * __asm__("bsr %1, %0" : "=r" (r) : "r"(a)); ++ */ + #elif defined(__GNUC__) || defined(__clang__) + r = 31 - __builtin_clz(a); // performs on arm -> clz, ppc -> cntlzw + #else +-- +2.25.1 + diff --git a/packages/numpy/patches/0014-disable-svml.patch b/packages/numpy/patches/0014-disable-svml.patch new file mode 100644 index 00000000000..93bf4861854 --- /dev/null +++ b/packages/numpy/patches/0014-disable-svml.patch @@ -0,0 +1,25 @@ +From 3f0c7d0265984d9a115432e3f2e8f5e027575076 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 31 Mar 2022 18:25:12 -0700 +Subject: [PATCH 14/14] disable svml + +--- + numpy/core/setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/numpy/core/setup.py b/numpy/core/setup.py +index cb31162d1..ba489c5ef 100644 +--- a/numpy/core/setup.py ++++ b/numpy/core/setup.py +@@ -77,7 +77,7 @@ def can_link_svml(): + return False + machine = platform.machine() + system = platform.system() +- return "x86_64" in machine and system == "Linux" ++ return False # "x86_64" in machine and system == "Linux" + + def check_svml_submodule(svmlpath): + if not os.path.exists(svmlpath + "/README.md"): +-- +2.25.1 + diff --git a/packages/numpy/patches/disable-maybe-uninitialized.patch b/packages/numpy/patches/disable-maybe-uninitialized.patch deleted file mode 100644 index 4698f4b5e4f..00000000000 --- a/packages/numpy/patches/disable-maybe-uninitialized.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/numpy/linalg/umath_linalg.c.src b/numpy/linalg/umath_linalg.c.src -index 36b99b522..7c2d21189 100644 ---- a/numpy/linalg/umath_linalg.c.src -+++ b/numpy/linalg/umath_linalg.c.src -@@ -797,7 +797,6 @@ update_pointers(npy_uint8** bases, ptrdiff_t* offsets, size_t count) - positives with this warning - */ - #pragma GCC diagnostic push --#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" - - /* - ***************************************************************************** diff --git a/packages/numpy/patches/fix-install-with-skip-build.patch b/packages/numpy/patches/fix-install-with-skip-build.patch deleted file mode 100644 index 683173a836c..00000000000 --- a/packages/numpy/patches/fix-install-with-skip-build.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/numpy/distutils/command/install_clib.py b/numpy/distutils/command/install_clib.py -index 662aa00bd..6f51e22ca 100644 ---- a/numpy/distutils/command/install_clib.py -+++ b/numpy/distutils/command/install_clib.py -@@ -20,6 +20,9 @@ class install_clib(Command): - def run (self): - build_clib_cmd = get_cmd("build_clib") - build_dir = build_clib_cmd.build_clib -+ if build_dir is None: -+ build_clib_cmd.finalize_options() -+ build_dir = build_clib_cmd.build_clib - - # We need the compiler to get the library name -> filename association - if not build_clib_cmd.compiler: diff --git a/packages/numpy/patches/fix-removed-decorators-module.patch b/packages/numpy/patches/fix-removed-decorators-module.patch deleted file mode 100644 index 73b817293e5..00000000000 --- a/packages/numpy/patches/fix-removed-decorators-module.patch +++ /dev/null @@ -1,10 +0,0 @@ -numpy.testing.decorators module has been removed from the new version of numpy. Add this back to be compatible with scipy. - - -diff --git a/numpy/testing/decorators.py b/numpy/testing/decorators.py -new file mode 100644 -index 000000000..690a9a2c6 ---- /dev/null -+++ b/numpy/testing/decorators.py -@@ -0,0 +1 @@ -+from numpy.testing._private.decorators import * diff --git a/packages/numpy/patches/limit-cpu-baseline.patch b/packages/numpy/patches/limit-cpu-baseline.patch deleted file mode 100644 index 41ce3e39035..00000000000 --- a/packages/numpy/patches/limit-cpu-baseline.patch +++ /dev/null @@ -1,17 +0,0 @@ -This is to limit the SIMD optimization level when compiling numpy due to emscripten's restriction on porting SIMD instructions: https://emscripten.org/docs/porting/simd.html#compiling-simd-code-targeting-x86-sse-instruction-set. Without this patch, we will get an error saying some intrinsic types can't be found. - -diff --git a/numpy/distutils/command/build.py b/numpy/distutils/command/build.py -index a4fda537d..a1c4bfdb1 100644 ---- a/numpy/distutils/command/build.py -+++ b/numpy/distutils/command/build.py -@@ -35,8 +35,8 @@ def initialize_options(self): - old_build.initialize_options(self) - self.fcompiler = None - self.warn_error = False -- self.cpu_baseline = "min" -- self.cpu_dispatch = "max -xop -fma4" # drop AMD legacy features by default -+ self.cpu_baseline = "min" -+ self.cpu_dispatch = "min" # drop AMD legacy features by default - self.disable_optimization = False - """ - the '_simd' module is a very large. Adding more dispatched features diff --git a/packages/numpy/patches/segfault-PyArray_Broadcast.patch b/packages/numpy/patches/segfault-PyArray_Broadcast.patch deleted file mode 100644 index 478288a969b..00000000000 --- a/packages/numpy/patches/segfault-PyArray_Broadcast.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 6f91dceca26bb06bada90e0e0bbe770538928d5f Mon Sep 17 00:00:00 2001 -From: Hood -Date: Fri, 16 Apr 2021 08:41:03 -0700 -Subject: [PATCH 2/2] Fix segfault in PyArray_Broadcast in chrome 89 #1474 - -Resolves #1473. - -This is a similar situation as with #1384 (see the other patch) however in this -case when I made modifications to `PyArray_Broadcast` to remove the offending -load, the crash moved around to other locations corresponding to different spots -in the source. After a significant amount of messing around with this, I just -reimplemented the first part of the function in Javascript. See -src/core/numpy_patch.c where we define PyArray_Broadcast_part1. - ---- - numpy/core/src/multiarray/iterators.c | 32 +++++++++++++++++++++------ - 1 file changed, 25 insertions(+), 7 deletions(-) - -diff --git a/numpy/core/src/multiarray/iterators.c b/numpy/core/src/multiarray/iterators.c -index 9da811f69..b59d32da1 100644 ---- a/numpy/core/src/multiarray/iterators.c -+++ b/numpy/core/src/multiarray/iterators.c -@@ -1153,13 +1153,13 @@ NPY_NO_EXPORT PyTypeObject PyArrayIter_Type = { - - /** END of Array Iterator **/ - --/* Adjust dimensionality and strides for index object iterators -- --- i.e. broadcast --*/ --/*NUMPY_API*/ --NPY_NO_EXPORT int --PyArray_Broadcast(PyArrayMultiIterObject *mit) --{ -+int -+PyArray_Broadcast_part1(void *mit); -+ -+#ifndef __EMSCRIPTEN__ -+int -+PyArray_Broadcast_part1(void *mit_void){ -+ PyArrayMultiIterObject *mit = (PyArrayMultiIterObject *)mit_void; - int i, nd, k, j; - npy_intp tmp; - PyArrayIterObject *it; -@@ -1195,6 +1195,24 @@ PyArray_Broadcast(PyArrayMultiIterObject *mit) - } - } - } -+ return 0; -+} -+#endif -+ -+/* Adjust dimensionality and strides for index object iterators -+ --- i.e. broadcast -+*/ -+/*NUMPY_API*/ -+NPY_NO_EXPORT int -+PyArray_Broadcast(PyArrayMultiIterObject *mit) -+{ -+ int i, nd, k, j; -+ npy_intp tmp; -+ PyArrayIterObject *it; -+ -+ if(PyArray_Broadcast_part1((void*)mit) == -1){ -+ return -1; -+ } - - /* - * Reset the iterator dimensions and strides of each iterator --- -2.17.1 - diff --git a/packages/numpy/patches/segfault-PyArray_PyIntAsIntp.patch b/packages/numpy/patches/segfault-PyArray_PyIntAsIntp.patch deleted file mode 100644 index 3bba78fb999..00000000000 --- a/packages/numpy/patches/segfault-PyArray_PyIntAsIntp.patch +++ /dev/null @@ -1,115 +0,0 @@ -From c5a48b544d465673d900459d7e9226379f79e84a Mon Sep 17 00:00:00 2001 -From: Hood -Date: Fri, 2 Apr 2021 14:24:24 -0700 -Subject: [PATCH 1/2] Fix segfault in PyArray_PyIntAsIntp in Chrome v89 #1384 - -Resolves https://github.com/pyodide/pyodide/issues/1384. - -There seems to be a bug in the v8 wasm runtime. The failure occurs in the numpy -definition of PyArray_IntpFromIndexSequence in the following code: -```C -vals[i] = PyArray_PyIntAsIntp(op); // line 958 -Py_DECREF(op); // line 959 -if(vals[i] == -1) { // line 960, fails here "RuntimeError: memory access out of bounds" - -} -``` -This is compiled to the following: -```wat -;; First calculate &vals[i]. vals is an array of i32, so this is vals + 4*i. -;; line 958: vals[i] = PyArray_PyIntAsIntp(op); -local.get $var1 ;; vals -local.get $var3 ;; i -i32.const 2 -i32.shl ;; i << 2 -i32.add ;; &vals[i] == vals + 4*i -local.tee $var7 ;; v7 = &vals[i], leave &vals[i] on stack -(; Snipped out argument setup and inlined PyArray_PyIntAsIntp ;) -call $PyArray_PyIntAsIntp -i32.store ;; vals[i] = PyArray_PyIntAsIntp(op) ;; Store to vals[i] -;; line 959: Py_DECREF(op); -(; Snipped out Py_DECREF(op) ;) -block $label1 - ;; line 960: if(vals[i] == -1) { - local.get $var7 ;; &vals[i] - i32.load ;; Load vals[i] back <== Fails here "RuntimeError: memory access out of bounds" - i32.const -1 - i32.ne ;; vals[i] != -1 - br_if $label1 -``` - -It doesn't make a whole lot of sense that it fails here, we just succesfully -stored to vals[i] so why does it fail when we load back? Unforunately the bug -disappears when devtools are open making it very difficult to assess what state -the wasm VM is in when the bug is triggered. - -The goal here was to refactor the source to remove the offending load -instruction. There are several options for how to change it, I chose the -following: -```C -int x = PyArray_PyIntAsIntp(op); // line 958 -Py_DECREF(op); // line 959 -if(x == -1) { // line 960 - -} -vals[i] = x; // line 971 -``` -Updated WAT looks like what you'd expect: -```wat - ;; line 958: int x = PyArray_PyIntAsIntp(op); - (; Snipped out argument setup and inlined PyArray_PyIntAsIntp ;) - call $PyArray_PyIntAsIntp - ;; Now instead of storing result into vals[i], we store it in a local variable - local.set $var6 - ;; line 959: Py_DECREF(op) - (; Snipped out Py_DECREF(op) ;) - ;; line 960: if(x == -1) - block $label1 - local.get $var6 ;; now check value of local variable - i32.const -1 - i32.ne - br_if $label1 - (; Snipped out body of conditional ;) - end $label1 - ;; line 971: vals[i] = x - local.get $var1 ;; vals - local.get $var3 ;; i - i32.const 2 - i32.shl ;; 4*i - i32.add ;; vals + 4*i - local.get $var6 ;; x - i32.store -``` -Indeed the offending `i32.load` is gone and so is the crash. - ---- - numpy/core/src/multiarray/conversion_utils.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/numpy/core/src/multiarray/conversion_utils.c b/numpy/core/src/multiarray/conversion_utils.c -index 52cb58726..5467f4d78 100644 ---- a/numpy/core/src/multiarray/conversion_utils.c -+++ b/numpy/core/src/multiarray/conversion_utils.c -@@ -955,9 +955,9 @@ PyArray_IntpFromIndexSequence(PyObject *seq, npy_intp *vals, npy_intp maxvals) - return -1; - } - -- vals[i] = PyArray_PyIntAsIntp(op); -+ npy_intp x = PyArray_PyIntAsIntp(op); - Py_DECREF(op); -- if(vals[i] == -1) { -+ if(x == -1) { - err = PyErr_Occurred(); - if (err && - PyErr_GivenExceptionMatches(err, PyExc_OverflowError)) { -@@ -968,6 +968,7 @@ PyArray_IntpFromIndexSequence(PyObject *seq, npy_intp *vals, npy_intp maxvals) - return -1; - } - } -+ vals[i] = x; - } - } - return nd; --- -2.17.1 - diff --git a/packages/numpy/test_numpy.py b/packages/numpy/test_numpy.py index c570e365f5c..9705e520b86 100644 --- a/packages/numpy/test_numpy.py +++ b/packages/numpy/test_numpy.py @@ -1,5 +1,11 @@ import pytest +from pyodide_build.testing import run_in_pyodide as run_in_pyodide_orig + + +def run_in_pyodide(**kwargs): + return run_in_pyodide_orig(module_scope=True, packages=["numpy"]) + def test_numpy(selenium): selenium.load_package("numpy") @@ -355,3 +361,11 @@ def test_fft(selenium): `); """ ) + + +@run_in_pyodide() +def test_np_unique(): + """Numpy comparator functions formerly had a fatal error, see PR #2110""" + import numpy as np + + np.unique(np.array([1.1, 1.1]), axis=-1) diff --git a/packages/opencv-python/cmake/Config.cmake b/packages/opencv-python/cmake/Config.cmake new file mode 100644 index 00000000000..97146e6e935 --- /dev/null +++ b/packages/opencv-python/cmake/Config.cmake @@ -0,0 +1,31 @@ +set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) +set(OPENCV_PYTHON_SKIP_LINKER_EXCLUDE_LIBS TRUE) +set(CMAKE_SKIP_COMPATIBILITY_TESTS 1) +set(CMAKE_SIZEOF_CHAR 1) +set(CMAKE_SIZEOF_UNSIGNED_SHORT 2) +set(CMAKE_SIZEOF_SHORT 2) +set(CMAKE_SIZEOF_INT 4) +set(CMAKE_SIZEOF_UNSIGNED_LONG 4) +set(CMAKE_SIZEOF_UNSIGNED_INT 4) +set(CMAKE_SIZEOF_LONG 4) +set(CMAKE_SIZEOF_VOID_P 4) +set(CMAKE_SIZEOF_FLOAT 4) +set(CMAKE_SIZEOF_DOUBLE 8) +set(CMAKE_C_SIZEOF_DATA_PTR 4) +set(CMAKE_CXX_SIZEOF_DATA_PTR 4) +set(CMAKE_HAVE_LIMITS_H 1) +set(CMAKE_HAVE_UNISTD_H 1) +set(CMAKE_HAVE_PTHREAD_H 1) +set(CMAKE_HAVE_SYS_PRCTL_H 1) +set(CMAKE_WORDS_BIGENDIAN 0) +set(CMAKE_DL_LIBS) +set(CMAKE_RANLIB echo) + +# Force filesystem support, it is disabled in Emscripten platform (needs fix) +# https://github.com/opencv/opencv/blob/17234f82d025e3bbfbf611089637e5aa2038e7b8/modules/core/include/opencv2/core/utils/filesystem.private.hpp#L10 +add_definitions(-DOPENCV_HAVE_FILESYSTEM_SUPPORT=1) + +if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") + set(EMSCRIPTEN_ROOT_PATH "$ENV{EMSCRIPTEN}") +endif() +list(APPEND CMAKE_MODULE_PATH "${EMSCRIPTEN_ROOT_PATH}/cmake/Modules") diff --git a/packages/opencv-python/cmake/OpenCVFindLibsGrfmt.cmake b/packages/opencv-python/cmake/OpenCVFindLibsGrfmt.cmake new file mode 100644 index 00000000000..f80e9605d5f --- /dev/null +++ b/packages/opencv-python/cmake/OpenCVFindLibsGrfmt.cmake @@ -0,0 +1,218 @@ +# ---------------------------------------------------------------------------- +# Detect 3rd-party image IO libraries +# ---------------------------------------------------------------------------- + +# We want to use emscripten-ported version of ZLIB, LIBJPEG, LIBPNG. +# However, OpenCV tries to find them in system paths. +# Let's deceive OpenCV and pretend we have them. + +set(HAVE_JPEG YES) +set(HAVE_PNG YES) +set(ZLIB_FOUND YES) + +# --- libtiff (optional, should be searched after zlib and libjpeg) --- +if(WITH_TIFF) + if(BUILD_TIFF) + ocv_clear_vars(TIFF_FOUND) + else() + ocv_clear_internal_cache_vars(TIFF_LIBRARY TIFF_INCLUDE_DIR) + include(FindTIFF) + if(TIFF_FOUND) + ocv_parse_header("${TIFF_INCLUDE_DIR}/tiff.h" TIFF_VERSION_LINES TIFF_VERSION_CLASSIC TIFF_VERSION_BIG TIFF_VERSION TIFF_BIGTIFF_VERSION) + endif() + endif() + + if(NOT TIFF_FOUND) + ocv_clear_vars(TIFF_LIBRARY TIFF_LIBRARIES TIFF_INCLUDE_DIR) + + set(TIFF_LIBRARY libtiff CACHE INTERNAL "") + set(TIFF_LIBRARIES ${TIFF_LIBRARY}) + add_subdirectory("${OpenCV_SOURCE_DIR}/3rdparty/libtiff") + set(TIFF_INCLUDE_DIR "${${TIFF_LIBRARY}_SOURCE_DIR}" "${${TIFF_LIBRARY}_BINARY_DIR}" CACHE INTERNAL "") + ocv_parse_header("${${TIFF_LIBRARY}_SOURCE_DIR}/tiff.h" TIFF_VERSION_LINES TIFF_VERSION_CLASSIC TIFF_VERSION_BIG TIFF_VERSION TIFF_BIGTIFF_VERSION) + endif() + + if(TIFF_VERSION_CLASSIC AND NOT TIFF_VERSION) + set(TIFF_VERSION ${TIFF_VERSION_CLASSIC}) + endif() + + if(TIFF_BIGTIFF_VERSION AND NOT TIFF_VERSION_BIG) + set(TIFF_VERSION_BIG ${TIFF_BIGTIFF_VERSION}) + endif() + + if(NOT TIFF_VERSION_STRING AND TIFF_INCLUDE_DIR) + list(GET TIFF_INCLUDE_DIR 0 _TIFF_INCLUDE_DIR) + if(EXISTS "${_TIFF_INCLUDE_DIR}/tiffvers.h") + file(STRINGS "${_TIFF_INCLUDE_DIR}/tiffvers.h" tiff_version_str REGEX "^#define[\t ]+TIFFLIB_VERSION_STR[\t ]+\"LIBTIFF, Version .*") + string(REGEX REPLACE "^#define[\t ]+TIFFLIB_VERSION_STR[\t ]+\"LIBTIFF, Version +([^ \\n]*).*" "\\1" TIFF_VERSION_STRING "${tiff_version_str}") + unset(tiff_version_str) + endif() + unset(_TIFF_INCLUDE_DIR) + endif() + + set(HAVE_TIFF YES) +endif() + +# --- libwebp (optional) --- + +if(WITH_WEBP) + if(BUILD_WEBP) + ocv_clear_vars(WEBP_FOUND WEBP_LIBRARY WEBP_LIBRARIES WEBP_INCLUDE_DIR) + else() + ocv_clear_internal_cache_vars(WEBP_LIBRARY WEBP_INCLUDE_DIR) + include(cmake/OpenCVFindWebP.cmake) + if(WEBP_FOUND) + set(HAVE_WEBP 1) + endif() + endif() +endif() + +# --- Add libwebp to 3rdparty/libwebp and compile it if not available --- +if(WITH_WEBP AND NOT WEBP_FOUND + AND (NOT ANDROID OR HAVE_CPUFEATURES) +) + ocv_clear_vars(WEBP_LIBRARY WEBP_INCLUDE_DIR) + set(WEBP_LIBRARY libwebp CACHE INTERNAL "") + set(WEBP_LIBRARIES ${WEBP_LIBRARY}) + + add_subdirectory("${OpenCV_SOURCE_DIR}/3rdparty/libwebp") + set(WEBP_INCLUDE_DIR "${${WEBP_LIBRARY}_SOURCE_DIR}/src" CACHE INTERNAL "") + set(HAVE_WEBP 1) +endif() + +if(NOT WEBP_VERSION AND WEBP_INCLUDE_DIR) + ocv_clear_vars(ENC_MAJ_VERSION ENC_MIN_VERSION ENC_REV_VERSION) + if(EXISTS "${WEBP_INCLUDE_DIR}/enc/vp8enci.h") + ocv_parse_header("${WEBP_INCLUDE_DIR}/enc/vp8enci.h" WEBP_VERSION_LINES ENC_MAJ_VERSION ENC_MIN_VERSION ENC_REV_VERSION) + set(WEBP_VERSION "${ENC_MAJ_VERSION}.${ENC_MIN_VERSION}.${ENC_REV_VERSION}") + elseif(EXISTS "${WEBP_INCLUDE_DIR}/webp/encode.h") + file(STRINGS "${WEBP_INCLUDE_DIR}/webp/encode.h" WEBP_ENCODER_ABI_VERSION REGEX "#define[ \t]+WEBP_ENCODER_ABI_VERSION[ \t]+([x0-9a-f]+)" ) + if(WEBP_ENCODER_ABI_VERSION MATCHES "#define[ \t]+WEBP_ENCODER_ABI_VERSION[ \t]+([x0-9a-f]+)") + set(WEBP_ENCODER_ABI_VERSION "${CMAKE_MATCH_1}") + set(WEBP_VERSION "encoder: ${WEBP_ENCODER_ABI_VERSION}") + else() + unset(WEBP_ENCODER_ABI_VERSION) + endif() + endif() +endif() + +# --- libopenjp2 (optional, check before libjasper) --- +if(WITH_OPENJPEG) + if(BUILD_OPENJPEG) + ocv_clear_vars(OpenJPEG_FOUND) + else() + find_package(OpenJPEG QUIET) + endif() + + if(NOT OpenJPEG_FOUND OR OPENJPEG_MAJOR_VERSION LESS 2) + ocv_clear_vars(OPENJPEG_MAJOR_VERSION OPENJPEG_MINOR_VERSION OPENJPEG_BUILD_VERSION OPENJPEG_LIBRARIES OPENJPEG_INCLUDE_DIRS) + message(STATUS "Could NOT find OpenJPEG (minimal suitable version: 2.0, " + "recommended version >= 2.3.1). OpenJPEG will be built from sources") + add_subdirectory("${OpenCV_SOURCE_DIR}/3rdparty/openjpeg") + if(OCV_CAN_BUILD_OPENJPEG) + set(HAVE_OPENJPEG YES) + message(STATUS "OpenJPEG libraries will be built from sources: ${OPENJPEG_LIBRARIES} " + "(version \"${OPENJPEG_VERSION}\")") + else() + set(HAVE_OPENJPEG NO) + message(STATUS "OpenJPEG libraries can't be built from sources. System requirements are not fulfilled.") + endif() + else() + set(HAVE_OPENJPEG YES) + message(STATUS "Found system OpenJPEG: ${OPENJPEG_LIBRARIES} " + "(found version \"${OPENJPEG_VERSION}\")") + endif() +endif() + +# --- libjasper (optional, should be searched after libjpeg) --- +if(WITH_JASPER AND NOT HAVE_OPENJPEG) + if(BUILD_JASPER) + ocv_clear_vars(JASPER_FOUND) + else() + include(FindJasper) + endif() + + if(NOT JASPER_FOUND) + ocv_clear_vars(JASPER_LIBRARY JASPER_LIBRARIES JASPER_INCLUDE_DIR) + + set(JASPER_LIBRARY libjasper CACHE INTERNAL "") + set(JASPER_LIBRARIES ${JASPER_LIBRARY}) + add_subdirectory("${OpenCV_SOURCE_DIR}/3rdparty/libjasper") + set(JASPER_INCLUDE_DIR "${${JASPER_LIBRARY}_SOURCE_DIR}" CACHE INTERNAL "") + endif() + + set(HAVE_JASPER YES) + + if(NOT JASPER_VERSION_STRING) + ocv_parse_header2(JASPER "${JASPER_INCLUDE_DIR}/jasper/jas_config.h" JAS_VERSION "") + endif() +endif() + +# --- OpenEXR (optional) --- +if(WITH_OPENEXR) + ocv_clear_vars(HAVE_OPENEXR) + if(NOT BUILD_OPENEXR) + ocv_clear_internal_cache_vars(OPENEXR_INCLUDE_PATHS OPENEXR_LIBRARIES OPENEXR_ILMIMF_LIBRARY OPENEXR_VERSION) + include("${OpenCV_SOURCE_DIR}/cmake/OpenCVFindOpenEXR.cmake") + endif() + + if(OPENEXR_FOUND) + set(HAVE_OPENEXR YES) + else() + ocv_clear_vars(OPENEXR_INCLUDE_PATHS OPENEXR_LIBRARIES OPENEXR_ILMIMF_LIBRARY OPENEXR_VERSION) + + set(OPENEXR_LIBRARIES IlmImf) + add_subdirectory("${OpenCV_SOURCE_DIR}/3rdparty/openexr") + if(OPENEXR_VERSION) # check via TARGET doesn't work + set(BUILD_OPENEXR ON) + set(HAVE_OPENEXR YES) + set(BUILD_OPENEXR ON) + endif() + endif() +endif() + +# --- GDAL (optional) --- +if(WITH_GDAL) + find_package(GDAL QUIET) + + if(NOT GDAL_FOUND) + set(HAVE_GDAL NO) + ocv_clear_vars(GDAL_VERSION GDAL_LIBRARIES) + else() + set(HAVE_GDAL YES) + ocv_include_directories(${GDAL_INCLUDE_DIR}) + endif() +endif() + +if(WITH_GDCM) + find_package(GDCM QUIET) + if(NOT GDCM_FOUND) + set(HAVE_GDCM NO) + ocv_clear_vars(GDCM_VERSION GDCM_LIBRARIES) + else() + set(HAVE_GDCM YES) + # include(${GDCM_USE_FILE}) + set(GDCM_LIBRARIES gdcmMSFF) # GDCM does not set this variable for some reason + endif() +endif() + +if(WITH_IMGCODEC_HDR) + set(HAVE_IMGCODEC_HDR ON) +elseif(DEFINED WITH_IMGCODEC_HDR) + set(HAVE_IMGCODEC_HDR OFF) +endif() +if(WITH_IMGCODEC_SUNRASTER) + set(HAVE_IMGCODEC_SUNRASTER ON) +elseif(DEFINED WITH_IMGCODEC_SUNRASTER) + set(HAVE_IMGCODEC_SUNRASTER OFF) +endif() +if(WITH_IMGCODEC_PXM) + set(HAVE_IMGCODEC_PXM ON) +elseif(DEFINED WITH_IMGCODEC_PXM) + set(HAVE_IMGCODEC_PXM OFF) +endif() +if(WITH_IMGCODEC_PFM) + set(HAVE_IMGCODEC_PFM ON) +elseif(DEFINED WITH_IMGCODEC_PFM) + set(HAVE_IMGCODEC_PFM OFF) +endif() diff --git a/packages/opencv-python/cmake/build_args.sh b/packages/opencv-python/cmake/build_args.sh new file mode 100755 index 00000000000..e77e220b84c --- /dev/null +++ b/packages/opencv-python/cmake/build_args.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +export CMAKE_ARGS=" \ +-DCMAKE_TOOLCHAIN_FILE=$PYODIDE_ROOT/packages/opencv-python/cmake/Config.cmake \ +-DPYTHON3_INCLUDE_PATH=$PYTHONINCLUDE \ +-DPYTHON3_LIBRARY=$PYTHONINCLUDE/../libpython$PYMAJOR.$PYMINOR.a \ +-DPYTHON3_VERSION_MAJOR=$PYMAJOR \ +-DPYTHON3_VERSION_MINOR=$PYMINOR \ +-DPYTHON3_NUMPY_INCLUDE_DIRS=$NUMPY_INCLUDE_DIR \ +\ +-DWITH_ADE=ON \ +-DWITH_JPEG=ON \ +-DWITH_PNG=ON \ +-DWITH_WEBP=ON \ +-DBUILD_WEBP=OFF \ +-DWEBP_INCLUDE_DIR=$LIBWEBP_ROOT/include \ +-DWEBP_LIBRARY=$LIBWEBP_ROOT/lib/libwebp.a \ +\ +-DBUILD_opencv_python3=ON \ +-DBUILD_opencv_world=ON \ +-DBUILD_opencv_imgcodecs=ON \ +-DBUILD_opencv_videoio=ON \ +-DBUILD_opencv_gapi=ON \ +-DBUILD_opencv_photo=ON \ +-DBUILD_opencv_stitching=ON \ +-DBUILD_opencv_highgui=ON \ +-DBUILD_opencv_features2d=ON \ +-DBUILD_opencv_flann=ON \ +-DBUILD_opencv_calib3d=ON \ +-DBUILD_opencv_dnn=ON \ +-DBUILD_opencv_ml=ON \ +-DBUILD_opencv_objdetect=ON \ +-DWITH_OPENCL=OFF \ +-DOPENCV_DNN_OPENCL=OFF \ +-DWITH_PROTOBUF=ON \ +-DWITH_FFMPEG=ON \ +\ +-DPYTHON3_EXECUTABLE=python \ +-DPYTHON3_LIMITED_API=ON \ +-DPYTHON_DEFAULT_EXECUTABLE=python \ +-DENABLE_PIC=FALSE \ +-DCMAKE_BUILD_TYPE=Release \ +-DCPU_BASELINE='' \ +-DCPU_DISPATCH='' \ +-DCV_TRACE=OFF \ +-DBUILD_SHARED_LIBS=OFF \ +-DWITH_1394=OFF \ +-DWITH_VTK=OFF \ +-DWITH_EIGEN=OFF \ +-DWITH_GSTREAMER=OFF \ +-DWITH_GTK=OFF \ +-DWITH_GTK_2_X=OFF \ +-DWITH_QT=OFF \ +-DWITH_IPP=OFF \ +-DWITH_JASPER=OFF \ +-DWITH_OPENJPEG=OFF \ +-DWITH_OPENEXR=OFF \ +-DWITH_OPENGL=OFF \ +-DWITH_OPENVX=OFF \ +-DWITH_OPENNI=OFF \ +-DWITH_OPENNI2=OFF \ +-DWITH_TBB=OFF \ +-DWITH_TIFF=OFF \ +-DWITH_V4L=OFF \ +-DWITH_OPENCL_SVM=OFF \ +-DWITH_OPENCLAMDFFT=OFF \ +-DWITH_OPENCLAMDBLAS=OFF \ +-DWITH_GPHOTO2=OFF \ +-DWITH_LAPACK=OFF \ +-DWITH_ITT=OFF \ +-DWITH_QUIRC=OFF \ +-DBUILD_ZLIB=OFF \ +-DBUILD_opencv_apps=OFF \ +-DBUILD_opencv_shape=OFF \ +-DBUILD_opencv_videostab=OFF \ +-DBUILD_opencv_superres=OFF \ +-DBUILD_opencv_java=OFF \ +-DBUILD_opencv_js=OFF \ +-DBUILD_opencv_python2=OFF \ +-DBUILD_EXAMPLES=OFF \ +-DBUILD_PACKAGE=OFF \ +-DBUILD_TESTS=OFF \ +-DBUILD_PERF_TESTS=OFF \ +-DBUILD_DOCS=OFF \ +-DWITH_PTHREADS_PF=OFF \ +-DCV_ENABLE_INTRINSICS=OFF \ +-DBUILD_WASM_INTRIN_TESTS=OFF \ +-DCMAKE_INSTALL_PREFIX=../cmake-install \ +-DCMAKE_VERBOSE_MAKEFILE=ON \ +" diff --git a/packages/opencv-python/cmake/detect_ffmpeg.cmake b/packages/opencv-python/cmake/detect_ffmpeg.cmake new file mode 100644 index 00000000000..772ee62a0f7 --- /dev/null +++ b/packages/opencv-python/cmake/detect_ffmpeg.cmake @@ -0,0 +1,49 @@ +# --- FFMPEG --- + +set(HAVE_FFMPEG TRUE) +set(FFMPEG_FOUND TRUE) + +set(FFMPEG_ROOT_PATH "$ENV{FFMPEG_ROOT}") +set(FFMPEG_INCLUDE_DIRS "${FFMPEG_ROOT_PATH}/include") +set(FFMPEG_LIBRARIES + "${FFMPEG_ROOT_PATH}/lib/libavcodec.a" + "${FFMPEG_ROOT_PATH}/lib/libavformat.a" + "${FFMPEG_ROOT_PATH}/lib/libavutil.a" + "${FFMPEG_ROOT_PATH}/lib/libswscale.a" + "${FFMPEG_ROOT_PATH}/lib/libswresample.a" +) + +ocv_add_external_target(ffmpeg "${FFMPEG_INCLUDE_DIRS}" "${FFMPEG_LIBRARIES}" "HAVE_FFMPEG") + +set(__builtin_defines "") +set(__builtin_include_dirs "") +set(__builtin_libs "") +set(__plugin_defines "") +set(__plugin_include_dirs "") +set(__plugin_libs "") +if(HAVE_OPENCL) +set(__opencl_dirs "") +if(OPENCL_INCLUDE_DIRS) + set(__opencl_dirs "${OPENCL_INCLUDE_DIRS}") +elseif(OPENCL_INCLUDE_DIR) + set(__opencl_dirs "${OPENCL_INCLUDE_DIR}") +else() + set(__opencl_dirs "${OpenCV_SOURCE_DIR}/3rdparty/include/opencl/1.2") +endif() +# extra dependencies for buildin code (OpenCL dir is required for extensions like cl_d3d11.h) +# buildin HAVE_OPENCL is already defined through cvconfig.h +list(APPEND __builtin_include_dirs "${__opencl_dirs}") + +# extra dependencies for +list(APPEND __plugin_defines "HAVE_OPENCL") +list(APPEND __plugin_include_dirs "${__opencl_dirs}") +endif() + +# TODO: libva, d3d11 + +if(__builtin_include_dirs OR __builtin_include_defines OR __builtin_include_libs) +ocv_add_external_target(ffmpeg.builtin_deps "${__builtin_include_dirs}" "${__builtin_include_libs}" "${__builtin_defines}") +endif() +if(VIDEOIO_ENABLE_PLUGINS AND __plugin_include_dirs OR __plugin_include_defines OR __plugin_include_libs) +ocv_add_external_target(ffmpeg.plugin_deps "${__plugin_include_dirs}" "${__plugin_include_libs}" "${__plugin_defines}") +endif() diff --git a/packages/opencv-python/meta.yaml b/packages/opencv-python/meta.yaml new file mode 100644 index 00000000000..d77617e84da --- /dev/null +++ b/packages/opencv-python/meta.yaml @@ -0,0 +1,65 @@ +package: + name: opencv-python + version: 4.5.5.64 +about: + home: https://github.com/skvark/opencv-python + PyPI: https://pypi.org/project/opencv-python + summary: Wrapper package for OpenCV python bindings. + license: MIT +source: + url: https://files.pythonhosted.org/packages/3c/61/ee4496192ed27f657532fdf0d814b05b9787e7fc5122ed3ca57282bae69c/opencv-python-4.5.5.64.tar.gz + sha256: f65de0446a330c3b773cd04ba10345d8ce1b15dcac3f49770204e37602d0b3f7 + extras: + - [cmake/OpenCVFindLibsGrfmt.cmake, opencv/cmake/OpenCVFindLibsGrfmt.cmake] + - [ + cmake/detect_ffmpeg.cmake, + opencv/modules/videoio/cmake/detect_ffmpeg.cmake, + ] + patches: + - patches/0001-Enable-file-system.patch + +requirements: + run: + - numpy + - ffmpeg + - libwebp +build: + cxxflags: | + -fPIC + -s USE_ZLIB=1 + -s USE_LIBJPEG=1 + -s USE_LIBPNG=1 + -s SIDE_MODULE=1 + ldflags: | + -ljpeg + -lz + -lpng + + # Note on CMAKE_ARGS: + # CMake args are adopted from OpenCV.js (https://github.com/opencv/opencv/blob/4.x/platforms/js/build_js.py) + # But we support more of modules than OpenCV.js. + # + # Note on CMAKE_TOOLCHAIN_FILE: + # We don't want to use toolchain file provided by Emscripten, + # because our build script hijack gcc, c++, ... and replace it with emcc, em++, ..., instead of calling them directly. + # + # List of OpenCV modules can be found at: https://docs.opencv.org/4.x/ + # Build configs can be found at: https://docs.opencv.org/4.x/db/d05/tutorial_config_reference.html + + script: | + pip install scikit-build + # TODO: remove this line after version update (https://github.com/opencv/opencv-python/issues/648) + sed -i "s/cmake_install_dir=cmake_install_reldir/_cmake_install_dir=cmake_install_reldir/" setup.py + + # export VERBOSE=1 + + export NUMPY_INCLUDE_DIR="$HOSTINSTALLDIR/lib/python$PYMAJOR.$PYMINOR/site-packages/numpy/core/include/" + export EMSCRIPTEN="$PYODIDE_ROOT/emsdk/emsdk/upstream/emscripten/" + export FFMPEG_ROOT="$PYODIDE_ROOT/packages/ffmpeg/build/ffmpeg-4.4.1/lib" + export LIBWEBP_ROOT="$PYODIDE_ROOT/packages/libwebp/build/libwebp-1.2.2/build/lib" + + source $PYODIDE_ROOT/packages/opencv-python/cmake/build_args.sh + +test: + imports: + - cv2 diff --git a/packages/opencv-python/patches/0001-Enable-file-system.patch b/packages/opencv-python/patches/0001-Enable-file-system.patch new file mode 100644 index 00000000000..b9b2093b1af --- /dev/null +++ b/packages/opencv-python/patches/0001-Enable-file-system.patch @@ -0,0 +1,75 @@ +From c7e9f892204ce1e47774fe21790195e2cbd7f2b3 Mon Sep 17 00:00:00 2001 +From: ryanking13 +Date: Tue, 29 Mar 2022 01:58:40 +0000 +Subject: [PATCH] Enable file system + +--- + .../include/opencv2/core/utils/plugin_loader.private.hpp | 8 ++++---- + modules/core/src/utils/filesystem.cpp | 4 ++-- + 2 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp b/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp +index d6390fc74a..c089309443 100644 +--- a/opencv/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp ++++ b/opencv/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp +@@ -12,7 +12,7 @@ + + #if defined(_WIN32) + #include +-#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) ++#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) + #include + #endif + +@@ -65,7 +65,7 @@ void* getSymbol_(LibHandle_t h, const char* symbolName) + { + #if defined(_WIN32) + return (void*)GetProcAddress(h, symbolName); +-#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) ++#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) + return dlsym(h, symbolName); + #endif + } +@@ -79,7 +79,7 @@ LibHandle_t libraryLoad_(const FileSystemPath_t& filename) + # else + return LoadLibraryW(filename.c_str()); + #endif +-#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) ++#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) + void* handle = dlopen(filename.c_str(), RTLD_NOW); + CV_LOG_IF_DEBUG(NULL, !handle, "dlopen() error: " << dlerror()); + return handle; +@@ -91,7 +91,7 @@ void libraryRelease_(LibHandle_t h) + { + #if defined(_WIN32) + FreeLibrary(h); +-#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) ++#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) + dlclose(h); + #endif + } +diff --git a/modules/core/src/utils/filesystem.cpp b/modules/core/src/utils/filesystem.cpp +index 663ec311e4..3ef4408d2d 100644 +--- a/opencv/modules/core/src/utils/filesystem.cpp ++++ b/opencv/modules/core/src/utils/filesystem.cpp +@@ -34,7 +34,7 @@ + #include + #include + #include +-#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ ++#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ + #include + #include + #include +@@ -343,7 +343,7 @@ private: + Impl& operator=(const Impl&); // disabled + }; + +-#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ ++#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ + + struct FileLock::Impl + { +-- +2.35.1 + diff --git a/packages/opencv-python/reference-images/baboon.png b/packages/opencv-python/reference-images/baboon.png new file mode 100644 index 00000000000..fb5d09e2c9e Binary files /dev/null and b/packages/opencv-python/reference-images/baboon.png differ diff --git a/packages/opencv-python/reference-images/baboon_canny.png b/packages/opencv-python/reference-images/baboon_canny.png new file mode 100644 index 00000000000..70a97f59892 Binary files /dev/null and b/packages/opencv-python/reference-images/baboon_canny.png differ diff --git a/packages/opencv-python/reference-images/baboon_decolor_color_boost.png b/packages/opencv-python/reference-images/baboon_decolor_color_boost.png new file mode 100644 index 00000000000..cefa9ccaa39 Binary files /dev/null and b/packages/opencv-python/reference-images/baboon_decolor_color_boost.png differ diff --git a/packages/opencv-python/reference-images/baboon_decolor_grayscale.png b/packages/opencv-python/reference-images/baboon_decolor_grayscale.png new file mode 100644 index 00000000000..44bd68da4fc Binary files /dev/null and b/packages/opencv-python/reference-images/baboon_decolor_grayscale.png differ diff --git a/packages/opencv-python/reference-images/baboon_kaze.png b/packages/opencv-python/reference-images/baboon_kaze.png new file mode 100644 index 00000000000..ef185710a08 Binary files /dev/null and b/packages/opencv-python/reference-images/baboon_kaze.png differ diff --git a/packages/opencv-python/reference-images/baboon_laplacian.png b/packages/opencv-python/reference-images/baboon_laplacian.png new file mode 100644 index 00000000000..384b8cd328f Binary files /dev/null and b/packages/opencv-python/reference-images/baboon_laplacian.png differ diff --git a/packages/opencv-python/reference-images/baboon_sobel.png b/packages/opencv-python/reference-images/baboon_sobel.png new file mode 100644 index 00000000000..7d2ae610fdb Binary files /dev/null and b/packages/opencv-python/reference-images/baboon_sobel.png differ diff --git a/packages/opencv-python/reference-images/box.png b/packages/opencv-python/reference-images/box.png new file mode 100644 index 00000000000..6f01082f796 Binary files /dev/null and b/packages/opencv-python/reference-images/box.png differ diff --git a/packages/opencv-python/reference-images/box_in_scene.png b/packages/opencv-python/reference-images/box_in_scene.png new file mode 100644 index 00000000000..cff246a3fef Binary files /dev/null and b/packages/opencv-python/reference-images/box_in_scene.png differ diff --git a/packages/opencv-python/reference-images/box_sift.png b/packages/opencv-python/reference-images/box_sift.png new file mode 100644 index 00000000000..22ccc031fae Binary files /dev/null and b/packages/opencv-python/reference-images/box_sift.png differ diff --git a/packages/opencv-python/reference-images/chessboard.png b/packages/opencv-python/reference-images/chessboard.png new file mode 100644 index 00000000000..3a68ab48420 Binary files /dev/null and b/packages/opencv-python/reference-images/chessboard.png differ diff --git a/packages/opencv-python/reference-images/chessboard_corners.png b/packages/opencv-python/reference-images/chessboard_corners.png new file mode 100644 index 00000000000..81d6f9a6821 Binary files /dev/null and b/packages/opencv-python/reference-images/chessboard_corners.png differ diff --git a/packages/opencv-python/reference-images/mnist.onnx b/packages/opencv-python/reference-images/mnist.onnx new file mode 100644 index 00000000000..b0817f7a340 Binary files /dev/null and b/packages/opencv-python/reference-images/mnist.onnx differ diff --git a/packages/opencv-python/reference-images/mnist_2.png b/packages/opencv-python/reference-images/mnist_2.png new file mode 100644 index 00000000000..e3b26cbdad6 Binary files /dev/null and b/packages/opencv-python/reference-images/mnist_2.png differ diff --git a/packages/opencv-python/reference-images/monalisa.png b/packages/opencv-python/reference-images/monalisa.png new file mode 100644 index 00000000000..0e276f8337c Binary files /dev/null and b/packages/opencv-python/reference-images/monalisa.png differ diff --git a/packages/opencv-python/reference-images/monalisa_facedetect.png b/packages/opencv-python/reference-images/monalisa_facedetect.png new file mode 100644 index 00000000000..3e149958d81 Binary files /dev/null and b/packages/opencv-python/reference-images/monalisa_facedetect.png differ diff --git a/packages/opencv-python/reference-images/mountain1.png b/packages/opencv-python/reference-images/mountain1.png new file mode 100644 index 00000000000..96a89c8f95b Binary files /dev/null and b/packages/opencv-python/reference-images/mountain1.png differ diff --git a/packages/opencv-python/reference-images/mountain2.png b/packages/opencv-python/reference-images/mountain2.png new file mode 100644 index 00000000000..c7e48cc7bba Binary files /dev/null and b/packages/opencv-python/reference-images/mountain2.png differ diff --git a/packages/opencv-python/reference-images/pca.png b/packages/opencv-python/reference-images/pca.png new file mode 100644 index 00000000000..b256602261a Binary files /dev/null and b/packages/opencv-python/reference-images/pca.png differ diff --git a/packages/opencv-python/reference-images/pca_result.png b/packages/opencv-python/reference-images/pca_result.png new file mode 100644 index 00000000000..afa916af55a Binary files /dev/null and b/packages/opencv-python/reference-images/pca_result.png differ diff --git a/packages/opencv-python/reference-images/traffic.mp4 b/packages/opencv-python/reference-images/traffic.mp4 new file mode 100644 index 00000000000..5968ccf1168 Binary files /dev/null and b/packages/opencv-python/reference-images/traffic.mp4 differ diff --git a/packages/opencv-python/reference-images/traffic_optical_flow.png b/packages/opencv-python/reference-images/traffic_optical_flow.png new file mode 100644 index 00000000000..4a99cbbf987 Binary files /dev/null and b/packages/opencv-python/reference-images/traffic_optical_flow.png differ diff --git a/packages/opencv-python/test_opencv_python.py b/packages/opencv-python/test_opencv_python.py new file mode 100644 index 00000000000..7acf4d88775 --- /dev/null +++ b/packages/opencv-python/test_opencv_python.py @@ -0,0 +1,551 @@ +import base64 +import pathlib + +from pyodide_build.testing import run_in_pyodide + +REFERENCE_IMAGES_PATH = pathlib.Path(__file__).parent / "reference-images" + + +def compare_with_reference_image(selenium, reference_image, var="img", grayscale=True): + reference_image_encoded = base64.b64encode(reference_image.read_bytes()) + grayscale = "cv.IMREAD_GRAYSCALE" if grayscale else "cv.IMREAD_COLOR" + match_ratio = selenium.run( + f""" + import base64 + import numpy as np + import cv2 as cv + DIFF_THRESHOLD = 2 + arr = np.frombuffer(base64.b64decode({reference_image_encoded!r}), np.uint8) + ref_data = cv.imdecode(arr, {grayscale}) + + pixels_match = np.count_nonzero(np.abs({var}.astype(np.int16) - ref_data.astype(np.int16)) <= DIFF_THRESHOLD) + pixels_total = ref_data.size + float(pixels_match / pixels_total) + """ + ) + + # Due to some randomness in the result, we allow a small difference + return match_ratio > 0.95 + + +def test_import(selenium): + selenium.set_script_timeout(60) + selenium.load_package("opencv-python") + selenium.run( + """ + import cv2 + cv2.__version__ + """ + ) + + +@run_in_pyodide(packages=["opencv-python", "numpy"]) +def test_image_extensions(): + import cv2 as cv + import numpy as np + + shape = (16, 16, 3) + img = np.zeros(shape, np.uint8) + + extensions = { + "bmp": b"BM6\x03", + "jpg": b"\xff\xd8\xff\xe0", + "jpeg": b"\xff\xd8\xff\xe0", + "png": b"\x89PNG", + "webp": b"RIFF", + } + + for ext, signature in extensions.items(): + result, buf = cv.imencode(f".{ext}", img) + assert result + assert bytes(buf[:4]) == signature + + +@run_in_pyodide(packages=["opencv-python", "numpy"]) +def test_io(): + import cv2 as cv + import numpy as np + + shape = (16, 16, 3) + img = np.zeros(shape, np.uint8) + + filename = "test.bmp" + cv.imwrite(filename, img) + img_ = cv.imread(filename) + assert img_.shape == img.shape + + +@run_in_pyodide(packages=["opencv-python", "numpy"]) +def test_drawing(): + import cv2 as cv + import numpy as np + + width = 100 + height = 100 + shape = (width, height, 3) + img = np.zeros(shape, np.uint8) + + cv.line(img, (0, 0), (width - 1, 0), (255, 0, 0), 5) + cv.line(img, (0, 0), (0, height - 1), (0, 0, 255), 5) + cv.rectangle(img, (0, 0), (width // 2, height // 2), (0, 255, 0), 2) + cv.circle(img, (0, 0), radius=width // 2, color=(255, 0, 0)) + cv.putText(img, "Hello Pyodide", (0, 0), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) + + +@run_in_pyodide(packages=["opencv-python", "numpy"]) +def test_pixel_access(): + import cv2 as cv + import numpy as np + + shape = (16, 16, 3) + img = np.zeros(shape, np.uint8) + + img[5, 5] = [1, 2, 3] + assert list(img[5, 5]) == [1, 2, 3] + + b, g, r = cv.split(img) + img_ = cv.merge([b, g, r]) + assert (img == img_).all() + + +@run_in_pyodide(packages=["opencv-python", "numpy"]) +def test_image_processing(): + import cv2 as cv + import numpy as np + + # Masking + img = np.random.randint(0, 255, size=500) + lower = np.array([0]) + upper = np.array([200]) + mask = cv.inRange(img, lower, upper) + res = cv.bitwise_and(img, img, mask=mask) + assert not (res > 200).any() + + +def test_edge_detection(selenium): + original_img = base64.b64encode((REFERENCE_IMAGES_PATH / "baboon.png").read_bytes()) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + src = np.frombuffer(base64.b64decode({original_img!r}), np.uint8) + src = cv.imdecode(src, cv.IMREAD_COLOR) + gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY) + sobel = cv.Sobel(gray, cv.CV_8U, 1, 0, 3) + laplacian = cv.Laplacian(gray, cv.CV_8U, ksize=3) + canny = cv.Canny(src, 100, 255) + None + """ + ) + + assert compare_with_reference_image( + selenium, REFERENCE_IMAGES_PATH / "baboon_sobel.png", "sobel" + ) + assert compare_with_reference_image( + selenium, REFERENCE_IMAGES_PATH / "baboon_laplacian.png", "laplacian" + ) + assert compare_with_reference_image( + selenium, REFERENCE_IMAGES_PATH / "baboon_canny.png", "canny" + ) + + +def test_photo_decolor(selenium): + original_img = base64.b64encode((REFERENCE_IMAGES_PATH / "baboon.png").read_bytes()) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + src = np.frombuffer(base64.b64decode({original_img!r}), np.uint8) + src = cv.imdecode(src, cv.IMREAD_COLOR) + grayscale, color_boost = cv.decolor(src) + None + """ + ) + + assert compare_with_reference_image( + selenium, REFERENCE_IMAGES_PATH / "baboon_decolor_grayscale.png", "grayscale" + ) + assert compare_with_reference_image( + selenium, + REFERENCE_IMAGES_PATH / "baboon_decolor_color_boost.png", + "color_boost", + grayscale=False, + ) + + +def test_stitch(selenium): + original_img_left = base64.b64encode( + (REFERENCE_IMAGES_PATH / "mountain1.png").read_bytes() + ) + original_img_right = base64.b64encode( + (REFERENCE_IMAGES_PATH / "mountain2.png").read_bytes() + ) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + left = np.frombuffer(base64.b64decode({original_img_left!r}), np.uint8) + left = cv.imdecode(left, cv.IMREAD_COLOR) + right = np.frombuffer(base64.b64decode({original_img_right!r}), np.uint8) + right = cv.imdecode(right, cv.IMREAD_COLOR) + stitcher = cv.Stitcher.create(cv.Stitcher_PANORAMA) + status, panorama = stitcher.stitch([left, right]) + + # It seems that the result is not always the same due to the randomness, so check the status and size instead + assert status == cv.Stitcher_OK + assert panorama.shape[0] >= max(left.shape[0], right.shape[0]) + assert panorama.shape[1] >= max(left.shape[1], right.shape[1]) + """ + ) + + +def test_video_optical_flow(selenium): + original_img = base64.b64encode( + (REFERENCE_IMAGES_PATH / "traffic.mp4").read_bytes() + ) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + + src = base64.b64decode({original_img!r}) + + video_path = "video.mp4" + with open(video_path, "wb") as f: + f.write(src) + + cap = cv.VideoCapture(video_path) + assert cap.isOpened() + + # params for ShiTomasi corner detection + feature_params = dict( maxCorners = 100, + qualityLevel = 0.3, + minDistance = 7, + blockSize = 7 ) + # Parameters for lucas kanade optical flow + lk_params = dict( winSize = (15, 15), + maxLevel = 2, + criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03)) + + # Take first frame and find corners in it + ret, old_frame = cap.read() + assert ret + + old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY) + p0 = cv.goodFeaturesToTrack(old_gray, mask = None, **feature_params) + # Create a mask image for drawing purposes + mask = np.zeros_like(old_frame) + while(1): + ret, frame = cap.read() + if not ret: + break + frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) + # calculate optical flow + p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params) + # Select good points + if p1 is not None: + good_new = p1[st==1] + good_old = p0[st==1] + # draw the tracks + for i, (new, old) in enumerate(zip(good_new, good_old)): + a, b = new.ravel() + c, d = old.ravel() + mask = cv.line(mask, (int(a), int(b)), (int(c), int(d)), [0, 0, 255], 2) + frame = cv.circle(frame, (int(a), int(b)), 5, [255, 0, 0], -1) + img = cv.add(frame, mask) + # Now update the previous frame and previous points + old_gray = frame_gray.copy() + p0 = good_new.reshape(-1, 1, 2) + + optical_flow = img + None + """ + ) + + assert compare_with_reference_image( + selenium, + REFERENCE_IMAGES_PATH / "traffic_optical_flow.png", + "optical_flow", + grayscale=False, + ) + + +def test_flann_sift(selenium): + original_img_src1 = base64.b64encode( + (REFERENCE_IMAGES_PATH / "box.png").read_bytes() + ) + original_img_src2 = base64.b64encode( + (REFERENCE_IMAGES_PATH / "box_in_scene.png").read_bytes() + ) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + src1 = np.frombuffer(base64.b64decode({original_img_src1!r}), np.uint8) + src1 = cv.imdecode(src1, cv.IMREAD_GRAYSCALE) + src2 = np.frombuffer(base64.b64decode({original_img_src2!r}), np.uint8) + src2 = cv.imdecode(src2, cv.IMREAD_GRAYSCALE) + + #-- Step 1: Detect the keypoints using SIFT Detector, compute the descriptors + detector = cv.SIFT_create() + keypoints1, descriptors1 = detector.detectAndCompute(src1, None) + keypoints2, descriptors2 = detector.detectAndCompute(src2, None) + + #-- Step 2: Matching descriptor vectors with a FLANN based matcher + matcher = cv.DescriptorMatcher_create(cv.DescriptorMatcher_FLANNBASED) + knn_matches = matcher.knnMatch(descriptors1, descriptors2, 2) + + #-- Filter matches using the Lowe's ratio test + ratio_thresh = 0.3 + good_matches = [] + for m,n in knn_matches: + if m.distance < ratio_thresh * n.distance: + good_matches.append(m) + + #-- Draw matches + matches = np.empty((max(src1.shape[0], src2.shape[0]), src1.shape[1]+src2.shape[1], 3), dtype=np.uint8) + cv.drawMatches(src1, keypoints1, src2, keypoints2, good_matches, matches, matchColor=[255, 0, 0], flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS) + + sift_result = cv.cvtColor(matches, cv.COLOR_BGR2GRAY) + None + """ + ) + + assert compare_with_reference_image( + selenium, + REFERENCE_IMAGES_PATH / "box_sift.png", + "sift_result", + grayscale=True, + ) + + +def test_dnn_mnist(selenium): + """ + Run tiny MNIST classification ONNX model + Training script: https://github.com/ryanking13/torch-opencv-mnist + """ + + original_img = base64.b64encode( + (REFERENCE_IMAGES_PATH / "mnist_2.png").read_bytes() + ) + tf_model = base64.b64encode((REFERENCE_IMAGES_PATH / "mnist.onnx").read_bytes()) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + + model_weights = base64.b64decode({tf_model!r}) + model_weights_path = './mnist.onnx' + with open(model_weights_path, 'wb') as f: + f.write(model_weights) + + src = np.frombuffer(base64.b64decode({original_img!r}), np.uint8) + src = cv.imdecode(src, cv.IMREAD_GRAYSCALE) + + net = cv.dnn.readNet(model_weights_path) + blob = cv.dnn.blobFromImage(src, 1.0, (28, 28), (0, 0, 0), False, False) + + net.setInput(blob) + prob = net.forward() + assert "output_0" in net.getLayerNames() + assert np.argmax(prob) == 2 + """ + ) + + +def test_ml_pca(selenium): + original_img = base64.b64encode((REFERENCE_IMAGES_PATH / "pca.png").read_bytes()) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + from math import atan2, cos, sin, sqrt, pi + + def drawAxis(img, p_, q_, colour, scale): + p = list(p_) + q = list(q_) + + angle = atan2(p[1] - q[1], p[0] - q[0]) # angle in radians + hypotenuse = sqrt((p[1] - q[1]) * (p[1] - q[1]) + (p[0] - q[0]) * (p[0] - q[0])) + # Here we lengthen the arrow by a factor of scale + q[0] = p[0] - scale * hypotenuse * cos(angle) + q[1] = p[1] - scale * hypotenuse * sin(angle) + cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA) + # create the arrow hooks + p[0] = q[0] + 9 * cos(angle + pi / 4) + p[1] = q[1] + 9 * sin(angle + pi / 4) + cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA) + p[0] = q[0] + 9 * cos(angle - pi / 4) + p[1] = q[1] + 9 * sin(angle - pi / 4) + cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA) + + def getOrientation(pts, img): + + sz = len(pts) + data_pts = np.empty((sz, 2), dtype=np.float64) + for i in range(data_pts.shape[0]): + data_pts[i,0] = pts[i,0,0] + data_pts[i,1] = pts[i,0,1] + # Perform PCA analysis + mean = np.empty((0)) + mean, eigenvectors, eigenvalues = cv.PCACompute2(data_pts, mean) + # Store the center of the object + cntr = (int(mean[0,0]), int(mean[0,1])) + + + cv.circle(img, cntr, 3, (255, 0, 255), 2) + p1 = (cntr[0] + 0.02 * eigenvectors[0,0] * eigenvalues[0,0], cntr[1] + 0.02 * eigenvectors[0,1] * eigenvalues[0,0]) + p2 = (cntr[0] - 0.02 * eigenvectors[1,0] * eigenvalues[1,0], cntr[1] - 0.02 * eigenvectors[1,1] * eigenvalues[1,0]) + drawAxis(img, cntr, p1, (0, 255, 0), 1) + drawAxis(img, cntr, p2, (255, 255, 0), 5) + angle = atan2(eigenvectors[0,1], eigenvectors[0,0]) # orientation in radians + + return angle + + src = np.frombuffer(base64.b64decode({original_img!r}), np.uint8) + src = cv.imdecode(src, cv.IMREAD_COLOR) + gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY) + + # Convert image to binary + _, bw = cv.threshold(gray, 50, 255, cv.THRESH_BINARY | cv.THRESH_OTSU) + contours, _ = cv.findContours(bw, cv.RETR_LIST, cv.CHAIN_APPROX_NONE) + for i, c in enumerate(contours): + # Calculate the area of each contour + area = cv.contourArea(c) + # Ignore contours that are too small or too large + if area < 1e2 or 1e5 < area: + continue + # Draw each contour only for visualisation purposes + cv.drawContours(src, contours, i, (0, 0, 255), 2) + # Find the orientation of each shape + getOrientation(c, src) + + pca_result = src + None + """ + ) + + assert compare_with_reference_image( + selenium, + REFERENCE_IMAGES_PATH / "pca_result.png", + "pca_result", + grayscale=False, + ) + + +def test_objdetect_face(selenium): + original_img = base64.b64encode( + (REFERENCE_IMAGES_PATH / "monalisa.png").read_bytes() + ) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + from pathlib import Path + + src = np.frombuffer(base64.b64decode({original_img!r}), np.uint8) + src = cv.imdecode(src, cv.IMREAD_COLOR) + gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY) + gray = cv.equalizeHist(gray) + + face_cascade = cv.CascadeClassifier() + eyes_cascade = cv.CascadeClassifier() + data_path = Path(cv.data.haarcascades) + face_cascade.load(str(data_path / "haarcascade_frontalface_alt.xml")) + eyes_cascade.load(str(data_path / "haarcascade_eye_tree_eyeglasses.xml")) + + faces = face_cascade.detectMultiScale(gray) + face_detected = src.copy() + for (x,y,w,h) in faces: + center = (x + w//2, y + h//2) + face_detected = cv.ellipse(face_detected, center, (w//2, h//2), 0, 0, 360, (255, 0, 255), 4) + faceROI = gray[y:y+h,x:x+w] + eyes = eyes_cascade.detectMultiScale(faceROI) + for (x2,y2,w2,h2) in eyes: + eye_center = (x + x2 + w2//2, y + y2 + h2//2) + radius = int(round((w2 + h2)*0.25)) + face_detected = cv.circle(face_detected, eye_center, radius, (255, 0, 0 ), 4) + + None + """ + ) + + assert compare_with_reference_image( + selenium, + REFERENCE_IMAGES_PATH / "monalisa_facedetect.png", + "face_detected", + grayscale=False, + ) + + +def test_feature2d_kaze(selenium): + original_img = base64.b64encode((REFERENCE_IMAGES_PATH / "baboon.png").read_bytes()) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + src = np.frombuffer(base64.b64decode({original_img!r}), np.uint8) + src = cv.imdecode(src, cv.IMREAD_COLOR) + + detector = cv.KAZE_create() + keypoints = detector.detect(src) + + kaze = cv.drawKeypoints(src, keypoints, None, color=(0, 0, 255), flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) + None + """ + ) + + assert compare_with_reference_image( + selenium, + REFERENCE_IMAGES_PATH / "baboon_kaze.png", + "kaze", + grayscale=False, + ) + + +def test_calib3d_chessboard(selenium): + original_img = base64.b64encode( + (REFERENCE_IMAGES_PATH / "chessboard.png").read_bytes() + ) + selenium.load_package("opencv-python") + selenium.run( + f""" + import base64 + import cv2 as cv + import numpy as np + src = np.frombuffer(base64.b64decode({original_img!r}), np.uint8) + src = cv.imdecode(src, cv.IMREAD_COLOR) + + criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001) + gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY) + ret, corners = cv.findChessboardCorners(gray, (9, 6), None) + cv.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) + cv.drawChessboardCorners(gray, (9, 6), corners, ret) + chessboard_corners = gray + None + """ + ) + + assert compare_with_reference_image( + selenium, + REFERENCE_IMAGES_PATH / "chessboard_corners.png", + "chessboard_corners", + ) diff --git a/packages/openssl/meta.yaml b/packages/openssl/meta.yaml new file mode 100644 index 00000000000..266a3f806da --- /dev/null +++ b/packages/openssl/meta.yaml @@ -0,0 +1,19 @@ +package: + name: openssl + version: 1.1.1n + +source: + url: https://www.openssl.org/source/openssl-1.1.1n.tar.gz + sha256: 40dceb51a4f6a5275bde0e6bf20ef4b91bfc32ed57c0552e2e8e15463372b17a +build: + sharedlibrary: true + script: | + emconfigure ./Configure gcc -no-ui-console -DHAVE_FORK=0 -DOPENSSL_NO_SECURE_MEMORY -DNO_SYSLOG -fPIC + sed -i 's!^CROSS_COMPILE=.*!!g' Makefile + make build_generated + make -j ${PYODIDE_JOBS:-3} libcrypto.a + make -j ${PYODIDE_JOBS:-3} libssl.a + emar -d libcrypto.a liblegacy-lib-bn_asm.o liblegacy-lib-des_enc.o liblegacy-lib-fcrypt_b.o + mkdir dist + emcc -sSIDE_MODULE=1 libcrypto.a -o dist/libcrypto.so + emcc -sSIDE_MODULE=1 libssl.a -o dist/libssl.so diff --git a/packages/optlang/meta.yaml b/packages/optlang/meta.yaml index 11b1a4e6bfb..42e1b01ca40 100644 --- a/packages/optlang/meta.yaml +++ b/packages/optlang/meta.yaml @@ -2,8 +2,8 @@ package: name: optlang version: 1.5.2 source: - sha256: 5514364aa06bf24381c6777188b2df79b13def2743312bdb80277392cdbb6477 - url: https://files.pythonhosted.org/packages/08/09/e87caa6d45be1bb5c787428822bc7694945d1bf2c9278939cbe5b2a11a2f/optlang-1.5.2.tar.gz + sha256: 14464cff638b58670c1a7f5896f19dd7b595a12c1d30a27c59074700833c1677 + url: https://files.pythonhosted.org/packages/12/3e/9d0b72cf5a8ff660e5787a0797906e04942081f3ad4a95f860488affff2b/optlang-1.5.2-py2.py3-none-any.whl requirements: run: - sympy diff --git a/packages/packaging/meta.yaml b/packages/packaging/meta.yaml index 03883c77b44..999b72ed6b2 100644 --- a/packages/packaging/meta.yaml +++ b/packages/packaging/meta.yaml @@ -2,8 +2,8 @@ package: name: packaging version: "21.3" source: - sha256: dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb - url: https://files.pythonhosted.org/packages/df/9e/d1a7217f69310c1db8fdf8ab396229f55a699ce34a203691794c5d1cad0c/packaging-21.3.tar.gz + sha256: ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 + url: https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl requirements: run: - pyparsing diff --git a/packages/pandas/meta.yaml b/packages/pandas/meta.yaml index 380c6c2fd35..7de2d209d5e 100644 --- a/packages/pandas/meta.yaml +++ b/packages/pandas/meta.yaml @@ -1,9 +1,9 @@ package: name: pandas - version: 1.3.5 + version: 1.4.2 source: - url: https://files.pythonhosted.org/packages/99/f0/f99700ef327e51d291efdf4a6de29e685c4d198cbf8531541fc84d169e0e/pandas-1.3.5.tar.gz - sha256: 1e4285f5de1012de20ca46b188ccf33521bff61ba5c5ebd78b4fb28e5416a9f1 + url: https://files.pythonhosted.org/packages/5a/ac/b3b9aa2318de52e40c26ae7b9ce6d4e9d1bcdaf5da0899a691642117cf60/pandas-1.4.2.tar.gz + sha256: 92bc1fc585f1463ca827b45535957815b7deb218c549b7c18402c322c7549a12 patches: - patches/fix_json_signature.patch build: diff --git a/packages/pandas/test_pandas.py b/packages/pandas/test_pandas.py index 5ef85d81552..aa4f094fb5c 100644 --- a/packages/pandas/test_pandas.py +++ b/packages/pandas/test_pandas.py @@ -1,10 +1,9 @@ -from typing import Dict import random import pytest -def generate_largish_json(n_rows: int = 91746) -> Dict: +def generate_largish_json(n_rows: int = 91746) -> dict: # with n_rows = 91746, the output JSON size will be ~15 MB/10k rows # Note: we don't fix the random seed here, but the actual values @@ -34,12 +33,6 @@ def generate_largish_json(n_rows: int = 91746) -> Dict: return data -@pytest.mark.driver_timeout(30) -def test_pandas(selenium, request): - selenium.load_package("pandas") - assert len(selenium.run("import pandas\ndir(pandas)")) == 142 - - @pytest.mark.driver_timeout(30) def test_extra_import(selenium, request): diff --git a/packages/parso/meta.yaml b/packages/parso/meta.yaml index 1e87430731e..79291994dee 100644 --- a/packages/parso/meta.yaml +++ b/packages/parso/meta.yaml @@ -2,8 +2,8 @@ package: name: parso version: 0.8.3 source: - sha256: 8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0 - url: https://files.pythonhosted.org/packages/a2/0e/41f0cca4b85a6ea74d66d2226a7cda8e41206a624f5b330b958ef48e2e52/parso-0.8.3.tar.gz + sha256: c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75 + url: https://files.pythonhosted.org/packages/05/63/8011bd08a4111858f79d2b09aad86638490d62fbf881c44e434a6dfca87b/parso-0.8.3-py2.py3-none-any.whl test: imports: - parso diff --git a/packages/pillow/meta.yaml b/packages/pillow/meta.yaml deleted file mode 100644 index ecb5d5a0535..00000000000 --- a/packages/pillow/meta.yaml +++ /dev/null @@ -1,17 +0,0 @@ -package: - name: pillow - version: 9.0.0 -source: - sha256: ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e - url: https://files.pythonhosted.org/packages/b0/43/3e286c93b9fa20e233d53532cc419b5aad8a468d91065dbef4c846058834/Pillow-9.0.0.tar.gz - patches: - - patches/setitup.patch - extras: - - - src/setup.cfg - - ./setup.cfg -build: - cflags: -s USE_ZLIB=1 -s USE_LIBJPEG=1 -s USE_FREETYPE=1 -s SIDE_MODULE=1 - ldflags: -ljpeg -test: - imports: - - PIL diff --git a/packages/pillow/src/setup.cfg b/packages/pillow/src/setup.cfg deleted file mode 100644 index bbff2085599..00000000000 --- a/packages/pillow/src/setup.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[build_ext] -disable-platform-guessing=True -enable-zlib=True -enable-jpeg=True -enable-freetype=True -disable-lcms=True -disable-tiff=True -disable-xcb=True -disable-webp=True -disable-webpmux=True -disable-jpeg2000=True -disable-imagequant=True diff --git a/packages/pillow/test_pillow.py b/packages/pillow/test_pillow.py deleted file mode 100644 index 68885c5ce85..00000000000 --- a/packages/pillow/test_pillow.py +++ /dev/null @@ -1,52 +0,0 @@ -from pyodide_build.testing import run_in_pyodide - - -@run_in_pyodide( - packages=["pillow"], -) -def test_pillow(): - from PIL import Image, ImageDraw, ImageOps - import io - - img = Image.new("RGB", (4, 4), color=(0, 0, 0)) - ctx = ImageDraw.Draw(img) - ctx.line([0, 0, 3, 0, 3, 3, 0, 3, 0, 0], (255, 0, 0), 1) - img.putpixel((1, 1), (0, 255, 0)) - img.putpixel((2, 2), (0, 0, 255)) - img = ImageOps.flip(img) - assert ( - img.tobytes() - == b"\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00" - ) - with io.BytesIO() as byio: - img.save(byio, format="PNG") - - assert ( - byio.getvalue() - == b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x04\x00\x00\x00\x04\x08\x02\x00\x00\x00&\x93\t)\x00\x00\x00\x1cIDATx\x9cc\xfc\xcf\x80\x04`\x9c\xff\xff\x19\x18\x98`\x02\x8c\x0c\x0c\x0c\x8c\xc8\xca\x00\xb5\x05\x06\x00\xcbi8B\x00\x00\x00\x00IEND\xaeB`\x82" - ) - img = Image.open(byio) - assert ( - img.tobytes() - == b"\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00" - ) - - with io.BytesIO() as asjpg: - img.save(asjpg, format="JPEG") - img = Image.open(asjpg) - - -@run_in_pyodide( - packages=["pillow"], -) -def test_jpeg_modes(): - from PIL import Image - - rgb = Image.new("RGB", (4, 4)) - rgb.save("rgb.jpg") - - gray = Image.new("L", (4, 4)) - gray.save("gray.jpg") - - bw = Image.new("1", (4, 4)) - bw.save("bw.jpg") diff --git a/packages/pkgconfig/meta.yaml b/packages/pkgconfig/meta.yaml new file mode 100644 index 00000000000..2fa2e00eafc --- /dev/null +++ b/packages/pkgconfig/meta.yaml @@ -0,0 +1,11 @@ +package: + name: pkgconfig + version: 1.5.5 + +source: + url: https://files.pythonhosted.org/packages/32/af/89487c7bbf433f4079044f3dc32f9a9f887597fe04614a37a292e373e16b/pkgconfig-1.5.5-py3-none-any.whl + sha256: d20023bbeb42ee6d428a0fac6e0904631f545985a10cdd71a20aa58bc47a4209 + +test: + imports: + - pkgconfig diff --git a/packages/pluggy/meta.yaml b/packages/pluggy/meta.yaml index 648b21c00ee..77196326f0e 100644 --- a/packages/pluggy/meta.yaml +++ b/packages/pluggy/meta.yaml @@ -2,8 +2,8 @@ package: name: pluggy version: 1.0.0 source: - sha256: 4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 - url: https://files.pythonhosted.org/packages/a1/16/db2d7de3474b6e37cbb9c008965ee63835bba517e22cdb8c35b5116b5ce1/pluggy-1.0.0.tar.gz + sha256: 74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 + url: https://files.pythonhosted.org/packages/9e/01/f38e2ff29715251cf25532b9082a1589ab7e4f571ced434f98d0139336dc/pluggy-1.0.0-py2.py3-none-any.whl test: imports: - pluggy diff --git a/packages/py/meta.yaml b/packages/py/meta.yaml index 9d767c5035b..40920c5b4b8 100644 --- a/packages/py/meta.yaml +++ b/packages/py/meta.yaml @@ -1,9 +1,9 @@ package: name: py - version: 1.9.0 + version: 1.11.0 source: - sha256: 9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 - url: https://files.pythonhosted.org/packages/97/a6/ab9183fe08f69a53d06ac0ee8432bc0ffbb3989c575cc69b73a0229a9a99/py-1.9.0.tar.gz + sha256: 607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 + url: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl test: imports: - py diff --git a/packages/pyb2d/meta.yaml b/packages/pyb2d/meta.yaml index b3b0fb0c05e..a81ab6f14d8 100644 --- a/packages/pyb2d/meta.yaml +++ b/packages/pyb2d/meta.yaml @@ -1,17 +1,16 @@ package: name: pyb2d - version: 0.4.4 + version: 0.7.2 source: - url: https://github.com/pyb2d/pyb2d/archive/refs/tags/0.4.4.tar.gz - sha256: 9e8d613bef2a5e0dc25da96ef23e5a3bf9f07eac9e07dfa632fee94df2a5273c - -build: - script: python -m pip install pybind11 + url: https://github.com/pyb2d/pyb2d/archive/refs/tags/0.7.2.tar.gz + sha256: d7cd30da300ea21073f2e9cc715a61f8c26495ff8c57ebbb7109b4dcfe62fc7c requirements: run: - numpy + - pydantic + - setuptools test: imports: diff --git a/packages/pycparser/meta.yaml b/packages/pycparser/meta.yaml index 186ebd66234..5ad625c430f 100644 --- a/packages/pycparser/meta.yaml +++ b/packages/pycparser/meta.yaml @@ -2,8 +2,11 @@ package: name: pycparser version: "2.21" source: - url: https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz - sha256: e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 + url: https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl + sha256: 8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 +build: + post: | + pip install -t $HOSTSITEPACKAGES pycparser==$PKG_VERSION test: imports: - pycparser diff --git a/packages/pydantic/meta.yaml b/packages/pydantic/meta.yaml new file mode 100644 index 00000000000..bab2617d3bf --- /dev/null +++ b/packages/pydantic/meta.yaml @@ -0,0 +1,12 @@ +package: + name: pydantic + version: 1.9.0 +source: + sha256: 742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a + url: https://files.pythonhosted.org/packages/60/a3/23a8a9378ff06853bda6527a39fe317b088d760adf41cf70fc0f6110e485/pydantic-1.9.0.tar.gz +test: + imports: + - pydantic +requirements: + run: + - typing-extensions diff --git a/packages/pyodide-interrupts/meta.yaml b/packages/pyodide-interrupts/meta.yaml deleted file mode 100644 index 94f8655e908..00000000000 --- a/packages/pyodide-interrupts/meta.yaml +++ /dev/null @@ -1,11 +0,0 @@ -package: - name: pyodide-interrupts - version: 0.1.1 - -source: - url: https://files.pythonhosted.org/packages/b1/c2/918c52e47bf91570d9883a1c761c4d78a59cf4d1d8f8c67c25a4e164ff87/pyodide-interrupts-0.1.1.tar.gz - sha256: b85bc38b92cd5c35dd1a5192a71495abe4cd57eadccfacbc0421fb44fb6c9e74 - -test: - imports: - - pyodide_interrupts diff --git a/packages/pyodide-interrupts/test_pyodide_interrupts.py b/packages/pyodide-interrupts/test_pyodide_interrupts.py deleted file mode 100644 index f6c0e304faf..00000000000 --- a/packages/pyodide-interrupts/test_pyodide_interrupts.py +++ /dev/null @@ -1,17 +0,0 @@ -def test_pyodide_interrupts(selenium): - selenium.load_package("pyodide-interrupts") - selenium.run("from pyodide_interrupts import check_interrupts") - assert ( - selenium.run( - "x = 0\n" - "def callback():\n" - " global x\n" - " print('check')\n" - " x += 1\n" - "with check_interrupts(callback, 10):\n" - " for i in range(50):\n" - " print(i, end=',')\n" - "x" - ) - == 11 - ) diff --git a/packages/pyparsing/meta.yaml b/packages/pyparsing/meta.yaml index daf054f25a3..349c2556f17 100644 --- a/packages/pyparsing/meta.yaml +++ b/packages/pyparsing/meta.yaml @@ -1,9 +1,9 @@ package: name: pyparsing - version: 3.0.6 + version: 3.0.7 source: - sha256: d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81 - url: https://files.pythonhosted.org/packages/ab/61/1a1613e3dcca483a7aa9d446cb4614e6425eb853b90db131c305bd9674cb/pyparsing-3.0.6.tar.gz + sha256: a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484 + url: https://files.pythonhosted.org/packages/80/c1/23fd82ad3121656b585351aba6c19761926bb0db2ebed9e4ff09a43a3fcc/pyparsing-3.0.7-py3-none-any.whl test: imports: - pyparsing diff --git a/packages/pyrsistent/meta.yaml b/packages/pyrsistent/meta.yaml index 05d86f49307..f84c6bdf46b 100644 --- a/packages/pyrsistent/meta.yaml +++ b/packages/pyrsistent/meta.yaml @@ -1,9 +1,9 @@ package: name: pyrsistent - version: 0.18.0 + version: 0.18.1 source: - url: https://files.pythonhosted.org/packages/f4/d7/0fa558c4fb00f15aabc6d42d365fcca7a15fcc1091cd0f5784a14f390b7f/pyrsistent-0.18.0.tar.gz - sha256: 773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b + url: https://files.pythonhosted.org/packages/42/ac/455fdc7294acc4d4154b904e80d964cc9aae75b087bbf486be04df9f2abd/pyrsistent-0.18.1.tar.gz + sha256: d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96 test: imports: - pyrsistent diff --git a/packages/pytest/meta.yaml b/packages/pytest/meta.yaml index c258e16836e..0220b2a62ca 100644 --- a/packages/pytest/meta.yaml +++ b/packages/pytest/meta.yaml @@ -1,9 +1,9 @@ package: name: pytest - version: 6.2.5 + version: 7.1.1 source: - url: https://files.pythonhosted.org/packages/4b/24/7d1f2d2537de114bdf1e6875115113ca80091520948d370c964b88070af2/pytest-6.2.5.tar.gz - sha256: 131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 + url: https://files.pythonhosted.org/packages/d2/ac/556e4410326ce77eeb1d1ec35a3e3ec847fb3e5cb30673729d2eeeffc970/pytest-7.1.1-py3-none-any.whl + sha256: 92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea requirements: run: - atomicwrites diff --git a/packages/python-dateutil/meta.yaml b/packages/python-dateutil/meta.yaml index 11c352b3c91..0535eae3855 100644 --- a/packages/python-dateutil/meta.yaml +++ b/packages/python-dateutil/meta.yaml @@ -2,8 +2,8 @@ package: name: python-dateutil version: 2.8.2 source: - sha256: 0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 - url: https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz + sha256: 961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 + url: https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl requirements: run: - six diff --git a/packages/python-sat/meta.yaml b/packages/python-sat/meta.yaml index a060f62734d..c53a5bb7f19 100644 --- a/packages/python-sat/meta.yaml +++ b/packages/python-sat/meta.yaml @@ -1,10 +1,10 @@ package: name: python-sat - version: 0.1.7.dev15 + version: 0.1.7.dev16 source: - sha256: 1b2735121952541a7ce4b4cb7a7c4bc95076220ec35ece58b88cf2a8e9694c78 - url: https://github.com/pysathq/pysat/releases/download/0.1.7.dev15/python-sat-0.1.7.dev15.tar.gz + sha256: 54ec89c031226c3f525b939b144a8a2a460a53d9a8e380cf6adcca3807f0bfe3 + url: https://files.pythonhosted.org/packages/f4/ce/e809612cc36c60aea071d6127a4aac7d4eeb0785c64ae040436197ab6aae/python-sat-0.1.7.dev16.tar.gz patches: - patches/force_malloc.patch diff --git a/packages/python_solvespace/meta.yaml b/packages/python_solvespace/meta.yaml new file mode 100644 index 00000000000..e5e234600ce --- /dev/null +++ b/packages/python_solvespace/meta.yaml @@ -0,0 +1,19 @@ +package: + name: python_solvespace + version: 3.0.6 +source: + url: https://files.pythonhosted.org/packages/50/a6/4e01d2c90de41a431041f813aebada874d05d6d578ce25b2462872d2a3e6/python_solvespace-3.0.6.tar.gz + sha256: 951b4cf3858dc337bbfa9f6d9efe9fd6e8dfa7fed022556196add1ba5a56b045 +build: + script: | + pip install -t $HOSTSITEPACKAGES cython + wget https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz && tar -zxvf eigen-3.4.0.tar.gz + cp -rn eigen-3.4.0/Eigen python_solvespace/include +test: + imports: + - python_solvespace +about: + home: https://github.com/KmolYuan/solvespace/tree/python + PyPI: https://pypi.org/project/python_solvespace + summary: Python library of Solvespace. + license: GPLv3+ diff --git a/packages/python_solvespace/test_python_solvespace.py b/packages/python_solvespace/test_python_solvespace.py new file mode 100644 index 00000000000..ce804c75f9f --- /dev/null +++ b/packages/python_solvespace/test_python_solvespace.py @@ -0,0 +1,26 @@ +from pyodide_build.testing import run_in_pyodide + + +@run_in_pyodide(packages=["python_solvespace"]) +def test_regex(): + from python_solvespace import ResultFlag, SolverSystem + + sys = SolverSystem() + wp = sys.create_2d_base() + p0 = sys.add_point_2d(0, 0, wp) + sys.dragged(p0, wp) + p1 = sys.add_point_2d(90, 0, wp) + sys.dragged(p1, wp) + line0 = sys.add_line_2d(p0, p1, wp) + p2 = sys.add_point_2d(20, 20, wp) + p3 = sys.add_point_2d(0, 10, wp) + p4 = sys.add_point_2d(30, 20, wp) + sys.distance(p2, p3, 40, wp) + sys.distance(p2, p4, 40, wp) + sys.distance(p3, p4, 70, wp) + sys.distance(p0, p3, 35, wp) + sys.distance(p1, p4, 70, wp) + line1 = sys.add_line_2d(p0, p3, wp) + sys.angle(line0, line1, 45, wp) + result_flag = sys.solve() + assert result_flag == ResultFlag.OKAY diff --git a/packages/pytz/meta.yaml b/packages/pytz/meta.yaml index 628cc94c95d..b92aa7b0a5a 100644 --- a/packages/pytz/meta.yaml +++ b/packages/pytz/meta.yaml @@ -1,9 +1,9 @@ package: name: pytz - version: "2021.3" + version: "2022.1" source: - sha256: acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326 - url: https://files.pythonhosted.org/packages/e3/8e/1cde9d002f48a940b9d9d38820aaf444b229450c0854bdf15305ce4a3d1a/pytz-2021.3.tar.gz + sha256: e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c + url: https://files.pythonhosted.org/packages/60/2e/dec1cc18c51b8df33c7c4d0a321b084cf38e1733b98f9d15018880fb4970/pytz-2022.1-py2.py3-none-any.whl test: imports: - pytz diff --git a/packages/pywavelets/meta.yaml b/packages/pywavelets/meta.yaml index a626e481104..e84df67f254 100644 --- a/packages/pywavelets/meta.yaml +++ b/packages/pywavelets/meta.yaml @@ -1,9 +1,9 @@ package: name: pywavelets - version: 1.2.0 + version: 1.3.0 source: - sha256: 6cbd69b047bb4e00873097472133425f5f08a4e6bc8b3f0ae709274d4d5e9a8d - url: https://files.pythonhosted.org/packages/35/e9/decd467448cde227aad94ff2976046afd3a51ad461ba9a325840687e8836/PyWavelets-1.2.0.tar.gz + sha256: cbaa9d62052d9daf8da765fc8e7c30c38ea2b8e9e1c18841913dfb4aec671ee5 + url: https://files.pythonhosted.org/packages/32/ab/b96b19cae562aecaa57f0cdb501be169a38ec685ddcc91f1de20f849b22e/PyWavelets-1.3.0.tar.gz requirements: run: - distutils diff --git a/packages/pywavelets/test_pywt.py b/packages/pywavelets/test_pywt.py index 9055c7d794c..299c6cb1419 100644 --- a/packages/pywavelets/test_pywt.py +++ b/packages/pywavelets/test_pywt.py @@ -5,8 +5,8 @@ packages=["pywavelets"], driver_timeout=30, xfail_browsers={"chrome": "xfail"} ) def test_pywt(): - import pywt import numpy as np + import pywt def checkit(a, v): assert (np.rint(a) == v).all() diff --git a/packages/pyyaml/meta.yaml b/packages/pyyaml/meta.yaml index d3f88df1aea..a79fc778a88 100644 --- a/packages/pyyaml/meta.yaml +++ b/packages/pyyaml/meta.yaml @@ -5,7 +5,6 @@ source: url: https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz sha256: 68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 build: - skip_host: true cflags: "-I$(PYTHONINCLUDE)\n-I$(PYODIDE_ROOT)/emsdk/emsdk/fastcomp/emscripten/system/include/libc\n\ -I$(PYODIDE_ROOT)/packages/libyaml/build/include\n" diff --git a/packages/regex/meta.yaml b/packages/regex/meta.yaml index 6bad4b6d588..1b293725c45 100644 --- a/packages/regex/meta.yaml +++ b/packages/regex/meta.yaml @@ -1,9 +1,9 @@ package: name: regex - version: 2021.7.6 + version: 2022.3.15 source: - sha256: 8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d - url: https://files.pythonhosted.org/packages/c0/d1/ad6afa6000ab869f6af2c85985d40558ffb298d9fcb2ab04c0648436008f/regex-2021.7.6.tar.gz + sha256: 0a7b75cc7bb4cc0334380053e4671c560e31272c9d2d5a6c4b8e9ae2c9bd0f82 + url: https://files.pythonhosted.org/packages/0c/06/8d851419ff870cbe2bf65ecdcfda59d80f11f41157392d794ee544f15bf6/regex-2022.3.15.tar.gz test: imports: - regex diff --git a/packages/requests/meta.yaml b/packages/requests/meta.yaml new file mode 100644 index 00000000000..daaf2206fc1 --- /dev/null +++ b/packages/requests/meta.yaml @@ -0,0 +1,25 @@ +package: + name: requests + version: 2.26.0 + +source: + url: https://github.com/psf/requests/archive/590350f8d094c216051510ed1dd18fe871b53b72.tar.gz + sha256: ffc977d3a7abfe19a3ea81f70b912ff656425825bc10c04b39750c3d2dd27577 + + patches: + # This patch is a dump of changes made in a fork of requests: + # https://github.com/bartbroere/requests/pull/1 + # and to get the patch file: + # https://patch-diff.githubusercontent.com/raw/bartbroere/requests/pull/1.patch + - patches/pyodide-requests-shim.patch + +requirements: + run: + - urllib3 + - idna + - charset-normalizer + - certifi + +test: + imports: + - requests diff --git a/packages/requests/patches/pyodide-requests-shim.patch b/packages/requests/patches/pyodide-requests-shim.patch new file mode 100644 index 00000000000..dc4dbefa400 --- /dev/null +++ b/packages/requests/patches/pyodide-requests-shim.patch @@ -0,0 +1,487 @@ +diff --git a/requests/models.py b/requests/models.py +index e7d292d5..8ab50510 100644 +--- a/requests/models.py ++++ b/requests/models.py +@@ -8,41 +8,36 @@ This module contains the primary objects that power Requests. + """ + + import datetime +-import sys +- + # Import encoding now, to avoid implicit import later. + # Implicit import within threads may cause LookupError when standard library is in a ZIP, + # such as in Embedded Python. See https://github.com/psf/requests/issues/3578. +-import encodings.idna ++from email.parser import Parser ++from io import UnsupportedOperation, BytesIO, StringIO + ++from urllib3.exceptions import ( ++ LocationParseError, ProtocolError, DecodeError, ReadTimeoutError) + from urllib3.fields import RequestField + from urllib3.filepost import encode_multipart_formdata + from urllib3.util import parse_url +-from urllib3.exceptions import ( +- DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) +- +-from io import UnsupportedOperation +-from .hooks import default_hooks +-from .structures import CaseInsensitiveDict + +-from .auth import HTTPBasicAuth +-from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar +-from .exceptions import ( +- HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, +- ContentDecodingError, ConnectionError, StreamConsumedError, +- InvalidJSONError) +-from .exceptions import JSONDecodeError as RequestsJSONDecodeError + from ._internal_utils import to_native_string, unicode_is_ascii +-from .utils import ( +- guess_filename, get_auth_from_url, requote_uri, +- stream_decode_response_unicode, to_key_val_list, parse_header_links, +- iter_slices, guess_json_utf, super_len, check_header_validity) ++from .auth import HTTPBasicAuth + from .compat import ( + Callable, Mapping, + cookielib, urlunparse, urlsplit, urlencode, str, bytes, +- is_py2, chardet, builtin_str, basestring, JSONDecodeError) ++ is_py2, chardet, builtin_str, basestring) + from .compat import json as complexjson ++from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar ++from .exceptions import ( ++ HTTPError, MissingSchema, InvalidURL, InvalidJSONError, ContentDecodingError, ChunkedEncodingError, ++ StreamConsumedError) ++from .hooks import default_hooks + from .status_codes import codes ++from .structures import CaseInsensitiveDict ++from .utils import ( ++ guess_filename, get_auth_from_url, requote_uri, ++ to_key_val_list, parse_header_links, ++ super_len, check_header_validity, iter_slices, stream_decode_response_unicode) + + #: The set of HTTP status codes that indicate an automatically + #: processable redirect. +@@ -603,24 +598,22 @@ class Response(object): + 'encoding', 'reason', 'cookies', 'elapsed', 'request' + ] + +- def __init__(self): ++ def __init__(self, request): ++ if request.responseIsBinary: ++ # bring everything outside the range of a single byte within this range ++ self.raw = BytesIO(bytes(ord(byte) & 0xff for byte in request.response)) ++ else: ++ self.text = str(request.response) ++ self.raw = StringIO(str(request.response)) ++ self.status_code = request.status ++ self.headers = CaseInsensitiveDict(Parser().parsestr(request.getAllResponseHeaders(), headersonly=True)) ++ # Strip the transfer-encoding header, since Python logic relying on checking this header will have a bad time ++ if 'transfer-encoding' in self.headers: ++ del self.headers['transfer-encoding'] + self._content = False + self._content_consumed = False + self._next = None + +- #: Integer Code of responded HTTP Status, e.g. 404 or 200. +- self.status_code = None +- +- #: Case-insensitive Dictionary of Response Headers. +- #: For example, ``headers['content-encoding']`` will return the +- #: value of a ``'Content-Encoding'`` response header. +- self.headers = CaseInsensitiveDict() +- +- #: File-like object representation of response (for advanced usage). +- #: Use of ``raw`` requires that ``stream=True`` be set on the request. +- #: This requirement does not apply for use internally to Requests. +- self.raw = None +- + #: Final URL location of Response. + self.url = None + +@@ -742,13 +735,11 @@ class Response(object): + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. +- + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. +- + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ +@@ -842,44 +833,6 @@ class Response(object): + # since we exhausted the data. + return self._content + +- @property +- def text(self): +- """Content of the response, in unicode. +- +- If Response.encoding is None, encoding will be guessed using +- ``charset_normalizer`` or ``chardet``. +- +- The encoding of the response content is determined based solely on HTTP +- headers, following RFC 2616 to the letter. If you can take advantage of +- non-HTTP knowledge to make a better guess at the encoding, you should +- set ``r.encoding`` appropriately before accessing this property. +- """ +- +- # Try charset from content-type +- content = None +- encoding = self.encoding +- +- if not self.content: +- return str('') +- +- # Fallback to auto-detected encoding. +- if self.encoding is None: +- encoding = self.apparent_encoding +- +- # Decode unicode from given encoding. +- try: +- content = str(self.content, encoding, errors='replace') +- except (LookupError, TypeError): +- # A LookupError is raised if the encoding was not found which could +- # indicate a misspelling or similar mistake. +- # +- # A TypeError can be raised if encoding is None +- # +- # So we try blindly encoding. +- content = str(self.content, errors='replace') +- +- return content +- + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + +@@ -887,34 +840,7 @@ class Response(object): + :raises requests.exceptions.JSONDecodeError: If the response body does not + contain valid json. + """ +- +- if not self.encoding and self.content and len(self.content) > 3: +- # No encoding set. JSON RFC 4627 section 3 states we should expect +- # UTF-8, -16 or -32. Detect which one to use; If the detection or +- # decoding fails, fall back to `self.text` (using charset_normalizer to make +- # a best guess). +- encoding = guess_json_utf(self.content) +- if encoding is not None: +- try: +- return complexjson.loads( +- self.content.decode(encoding), **kwargs +- ) +- except UnicodeDecodeError: +- # Wrong UTF codec detected; usually because it's not UTF-8 +- # but some other 8-bit codec. This is an RFC violation, +- # and the server didn't bother to tell us what codec *was* +- # used. +- pass +- +- try: +- return complexjson.loads(self.text, **kwargs) +- except JSONDecodeError as e: +- # Catch JSON-related errors and raise as requests.JSONDecodeError +- # This aliases json.JSONDecodeError and simplejson.JSONDecodeError +- if is_py2: # e is a ValueError +- raise RequestsJSONDecodeError(e.message) +- else: +- raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) ++ return complexjson.loads(self.text) + + @property + def links(self): +diff --git a/requests/sessions.py b/requests/sessions.py +index ae4bcc8e..2a0c8853 100644 +--- a/requests/sessions.py ++++ b/requests/sessions.py +@@ -7,35 +7,40 @@ requests.sessions + This module provides a Session object to manage and persist settings across + requests (cookies, auth, proxies). + """ ++import json as json_module + import os + import sys + import time ++import warnings ++from collections import OrderedDict, Iterable + from datetime import timedelta +-from collections import OrderedDict ++from urllib.parse import urlencode + ++from js import XMLHttpRequest ++ ++try: ++ from js import Blob ++except: ++ from js.buffer import Blob ++ ++from ._internal_utils import to_native_string + from .auth import _basic_auth_str +-from .compat import cookielib, is_py3, urljoin, urlparse, Mapping ++from .compat import is_py3, urljoin, urlparse, Mapping + from .cookies import ( +- cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) +-from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT +-from .hooks import default_hooks, dispatch_hook +-from ._internal_utils import to_native_string +-from .utils import to_key_val_list, default_headers, DEFAULT_PORTS ++ extract_cookies_to_jar, merge_cookies) + from .exceptions import ( + TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) +- ++from .hooks import default_hooks, dispatch_hook ++from .models import Request, DEFAULT_REDIRECT_LIMIT, Response ++from .status_codes import codes + from .structures import CaseInsensitiveDict +-from .adapters import HTTPAdapter +- + from .utils import ( + requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, + get_auth_from_url, rewind_body + ) +- +-from .status_codes import codes ++from .utils import to_key_val_list, DEFAULT_PORTS + + # formerly defined here, reexposed here for backward compatibility +-from .models import REDIRECT_STATI + + # Preferred clock, based on which one is more accurate on a given system. + if sys.platform == 'win32': +@@ -334,25 +339,14 @@ class SessionRedirectMixin(object): + prepared_request.method = method + + +-class Session(SessionRedirectMixin): +- """A Requests session. +- +- Provides cookie persistence, connection-pooling, and configuration. +- +- Basic Usage:: +- +- >>> import requests +- >>> s = requests.Session() +- >>> s.get('https://httpbin.org/get') +- ++class Session: ++ """ ++ No-op context manager for packages that rely on requests.Session. + +- Or as a context manager:: ++ It has been made entirely no-op because the browser will handle cookies, headers etc., unless explicitly set by ++ the user of this requests version. + +- >>> with requests.Session() as s: +- ... s.get('https://httpbin.org/get') +- + """ +- + __attrs__ = [ + 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', + 'cert', 'adapters', 'stream', 'trust_env', +@@ -361,111 +355,37 @@ class Session(SessionRedirectMixin): + + def __init__(self): + +- #: A case-insensitive dictionary of headers to be sent on each +- #: :class:`Request ` sent from this +- #: :class:`Session `. +- self.headers = default_headers() +- +- #: Default Authentication tuple or object to attach to +- #: :class:`Request `. ++ self.headers = CaseInsensitiveDict({}) + self.auth = None +- +- #: Dictionary mapping protocol or protocol and host to the URL of the proxy +- #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to +- #: be used on each :class:`Request `. + self.proxies = {} +- +- #: Event-handling hooks. + self.hooks = default_hooks() +- +- #: Dictionary of querystring data to attach to each +- #: :class:`Request `. The dictionary values may be lists for +- #: representing multivalued query parameters. + self.params = {} +- +- #: Stream response content default. + self.stream = False +- +- #: SSL Verification default. +- #: Defaults to `True`, requiring requests to verify the TLS certificate at the +- #: remote end. +- #: If verify is set to `False`, requests will accept any TLS certificate +- #: presented by the server, and will ignore hostname mismatches and/or +- #: expired certificates, which will make your application vulnerable to +- #: man-in-the-middle (MitM) attacks. +- #: Only set this to `False` for testing. + self.verify = True +- +- #: SSL client certificate default, if String, path to ssl client +- #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None +- +- #: Maximum number of redirects allowed. If the request exceeds this +- #: limit, a :class:`TooManyRedirects` exception is raised. +- #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is +- #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT +- +- #: Trust environment settings for proxy configuration, default +- #: authentication and similar. + self.trust_env = True +- +- #: A CookieJar containing all currently outstanding cookies set on this +- #: session. By default it is a +- #: :class:`RequestsCookieJar `, but +- #: may be any other ``cookielib.CookieJar`` compatible object. +- self.cookies = cookiejar_from_dict({}) +- +- # Default connection adapters. +- self.adapters = OrderedDict() +- self.mount('https://', HTTPAdapter()) +- self.mount('http://', HTTPAdapter()) ++ self.cookies = {} ++ self.adapters = {} + + def __enter__(self): + return self + + def __exit__(self, *args): +- self.close() +- +- def prepare_request(self, request): +- """Constructs a :class:`PreparedRequest ` for +- transmission and returns it. The :class:`PreparedRequest` has settings +- merged from the :class:`Request ` instance and those of the +- :class:`Session`. +- +- :param request: :class:`Request` instance to prepare with this +- session's settings. +- :rtype: requests.PreparedRequest +- """ +- cookies = request.cookies or {} +- +- # Bootstrap CookieJar. +- if not isinstance(cookies, cookielib.CookieJar): +- cookies = cookiejar_from_dict(cookies) +- +- # Merge with session cookies +- merged_cookies = merge_cookies( +- merge_cookies(RequestsCookieJar(), self.cookies), cookies) +- +- # Set environment's basic authentication if not explicitly set. +- auth = request.auth +- if self.trust_env and not auth and not self.auth: +- auth = get_netrc_auth(request.url) +- +- p = PreparedRequest() +- p.prepare( +- method=request.method.upper(), +- url=request.url, +- files=request.files, +- data=request.data, +- json=request.json, +- headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), +- params=merge_setting(request.params, self.params), +- auth=merge_setting(auth, self.auth), +- cookies=merged_cookies, +- hooks=merge_hooks(request.hooks, self.hooks), +- ) +- return p ++ ... ++ ++ def set_headers(self, request, headers): ++ for header, value in self.headers.items(): ++ request.setRequestHeader(header, value) ++ if isinstance(headers, Mapping): ++ for header, value in headers.items(): ++ request.setRequestHeader(header, value) ++ return CaseInsensitiveDict(headers) ++ if isinstance(headers, Iterable): ++ for header, value in headers: ++ request.setRequestHeader(header, value) ++ return CaseInsensitiveDict(headers) ++ return CaseInsensitiveDict({}) + + def request(self, method, url, + params=None, data=None, headers=None, cookies=None, files=None, +@@ -512,36 +432,44 @@ class Session(SessionRedirectMixin): + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ +- # Create the Request. +- req = Request( +- method=method.upper(), +- url=url, +- headers=headers, +- files=files, +- data=data or {}, +- json=json, +- params=params or {}, +- auth=auth, +- cookies=cookies, +- hooks=hooks, +- ) +- prep = self.prepare_request(req) +- +- proxies = proxies or {} +- +- settings = self.merge_environment_settings( +- prep.url, proxies, stream, verify, cert +- ) +- +- # Send the request. +- send_kwargs = { +- 'timeout': timeout, +- 'allow_redirects': allow_redirects, +- } +- send_kwargs.update(settings) +- resp = self.send(prep, **send_kwargs) +- +- return resp ++ request = XMLHttpRequest.new() ++ request.responseIsBinary = False ++ if stream: ++ # we ask the browser not to worry about the character set, keeping the raw bytes intact ++ request.overrideMimeType('text/plain; charset=x-user-defined') ++ request.responseIsBinary = True ++ # Send cookies that might be set in the browser already ++ request.withCredentials = True ++ request.open(method.upper(), url, False) ++ if params: ++ if isinstance(params, Mapping): ++ url = url + '?' + urlencode(params) ++ headers = self.set_headers(request, headers) ++ if ('range' in headers) or ('accept' in headers and 'application/octet-stream' in headers['accept']): ++ request.overrideMimeType('text/plain; charset=x-user-defined') ++ request.responseIsBinary = True ++ if data: ++ if isinstance(data, Mapping): ++ data = Blob.new([json_module.dumps(data)], { ++ 'type': 'application/json', ++ }) ++ else: ++ warnings.warn('This type of input to the data parameter of Pyodide requests is not (yet) supported') ++ if json: ++ if isinstance(json, Mapping): ++ data = Blob.new([json_module.dumps(json)], { ++ 'type': 'application/json', ++ }) ++ else: ++ warnings.warn('This type of input to the json parameter of Pyodide requests is not (yet) supported') ++ if verify is not None or cert or not allow_redirects or proxies or auth or hooks or files or cookies: ++ warnings.warn('The Pyodide version of requests does not support the following parameters (yet): ' ++ 'verify, cert, allow_redirects, proxies, auth, hooks, files and cookies') ++ if data: ++ request.send(data) ++ else: ++ request.send() ++ return Response(request) + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. diff --git a/packages/scikit-image/meta.yaml b/packages/scikit-image/meta.yaml index aa29cb57c2d..ee01cc6de80 100644 --- a/packages/scikit-image/meta.yaml +++ b/packages/scikit-image/meta.yaml @@ -1,12 +1,12 @@ package: name: scikit-image - version: 0.19.1 + version: 0.19.2 source: patches: - patches/preferred_plugins.patch - patches/make-tifffile-optional.patch - sha256: 48f00ee1e8ec2818ae6a152c72df15f4db7f566e839f5c34e1a0c3c9e5210138 - url: https://pypi.io/packages/source/s/scikit-image/scikit-image-0.19.1.tar.gz + sha256: d433b4642a6f8219e749dfbbe4b5e742d560996540c9749ede510274d061866d + url: https://files.pythonhosted.org/packages/83/7d/756dcbf1f2fcbfd60e14842aeadefa2354eff714ed4ec3ae7a107a5787d1/scikit-image-0.19.2.tar.gz requirements: run: - distutils @@ -15,16 +15,10 @@ requirements: - scipy - matplotlib - networkx - - pillow + - Pillow - imageio - pywavelets -build: - cflags: | - -I$(PYODIDE_ROOT)/packages/.artifacts/pythran/ - script: | - pip install pythran - pip install --upgrade pythran -t $PYODIDE_ROOT/packages/.artifacts/ test: imports: - skimage diff --git a/packages/scikit-image/test_skimage.py b/packages/scikit-image/test_skimage.py index 00312123f98..aafc1313601 100644 --- a/packages/scikit-image/test_skimage.py +++ b/packages/scikit-image/test_skimage.py @@ -13,9 +13,7 @@ ) def test_skimage(): import numpy as np - - from skimage import data - from skimage import color + from skimage import color, data from skimage.util import view_as_blocks # get astronaut from skimage.data in grayscale @@ -35,10 +33,10 @@ def test_skimage(): to = threshold_otsu(l) assert to.hex() == "0x1.8e00000000000p-2" - from skimage.data import astronaut from skimage.color import rgb2gray + from skimage.data import astronaut from skimage.filters import sobel - from skimage.segmentation import felzenszwalb, slic, quickshift, watershed + from skimage.segmentation import felzenszwalb, quickshift, slic, watershed from skimage.util import img_as_float img = img_as_float(astronaut()[::2, ::2]) @@ -52,3 +50,4 @@ def test_skimage(): assert len(np.unique(segments_fz)) == 194 assert len(np.unique(segments_slic)) == 196 assert len(np.unique(segments_quick)) == 695 + assert len(np.unique(segments_watershed)) == 256 diff --git a/packages/scikit-learn/test_scikit-learn.py b/packages/scikit-learn/test_scikit-learn.py index 3fe49deb16a..acac4fd7950 100644 --- a/packages/scikit-learn/test_scikit-learn.py +++ b/packages/scikit-learn/test_scikit-learn.py @@ -1,4 +1,5 @@ import pytest + from conftest import selenium_context_manager diff --git a/packages/scipy/meta.yaml b/packages/scipy/meta.yaml index c9821336c00..c1821fab8c3 100644 --- a/packages/scipy/meta.yaml +++ b/packages/scipy/meta.yaml @@ -1,6 +1,6 @@ package: name: scipy - version: 1.7.3 # when updating version make sure to update pip install scipy==1.7.3 below too. + version: 1.8.0 # See extra explanation in info.md # @@ -13,93 +13,90 @@ package: # subroutine. Try deleting it. source: - url: https://files.pythonhosted.org/packages/61/67/1a654b96309c991762ee9bc39c363fc618076b155fe52d295211cf2536c7/scipy-1.7.3.tar.gz - sha256: ab5875facfdef77e0a47d5fd39ea178b58e60e454a4c85aa1e52fcb80db7babf + url: https://files.pythonhosted.org/packages/b4/a2/4faa34bf0cdbefd5c706625f1234987795f368eb4e97bde9d6f46860843e/scipy-1.8.0.tar.gz + sha256: 31d4f2d6b724bc9a98e527b5849b8a7e589bf1ea630c33aa563eda912c9ff0bd patches: - - patches/add-lapack_extras-to-flapack.patch - - patches/convert-xerrwv-message-arg-from-int-to-str.patch - - patches/disable-blas-detection.patch - - patches/fix-fortran-files-minpack.patch - - patches/gemm_-no-const.patch - - patches/make-int-return-values.patch - - patches/remove-cuncsd-dorcsd-sorcsd-zuncsd.patch - - patches/remove-mvnun-not-fortran-77-compliant.patch - - patches/sasum-returns-double-not-float.patch - - patches/skip-fortran-fails-to-link.patch - - patches/rename-_page_trend_test.patch + - patches/0001-Fix-dstevr-in-special-lapack_defs.h.patch + - patches/0002-loadDynamicLibrary-flapack.patch + - patches/0003-Add-lapack_extras-to-linalg-setup.py.patch + - patches/0004-int-to-string.patch + - patches/0005-disable-blas-detection.patch + - patches/0006-fix-fotran-files-minpack.patch + - patches/0007-gemm_-no-const.patch + - patches/0008-make-int-return-values.patch + - patches/0009-Rename-_page_trend_test.py-to-prevent-test-unvendori.patch + - patches/0010-sasum-returns-double-not-float.patch + - patches/0011-skip-fortran-fails-to-link.patch + - patches/0012-Disable-lapack-detection.patch + - patches/0013-Add-extra-END-to-prini.f.patch + - patches/0014-BUG-Fix-signature-of-D_IIR_forback-1-2.patch build: - # set linker and C flags to error on anything to do with function declarations being wrong. - # In webassembly, any conflicts mean that a randomly selected 50% of calls to the function - # will fail. Better to fail at compile or link time. cflags: | - -I$(PYODIDE_ROOT)/packages/numpy/config -I$(PYODIDE_ROOT)/packages/CLAPACK/build/CLAPACK-3.2.1/INCLUDE - -I$(PYODIDE_ROOT)/packages/.artifacts/include/ - -I$(PYODIDE_ROOT)/packages/.artifacts/pythran/ - -DUNDERSCORE_G77 - -Werror=implicit-function-declaration - -Werror=mismatched-parameter-types - -Werror=mismatched-return-types + -I$(HOSTSITEPACKAGES)/pythran/ -Wno-return-type + -DUNDERSCORE_G77 ldflags: | -L$(NUMPY_LIB) - -L$(NUMPY_LIB_BASE)/random/lib - -Wl,--fatal-warnings # IMPORTANT: Other locations important in scipy build process: # There are two files built in the "capture" pass that need patching: # _blas_subroutines.h, and _cython # Scipy has a bunch of custom logic implemented in # pyodide-build/pyodide_build/_f2c_fixes.py. script: | - pip install scipy==1.7.3 pybind11[global] pythran - pip install --upgrade pybind11[global] pythran -t $PYODIDE_ROOT/packages/.artifacts/ + pip install -t $HOSTSITEPACKAGES pythran scipy==$PKG_VERSION # We get linker errors because the following 36 functions are missing # Copying them from a more recent LAPACK seems to work fine. wget https://github.com/Reference-LAPACK/lapack/archive/refs/tags/v3.10.0.tar.gz tar xzf v3.10.0.tar.gz cd lapack-3.10.0/SRC + cat \ - cgemqrt.f cgeqrfp.f cgeqrt.f clahqr.f csyconv.f csyconvf.f csyconvf_rook.f ctpmqrt.f ctpqrt.f \ - dgemqrt.f dgeqrfp.f dgeqrt.f dlahqr.f dsyconv.f dsyconvf.f dsyconvf_rook.f dtpmqrt.f dtpqrt.f \ - sgemqrt.f sgeqrfp.f sgeqrt.f slahqr.f ssyconv.f ssyconvf.f ssyconvf_rook.f stpmqrt.f stpqrt.f \ - zgemqrt.f zgeqrfp.f zgeqrt.f zlahqr.f zsyconv.f zsyconvf.f zsyconvf_rook.f ztpmqrt.f ztpqrt.f \ + cgemqrt.f cgeqrfp.f cgeqrt.f clahqr.f csyconv.f csyconvf.f csyconvf_rook.f ctpmqrt.f ctpqrt.f cuncsd.f \ + dgemqrt.f dgeqrfp.f dgeqrt.f dlahqr.f dsyconv.f dsyconvf.f dsyconvf_rook.f dtpmqrt.f dtpqrt.f dorcsd.f \ + sgemqrt.f sgeqrfp.f sgeqrt.f slahqr.f ssyconv.f ssyconvf.f ssyconvf_rook.f stpmqrt.f stpqrt.f sorcsd.f \ + zgemqrt.f zgeqrfp.f zgeqrt.f zlahqr.f zsyconv.f zsyconvf.f zsyconvf_rook.f ztpmqrt.f ztpqrt.f zuncsd.f \ >> ../../scipy/linalg/lapack_extras.f + sed -i 's/CHARACTER/INTEGER/g' ../../scipy/linalg/lapack_extras.f + sed -i 's/RECURSIVE//g' ../../scipy/linalg/lapack_extras.f cd ../.. - # The additional four functions cuncsd, dorcsd, sorcsd, and zuncsd are also - # missing but they use features of Fortran that aren't Fortran 77 compatible - # so f2c can't handle them. We stub them with C definitions that do nothing. - # These stubs come from f2cpatches/wrap_dummy_g77_abi.patch # Change many functions that return void into functions that return int find scipy -name "*.c*" | xargs sed -i 's/extern void F_FUNC/extern int F_FUNC/g' + sed -i 's/void F_FUNC/int F_FUNC/g' scipy/odr/__odrpack.c + sed -i 's/^void/int/g' scipy/odr/odrpack.h + sed -i 's/^void/int/g' scipy/odr/__odrpack.c + + sed -i 's/void BLAS_FUNC/int BLAS_FUNC/g' scipy/special/lapack_defs.h + # sed -i 's/void F_FUNC/int F_FUNC/g' scipy/linalg/_lapack_subroutines.h sed -i 's/extern void/extern int/g' scipy/optimize/__minpack.h - sed -i 's/^void/int/g' scipy/interpolate/src/_fitpackmodule.c sed -i 's/void/int/g' scipy/linalg/cython_blas_signatures.txt + sed -i 's/^void/int/g' scipy/interpolate/src/_fitpackmodule.c + + sed -i 's/^void/int/g' scipy/optimize/_trlib/trlib_private.h + sed -i 's/^void/int/g' scipy/optimize/_trlib/trlib/trlib_private.h + sed -i 's/, int)/)/g' scipy/optimize/_trlib/trlib_private.h + sed -i 's/, 1)/)/g' scipy/optimize/_trlib/trlib_private.h + + sed -i 's/^void/int/g' scipy/spatial/qhull_misc.h + sed -i 's/, size_t)/)/g' scipy/spatial/qhull_misc.h + sed -i 's/,1)/)/g' scipy/spatial/qhull_misc.h + # Missing declaration from cython_lapack_signatures.txt echo "void ilaenv(int *ispec, char *name, char *opts, int *n1, int *n2, int *n3, int *n4)" \ >> scipy/linalg/cython_lapack_signatures.txt + # sed -i 's/^void/int/g' scipy/linalg/cython_lapack_signatures.txt + # Input error causes "duplicate symbol" linker errors. Empty out the file. - echo "" > scipy/sparse/linalg/dsolve/SuperLU/SRC/input_error.c - echo 'import sys' >> scipy/__init__.py - echo 'if "pyodide_js" in sys.modules:' >> scipy/__init__.py - echo ' from pyodide_js._module import loadDynamicLibrary' >> scipy/__init__.py - echo ' loadDynamicLibrary("/lib/python3.9/site-packages/scipy/linalg/_flapack.so")' >> scipy/__init__.py - - prereplay: | - sed -i 's/void F_FUNC/int F_FUNC/g' scipy/linalg/_lapack_subroutines.h - sed -i 's/void F_FUNC/int F_FUNC/g' scipy/linalg/_blas_subroutines.h - find -name '*.c' | xargs sed -i 's/extern void F_WRAPPEDFUNC/extern int F_WRAPPEDFUNC/g' - find -name '*.c' | xargs sed -i 's/void (\*f2py_func)/int (*f2py_func)/g' - find -name '*.c' | xargs sed -i 's/static void cb_/static int cb_/g' - find -name '*.c' | xargs sed -i 's/typedef void(\*cb_/typedef int(*cb_/g' - sed -i 's/,size_t//' build/src.linux-x86_64-3.9/build/src.linux-x86_64-3.9/scipy/linalg/_flapackmodule.c - sed -i 's/,slen([a-z]*))/)/g' build/src.linux-x86_64-3.9/build/src.linux-x86_64-3.9/scipy/linalg/_flapackmodule.c - sed -i 's/ float (\*f2py_func)/ double (\*f2py_func)/g' build/src.linux-x86_64-3.9/build/src.linux-x86_64-3.9/scipy/linalg/_fblasmodule.c + echo "" > scipy/sparse/linalg/_dsolve/SuperLU/SRC/input_error.c + + post: | + cp $PKG_BUILD_DIR/scipy/linalg/cython_{lapack,blas}.pxd $HOSTSITEPACKAGES/scipy/linalg/ requirements: run: diff --git a/packages/scipy/patches/0001-Fix-dstevr-in-special-lapack_defs.h.patch b/packages/scipy/patches/0001-Fix-dstevr-in-special-lapack_defs.h.patch new file mode 100644 index 00000000000..c7c8b64aacb --- /dev/null +++ b/packages/scipy/patches/0001-Fix-dstevr-in-special-lapack_defs.h.patch @@ -0,0 +1,32 @@ +From 90a2cac7e8812f0a3e64c6bbc45fb1ece8d5f4d1 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Fri, 18 Mar 2022 16:25:39 -0700 +Subject: [PATCH 01/14] Fix dstevr in special/lapack_defs.h + +--- + scipy/special/lapack_defs.h | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/scipy/special/lapack_defs.h b/scipy/special/lapack_defs.h +index 0d20ba1ca..d4325f71f 100644 +--- a/scipy/special/lapack_defs.h ++++ b/scipy/special/lapack_defs.h +@@ -8,13 +8,12 @@ extern void BLAS_FUNC(dstevr)(char *jobz, char *range, CBLAS_INT *n, double *d, + double *vl, double *vu, CBLAS_INT *il, CBLAS_INT *iu, double *abstol, + CBLAS_INT *m, double *w, double *z, CBLAS_INT *ldz, CBLAS_INT *isuppz, + double *work, CBLAS_INT *lwork, CBLAS_INT *iwork, CBLAS_INT *liwork, +- CBLAS_INT *info, size_t jobz_len, size_t range_len); ++ CBLAS_INT *info); + + static void c_dstevr(char *jobz, char *range, CBLAS_INT *n, double *d, double *e, + double *vl, double *vu, CBLAS_INT *il, CBLAS_INT *iu, double *abstol, + CBLAS_INT *m, double *w, double *z, CBLAS_INT *ldz, CBLAS_INT *isuppz, + double *work, CBLAS_INT *lwork, CBLAS_INT *iwork, CBLAS_INT *liwork, CBLAS_INT *info) { + BLAS_FUNC(dstevr)(jobz, range, n, d, e, vl, vu, il, iu, abstol, m, +- w, z, ldz, isuppz, work, lwork, iwork, liwork, info, +- 1, 1); ++ w, z, ldz, isuppz, work, lwork, iwork, liwork, info); + } +-- +2.25.1 + diff --git a/packages/scipy/patches/0002-loadDynamicLibrary-flapack.patch b/packages/scipy/patches/0002-loadDynamicLibrary-flapack.patch new file mode 100644 index 00000000000..5b97d3a28ce --- /dev/null +++ b/packages/scipy/patches/0002-loadDynamicLibrary-flapack.patch @@ -0,0 +1,56 @@ +From 9e6c2ad31267a5ed0c30645e70a0b290b3973dd1 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Sat, 2 Apr 2022 14:20:25 -0700 +Subject: [PATCH 02/14] loadDynamicLibrary flapack + +We are using CLAPACK for our LAPACK, but CLAPACK only supports +LAPACK v3.2 since starting in v3.3 LAPACK started adding fortran +code from Fortran standards more recent that 1990 and f2c only +supports Fortran '90. Scipy uses some functions that were added +in LAPACK v3.4, but all of these are Fortran 77 compliant and so +we *can* f2c them. In scipy/meta.yaml we inject these into +`lapack_extras.f` which gets built into `_flapack.so`. + +Why do we do it this way? It was the first thing I tried that +worked and I was so exhausted that I didn't have the energy to be +bothered by the hack. Ideally we would copy these into our CLAPACK +but CLAPACK is the result of hand modified f2c output. (In fact +this is how f2c was always intended to be used, the output is not +good C code by itself.) We are automating the patches that are +necessary in `_f2c_fixes.py`, but these don't get applied to CLAPACK. + +Anyways, the issue is that CLAPACK is loaded as a "shared library" +which means that we call `loadDynamicLibrary` on it so that all of +its symbols are globally scoped (as opposed to needing to manually +look them up with `dlsym`). Scipy is not loaded this way. But we +need the `lapack_extras.f` symbols to have this global visibility. +So we have to call loadDynamicLibrary on it, hence this patch. +--- + scipy/linalg/lapack.py | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/scipy/linalg/lapack.py b/scipy/linalg/lapack.py +index 9850d80c4..a28c3c6ca 100644 +--- a/scipy/linalg/lapack.py ++++ b/scipy/linalg/lapack.py +@@ -809,6 +809,17 @@ All functions + ilaver + + """ ++ ++import sysconfig ++import pyodide_js ++from site import getsitepackages ++ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") ++pyodide_js._module.loadDynamicLibrary(f'{getsitepackages()[0]}/scipy/linalg/_flapack{ext_suffix}') ++del getsitepackages ++del pyodide_js ++del sysconfig ++del ext_suffix ++ + # + # Author: Pearu Peterson, March 2002 + # +-- +2.25.1 + diff --git a/packages/scipy/patches/add-lapack_extras-to-flapack.patch b/packages/scipy/patches/0003-Add-lapack_extras-to-linalg-setup.py.patch similarity index 86% rename from packages/scipy/patches/add-lapack_extras-to-flapack.patch rename to packages/scipy/patches/0003-Add-lapack_extras-to-linalg-setup.py.patch index f3629ac49bb..87b2c0d3d57 100644 --- a/packages/scipy/patches/add-lapack_extras-to-flapack.patch +++ b/packages/scipy/patches/0003-Add-lapack_extras-to-linalg-setup.py.patch @@ -1,7 +1,7 @@ -From 117b00d72c0472eb6a33e8fcbf17e3bfe3337a88 Mon Sep 17 00:00:00 2001 +From f6e479ea524c286f0a96d5df60d1f8edb595f643 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 31 Dec 2021 22:27:07 -0800 -Subject: [PATCH] Add lapack_extras to linalg/setup.py +Subject: [PATCH 03/14] Add lapack_extras to linalg/setup.py --- scipy/linalg/setup.py | 2 +- diff --git a/packages/scipy/patches/convert-xerrwv-message-arg-from-int-to-str.patch b/packages/scipy/patches/0004-int-to-string.patch similarity index 89% rename from packages/scipy/patches/convert-xerrwv-message-arg-from-int-to-str.patch rename to packages/scipy/patches/0004-int-to-string.patch index 076d06b2ad2..ff7967253c4 100644 --- a/packages/scipy/patches/convert-xerrwv-message-arg-from-int-to-str.patch +++ b/packages/scipy/patches/0004-int-to-string.patch @@ -1,14 +1,13 @@ -From 109051fc810881a7291b47ba9353a69a36b600e3 Mon Sep 17 00:00:00 2001 +From 3c0ad123668366e82b64f459941eb36943745c43 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sat, 25 Dec 2021 18:04:18 -0800 -Subject: [PATCH] int to string +Subject: [PATCH 04/14] int to string f2c does not handle implicit casts of function arguments correctly. The msg -argument of `xerrwv` is defined to be an `int *`, and then implicitly cast +argument of `xerrwv` is defined to be an `int *`, and then implicitly cast from a string at the call site. This doesn't work correctly. We redefine the type of the first argument to be string to fix the problem. - --- scipy/integrate/odepack/xerrwv.f | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/scipy/patches/disable-blas-detection.patch b/packages/scipy/patches/0005-disable-blas-detection.patch similarity index 62% rename from packages/scipy/patches/disable-blas-detection.patch rename to packages/scipy/patches/0005-disable-blas-detection.patch index e45af8efc1b..1b3056a08fa 100644 --- a/packages/scipy/patches/disable-blas-detection.patch +++ b/packages/scipy/patches/0005-disable-blas-detection.patch @@ -1,19 +1,21 @@ -commit 7352fc4a977a3414cf3194faf6ef990638a6329c -Author: Roman Yurchak -Date: Sat Feb 27 11:13:29 2021 +0100 - - disable-blas-detection.patch +From 25612fa1854608cbbdcb994abb8486fc6f4a0736 Mon Sep 17 00:00:00 2001 +From: Roman Yurchak +Date: Wed, 6 Apr 2022 21:19:55 -0700 +Subject: [PATCH 05/14] disable blas detection BLAS and LAPACK aren't available on host because we only cross compile these libraries (see CLAPACK/meta.yaml). Scipy tries to detect these libraries and if it fails to find them errors out the build. But we have them installed correctly for our target, so we just disable this detection mechanism. +--- + setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py -index 0e11de60f..8c28c613d 100755 +index a5ccaa159..8c3eb13c3 100755 --- a/setup.py +++ b/setup.py -@@ -484,7 +484,7 @@ def configuration(parent_package='', top_path=None): +@@ -507,7 +507,7 @@ def configuration(parent_package='', top_path=None): lapack_opt = get_info('lapack_opt') @@ -22,3 +24,6 @@ index 0e11de60f..8c28c613d 100755 if sys.platform == "darwin": msg = ('No BLAS/LAPACK libraries found. ' 'Note: Accelerate is no longer supported.') +-- +2.25.1 + diff --git a/packages/scipy/patches/fix-fortran-files-minpack.patch b/packages/scipy/patches/0006-fix-fotran-files-minpack.patch similarity index 89% rename from packages/scipy/patches/fix-fortran-files-minpack.patch rename to packages/scipy/patches/0006-fix-fotran-files-minpack.patch index c5c46634677..f22f4a67850 100644 --- a/packages/scipy/patches/fix-fortran-files-minpack.patch +++ b/packages/scipy/patches/0006-fix-fotran-files-minpack.patch @@ -1,3 +1,34 @@ +From 84a590dd8d7563306c09f1f3ba5299e68c9bd359 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Wed, 6 Apr 2022 21:21:53 -0700 +Subject: [PATCH 06/14] fix fotran files minpack + +--- + scipy/optimize/minpack/chkder.f | 3 +-- + scipy/optimize/minpack/dogleg.f | 3 +-- + scipy/optimize/minpack/dpmpar.f | 3 +-- + scipy/optimize/minpack/enorm.f | 3 +-- + scipy/optimize/minpack/fdjac1.f | 3 +-- + scipy/optimize/minpack/fdjac2.f | 3 +-- + scipy/optimize/minpack/hybrd.f | 3 +-- + scipy/optimize/minpack/hybrd1.f | 3 +-- + scipy/optimize/minpack/hybrj.f | 3 +-- + scipy/optimize/minpack/hybrj1.f | 3 +-- + scipy/optimize/minpack/lmder.f | 3 +-- + scipy/optimize/minpack/lmder1.f | 3 +-- + scipy/optimize/minpack/lmdif.f | 3 +-- + scipy/optimize/minpack/lmdif1.f | 3 +-- + scipy/optimize/minpack/lmpar.f | 3 +-- + scipy/optimize/minpack/lmstr.f | 3 +-- + scipy/optimize/minpack/lmstr1.f | 3 +-- + scipy/optimize/minpack/qform.f | 3 +-- + scipy/optimize/minpack/qrfac.f | 3 +-- + scipy/optimize/minpack/qrsolv.f | 3 +-- + scipy/optimize/minpack/r1mpyq.f | 3 +-- + scipy/optimize/minpack/r1updt.f | 3 +-- + scipy/optimize/minpack/rwupdt.f | 3 +-- + 23 files changed, 23 insertions(+), 46 deletions(-) + diff --git a/scipy/optimize/minpack/chkder.f b/scipy/optimize/minpack/chkder.f index 0657ab56a..29578fc41 100644 --- a/scipy/optimize/minpack/chkder.f @@ -251,3 +282,6 @@ index 61a7928bb..05282b556 100644 integer n,ldr double precision alpha double precision r(ldr,n),w(n),b(n),cos(n),sin(n) +-- +2.25.1 + diff --git a/packages/scipy/patches/gemm_-no-const.patch b/packages/scipy/patches/0007-gemm_-no-const.patch similarity index 68% rename from packages/scipy/patches/gemm_-no-const.patch rename to packages/scipy/patches/0007-gemm_-no-const.patch index 8d84b5df6f8..7b4b5d4dbcf 100644 --- a/packages/scipy/patches/gemm_-no-const.patch +++ b/packages/scipy/patches/0007-gemm_-no-const.patch @@ -1,23 +1,22 @@ -From 549ab1f7916e16222bdfc67a46f7a7b9b2125265 Mon Sep 17 00:00:00 2001 +From e3140b25165537ddc731f1d72e46ec9059faa6d6 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sat, 18 Dec 2021 11:41:15 -0800 -Subject: [PATCH] gemm_ no const +Subject: [PATCH 07/14] gemm_ no const -cgemm, dgemm, sgemm, and zgemm are declared with `const` in slu_cdefs.h, but +cgemm, dgemm, sgemm, and zgemm are declared with `const` in slu_cdefs.h, but other places don't have the cosnt causing compile errors. This patch drops the consts and fixes the problem. - --- - scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_cdefs.h | 6 +++--- - scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_ddefs.h | 6 +++--- - scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_sdefs.h | 6 +++--- - scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_zdefs.h | 6 +++--- + scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_cdefs.h | 6 +++--- + scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_ddefs.h | 6 +++--- + scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_sdefs.h | 6 +++--- + scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_zdefs.h | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_cdefs.h b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_cdefs.h +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_cdefs.h b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_cdefs.h index 346f9af0a..8af19888f 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_cdefs.h -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_cdefs.h +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_cdefs.h ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_cdefs.h @@ -272,9 +272,9 @@ extern void ccheck_tempv(int, complex *); /*! \brief BLAS */ @@ -31,10 +30,10 @@ index 346f9af0a..8af19888f 100644 extern int ctrsv_(char*, char*, char*, int*, complex*, int*, complex*, int*); extern int ctrsm_(char*, char*, char*, char*, int*, int*, -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_ddefs.h b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_ddefs.h -index 934cd677a..33e869fb7 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_ddefs.h -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_ddefs.h +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_ddefs.h b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_ddefs.h +index fd971f365..e70699aef 100644 +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_ddefs.h ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_ddefs.h @@ -269,9 +269,9 @@ extern void dcheck_tempv(int, double *); /*! \brief BLAS */ @@ -48,10 +47,10 @@ index 934cd677a..33e869fb7 100644 extern int dtrsv_(char*, char*, char*, int*, double*, int*, double*, int*); extern int dtrsm_(char*, char*, char*, char*, int*, int*, -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_sdefs.h b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_sdefs.h +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_sdefs.h b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_sdefs.h index 1b6db977e..fee4bc1d6 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_sdefs.h -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_sdefs.h +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_sdefs.h ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_sdefs.h @@ -269,9 +269,9 @@ extern void scheck_tempv(int, float *); /*! \brief BLAS */ @@ -65,10 +64,10 @@ index 1b6db977e..fee4bc1d6 100644 extern int strsv_(char*, char*, char*, int*, float*, int*, float*, int*); extern int strsm_(char*, char*, char*, char*, int*, int*, -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_zdefs.h b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_zdefs.h +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_zdefs.h b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_zdefs.h index abb7d937e..6c572ff2b 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_zdefs.h -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_zdefs.h +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_zdefs.h ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_zdefs.h @@ -272,9 +272,9 @@ extern void zcheck_tempv(int, doublecomplex *); /*! \brief BLAS */ diff --git a/packages/scipy/patches/make-int-return-values.patch b/packages/scipy/patches/0008-make-int-return-values.patch similarity index 75% rename from packages/scipy/patches/make-int-return-values.patch rename to packages/scipy/patches/0008-make-int-return-values.patch index 3c6990870dd..2f8413fbc89 100644 --- a/packages/scipy/patches/make-int-return-values.patch +++ b/packages/scipy/patches/0008-make-int-return-values.patch @@ -1,3 +1,8 @@ +From b540dfc763970a818b70b520b7b1e0b365bc3825 Mon Sep 17 00:00:00 2001 +From: Joe Marshall +Date: Wed, 6 Apr 2022 21:25:13 -0700 +Subject: [PATCH 08/14] make int return values + The return values of f2c functions are insignificant in most cases, so often it is treated as returning void, when it really should return int (values are "returned" by writing to pointers passed as an argument, but an obscure feature @@ -7,15 +12,42 @@ There's a big change to scipy/linalg/_cython_wrapper_generators.py, which is called on build to generate python wrappers for lapack and BLAS. The change makes everything call direct to CLAPACK with the correct function signatures and also fixes some fortran -> c linking oddities that occur because f2py assumes -different function signatures to f2c, which in turn creates different function -signatures compared to what has been done in CLAPACK. +different function signatures to f2c, which in turn creates different function +signatures compared to what has been done in CLAPACK. f2py is patched in numpy to make subroutines return int. emscripten is very strict about void vs int returns and function signatures, so -we change everything to return int from subroutines, and signatures are altered +we change everything to return int from subroutines, and signatures are altered to be consistent. - +--- + scipy/_build_utils/src/wrap_dummy_g77_abi.f | 16 ---- + scipy/integrate/_odepackmodule.c | 6 +- + scipy/linalg/fblas_l1.pyf.src | 78 +++++++++++++------ + scipy/linalg/setup.py | 7 +- + scipy/optimize/_lsq/setup.py | 2 +- + .../linalg/_dsolve/SuperLU/SRC/cgsrfs.c | 7 -- + .../linalg/_dsolve/SuperLU/SRC/dgscon.c | 3 - + .../linalg/_dsolve/SuperLU/SRC/dgsrfs.c | 7 -- + .../_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c | 2 - + .../_dsolve/SuperLU/SRC/ilu_cdrop_row.c | 9 --- + .../_dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c | 1 - + .../_dsolve/SuperLU/SRC/ilu_ddrop_row.c | 7 -- + .../_dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c | 2 +- + .../_dsolve/SuperLU/SRC/ilu_sdrop_row.c | 7 -- + .../_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c | 1 - + .../_dsolve/SuperLU/SRC/ilu_zdrop_row.c | 9 --- + .../_dsolve/SuperLU/SRC/scipy_slu_config.h | 8 ++ + .../linalg/_dsolve/SuperLU/SRC/sgsrfs.c | 7 -- + .../linalg/_dsolve/SuperLU/SRC/sgssvx.c | 7 +- + .../linalg/_dsolve/SuperLU/SRC/slu_dcomplex.h | 5 +- + .../linalg/_dsolve/SuperLU/SRC/slu_scomplex.h | 5 +- + .../linalg/_dsolve/SuperLU/SRC/slu_util.h | 1 - + .../linalg/_dsolve/SuperLU/SRC/zgsrfs.c | 7 -- + scipy/sparse/linalg/_dsolve/_superlu_utils.c | 4 +- + .../linalg/_eigen/arpack/ARPACK/SRC/debug.h | 20 ++--- + .../linalg/_eigen/arpack/ARPACK/SRC/stat.h | 26 +++---- + 26 files changed, 109 insertions(+), 145 deletions(-) diff --git a/scipy/_build_utils/src/wrap_dummy_g77_abi.f b/scipy/_build_utils/src/wrap_dummy_g77_abi.f index caf99ac63..73cdebd96 100644 @@ -52,7 +84,7 @@ index caf99ac63..73cdebd96 100644 COMPLEX X, Y EXTERNAL CLADIV diff --git a/scipy/integrate/_odepackmodule.c b/scipy/integrate/_odepackmodule.c -index b296e6e30..f5029bee9 100644 +index 9974ae0f3..f74c379df 100644 --- a/scipy/integrate/_odepackmodule.c +++ b/scipy/integrate/_odepackmodule.c @@ -158,13 +158,13 @@ typedef void lsoda_f_t(F_INT *n, double *t, double *y, double *ydot); @@ -81,7 +113,7 @@ index b296e6e30..f5029bee9 100644 { /* diff --git a/scipy/linalg/fblas_l1.pyf.src b/scipy/linalg/fblas_l1.pyf.src -index 4803a4a97..5adc703a4 100644 +index 89e50a990..b7c77115d 100644 --- a/scipy/linalg/fblas_l1.pyf.src +++ b/scipy/linalg/fblas_l1.pyf.src @@ -279,14 +279,16 @@ end subroutine axpy @@ -239,12 +271,12 @@ index 4803a4a97..5adc703a4 100644 dimension(*), intent(in) :: x asum,s diff --git a/scipy/linalg/setup.py b/scipy/linalg/setup.py -index 0cbd5ffdf..d4ee4f867 100644 +index d384081a2..d318fa8b8 100644 --- a/scipy/linalg/setup.py +++ b/scipy/linalg/setup.py -@@ -44,9 +44,10 @@ def configuration(parent_package='', top_path=None): +@@ -45,9 +45,10 @@ def configuration(parent_package='', top_path=None): # flapack: - sources = ['flapack.pyf.src'] + sources = ['flapack.pyf.src', 'lapack_extras.f'] sources += get_g77_abi_wrappers(lapack_opt) - dep_pfx = join('src', 'lapack_deprecations') - deprecated_lapack_routines = [join(dep_pfx, c + 'gegv.f') for c in 'cdsz'] @@ -269,10 +301,10 @@ index 7ce589c0c..6412886e0 100644 return config -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/cgsrfs.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/cgsrfs.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/cgsrfs.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/cgsrfs.c index a7dd2f8fd..3b2544d4f 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/cgsrfs.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/cgsrfs.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/cgsrfs.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/cgsrfs.c @@ -171,13 +171,6 @@ cgsrfs(trans_t trans, SuperMatrix *A, SuperMatrix *L, SuperMatrix *U, int isave[3]; @@ -287,10 +319,10 @@ index a7dd2f8fd..3b2544d4f 100644 Astore = A->Store; Aval = Astore->nzval; -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/dgscon.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/dgscon.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dgscon.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dgscon.c index d51f24c3b..b76dc7a78 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/dgscon.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/dgscon.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dgscon.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dgscon.c @@ -92,10 +92,7 @@ dgscon(char *norm, SuperMatrix *L, SuperMatrix *U, double *work; int *iwork; @@ -302,10 +334,10 @@ index d51f24c3b..b76dc7a78 100644 /* Test the input parameters. */ *info = 0; -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/dgsrfs.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/dgsrfs.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dgsrfs.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dgsrfs.c index d37226035..69f6bd8cf 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/dgsrfs.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/dgsrfs.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dgsrfs.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/dgsrfs.c @@ -171,13 +171,6 @@ dgsrfs(trans_t trans, SuperMatrix *A, SuperMatrix *L, SuperMatrix *U, int isave[3]; @@ -320,10 +352,10 @@ index d37226035..69f6bd8cf 100644 Astore = A->Store; Aval = Astore->nzval; -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c -index a5eb0b613..bc8ce3537 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c +index 027b9260f..e1299bed2 100644 +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c @@ -26,8 +26,6 @@ at the top-level directory. int num_drop_U; #endif @@ -333,10 +365,10 @@ index a5eb0b613..bc8ce3537 100644 #if 0 static complex *A; /* used in _compare_ only */ static int _compare_(const void *a, const void *b) -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_cdrop_row.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_cdrop_row.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_cdrop_row.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_cdrop_row.c index 09b8a937d..fdd6064de 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_cdrop_row.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_cdrop_row.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_cdrop_row.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_cdrop_row.c @@ -23,15 +23,6 @@ at the top-level directory. #include #include "slu_cdefs.h" @@ -353,10 +385,10 @@ index 09b8a937d..fdd6064de 100644 static float *A; /* used in _compare_ only */ static int _compare_(const void *a, const void *b) { -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c index 2bb889c39..658e7e71e 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_dcopy_to_ucol.c @@ -26,7 +26,6 @@ at the top-level directory. int num_drop_U; #endif @@ -365,10 +397,10 @@ index 2bb889c39..658e7e71e 100644 #if 0 static double *A; /* used in _compare_ only */ -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_ddrop_row.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_ddrop_row.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ddrop_row.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ddrop_row.c index f25b5085a..19afee76c 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_ddrop_row.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_ddrop_row.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ddrop_row.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ddrop_row.c @@ -23,13 +23,6 @@ at the top-level directory. #include #include "slu_ddefs.h" @@ -383,10 +415,10 @@ index f25b5085a..19afee76c 100644 static double *A; /* used in _compare_ only */ static int _compare_(const void *a, const void *b) -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c index 6dc0460c1..994224c35 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_scopy_to_ucol.c @@ -26,7 +26,7 @@ at the top-level directory. int num_drop_U; #endif @@ -396,10 +428,10 @@ index 6dc0460c1..994224c35 100644 #if 0 static float *A; /* used in _compare_ only */ -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_sdrop_row.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_sdrop_row.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_sdrop_row.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_sdrop_row.c index 836ee5450..8d1368838 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_sdrop_row.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_sdrop_row.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_sdrop_row.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_sdrop_row.c @@ -23,13 +23,6 @@ at the top-level directory. #include #include "slu_sdefs.h" @@ -414,10 +446,10 @@ index 836ee5450..8d1368838 100644 static float *A; /* used in _compare_ only */ static int _compare_(const void *a, const void *b) -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c -index afe4a7e04..4f45bd11d 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c +index e7847a37d..9c1f02e09 100644 +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c @@ -26,7 +26,6 @@ at the top-level directory. int num_drop_U; #endif @@ -426,10 +458,10 @@ index afe4a7e04..4f45bd11d 100644 #if 0 static doublecomplex *A; /* used in _compare_ only */ -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_zdrop_row.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_zdrop_row.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zdrop_row.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zdrop_row.c index 2de1226ef..362e18210 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_zdrop_row.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/ilu_zdrop_row.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zdrop_row.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zdrop_row.c @@ -23,15 +23,6 @@ at the top-level directory. #include #include "slu_zdefs.h" @@ -446,10 +478,10 @@ index 2de1226ef..362e18210 100644 static double *A; /* used in _compare_ only */ static int _compare_(const void *a, const void *b) { -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/scipy_slu_config.h b/scipy/sparse/linalg/dsolve/SuperLU/SRC/scipy_slu_config.h +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/scipy_slu_config.h b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/scipy_slu_config.h index 5afc93b5d..1a2c4ca36 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/scipy_slu_config.h -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/scipy_slu_config.h +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/scipy_slu_config.h ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/scipy_slu_config.h @@ -3,6 +3,14 @@ #include @@ -465,10 +497,10 @@ index 5afc93b5d..1a2c4ca36 100644 /* * Support routines */ -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/sgsrfs.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/sgsrfs.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/sgsrfs.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/sgsrfs.c index 5faab1dfb..7380eb6cc 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/sgsrfs.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/sgsrfs.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/sgsrfs.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/sgsrfs.c @@ -171,13 +171,6 @@ sgsrfs(trans_t trans, SuperMatrix *A, SuperMatrix *L, SuperMatrix *U, int isave[3]; @@ -483,10 +515,10 @@ index 5faab1dfb..7380eb6cc 100644 Astore = A->Store; Aval = Astore->nzval; -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/sgssvx.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/sgssvx.c -index c7aa79b3c..4f3a6310b 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/sgssvx.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/sgssvx.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/sgssvx.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/sgssvx.c +index 7ee2e77ea..d13914e04 100644 +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/sgssvx.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/sgssvx.c @@ -21,6 +21,8 @@ at the top-level directory. */ #include "slu_sdefs.h" @@ -505,7 +537,7 @@ index c7aa79b3c..4f3a6310b 100644 Bstore = B->Store; Xstore = X->Store; -@@ -575,7 +575,8 @@ printf("dgssvx: Fact=%4d, Trans=%4d, equed=%c\n", +@@ -579,7 +579,8 @@ printf("dgssvx: Fact=%4d, Trans=%4d, equed=%c\n", } else { *(unsigned char *)norm = 'I'; } @@ -515,10 +547,10 @@ index c7aa79b3c..4f3a6310b 100644 sgscon(norm, L, U, anorm, rcond, stat, &info1); utime[RCOND] = SuperLU_timer_() - t0; } -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_dcomplex.h b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_dcomplex.h +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_dcomplex.h b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_dcomplex.h index 67e83bcc7..e5757d5c4 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_dcomplex.h -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_dcomplex.h +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_dcomplex.h ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_dcomplex.h @@ -28,7 +28,10 @@ at the top-level directory. #ifndef DCOMPLEX_INCLUDE #define DCOMPLEX_INCLUDE @@ -531,10 +563,10 @@ index 67e83bcc7..e5757d5c4 100644 /* Macro definitions */ -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_scomplex.h b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_scomplex.h +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_scomplex.h b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_scomplex.h index 5c9aa7058..a10f9a52f 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_scomplex.h -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_scomplex.h +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_scomplex.h ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_scomplex.h @@ -27,8 +27,9 @@ at the top-level directory. #ifndef SCOMPLEX_INCLUDE @@ -547,10 +579,10 @@ index 5c9aa7058..a10f9a52f 100644 /* Macro definitions */ -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_util.h b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_util.h -index d34e4fe5b..7df125f68 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_util.h -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slu_util.h +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_util.h b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_util.h +index 42bab985d..936db381d 100644 +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_util.h ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slu_util.h @@ -398,7 +398,6 @@ extern int spcoletree (int *, int *, int *, int, int, int *); extern int *TreePostorder (int, int *); extern double SuperLU_timer_ (); @@ -559,10 +591,10 @@ index d34e4fe5b..7df125f68 100644 extern void ifill (int *, int, int); extern void snode_profile (int, int *); extern void super_stats (int, int *); -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/zgsrfs.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/zgsrfs.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/zgsrfs.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/zgsrfs.c index 02b63df30..71209b3b7 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/zgsrfs.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/zgsrfs.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/zgsrfs.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/zgsrfs.c @@ -171,13 +171,6 @@ zgsrfs(trans_t trans, SuperMatrix *A, SuperMatrix *L, SuperMatrix *U, int isave[3]; @@ -577,10 +609,10 @@ index 02b63df30..71209b3b7 100644 Astore = A->Store; Aval = Astore->nzval; -diff --git a/scipy/sparse/linalg/dsolve/_superlu_utils.c b/scipy/sparse/linalg/dsolve/_superlu_utils.c -index 16bb03392..13e63c0ba 100644 ---- a/scipy/sparse/linalg/dsolve/_superlu_utils.c -+++ b/scipy/sparse/linalg/dsolve/_superlu_utils.c +diff --git a/scipy/sparse/linalg/_dsolve/_superlu_utils.c b/scipy/sparse/linalg/_dsolve/_superlu_utils.c +index 49b928a43..082268771 100644 +--- a/scipy/sparse/linalg/_dsolve/_superlu_utils.c ++++ b/scipy/sparse/linalg/_dsolve/_superlu_utils.c @@ -243,12 +243,12 @@ int input_error(char *srname, int *info) * Stubs for Harwell Subroutine Library functions that SuperLU tries to call. */ @@ -596,10 +628,10 @@ index 16bb03392..13e63c0ba 100644 int *g, int h[], int *i, int j[], int *k, double l[], int m[], int n[]) { -diff --git a/scipy/sparse/linalg/eigen/arpack/ARPACK/SRC/debug.h b/scipy/sparse/linalg/eigen/arpack/ARPACK/SRC/debug.h +diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/debug.h b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/debug.h index 5eb0bb1b3..81a6efafb 100644 ---- a/scipy/sparse/linalg/eigen/arpack/ARPACK/SRC/debug.h -+++ b/scipy/sparse/linalg/eigen/arpack/ARPACK/SRC/debug.h +--- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/debug.h ++++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/debug.h @@ -1,16 +1,16 @@ -c + @@ -627,10 +659,10 @@ index 5eb0bb1b3..81a6efafb 100644 +c & msaupd, msaup2, msaitr, mseigt, msapps, msgets, mseupd, +c & mnaupd, mnaup2, mnaitr, mneigh, mnapps, mngets, mneupd, +c & mcaupd, mcaup2, mcaitr, mceigh, mcapps, mcgets, mceupd -diff --git a/scipy/sparse/linalg/eigen/arpack/ARPACK/SRC/stat.h b/scipy/sparse/linalg/eigen/arpack/ARPACK/SRC/stat.h +diff --git a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/stat.h b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/stat.h index 66a8e9f87..81d49c3bd 100644 ---- a/scipy/sparse/linalg/eigen/arpack/ARPACK/SRC/stat.h -+++ b/scipy/sparse/linalg/eigen/arpack/ARPACK/SRC/stat.h +--- a/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/stat.h ++++ b/scipy/sparse/linalg/_eigen/arpack/ARPACK/SRC/stat.h @@ -5,17 +5,17 @@ c c\SCCS Information: @(#) c FILE: stat.h SID: 2.2 DATE OF SID: 11/16/95 RELEASE: 2 diff --git a/packages/scipy/patches/rename-_page_trend_test.patch b/packages/scipy/patches/0009-Rename-_page_trend_test.py-to-prevent-test-unvendori.patch similarity index 71% rename from packages/scipy/patches/rename-_page_trend_test.patch rename to packages/scipy/patches/0009-Rename-_page_trend_test.py-to-prevent-test-unvendori.patch index 9ec190ce28e..b049f2863b3 100644 --- a/packages/scipy/patches/rename-_page_trend_test.patch +++ b/packages/scipy/patches/0009-Rename-_page_trend_test.py-to-prevent-test-unvendori.patch @@ -1,11 +1,10 @@ -From 6ffb169dbe331627d70da3f79f928057f1a4e163 Mon Sep 17 00:00:00 2001 +From 2597c12ed2438518e826fbb6721cc266af30de2f Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sun, 26 Dec 2021 07:34:40 -0800 -Subject: [PATCH] Rename _page_trend_test.py to prevent test unvendoring +Subject: [PATCH 09/14] Rename _page_trend_test.py to prevent test unvendoring -unvendor_tests will unvendor any file that ends in _test.py. +unvendor_tests will unvendor any file that ends in _test.py. Prevent that by changing the name of this file. - --- scipy/stats/__init__.py | 2 +- scipy/stats/{_page_trend_test.py => _page_trend_test_.py} | 0 @@ -13,18 +12,18 @@ Prevent that by changing the name of this file. rename scipy/stats/{_page_trend_test.py => _page_trend_test_.py} (100%) diff --git a/scipy/stats/__init__.py b/scipy/stats/__init__.py -index f410f5410..ad702c38d 100644 +index f416e1a4f..2268caa3e 100644 --- a/scipy/stats/__init__.py +++ b/scipy/stats/__init__.py -@@ -453,7 +453,7 @@ from ._bootstrap import bootstrap +@@ -466,7 +466,7 @@ from ._bootstrap import bootstrap, BootstrapDegenerateDistributionWarning from ._entropy import * from ._hypotests import * - from ._rvs_sampling import rvs_ratio_uniforms, NumericalInverseHermite + from ._rvs_sampling import rvs_ratio_uniforms, NumericalInverseHermite # noqa -from ._page_trend_test import page_trend_test +from ._page_trend_test_ import page_trend_test from ._mannwhitneyu import mannwhitneyu - __all__ = [s for s in dir() if not s.startswith("_")] # Remove dunders. + # Deprecated namespaces, to be removed in v2.0.0 diff --git a/scipy/stats/_page_trend_test.py b/scipy/stats/_page_trend_test_.py similarity index 100% rename from scipy/stats/_page_trend_test.py diff --git a/packages/scipy/patches/sasum-returns-double-not-float.patch b/packages/scipy/patches/0010-sasum-returns-double-not-float.patch similarity index 61% rename from packages/scipy/patches/sasum-returns-double-not-float.patch rename to packages/scipy/patches/0010-sasum-returns-double-not-float.patch index 7d6864ae82b..19d18de6613 100644 --- a/packages/scipy/patches/sasum-returns-double-not-float.patch +++ b/packages/scipy/patches/0010-sasum-returns-double-not-float.patch @@ -1,16 +1,16 @@ -From 9fe7ab77cbc2227982f87c9dbb8aa675b98fe9a8 Mon Sep 17 00:00:00 2001 +From b41cd9f3f5302c30281d6b8ed6c068586ecbe683 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sat, 18 Dec 2021 12:31:51 -0800 -Subject: [PATCH] sasum returns double not float +Subject: [PATCH 10/14] sasum returns double not float --- - scipy/sparse/linalg/dsolve/SuperLU/SRC/slacon2.c | 2 +- + scipy/sparse/linalg/_dsolve/SuperLU/SRC/slacon2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) -diff --git a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slacon2.c b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slacon2.c +diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slacon2.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slacon2.c index 50efe7849..c176fddb4 100644 ---- a/scipy/sparse/linalg/dsolve/SuperLU/SRC/slacon2.c -+++ b/scipy/sparse/linalg/dsolve/SuperLU/SRC/slacon2.c +--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slacon2.c ++++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/slacon2.c @@ -104,7 +104,7 @@ slacon2_(int *n, float *v, float *x, int *isgn, float *est, int *kase, int isave extern int SCOPY(int *, float *, int *, float *, int *); #else diff --git a/packages/scipy/patches/skip-fortran-fails-to-link.patch b/packages/scipy/patches/0011-skip-fortran-fails-to-link.patch similarity index 89% rename from packages/scipy/patches/skip-fortran-fails-to-link.patch rename to packages/scipy/patches/0011-skip-fortran-fails-to-link.patch index 5706237f0c3..1d2eae73144 100644 --- a/packages/scipy/patches/skip-fortran-fails-to-link.patch +++ b/packages/scipy/patches/0011-skip-fortran-fails-to-link.patch @@ -1,18 +1,17 @@ -From ad5d7fb18134af51cd1d40599a0bf59a4782ec53 Mon Sep 17 00:00:00 2001 +From d2107ce2e3edcd33adf41cf682bfc56358f52f00 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sat, 25 Dec 2021 15:08:18 -0800 -Subject: [PATCH] skip fortran fails to link +Subject: [PATCH 11/14] skip fortran fails to link These are tests and they have both void vs int return value problems and implicit function argument cast problems. Not worth fixing for tests. - --- scipy/integrate/setup.py | 6 ------ scipy/io/setup.py | 3 --- 2 files changed, 9 deletions(-) diff --git a/scipy/integrate/setup.py b/scipy/integrate/setup.py -index 11ce3d1aa..ddd7b08b2 100644 +index 1ba82d18c..dd298a480 100644 --- a/scipy/integrate/setup.py +++ b/scipy/integrate/setup.py @@ -95,12 +95,6 @@ def configuration(parent_package='',top_path=None): @@ -29,7 +28,7 @@ index 11ce3d1aa..ddd7b08b2 100644 config.add_subpackage('_ivp') diff --git a/scipy/io/setup.py b/scipy/io/setup.py -index bec840e36..f3fb726c2 100644 +index 0b936ceed..bf0c16d6c 100644 --- a/scipy/io/setup.py +++ b/scipy/io/setup.py @@ -3,9 +3,6 @@ def configuration(parent_package='',top_path=None): diff --git a/packages/scipy/patches/0012-Disable-lapack-detection.patch b/packages/scipy/patches/0012-Disable-lapack-detection.patch new file mode 100644 index 00000000000..bf97911980b --- /dev/null +++ b/packages/scipy/patches/0012-Disable-lapack-detection.patch @@ -0,0 +1,25 @@ +From 2a48cdef1a686053891880a4f97283f0fba94488 Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Wed, 6 Apr 2022 21:52:29 -0700 +Subject: [PATCH 12/14] Disable lapack detection + +--- + scipy/sparse/linalg/_propack/setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/scipy/sparse/linalg/_propack/setup.py b/scipy/sparse/linalg/_propack/setup.py +index b593cbca9..824980e24 100644 +--- a/scipy/sparse/linalg/_propack/setup.py ++++ b/scipy/sparse/linalg/_propack/setup.py +@@ -21,7 +21,7 @@ def configuration(parent_package='', top_path=None): + get_g77_abi_wrappers, + needs_g77_abi_wrapper) + +- lapack_opt = get_info('lapack_opt') ++ lapack_opt = {"extra_link_args":""} + pre_build_hook = gfortran_legacy_flag_hook + f2py_options = None + +-- +2.25.1 + diff --git a/packages/scipy/patches/0013-Add-extra-END-to-prini.f.patch b/packages/scipy/patches/0013-Add-extra-END-to-prini.f.patch new file mode 100644 index 00000000000..4565c8f9689 --- /dev/null +++ b/packages/scipy/patches/0013-Add-extra-END-to-prini.f.patch @@ -0,0 +1,23 @@ +From ca3f2eb55d8b076d02a126863c94d6b2169965ae Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 7 Apr 2022 10:41:26 -0700 +Subject: [PATCH 13/14] Add extra END to prini.f + +--- + scipy/linalg/src/id_dist/src/prini.f | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/scipy/linalg/src/id_dist/src/prini.f b/scipy/linalg/src/id_dist/src/prini.f +index 33a0bc8d3..f93289925 100644 +--- a/scipy/linalg/src/id_dist/src/prini.f ++++ b/scipy/linalg/src/id_dist/src/prini.f +@@ -110,4 +110,5 @@ C + 1 WRITE(IQ,1800) (MES(I),I=1,I1) + 1800 FORMAT(1X,80A1) + RETURN ++ END + END +\ No newline at end of file +-- +2.25.1 + diff --git a/packages/scipy/patches/0014-BUG-Fix-signature-of-D_IIR_forback-1-2.patch b/packages/scipy/patches/0014-BUG-Fix-signature-of-D_IIR_forback-1-2.patch new file mode 100644 index 00000000000..c8da7ebb81d --- /dev/null +++ b/packages/scipy/patches/0014-BUG-Fix-signature-of-D_IIR_forback-1-2.patch @@ -0,0 +1,36 @@ +From 48493e305cfd78a6b823cc92e916d8b79340877d Mon Sep 17 00:00:00 2001 +From: Hood Chatham +Date: Thu, 7 Apr 2022 11:02:29 -0700 +Subject: [PATCH 14/14] BUG Fix signature of D_IIR_forback(1,2) + +The precision parameter has type float but it is declared in `_splinemodule.c` +with type double. +--- + scipy/signal/_splinemodule.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/scipy/signal/_splinemodule.c b/scipy/signal/_splinemodule.c +index f66dd79aa..d4bcc1460 100644 +--- a/scipy/signal/_splinemodule.c ++++ b/scipy/signal/_splinemodule.c +@@ -15,14 +15,14 @@ extern int S_separable_2Dconvolve_mirror(float*,float*,int,int,float*,float*,int + + extern int D_cubic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); + extern int D_quadratic_spline2D(double*,double*,int,int,double,npy_intp*,npy_intp*,double); +-extern int D_IIR_forback1(double,double,double*,double*,int,int,int,double); +-extern int D_IIR_forback2(double,double,double*,double*,int,int,int,double); ++extern int D_IIR_forback1(double,double,double*,double*,int,int,int,float); ++extern int D_IIR_forback2(double,double,double*,double*,int,int,int,float); + extern int D_separable_2Dconvolve_mirror(double*,double*,int,int,double*,double*,int,int,npy_intp*,npy_intp*); + + #ifdef __GNUC__ + extern int C_IIR_forback1(__complex__ float,__complex__ float,__complex__ float*,__complex__ float*,int,int,int,float); + extern int C_separable_2Dconvolve_mirror(__complex__ float*,__complex__ float*,int,int,__complex__ float*,__complex__ float*,int,int,npy_intp*,npy_intp*); +-extern int Z_IIR_forback1(__complex__ double,__complex__ double,__complex__ double*,__complex__ double*,int,int,int,double); ++extern int Z_IIR_forback1(__complex__ double,__complex__ double,__complex__ double*,__complex__ double*,int,int,int,float); + extern int Z_separable_2Dconvolve_mirror(__complex__ double*,__complex__ double*,int,int,__complex__ double*,__complex__ double*,int,int,npy_intp*,npy_intp*); + #endif + +-- +2.25.1 + diff --git a/packages/scipy/patches/fix-implicit-cast-args-from-newer-lapack.patch b/packages/scipy/patches/fix-implicit-cast-args-from-newer-lapack.patch deleted file mode 100644 index 204707cf871..00000000000 --- a/packages/scipy/patches/fix-implicit-cast-args-from-newer-lapack.patch +++ /dev/null @@ -1,317 +0,0 @@ -We are hijacking dfft.f to inject missing functions from future versions of LAPACK. - -This patch is applied to the generated dfft.c after f2c is applied to dfft.f. -This must be done in between when f2c is applied to dfft.f and when dfft.c is compiled. -This is arranged in _f2c_fixes.py. - -f2c does not correctly handle implicit casts of function arguments. If the implicit casts -are between integer types then we can handle that automatically in _f2c_fixes.py. This -patch fixes implicit casts from char* to int, which we need to deal with manually. - ---- a/scipy/linalg/src/id_dist/src/dfft.c -+++ b/scipy/linalg/src/id_dist/src/dfft.c -@@ -2855,8 +2855,7 @@ f_rook.f"> */ - extern /* Subroutine */ int xerbla_(char *, integer *, ftnlen), ctprfb_( - char *, char *, char *, char *, integer *, integer *, integer *, - integer *, complex *, integer *, complex *, integer *, complex *, -- integer *, complex *, integer *, complex *, integer *, ftnlen, -- ftnlen, ftnlen, ftnlen); -+ integer *, complex *, integer *, complex *, integer *); - static logical notran; - - -@@ -2967,8 +2966,7 @@ f_rook.f"> */ - } - ctprfb_("L", "C", "F", "C", &mb, n, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ + a_dim1], lda, & -- b[b_offset], ldb, &work[1], &ib, (ftnlen)1, (ftnlen)1, ( -- ftnlen)1, (ftnlen)1); -+ b[b_offset], ldb, &work[1], &ib); - } - - } else if (right && notran) { -@@ -2989,8 +2987,7 @@ f_rook.f"> */ - } - ctprfb_("R", "N", "F", "C", m, &mb, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ * a_dim1 + 1], -- lda, &b[b_offset], ldb, &work[1], m, (ftnlen)1, (ftnlen)1, -- (ftnlen)1, (ftnlen)1); -+ lda, &b[b_offset], ldb, &work[1], m); - } - - } else if (left && notran) { -@@ -3011,8 +3008,7 @@ f_rook.f"> */ - } - ctprfb_("L", "N", "F", "C", &mb, n, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ + a_dim1], lda, & -- b[b_offset], ldb, &work[1], &ib, (ftnlen)1, (ftnlen)1, ( -- ftnlen)1, (ftnlen)1); -+ b[b_offset], ldb, &work[1], &ib); - } - - } else if (right && tran) { -@@ -3033,8 +3029,7 @@ f_rook.f"> */ - } - ctprfb_("R", "C", "F", "C", m, &mb, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ * a_dim1 + 1], -- lda, &b[b_offset], ldb, &work[1], m, (ftnlen)1, (ftnlen)1, -- (ftnlen)1, (ftnlen)1); -+ lda, &b[b_offset], ldb, &work[1], m); - } - - } -@@ -3247,8 +3242,7 @@ f"> */ - extern /* Subroutine */ int xerbla_(char *, integer *, ftnlen), ctprfb_( - char *, char *, char *, char *, integer *, integer *, integer *, - integer *, complex *, integer *, complex *, integer *, complex *, -- integer *, complex *, integer *, complex *, integer *, ftnlen, -- ftnlen, ftnlen, ftnlen), ctpqrt2_(integer *, integer *, integer *, -+ integer *, complex *, integer *, complex *, integer *), ctpqrt2_(integer *, integer *, integer *, - complex *, integer *, complex *, integer *, complex *, integer *, - integer *); - -@@ -3342,7 +3336,7 @@ f"> */ - ctprfb_("L", "C", "F", "C", &mb, &i__3, &ib, &lb, &b[i__ * b_dim1 - + 1], ldb, &t[i__ * t_dim1 + 1], ldt, &a[i__ + (i__ + ib) - * a_dim1], lda, &b[(i__ + ib) * b_dim1 + 1], ldb, &work[1] -- , &ib, (ftnlen)1, (ftnlen)1, (ftnlen)1, (ftnlen)1); -+ , &ib); - } - } - return 0; -@@ -6041,11 +6035,13 @@ f_rook.f"> */ - static integer ldvq; - extern logical lsame_(char *, char *, ftnlen, ftnlen); - static logical right; -- extern /* Subroutine */ int xerbla_(char *, integer *, ftnlen), dtprfb_( -+ extern /* Subroutine */ int xerbla_(char *, integer *, ftnlen), -+ -+ dtprfb_( - char *, char *, char *, char *, integer *, integer *, integer *, - integer *, doublereal *, integer *, doublereal *, integer *, - doublereal *, integer *, doublereal *, integer *, doublereal *, -- integer *, ftnlen, ftnlen, ftnlen, ftnlen); -+ integer *); - static logical notran; - - -@@ -6156,8 +6152,7 @@ f_rook.f"> */ - } - dtprfb_("L", "T", "F", "C", &mb, n, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ + a_dim1], lda, & -- b[b_offset], ldb, &work[1], &ib, (ftnlen)1, (ftnlen)1, ( -- ftnlen)1, (ftnlen)1); -+ b[b_offset], ldb, &work[1], &ib); - } - - } else if (right && notran) { -@@ -6178,8 +6173,7 @@ f_rook.f"> */ - } - dtprfb_("R", "N", "F", "C", m, &mb, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ * a_dim1 + 1], -- lda, &b[b_offset], ldb, &work[1], m, (ftnlen)1, (ftnlen)1, -- (ftnlen)1, (ftnlen)1); -+ lda, &b[b_offset], ldb, &work[1], m); - } - - } else if (left && notran) { -@@ -6200,8 +6194,7 @@ f_rook.f"> */ - } - dtprfb_("L", "N", "F", "C", &mb, n, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ + a_dim1], lda, & -- b[b_offset], ldb, &work[1], &ib, (ftnlen)1, (ftnlen)1, ( -- ftnlen)1, (ftnlen)1); -+ b[b_offset], ldb, &work[1], &ib); - } - - } else if (right && tran) { -@@ -6222,8 +6215,7 @@ f_rook.f"> */ - } - dtprfb_("R", "T", "F", "C", m, &mb, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ * a_dim1 + 1], -- lda, &b[b_offset], ldb, &work[1], m, (ftnlen)1, (ftnlen)1, -- (ftnlen)1, (ftnlen)1); -+ lda, &b[b_offset], ldb, &work[1], m); - } - - } -@@ -6433,13 +6425,12 @@ f"> */ - - /* Local variables */ - static integer i__, ib, lb, mb, iinfo; -- extern /* Subroutine */ int xerbla_(char *, integer *, ftnlen), dtprfb_( -- char *, char *, char *, char *, integer *, integer *, integer *, -- integer *, doublereal *, integer *, doublereal *, integer *, -- doublereal *, integer *, doublereal *, integer *, doublereal *, -- integer *, ftnlen, ftnlen, ftnlen, ftnlen), dtpqrt2_(integer *, -- integer *, integer *, doublereal *, integer *, doublereal *, -- integer *, doublereal *, integer *, integer *); -+ extern /* Subroutine */ int xerbla_(char *, integer *, ftnlen), -+ -+dtprfb_(char *, char *, char *, char *, integer *, integer *, integer *, integer *, doublereal *, integer *, doublereal *, integer *, -+ doublereal *, integer *, doublereal *, integer *, doublereal *, integer *), -+ -+ dtpqrt2_(integer *, integer *, integer *, doublereal *, integer *, doublereal *, integer *, doublereal *, integer *, integer *); - - - /* -- LAPACK computational routine -- */ -@@ -6531,9 +6522,8 @@ f"> */ - dtprfb_("L", "T", "F", "C", &mb, &i__3, &ib, &lb, &b[i__ * b_dim1 - + 1], ldb, &t[i__ * t_dim1 + 1], ldt, &a[i__ + (i__ + ib) - * a_dim1], lda, &b[(i__ + ib) * b_dim1 + 1], ldb, &work[1] -- , &ib, (ftnlen)1, (ftnlen)1, (ftnlen)1, (ftnlen)1); -- } -- } -+ , &ib); -+ }} - return 0; - - /* End of DTPQRT */ -@@ -9226,8 +9216,7 @@ f_rook.f"> */ - extern /* Subroutine */ int xerbla_(char *, integer *, ftnlen), stprfb_( - char *, char *, char *, char *, integer *, integer *, integer *, - integer *, real *, integer *, real *, integer *, real *, integer * -- , real *, integer *, real *, integer *, ftnlen, ftnlen, ftnlen, -- ftnlen); -+ , real *, integer *, real *, integer *); - static logical notran; - - -@@ -9338,8 +9327,7 @@ f_rook.f"> */ - } - stprfb_("L", "T", "F", "C", &mb, n, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ + a_dim1], lda, & -- b[b_offset], ldb, &work[1], &ib, (ftnlen)1, (ftnlen)1, ( -- ftnlen)1, (ftnlen)1); -+ b[b_offset], ldb, &work[1], &ib); - } - - } else if (right && notran) { -@@ -9360,8 +9348,7 @@ f_rook.f"> */ - } - stprfb_("R", "N", "F", "C", m, &mb, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ * a_dim1 + 1], -- lda, &b[b_offset], ldb, &work[1], m, (ftnlen)1, (ftnlen)1, -- (ftnlen)1, (ftnlen)1); -+ lda, &b[b_offset], ldb, &work[1], m); - } - - } else if (left && notran) { -@@ -9382,8 +9369,7 @@ f_rook.f"> */ - } - stprfb_("L", "N", "F", "C", &mb, n, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ + a_dim1], lda, & -- b[b_offset], ldb, &work[1], &ib, (ftnlen)1, (ftnlen)1, ( -- ftnlen)1, (ftnlen)1); -+ b[b_offset], ldb, &work[1], &ib); - } - - } else if (right && tran) { -@@ -9404,8 +9390,7 @@ f_rook.f"> */ - } - stprfb_("R", "T", "F", "C", m, &mb, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ * a_dim1 + 1], -- lda, &b[b_offset], ldb, &work[1], m, (ftnlen)1, (ftnlen)1, -- (ftnlen)1, (ftnlen)1); -+ lda, &b[b_offset], ldb, &work[1], m); - } - - } -@@ -9618,8 +9603,7 @@ f"> */ - extern /* Subroutine */ int xerbla_(char *, integer *, ftnlen), stprfb_( - char *, char *, char *, char *, integer *, integer *, integer *, - integer *, real *, integer *, real *, integer *, real *, integer * -- , real *, integer *, real *, integer *, ftnlen, ftnlen, ftnlen, -- ftnlen), stpqrt2_(integer *, integer *, integer *, real *, -+ , real *, integer *, real *, integer *), stpqrt2_(integer *, integer *, integer *, real *, - integer *, real *, integer *, real *, integer *, integer *); - - -@@ -9712,7 +9696,7 @@ f"> */ - stprfb_("L", "T", "F", "C", &mb, &i__3, &ib, &lb, &b[i__ * b_dim1 - + 1], ldb, &t[i__ * t_dim1 + 1], ldt, &a[i__ + (i__ + ib) - * a_dim1], lda, &b[(i__ + ib) * b_dim1 + 1], ldb, &work[1] -- , &ib, (ftnlen)1, (ftnlen)1, (ftnlen)1, (ftnlen)1); -+ , &ib); - } - } - return 0; -@@ -12502,8 +12486,7 @@ f_rook.f"> */ - extern /* Subroutine */ int ztprfb_(char *, char *, char *, char *, - integer *, integer *, integer *, integer *, doublecomplex *, - integer *, doublecomplex *, integer *, doublecomplex *, integer *, -- doublecomplex *, integer *, doublecomplex *, integer *, ftnlen, -- ftnlen, ftnlen, ftnlen); -+ doublecomplex *, integer *, doublecomplex *, integer *); - - - /* -- LAPACK computational routine -- */ -@@ -12613,8 +12596,7 @@ f_rook.f"> */ - } - ztprfb_("L", "C", "F", "C", &mb, n, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ + a_dim1], lda, & -- b[b_offset], ldb, &work[1], &ib, (ftnlen)1, (ftnlen)1, ( -- ftnlen)1, (ftnlen)1); -+ b[b_offset], ldb, &work[1], &ib); - } - - } else if (right && notran) { -@@ -12635,8 +12617,7 @@ f_rook.f"> */ - } - ztprfb_("R", "N", "F", "C", m, &mb, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ * a_dim1 + 1], -- lda, &b[b_offset], ldb, &work[1], m, (ftnlen)1, (ftnlen)1, -- (ftnlen)1, (ftnlen)1); -+ lda, &b[b_offset], ldb, &work[1], m); - } - - } else if (left && notran) { -@@ -12657,8 +12638,7 @@ f_rook.f"> */ - } - ztprfb_("L", "N", "F", "C", &mb, n, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ + a_dim1], lda, & -- b[b_offset], ldb, &work[1], &ib, (ftnlen)1, (ftnlen)1, ( -- ftnlen)1, (ftnlen)1); -+ b[b_offset], ldb, &work[1], &ib); - } - - } else if (right && tran) { -@@ -12679,8 +12659,7 @@ f_rook.f"> */ - } - ztprfb_("R", "C", "F", "C", m, &mb, &ib, &lb, &v[i__ * v_dim1 + 1] - , ldv, &t[i__ * t_dim1 + 1], ldt, &a[i__ * a_dim1 + 1], -- lda, &b[b_offset], ldb, &work[1], m, (ftnlen)1, (ftnlen)1, -- (ftnlen)1, (ftnlen)1); -+ lda, &b[b_offset], ldb, &work[1], m); - } - - } -@@ -12894,7 +12873,7 @@ f"> */ - char *, char *, char *, char *, integer *, integer *, integer *, - integer *, doublecomplex *, integer *, doublecomplex *, integer *, - doublecomplex *, integer *, doublecomplex *, integer *, -- doublecomplex *, integer *, ftnlen, ftnlen, ftnlen, ftnlen), -+ doublecomplex *, integer *), - ztpqrt2_(integer *, integer *, integer *, doublecomplex *, - integer *, doublecomplex *, integer *, doublecomplex *, integer *, - integer *); -@@ -12989,7 +12968,7 @@ f"> */ - ztprfb_("L", "C", "F", "C", &mb, &i__3, &ib, &lb, &b[i__ * b_dim1 - + 1], ldb, &t[i__ * t_dim1 + 1], ldt, &a[i__ + (i__ + ib) - * a_dim1], lda, &b[(i__ + ib) * b_dim1 + 1], ldb, &work[1] -- , &ib, (ftnlen)1, (ftnlen)1, (ftnlen)1, (ftnlen)1); -+ , &ib); - } - } - return 0; -@@ -12998,3 +12977,10 @@ f"> */ - - } /* ztpqrt_ */ - -+ -+typedef struct { float real, imag; } npy_complex64; -+typedef struct { double real, imag; } npy_complex128; -+int sorcsd_(char *jobu1, char *jobu2, char *jobv1t, char *jobv2t, char *trans, char *signs, int *m, int *p, int *q, float *x11, int *ldx11, float *x12, int *ldx12, float *x21, int *ldx21, float *x22, int *ldx22, float *theta, float *u1, int *ldu1, float *u2, int *ldu2, float *v1t, int *ldv1t, float *v2t, int *ldv2t, float *work, int *lwork, int *iwork, int *info){ return 0; } -+int dorcsd_(char *jobu1, char *jobu2, char *jobv1t, char *jobv2t, char *trans, char *signs, int *m, int *p, int *q, double *x11, int *ldx11, double *x12, int *ldx12, double *x21, int *ldx21, double *x22, int *ldx22, double *theta, double *u1, int *ldu1, double *u2, int *ldu2, double *v1t, int *ldv1t, double *v2t, int *ldv2t, double *work, int *lwork, int *iwork, int *info){ return 0; } -+int zuncsd_(char *jobu1, char *jobu2, char *jobv1t, char *jobv2t, char *trans, char *signs, int *m, int *p, int *q, npy_complex128 *x11, int *ldx11, npy_complex128 *x12, int *ldx12, npy_complex128 *x21, int *ldx21, npy_complex128 *x22, int *ldx22, double *theta, npy_complex128 *u1, int *ldu1, npy_complex128 *u2, int *ldu2, npy_complex128 *v1t, int *ldv1t, npy_complex128 *v2t, int *ldv2t, npy_complex128 *work, int *lwork, double *rwork, int *lrwork, int *iwork, int *info){ return 0; } -+int cuncsd_(char *jobu1, char *jobu2, char *jobv1t, char *jobv2t, char *trans, char *signs, int *m, int *p, int *q, npy_complex64 *x11, int *ldx11, npy_complex64 *x12, int *ldx12, npy_complex64 *x21, int *ldx21, npy_complex64 *x22, int *ldx22, float *theta, npy_complex64 *u1, int *ldu1, npy_complex64 *u2, int *ldu2, npy_complex64 *v1t, int *ldv1t, npy_complex64 *v2t, int *ldv2t, npy_complex64 *work, int *lwork, float *rwork, int *lrwork, int *iwork, int *info){ return 0; } diff --git a/packages/scipy/patches/remove-cuncsd-dorcsd-sorcsd-zuncsd.patch b/packages/scipy/patches/remove-cuncsd-dorcsd-sorcsd-zuncsd.patch deleted file mode 100644 index c1252658f2a..00000000000 --- a/packages/scipy/patches/remove-cuncsd-dorcsd-sorcsd-zuncsd.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 7b56e1341c486422973dfa890158872200527b51 Mon Sep 17 00:00:00 2001 -From: Hood Chatham -Date: Sat, 25 Dec 2021 20:31:40 -0800 -Subject: [PATCH] Remove cuncsd, dorcsd, sorcsd, zuncsd - -These functions don't work because their definitions involve code that isn't -fortran 77 compliant. In order to get _flapack.so to link correctly, we need -to provide stub definitions for these functions, but they don't work. Remove -them to make sure people get an `AttributeError` if they try to use them -rather than just having weird buggy code. - ---- - scipy/linalg/__init__.py | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/scipy/linalg/__init__.py b/scipy/linalg/__init__.py -index e88479b48..746bf3dce 100644 ---- a/scipy/linalg/__init__.py -+++ b/scipy/linalg/__init__.py -@@ -191,6 +191,12 @@ Low-level routines - `scipy.linalg.cython_lapack` -- Low-level LAPACK functions for Cython - - """ # noqa: E501 -+from . import lapack -+del lapack.cuncsd -+del lapack.dorcsd -+del lapack.sorcsd -+del lapack.zuncsd -+del lapack - - from .misc import * - from .basic import * --- -2.25.1 - diff --git a/packages/scipy/patches/remove-mvnun-not-fortran-77-compliant.patch b/packages/scipy/patches/remove-mvnun-not-fortran-77-compliant.patch deleted file mode 100644 index acb8471a1ff..00000000000 --- a/packages/scipy/patches/remove-mvnun-not-fortran-77-compliant.patch +++ /dev/null @@ -1,199 +0,0 @@ -From 8e62ac7ee97e6046745ba086545ac9c2511279f8 Mon Sep 17 00:00:00 2001 -From: Hood Chatham -Date: Sat, 25 Dec 2021 16:02:23 -0800 -Subject: [PATCH] remove mvnun -- not fortran 77 compliant - -These two functions use features from newer fortran standards which won't work -with f2c. This deletes them. - ---- - scipy/stats/mvn.pyf | 29 ---------- - scipy/stats/mvndst.f | 132 ------------------------------------------- - 2 files changed, 161 deletions(-) - -diff --git a/scipy/stats/mvn.pyf b/scipy/stats/mvn.pyf -index c9022b8de..cb0862a76 100644 ---- a/scipy/stats/mvn.pyf -+++ b/scipy/stats/mvn.pyf -@@ -3,35 +3,6 @@ - - python module mvn ! in - interface ! in :mvn -- subroutine mvnun(d,n,lower,upper,means,covar,maxpts,abseps,releps,value,inform) ! in :mvn:mvndst.f -- integer intent(hide) :: d=shape(means,0) -- integer intent(hide) :: n=shape(means,1) -- double precision dimension(d) :: lower -- double precision dimension(d) :: upper -- double precision dimension(d,n) :: means -- double precision dimension(d,d) :: covar -- integer intent(optional) :: maxpts=d*1000 -- double precision intent(optional) :: abseps=1e-6 -- double precision intent(optional) :: releps=1e-6 -- double precision intent(out) :: value -- integer intent(out) :: inform -- end subroutine mvnun -- -- subroutine mvnun_weighted(d,n,lower,upper,means,weights,covar,maxpts,abseps,releps,value,inform) ! in :mvn:mvndst.f -- integer intent(hide) :: d=shape(means,0) -- integer intent(hide) :: n=shape(means,1) -- double precision dimension(d) :: lower -- double precision dimension(d) :: upper -- double precision dimension(d,n) :: means -- double precision dimension(n) :: weights -- double precision dimension(d,d) :: covar -- integer intent(optional) :: maxpts=d*1000 -- double precision intent(optional) :: abseps=1e-6 -- double precision intent(optional) :: releps=1e-6 -- double precision intent(out) :: value -- integer intent(out) :: inform -- end subroutine mvnun_weighted -- - subroutine mvndst(n,lower,upper,infin,correl,maxpts,abseps,releps,error,value,inform) ! in :mvn:mvndst.f - integer intent(hide) :: n=len(lower) - double precision dimension(n) :: lower -diff --git a/scipy/stats/mvndst.f b/scipy/stats/mvndst.f -index 41afa7e74..76d9690e3 100644 ---- a/scipy/stats/mvndst.f -+++ b/scipy/stats/mvndst.f -@@ -21,138 +21,6 @@ - * Pullman, WA 99164-3113 - * Email : alangenz@wsu.edu - * -- SUBROUTINE mvnun(d, n, lower, upper, means, covar, maxpts, -- & abseps, releps, value, inform) --* Parameters --* --* d integer, dimensionality of the data --* n integer, the number of data points --* lower double(2), the lower integration limits --* upper double(2), the upper integration limits --* means double(n), the mean of each kernel --* covar double(2,2), the covariance matrix --* maxpts integer, the maximum number of points to evaluate at --* abseps double, absolute error tolerance --* releps double, relative error tolerance --* value double intent(out), integral value --* inform integer intent(out), --* if inform == 0: error < eps --* elif inform == 1: error > eps, all maxpts used -- integer n, d, infin(d), maxpts, inform, tmpinf -- double precision lower(d), upper(d), releps, abseps, -- & error, value, stdev(d), rho(d*(d-1)/2), -- & covar(d,d), -- & nlower(d), nupper(d), means(d,n), tmpval, -- & inf -- integer i, j -- -- inf = 0d0 -- inf = 1d0 / inf -- -- do i=1,d -- stdev(i) = dsqrt(covar(i,i)) -- if (upper(i).eq.inf.and.lower(i).eq.-inf) then -- infin(i) = -1 -- else if (lower(i).eq.-inf) then -- infin(i) = 0 -- else if (upper(i).eq.inf) then -- infin(i) = 1 -- else -- infin(i) = 2 -- end if -- end do -- do i=1,d -- do j=1,i-1 -- rho(j+(i-2)*(i-1)/2) = covar(i,j)/stdev(i)/stdev(j) -- end do -- end do -- value = 0d0 -- -- inform = 0 -- -- do i=1,n -- do j=1,d -- nlower(j) = (lower(j) - means(j,i))/stdev(j) -- nupper(j) = (upper(j) - means(j,i))/stdev(j) -- end do -- call mvndst(d,nlower,nupper,infin,rho,maxpts,abseps,releps, -- & error,tmpval,tmpinf) -- value = value + tmpval -- if (tmpinf .eq. 1) then -- inform = 1 -- end if -- end do -- -- value = value / n -- -- END -- -- -- SUBROUTINE mvnun_weighted(d, n, lower, upper, means, weights, -- & covar, maxpts, abseps, releps, -- & value, inform) --* Parameters --* --* d integer, dimensionality of the data --* n integer, the number of data points --* lower double(2), the lower integration limits --* upper double(2), the upper integration limits --* means double(n), the mean of each kernel --* weights double(n), the weight of each kernel --* covar double(2,2), the covariance matrix --* maxpts integer, the maximum number of points to evaluate at --* abseps double, absolute error tolerance --* releps double, relative error tolerance --* value double intent(out), integral value --* inform integer intent(out), --* if inform == 0: error < eps --* elif inform == 1: error > eps, all maxpts used -- integer n, d, infin(d), maxpts, inform, tmpinf -- double precision lower(d), upper(d), releps, abseps, -- & error, value, stdev(d), rho(d*(d-1)/2), -- & covar(d,d), -- & nlower(d), nupper(d), means(d,n), tmpval, -- & inf, weights(n) -- integer i, j -- -- inf = 0d0 -- inf = 1d0 / inf -- -- do i=1,d -- stdev(i) = dsqrt(covar(i,i)) -- if (upper(i).eq.inf.and.lower(i).eq.-inf) then -- infin(i) = -1 -- else if (lower(i).eq.-inf) then -- infin(i) = 0 -- else if (upper(i).eq.inf) then -- infin(i) = 1 -- else -- infin(i) = 2 -- end if -- end do -- do i=1,d -- do j=1,i-1 -- rho(j+(i-2)*(i-1)/2) = covar(i,j)/stdev(i)/stdev(j) -- end do -- end do -- value = 0d0 -- -- inform = 0 -- -- do i=1,n -- do j=1,d -- nlower(j) = (lower(j) - means(j,i))/stdev(j) -- nupper(j) = (upper(j) - means(j,i))/stdev(j) -- end do -- call mvndst(d,nlower,nupper,infin,rho,maxpts,abseps,releps, -- & error,tmpval,tmpinf) -- value = value + tmpval * weights(i) -- if (tmpinf .eq. 1) then -- inform = 1 -- end if -- end do -- -- END - - SUBROUTINE MVNDST( N, LOWER, UPPER, INFIN, CORREL, MAXPTS, - & ABSEPS, RELEPS, ERROR, VALUE, INFORM ) --- -2.25.1 - diff --git a/packages/scipy/test_scipy.py b/packages/scipy/test_scipy.py index 2a326c2be94..c1991c5b194 100644 --- a/packages/scipy/test_scipy.py +++ b/packages/scipy/test_scipy.py @@ -1,52 +1,66 @@ -import pytest -from conftest import selenium_context_manager +from pyodide_build import testing +run_in_pyodide = testing.run_in_pyodide( + module_scope=True, + packages=["scipy"], + # xfail_browsers={"chrome": "Times out in chrome"}, + driver_timeout=40, +) -@pytest.mark.driver_timeout(40) -def test_scipy_linalg(selenium_module_scope): - if selenium_module_scope.browser == "chrome": - pytest.xfail("Times out in chrome") - with selenium_context_manager(selenium_module_scope) as selenium: - selenium.load_package("scipy") - selenium.run( - r""" - import numpy as np - import scipy as sp - import scipy.linalg - from numpy.testing import assert_allclose - N = 10 - X = np.random.RandomState(42).rand(N, N) +@run_in_pyodide +def test_scipy_linalg(): + import numpy as np + import scipy.linalg + from numpy.testing import assert_allclose - X_inv = scipy.linalg.inv(X) + N = 10 + X = np.random.RandomState(42).rand(N, N) - res = X.dot(X_inv) + X_inv = scipy.linalg.inv(X) - assert_allclose(res, np.identity(N), - rtol=1e-07, atol=1e-9) - """ - ) + res = X.dot(X_inv) + assert_allclose(res, np.identity(N), rtol=1e-07, atol=1e-9) -@pytest.mark.driver_timeout(40) -def test_brentq(selenium_module_scope): - with selenium_context_manager(selenium_module_scope) as selenium: - selenium.load_package("scipy") - selenium.run( - """ - from scipy.optimize import brentq - brentq(lambda x: x, -1, 1) - """ - ) +@run_in_pyodide +def test_brentq(): + from scipy.optimize import brentq + + brentq(lambda x: x, -1, 1) + + +@run_in_pyodide +def test_dlamch(): + from scipy.linalg import lapack + + lapack.dlamch("Epsilon-Machine") -@pytest.mark.driver_timeout(40) -def test_dlamch(selenium_module_scope): - with selenium_context_manager(selenium_module_scope) as selenium: - selenium.load_package("scipy") - selenium.run( - """ - from scipy.linalg import lapack - lapack.dlamch('Epsilon-Machine') - """ + +@run_in_pyodide +def test_binom_ppf(): + from scipy.stats import binom + + assert binom.ppf(0.9, 1000, 0.1) == 112 + + +@testing.run_in_pyodide(module_scope=True, packages=["pytest", "scipy-tests"]) +def test_scipy_pytest(): + import pytest + + def runtest(module, filter): + pytest.main( + [ + "--pyargs", + f"scipy.{module}", + "--continue-on-collection-errors", + "-vv", + "-k", + filter, + ] ) + + runtest("odr", "explicit") + runtest("signal.tests.test_ltisys", "TestImpulse2") + runtest("stats.tests.test_multivariate", "haar") diff --git a/packages/setuptools/meta.yaml b/packages/setuptools/meta.yaml index 5d30ab82951..73b05e2e7cf 100644 --- a/packages/setuptools/meta.yaml +++ b/packages/setuptools/meta.yaml @@ -1,9 +1,12 @@ package: name: setuptools - version: 60.3.1 + version: 62.0.0 source: - url: https://files.pythonhosted.org/packages/d9/15/0c48951d6fe38d7b3396401f2cfc43c86bc32408ed6c637ce1a9c6df1bdd/setuptools-60.3.1.tar.gz - sha256: 2b62b3dbec1fe95585dadf3bd9a014c8c578624918190428c3a7994bb52ae2b8 + url: https://files.pythonhosted.org/packages/0c/3c/548d361162702df85a0301f0cd0c47d05176b20bb086077a0fda740daf41/setuptools-62.0.0-py3-none-any.whl + sha256: a65e3802053e99fc64c6b3b29c11132943d5b8c8facbcc461157511546510967 +build: + post: | + find build/setuptools-*/dist -name '*.exe' -delete requirements: run: - distutils diff --git a/packages/sharedlib-test-py/meta.yaml b/packages/sharedlib-test-py/meta.yaml index 4123ca3d1cf..6e537fb2008 100644 --- a/packages/sharedlib-test-py/meta.yaml +++ b/packages/sharedlib-test-py/meta.yaml @@ -1,6 +1,7 @@ package: name: sharedlib-test-py version: "1.0" + _tag: pyodide.test requirements: run: - sharedlib-test diff --git a/packages/sharedlib-test-py/src/setup.py b/packages/sharedlib-test-py/src/setup.py index 42cbd63d6c8..8c87433fa7c 100644 --- a/packages/sharedlib-test-py/src/setup.py +++ b/packages/sharedlib-test-py/src/setup.py @@ -2,7 +2,6 @@ from setuptools import Extension, setup - setup( name="sharedlib-test-py", version="1.0", diff --git a/packages/sharedlib-test-py/src/sharedlib-test.c b/packages/sharedlib-test-py/src/sharedlib-test.c index 72dcf6713ec..06faf1c9589 100644 --- a/packages/sharedlib-test-py/src/sharedlib-test.c +++ b/packages/sharedlib-test-py/src/sharedlib-test.c @@ -1,39 +1,42 @@ #define PY_SSIZE_T_CLEAN -#include #include "sharedlibtest.h" +#include -static PyObject *one(PyObject *self){ - Py_RETURN_NONE; +static PyObject* +one(PyObject* self) +{ + Py_RETURN_NONE; } -static PyObject *do_the_thing_pywrapper(PyObject *self, PyObject *args){ - int a,b; - if(!PyArg_ParseTuple(args, "ii:do_the_thing", &a, &b)){ - return NULL; - } - int res = do_the_thing(a, b); - return PyLong_FromLong(res); +static PyObject* +do_the_thing_pywrapper(PyObject* self, PyObject* args) +{ + int a, b; + if (!PyArg_ParseTuple(args, "ii:do_the_thing", &a, &b)) { + return NULL; + } + int res = do_the_thing(a, b); + return PyLong_FromLong(res); } - // These two structs are the same but it's important that they have to be // duplicated here or else we miss test coverage. static PyMethodDef Test_Functions[] = { - {"do_the_thing", do_the_thing_pywrapper, METH_VARARGS}, - {0}, + { "do_the_thing", do_the_thing_pywrapper, METH_VARARGS }, + { 0 }, }; static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "sharedlib_test", /* name of module */ - "Tests for shared library loading", /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module, - or -1 if the module keeps state in global variables. */ - Test_Functions + PyModuleDef_HEAD_INIT, + "sharedlib_test", /* name of module */ + "Tests for shared library loading", /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + Test_Functions }; - -PyMODINIT_FUNC PyInit_sharedlib_test(void) +PyMODINIT_FUNC +PyInit_sharedlib_test(void) { - return PyModule_Create(&module); + return PyModule_Create(&module); } diff --git a/packages/sharedlib-test/meta.yaml b/packages/sharedlib-test/meta.yaml index 11d97646f86..8af287cc9e6 100644 --- a/packages/sharedlib-test/meta.yaml +++ b/packages/sharedlib-test/meta.yaml @@ -1,6 +1,7 @@ package: name: sharedlib-test version: "1.0" + _tag: pyodide.test source: path: src @@ -9,5 +10,5 @@ build: sharedlibrary: true script: | emcc -c main.c -o main.o -fPIC - mkdir install - emcc main.o -sSIDE_MODULE -o install/sharedlib-test.so + mkdir dist + emcc main.o -sSIDE_MODULE -o dist/sharedlib-test.so diff --git a/packages/sharedlib-test/src/include/sharedlibtest.h b/packages/sharedlib-test/src/include/sharedlibtest.h index fc84cbd75f6..34a24bade7c 100644 --- a/packages/sharedlib-test/src/include/sharedlibtest.h +++ b/packages/sharedlib-test/src/include/sharedlibtest.h @@ -1 +1,2 @@ -int do_the_thing(int a, int b); +int +do_the_thing(int a, int b); diff --git a/packages/sharedlib-test/src/main.c b/packages/sharedlib-test/src/main.c index 064028b0348..dca6ec3ba5c 100644 --- a/packages/sharedlib-test/src/main.c +++ b/packages/sharedlib-test/src/main.c @@ -1,3 +1,5 @@ -int do_the_thing(int a, int b){ - return a + b + a*b; +int +do_the_thing(int a, int b) +{ + return a + b + a * b; } diff --git a/packages/six/meta.yaml b/packages/six/meta.yaml index c65a61137ec..2d1292e64ba 100644 --- a/packages/six/meta.yaml +++ b/packages/six/meta.yaml @@ -2,5 +2,5 @@ package: name: six version: 1.16.0 source: - sha256: 1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 - url: https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz + sha256: 8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + url: https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl diff --git a/packages/soupsieve/meta.yaml b/packages/soupsieve/meta.yaml index e5a120a4c32..064d0de1747 100644 --- a/packages/soupsieve/meta.yaml +++ b/packages/soupsieve/meta.yaml @@ -1,9 +1,9 @@ package: name: soupsieve - version: 2.3.1 + version: 2.3.2 source: - sha256: b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9 - url: https://files.pythonhosted.org/packages/e1/25/a3005eedafb34e1258458e8a4b94900a60a41a2b4e459e0e19631648a2a0/soupsieve-2.3.1.tar.gz + sha256: a714129d3021ec17ce5be346b1007300558b378332c289a1a20e7d4de6ff18a5 + url: https://files.pythonhosted.org/packages/7d/1e/294d3cb3fc81212914043beba5eeb38882c0b449402353c549745e823fcf/soupsieve-2.3.2-py3-none-any.whl test: imports: - soupsieve diff --git a/packages/sqlalchemy/meta.yaml b/packages/sqlalchemy/meta.yaml new file mode 100644 index 00000000000..a79a4771091 --- /dev/null +++ b/packages/sqlalchemy/meta.yaml @@ -0,0 +1,14 @@ +package: + name: sqlalchemy + version: 1.4.35 +source: + url: https://files.pythonhosted.org/packages/f6/d6/fcf14b752daba13a02a6669eccb025bf3ba3f814741cd23253c180a12fff/SQLAlchemy-1.4.35.tar.gz + sha256: 2ffc813b01dc6473990f5e575f210ca5ac2f5465ace3908b78ffd6d20058aab5 +test: + imports: + - sqlalchemy +about: + home: https://www.sqlalchemy.org + PyPI: https://pypi.org/project/sqlalchemy + summary: Database Abstraction Library + license: MIT diff --git a/packages/sqlalchemy/test_sqlalchemy.py b/packages/sqlalchemy/test_sqlalchemy.py new file mode 100644 index 00000000000..97c2cfd64e0 --- /dev/null +++ b/packages/sqlalchemy/test_sqlalchemy.py @@ -0,0 +1,25 @@ +from pyodide_build.testing import run_in_pyodide + + +@run_in_pyodide(packages=["sqlalchemy"]) +def test_sqlalchemy(): + from sqlalchemy import create_engine, text + + engine = create_engine("sqlite+pysqlite:///:memory:", future=True) + with engine.connect() as conn: + result = conn.execute(text("select 'hello world'")) + assert result.all()[0] == ("hello world",) + + conn.execute(text("CREATE TABLE some_table (x int, y int)")) + conn.execute( + text("INSERT INTO some_table (x, y) VALUES (:x, :y)"), + [{"x": 1, "y": 1}, {"x": 2, "y": 4}], + ) + conn.commit() + + result = conn.execute(text("SELECT x, y FROM some_table")).all() + assert len(result) == 2 + + result = conn.execute(text("SELECT x, y FROM some_table WHERE x=2")).all() + assert len(result) == 1 + assert result[0].y == 4 diff --git a/packages/ssl/empty/.keep b/packages/ssl/empty/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/ssl/meta.yaml b/packages/ssl/meta.yaml new file mode 100644 index 00000000000..5b349a73908 --- /dev/null +++ b/packages/ssl/meta.yaml @@ -0,0 +1,22 @@ +package: + name: ssl + version: 1.0.0 # Nonesense +source: + path: empty +build: + sharedlibrary: true + script: | + mkdir dist + export DISTDIR=$(pwd)/dist + cd $CPYTHONBUILD + emcc $STDLIB_MODULE_CFLAGS -c Modules/socketmodule.c -o Modules/socketmodule.o + emcc $STDLIB_MODULE_CFLAGS -c Modules/_ssl.c -o Modules/_ssl.o -I$OPEN_SSL_ROOT/include/ \ + -DOPENSSL_THREADS # This declares that OPENSSL is threadsafe. We are single threaded so everything is threadsafe. + emcc Modules/_ssl.o -o $DISTDIR/_ssl.so $SIDE_MODULE_LDFLAGS + emcc Modules/socketmodule.o -o $DISTDIR/socketmodule.so $SIDE_MODULE_LDFLAGS +requirements: + run: + - openssl +test: + imports: + - ssl diff --git a/packages/ssl/test_ssl.py b/packages/ssl/test_ssl.py new file mode 100644 index 00000000000..e05ebc9cd39 --- /dev/null +++ b/packages/ssl/test_ssl.py @@ -0,0 +1,32 @@ +from pyodide_build.testing import run_in_pyodide + + +@run_in_pyodide(packages=["test", "ssl"]) +def test_ssl(): + import platform + import unittest + import unittest.mock + from test import libregrtest + + platform.platform(aliased=True) + name = "test_ssl" + ignore_tests = [ + "*test_context_custom_class*", + "*ThreadedTests*", + "*ocket*", + "test_verify_flags", + "test_subclass", + "test_lib_reason", + ] + + try: + with unittest.mock.patch( + "test.support.socket_helper.bind_port", + side_effect=unittest.SkipTest("nope!"), + ): + libregrtest.main( + [name], ignore_tests=ignore_tests, verbose=True, verbose3=True + ) + except SystemExit as e: + if e.code != 0: + raise RuntimeError(f"Failed with code: {e.code}") diff --git a/packages/statsmodels/meta.yaml b/packages/statsmodels/meta.yaml index 84ad7311639..24e2de0cabe 100644 --- a/packages/statsmodels/meta.yaml +++ b/packages/statsmodels/meta.yaml @@ -1,10 +1,10 @@ package: name: statsmodels - version: 0.13.1 + version: 0.13.2 source: - url: https://github.com/statsmodels/statsmodels/archive/refs/tags/v0.13.1.tar.gz - sha256: 4bf899994549079aa91a6ee5e06f4c900b7eba22af6d2f27a26ab58dd4a7ccac + url: https://files.pythonhosted.org/packages/e1/4a/0eb4efa49cc352e2721e2aebfe8573264db2add195545ec3979c98040c3b/statsmodels-0.13.2.tar.gz + sha256: 77dc292c9939c036a476f1770f9d08976b05437daa229928da73231147cde7d4 patches: - patches/fix-scipy-blas-cythonize.patch @@ -18,6 +18,7 @@ requirements: - scipy - pandas - patsy + - packaging test: imports: diff --git a/packages/sympy/meta.yaml b/packages/sympy/meta.yaml index dae2b3abe31..d7ed8ae15f4 100644 --- a/packages/sympy/meta.yaml +++ b/packages/sympy/meta.yaml @@ -1,13 +1,13 @@ package: name: sympy - version: "1.9" + version: 1.10.1 requirements: run: - distutils - mpmath source: - sha256: c7a880e229df96759f955d4f3970d4cabce79f60f5b18830c08b90ce77cd5fdc - url: https://files.pythonhosted.org/packages/26/86/902ee78db1bab1f0410f799869a49bb03b83be8d44c23b224d9db34f21c3/sympy-1.9.tar.gz + sha256: df75d738930f6fe9ebe7034e59d56698f29e85f443f743e51e47df0caccc2130 + url: https://files.pythonhosted.org/packages/d0/04/66be21ceb305c66a4b326b0ae44cc4f027a43bc08cac204b48fb45bb3653/sympy-1.10.1-py3-none-any.whl test: imports: - sympy diff --git a/packages/sympy/test_sympy.py b/packages/sympy/test_sympy.py index 3c4c4782925..7ea57873c2b 100644 --- a/packages/sympy/test_sympy.py +++ b/packages/sympy/test_sympy.py @@ -6,6 +6,6 @@ def test_sympy(): import sympy a, b = sympy.symbols("a,b") - c = sympy.sqrt(a ** 2 + b ** 2) + c = sympy.sqrt(a**2 + b**2) - c.subs({a: 3, b: 4}) == 5 + assert c.subs({a: 3, b: 4}) == 5 diff --git a/packages/test_packages_common.py b/packages/test_packages_common.py index bbc7192bdd4..fa1498ec1ca 100644 --- a/packages/test_packages_common.py +++ b/packages/test_packages_common.py @@ -1,17 +1,16 @@ -import pytest -import os -from pathlib import Path -from typing import List, Dict import functools +import os + +import pytest +from conftest import ROOT_PATH, _package_is_built from pyodide_build.io import parse_package_config -PKG_DIR = Path(__file__).parent -BUILD_DIR = PKG_DIR.parent / "build" +PKG_DIR = ROOT_PATH / "packages" @functools.cache -def registered_packages() -> List[str]: +def registered_packages() -> list[str]: """Returns a list of registered package names""" packages = [] for name in os.listdir(PKG_DIR): @@ -20,39 +19,10 @@ def registered_packages() -> List[str]: return packages -@functools.cache -def built_packages() -> List[str]: - """Returns a list of built package names. - - This functions lists the names of the .data files in the build/ directory. - """ - if not BUILD_DIR.exists(): - return [] - registered_packages_ = registered_packages() - packages = [] - for fpath in os.listdir(BUILD_DIR): - if not fpath.endswith(".data"): - continue - name = fpath.split(".")[0] - if name in registered_packages_: - packages.append(name) - return packages - - -def registered_packages_meta(): - """Returns a dictionary with the contents of `meta.yaml` - for each registered package - """ - packages = registered_packages - return { - name: parse_package_config(PKG_DIR / name / "meta.yaml") for name in packages - } - - -UNSUPPORTED_PACKAGES: Dict[str, List[str]] = { +UNSUPPORTED_PACKAGES: dict[str, list[str]] = { "chrome": [], "firefox": [], - "node": [], + "node": ["cmyt", "yt"], } if "CI" in os.environ: UNSUPPORTED_PACKAGES["chrome"].extend(["statsmodels"]) @@ -63,18 +33,18 @@ def test_parse_package(name): # check that we can parse the meta.yaml meta = parse_package_config(PKG_DIR / name / "meta.yaml") - skip_host = meta.get("build", {}).get("skip_host", True) - if name == "numpy": - assert skip_host is False - elif name == "pandas": - assert skip_host is True + sharedlibrary = meta.get("build", {}).get("sharedlibrary", False) + if name == "sharedlib-test": + assert sharedlibrary is True + elif name == "sharedlib-test-py": + assert sharedlibrary is False @pytest.mark.skip_refcount_check -@pytest.mark.driver_timeout(40) +@pytest.mark.driver_timeout(60) @pytest.mark.parametrize("name", registered_packages()) def test_import(name, selenium_standalone): - if name not in built_packages(): + if not _package_is_built(name): raise AssertionError( "Implementation error. Test for an unbuilt package " "should have been skipped in selenium_standalone fixture" @@ -89,12 +59,12 @@ def test_import(name, selenium_standalone): ) ) - selenium_standalone.run("import glob, os") + selenium_standalone.run("import glob, os, site") baseline_pyc = selenium_standalone.run( """ len(list(glob.glob( - '/lib/python3.9/site-packages/**/*.pyc', + site.getsitepackages()[0] + '/**/*.pyc', recursive=True) )) """ @@ -106,11 +76,23 @@ def test_import(name, selenium_standalone): assert ( selenium_standalone.run( """ - len(list(glob.glob( - '/lib/python3.9/site-packages/**/*.pyc', - recursive=True) - )) - """ + len(list(glob.glob( + site.getsitepackages()[0] + '/**/*.pyc', + recursive=True) + )) + """ ) == baseline_pyc ) + # Make sure no exe files were loaded! + assert ( + selenium_standalone.run( + """ + len(list(glob.glob( + site.getsitepackages()[0] + '/**/*.exe', + recursive=True) + )) + """ + ) + == 0 + ) diff --git a/packages/threadpoolctl/meta.yaml b/packages/threadpoolctl/meta.yaml index a3b5fc7e2d2..fb2ca2144b6 100644 --- a/packages/threadpoolctl/meta.yaml +++ b/packages/threadpoolctl/meta.yaml @@ -1,7 +1,7 @@ package: name: threadpoolctl - version: 3.0.0 + version: 3.1.0 source: - url: https://files.pythonhosted.org/packages/99/29/1a048829f9cdb14b98e676bf9970afb7c2b9d27e6403eb820322ba476d36/threadpoolctl-3.0.0.tar.gz - sha256: d03115321233d0be715f0d3a5ad1d6c065fe425ddc2d671ca8e45e9fd5d7a52a + url: https://files.pythonhosted.org/packages/61/cf/6e354304bcb9c6413c4e02a747b600061c21d38ba51e7e544ac7bc66aecc/threadpoolctl-3.1.0-py3-none-any.whl + sha256: 8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b diff --git a/packages/tomli-w/meta.yaml b/packages/tomli-w/meta.yaml new file mode 100644 index 00000000000..ef936658f63 --- /dev/null +++ b/packages/tomli-w/meta.yaml @@ -0,0 +1,14 @@ +package: + name: tomli-w + version: 1.0.0 +source: + url: https://files.pythonhosted.org/packages/bb/01/1da9c66ecb20f31ed5aa5316a957e0b1a5e786a0d9689616ece4ceaf1321/tomli_w-1.0.0-py3-none-any.whl + sha256: 9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463 +test: + imports: + - tomli_w +about: + home: https://github.com/hukkin/tomli-w + PyPI: https://pypi.org/project/tomli-w + summary: "A lil' TOML writer" + license: "MIT License" diff --git a/packages/tomli/meta.yaml b/packages/tomli/meta.yaml new file mode 100644 index 00000000000..909c868bae4 --- /dev/null +++ b/packages/tomli/meta.yaml @@ -0,0 +1,14 @@ +package: + name: tomli + version: 2.0.1 +source: + url: https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl + sha256: 939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc +test: + imports: + - tomli +about: + home: "" + PyPI: https://pypi.org/project/tomli + summary: A lil' TOML parser + license: "" diff --git a/packages/toolz/meta.yaml b/packages/toolz/meta.yaml index 285702e4523..ab4f3bb8591 100644 --- a/packages/toolz/meta.yaml +++ b/packages/toolz/meta.yaml @@ -2,8 +2,8 @@ package: name: toolz version: 0.11.2 source: - sha256: 6b312d5e15138552f1bda8a4e66c30e236c831b612b2bf0005f8a1df10a4bc33 - url: https://files.pythonhosted.org/packages/5a/f3/f8c3d075da8d949b12fb12ef934ee12fcce369ff83b60253fc2833f8901c/toolz-0.11.2.tar.gz + sha256: a5700ce83414c64514d82d60bcda8aabfde092d1c1a8663f9200c07fdcc6da8f + url: https://files.pythonhosted.org/packages/b5/f1/3df506b493736e3ee11fc1a3c2de8014a55f025d830a71bb499acc049a2c/toolz-0.11.2-py3-none-any.whl test: imports: - toolz diff --git a/packages/tqdm/meta.yaml b/packages/tqdm/meta.yaml new file mode 100644 index 00000000000..97531c7ad71 --- /dev/null +++ b/packages/tqdm/meta.yaml @@ -0,0 +1,14 @@ +package: + name: tqdm + version: 4.64.0 +source: + url: https://files.pythonhosted.org/packages/8a/c4/d15f1e627fff25443ded77ea70a7b5532d6371498f9285d44d62587e209c/tqdm-4.64.0-py2.py3-none-any.whl + sha256: 74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6 +test: + imports: + - tqdm +about: + home: https://tqdm.github.io + PyPI: https://pypi.org/project/tqdm + summary: Fast, Extensible Progress Meter + license: MPLv2.0, MIT Licences diff --git a/packages/typing-extensions/empty/empty_file.txt b/packages/typing-extensions/empty/empty_file.txt deleted file mode 100644 index 8b137891791..00000000000 --- a/packages/typing-extensions/empty/empty_file.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/typing-extensions/meta.yaml b/packages/typing-extensions/meta.yaml index e1da2da65f0..a0bd5a19f56 100644 --- a/packages/typing-extensions/meta.yaml +++ b/packages/typing-extensions/meta.yaml @@ -1,17 +1,9 @@ package: name: typing-extensions - version: 4.0.1 - # I needed some dumb hacks to get this to work. TODO: fix build system after - # 0.19 release. + version: 4.1.1 source: - path: "empty" -build: - sharedlibrary: True - script: | - wget https://files.pythonhosted.org/packages/05/e4/baf0031e39cf545f0c9edd5b1a2ea12609b7fcba2d58e118b11753d68cf0/typing_extensions-4.0.1-py3-none-any.whl - python -m wheel unpack typing_extensions-4.0.1-py3-none-any.whl - mkdir -p install/lib/python3.9/site-packages - cp typing_extensions-4.0.1/typing_extensions.py install/lib/python3.9/site-packages + url: https://files.pythonhosted.org/packages/45/6b/44f7f8f1e110027cf88956b59f2fad776cca7e1704396d043f89effd3a0e/typing_extensions-4.1.1-py3-none-any.whl + sha256: 21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2 test: imports: diff --git a/packages/unyt/meta.yaml b/packages/unyt/meta.yaml new file mode 100644 index 00000000000..a5e7e3ebd5c --- /dev/null +++ b/packages/unyt/meta.yaml @@ -0,0 +1,18 @@ +package: + name: unyt + version: 2.8.0 +source: + url: https://files.pythonhosted.org/packages/8a/ca/d899183a35fbc6bff4a4425dbb951ca48e58a9e533f547feb4384cb30592/unyt-2.8.0-py2.py3-none-any.whl + sha256: 8b3278990fe0f72723f43d9a1cf46703ef63f3270424a0dbd7e74cd28c3a2d54 +requirements: + run: + - numpy + - sympy +test: + imports: + - unyt +about: + home: https://github.com/yt-project/unyt + PyPI: https://pypi.org/project/unyt + summary: A package for handling numpy arrays with units + license: BSD license diff --git a/packages/urllib3/meta.yaml b/packages/urllib3/meta.yaml new file mode 100644 index 00000000000..afe42c007f1 --- /dev/null +++ b/packages/urllib3/meta.yaml @@ -0,0 +1,7 @@ +package: + name: urllib3 + version: 1.26.7 + +source: + sha256: 4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece + url: https://files.pythonhosted.org/packages/80/be/3ee43b6c5757cabea19e75b8f46eaf05a2f5144107d7db48c7cf3a864f73/urllib3-1.26.7.tar.gz diff --git a/packages/webencodings/meta.yaml b/packages/webencodings/meta.yaml index 30a7b8290c4..6f18453e1bd 100644 --- a/packages/webencodings/meta.yaml +++ b/packages/webencodings/meta.yaml @@ -2,8 +2,8 @@ package: name: webencodings version: 0.5.1 source: - sha256: b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 - url: https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz + sha256: a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 + url: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl test: imports: - webencodings diff --git a/packages/wrapt/meta.yaml b/packages/wrapt/meta.yaml new file mode 100644 index 00000000000..7b6dcd860d2 --- /dev/null +++ b/packages/wrapt/meta.yaml @@ -0,0 +1,14 @@ +package: + name: wrapt + version: 1.14.0 +source: + url: https://files.pythonhosted.org/packages/c7/b4/3a937c7f8ee4751b38274c8542e02f42ebf3e080f1344c4a2aff6416630e/wrapt-1.14.0.tar.gz + sha256: 8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311 +test: + imports: + - wrapt +about: + home: https://github.com/GrahamDumpleton/wrapt + PyPI: https://pypi.org/project/wrapt + summary: Module for decorators, wrappers and monkey patching. + license: BSD diff --git a/packages/wrapt/test_wrapt.py b/packages/wrapt/test_wrapt.py new file mode 100644 index 00000000000..ed967d08600 --- /dev/null +++ b/packages/wrapt/test_wrapt.py @@ -0,0 +1,83 @@ +from pyodide_build.testing import run_in_pyodide + + +@run_in_pyodide(packages=["wrapt"]) +def test_wrapt(): + import inspect + import unittest + + import wrapt + + @wrapt.decorator + def passthru_decorator(wrapped, instance, args, kwargs): + return wrapped(*args, **kwargs) + + def function1(arg): + """documentation""" + return arg + + function1o = function1 + function1d = passthru_decorator(function1) + assert function1d is not function1o + + class TestNamingFunction(unittest.TestCase): + def test_object_name(self): + # Test preservation of function __name__ attribute. + + self.assertEqual(function1d.__name__, function1o.__name__) + + def test_object_qualname(self): + # Test preservation of function __qualname__ attribute. + + try: + __qualname__ = function1o.__qualname__ + except AttributeError: + pass + else: + self.assertEqual(function1d.__qualname__, __qualname__) + + def test_module_name(self): + # Test preservation of function __module__ attribute. + + self.assertEqual(function1d.__module__, __name__) + + def test_doc_string(self): + # Test preservation of function __doc__ attribute. + + self.assertEqual(function1d.__doc__, function1o.__doc__) + + def test_argspec(self): + # Test preservation of function argument specification. + + function1o_argspec = inspect.getargspec(function1o) + function1d_argspec = inspect.getargspec(function1d) + self.assertEqual(function1o_argspec, function1d_argspec) + + def test_isinstance(self): + # Test preservation of isinstance() checks. + + self.assertTrue(isinstance(function1d, type(function1o))) + + class TestCallingFunction(unittest.TestCase): + def test_call_function(self): + _args = (1, 2) + _kwargs = {"one": 1, "two": 2} + + @wrapt.decorator + def _decorator(wrapped, instance, args, kwargs): + self.assertEqual(instance, None) + self.assertEqual(args, _args) + self.assertEqual(kwargs, _kwargs) + return wrapped(*args, **kwargs) + + @_decorator + def _function(*args, **kwargs): + return args, kwargs + + result = _function(*_args, **_kwargs) + + self.assertEqual(result, (_args, _kwargs)) + + # Run tests + with unittest.TestCase().assertRaisesRegex(SystemExit, "False"): + unittest.main() diff --git a/packages/xlrd/meta.yaml b/packages/xlrd/meta.yaml index 5c503c04ca9..2ff44f11616 100644 --- a/packages/xlrd/meta.yaml +++ b/packages/xlrd/meta.yaml @@ -2,8 +2,8 @@ package: name: xlrd version: 2.0.1 source: - sha256: f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88 - url: https://files.pythonhosted.org/packages/a6/b3/19a2540d21dea5f908304375bd43f5ed7a4c28a370dc9122c565423e6b44/xlrd-2.0.1.tar.gz + sha256: 6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd + url: https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl test: imports: - xlrd diff --git a/packages/yt/meta.yaml b/packages/yt/meta.yaml index 4555b0acf4e..c1a3df7908f 100644 --- a/packages/yt/meta.yaml +++ b/packages/yt/meta.yaml @@ -1,13 +1,14 @@ package: name: yt - version: 3.6.1 + version: 4.0.2 source: - url: https://files.pythonhosted.org/packages/1e/ce/e75e41f077e330f25d912eda4a1ba124e6b5f896f457ec5ca4e8c89b97c2/yt-3.6.1.tar.gz - sha256: be454f9d05dcbe0623328b4df43a1bfd1f0925e516be97399710452931a19bb0 + url: https://files.pythonhosted.org/packages/b6/ba/c28c9af83797c0e7a9273004a9ac0a924e6c5920fd0aa944d305e8288b30/yt-4.0.2.tar.gz + sha256: 76b238eef0fbbbea810dda4821331a9b03f74e5e2dda9997e401c685deec1631 patches: - patches/skip-openmp.patch + - patches/tomli-3833.patch requirements: run: @@ -15,6 +16,13 @@ requirements: - matplotlib - sympy - setuptools + - packaging + - unyt + - cmyt + - colorspacious + - tqdm + - tomli + - tomli-w build: # The test module is imported from the top level `__init__.py` diff --git a/packages/yt/patches/skip-openmp.patch b/packages/yt/patches/skip-openmp.patch index 38301ad5711..50cbad99676 100644 --- a/packages/yt/patches/skip-openmp.patch +++ b/packages/yt/patches/skip-openmp.patch @@ -1,13 +1,13 @@ diff --git a/setupext.py b/setupext.py -index 92c456bab..ce2c5fbad 100644 +index 985d699fc..0b40c81be 100644 --- a/setupext.py +++ b/setupext.py -@@ -58,6 +58,8 @@ def check_for_openmp(): +@@ -55,6 +55,8 @@ def check_for_openmp(): Robitaille and Curtis McCully. """ - -+ return False + ++ return [], [] + - # See https://bugs.python.org/issue25150 - if sys.version_info[:3] == (3, 5, 0): - return False + # Create a temporary directory + ccompiler = new_compiler() + customize_compiler(ccompiler) diff --git a/packages/yt/patches/tomli-3833.patch b/packages/yt/patches/tomli-3833.patch new file mode 100644 index 00000000000..4580995cdea --- /dev/null +++ b/packages/yt/patches/tomli-3833.patch @@ -0,0 +1,52 @@ +From 4867d140e8613d5fdf999b4354f1ef6958dfeffb Mon Sep 17 00:00:00 2001 +From: Corentin Cadiou +Date: Thu, 3 Mar 2022 12:47:23 +0100 +Subject: [PATCH] Merge pull request #3831 from neutrinoceros/migrate2tomli + +MNT: migrate from toml to tomli + tomli_w + +diff --git a/yt/config.py b/yt/config.py +index 5dfc809a56..c5da7ad93a 100644 +--- a/yt/config.py ++++ b/yt/config.py +@@ -1,7 +1,9 @@ + import os + import warnings + +-import toml ++# TODO: import tomllib from the standard library instead in Python >= 3.11 ++import tomli as tomllib ++import tomli_w + from more_itertools import always_iterable + + from yt._maintenance.deprecation import issue_deprecation_warning +@@ -154,14 +156,16 @@ def read(self, file_names): + if not os.path.exists(fname): + continue + metadata = {"source": f"file: {fname}"} +- self.update(toml.load(fname), metadata=metadata) ++ with open(fname, "rb") as fh: ++ data = tomllib.load(fh) ++ self.update(data, metadata=metadata) + file_names_read.append(fname) + + return file_names_read + + def write(self, file_handler): + value = self.config_root.as_dict() +- config_as_str = toml.dumps(value) ++ config_as_str = tomli_w.dumps(value) + + try: + # Assuming file_handler has a write attribute +@@ -222,8 +226,8 @@ def _repr_json_(self): + if not os.path.exists(_global_config_file): + cfg = {"yt": {}} + try: +- with open(_global_config_file, mode="w") as fd: +- toml.dump(cfg, fd) ++ with open(_global_config_file, mode="wb") as fd: ++ tomli_w.dump(cfg, fd) + except OSError: + warnings.warn("unable to write new config file") + diff --git a/packages/zarr/meta.yaml b/packages/zarr/meta.yaml index 0233e0c5179..0e782e21cd1 100644 --- a/packages/zarr/meta.yaml +++ b/packages/zarr/meta.yaml @@ -1,9 +1,9 @@ package: name: zarr - version: 2.8.3 + version: 2.11.3 source: - sha256: 8aece33269ba3ee2af9320aa528d5fe93f76c30e4ad7fdbfb604b1db3f0d779f - url: https://files.pythonhosted.org/packages/fe/35/e9b1abd0f842a475d9dbf06a9dd63e06fea6c4221bc9c455c2e25e9fe7bc/zarr-2.8.3.tar.gz + sha256: 6ee84547aec60fd06fc9356e9194302ebbdb2fd912fd365a0a652ad5c69636f5 + url: https://files.pythonhosted.org/packages/ca/84/d51f7f537ccad7f1700c14fa8d949a8ef4cd7f9503bfd750abbe44fa7668/zarr-2.11.3.tar.gz patches: - patches/fix-zarrsync.patch requirements: diff --git a/packages/zarr/test_zarr.py b/packages/zarr/test_zarr.py index ff76e3bb2fc..f9244e0032a 100644 --- a/packages/zarr/test_zarr.py +++ b/packages/zarr/test_zarr.py @@ -3,9 +3,9 @@ @run_in_pyodide(packages=["numpy", "numcodecs", "zarr"]) def test_zarr(): - from numcodecs import Blosc import numpy as np import zarr + from numcodecs import Blosc # basic test z = zarr.zeros((1000, 1000), chunks=(100, 100), dtype="i4") diff --git a/packages/zlib/meta.yaml b/packages/zlib/meta.yaml index 0089f3f2c7b..e1060f39da2 100644 --- a/packages/zlib/meta.yaml +++ b/packages/zlib/meta.yaml @@ -1,10 +1,10 @@ package: name: zlib - version: 1.2.11 + version: 1.2.12 source: - sha256: c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 - url: https://zlib.net/zlib-1.2.11.tar.gz + sha256: 91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9 + url: https://zlib.net/zlib-1.2.12.tar.gz build: library: true diff --git a/pyodide-build/pyodide_build/__main__.py b/pyodide-build/pyodide_build/__main__.py index b99e3d0e09e..44ce913911e 100755 --- a/pyodide-build/pyodide_build/__main__.py +++ b/pyodide-build/pyodide_build/__main__.py @@ -1,14 +1,10 @@ #!/usr/bin/env python3 import argparse -import pathlib import os import sys -from . import buildall -from . import buildpkg -from . import serve -from . import mkpkg -from .common import get_make_environment_vars +from . import buildall, buildpkg, mkpkg, serve +from .common import get_hostsitepackages, get_make_environment_vars, search_pyodide_root def make_parser() -> argparse.ArgumentParser: @@ -31,20 +27,26 @@ def make_parser() -> argparse.ArgumentParser: ]: # Likely building documentation, skip private API continue - parser = module.make_parser(subparsers.add_parser(command_name)) # type: ignore - parser.set_defaults(func=module.main) # type: ignore + parser = module.make_parser(subparsers.add_parser(command_name)) + parser.set_defaults(func=module.main) return main_parser def main(): if not os.environ.get("__LOADED_PYODIDE_ENV"): - PYODIDE_ROOT = str(pathlib.Path(__file__).parents[2].resolve()) - os.environ["PYODIDE_ROOT"] = PYODIDE_ROOT + # If we are building docs, we don't need to know the PYODIDE_ROOT + if "sphinx" in sys.modules: + os.environ["PYODIDE_ROOT"] = "" + + if "PYODIDE_ROOT" not in os.environ: + os.environ["PYODIDE_ROOT"] = str(search_pyodide_root(os.getcwd())) + os.environ.update(get_make_environment_vars()) - HOSTINSTALLDIR = os.environ["HOSTINSTALLDIR"] - os.environ[ - "PYTHONPATH" - ] = f"{HOSTINSTALLDIR}/lib/python:{PYODIDE_ROOT}/pyodide-build/" + hostsitepackages = get_hostsitepackages() + pythonpath = [ + hostsitepackages, + ] + os.environ["PYTHONPATH"] = ":".join(pythonpath) os.environ["BASH_ENV"] = "" os.environ["__LOADED_PYODIDE_ENV"] = "1" diff --git a/pyodide-build/pyodide_build/_f2c_fixes.py b/pyodide-build/pyodide_build/_f2c_fixes.py index 29293399ab6..db62e019e90 100644 --- a/pyodide-build/pyodide_build/_f2c_fixes.py +++ b/pyodide-build/pyodide_build/_f2c_fixes.py @@ -1,30 +1,208 @@ import re -import subprocess -from textwrap import dedent # for doctests -from typing import List, Iterable, Iterator, Tuple from pathlib import Path +from textwrap import dedent # for doctests +from typing import Iterable, Iterator + + +def prepare_doctest(x): + return dedent(x).strip().splitlines(True) + + +def fix_f2c_input(f2c_input_path: str): + """ + CLAPACK has been manually modified to remove useless arguments generated by + f2c. But the mismatches between the f2c ABI and the human-curated sensible + ABI in CLAPACK cause us great pain. + + This stuff applies to actual source files, but scipy also has multiple + templating engines for Fortran, so these changes have to be applied + immediately prior to f2c'ing a .f file to ensure that they also work + correctly on templated files. + + Fortran seems to be mostly case insensitive. The templated files in + particular can include weird mixtures of lower and upper case. + + Mostly the issues are related to 'character' types. Most LAPACK functions + that take string arguments use them as enums and only care about the first + character of the string. f2c generates a 'length' argument to indicate how + long the string is, but CLAPACK leaves these length arguments out because + the strings are assumed to have length 1. + + So the goal is to cause f2c to generate no length argument. We can achieve + this by replacing the string with the ascii code of the first character + e.g.,: + + f('UPPER') --> f(85) + + Coming from C this surprises me a bit. I would expect `f(85)` to cause a + segfault or something when f tries to find its string at memory address 85. + + f("UPPER") gets f2c'd to: + + f("UPPER", 5) + + But f2c compiles f(85) to the C code: + + static integer c__85 = 85; + f(&c__85); + + This is perfect. Not sure why it does this, but it's very convenient for us. + + chla_transtype is a special case. The CLAPACK version of chla_transtype takes + a return argument, whereas f2c thinks it should return the value. + + """ + f2c_input = Path(f2c_input_path) + with open(f2c_input) as f: + lines = f.readlines() + new_lines = [] + lines = char1_args_to_int(lines) + + for line in lines: + line = fix_string_args(line) + + if f2c_input_path.endswith("_flapack-f2pywrappers.f"): + line = line.replace("character cmach", "integer cmach") + line = line.replace("character norm", "integer norm") + if "id_dist" in str(f2c_input): + line = line.replace("character*1 jobz", "integer jobz") + if "jobz =" in line: + line = re.sub("'(.)'", lambda r: str(ord(r.group(1))), line) + + if f2c_input.name in [ + "_lapack_subroutine_wrappers.f", + "_blas_subroutine_wrappers.f", + ]: + line = line.replace("character", "integer") + line = line.replace("ret = chla_transtype(", "call chla_transtype(ret, 1,") + + # f2c has no support for variable sized arrays, so we replace them with + # dummy fixed sized arrays and then put the formulas back in in + # fix_f2c_output. Luckily, variable sized arrays are scarce in the scipy + # code base. + if "PROPACK" in str(f2c_input): + line = line.replace("ylocal(n)", "ylocal(123001)") + line = line.replace("character*1", "integer") + + if f2c_input.name == "mvndst.f": + line = re.sub(r"(infin|stdev|nlower|nupper)\(d\)", r"\1(123001)", line) + line = line.replace("rho(d*(d-1)/2)", "rho(123002)") + + new_lines.append(line) + + with open(f2c_input_path, "w") as f: + f.writelines(new_lines) + + +def fix_string_args(line): + """ + The two functions ilaenv and xerbla have real string args, f2c generates + inaccurate signatures for them. Instead of manually fixing the signatures + (xerbla happens a lot) we inject wrappers called `xerblaf2py` and + `ilaenvf2py` that have the signatures f2c expects and call these instead. + + Also, replace all single character strings in (the first line of) "call" + statements with their ascci codes. + """ + line = re.sub("ilaenv", "ilaenvf2py", line, flags=re.I) + if ( + not re.search("call", line, re.I) + and "SIGNST" not in line + and "TRANST" not in line + ): + return line + if re.search("xerbla", line, re.I): + return re.sub("xerbla", "xerblaf2py", line, flags=re.I) + else: + return re.sub("'[A-Za-z0-9]'", lambda y: str(ord(y.group(0)[1])), line) + + +def char1_to_int(x): + """ + Replace multicharacter strings with the ascii code of their first character. + + >>> char1_to_int("CALL sTRSV( 'UPPER', 'NOTRANS', 'NONUNIT', J, H, LDH, Y, 1 )") + 'CALL sTRSV( 85, 78, 78, J, H, LDH, Y, 1 )' + """ + return re.sub("'(.)[A-Za-z -]*'", lambda r: str(ord(r.group(1))), x) + + +def char1_args_to_int(lines): + """ + Replace strings with the ascii code of their first character if they are + arguments to one of a long list of hard coded LAPACK functions (see + fncstems). This handles multiline function calls. + + >>> print(char1_args_to_int(["CALL sTRSV( 'UPPER', 'NOTRANS', 'NONUNIT', J, H, LDH, Y, 1 )"])) + ['CALL sTRSV( 85, 78, 78, J, H, LDH, Y, 1 )'] + + >>> print("".join(char1_args_to_int(prepare_doctest(''' + ... call cvout (logfil, nconv, workl(ihbds), ndigit, + ... & '_neupd: Last row of the eigenvector matrix for T') + ... call ctrmm('Right' , 'Upper' , 'No transpose', + ... & 'Non-unit', n , nconv , + ... & one , workl(invsub), ldq , + ... & z , ldz) + ... ''')))) + call cvout (logfil, nconv, workl(ihbds), ndigit, + & '_neupd: Last row of the eigenvector matrix for T') + call ctrmm(82 , 85 , 78, + & 78, n , nconv , + & one , workl(invsub), ldq , + & z , ldz) + """ + fncstems = [ + "gemm", + "ggbak", + "gghrd", + "lacpy", + "lamch", + "lanhs", + "lanst", + "larf", + "lascl", + "laset", + "lasr", + "ormqr", + "orm2r", + "steqr", + "stevr", + "trevc", + "trmm", + "trsen", + "trsv", + "unm2r", + "unmqr", + ] + fncnames = [] + for c in "cdsz": + for stem in fncstems: + fncnames.append(c + stem) + fncnames += ["lsame"] + + funcs_pattern = "|".join(fncnames) + new_lines = [] + replace = False + for line in lines: + if re.search(funcs_pattern, line, re.IGNORECASE): + replace = True + if replace: + line = char1_to_int(line) + if not re.search(r",\s*$", line): + replace = False + new_lines.append(line) + return new_lines def fix_f2c_output(f2c_output_path: str): """ This function is called on the name of each C output file. It fixes up the C - output in various ways to compensate for the lack of f2c support for Fortan + output in various ways to compensate for the lack of f2c support for Fortran 90 and Fortran 95. """ f2c_output = Path(f2c_output_path) - if f2c_output.name == "lapack_extras.c": - # dfft.c has a bunch of implicit cast args coming from functions copied - # out of future lapack versions. fix_inconsistent_decls will fix all - # except string to int. - subprocess.check_call( - [ - "patch", - str(f2c_output_path), - f"../../patches/fix-implicit-cast-args-from-newer-lapack.patch", - ] - ) - - with open(f2c_output, "r") as f: + + with open(f2c_output) as f: lines = f.readlines() if "id_dist" in f2c_output_path: # Fix implicit casts in id_dist. @@ -45,51 +223,56 @@ def fix_f2c_output(f2c_output_path: str): else: add_externs_to_structs(lines) - if f2c_output.name in [ - "wrap_dummy_g77_abi.c", - "_lapack_subroutine_wrappers.c", - "_blas_subroutine_wrappers.c", - "_flapack-f2pywrappers.c", - ]: - lines = remove_ftnlen_args(lines) + if f2c_output.name == "_lapack_subroutine_wrappers.c": + lines = [ + line.replace("integer chla_transtype__", "void chla_transtype__") + for line in lines + ] - with open(f2c_output, "w") as f: - f.writelines(lines) + # Substitute back the dummy fixed array sizes. We also have to remove the + # "static" storage specifier since variable sized arrays can't have static + # storage. + if f2c_output.name == "mvndst.c": + lines = fix_inconsistent_decls(lines) + def fix_line(line): + if "12300" in line: + return ( + line.replace("static", "") + .replace("123001", "(*d__)") + .replace("123002", "(*d__)*((*d__)-1)/2") + ) + return line -def prepare_doctest(x): - return dedent(x).strip().split("\n") + lines = list(map(fix_line, lines)) + if "PROPACK" in str(f2c_output): -def remove_ftnlen_args(lines: List[str]) -> List[str]: - """ - Functions with "character" arguments have these extra ftnlen arguments at - the end (which are never used). Other places declare these arguments as - "integer" which don't get length arguments. This automates the removal of - the problematic arguments. - - >>> print("\\n".join(remove_ftnlen_args(prepare_doctest(''' - ... /* Subroutine */ int chla_transtypewrp__(char *ret, integer *trans, ftnlen - ... ret_len) - ... ''')))) - /* Subroutine */ int chla_transtypewrp__(char *ret, integer *trans) + def fix_line(line): + if f2c_output.name != "cgemm_ovwr.c": + line = line.replace("struct", "extern struct") + if "12300" in line: + return line.replace("static", "").replace("123001", "(*n)") + return line - >>> print("\\n".join(remove_ftnlen_args(prepare_doctest(''' - ... /* Subroutine */ int clanhfwrp_(real *ret, char *norm, char *transr, char * - ... uplo, integer *n, complex *a, real *work, ftnlen norm_len, ftnlen - ... transr_len, ftnlen uplo_len) - ... ''')))) - /* Subroutine */ int clanhfwrp_(real *ret, char *norm, char *transr, char * uplo, integer *n, complex *a, real *work) - """ - new_lines = [] - for line in regroup_lines(lines): - if line.startswith("/* Subroutine */"): - line = re.sub(r",\s*ftnlen [a-z]*_len", "", line) - new_lines.append(line) - return new_lines + lines = list(map(fix_line, lines)) + if f2c_output.name.endswith("lansvd.c"): + lines.append( + """ + #include + + int second_(real *t) { + *t = clock()/1000; + return 0; + } + """ + ) + + with open(f2c_output, "w") as f: + f.writelines(lines) -def add_externs_to_structs(lines: List[str]): +def add_externs_to_structs(lines: list[str]): """ The fortran "common" keyword is supposed to share variables between a bunch of files. f2c doesn't handle this correctly (it isn't possible for it to @@ -108,7 +291,7 @@ def add_externs_to_structs(lines: List[str]): ... } eh0001_; ... ''') >>> add_externs_to_structs(lines) - >>> print("\\n".join(lines)) + >>> print("".join(lines)) extern struct { doublereal rls[218]; integer ils[39]; } ls0001_; @@ -128,7 +311,7 @@ def regroup_lines(lines: Iterable[str]) -> Iterator[str]: Make sure that functions and declarations have their argument list only on one line. - >>> print("\\n".join(regroup_lines(prepare_doctest(''' + >>> print("".join(regroup_lines(prepare_doctest(''' ... /* Subroutine */ int clanhfwrp_(real *ret, char *norm, char *transr, char * ... uplo, integer *n, complex *a, real *work, ftnlen norm_len, ftnlen ... transr_len, ftnlen uplo_len) @@ -137,15 +320,14 @@ def regroup_lines(lines: Iterable[str]) -> Iterator[str]: ... extern /* Subroutine */ int dqelg_(integer *, doublereal *, doublereal *, ... doublereal *, doublereal *, integer *); ... ''')))) - /* Subroutine */ int clanhfwrp_(real *ret, char *norm, char *transr, char * uplo, integer *n, complex *a, real *work, ftnlen norm_len, ftnlen transr_len, ftnlen uplo_len) - { + /* Subroutine */ int clanhfwrp_(real *ret, char *norm, char *transr, char * uplo, integer *n, complex *a, real *work, ftnlen norm_len, ftnlen transr_len, ftnlen uplo_len){ static doublereal psum[52]; extern /* Subroutine */ int dqelg_(integer *, doublereal *, doublereal *, doublereal *, doublereal *, integer *); """ line_iter = iter(lines) for line in line_iter: - if not "/* Subroutine */" in line: + if "/* Subroutine */" not in line: yield line continue @@ -167,7 +349,7 @@ def regroup_lines(lines: Iterable[str]) -> Iterator[str]: yield from (x + ";" for x in joined_line.split(";")[:-1]) -def fix_inconsistent_decls(lines: List[str]) -> List[str]: +def fix_inconsistent_decls(lines: list[str]) -> list[str]: """ Fortran functions in id_dist use implicit casting of function args which f2c doesn't support. @@ -196,7 +378,7 @@ def fix_inconsistent_decls(lines: List[str]) -> List[str]: declarations and fixes them if necessary so that the declaration matches the definition. - >>> print("\\n".join(fix_inconsistent_decls(prepare_doctest(''' + >>> print("".join(fix_inconsistent_decls(prepare_doctest(''' ... /* Subroutine */ double f(double x){ ... return x + 5; ... } @@ -222,7 +404,7 @@ def fix_inconsistent_decls(lines: List[str]) -> List[str]: func_types[func_name] = types for idx, line in enumerate(lines): - if not "extern /* Subroutine */" in line: + if "extern /* Subroutine */" not in line: continue decls = line.split(")")[:-1] for decl in decls: @@ -238,7 +420,7 @@ def fix_inconsistent_decls(lines: List[str]) -> List[str]: return lines -def get_subroutine_decl(sub: str) -> Tuple[str, List[str]]: +def get_subroutine_decl(sub: str) -> tuple[str, list[str]]: """ >>> get_subroutine_decl( ... "extern /* Subroutine */ int dqelg_(integer *, doublereal *, doublereal *, doublereal *, doublereal *, integer *);" @@ -257,3 +439,42 @@ def get_subroutine_decl(sub: str) -> Tuple[str, List[str]]: type = arg.partition(" ")[0] types.append(type.strip()) return (func_name, types) + + +def scipy_fix_cfile(path): + """ + Replace void return types with int return types in various generated .c and + .h files. We can't achieve this with a simple patch because these files are + not in the sdist, they are generated as part of the build. + """ + source_path = Path(path) + text = source_path.read_text() + text = text.replace("extern void F_WRAPPEDFUNC", "extern int F_WRAPPEDFUNC") + text = text.replace("extern void F_FUNC", "extern int F_FUNC") + text = text.replace("void (*f2py_func)", "int (*f2py_func)") + text = text.replace("static void cb_", "static int cb_") + text = text.replace("typedef void(*cb_", "typedef int(*cb_") + text = text.replace("void(*)", "int(*)") + text = text.replace("static void f2py_setup_", "static int f2py_setup_") + + if path.endswith("_flapackmodule.c"): + text = text.replace(",size_t", "") + text = re.sub(r",slen\([a-z]*\)\)", ")", text) + + if path.endswith("_fblasmodule.c"): + text = text.replace(" float (*f2py_func)", " double (*f2py_func)") + + source_path.write_text(text) + + for lib in ["lapack", "blas"]: + if path.endswith(f"cython_{lib}.c"): + header_path = Path(path).with_name(f"_{lib}_subroutines.h") + header_text = header_path.read_text() + header_text = header_text.replace("void F_FUNC", "int F_FUNC") + header_path.write_text(header_text) + + +def scipy_fixes(args): + for arg in args: + if arg.endswith(".c"): + scipy_fix_cfile(arg) diff --git a/pyodide-build/pyodide_build/buildall.py b/pyodide-build/pyodide_build/buildall.py index a17e0513a4d..fa55678ff58 100755 --- a/pyodide-build/pyodide_build/buildall.py +++ b/pyodide-build/pyodide_build/buildall.py @@ -5,22 +5,29 @@ """ import argparse -from functools import total_ordering +import hashlib import json -from pathlib import Path -from queue import Queue, PriorityQueue +import os import shutil import subprocess import sys -from threading import Thread, Lock -from time import sleep, perf_counter -from typing import Dict, Set, Optional, List, Any -import os +from functools import total_ordering +from pathlib import Path +from queue import PriorityQueue, Queue +from threading import Lock, Thread +from time import perf_counter, sleep +from typing import Any from . import common -from .io import parse_package_config -from .common import UNVENDORED_STDLIB_MODULES from .buildpkg import needs_rebuild +from .common import UNVENDORED_STDLIB_MODULES, find_matching_wheels +from .io import parse_package_config + + +class BuildError(Exception): + def __init__(self, returncode): + self.returncode = returncode + super().__init__() class BasePackage: @@ -30,10 +37,12 @@ class BasePackage: meta: dict library: bool shared_library: bool - dependencies: List[str] - unbuilt_dependencies: Set[str] - dependents: Set[str] - unvendored_tests: Optional[bool] = None + dependencies: list[str] + unbuilt_dependencies: set[str] + dependents: set[str] + unvendored_tests: Path | None = None + file_name: str | None = None + install_dir: str = "site" # We use this in the priority queue, which pops off the smallest element. # So we want the smallest element to have the largest number of dependents @@ -43,6 +52,14 @@ def __lt__(self, other) -> bool: def __eq__(self, other) -> bool: return len(self.dependents) == len(other.dependents) + def __repr__(self) -> str: + return f"{type(self).__name__}({self.name})" + + def needs_rebuild(self) -> bool: + return needs_rebuild( + self.pkgdir, self.pkgdir / "build", self.meta.get("source", {}) + ) + @total_ordering class StdLibPackage(BasePackage): @@ -56,6 +73,7 @@ def __init__(self, pkgdir: Path): self.dependencies = [] self.unbuilt_dependencies = set() self.dependents = set() + self.install_dir = "lib" def build(self, outputdir: Path, args) -> None: # All build / packaging steps are already done in the main Makefile @@ -86,91 +104,86 @@ def __init__(self, pkgdir: Path): self.unbuilt_dependencies = set(self.dependencies) self.dependents = set() - def build(self, outputdir: Path, args) -> None: - with open(self.pkgdir / "build.log.tmp", "w") as f: - p = subprocess.run( - [ - sys.executable, - "-m", - "pyodide_build", - "buildpkg", - str(self.pkgdir / "meta.yaml"), - "--cflags", - args.cflags, - "--cxxflags", - args.cxxflags, - "--ldflags", - args.ldflags, - "--target-install-dir", - args.target_install_dir, - "--host-install-dir", - args.host_install_dir, - # Either this package has been updated and this doesn't - # matter, or this package is dependent on a package that has - # been updated and should be rebuilt even though its own - # files haven't been updated. - "--force-rebuild", - ], - check=False, - stdout=f, - stderr=subprocess.STDOUT, + def wheel_path(self) -> Path: + dist_dir = self.pkgdir / "dist" + wheels = list(find_matching_wheels(dist_dir.glob("*.whl"))) + if len(wheels) != 1: + raise Exception( + f"Unexpected number of wheels {len(wheels)} when building {self.name}" ) + return wheels[0] - # Don't overwrite build log if we didn't build the file. - # If the file didn't need to be rebuilt, the log will have exactly two lines. - rebuilt = True - with open(self.pkgdir / "build.log.tmp", "r") as f: - try: - next(f) - next(f) - next(f) - except StopIteration: - rebuilt = False - - if rebuilt: - shutil.move(self.pkgdir / "build.log.tmp", self.pkgdir / "build.log") # type: ignore - else: - (self.pkgdir / "build.log.tmp").unlink() + def tests_path(self) -> Path | None: + tests = list((self.pkgdir / "dist").glob("*-tests.tar")) + assert len(tests) <= 1 + if tests: + return tests[0] + return None - if args.log_dir and (self.pkgdir / "build.log").exists(): + def build(self, outputdir: Path, args) -> None: + + p = subprocess.run( + [ + sys.executable, + "-m", + "pyodide_build", + "buildpkg", + str(self.pkgdir / "meta.yaml"), + "--cflags", + args.cflags, + "--cxxflags", + args.cxxflags, + "--ldflags", + args.ldflags, + "--target-install-dir", + args.target_install_dir, + "--host-install-dir", + args.host_install_dir, + # Either this package has been updated and this doesn't + # matter, or this package is dependent on a package that has + # been updated and should be rebuilt even though its own + # files haven't been updated. + "--force-rebuild", + ], + check=False, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + log_dir = Path(args.log_dir).resolve() if args.log_dir else None + if log_dir and (self.pkgdir / "build.log").exists(): + log_dir.mkdir(exist_ok=True, parents=True) shutil.copy( - self.pkgdir / "build.log", Path(args.log_dir) / f"{self.name}.log" + self.pkgdir / "build.log", + log_dir / f"{self.name}.log", ) - try: - p.check_returncode() - except subprocess.CalledProcessError: + if p.returncode != 0: print(f"Error building {self.name}. Printing build logs.") - with open(self.pkgdir / "build.log", "r") as f: + with open(self.pkgdir / "build.log") as f: shutil.copyfileobj(f, sys.stdout) - raise + print("ERROR: cancelling buildall") + raise BuildError(p.returncode) if self.library: return - shutil.copyfile( - self.pkgdir / "build" / (self.name + ".data"), - outputdir / (self.name + ".data"), - ) - shutil.copyfile( - self.pkgdir / "build" / (self.name + ".js"), - outputdir / (self.name + ".js"), - ) - if (self.pkgdir / "build" / (self.name + "-tests.data")).exists(): - shutil.copyfile( - self.pkgdir / "build" / (self.name + "-tests.data"), - outputdir / (self.name + "-tests.data"), - ) - shutil.copyfile( - self.pkgdir / "build" / (self.name + "-tests.js"), - outputdir / (self.name + "-tests.js"), - ) + if self.shared_library: + file_path = Path(self.pkgdir / f"{self.name}-{self.version}.zip") + shutil.copy(file_path, outputdir) + file_path.unlink() + return + + shutil.copy(self.wheel_path(), outputdir) + test_path = self.tests_path() + if test_path: + shutil.copy(test_path, outputdir) def generate_dependency_graph( - packages_dir: Path, packages: Set[str] -) -> Dict[str, BasePackage]: + packages_dir: Path, packages: set[str] +) -> dict[str, BasePackage]: """This generates a dependency graph for listed packages. A node in the graph is a BasePackage object defined above, which maintains @@ -191,18 +204,23 @@ def generate_dependency_graph( - pkg_map: dictionary mapping package names to BasePackage objects """ - pkg_map: Dict[str, BasePackage] = {} + pkg_map: dict[str, BasePackage] = {} if "*" in packages: packages.discard("*") packages.update( - str(x) for x in packages_dir.iterdir() if (x / "meta.yaml").is_file() + str(x.name) for x in packages_dir.iterdir() if (x / "meta.yaml").is_file() ) no_numpy_dependents = "no-numpy-dependents" in packages if no_numpy_dependents: packages.discard("no-numpy-dependents") + packages_exclude = list(filter(lambda pkg: pkg.startswith("!"), packages)) + for pkg_exclude in packages_exclude: + packages.discard(pkg_exclude) + packages.discard(pkg_exclude[1:]) + while packages: pkgname = packages.pop() @@ -248,10 +266,10 @@ def print_with_progress_line(str, progress_line): def get_progress_line(package_set): if not package_set: return None - return f"In progress: " + ", ".join(package_set.keys()) + return "In progress: " + ", ".join(package_set.keys()) -def format_name_list(l: List[str]) -> str: +def format_name_list(l: list[str]) -> str: """ >>> format_name_list(["regex"]) 'regex' @@ -269,7 +287,7 @@ def format_name_list(l: List[str]) -> str: def mark_package_needs_build( - pkg_map: Dict[str, BasePackage], pkg: BasePackage, needs_build: Set[str] + pkg_map: dict[str, BasePackage], pkg: BasePackage, needs_build: set[str] ): """ Helper for generate_needs_build_set. Modifies needs_build in place. @@ -284,7 +302,7 @@ def mark_package_needs_build( mark_package_needs_build(pkg_map, pkg_map[dep], needs_build) -def generate_needs_build_set(pkg_map): +def generate_needs_build_set(pkg_map: dict[str, BasePackage]) -> set[str]: """ Generate the set of packages that need to be rebuilt. @@ -293,15 +311,15 @@ def generate_needs_build_set(pkg_map): according to needs_rebuild, and 2. packages which depend on case 1 packages. """ - needs_build = set() + needs_build: set[str] = set() for pkg in pkg_map.values(): # Otherwise, rebuild packages that have been updated and their dependents. - if needs_rebuild(pkg.pkgdir, pkg.pkgdir / "build", pkg.meta): + if pkg.needs_rebuild(): mark_package_needs_build(pkg_map, pkg, needs_build) return needs_build -def build_from_graph(pkg_map: Dict[str, BasePackage], outputdir: Path, args) -> None: +def build_from_graph(pkg_map: dict[str, BasePackage], outputdir: Path, args) -> None: """ This builds packages in pkg_map in parallel, building at most args.n_jobs packages at once. @@ -355,7 +373,8 @@ def build_from_graph(pkg_map: Dict[str, BasePackage], outputdir: Path, args) -> built_queue: Queue = Queue() thread_lock = Lock() queue_idx = 1 - package_set = {} + # Using dict keys for insertion order preservation + package_set: dict[str, None] = {} def builder(n): nonlocal queue_idx @@ -393,6 +412,8 @@ def builder(n): num_built = len(already_built) while num_built < len(pkg_map): pkg = built_queue.get() + if isinstance(pkg, BuildError): + raise SystemExit(pkg.returncode) if isinstance(pkg, Exception): raise pkg @@ -404,40 +425,40 @@ def builder(n): if len(dependent.unbuilt_dependencies) == 0: build_queue.put((job_priority(dependent), dependent)) - for name in list(pkg_map): - if (outputdir / (name + "-tests.js")).exists(): - pkg_map[name].unvendored_tests = True - print( "\n===================================================\n" f"built all packages in {perf_counter() - t0:.2f} s" ) -def generate_packages_json(pkg_map: Dict[str, BasePackage]) -> Dict: +def _generate_package_hash(full_path: Path) -> str: + sha256_hash = hashlib.sha256() + with open(full_path, "rb") as f: + while chunk := f.read(4096): + sha256_hash.update(chunk) + return sha256_hash.hexdigest() + + +def generate_packages_json(output_dir: Path, pkg_map: dict[str, BasePackage]) -> dict: """Generate the package.json file""" # Build package.json data. - package_data: Dict[str, Dict[str, Any]] = { + package_data: dict[str, dict[str, Any]] = { "info": {"arch": "wasm32", "platform": "Emscripten-1.0"}, "packages": {}, } libraries = [pkg.name for pkg in pkg_map.values() if pkg.library] - # unvendored stdlib modules - for name in UNVENDORED_STDLIB_MODULES: - pkg_entry: Dict[str, Any] = { - "name": name, - "version": "1.0", - "depends": [], - "imports": [name], - } - package_data["packages"][name.lower()] = pkg_entry - for name, pkg in pkg_map.items(): - if pkg.library: + if not pkg.file_name: continue - pkg_entry = {"name": name, "version": pkg.version} + pkg_entry: Any = { + "name": name, + "version": pkg.version, + "file_name": pkg.file_name, + "install_dir": pkg.install_dir, + "sha_256": _generate_package_hash(Path(output_dir, pkg.file_name)), + } if pkg.shared_library: pkg_entry["shared_library"] = True pkg_entry["depends"] = [ @@ -456,6 +477,8 @@ def generate_packages_json(pkg_map: Dict[str, BasePackage]) -> Dict: "version": pkg.version, "depends": [name.lower()], "imports": [], + "file_name": pkg.unvendored_tests.name, + "install_dir": pkg.install_dir, } package_data["packages"][name.lower() + "-tests"] = pkg_entry @@ -470,17 +493,32 @@ def generate_packages_json(pkg_map: Dict[str, BasePackage]) -> Dict: return package_data -def build_packages(packages_dir: Path, outputdir: Path, args) -> None: +def build_packages(packages_dir: Path, output_dir: Path, args) -> None: packages = common._parse_package_subset(args.only) pkg_map = generate_dependency_graph(packages_dir, packages) - build_from_graph(pkg_map, outputdir, args) + build_from_graph(pkg_map, output_dir, args) + for pkg in pkg_map.values(): + if pkg.library: + continue + if isinstance(pkg, StdLibPackage): + pkg.file_name = pkg.name + ".tar" + continue + if pkg.needs_rebuild(): + continue + if pkg.shared_library: + pkg.file_name = f"{pkg.name}-{pkg.version}.zip" + continue + assert isinstance(pkg, Package) + pkg.file_name = pkg.wheel_path().name + pkg.unvendored_tests = pkg.tests_path() - package_data = generate_packages_json(pkg_map) + package_data = generate_packages_json(output_dir, pkg_map) - with open(outputdir / "packages.json", "w") as fd: + with open(output_dir / "packages.json", "w") as fd: json.dump(package_data, fd) + fd.write("\n") def make_parser(parser): diff --git a/pyodide-build/pyodide_build/buildpkg.py b/pyodide-build/pyodide_build/buildpkg.py index 92dea64c33b..4c3d3722227 100755 --- a/pyodide-build/pyodide_build/buildpkg.py +++ b/pyodide-build/pyodide_build/buildpkg.py @@ -6,23 +6,25 @@ import argparse import cgi +import fnmatch import hashlib import json import os -import re import shutil import subprocess import sys -import fnmatch - +import sysconfig +import textwrap from contextlib import contextmanager from datetime import datetime from pathlib import Path from textwrap import dedent -from typing import Any, Dict +from types import TracebackType +from typing import Any, NoReturn, TextIO from urllib import request from . import pywasmcross +from .common import find_matching_wheels @contextmanager @@ -39,8 +41,28 @@ def chdir(new_dir: Path): from .io import parse_package_config +def _make_whlfile(*args, owner=None, group=None, **kwargs): + return shutil._make_zipfile(*args, **kwargs) # type: ignore[attr-defined] + + +shutil.register_archive_format("whl", _make_whlfile, description="Wheel file") +shutil.register_unpack_format( + "whl", [".whl", ".wheel"], shutil._unpack_zipfile, description="Wheel file" # type: ignore[attr-defined] +) + + +def exit_with_stdio(result: subprocess.CompletedProcess) -> NoReturn: + if result.stdout: + print(" stdout:") + print(textwrap.indent(result.stdout, " ")) + if result.stderr: + print(" stderr:") + print(textwrap.indent(result.stderr, " ")) + raise SystemExit(result.returncode) + + class BashRunnerWithSharedEnvironment: - """Run multiple bash scripts with persisent environment. + """Run multiple bash scripts with persistent environment. Environment is stored to "env" member between runs. This can be updated directly to adjust the environment, or read to get variables. @@ -49,12 +71,21 @@ class BashRunnerWithSharedEnvironment: def __init__(self, env=None): if env is None: env = dict(os.environ) - self.env: Dict[str, str] = env - self._fd_read, self._fd_write = os.pipe() - self._reader = os.fdopen(self._fd_read, "r") + + self._reader: TextIO | None + self._fd_write: int | None + self.env: dict[str, str] = env + + def __enter__(self) -> "BashRunnerWithSharedEnvironment": + fd_read, self._fd_write = os.pipe() + self._reader = os.fdopen(fd_read, "r") + return self def run(self, cmd, **opts): """Run a bash script. Any keyword arguments are passed on to subprocess.run.""" + assert self._fd_write is not None + assert self._reader is not None + write_env_pycode = ";".join( [ "import os", @@ -63,20 +94,32 @@ def run(self, cmd, **opts): ] ) write_env_shell_cmd = f"{sys.executable} -c '{write_env_pycode}'" - cmd += "\n" + write_env_shell_cmd + full_cmd = f"{cmd}\n{write_env_shell_cmd}" result = subprocess.run( - ["bash", "-ce", cmd], pass_fds=[self._fd_write], env=self.env, **opts + ["bash", "-ce", full_cmd], pass_fds=[self._fd_write], env=self.env, **opts ) + if result.returncode != 0: + print("ERROR: bash command failed") + print(textwrap.indent(cmd, " ")) + exit_with_stdio(result) + self.env = json.loads(self._reader.readline()) return result - def close(self): + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: """Free the file descriptors.""" - if self._fd_read: - os.close(self._fd_read) + + if self._fd_write: os.close(self._fd_write) - self._fd_read = None self._fd_write = None + if self._reader: + self._reader.close() + self._reader = None @contextmanager @@ -85,43 +128,35 @@ def get_bash_runner(): env = { key: os.environ[key] for key in [ + # TODO: Stabilize and document more of these in meta-yaml.md "PATH", "PYTHONPATH", "PYODIDE_ROOT", "PYTHONINCLUDE", "NUMPY_LIB", "PYODIDE_PACKAGE_ABI", + "HOSTINSTALLDIR", + "HOSTSITEPACKAGES", + "PYMAJOR", + "PYMINOR", + "PYMICRO", + "CPYTHONBUILD", + "SIDE_MODULE_CFLAGS", + "SIDE_MODULE_LDFLAGS", + "STDLIB_MODULE_CFLAGS", + "OPEN_SSL_ROOT", ] - } - env["PYODIDE"] = "1" + } | {"PYODIDE": "1"} if "PYODIDE_JOBS" in os.environ: env["PYODIDE_JOBS"] = os.environ["PYODIDE_JOBS"] - b = BashRunnerWithSharedEnvironment(env=env) - b.run(f"source {PYODIDE_ROOT}/emsdk/emsdk/emsdk_env.sh", stderr=subprocess.DEVNULL) - try: - yield b - finally: - b.close() - - -def _have_terser(): - try: - # Check npm exists and terser is installed locally - subprocess.run( - [ - "npm", - "list", - "terser", - ], - stdout=subprocess.DEVNULL, + with BashRunnerWithSharedEnvironment(env=env) as b: + b.run( + f"source {PYODIDE_ROOT}/emsdk/emsdk/emsdk_env.sh", stderr=subprocess.DEVNULL ) - except subprocess.CalledProcessError: - return False - - return True + yield b -def check_checksum(archive: Path, source_metadata: Dict[str, Any]): +def check_checksum(archive: Path, source_metadata: dict[str, Any]): """ Checks that an archive matches the checksum in the package metadata. @@ -152,7 +187,7 @@ def check_checksum(archive: Path, source_metadata: Dict[str, Any]): if len(chunk) < CHUNK_SIZE: break if h.hexdigest() != checksum: - raise ValueError("Invalid {} checksum".format(checksum_algorithm)) + raise ValueError(f"Invalid {checksum_algorithm} checksum") def trim_archive_extension(tarballname): @@ -165,13 +200,14 @@ def trim_archive_extension(tarballname): ".tar.xz", ".txz", ".zip", + ".whl", ]: if tarballname.endswith(extension): return tarballname[: -len(extension)] return tarballname -def download_and_extract(buildpath: Path, srcpath: Path, src_metadata: Dict[str, Any]): +def download_and_extract(buildpath: Path, srcpath: Path, src_metadata: dict[str, Any]): """ Download the source from specified in the meta data, then checksum it, then extract the archive into srcpath. @@ -208,17 +244,23 @@ def download_and_extract(buildpath: Path, srcpath: Path, src_metadata: Dict[str, tarballpath.unlink() raise + if tarballpath.suffix == ".whl": + os.makedirs(srcpath / "dist") + shutil.copy(tarballpath, srcpath / "dist") + return + if not srcpath.is_dir(): - shutil.unpack_archive(str(tarballpath), str(buildpath)) + shutil.unpack_archive(tarballpath, buildpath) extract_dir_name = src_metadata.get("extract_dir") if not extract_dir_name: extract_dir_name = trim_archive_extension(tarballname) + shutil.move(buildpath / extract_dir_name, srcpath) def prepare_source( - pkg_root: Path, buildpath: Path, srcpath: Path, src_metadata: Dict[str, Any] + pkg_root: Path, buildpath: Path, srcpath: Path, src_metadata: dict[str, Any] ): """ Figure out from the "source" key in the package metadata where to get the source @@ -252,7 +294,6 @@ def prepare_source( if "url" in src_metadata: download_and_extract(buildpath, srcpath, src_metadata) - patch(pkg_root, srcpath, src_metadata) return if "path" not in src_metadata: raise ValueError( @@ -267,7 +308,7 @@ def prepare_source( shutil.copytree(srcdir, srcpath) -def patch(pkg_root: Path, srcpath: Path, src_metadata: Dict[str, Any]): +def patch(pkg_root: Path, srcpath: Path, src_metadata: dict[str, Any]): """ Apply patches to the source. @@ -293,12 +334,20 @@ def patch(pkg_root: Path, srcpath: Path, src_metadata: Dict[str, Any]): if not patches and not extras: return + # We checked these in check_package_config. + assert "url" in src_metadata + assert not src_metadata["url"].endswith(".whl") + # Apply all the patches with chdir(srcpath): for patch in patches: - subprocess.run( - ["patch", "-p1", "--binary", "-i", pkg_root / patch], check=True + result = subprocess.run( + ["patch", "-p1", "--binary", "--verbose", "-i", pkg_root / patch], + check=False, ) + if result.returncode != 0: + print(f"ERROR: Patch {pkg_root/patch} failed") + exit_with_stdio(result) # Add any extra files for src, dst in extras: @@ -308,38 +357,34 @@ def patch(pkg_root: Path, srcpath: Path, src_metadata: Dict[str, Any]): fd.write(b"\n") -def install_for_distribution(): - commands = [ - sys.executable, - "setup.py", - "install", - "--skip-build", - "--prefix=install", - "--old-and-unmanageable", - ] - try: - subprocess.check_call(commands) - except Exception: - print( - f'Warning: {" ".join(str(arg) for arg in commands)} failed ' - f"with distutils, possibly due to the use of distutils " - f"that does not support the --old-and-unmanageable " - "argument. Re-trying the install without this argument." +def unpack_wheel(path): + with chdir(path.parent): + result = subprocess.run( + [sys.executable, "-m", "wheel", "unpack", path.name], check=False + ) + if result.returncode != 0: + print(f"ERROR: Unpacking wheel {path.name} failed") + exit_with_stdio(result) + + +def pack_wheel(path): + with chdir(path.parent): + result = subprocess.run( + [sys.executable, "-m", "wheel", "pack", path.name], check=False ) - subprocess.check_call(commands[:-1]) + if result.returncode != 0: + print(f"ERROR: Packing wheel {path} failed") + exit_with_stdio(result) def compile( - pkg_root: Path, + name: str, srcpath: Path, - build_metadata: Dict[str, Any], + build_metadata: dict[str, Any], bash_runner: BashRunnerWithSharedEnvironment, *, target_install_dir: str, host_install_dir: str, - should_capture_compile: bool, - should_replay_compile: bool, - replay_from: int = 0, ): """ Runs pywasmcross for the package. The effect of this is to first run setup.py @@ -353,10 +398,6 @@ def compile( Parameters ---------- - pkg_root - The path to the root directory for the package. Generally - $PYODIDE_ROOT/packages/ - srcpath The path to the source. We extract the source into the build directory, so it will be something like @@ -378,57 +419,108 @@ def compile( needed if you want to build other packages that depend on this one. """ # This function runs setup.py. library and sharedlibrary don't have setup.py - if build_metadata.get("library") or build_metadata.get("sharedlibrary"): + if build_metadata.get("sharedlibrary"): return - if (srcpath / ".built").is_file(): - return + replace_libs = ";".join(build_metadata.get("replace-libs", [])) + with chdir(srcpath): + pywasmcross.compile( + env=bash_runner.env, + pkgname=name, + cflags=build_metadata["cflags"], + cxxflags=build_metadata["cxxflags"], + ldflags=build_metadata["ldflags"], + host_install_dir=host_install_dir, + target_install_dir=target_install_dir, + replace_libs=replace_libs, + ) - skip_host = build_metadata.get("skip_host", True) - replace_libs = ";".join(build_metadata.get("replace-libs", [])) +def replace_so_abi_tags(wheel_dir: Path): + """Replace native abi tag with emscripten abi tag in .so file names""" + build_soabi = sysconfig.get_config_var("SOABI") + assert build_soabi + ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") + assert ext_suffix + build_triplet = "-".join(build_soabi.split("-")[2:]) + host_triplet = common.get_make_flag("PLATFORM_TRIPLET") + for file in wheel_dir.glob(f"**/*{ext_suffix}"): + file.rename(file.with_name(file.name.replace(build_triplet, host_triplet))) - with chdir(srcpath): - if should_capture_compile: - pywasmcross.capture_compile( - host_install_dir=host_install_dir, - skip_host=skip_host, - env=bash_runner.env, - ) - prereplay = build_metadata.get("prereplay") - if prereplay: - bash_runner.run(prereplay) - if should_replay_compile: - pywasmcross.replay_compile( - cflags=build_metadata["cflags"], - cxxflags=build_metadata["cxxflags"], - ldflags=build_metadata["ldflags"], - target_install_dir=target_install_dir, - host_install_dir=host_install_dir, - replace_libs=replace_libs, - replay_from=replay_from, - ) - install_for_distribution() - post = build_metadata.get("post") - if post: - # use Python, 3.9 by default - pyfolder = "".join( - [ - "python", - os.environ.get("PYMAJOR", "3"), - ".", - os.environ.get("PYMINOR", "9"), - ] - ) - site_packages_dir = srcpath / "install" / "lib" / pyfolder / "site-packages" - bash_runner.env.update( - {"SITEPACKAGES": str(site_packages_dir), "PKGDIR": str(pkg_root)} +def package_wheel( + pkg_name: str, + pkg_root: Path, + srcpath: Path, + build_metadata: dict[str, Any], + bash_runner: BashRunnerWithSharedEnvironment, +): + """Package a wheel + + This unpacks the wheel, unvendors tests if necessary, runs and "build.post" + script, and then repacks the wheel. + + Parameters + ---------- + pkg_name + The name of the package + + pkg_root + The path to the root directory for the package. Generally + $PYODIDE_ROOT/packages/ + + srcpath + The path to the source. We extract the source into the build directory, + so it will be something like + $(PYOIDE_ROOT)/packages//build/-. + + build_metadata + The build section from meta.yaml. + + bash_runner + The runner we will use to execute our bash commands. Preserves + environment variables from one invocation to the next. + """ + if build_metadata.get("sharedlibrary"): + return + + distdir = srcpath / "dist" + wheel, *rest = find_matching_wheels(distdir.glob("*.whl")) + if rest: + raise Exception( + f"Unexpected number of wheels {len(rest) + 1} when building {pkg_name}" ) - bash_runner.run(post, check=True) + unpack_wheel(wheel) + wheel.unlink() + name, ver, _ = wheel.name.split("-", 2) + wheel_dir_name = f"{name}-{ver}" + wheel_dir = distdir / wheel_dir_name - with open(srcpath / ".built", "wb") as fd: - fd.write(b"\n") + # update so abi tags after build is complete but before running post script + # to maximize sanity. + replace_so_abi_tags(wheel_dir) + + post = build_metadata.get("post") + if post: + bash_runner.env.update({"PKGDIR": str(pkg_root)}) + result = bash_runner.run(post) + if result.returncode != 0: + print("ERROR: post failed") + exit_with_stdio(result) + + test_dir = distdir / "tests" + nmoved = 0 + if build_metadata.get("unvendor-tests", True): + nmoved = unvendor_tests(wheel_dir, test_dir) + if nmoved: + with chdir(distdir): + shutil.make_archive(f"{pkg_name}-tests", "tar", test_dir) + pack_wheel(wheel_dir) + # wheel_dir causes pytest collection failures for in-tree packages like + # micropip. To prevent these, we get rid of wheel_dir after repacking the + # wheel. + shutil.rmtree(wheel_dir) + shutil.rmtree(test_dir, ignore_errors=True) def unvendor_tests(install_prefix: Path, test_install_prefix: Path) -> int: @@ -454,7 +546,7 @@ def unvendor_tests(install_prefix: Path, test_install_prefix: Path) -> int: n_moved = 0 out_files = [] shutil.rmtree(test_install_prefix, ignore_errors=True) - for root, dirs, files in os.walk(install_prefix): + for root, _dirs, files in os.walk(install_prefix): root_rel = Path(root).relative_to(install_prefix) if root_rel.name == "__pycache__" or root_rel.name.endswith(".egg_info"): continue @@ -481,80 +573,14 @@ def unvendor_tests(install_prefix: Path, test_install_prefix: Path) -> int: return n_moved -def package_files( - pkg_name: str, - buildpath: Path, - srcpath: Path, - *, - should_unvendor_tests: bool = True, - compress: bool = False, -) -> None: - """Package the installation folder into .data and .js files - - Parameters - ---------- - pkg_name - the name of the package - - buildpath - the package build path. Usually `packages//build` - - srcpath - the package source path. Usually - `packages//build/-`. - - should_unvendor_tests - should we unvendor tests - - compress - should we compress the output - - Notes - ----- - The files to packages are located under the `install_prefix` corresponding - to `srcpath / 'install'`. - - """ - if (buildpath / ".packaged").is_file(): - return - - install_prefix = (srcpath / "install").resolve() - test_install_prefix = (srcpath / "install-test").resolve() - - if should_unvendor_tests: - n_unvendored = unvendor_tests(install_prefix, test_install_prefix) - else: - n_unvendored = 0 - - # Package the package except for tests - common.invoke_file_packager( - name=pkg_name, - root_dir=buildpath, - base_dir=install_prefix, - pyodidedir="/", - compress=compress, - ) - - # Package tests - if n_unvendored > 0: - common.invoke_file_packager( - name=f"{pkg_name}-tests", - root_dir=buildpath, - base_dir=test_install_prefix, - pyodidedir="/", - compress=compress, - ) - - def create_packaged_token(buildpath: Path): - with open(buildpath / ".packaged", "wb") as fd: - fd.write(b"\n") + (buildpath / ".packaged").write_text("\n") def run_script( buildpath: Path, srcpath: Path, - build_metadata: Dict[str, Any], + build_metadata: dict[str, Any], bash_runner: BashRunnerWithSharedEnvironment, ): """ @@ -581,11 +607,14 @@ def run_script( return with chdir(srcpath): - bash_runner.run(script, check=True) + result = bash_runner.run(script) + if result.returncode != 0: + print("ERROR: script failed") + exit_with_stdio(result) def needs_rebuild( - pkg_root: Path, buildpath: Path, source_metadata: Dict[str, Any] + pkg_root: Path, buildpath: Path, source_metadata: dict[str, Any] ) -> bool: """ Determines if a package needs a rebuild because its meta.yaml, patches, or @@ -610,11 +639,16 @@ def needs_rebuild( def source_files(): yield pkg_root / "meta.yaml" - yield from source_metadata.get("patches", []) - yield from (x[0] for x in source_metadata.get("extras", [])) + yield from ( + pkg_root / patch_path for patch_path in source_metadata.get("patches", []) + ) + yield from ( + pkg_root / patch_path + for [patch_path, _] in source_metadata.get("extras", []) + ) src_path = source_metadata.get("path") if src_path: - yield from Path(src_path).glob("**/*") + yield from (pkg_root / src_path).resolve().glob("**/*") for source_file in source_files(): source_file = Path(source_file) @@ -625,17 +659,12 @@ def source_files(): def build_package( pkg_root: Path, - pkg: Dict[str, Any], + pkg: dict[str, Any], *, target_install_dir: str, host_install_dir: str, - compress_package: bool, force_rebuild: bool, - should_run_script: bool, - should_prepare_source: bool, - should_capture_compile: bool, - should_replay_compile: bool, - replay_from: int, + continue_: bool, ): """ Build the package. The main entrypoint in this module. @@ -652,58 +681,91 @@ def build_package( host_install_dir Directory for installing built host packages. - - compress_package - Should we compress the package? """ pkg_metadata = pkg["package"] source_metadata = pkg["source"] build_metadata = pkg["build"] name = pkg_metadata["name"] + version = pkg_metadata["version"] build_dir = pkg_root / "build" - src_dir_name: str = f"{pkg_metadata['name']}-{pkg_metadata['version']}" + src_dir_name: str = f"{name}-{version}" srcpath = build_dir / src_dir_name + url = source_metadata.get("url") + finished_wheel = url and url.endswith(".whl") + script = build_metadata.get("script") + library = build_metadata.get("library", False) + sharedlibrary = build_metadata.get("sharedlibrary", False) + post = build_metadata.get("post") + + # These are validated in io.check_package_config + # If any of these assertions fail, the code path through here might get a + # bit weird + assert not (library and sharedlibrary) + if finished_wheel: + assert not script + assert not library + assert not sharedlibrary + if post: + assert not library + assert not sharedlibrary + if not force_rebuild and not needs_rebuild(pkg_root, build_dir, source_metadata): return - if not should_prepare_source and not srcpath.exists(): - raise IOError( + if continue_ and not srcpath.exists(): + raise OSError( "Cannot find source for rebuild. Expected to find the source " f"directory at the path {srcpath}, but that path does not exist." ) + import os + import subprocess + import sys + + tee = subprocess.Popen(["tee", pkg_root / "build.log"], stdin=subprocess.PIPE) + # Cause tee's stdin to get a copy of our stdin/stdout (as well as that + # of any child processes we spawn) + os.dup2(tee.stdin.fileno(), sys.stdout.fileno()) # type: ignore[union-attr] + os.dup2(tee.stdin.fileno(), sys.stderr.fileno()) # type: ignore[union-attr] + with chdir(pkg_root), get_bash_runner() as bash_runner: - if should_prepare_source: + bash_runner.env["PKG_VERSION"] = version + bash_runner.env["PKG_BUILD_DIR"] = str(srcpath) + if not continue_: prepare_source(pkg_root, build_dir, srcpath, source_metadata) + patch(pkg_root, srcpath, source_metadata) - if should_run_script: - run_script(build_dir, srcpath, build_metadata, bash_runner) + run_script(build_dir, srcpath, build_metadata, bash_runner) - if build_metadata.get("library"): + if library: create_packaged_token(build_dir) return - compile( - pkg_root, - srcpath, - build_metadata, - bash_runner, - target_install_dir=target_install_dir, - host_install_dir=host_install_dir, - should_capture_compile=should_capture_compile, - should_replay_compile=should_replay_compile, - replay_from=replay_from, - ) + if not sharedlibrary and not finished_wheel: + compile( + name, + srcpath, + build_metadata, + bash_runner, + target_install_dir=target_install_dir, + host_install_dir=host_install_dir, + ) + if not sharedlibrary: + package_wheel( + name, + pkg_root, + srcpath, + build_metadata, + bash_runner, + ) + + shutil.rmtree(pkg_root / "dist", ignore_errors=True) + shutil.copytree(srcpath / "dist", pkg_root / "dist") + + if sharedlibrary: + shutil.make_archive(f"{name}-{version}", "zip", pkg_root / "dist") - should_unvendor_tests = build_metadata.get("unvendor-tests", True) - package_files( - name, - build_dir, - srcpath, - should_unvendor_tests=should_unvendor_tests, - compress=compress_package, - ) create_packaged_token(build_dir) @@ -764,79 +826,23 @@ def make_parser(parser: argparse.ArgumentParser): ) parser.add_argument( "--continue", - type=str, - nargs="?", - dest="continue_from", - default="None", - const="script", + dest="continue_", + action="store_true", help=( dedent( """ - Continue a build from the middle. For debugging. Implies - "--force-rebuild". Possible arguments: - - 'script' : Don't prepare source, start with running script. `--continue` with no argument has the same effect. - - 'capture' : Start with capture step - - 'replay' : Start with replay step - - 'replay:15' : Replay the capture step starting with the 15th compile command (any integer works) + Continue a build from the middle. For debugging. Implies "--force-rebuild". """ ).strip() ), ) - parser.add_argument( - "--no-compress-package", - action="store_false", - default=True, - dest="compress_package", - help="Do not compress built packages.", - ) return parser -def parse_continue_arg(continue_from: str) -> Dict[str, Any]: - from itertools import accumulate - - is_none = continue_from == "None" - is_script = continue_from == "script" - is_capture = continue_from == "capture" - is_replay = continue_from == "replay" or re.fullmatch( - r"replay(:[0-9]+)?", continue_from - ) - - [ - should_prepare_source, - should_run_script, - should_capture_compile, - should_replay_compile, - ] = accumulate([is_none, is_script, is_capture, is_replay], lambda a, b: a or b) - - if not should_replay_compile: - raise IOError( - f"Unexpected --continue argument '{continue_from}', should have been 'script', 'capture', 'replay', or 'replay:##'" - ) - - result: Dict[str, Any] = {} - result["should_prepare_source"] = should_prepare_source - result["should_run_script"] = should_run_script - result["should_capture_compile"] = should_capture_compile - result["should_replay_compile"] = should_replay_compile - result["replay_from"] = 1 - if continue_from.startswith("replay:"): - result["replay_from"] = int(continue_from.removeprefix("replay:")) - return result - - def main(args): - if args.compress_package and not _have_terser(): - raise RuntimeError( - "Terser is required to compress packages. Try `npm install -g terser` to install terser." - ) - step_controls = parse_continue_arg(args.continue_from) + continue_ = not not args.continue_ # --continue implies --force-rebuild - force_rebuild = args.force_rebuild or not not args.continue_from + force_rebuild = args.force_rebuild or continue_ meta_file = Path(args.package[0]).resolve() @@ -864,18 +870,17 @@ def main(args): pkg, target_install_dir=args.target_install_dir, host_install_dir=args.host_install_dir, - compress_package=args.compress_package, force_rebuild=force_rebuild, - **step_controls, + continue_=continue_, ) - except: + except Exception: success = False raise finally: t1 = datetime.now() datestamp = "[{}]".format(t1.strftime("%Y-%m-%d %H:%M:%S")) - total_seconds = "{:.1f}".format((t1 - t0).total_seconds()) + total_seconds = f"{(t1 - t0).total_seconds():.1f}" status = "Succeeded" if success else "Failed" print( f"{datestamp} {status} building package {name} in {total_seconds} seconds." diff --git a/pyodide-build/pyodide_build/common.py b/pyodide-build/pyodide_build/common.py index 4923702e6bf..d17472482eb 100644 --- a/pyodide-build/pyodide_build/common.py +++ b/pyodide-build/pyodide_build/common.py @@ -1,19 +1,92 @@ -from pathlib import Path -from typing import Optional, Set - import functools +import os import subprocess +from pathlib import Path +from typing import Iterable, Iterator + +import tomli +from packaging.tags import Tag, compatible_tags, cpython_tags +from packaging.utils import parse_wheel_filename + +PLATFORM = "emscripten_wasm32" -UNVENDORED_STDLIB_MODULES = ["test", "distutils"] + +def pyodide_tags() -> Iterator[Tag]: + """ + Returns the sequence of tag triples for the Pyodide interpreter. + + The sequence is ordered in decreasing specificity. + """ + PYMAJOR = get_make_flag("PYMAJOR") + PYMINOR = get_make_flag("PYMINOR") + python_version = (int(PYMAJOR), int(PYMINOR)) + yield from cpython_tags(platforms=[PLATFORM], python_version=python_version) + yield from compatible_tags(platforms=[PLATFORM], python_version=python_version) -def _parse_package_subset(query: Optional[str]) -> Set[str]: +def find_matching_wheels(wheel_paths: Iterable[Path]) -> Iterator[Path]: + """ + Returns the sequence wheels whose tags match the Pyodide interpreter. + + Parameters + ---------- + wheel_paths + A list of paths to wheels + + Returns + ------- + The subset of wheel_paths that have tags that match the Pyodide interpreter. + """ + wheel_paths = list(wheel_paths) + wheel_tags_list: list[frozenset[Tag]] = [] + for wheel in wheel_paths: + _, _, _, tags = parse_wheel_filename(wheel.name) + wheel_tags_list.append(tags) + for supported_tag in pyodide_tags(): + for wheel_path, wheel_tags in zip(wheel_paths, wheel_tags_list): + if supported_tag in wheel_tags: + yield wheel_path + + +UNVENDORED_STDLIB_MODULES = {"test", "distutils"} + +ALWAYS_PACKAGES = { + "pyparsing", + "packaging", + "micropip", +} + +CORE_PACKAGES = { + "micropip", + "pyparsing", + "pytz", + "packaging", + "Jinja2", + "regex", + "fpcast-test", + "sharedlib-test-py", + "cpp-exceptions-test", + "ssl", +} + +CORE_SCIPY_PACKAGES = { + "numpy", + "scipy", + "pandas", + "matplotlib", + "scikit-learn", + "joblib", + "pytest", +} + + +def _parse_package_subset(query: str | None) -> set[str]: """Parse the list of packages specified with PYODIDE_PACKAGES env var. Also add the list of mandatory packages: ["pyparsing", "packaging", "micropip"] - Supports folowing meta-packages, + Supports following meta-packages, - 'core': corresponds to packages needed to run the core test suite {"micropip", "pyparsing", "pytz", "packaging", "Jinja2", "fpcast-test"}. This is the default option if query is None. @@ -33,33 +106,15 @@ def _parse_package_subset(query: Optional[str]) -> Set[str]: if query is None: query = "core" - core_packages = { - "micropip", - "pyparsing", - "pytz", - "packaging", - "Jinja2", - "regex", - "fpcast-test", - "sharedlib-test-py", - } - core_scipy_packages = { - "numpy", - "scipy", - "pandas", - "matplotlib", - "scikit-learn", - "joblib", - "pytest", - } packages = {el.strip() for el in query.split(",")} - packages.update(["pyparsing", "packaging", "micropip"]) + packages.update(ALWAYS_PACKAGES) + packages.update(UNVENDORED_STDLIB_MODULES) # handle meta-packages if "core" in packages: - packages |= core_packages + packages |= CORE_PACKAGES packages.discard("core") if "min-scipy-stack" in packages: - packages |= core_packages | core_scipy_packages + packages |= CORE_PACKAGES | CORE_SCIPY_PACKAGES packages.discard("min-scipy-stack") # Hack to deal with the circular dependence between soupsieve and @@ -70,49 +125,6 @@ def _parse_package_subset(query: Optional[str]) -> Set[str]: return packages -def file_packager_path() -> Path: - ROOTDIR = Path(__file__).parents[2].resolve() - return ROOTDIR / "emsdk/emsdk/upstream/emscripten/tools/file_packager" - - -def invoke_file_packager( - *, - name, - root_dir=".", - base_dir, - pyodidedir, - compress=False, -): - subprocess.run( - [ - str(file_packager_path()), - f"{name}.data", - f"--js-output={name}.js", - "--preload", - f"{base_dir}@{pyodidedir}", - "--lz4", - "--export-name=globalThis.__pyodide_module", - "--exclude", - "*__pycache__*", - "--use-preload-plugins", - ], - cwd=root_dir, - check=True, - ) - if compress: - subprocess.run( - [ - "npx", - "--no-install", - "terser", - root_dir / f"{name}.js", - "-o", - root_dir / f"{name}.js", - ], - check=True, - ) - - def get_make_flag(name): """Get flags from makefile.envs. @@ -125,16 +137,30 @@ def get_make_flag(name): return get_make_environment_vars()[name] -@functools.lru_cache(maxsize=None) +def get_pyversion(): + PYMAJOR = get_make_flag("PYMAJOR") + PYMINOR = get_make_flag("PYMINOR") + return f"python{PYMAJOR}.{PYMINOR}" + + +def get_hostsitepackages(): + return get_make_flag("HOSTSITEPACKAGES") + + +@functools.cache def get_make_environment_vars(): """Load environment variables from Makefile.envs This allows us to set all build vars in one place""" - # TODO: make this not rely on paths outside of pyodide-build - rootdir = Path(__file__).parents[2].resolve() + + if "PYODIDE_ROOT" in os.environ: + PYODIDE_ROOT = Path(os.environ["PYODIDE_ROOT"]) + else: + PYODIDE_ROOT = search_pyodide_root(os.getcwd()) + environment = {} result = subprocess.run( - ["make", "-f", str(rootdir / "Makefile.envs"), ".output_vars"], + ["make", "-f", str(PYODIDE_ROOT / "Makefile.envs"), ".output_vars"], capture_output=True, text=True, ) @@ -146,3 +172,33 @@ def get_make_environment_vars(): value = value.strip("'").strip() environment[varname] = value return environment + + +def search_pyodide_root(curdir: str | Path, *, max_depth: int = 5) -> Path: + """ + Recursively search for the root of the Pyodide repository, + by looking for the pyproject.toml file in the parent directories + which contains [tool.pyodide] section. + """ + + # We want to include "curdir" in parent_dirs, so add a garbage suffix + parent_dirs = (Path(curdir) / "garbage").parents[:max_depth] + + for base in parent_dirs: + pyproject_file = base / "pyproject.toml" + + if not pyproject_file.is_file(): + continue + + try: + with pyproject_file.open("rb") as f: + configs = tomli.load(f) + except tomli.TOMLDecodeError: + raise ValueError(f"Could not parse {pyproject_file}.") + + if "tool" in configs and "pyodide" in configs["tool"]: + return base + + raise FileNotFoundError( + "Could not find Pyodide root directory. If you are not in the Pyodide directory, set `PYODIDE_ROOT=`." + ) diff --git a/pyodide-build/pyodide_build/io.py b/pyodide-build/pyodide_build/io.py index 34b90ef0dd9..e74a8403efe 100644 --- a/pyodide-build/pyodide_build/io.py +++ b/pyodide-build/pyodide_build/io.py @@ -1,32 +1,29 @@ from pathlib import Path -from typing import Dict, Any, List, Optional - +from typing import Any, Iterator # TODO: support more complex types for validation -PACKAGE_CONFIG_SPEC: Dict[str, Dict[str, Any]] = { +PACKAGE_CONFIG_SPEC: dict[str, dict[str, Any]] = { "package": { "name": str, "version": str, + "_tag": str, }, "source": { "url": str, "extract_dir": str, "path": str, - "patches": list, # List[str] - "md5": str, "sha256": str, + "patches": list, # List[str] "extras": list, # List[Tuple[str, str]], }, "build": { - "skip_host": bool, "cflags": str, "cxxflags": str, "ldflags": str, "library": bool, "sharedlibrary": bool, "script": str, - "prereplay": str, "post": str, "replace-libs": list, "unvendor-tests": bool, @@ -46,33 +43,11 @@ } -def check_package_config( - config: Dict[str, Any], raise_errors: bool = True, file_path: Optional[Path] = None -) -> List[str]: - """Check the validity of a loaded meta.yaml file - - Currently the following checks are applied: - - - - TODO: - - check for mandatory fields - - Parameter - --------- - config - loaded meta.yaml as a dict - raise_errors - if true raise errors, otherwise return the list of error messages. - file_path - optional meta.yaml file path. Only used for more explicit error output, - when raise_errors = True. - """ - errors_msg = [] - +def _check_config_keys(config: dict[str, Any]) -> Iterator[str]: # Check top level sections wrong_keys = set(config.keys()).difference(PACKAGE_CONFIG_SPEC.keys()) if wrong_keys: - errors_msg.append( + yield ( f"Found unknown sections {list(wrong_keys)}. Expected " f"sections are {list(PACKAGE_CONFIG_SPEC)}." ) @@ -80,46 +55,149 @@ def check_package_config( # Check subsections for section_key in config: if section_key not in PACKAGE_CONFIG_SPEC: - # Don't check subsections is the main section is invalid + # Don't check subsections if the main section is invalid continue actual_keys = set(config[section_key].keys()) expected_keys = set(PACKAGE_CONFIG_SPEC[section_key].keys()) wrong_keys = set(actual_keys).difference(expected_keys) if wrong_keys: - errors_msg.append( + yield ( f"Found unknown keys " f"{[section_key + '/' + key for key in wrong_keys]}. " f"Expected keys are " f"{[section_key + '/' + key for key in expected_keys]}." ) + +def _check_config_types(config: dict[str, Any]) -> Iterator[str]: # Check value types for section_key, section in config.items(): for subsection_key, value in section.items(): try: expected_type = PACKAGE_CONFIG_SPEC[section_key][subsection_key] except KeyError: - # Unkown key, which was already reported previously, don't + # Unknown key, which was already reported previously, don't # check types continue if not isinstance(value, expected_type): - errors_msg.append( + yield ( f"Wrong type for '{section_key}/{subsection_key}': " f"expected {expected_type.__name__}, got {type(value).__name__}." ) - if raise_errors and errors_msg: + +def _check_config_source(config: dict[str, Any]) -> Iterator[str]: + if "source" not in config: + yield "Missing source section" + return + + src_metadata = config["source"] + patches = src_metadata.get("patches", []) + extras = src_metadata.get("extras", []) + + in_tree = "path" in src_metadata + from_url = "url" in src_metadata + + if not (in_tree or from_url): + yield "Source section should have a 'url' or 'path' key" + return + + if in_tree and from_url: + yield "Source section should not have both a 'url' and a 'path' key" + return + + if in_tree and (patches or extras): + yield "If source is in tree, 'source/patches' and 'source/extras' keys are not allowed" + + if from_url: + if "sha256" not in src_metadata: + yield "If source is downloaded from url, it must have a 'source/sha256' hash." + + +def _check_config_build(config: dict[str, Any]) -> Iterator[str]: + if "build" not in config: + return + build_metadata = config["build"] + library = build_metadata.get("library", False) + sharedlibrary = build_metadata.get("sharedlibrary", False) + if not library and not sharedlibrary: + return + if library and sharedlibrary: + yield "build/library and build/sharedlibrary cannot both be true." + + allowed_keys = {"library", "sharedlibrary", "script"} + typ = "library" if library else "sharedlibrary" + for key in build_metadata.keys(): + if key not in PACKAGE_CONFIG_SPEC["build"]: + continue + if key not in allowed_keys: + yield f"If building a {typ}, 'build/{key}' key is not allowed." + + +def _check_config_wheel_build(config: dict[str, Any]) -> Iterator[str]: + if "source" not in config: + return + src_metadata = config["source"] + if "url" not in src_metadata: + return + if not src_metadata["url"].endswith(".whl"): + return + patches = src_metadata.get("patches", []) + extras = src_metadata.get("extras", []) + if patches or extras: + yield "If source is a wheel, 'source/patches' and 'source/extras' keys are not allowed" + if "build" not in config: + return + build_metadata = config["build"] + allowed_keys = {"post", "unvendor-tests"} + for key in build_metadata.keys(): + if key not in PACKAGE_CONFIG_SPEC["build"]: + continue + if key not in allowed_keys: + yield f"If source is a wheel, 'build/{key}' key is not allowed" + + +def check_package_config_generate_errors( + config: dict[str, Any], +) -> Iterator[str]: + """Check the validity of a loaded meta.yaml file + + Currently the following checks are applied: + - + + TODO: + - check for mandatory fields + + Parameter + --------- + config + loaded meta.yaml as a dict + raise_errors + if true raise errors, otherwise return the list of error messages. + file_path + optional meta.yaml file path. Only used for more explicit error output, + when raise_errors = True. + """ + yield from _check_config_keys(config) + yield from _check_config_types(config) + yield from _check_config_source(config) + yield from _check_config_build(config) + yield from _check_config_wheel_build(config) + + +def check_package_config(config: dict[str, Any], file_path: Path | str | None = None): + errors_msg = list(check_package_config_generate_errors(config)) + + if errors_msg: if file_path is None: file_path = Path("meta.yaml") raise ValueError( f"{file_path} validation failed: \n - " + "\n - ".join(errors_msg) ) - return errors_msg - -def parse_package_config(path: Path, check: bool = True) -> Dict[str, Any]: +def parse_package_config(path: Path | str, check: bool = True) -> dict[str, Any]: """Load a meta.yaml file Parameters @@ -127,7 +205,7 @@ def parse_package_config(path: Path, check: bool = True) -> Dict[str, Any]: path path to the meta.yaml file check - check the consitency of the config file + check the consistency of the config file Returns ------- diff --git a/pyodide-build/pyodide_build/mkpkg.py b/pyodide-build/pyodide_build/mkpkg.py index 58bc753aee3..9e6b8af7d9f 100755 --- a/pyodide-build/pyodide_build/mkpkg.py +++ b/pyodide-build/pyodide_build/mkpkg.py @@ -4,47 +4,75 @@ import json import os import shutil -import urllib.request -import urllib.error +import subprocess import sys -from pathlib import Path -from typing import Dict, Any, Optional +import urllib.error +import urllib.request import warnings +from pathlib import Path +from typing import Any, Literal -from .io import parse_package_config - -PACKAGES_ROOT = Path(__file__).parents[2] / "packages" +from ruamel.yaml import YAML class MkpkgFailedException(Exception): pass -def _extract_sdist(pypi_metadata: Dict[str, Any]) -> Dict: - """Get sdist file path from the meta-data""" - sdist_extensions = tuple( - extension - for (name, extensions, description) in shutil.get_unpack_formats() - for extension in extensions - ) +SDIST_EXTENSIONS = tuple( + extension + for (name, extensions, description) in shutil.get_unpack_formats() + for extension in extensions +) + +def _find_sdist(pypi_metadata: dict[str, Any]) -> dict[str, Any] | None: + """Get sdist file path from the metadata""" # The first one we can use. Usually a .tar.gz for entry in pypi_metadata["urls"]: if entry["packagetype"] == "sdist" and entry["filename"].endswith( - sdist_extensions + SDIST_EXTENSIONS ): return entry + return None - raise MkpkgFailedException( - "No sdist URL found for package %s (%s)" - % ( - pypi_metadata["info"].get("name"), - pypi_metadata["info"].get("package_url"), - ) - ) +def _find_wheel(pypi_metadata: dict[str, Any]) -> dict[str, Any] | None: + """Get wheel file path from the metadata""" + for entry in pypi_metadata["urls"]: + if entry["packagetype"] == "bdist_wheel" and entry["filename"].endswith( + "py3-none-any.whl" + ): + return entry + return None + + +def _find_dist( + pypi_metadata: dict[str, Any], source_types=list[Literal["wheel", "sdist"]] +) -> dict[str, Any]: + """Find a wheel or sdist, as appropriate. -def _get_metadata(package: str, version: Optional[str] = None) -> Dict: + source_types controls which types (wheel and/or sdist) are accepted and also + the priority order. + E.g., ["wheel", "sdist"] means accept either wheel or sdist but prefer wheel. + ["sdist", "wheel"] means accept either wheel or sdist but prefer sdist. + """ + result = None + for source in source_types: + if source == "wheel": + result = _find_wheel(pypi_metadata) + if source == "sdist": + result = _find_sdist(pypi_metadata) + if result: + return result + + types_str = " or ".join(source_types) + name = pypi_metadata["info"].get("name") + url = pypi_metadata["info"].get("package_url") + raise MkpkgFailedException(f"No {types_str} found for package {name} ({url})") + + +def _get_metadata(package: str, version: str | None = None) -> dict: """Download metadata for a package from PyPI""" version = ("/" + version) if version is not None else "" url = f"https://pypi.org/pypi/{package}{version}/json" @@ -61,33 +89,35 @@ def _get_metadata(package: str, version: Optional[str] = None) -> Dict: return pypi_metadata -def _import_ruamel_yaml(): - """Import ruamel.yaml with a better error message is not installed.""" - try: - from ruamel.yaml import YAML - except ImportError as err: - raise ImportError( - "No module named 'ruamel'. " - "It can be installed with pip install ruamel.yaml" - ) from err - return YAML +def run_prettier(meta_path): + subprocess.run(["npx", "prettier", "-w", meta_path]) -def make_package(package: str, version: Optional[str] = None): +def make_package( + packages_dir: Path, + package: str, + version: str | None = None, + source_fmt: Literal["wheel", "sdist"] | None = None, +): """ Creates a template that will work for most pure Python packages, but will have to be edited for more complex things. """ print(f"Creating meta.yaml package for {package}") - YAML = _import_ruamel_yaml() yaml = YAML() pypi_metadata = _get_metadata(package, version) - sdist_metadata = _extract_sdist(pypi_metadata) - url = sdist_metadata["url"] - sha256 = sdist_metadata["digests"]["sha256"] + if source_fmt: + sources = [source_fmt] + else: + # Prefer wheel unless sdist is specifically requested. + sources = ["wheel", "sdist"] + dist_metadata = _find_dist(pypi_metadata, sources) + + url = dist_metadata["url"] + sha256 = dist_metadata["digests"]["sha256"] version = pypi_metadata["info"]["version"] homepage = pypi_metadata["info"]["home_page"] @@ -107,14 +137,23 @@ def make_package(package: str, version: Optional[str] = None): }, } - if not (PACKAGES_ROOT / package).is_dir(): - os.makedirs(PACKAGES_ROOT / package) - out_path = PACKAGES_ROOT / package / "meta.yaml" - with open(out_path, "w") as fd: - yaml.dump(yaml_content, fd) - success(f"Output written to {out_path}") + package_dir = packages_dir / package + package_dir.mkdir(parents=True, exist_ok=True) + meta_path = package_dir / "meta.yaml" + if meta_path.exists(): + raise MkpkgFailedException(f"The package {package} already exists") + + yaml.dump(yaml_content, meta_path) + try: + run_prettier(meta_path) + except FileNotFoundError: + warnings.warn("'npx' executable missing, output has not been prettified.") + success(f"Output written to {meta_path}") + + +# TODO: use rich for coloring outputs class bcolors: HEADER = "\033[95m" OKBLUE = "\033[94m" @@ -140,42 +179,45 @@ def success(msg): print(bcolors.OKBLUE + msg + bcolors.ENDC) -def update_package(package: str, update_patched: bool = True): - - YAML = _import_ruamel_yaml() +def update_package( + root: Path, + package: str, + version: str | None = None, + update_patched: bool = True, + source_fmt: Literal["wheel", "sdist"] | None = None, +): yaml = YAML() - meta_path = PACKAGES_ROOT / package / "meta.yaml" - try: - yaml_content = parse_package_config(meta_path) - except: - sys.exit(0) + meta_path = root / package / "meta.yaml" + if not meta_path.exists(): + abort(f"{meta_path} does not exist") + + yaml_content = yaml.load(meta_path.read_bytes()) if "url" not in yaml_content["source"]: - print(f"Skipping: {package} is a local package!") - sys.exit(0) + raise MkpkgFailedException(f"Skipping: {package} is a local package!") build_info = yaml_content.get("build", {}) if build_info.get("library", False) or build_info.get("sharedlibrary", False): - print(f"Skipping: {package} is a library!") - sys.exit(0) + raise MkpkgFailedException(f"Skipping: {package} is a library!") - pypi_metadata = _get_metadata(package) + if yaml_content["source"]["url"].endswith("whl"): + old_fmt = "wheel" + else: + old_fmt = "sdist" + + pypi_metadata = _get_metadata(package, version) pypi_ver = pypi_metadata["info"]["version"] local_ver = yaml_content["package"]["version"] - if pypi_ver <= local_ver: + already_up_to_date = pypi_ver <= local_ver and ( + source_fmt is None or source_fmt == old_fmt + ) + if already_up_to_date: print(f"{package} already up to date. Local: {local_ver} PyPI: {pypi_ver}") - sys.exit(0) + return print(f"{package} is out of date: {local_ver} <= {pypi_ver}.") - if set(yaml_content.keys()).difference( - ("package", "source", "test", "requirements") - ): - abort( - f"{package}: Only pure python packages can be updated using this script. " - f"Aborting." - ) if "patches" in yaml_content["source"]: if update_patched: @@ -184,16 +226,30 @@ def update_package(package: str, update_patched: bool = True): "patches (if needed) to avoid build failing." ) else: - abort(f"Pyodide applies patches to {package}. Skipping update.") + raise MkpkgFailedException( + f"Pyodide applies patches to {package}. Skipping update." + ) - sdist_metadata = _extract_sdist(pypi_metadata) + if source_fmt: + # require the type requested + sources = [source_fmt] + elif old_fmt == "wheel": + # prefer wheel to sdist + sources = ["wheel", "sdist"] + else: + # prefer sdist to wheel + sources = ["sdist", "wheel"] - yaml_content["source"]["url"] = sdist_metadata["url"] + dist_metadata = _find_dist(pypi_metadata, sources) + + yaml_content["source"]["url"] = dist_metadata["url"] yaml_content["source"].pop("md5", None) - yaml_content["source"]["sha256"] = sdist_metadata["digests"]["sha256"] + yaml_content["source"]["sha256"] = dist_metadata["digests"]["sha256"] yaml_content["package"]["version"] = pypi_metadata["info"]["version"] - with open(PACKAGES_ROOT / package / "meta.yaml", "w") as fd: - yaml.dump(yaml_content, fd) + + yaml.dump(yaml_content, meta_path) + run_prettier(meta_path) + success(f"Updated {package} from {local_ver} to {pypi_ver}.") @@ -209,6 +265,12 @@ def make_parser(parser): action="store_true", help="Update existing package if it has no patches", ) + parser.add_argument( + "--source-format", + help="Which source format is preferred. Options are wheel or sdist. " + "If none is provided, then either a wheel or an sdist will be used. " + "When updating a package, the type will be kept the same if possible.", + ) parser.add_argument( "--version", type=str, @@ -220,15 +282,35 @@ def make_parser(parser): def main(args): + PYODIDE_ROOT = os.environ.get("PYODIDE_ROOT") + if PYODIDE_ROOT is None: + raise ValueError("PYODIDE_ROOT is not set") + + PACKAGES_ROOT = Path(PYODIDE_ROOT) / "packages" + try: package = args.package[0] if args.update: - update_package(package, update_patched=True) + update_package( + PACKAGES_ROOT, + package, + args.version, + update_patched=True, + source_fmt=args.source_format, + ) return if args.update_if_not_patched: - update_package(package, update_patched=False) + update_package( + PACKAGES_ROOT, + package, + args.version, + update_patched=False, + source_fmt=args.source_format, + ) return - make_package(package, args.version) + make_package( + PACKAGES_ROOT, package, args.version, source_fmt=args.source_format + ) except MkpkgFailedException as e: # This produces two types of error messages: # diff --git a/pyodide-build/pyodide_build/pypabuild.py b/pyodide-build/pyodide_build/pypabuild.py new file mode 100644 index 00000000000..b599ac8f813 --- /dev/null +++ b/pyodide-build/pyodide_build/pypabuild.py @@ -0,0 +1,105 @@ +import contextlib +import os +import sys +import traceback +from itertools import chain +from pathlib import Path +from typing import Mapping + +from build import BuildBackendException, ProjectBuilder # type: ignore[import] +from build.__main__ import ( # type: ignore[import] + _STYLES, + _error, + _handle_build_error, + _IsolatedEnvBuilder, + _ProjectBuilder, +) +from build.env import IsolatedEnv # type: ignore[import] +from packaging.requirements import Requirement + +from .common import get_hostsitepackages, get_pyversion + +UNISOLATED_PACKAGES = ["numpy", "scipy", "cffi", "pycparser", "pythran", "cython"] + + +def symlink_unisolated_packages(env: IsolatedEnv): + pyversion = get_pyversion() + site_packages_path = f"lib/{pyversion}/site-packages" + env_site_packages = Path(env._path) / site_packages_path + host_site_packages = Path(get_hostsitepackages()) + for name in UNISOLATED_PACKAGES: + for path in chain( + host_site_packages.glob(f"{name}*"), host_site_packages.glob(f"_{name}*") + ): + (env_site_packages / path.name).unlink(missing_ok=True) + (env_site_packages / path.name).symlink_to(path) + + +def remove_unisolated_requirements(requires: set[str]) -> set[str]: + for reqstr in list(requires): + req = Requirement(reqstr) + for avoid_name in UNISOLATED_PACKAGES: + if avoid_name in req.name: + requires.remove(reqstr) + return requires + + +@contextlib.contextmanager +def replace_env(build_env: Mapping[str, str]): + old_environ = dict(os.environ) + os.environ.clear() + os.environ.update(build_env) + try: + yield + finally: + os.environ.clear() + os.environ.update(old_environ) + + +def install_reqs(env: IsolatedEnv, reqs: set[str]): + env.install(remove_unisolated_requirements(reqs)) + + +def _build_in_isolated_env( + build_env: Mapping[str, str], + builder: ProjectBuilder, + outdir: str, + distribution: str, +) -> str: + with _IsolatedEnvBuilder() as env: + builder.python_executable = env.executable + builder.scripts_dir = env.scripts_dir + # first install the build dependencies + symlink_unisolated_packages(env) + install_reqs(env, builder.build_system_requires) + installed_requires_for_build = False + try: + build_reqs = builder.get_requires_for_build(distribution) + except BuildBackendException: + pass + else: + install_reqs(env, build_reqs) + installed_requires_for_build = True + + with replace_env(build_env): + if not installed_requires_for_build: + install_reqs(env, builder.get_requires_for_build(distribution)) + return builder.build(distribution, outdir, {}) + + +def build(build_env: Mapping[str, str]): + srcdir = Path.cwd() + outdir = srcdir / "dist" + builder = _ProjectBuilder(srcdir) + distribution = "wheel" + try: + with _handle_build_error(): + built = _build_in_isolated_env( + build_env, builder, str(outdir), distribution + ) + print("{bold}{green}Successfully built {}{reset}".format(built, **_STYLES)) + except Exception as e: # pragma: no cover + tb = traceback.format_exc().strip("\n") + print("\n{dim}{}{reset}\n".format(tb, **_STYLES)) + _error(str(e)) + sys.exit(1) diff --git a/pyodide-build/pyodide_build/pywasmcross.py b/pyodide-build/pyodide_build/pywasmcross.py index 6624d5e9c92..06411c732dd 100755 --- a/pyodide-build/pyodide_build/pywasmcross.py +++ b/pyodide-build/pyodide_build/pywasmcross.py @@ -4,129 +4,64 @@ distutils has never had a proper cross-compilation story. This is a hack, which miraculously works, to get around that. -The gist is: - -- Compile the package natively, replacing calls to the compiler and linker with - wrappers that store the arguments in a log, and then delegate along to the - real native compiler and linker. - -- Remove all the native build products. - -- Play back the log, replacing the native compiler with emscripten and - adjusting include paths and flags as necessary for cross-compiling to - emscripten. This overwrites the results from the original native compilation. - -While this results in more work than strictly necessary (it builds a native -version of the package, even though we then throw it away), it seems to be the -only reliable way to automatically build a package that interleaves -configuration with build. +The gist is we compile the package replacing calls to the compiler and linker +with wrappers that adjusting include paths and flags as necessary for +cross-compiling and then pass the command long to emscripten. """ - - -from collections import namedtuple -import importlib.machinery import json import os -from pathlib import Path, PurePosixPath -import re -import subprocess -import shutil import sys +IS_MAIN = __name__ == "__main__" +if IS_MAIN: + PYWASMCROSS_ARGS = json.loads(os.environ["PYWASMCROSS_ARGS"]) + # restore __name__ so that relative imports work as we expect + __name__ = PYWASMCROSS_ARGS.pop("orig__name__") + sys.path = PYWASMCROSS_ARGS.pop("PYTHONPATH") + + PYWASMCROSS_ARGS["pythoninclude"] = os.environ["PYTHONINCLUDE"] -from typing import List, Dict, Set, Optional, overload +import re +import subprocess +from collections import namedtuple +from pathlib import Path, PurePosixPath +from typing import Any, MutableMapping, NoReturn, overload -# absolute import is necessary as this file will be symlinked -# under tools from pyodide_build import common -from pyodide_build._f2c_fixes import fix_f2c_output +from pyodide_build._f2c_fixes import fix_f2c_input, fix_f2c_output, scipy_fixes +symlinks = {"cc", "c++", "ld", "ar", "gcc", "gfortran"} + + +def symlink_dir(): + return Path(common.get_make_flag("TOOLSDIR")) / "symlinks" -symlinks = set(["cc", "c++", "ld", "ar", "gcc", "gfortran"]) ReplayArgs = namedtuple( "ReplayArgs", [ + "pkgname", "cflags", "cxxflags", "ldflags", "host_install_dir", "target_install_dir", "replace_libs", + "builddir", + "pythoninclude", ], ) -def capture_command(command: str, args: List[str]) -> int: - """ - This is called when this script is called through a symlink that looks like - a compiler or linker. - - It writes the arguments to the build.log, and then delegates to the real - native compiler or linker (unless it decides to skip host compilation). - - Returns - ------- - The exit code of the real native compiler invocation - """ - TOOLSDIR = Path(common.get_make_flag("TOOLSDIR")) - # Remove the symlink compiler from the PATH, so we can delegate to the - # native compiler - env = dict(os.environ) - path = env["PATH"] - while str(TOOLSDIR) + ":" in path: - path = path.replace(str(TOOLSDIR) + ":", "") - env["PATH"] = path - - skip_host = "SKIP_HOST" in os.environ - - # Skip compilations of C/Fortran extensions for the target environment. - # We still need to generate the output files for distutils to continue - # the build. - # TODO: This may need slight tuning for new projects. In particular, - # currently ar is not skipped, so a known failure would happen when - # we create some object files (that are empty as gcc is skipped), on - # which we run the actual ar command. - skip = False - if ( - command in ["gcc", "cc", "c++", "gfortran", "ld"] - and "-o" in args - # do not skip numpy as it is needed as build time - # dependency by other packages (e.g. matplotlib) - and skip_host - ): - out_idx = args.index("-o") - if (out_idx + 1) < len(args): - # get the index of the output file path - out_idx += 1 - with open(args[out_idx], "wb") as fh: - fh.write(b"") - skip = True - - with open("build.log", "a") as fd: - # TODO: store skip status in the build.log - json.dump([command] + args, fd) - fd.write("\n") - - if skip: - return 0 - compiler_command = [command] - if shutil.which("ccache") is not None: - # Enable ccache if it's installed - compiler_command.insert(0, "ccache") - - return subprocess.run(compiler_command + args, env=env).returncode - - -def capture_make_command_wrapper_symlinks(env: Dict[str, str]): +def make_command_wrapper_symlinks(env: MutableMapping[str, str]): """ Makes sure all the symlinks that make this script look like a compiler exist. """ - TOOLSDIR = Path(common.get_make_flag("TOOLSDIR")) exec_path = Path(__file__).resolve() + SYMLINKDIR = symlink_dir() for symlink in symlinks: - symlink_path = TOOLSDIR / symlink + symlink_path = SYMLINKDIR / symlink if os.path.lexists(symlink_path) and not symlink_path.exists(): # remove broken symlink so it can be re-created symlink_path.unlink() @@ -141,28 +76,51 @@ def capture_make_command_wrapper_symlinks(env: Dict[str, str]): env[var] = symlink -def capture_compile(*, host_install_dir: str, skip_host: bool, env: Dict[str, str]): - TOOLSDIR = Path(common.get_make_flag("TOOLSDIR")) - env = dict(env) - env["PATH"] = str(TOOLSDIR) + ":" + env["PATH"] - capture_make_command_wrapper_symlinks(env) +@overload +def compile( + env: dict[str, str], + *, + pkgname: str, + cflags: str, + cxxflags: str, + ldflags: str, + host_install_dir: str, + target_install_dir: str, + replace_libs: str, +): + ... - cmd = [sys.executable, "setup.py", "install"] - if skip_host: - env["SKIP_HOST"] = "1" - assert host_install_dir, "Missing host_install_dir" - cmd.extend(["--home", host_install_dir]) - result = subprocess.run(cmd, env=env) - if result.returncode != 0: +@overload +def compile(*, mypy__Single_overload_definition_multiple_required: int): + ... + + +def compile(env, **kwargs): + args = environment_substitute_args(kwargs, env) + args["builddir"] = str(Path(".").absolute()) + + env = dict(env) + SYMLINKDIR = symlink_dir() + env["PATH"] = f"{SYMLINKDIR}:{env['PATH']}" + args["PYTHONPATH"] = sys.path + args["orig__name__"] = __name__ + make_command_wrapper_symlinks(env) + env["PYWASMCROSS_ARGS"] = json.dumps(args) + env["_PYTHON_HOST_PLATFORM"] = common.PLATFORM + + from pyodide_build.pypabuild import build + + try: + build(env) + except BaseException: build_log_path = Path("build.log") if build_log_path.exists(): build_log_path.unlink() - result.check_returncode() - clean_out_native_artifacts() + raise -def replay_f2c(args: List[str], dryrun: bool = False) -> Optional[List[str]]: +def replay_f2c(args: list[str], dryrun: bool = False) -> list[str] | None: """Apply f2c to compilation arguments Parameters @@ -187,12 +145,26 @@ def replay_f2c(args: List[str], dryrun: bool = False) -> Optional[List[str]]: new_args = ["gcc"] found_source = False for arg in args[1:]: - if arg.endswith(".f"): - filename = os.path.abspath(arg) + if arg.endswith(".f") or arg.endswith(".F"): + filepath = Path(arg).resolve() if not dryrun: - subprocess.check_call( - ["f2c", os.path.basename(filename)], cwd=os.path.dirname(filename) - ) + fix_f2c_input(arg) + if arg.endswith(".F"): + # .F files apparently expect to be run through the C + # preprocessor (they have #ifdef's in them) + subprocess.check_call( + [ + "gcc", + "-E", + "-C", + "-P", + filepath, + "-o", + filepath.with_suffix(".f"), + ] + ) + filepath = filepath.with_suffix(".f") + subprocess.check_call(["f2c", filepath.name], cwd=filepath.parent) fix_f2c_output(arg[:-2] + ".c") new_args.append(arg[:-2] + ".c") found_source = True @@ -209,7 +181,7 @@ def replay_f2c(args: List[str], dryrun: bool = False) -> Optional[List[str]]: return new_args -def get_library_output(line: List[str]) -> Optional[str]: +def get_library_output(line: list[str]) -> str | None: """ Check if the command is a linker invocation. If so, return the name of the output file. @@ -220,7 +192,7 @@ def get_library_output(line: List[str]) -> Optional[str]: return None -def parse_replace_libs(replace_libs: str) -> Dict[str, str]: +def parse_replace_libs(replace_libs: str) -> dict[str, str]: """ Parameters ---------- @@ -247,8 +219,8 @@ def parse_replace_libs(replace_libs: str) -> Dict[str, str]: def replay_genargs_handle_dashl( - arg: str, replace_libs: Dict[str, str], used_libs: Set[str] -) -> Optional[str]: + arg: str, replace_libs: dict[str, str], used_libs: set[str] +) -> str | None: """ Figure out how to replace a `-lsomelib` argument. @@ -289,7 +261,7 @@ def replay_genargs_handle_dashl( return arg -def replay_genargs_handle_dashI(arg: str, target_install_dir: str) -> Optional[str]: +def replay_genargs_handle_dashI(arg: str, target_install_dir: str) -> str | None: """ Figure out how to replace a `-Iincludepath` argument. @@ -351,7 +323,7 @@ def replay_genargs_handle_linker_opts(arg): return None -def replay_genargs_handle_argument(arg: str) -> Optional[str]: +def replay_genargs_handle_argument(arg: str) -> str | None: """ Figure out how to replace a general argument. @@ -394,14 +366,14 @@ def replay_genargs_handle_argument(arg: str) -> Optional[str]: return arg -def replay_command_generate_args( - line: List[str], args: ReplayArgs, is_link_command: bool -) -> List[str]: +def handle_command_generate_args( + line: list[str], args: ReplayArgs, is_link_command: bool +) -> list[str]: """ - A helper command for `replay_command` that generates the new arguments for + A helper command for `handle_command` that generates the new arguments for the compilation. - Unlike `replay_command` this avoids I/O: it doesn't sys.exit, it doesn't run + Unlike `handle_command` this avoids I/O: it doesn't sys.exit, it doesn't run subprocesses, it doesn't create any files, and it doesn't write to stdout. Parameters @@ -416,11 +388,27 @@ def replay_command_generate_args( Returns ------- An updated argument list suitable for use with emscripten. + + + Examples + -------- + + >>> from collections import namedtuple + >>> Args = namedtuple('args', ['cflags', 'cxxflags', 'ldflags', 'host_install_dir','replace_libs','target_install_dir']) + >>> args = Args(cflags='', cxxflags='', ldflags='', host_install_dir='',replace_libs='',target_install_dir='') + >>> handle_command_generate_args(['gcc', 'test.c'], args, False) + ['emcc', '-Werror=implicit-function-declaration', '-Werror=mismatched-parameter-types', '-Werror=return-type', 'test.c'] """ - replace_libs = parse_replace_libs(args.replace_libs) + if "-print-multiarch" in line: + return ["echo", "wasm32-emscripten"] + for arg in line: + if arg.startswith("-print-file-name"): + return line + cmd = line[0] if cmd == "ar": - new_args = ["emar"] + line[0] = "emar" + return line elif cmd == "c++" or cmd == "g++": new_args = ["em++"] elif cmd == "cc" or cmd == "gcc" or cmd == "ld": @@ -429,14 +417,29 @@ def replay_command_generate_args( if any(arg.endswith((".cpp", ".cc")) for arg in line): new_args = ["em++"] else: - raise AssertionError(f"Unexpected command {line[0]}") + return line + + # set linker and C flags to error on anything to do with function declarations being wrong. + # In webassembly, any conflicts mean that a randomly selected 50% of calls to the function + # will fail. Better to fail at compile or link time. + if is_link_command: + new_args.append("-Wl,--fatal-warnings") + new_args.extend( + [ + "-Werror=implicit-function-declaration", + "-Werror=mismatched-parameter-types", + "-Werror=return-type", + ] + ) if is_link_command: new_args.extend(args.ldflags.split()) - elif new_args[0] == "emcc": - new_args.extend(args.cflags.split()) - elif new_args[0] == "em++": - new_args.extend(args.cflags.split() + args.cxxflags.split()) + if "-c" in line: + if new_args[0] == "emcc": + new_args.extend(args.cflags.split()) + elif new_args[0] == "em++": + new_args.extend(args.cflags.split() + args.cxxflags.split()) + new_args.extend(["-I", args.pythoninclude]) optflags_valid = [f"-O{tok}" for tok in "01234sz"] optflag = None @@ -454,7 +457,7 @@ def replay_command_generate_args( debugflag = arg break - used_libs: Set[str] = set() + used_libs: set[str] = set() # Go through and adjust arguments for arg in line[1:]: # The native build is possibly multithreaded, but the emscripten one @@ -479,6 +482,7 @@ def replay_command_generate_args( ): continue + replace_libs = parse_replace_libs(args.replace_libs) if arg.startswith("-l"): result = replay_genargs_handle_dashl(arg, replace_libs, used_libs) elif arg.startswith("-I"): @@ -490,158 +494,87 @@ def replay_command_generate_args( if result: new_args.append(result) + return new_args -def replay_command( - line: List[str], args: ReplayArgs, dryrun: bool = False -) -> Optional[List[str]]: - """Handle a compilation command +def handle_command( + line: list[str], + args: ReplayArgs, +) -> NoReturn: + """Handle a compilation command. Exit with an appropriate exit code when done. Parameters ---------- line : iterable an iterable with the compilation arguments args : {object, namedtuple} - an container with additional compilation options, - in particular containing ``args.cflags``, ``args.cxxflags``, and ``args.ldflags`` - dryrun : bool, default=False - if True do not run the resulting command, only return it - - Examples - -------- - - >>> from collections import namedtuple - >>> Args = namedtuple('args', ['cflags', 'cxxflags', 'ldflags', 'host_install_dir','replace_libs','target_install_dir']) - >>> args = Args(cflags='', cxxflags='', ldflags='', host_install_dir='',replace_libs='',target_install_dir='') - >>> replay_command(['gcc', 'test.c'], args, dryrun=True) - emcc test.c - ['emcc', 'test.c'] + an container with additional compilation options, in particular + containing ``args.cflags``, ``args.cxxflags``, and ``args.ldflags`` """ # some libraries have different names on wasm e.g. png16 = png - - # This is a special case to skip the compilation tests in numpy that aren't - # actually part of the build - for arg in line: - if r"/file.c" in arg or "_configtest" in arg: - return None - if re.match(r"/tmp/.*/source\.[bco]+", arg): - return None - if arg == "-print-multiarch": - return None - if arg.startswith("/tmp"): - return None - if arg.startswith("-print-file-name"): - return None - if arg == "/dev/null": - return None - - library_output = get_library_output(line) - is_link_cmd = library_output is not None + is_link_cmd = get_library_output(line) is not None if line[0] == "gfortran": + if "-dumpversion" in line: + sys.exit(subprocess.run(line).returncode) tmp = replay_f2c(line) if tmp is None: - return None + sys.exit(0) line = tmp - new_args = replay_command_generate_args(line, args, is_link_cmd) + new_args = handle_command_generate_args(line, args, is_link_cmd) - # This can only be used for incremental rebuilds -- it generates - # an error during clean build of numpy - # if os.path.isfile(output): - # print('SKIPPING: ' + ' '.join(new_args)) - # return + if args.pkgname == "scipy": + scipy_fixes(new_args) - print(" ".join(new_args)) + # FIXME: For some unknown reason, + # opencv-python tries to link a same library (libopencv_world.a) multiple times, + # which leads to 'duplicated symbols' error. + if args.pkgname == "opencv-python": + duplicated_lib = "libopencv_world.a" + _new_args = [] + for arg in new_args: + if duplicated_lib in arg and arg in _new_args: + continue + _new_args.append(arg) - if not dryrun: - returncode = subprocess.run(new_args).returncode - if returncode != 0: - sys.exit(returncode) + new_args = _new_args - # Emscripten .so files shouldn't have the native platform slug - if library_output: - renamed = library_output - for ext in importlib.machinery.EXTENSION_SUFFIXES: - if ext == ".so": - continue - if renamed.endswith(ext): - renamed = renamed[: -len(ext)] + ".so" - break - if not dryrun and library_output != renamed: - os.rename(library_output, renamed) - return new_args + returncode = subprocess.run(new_args).returncode + if returncode != 0: + sys.exit(returncode) + + sys.exit(returncode) def environment_substitute_args( - args: Dict[str, str], env: Dict[str, str] = None -) -> Dict[str, str]: + args: dict[str, str], env: dict[str, str] | None = None +) -> dict[str, Any]: if env is None: env = dict(os.environ) subbed_args = {} for arg, value in args.items(): - for e_name, e_value in env.items(): - value = value.replace(f"$({e_name})", e_value) + if isinstance(value, str): + for e_name, e_value in env.items(): + value = value.replace(f"$({e_name})", e_value) subbed_args[arg] = value return subbed_args -@overload -def replay_compile( - *, - cflags: str, - cxxflags: str, - ldflags: str, - host_install_dir: str, - target_install_dir: str, - replace_libs: str, - replay_from: int = 1, -): - ... - - -@overload -def replay_compile(*, _this_is_just_here_to_appease_mypy: str): - ... - - -def replay_compile(replay_from: int = 1, **kwargs): - args = ReplayArgs(**environment_substitute_args(kwargs)) - # If pure Python, there will be no build.log file, which is fine -- just do - # nothing - build_log_path = Path("build.log") - if not build_log_path.is_file(): - return - - lines_str = ( - subprocess.check_output(["wc", "-l", str(build_log_path)]) - .decode() - .split(" ")[0] - ) - num_lines = str(lines_str) - with open(build_log_path, "r") as fd: - num_lines = sum(1 for _1 in fd) # type: ignore - fd.seek(0) - for idx, line_str in enumerate(fd): - if idx < replay_from - 1: - continue - line = json.loads(line_str) - print(f"[line {idx + 1} of {num_lines}]") - replay_command(line, args) - - -def clean_out_native_artifacts(): - for root, dirs, files in os.walk("."): - for file in files: - path = Path(root) / file - if path.suffix in (".o", ".so", ".a"): - path.unlink() +if IS_MAIN: + path = os.environ["PATH"] + SYMLINKDIR = symlink_dir() + while f"{SYMLINKDIR}:" in path: + path = path.replace(f"{SYMLINKDIR}:", "") + os.environ["PATH"] = path + REPLAY_ARGS = ReplayArgs(**PYWASMCROSS_ARGS) -if __name__ == "__main__": basename = Path(sys.argv[0]).name + args = list(sys.argv) + args[0] = basename if basename in symlinks: - sys.exit(capture_command(basename, sys.argv[1:])) + sys.exit(handle_command(args, REPLAY_ARGS)) else: raise Exception(f"Unexpected invocation '{basename}'") diff --git a/pyodide-build/pyodide_build/serve.py b/pyodide-build/pyodide_build/serve.py index 6287d919bfc..3cd8c74eb30 100644 --- a/pyodide-build/pyodide_build/serve.py +++ b/pyodide-build/pyodide_build/serve.py @@ -1,11 +1,9 @@ -import os -import sys import argparse import http.server -import socketserver +import os import pathlib - -BUILD_PATH = pathlib.Path(__file__).resolve().parents[2] / "build" +import socketserver +import sys class Handler(http.server.SimpleHTTPRequestHandler): @@ -18,16 +16,20 @@ def end_headers(self): def make_parser(parser): - parser.description = "Start a server with the supplied " "build_dir and port." + parser.description = "Start a server with the supplied dist-dir and port." parser.add_argument( - "--build_dir", + "--dist-dir", action="store", type=str, - default=BUILD_PATH, - help="set the build directory", + default="dist", + help="set the dist directory (default: %(default)s)", ) parser.add_argument( - "--port", action="store", type=int, default=8000, help="set the PORT number" + "--port", + action="store", + type=int, + default=8000, + help="set the PORT number (default: %(default)s)", ) return parser @@ -38,11 +40,11 @@ def server(port): def main(args): - build_dir = args.build_dir + build_dir = pathlib.Path(args.build_dir).resolve() port = args.port httpd = server(port) os.chdir(build_dir) - print("serving from {0} at localhost:".format(build_dir) + str(port)) + print(f"serving from {build_dir} at localhost:{port}") try: httpd.serve_forever() except KeyboardInterrupt: diff --git a/pyodide-build/pyodide_build/testing.py b/pyodide-build/pyodide_build/testing.py index 0adad236299..6c21d74fafc 100644 --- a/pyodide-build/pyodide_build/testing.py +++ b/pyodide-build/pyodide_build/testing.py @@ -1,8 +1,9 @@ -import pytest -import inspect -from typing import Callable, Dict, List, Optional, Union import contextlib +import inspect from base64 import b64encode +from typing import Callable, Collection + +import pytest def _run_in_pyodide_get_source(f): @@ -26,18 +27,18 @@ def chunkstring(string, length): def run_in_pyodide( - _function: Optional[Callable] = None, + _function: Callable | None = None, *, standalone: bool = False, module_scope: bool = False, - packages: List[str] = [], - xfail_browsers: Dict[str, str] = {}, - driver_timeout: Optional[Union[str, int]] = None, + packages: Collection[str] = (), + xfail_browsers: dict[str, str] | None = None, + driver_timeout: float | None = None, ) -> Callable: """ This decorator can be called in two ways --- with arguments and without arguments. If it is called without arguments, then the `_function` kwarg - catches the function the decorator is applied to. Otherewise, standalone + catches the function the decorator is applied to. Otherwise, standalone and packages are the actual arguments to the decorator. See docs/testing.md for details on how to use this. @@ -48,14 +49,16 @@ def run_in_pyodide( Whether to use a standalone selenium instance to run the test or not packages : List[str] List of packages to load before running the test - driver_timeout : Optional[Union[str, int]] + driver_timeout : Optional[float] selenium driver timeout (in seconds) """ + xfail_browsers_local = xfail_browsers or {} + def decorator(f): def inner(selenium): - if selenium.browser in xfail_browsers: - xfail_message = xfail_browsers[selenium.browser] + if selenium.browser in xfail_browsers_local: + xfail_message = xfail_browsers_local[selenium.browser] pytest.xfail(xfail_message) with set_webdriver_script_timeout(selenium, driver_timeout): if len(packages) > 0: @@ -83,7 +86,7 @@ def inner(selenium): eval_code.callKwargs( {{ source : atob({encoded}.join("")), - globals : pyodide._module.globals, + globals : pyodide._api.globals, filename : {filename!r} }} ); @@ -104,17 +107,17 @@ def inner(selenium): if standalone: - def wrapped(selenium_standalone): # type: ignore + def wrapped(selenium_standalone): inner(selenium_standalone) elif module_scope: - def wrapped(selenium_module_scope): # type: ignore + def wrapped(selenium_module_scope): # type: ignore[misc] inner(selenium_module_scope) else: - def wrapped(selenium): # type: ignore + def wrapped(selenium): # type: ignore[misc] inner(selenium) return wrapped @@ -126,7 +129,7 @@ def wrapped(selenium): # type: ignore @contextlib.contextmanager -def set_webdriver_script_timeout(selenium, script_timeout: Optional[Union[int, float]]): +def set_webdriver_script_timeout(selenium, script_timeout: float | None): """Set selenium script timeout Parameters @@ -144,7 +147,7 @@ def set_webdriver_script_timeout(selenium, script_timeout: Optional[Union[int, f selenium.set_script_timeout(selenium.script_timeout) -def parse_driver_timeout(request) -> Optional[Union[int, float]]: +def parse_driver_timeout(request) -> float | None: """Parse driver timeout value from pytest request object""" mark = request.node.get_closest_marker("driver_timeout") if mark is None: diff --git a/pyodide-build/pyodide_build/tests/_test_packages/beautifulsoup4/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/beautifulsoup4/meta.yaml new file mode 100644 index 00000000000..e033c26d0d9 --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/beautifulsoup4/meta.yaml @@ -0,0 +1,12 @@ +package: + name: beautifulsoup4 + version: 4.10.0 +requirements: + run: + - soupsieve +source: + sha256: 9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf + url: https://files.pythonhosted.org/packages/69/bf/f0f194d3379d3f3347478bd267f754fc68c11cbf2fe302a6ab69447b1417/beautifulsoup4-4.10.0-py3-none-any.whl +test: + imports: + - bs4 diff --git a/pyodide-build/pyodide_build/tests/_test_packages/micropip/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/micropip/meta.yaml new file mode 100644 index 00000000000..e25cd7fbbff --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/micropip/meta.yaml @@ -0,0 +1,13 @@ +package: + name: micropip + version: "0.1" +requirements: + run: + - pyparsing + - packaging + - distutils # TODO: remove once there is a release with https://github.com/pypa/packaging/pull/396 +source: + path: src +test: + imports: + - micropip diff --git a/pyodide-build/pyodide_build/tests/_test_packages/micropip/src/.gitkeep b/pyodide-build/pyodide_build/tests/_test_packages/micropip/src/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyodide-build/pyodide_build/tests/_test_packages/packaging/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/packaging/meta.yaml new file mode 100644 index 00000000000..999b72ed6b2 --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/packaging/meta.yaml @@ -0,0 +1,12 @@ +package: + name: packaging + version: "21.3" +source: + sha256: ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 + url: https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl +requirements: + run: + - pyparsing +test: + imports: + - packaging diff --git a/pyodide-build/pyodide_build/tests/_test_packages/pkg_1/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/pkg_1/meta.yaml new file mode 100644 index 00000000000..35963d286da --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/pkg_1/meta.yaml @@ -0,0 +1,13 @@ +package: + name: pkg_1 + version: 1.0.0 +source: + sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + url: https://dummy-url-that-not-exists.com/pkg_1-py3-none-any.whl +requirements: + run: + - pkg_1_1 + - pkg_3 +test: + imports: + - pkg_1 diff --git a/pyodide-build/pyodide_build/tests/_test_packages/pkg_1_1/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/pkg_1_1/meta.yaml new file mode 100644 index 00000000000..906ed105e19 --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/pkg_1_1/meta.yaml @@ -0,0 +1,9 @@ +package: + name: pkg_1_1 + version: 1.0.0 +source: + sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + url: https://dummy-url-that-not-exists.com/pkg_1_1-py3-none-any.whl +test: + imports: + - pkg_1_1 diff --git a/pyodide-build/pyodide_build/tests/_test_packages/pkg_2/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/pkg_2/meta.yaml new file mode 100644 index 00000000000..c03b78a06c8 --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/pkg_2/meta.yaml @@ -0,0 +1,12 @@ +package: + name: pkg_2 + version: 1.0.0 +source: + sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + url: https://dummy-url-that-not-exists.com/pkg_2-py3-none-any.whl +requirements: + run: + - pkg_3 +test: + imports: + - pkg_2 diff --git a/pyodide-build/pyodide_build/tests/_test_packages/pkg_3/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/pkg_3/meta.yaml new file mode 100644 index 00000000000..ea9886fe42c --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/pkg_3/meta.yaml @@ -0,0 +1,12 @@ +package: + name: pkg_3 + version: 1.0.0 +source: + sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + url: https://dummy-url-that-not-exists.com/pkg_3-py3-none-any.whls +requirements: + run: + - pkg_3_1 +test: + imports: + - pkg_3 diff --git a/pyodide-build/pyodide_build/tests/_test_packages/pkg_3_1/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/pkg_3_1/meta.yaml new file mode 100644 index 00000000000..6d31d5fc89c --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/pkg_3_1/meta.yaml @@ -0,0 +1,9 @@ +package: + name: pkg_3_1 + version: 1.0.0 +source: + sha256: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef + url: https://dummy-url-that-not-exists.com/pkg_3_1-py3-none-any.whl +test: + imports: + - pkg_3_1 diff --git a/pyodide-build/pyodide_build/tests/_test_packages/pyparsing/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/pyparsing/meta.yaml new file mode 100644 index 00000000000..349c2556f17 --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/pyparsing/meta.yaml @@ -0,0 +1,9 @@ +package: + name: pyparsing + version: 3.0.7 +source: + sha256: a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484 + url: https://files.pythonhosted.org/packages/80/c1/23fd82ad3121656b585351aba6c19761926bb0db2ebed9e4ff09a43a3fcc/pyparsing-3.0.7-py3-none-any.whl +test: + imports: + - pyparsing diff --git a/pyodide-build/pyodide_build/tests/_test_packages/soupsieve/meta.yaml b/pyodide-build/pyodide_build/tests/_test_packages/soupsieve/meta.yaml new file mode 100644 index 00000000000..92180530d06 --- /dev/null +++ b/pyodide-build/pyodide_build/tests/_test_packages/soupsieve/meta.yaml @@ -0,0 +1,9 @@ +package: + name: soupsieve + version: 2.3.1 +source: + sha256: 1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb + url: https://files.pythonhosted.org/packages/72/a6/fd01694427f1c3fcadfdc5f1de901b813b9ac756f0806ef470cfed1de281/soupsieve-2.3.1-py3-none-any.whl +test: + imports: + - soupsieve diff --git a/pyodide-build/pyodide_build/tests/test_buildall.py b/pyodide-build/pyodide_build/tests/test_buildall.py index 03582b9c0e3..b0430c7944a 100644 --- a/pyodide-build/pyodide_build/tests/test_buildall.py +++ b/pyodide-build/pyodide_build/tests/test_buildall.py @@ -1,12 +1,12 @@ from collections import namedtuple +from pathlib import Path from time import sleep -from pathlib import Path +import pytest from pyodide_build import buildall -import pytest -PACKAGES_DIR = (Path(__file__).parents[3] / "packages").resolve() +PACKAGES_DIR = Path(__file__).parent / "_test_packages" def test_generate_dependency_graph(): @@ -22,28 +22,32 @@ def test_generate_dependency_graph(): assert pkg_map["beautifulsoup4"].dependents == set() -def test_generate_packages_json(): - pkg_map = buildall.generate_dependency_graph( - PACKAGES_DIR, {"beautifulsoup4", "micropip"} - ) +def test_generate_packages_json(tmp_path): + pkg_map = buildall.generate_dependency_graph(PACKAGES_DIR, {"pkg_1", "pkg_2"}) + for pkg in pkg_map.values(): + pkg.file_name = pkg.file_name or pkg.name + ".file" + # Write dummy package file for SHA-256 hash verification + with open(tmp_path / pkg.file_name, "w") as f: + f.write(pkg.name) - package_data = buildall.generate_packages_json(pkg_map) + package_data = buildall.generate_packages_json(tmp_path, pkg_map) assert set(package_data.keys()) == {"info", "packages"} assert package_data["info"] == {"arch": "wasm32", "platform": "Emscripten-1.0"} assert set(package_data["packages"]) == { - "test", - "distutils", - "pyparsing", - "packaging", - "soupsieve", - "beautifulsoup4", - "micropip", + "pkg_1", + "pkg_1_1", + "pkg_2", + "pkg_3", + "pkg_3_1", } - assert package_data["packages"]["micropip"] == { - "name": "micropip", - "version": "0.1", - "depends": ["pyparsing", "packaging", "distutils"], - "imports": ["micropip"], + assert package_data["packages"]["pkg_1"] == { + "name": "pkg_1", + "version": "1.0.0", + "file_name": "pkg_1.file", + "depends": ["pkg_1_1", "pkg_3"], + "imports": ["pkg_1"], + "install_dir": "site", + "sha_256": "c1e38241013b5663e902fff97eb8585e98e6df446585da1dcf2ad121b52c2143", } @@ -57,32 +61,23 @@ def build(self, outputdir: Path, args) -> None: monkeypatch.setattr(buildall, "Package", MockPackage) - pkg_map = buildall.generate_dependency_graph(PACKAGES_DIR, {"lxml", "micropip"}) + pkg_map = buildall.generate_dependency_graph(PACKAGES_DIR, {"pkg_1", "pkg_2"}) - Args = namedtuple("args", ["n_jobs", "force_rebuild"]) + Args = namedtuple("Args", ["n_jobs", "force_rebuild"]) buildall.build_from_graph( pkg_map, Path("."), Args(n_jobs=n_jobs, force_rebuild=True) ) assert set(build_list) == { - "packaging", - "pyparsing", - "soupsieve", - "beautifulsoup4", - "micropip", - "webencodings", - "html5lib", - "cssselect", - "lxml", - "libxslt", - "libxml", - "zlib", - "libiconv", - "six", + "pkg_1", + "pkg_1_1", + "pkg_2", + "pkg_3", + "pkg_3_1", } - assert build_list.index("pyparsing") < build_list.index("packaging") - assert build_list.index("packaging") < build_list.index("micropip") - assert build_list.index("soupsieve") < build_list.index("beautifulsoup4") + assert build_list.index("pkg_1_1") < build_list.index("pkg_1") + assert build_list.index("pkg_3") < build_list.index("pkg_1") + assert build_list.index("pkg_3_1") < build_list.index("pkg_3") @pytest.mark.parametrize("n_jobs", [1, 4]) @@ -102,7 +97,7 @@ def build(self, outputdir: Path, args) -> None: pkg_map = buildall.generate_dependency_graph(PACKAGES_DIR, packages={"*"}) - Args = namedtuple("args", ["n_jobs", "force_rebuild"]) + Args = namedtuple("Args", ["n_jobs", "force_rebuild"]) buildall.build_from_graph( pkg_map, Path("."), Args(n_jobs=n_jobs, force_rebuild=False) ) @@ -118,10 +113,10 @@ def build(self, outputdir: Path, args) -> None: monkeypatch.setattr(buildall, "Package", MockPackage) - pkg_map = buildall.generate_dependency_graph(PACKAGES_DIR, {"lxml"}) + pkg_map = buildall.generate_dependency_graph(PACKAGES_DIR, {"pkg_1"}) with pytest.raises(ValueError, match="Failed build"): - Args = namedtuple("args", ["n_jobs", "force_rebuild"]) + Args = namedtuple("Args", ["n_jobs", "force_rebuild"]) buildall.build_from_graph( pkg_map, Path("."), Args(n_jobs=n_jobs, force_rebuild=True) ) diff --git a/pyodide-build/pyodide_build/tests/test_buildpkg.py b/pyodide-build/pyodide_build/tests/test_buildpkg.py index b1cb81c9f68..ea17ef49136 100644 --- a/pyodide-build/pyodide_build/tests/test_buildpkg.py +++ b/pyodide-build/pyodide_build/tests/test_buildpkg.py @@ -1,7 +1,6 @@ import shutil import subprocess import time - from pathlib import Path import pytest @@ -9,29 +8,31 @@ from pyodide_build import buildpkg from pyodide_build.io import parse_package_config +PACKAGES_DIR = Path(__file__).parent / "_test_packages" + def test_subprocess_with_shared_env(): - p = buildpkg.BashRunnerWithSharedEnvironment() - p.env.pop("A", None) + with buildpkg.BashRunnerWithSharedEnvironment() as p: + p.env.pop("A", None) - res = p.run("A=6; echo $A", stdout=subprocess.PIPE) - assert res.stdout == b"6\n" - assert p.env.get("A", None) is None + res = p.run("A=6; echo $A", stdout=subprocess.PIPE) + assert res.stdout == b"6\n" + assert p.env.get("A", None) is None - p.run("export A=2") - assert p.env["A"] == "2" + p.run("export A=2") + assert p.env["A"] == "2" - res = p.run("echo $A", stdout=subprocess.PIPE) - assert res.stdout == b"2\n" + res = p.run("echo $A", stdout=subprocess.PIPE) + assert res.stdout == b"2\n" - res = p.run("A=6; echo $A", stdout=subprocess.PIPE) - assert res.stdout == b"6\n" - assert p.env.get("A", None) == "6" + res = p.run("A=6; echo $A", stdout=subprocess.PIPE) + assert res.stdout == b"6\n" + assert p.env.get("A", None) == "6" - p.env["A"] = "7" - res = p.run("echo $A", stdout=subprocess.PIPE) - assert res.stdout == b"7\n" - assert p.env["A"] == "7" + p.env["A"] = "7" + res = p.run("echo $A", stdout=subprocess.PIPE) + assert res.stdout == b"7\n" + assert p.env["A"] == "7" def test_prepare_source(monkeypatch): @@ -42,19 +43,8 @@ def test_prepare_source(monkeypatch): test_pkgs = [] - # tarballname == version - test_pkgs.append(parse_package_config("./packages/scipy/meta.yaml")) - test_pkgs.append(parse_package_config("./packages/numpy/meta.yaml")) - - # tarballname != version - test_pkgs.append( - { - "package": {"name": "pyyaml", "version": "5.3.1"}, - "source": { - "url": "https://files.pythonhosted.org/packages/64/c2/b80047c7ac2478f9501676c988a5411ed5572f35d1beff9cae07d321512c/PyYAML-5.3.1.tar.gz" - }, - } - ) + test_pkgs.append(parse_package_config(PACKAGES_DIR / "packaging/meta.yaml")) + test_pkgs.append(parse_package_config(PACKAGES_DIR / "micropip/meta.yaml")) for pkg in test_pkgs: pkg["source"]["patches"] = [] @@ -67,6 +57,8 @@ def test_prepare_source(monkeypatch): srcpath = buildpath / source_dir_name buildpkg.prepare_source(pkg_root, buildpath, srcpath, src_metadata) + assert srcpath.is_dir() + @pytest.mark.parametrize("is_library", [True, False]) def test_run_script(is_library, tmpdir): @@ -74,9 +66,9 @@ def test_run_script(is_library, tmpdir): src_dir = Path(tmpdir.mkdir("build/package_name")) script = "touch out.txt" build_metadata = {"script": script, "library": is_library} - shared_env = buildpkg.BashRunnerWithSharedEnvironment() - buildpkg.run_script(build_dir, src_dir, build_metadata, shared_env) - assert (src_dir / "out.txt").exists() + with buildpkg.BashRunnerWithSharedEnvironment() as shared_env: + buildpkg.run_script(build_dir, src_dir, build_metadata, shared_env) + assert (src_dir / "out.txt").exists() def test_run_script_environment(tmpdir): @@ -84,10 +76,10 @@ def test_run_script_environment(tmpdir): src_dir = Path(tmpdir.mkdir("build/package_name")) script = "export A=2" build_metadata = {"script": script, "library": False} - shared_env = buildpkg.BashRunnerWithSharedEnvironment() - shared_env.env.pop("A", None) - buildpkg.run_script(build_dir, src_dir, build_metadata, shared_env) - assert shared_env.env["A"] == "2" + with buildpkg.BashRunnerWithSharedEnvironment() as shared_env: + shared_env.env.pop("A", None) + buildpkg.run_script(build_dir, src_dir, build_metadata, shared_env) + assert shared_env.env["A"] == "2" def test_unvendor_tests(tmpdir): diff --git a/pyodide-build/pyodide_build/tests/test_common.py b/pyodide-build/pyodide_build/tests/test_common.py index 39448df8a30..7efe4f6bd6f 100644 --- a/pyodide-build/pyodide_build/tests/test_common.py +++ b/pyodide-build/pyodide_build/tests/test_common.py @@ -1,70 +1,67 @@ +import pytest + from pyodide_build.common import ( + ALWAYS_PACKAGES, + CORE_PACKAGES, + CORE_SCIPY_PACKAGES, + UNVENDORED_STDLIB_MODULES, _parse_package_subset, - get_make_flag, + find_matching_wheels, get_make_environment_vars, -) # noqa + get_make_flag, + search_pyodide_root, +) def test_parse_package_subset(): - # micropip is always included - assert _parse_package_subset("numpy,pandas") == { - "pyparsing", - "packaging", - "micropip", - "numpy", - "pandas", - } + assert ( + _parse_package_subset("numpy,pandas") + == { + "numpy", + "pandas", + } + | UNVENDORED_STDLIB_MODULES + | ALWAYS_PACKAGES + ) # duplicates are removed - assert _parse_package_subset("numpy,numpy") == { - "pyparsing", - "packaging", - "micropip", - "numpy", - } + assert ( + _parse_package_subset("numpy,numpy") + == { + "numpy", + } + | UNVENDORED_STDLIB_MODULES + | ALWAYS_PACKAGES + ) # no empty package name included, spaces are handled - assert _parse_package_subset("x, a, b, c ,,, d,,") == { - "pyparsing", - "packaging", - "micropip", - "x", - "a", - "b", - "c", - "d", - } + assert ( + _parse_package_subset("x, a, b, c ,,, d,,") + == { + "x", + "a", + "b", + "c", + "d", + } + | UNVENDORED_STDLIB_MODULES + | ALWAYS_PACKAGES + ) - assert _parse_package_subset("core") == { - "pyparsing", - "packaging", - "pytz", - "Jinja2", - "micropip", - "regex", - "fpcast-test", - "sharedlib-test-py", - } + assert ( + _parse_package_subset("core") + == CORE_PACKAGES | UNVENDORED_STDLIB_MODULES | ALWAYS_PACKAGES + ) # by default core packages are built assert _parse_package_subset(None) == _parse_package_subset("core") - assert _parse_package_subset("min-scipy-stack") == { - "pyparsing", - "packaging", - "pytz", - "Jinja2", - "micropip", - "regex", - "fpcast-test", - "numpy", - "scipy", - "pandas", - "matplotlib", - "scikit-learn", - "joblib", - "pytest", - "sharedlib-test-py", - } + assert ( + _parse_package_subset("min-scipy-stack") + == CORE_SCIPY_PACKAGES + | CORE_PACKAGES + | UNVENDORED_STDLIB_MODULES + | ALWAYS_PACKAGES + ) # reserved key words can be combined with other packages assert _parse_package_subset("core, unknown") == _parse_package_subset("core") | { "unknown" @@ -84,3 +81,50 @@ def test_get_make_environment_vars(): assert "SIDE_MODULE_CFLAGS" in vars assert "SIDE_MODULE_CXXFLAGS" in vars assert "TOOLSDIR" in vars + + +def test_wheel_paths(): + from pathlib import Path + + old_version = "cp38" + PYMAJOR = int(get_make_flag("PYMAJOR")) + PYMINOR = int(get_make_flag("PYMINOR")) + current_version = f"cp{PYMAJOR}{PYMINOR}" + future_version = f"cp{PYMAJOR}{PYMINOR + 1}" + strings = [] + + for interp in [ + old_version, + current_version, + future_version, + "py3", + "py2", + "py2.py3", + ]: + for abi in [interp, "abi3", "none"]: + for arch in ["emscripten_wasm32", "linux_x86_64", "any"]: + strings.append(f"wrapt-1.13.3-{interp}-{abi}-{arch}.whl") + + paths = [Path(x) for x in strings] + assert [x.stem.split("-", 2)[-1] for x in find_matching_wheels(paths)] == [ + f"{current_version}-{current_version}-emscripten_wasm32", + f"{current_version}-abi3-emscripten_wasm32", + f"{current_version}-none-emscripten_wasm32", + f"{old_version}-abi3-emscripten_wasm32", + "py3-none-emscripten_wasm32", + "py2.py3-none-emscripten_wasm32", + "py3-none-any", + "py2.py3-none-any", + ] + + +def test_search_pyodide_root(tmp_path): + pyproject_file = tmp_path / "pyproject.toml" + pyproject_file.write_text("[tool.pyodide]") + assert search_pyodide_root(tmp_path) == tmp_path + assert search_pyodide_root(tmp_path / "subdir") == tmp_path + assert search_pyodide_root(tmp_path / "subdir" / "subdir") == tmp_path + + pyproject_file.unlink() + with pytest.raises(FileNotFoundError): + search_pyodide_root(tmp_path) diff --git a/pyodide-build/pyodide_build/tests/test_mkpkg.py b/pyodide-build/pyodide_build/tests/test_mkpkg.py index e69bf544c71..0f12274222b 100644 --- a/pyodide-build/pyodide_build/tests/test_mkpkg.py +++ b/pyodide-build/pyodide_build/tests/test_mkpkg.py @@ -1,7 +1,7 @@ import os from pathlib import Path -import pytest +import pytest import yaml from pkg_resources import parse_version @@ -13,12 +13,11 @@ # unlikely to fail. -def test_mkpkg(tmpdir, monkeypatch, capsys): - assert pyodide_build.mkpkg.PACKAGES_ROOT.exists() - +@pytest.mark.parametrize("source_fmt", ["wheel", "sdist"]) +def test_mkpkg(tmpdir, capsys, source_fmt): base_dir = Path(str(tmpdir)) - monkeypatch.setattr(pyodide_build.mkpkg, "PACKAGES_ROOT", base_dir) - pyodide_build.mkpkg.make_package("idna") + + pyodide_build.mkpkg.make_package(base_dir, "idna", None, source_fmt) assert os.listdir(base_dir) == ["idna"] meta_path = base_dir / "idna" / "meta.yaml" assert meta_path.exists() @@ -28,31 +27,46 @@ def test_mkpkg(tmpdir, monkeypatch, capsys): db = parse_package_config(meta_path) assert db["package"]["name"] == "idna" - assert db["source"]["url"].endswith(".tar.gz") + if source_fmt == "wheel": + assert db["source"]["url"].endswith(".whl") + else: + assert db["source"]["url"].endswith(".tar.gz") -def test_mkpkg_update(tmpdir, monkeypatch): - pytest.importorskip("ruamel") +@pytest.mark.parametrize("old_dist_type", ["wheel", "sdist"]) +@pytest.mark.parametrize("new_dist_type", ["wheel", "sdist", "same"]) +def test_mkpkg_update(tmpdir, old_dist_type, new_dist_type): base_dir = Path(str(tmpdir)) - monkeypatch.setattr(pyodide_build.mkpkg, "PACKAGES_ROOT", base_dir) - db_init = { + old_ext = ".tar.gz" if old_dist_type == "sdist" else ".whl" + old_url = "https:///idna-2.0" + old_ext + db_init: dict[str, dict[str, str | list[str]]] = { "package": {"name": "idna", "version": "2.0"}, "source": { "sha256": "b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "url": "https:///idna-2.0.tar.gz", + "url": old_url, }, "test": {"imports": ["idna"]}, } - os.mkdir(base_dir / "idna") - meta_path = base_dir / "idna" / "meta.yaml" - with open(meta_path, "w") as fh: - yaml.dump(db_init, fh) - pyodide_build.mkpkg.update_package("idna") + package_dir = base_dir / "idna" + package_dir.mkdir(parents=True) + meta_path = package_dir / "meta.yaml" + with open(meta_path, "w") as f: + yaml.dump(db_init, f) + source_fmt = new_dist_type + if new_dist_type == "same": + source_fmt = None + pyodide_build.mkpkg.update_package(base_dir, "idna", None, False, source_fmt) db = parse_package_config(meta_path) assert list(db.keys()) == list(db_init.keys()) assert parse_version(db["package"]["version"]) > parse_version( - db_init["package"]["version"] + db_init["package"]["version"] # type: ignore[arg-type] ) + if new_dist_type == "wheel": + assert db["source"]["url"].endswith(".whl") + elif new_dist_type == "sdist": + assert db["source"]["url"].endswith(".tar.gz") + else: + assert db["source"]["url"].endswith(old_ext) diff --git a/pyodide-build/pyodide_build/tests/test_pywasmcross.py b/pyodide-build/pyodide_build/tests/test_pywasmcross.py index 343600028ea..b8430c1423d 100644 --- a/pyodide-build/pyodide_build/tests/test_pywasmcross.py +++ b/pyodide-build/pyodide_build/tests/test_pywasmcross.py @@ -2,7 +2,7 @@ import pytest -from pyodide_build.pywasmcross import replay_command # noqa: E402 +from pyodide_build.pywasmcross import handle_command_generate_args # noqa: E402 from pyodide_build.pywasmcross import replay_f2c # noqa: E402 from pyodide_build.pywasmcross import environment_substitute_args @@ -17,6 +17,7 @@ class BuildArgs: replace_libs: str = "" host_install_dir: str = "" target_install_dir: str = "" + pythoninclude: str = "python/include" def _args_wrapper(func): @@ -37,16 +38,40 @@ def _inner(line, *pargs): return _inner -replay_command_wrap = _args_wrapper(replay_command) f2c_wrap = _args_wrapper(replay_f2c) +def generate_args(line: str, args, is_link_cmd=False) -> str: + splitline = line.split() + res = handle_command_generate_args(splitline, args, is_link_cmd) + for arg in [ + "-Werror=implicit-function-declaration", + "-Werror=mismatched-parameter-types", + "-Werror=return-type", + ]: + assert arg in res + res.remove(arg) + if "-c" in splitline: + include_index = res.index("python/include") + del res[include_index] + del res[include_index - 1] + + if is_link_cmd: + arg = "-Wl,--fatal-warnings" + assert arg in res + res.remove(arg) + return " ".join(res) + + def test_handle_command(): args = BuildArgs() - assert replay_command_wrap("gcc -print-multiarch", args) is None - assert replay_command_wrap("gcc test.c", args) == "emcc test.c" + assert handle_command_generate_args(["gcc", "-print-multiarch"], args, True) == [ # type: ignore[arg-type] + "echo", + "wasm32-emscripten", + ] + assert generate_args("gcc test.c", args) == "emcc test.c" assert ( - replay_command_wrap("gcc -shared -c test.o -o test.so", args) + generate_args("gcc -shared -c test.o -o test.so", args, True) == "emcc -c test.o -o test.so" ) @@ -57,8 +82,8 @@ def test_handle_command(): ldflags="-lm", ) assert ( - replay_command_wrap("gcc -I./lib1 test.cpp -o test.o", args) - == "em++ -I./lib2 -std=c++11 -I./lib1 test.cpp -o test.o" + generate_args("gcc -I./lib1 -c test.cpp -o test.o", args) + == "em++ -I./lib2 -std=c++11 -I./lib1 -c test.cpp -o test.o" ) # check ldflags injection @@ -71,7 +96,7 @@ def test_handle_command(): target_install_dir="", ) assert ( - replay_command_wrap("gcc -shared -c test.o -o test.so", args) + generate_args("gcc -shared -c test.o -o test.so", args, True) == "emcc -lm -c test.o -o test.so" ) @@ -80,22 +105,20 @@ def test_handle_command(): replace_libs="bob=fred", ) assert ( - replay_command_wrap("gcc -shared test.o -lbob -ljim -ljim -o test.so", args) + generate_args("gcc -shared test.o -lbob -ljim -ljim -o test.so", args) == "emcc test.o -lfred -ljim -o test.so" ) - # compilation checks in numpy - assert replay_command_wrap("gcc /usr/file.c", args) is None - def test_handle_command_ldflags(): # Make sure to remove unsupported link flags for wasm-ld args = BuildArgs() assert ( - replay_command_wrap( + generate_args( "gcc -Wl,--strip-all,--as-needed -Wl,--sort-common,-z,now,-Bsymbolic-functions -shared -c test.o -o test.so", args, + True, ) == "emcc -Wl,-z,now -c test.o -o test.so" ) @@ -115,8 +138,8 @@ def test_handle_command_optflags(in_ext, out_ext, executable, flag_name): args = BuildArgs(**{flag_name: "-Oz"}) assert ( - replay_command_wrap(f"gcc -O3 test.{in_ext} -o test.{out_ext}", args) - == f"{executable} -Oz test.{in_ext} -o test.{out_ext}" + generate_args(f"gcc -O3 -c test.{in_ext} -o test.{out_ext}", args, True) + == f"{executable} -Oz -c test.{in_ext} -o test.{out_ext}" ) @@ -131,16 +154,16 @@ def test_f2c(): def test_conda_unsupported_args(): - # Check that compile arguments that are not suported by emcc and are sometimes + # Check that compile arguments that are not supported by emcc and are sometimes # used in conda are removed. args = BuildArgs() - assert replay_command_wrap( + assert generate_args( "gcc -shared -c test.o -B /compiler_compat -o test.so", args ) == ("emcc -c test.o -o test.so") - assert replay_command_wrap( - "gcc -shared -c test.o -Wl,--sysroot=/ -o test.so", args - ) == ("emcc -c test.o -o test.so") + assert generate_args("gcc -shared -c test.o -Wl,--sysroot=/ -o test.so", args) == ( + "emcc -c test.o -o test.so" + ) def test_environment_var_substitution(monkeypatch): diff --git a/pyodide-build/pyodide_build/tests/test_run_docker.py b/pyodide-build/pyodide_build/tests/test_run_docker.py index bb473e3558c..1c9e67df33a 100644 --- a/pyodide-build/pyodide_build/tests/test_run_docker.py +++ b/pyodide-build/pyodide_build/tests/test_run_docker.py @@ -1,24 +1,23 @@ -from pathlib import Path +import os import subprocess +from pathlib import Path -BASE_DIR = Path(__file__).parents[3] +PYODIDE_ROOT = Path(os.environ.get("PYODIDE_ROOT", os.getcwd())) def test_run_docker_script(): res = subprocess.run( - ["bash", str(BASE_DIR / "run_docker"), "--help"], + ["bash", str(PYODIDE_ROOT / "run_docker"), "--help"], check=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, ) assert "Usage: run_docker" in res.stdout.decode("utf-8") res = subprocess.run( - ["bash", str(BASE_DIR / "run_docker"), "--invalid-param"], + ["bash", str(PYODIDE_ROOT / "run_docker"), "--invalid-param"], check=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, ) assert res.returncode > 0 assert "Unknown option --invalid-param" in res.stderr.decode("utf-8") diff --git a/pyodide-build/setup.cfg b/pyodide-build/setup.cfg index 504c6720431..8f767dd5612 100644 --- a/pyodide-build/setup.cfg +++ b/pyodide-build/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pyodide-build -version = 0.19.0 +version = 0.20.0dev0 author = Pyodide developers description = "Tools for building Pyodide" long_description = file: README.md @@ -22,9 +22,19 @@ python_requires = >=3.8 install_requires = pyyaml cython<3.0 + ruamel.yaml + packaging + wheel + tomli + build==0.7.0 + [options.entry_points] console_scripts = pyodide-build = pyodide_build.__main__:main +[options.extras_require] +test = + pytest + [options.packages.find] where = . diff --git a/pyodide_env.sh b/pyodide_env.sh index 297f5f8f64b..9659b221a5f 100755 --- a/pyodide_env.sh +++ b/pyodide_env.sh @@ -1,13 +1,17 @@ #!/bin/bash # get the absolute path of the root folder -ROOT=`cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 ; pwd -P` +# shellcheck disable=SC2164 +ROOT=$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 ; pwd -P) # emsdk_env.sh is fairly noisy, and suppress error message if the file doesn't # exist yet (i.e. before building emsdk) +# shellcheck source=/dev/null source "$ROOT/emsdk/emsdk/emsdk_env.sh" 2> /dev/null || true export PATH="$ROOT/node_modules/.bin/:$ROOT/emsdk/emsdk/ccache/git-emscripten_64bit/bin:$PATH:$ROOT/packages/.artifacts/bin/" -export EM_DIR=$(dirname $(which emcc.py || echo ".")) +EMCC_PATH=$(which emcc.py || echo ".") +EM_DIR=$(dirname "$EMCC_PATH") +export EM_DIR # Following two variables are set by emsdk activated otherwise export _EMCC_CCACHE=1 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..9aac1290a9d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,59 @@ +[tool.mypy] +python_version = "3.10" +mypy_path = ["src/py", "pyodide-build"] +show_error_codes = true +warn_unreachable = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] + +# Strict checks +warn_unused_configs = true +check_untyped_defs = true +disallow_any_generics = false +disallow_subclassing_any = false +disallow_untyped_calls = false +disallow_untyped_defs = false +disallow_incomplete_defs = false +disallow_untyped_decorators = false +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_return_any = false +no_implicit_reexport = false +strict_equality = false + +[[tool.mypy.overrides]] +module = [ + "_pyodide_core", + "docutils.parsers.rst", + "js", + "pyodide_js", + "pytest", + "ruamel.yaml", + "matplotlib.*", + "PIL.*", +] +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = [ + "micropip.externals.*", +] +check_untyped_defs = false + +[tool.pycln] +all = true + +[tool.isort] +profile = "black" +known_first_party = [ + "pyodide", + "pyodide_js", + "micropip", + "pyodide_build", + "_pyodide", +] +known_third_party = [ + "build", +] + +[tool.pyodide] diff --git a/repository-structure.md b/repository-structure.md index c82bb1fd79f..fb0180025f6 100644 --- a/repository-structure.md +++ b/repository-structure.md @@ -3,7 +3,7 @@ initialization-time (or import-time) dependencies. 1. CPython 2. The py/\_pyodide package which is a Python package with pure Python code - avaiable in the inner stage of the Pyodide bootstrap process. + available in the inner stage of the Pyodide bootstrap process. 3. The core/pyodide code, implemented in a mix of C and JavaScript, which embeds the CPython interpreter in an emscripten application. This relies on py/pyodide and js/pyodide at runtime. The final stage of initialization is to diff --git a/requirements.txt b/requirements.txt index cb72bacde9a..05f48a76f82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,8 @@ - # core - cython<3.0 - packaging - pyyaml ruamel.yaml # lint - black - flake8 - mypy==0.812 + pre-commit # testing + build==0.7.0 hypothesis pexpect pytest @@ -17,4 +12,4 @@ pytest-instafail pytest-rerunfailures pytest-xdist - selenium==4.0.0.b3 + selenium==4.1.0 diff --git a/run_docker b/run_docker index b471304377f..14e8df719c8 100755 --- a/run_docker +++ b/run_docker @@ -1,8 +1,8 @@ #!/usr/bin/env bash PYODIDE_IMAGE_REPO="pyodide" -PYODIDE_IMAGE_TAG="19" -PYODIDE_PREBUILT_IMAGE_TAG="0.19.0" +PYODIDE_IMAGE_TAG="20220411-chrome99-firefox98" +PYODIDE_PREBUILT_IMAGE_TAG="0.20.0" DEFAULT_PYODIDE_DOCKER_IMAGE="${PYODIDE_IMAGE_REPO}/pyodide-env:${PYODIDE_IMAGE_TAG}" DEFAULT_PYODIDE_SYSTEM_PORT="none" DOCKER_COMMAND="/bin/bash" @@ -11,13 +11,12 @@ DOCKER_INTERACTIVE="--interactive" USER_HOME="/src/.docker_home" USER_NAME="$(id -u -n)" -USER_PASS="x" USER_ID="$(id -u)" USER_GID="$(id -g)" USER_COMMENT_FIELD="${USER_NAME} pyodide user alias" USER_INTERPRETER="/sbin/nologin" -USER_ACCOUNT_INFO="${USER_NAME}:${USER_PASS}:${USER_ID}:${USER_GID}:${USER_COMMENT_FIELD}:${USER_HOME}:${USER_INTERPRETER}" -USER_FLAG="--user $USER_ID:$USER_GID" +USER_FLAG=("--user" "$USER_ID:$USER_GID") +HEALTHCHECK_MESSAGE="Done initialization" set -eo pipefail @@ -83,7 +82,7 @@ do shift ;; --root) - USER_FLAG="" + USER_FLAG=() shift ;; -*) @@ -91,7 +90,7 @@ do error ;; *) - DOCKER_COMMAND="$@" + DOCKER_COMMAND="$*" break ;; esac @@ -104,14 +103,14 @@ PYODIDE_DOCKER_IMAGE=${PYODIDE_DOCKER_IMAGE:-${DEFAULT_PYODIDE_DOCKER_IMAGE}} # in case the port is not a number, do not bind the port case $PYODIDE_SYSTEM_PORT in none) - PORT_CONFIGURATION_LINE="" + PORT_CONFIGURATION_LINE=() ;; ''|*[!0-9]*) # contains a non-digit character, therefore it is not a number echo "WARNING: Invalid port argument '$PYODIDE_SYSTEM_PORT'. Port binding disabled." - PORT_CONFIGURATION_LINE="" + PORT_CONFIGURATION_LINE=() ;; *) - PORT_CONFIGURATION_LINE="-p $PYODIDE_SYSTEM_PORT:$PYODIDE_DOCKER_PORT" + PORT_CONFIGURATION_LINE=("-p" "$PYODIDE_SYSTEM_PORT:$PYODIDE_DOCKER_PORT") ;; esac @@ -121,25 +120,41 @@ mkdir -p .docker_home # then run forever CONTAINER=$(\ docker run \ - $PORT_CONFIGURATION_LINE \ + "${PORT_CONFIGURATION_LINE[@]}" \ -d --rm \ - -v $PWD:/src \ + -v "$PWD":/src \ --user root \ --shm-size 2g \ "${PYODIDE_DOCKER_IMAGE}" \ /bin/bash -c " \ - echo '${USER_ACCOUNT_INFO}' >> /etc/passwd ; \ + groupadd '$USER_GID'; \ + useradd \ + --home-dir '$USER_HOME' \ + --uid '$USER_ID' \ + --gid '$USER_GID' \ + --comment '$USER_COMMENT_FIELD' \ + --shell '$USER_INTERPRETER' \ + --groups sudo \ + $USER_NAME \ + ; \ + echo '%sudo ALL=(ALL:ALL) NOPASSWD:ALL' >> /etc/sudoers ; \ + echo '$HEALTHCHECK_MESSAGE'; \ tail -f /dev/null \ " \ ) +until docker logs "$CONTAINER" 2>&1 | grep -q "$HEALTHCHECK_MESSAGE"; do + echo "Waiting for initialization to finish..." + sleep 0.2 +done + EXIT_STATUS=0 # Execute the provided command as the host user with HOME=/src docker exec \ - $DOCKER_INTERACTIVE --tty \ - $USER_FLAG \ - $CONTAINER \ + "$DOCKER_INTERACTIVE" --tty \ + "${USER_FLAG[@]}" \ + "$CONTAINER" \ /bin/bash -c "${DOCKER_COMMAND}" || EXIT_STATUS=$? -docker kill $CONTAINER > /dev/null +docker kill "$CONTAINER" > /dev/null exit $EXIT_STATUS diff --git a/setup.cfg b/setup.cfg index f54326d68d5..7a6daab4a3c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ testpaths = [bumpversion] -current_version = 0.19.0 +current_version = 0.20.0 commit = True tag = True tag_name = {new_version} diff --git a/src/core/error_handling.c b/src/core/error_handling.c index 5093d94f2c8..a687235e291 100644 --- a/src/core/error_handling.c +++ b/src/core/error_handling.c @@ -22,7 +22,7 @@ EM_JS(void, console_error, (char* msg), { // Right now this is dead code (probably), please don't remove it. // Intended for debugging purposes. EM_JS(void, console_error_obj, (JsRef obj), { - console.error(Module.hiwire.get_value(obj)); + console.error(Hiwire.get_value(obj)); }); /** @@ -46,8 +46,7 @@ set_error(PyObject* err) * err - The error object */ EM_JS_REF(JsRef, new_error, (const char* msg, PyObject* err), { - return Module.hiwire.new_value( - new Module.PythonError(UTF8ToString(msg), err)); + return Hiwire.new_value(new API.PythonError(UTF8ToString(msg), err)); }); /** @@ -130,7 +129,7 @@ restore_sys_last_exception(void* value) return success; } -EM_JS(void, fail_test, (), { Module.fail_test = true; }) +EM_JS(void, fail_test, (), { API.fail_test = true; }) /** * Calls traceback.format_exception(type, value, traceback) and joins the @@ -214,10 +213,10 @@ EM_JS(void, log_python_error, (JsRef jserror), { // If a js error occurs in here, it's a weird edge case. This will probably // never happen, but for maximum paranoia let's double check. try { - let msg = Module.hiwire.get_value(jserror).message; + let msg = Hiwire.get_value(jserror).message; console.warn("Python exception:\n" + msg + "\n"); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } }); @@ -233,73 +232,6 @@ pythonexc2js() hiwire_throw_error(jserror); } -char* error__js_funcname_string = ""; -char* error__js_filename_string = "???.js"; - -EM_JS_NUM(errcode, error_handling_init_js, (), { - Module.handle_js_error = function(e) - { - if (e.pyodide_fatal_error) { - throw e; - } - if (e instanceof Module._PropagatePythonError) { - // Python error indicator is already set in this case. If this branch is - // not taken, Python error indicator should be unset, and we have to set - // it. In this case we don't want to tamper with the traceback. - return; - } - let restored_error = false; - if (e instanceof Module.PythonError) { - // Try to restore the original Python exception. - restored_error = _restore_sys_last_exception(e.__error_address); - } - if (!restored_error) { - // Wrap the JavaScript error - let eidx = Module.hiwire.new_value(e); - let err = _JsProxy_create(eidx); - _set_error(err); - _Py_DecRef(err); - Module.hiwire.decref(eidx); - } - // Add a marker to the traceback to indicate that we passed through "native" - // frames. - // TODO? Use stacktracejs to add more detailed info here. - __PyTraceback_Add(HEAPU32[_error__js_funcname_string / 4], - HEAPU32[_error__js_filename_string / 4], - -1); - }; - class PythonError extends Error - { - constructor(message, error_address) - { - super(message); - this.name = this.constructor.name; - // The address of the error we are wrapping. We may later compare this - // against sys.last_value. - // WARNING: we don't own a reference to this pointer, dereferencing it - // may be a use-after-free error! - this.__error_address = error_address; - } - }; - Module.PythonError = PythonError; - // A special marker. If we call a CPython API from an EM_JS function and the - // CPython API sets an error, we might want to return an error status back to - // C keeping the current Python error flag. This signals to the EM_JS wrappers - // that the Python error flag is set and to leave it alone and return the - // appropriate error value (either NULL or -1). - class _PropagatePythonError extends Error - { - constructor() - { - Module.fail_test = true; - super("If you are seeing this message, an internal Pyodide error has " + - "occurred. Please report it to the Pyodide maintainers."); - } - }; - Module._PropagatePythonError = _PropagatePythonError; - return 0; -}) - PyObject* trigger_fatal_error(PyObject* mod, PyObject* _args) { @@ -307,12 +239,24 @@ trigger_fatal_error(PyObject* mod, PyObject* _args) Py_UNREACHABLE(); } +/** + * This is for testing fatal errors in test_pyodide + */ +PyObject* +raw_call(PyObject* mod, PyObject* jsproxy) +{ + JsRef func = JsProxy_AsJs(jsproxy); + EM_ASM(Hiwire.get_value($0)(), func); + Py_RETURN_NONE; +} + static PyMethodDef methods[] = { { "trigger_fatal_error", trigger_fatal_error, METH_NOARGS, }, + { "raw_call", raw_call, METH_O }, { NULL } /* Sentinel */ }; @@ -337,8 +281,6 @@ error_handling_init(PyObject* core_module) PyObject_SetAttrString(core_module, "ConversionError", conversion_error)); FAIL_IF_MINUS_ONE(PyModule_AddFunctions(core_module, methods)); - FAIL_IF_MINUS_ONE(error_handling_init_js()); - tbmod = PyImport_ImportModule("traceback"); FAIL_IF_NULL(tbmod); diff --git a/src/core/error_handling.ts b/src/core/error_handling.ts new file mode 100644 index 00000000000..845c9d41854 --- /dev/null +++ b/src/core/error_handling.ts @@ -0,0 +1,335 @@ +import ErrorStackParser from "error-stack-parser"; +declare var Module: any; +declare var Hiwire: any; +declare var API: any; +declare var Tests: any; + +/** + * Dump the Python traceback to the browser console. + * + * @private + */ +API.dump_traceback = function () { + const fd_stdout = 1; + Module.__Py_DumpTraceback(fd_stdout, Module._PyGILState_GetThisThreadState()); +}; + +function ensureCaughtObjectIsError(e: any) { + if (typeof e === "string") { + // Sometimes emscripten throws a raw string... + e = new Error(e); + } else if ( + typeof e !== "object" || + e === null || + typeof e.stack !== "string" || + typeof e.message !== "string" + ) { + // We caught something really weird. Be brave! + let msg = `A value of type ${typeof e} with tag ${Object.prototype.toString.call( + e + )} was thrown as an error!`; + try { + msg += `\nString interpolation of the thrown value gives """${e}""".`; + } catch (e) { + msg += `\nString interpolation of the thrown value fails.`; + } + try { + msg += `\nThe thrown value's toString method returns """${e.toString()}""".`; + } catch (e) { + msg += `\nThe thrown value's toString method fails.`; + } + e = new Error(msg); + } + // Post conditions: + // 1. typeof e is object + // 2. hiwire_is_error(e) returns true + return e; +} + +let fatal_error_occurred = false; +/** + * Signal a fatal error. + * + * Dumps the Python traceback, shows a JavaScript traceback, and prints a clear + * message indicating a fatal error. It then dummies out the public API so that + * further attempts to use Pyodide will clearly indicate that Pyodide has failed + * and can no longer be used. pyodide._module is left accessible, and it is + * possible to continue using Pyodide for debugging purposes if desired. + * + * @argument e {Error} The cause of the fatal error. + * @private + */ +API.fatal_error = function (e: any) { + if (e && e.pyodide_fatal_error) { + return; + } + if (fatal_error_occurred) { + console.error("Recursive call to fatal_error. Inner error was:"); + console.error(e); + return; + } + if (typeof e === "number") { + // Hopefully a C++ exception? Have to do some conversion work. + e = convertCppException(e); + } else { + e = ensureCaughtObjectIsError(e); + } + // Mark e so we know not to handle it later in EM_JS wrappers + e.pyodide_fatal_error = true; + fatal_error_occurred = true; + console.error( + "Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers." + ); + console.error("The cause of the fatal error was:"); + if (API.inTestHoist) { + // Test hoist won't print the error object in a useful way so convert it to + // string. + console.error(e.toString()); + console.error(e.stack); + } else { + console.error(e); + } + try { + API.dump_traceback(); + for (let key of Object.keys(API.public_api)) { + if (key.startsWith("_") || key === "version") { + continue; + } + Object.defineProperty(API.public_api, key, { + enumerable: true, + configurable: true, + get: () => { + throw new Error( + "Pyodide already fatally failed and can no longer be used." + ); + }, + }); + } + if (API.on_fatal) { + API.on_fatal(e); + } + } catch (err2) { + console.error("Another error occurred while handling the fatal error:"); + console.error(err2); + } + throw e; +}; + +class CppException extends Error { + ty: string; + constructor(ty: string, msg: string) { + super(msg); + this.ty = ty; + } +} +Object.defineProperty(CppException.prototype, "name", { + get() { + return `${this.constructor.name} ${this.ty}`; + }, +}); + +/** + * + * Return the type name, whether the pointer inherits from exception, and the + * vtable pointer for the type. + * + * This code is based on imitating: + * 1. the implementation of __cxa_find_matching_catch + * 2. the disassembly from: + * ```C++ + * try { + * ... + * } catch(exception e){ + * ... + * } + * + * @param ptr + * @returns + * exc_type_name : the type name of the exception, as would be reported by + * `typeid(type).name()` but also demangled. + * + * is_exception_subclass : true if the object is a subclass of exception. In + * this case we will use `exc.what()` to get an error message. + * + * adjusted_ptr : The adjusted vtable pointer for the exception to use to invoke + * exc.what(). + * + * @private + */ +function cppExceptionInfo(ptr: number): [string, boolean, number] { + const base_exception_type = Module._exc_type(); + const ei = new Module.ExceptionInfo(ptr); + const caught_exception_type = ei.get_type(); + const stackTop = Module.stackSave(); + const exceptionThrowBuf = Module.stackAlloc(4); + Module.HEAP32[exceptionThrowBuf / 4] = ptr; + const exc_type_name = Module.demangle( + Module.UTF8ToString(Module._exc_typename(caught_exception_type)) + ); + const is_exception_subclass = !!Module.___cxa_can_catch( + base_exception_type, + caught_exception_type, + exceptionThrowBuf + ); + const adjusted_ptr = Module.HEAP32[exceptionThrowBuf / 4]; + Module.stackRestore(stackTop); + return [exc_type_name, is_exception_subclass, adjusted_ptr]; +} + +function convertCppException(ptr: number): CppException { + const [exc_type_name, is_exception_subclass, adjusted_ptr] = + cppExceptionInfo(ptr); + let msg; + if (is_exception_subclass) { + // If the ptr inherits from exception, we can use exception.what() to + // generate a message + const msgPtr = Module._exc_what(adjusted_ptr); + msg = Module.UTF8ToString(msgPtr); + } else { + msg = `The exception is an object of type ${exc_type_name} at address ${ptr} which does not inherit from std::exception`; + } + return new CppException(exc_type_name, msg); +} +// Expose for testing +Tests.convertCppException = convertCppException; + +function isPyodideFrame(frame: ErrorStackParser.StackFrame): boolean { + const fileName = frame.fileName || ""; + if (fileName.includes("wasm-function")) { + return true; + } + if (!fileName.includes("pyodide.asm.js")) { + return false; + } + let funcName = frame.functionName || ""; + if (funcName.startsWith("Object.")) { + funcName = funcName.slice("Object.".length); + } + if (funcName in API.public_api && funcName !== "PythonError") { + frame.functionName = funcName; + return false; + } + return true; +} + +function isErrorStart(frame: ErrorStackParser.StackFrame): boolean { + if (!isPyodideFrame(frame)) { + return false; + } + const funcName = frame.functionName; + return funcName === "PythonError" || funcName === "new_error"; +} + +Module.handle_js_error = function (e: any) { + if (e && e.pyodide_fatal_error) { + throw e; + } + if (e instanceof Module._PropagatePythonError) { + // Python error indicator is already set in this case. If this branch is + // not taken, Python error indicator should be unset, and we have to set + // it. In this case we don't want to tamper with the traceback. + return; + } + let restored_error = false; + if (e instanceof API.PythonError) { + // Try to restore the original Python exception. + restored_error = Module._restore_sys_last_exception(e.__error_address); + } + let stack: any; + let weirdCatch; + try { + stack = ErrorStackParser.parse(e); + } catch (_) { + weirdCatch = true; + } + if (weirdCatch) { + e = ensureCaughtObjectIsError(e); + } + if (!restored_error) { + // Wrap the JavaScript error + let eidx = Hiwire.new_value(e); + let err = Module._JsProxy_create(eidx); + Module._set_error(err); + Module._Py_DecRef(err); + Hiwire.decref(eidx); + } + if (weirdCatch) { + // In this case we have no stack frames so we can quit + return; + } + if (isErrorStart(stack[0])) { + while (isPyodideFrame(stack[0])) { + stack.shift(); + } + } + // Add the Javascript stack frames to the Python traceback + for (const frame of stack) { + if (isPyodideFrame(frame)) { + break; + } + const funcnameAddr = Module.stringToNewUTF8(frame.functionName || "???"); + const fileNameAddr = Module.stringToNewUTF8(frame.fileName || "???.js"); + Module.__PyTraceback_Add(funcnameAddr, fileNameAddr, frame.lineNumber); + Module._free(funcnameAddr); + Module._free(fileNameAddr); + } +}; + +/** + * A JavaScript error caused by a Python exception. + * + * In order to reduce the risk of large memory leaks, the ``PythonError`` + * contains no reference to the Python exception that caused it. You can find + * the actual Python exception that caused this error as `sys.last_value + * `_. + * + * See :ref:`type-translations-errors` for more information. + * + * .. admonition:: Avoid Stack Frames + * :class: warning + * + * If you make a :any:`PyProxy` of ``sys.last_value``, you should be + * especially careful to :any:`destroy() ` it when you are + * done. You may leak a large amount of memory including the local + * variables of all the stack frames in the traceback if you don't. The + * easiest way is to only handle the exception in Python. + */ +export class PythonError extends Error { + /** The address of the error we are wrapping. We may later compare this + * against sys.last_value. + * WARNING: we don't own a reference to this pointer, dereferencing it + * may be a use-after-free error! + * @private + */ + __error_address: number; + + constructor(message: string, error_address: number) { + const oldLimit = Error.stackTraceLimit; + Error.stackTraceLimit = Infinity; + super(message); + Error.stackTraceLimit = oldLimit; + this.__error_address = error_address; + } +} +Object.defineProperty(PythonError.prototype, "name", { + value: PythonError.name, +}); +API.PythonError = PythonError; +// A special marker. If we call a CPython API from an EM_JS function and the +// CPython API sets an error, we might want to return an error status back to +// C keeping the current Python error flag. This signals to the EM_JS wrappers +// that the Python error flag is set and to leave it alone and return the +// appropriate error value (either NULL or -1). +class _PropagatePythonError extends Error { + constructor() { + API.fail_test = true; + super( + "If you are seeing this message, an internal Pyodide error has " + + "occurred. Please report it to the Pyodide maintainers." + ); + } +} +Object.defineProperty(_PropagatePythonError.prototype, "name", { + value: _PropagatePythonError.name, +}); +Module._PropagatePythonError = _PropagatePythonError; diff --git a/src/core/error_handling_cpp.cpp b/src/core/error_handling_cpp.cpp new file mode 100644 index 00000000000..bf3f22569bc --- /dev/null +++ b/src/core/error_handling_cpp.cpp @@ -0,0 +1,13 @@ +#include +#include +using namespace std; + +extern "C" +{ + + const char* exc_what(exception& e) { return e.what(); } + + const std::type_info* exc_type() { return &typeid(exception); } + + const char* exc_typename(std::type_info* type) { return type->name(); } +} diff --git a/src/core/hiwire.c b/src/core/hiwire.c index 5aa933b5ade..9afb71af82d 100644 --- a/src/core/hiwire.c +++ b/src/core/hiwire.c @@ -24,9 +24,15 @@ hiwire_from_bool(bool boolean) return boolean ? Js_true : Js_false; } +// clang-format off EM_JS(bool, hiwire_to_bool, (JsRef val), { - return !!Module.hiwire.get_value(val); + return !!Hiwire.get_value(val); }); +// clang-format on + +#ifdef DEBUG_F +bool tracerefs; +#endif EM_JS_NUM(int, hiwire_init, (), { let _hiwire = { @@ -43,27 +49,26 @@ EM_JS_NUM(int, hiwire_init, (), { // conventions. counter : new Uint32Array([1]) }; - Module.hiwire = {}; - Module.hiwire.UNDEFINED = DEREF_U8(_Js_undefined, 0); - Module.hiwire.JSNULL = DEREF_U8(_Js_null, 0); - Module.hiwire.TRUE = DEREF_U8(_Js_true, 0); - Module.hiwire.FALSE = DEREF_U8(_Js_false, 0); - - _hiwire.objects.set(Module.hiwire.UNDEFINED, [ undefined, -1 ]); - _hiwire.objects.set(Module.hiwire.JSNULL, [ null, -1 ]); - _hiwire.objects.set(Module.hiwire.TRUE, [ true, -1 ]); - _hiwire.objects.set(Module.hiwire.FALSE, [ false, -1 ]); - let hiwire_next_permanent = Module.hiwire.FALSE + 2; + Hiwire.UNDEFINED = DEREF_U8(_Js_undefined, 0); + Hiwire.JSNULL = DEREF_U8(_Js_null, 0); + Hiwire.TRUE = DEREF_U8(_Js_true, 0); + Hiwire.FALSE = DEREF_U8(_Js_false, 0); + + _hiwire.objects.set(Hiwire.UNDEFINED, [ undefined, -1 ]); + _hiwire.objects.set(Hiwire.JSNULL, [ null, -1 ]); + _hiwire.objects.set(Hiwire.TRUE, [ true, -1 ]); + _hiwire.objects.set(Hiwire.FALSE, [ false, -1 ]); + let hiwire_next_permanent = Hiwire.FALSE + 2; #ifdef DEBUG_F - Module.hiwire._hiwire = _hiwire; + Hiwire._hiwire = _hiwire; let many_objects_warning_threshold = 200; #endif - Module.hiwire.new_value = function(jsval) + Hiwire.new_value = function(jsval) { // Should we guard against duplicating standard values? - // Probably not worth it for performance: it's harmless to ocassionally + // Probably not worth it for performance: it's harmless to occasionally // duplicate. Maybe in test builds we could raise if jsval is a standard // value? while (_hiwire.objects.has(_hiwire.counter[0])) { @@ -82,13 +87,15 @@ EM_JS_NUM(int, hiwire_init, (), { many_objects_warning_threshold += 100; } #endif -#ifdef HW_TRACE_REFS - console.warn("hw.new_value", idval, jsval); +#ifdef DEBUG_F + if (DEREF_U8(_tracerefs, 0)) { + console.warn("hw.new_value", idval, jsval); + } #endif return idval; }; - Module.hiwire.intern_object = function(obj) + Hiwire.intern_object = function(obj) { let id = hiwire_next_permanent; hiwire_next_permanent += 2; @@ -97,23 +104,23 @@ EM_JS_NUM(int, hiwire_init, (), { }; // for testing purposes. - Module.hiwire.num_keys = function(){ + Hiwire.num_keys = function(){ // clang-format off return Array.from(_hiwire.objects.keys()).filter((x) => x % 2).length // clang-format on }; - Module.hiwire.get_value = function(idval) + Hiwire.get_value = function(idval) { if (!idval) { - Module.fail_test = true; + API.fail_test = true; // clang-format off // This might have happened because the error indicator is set. Let's // check. if (_PyErr_Occurred()) { // This will lead to a more helpful error message. let exc = _wrap_exception(); - let e = Module.hiwire.pop_value(exc); + let e = Hiwire.pop_value(exc); console.error( `Internal error: Argument '${idval}' to hiwire.get_value is falsy. ` + "This was probably because the Python error indicator was set when get_value was called. " + @@ -142,7 +149,7 @@ EM_JS_NUM(int, hiwire_init, (), { return _hiwire.objects.get(idval)[0]; }; - Module.hiwire.decref = function(idval) + Hiwire.decref = function(idval) { // clang-format off if ((idval & 1) === 0) { @@ -150,6 +157,11 @@ EM_JS_NUM(int, hiwire_init, (), { // We don't reference count interned values. return; } +#ifdef DEBUG_F + if(DEREF_U8(_tracerefs, 0)){ + console.warn("hw.decref", idval, _hiwire.objects.get(idval)); + } +#endif let new_refcnt = --_hiwire.objects.get(idval)[1]; if (new_refcnt === 0) { _hiwire.objects.delete(idval); @@ -157,17 +169,25 @@ EM_JS_NUM(int, hiwire_init, (), { // clang-format on }; - Module.hiwire.incref = function(idval) { _hiwire.objects.get(idval)[1]++; }; + Hiwire.incref = function(idval) + { + _hiwire.objects.get(idval)[1]++; +#ifdef DEBUG_F + if (DEREF_U8(_tracerefs, 0)) { + console.warn("hw.incref", idval, _hiwire.objects.get(idval)); + } +#endif + }; - Module.hiwire.pop_value = function(idval) + Hiwire.pop_value = function(idval) { - let result = Module.hiwire.get_value(idval); - Module.hiwire.decref(idval); + let result = Hiwire.get_value(idval); + Hiwire.decref(idval); return result; }; // This is factored out primarily for testing purposes. - Module.hiwire.isPromise = function(obj) + Hiwire.isPromise = function(obj) { try { // clang-format off @@ -247,7 +267,7 @@ EM_JS_NUM(int, hiwire_init, (), { EM_JS_REF(JsRef, JsString_InternFromCString, (const char* str), { let jsstring = UTF8ToString(str); - return Module.hiwire.intern_object(jsstring); + return Hiwire.intern_object(jsstring); }) JsRef @@ -263,16 +283,22 @@ EM_JS(JsRef, hiwire_incref, (JsRef idval), { if (idval & 1) { // least significant bit unset ==> idval is a singleton. // We don't reference count singletons. - Module.hiwire.incref(idval); + Hiwire.incref(idval); } return idval; }); -EM_JS(void, hiwire_decref, (JsRef idval), { Module.hiwire.decref(idval); }); +// clang-format off +EM_JS(void, hiwire_decref, (JsRef idval), { + Hiwire.decref(idval); +}); +// clang-format on +// clang-format off EM_JS_REF(JsRef, hiwire_int, (int val), { - return Module.hiwire.new_value(val); + return Hiwire.new_value(val); }); +// clang-format on // clang-format off EM_JS_REF(JsRef, @@ -286,12 +312,12 @@ hiwire_int_from_digits, (const unsigned int* digits, size_t ndigits), { if (-Number.MAX_SAFE_INTEGER < result && result < Number.MAX_SAFE_INTEGER) { result = Number(result); } - return Module.hiwire.new_value(result); + return Hiwire.new_value(result); }) // clang-format on EM_JS_REF(JsRef, hiwire_double, (double val), { - return Module.hiwire.new_value(val); + return Hiwire.new_value(val); }); EM_JS_REF(JsRef, hiwire_string_ucs4, (const char* ptr, int len), { @@ -299,7 +325,7 @@ EM_JS_REF(JsRef, hiwire_string_ucs4, (const char* ptr, int len), { for (let i = 0; i < len; ++i) { jsstr += String.fromCodePoint(DEREF_U32(ptr, i)); } - return Module.hiwire.new_value(jsstr); + return Hiwire.new_value(jsstr); }); EM_JS_REF(JsRef, hiwire_string_ucs2, (const char* ptr, int len), { @@ -307,7 +333,7 @@ EM_JS_REF(JsRef, hiwire_string_ucs2, (const char* ptr, int len), { for (let i = 0; i < len; ++i) { jsstr += String.fromCharCode(DEREF_U16(ptr, i)); } - return Module.hiwire.new_value(jsstr); + return Hiwire.new_value(jsstr); }); EM_JS_REF(JsRef, hiwire_string_ucs1, (const char* ptr, int len), { @@ -315,23 +341,23 @@ EM_JS_REF(JsRef, hiwire_string_ucs1, (const char* ptr, int len), { for (let i = 0; i < len; ++i) { jsstr += String.fromCharCode(DEREF_U8(ptr, i)); } - return Module.hiwire.new_value(jsstr); + return Hiwire.new_value(jsstr); }); EM_JS_REF(JsRef, hiwire_string_utf8, (const char* ptr), { - return Module.hiwire.new_value(UTF8ToString(ptr)); + return Hiwire.new_value(UTF8ToString(ptr)); }); EM_JS_REF(JsRef, hiwire_string_ascii, (const char* ptr), { - return Module.hiwire.new_value(AsciiToString(ptr)); + return Hiwire.new_value(AsciiToString(ptr)); }); EM_JS(void _Py_NO_RETURN, hiwire_throw_error, (JsRef iderr), { - throw Module.hiwire.pop_value(iderr); + throw Hiwire.pop_value(iderr); }); EM_JS(bool, JsArray_Check, (JsRef idobj), { - let obj = Module.hiwire.get_value(idobj); + let obj = Hiwire.get_value(idobj); if (Array.isArray(obj)) { return true; } @@ -351,20 +377,28 @@ EM_JS(bool, JsArray_Check, (JsRef idobj), { return false; }); -EM_JS_REF(JsRef, JsArray_New, (), { return Module.hiwire.new_value([]); }); +// clang-format off +EM_JS_REF(JsRef, JsArray_New, (), { + return Hiwire.new_value([]); +}); +// clang-format on EM_JS_NUM(errcode, JsArray_Push, (JsRef idarr, JsRef idval), { - Module.hiwire.get_value(idarr).push(Module.hiwire.get_value(idval)); + Hiwire.get_value(idarr).push(Hiwire.get_value(idval)); }); EM_JS(void, JsArray_Push_unchecked, (JsRef idarr, JsRef idval), { - Module.hiwire.get_value(idarr).push(Module.hiwire.get_value(idval)); + Hiwire.get_value(idarr).push(Hiwire.get_value(idval)); }); -EM_JS_REF(JsRef, JsObject_New, (), { return Module.hiwire.new_value({}); }); +// clang-format off +EM_JS_REF(JsRef, JsObject_New, (), { + return Hiwire.new_value({}); +}); +// clang-format on EM_JS_REF(JsRef, JsObject_GetString, (JsRef idobj, const char* ptrkey), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); let jskey = UTF8ToString(ptrkey); let result = jsobj[jskey]; // clang-format off @@ -372,49 +406,44 @@ EM_JS_REF(JsRef, JsObject_GetString, (JsRef idobj, const char* ptrkey), { // clang-format on return ERROR_REF; } - return Module.hiwire.new_value(result); + return Hiwire.new_value(result); }); // clang-format off -EM_JS_NUM( -errcode, -JsObject_SetString, -(JsRef idobj, const char* ptrkey, JsRef idval), +EM_JS_NUM(errcode, + JsObject_SetString, + (JsRef idobj, const char* ptrkey, JsRef idval), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); let jskey = UTF8ToString(ptrkey); - let jsval = Module.hiwire.get_value(idval); + let jsval = Hiwire.get_value(idval); jsobj[jskey] = jsval; }); +// clang-format on -EM_JS_NUM( -errcode, -JsObject_DeleteString, -(JsRef idobj, const char* ptrkey), -{ - let jsobj = Module.hiwire.get_value(idobj); +EM_JS_NUM(errcode, JsObject_DeleteString, (JsRef idobj, const char* ptrkey), { + let jsobj = Hiwire.get_value(idobj); let jskey = UTF8ToString(ptrkey); delete jsobj[jskey]; }); -// clang-format on EM_JS_REF(JsRef, JsArray_Get, (JsRef idobj, int idx), { - let obj = Module.hiwire.get_value(idobj); + let obj = Hiwire.get_value(idobj); let result = obj[idx]; // clang-format off if (result === undefined && !(idx in obj)) { // clang-format on return ERROR_REF; } - return Module.hiwire.new_value(result); + return Hiwire.new_value(result); }); EM_JS_NUM(errcode, JsArray_Set, (JsRef idobj, int idx, JsRef idval), { - Module.hiwire.get_value(idobj)[idx] = Module.hiwire.get_value(idval); + Hiwire.get_value(idobj)[idx] = Hiwire.get_value(idval); }); EM_JS_NUM(errcode, JsArray_Delete, (JsRef idobj, int idx), { - let obj = Module.hiwire.get_value(idobj); + let obj = Hiwire.get_value(idobj); // Weird edge case: allow deleting an empty entry, but we raise a key error if // access is attempted. if (idx < 0 || idx >= obj.length) { @@ -424,7 +453,7 @@ EM_JS_NUM(errcode, JsArray_Delete, (JsRef idobj, int idx), { }); EM_JS_REF(JsRef, JsObject_Dir, (JsRef idobj), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); let result = []; do { // clang-format off @@ -436,7 +465,7 @@ EM_JS_REF(JsRef, JsObject_Dir, (JsRef idobj), { )); // clang-format on } while (jsobj = Object.getPrototypeOf(jsobj)); - return Module.hiwire.new_value(result); + return Hiwire.new_value(result); }); static JsRef @@ -455,9 +484,9 @@ convert_va_args(va_list args) } EM_JS_REF(JsRef, hiwire_call, (JsRef idfunc, JsRef idargs), { - let jsfunc = Module.hiwire.get_value(idfunc); - let jsargs = Module.hiwire.get_value(idargs); - return Module.hiwire.new_value(jsfunc(... jsargs)); + let jsfunc = Hiwire.get_value(idfunc); + let jsargs = Hiwire.get_value(idargs); + return Hiwire.new_value(jsfunc(... jsargs)); }); JsRef @@ -472,32 +501,32 @@ hiwire_call_va(JsRef idobj, ...) } EM_JS_REF(JsRef, hiwire_call_OneArg, (JsRef idfunc, JsRef idarg), { - let jsfunc = Module.hiwire.get_value(idfunc); - let jsarg = Module.hiwire.get_value(idarg); - return Module.hiwire.new_value(jsfunc(jsarg)); + let jsfunc = Hiwire.get_value(idfunc); + let jsarg = Hiwire.get_value(idarg); + return Hiwire.new_value(jsfunc(jsarg)); }); // clang-format off EM_JS_REF(JsRef, -hiwire_call_bound, -(JsRef idfunc, JsRef idthis, JsRef idargs), + hiwire_call_bound, + (JsRef idfunc, JsRef idthis, JsRef idargs), { - let func = Module.hiwire.get_value(idfunc); + let func = Hiwire.get_value(idfunc); let this_; if (idthis === 0) { this_ = null; } else { - this_ = Module.hiwire.get_value(idthis); + this_ = Hiwire.get_value(idthis); } - let args = Module.hiwire.get_value(idargs); - return Module.hiwire.new_value(func.apply(this_, args)); + let args = Hiwire.get_value(idargs); + return Hiwire.new_value(func.apply(this_, args)); }); // clang-format on EM_JS(bool, hiwire_HasMethod, (JsRef obj_id, JsRef name), { // clang-format off - let obj = Module.hiwire.get_value(obj_id); - return obj && typeof obj[Module.hiwire.get_value(name)] === "function"; + let obj = Hiwire.get_value(obj_id); + return obj && typeof obj[Hiwire.get_value(name)] === "function"; // clang-format on }) @@ -508,35 +537,33 @@ hiwire_HasMethodId(JsRef obj, Js_Identifier* name) } // clang-format off -EM_JS_REF( -JsRef, -hiwire_CallMethodString, -(JsRef idobj, const char* name, JsRef idargs), +EM_JS_REF(JsRef, + hiwire_CallMethodString, + (JsRef idobj, const char* name, JsRef idargs), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); let jsname = UTF8ToString(name); - let jsargs = Module.hiwire.get_value(idargs); - return Module.hiwire.new_value(jsobj[jsname](...jsargs)); + let jsargs = Hiwire.get_value(idargs); + return Hiwire.new_value(jsobj[jsname](...jsargs)); }); // clang-format on EM_JS_REF(JsRef, hiwire_CallMethod, (JsRef idobj, JsRef name, JsRef idargs), { - let jsobj = Module.hiwire.get_value(idobj); - let jsname = Module.hiwire.get_value(name); - let jsargs = Module.hiwire.get_value(idargs); - return Module.hiwire.new_value(jsobj[jsname](... jsargs)); + let jsobj = Hiwire.get_value(idobj); + let jsname = Hiwire.get_value(name); + let jsargs = Hiwire.get_value(idargs); + return Hiwire.new_value(jsobj[jsname](... jsargs)); }); // clang-format off -EM_JS_REF( -JsRef, -hiwire_CallMethod_OneArg, -(JsRef idobj, JsRef name, JsRef idarg), +EM_JS_REF(JsRef, + hiwire_CallMethod_OneArg, + (JsRef idobj, JsRef name, JsRef idarg), { - let jsobj = Module.hiwire.get_value(idobj); - let jsname = Module.hiwire.get_value(name); - let jsarg = Module.hiwire.get_value(idarg); - return Module.hiwire.new_value(jsobj[jsname](jsarg)); + let jsobj = Hiwire.get_value(idobj); + let jsname = Hiwire.get_value(name); + let jsarg = Hiwire.get_value(idarg); + return Hiwire.new_value(jsobj[jsname](jsarg)); }); // clang-format on @@ -583,13 +610,13 @@ hiwire_CallMethodId_OneArg(JsRef obj, Js_Identifier* name, JsRef arg) } EM_JS_REF(JsRef, hiwire_construct, (JsRef idobj, JsRef idargs), { - let jsobj = Module.hiwire.get_value(idobj); - let jsargs = Module.hiwire.get_value(idargs); - return Module.hiwire.new_value(Reflect.construct(jsobj, jsargs)); + let jsobj = Hiwire.get_value(idobj); + let jsargs = Hiwire.get_value(idargs); + return Hiwire.new_value(Reflect.construct(jsobj, jsargs)); }); EM_JS(bool, hiwire_has_length, (JsRef idobj), { - let val = Module.hiwire.get_value(idobj); + let val = Hiwire.get_value(idobj); // clang-format off return (typeof val.size === "number") || (typeof val.length === "number" && typeof val !== "function"); @@ -597,7 +624,7 @@ EM_JS(bool, hiwire_has_length, (JsRef idobj), { }); EM_JS_NUM(int, hiwire_get_length, (JsRef idobj), { - let val = Module.hiwire.get_value(idobj); + let val = Hiwire.get_value(idobj); // clang-format off if (typeof val.size === "number") { return val.size; @@ -610,7 +637,7 @@ EM_JS_NUM(int, hiwire_get_length, (JsRef idobj), { }); EM_JS(bool, hiwire_get_bool, (JsRef idobj), { - let val = Module.hiwire.get_value(idobj); + let val = Hiwire.get_value(idobj); // clang-format off if (!val) { return false; @@ -627,23 +654,23 @@ EM_JS(bool, hiwire_get_bool, (JsRef idobj), { }); EM_JS(bool, hiwire_is_pyproxy, (JsRef idobj), { - return Module.isPyProxy(Module.hiwire.get_value(idobj)); + return API.isPyProxy(Hiwire.get_value(idobj)); }); EM_JS(bool, hiwire_is_function, (JsRef idobj), { // clang-format off - return typeof Module.hiwire.get_value(idobj) === 'function'; + return typeof Hiwire.get_value(idobj) === 'function'; // clang-format on }); EM_JS(bool, hiwire_is_comlink_proxy, (JsRef idobj), { - let value = Module.hiwire.get_value(idobj); - return !!(Module.Comlink && value[Module.Comlink.createEndpoint]); + let value = Hiwire.get_value(idobj); + return !!(API.Comlink && value[API.Comlink.createEndpoint]); }); EM_JS(bool, hiwire_is_error, (JsRef idobj), { // From https://stackoverflow.com/a/45496068 - let value = Module.hiwire.get_value(idobj); + let value = Hiwire.get_value(idobj); // clang-format off return !!(value && typeof value.stack === "string" && typeof value.message === "string"); @@ -652,34 +679,34 @@ EM_JS(bool, hiwire_is_error, (JsRef idobj), { EM_JS(bool, hiwire_is_promise, (JsRef idobj), { // clang-format off - let obj = Module.hiwire.get_value(idobj); - return Module.hiwire.isPromise(obj); + let obj = Hiwire.get_value(idobj); + return Hiwire.isPromise(obj); // clang-format on }); EM_JS_REF(JsRef, hiwire_resolve_promise, (JsRef idobj), { // clang-format off - let obj = Module.hiwire.get_value(idobj); + let obj = Hiwire.get_value(idobj); let result = Promise.resolve(obj); - return Module.hiwire.new_value(result); + return Hiwire.new_value(result); // clang-format on }); EM_JS_REF(JsRef, hiwire_to_string, (JsRef idobj), { - return Module.hiwire.new_value(Module.hiwire.get_value(idobj).toString()); + return Hiwire.new_value(Hiwire.get_value(idobj).toString()); }); EM_JS_REF(JsRef, hiwire_typeof, (JsRef idobj), { - return Module.hiwire.new_value(typeof Module.hiwire.get_value(idobj)); + return Hiwire.new_value(typeof Hiwire.get_value(idobj)); }); EM_JS_REF(char*, hiwire_constructor_name, (JsRef idobj), { - return stringToNewUTF8(Module.hiwire.get_value(idobj).constructor.name); + return stringToNewUTF8(Hiwire.get_value(idobj).constructor.name); }); #define MAKE_OPERATOR(name, op) \ EM_JS(bool, hiwire_##name, (JsRef ida, JsRef idb), { \ - return !!(Module.hiwire.get_value(ida) op Module.hiwire.get_value(idb)); \ + return !!(Hiwire.get_value(ida) op Hiwire.get_value(idb)); \ }) MAKE_OPERATOR(less_than, <); @@ -692,83 +719,83 @@ MAKE_OPERATOR(greater_than, >); MAKE_OPERATOR(greater_than_equal, >=); EM_JS(bool, hiwire_is_iterator, (JsRef idobj), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); // clang-format off return typeof jsobj.next === 'function'; // clang-format on }); EM_JS_NUM(int, hiwire_next, (JsRef idobj, JsRef* result_ptr), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); // clang-format off let { done, value } = jsobj.next(); // clang-format on - let result_id = Module.hiwire.new_value(value); + let result_id = Hiwire.new_value(value); DEREF_U32(result_ptr, 0) = result_id; return done; }); EM_JS(bool, hiwire_is_iterable, (JsRef idobj), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); // clang-format off return typeof jsobj[Symbol.iterator] === 'function'; // clang-format on }); EM_JS_REF(JsRef, hiwire_get_iterator, (JsRef idobj), { - let jsobj = Module.hiwire.get_value(idobj); - return Module.hiwire.new_value(jsobj[Symbol.iterator]()); + let jsobj = Hiwire.get_value(idobj); + return Hiwire.new_value(jsobj[Symbol.iterator]()); }) EM_JS_REF(JsRef, JsObject_Entries, (JsRef idobj), { - let jsobj = Module.hiwire.get_value(idobj); - return Module.hiwire.new_value(Object.entries(jsobj)); + let jsobj = Hiwire.get_value(idobj); + return Hiwire.new_value(Object.entries(jsobj)); }); EM_JS_REF(JsRef, JsObject_Keys, (JsRef idobj), { - let jsobj = Module.hiwire.get_value(idobj); - return Module.hiwire.new_value(Object.keys(jsobj)); + let jsobj = Hiwire.get_value(idobj); + return Hiwire.new_value(Object.keys(jsobj)); }); EM_JS_REF(JsRef, JsObject_Values, (JsRef idobj), { - let jsobj = Module.hiwire.get_value(idobj); - return Module.hiwire.new_value(Object.values(jsobj)); + let jsobj = Hiwire.get_value(idobj); + return Hiwire.new_value(Object.values(jsobj)); }); EM_JS(bool, hiwire_is_typedarray, (JsRef idobj), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); // clang-format off return ArrayBuffer.isView(jsobj) || jsobj.constructor.name === "ArrayBuffer"; // clang-format on }); EM_JS_NUM(errcode, hiwire_assign_to_ptr, (JsRef idobj, void* ptr), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); Module.HEAPU8.set(Module.typedArrayAsUint8Array(jsobj), ptr); }); EM_JS_NUM(errcode, hiwire_assign_from_ptr, (JsRef idobj, void* ptr), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); Module.typedArrayAsUint8Array(jsobj).set( Module.HEAPU8.subarray(ptr, ptr + jsobj.byteLength)); }); EM_JS_NUM(errcode, hiwire_read_from_file, (JsRef idobj, int fd), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); let uint8_buffer = Module.typedArrayAsUint8Array(jsobj); let stream = Module.FS.streams[fd]; Module.FS.read(stream, uint8_buffer, 0, uint8_buffer.byteLength); }); EM_JS_NUM(errcode, hiwire_write_to_file, (JsRef idobj, int fd), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); let uint8_buffer = Module.typedArrayAsUint8Array(jsobj); let stream = Module.FS.streams[fd]; Module.FS.write(stream, uint8_buffer, 0, uint8_buffer.byteLength); }); EM_JS_NUM(errcode, hiwire_into_file, (JsRef idobj, int fd), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); let uint8_buffer = Module.typedArrayAsUint8Array(jsobj); let stream = Module.FS.streams[fd]; // set canOwn param to true, leave position undefined. @@ -779,10 +806,13 @@ EM_JS_NUM(errcode, hiwire_into_file, (JsRef idobj, int fd), { // clang-format off EM_JS_UNCHECKED( void, -hiwire_get_buffer_info, -(JsRef idobj, Py_ssize_t* byteLength_ptr, char** format_ptr, Py_ssize_t* size_ptr, bool* checked_ptr), +hiwire_get_buffer_info, (JsRef idobj, + Py_ssize_t* byteLength_ptr, + char** format_ptr, + Py_ssize_t* size_ptr, + bool* checked_ptr), { - let jsobj = Module.hiwire.get_value(idobj); + let jsobj = Hiwire.get_value(idobj); let byteLength = jsobj.byteLength; let [format_utf8, size, checked] = Module.get_buffer_datatype(jsobj); // Store results into arguments @@ -794,24 +824,32 @@ hiwire_get_buffer_info, // clang-format on EM_JS_REF(JsRef, hiwire_subarray, (JsRef idarr, int start, int end), { - let jsarr = Module.hiwire.get_value(idarr); + let jsarr = Hiwire.get_value(idarr); let jssub = jsarr.subarray(start, end); - return Module.hiwire.new_value(jssub); + return Hiwire.new_value(jssub); }); -EM_JS_REF(JsRef, JsMap_New, (), { return Module.hiwire.new_value(new Map()); }) +// clang-format off +EM_JS_REF(JsRef, JsMap_New, (), { + return Hiwire.new_value(new Map()); +}) +// clang-format on EM_JS_NUM(errcode, JsMap_Set, (JsRef mapid, JsRef keyid, JsRef valueid), { - let map = Module.hiwire.get_value(mapid); - let key = Module.hiwire.get_value(keyid); - let value = Module.hiwire.get_value(valueid); + let map = Hiwire.get_value(mapid); + let key = Hiwire.get_value(keyid); + let value = Hiwire.get_value(valueid); map.set(key, value); }) -EM_JS_REF(JsRef, JsSet_New, (), { return Module.hiwire.new_value(new Set()); }) +// clang-format off +EM_JS_REF(JsRef, JsSet_New, (), { + return Hiwire.new_value(new Set()); +}) +// clang-format on EM_JS_NUM(errcode, JsSet_Add, (JsRef mapid, JsRef keyid), { - let set = Module.hiwire.get_value(mapid); - let key = Module.hiwire.get_value(keyid); + let set = Hiwire.get_value(mapid); + let key = Hiwire.get_value(keyid); set.add(key); }) diff --git a/src/core/hiwire.h b/src/core/hiwire.h index 5eec98e11d3..4e9d0bcee44 100644 --- a/src/core/hiwire.h +++ b/src/core/hiwire.h @@ -23,7 +23,7 @@ // I checked and // alignof(JsRef) = alignof(int) = 4 // sizeof(JsRef) = sizeof(int) = 4 -// Just to be extra future proof, I added assertions about this to the begining +// Just to be extra future proof, I added assertions about this to the beginning // of main.c So we are all good for using JsRef as a newtype for int. I also // added // -Werror=int-conversion -Werror=incompatible-pointer-types @@ -34,6 +34,11 @@ struct _JsRefStruct typedef struct _JsRefStruct* JsRef; +_Static_assert(alignof(JsRef) == alignof(int), + "JsRef should have the same alignment as int."); +_Static_assert(sizeof(JsRef) == sizeof(int), + "JsRef should have the same size as int."); + // Error handling will want to see JsRef. #include "error_handling.h" diff --git a/src/core/include_js_file.h b/src/core/include_js_file.h index 65bd74ce2c2..9e233d07e9d 100644 --- a/src/core/include_js_file.h +++ b/src/core/include_js_file.h @@ -12,7 +12,8 @@ #define UNPAIRED_OPEN_BRACE { #define UNPAIRED_CLOSE_BRACE } // Just here to help text editors pair braces up #define JS_FILE(func_name, a, args...) \ - EM_JS_NUM(int, func_name, (), UNPAIRED_OPEN_BRACE { args return 0; }) + EM_JS_NUM( \ + int, func_name, (), UNPAIRED_OPEN_BRACE { args return 0; }) // A macro to allow us to add code that is only intended to influence JsDoc // output, but shouldn't end up in generated code. diff --git a/src/core/js2python.c b/src/core/js2python.c index 2fdb1c5e08b..f5b948027b8 100644 --- a/src/core/js2python.c +++ b/src/core/js2python.c @@ -43,8 +43,8 @@ _js2python_pyproxy(PyObject* val) } EM_JS_REF(PyObject*, js2python, (JsRef id), { - let value = Module.hiwire.get_value(id); - let result = Module._js2python_convertImmutable(value); + let value = Hiwire.get_value(id); + let result = Module.js2python_convertImmutable(value); // clang-format off if (result !== undefined) { // clang-format on @@ -57,323 +57,14 @@ EM_JS_REF(PyObject*, js2python, (JsRef id), { * Convert a JavaScript object to Python to a given depth. This is the * implementation of `toJs`. */ -EM_JS_REF(PyObject*, js2python_convert, (JsRef id, int depth), { - return Module.js2python_convert(id, new Map(), depth); +// clang-format off +EM_JS_REF(PyObject*, js2python_convert, (JsRef id, int depth, JsRef default_converter), { + let defaultConverter = default_converter + ? Module.hiwire.get_value(default_converter) + : undefined; + return Module.js2python_convert(id, { depth, defaultConverter }); }); +// clang-format on -EM_JS_NUM(errcode, js2python_init, (), { - let PropagateError = Module._PropagatePythonError; - function __js2python_string(value) - { - // The general idea here is to allocate a Python string and then - // have JavaScript write directly into its buffer. We first need - // to determine if is needs to be a 1-, 2- or 4-byte string, since - // Python handles all 3. - let max_code_point = 0; - let num_code_points = 0; - for (let c of value) { - num_code_points++; - let code_point = c.codePointAt(0); - max_code_point = - code_point > max_code_point ? code_point : max_code_point; - } - - let result = _PyUnicode_New(num_code_points, max_code_point); - // clang-format off - if (result === 0) { - // clang-format on - throw new PropagateError(); - } - - let ptr = _PyUnicode_Data(result); - if (max_code_point > 0xffff) { - for (let c of value) { - HEAPU32[ptr / 4] = c.codePointAt(0); - ptr += 4; - } - } else if (max_code_point > 0xff) { - for (let c of value) { - HEAPU16[ptr / 2] = c.codePointAt(0); - ptr += 2; - } - } else { - for (let c of value) { - HEAPU8[ptr] = c.codePointAt(0); - ptr += 1; - } - } - - return result; - }; - - function __js2python_bigint(value) - { - let value_orig = value; - let length = 0; - if (value < 0) { - value = -value; - } - while (value) { - length++; - value >>= BigInt(32); - } - let stackTop = stackSave(); - let ptr = stackAlloc(length * 4); - value = value_orig; - for (let i = 0; i < length; i++) { - DEREF_U32(ptr, i) = Number(value & BigInt(0xffffffff)); - value >>= BigInt(32); - } - let result = __PyLong_FromByteArray(ptr, - length * 4 /* length in bytes */, - true /* little endian */, - true /* signed? */); - stackRestore(stackTop); - return result; - }; - - /** - * This function converts immutable types. numbers, bigints, strings, - * booleans, undefined, and null are converted. PyProxies are unwrapped. - * - * If `value` is of any other type then `undefined` is returned. - * - * If `value` is one of those types but an error is raised during conversion, - * we throw a PropagateError to propogate the error out to C. This causes - * special handling in the EM_JS wrapper. - */ - Module._js2python_convertImmutable = function(value) - { - let result = __js2python_convertImmutableInner(value); - // clang-format off - if (result === 0) { - // clang-format on - throw new PropagateError(); - } - return result; - }; - - function __js2python_convertImmutableInner(value) - { - let type = typeof value; - // clang-format off - if (type === 'string') { - return __js2python_string(value); - } else if (type === 'number') { - if(Number.isSafeInteger(value)){ - return _PyLong_FromDouble(value); - } else { - return _PyFloat_FromDouble(value); - } - } else if(type === "bigint"){ - return __js2python_bigint(value); - } else if (value === undefined || value === null) { - return __js2python_none(); - } else if (value === true) { - return __js2python_true(); - } else if (value === false) { - return __js2python_false(); - } else if (Module.isPyProxy(value)) { - return __js2python_pyproxy(Module.PyProxy_getPtr(value)); - } - // clang-format on - return undefined; - }; - - function __js2python_convertList(obj, cache, depth) - { - let list = _PyList_New(obj.length); - // clang-format off - if (list === 0) { - // clang-format on - return 0; - } - let entryid = 0; - let item = 0; - try { - cache.set(obj, list); - for (let i = 0; i < obj.length; i++) { - entryid = Module.hiwire.new_value(obj[i]); - item = Module.js2python_convert(entryid, cache, depth); - // clang-format off - // PyList_SetItem steals a reference to item no matter what - _Py_IncRef(item); - if (_PyList_SetItem(list, i, item) === -1) { - // clang-format on - throw new PropagateError(); - } - Module.hiwire.decref(entryid); - entryid = 0; - _Py_DecRef(item); - item = 0; - } - } catch (e) { - Module.hiwire.decref(entryid); - _Py_DecRef(item); - _Py_DecRef(list); - throw e; - } - - return list; - }; - - function __js2python_convertMap(obj, entries, cache, depth) - { - let dict = _PyDict_New(); - // clang-format off - if (dict === 0) { - // clang-format on - return 0; - } - let key_py = 0; - let value_id = 0; - let value_py = 0; - try { - cache.set(obj, dict); - for (let[key_js, value_js] of entries) { - key_py = Module._js2python_convertImmutable(key_js); - // clang-format off - if (key_py === undefined) { - // clang-format on - let key_type = - (key_js.constructor && key_js.constructor.name) || typeof(key_js); - // clang-format off - throw new Error(`Cannot use key of type ${key_type} as a key to a Python dict`); - // clang-format on - } - value_id = Module.hiwire.new_value(value_js); - value_py = Module.js2python_convert(value_id, cache, depth); - - // clang-format off - if (_PyDict_SetItem(dict, key_py, value_py) === -1) { - // clang-format on - throw new PropagateError(); - } - _Py_DecRef(key_py); - key_py = 0; - Module.hiwire.decref(value_id); - value_id = 0; - _Py_DecRef(value_py); - value_py = 0; - } - } catch (e) { - _Py_DecRef(key_py); - Module.hiwire.decref(value_id); - _Py_DecRef(value_py); - _Py_DecRef(dict); - throw e; - } - return dict; - }; - - function __js2python_convertSet(obj, cache, depth) - { - let set = _PySet_New(0); - // clang-format off - if (set === 0) { - // clang-format on - return 0; - } - let key_py = 0; - try { - cache.set(obj, set); - for (let key_js of obj) { - key_py = Module._js2python_convertImmutable(key_js); - // clang-format off - if (key_py === undefined) { - // clang-format on - let key_type = - (key_js.constructor && key_js.constructor.name) || typeof(key_js); - // clang-format off - throw new Error(`Cannot use key of type ${key_type} as a key to a Python set`); - // clang-format on - } - let errcode = _PySet_Add(set, key_py); - // clang-format off - if (errcode === -1) { - // clang-format on - throw new PropagateError(); - } - _Py_DecRef(key_py); - key_py = 0; - } - } catch (e) { - _Py_DecRef(key_py); - _Py_DecRef(set); - throw e; - } - return set; - }; - - function checkBoolIntCollision(obj, ty) - { - if (obj.has(1) && obj.has(true)) { - throw new Error(`Cannot faithfully convert ${ - ty } into Python since it ` + - "contains both 1 and true as keys."); - } - if (obj.has(0) && obj.has(false)) { - throw new Error(`Cannot faithfully convert ${ - ty } into Python since it ` + - "contains both 0 and false as keys."); - } - } - - /** - * Convert mutable types: Array, Map, Set, and Objects whose prototype is - * either null or the default. Anything else is wrapped in a Proxy. This - * should only be used on values for which __js2python_convertImmutable - * returned `undefined`. - */ - function __js2python_convertOther(id, value, cache, depth) - { - let toStringTag = Object.prototype.toString.call(value); - // clang-format off - if (Array.isArray(value) || value === "[object HTMLCollection]" || - value === "[object NodeList]") { - return __js2python_convertList(value, cache, depth); - } - if (toStringTag === "[object Map]" || value instanceof Map) { - checkBoolIntCollision(value, "Map"); - return __js2python_convertMap(value, value.entries(), cache, depth); - } - if (toStringTag === "[object Set]" || value instanceof Set) { - checkBoolIntCollision(value, "Set"); - return __js2python_convertSet(value, cache, depth); - } - if (toStringTag === "[object Object]" && (value.constructor === undefined || value.constructor.name === "Object")) { - return __js2python_convertMap(value, Object.entries(value), cache, depth); - } - if (toStringTag === "[object ArrayBuffer]" || ArrayBuffer.isView(value)){ - let [format_utf8, itemsize] = Module.get_buffer_datatype(value); - return _JsBuffer_CopyIntoMemoryView(id, value.byteLength, format_utf8, itemsize); - } - // clang-format on - return _JsProxy_create(id); - }; - - /** - * Convert a JavaScript object to Python to a given depth. The `cache` - * argument should be a new empty map (it is needed for recursive calls). - */ - Module.js2python_convert = function(id, cache, depth) - { - let value = Module.hiwire.get_value(id); - let result = Module._js2python_convertImmutable(value); - // clang-format off - if (result !== undefined) { - return result; - } - if (depth === 0) { - return _JsProxy_create(id); - } - result = cache.get(value); - if (result !== undefined) { - return result; - } - // clang-format on - return __js2python_convertOther(id, value, cache, depth - 1); - }; - - return 0; -}) +#include "include_js_file.h" +#include "js2python.js" diff --git a/src/core/js2python.h b/src/core/js2python.h index 9a96fcc0162..38edd3d4ec3 100644 --- a/src/core/js2python.h +++ b/src/core/js2python.h @@ -18,7 +18,7 @@ PyObject* js2python(JsRef x); PyObject* -js2python_convert(JsRef x, int depth); +js2python_convert(JsRef x, int depth, JsRef defaultConverter); /** Initialize any global variables used by this module. */ int diff --git a/src/core/js2python.js b/src/core/js2python.js new file mode 100644 index 00000000000..2a72a9fce04 --- /dev/null +++ b/src/core/js2python.js @@ -0,0 +1,362 @@ +JS_FILE(js2python_init, () => { + 0, 0; /* Magic, see include_js_file.h */ + let PropagateError = Module._PropagatePythonError; + function js2python_string(value) { + // The general idea here is to allocate a Python string and then + // have JavaScript write directly into its buffer. We first need + // to determine if is needs to be a 1-, 2- or 4-byte string, since + // Python handles all 3. + let max_code_point = 0; + let num_code_points = 0; + for (let c of value) { + num_code_points++; + let code_point = c.codePointAt(0); + max_code_point = + code_point > max_code_point ? code_point : max_code_point; + } + + let result = _PyUnicode_New(num_code_points, max_code_point); + if (result === 0) { + throw new PropagateError(); + } + + let ptr = _PyUnicode_Data(result); + if (max_code_point > 0xffff) { + for (let c of value) { + HEAPU32[ptr / 4] = c.codePointAt(0); + ptr += 4; + } + } else if (max_code_point > 0xff) { + for (let c of value) { + HEAPU16[ptr / 2] = c.codePointAt(0); + ptr += 2; + } + } else { + for (let c of value) { + HEAPU8[ptr] = c.codePointAt(0); + ptr += 1; + } + } + + return result; + } + + function js2python_bigint(value) { + let value_orig = value; + let length = 0; + if (value < 0) { + value = -value; + } + while (value) { + length++; + value >>= BigInt(32); + } + let stackTop = stackSave(); + let ptr = stackAlloc(length * 4); + value = value_orig; + for (let i = 0; i < length; i++) { + ASSIGN_U32(ptr, i, Number(value & BigInt(0xffffffff))); + value >>= BigInt(32); + } + let result = __PyLong_FromByteArray( + ptr, + length * 4 /* length in bytes */, + true /* little endian */, + true /* signed? */ + ); + stackRestore(stackTop); + return result; + } + + /** + * This function converts immutable types. numbers, bigints, strings, + * booleans, undefined, and null are converted. PyProxies are unwrapped. + * + * If `value` is of any other type then `undefined` is returned. + * + * If `value` is one of those types but an error is raised during conversion, + * we throw a PropagateError to propagate the error out to C. This causes + * special handling in the EM_JS wrapper. + */ + function js2python_convertImmutable(value) { + let result = js2python_convertImmutableInner(value); + if (result === 0) { + throw new PropagateError(); + } + return result; + } + // js2python_convertImmutable is used from js2python.c so we need to add it + // to Module. + Module.js2python_convertImmutable = js2python_convertImmutable; + + /** + * Returns a pointer to a Python object, 0, or undefined. + * + * If we return 0 it means we tried to convert but an error occurred, if we + * return undefined, no conversion was attempted. + */ + function js2python_convertImmutableInner(value) { + let type = typeof value; + if (type === "string") { + return js2python_string(value); + } else if (type === "number") { + if (Number.isSafeInteger(value)) { + return _PyLong_FromDouble(value); + } else { + return _PyFloat_FromDouble(value); + } + } else if (type === "bigint") { + return js2python_bigint(value); + } else if (value === undefined || value === null) { + return __js2python_none(); + } else if (value === true) { + return __js2python_true(); + } else if (value === false) { + return __js2python_false(); + } else if (API.isPyProxy(value)) { + return __js2python_pyproxy(Module.PyProxy_getPtr(value)); + } + return undefined; + } + + function js2python_convertList(obj, context) { + let list = _PyList_New(obj.length); + if (list === 0) { + return 0; + } + let entryid = 0; + let item = 0; + try { + context.cache.set(obj, list); + for (let i = 0; i < obj.length; i++) { + entryid = Hiwire.new_value(obj[i]); + item = js2python_convert_with_context(entryid, context); + // PyList_SetItem steals a reference to item no matter what + _Py_IncRef(item); + if (_PyList_SetItem(list, i, item) === -1) { + throw new PropagateError(); + } + Hiwire.decref(entryid); + entryid = 0; + _Py_DecRef(item); + item = 0; + } + } catch (e) { + Hiwire.decref(entryid); + _Py_DecRef(item); + _Py_DecRef(list); + throw e; + } + + return list; + } + + function js2python_convertMap(obj, entries, context) { + let dict = _PyDict_New(); + if (dict === 0) { + return 0; + } + let key_py = 0; + let value_id = 0; + let value_py = 0; + try { + context.cache.set(obj, dict); + for (let [key_js, value_js] of entries) { + key_py = js2python_convertImmutable(key_js); + if (key_py === undefined) { + let key_type = + (key_js.constructor && key_js.constructor.name) || typeof key_js; + throw new Error( + `Cannot use key of type ${key_type} as a key to a Python dict` + ); + } + value_id = Hiwire.new_value(value_js); + value_py = js2python_convert_with_context(value_id, context); + + if (_PyDict_SetItem(dict, key_py, value_py) === -1) { + throw new PropagateError(); + } + _Py_DecRef(key_py); + key_py = 0; + Hiwire.decref(value_id); + value_id = 0; + _Py_DecRef(value_py); + value_py = 0; + } + } catch (e) { + _Py_DecRef(key_py); + Hiwire.decref(value_id); + _Py_DecRef(value_py); + _Py_DecRef(dict); + throw e; + } + return dict; + } + + function js2python_convertSet(obj, context) { + let set = _PySet_New(0); + if (set === 0) { + return 0; + } + let key_py = 0; + try { + context.cache.set(obj, set); + for (let key_js of obj) { + key_py = js2python_convertImmutable(key_js); + if (key_py === undefined) { + let key_type = + (key_js.constructor && key_js.constructor.name) || typeof key_js; + throw new Error( + `Cannot use key of type ${key_type} as a key to a Python set` + ); + } + let errcode = _PySet_Add(set, key_py); + if (errcode === -1) { + throw new PropagateError(); + } + _Py_DecRef(key_py); + key_py = 0; + } + } catch (e) { + _Py_DecRef(key_py); + _Py_DecRef(set); + throw e; + } + return set; + } + + function checkBoolIntCollision(obj, ty) { + if (obj.has(1) && obj.has(true)) { + throw new Error( + `Cannot faithfully convert ${ty} into Python since it ` + + "contains both 1 and true as keys." + ); + } + if (obj.has(0) && obj.has(false)) { + throw new Error( + `Cannot faithfully convert ${ty} into Python since it ` + + "contains both 0 and false as keys." + ); + } + } + + /** + * Convert mutable types: Array, Map, Set, and Objects whose prototype is + * either null or the default. Anything else is wrapped in a Proxy. This + * should only be used on values for which js2python_convertImmutable + * returned `undefined`. + */ + function js2python_convertOther(id, value, context) { + let toStringTag = Object.prototype.toString.call(value); + if ( + Array.isArray(value) || + value === "[object HTMLCollection]" || + value === "[object NodeList]" + ) { + return js2python_convertList(value, context); + } + if (toStringTag === "[object Map]" || value instanceof Map) { + checkBoolIntCollision(value, "Map"); + return js2python_convertMap(value, value.entries(), context); + } + if (toStringTag === "[object Set]" || value instanceof Set) { + checkBoolIntCollision(value, "Set"); + return js2python_convertSet(value, context); + } + if ( + toStringTag === "[object Object]" && + (value.constructor === undefined || value.constructor.name === "Object") + ) { + return js2python_convertMap(value, Object.entries(value), context); + } + if (toStringTag === "[object ArrayBuffer]" || ArrayBuffer.isView(value)) { + let [format_utf8, itemsize] = Module.get_buffer_datatype(value); + return _JsBuffer_CopyIntoMemoryView( + id, + value.byteLength, + format_utf8, + itemsize + ); + } + return undefined; + } + + /** + * Convert a JavaScript object to Python to a given depth. + */ + function js2python_convert_with_context(id, context) { + let value = Hiwire.get_value(id); + let result; + result = js2python_convertImmutable(value); + if (result !== undefined) { + return result; + } + if (context.depth === 0) { + return _JsProxy_create(id); + } + result = context.cache.get(value); + if (result !== undefined) { + return result; + } + context.depth--; + try { + result = js2python_convertOther(id, value, context); + if (result !== undefined) { + return result; + } + if (context.defaultConverter === undefined) { + return _JsProxy_create(id); + } + let result_js = context.defaultConverter( + value, + context.converter, + context.cacheConversion + ); + result = js2python_convertImmutable(result_js); + if (API.isPyProxy(result_js)) { + result_js.destroy(); + } + if (result !== undefined) { + return result; + } + let result_id = Module.hiwire.new_value(result_js); + result = _JsProxy_create(result_id); + Module.hiwire.decref(result_id); + return result; + } finally { + context.depth++; + } + } + + /** + * Convert a JavaScript object to Python to a given depth. + */ + function js2python_convert(id, { depth, defaultConverter }) { + let context = { + cache: new Map(), + depth, + defaultConverter, + // arguments for defaultConverter + converter(x) { + let id = Module.hiwire.new_value(x); + try { + return Module.pyproxy_new( + js2python_convert_with_context(id, context) + ); + } finally { + Module.hiwire.decref(id); + } + }, + cacheConversion(input, output) { + if (API.isPyProxy(output)) { + context.cache.set(input, Module.PyProxy_getPtr(output)); + } else { + throw new Error("Second argument should be a PyProxy!"); + } + }, + }; + return js2python_convert_with_context(id, context); + } + + Module.js2python_convert = js2python_convert; +}); diff --git a/src/core/jsmemops.h b/src/core/jsmemops.h index 40124fb8e96..e38cf7ee49d 100644 --- a/src/core/jsmemops.h +++ b/src/core/jsmemops.h @@ -12,6 +12,7 @@ #define DEREF_F32(addr, offset) HEAPF32[(addr >> 2) + offset] #define DEREF_F64(addr, offset) HEAPF64[(addr >> 3) + offset] +#define ASSIGN_U32(addr, offset, value) DEREF_U32(addr, offset) = value #if WASM_BIGINT // We have HEAPU64 / HEAPI64 in this case. #define DEREF_U64(addr, offset) HEAPU64[(addr >> 3) + offset] diff --git a/src/core/jsproxy.c b/src/core/jsproxy.c index 1830914c5bb..b8ae4e0421c 100644 --- a/src/core/jsproxy.c +++ b/src/core/jsproxy.c @@ -101,8 +101,11 @@ typedef struct static void JsProxy_dealloc(JsProxy* self) { -#ifdef HW_TRACE_REFS - printf("jsproxy delloc %zd, %zd\n", (long)self, (long)self->js); +#ifdef DEBUG_F + extern bool tracerefs; + if (tracerefs) { + printf("jsproxy delloc %zd, %zd\n", (long)self, (long)self->js); + } #endif hiwire_CLEAR(self->js); hiwire_CLEAR(self->this_); @@ -291,8 +294,7 @@ JsProxy_GetIter(PyObject* o) /** * next overload. Controlled by IS_ITERATOR. - * TODO: Should add a similar send method for generator support. - * Python 3.10 has a different way to handle this. + * TODO: Implement Py_am_send method for generator support */ static PyObject* JsProxy_IterNext(PyObject* o) @@ -497,8 +499,8 @@ JsProxy_ass_subscript_array(PyObject* o, PyObject* item, PyObject* pyvalue) // A helper method for jsproxy_subscript. EM_JS_REF(JsRef, JsProxy_subscript_js, (JsRef idobj, JsRef idkey), { - let obj = Module.hiwire.get_value(idobj); - let key = Module.hiwire.get_value(idkey); + let obj = Hiwire.get_value(idobj); + let key = Hiwire.get_value(idkey); let result = obj.get(key); // clang-format off if (result === undefined) { @@ -511,7 +513,7 @@ EM_JS_REF(JsRef, JsProxy_subscript_js, (JsRef idobj, JsRef idkey), { } } // clang-format on - return Module.hiwire.new_value(result); + return Hiwire.new_value(result); }); /** @@ -704,14 +706,25 @@ JsProxy_toPy(PyObject* self, Py_ssize_t nargs, PyObject* kwnames) { - static const char* const _keywords[] = { "depth", 0 }; - static struct _PyArg_Parser _parser = { "|$i:toPy", _keywords, 0 }; + static const char* const _keywords[] = { "depth", "default_converter", 0 }; + static struct _PyArg_Parser _parser = { "|$iO:toPy", _keywords, 0 }; int depth = -1; - if (kwnames != NULL && - !_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, &depth)) { + PyObject* default_converter = NULL; + if (!_PyArg_ParseStackAndKeywords( + args, nargs, kwnames, &_parser, &depth, &default_converter)) { return NULL; } - return js2python_convert(GET_JSREF(self), depth); + JsRef default_converter_js = NULL; + if (default_converter != NULL) { + default_converter_js = python2js(default_converter); + } + PyObject* result = + js2python_convert(GET_JSREF(self), depth, default_converter_js); + if (pyproxy_Check(default_converter_js)) { + destroy_proxy(default_converter_js, NULL); + } + hiwire_decref(default_converter_js); + return result; } static PyMethodDef JsProxy_toPy_MethodDef = { @@ -980,8 +993,11 @@ JsProxy_cinit(PyObject* obj, JsRef idobj) { JsProxy* self = (JsProxy*)obj; self->js = hiwire_incref(idobj); -#ifdef HW_TRACE_REFS - printf("JsProxy cinit: %zd, object: %zd\n", (long)obj, (long)self->js); +#ifdef DEBUG_F + extern bool tracerefs; + if (tracerefs) { + printf("JsProxy cinit: %zd, object: %zd\n", (long)obj, (long)self->js); + } #endif return 0; } @@ -1168,15 +1184,15 @@ JsMethod_ConvertArgs(PyObject* const* args, * destroys them and the result of the Promise. */ EM_JS_REF(JsRef, get_async_js_call_done_callback, (JsRef proxies_id), { - let proxies = Module.hiwire.get_value(proxies_id); - return Module.hiwire.new_value(function(result) { + let proxies = Hiwire.get_value(proxies_id); + return Hiwire.new_value(function(result) { let msg = "This borrowed proxy was automatically destroyed " + "at the end of an asynchronous function call. Try " + "using create_proxy or create_once_callable."; for (let px of proxies) { Module.pyproxy_destroy(px, msg); } - if (Module.isPyProxy(result)) { + if (API.isPyProxy(result)) { Module.pyproxy_destroy(result, msg); } }); @@ -1581,7 +1597,7 @@ EM_JS_REF(JsRef, JsBuffer_DecodeString_js, (JsRef jsbuffer_id, char* encoding), { - let buffer = Module.hiwire.get_value(jsbuffer_id); + let buffer = Hiwire.get_value(jsbuffer_id); let encoding_js; if (encoding) { encoding_js = UTF8ToString(encoding); @@ -1597,7 +1613,7 @@ JsBuffer_DecodeString_js, } throw e; } - return Module.hiwire.new_value(res); + return Hiwire.new_value(res); }) // clang-format on @@ -1626,7 +1642,7 @@ JsBuffer_ToString(JsRef jsbuffer, char* encoding) } static PyObject* -JsBuffer_tomemoryview(PyObject* buffer) +JsBuffer_tomemoryview(PyObject* buffer, PyObject* _ignored) { JsProxy* self = (JsProxy*)buffer; return JsBuffer_CopyIntoMemoryView( @@ -1640,7 +1656,7 @@ static PyMethodDef JsBuffer_tomemoryview_MethodDef = { }; static PyObject* -JsBuffer_tobytes(PyObject* buffer) +JsBuffer_tobytes(PyObject* buffer, PyObject* _ignored) { JsProxy* self = (JsProxy*)buffer; return JsBuffer_CopyIntoBytes(self->js, self->byteLength); @@ -1923,8 +1939,8 @@ JsProxy_create_subtype(int flags) FAIL_IF_NULL(result); if (flags & IS_CALLABLE) { // Python 3.9 provides an alternate way to do this by setting a special - // member __vectorcall_offset__ but it doesn't work in 3.8. I like this - // approach better. + // member __vectorcall_offset__, we might consider switching to using that + // approach. ((PyTypeObject*)result)->tp_vectorcall_offset = offsetof(JsProxy, vectorcall); } @@ -2129,6 +2145,8 @@ JsProxy_init(PyObject* core_module) JsProxy_TypeDict = PyDict_New(); FAIL_IF_NULL(JsProxy_TypeDict); + PyModule_AddObject(core_module, "jsproxy_typedict", JsProxy_TypeDict); + PyExc_BaseException_Type = (PyTypeObject*)PyExc_BaseException; _Exc_JsException.tp_base = (PyTypeObject*)PyExc_Exception; diff --git a/src/core/keyboard_interrupt.c b/src/core/keyboard_interrupt.c deleted file mode 100644 index e8e8f11c30e..00000000000 --- a/src/core/keyboard_interrupt.c +++ /dev/null @@ -1,35 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include "Python.h" - -#include "keyboard_interrupt.h" -#include - -static int callback_clock = 50; - -int -pyodide_callback(void) -{ - callback_clock--; - if (callback_clock == 0) { - callback_clock = 50; - int interrupt_buffer = EM_ASM_INT({ - let result = Module.interrupt_buffer[0]; - Module.interrupt_buffer[0] = 0; - return result; - }); - if (interrupt_buffer == 2) { - PyErr_SetInterrupt(); - } - } - return 0; -} - -void -set_pyodide_callback(int x) -{ - if (x) { - PyPyodide_SetPyodideCallback(pyodide_callback); - } else { - PyPyodide_SetPyodideCallback(NULL); - } -} diff --git a/src/core/keyboard_interrupt.h b/src/core/keyboard_interrupt.h deleted file mode 100644 index 01d0fb13ed7..00000000000 --- a/src/core/keyboard_interrupt.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef KEYBOARD_INTERRUPT_H -#define KEYBOARD_INTERRUPT_H - -int -keyboard_interrupt_init(); - -#endif /* KEYBOARD_INTERRUPT_H */ diff --git a/src/core/main.c b/src/core/main.c index 7981155180f..b3fc51c62c4 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -9,7 +9,6 @@ #include "hiwire.h" #include "js2python.h" #include "jsproxy.h" -#include "keyboard_interrupt.h" #include "pyproxy.h" #include "python2js.h" #include "python2js_buffer.h" @@ -34,11 +33,20 @@ #define TRY_INIT(mod) \ do { \ + int mod##_init(); \ if (mod##_init()) { \ FATAL_ERROR("Failed to initialize module %s.", #mod); \ } \ } while (0) +#define TRY_INIT_WITH_CORE_MODULE(mod) \ + do { \ + int mod##_init(PyObject* mod); \ + if (mod##_init(core_module)) { \ + FATAL_ERROR("Failed to initialize module %s.", #mod); \ + } \ + } while (0) + // Initialize python. exit() and print message to stderr on failure. static void initialize_python() @@ -50,7 +58,6 @@ initialize_python() status = PyConfig_SetBytesString(&config, &config.home, "/"); FAIL_IF_STATUS_EXCEPTION(status); config.write_bytecode = false; - config.install_signal_handlers = false; status = Py_InitializeFromConfig(&config); FAIL_IF_STATUS_EXCEPTION(status); @@ -62,12 +69,6 @@ initialize_python() Py_ExitStatusException(status); } } -#define TRY_INIT_WITH_CORE_MODULE(mod) \ - do { \ - if (mod##_init(core_module)) { \ - FATAL_ERROR("Failed to initialize module %s.", #mod); \ - } \ - } while (0) static struct PyModuleDef core_module_def = { PyModuleDef_HEAD_INIT, @@ -76,17 +77,6 @@ static struct PyModuleDef core_module_def = { .m_size = -1, }; -// from numpy_patch.c (no need for a header just for this) -int -numpy_patch_init(); - -int -get_python_stack_depth() -{ - PyThreadState* tstate = PyThreadState_GET(); - return tstate->recursion_depth; -} - /** * Bootstrap steps here: * 1. Import _pyodide package (we depend on this in _pyodide_core) @@ -110,13 +100,6 @@ main(int argc, char** argv) // This exits and prints a message to stderr on failure, // no status code to check. initialize_python(); - - if (alignof(JsRef) != alignof(int)) { - FATAL_ERROR("JsRef doesn't have the same alignment as int."); - } - if (sizeof(JsRef) != sizeof(int)) { - FATAL_ERROR("JsRef doesn't have the same size as int."); - } emscripten_exit_with_live_runtime(); return 0; } @@ -141,7 +124,6 @@ pyodide_init(void) TRY_INIT_WITH_CORE_MODULE(error_handling); TRY_INIT(hiwire); TRY_INIT(docstring); - TRY_INIT(numpy_patch); TRY_INIT(js2python); TRY_INIT_WITH_CORE_MODULE(python2js); TRY_INIT(python2js_buffer); @@ -158,7 +140,7 @@ pyodide_init(void) if (_pyodide_proxy == NULL) { FATAL_ERROR("Failed to create _pyodide proxy."); } - EM_ASM({ Module._pyodide = Module.hiwire.pop_value($0); }, _pyodide_proxy); + EM_ASM({ API._pyodide = Hiwire.pop_value($0); }, _pyodide_proxy); Py_CLEAR(_pyodide); Py_CLEAR(core_module); diff --git a/src/core/numpy_patch.c b/src/core/numpy_patch.c deleted file mode 100644 index c49c04e09e8..00000000000 --- a/src/core/numpy_patch.c +++ /dev/null @@ -1,266 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include "Python.h" - -#include "error_handling.h" - -// Stuff copied from ndarraytypes.h to check offsets -// First we need a couple of things. -typedef int npy_intp; -typedef void PyArray_Descr; // makes it opaque -typedef bool npy_bool; - -#if true /* begin copied from numpy */ - -#define NPY_MAXDIMS 32 -#define NPY_MAXARGS 32 - -/* - * The main array object structure. - * - * It has been recommended to use the inline functions defined below - * (PyArray_DATA and friends) to access fields here for a number of - * releases. Direct access to the members themselves is deprecated. - * To ensure that your code does not use deprecated access, - * #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION - * (or NPY_1_8_API_VERSION or higher as required). - */ -/* This struct will be moved to a private header in a future release */ -typedef struct tagPyArrayObject_fields -{ - PyObject_HEAD - /* Pointer to the raw data buffer */ - char* data; - /* The number of dimensions, also called 'ndim' */ - int nd; - /* The size in each dimension, also called 'shape' */ - npy_intp* dimensions; - /* - * Number of bytes to jump to get to the - * next element in each dimension - */ - npy_intp* strides; - /* - * This object is decref'd upon - * deletion of array. Except in the - * case of WRITEBACKIFCOPY which has - * special handling. - * - * For views it points to the original - * array, collapsed so no chains of - * views occur. - * - * For creation from buffer object it - * points to an object that should be - * decref'd on deletion - * - * For WRITEBACKIFCOPY flag this is an - * array to-be-updated upon calling - * PyArray_ResolveWritebackIfCopy - */ - PyObject* base; - /* Pointer to type structure */ - PyArray_Descr* descr; - /* Flags describing array -- see below */ - int flags; - /* For weak references */ - PyObject* weakreflist; -} PyArrayObject_fields; - -/* - * Can't put this in npy_deprecated_api.h like the others. - * PyArrayObject field access is deprecated as of NumPy 1.7. - */ -typedef PyArrayObject_fields PyArrayObject; - -/* FWD declaration */ -typedef struct PyArrayIterObject_tag PyArrayIterObject; - -/* - * type of the function which translates a set of coordinates to a - * pointer to the data - */ -typedef char* (*npy_iter_get_dataptr_t)(PyArrayIterObject* iter, npy_intp*); - -struct PyArrayIterObject_tag -{ - PyObject_HEAD int nd_m1; /* number of dimensions - 1 */ - npy_intp index, size; - npy_intp coordinates[NPY_MAXDIMS]; /* N-dimensional loop */ - npy_intp dims_m1[NPY_MAXDIMS]; /* ao->dimensions - 1 */ - npy_intp strides[NPY_MAXDIMS]; /* ao->strides or fake */ - npy_intp backstrides[NPY_MAXDIMS]; /* how far to jump back */ - npy_intp factors[NPY_MAXDIMS]; /* shape factors */ - PyArrayObject* ao; - char* dataptr; /* pointer to current item*/ - npy_bool contiguous; - - npy_intp bounds[NPY_MAXDIMS][2]; - npy_intp limits[NPY_MAXDIMS][2]; - npy_intp limits_sizes[NPY_MAXDIMS]; - npy_iter_get_dataptr_t translate; -}; - -typedef struct -{ - PyObject_HEAD int numiter; /* number of iters */ - npy_intp size; /* broadcasted size */ - npy_intp index; /* current index */ - int nd; /* number of dims */ - npy_intp dimensions[NPY_MAXDIMS]; /* dimensions */ - PyArrayIterObject* iters[NPY_MAXARGS]; /* iterators */ -} PyArrayMultiIterObject; - -#endif /* end copied from numpy */ - -// To minimize change of confusion, make sure variables in here don't match any -// variables used in PyArray_Broadcast_part1 -#define offset_Array_nd 12 -#define offset_Array_dimensions 16 - -#define offset_Iter_ao 660 - -#define offset_MultIter_numiter 8 -#define offset_MultIter_nd 20 -#define offset_MultIter_dimensions 24 -#define offset_MultIter_iters 152 - -/** - * Check that our offsets match the numpy declarations. - */ -// clang-format off -int numpy_patch_init(){ - assert(offset_Array_nd == offsetof(PyArrayObject, nd)); - assert(offset_Array_dimensions == offsetof(PyArrayObject, dimensions)); - - assert(offset_Iter_ao == offsetof(PyArrayIterObject, ao)); - - assert(offset_MultIter_numiter == offsetof(PyArrayMultiIterObject, numiter)); - assert(offset_MultIter_nd == offsetof(PyArrayMultiIterObject, nd)); - assert(offset_MultIter_dimensions == offsetof(PyArrayMultiIterObject, dimensions)); - assert(offset_MultIter_iters == offsetof(PyArrayMultiIterObject, iters)); - return 0; -} -// clang-format on - -// clang-format off -#define LOAD(ptr) HEAP32[(ptr)/4] -#define LOAD_ARRAY(ptr, idx) LOAD(ptr + 4*idx) - -#define Array_nd(ptr) LOAD(ptr + offset_Array_nd) -#define Array_dimensions(ptr) LOAD(ptr + offset_Array_dimensions) - -#define Iter_array(ptr) LOAD(ptr + offset_Iter_ao) - -#define MultiIter_numiter(ptr) LOAD(ptr + offset_MultIter_numiter) -#define MultiIter_nd(ptr) LOAD(ptr + offset_MultIter_nd) -#define MultiIter_iter(ptr, index) LOAD_ARRAY(ptr + offset_MultIter_iters, index) -#define MultiIter_dimension(ptr, index) LOAD_ARRAY(ptr + offset_MultIter_dimensions, index) -// clang-format on - -/** - * It's annoying to set errors from JavaScript. - */ -void -set_shape_mismatch_err() -{ - PyErr_SetString( - PyExc_ValueError, - "shape mismatch: objects cannot be broadcast to a single shape"); -} - -/** - * This is basically a 1-1 port of the first segment of PyArray_Broadcast. - * I rearranged the code a small amount to save effort. - * Pretty much all that happened is that we destroyed type information, replaced - * declarations with "let", and had to make special macros to do most memory - * access which is much more annoying than in JavaScript. - * - * See below for the C equivalent. - */ -EM_JS_NUM(int, PyArray_Broadcast_part1, (void* mit), { - let numiter = MultiIter_numiter(mit); - /* Discover the broadcast number of dimensions */ - let nd = 0; - for (let i = 0; i < numiter; i++) { - let cur_nd = Array_nd(Iter_array(MultiIter_iter(mit, i))); - nd = (cur_nd > nd) ? cur_nd : nd; - } - MultiIter_nd(mit) = nd; - - /* Discover the broadcast shape in each dimension */ - let start_offset = (mit + offset_MultIter_dimensions) / 4; - HEAP32.subarray(start_offset, start_offset + nd).fill(1); - - for (let j = 0; j < numiter; j++) { - let it = MultiIter_iter(mit, j); - for (i = 0; i < nd; i++) { - /* This prepends 1 to shapes not already equal to nd */ - let cur_array = Iter_array(it); - let cur_nd = Array_nd(cur_array); - let k = i + cur_nd - nd; - if (k >= 0) { - let tmp = LOAD_ARRAY(Array_dimensions(cur_array), k); - if (tmp == 1) { - continue; - } - let mit_dim_i = MultiIter_dimension(mit, i); - if (mit_dim_i == 1) { - MultiIter_dimension(mit, i) = tmp; - } else if (mit_dim_i != tmp) { - _set_shape_mismatch_err(); - return -1; - } - } - } - } -}) - -// Here is the C code I based the above function on. -// This is lightly reorganized from the original definition. -/* -NPY_NO_EXPORT int -PyArray_Broadcast(PyArrayMultiIterObject *mit) -{ - int i, nd, k, j; - npy_intp tmp, tmp2; - PyArrayIterObject *it; - PyArrayIterObject **it_ptr; - - / * Discover the broadcast number of dimensions * / - for (i = 0, nd = 0; i < mit->numiter; i++) { - nd = PyArray_MAX(nd, PyArray_NDIM(mit->iters[i]->ao)); - } - mit->nd = nd; - - / * Discover the broadcast shape in each dimension * / - for (i = 0; i < nd; i++) { - mit->dimensions[i] = 1; - } - - for (j = 0; j < mit->numiter; j++) { - it = mit->iters[j]; - for (i = 0; i < nd; i++) { - / * This prepends 1 to shapes not already equal to nd * / - k = i + PyArray_NDIM(it->ao) - nd; - if (k >= 0) { - tmp = PyArray_DIMS(it->ao)[k]; - if (tmp == 1) { - continue; - } - if (mit->dimensions[i] == 1) { - mit->dimensions[i] = tmp; - } - else if (mit->dimensions[i] != tmp) { - PyErr_SetString(PyExc_ValueError, - "shape mismatch: objects" \ - " cannot be broadcast" \ - " to a single shape"); - return -1; - } - } - } - } - - // Rest of function skipped -} -*/ diff --git a/src/core/pre.js b/src/core/pre.js new file mode 100644 index 00000000000..9a8b6e21a31 --- /dev/null +++ b/src/core/pre.js @@ -0,0 +1,5 @@ +const API = Module.API; +const Hiwire = {}; +const Tests = {}; +API.tests = Tests; +Module.hiwire = Hiwire; diff --git a/src/core/pyproxy.c b/src/core/pyproxy.c index 63cb4a3f0e6..348c77b4ae8 100644 --- a/src/core/pyproxy.c +++ b/src/core/pyproxy.c @@ -8,6 +8,7 @@ #include "js2python.h" #include "jsmemops.h" // for pyproxy.js #include "jsproxy.h" +#include "pyproxy.h" #include "python2js.h" _Py_IDENTIFIER(result); @@ -21,8 +22,8 @@ EM_JS(int, pyproxy_Check, (JsRef x), { if (x == 0) { return false; } - let val = Module.hiwire.get_value(x); - return Module.isPyProxy(val); + let val = Hiwire.get_value(x); + return API.isPyProxy(val); }); EM_JS(void, destroy_proxies, (JsRef proxies_id, char* msg_ptr), { @@ -30,12 +31,20 @@ EM_JS(void, destroy_proxies, (JsRef proxies_id, char* msg_ptr), { if (msg_ptr) { msg = UTF8ToString(msg_ptr); } - let proxies = Module.hiwire.get_value(proxies_id); + let proxies = Hiwire.get_value(proxies_id); for (let px of proxies) { Module.pyproxy_destroy(px, msg); } }); +EM_JS(void, destroy_proxy, (JsRef proxy_id, char* msg_ptr), { + let msg = undefined; + if (msg_ptr) { + msg = UTF8ToString(msg_ptr); + } + Module.pyproxy_destroy(Module.hiwire.get_value(proxy_id), msg); +}); + static PyObject* asyncio; // Flags controlling presence or absence of many small mixins depending on which @@ -105,7 +114,8 @@ pyproxy_getflags(PyObject* pyobj) result |= HAS_GET; } else if (PyType_Check(pyobj)) { _Py_IDENTIFIER(__class_getitem__); - if (_PyObject_HasAttrId(pyobj, &PyId___class_getitem__)) { + PyObject* oname = _PyUnicode_FromId(&PyId___class_getitem__); /* borrowed */ + if (PyObject_HasAttr(pyobj, oname)) { result |= HAS_GET; } } @@ -236,18 +246,18 @@ int _PyObject_GetMethod(PyObject* obj, PyObject* name, PyObject** method); EM_JS(JsRef, proxy_cache_get, (JsRef proxyCacheId, PyObject* descr), { - let proxyCache = Module.hiwire.get_value(proxyCacheId); + let proxyCache = Hiwire.get_value(proxyCacheId); let proxyId = proxyCache.get(descr); if (!proxyId) { return undefined; } // Okay found a proxy. Is it alive? - if (Module.hiwire.get_value(proxyId).$$.ptr) { + if (Hiwire.get_value(proxyId).$$.ptr) { return proxyId; } else { // It's dead, tidy up proxyCache.delete(descr); - Module.hiwire.decref(proxyId); + Hiwire.decref(proxyId); return undefined; } }) @@ -256,7 +266,7 @@ EM_JS(JsRef, proxy_cache_get, (JsRef proxyCacheId, PyObject* descr), { EM_JS(void, proxy_cache_set, (JsRef proxyCacheId, PyObject* descr, JsRef proxy), { - let proxyCache = Module.hiwire.get_value(proxyCacheId); + let proxyCache = Hiwire.get_value(proxyCacheId); proxyCache.set(descr, proxy); }) // clang-format on @@ -471,7 +481,7 @@ _pyproxy_ownKeys(PyObject* pyobj) } /** - * This sets up a call to _PyObject_Vectorcall. It's a helper fucntion for + * This sets up a call to _PyObject_Vectorcall. It's a helper function for * callPyObjectKwargs. This is the primary entrypoint from JavaScript into * Python code. * @@ -570,66 +580,30 @@ _pyproxy_iter_next(PyObject* iterator) return result; } -/** - * In Python 3.10, they have added the PyIter_Send API (and removed _PyGen_Send) - * so in v3.10 this would be a simple API call wrapper like the rest of the code - * here. For now, we're just copying the YIELD_FROM opcode (see ceval.c). - * - * When the iterator is done, it returns NULL and sets StopIteration. We'll use - * _pyproxyGen_FetchStopIterationValue below to get the return value of the - * generator (again copying from YIELD_FROM). - */ -JsRef -_pyproxyGen_Send(PyObject* receiver, JsRef jsval) +PySendResult +_pyproxyGen_Send(PyObject* receiver, JsRef jsval, JsRef* result) { bool success = false; PyObject* v = NULL; PyObject* retval = NULL; - JsRef jsresult = NULL; v = js2python(jsval); FAIL_IF_NULL(v); - if (PyGen_CheckExact(receiver) || PyCoro_CheckExact(receiver)) { - retval = _PyGen_Send((PyGenObject*)receiver, v); - } else if (v == Py_None) { - retval = Py_TYPE(receiver)->tp_iternext(receiver); - } else { - _Py_IDENTIFIER(send); - retval = _PyObject_CallMethodIdOneArg(receiver, &PyId_send, v); + PySendResult status = PyIter_Send(receiver, v, &retval); + if (status == PYGEN_ERROR) { + FAIL(); } - FAIL_IF_NULL(retval); - - jsresult = python2js(retval); - FAIL_IF_NULL(jsresult); + *result = python2js(retval); + FAIL_IF_NULL(*result); success = true; finally: Py_CLEAR(v); Py_CLEAR(retval); if (!success) { - hiwire_CLEAR(jsresult); + status = PYGEN_ERROR; } - return jsresult; -} - -/** - * If StopIteration was set, return the value it was set with. Otherwise, return - * NULL. - */ -JsRef -_pyproxyGen_FetchStopIterationValue() -{ - PyObject* val = NULL; - // cf implementation of YIELD_FROM opcode in ceval.c - // _PyGen_FetchStopIterationValue returns an error code, but it seems - // redundant - _PyGen_FetchStopIterationValue(&val); - if (val == NULL) { - return NULL; - } - JsRef result = python2js(val); - Py_CLEAR(val); - return result; + return status; } /////////////////////////////////////////////////////////////////////////////// @@ -805,7 +779,7 @@ size_t py_buffer_shape_offset = offsetof(Py_buffer, shape); * Convert a C array of Py_ssize_t to JavaScript. */ EM_JS_REF(JsRef, array_to_js, (Py_ssize_t * array, int len), { - return Module.hiwire.new_value( + return Hiwire.new_value( Array.from(HEAP32.subarray(array / 4, array / 4 + len))); }) @@ -838,7 +812,7 @@ size_t buffer_struct_size = sizeof(buffer_struct); * * We use PyObject_GetBuffer to acquire a Py_buffer view to the object, then we * determine the locations of: the first element of the buffer, the earliest - * element of the buffer in memory the lastest element of the buffer in memory + * element of the buffer in memory the latest element of the buffer in memory * (plus one itemsize). * * We will use this information to slice out a subarray of the wasm heap that @@ -937,7 +911,7 @@ _pyproxy_get_buffer(buffer_struct* target, PyObject* ptrobj) } EM_JS_REF(JsRef, pyproxy_new, (PyObject * ptrobj), { - return Module.hiwire.new_value(Module.pyproxy_new(ptrobj)); + return Hiwire.new_value(Module.pyproxy_new(ptrobj)); }); /** @@ -970,7 +944,7 @@ EM_JS_REF(JsRef, create_once_callable, (PyObject * obj), { _Py_DecRef(obj); }; Module.finalizationRegistry.register(wrapper, [ obj, undefined ], wrapper); - return Module.hiwire.new_value(wrapper); + return Hiwire.new_value(wrapper); }); static PyObject* @@ -1021,7 +995,7 @@ EM_JS_REF(JsRef, create_promise_handles, ( } let done_callback = (x) => {}; if(done_callback_id){ - done_callback = Module.hiwire.get_value(done_callback_id); + done_callback = Hiwire.get_value(done_callback_id); } let used = false; function checkUsed(){ @@ -1063,7 +1037,7 @@ EM_JS_REF(JsRef, create_promise_handles, ( } onFulfilled.destroy = destroy; onRejected.destroy = destroy; - return Module.hiwire.new_value( + return Hiwire.new_value( [onFulfilled, onRejected] ); }) diff --git a/src/core/pyproxy.h b/src/core/pyproxy.h index fa2f9a482c3..7f86e203ef3 100644 --- a/src/core/pyproxy.h +++ b/src/core/pyproxy.h @@ -25,11 +25,17 @@ int pyproxy_Check(JsRef x); /** - * Destroy a list of PyProxies. Steals the reference to the list. + * Destroy a list of PyProxies. */ void destroy_proxies(JsRef proxies_id, char* msg); +/** + * Destroy a PyProxy. + */ +void +destroy_proxy(JsRef proxy, char* msg); + /** * Wrap a Python callable in a JavaScript function that can be called once. * After being called, the reference count of the python object is automatically @@ -54,4 +60,10 @@ create_promise_handles(PyObject* onfulfilled, int pyproxy_init(); +// These are defined as an enum in Python.h but we want to use them in +// pyproxy.ts. +#define PYGEN_NEXT 1 +#define PYGEN_RETURN 0 +#define PYGEN_ERROR -1 + #endif /* PYPROXY_H */ diff --git a/src/core/pyproxy.js b/src/core/pyproxy.ts similarity index 67% rename from src/core/pyproxy.js rename to src/core/pyproxy.ts index a7a8e1bbb50..b18a00d419c 100644 --- a/src/core/pyproxy.js +++ b/src/core/pyproxy.ts @@ -11,33 +11,70 @@ * any macros available in pyproxy.c are available here. We only need the flags * macros HAS_LENGTH, etc. * - * See Makefile recipe for src/js/pyproxy.js + * See Makefile recipe for src/js/pyproxy.gen.ts */ -import { Module } from "./module.js"; +declare var Module: any; +declare var Hiwire: any; +declare var API: any; + +// pyodide-skip + +// Just for this file, we implement a special "skip" pragma. These lines are +// skipped by the Makefile when producing pyproxy.gen.ts These are actually C +// macros, but we declare them to make typescript okay with processing the raw +// file. We need to process the raw file to generate the docs because the C +// preprocessor deletes comments which kills all the docstrings. + +// These declarations make Typescript accept the raw file. However, if we macro +// preprocess these lines, we get a bunch of syntax errors so they need to be +// removed from the preprocessed version. + +// This also has the benefit that it makes intellisense happy. +declare var IS_CALLABLE: number; +declare var HAS_LENGTH: number; +declare var HAS_GET: number; +declare var HAS_SET: number; +declare var HAS_CONTAINS: number; +declare var IS_ITERABLE: number; +declare var IS_ITERATOR: number; +declare var IS_AWAITABLE: number; +declare var IS_BUFFER: number; + +declare var PYGEN_NEXT: number; +declare var PYGEN_RETURN: number; +declare var PYGEN_ERROR: number; + +declare function DEREF_U32(ptr: number, offset: number): number; +// end-pyodide-skip /** * Is the argument a :any:`PyProxy`? - * @param jsobj {any} Object to test. - * @returns {jsobj is PyProxy} Is ``jsobj`` a :any:`PyProxy`? + * @param jsobj Object to test. + * @returns Is ``jsobj`` a :any:`PyProxy`? */ -export function isPyProxy(jsobj) { +export function isPyProxy(jsobj: any): jsobj is PyProxy { return !!jsobj && jsobj.$$ !== undefined && jsobj.$$.type === "PyProxy"; } -Module.isPyProxy = isPyProxy; +API.isPyProxy = isPyProxy; + +declare var FinalizationRegistry: any; +declare var globalThis: any; if (globalThis.FinalizationRegistry) { - Module.finalizationRegistry = new FinalizationRegistry(([ptr, cache]) => { - cache.leaked = true; - pyproxy_decref_cache(cache); - try { - Module._Py_DecRef(ptr); - } catch (e) { - // I'm not really sure what happens if an error occurs inside of a - // finalizer... - Module.fatal_error(e); + Module.finalizationRegistry = new FinalizationRegistry( + ([ptr, cache]: [ptr: number, cache: PyProxyCache]) => { + cache.leaked = true; + pyproxy_decref_cache(cache); + try { + Module._Py_DecRef(ptr); + } catch (e) { + // I'm not really sure what happens if an error occurs inside of a + // finalizer... + API.fatal_error(e); + } } - }); + ); // For some unclear reason this code screws up selenium FirefoxDriver. Works // fine in chrome and when I test it in browser. It seems to be sensitive to // changes that don't make a difference to the semantics. @@ -47,7 +84,7 @@ if (globalThis.FinalizationRegistry) { // Module._PyBuffer_Release(ptr); // Module._PyMem_Free(ptr); // } catch (e) { - // Module.fatal_error(e); + // API.fatal_error(e); // } // }); } else { @@ -57,25 +94,27 @@ if (globalThis.FinalizationRegistry) { let pyproxy_alloc_map = new Map(); Module.pyproxy_alloc_map = pyproxy_alloc_map; -let trace_pyproxy_alloc; -let trace_pyproxy_dealloc; +let trace_pyproxy_alloc: (proxy: any) => void; +let trace_pyproxy_dealloc: (proxy: any) => void; Module.enable_pyproxy_allocation_tracing = function () { - trace_pyproxy_alloc = function (proxy) { + trace_pyproxy_alloc = function (proxy: any) { pyproxy_alloc_map.set(proxy, Error().stack); }; - trace_pyproxy_dealloc = function (proxy) { + trace_pyproxy_dealloc = function (proxy: any) { pyproxy_alloc_map.delete(proxy); }; }; Module.disable_pyproxy_allocation_tracing = function () { - trace_pyproxy_alloc = function (proxy) {}; - trace_pyproxy_dealloc = function (proxy) {}; + trace_pyproxy_alloc = function (proxy: any) {}; + trace_pyproxy_dealloc = function (proxy: any) {}; }; Module.disable_pyproxy_allocation_tracing(); +type PyProxyCache = { cacheId: number; refcnt: number; leaked?: boolean }; + /** - * Create a new PyProxy wraping ptrobj which is a PyObject*. + * Create a new PyProxy wrapping ptrobj which is a PyObject*. * * The argument cache is only needed to implement the PyProxy.copy API, it * allows the copy of the PyProxy to share its attribute cache with the original @@ -87,7 +126,7 @@ Module.disable_pyproxy_allocation_tracing(); * many as possible. * @private */ -Module.pyproxy_new = function (ptrobj, cache) { +Module.pyproxy_new = function (ptrobj: number, cache?: PyProxyCache) { let flags = Module._pyproxy_getflags(ptrobj); let cls = Module.getPyProxyClass(flags); // Reflect.construct calls the constructor of Module.PyProxyClass but sets @@ -112,7 +151,7 @@ Module.pyproxy_new = function (ptrobj, cache) { if (!cache) { // The cache needs to be accessed primarily from the C function // _pyproxy_getattr so we make a hiwire id. - let cacheId = Module.hiwire.new_value(new Map()); + let cacheId = Hiwire.new_value(new Map()); cache = { cacheId, refcnt: 0 }; } cache.refcnt++; @@ -126,32 +165,25 @@ Module.pyproxy_new = function (ptrobj, cache) { return proxy; }; -function _getPtr(jsobj) { - let ptr = jsobj.$$.ptr; - if (ptr === null) { - throw new Error( - jsobj.$$.destroyed_msg || "Object has already been destroyed" - ); +function _getPtr(jsobj: any) { + let ptr: number = jsobj.$$.ptr; + if (ptr === 0) { + throw new Error(jsobj.$$.destroyed_msg); } return ptr; } let pyproxyClassMap = new Map(); /** - * Retreive the appropriate mixins based on the features requested in flags. + * Retrieve the appropriate mixins based on the features requested in flags. * Used by pyproxy_new. The "flags" variable is produced by the C function * pyproxy_getflags. Multiple PyProxies with the same set of feature flags * will share the same prototype, so the memory footprint of each individual * PyProxy is minimal. * @private */ -Module.getPyProxyClass = function (flags) { - let result = pyproxyClassMap.get(flags); - if (result) { - return result; - } - let descriptors = {}; - for (let [feature_flag, methods] of [ +Module.getPyProxyClass = function (flags: number) { + const FLAG_TYPE_PAIRS: [number, any][] = [ [HAS_LENGTH, PyProxyLengthMethods], [HAS_GET, PyProxyGetItemMethods], [HAS_SET, PyProxySetItemMethods], @@ -161,7 +193,13 @@ Module.getPyProxyClass = function (flags) { [IS_AWAITABLE, PyProxyAwaitableMethods], [IS_BUFFER, PyProxyBufferMethods], [IS_CALLABLE, PyProxyCallableMethods], - ]) { + ]; + let result = pyproxyClassMap.get(flags); + if (result) { + return result; + } + let descriptors: any = {}; + for (let [feature_flag, methods] of FLAG_TYPE_PAIRS) { if (flags & feature_flag) { Object.assign( descriptors, @@ -192,15 +230,15 @@ const pyproxy_cache_destroyed_msg = "This borrowed attribute proxy was automatically destroyed in the " + "process of destroying the proxy it was borrowed from. Try using the 'copy' method."; -function pyproxy_decref_cache(cache) { +function pyproxy_decref_cache(cache: PyProxyCache) { if (!cache) { return; } cache.refcnt--; if (cache.refcnt === 0) { - let cache_map = Module.hiwire.pop_value(cache.cacheId); + let cache_map = Hiwire.pop_value(cache.cacheId); for (let proxy_id of cache_map.values()) { - const cache_entry = Module.hiwire.pop_value(proxy_id); + const cache_entry = Hiwire.pop_value(proxy_id); if (!cache.leaked) { Module.pyproxy_destroy(cache_entry, pyproxy_cache_destroyed_msg); } @@ -208,30 +246,46 @@ function pyproxy_decref_cache(cache) { } } -Module.pyproxy_destroy = function (proxy, destroyed_msg) { - if (proxy.$$.ptr === null) { +Module.pyproxy_destroy = function (proxy: PyProxy, destroyed_msg: string) { + if (proxy.$$.ptr === 0) { return; } let ptrobj = _getPtr(proxy); Module.finalizationRegistry.unregister(proxy); + destroyed_msg = destroyed_msg || "Object has already been destroyed"; + let proxy_type = proxy.type; + let proxy_repr; + try { + proxy_repr = proxy.toString(); + } catch (e) { + if (e.pyodide_fatal_error) { + throw e; + } + } // Maybe the destructor will call JavaScript code that will somehow try // to use this proxy. Mark it deleted before decrementing reference count // just in case! - proxy.$$.ptr = null; + proxy.$$.ptr = 0; + destroyed_msg += "\n" + `The object was of type "${proxy_type}" and `; + if (proxy_repr) { + destroyed_msg += `had repr "${proxy_repr}"`; + } else { + destroyed_msg += "an error was raised when trying to generate its repr"; + } proxy.$$.destroyed_msg = destroyed_msg; pyproxy_decref_cache(proxy.$$.cache); try { Module._Py_DecRef(ptrobj); trace_pyproxy_dealloc(proxy); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } }; // Now a lot of boilerplate to wrap the abstract Object protocol wrappers // defined in pyproxy.c in JavaScript functions. -Module.callPyObjectKwargs = function (ptrobj, ...jsargs) { +Module.callPyObjectKwargs = function (ptrobj: number, ...jsargs: any) { // We don't do any checking for kwargs, checks are in PyProxy.callKwargs // which only is used when the keyword arguments come from the user. let kwargs = jsargs.pop(); @@ -241,8 +295,8 @@ Module.callPyObjectKwargs = function (ptrobj, ...jsargs) { let num_kwargs = kwargs_names.length; jsargs.push(...kwargs_values); - let idargs = Module.hiwire.new_value(jsargs); - let idkwnames = Module.hiwire.new_value(kwargs_names); + let idargs = Hiwire.new_value(jsargs); + let idkwnames = Hiwire.new_value(kwargs_names); let idresult; try { idresult = Module.__pyproxy_apply( @@ -253,26 +307,33 @@ Module.callPyObjectKwargs = function (ptrobj, ...jsargs) { num_kwargs ); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idargs); - Module.hiwire.decref(idkwnames); + Hiwire.decref(idargs); + Hiwire.decref(idkwnames); } if (idresult === 0) { Module._pythonexc2js(); } - return Module.hiwire.pop_value(idresult); + let result = Hiwire.pop_value(idresult); + // Automatically schedule coroutines + if (result && result.type === "coroutine" && result._ensure_future) { + result._ensure_future(); + } + return result; }; -Module.callPyObject = function (ptrobj, ...jsargs) { +Module.callPyObject = function (ptrobj: number, ...jsargs: any) { return Module.callPyObjectKwargs(ptrobj, ...jsargs, {}); }; -/** - * @typedef {(PyProxyClass & {[x : string] : Py2JsResult})} PyProxy - * @typedef { PyProxy | number | bigint | string | boolean | undefined } Py2JsResult - */ -class PyProxyClass { +export type PyProxy = PyProxyClass & { [x: string]: any }; + +export class PyProxyClass { + $$: { ptr: number; cache: PyProxyCache; destroyed_msg?: string }; + $$flags: number; + + /** @private */ constructor() { throw new TypeError("PyProxy is not a constructor"); } @@ -294,51 +355,45 @@ class PyProxyClass { * else: * ty.__module__ + "." + ty.__name__ * - * @type {string} */ - get type() { + get type(): string { let ptrobj = _getPtr(this); - return Module.hiwire.pop_value(Module.__pyproxy_type(ptrobj)); + return Hiwire.pop_value(Module.__pyproxy_type(ptrobj)); } - /** - * @returns {string} - */ - toString() { + toString(): string { let ptrobj = _getPtr(this); let jsref_repr; try { jsref_repr = Module.__pyproxy_repr(ptrobj); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } if (jsref_repr === 0) { Module._pythonexc2js(); } - return Module.hiwire.pop_value(jsref_repr); + return Hiwire.pop_value(jsref_repr); } /** - * Destroy the ``PyProxy``. This will release the memory. Any further - * attempt to use the object will raise an error. + * Destroy the ``PyProxy``. This will release the memory. Any further attempt + * to use the object will raise an error. * * In a browser supporting `FinalizationRegistry * `_ * Pyodide will automatically destroy the ``PyProxy`` when it is garbage - * collected, however there is no guarantee that the finalizer will be run - * in a timely manner so it is better to ``destroy`` the proxy explicitly. + * collected, however there is no guarantee that the finalizer will be run in + * a timely manner so it is better to ``destroy`` the proxy explicitly. * - * @param {string} [destroyed_msg] The error message to print if use is - * attempted after destroying. Defaults to "Object has already been - * destroyed". + * @param destroyed_msg The error message to print if use is attempted after + * destroying. Defaults to "Object has already been destroyed". */ - destroy(destroyed_msg) { + destroy(destroyed_msg?: string) { Module.pyproxy_destroy(this, destroyed_msg); } /** * Make a new PyProxy pointing to the same Python object. * Useful if the PyProxy is destroyed somewhere else. - * @returns {PyProxy} */ - copy() { + copy(): PyProxy { let ptrobj = _getPtr(this); return Module.pyproxy_new(ptrobj, this.$$.cache); } @@ -347,125 +402,141 @@ class PyProxyClass { * default does a deep conversion, if a shallow conversion is desired, you can * use ``proxy.toJs({depth : 1})``. See :ref:`Explicit Conversion of PyProxy * ` for more info. - * - * @param {object} options - * @param {number} [options.depth] How many layers deep to perform the - * conversion. Defaults to infinite. - * @param {array} [options.pyproxies] If provided, ``toJs`` will store all - * PyProxies created in this list. This allows you to easily destroy all the - * PyProxies by iterating the list without having to recurse over the - * generated structure. The most common use case is to create a new empty - * list, pass the list as `pyproxies`, and then later iterate over `pyproxies` - * to destroy all of created proxies. - * @param {boolean} [options.create_pyproxies] If false, ``toJs`` will throw a - * ``ConversionError`` rather than producing a ``PyProxy``. - * @param {boolean} [options.dict_converter] A function to be called on an - * iterable of pairs ``[key, value]``. Convert this iterable of pairs to the - * desired output. For instance, ``Object.fromEntries`` would convert the dict - * to an object, ``Array.from`` converts it to an array of entries, and ``(it) => - * new Map(it)`` converts it to a ``Map`` (which is the default behavior). - * @return {any} The JavaScript object resulting from the conversion. + * @param options + * @return The JavaScript object resulting from the conversion. */ toJs({ depth = -1, - pyproxies, + pyproxies = undefined, create_pyproxies = true, - dict_converter, - } = {}) { + dict_converter = undefined, + default_converter = undefined, + }: { + /** How many layers deep to perform the conversion. Defaults to infinite */ + depth?: number; + /** + * If provided, ``toJs`` will store all PyProxies created in this list. This + * allows you to easily destroy all the PyProxies by iterating the list + * without having to recurse over the generated structure. The most common + * use case is to create a new empty list, pass the list as `pyproxies`, and + * then later iterate over `pyproxies` to destroy all of created proxies. + */ + pyproxies?: PyProxy[]; + /** + * If false, ``toJs`` will throw a ``ConversionError`` rather than + * producing a ``PyProxy``. + */ + create_pyproxies?: boolean; + /** + * A function to be called on an iterable of pairs ``[key, value]``. Convert + * this iterable of pairs to the desired output. For instance, + * ``Object.fromEntries`` would convert the dict to an object, ``Array.from`` + * converts it to an array of entries, and ``(it) => new Map(it)`` converts + * it to a ``Map`` (which is the default behavior). + */ + dict_converter?: (array: Iterable<[key: string, value: any]>) => any; + /** + * Optional argument to convert objects with no default conversion. See the + * documentation of :any:`pyodide.to_js`. + */ + default_converter?: ( + obj: PyProxy, + convert: (obj: PyProxy) => any, + cacheConversion: (obj: PyProxy, result: any) => void + ) => any; + } = {}): any { let ptrobj = _getPtr(this); let idresult; let proxies_id; let dict_converter_id = 0; + let default_converter_id = 0; if (!create_pyproxies) { proxies_id = 0; } else if (pyproxies) { - proxies_id = Module.hiwire.new_value(pyproxies); + proxies_id = Hiwire.new_value(pyproxies); } else { - proxies_id = Module.hiwire.new_value([]); + proxies_id = Hiwire.new_value([]); } if (dict_converter) { - dict_converter_id = Module.hiwire.new_value(dict_converter); + dict_converter_id = Hiwire.new_value(dict_converter); + } + if (default_converter) { + default_converter_id = Hiwire.new_value(default_converter); } try { - idresult = Module._python2js_custom_dict_converter( + idresult = Module._python2js_custom( ptrobj, depth, proxies_id, - dict_converter_id + dict_converter_id, + default_converter_id ); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(proxies_id); - Module.hiwire.decref(dict_converter_id); + Hiwire.decref(proxies_id); + Hiwire.decref(dict_converter_id); + Hiwire.decref(default_converter_id); } if (idresult === 0) { Module._pythonexc2js(); } - return Module.hiwire.pop_value(idresult); + return Hiwire.pop_value(idresult); } /** * Check whether the :any:`PyProxy.length` getter is available on this PyProxy. A * Typescript type guard. - * @returns {this is PyProxyWithLength} */ - supportsLength() { + supportsLength(): this is PyProxyWithLength { return !!(this.$$flags & HAS_LENGTH); } /** * Check whether the :any:`PyProxy.get` method is available on this PyProxy. A * Typescript type guard. - * @returns {this is PyProxyWithGet} */ - supportsGet() { + supportsGet(): this is PyProxyWithGet { return !!(this.$$flags & HAS_GET); } /** * Check whether the :any:`PyProxy.set` method is available on this PyProxy. A * Typescript type guard. - * @returns {this is PyProxyWithSet} */ - supportsSet() { + supportsSet(): this is PyProxyWithSet { return !!(this.$$flags & HAS_SET); } /** * Check whether the :any:`PyProxy.has` method is available on this PyProxy. A * Typescript type guard. - * @returns {this is PyProxyWithHas} */ - supportsHas() { + supportsHas(): this is PyProxyWithHas { return !!(this.$$flags & HAS_CONTAINS); } /** * Check whether the PyProxy is iterable. A Typescript type guard for - * :any:`PyProxy.[Symbol.iterator]`. - * @returns {this is PyProxyIterable} + * :any:`PyProxy.[iterator]`. */ - isIterable() { + isIterable(): this is PyProxyIterable { return !!(this.$$flags & (IS_ITERABLE | IS_ITERATOR)); } /** * Check whether the PyProxy is iterable. A Typescript type guard for * :any:`PyProxy.next`. - * @returns {this is PyProxyIterator} */ - isIterator() { + isIterator(): this is PyProxyIterator { return !!(this.$$flags & IS_ITERATOR); } /** * Check whether the PyProxy is awaitable. A Typescript type guard, if this * function returns true Typescript considers the PyProxy to be a ``Promise``. - * @returns {this is PyProxyAwaitable} */ - isAwaitable() { + isAwaitable(): this is PyProxyAwaitable { return !!(this.$$flags & IS_AWAITABLE); } /** * Check whether the PyProxy is a buffer. A Typescript type guard for * :any:`PyProxy.getBuffer`. - * @returns {this is PyProxyBuffer} */ - isBuffer() { + isBuffer(): this is PyProxyBuffer { return !!(this.$$flags & IS_BUFFER); } /** @@ -473,32 +544,28 @@ class PyProxyClass { * returns true then Typescript considers the Proxy to be callable of * signature ``(args... : any[]) => PyProxy | number | bigint | string | * boolean | undefined``. - * @returns {this is PyProxyCallable} */ - isCallable() { + isCallable(): this is PyProxyCallable { return !!(this.$$flags & IS_CALLABLE); } } -/** - * @typedef { PyProxy & PyProxyLengthMethods } PyProxyWithLength - */ +export type PyProxyWithLength = PyProxy & PyProxyLengthMethods; // Controlled by HAS_LENGTH, appears for any object with __len__ or sq_length // or mp_length methods -class PyProxyLengthMethods { +export class PyProxyLengthMethods { /** * The length of the object. * * Present only if the proxied Python object has a ``__len__`` method. - * @returns {number} */ - get length() { + get length(): number { let ptrobj = _getPtr(this); let length; try { length = Module._PyObject_Size(ptrobj); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } if (length === -1) { Module._pythonexc2js(); @@ -507,34 +574,29 @@ class PyProxyLengthMethods { } } -/** - * @typedef {PyProxy & PyProxyGetItemMethods} PyProxyWithGet - */ +export type PyProxyWithGet = PyProxy & PyProxyGetItemMethods; // Controlled by HAS_GET, appears for any class with __getitem__, // mp_subscript, or sq_item methods -/** - * @interface - */ -class PyProxyGetItemMethods { +export class PyProxyGetItemMethods { /** * This translates to the Python code ``obj[key]``. * * Present only if the proxied Python object has a ``__getitem__`` method. * - * @param {any} key The key to look up. - * @returns {Py2JsResult} The corresponding value. + * @param key The key to look up. + * @returns The corresponding value. */ - get(key) { + get(key: any): any { let ptrobj = _getPtr(this); - let idkey = Module.hiwire.new_value(key); + let idkey = Hiwire.new_value(key); let idresult; try { idresult = Module.__pyproxy_getitem(ptrobj, idkey); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idkey); + Hiwire.decref(idkey); } if (idresult === 0) { if (Module._PyErr_Occurred()) { @@ -543,36 +605,34 @@ class PyProxyGetItemMethods { return undefined; } } - return Module.hiwire.pop_value(idresult); + return Hiwire.pop_value(idresult); } } -/** - * @typedef {PyProxy & PyProxySetItemMethods} PyProxyWithSet - */ +export type PyProxyWithSet = PyProxy & PyProxySetItemMethods; // Controlled by HAS_SET, appears for any class with __setitem__, __delitem__, // mp_ass_subscript, or sq_ass_item. -class PyProxySetItemMethods { +export class PyProxySetItemMethods { /** * This translates to the Python code ``obj[key] = value``. * * Present only if the proxied Python object has a ``__setitem__`` method. * - * @param {any} key The key to set. - * @param {any} value The value to set it to. + * @param key The key to set. + * @param value The value to set it to. */ - set(key, value) { + set(key: any, value: any) { let ptrobj = _getPtr(this); - let idkey = Module.hiwire.new_value(key); - let idval = Module.hiwire.new_value(value); + let idkey = Hiwire.new_value(key); + let idval = Hiwire.new_value(value); let errcode; try { errcode = Module.__pyproxy_setitem(ptrobj, idkey, idval); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idkey); - Module.hiwire.decref(idval); + Hiwire.decref(idkey); + Hiwire.decref(idval); } if (errcode === -1) { Module._pythonexc2js(); @@ -583,18 +643,18 @@ class PyProxySetItemMethods { * * Present only if the proxied Python object has a ``__delitem__`` method. * - * @param {any} key The key to delete. + * @param key The key to delete. */ - delete(key) { + delete(key: any) { let ptrobj = _getPtr(this); - let idkey = Module.hiwire.new_value(key); + let idkey = Hiwire.new_value(key); let errcode; try { errcode = Module.__pyproxy_delitem(ptrobj, idkey); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idkey); + Hiwire.decref(idkey); } if (errcode === -1) { Module._pythonexc2js(); @@ -602,31 +662,29 @@ class PyProxySetItemMethods { } } -/** - * @typedef {PyProxy & PyProxyContainsMethods} PyProxyWithHas - */ +export type PyProxyWithHas = PyProxy & PyProxyContainsMethods; // Controlled by HAS_CONTAINS flag, appears for any class with __contains__ or // sq_contains -class PyProxyContainsMethods { +export class PyProxyContainsMethods { /** * This translates to the Python code ``key in obj``. * * Present only if the proxied Python object has a ``__contains__`` method. * - * @param {*} key The key to check for. - * @returns {boolean} Is ``key`` present? + * @param key The key to check for. + * @returns Is ``key`` present? */ - has(key) { + has(key: any): boolean { let ptrobj = _getPtr(this); - let idkey = Module.hiwire.new_value(key); + let idkey = Hiwire.new_value(key); let result; try { result = Module.__pyproxy_contains(ptrobj, idkey); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idkey); + Hiwire.decref(idkey); } if (result === -1) { Module._pythonexc2js(); @@ -651,14 +709,14 @@ class PyProxyContainsMethods { * * @private */ -function* iter_helper(iterptr, token) { +function* iter_helper(iterptr: number, token: {}): Generator { try { let item; while ((item = Module.__pyproxy_iter_next(iterptr))) { - yield Module.hiwire.pop_value(item); + yield Hiwire.pop_value(item); } } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { Module.finalizationRegistry.unregister(token); Module._Py_DecRef(iterptr); @@ -668,15 +726,13 @@ function* iter_helper(iterptr, token) { } } -/** - * @typedef {PyProxy & PyProxyIterableMethods} PyProxyIterable - */ +export type PyProxyIterable = PyProxy & PyProxyIterableMethods; // Controlled by IS_ITERABLE, appears for any object with __iter__ or tp_iter, // unless they are iterators. See: https://docs.python.org/3/c-api/iter.html // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols // This avoids allocating a PyProxy wrapper for the temporary iterator. -class PyProxyIterableMethods { +export class PyProxyIterableMethods { /** * This translates to the Python code ``iter(obj)``. Return an iterator * associated to the proxy. See the documentation for `Symbol.iterator @@ -686,17 +742,15 @@ class PyProxyIterableMethods { * ``__iter__`` method). * * This will be used implicitly by ``for(let x of proxy){}``. - * - * @returns {Iterator} An iterator for the proxied Python object. */ - [Symbol.iterator]() { + [Symbol.iterator](): Iterator { let ptrobj = _getPtr(this); let token = {}; let iterptr; try { iterptr = Module._PyObject_GetIter(ptrobj); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } if (iterptr === 0) { Module._pythonexc2js(); @@ -708,56 +762,56 @@ class PyProxyIterableMethods { } } -/** - * @typedef {PyProxy & PyProxyIteratorMethods} PyProxyIterator - */ +export type PyProxyIterator = PyProxy & PyProxyIteratorMethods; // Controlled by IS_ITERATOR, appears for any object with a __next__ or // tp_iternext method. -class PyProxyIteratorMethods { +export class PyProxyIteratorMethods { + /** @private */ [Symbol.iterator]() { return this; } /** - * This translates to the Python code ``next(obj)``. Returns the next value - * of the generator. See the documentation for `Generator.prototype.next + * This translates to the Python code ``next(obj)``. Returns the next value of + * the generator. See the documentation for `Generator.prototype.next * `_. * The argument will be sent to the Python generator. * * This will be used implicitly by ``for(let x of proxy){}``. * - * Present only if the proxied Python object is a generator or iterator - * (i.e., has a ``send`` or ``__next__`` method). + * Present only if the proxied Python object is a generator or iterator (i.e., + * has a ``send`` or ``__next__`` method). * - * @param {any=} [value] The value to send to the generator. The value will be - * assigned as a result of a yield expression. - * @returns {IteratorResult} An Object with two properties: ``done`` and ``value``. - * When the generator yields ``some_value``, ``next`` returns ``{done : - * false, value : some_value}``. When the generator raises a - * ``StopIteration(result_value)`` exception, ``next`` returns ``{done : - * true, value : result_value}``. + * @param any The value to send to the generator. The value will be assigned + * as a result of a yield expression. + * @returns An Object with two properties: ``done`` and ``value``. When the + * generator yields ``some_value``, ``next`` returns ``{done : false, value : + * some_value}``. When the generator raises a ``StopIteration(result_value)`` + * exception, ``next`` returns ``{done : true, value : result_value}``. */ - next(arg = undefined) { - let idresult; + next(arg: any = undefined): IteratorResult { // Note: arg is optional, if arg is not supplied, it will be undefined // which gets converted to "Py_None". This is as intended. - let idarg = Module.hiwire.new_value(arg); + let idarg = Hiwire.new_value(arg); + let status; let done; + let stackTop = Module.stackSave(); + let res_ptr = Module.stackAlloc(4); try { - idresult = Module.__pyproxyGen_Send(_getPtr(this), idarg); - done = idresult === 0; - if (done) { - idresult = Module.__pyproxyGen_FetchStopIterationValue(); - } + status = Module.__pyproxyGen_Send(_getPtr(this), idarg, res_ptr); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idarg); + Hiwire.decref(idarg); } - if (done && idresult === 0) { + let HEAPU32 = Module.HEAPU32; + let idresult = DEREF_U32(res_ptr, 0); + Module.stackRestore(stackTop); + if (status === PYGEN_ERROR) { Module._pythonexc2js(); } - let value = Module.hiwire.pop_value(idresult); + let value = Hiwire.pop_value(idresult); + done = status === PYGEN_RETURN; return { done, value }; } } @@ -766,16 +820,16 @@ class PyProxyIteratorMethods { // to deal with straining out the spurious "Function" properties "prototype", // "arguments", and "length", to deal with correctly satisfying the Proxy // invariants, and to deal with the mro -function python_hasattr(jsobj, jskey) { +function python_hasattr(jsobj: PyProxyClass, jskey: any) { let ptrobj = _getPtr(jsobj); - let idkey = Module.hiwire.new_value(jskey); + let idkey = Hiwire.new_value(jskey); let result; try { result = Module.__pyproxy_hasattr(ptrobj, idkey); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idkey); + Hiwire.decref(idkey); } if (result === -1) { Module._pythonexc2js(); @@ -786,17 +840,17 @@ function python_hasattr(jsobj, jskey) { // Returns a JsRef in order to allow us to differentiate between "not found" // (in which case we return 0) and "found 'None'" (in which case we return // Js_undefined). -function python_getattr(jsobj, jskey) { +function python_getattr(jsobj: PyProxyClass, jskey: any) { let ptrobj = _getPtr(jsobj); - let idkey = Module.hiwire.new_value(jskey); + let idkey = Hiwire.new_value(jskey); let idresult; let cacheId = jsobj.$$.cache.cacheId; try { idresult = Module.__pyproxy_getattr(ptrobj, idkey, cacheId); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idkey); + Hiwire.decref(idkey); } if (idresult === 0) { if (Module._PyErr_Occurred()) { @@ -806,34 +860,34 @@ function python_getattr(jsobj, jskey) { return idresult; } -function python_setattr(jsobj, jskey, jsval) { +function python_setattr(jsobj: PyProxyClass, jskey: any, jsval: any) { let ptrobj = _getPtr(jsobj); - let idkey = Module.hiwire.new_value(jskey); - let idval = Module.hiwire.new_value(jsval); + let idkey = Hiwire.new_value(jskey); + let idval = Hiwire.new_value(jsval); let errcode; try { errcode = Module.__pyproxy_setattr(ptrobj, idkey, idval); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idkey); - Module.hiwire.decref(idval); + Hiwire.decref(idkey); + Hiwire.decref(idval); } if (errcode === -1) { Module._pythonexc2js(); } } -function python_delattr(jsobj, jskey) { +function python_delattr(jsobj: PyProxyClass, jskey: any) { let ptrobj = _getPtr(jsobj); - let idkey = Module.hiwire.new_value(jskey); + let idkey = Hiwire.new_value(jskey); let errcode; try { errcode = Module.__pyproxy_delattr(ptrobj, idkey); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(idkey); + Hiwire.decref(idkey); } if (errcode === -1) { Module._pythonexc2js(); @@ -847,7 +901,7 @@ let PyProxyHandlers = { isExtensible() { return true; }, - has(jsobj, jskey) { + has(jsobj: PyProxyClass, jskey: any) { // Note: must report "prototype" in proxy when we are callable. // (We can return the wrong value from "get" handler though.) let objHasKey = Reflect.has(jsobj, jskey); @@ -863,7 +917,7 @@ let PyProxyHandlers = { } return python_hasattr(jsobj, jskey); }, - get(jsobj, jskey) { + get(jsobj: PyProxyClass, jskey: any) { // Preference order: // 1. stuff from JavaScript // 2. the result of Python getattr @@ -880,10 +934,10 @@ let PyProxyHandlers = { // 2. The result of getattr let idresult = python_getattr(jsobj, jskey); if (idresult !== 0) { - return Module.hiwire.pop_value(idresult); + return Hiwire.pop_value(idresult); } }, - set(jsobj, jskey, jsval) { + set(jsobj: PyProxyClass, jskey: any, jsval: any) { let descr = Object.getOwnPropertyDescriptor(jsobj, jskey); if (descr && !descr.writable) { throw new TypeError(`Cannot set read only field '${jskey}'`); @@ -898,7 +952,7 @@ let PyProxyHandlers = { python_setattr(jsobj, jskey, jsval); return true; }, - deleteProperty(jsobj, jskey) { + deleteProperty(jsobj: PyProxyClass, jskey: any): boolean { let descr = Object.getOwnPropertyDescriptor(jsobj, jskey); if (descr && !descr.writable) { throw new TypeError(`Cannot delete read only field '${jskey}'`); @@ -912,44 +966,43 @@ let PyProxyHandlers = { python_delattr(jsobj, jskey); // Must return "false" if "jskey" is a nonconfigurable own property. // Otherwise JavaScript will throw a TypeError. - return !descr || descr.configurable; + return !descr || !!descr.configurable; }, - ownKeys(jsobj) { + ownKeys(jsobj: PyProxyClass) { let ptrobj = _getPtr(jsobj); let idresult; try { idresult = Module.__pyproxy_ownKeys(ptrobj); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } if (idresult === 0) { Module._pythonexc2js(); } - let result = Module.hiwire.pop_value(idresult); + let result = Hiwire.pop_value(idresult); result.push(...Reflect.ownKeys(jsobj)); return result; }, - apply(jsobj, jsthis, jsargs) { + apply(jsobj: PyProxyClass & Function, jsthis: any, jsargs: any) { return jsobj.apply(jsthis, jsargs); }, }; -/** - * @typedef {PyProxy & Promise} PyProxyAwaitable - */ +export type PyProxyAwaitable = PyProxy & Promise; /** * The Promise / JavaScript awaitable API. * @private */ -class PyProxyAwaitableMethods { +export class PyProxyAwaitableMethods { + $$: any; /** * This wraps __pyproxy_ensure_future and makes a function that converts a * Python awaitable to a promise, scheduling the awaitable on the Python * event loop if necessary. * @private */ - _ensure_future() { + _ensure_future(): Promise { if (this.$$.promise) { return this.$$.promise; } @@ -960,8 +1013,8 @@ class PyProxyAwaitableMethods { resolveHandle = resolve; rejectHandle = reject; }); - let resolve_handle_id = Module.hiwire.new_value(resolveHandle); - let reject_handle_id = Module.hiwire.new_value(rejectHandle); + let resolve_handle_id = Hiwire.new_value(resolveHandle); + let reject_handle_id = Hiwire.new_value(rejectHandle); let errcode; try { errcode = Module.__pyproxy_ensure_future( @@ -970,15 +1023,16 @@ class PyProxyAwaitableMethods { reject_handle_id ); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } finally { - Module.hiwire.decref(reject_handle_id); - Module.hiwire.decref(resolve_handle_id); + Hiwire.decref(reject_handle_id); + Hiwire.decref(resolve_handle_id); } if (errcode === -1) { Module._pythonexc2js(); } this.$$.promise = promise; + // @ts-ignore this.destroy(); return promise; } @@ -986,7 +1040,7 @@ class PyProxyAwaitableMethods { * Runs ``asyncio.ensure_future(awaitable)``, executes * ``onFulfilled(result)`` when the ``Future`` resolves successfully, * executes ``onRejected(error)`` when the ``Future`` fails. Will be used - * implictly by ``await obj``. + * implicitly by ``await obj``. * * See the documentation for * `Promise.then @@ -995,13 +1049,16 @@ class PyProxyAwaitableMethods { * Present only if the proxied Python object is `awaitable * `_. * - * @param {Function} onFulfilled A handler called with the result as an + * @param onFulfilled A handler called with the result as an * argument if the awaitable succeeds. - * @param {Function} onRejected A handler called with the error as an + * @param onRejected A handler called with the error as an * argument if the awaitable fails. - * @returns {Promise} The resulting Promise. + * @returns The resulting Promise. */ - then(onFulfilled, onRejected) { + then( + onFulfilled: (value: any) => any, + onRejected: (reason: any) => any + ): Promise { let promise = this._ensure_future(); return promise.then(onFulfilled, onRejected); } @@ -1016,11 +1073,11 @@ class PyProxyAwaitableMethods { * Present only if the proxied Python object is `awaitable * `_. * - * @param {Function} onRejected A handler called with the error as an + * @param onRejected A handler called with the error as an * argument if the awaitable fails. - * @returns {Promise} The resulting Promise. + * @returns The resulting Promise. */ - catch(onRejected) { + catch(onRejected: (reason: any) => any) { let promise = this._ensure_future(); return promise.catch(onRejected); } @@ -1036,33 +1093,34 @@ class PyProxyAwaitableMethods { * `_. * * - * @param {Function} onFinally A handler that is called with zero arguments + * @param onFinally A handler that is called with zero arguments * when the awaitable resolves. - * @returns {Promise} A Promise that resolves or rejects with the same + * @returns A Promise that resolves or rejects with the same * result as the original Promise, but only after executing the * ``onFinally`` handler. */ - finally(onFinally) { + finally(onFinally: () => void) { let promise = this._ensure_future(); return promise.finally(onFinally); } } -/** - * @typedef { PyProxy & PyProxyCallableMethods & ((...args : any[]) => Py2JsResult) } PyProxyCallable - */ -class PyProxyCallableMethods { - apply(jsthis, jsargs) { +export type PyProxyCallable = PyProxy & + PyProxyCallableMethods & + ((...args: any[]) => any); + +export class PyProxyCallableMethods { + apply(jsthis: PyProxyClass, jsargs: any) { return Module.callPyObject(_getPtr(this), ...jsargs); } - call(jsthis, ...jsargs) { + call(jsthis: PyProxyClass, ...jsargs: any) { return Module.callPyObject(_getPtr(this), ...jsargs); } /** * Call the function with key word arguments. * The last argument must be an object with the keyword arguments. */ - callKwargs(...jsargs) { + callKwargs(...jsargs: any) { if (jsargs.length === 0) { throw new TypeError( "callKwargs requires at least one argument (the key word argument object)" @@ -1078,9 +1136,11 @@ class PyProxyCallableMethods { return Module.callPyObjectKwargs(_getPtr(this), ...jsargs); } } +// @ts-ignore PyProxyCallableMethods.prototype.prototype = Function.prototype; -let type_to_array_map = new Map([ +// @ts-ignore +let type_to_array_map: Map = new Map([ ["i8", Int8Array], ["u8", Uint8Array], ["u8clamped", Uint8ClampedArray], @@ -1099,10 +1159,9 @@ let type_to_array_map = new Map([ ["dataview", DataView], ]); -/** - * @typedef {PyProxy & PyProxyBufferMethods} PyProxyBuffer - */ -class PyProxyBufferMethods { +export type PyProxyBuffer = PyProxy & PyProxyBufferMethods; + +export class PyProxyBufferMethods { /** * Get a view of the buffer data which is usable from JavaScript. No copy is * ever performed. @@ -1124,16 +1183,16 @@ class PyProxyBufferMethods { * have support for big endian data, so you might want to pass * ``'dataview'`` as the type argument in that case. * - * @param {string=} [type] The type of the :any:`PyBuffer.data ` field in the + * @param type The type of the :any:`PyBuffer.data ` field in the * output. Should be one of: ``"i8"``, ``"u8"``, ``"u8clamped"``, ``"i16"``, * ``"u16"``, ``"i32"``, ``"u32"``, ``"i32"``, ``"u32"``, ``"i64"``, * ``"u64"``, ``"f32"``, ``"f64``, or ``"dataview"``. This argument is * optional, if absent ``getBuffer`` will try to determine the appropriate * output type based on the buffer `format string * `_. - * @returns {PyBuffer} :any:`PyBuffer ` + * @returns :any:`PyBuffer ` */ - getBuffer(type) { + getBuffer(type?: string): PyBuffer { let ArrayType = undefined; if (type) { ArrayType = type_to_array_map.get(type); @@ -1151,7 +1210,7 @@ class PyProxyBufferMethods { try { errcode = Module.__pyproxy_get_buffer(buffer_struct_ptr, this_ptr); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } if (errcode === -1) { Module._pythonexc2js(); @@ -1165,8 +1224,8 @@ class PyProxyBufferMethods { let readonly = !!DEREF_U32(buffer_struct_ptr, 3); let format_ptr = DEREF_U32(buffer_struct_ptr, 4); let itemsize = DEREF_U32(buffer_struct_ptr, 5); - let shape = Module.hiwire.pop_value(DEREF_U32(buffer_struct_ptr, 6)); - let strides = Module.hiwire.pop_value(DEREF_U32(buffer_struct_ptr, 7)); + let shape = Hiwire.pop_value(DEREF_U32(buffer_struct_ptr, 6)); + let strides = Hiwire.pop_value(DEREF_U32(buffer_struct_ptr, 7)); let view_ptr = DEREF_U32(buffer_struct_ptr, 8); let c_contiguous = !!DEREF_U32(buffer_struct_ptr, 9); @@ -1245,16 +1304,24 @@ class PyProxyBufferMethods { Module._PyBuffer_Release(view_ptr); Module._PyMem_Free(view_ptr); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } } } } } -/** - * @typedef {Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array} TypedArray; - */ +export type TypedArray = + | Int8Array + | Uint8Array + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array; +export type PyProxyDict = PyProxyWithGet & PyProxyWithSet & PyProxyWithHas; /** * A class to allow access to a Python data buffers from JavaScript. These are @@ -1323,90 +1390,93 @@ class PyProxyBufferMethods { * ); */ export class PyBuffer { - constructor() { - /** - * The offset of the first entry of the array. For instance if our array - * is 3d, then you will find ``array[0,0,0]`` at - * ``pybuf.data[pybuf.offset]`` - * @type {number} - */ - this.offset; + /** + * The offset of the first entry of the array. For instance if our array + * is 3d, then you will find ``array[0,0,0]`` at + * ``pybuf.data[pybuf.offset]`` + */ + offset: number; - /** - * If the data is readonly, you should not modify it. There is no way - * for us to enforce this, but it may cause very weird behavior. - * @type {boolean} - */ - this.readonly; + /** + * If the data is readonly, you should not modify it. There is no way + * for us to enforce this, but it may cause very weird behavior. + */ + readonly: boolean; - /** - * The format string for the buffer. See `the Python documentation on - * format strings - * `_. - * @type {string} - */ - this.format; + /** + * The format string for the buffer. See `the Python documentation on + * format strings + * `_. + */ + format: string; - /** - * How large is each entry (in bytes)? - * @type {number} - */ - this.itemsize; + /** + * How large is each entry (in bytes)? + */ + itemsize: number; - /** - * The number of dimensions of the buffer. If ``ndim`` is 0, the buffer - * represents a single scalar or struct. Otherwise, it represents an - * array. - * @type {number} - */ - this.ndim; + /** + * The number of dimensions of the buffer. If ``ndim`` is 0, the buffer + * represents a single scalar or struct. Otherwise, it represents an + * array. + */ + ndim: number; - /** - * The total number of bytes the buffer takes up. This is equal to - * ``buff.data.byteLength``. - * @type {number} - */ - this.nbytes; + /** + * The total number of bytes the buffer takes up. This is equal to + * ``buff.data.byteLength``. + */ + nbytes: number; - /** - * The shape of the buffer, that is how long it is in each dimension. - * The length will be equal to ``ndim``. For instance, a 2x3x4 array - * would have shape ``[2, 3, 4]``. - * @type {number[]} - */ - this.shape; + /** + * The shape of the buffer, that is how long it is in each dimension. + * The length will be equal to ``ndim``. For instance, a 2x3x4 array + * would have shape ``[2, 3, 4]``. + */ + shape: number[]; - /** - * An array of of length ``ndim`` giving the number of elements to skip - * to get to a new element in each dimension. See the example definition - * of a ``multiIndexToIndex`` function above. - * @type {number[]} - */ - this.strides; + /** + * An array of of length ``ndim`` giving the number of elements to skip + * to get to a new element in each dimension. See the example definition + * of a ``multiIndexToIndex`` function above. + */ + strides: number[]; - /** - * The actual data. A typed array of an appropriate size backed by a - * segment of the WASM memory. - * - * The ``type`` argument of :any:`PyProxy.getBuffer` - * determines which sort of ``TypedArray`` this is. By default - * :any:`PyProxy.getBuffer` will look at the format string to determine the most - * appropriate option. - * @type {TypedArray} - */ - this.data; + /** + * The actual data. A typed array of an appropriate size backed by a + * segment of the WASM memory. + * + * The ``type`` argument of :any:`PyProxy.getBuffer` + * determines which sort of ``TypedArray`` this is. By default + * :any:`PyProxy.getBuffer` will look at the format string to determine the most + * appropriate option. + */ + data: TypedArray; - /** - * Is it C contiguous? - * @type {boolean} - */ - this.c_contiguous; + /** + * Is it C contiguous? + */ + c_contiguous: boolean; - /** - * Is it Fortran contiguous? - * @type {boolean} - */ - this.f_contiguous; + /** + * Is it Fortran contiguous? + */ + f_contiguous: boolean; + + /** + * @private + */ + _released: boolean; + + /** + * @private + */ + _view_ptr: number; + + /** + * @private + */ + constructor() { throw new TypeError("PyBuffer is not a constructor"); } @@ -1422,9 +1492,10 @@ export class PyBuffer { Module._PyBuffer_Release(this._view_ptr); Module._PyMem_Free(this._view_ptr); } catch (e) { - Module.fatal_error(e); + API.fatal_error(e); } this._released = true; + // @ts-ignore this.data = null; } } diff --git a/src/core/python2js.c b/src/core/python2js.c index 30a46d0d098..a35c2064433 100644 --- a/src/core/python2js.c +++ b/src/core/python2js.c @@ -34,6 +34,7 @@ typedef struct ConversionContext_s JsRef key, JsRef value); JsRef (*dict_postprocess)(struct ConversionContext_s context, JsRef dict); + bool default_converter; } ConversionContext; JsRef @@ -305,6 +306,9 @@ _python2js_proxy(PyObject* x) return Js_novalue; } +JsRef +python2js__default_converter(JsRef jscontext, PyObject* object); + /** * This function is a helper function for _python2js which handles the case when * we want to convert at least the outermost layer. @@ -326,6 +330,9 @@ _python2js_deep(ConversionContext context, PyObject* x) if (PyObject_CheckBuffer(x)) { return _python2js_buffer(x); } + if (context.default_converter) { + return python2js__default_converter(context.jscontext, x); + } if (context.proxies) { JsRef proxy = pyproxy_new(x); JsArray_Push_unchecked(context.proxies, proxy); @@ -396,6 +403,11 @@ _python2js(ConversionContext context, PyObject* x) } FAIL_IF_ERR_OCCURRED(); if (context.depth == 0) { + RETURN_IF_HAS_VALUE(_python2js_immutable(x)); + RETURN_IF_HAS_VALUE(_python2js_proxy(x)); + if (context.default_converter) { + return python2js__default_converter(context.jscontext, x); + } return python2js_track_proxies(x, context.proxies); } else { context.depth--; @@ -475,44 +487,6 @@ _JsMap_Set(ConversionContext context, JsRef map, JsRef key, JsRef value) return JsMap_Set(map, key, value); } -/** - * Do a conversion from Python to JavaScript using settings from - * ConversionContext - */ -JsRef -python2js_with_context(ConversionContext context, PyObject* x) -{ - PyObject* cache = PyDict_New(); - if (cache == NULL) { - return NULL; - } - context.cache = cache; - JsRef result = _python2js(context, x); - // Destroy the cache. Because the cache has raw JsRefs inside, we need to - // manually dealloc them. - PyObject *pykey, *pyval; - Py_ssize_t pos = 0; - while (PyDict_Next(cache, &pos, &pykey, &pyval)) { - JsRef obj = (JsRef)PyLong_AsSize_t(pyval); - hiwire_decref(obj); - } - Py_DECREF(cache); - if (result == NULL || result == Js_novalue) { - result = NULL; - if (PyErr_Occurred()) { - if (!PyErr_ExceptionMatches(conversion_error)) { - _PyErr_FormatFromCause(conversion_error, - "Conversion from python to javascript failed"); - } - } else { - fail_test(); - PyErr_SetString(internal_error, - "Internal error occurred in python2js_with_depth"); - } - } - return result; -} - /** * Do a conversion from Python to JavaScript, converting lists, dicts, and sets * down to depth "depth". @@ -520,13 +494,7 @@ python2js_with_context(ConversionContext context, PyObject* x) JsRef python2js_with_depth(PyObject* x, int depth, JsRef proxies) { - ConversionContext context = { - .depth = depth, - .proxies = proxies, - .dict_new = _JsMap_New, - .dict_add_keyvalue = _JsMap_Set, - }; - return python2js_with_context(context, x); + return python2js_custom(x, depth, proxies, NULL, NULL); } static JsRef @@ -539,8 +507,8 @@ EM_JS_NUM(int, _JsArray_PushEntry_helper, (JsRef array, JsRef key, JsRef value), { - Module.hiwire.get_value(array).push( - [ Module.hiwire.get_value(key), Module.hiwire.get_value(value) ]); + Hiwire.get_value(array).push( + [ Hiwire.get_value(key), Hiwire.get_value(value) ]); }) static int @@ -553,10 +521,27 @@ _JsArray_PushEntry(ConversionContext context, } EM_JS_REF(JsRef, _JsArray_PostProcess_helper, (JsRef jscontext, JsRef array), { - return Module.hiwire.new_value( - Module.hiwire.get_value(jscontext).dict_converter( - Module.hiwire.get_value(array))); + return Hiwire.new_value( + Hiwire.get_value(jscontext).dict_converter(Hiwire.get_value(array))); +}) + +// clang-format off +EM_JS_REF( +JsRef, +python2js__default_converter, +(JsRef jscontext, PyObject* object), +{ + let context = Hiwire.get_value(jscontext); + let proxy = Module.pyproxy_new(object); + let result = context.default_converter( + proxy, + context.converter, + context.cacheConversion + ); + proxy.destroy(); + return Hiwire.new_value(result); }) +// clang-format on static JsRef _JsArray_PostProcess(ConversionContext context, JsRef array) @@ -564,37 +549,111 @@ _JsArray_PostProcess(ConversionContext context, JsRef array) return _JsArray_PostProcess_helper(context.jscontext, array); } +// clang-format off +EM_JS_REF( +JsRef, +python2js_custom__create_jscontext, +(ConversionContext context, + PyObject* cache, + JsRef dict_converter, + JsRef default_converter), +{ + let jscontext = {}; + if (dict_converter !== 0) { + jscontext.dict_converter = Hiwire.get_value(dict_converter); + } + if (default_converter !== 0) { + jscontext.default_converter = Hiwire.get_value(default_converter); + jscontext.cacheConversion = function (input, output) { + // input should be a PyProxy, output should be a Javascript + // object + if (!API.isPyProxy(input)) { + throw new TypeError("The first argument to cacheConversion must be a PyProxy."); + } + let input_ptr = Module.PyProxy_getPtr(input); + let output_key = Hiwire.new_value(output); + __python2js_add_to_cache(cache, input_ptr, output_key); + Hiwire.decref(output_key); + }; + jscontext.converter = function (x) { + if (!API.isPyProxy(x)) { + return x; + } + let ptr = Module.PyProxy_getPtr(x); + let res = __python2js(context, ptr); + return Hiwire.pop_value(res); + }; + } + return Hiwire.new_value(jscontext); +}) +// clang-format on + /** * dict_converter should be a JavaScript function that converts an Iterable of * pairs into the desired JavaScript object. If dict_converter is NULL, we use * python2js_with_depth which converts dicts to Map (the default) */ JsRef -python2js_custom_dict_converter(PyObject* x, - int depth, - JsRef proxies, - JsRef dict_converter) +python2js_custom(PyObject* x, + int depth, + JsRef proxies, + JsRef dict_converter, + JsRef default_converter) { - if (dict_converter == NULL) { - // No custom converter provided, go back to default convertion to Map. - return python2js_with_depth(x, depth, proxies); - } - JsRef jscontext = (JsRef)EM_ASM_INT( - { - return Module.hiwire.new_value( - { dict_converter : Module.hiwire.get_value($0) }); - }, - dict_converter); + PyObject* cache = PyDict_New(); + if (cache == NULL) { + return NULL; + } ConversionContext context = { + .cache = cache, .depth = depth, .proxies = proxies, - .dict_new = _JsArray_New, - .dict_add_keyvalue = _JsArray_PushEntry, - .dict_postprocess = _JsArray_PostProcess, - .jscontext = jscontext, + .jscontext = NULL, + .default_converter = false, }; - JsRef result = python2js_with_context(context, x); - hiwire_CLEAR(jscontext); + if (dict_converter == NULL) { + // No custom converter provided, go back to default conversion to Map. + context.dict_new = _JsMap_New; + context.dict_add_keyvalue = _JsMap_Set; + context.dict_postprocess = NULL; + } else { + context.dict_new = _JsArray_New; + context.dict_add_keyvalue = _JsArray_PushEntry; + context.dict_postprocess = _JsArray_PostProcess; + } + if (default_converter) { + context.default_converter = true; + } + if (dict_converter || default_converter) { + context.jscontext = python2js_custom__create_jscontext( + context, cache, dict_converter, default_converter); + } + JsRef result = _python2js(context, x); + if (context.jscontext) { + hiwire_CLEAR(context.jscontext); + } + // Destroy the cache. Because the cache has raw JsRefs inside, we need to + // manually dealloc them. + PyObject *pykey, *pyval; + Py_ssize_t pos = 0; + while (PyDict_Next(cache, &pos, &pykey, &pyval)) { + JsRef obj = (JsRef)PyLong_AsSize_t(pyval); + hiwire_decref(obj); + } + Py_DECREF(cache); + if (result == NULL || result == Js_novalue) { + result = NULL; + if (PyErr_Occurred()) { + if (!PyErr_ExceptionMatches(conversion_error)) { + _PyErr_FormatFromCause(conversion_error, + "Conversion from python to javascript failed"); + } + } else { + fail_test(); + PyErr_SetString(internal_error, + "Internal error occurred in python2js_with_depth"); + } + } return result; } @@ -609,30 +668,36 @@ to_js(PyObject* self, PyObject* pyproxies = NULL; bool create_proxies = true; PyObject* py_dict_converter = NULL; - static const char* const _keywords[] = { - "", "depth", "pyproxies", "create_pyproxies", "dict_converter", 0 - }; + PyObject* py_default_converter = NULL; + static const char* const _keywords[] = { "", + "depth", + "create_pyproxies", + "pyproxies", + "dict_converter", + "default_converter", + 0 }; // See argparse docs on format strings: // https://docs.python.org/3/c-api/arg.html?highlight=pyarg_parse#parsing-arguments - // O|$iOpO:to_js - // O - Object - // | - start of optional args - // $ - start of kwonly args - // i - signed integer - // O - Object - // p - predicate (ie bool) - // O - Object - // :to_js - name of this function for error messages - static struct _PyArg_Parser _parser = { "O|$iOpO:to_js", _keywords, 0 }; - if (kwnames != NULL && !_PyArg_ParseStackAndKeywords(args, - nargs, - kwnames, - &_parser, - &obj, - &depth, - &pyproxies, - &create_proxies, - &py_dict_converter)) { + // O|$iOpOO:to_js + // O - self -- Object + // | - start of optional args + // $ - start of kwonly args + // i - depth -- signed integer + // p - create_pyproxies -- predicate (ie bool) + // OOO - PyObject* arguments for pyproxies, dict_converter, and + // default_converter. + // :to_js - name of this function for error messages + static struct _PyArg_Parser _parser = { "O|$ipOOO:to_js", _keywords, 0 }; + if (!_PyArg_ParseStackAndKeywords(args, + nargs, + kwnames, + &_parser, + &obj, + &depth, + &create_proxies, + &pyproxies, + &py_dict_converter, + &py_default_converter)) { return NULL; } @@ -646,6 +711,7 @@ to_js(PyObject* self, } JsRef proxies = NULL; JsRef js_dict_converter = NULL; + JsRef js_default_converter = NULL; JsRef js_result = NULL; PyObject* py_result = NULL; @@ -669,8 +735,11 @@ to_js(PyObject* self, if (py_dict_converter) { js_dict_converter = python2js(py_dict_converter); } - js_result = - python2js_custom_dict_converter(obj, depth, proxies, js_dict_converter); + if (py_default_converter) { + js_default_converter = python2js(py_default_converter); + } + js_result = python2js_custom( + obj, depth, proxies, js_dict_converter, js_default_converter); FAIL_IF_NULL(js_result); if (hiwire_is_pyproxy(js_result)) { // Oops, just created a PyProxy. Wrap it I guess? @@ -679,14 +748,21 @@ to_js(PyObject* self, py_result = js2python(js_result); } finally: + if (pyproxy_Check(js_dict_converter)) { + destroy_proxy(js_dict_converter, NULL); + } + if (pyproxy_Check(js_default_converter)) { + destroy_proxy(js_default_converter, NULL); + } hiwire_CLEAR(proxies); hiwire_CLEAR(js_dict_converter); + hiwire_CLEAR(js_default_converter); hiwire_CLEAR(js_result); return py_result; } EM_JS_NUM(errcode, destroy_proxies_js, (JsRef proxies_id), { - for (let proxy of Module.hiwire.get_value(proxies_id)) { + for (let proxy of Hiwire.get_value(proxies_id)) { proxy.destroy(); } }) diff --git a/src/core/python2js.h b/src/core/python2js.h index 0c7357407c1..d98092216d6 100644 --- a/src/core/python2js.h +++ b/src/core/python2js.h @@ -40,6 +40,18 @@ python2js_track_proxies(PyObject* x, JsRef proxies); JsRef python2js_with_depth(PyObject* x, int depth, JsRef proxies); +/** + * dict_converter should be a JavaScript function that converts an Iterable of + * pairs into the desired JavaScript object. If dict_converter is NULL, we use + * python2js_with_depth which converts dicts to Map (the default) + */ +JsRef +python2js_custom(PyObject* x, + int depth, + JsRef proxies, + JsRef dict_converter, + JsRef default_converter); + int python2js_init(PyObject* core); diff --git a/src/core/python2js_buffer.c b/src/core/python2js_buffer.c index 0c39b7c74dd..a542d79b201 100644 --- a/src/core/python2js_buffer.c +++ b/src/core/python2js_buffer.c @@ -42,7 +42,7 @@ EM_JS_REF(JsRef, _python2js_buffer_inner, ( suboffsets, converter, }); - return Module.hiwire.new_value(result); + return Hiwire.new_value(result); }); // clang-format on diff --git a/src/js/api.js b/src/js/api.js deleted file mode 100644 index ba785830145..00000000000 --- a/src/js/api.js +++ /dev/null @@ -1,421 +0,0 @@ -import { Module } from "./module.js"; -import { loadPackage, loadedPackages } from "./load-pyodide.js"; -import { isPyProxy, PyBuffer } from "./pyproxy.gen.js"; -export { loadPackage, loadedPackages, isPyProxy }; - -/** - * @typedef {import('./pyproxy.gen').Py2JsResult} Py2JsResult - * @typedef {import('./pyproxy.gen').PyProxy} PyProxy - * @typedef {import('./pyproxy.gen').TypedArray} TypedArray - * @typedef {import('emscripten')} Emscripten - * @typedef {import('emscripten').Module.FS} FS - */ - -/** - * An alias to the Python :py:mod:`pyodide` package. - * - * You can use this to call functions defined in the Pyodide Python package - * from JavaScript. - * - * @type {PyProxy} - */ -let pyodide_py = {}; // actually defined in loadPyodide (see pyodide.js) - -/** - * - * An alias to the global Python namespace. - * - * For example, to access a variable called ``foo`` in the Python global - * scope, use ``pyodide.globals.get("foo")`` - * - * @type {PyProxy} - */ -let globals = {}; // actually defined in loadPyodide (see pyodide.js) - -/** - * A JavaScript error caused by a Python exception. - * - * In order to reduce the risk of large memory leaks, the ``PythonError`` - * contains no reference to the Python exception that caused it. You can find - * the actual Python exception that caused this error as `sys.last_value - * `_. - * - * See :ref:`type-translations-errors` for more information. - * - * .. admonition:: Avoid Stack Frames - * :class: warning - * - * If you make a :any:`PyProxy` of ``sys.last_value``, you should be - * especially careful to :any:`destroy() ` it when you are - * done. You may leak a large amount of memory including the local - * variables of all the stack frames in the traceback if you don't. The - * easiest way is to only handle the exception in Python. - * - * @class - */ -export class PythonError { - // actually defined in error_handling.c. TODO: would be good to move this - // documentation and the definition of PythonError to error_handling.js - constructor() { - /** - * The Python traceback. - * @type {string} - */ - this.message; - } -} - -/** - * - * The Pyodide version. - * - * It can be either the exact release version (e.g. ``0.1.0``), or - * the latest release version followed by the number of commits since, and - * the git hash of the current commit (e.g. ``0.1.0-1-bd84646``). - * - * @type {string} - */ -export let version = ""; // actually defined in loadPyodide (see pyodide.js) - -/** - * Runs a string of Python code from JavaScript. - * - * The last part of the string may be an expression, in which case, its value - * is returned. - * - * @param {string} code Python code to evaluate - * @param {PyProxy=} globals An optional Python dictionary to use as the globals. - * Defaults to :any:`pyodide.globals`. Uses the Python API - * :any:`pyodide.eval_code` to evaluate the code. - * @returns {Py2JsResult} The result of the Python code translated to JavaScript. See the - * documentation for :any:`pyodide.eval_code` for more info. - */ -export function runPython(code, globals = Module.globals) { - return Module.pyodide_py.eval_code(code, globals); -} -Module.runPython = runPython; - -/** - * @callback LogFn - * @param {string} msg - * @returns {void} - * @private - */ - -/** - * Inspect a Python code chunk and use :js:func:`pyodide.loadPackage` to install - * any known packages that the code chunk imports. Uses the Python API - * :func:`pyodide.find\_imports` to inspect the code. - * - * For example, given the following code as input - * - * .. code-block:: python - * - * import numpy as np x = np.array([1, 2, 3]) - * - * :js:func:`loadPackagesFromImports` will call - * ``pyodide.loadPackage(['numpy'])``. - * - * @param {string} code The code to inspect. - * @param {LogFn=} messageCallback The ``messageCallback`` argument of - * :any:`pyodide.loadPackage` (optional). - * @param {LogFn=} errorCallback The ``errorCallback`` argument of - * :any:`pyodide.loadPackage` (optional). - * @async - */ -export async function loadPackagesFromImports( - code, - messageCallback, - errorCallback -) { - let pyimports = Module.pyodide_py.find_imports(code); - let imports; - try { - imports = pyimports.toJs(); - } finally { - pyimports.destroy(); - } - if (imports.length === 0) { - return; - } - - let packageNames = Module._import_name_to_package_name; - let packages = new Set(); - for (let name of imports) { - if (packageNames.has(name)) { - packages.add(packageNames.get(name)); - } - } - if (packages.size) { - await loadPackage(Array.from(packages), messageCallback, errorCallback); - } -} - -/** - * Runs Python code using `PyCF_ALLOW_TOP_LEVEL_AWAIT - * `_. - * - * .. admonition:: Python imports - * :class: warning - * - * Since pyodide 0.18.0, you must call :js:func:`loadPackagesFromImports` to - * import any python packages referenced via `import` statements in your code. - * This function will no longer do it for you. - * - * For example: - * - * .. code-block:: pyodide - * - * let result = await pyodide.runPythonAsync(` - * from js import fetch - * response = await fetch("./packages.json") - * packages = await response.json() - * # If final statement is an expression, its value is returned to JavaScript - * len(packages.packages.object_keys()) - * `); - * console.log(result); // 79 - * - * @param {string} code Python code to evaluate - * @param {PyProxy=} globals An optional Python dictionary to use as the globals. - * Defaults to :any:`pyodide.globals`. Uses the Python API - * :any:`pyodide.eval_code_async` to evaluate the code. - * @returns {Py2JsResult} The result of the Python code translated to JavaScript. - * @async - */ -export async function runPythonAsync(code, globals = Module.globals) { - return await Module.pyodide_py.eval_code_async(code, globals); -} -Module.runPythonAsync = runPythonAsync; - -/** - * Registers the JavaScript object ``module`` as a JavaScript module named - * ``name``. This module can then be imported from Python using the standard - * Python import system. If another module by the same name has already been - * imported, this won't have much effect unless you also delete the imported - * module from ``sys.modules``. This calls the ``pyodide_py`` API - * :func:`pyodide.register_js_module`. - * - * @param {string} name Name of the JavaScript module to add - * @param {object} module JavaScript object backing the module - */ -export function registerJsModule(name, module) { - Module.pyodide_py.register_js_module(name, module); -} - -/** - * Tell Pyodide about Comlink. - * Necessary to enable importing Comlink proxies into Python. - */ -export function registerComlink(Comlink) { - Module._Comlink = Comlink; -} - -/** - * Unregisters a JavaScript module with given name that has been previously - * registered with :js:func:`pyodide.registerJsModule` or - * :func:`pyodide.register_js_module`. If a JavaScript module with that name - * does not already exist, will throw an error. Note that if the module has - * already been imported, this won't have much effect unless you also delete - * the imported module from ``sys.modules``. This calls the ``pyodide_py`` API - * :func:`pyodide.unregister_js_module`. - * - * @param {string} name Name of the JavaScript module to remove - */ -export function unregisterJsModule(name) { - Module.pyodide_py.unregister_js_module(name); -} - -/** - * Convert the JavaScript object to a Python object as best as possible. - * - * This is similar to :any:`JsProxy.to_py` but for use from JavaScript. If the - * object is immutable or a :any:`PyProxy`, it will be returned unchanged. If - * the object cannot be converted into Python, it will be returned unchanged. - * - * See :ref:`type-translations-jsproxy-to-py` for more information. - * - * @param {*} obj - * @param {object} options - * @param {number=} options.depth Optional argument to limit the depth of the - * conversion. - * @returns {PyProxy} The object converted to Python. - */ -export function toPy(obj, { depth = -1 } = {}) { - // No point in converting these, it'd be dumb to proxy them so they'd just - // get converted back by `js2python` at the end - switch (typeof obj) { - case "string": - case "number": - case "boolean": - case "bigint": - case "undefined": - return obj; - } - if (!obj || Module.isPyProxy(obj)) { - return obj; - } - let obj_id = 0; - let py_result = 0; - let result = 0; - try { - obj_id = Module.hiwire.new_value(obj); - try { - py_result = Module.js2python_convert(obj_id, new Map(), depth); - } catch (e) { - if (e instanceof Module._PropagatePythonError) { - Module._pythonexc2js(); - } - throw e; - } - if (Module._JsProxy_Check(py_result)) { - // Oops, just created a JsProxy. Return the original object. - return obj; - // return Module.pyproxy_new(py_result); - } - result = Module._python2js(py_result); - if (result === 0) { - Module._pythonexc2js(); - } - } finally { - Module.hiwire.decref(obj_id); - Module._Py_DecRef(py_result); - } - return Module.hiwire.pop_value(result); -} - -/** - * Imports a module and returns it. - * - * .. admonition:: Warning - * :class: warning - * - * This function has a completely different behavior than the old removed pyimport function! - * - * ``pyimport`` is roughly equivalent to: - * - * .. code-block:: js - * - * pyodide.runPython(`import ${pkgname}; ${pkgname}`); - * - * except that the global namespace will not change. - * - * Example: - * - * .. code-block:: js - * - * let sysmodule = pyodide.pyimport("sys"); - * let recursionLimit = sys.getrecursionlimit(); - * - * @param {string} mod_name The name of the module to import - * @returns A PyProxy for the imported module - */ -export function pyimport(mod_name) { - return Module.importlib.import_module(mod_name); -} - -/** - * Unpack an archive into a target directory. - * - * @param {ArrayBuffer} buffer The archive as an ArrayBuffer (it's also fine to pass a TypedArray). - * @param {string} format The format of the archive. Should be one of the formats recognized by `shutil.unpack_archive`. - * By default the options are 'bztar', 'gztar', 'tar', 'zip', and 'wheel'. Several synonyms are accepted for each format, e.g., - * for 'gztar' any of '.gztar', '.tar.gz', '.tgz', 'tar.gz' or 'tgz' are considered to be synonyms. - * - * @param {string=} extract_dir The directory to unpack the archive into. Defaults to the working directory. - */ -export function unpackArchive(buffer, format, extract_dir) { - if (!Module._util_module) { - Module._util_module = pyimport("pyodide._util"); - } - Module._util_module.unpack_buffer_archive.callKwargs(buffer, { - format, - extract_dir, - }); -} - -/** - * @private - */ -Module.saveState = () => Module.pyodide_py._state.save_state(); - -/** - * @private - */ -Module.restoreState = (state) => Module.pyodide_py._state.restore_state(state); - -/** - * Sets the interrupt buffer to be `interrupt_buffer`. This is only useful when - * Pyodide is used in a webworker. The buffer should be a `SharedArrayBuffer` - * shared with the main browser thread (or another worker). To request an - * interrupt, a `2` should be written into `interrupt_buffer` (2 is the posix - * constant for SIGINT). - * - * @param {TypedArray} interrupt_buffer - */ -export function setInterruptBuffer(interrupt_buffer) { - Module.interrupt_buffer = interrupt_buffer; - Module._set_pyodide_callback(!!interrupt_buffer); -} - -/** - * Throws a KeyboardInterrupt error if a KeyboardInterrupt has been requested - * via the interrupt buffer. - * - * This can be used to enable keyboard interrupts during execution of JavaScript - * code, just as `PyErr_CheckSignals` is used to enable keyboard interrupts - * during execution of C code. - */ -export function checkInterrupt() { - if (Module.interrupt_buffer[0] === 2) { - Module.interrupt_buffer[0] = 0; - Module._PyErr_SetInterrupt(); - Module.runPython(""); - } -} - -export function makePublicAPI() { - /** - * An alias to the `Emscripten File System API - * `_. - * - * This provides a wide range of POSIX-`like` file/device operations, including - * `mount - * `_ - * which can be used to extend the in-memory filesystem with features like `persistence - * `_. - * - * While all the file systems implementations are enabled, only the default - * ``MEMFS`` is guaranteed to work in all runtime settings. The implementations - * are available as members of ``FS.filesystems``: - * ``IDBFS``, ``NODEFS``, ``PROXYFS``, ``WORKERFS``. - * - * @type {FS} - */ - const FS = Module.FS; - let namespace = { - globals, - FS, - pyodide_py, - version, - loadPackage, - loadPackagesFromImports, - loadedPackages, - isPyProxy, - runPython, - runPythonAsync, - registerJsModule, - unregisterJsModule, - setInterruptBuffer, - checkInterrupt, - toPy, - pyimport, - unpackArchive, - registerComlink, - PythonError, - PyBuffer, - }; - - namespace._module = Module; // @private - Module.public_api = namespace; - return namespace; -} diff --git a/src/js/api.ts b/src/js/api.ts new file mode 100644 index 00000000000..e48de32b3bb --- /dev/null +++ b/src/js/api.ts @@ -0,0 +1,510 @@ +declare var Module: any; +declare var Hiwire: any; +declare var API: any; +import "./module.ts"; + +import { loadPackage, loadedPackages } from "./load-package"; +import { isPyProxy, PyBuffer, PyProxy, TypedArray } from "./pyproxy.gen"; +import { PythonError } from "./error_handling.gen"; +export { loadPackage, loadedPackages, isPyProxy }; +import "./error_handling.gen.js"; + +/** + * An alias to the Python :py:mod:`pyodide` package. + * + * You can use this to call functions defined in the Pyodide Python package + * from JavaScript. + */ +export let pyodide_py: PyProxy; // actually defined in loadPyodide (see pyodide.js) + +/** + * + * An alias to the global Python namespace. + * + * For example, to access a variable called ``foo`` in the Python global + * scope, use ``pyodide.globals.get("foo")`` + */ +export let globals: PyProxy; // actually defined in loadPyodide (see pyodide.js) + +/** + * + * The Pyodide version. + * + * It can be either the exact release version (e.g. ``0.1.0``), or + * the latest release version followed by the number of commits since, and + * the git hash of the current commit (e.g. ``0.1.0-1-bd84646``). + */ +export let version: string = ""; // actually defined in loadPyodide (see pyodide.js) + +/** + * Just like `runPython` except uses a different globals dict and gets + * `eval_code` from `_pyodide` so that it can work before `pyodide` is imported. + * @private + */ +API.runPythonInternal = function (code: string): any { + // API.runPythonInternal_dict is initialized in finalizeBootstrap + return API._pyodide._base.eval_code(code, API.runPythonInternal_dict); +}; + +let runPythonPositionalGlobalsDeprecationWarned = false; +/** + * Runs a string of Python code from JavaScript, using :any:`pyodide.eval_code` + * to evaluate the code. If the last statement in the Python code is an + * expression (and the code doesn't end with a semicolon), the value of the + * expression is returned. + * + * .. admonition:: Positional globals argument + * :class: warning + * + * In Pyodide v0.19, this function took the globals parameter as a positional + * argument rather than as a named argument. In v0.20 this will still work + * but it is deprecated. It will be removed in v0.21. + * + * @param code Python code to evaluate + * @param options + * @param options.globals An optional Python dictionary to use as the globals. + * Defaults to :any:`pyodide.globals`. + * @returns The result of the Python code translated to JavaScript. See the + * documentation for :any:`pyodide.eval_code` for more info. + */ +export function runPython( + code: string, + options: { globals?: PyProxy } = {} +): any { + if (API.isPyProxy(options)) { + options = { globals: options as PyProxy }; + if (!runPythonPositionalGlobalsDeprecationWarned) { + console.warn( + "Passing a PyProxy as the second argument to runPython is deprecated and will be removed in v0.21. Use 'runPython(code, {globals : some_dict})' instead." + ); + runPythonPositionalGlobalsDeprecationWarned = true; + } + } + if (!options.globals) { + options.globals = API.globals; + } + return API.pyodide_py.eval_code(code, options.globals); +} +API.runPython = runPython; + +/** + * Inspect a Python code chunk and use :js:func:`pyodide.loadPackage` to install + * any known packages that the code chunk imports. Uses the Python API + * :func:`pyodide.find\_imports` to inspect the code. + * + * For example, given the following code as input + * + * .. code-block:: python + * + * import numpy as np x = np.array([1, 2, 3]) + * + * :js:func:`loadPackagesFromImports` will call + * ``pyodide.loadPackage(['numpy'])``. + * + * @param code The code to inspect. + * @param messageCallback The ``messageCallback`` argument of + * :any:`pyodide.loadPackage` (optional). + * @param errorCallback The ``errorCallback`` argument of + * :any:`pyodide.loadPackage` (optional). + * @async + */ +export async function loadPackagesFromImports( + code: string, + messageCallback?: (msg: string) => void, + errorCallback?: (err: string) => void +) { + let pyimports = API.pyodide_py.find_imports(code); + let imports; + try { + imports = pyimports.toJs(); + } finally { + pyimports.destroy(); + } + if (imports.length === 0) { + return; + } + + let packageNames = API._import_name_to_package_name; + let packages: Set = new Set(); + for (let name of imports) { + if (packageNames.has(name)) { + packages.add(packageNames.get(name)); + } + } + if (packages.size) { + await loadPackage(Array.from(packages), messageCallback, errorCallback); + } +} + +/** + * Run a Python code string with top level await using + * :any:`pyodide.eval_code_async` to evaluate the code. Returns a promise which + * resolves when execution completes. If the last statement in the Python code + * is an expression (and the code doesn't end with a semicolon), the returned + * promise will resolve to the value of this expression. + * + * For example: + * + * .. code-block:: pyodide + * + * let result = await pyodide.runPythonAsync(` + * from js import fetch + * response = await fetch("./packages.json") + * packages = await response.json() + * # If final statement is an expression, its value is returned to JavaScript + * len(packages.packages.object_keys()) + * `); + * console.log(result); // 79 + * + * .. admonition:: Python imports + * :class: warning + * + * Since pyodide 0.18.0, you must call :js:func:`loadPackagesFromImports` to + * import any python packages referenced via `import` statements in your + * code. This function will no longer do it for you. + * + * .. admonition:: Positional globals argument + * :class: warning + * + * In Pyodide v0.19, this function took the globals parameter as a + * positional argument rather than as a named argument. In v0.20 this will + * still work but it is deprecated. It will be removed in v0.21. + * + * @param code Python code to evaluate + * @param options + * @param options.globals An optional Python dictionary to use as the globals. + * Defaults to :any:`pyodide.globals`. + * @returns The result of the Python code translated to JavaScript. + * @async + */ +export async function runPythonAsync( + code: string, + options: { globals?: PyProxy } = {} +): Promise { + if (API.isPyProxy(options)) { + options = { globals: options as PyProxy }; + if (!runPythonPositionalGlobalsDeprecationWarned) { + console.warn( + "Passing a PyProxy as the second argument to runPythonAsync is deprecated and will be removed in v0.21. Use 'runPythonAsync(code, {globals : some_dict})' instead." + ); + runPythonPositionalGlobalsDeprecationWarned = true; + } + } + if (!options.globals) { + options.globals = API.globals; + } + return await API.pyodide_py.eval_code_async(code, options.globals); +} +API.runPythonAsync = runPythonAsync; + +/** + * Registers the JavaScript object ``module`` as a JavaScript module named + * ``name``. This module can then be imported from Python using the standard + * Python import system. If another module by the same name has already been + * imported, this won't have much effect unless you also delete the imported + * module from ``sys.modules``. This calls the {any}`pyodide_py` API + * :func:`pyodide.register_js_module`. + * + * @param name Name of the JavaScript module to add + * @param module JavaScript object backing the module + */ +export function registerJsModule(name: string, module: object) { + API.pyodide_py.register_js_module(name, module); +} + +/** + * Tell Pyodide about Comlink. + * Necessary to enable importing Comlink proxies into Python. + */ +export function registerComlink(Comlink: any) { + API._Comlink = Comlink; +} + +/** + * Unregisters a JavaScript module with given name that has been previously + * registered with :js:func:`pyodide.registerJsModule` or + * :func:`pyodide.register_js_module`. If a JavaScript module with that name + * does not already exist, will throw an error. Note that if the module has + * already been imported, this won't have much effect unless you also delete + * the imported module from ``sys.modules``. This calls the :any:`pyodide_py` API + * :func:`pyodide.unregister_js_module`. + * + * @param name Name of the JavaScript module to remove + */ +export function unregisterJsModule(name: string) { + API.pyodide_py.unregister_js_module(name); +} + +/** + * Convert the JavaScript object to a Python object as best as possible. + * + * This is similar to :any:`JsProxy.to_py` but for use from JavaScript. If the + * object is immutable or a :any:`PyProxy`, it will be returned unchanged. If + * the object cannot be converted into Python, it will be returned unchanged. + * + * See :ref:`type-translations-jsproxy-to-py` for more information. + * + * @param obj + * @param options + * @returns The object converted to Python. + */ +export function toPy( + obj: any, + { + depth, + defaultConverter, + }: { + /** + * Optional argument to limit the depth of the conversion. + */ + depth: number; + /** + * Optional argument to convert objects with no default conversion. See the + * documentation of :any:`JsProxy.to_py`. + */ + defaultConverter?: ( + value: any, + converter: (value: any) => any, + cacheConversion: (input: any, output: any) => any + ) => any; + } = { depth: -1 } +): any { + // No point in converting these, it'd be dumb to proxy them so they'd just + // get converted back by `js2python` at the end + switch (typeof obj) { + case "string": + case "number": + case "boolean": + case "bigint": + case "undefined": + return obj; + } + if (!obj || API.isPyProxy(obj)) { + return obj; + } + let obj_id = 0; + let py_result = 0; + let result = 0; + try { + obj_id = Hiwire.new_value(obj); + try { + py_result = Module.js2python_convert(obj_id, { depth, defaultConverter }); + } catch (e) { + if (e instanceof Module._PropagatePythonError) { + Module._pythonexc2js(); + } + throw e; + } + if (Module._JsProxy_Check(py_result)) { + // Oops, just created a JsProxy. Return the original object. + return obj; + // return Module.pyproxy_new(py_result); + } + result = Module._python2js(py_result); + if (result === 0) { + Module._pythonexc2js(); + } + } finally { + Hiwire.decref(obj_id); + Module._Py_DecRef(py_result); + } + return Hiwire.pop_value(result); +} + +/** + * Imports a module and returns it. + * + * .. admonition:: Warning + * :class: warning + * + * This function has a completely different behavior than the old removed pyimport function! + * + * ``pyimport`` is roughly equivalent to: + * + * .. code-block:: js + * + * pyodide.runPython(`import ${pkgname}; ${pkgname}`); + * + * except that the global namespace will not change. + * + * Example: + * + * .. code-block:: js + * + * let sysmodule = pyodide.pyimport("sys"); + * let recursionLimit = sys.getrecursionlimit(); + * + * @param mod_name The name of the module to import + * @returns A PyProxy for the imported module + */ +export function pyimport(mod_name: string): PyProxy { + return API.importlib.import_module(mod_name); +} + +let unpackArchivePositionalExtractDirDeprecationWarned = false; +/** + * Unpack an archive into a target directory. + * + * .. admonition:: Positional globals argument :class: warning + * + * In Pyodide v0.19, this function took the extract_dir parameter as a + * positional argument rather than as a named argument. In v0.20 this will + * still work but it is deprecated. It will be removed in v0.21. + * + * @param buffer The archive as an ArrayBuffer or TypedArray. + * @param format The format of the archive. Should be one of the formats + * recognized by `shutil.unpack_archive`. By default the options are 'bztar', + * 'gztar', 'tar', 'zip', and 'wheel'. Several synonyms are accepted for each + * format, e.g., for 'gztar' any of '.gztar', '.tar.gz', '.tgz', 'tar.gz' or + * 'tgz' are considered to be synonyms. + * + * @param options + * @param options.extractDir The directory to unpack the archive into. Defaults + * to the working directory. + */ +export function unpackArchive( + buffer: TypedArray, + format: string, + options: { + extractDir?: string; + } = {} +) { + if (typeof options === "string") { + if (!unpackArchivePositionalExtractDirDeprecationWarned) { + console.warn( + "Passing a string as the third argument to unpackArchive is deprecated and will be removed in v0.21. Instead use { extract_dir : 'some_path' }" + ); + unpackArchivePositionalExtractDirDeprecationWarned = true; + } + options = { extractDir: options }; + } + let extract_dir = options.extractDir; + API.package_loader.unpack_buffer.callKwargs({ + buffer, + format, + extract_dir, + }); +} + +/** + * @private + */ +API.saveState = () => API.pyodide_py._state.save_state(); + +/** + * @private + */ +API.restoreState = (state: any) => API.pyodide_py._state.restore_state(state); + +/** + * Sets the interrupt buffer to be ``interrupt_buffer``. This is only useful + * when Pyodide is used in a webworker. The buffer should be a + * ``SharedArrayBuffer`` shared with the main browser thread (or another + * worker). In that case, signal ``signum`` may be sent by writing ``signum`` + * into the interrupt buffer. If ``signum`` does not satisfy 0 < ``signum`` < + * ``NSIG`` it will be silently ignored. NSIG is 65 (internally signals are + * indicated by a bitflag). + * + * You can disable interrupts by calling `setInterruptBuffer(undefined)`. + * + * If you wish to trigger a ``KeyboardInterrupt``, write ``SIGINT`` (a 2), into + * the interrupt buffer. + * + * By default ``SIGINT`` raises a ``KeyboardInterrupt`` and all other signals + * are ignored. You can install custom signal handlers with the signal module. + * Even signals that normally have special meaning and can't be overridden like + * ``SIGKILL`` and ``SIGSEGV`` are ignored by default and can be used for any + * purpose you like. + */ +export function setInterruptBuffer(interrupt_buffer: TypedArray) { + Module.HEAP8[Module._Py_EMSCRIPTEN_SIGNAL_HANDLING] = !!interrupt_buffer; + Module.Py_EmscriptenSignalBuffer = interrupt_buffer; +} + +/** + * Throws a KeyboardInterrupt error if a KeyboardInterrupt has been requested + * via the interrupt buffer. + * + * This can be used to enable keyboard interrupts during execution of JavaScript + * code, just as ``PyErr_CheckSignals`` is used to enable keyboard interrupts + * during execution of C code. + */ +export function checkInterrupt() { + if (Module.__PyErr_CheckSignals()) { + Module._pythonexc2js(); + } +} + +export type PyodideInterface = { + globals: typeof globals; + FS: typeof FS; + pyodide_py: typeof pyodide_py; + version: typeof version; + loadPackage: typeof loadPackage; + loadPackagesFromImports: typeof loadPackagesFromImports; + loadedPackages: typeof loadedPackages; + isPyProxy: typeof isPyProxy; + runPython: typeof runPython; + runPythonAsync: typeof runPythonAsync; + registerJsModule: typeof registerJsModule; + unregisterJsModule: typeof unregisterJsModule; + setInterruptBuffer: typeof setInterruptBuffer; + checkInterrupt: typeof checkInterrupt; + toPy: typeof toPy; + pyimport: typeof pyimport; + unpackArchive: typeof unpackArchive; + registerComlink: typeof registerComlink; + PythonError: typeof PythonError; + PyBuffer: typeof PyBuffer; +}; + +/** + * An alias to the `Emscripten File System API + * `_. + * + * This provides a wide range of POSIX-`like` file/device operations, including + * `mount + * `_ + * which can be used to extend the in-memory filesystem with features like `persistence + * `_. + * + * While all the file systems implementations are enabled, only the default + * ``MEMFS`` is guaranteed to work in all runtime settings. The implementations + * are available as members of ``FS.filesystems``: + * ``IDBFS``, ``NODEFS``, ``PROXYFS``, ``WORKERFS``. + */ +export let FS: any; + +/** + * @private + */ +API.makePublicAPI = function (): PyodideInterface { + FS = Module.FS; + let namespace = { + globals, + FS, + pyodide_py, + version, + loadPackage, + loadPackagesFromImports, + loadedPackages, + isPyProxy, + runPython, + runPythonAsync, + registerJsModule, + unregisterJsModule, + setInterruptBuffer, + checkInterrupt, + toPy, + pyimport, + unpackArchive, + registerComlink, + PythonError, + PyBuffer, + _module: Module, + _api: API, + }; + + API.public_api = namespace; + return namespace; +}; diff --git a/src/js/compat.ts b/src/js/compat.ts new file mode 100644 index 00000000000..a9438e9b663 --- /dev/null +++ b/src/js/compat.ts @@ -0,0 +1,188 @@ +// Detect if we're in node +declare var process: any; + +export const IN_NODE = + typeof process !== "undefined" && + process.release && + process.release.name === "node" && + typeof process.browser === + "undefined"; /* This last condition checks if we run the browser shim of process */ + +let nodePathMod: any; +let nodeFetch: any; +let nodeVmMod: any; +/** @private */ +export let nodeFsPromisesMod: any; + +declare var globalThis: { + importScripts: (url: string) => void; + document?: any; +}; + +/** + * If we're in node, it's most convenient to import various node modules on + * initialization. Otherwise, this does nothing. + * @private + */ +export async function initNodeModules() { + if (!IN_NODE) { + return; + } + // @ts-ignore + nodePathMod = (await import("path")).default; + nodeFsPromisesMod = await import("fs/promises"); + // @ts-ignore + nodeFetch = (await import("node-fetch")).default; + // @ts-ignore + nodeVmMod = (await import("vm")).default; + if (typeof require !== "undefined") { + return; + } + // Emscripten uses `require`, so if it's missing (because we were imported as + // an ES6 module) we need to polyfill `require` with `import`. `import` is + // async and `require` is synchronous, so we import all packages that might be + // required up front and define require to look them up in this table. + + // These are all the packages required in pyodide.asm.js. You can get this + // list with: + // $ grep -o 'require("[a-z]*")' pyodide.asm.js | sort -u + const fs = await import("fs"); + const crypto = await import("crypto"); + const ws = await import("ws"); + const child_process = await import("child_process"); + const node_modules: { [mode: string]: any } = { + fs, + crypto, + ws, + child_process, + }; + // Since we're in an ES6 module, this is only modifying the module namespace, + // it's still private to Pyodide. + (globalThis as any).require = function (mod: string): any { + return node_modules[mod]; + }; +} + +/** + * Load a binary file, only for use in Node. If the path explicitly is a URL, + * then fetch from a URL, else load from the file system. + * @param indexURL base path to resolve relative paths + * @param path the path to load + * @returns An ArrayBuffer containing the binary data + * @private + */ +async function node_loadBinaryFile( + indexURL: string, + path: string +): Promise { + if (!path.startsWith("/") && !path.includes("://")) { + // If path starts with a "/" or starts with a protocol "blah://", we + // interpret it as an absolute path, otherwise "resolve" it by + // joining it with indexURL. + path = `${indexURL}${path}`; + } + if (path.startsWith("file://")) { + // handle file:// with filesystem operations rather than with fetch. + path = path.slice("file://".length); + } + if (path.includes("://")) { + // If it has a protocol, make a fetch request + let response = await nodeFetch(path); + if (!response.ok) { + throw new Error(`Failed to load '${path}': request failed.`); + } + return new Uint8Array(await response.arrayBuffer()); + } else { + // Otherwise get it from the file system + const data = await nodeFsPromisesMod.readFile(path); + return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + } +} + +/** + * Load a binary file, only for use in browser. Resolves relative paths against + * indexURL. + * + * @param indexURL base path to resolve relative paths + * @param path the path to load + * @returns A Uint8Array containing the binary data + * @private + */ +async function browser_loadBinaryFile( + indexURL: string, + path: string +): Promise { + // @ts-ignore + const base = new URL(indexURL, location); + const url = new URL(path, base); + // @ts-ignore + let response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to load '${url}': request failed.`); + } + return new Uint8Array(await response.arrayBuffer()); +} + +/** @private */ +export let _loadBinaryFile: ( + indexURL: string, + path: string +) => Promise; +if (IN_NODE) { + _loadBinaryFile = node_loadBinaryFile; +} else { + _loadBinaryFile = browser_loadBinaryFile; +} + +/** + * Currently loadScript is only used once to load `pyodide.asm.js`. + * @param url + * @async + * @private + */ +export let loadScript: (url: string) => Promise; + +if (globalThis.document) { + // browser + loadScript = async (url) => await import(url); +} else if (globalThis.importScripts) { + // webworker + loadScript = async (url) => { + // This is async only for consistency + try { + // use importScripts in classic web worker + globalThis.importScripts(url); + } catch (e) { + // importScripts throws TypeError in a module type web worker, use import instead + if (e instanceof TypeError) { + await import(url); + } else { + throw e; + } + } + }; +} else if (IN_NODE) { + loadScript = nodeLoadScript; +} else { + throw new Error("Cannot determine runtime environment"); +} + +/** + * Load a text file and executes it as Javascript + * @param url The path to load. May be a url or a relative file system path. + * @private + */ +async function nodeLoadScript(url: string) { + if (url.startsWith("file://")) { + // handle file:// with filesystem operations rather than with fetch. + url = url.slice("file://".length); + } + if (url.includes("://")) { + // If it's a url, load it with fetch then eval it. + nodeVmMod.runInThisContext(await (await nodeFetch(url)).text()); + } else { + // Otherwise, hopefully it is a relative path we can load from the file + // system. + await import(nodePathMod.resolve(url)); + } +} diff --git a/src/js/load-package.ts b/src/js/load-package.ts new file mode 100644 index 00000000000..1f7a8cdfa5d --- /dev/null +++ b/src/js/load-package.ts @@ -0,0 +1,453 @@ +declare var Module: any; +declare var Tests: any; +declare var API: any; + +import { + IN_NODE, + nodeFsPromisesMod, + _loadBinaryFile, + initNodeModules, +} from "./compat.js"; +import { PyProxy, isPyProxy } from "./pyproxy.gen"; + +/** @private */ +let baseURL: string; +/** + * Initialize the packages index. This is called as early as possible in + * loadPyodide so that fetching packages.json can occur in parallel with other + * operations. + * @param indexURL + * @private + */ +export async function initializePackageIndex(indexURL: string) { + baseURL = indexURL; + let package_json; + if (IN_NODE) { + await initNodeModules(); + const package_string = await nodeFsPromisesMod.readFile( + `${indexURL}packages.json` + ); + package_json = JSON.parse(package_string); + } else { + let response = await fetch(`${indexURL}packages.json`); + package_json = await response.json(); + } + if (!package_json.packages) { + throw new Error( + "Loaded packages.json does not contain the expected key 'packages'." + ); + } + API.packages = package_json.packages; + + // compute the inverted index for imports to package names + API._import_name_to_package_name = new Map(); + for (let name of Object.keys(API.packages)) { + for (let import_name of API.packages[name].imports) { + API._import_name_to_package_name.set(import_name, name); + } + } +} + +/** + * Only used in Node. If we can't find a package in node_modules, we'll use this + * to fetch the package from the cdn (and we'll store it into node_modules so + * subsequent loads don't require a web request). + * @private + */ +let cdnURL: string; +API.setCdnUrl = function (url: string) { + cdnURL = url; +}; + +// +// Dependency resolution +// +const DEFAULT_CHANNEL = "default channel"; +// Regexp for validating package name and URI +const package_uri_regexp = /^.*?([^\/]*)\.whl$/; + +function _uri_to_package_name(package_uri: string): string | undefined { + let match = package_uri_regexp.exec(package_uri); + if (match) { + let wheel_name = match[1].toLowerCase(); + return wheel_name.split("-").slice(0, -4).join("-"); + } +} + +/** + * Recursively add a package and its dependencies to toLoad and toLoadShared. + * A helper function for recursiveDependencies. + * @param name The package to add + * @param toLoad The set of names of packages to load + * @param toLoadShared The set of names of shared libraries to load + * @private + */ +function addPackageToLoad( + name: string, + toLoad: Map, + toLoadShared: Map +) { + name = name.toLowerCase(); + if (toLoad.has(name)) { + return; + } + const pkg_info = API.packages[name]; + if (!pkg_info) { + throw new Error(`No known package with name '${name}'`); + } + if (pkg_info.shared_library) { + toLoadShared.set(name, DEFAULT_CHANNEL); + } else { + toLoad.set(name, DEFAULT_CHANNEL); + } + // If the package is already loaded, we don't add dependencies, but warn + // the user later. This is especially important if the loaded package is + // from a custom url, in which case adding dependencies is wrong. + if (loadedPackages[name] !== undefined) { + return; + } + + for (let dep_name of pkg_info.depends) { + addPackageToLoad(dep_name, toLoad, toLoadShared); + } +} + +/** + * Calculate the dependencies of a set of packages + * @param names The list of names whose dependencies we need to calculate. + * @returns Two sets, the set of normal dependencies and the set of shared + * dependencies + * @private + */ +function recursiveDependencies( + names: string[], + errorCallback: (err: string) => void +) { + const toLoad = new Map(); + const toLoadShared = new Map(); + for (let name of names) { + const pkgname = _uri_to_package_name(name); + if (pkgname === undefined) { + addPackageToLoad(name, toLoad, toLoadShared); + continue; + } + if (toLoad.has(pkgname) && toLoad.get(pkgname) !== name) { + errorCallback( + `Loading same package ${pkgname} from ${name} and ${toLoad.get( + pkgname + )}` + ); + continue; + } + toLoad.set(pkgname, name); + } + return [toLoad, toLoadShared]; +} + +// +// Dependency download and install +// + +/** + * Download a package. If `channel` is `DEFAULT_CHANNEL`, look up the wheel URL + * relative to baseURL from `packages.json`, otherwise use the URL specified by + * `channel`. + * @param name The name of the package + * @param channel Either `DEFAULT_CHANNEL` or the absolute URL to the + * wheel or the path to the wheel relative to baseURL. + * @returns The binary data for the package + * @private + */ +async function downloadPackage( + name: string, + channel: string +): Promise { + let file_name; + if (channel === DEFAULT_CHANNEL) { + if (!(name in API.packages)) { + throw new Error(`Internal error: no entry for package named ${name}`); + } + file_name = API.packages[name].file_name; + } else { + file_name = channel; + } + try { + return await _loadBinaryFile(baseURL, file_name); + } catch (e) { + if (!IN_NODE) { + throw e; + } + } + console.log( + `Didn't find package ${file_name}, attempting to load from ${cdnURL}` + ); + // If we are IN_NODE, download the package from the cdn, then stash it into + // the node_modules directory for future use. + let binary = await _loadBinaryFile(cdnURL, file_name); + console.log( + `Package ${file_name} loaded from ${cdnURL}, caching the wheel in node_modules for future use.` + ); + await nodeFsPromisesMod.writeFile(`${baseURL}${file_name}`, binary); + return binary; +} + +/** + * Install the package into the file system. + * @param name The name of the package + * @param buffer The binary data returned by downloadPkgBuffer + * @private + */ +async function installPackage(name: string, buffer: Uint8Array) { + let pkg = API.packages[name]; + if (!pkg) { + pkg = { + file_name: ".whl", + install_dir: "site", + shared_library: false, + depends: [], + imports: [] as string[], + }; + } + const filename = pkg.file_name; + // This Python helper function unpacks the buffer and lists out any so files therein. + const dynlibs = API.package_loader.unpack_buffer.callKwargs({ + buffer, + filename, + target: pkg.install_dir, + calculate_dynlibs: true, + }); + for (const dynlib of dynlibs) { + await loadDynlib(dynlib, pkg.shared_library); + } + loadedPackages[name] = pkg; +} + +/** + * @returns A new asynchronous lock + * @private + */ +function createLock() { + // This is a promise that is resolved when the lock is open, not resolved when lock is held. + let _lock = Promise.resolve(); + + /** + * Acquire the async lock + * @returns A zero argument function that releases the lock. + * @private + */ + async function acquireLock() { + const old_lock = _lock; + let releaseLock: () => void; + _lock = new Promise((resolve) => (releaseLock = resolve)); + await old_lock; + // @ts-ignore + return releaseLock; + } + return acquireLock; +} + +// Emscripten has a lock in the corresponding code in library_browser.js. I +// don't know why we need it, but quite possibly bad stuff will happen without +// it. +const acquireDynlibLock = createLock(); + +/** + * Load a dynamic library. This is an async operation and Python imports are + * synchronous so we have to do it ahead of time. When we add more support for + * synchronous I/O, we could consider doing this later as a part of a Python + * import hook. + * + * @param lib The file system path to the library. + * @param shared Is this a shared library or not? + * @private + */ +async function loadDynlib(lib: string, shared: boolean) { + const node = Module.FS.lookupPath(lib).node; + let byteArray; + if (node.mount.type == Module.FS.filesystems.MEMFS) { + byteArray = Module.FS.filesystems.MEMFS.getFileDataAsTypedArray( + Module.FS.lookupPath(lib).node + ); + } else { + byteArray = Module.FS.readFile(lib); + } + const releaseDynlibLock = await acquireDynlibLock(); + try { + const module = await Module.loadWebAssemblyModule(byteArray, { + loadAsync: true, + nodelete: true, + allowUndefined: true, + }); + Module.preloadedWasm[lib] = module; + Module.preloadedWasm[lib.split("/").pop()!] = module; + if (shared) { + Module.loadDynamicLibrary(lib, { + global: true, + nodelete: true, + }); + } + } catch (e) { + if (e.message.includes("need to see wasm magic number")) { + console.warn( + `Failed to load dynlib ${lib}. We probably just tried to load a linux .so file or something.` + ); + return; + } + throw e; + } finally { + releaseDynlibLock(); + } +} +Tests.loadDynlib = loadDynlib; + +const acquirePackageLock = createLock(); + +/** + * Load a package or a list of packages over the network. This installs the + * package in the virtual filesystem. The package needs to be imported from + * Python before it can be used. + * + * @param names Either a single package name or + * URL or a list of them. URLs can be absolute or relative. The URLs must have + * file name ``.js`` and there must be a file called + * ``.data`` in the same directory. The argument can be a + * ``PyProxy`` of a list, in which case the list will be converted to JavaScript + * and the ``PyProxy`` will be destroyed. + * @param messageCallback A callback, called with progress messages + * (optional) + * @param errorCallback A callback, called with error/warning messages + * (optional) + * @async + */ +export async function loadPackage( + names: string | PyProxy | Array, + messageCallback?: (msg: string) => void, + errorCallback?: (msg: string) => void +) { + messageCallback = messageCallback || console.log; + errorCallback = errorCallback || console.error; + if (isPyProxy(names)) { + names = names.toJs(); + } + if (!Array.isArray(names)) { + names = [names as string]; + } + + const [toLoad, toLoadShared] = recursiveDependencies(names, errorCallback); + + for (const [pkg, uri] of [...toLoad, ...toLoadShared]) { + const loaded = loadedPackages[pkg]; + if (loaded === undefined) { + continue; + } + toLoad.delete(pkg); + toLoadShared.delete(pkg); + // If uri is from the DEFAULT_CHANNEL, we assume it was added as a + // dependency, which was previously overridden. + if (loaded === uri || uri === DEFAULT_CHANNEL) { + messageCallback(`${pkg} already loaded from ${loaded}`); + } else { + errorCallback( + `URI mismatch, attempting to load package ${pkg} from ${uri} ` + + `while it is already loaded from ${loaded}. To override a dependency, ` + + `load the custom package first.` + ); + } + } + + if (toLoad.size === 0 && toLoadShared.size === 0) { + messageCallback("No new packages to load"); + return; + } + + const packageNames = [...toLoad.keys(), ...toLoadShared.keys()].join(", "); + const releaseLock = await acquirePackageLock(); + try { + messageCallback(`Loading ${packageNames}`); + const sharedLibraryLoadPromises: { [name: string]: Promise } = + {}; + const packageLoadPromises: { [name: string]: Promise } = {}; + for (const [name, channel] of toLoadShared) { + if (loadedPackages[name]) { + // Handle the race condition where the package was loaded between when + // we did dependency resolution and when we acquired the lock. + toLoadShared.delete(name); + continue; + } + sharedLibraryLoadPromises[name] = downloadPackage(name, channel); + } + for (const [name, channel] of toLoad) { + if (loadedPackages[name]) { + // Handle the race condition where the package was loaded between when + // we did dependency resolution and when we acquired the lock. + toLoad.delete(name); + continue; + } + packageLoadPromises[name] = downloadPackage(name, channel); + } + + const loaded: string[] = []; + const failed: { [name: string]: any } = {}; + // TODO: add support for prefetching modules by awaiting on a promise right + // here which resolves in loadPyodide when the bootstrap is done. + const sharedLibraryInstallPromises: { [name: string]: Promise } = {}; + const packageInstallPromises: { [name: string]: Promise } = {}; + for (const [name, channel] of toLoadShared) { + sharedLibraryInstallPromises[name] = sharedLibraryLoadPromises[name] + .then(async (buffer) => { + await installPackage(name, buffer); + loaded.push(name); + loadedPackages[name] = channel; + }) + .catch((err) => { + console.warn(err); + failed[name] = err; + }); + } + + await Promise.all(Object.values(sharedLibraryInstallPromises)); + for (const [name, channel] of toLoad) { + packageInstallPromises[name] = packageLoadPromises[name] + .then(async (buffer) => { + await installPackage(name, buffer); + loaded.push(name); + loadedPackages[name] = channel; + }) + .catch((err) => { + console.warn(err); + failed[name] = err; + }); + } + await Promise.all(Object.values(packageInstallPromises)); + + Module.reportUndefinedSymbols(); + if (loaded.length > 0) { + const successNames = loaded.join(", "); + messageCallback(`Loaded ${successNames}`); + } + if (Object.keys(failed).length > 0) { + const failedNames = Object.keys(failed).join(", "); + messageCallback(`Failed to load ${failedNames}`); + for (const [name, err] of Object.entries(failed)) { + console.warn(`The following error occurred while loading ${name}:`); + console.error(err); + } + } + + // We have to invalidate Python's import caches, or it won't + // see the new files. + API.importlib.invalidate_caches(); + } finally { + releaseLock(); + } +} + +/** + * The list of packages that Pyodide has loaded. + * Use ``Object.keys(pyodide.loadedPackages)`` to get the list of names of + * loaded packages, and ``pyodide.loadedPackages[package_name]`` to access + * install location for a particular ``package_name``. + */ +export let loadedPackages: { [key: string]: string } = {}; + +API.packageIndexReady = initializePackageIndex(API.config.indexURL); diff --git a/src/js/load-pyodide.js b/src/js/load-pyodide.js deleted file mode 100644 index e3d01e4f139..00000000000 --- a/src/js/load-pyodide.js +++ /dev/null @@ -1,426 +0,0 @@ -import { Module } from "./module.js"; - -const IN_NODE = - typeof process !== "undefined" && - process.release && - process.release.name === "node" && - typeof process.browser === - "undefined"; /* This last condition checks if we run the browser shim of process */ - -/** @typedef {import('./pyproxy.js').PyProxy} PyProxy */ -/** @private */ -let baseURL; -/** - * @param {string} indexURL - * @private - */ -export async function initializePackageIndex(indexURL) { - baseURL = indexURL; - let package_json; - if (IN_NODE) { - const fsPromises = await import(/* webpackIgnore: true */ "fs/promises"); - const package_string = await fsPromises.readFile( - `${indexURL}packages.json` - ); - package_json = JSON.parse(package_string); - } else { - let response = await fetch(`${indexURL}packages.json`); - package_json = await response.json(); - } - if (!package_json.packages) { - throw new Error( - "Loaded packages.json does not contain the expected key 'packages'." - ); - } - Module.packages = package_json.packages; - - // compute the inverted index for imports to package names - Module._import_name_to_package_name = new Map(); - for (let name of Object.keys(Module.packages)) { - for (let import_name of Module.packages[name].imports) { - Module._import_name_to_package_name.set(import_name, name); - } - } -} - -export async function _fetchBinaryFile(indexURL, path) { - if (IN_NODE) { - const fsPromises = await import(/* webpackIgnore: true */ "fs/promises"); - const tar_buffer = await fsPromises.readFile(`${indexURL}${path}`); - return tar_buffer.buffer; - } else { - let response = await fetch(`${indexURL}${path}`); - return await response.arrayBuffer(); - } -} - -//////////////////////////////////////////////////////////// -// Package loading -const DEFAULT_CHANNEL = "default channel"; - -// Regexp for validating package name and URI -const package_uri_regexp = /^.*?([^\/]*)\.js$/; - -function _uri_to_package_name(package_uri) { - let match = package_uri_regexp.exec(package_uri); - if (match) { - return match[1].toLowerCase(); - } -} - -/** - * @param {string) url - * @async - * @private - */ -export let loadScript; -if (globalThis.document) { - // browser - loadScript = async (url) => await import(/* webpackIgnore: true */ url); -} else if (globalThis.importScripts) { - // webworker - loadScript = async (url) => { - // This is async only for consistency - globalThis.importScripts(url); - }; -} else if (IN_NODE) { - const pathPromise = import(/* webpackIgnore: true */ "path").then( - (M) => M.default - ); - const fetchPromise = import("node-fetch").then((M) => M.default); - const vmPromise = import(/* webpackIgnore: true */ "vm").then( - (M) => M.default - ); - loadScript = async (url) => { - if (url.includes("://")) { - // If it's a url, have to load it with fetch and then eval it. - const fetch = await fetchPromise; - const vm = await vmPromise; - vm.runInThisContext(await (await fetch(url)).text()); - } else { - // Otherwise, hopefully it is a relative path we can load from the file - // system. - const path = await pathPromise; - await import(path.resolve(url)); - } - }; -} else { - throw new Error("Cannot determine runtime environment"); -} - -function addPackageToLoad(name, toLoad) { - name = name.toLowerCase(); - if (toLoad.has(name)) { - return; - } - toLoad.set(name, DEFAULT_CHANNEL); - // If the package is already loaded, we don't add dependencies, but warn - // the user later. This is especially important if the loaded package is - // from a custom url, in which case adding dependencies is wrong. - if (loadedPackages[name] !== undefined) { - return; - } - for (let dep_name of Module.packages[name].depends) { - addPackageToLoad(dep_name, toLoad); - } -} - -function recursiveDependencies( - names, - _messageCallback, - errorCallback, - sharedLibsOnly -) { - const toLoad = new Map(); - for (let name of names) { - const pkgname = _uri_to_package_name(name); - if (toLoad.has(pkgname) && toLoad.get(pkgname) !== name) { - errorCallback( - `Loading same package ${pkgname} from ${name} and ${toLoad.get( - pkgname - )}` - ); - continue; - } - if (pkgname !== undefined) { - toLoad.set(pkgname, name); - continue; - } - name = name.toLowerCase(); - if (name in Module.packages) { - addPackageToLoad(name, toLoad); - continue; - } - errorCallback(`Skipping unknown package '${name}'`); - } - if (sharedLibsOnly) { - let onlySharedLibs = new Map(); - for (let c of toLoad) { - let name = c[0]; - if (Module.packages[name].shared_library) { - onlySharedLibs.set(name, toLoad.get(name)); - } - } - return onlySharedLibs; - } - return toLoad; -} - -// locateFile is the function used by the .js file to locate the .data file -// given the filename -Module.locateFile = function (path) { - // handle packages loaded from custom URLs - let pkg = path.replace(/\.data$/, ""); - const toLoad = Module.locateFile_packagesToLoad; - if (toLoad && toLoad.has(pkg)) { - let package_uri = toLoad.get(pkg); - if (package_uri != DEFAULT_CHANNEL) { - return package_uri.replace(/\.js$/, ".data"); - } - } - return baseURL + path; -}; - -// When the JS loads, it synchronously adds a runDependency to emscripten. It -// then loads the data file, and removes the runDependency from emscripten. -// This function returns a promise that resolves when there are no pending -// runDependencies. -function waitRunDependency() { - const promise = new Promise((r) => { - Module.monitorRunDependencies = (n) => { - if (n === 0) { - r(); - } - }; - }); - // If there are no pending dependencies left, monitorRunDependencies will - // never be called. Since we can't check the number of dependencies, - // manually trigger a call. - Module.addRunDependency("dummy"); - Module.removeRunDependency("dummy"); - return promise; -} - -async function _loadPackage(names, messageCallback, errorCallback) { - // toLoad is a map pkg_name => pkg_uri - let toLoad = recursiveDependencies(names, messageCallback, errorCallback); - // Tell Module.locateFile about the packages we're loading - Module.locateFile_packagesToLoad = toLoad; - if (toLoad.size === 0) { - return Promise.resolve("No new packages to load"); - } else { - let packageNames = Array.from(toLoad.keys()).join(", "); - messageCallback(`Loading ${packageNames}`); - } - - // This is a collection of promises that resolve when the package's JS file is - // loaded. The promises already handle error and never fail. - let scriptPromises = []; - - for (let [pkg, uri] of toLoad) { - let loaded = loadedPackages[pkg]; - if (loaded !== undefined) { - // If uri is from the DEFAULT_CHANNEL, we assume it was added as a - // depedency, which was previously overridden. - if (loaded === uri || uri === DEFAULT_CHANNEL) { - messageCallback(`${pkg} already loaded from ${loaded}`); - continue; - } else { - errorCallback( - `URI mismatch, attempting to load package ${pkg} from ${uri} ` + - `while it is already loaded from ${loaded}. To override a dependency, ` + - `load the custom package first.` - ); - continue; - } - } - let pkgname = (Module.packages[pkg] && Module.packages[pkg].name) || pkg; - let scriptSrc = uri === DEFAULT_CHANNEL ? `${baseURL}${pkgname}.js` : uri; - messageCallback(`Loading ${pkg} from ${scriptSrc}`); - scriptPromises.push( - loadScript(scriptSrc).catch((e) => { - errorCallback(`Couldn't load package from URL ${scriptSrc}`, e); - toLoad.delete(pkg); - }) - ); - } - - // We must start waiting for runDependencies *after* all the JS files are - // loaded, since the number of runDependencies may happen to equal zero - // between package files loading. - try { - await Promise.all(scriptPromises).then(waitRunDependency); - } finally { - delete Module.monitorRunDependencies; - } - - let packageList = []; - for (let [pkg, uri] of toLoad) { - loadedPackages[pkg] = uri; - packageList.push(pkg); - } - - let resolveMsg; - if (packageList.length > 0) { - let packageNames = packageList.join(", "); - resolveMsg = `Loaded ${packageNames}`; - } else { - resolveMsg = "No packages loaded"; - } - - Module.reportUndefinedSymbols(); - - messageCallback(resolveMsg); - - // We have to invalidate Python's import caches, or it won't - // see the new files. - Module.importlib.invalidate_caches(); -} - -// This is a promise that is resolved iff there are no pending package loads. It -// never fails. -let _package_lock = Promise.resolve(); - -/** - * An async lock for package loading. Prevents race conditions in loadPackage. - * @returns A zero argument function that releases the lock. - * @private - */ -async function acquirePackageLock() { - let old_lock = _package_lock; - let releaseLock; - _package_lock = new Promise((resolve) => (releaseLock = resolve)); - await old_lock; - return releaseLock; -} - -/** - * - * The list of packages that Pyodide has loaded. - * Use ``Object.keys(pyodide.loadedPackages)`` to get the list of names of - * loaded packages, and ``pyodide.loadedPackages[package_name]`` to access - * install location for a particular ``package_name``. - * - * @type {object} - */ -export let loadedPackages = {}; - -let sharedLibraryWasmPlugin; -let origWasmPlugin; -let wasmPluginIndex; -function initSharedLibraryWasmPlugin() { - for (let p in Module.preloadPlugins) { - if (Module.preloadPlugins[p].canHandle("test.so")) { - origWasmPlugin = Module.preloadPlugins[p]; - wasmPluginIndex = p; - break; - } - } - sharedLibraryWasmPlugin = { - canHandle: origWasmPlugin.canHandle, - handle(byteArray, name, onload, onerror) { - origWasmPlugin.handle(byteArray, name, onload, onerror); - origWasmPlugin.asyncWasmLoadPromise = (async () => { - await origWasmPlugin.asyncWasmLoadPromise; - Module.loadDynamicLibrary(name, { - global: true, - nodelete: true, - }); - })(); - }, - }; -} - -// override the load plugin so that it calls "Module.loadDynamicLibrary" on any -// .so files. -// this only needs to be done for shared library packages because we assume that -// if a package depends on a shared library it needs to have access to it. not -// needed for .so in standard module because those are linked together -// correctly, it is only where linking goes across modules that it needs to be -// done. Hence, we only put this extra preload plugin in during the shared -// library load -function useSharedLibraryWasmPlugin() { - if (!sharedLibraryWasmPlugin) { - initSharedLibraryWasmPlugin(); - } - Module.preloadPlugins[wasmPluginIndex] = sharedLibraryWasmPlugin; -} - -function restoreOrigWasmPlugin() { - Module.preloadPlugins[wasmPluginIndex] = origWasmPlugin; -} - -/** - * @callback LogFn - * @param {string} msg - * @returns {void} - * @private - */ - -/** - * Load a package or a list of packages over the network. This installs the - * package in the virtual filesystem. The package needs to be imported from - * Python before it can be used. - * - * @param {string | string[] | PyProxy} names Either a single package name or - * URL or a list of them. URLs can be absolute or relative. The URLs must have - * file name ``.js`` and there must be a file called - * ``.data`` in the same directory. The argument can be a - * ``PyProxy`` of a list, in which case the list will be converted to JavaScript - * and the ``PyProxy`` will be destroyed. - * @param {LogFn=} messageCallback A callback, called with progress messages - * (optional) - * @param {LogFn=} errorCallback A callback, called with error/warning messages - * (optional) - * @async - */ -export async function loadPackage(names, messageCallback, errorCallback) { - if (Module.isPyProxy(names)) { - let temp; - try { - temp = names.toJs(); - } finally { - names.destroy(); - } - names = temp; - } - - if (!Array.isArray(names)) { - names = [names]; - } - // get shared library packages and load those first - // otherwise bad things happen with linking them in firefox. - let sharedLibraryNames = []; - try { - let sharedLibraryPackagesToLoad = recursiveDependencies( - names, - messageCallback, - errorCallback, - true - ); - for (let pkg of sharedLibraryPackagesToLoad) { - sharedLibraryNames.push(pkg[0]); - } - } catch (e) { - // do nothing - let the main load throw any errors - } - - let releaseLock = await acquirePackageLock(); - try { - useSharedLibraryWasmPlugin(); - await _loadPackage( - sharedLibraryNames, - messageCallback || console.log, - errorCallback || console.error - ); - restoreOrigWasmPlugin(); - await _loadPackage( - names, - messageCallback || console.log, - errorCallback || console.error - ); - } finally { - restoreOrigWasmPlugin(); - releaseLock(); - } -} diff --git a/src/js/module.js b/src/js/module.ts similarity index 72% rename from src/js/module.js rename to src/js/module.ts index 999679a1d94..d5cda490fa0 100644 --- a/src/js/module.js +++ b/src/js/module.ts @@ -1,28 +1,44 @@ -/** - * @typedef {import('emscripten').Module} Module - */ +/** @private */ +export interface Module { + noImageDecoding: boolean; + noAudioDecoding: boolean; + noWasmDecoding: boolean; + preRun: { (): void }[]; + print: (a: string) => void; + printErr: (a: string) => void; + ENV: { [key: string]: string }; + preloadedWasm: any; + FS: any; +} /** * The Emscripten Module. * * @private - * @type {Module} */ -export let Module = {}; -Module.noImageDecoding = true; -Module.noAudioDecoding = true; -Module.noWasmDecoding = false; // we preload wasm using the built in plugin now -Module.preloadedWasm = {}; -Module.preRun = []; +export function createModule(): any { + let Module: any = {}; + Module.noImageDecoding = true; + Module.noAudioDecoding = true; + Module.noWasmDecoding = false; // we preload wasm using the built in plugin now + Module.preloadedWasm = {}; + Module.preRun = []; + return Module; +} /** * - * @param {undefined | function(): string} stdin - * @param {undefined | function(string)} stdout - * @param {undefined | function(string)} stderr + * @param stdin + * @param stdout + * @param stderr * @private */ -export function setStandardStreams(stdin, stdout, stderr) { +export function setStandardStreams( + Module: Module, + stdin?: () => string, + stdout?: (a: string) => void, + stderr?: (a: string) => void +) { // For stdout and stderr, emscripten provides convenient wrappers that save us the trouble of converting the bytes into a string if (stdout) { Module.print = stdout; @@ -40,7 +56,7 @@ export function setStandardStreams(stdin, stdout, stderr) { } } -function createStdinWrapper(stdin) { +function createStdinWrapper(stdin: () => string) { // When called, it asks the user for one whole line of input (stdin) // Then, it passes the individual bytes of the input to emscripten, one after another. // And finally, it terminates it with null. @@ -89,10 +105,10 @@ function createStdinWrapper(stdin) { * Make the home directory inside the virtual file system, * then change the working directory to it. * - * @param {string} path + * @param path * @private */ -export function setHomeDirectory(path) { +export function setHomeDirectory(Module: Module, path: string) { Module.preRun.push(function () { const fallbackPath = "/"; try { diff --git a/src/js/package-lock.json b/src/js/package-lock.json index ea6897e31fb..f893e53075a 100644 --- a/src/js/package-lock.json +++ b/src/js/package-lock.json @@ -1,377 +1,9835 @@ { "name": "pyodide", - "version": "0.19.0-dev.0", - "lockfileVersion": 1, + "version": "0.21.0-dev.0", + "lockfileVersion": 2, "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" + "packages": { + "": { + "name": "pyodide", + "version": "0.21.0-dev.0", + "license": "Apache-2.0", + "dependencies": { + "base-64": "^1.0.0", + "node-fetch": "^2.6.1", + "ws": "^8.5.0" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-node-resolve": "^13.1.3", + "@types/assert": "^1.5.6", + "@types/emscripten": "^1.39.5", + "@types/expect": "^24.3.0", + "@types/mocha": "^9.1.0", + "@types/node": "^17.0.25", + "@types/node-fetch": "^2.6.1", + "@types/ws": "^8.5.3", + "chai": "^4.3.6", + "chai-as-promised": "^7.1.1", + "cross-env": "^7.0.3", + "dts-bundle-generator": "^6.7.0", + "error-stack-parser": "^2.0.6", + "express": "^4.17.3", + "mocha": "^9.0.2", + "npm-run-all": "^4.1.5", + "nyc": "^15.1.0", + "prettier": "^2.2.1", + "puppeteer": "^13.6.0", + "rollup": "^2.48.0", + "rollup-plugin-terser": "^7.0.2", + "rollup-plugin-ts": "^2.0.5", + "terser": "^5.7.0", + "ts-mocha": "^9.0.2", + "tsd": "^0.15.1", + "typedoc": "^0.22.11", + "typescript": "^4.2.4" } }, - "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "node_modules/@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "@jridgewell/trace-mapping": "^0.3.0" + }, + "engines": { + "node": ">=6.0.0" } }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", - "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "node_modules/@babel/compat-data": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "engines": { + "node": ">=6.9.0" } }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true + "node_modules/@babel/core": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz", + "integrity": "sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.9", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.9", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.9", + "@babel/types": "^7.17.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, - "@types/emscripten": { - "version": "1.39.5", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.5.tgz", - "integrity": "sha512-DIOOg+POSrYl+OlNRHQuIEqCd8DCtynG57H862UCce16nXJX7J8eWxNGgOcf8Eyge8zXeSs27mz1UcFu8L/L7g==", - "dev": true + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } }, - "@types/eslint": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.14.tgz", - "integrity": "sha512-pESyhSbUOskqrGcaN+bCXIQDyT5zTaRWfj5ZjjSlMatgGjIn3QQPfocAu4WSabUR7CGyLZ2CQaZyISOEX7/saw==", + "node_modules/@babel/generator": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", + "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" + "dependencies": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true + "node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true + "node_modules/@babel/helper-compilation-targets": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } }, - "@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", - "dev": true + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } }, - "@types/node": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.0.0.tgz", - "integrity": "sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg==", - "dev": true + "node_modules/@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true + "node_modules/@babel/helper-function-name": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } }, - "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, - "requires": { - "string-width": "^3.0.0" + "dependencies": { + "@babel/types": "^7.16.7" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", + "dev": true, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "node_modules/@babel/helper-simple-access": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, - "requires": { - "type-fest": "^0.21.3" + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true, - "requires": { - "color-convert": "^2.0.1" + "engines": { + "node": ">=6.9.0" } }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "node_modules/@babel/helpers": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.9", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "argparse": { + "node_modules/@babel/highlight": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", + "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", + "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", + "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.9", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.9", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mdn/browser-compat-data": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.4.tgz", + "integrity": "sha512-GT+WV0p/3Y90zfyv3alYZykqkySbFxq6lJcK1RMnaoPoA6RGrbXwOnOhD9RPw+Jy6IKdzfUjvtXvrep+qkwwrQ==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", + "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz", + "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^2.38.3" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", + "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^2.42.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@types/assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/assert/-/assert-1.5.6.tgz", + "integrity": "sha512-Y7gDJiIqb9qKUHfBQYOWGngUpLORtirAVPuj/CWJrU2C6ZM4/y3XLwuwfGMF8s7QzW746LQZx23m0+1FSgjfug==", + "dev": true + }, + "node_modules/@types/emscripten": { + "version": "1.39.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.5.tgz", + "integrity": "sha512-DIOOg+POSrYl+OlNRHQuIEqCd8DCtynG57H862UCce16nXJX7J8eWxNGgOcf8Eyge8zXeSs27mz1UcFu8L/L7g==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.14.tgz", + "integrity": "sha512-pESyhSbUOskqrGcaN+bCXIQDyT5zTaRWfj5ZjjSlMatgGjIn3QQPfocAu4WSabUR7CGyLZ2CQaZyISOEX7/saw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "dev": true + }, + "node_modules/@types/expect": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-24.3.0.tgz", + "integrity": "sha512-aq5Z+YFBz5o2b6Sp1jigx5nsmoZMK5Ceurjwy6PZmRv7dEi1jLtkARfvB1ME+OXJUG+7TZUDcv3WoCr/aor6dQ==", + "deprecated": "This is a stub types definition. expect provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "expect": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true, + "optional": true + }, + "node_modules/@types/minimist": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", + "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==", + "dev": true + }, + "node_modules/@types/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "node_modules/@types/object-path": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@types/object-path/-/object-path-0.11.1.tgz", + "integrity": "sha512-219LSCO9HPcoXcRTC6DbCs0FRhZgBnEMzf16RRqkT40WbkKx3mOeQuz3e2XqbfhOz/AHfbru0kzB1n1RCAsIIg==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/ua-parser-js": { + "version": "0.7.36", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz", + "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/@wessberg/stringutil": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@wessberg/stringutil/-/stringutil-1.0.19.tgz", + "integrity": "sha512-9AZHVXWlpN8Cn9k5BC/O0Dzb9E9xfEMXzYrNunwvkUTvuK7xgQPVRZpLo+jWCOZ5r8oBa8NIrHuPEu1hzbb6bg==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "dependencies": { + "string-width": "^3.0.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/browserslist-generator": { + "version": "1.0.65", + "resolved": "https://registry.npmjs.org/browserslist-generator/-/browserslist-generator-1.0.65.tgz", + "integrity": "sha512-2+p27BTZ0T6fGAn57IZOoGRTDqIhaHVPHWTg5ZejMA/SKaQG1ChvfOnb9sxqRwLMUGtrUXbX0QQA7H1s6oQh0A==", + "dev": true, + "dependencies": { + "@mdn/browser-compat-data": "^4.0.11", + "@types/object-path": "^0.11.1", + "@types/semver": "^7.3.9", + "@types/ua-parser-js": "^0.7.36", + "browserslist": "4.18.1", + "caniuse-lite": "^1.0.30001282", + "isbot": "3.3.4", + "object-path": "^0.11.8", + "semver": "^7.3.5", + "ua-parser-js": "^1.0.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/wessberg/browserslist-generator?sponsor=1" + } + }, + "node_modules/browserslist-generator/node_modules/browserslist": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz", + "integrity": "sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001280", + "electron-to-chromium": "^1.3.896", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/browserslist-generator/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001301", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001301.tgz", + "integrity": "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 5" + } + }, + "node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/compatfactory": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/compatfactory/-/compatfactory-0.0.12.tgz", + "integrity": "sha512-DD5S1s2mIoVIpYfhCqNZPbOFlt9JDLkXc4d8fAZaeWWIsl7w3bmVS0HNlUkU2SB6iZOdXOjYZgeJZClmL1xnRg==", + "dev": true, + "dependencies": { + "helpertypes": "^0.0.17" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=3.x || >= 4.x" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/convert-source-map/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crosspath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crosspath/-/crosspath-1.0.0.tgz", + "integrity": "sha512-mpjkSErNO6vioL/Cde2aF4UBysPFEMyn+1AN1t7Oc4yqvzSRWe8iBte4P8BHyjo64OmC+ZBxwjIqmpSpIWiQ7Q==", + "dev": true, + "dependencies": { + "@types/node": "^16.11.7" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/crosspath/node_modules/@types/node": { + "version": "16.11.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.21.tgz", + "integrity": "sha512-Pf8M1XD9i1ksZEcCP8vuSNwooJ/bZapNmIzpmsMaL+jMI+8mEYU3PKvs+xDNuQcJWF/x24WzY4qxLtB0zNow9A==", + "dev": true + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/default-require-extensions/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "node_modules/devtools-protocol": { + "version": "0.0.981744", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", + "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", + "dev": true + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dts-bundle-generator": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-6.7.0.tgz", + "integrity": "sha512-Rg46ug107sbdQ9cY3Z54lUHixzrt3cZULInEGq0aYxm36UoKIEoPh1jPt/9qoHqZ1cUFmSOIpVfEbds68lhdJw==", + "dev": true, + "dependencies": { + "typescript": ">=3.0.1", + "yargs": "^17.2.1" + }, + "bin": { + "dts-bundle-generator": "dist/bin/dts-bundle-generator.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/dts-bundle-generator/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dts-bundle-generator/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dts-bundle-generator/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dts-bundle-generator/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dts-bundle-generator/node_modules/yargs": { + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.1.tgz", + "integrity": "sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dts-bundle-generator/node_modules/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.51", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.51.tgz", + "integrity": "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "dev": true, + "dependencies": { + "stackframe": "^1.1.1" + } + }, + "node_modules/es-abstract": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz", + "integrity": "sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-formatter-pretty": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz", + "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==", + "dev": true, + "dependencies": { + "@types/eslint": "^7.2.13", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "eslint-rule-docs": "^1.1.5", + "log-symbols": "^4.0.0", + "plur": "^4.0.0", + "string-width": "^4.2.0", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-rule-docs": { + "version": "1.1.230", + "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.230.tgz", + "integrity": "sha512-dT3rxxc3TmP57RHm9OYTQhT0N4Yu7bjkBW0hvrGRO5sUhB2ron8KPxMDE6pgO44oHvccrsB6TYlCCM5jccdPHw==", + "dev": true + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-glob": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", + "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fastq": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", + "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "dev": true, + "dependencies": { + "ini": "1.3.7" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/helpertypes": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/helpertypes/-/helpertypes-0.0.17.tgz", + "integrity": "sha512-muWKRSBsqN3MzqLdh82QfV7vWWwAYvHh3On87z898X+xZ5H2tPRQ5Y6hHA3BXSE+TueztA07iw5bInjwAT3x8A==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/irregular-plurals": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.3.0.tgz", + "integrity": "sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dev": true, + "dependencies": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "node_modules/isbot": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-3.3.4.tgz", + "integrity": "sha512-a6o/e6nBMoRGvoovg5NT2r/N7S4398yCDXc6HgEOILdBAjYv05SX1MBhgc8SHnEJdRyLfOpAPqc10ezLWkj7rQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "optional": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "dev": true + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/map-obj": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", + "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/meow": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", + "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/meow/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mocha": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.2.tgz", + "integrity": "sha512-FpspiWU+UT9Sixx/wKimvnpkeW0mh6ROAKkIaPokj3xZgxeRhcna/k5X57jJghEr8X+Cgu/Vegf8zCX5ugSuTA==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.2", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.23", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.5", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-path": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", + "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", + "dev": true, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/plur": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", + "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", + "dev": true, + "dependencies": { + "irregular-plurals": "^3.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/puppeteer": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.6.0.tgz", + "integrity": "sha512-EJXhTyY5bXNPLFXPGcY9JaF6EKJIX8ll8cGG3WUK+553Jx96oDf1cB+lkFOro9p0X16tY+9xx7zYWl+vnWgW2g==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.981744", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.5.0" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "dev": true, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.52.7", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.7.tgz", + "integrity": "sha512-55cSH4CCU6MaPr9TAOyrIC+7qFCHscL7tkNsm1MBfIJRRqRbCEY0mmeFn4Wg8FKsHtEH8r389Fz38r/o+kgXLg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/rollup-plugin-ts": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-ts/-/rollup-plugin-ts-2.0.5.tgz", + "integrity": "sha512-yLfu46XsheAEDs+OxCMnHszble9pYwGYDML82lpMw3x/65kgwd9UQSkPX0HZGk+6L+MN8hFgqeh+SPmv+uOz1w==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.1.2", + "@wessberg/stringutil": "^1.0.19", + "browserslist": "^4.19.1", + "browserslist-generator": "^1.0.65", + "chalk": "4.1.2", + "compatfactory": "^0.0.12", + "crosspath": "1.0.0", + "magic-string": "^0.25.7", + "ts-clone-node": "^0.3.30", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=10.0.0", + "npm": ">=7.0.0", + "pnpm": ">=3.2.0", + "yarn": ">=1.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/wessberg/rollup-plugin-ts?sponsor=1" + }, + "peerDependencies": { + "@babel/core": ">=6.x || >=7.x", + "@babel/plugin-transform-runtime": ">=6.x || >=7.x", + "@babel/preset-env": ">=6.x || >=7.x", + "@babel/runtime": ">=6.x || >=7.x", + "@swc/core": ">=1.x", + "@swc/helpers": ">=0.2", + "rollup": ">=1.x || >=2.x", + "typescript": ">=3.2.x || >= 4.x" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@babel/plugin-transform-runtime": { + "optional": true + }, + "@babel/preset-env": { + "optional": true + }, + "@babel/runtime": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-ts/node_modules/@rollup/pluginutils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz", + "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/rollup-plugin-ts/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/rollup-plugin-ts/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "node_modules/shiki": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.0.tgz", + "integrity": "sha512-iczxaIYeBFHTFrQPb9DVy2SKgYxC4Wo7Iucm7C17cCh2Ge/refnvHscUOxM85u57MfLoNOtjoEFUWt9gBexblA==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "5.2.0" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "dev": true + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.1.tgz", + "integrity": "sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-clone-node": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/ts-clone-node/-/ts-clone-node-0.3.30.tgz", + "integrity": "sha512-T9RLibxk0UBHelLUnSIZNyTxlPzcEk+KFFLXBUAG9YFJ3gPOYRrgD/RHnrlAxwEJ9Gq5S/iB8m3tcuTKtgf5Rw==", + "dev": true, + "dependencies": { + "compatfactory": "^0.0.12" + }, + "engines": { + "node": ">=10.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/wessberg/ts-clone-node?sponsor=1" + }, + "peerDependencies": { + "typescript": "^3.x || ^4.x" + } + }, + "node_modules/ts-mocha": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-9.0.2.tgz", + "integrity": "sha512-WyQjvnzwrrubl0JT7EC1yWmNpcsU3fOuBFfdps30zbmFBgKniSaSOyZMZx+Wq7kytUs5CY+pEbSYEbGfIKnXTw==", + "dev": true, + "dependencies": { + "ts-node": "7.0.1" + }, + "bin": { + "ts-mocha": "bin/ts-mocha" + }, + "engines": { + "node": ">= 6.X.X" + }, + "optionalDependencies": { + "tsconfig-paths": "^3.5.0" + }, + "peerDependencies": { + "mocha": "^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X" + } + }, + "node_modules/ts-mocha/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-mocha/node_modules/ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ts-mocha/node_modules/yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "optional": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsd": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.15.1.tgz", + "integrity": "sha512-8ADO2rPntfNiJV4KiqJiiiitfkXLxCbKEFN672JgwNiaEIuiyurTc1+w3InZ+0DqBz73B6Z3UflZcNGw5xMaDA==", + "dev": true, + "dependencies": { + "eslint-formatter-pretty": "^4.0.0", + "globby": "^11.0.1", + "meow": "^7.0.1", + "path-exists": "^4.0.0", + "read-pkg-up": "^7.0.0", + "update-notifier": "^4.1.0" + }, + "bin": { + "tsd": "dist/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typedoc": { + "version": "0.22.11", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.11.tgz", + "integrity": "sha512-pVr3hh6dkS3lPPaZz1fNpvcrqLdtEvXmXayN55czlamSgvEjh+57GUqfhAI1Xsuu/hNHUT1KNSx8LH2wBP/7SA==", + "dev": true, + "dependencies": { + "glob": "^7.2.0", + "lunr": "^2.3.9", + "marked": "^4.0.10", + "minimatch": "^3.0.4", + "shiki": "^0.10.0" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 12.10.0" + }, + "peerDependencies": { + "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x" + } + }, + "node_modules/typedoc/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.2.tgz", + "integrity": "sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "dev": true, + "dependencies": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz", + "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", + "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/workerpool": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.0" + } + }, + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/compat-data": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", + "dev": true + }, + "@babel/core": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz", + "integrity": "sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.9", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.9", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.9", + "@babel/types": "^7.17.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", + "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-function-name": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-module-transforms": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true + }, + "@babel/helpers": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.9", + "@babel/types": "^7.17.0" + } + }, + "@babel/highlight": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", + "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", + "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", + "dev": true + }, + "@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/traverse": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", + "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.9", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.9", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@mdn/browser-compat-data": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.4.tgz", + "integrity": "sha512-GT+WV0p/3Y90zfyv3alYZykqkySbFxq6lJcK1RMnaoPoA6RGrbXwOnOhD9RPw+Jy6IKdzfUjvtXvrep+qkwwrQ==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", + "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@rollup/plugin-commonjs": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz", + "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + } + }, + "@rollup/plugin-node-resolve": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", + "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } + } + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@types/assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/assert/-/assert-1.5.6.tgz", + "integrity": "sha512-Y7gDJiIqb9qKUHfBQYOWGngUpLORtirAVPuj/CWJrU2C6ZM4/y3XLwuwfGMF8s7QzW746LQZx23m0+1FSgjfug==", + "dev": true + }, + "@types/emscripten": { + "version": "1.39.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.5.tgz", + "integrity": "sha512-DIOOg+POSrYl+OlNRHQuIEqCd8DCtynG57H862UCce16nXJX7J8eWxNGgOcf8Eyge8zXeSs27mz1UcFu8L/L7g==", + "dev": true + }, + "@types/eslint": { + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.14.tgz", + "integrity": "sha512-pESyhSbUOskqrGcaN+bCXIQDyT5zTaRWfj5ZjjSlMatgGjIn3QQPfocAu4WSabUR7CGyLZ2CQaZyISOEX7/saw==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/estree": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "dev": true + }, + "@types/expect": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-24.3.0.tgz", + "integrity": "sha512-aq5Z+YFBz5o2b6Sp1jigx5nsmoZMK5Ceurjwy6PZmRv7dEi1jLtkARfvB1ME+OXJUG+7TZUDcv3WoCr/aor6dQ==", + "dev": true, + "requires": { + "expect": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true, + "optional": true + }, + "@types/minimist": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "dev": true + }, + "@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "@types/node": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", + "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/object-path": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@types/object-path/-/object-path-0.11.1.tgz", + "integrity": "sha512-219LSCO9HPcoXcRTC6DbCs0FRhZgBnEMzf16RRqkT40WbkKx3mOeQuz3e2XqbfhOz/AHfbru0kzB1n1RCAsIIg==", + "dev": true + }, + "@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/semver": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/ua-parser-js": { + "version": "0.7.36", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz", + "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", + "dev": true + }, + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "@wessberg/stringutil": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@wessberg/stringutil/-/stringutil-1.0.19.tgz", + "integrity": "sha512-9AZHVXWlpN8Cn9k5BC/O0Dzb9E9xfEMXzYrNunwvkUTvuK7xgQPVRZpLo+jWCOZ5r8oBa8NIrHuPEu1hzbb6bg==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserslist": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + } + }, + "browserslist-generator": { + "version": "1.0.65", + "resolved": "https://registry.npmjs.org/browserslist-generator/-/browserslist-generator-1.0.65.tgz", + "integrity": "sha512-2+p27BTZ0T6fGAn57IZOoGRTDqIhaHVPHWTg5ZejMA/SKaQG1ChvfOnb9sxqRwLMUGtrUXbX0QQA7H1s6oQh0A==", + "dev": true, + "requires": { + "@mdn/browser-compat-data": "^4.0.11", + "@types/object-path": "^0.11.1", + "@types/semver": "^7.3.9", + "@types/ua-parser-js": "^0.7.36", + "browserslist": "4.18.1", + "caniuse-lite": "^1.0.30001282", + "isbot": "3.3.4", + "object-path": "^0.11.8", + "semver": "^7.3.5", + "ua-parser-js": "^1.0.2" + }, + "dependencies": { + "browserslist": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz", + "integrity": "sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001280", + "electron-to-chromium": "^1.3.896", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30001301", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001301.tgz", + "integrity": "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA==", + "dev": true + }, + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compatfactory": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/compatfactory/-/compatfactory-0.0.12.tgz", + "integrity": "sha512-DD5S1s2mIoVIpYfhCqNZPbOFlt9JDLkXc4d8fAZaeWWIsl7w3bmVS0HNlUkU2SB6iZOdXOjYZgeJZClmL1xnRg==", + "dev": true, + "requires": { + "helpertypes": "^0.0.17" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "requires": { + "node-fetch": "2.6.7" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crosspath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crosspath/-/crosspath-1.0.0.tgz", + "integrity": "sha512-mpjkSErNO6vioL/Cde2aF4UBysPFEMyn+1AN1t7Oc4yqvzSRWe8iBte4P8BHyjo64OmC+ZBxwjIqmpSpIWiQ7Q==", + "dev": true, + "requires": { + "@types/node": "^16.11.7" + }, + "dependencies": { + "@types/node": { + "version": "16.11.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.21.tgz", + "integrity": "sha512-Pf8M1XD9i1ksZEcCP8vuSNwooJ/bZapNmIzpmsMaL+jMI+8mEYU3PKvs+xDNuQcJWF/x24WzY4qxLtB0zNow9A==", + "dev": true + } + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + } + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "devtools-protocol": { + "version": "0.0.981744", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", + "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "dts-bundle-generator": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-6.7.0.tgz", + "integrity": "sha512-Rg46ug107sbdQ9cY3Z54lUHixzrt3cZULInEGq0aYxm36UoKIEoPh1jPt/9qoHqZ1cUFmSOIpVfEbds68lhdJw==", + "dev": true, + "requires": { + "typescript": ">=3.0.1", + "yargs": "^17.2.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "yargs": { + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.1.tgz", + "integrity": "sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true + } + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.51", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.51.tgz", + "integrity": "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "dev": true, + "requires": { + "stackframe": "^1.1.1" + } + }, + "es-abstract": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz", + "integrity": "sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-formatter-pretty": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz", + "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==", + "dev": true, + "requires": { + "@types/eslint": "^7.2.13", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "eslint-rule-docs": "^1.1.5", + "log-symbols": "^4.0.0", + "plur": "^4.0.0", + "string-width": "^4.2.0", + "supports-hyperlinks": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "eslint-rule-docs": { + "version": "1.1.230", + "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.230.tgz", + "integrity": "sha512-dT3rxxc3TmP57RHm9OYTQhT0N4Yu7bjkBW0hvrGRO5sUhB2ron8KPxMDE6pgO44oHvccrsB6TYlCCM5jccdPHw==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dev": true, + "requires": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + } + }, + "express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extract-zip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "fast-glob": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", + "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", + "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "dev": true, + "requires": { + "ini": "1.3.7" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true }, - "base-64": { + "has-tostringtag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, - "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -380,1131 +9838,1400 @@ } } }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "helpertypes": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/helpertypes/-/helpertypes-0.0.17.tgz", + "integrity": "sha512-muWKRSBsqN3MzqLdh82QfV7vWWwAYvHh3On87z898X+xZ5H2tPRQ5Y6hHA3BXSE+TueztA07iw5bInjwAT3x8A==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "irregular-plurals": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.3.0.tgz", + "integrity": "sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "has-tostringtag": "^1.0.0" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "is-extglob": "^2.1.1" } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dev": true, + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } + "has-tostringtag": "^1.0.0" } }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } + "@types/estree": "*" } }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, - "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", "dev": true, "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "call-bind": "^1.0.2" } }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } + "has-tostringtag": "^1.0.0" } }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "requires": { - "mimic-response": "^1.0.0" + "has-symbols": "^1.0.2" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "call-bind": "^1.0.2" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "isbot": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-3.3.4.tgz", + "integrity": "sha512-a6o/e6nBMoRGvoovg5NT2r/N7S4398yCDXc6HgEOILdBAjYv05SX1MBhgc8SHnEJdRyLfOpAPqc10ezLWkj7rQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" + "append-transform": "^2.0.0" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { - "ms": "2.1.2" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" }, "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + } }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "dependencies": { - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "requires": { - "mimic-response": "^1.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", "dev": true, "requires": { - "path-type": "^4.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", "dev": true, "requires": { - "is-obj": "^2.0.0" + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", "dev": true }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", "dev": true, "requires": { - "once": "^1.4.0" + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" } }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" } }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-formatter-pretty": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz", - "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==", + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", "dev": true, "requires": { - "@types/eslint": "^7.2.13", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "eslint-rule-docs": "^1.1.5", - "log-symbols": "^4.0.0", - "plur": "^4.0.0", - "string-width": "^4.2.0", - "supports-hyperlinks": "^2.0.0" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "has-flag": "^4.0.0" } } } }, - "eslint-rule-docs": { - "version": "1.1.230", - "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.230.tgz", - "integrity": "sha512-dT3rxxc3TmP57RHm9OYTQhT0N4Yu7bjkBW0hvrGRO5sUhB2ron8KPxMDE6pgO44oHvccrsB6TYlCCM5jccdPHw==", + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "fast-glob": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", - "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fastq": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", - "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "reusify": "^1.0.4" + "argparse": "^2.0.1" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "optional": true, + "requires": { + "minimist": "^1.2.0" + } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", "dev": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", "dev": true, "requires": { - "pump": "^3.0.0" + "json-buffer": "3.0.0" } }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "package-json": "^6.3.0" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "is-glob": "^4.0.1" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } } }, - "global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "ini": "1.3.7" + "p-locate": "^5.0.0" } }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", "dev": true, "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "get-func-name": "^2.0.0" } }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "yallist": "^4.0.0" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "dev": true }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "map-obj": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", "dev": true }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "marked": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", + "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==", "dev": true }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", "dev": true }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "meow": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", + "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, - "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, - "irregular-plurals": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.3.0.tgz", - "integrity": "sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==", + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "requires": { - "has": "^1.0.3" + "braces": "^3.0.1", + "picomatch": "^2.2.3" } }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" + "mime-db": "1.52.0" } }, - "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" }, "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true } } }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "mocha": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.2.tgz", + "integrity": "sha512-FpspiWU+UT9Sixx/wKimvnpkeW0mh6ROAKkIaPokj3xZgxeRhcna/k5X57jJghEr8X+Cgu/Vegf8zCX5ugSuTA==", "dev": true, "requires": { - "argparse": "^2.0.1" + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.2", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.23", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.5", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" } }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", "dev": true }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "requires": { - "package-json": "^6.3.0" + "process-on-spawn": "^1.0.0" } }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "node-releases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "p-locate": "^5.0.0" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "dev": true, "requires": { - "semver": "^6.0.0" + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, - "map-obj": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", - "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", - "dev": true - }, - "meow": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", - "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", "dev": true, "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" }, "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "p-locate": "^4.1.0" } - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "object-path": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", + "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", "dev": true }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "dependencies": { - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - } - } - }, - "mocha": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.2.tgz", - "integrity": "sha512-FpspiWU+UT9Sixx/wKimvnpkeW0mh6ROAKkIaPokj3xZgxeRhcna/k5X57jJghEr8X+Cgu/Vegf8zCX5ugSuTA==", + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.7", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.23", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.5", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", - "dev": true - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "ee-first": "1.1.1" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1538,12 +11265,33 @@ "p-limit": "^3.0.2" } }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, "package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -1576,6 +11324,12 @@ "lines-and-columns": "^1.1.6" } }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1588,24 +11342,114 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, "plur": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", @@ -1627,6 +11471,62 @@ "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", "dev": true }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1646,6 +11546,49 @@ "escape-goat": "^2.0.0" } }, + "puppeteer": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.6.0.tgz", + "integrity": "sha512-EJXhTyY5bXNPLFXPGcY9JaF6EKJIX8ll8cGG3WUK+553Jx96oDf1cB+lkFOro9p0X16tY+9xx7zYWl+vnWgW2g==", + "dev": true, + "requires": { + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.981744", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.5.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1667,6 +11610,24 @@ "safe-buffer": "^5.1.0" } }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1687,6 +11648,12 @@ } } }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -1763,6 +11730,17 @@ } } }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1797,7 +11775,16 @@ "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", "dev": true, "requires": { - "rc": "^1.2.8" + "rc": "^1.2.8" + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" } }, "require-directory": { @@ -1806,6 +11793,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -1816,6 +11809,12 @@ "path-parse": "^1.0.6" } }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, "responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", @@ -1831,6 +11830,15 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "rollup": { "version": "2.52.7", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.7.tgz", @@ -1863,6 +11871,55 @@ } } }, + "rollup-plugin-ts": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-ts/-/rollup-plugin-ts-2.0.5.tgz", + "integrity": "sha512-yLfu46XsheAEDs+OxCMnHszble9pYwGYDML82lpMw3x/65kgwd9UQSkPX0HZGk+6L+MN8hFgqeh+SPmv+uOz1w==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^4.1.2", + "@wessberg/stringutil": "^1.0.19", + "browserslist": "^4.19.1", + "browserslist-generator": "^1.0.65", + "chalk": "4.1.2", + "compatfactory": "^0.0.12", + "crosspath": "1.0.0", + "magic-string": "^0.25.7", + "ts-clone-node": "^0.3.30", + "tslib": "^2.3.1" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz", + "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==", + "dev": true, + "requires": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -1878,6 +11935,12 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -1901,6 +11964,46 @@ } } }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + } + } + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -1910,6 +12013,73 @@ "randombytes": "^2.1.0" } }, + "serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "shiki": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.0.tgz", + "integrity": "sha512-iczxaIYeBFHTFrQPb9DVy2SKgYxC4Wo7Iucm7C17cCh2Ge/refnvHscUOxM85u57MfLoNOtjoEFUWt9gBexblA==", + "dev": true, + "requires": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "5.2.0" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -1929,9 +12099,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -1946,6 +12116,26 @@ } } }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + } + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -1978,6 +12168,50 @@ "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", "dev": true }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1988,6 +12222,37 @@ "strip-ansi": "^4.0.0" } }, + "string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -1997,6 +12262,12 @@ "ansi-regex": "^3.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -2042,6 +12313,31 @@ } } }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, "term-size": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", @@ -2059,6 +12355,29 @@ "source-map-support": "~0.5.19" } }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -2074,12 +12393,85 @@ "is-number": "^7.0.0" } }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "ts-clone-node": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/ts-clone-node/-/ts-clone-node-0.3.30.tgz", + "integrity": "sha512-T9RLibxk0UBHelLUnSIZNyTxlPzcEk+KFFLXBUAG9YFJ3gPOYRrgD/RHnrlAxwEJ9Gq5S/iB8m3tcuTKtgf5Rw==", + "dev": true, + "requires": { + "compatfactory": "^0.0.12" + } + }, + "ts-mocha": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-9.0.2.tgz", + "integrity": "sha512-WyQjvnzwrrubl0JT7EC1yWmNpcsU3fOuBFfdps30zbmFBgKniSaSOyZMZx+Wq7kytUs5CY+pEbSYEbGfIKnXTw==", + "dev": true, + "requires": { + "ts-node": "7.0.1", + "tsconfig-paths": "^3.5.0" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + } + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + } + } + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "optional": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "tsd": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.15.1.tgz", @@ -2094,12 +12486,34 @@ "update-notifier": "^4.1.0" } }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -2109,12 +12523,69 @@ "is-typedarray": "^1.0.0" } }, + "typedoc": { + "version": "0.22.11", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.11.tgz", + "integrity": "sha512-pVr3hh6dkS3lPPaZz1fNpvcrqLdtEvXmXayN55czlamSgvEjh+57GUqfhAI1Xsuu/hNHUT1KNSx8LH2wBP/7SA==", + "dev": true, + "requires": { + "glob": "^7.2.0", + "lunr": "^2.3.9", + "marked": "^4.0.10", + "minimatch": "^3.0.4", + "shiki": "^0.10.0" + }, + "dependencies": { + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, + "ua-parser-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.2.tgz", + "integrity": "sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -2124,6 +12595,12 @@ "crypto-random-string": "^2.0.0" } }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, "update-notifier": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", @@ -2175,6 +12652,24 @@ "prepend-http": "^2.0.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -2185,6 +12680,38 @@ "spdx-expression-parse": "^3.0.0" } }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "vscode-oniguruma": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz", + "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==", + "dev": true + }, + "vscode-textmate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", + "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2194,6 +12721,25 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -2213,9 +12759,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -2264,9 +12810,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -2315,6 +12861,12 @@ "typedarray-to-buffer": "^3.1.5" } }, + "ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "requires": {} + }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", @@ -2327,6 +12879,12 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -2343,9 +12901,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -2394,6 +12952,16 @@ "is-plain-obj": "^2.1.0" } }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/src/js/package.json b/src/js/package.json index dd12de56427..14f1ee6bdc6 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -1,6 +1,6 @@ { "name": "pyodide", - "version": "0.19.0", + "version": "0.21.0-dev.0", "description": "The Pyodide JavaScript package", "keywords": [ "python", @@ -16,33 +16,112 @@ }, "license": "Apache-2.0", "devDependencies": { + "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-node-resolve": "^13.1.3", + "@types/assert": "^1.5.6", + "@types/emscripten": "^1.39.5", + "@types/expect": "^24.3.0", + "@types/mocha": "^9.1.0", + "@types/node": "^17.0.25", + "@types/node-fetch": "^2.6.1", + "@types/ws": "^8.5.3", + "chai": "^4.3.6", + "chai-as-promised": "^7.1.1", + "cross-env": "^7.0.3", + "dts-bundle-generator": "^6.7.0", + "error-stack-parser": "^2.0.6", + "express": "^4.17.3", + "mocha": "^9.0.2", + "npm-run-all": "^4.1.5", + "nyc": "^15.1.0", "prettier": "^2.2.1", - "terser": "^5.7.0", + "puppeteer": "^13.6.0", "rollup": "^2.48.0", "rollup-plugin-terser": "^7.0.2", + "rollup-plugin-ts": "^2.0.5", + "terser": "^5.7.0", + "ts-mocha": "^9.0.2", "tsd": "^0.15.1", - "typescript": "^4.2.4", - "mocha": "^9.0.2", - "@types/emscripten": "^1.39.5" + "typedoc": "^0.22.11", + "typescript": "^4.2.4" + }, + "main": "pyodide.js", + "exports": { + ".": { + "require": "./pyodide.js", + "import": "./pyodide.mjs" + }, + "./pyodide.mjs": "./pyodide.mjs", + "./pyodide.js": "./pyodide.js", + "./package.json": "./package.json" + }, + "files": [ + "pyodide*", + "packages.json", + "distutils.tar", + "console.html" + ], + "browser": { + "child_process": false, + "crypto": false, + "fs": false, + "fs/promises": false, + "path": false, + "vm": false, + "ws": false }, - "type": "module", "scripts": { - "test": "mocha" + "test": "npm-run-all test:*", + "test:unit": "cross-env TEST_NODE=1 ts-mocha -p tsconfig.test.json test/unit/**/*.test.ts", + "test:node": "cross-env TEST_NODE=1 mocha test/integration/**/*.test.js", + "test:browser": "mocha test/integration/**/*.test.js", + "coverage": "cross-env TEST_NODE=1 npm-run-all coverage:*", + "coverage:build": "nyc npm run test:node" }, "mocha": { + "bail": false, "timeout": 30000, - "file": "./test/conftest.js" + "full-trace": true, + "inline-diffs": true, + "check-leaks": false, + "global": [ + "pyodide", + "page", + "chai" + ], + "file": [ + "test/conftest.js" + ] + }, + "nyc": { + "reporter": [ + "html", + "text-summary" + ], + "include": [ + "*.ts" + ], + "all": true, + "clean": true, + "cache": false, + "instrument": false, + "checkCoverage": true, + "statements": 95, + "functions": 95, + "branches": 80, + "lines": 95 }, "tsd": { "compilerOptions": { "lib": [ - "ES2018", + "ES2017", "DOM" ] } }, "dependencies": { "base-64": "^1.0.0", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "ws": "^8.5.0" } } diff --git a/src/js/pyodide.js b/src/js/pyodide.js deleted file mode 100644 index 4761f243c72..00000000000 --- a/src/js/pyodide.js +++ /dev/null @@ -1,302 +0,0 @@ -/** - * The main bootstrap code for loading pyodide. - */ -import { Module, setStandardStreams, setHomeDirectory } from "./module.js"; -import { - loadScript, - initializePackageIndex, - _fetchBinaryFile, - loadPackage, -} from "./load-pyodide.js"; -import { makePublicAPI, registerJsModule } from "./api.js"; -import "./pyproxy.gen.js"; - -/** - * @typedef {import('./pyproxy.gen').PyProxy} PyProxy - * @typedef {import('./pyproxy.gen').PyProxyWithLength} PyProxyWithLength - * @typedef {import('./pyproxy.gen').PyProxyWithGet} PyProxyWithGet - * @typedef {import('./pyproxy.gen').PyProxyWithSet} PyProxyWithSet - * @typedef {import('./pyproxy.gen').PyProxyWithHas} PyProxyWithHas - * @typedef {import('./pyproxy.gen').PyProxyIterable} PyProxyIterable - * @typedef {import('./pyproxy.gen').PyProxyIterator} PyProxyIterator - * @typedef {import('./pyproxy.gen').PyProxyAwaitable} PyProxyAwaitable - * @typedef {import('./pyproxy.gen').PyProxyBuffer} PyProxyBuffer - * @typedef {import('./pyproxy.gen').PyProxyCallable} PyProxyCallable - * - * @typedef {import('./pyproxy.gen').Py2JsResult} Py2JsResult - * - * @typedef {import('./pyproxy.gen').TypedArray} TypedArray - * @typedef {import('./pyproxy.gen').PyBuffer} PyBuffer - */ - -/** - * Dump the Python traceback to the browser console. - * - * @private - */ -Module.dump_traceback = function () { - const fd_stdout = 1; - Module.__Py_DumpTraceback(fd_stdout, Module._PyGILState_GetThisThreadState()); -}; - -let fatal_error_occurred = false; -/** - * Signal a fatal error. - * - * Dumps the Python traceback, shows a JavaScript traceback, and prints a clear - * message indicating a fatal error. It then dummies out the public API so that - * further attempts to use Pyodide will clearly indicate that Pyodide has failed - * and can no longer be used. pyodide._module is left accessible, and it is - * possible to continue using Pyodide for debugging purposes if desired. - * - * @argument e {Error} The cause of the fatal error. - * @private - */ -Module.fatal_error = function (e) { - if (e.pyodide_fatal_error) { - return; - } - if (fatal_error_occurred) { - console.error("Recursive call to fatal_error. Inner error was:"); - console.error(e); - return; - } - // Mark e so we know not to handle it later in EM_JS wrappers - e.pyodide_fatal_error = true; - fatal_error_occurred = true; - console.error( - "Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers." - ); - console.error("The cause of the fatal error was:"); - if (Module.inTestHoist) { - // Test hoist won't print the error object in a useful way so convert it to - // string. - console.error(e.toString()); - console.error(e.stack); - } else { - console.error(e); - } - try { - Module.dump_traceback(); - for (let key of Object.keys(Module.public_api)) { - if (key.startsWith("_") || key === "version") { - continue; - } - Object.defineProperty(Module.public_api, key, { - enumerable: true, - configurable: true, - get: () => { - throw new Error( - "Pyodide already fatally failed and can no longer be used." - ); - }, - }); - } - if (Module.on_fatal) { - Module.on_fatal(e); - } - } catch (err2) { - console.error("Another error occurred while handling the fatal error:"); - console.error(err2); - } - throw e; -}; - -let runPythonInternal_dict; // Initialized in finalizeBootstrap -/** - * Just like `runPython` except uses a different globals dict and gets - * `eval_code` from `_pyodide` so that it can work before `pyodide` is imported. - * @private - */ -Module.runPythonInternal = function (code) { - return Module._pyodide._base.eval_code(code, runPythonInternal_dict); -}; - -/** - * A proxy around globals that falls back to checking for a builtin if has or - * get fails to find a global with the given key. Note that this proxy is - * transparent to js2python: it won't notice that this wrapper exists at all and - * will translate this proxy to the globals dictionary. - * @private - */ -function wrapPythonGlobals(globals_dict, builtins_dict) { - return new Proxy(globals_dict, { - get(target, symbol) { - if (symbol === "get") { - return (key) => { - let result = target.get(key); - if (result === undefined) { - result = builtins_dict.get(key); - } - return result; - }; - } - if (symbol === "has") { - return (key) => target.has(key) || builtins_dict.has(key); - } - return Reflect.get(target, symbol); - }, - }); -} - -function unpackPyodidePy(pyodide_py_tar) { - const fileName = "/pyodide_py.tar"; - let stream = Module.FS.open(fileName, "w"); - Module.FS.write( - stream, - new Uint8Array(pyodide_py_tar), - 0, - pyodide_py_tar.byteLength, - undefined, - true - ); - Module.FS.close(stream); - const code_ptr = Module.stringToNewUTF8(` -import shutil -shutil.unpack_archive("/pyodide_py.tar", "/lib/python3.9/site-packages/") -del shutil -import importlib -importlib.invalidate_caches() -del importlib - `); - let errcode = Module._PyRun_SimpleString(code_ptr); - if (errcode) { - throw new Error("OOPS!"); - } - Module._free(code_ptr); - Module.FS.unlink(fileName); -} - -/** - * This function is called after the emscripten module is finished initializing, - * so eval_code is newly available. - * It finishes the bootstrap so that once it is complete, it is possible to use - * the core `pyodide` apis. (But package loading is not ready quite yet.) - * @private - */ -function finalizeBootstrap(config) { - // First make internal dict so that we can use runPythonInternal. - // runPythonInternal uses a separate namespace, so we don't pollute the main - // environment with variables from our setup. - runPythonInternal_dict = Module._pyodide._base.eval_code("{}"); - Module.importlib = Module.runPythonInternal("import importlib; importlib"); - let import_module = Module.importlib.import_module; - - Module.sys = import_module("sys"); - Module.sys.path.insert(0, config.homedir); - - // Set up globals - let globals = Module.runPythonInternal("import __main__; __main__.__dict__"); - let builtins = Module.runPythonInternal("import builtins; builtins.__dict__"); - Module.globals = wrapPythonGlobals(globals, builtins); - - // Set up key Javascript modules. - let importhook = Module._pyodide._importhook; - importhook.register_js_finder(); - importhook.register_js_module("js", config.jsglobals); - - let pyodide = makePublicAPI(); - importhook.register_js_module("pyodide_js", pyodide); - - // import pyodide_py. We want to ensure that as much stuff as possible is - // already set up before importing pyodide_py to simplify development of - // pyodide_py code (Otherwise it's very hard to keep track of which things - // aren't set up yet.) - Module.pyodide_py = import_module("pyodide"); - Module.version = Module.pyodide_py.__version__; - - // copy some last constants onto public API. - pyodide.pyodide_py = Module.pyodide_py; - pyodide.version = Module.version; - pyodide.globals = Module.globals; - return pyodide; -} - -/** - * Load the main Pyodide wasm module and initialize it. - * - * Only one copy of Pyodide can be loaded in a given JavaScript global scope - * because Pyodide uses global variables to load packages. If an attempt is made - * to load a second copy of Pyodide, :any:`loadPyodide` will throw an error. - * (This can be fixed once `Firefox adopts support for ES6 modules in webworkers - * `_.) - * - * @param {string} config.indexURL - The URL from which Pyodide will load - * packages - * @param {string} config.homedir - The home directory which Pyodide will use inside virtual file system - * Default: /home/pyodide - * @param {boolean} config.fullStdLib - Load the full Python standard library. - * Setting this to false excludes following modules: distutils. - * Default: true - * @param {undefined | function(): string} config.stdin - Override the standard input callback. Should ask the user for one line of input. - * Default: undefined - * @param {undefined | function(string)} config.stdout - Override the standard output callback. - * Default: undefined - * @param {undefined | function(string)} config.stderr - Override the standard error output callback. - * Default: undefined - * @returns The :ref:`js-api-pyodide` module. - * @memberof globalThis - * @async - */ -export async function loadPyodide(config) { - if (globalThis.__pyodide_module) { - throw new Error("Pyodide is already loading."); - } - if (!config.indexURL) { - throw new Error("Please provide indexURL parameter to loadPyodide"); - } - - loadPyodide.inProgress = true; - // A global "mount point" for the package loaders to talk to pyodide - // See "--export-name=__pyodide_module" in buildpkg.py - globalThis.__pyodide_module = Module; - - const default_config = { - fullStdLib: true, - jsglobals: globalThis, - stdin: globalThis.prompt ? globalThis.prompt : undefined, - homedir: "/home/pyodide", - }; - config = Object.assign(default_config, config); - - if (!config.indexURL.endsWith("/")) { - config.indexURL += "/"; - } - Module.indexURL = config.indexURL; - let packageIndexReady = initializePackageIndex(config.indexURL); - let pyodide_py_tar_promise = _fetchBinaryFile( - config.indexURL, - "pyodide_py.tar" - ); - - setStandardStreams(config.stdin, config.stdout, config.stderr); - setHomeDirectory(config.homedir); - - let moduleLoaded = new Promise((r) => (Module.postRun = r)); - - const scriptSrc = `${config.indexURL}pyodide.asm.js`; - await loadScript(scriptSrc); - - // _createPyodideModule is specified in the Makefile by the linker flag: - // `-s EXPORT_NAME="'_createPyodideModule'"` - await _createPyodideModule(Module); - - // There is some work to be done between the module being "ready" and postRun - // being called. - await moduleLoaded; - - const pyodide_py_tar = await pyodide_py_tar_promise; - unpackPyodidePy(pyodide_py_tar); - Module._pyodide_init(); - - let pyodide = finalizeBootstrap(config); - // Module.runPython works starting here. - - await packageIndexReady; - if (config.fullStdLib) { - await loadPackage(["distutils"]); - } - pyodide.runPython("print('Python initialization complete')"); - return pyodide; -} -globalThis.loadPyodide = loadPyodide; diff --git a/src/js/pyodide.ts b/src/js/pyodide.ts new file mode 100644 index 00000000000..1b8e349a959 --- /dev/null +++ b/src/js/pyodide.ts @@ -0,0 +1,302 @@ +/** + * The main bootstrap code for loading pyodide. + */ +import ErrorStackParser from "error-stack-parser"; +import { loadScript, _loadBinaryFile, initNodeModules } from "./compat"; + +import { createModule, setStandardStreams, setHomeDirectory } from "./module"; + +import type { PyodideInterface } from "./api.js"; +import type { PyProxy, PyProxyDict } from "./pyproxy.gen"; +export type { PyodideInterface }; + +export type { + PyProxy, + PyProxyWithLength, + PyProxyDict, + PyProxyWithGet, + PyProxyWithSet, + PyProxyWithHas, + PyProxyIterable, + PyProxyIterator, + PyProxyAwaitable, + PyProxyBuffer, + PyProxyCallable, + TypedArray, + PyBuffer, +} from "./pyproxy.gen"; + +export type Py2JsResult = any; + +/** + * A proxy around globals that falls back to checking for a builtin if has or + * get fails to find a global with the given key. Note that this proxy is + * transparent to js2python: it won't notice that this wrapper exists at all and + * will translate this proxy to the globals dictionary. + * @private + */ +function wrapPythonGlobals( + globals_dict: PyProxyDict, + builtins_dict: PyProxyDict +) { + return new Proxy(globals_dict, { + get(target, symbol) { + if (symbol === "get") { + return (key: any) => { + let result = target.get(key); + if (result === undefined) { + result = builtins_dict.get(key); + } + return result; + }; + } + if (symbol === "has") { + return (key: any) => target.has(key) || builtins_dict.has(key); + } + return Reflect.get(target, symbol); + }, + }); +} + +function unpackPyodidePy(Module: any, pyodide_py_tar: Uint8Array) { + const fileName = "/pyodide_py.tar"; + let stream = Module.FS.open(fileName, "w"); + Module.FS.write( + stream, + pyodide_py_tar, + 0, + pyodide_py_tar.byteLength, + undefined, + true + ); + Module.FS.close(stream); + const code_ptr = Module.stringToNewUTF8(` +from sys import version_info +pyversion = f"python{version_info.major}.{version_info.minor}" +import shutil +shutil.unpack_archive("/pyodide_py.tar", f"/lib/{pyversion}/site-packages/") +del shutil +import importlib +importlib.invalidate_caches() +del importlib + `); + let errcode = Module._PyRun_SimpleString(code_ptr); + if (errcode) { + throw new Error("OOPS!"); + } + Module._free(code_ptr); + Module.FS.unlink(fileName); +} + +/** + * This function is called after the emscripten module is finished initializing, + * so eval_code is newly available. + * It finishes the bootstrap so that once it is complete, it is possible to use + * the core `pyodide` apis. (But package loading is not ready quite yet.) + * @private + */ +function finalizeBootstrap(API: any, config: ConfigType) { + // First make internal dict so that we can use runPythonInternal. + // runPythonInternal uses a separate namespace, so we don't pollute the main + // environment with variables from our setup. + API.runPythonInternal_dict = API._pyodide._base.eval_code("{}") as PyProxy; + API.importlib = API.runPythonInternal("import importlib; importlib"); + let import_module = API.importlib.import_module; + + API.sys = import_module("sys"); + API.sys.path.insert(0, config.homedir); + + // Set up globals + let globals = API.runPythonInternal( + "import __main__; __main__.__dict__" + ) as PyProxyDict; + let builtins = API.runPythonInternal( + "import builtins; builtins.__dict__" + ) as PyProxyDict; + API.globals = wrapPythonGlobals(globals, builtins); + + // Set up key Javascript modules. + let importhook = API._pyodide._importhook; + importhook.register_js_finder(); + importhook.register_js_module("js", config.jsglobals); + + let pyodide = API.makePublicAPI(); + importhook.register_js_module("pyodide_js", pyodide); + + // import pyodide_py. We want to ensure that as much stuff as possible is + // already set up before importing pyodide_py to simplify development of + // pyodide_py code (Otherwise it's very hard to keep track of which things + // aren't set up yet.) + API.pyodide_py = import_module("pyodide"); + API.package_loader = import_module("pyodide._package_loader"); + API.version = API.pyodide_py.__version__; + + // copy some last constants onto public API. + pyodide.pyodide_py = API.pyodide_py; + pyodide.version = API.version; + pyodide.globals = API.globals; + return pyodide; +} + +declare function _createPyodideModule(Module: any): Promise; + +/** + * If indexURL isn't provided, throw an error and catch it and then parse our + * file name out from the stack trace. + * + * Question: But getting the URL from error stack trace is well... really + * hacky. Can't we use + * [`document.currentScript`](https://developer.mozilla.org/en-US/docs/Web/API/Document/currentScript) + * or + * [`import.meta.url`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import.meta) + * instead? + * + * Answer: `document.currentScript` works for the browser main thread. + * `import.meta` works for es6 modules. In a classic webworker, I think there + * is no approach that works. Also we would need some third approach for node + * when loading a commonjs module using `require`. On the other hand, this + * stack trace approach works for every case without any feature detection + * code. + */ +function calculateIndexURL(): string { + let err; + try { + throw new Error(); + } catch (e) { + err = e; + } + let fileName = ErrorStackParser.parse(err)[0].fileName!; + return fileName.slice(0, fileName.lastIndexOf("/")); +} + +/** + * See documentation for loadPyodide. + * @private + */ +export type ConfigType = { + indexURL: string; + homedir: string; + fullStdLib?: boolean; + stdin?: () => string; + stdout?: (msg: string) => void; + stderr?: (msg: string) => void; + jsglobals?: object; +}; + +/** + * Load the main Pyodide wasm module and initialize it. + * + * Only one copy of Pyodide can be loaded in a given JavaScript global scope + * because Pyodide uses global variables to load packages. If an attempt is made + * to load a second copy of Pyodide, :any:`loadPyodide` will throw an error. + * (This can be fixed once `Firefox adopts support for ES6 modules in webworkers + * `_.) + * + * @returns The :ref:`js-api-pyodide` module. + * @memberof globalThis + * @async + */ +export async function loadPyodide( + options: { + /** + * The URL from which Pyodide will load the main Pyodide runtime and + * packages. Defaults to the url that pyodide is loaded from with the file + * name (pyodide.js or pyodide.mjs) removed. It is recommended that you + * leave this undefined, providing an incorrect value can cause broken + * behavior. + */ + indexURL?: string; + + /** + * The home directory which Pyodide will use inside virtual file system. Default: "/home/pyodide" + */ + homedir?: string; + + /** Load the full Python standard library. + * Setting this to false excludes following modules: distutils. + * Default: true + */ + fullStdLib?: boolean; + /** + * Override the standard input callback. Should ask the user for one line of input. + */ + stdin?: () => string; + /** + * Override the standard output callback. + * Default: undefined + */ + stdout?: (msg: string) => void; + /** + * Override the standard error output callback. + * Default: undefined + */ + stderr?: (msg: string) => void; + jsglobals?: object; + } = {} +): Promise { + if (!options.indexURL) { + options.indexURL = calculateIndexURL(); + } + + const default_config = { + fullStdLib: true, + jsglobals: globalThis, + stdin: globalThis.prompt ? globalThis.prompt : undefined, + homedir: "/home/pyodide", + }; + const config = Object.assign(default_config, options) as ConfigType; + if (!config.indexURL.endsWith("/")) { + config.indexURL += "/"; + } + await initNodeModules(); + const pyodide_py_tar_promise = _loadBinaryFile( + config.indexURL, + "pyodide_py.tar" + ); + + const Module = createModule(); + const API: any = { config }; + Module.API = API; + + setStandardStreams(Module, config.stdin, config.stdout, config.stderr); + setHomeDirectory(Module, config.homedir); + + const moduleLoaded = new Promise((r) => (Module.postRun = r)); + + // locateFile tells Emscripten where to find the data files that initialize + // the file system. + Module.locateFile = (path: string) => config.indexURL + path; + const scriptSrc = `${config.indexURL}pyodide.asm.js`; + await loadScript(scriptSrc); + + // _createPyodideModule is specified in the Makefile by the linker flag: + // `-s EXPORT_NAME="'_createPyodideModule'"` + await _createPyodideModule(Module); + + // There is some work to be done between the module being "ready" and postRun + // being called. + await moduleLoaded; + + // Disable further loading of Emscripten file_packager stuff. + Module.locateFile = (path: string) => { + throw new Error("Didn't expect to load any more file_packager files!"); + }; + + const pyodide_py_tar = await pyodide_py_tar_promise; + unpackPyodidePy(Module, pyodide_py_tar); + Module._pyodide_init(); + + const pyodide = finalizeBootstrap(API, config); + // API.runPython works starting here. + if (!pyodide.version.includes("dev")) { + // Currently only used in Node to download packages the first time they are + // loaded. But in other cases it's harmless. + API.setCdnUrl(`https://cdn.jsdelivr.net/pyodide/v${pyodide.version}/full/`); + } + await API.packageIndexReady; + if (config.fullStdLib) { + await pyodide.loadPackage(["distutils"]); + } + pyodide.runPython("print('Python initialization complete')"); + return pyodide; +} diff --git a/src/js/pyodide.umd.ts b/src/js/pyodide.umd.ts new file mode 100644 index 00000000000..a7bfb8040e1 --- /dev/null +++ b/src/js/pyodide.umd.ts @@ -0,0 +1,3 @@ +import { loadPyodide } from "./pyodide"; +export { loadPyodide }; +(globalThis as any).loadPyodide = loadPyodide; diff --git a/src/js/rollup.config.js b/src/js/rollup.config.js index 04f3bef5303..98bd6731c91 100644 --- a/src/js/rollup.config.js +++ b/src/js/rollup.config.js @@ -1,18 +1,36 @@ +import commonjs from "@rollup/plugin-commonjs"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; import { terser } from "rollup-plugin-terser"; +import ts from "rollup-plugin-ts"; -function config({ input, format, minify, ext = "js" }) { - const dir = `build/`; - // const minifierSuffix = minify ? ".min" : ""; - const minifierSuffix = ""; +function config({ input, output, name, format, minify }) { return { - input: `./src/js/${input}.js`, + input: `./src/js/${input}.ts`, output: { - name: "loadPyodide", - file: `${dir}/${input}${minifierSuffix}.${ext}`, + file: output, + name, format, sourcemap: true, }, + external: [ + "path", + "fs/promises", + "node-fetch", + "vm", + "fs", + "crypto", + "ws", + "child_process", + ], plugins: [ + commonjs(), + ts({ + tsconfig: "src/js/tsconfig.json", + }), + // The nodeResolve plugin allows us to import packages from node_modules. + // We need to include node-only packages in `external` to ensure they + // aren't bundled for use in browser. + nodeResolve(), minify ? terser({ compress: true, @@ -24,8 +42,23 @@ function config({ input, format, minify, ext = "js" }) { } export default [ - // { input: "pyodide", format: "esm", minify: false, ext: "mjs" }, - { input: "pyodide", format: "esm", minify: true, ext: "mjs" }, - // { input: "pyodide", format: "umd", minify: false }, - { input: "pyodide", format: "umd", minify: true }, + { + input: "pyodide", + output: "dist/pyodide.mjs", + format: "esm", + minify: true, + }, + { + input: "pyodide.umd", + output: "dist/pyodide.js", + format: "umd", + name: "loadPyodide", + minify: true, + }, + { + input: "api", + output: "src/js/_pyodide.out.js", + format: "iife", + minify: true, + }, ].map(config); diff --git a/src/js/test/conftest.js b/src/js/test/conftest.js index 7d01a9e1901..e52e7b85b48 100644 --- a/src/js/test/conftest.js +++ b/src/js/test/conftest.js @@ -1,7 +1,58 @@ -import { loadPyodide } from "../pyodide.js"; -import path from "path"; -globalThis.path = path; +const path = require("path"); +const puppeteer = require("puppeteer"); +const express = require("express"); +const chai = require("chai"); + +const __ROOT = path.resolve(__dirname, "../../../", "dist"); + +const { loadPyodide } = require(path.resolve(__ROOT, "pyodide")); + +// chai.use(require("deep-equal-in-any-order")); +chai.use(require("chai-as-promised")); +const app = express(); +app.use(express.static(__ROOT)); + +const NODE = process.env.TEST_NODE; +const BROWSER = !NODE; + +let browser = null; +let server = null; +let hostUrl = ""; before(async () => { - globalThis.pyodide = await loadPyodide({ indexURL: "../../build/" }); + globalThis.path = path; + globalThis.chai = chai; + + if (BROWSER) { + browser = await puppeteer.launch(/*{ headless: false, devtools: true }*/); + const page = await browser.newPage(); + await new Promise((resolve) => { + server = app.listen(() => resolve("")); + hostUrl = `http://localhost:${server.address().port}/`; + }); + await page.goto(`${hostUrl}test.html`); + globalThis.page = page; + } else { + globalThis.page = { + title: () => Promise.resolve("pyodide"), + goto: () => Promise.resolve(), + evaluate: (fn, ...args) => fn(...args), + }; + } + await page.evaluate( + async (indexURL) => { + globalThis.pyodide = await loadPyodide({ + indexURL, + }); + return globalThis.pyodide; + }, + BROWSER ? undefined : __ROOT + ); +}); + +after(async function () { + if (BROWSER) { + await browser.close(); + server.close(); + } }); diff --git a/src/js/test/filesystem.test.js b/src/js/test/filesystem.test.js deleted file mode 100644 index a3c6d38ec00..00000000000 --- a/src/js/test/filesystem.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import assert from "assert"; - -// for a persistence-related browser test see /src/tests/test_filesystem.py - -describe("FS", () => { - const exists = () => { - return pyodide.runPython("import os; os.path.exists('/tmp/js-test')"); - }; - it("no dir", async () => assert.strictEqual(exists(), false)); - it("mkdir", async () => pyodide.FS.mkdir("/tmp/js-test")); - it("made dir", async () => assert.strictEqual(exists(), true)); -}); diff --git a/src/js/test/integration/filesystem.test.js b/src/js/test/integration/filesystem.test.js new file mode 100644 index 00000000000..50a97c6be04 --- /dev/null +++ b/src/js/test/integration/filesystem.test.js @@ -0,0 +1,27 @@ +const chai = require("chai"); + +// for a persistence-related browser test see /src/tests/test_filesystem.py + +describe("FS", () => { + it("no directory", async () => { + const factory = async () => { + const result = pyodide.runPython( + "import os; os.path.exists('/tmp/js-test')" + ); + return result; + }; + const result = await chai.assert.isFulfilled(page.evaluate(factory)); + chai.assert.isFalse(result); + }); + it("has directory", async () => { + const factory = async () => { + pyodide.FS.mkdir("/tmp/js-test"); + const result = pyodide.runPython( + "import os; os.path.exists('/tmp/js-test')" + ); + return result; + }; + const result = await chai.assert.isFulfilled(page.evaluate(factory)); + chai.assert.isTrue(result); + }); +}); diff --git a/src/js/test/integration/pyodide.test.js b/src/js/test/integration/pyodide.test.js new file mode 100644 index 00000000000..ee9782bc9f4 --- /dev/null +++ b/src/js/test/integration/pyodide.test.js @@ -0,0 +1,43 @@ +const chai = require("chai"); +const fetch = require("node-fetch"); + +describe("Pyodide", () => { + it("runPython", async () => { + const factory = async () => { + return pyodide.runPython("1+1"); + }; + const result = await chai.assert.isFulfilled(page.evaluate(factory)); + chai.assert.equal(result, 2); + }); + describe("micropip", () => { + const globalFetch = globalThis.fetch; + + before(async () => { + const factory = async () => { + globalThis.fetch = fetch; + await pyodide.loadPackage(["micropip"]); + }; + await chai.assert.isFulfilled(page.evaluate(factory)); + }); + after(async () => { + const factory = async (globalFetch) => { + globalThis.fetch = globalFetch || fetch; + }; + await chai.assert.isFulfilled(page.evaluate(factory, globalFetch)); + }); + + it("install", async () => { + const factory = async () => { + await pyodide.runPythonAsync( + 'import micropip; await micropip.install("snowballstemmer")' + ); + return pyodide.runPython(` + import snowballstemmer + len(snowballstemmer.stemmer('english').stemWords(['A', 'node', 'test'])) + `); + }; + const result = await chai.assert.isFulfilled(page.evaluate(factory)); + chai.assert.equal(result, 3); + }); + }); +}); diff --git a/src/js/test/integration/sanity.test.ts b/src/js/test/integration/sanity.test.ts new file mode 100644 index 00000000000..2516ca4488a --- /dev/null +++ b/src/js/test/integration/sanity.test.ts @@ -0,0 +1,11 @@ +import chai from "chai"; + +it("should pass a basic truthy sanity test (node)", async () => { + await chai.assert.isFulfilled(Promise.resolve()); +}); + +it("should pass a basic sanity test in browser (puppeteer)", async () => { + const title = await chai.assert.isFulfilled(page.title()); + chai.assert.isString(title); + chai.assert.equal(title, "pyodide"); +}); diff --git a/src/js/test/module.test.js b/src/js/test/module.test.js deleted file mode 100644 index c542d2e7e1d..00000000000 --- a/src/js/test/module.test.js +++ /dev/null @@ -1,10 +0,0 @@ -import assert from "assert"; -import { Module } from "../module.js"; - -describe("Module", () => { - describe("noWasmDecoding", () => { - it("should be false ", () => { - assert.equal(Module.noWasmDecoding, false); - }); - }); -}); diff --git a/src/js/test/pyodide.test.mjs b/src/js/test/pyodide.test.mjs deleted file mode 100644 index 69c76adbcfc..00000000000 --- a/src/js/test/pyodide.test.mjs +++ /dev/null @@ -1,26 +0,0 @@ -import assert from "assert"; -import { loadPyodide } from "../pyodide.js"; - -import fetch from "node-fetch"; - -describe("Pyodide", () => { - it("runPython", async () => { - let res = pyodide.runPython("1+1"); - assert.equal(res, 2); - }); - it("loadPackage", async () => { - await pyodide.loadPackage(["micropip"]); - }); - it("micropip.install", async () => { - // TODO: micropip currently requires a globally defined fetch function - global.fetch = fetch; - await pyodide.runPythonAsync( - 'import micropip; await micropip.install("snowballstemmer")' - ); - let res = pyodide.runPython(` - import snowballstemmer - len(snowballstemmer.stemmer('english').stemWords(['A', 'node', 'test'])) - `); - assert.equal(res, 3); - }); -}); diff --git a/src/js/test/unit/module.test.ts b/src/js/test/unit/module.test.ts new file mode 100644 index 00000000000..b084ffef51f --- /dev/null +++ b/src/js/test/unit/module.test.ts @@ -0,0 +1,11 @@ +import chai from "chai"; +import * as pyodideModule from "../../module"; + +describe("Module", () => { + describe("noWasmDecoding", () => { + it("should be false ", () => { + const Module = pyodideModule.createModule(); + chai.assert.equal(Module.noWasmDecoding, false); + }); + }); +}); diff --git a/src/js/tsconfig.json b/src/js/tsconfig.json index 9ada2a2bbfd..15790421a1a 100644 --- a/src/js/tsconfig.json +++ b/src/js/tsconfig.json @@ -1,13 +1,15 @@ { "$schema": "http://json.schemastore.org/tsconfig", "compilerOptions": { - "allowJs": true, - "declaration": true, - "emitDeclarationOnly": true, - "lib": ["ES2018", "DOM"], - "outDir": "../../build", - "rootDir": "." + "outDir": "../../dist", + "esModuleInterop": true, + "target": "ES2017", + "module": "ES2020", + "moduleResolution": "node", + "noImplicitAny": true, + "allowSyntheticDefaultImports": true, + "types": ["node"] }, - "include": ["./*.js"], - "exclude": ["rollup.config.js"] + "include": ["*.ts"], + "exclude": ["test/**/*"] } diff --git a/src/js/tsconfig.test.json b/src/js/tsconfig.test.json new file mode 100644 index 00000000000..2399e9923c3 --- /dev/null +++ b/src/js/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "target": "ES2021" + }, + "exclude": [""] +} diff --git a/src/py/_pyodide/__init__.py b/src/py/_pyodide/__init__.py index 65ad5be0391..dc98fd798ee 100644 --- a/src/py/_pyodide/__init__.py +++ b/src/py/_pyodide/__init__.py @@ -9,7 +9,6 @@ # All pure Python code that doesn't require imports from js, pyodide_js, or # _pyodide_core belongs in _pyodide. Code that requires such imports belongs in # pyodide. -from . import _base -from . import _importhook +from . import _base, _importhook __all__ = ["_base", "_importhook"] diff --git a/src/py/_pyodide/_base.py b/src/py/_pyodide/_base.py index bf9ffafafda..1d8a555848e 100644 --- a/src/py/_pyodide/_base.py +++ b/src/py/_pyodide/_base.py @@ -6,12 +6,12 @@ import ast import builtins +import tokenize from copy import deepcopy from io import StringIO from textwrap import dedent -import tokenize from types import CodeType -from typing import Any, Dict, Generator, List, Optional +from typing import Any, Generator def should_quiet(source: str) -> bool: @@ -98,7 +98,7 @@ def __init__(self, v): # Put it into a list to avoid breaking CPython test test_inheritance # (test.test_baseexception.ExceptionClassTests) which examines all Exceptions in # builtins. -builtins.___EvalCodeResultException = [EvalCodeResultException] # type: ignore +builtins.___EvalCodeResultException = [EvalCodeResultException] # type: ignore[attr-defined] # We will substitute in the value of x we are trying to return. _raise_template_ast = ast.parse("raise ___EvalCodeResultException[0](x)").body[0] @@ -115,7 +115,7 @@ def _last_expr_to_raise(mod: ast.Module): return raise_expr = deepcopy(_raise_template_ast) # Replace x with our value in _raise_template_ast. - raise_expr.exc.args[0] = last_node.value # type: ignore + raise_expr.exc.args[0] = last_node.value # type: ignore[attr-defined] mod.body[-1] = raise_expr @@ -164,7 +164,7 @@ class CodeRunner: It is primarily intended for REPLs and other sophisticated consumers that may wish to add their own AST transformations, separately signal to the user when parsing is complete, etc. The simpler :any:`eval_code` and - :any:`eval_code_async` apis should be prefered when their flexibility + :any:`eval_code_async` apis should be preferred when their flexibility suffices. Parameters @@ -211,7 +211,7 @@ class CodeRunner: modify this variable before calling :any:`CodeRunner.compile`. code : Once you call :any:`CodeRunner.compile` the compiled code will - be avaible in the code field. You can modify this variable + be available in the code field. You can modify this variable before calling :any:`CodeRunner.run` to do a code transform. """ @@ -251,10 +251,14 @@ def compile(self): # generator must return, which raises StopIteration self.code = e.value else: - assert False + raise AssertionError() return self - def run(self, globals: Dict[str, Any] = None, locals: Dict[str, Any] = None): + def run( + self, + globals: dict[str, Any] | None = None, + locals: dict[str, Any] | None = None, + ): """Executes ``self.code``. Can only be used after calling compile. The code may not use top level @@ -303,7 +307,9 @@ def run(self, globals: Dict[str, Any] = None, locals: Dict[str, Any] = None): return e.value async def run_async( - self, globals: Dict[str, Any] = None, locals: Dict[str, Any] = None + self, + globals: dict[str, Any] | None = None, + locals: dict[str, Any] | None = None, ): """Runs ``self.code`` which may use top level await. @@ -351,8 +357,8 @@ async def run_async( def eval_code( source: str, - globals: Optional[Dict[str, Any]] = None, - locals: Optional[Dict[str, Any]] = None, + globals: dict[str, Any] | None = None, + locals: dict[str, Any] | None = None, *, return_mode: str = "last_expr", quiet_trailing_semicolon: bool = True, @@ -426,8 +432,8 @@ def eval_code( async def eval_code_async( source: str, - globals: Optional[Dict[str, Any]] = None, - locals: Optional[Dict[str, Any]] = None, + globals: dict[str, Any] | None = None, + locals: dict[str, Any] | None = None, *, return_mode: str = "last_expr", quiet_trailing_semicolon: bool = True, @@ -489,7 +495,7 @@ async def eval_code_async( expression. Use the ``return_mode`` and ``quiet_trailing_semicolon`` parameters to modify this default behavior. """ - flags = flags or ast.PyCF_ALLOW_TOP_LEVEL_AWAIT # type: ignore + flags = flags or ast.PyCF_ALLOW_TOP_LEVEL_AWAIT return ( await CodeRunner( source, @@ -503,7 +509,7 @@ async def eval_code_async( ) -def find_imports(source: str) -> List[str]: +def find_imports(source: str) -> list[str]: """ Finds the imports in a Python source code string diff --git a/src/py/_pyodide/_core_docs.py b/src/py/_pyodide/_core_docs.py index 6e6e24cb083..8b777c84e92 100644 --- a/src/py/_pyodide/_core_docs.py +++ b/src/py/_pyodide/_core_docs.py @@ -1,5 +1,5 @@ -from typing import Any, Callable, Iterable from io import IOBase +from typing import Any, Callable, Iterable # All docstrings for public `core` APIs should be extracted from here. We use # the utilities in `docstring.py` and `docstring.c` to format them @@ -10,6 +10,7 @@ _save_name = __name__ __name__ = "pyodide" + # From jsproxy.c class JsException(Exception): """ @@ -48,13 +49,89 @@ def object_values(self) -> "JsProxy": def new(self, *args, **kwargs) -> "JsProxy": """Construct a new instance of the JavaScript object""" - def to_py(self, *, depth: int = -1) -> Any: + def to_py( + self, + *, + depth: int = -1, + default_converter: Callable[ + ["JsProxy", Callable[["JsProxy"], Any], Callable[["JsProxy", Any], None]], + Any, + ] + | None = None, + ) -> Any: """Convert the :class:`JsProxy` to a native Python object as best as possible. - By default, does a deep conversion, if a shallow conversion is - desired, you can use ``proxy.to_py(depth=1)``. See + By default, does a deep conversion, if a shallow conversion is desired, + you can use ``proxy.to_py(depth=1)``. See :ref:`type-translations-jsproxy-to-py` for more information. + + ``default_converter`` if present will be invoked whenever Pyodide does + not have some built in conversion for the object. + If ``default_converter`` raises an error, the error will be allowed to + propagate. Otherwise, the object returned will be used as the + conversion. ``default_converter`` takes three arguments. The first + argument is the value to be converted. + + Here are a couple examples of converter functions. In addition to the + normal conversions, convert ``Date`` to ``datetime``: + + .. code-block:: python + + from datetime import datetime + def default_converter(value, _ignored1, _ignored2): + if value.constructor.name == "Date": + return datetime.fromtimestamp(d.valueOf()/1000) + return value + + Don't create any JsProxies, require a complete conversion or raise an error: + + .. code-block:: python + + def default_converter(_value, _ignored1, _ignored2): + raise Exception("Failed to completely convert object") + + The second and third arguments are only needed for converting + containers. The second argument is a conversion function which is used + to convert the elements of the container with the same settings. The + third argument is a "cache" function which is needed to handle self + referential containers. Consider the following example. Suppose we have + a Javascript ``Pair`` class: + + .. code-block:: javascript + + class Pair { + constructor(first, second){ + this.first = first; + this.second = second; + } + } + + We can use the following ``default_converter`` to convert ``Pair`` to ``list``: + + .. code-block:: python + + def default_converter(value, convert, cache): + if value.constructor.name != "Pair": + return value + result = [] + cache(value, result); + result.append(convert(value.first)) + result.append(convert(value.second)) + return result + + Note that we have to cache the conversion of ``value`` before converting + ``value.first`` and ``value.second``. To see why, consider a self + referential pair: + + .. code-block:: javascript + + let p = new Pair(0, 0); + p.first = p; + + Without ``cache(value, result);``, converting ``p`` would lead to an + infinite recurse. With it, we can successfully convert ``p`` to a list + such that ``l[0] is l``. """ pass @@ -67,7 +144,7 @@ def then(self, onfulfilled: Callable, onrejected: Callable) -> "Promise": when the promise resolves. """ - def catch(self, onrejected: Callable) -> "Promise": + def catch(self, onrejected: Callable, /) -> "Promise": """The ``Promise.catch`` API, wrapped to manage the lifetimes of the handler. @@ -76,7 +153,7 @@ def catch(self, onrejected: Callable) -> "Promise": when the promise resolves. """ - def finally_(self, onfinally: Callable) -> "Promise": + def finally_(self, onfinally: Callable, /) -> "Promise": """The ``Promise.finally`` API, wrapped to manage the lifetimes of the handler. @@ -93,7 +170,7 @@ def finally_(self, onfinally: Callable) -> "Promise": # Argument should be a buffer. # See https://github.com/python/typing/issues/593 - def assign(self, rhs: Any): + def assign(self, rhs: Any, /): """Assign from a Python buffer into the JavaScript buffer. Present only if the wrapped JavaScript object is an ArrayBuffer or @@ -102,7 +179,7 @@ def assign(self, rhs: Any): # Argument should be a buffer. # See https://github.com/python/typing/issues/593 - def assign_to(self, to: Any): + def assign_to(self, to: Any, /): """Assign to a Python buffer from the JavaScript buffer. Present only if the wrapped JavaScript object is an ArrayBuffer or @@ -110,7 +187,7 @@ def assign_to(self, to: Any): """ def to_memoryview(self) -> memoryview: - """Convert the buffer to a memoryview. + """Convert a buffer to a memoryview. Copies the data once. This currently has the same effect as :any:`to_py`. Present only if the wrapped Javascript object is an ArrayBuffer or @@ -118,15 +195,15 @@ def to_memoryview(self) -> memoryview: """ def to_bytes(self) -> bytes: - """Convert the buffer to a bytes object. + """Convert a buffer to a bytes object. Copies the data once. Present only if the wrapped Javascript object is an ArrayBuffer or an ArrayBuffer view. """ - def to_file(self, file: IOBase): - """Writes the entire buffer to a file. + def to_file(self, file: IOBase, /): + """Writes a buffer to a file. Will write the entire contents of the buffer to the current position of the file. @@ -149,8 +226,8 @@ def to_file(self, file: IOBase): data once. """ - def from_file(self, file: IOBase): - """Reads from a file into the buffer. + def from_file(self, file: IOBase, /): + """Reads from a file into a buffer. Will try to read a chunk of data the same size as the buffer from the current position of the file. @@ -175,8 +252,8 @@ def from_file(self, file: IOBase): data once. """ - def _into_file(self, file: IOBase): - """Will write the entire contents of the buffer into a file using + def _into_file(self, file: IOBase, /): + """Will write the entire contents of a buffer into a file using ``canOwn : true`` without any copy. After this, the buffer cannot be used again. @@ -204,7 +281,7 @@ def _into_file(self, file: IOBase): """ def to_string(self, encoding=None) -> str: - """Convert the buffer to a string object. + """Convert a buffer to a string object. Copies the data twice. @@ -222,17 +299,17 @@ def to_string(self, encoding=None) -> str: # from pyproxy.c -def create_once_callable(obj: Callable) -> JsProxy: +def create_once_callable(obj: Callable, /) -> JsProxy: """Wrap a Python callable in a JavaScript function that can be called once. After being called the proxy will decrement the reference count of the Callable. The JavaScript function also has a ``destroy`` API that can be used to release the proxy without calling it. """ - return obj # type: ignore + return obj # type: ignore[return-value] -def create_proxy(obj: Any) -> JsProxy: +def create_proxy(obj: Any, /) -> JsProxy: """Create a ``JsProxy`` of a ``PyProxy``. This allows explicit control over the lifetime of the ``PyProxy`` from @@ -246,11 +323,16 @@ def create_proxy(obj: Any) -> JsProxy: def to_js( obj: Any, + /, *, depth: int = -1, - pyproxies: JsProxy = None, + pyproxies: JsProxy | None = None, create_pyproxies: bool = True, - dict_converter: Callable[[Iterable[JsProxy]], JsProxy] = None, + dict_converter: Callable[[Iterable[JsProxy]], JsProxy] | None = None, + default_converter: Callable[ + [Any, Callable[[Any], JsProxy], Callable[[Any, JsProxy], None]], JsProxy + ] + | None = None, ) -> JsProxy: """Convert the object to JavaScript. @@ -280,8 +362,8 @@ def to_js( create_pyproxies: bool, default=True If you set this to False, :any:`to_js` will raise an error - dict_converter: Callable[[Iterable[JsProxy]], JsProxy], defauilt = None - This converter if provided recieves a (JavaScript) iterable of + dict_converter: Callable[[Iterable[JsProxy]], JsProxy], default = None + This converter if provided receives a (JavaScript) iterable of (JavaScript) pairs [key, value]. It is expected to return the desired result of the dict conversion. Some suggested values for this argument: @@ -289,6 +371,72 @@ def to_js( js.Map.new -- similar to the default behavior js.Array.from -- convert to an array of entries js.Object.fromEntries -- convert to a JavaScript object + default_converter: Callable[[Any, Callable[[Any], JsProxy], Callable[[Any, JsProxy], None]], JsProxy], default=None + If present will be invoked whenever Pyodide does not have some built in + conversion for the object. If ``default_converter`` raises an error, the + error will be allowed to propagate. Otherwise, the object returned will + be used as the conversion. ``default_converter`` takes three arguments. + The first argument is the value to be converted. + + Here are a couple examples of converter functions. In addition to the + normal conversions, convert ``Date`` to ``datetime``: + + .. code-block:: python + + from datetime import datetime + from js import Date + def default_converter(value, _ignored1, _ignored2): + if isinstance(value, datetime): + return Date.new(value.timestamp() * 1000) + return value + + Don't create any PyProxies, require a complete conversion or raise an error: + + .. code-block:: python + + def default_converter(_value, _ignored1, _ignored2): + raise Exception("Failed to completely convert object") + + The second and third arguments are only needed for converting + containers. The second argument is a conversion function which is used + to convert the elements of the container with the same settings. The + third argument is a "cache" function which is needed to handle self + referential containers. Consider the following example. Suppose we have + a Python ``Pair`` class: + + .. code-block:: python + + class Pair: + def __init__(self, first, second): + self.first = first + self.second = second + + We can use the following ``default_converter`` to convert ``Pair`` to ``Array``: + + .. code-block:: python + + from js import Array + def default_converter(value, convert, cache): + if not isinstance(value, Pair): + return value + result = Array.new() + cache(value, result); + result.push(convert(value.first)) + result.push(convert(value.second)) + return result + + Note that we have to cache the conversion of ``value`` before converting + ``value.first`` and ``value.second``. To see why, consider a self + referential pair: + + .. code-block:: javascript + + p = Pair(0, 0); + p.first = p; + + Without ``cache(value, result);``, converting ``p`` would lead to an + infinite recurse. With it, we can successfully convert ``p`` to an Array + such that ``l[0] === l``. """ return obj @@ -297,7 +445,7 @@ class Promise(JsProxy): pass -def destroy_proxies(pyproxies: JsProxy): +def destroy_proxies(pyproxies: JsProxy, /): """Destroy all PyProxies in a JavaScript array. pyproxies must be a JsProxy of type PyProxy[]. Intended for use with the diff --git a/src/py/_pyodide/_importhook.py b/src/py/_pyodide/_importhook.py index 07a8511629a..f5c072d9444 100644 --- a/src/py/_pyodide/_importhook.py +++ b/src/py/_pyodide/_importhook.py @@ -1,6 +1,6 @@ -from importlib.abc import MetaPathFinder, Loader -from importlib.util import spec_from_loader import sys +from importlib.abc import Loader, MetaPathFinder +from importlib.util import spec_from_loader class JsFinder(MetaPathFinder): @@ -8,6 +8,7 @@ def __init__(self): self.jsproxies = {} def find_spec(self, fullname, path, target=None): + assert JsProxy is not None [parent, _, child] = fullname.rpartition(".") if parent: parent_module = sys.modules[parent] @@ -49,6 +50,7 @@ def register_js_module(self, name: str, jsproxy): jsproxy : JsProxy JavaScript object backing the module """ + assert JsProxy is not None if not isinstance(name, str): raise TypeError( f"Argument 'name' must be a str, not {type(name).__name__!r}" @@ -97,7 +99,7 @@ def is_package(self, fullname): return True -JsProxy: type = None # type: ignore +JsProxy: type | None = None jsfinder: JsFinder = JsFinder() register_js_module = jsfinder.register_js_module unregister_js_module = jsfinder.unregister_js_module @@ -115,8 +117,8 @@ def register_js_finder(): This needs to be a function to allow the late import from ``_pyodide_core``. """ - import _pyodide_core # type: ignore + import _pyodide_core global JsProxy JsProxy = _pyodide_core.JsProxy - sys.meta_path.append(jsfinder) # type: ignore + sys.meta_path.append(jsfinder) diff --git a/src/py/_pyodide/console.py b/src/py/_pyodide/console.py deleted file mode 100644 index 91ecfd8ec18..00000000000 --- a/src/py/_pyodide/console.py +++ /dev/null @@ -1,489 +0,0 @@ -import ast -import asyncio -from asyncio import ensure_future, Future -from codeop import Compile, CommandCompiler, _features # type: ignore -from contextlib import ( - contextmanager, - redirect_stdout, - redirect_stderr, - ExitStack, -) -from contextlib import _RedirectStream # type: ignore -import rlcompleter -import platform -import sys -from tokenize import TokenError -import traceback -from typing import Literal -from typing import ( - Optional, - Callable, - Any, - List, - Tuple, - Union, - Tuple, -) - -from _pyodide._base import should_quiet, CodeRunner - -__all__ = ["repr_shorten", "BANNER", "Console", "PyodideConsole", "ConsoleFuture"] - - -def _banner(): - """A banner similar to the one printed by the real Python interpreter.""" - # copied from https://github.com/python/cpython/blob/799f8489d418b7f9207d333eac38214931bd7dcc/Lib/code.py#L214 - cprt = 'Type "help", "copyright", "credits" or "license" for more information.' - version = platform.python_version() - build = f"({', '.join(platform.python_build())})" - return f"Python {version} {build} on WebAssembly VM\n{cprt}" - - -BANNER = _banner() -del _banner - - -class redirect_stdin(_RedirectStream): - _stream = "stdin" - - -class _WriteStream: - """A utility class so we can specify our own handlers for writes to sdout, stderr""" - - def __init__(self, write_handler, name=None): - self.write_handler = write_handler - self.name = name - - def write(self, text): - self.write_handler(text) - - def flush(self): - pass - - def isatty(self) -> bool: - return True - - -class _ReadStream: - """A utility class so we can specify our own handler for reading from stdin""" - - def __init__(self, read_handler, name=None): - self.read_handler = read_handler - self.name = name - - def readline(self, n=-1): - return self.read_handler(n) - - def flush(self): - pass - - def isatty(self) -> bool: - return True - - -class _Compile(Compile): - """Compile code with CodeRunner, and remember future imports - - Instances of this class behave much like the built-in compile function, - but if one is used to compile text containing a future statement, it - "remembers" and compiles all subsequent program texts with the statement in - force. It uses CodeRunner instead of the built-in compile. - """ - - def __init__( - self, - *, - return_mode="last_expr", - quiet_trailing_semicolon=True, - flags=0x0, - ): - super().__init__() - self.flags |= flags - self.return_mode = return_mode - self.quiet_trailing_semicolon = quiet_trailing_semicolon - - def __call__(self, source, filename, symbol) -> CodeRunner: # type: ignore - return_mode = self.return_mode - try: - if self.quiet_trailing_semicolon and should_quiet(source): - return_mode = None - except (TokenError, SyntaxError): - # Invalid code, let the Python parser throw the error later. - pass - - code_runner = CodeRunner( - source, - mode=symbol, - filename=filename, - return_mode=return_mode, - flags=self.flags, - ).compile() - for feature in _features: - if code_runner.code.co_flags & feature.compiler_flag: - self.flags |= feature.compiler_flag - return code_runner - - -class _CommandCompiler(CommandCompiler): - """Compile code with CodeRunner, and remember future imports, return None if - code is incomplete. - - Instances of this class have __call__ methods identical in signature to - compile; the difference is that if the instance compiles program text - containing a __future__ statement, the instance 'remembers' and compiles all - subsequent program texts with the statement in force. - - If the source is determined to be incomplete, will suppress the SyntaxError - and return ``None``. - """ - - def __init__( - self, - *, - return_mode="last_expr", - quiet_trailing_semicolon=True, - flags=0x0, - ): - self.compiler = _Compile( - return_mode=return_mode, - quiet_trailing_semicolon=quiet_trailing_semicolon, - flags=flags, - ) - - def __call__(self, source, filename="", symbol="single") -> Optional[CodeRunner]: # type: ignore - return super().__call__(source, filename, symbol) # type: ignore - - -INCOMPLETE: Literal["incomplete"] = "incomplete" -SYNTAX_ERROR: Literal["syntax-error"] = "syntax-error" -COMPLETE: Literal["complete"] = "complete" - - -class ConsoleFuture(Future): - """A future with extra fields used as the return value for :any:`Console` apis. - - Attributes - ---------- - syntax_check : str - One of ``"incomplete"``, ``"syntax-error"``, or ``"complete"``. If the value is - ``"incomplete"`` then the future has already been resolved with result equal to - ``None``. If the value is ``"syntax-error"``, the ``Future`` has already been - rejected with a ``SyntaxError``. If the value is ``"complete"``, then the input - complete and syntactically correct. - - formatted_error : str - If the ``Future`` is rejected, this will be filled with a formatted version of - the code. This is a convenience that simplifies code and helps to avoid large - memory leaks when using from JavaScript. - - """ - - def __init__( - self, - syntax_check: Union[ - Literal["incomplete"], Literal["syntax-error"], Literal["complete"] - ], - ): - super().__init__() - self.syntax_check: Union[ - Literal["incomplete"], Literal["syntax-error"], Literal["complete"] - ] = syntax_check - self.formatted_error: Optional[str] = None - - -class Console: - """Interactive Pyodide console - - An interactive console based on the Python standard library - `code.InteractiveConsole` that manages stream redirections and asynchronous - execution of the code. - - The stream callbacks can be modified directly as long as - `persistent_stream_redirection` isn't in effect. - - Parameters - ---------- - globals : ``dict`` - The global namespace in which to evaluate the code. Defaults to a new empty dictionary. - - stdin_callback : ``Callable[[str], None]`` - Function to call at each read from ``sys.stdin``. Defaults to ``None``. - - stdout_callback : ``Callable[[str], None]`` - Function to call at each write to ``sys.stdout``. Defaults to ``None``. - - stderr_callback : ``Callable[[str], None]`` - Function to call at each write to ``sys.stderr``. Defaults to ``None``. - - persistent_stream_redirection : ``bool`` - Should redirection of standard streams be kept between calls to :any:`runcode `? - Defaults to ``False``. - - filename : ``str`` - The file name to report in error messages. Defaults to ````. - - Attributes - ---------- - globals : ``Dict[str, Any]`` - The namespace used as the global - - stdin_callback : ``Callback[[str], None]`` - Function to call at each read from ``sys.stdin``. - - stdout_callback : ``Callback[[str], None]`` - Function to call at each write to ``sys.stdout``. - - stderr_callback : ``Callback[[str], None]`` - Function to call at each write to ``sys.stderr``. - - buffer : ``List[str]`` - The list of strings that have been :any:`pushed ` to the console. - - completer_word_break_characters : ``str`` - The set of characters considered by :any:`complete ` to be word breaks. - """ - - def __init__( - self, - globals: Optional[dict] = None, - *, - stdin_callback: Optional[Callable[[str], None]] = None, - stdout_callback: Optional[Callable[[str], None]] = None, - stderr_callback: Optional[Callable[[str], None]] = None, - persistent_stream_redirection: bool = False, - filename: str = "", - ): - if globals is None: - globals = {"__name__": "__console__", "__doc__": None} - self.globals = globals - self._stdout = None - self._stderr = None - self.stdin_callback = stdin_callback - self.stdout_callback = stdout_callback - self.stderr_callback = stderr_callback - self.filename = filename - self.buffer: List[str] = [] - self._lock = asyncio.Lock() - self._streams_redirected = False - self._stream_generator = None # track persistent stream redirection - if persistent_stream_redirection: - self.persistent_redirect_streams() - self._completer = rlcompleter.Completer(self.globals) # type: ignore - # all nonalphanums except '.' - # see https://github.com/python/cpython/blob/a4258e8cd776ba655cc54ba54eaeffeddb0a267c/Modules/readline.c#L1211 - self.completer_word_break_characters = ( - """ \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?""" - ) - self._compile = _CommandCompiler(flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) # type: ignore - - def persistent_redirect_streams(self): - """Redirect stdin/stdout/stderr persistently""" - if self._stream_generator: - return - self._stream_generator = self._stdstreams_redirections_inner() - next(self._stream_generator) # trigger stream redirection - # streams will be reverted to normal when self._stream_generator is destroyed. - - def persistent_restore_streams(self): - """Restore stdin/stdout/stderr if they have been persistently redirected""" - # allowing _stream_generator to be garbage collected restores the streams - self._stream_generator = None - - @contextmanager - def redirect_streams(self): - """A context manager to redirect standard streams. - - This supports nesting.""" - yield from self._stdstreams_redirections_inner() - - def _stdstreams_redirections_inner(self): - """This is the generator which implements redirect_streams and the stdstreams_redirections""" - # already redirected? - if self._streams_redirected: - yield - return - redirects = [] - if self.stdin_callback: - stdin_name = getattr(sys.stdin, "name", "") - stdin_stream = _ReadStream(self.stdin_callback, name=stdin_name) - redirects.append(redirect_stdin(stdin_stream)) - if self.stdout_callback: - stdout_name = getattr(sys.stdout, "name", "") - stdout_stream = _WriteStream(self.stdout_callback, name=stdout_name) - redirects.append(redirect_stdout(stdout_stream)) - if self.stderr_callback: - stderr_name = getattr(sys.stderr, "name", "") - stderr_stream = _WriteStream(self.stderr_callback, name=stderr_name) - redirects.append(redirect_stderr(stderr_stream)) - try: - self._streams_redirected = True - with ExitStack() as stack: - for redirect in redirects: - stack.enter_context(redirect) - yield - finally: - self._streams_redirected = False - - def runsource(self, source: str, filename: str = "") -> ConsoleFuture: - """Compile and run source code in the interpreter. - - Returns - ------- - :any:`ConsoleFuture` - - """ - try: - code = self._compile(source, filename, "single") - except (OverflowError, SyntaxError, ValueError) as e: - # Case 1 - if e.__traceback__: - traceback.clear_frames(e.__traceback__) - res = ConsoleFuture(SYNTAX_ERROR) - res.set_exception(e) - res.formatted_error = self.formatsyntaxerror(e) - return res - - if code is None: - res = ConsoleFuture(INCOMPLETE) - res.set_result(None) - return res - - res = ConsoleFuture(COMPLETE) - - def done_cb(fut): - nonlocal res - exc = fut.exception() - if exc: - res.formatted_error = self.formattraceback(exc) - res.set_exception(exc) - exc = None - else: - res.set_result(fut.result()) - res = None # type: ignore - - ensure_future(self.runcode(source, code)).add_done_callback(done_cb) - return res - - async def runcode(self, source: str, code: CodeRunner) -> Any: - """Execute a code object and return the result.""" - async with self._lock: - with self.redirect_streams(): - try: - return await code.run_async(self.globals) - finally: - sys.stdout.flush() - sys.stderr.flush() - - def formatsyntaxerror(self, e: Exception) -> str: - """Format the syntax error that just occurred. - - This doesn't include a stack trace because there isn't one. The actual - error object is stored into `sys.last_value`. - """ - sys.last_type = type(e) - sys.last_value = e - sys.last_traceback = None - try: - return "".join(traceback.format_exception_only(type(e), e)) - finally: - e = None # type: ignore - - def num_frames_to_keep(self, tb): - keep_frames = False - kept_frames = 0 - # Try to trim out stack frames inside our code - for (frame, _) in traceback.walk_tb(tb): - keep_frames = keep_frames or frame.f_code.co_filename == "" - keep_frames = keep_frames or frame.f_code.co_filename == "" - if keep_frames: - kept_frames += 1 - return kept_frames - - def formattraceback(self, e: Exception) -> str: - """Format the exception that just occurred. - - The actual error object is stored into `sys.last_value`. - """ - try: - sys.last_type = type(e) - sys.last_value = e - sys.last_traceback = e.__traceback__ - nframes = self.num_frames_to_keep(e.__traceback__) - return "".join( - traceback.format_exception(type(e), e, e.__traceback__, -nframes) - ) - finally: - e = None # type: ignore - - def push(self, line: str) -> ConsoleFuture: - """Push a line to the interpreter. - - The line should not have a trailing newline; it may have internal - newlines. The line is appended to a buffer and the interpreter's - runsource() method is called with the concatenated contents of the - buffer as source. If this indicates that the command was executed or - invalid, the buffer is reset; otherwise, the command is incomplete, and - the buffer is left as it was after the line was appended. - - The return value is the result of calling :any:`Console.runsource` on the current buffer - contents. - """ - self.buffer.append(line) - source = "\n".join(self.buffer) - result = self.runsource(source, self.filename) - if result.syntax_check != INCOMPLETE: - self.buffer = [] - return result - - def complete(self, source: str) -> Tuple[List[str], int]: - """Use Python's rlcompleter to complete the source string using the :any:`globals ` namespace. - - Finds last "word" in the source string and completes it with rlcompleter. Word - breaks are determined by the set of characters in - :any:`completer_word_break_characters `. - - Parameters - ---------- - source : str - The source string to complete at the end. - - Returns - ------- - completions : List[str] - A list of completion strings. - start : int - The index where completion starts. - - Examples - -------- - >>> shell = Console() - >>> shell.complete("str.isa") - (['str.isalnum(', 'str.isalpha(', 'str.isascii('], 0) - >>> shell.complete("a = 5 ; str.isa") - (['str.isalnum(', 'str.isalpha(', 'str.isascii('], 8) - """ - start = max(map(source.rfind, self.completer_word_break_characters)) + 1 - source = source[start:] - if "." in source: - completions = self._completer.attr_matches(source) # type: ignore - else: - completions = self._completer.global_matches(source) # type: ignore - return completions, start - - -def repr_shorten( - value: Any, limit: int = 1000, split: Optional[int] = None, separator: str = "..." -) -> str: - """Compute the string representation of ``value`` and shorten it - if necessary. - - If it is longer than ``limit`` then return the firsts ``split`` - characters and the last ``split`` characters seperated by '...'. - Default value for ``split`` is `limit // 2`. - """ - if split is None: - split = limit // 2 - text = repr(value) - if len(text) > limit: - text = f"{text[:split]}{separator}{text[-split:]}" - return text diff --git a/src/py/_pyodide/docstring.py b/src/py/_pyodide/docstring.py index 36df5101e36..ffb20512877 100644 --- a/src/py/_pyodide/docstring.py +++ b/src/py/_pyodide/docstring.py @@ -37,12 +37,12 @@ def get_cmeth_docstring(func): >>> get_cmeth_docstring(sum)[:80] "sum(iterable, /, start=0)\\n--\\n\\nReturn the sum of a 'start' value (default: 0) plu" """ - from inspect import signature, _empty + from inspect import _empty, signature sig = signature(func) # remove param and return annotations and for param in sig.parameters.values(): - param._annotation = _empty - sig._return_annotation = _empty + param._annotation = _empty # type: ignore[attr-defined] + sig._return_annotation = _empty # type: ignore[attr-defined] return func.__name__ + str(sig) + "\n--\n\n" + dedent_docstring(func.__doc__) diff --git a/src/py/lib/_testcapi.py b/src/py/lib/_testcapi.py deleted file mode 100644 index 5613d0e1810..00000000000 --- a/src/py/lib/_testcapi.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -A shim to skip tests involving the _testcapi extension, since we don't build -it. -""" - -import unittest - -raise unittest.SkipTest("No _testcapi") diff --git a/src/py/lib/_testinternalcapi.py b/src/py/lib/_testinternalcapi.py deleted file mode 100644 index 9d18c956d85..00000000000 --- a/src/py/lib/_testinternalcapi.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -A shim to skip tests involving the _testinternalcapi extension, since we don't build -it. -""" - -import unittest - -raise unittest.SkipTest("No _testinternalcapi") diff --git a/src/py/pyodide/__init__.py b/src/py/pyodide/__init__.py index a40a8b65d86..f05e7269fbb 100644 --- a/src/py/pyodide/__init__.py +++ b/src/py/pyodide/__init__.py @@ -11,36 +11,37 @@ # pytest mocks for js or pyodide_js, so make sure to test "if IN_BROWSER" before # importing from these. -from ._core import ( - JsProxy, - JsException, - create_once_callable, - create_proxy, - to_js, - IN_BROWSER, - ConversionError, - destroy_proxies, -) from _pyodide._base import ( + CodeRunner, eval_code, eval_code_async, find_imports, - CodeRunner, should_quiet, ) -from .http import open_url -from . import _state # noqa - from _pyodide._importhook import register_js_module, unregister_js_module +from . import _state # noqa: F401 +from ._core import ( + IN_BROWSER, + ConversionError, + JsException, + JsProxy, + create_once_callable, + create_proxy, + destroy_proxies, + to_js, +) +from .http import open_url + if IN_BROWSER: import asyncio + from .webloop import WebLoopPolicy asyncio.set_event_loop_policy(WebLoopPolicy()) -__version__ = "0.19.0" +__version__ = "0.20.0" __all__ = [ "open_url", diff --git a/src/py/pyodide/_core.py b/src/py/pyodide/_core.py index 73a8db8e142..5ccbf682f32 100644 --- a/src/py/pyodide/_core.py +++ b/src/py/pyodide/_core.py @@ -5,22 +5,22 @@ if IN_BROWSER: from _pyodide_core import ( ConversionError, - create_proxy, - create_once_callable, - JsProxy, JsException, - to_js, + JsProxy, + create_once_callable, + create_proxy, destroy_proxies, + to_js, ) else: from _pyodide._core_docs import ( ConversionError, - create_proxy, - create_once_callable, - JsProxy, JsException, - to_js, + JsProxy, + create_once_callable, + create_proxy, destroy_proxies, + to_js, ) diff --git a/src/py/pyodide/_package_loader.py b/src/py/pyodide/_package_loader.py new file mode 100644 index 00000000000..8246756e495 --- /dev/null +++ b/src/py/pyodide/_package_loader.py @@ -0,0 +1,181 @@ +import re +import shutil +import sysconfig +import tarfile +from importlib.machinery import EXTENSION_SUFFIXES +from pathlib import Path +from site import getsitepackages +from tempfile import NamedTemporaryFile +from typing import IO, Iterable, Literal +from zipfile import ZipFile + +from ._core import IN_BROWSER, JsProxy, to_js + +SITE_PACKAGES = Path(getsitepackages()[0]) +STD_LIB = Path(sysconfig.get_path("stdlib")) +TARGETS = {"site": SITE_PACKAGES, "lib": STD_LIB} +ZIP_TYPES = {".whl", ".zip"} +TAR_TYPES = {".tar", ".gz", ".bz", ".gz", ".tgz", ".bz2", ".tbz2"} +EXTENSION_TAGS = [suffix.removesuffix(".so") for suffix in EXTENSION_SUFFIXES] +# See PEP 3149. I think the situation has since been updated since PEP 3149 does +# not talk about platform triples. But I could not find any newer pep discussing +# shared library names. +# +# There are other interpreters but it's better to have false negatives than +# false positives. +PLATFORM_TAG_REGEX = re.compile( + r"\.(cpython|pypy|jython)-[0-9]{2,}[a-z]*(-[a-z0-9_-]*)?" +) + + +def make_whlfile(*args, owner=None, group=None, **kwargs): + return shutil._make_zipfile(*args, **kwargs) # type: ignore[attr-defined] + + +if IN_BROWSER: + shutil.register_archive_format("whl", make_whlfile, description="Wheel file") + shutil.register_unpack_format( + "whl", [".whl", ".wheel"], shutil._unpack_zipfile, description="Wheel file" # type: ignore[attr-defined] + ) + + +def get_format(format: str) -> str: + for (fmt, extensions, _) in shutil.get_unpack_formats(): + if format == fmt: + return fmt + if format in extensions: + return fmt + if "." + format in extensions: + return fmt + raise ValueError(f"Unrecognized format {format}") + + +def unpack_buffer( + buffer: JsProxy, + *, + filename: str = "", + format: str | None = None, + target: Literal["site", "lib"] | None = None, + extract_dir: str | None = None, + calculate_dynlibs: bool = False, +) -> JsProxy | None: + """Used to install a package either into sitepackages or into the standard + library. + + This is a helper method called from ``loadPackage``. + + Parameters + ---------- + buffer + A Javascript ``Uint8Array`` with the binary data for the archive. + + filename + The name of the file we are extracting. We only care about it to figure + out whether the buffer represents a tar file or a zip file. Ignored if + format argument is present. + + format + Controls the format that we assume the archive has. Overrides the file + extension of filename. In particular we decide the file format as + follows: + + 1. If format is present, we use that. + 2. If file name is present, it should have an extension, like a.zip, + a.tar, etc. Then we use that. + 3. If neither is present or the file name has no extension, we throw an + error. + + + extract_dir + Controls which directory the file is unpacked into. Default is the + working directory. Mutually exclusive with target. + + target + Controls which directory the file is unpacked into. Either "site" which + unpacked the file into the sitepackages directory or "lib" which + unpacked the file into the standard library. Mutually exclusive with + extract_dir. + + calculate_dynlibs + If true, will return a Javascript Array of paths to dynamic libraries + ('.so' files) that were in the archive. We need to precompile these Wasm + binaries in `load-pyodide.js`. These paths point to the unpacked + locations of the .so files. + + Returns + ------- + If calculate_dynlibs is True, a Javascript Array of dynamic libraries. + Otherwise, return None. + + """ + if format: + format = get_format(format) + if target and extract_dir: + raise ValueError("Cannot provide both 'target' and 'extract_dir'") + if not filename and format is None: + raise ValueError("At least one of filename and format must be provided") + if target: + extract_path = TARGETS[target] + elif extract_dir: + extract_path = Path(extract_dir) + else: + extract_path = Path(".") + with NamedTemporaryFile(suffix=filename) as f: + buffer._into_file(f) + shutil.unpack_archive(f.name, extract_path, format) + if calculate_dynlibs: + return to_js(get_dynlibs(f, extract_path)) + else: + return None + + +def should_load_dynlib(path: str): + suffixes = Path(path).suffixes + if not suffixes: + return False + if suffixes[-1] != ".so": + return False + if len(suffixes) == 1: + return True + tag = suffixes[-2] + if tag in EXTENSION_TAGS: + return True + # Okay probably it's not compatible now. But it might be an unrelated .so + # file with a name with an extra dot: `some.name.so` vs + # `some.cpython-39-x86_64-linux-gnu.so` Let's make a best effort here to + # check. + return not PLATFORM_TAG_REGEX.match(tag) + + +def get_dynlibs(archive: IO[bytes], target_dir: Path) -> list[str]: + """List out the paths to .so files in a zip or tar archive. + + Parameters + ---------- + archive + A binary representation of either a zip or a tar archive. We use the `.name` + field to determine which file type. + + target_dir + The directory the archive is unpacked into. Paths will be adjusted to point + inside this directory. + + Returns + ------- + The list of paths to dynamic libraries ('.so' files) that were in the archive, + but adjusted to point to their unpacked locations. + """ + suffix = Path(archive.name).suffix + dynlib_paths_iter: Iterable[str] + if suffix in ZIP_TYPES: + dynlib_paths_iter = ZipFile(archive).namelist() + elif suffix in TAR_TYPES: + dynlib_paths_iter = (tinfo.name for tinfo in tarfile.open(archive.name)) + else: + raise ValueError(f"Unexpected suffix {suffix}") + + return [ + str((target_dir / path).resolve()) + for path in dynlib_paths_iter + if should_load_dynlib(path) + ] diff --git a/src/py/pyodide/_state.py b/src/py/pyodide/_state.py index 1f0a2a98ca7..0f2b19d77a5 100644 --- a/src/py/pyodide/_state.py +++ b/src/py/pyodide/_state.py @@ -1,10 +1,12 @@ -import __main__ -import sys import gc +import sys + +import __main__ -from ._core import JsProxy from _pyodide._importhook import jsfinder +from ._core import JsProxy + def save_state() -> dict: """Record the current global state. diff --git a/src/py/pyodide/_util.py b/src/py/pyodide/_util.py deleted file mode 100644 index 8195c5b325a..00000000000 --- a/src/py/pyodide/_util.py +++ /dev/null @@ -1,33 +0,0 @@ -from tempfile import NamedTemporaryFile -import shutil -from ._core import IN_BROWSER - - -def make_whlfile(*args, owner=None, group=None, **kwargs): - return shutil._make_zipfile(*args, **kwargs) # type: ignore - - -if IN_BROWSER: - shutil.register_archive_format("whl", make_whlfile, description="Wheel file") - shutil.register_unpack_format( - "whl", [".whl", ".wheel"], shutil._unpack_zipfile, description="Wheel file" # type: ignore - ) - - -def get_format(format): - for (fmt, extensions, _) in shutil.get_unpack_formats(): - if format == fmt: - return fmt - if format in extensions: - return fmt - if "." + format in extensions: - return fmt - raise ValueError(f"Unrecognized format {format}") - - -def unpack_buffer_archive(buf, *, filename="", format=None, extract_dir="."): - if format: - format = get_format(format) - with NamedTemporaryFile(suffix=filename) as f: - buf._into_file(f) - shutil.unpack_archive(f.name, extract_dir, format) diff --git a/src/py/pyodide/console.py b/src/py/pyodide/console.py index 36ee4ba2220..8b8c11cc9bb 100644 --- a/src/py/pyodide/console.py +++ b/src/py/pyodide/console.py @@ -1,10 +1,458 @@ -from _pyodide.console import Console, repr_shorten, ConsoleFuture -import _pyodide.console +import ast +import asyncio +import rlcompleter +import sys +import traceback +from asyncio import Future, ensure_future +from codeop import CommandCompiler, Compile, _features # type: ignore[attr-defined] +from contextlib import ( # type: ignore[attr-defined] + ExitStack, + _RedirectStream, + contextmanager, + redirect_stderr, + redirect_stdout, +) +from platform import python_build, python_version +from tokenize import TokenError +from typing import Any, Callable, Generator, Literal -BANNER = _pyodide.console.BANNER -from _pyodide._base import CodeRunner +from _pyodide._base import CodeRunner, should_quiet -__all__ = ["Console", "PyodideConsole", "Banner", "repr_shorten", "ConsoleFuture"] +__all__ = ["Console", "PyodideConsole", "BANNER", "repr_shorten", "ConsoleFuture"] + + +BANNER = f""" +Python {python_version()} ({', '.join(python_build())}) on WebAssembly VM +Type "help", "copyright", "credits" or "license" for more information. +""".strip() + + +class redirect_stdin(_RedirectStream): + _stream = "stdin" + + +class _WriteStream: + """A utility class so we can specify our own handlers for writes to sdout, stderr""" + + def __init__(self, write_handler, name=None): + self.write_handler = write_handler + self.name = name + + def write(self, text): + self.write_handler(text) + + def flush(self): + pass + + def isatty(self) -> bool: + return True + + +class _ReadStream: + """A utility class so we can specify our own handler for reading from stdin""" + + def __init__(self, read_handler, name=None): + self.read_handler = read_handler + self.name = name + + def readline(self, n=-1): + return self.read_handler(n) + + def flush(self): + pass + + def isatty(self) -> bool: + return True + + +class _Compile(Compile): + """Compile code with CodeRunner, and remember future imports + + Instances of this class behave much like the built-in compile function, + but if one is used to compile text containing a future statement, it + "remembers" and compiles all subsequent program texts with the statement in + force. It uses CodeRunner instead of the built-in compile. + """ + + def __init__( + self, + *, + return_mode="last_expr", + quiet_trailing_semicolon=True, + flags=0x0, + ): + super().__init__() + self.flags |= flags + self.return_mode = return_mode + self.quiet_trailing_semicolon = quiet_trailing_semicolon + + def __call__(self, source, filename, symbol) -> CodeRunner: # type: ignore[override] + return_mode = self.return_mode + try: + if self.quiet_trailing_semicolon and should_quiet(source): + return_mode = None + except (TokenError, SyntaxError): + # Invalid code, let the Python parser throw the error later. + pass + + code_runner = CodeRunner( + source, + mode=symbol, + filename=filename, + return_mode=return_mode, + flags=self.flags, + ).compile() + for feature in _features: + if code_runner.code.co_flags & feature.compiler_flag: + self.flags |= feature.compiler_flag + return code_runner + + +class _CommandCompiler(CommandCompiler): + """Compile code with CodeRunner, and remember future imports, return None if + code is incomplete. + + Instances of this class have __call__ methods identical in signature to + compile; the difference is that if the instance compiles program text + containing a __future__ statement, the instance 'remembers' and compiles all + subsequent program texts with the statement in force. + + If the source is determined to be incomplete, will suppress the SyntaxError + and return ``None``. + """ + + def __init__( + self, + *, + return_mode="last_expr", + quiet_trailing_semicolon=True, + flags=0x0, + ): + self.compiler = _Compile( + return_mode=return_mode, + quiet_trailing_semicolon=quiet_trailing_semicolon, + flags=flags, + ) + + def __call__( # type: ignore[override] + self, source, filename="", symbol="single" + ) -> CodeRunner | None: + return super().__call__(source, filename, symbol) # type: ignore[return-value] + + +INCOMPLETE: Literal["incomplete"] = "incomplete" +SYNTAX_ERROR: Literal["syntax-error"] = "syntax-error" +COMPLETE: Literal["complete"] = "complete" + + +class ConsoleFuture(Future): + """A future with extra fields used as the return value for :any:`Console` apis. + + Attributes + ---------- + syntax_check : str + One of ``"incomplete"``, ``"syntax-error"``, or ``"complete"``. If the value is + ``"incomplete"`` then the future has already been resolved with result equal to + ``None``. If the value is ``"syntax-error"``, the ``Future`` has already been + rejected with a ``SyntaxError``. If the value is ``"complete"``, then the input + complete and syntactically correct. + + formatted_error : str + If the ``Future`` is rejected, this will be filled with a formatted version of + the code. This is a convenience that simplifies code and helps to avoid large + memory leaks when using from JavaScript. + + """ + + def __init__( + self, + syntax_check: (Literal["incomplete", "syntax-error", "complete"]), + ): + super().__init__() + self.syntax_check: ( + Literal["incomplete", "syntax-error", "complete"] + ) = syntax_check + self.formatted_error: str | None = None + + +class Console: + """Interactive Pyodide console + + An interactive console based on the Python standard library + `code.InteractiveConsole` that manages stream redirections and asynchronous + execution of the code. + + The stream callbacks can be modified directly as long as + `persistent_stream_redirection` isn't in effect. + + Parameters + ---------- + globals : ``dict`` + The global namespace in which to evaluate the code. Defaults to a new empty dictionary. + + stdin_callback : ``Callable[[], str]`` + Function to call at each read from ``sys.stdin``. Defaults to ``None``. + + stdout_callback : ``Callable[[str], None]`` + Function to call at each write to ``sys.stdout``. Defaults to ``None``. + + stderr_callback : ``Callable[[str], None]`` + Function to call at each write to ``sys.stderr``. Defaults to ``None``. + + persistent_stream_redirection : ``bool`` + Should redirection of standard streams be kept between calls to :any:`runcode `? + Defaults to ``False``. + + filename : ``str`` + The file name to report in error messages. Defaults to ````. + + Attributes + ---------- + globals : ``Dict[str, Any]`` + The namespace used as the global + + stdin_callback : ``Callback[[], str]`` + Function to call at each read from ``sys.stdin``. + + stdout_callback : ``Callback[[str], None]`` + Function to call at each write to ``sys.stdout``. + + stderr_callback : ``Callback[[str], None]`` + Function to call at each write to ``sys.stderr``. + + buffer : ``List[str]`` + The list of strings that have been :any:`pushed ` to the console. + + completer_word_break_characters : ``str`` + The set of characters considered by :any:`complete ` to be word breaks. + """ + + def __init__( + self, + globals: dict | None = None, + *, + stdin_callback: Callable[[], str] | None = None, + stdout_callback: Callable[[str], None] | None = None, + stderr_callback: Callable[[str], None] | None = None, + persistent_stream_redirection: bool = False, + filename: str = "", + ): + if globals is None: + globals = {"__name__": "__console__", "__doc__": None} + self.globals = globals + self._stdout = None + self._stderr = None + self.stdin_callback = stdin_callback + self.stdout_callback = stdout_callback + self.stderr_callback = stderr_callback + self.filename = filename + self.buffer: list[str] = [] + self._lock = asyncio.Lock() + self._streams_redirected = False + self._stream_generator: Generator[ + None, None, None + ] | None = None # track persistent stream redirection + if persistent_stream_redirection: + self.persistent_redirect_streams() + self._completer = rlcompleter.Completer(self.globals) + # all nonalphanums except '.' + # see https://github.com/python/cpython/blob/a4258e8cd776ba655cc54ba54eaeffeddb0a267c/Modules/readline.c#L1211 + self.completer_word_break_characters = ( + """ \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?""" + ) + self._compile = _CommandCompiler(flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) + + def persistent_redirect_streams(self): + """Redirect stdin/stdout/stderr persistently""" + if self._stream_generator: + return + self._stream_generator = self._stdstreams_redirections_inner() + assert self._stream_generator is not None + next(self._stream_generator) # trigger stream redirection + # streams will be reverted to normal when self._stream_generator is destroyed. + + def persistent_restore_streams(self): + """Restore stdin/stdout/stderr if they have been persistently redirected""" + # allowing _stream_generator to be garbage collected restores the streams + self._stream_generator = None + + @contextmanager + def redirect_streams(self) -> Generator[None, None, None]: + """A context manager to redirect standard streams. + + This supports nesting.""" + yield from self._stdstreams_redirections_inner() + + def _stdstreams_redirections_inner(self) -> Generator[None, None, None]: + """This is the generator which implements redirect_streams and the stdstreams_redirections""" + # already redirected? + if self._streams_redirected: + yield + return + redirects: list[Any] = [] + if self.stdin_callback: + stdin_name = getattr(sys.stdin, "name", "") + stdin_stream = _ReadStream(self.stdin_callback, name=stdin_name) + redirects.append(redirect_stdin(stdin_stream)) + if self.stdout_callback: + stdout_name = getattr(sys.stdout, "name", "") + stdout_stream = _WriteStream(self.stdout_callback, name=stdout_name) + redirects.append(redirect_stdout(stdout_stream)) + if self.stderr_callback: + stderr_name = getattr(sys.stderr, "name", "") + stderr_stream = _WriteStream(self.stderr_callback, name=stderr_name) + redirects.append(redirect_stderr(stderr_stream)) + try: + self._streams_redirected = True + with ExitStack() as stack: + for redirect in redirects: + stack.enter_context(redirect) + yield + finally: + self._streams_redirected = False + + def runsource(self, source: str, filename: str = "") -> ConsoleFuture: + """Compile and run source code in the interpreter. + + Returns + ------- + :any:`ConsoleFuture` + + """ + res: ConsoleFuture | None + + try: + code = self._compile(source, filename, "single") + except (OverflowError, SyntaxError, ValueError) as e: + # Case 1 + if e.__traceback__: + traceback.clear_frames(e.__traceback__) + res = ConsoleFuture(SYNTAX_ERROR) + res.set_exception(e) + res.formatted_error = self.formatsyntaxerror(e) + return res + + if code is None: + res = ConsoleFuture(INCOMPLETE) + res.set_result(None) + return res + + res = ConsoleFuture(COMPLETE) + + def done_cb(fut): + nonlocal res + assert res is not None + exc = fut.exception() + if exc: + res.formatted_error = self.formattraceback(exc) + res.set_exception(exc) + exc = None + else: + res.set_result(fut.result()) + res = None + + ensure_future(self.runcode(source, code)).add_done_callback(done_cb) + return res + + async def runcode(self, source: str, code: CodeRunner) -> Any: + """Execute a code object and return the result.""" + async with self._lock: + with self.redirect_streams(): + try: + return await code.run_async(self.globals) + finally: + sys.stdout.flush() + sys.stderr.flush() + + def formatsyntaxerror(self, e: Exception) -> str: + """Format the syntax error that just occurred. + + This doesn't include a stack trace because there isn't one. The actual + error object is stored into `sys.last_value`. + """ + sys.last_type = type(e) + sys.last_value = e + sys.last_traceback = None + return "".join(traceback.format_exception_only(type(e), e)) + + def num_frames_to_keep(self, tb): + keep_frames = False + kept_frames = 0 + # Try to trim out stack frames inside our code + for (frame, _) in traceback.walk_tb(tb): + keep_frames = keep_frames or frame.f_code.co_filename == "" + keep_frames = keep_frames or frame.f_code.co_filename == "" + if keep_frames: + kept_frames += 1 + return kept_frames + + def formattraceback(self, e: Exception) -> str: + """Format the exception that just occurred. + + The actual error object is stored into `sys.last_value`. + """ + sys.last_type = type(e) + sys.last_value = e + sys.last_traceback = e.__traceback__ + nframes = self.num_frames_to_keep(e.__traceback__) + return "".join( + traceback.format_exception(type(e), e, e.__traceback__, -nframes) + ) + + def push(self, line: str) -> ConsoleFuture: + """Push a line to the interpreter. + + The line should not have a trailing newline; it may have internal + newlines. The line is appended to a buffer and the interpreter's + runsource() method is called with the concatenated contents of the + buffer as source. If this indicates that the command was executed or + invalid, the buffer is reset; otherwise, the command is incomplete, and + the buffer is left as it was after the line was appended. + + The return value is the result of calling :any:`Console.runsource` on the current buffer + contents. + """ + self.buffer.append(line) + source = "\n".join(self.buffer) + result = self.runsource(source, self.filename) + if result.syntax_check != INCOMPLETE: + self.buffer = [] + return result + + def complete(self, source: str) -> tuple[list[str], int]: + """Use Python's rlcompleter to complete the source string using the :any:`globals ` namespace. + + Finds last "word" in the source string and completes it with rlcompleter. Word + breaks are determined by the set of characters in + :any:`completer_word_break_characters `. + + Parameters + ---------- + source : str + The source string to complete at the end. + + Returns + ------- + completions : List[str] + A list of completion strings. + start : int + The index where completion starts. + + Examples + -------- + >>> shell = Console() + >>> shell.complete("str.isa") + (['str.isalnum(', 'str.isalpha(', 'str.isascii('], 0) + >>> shell.complete("a = 5 ; str.isa") + (['str.isalnum(', 'str.isalpha(', 'str.isascii('], 8) + """ + start = max(map(source.rfind, self.completer_word_break_characters)) + 1 + source = source[start:] + if "." in source: + completions = self._completer.attr_matches(source) # type: ignore[attr-defined] + else: + completions = self._completer.global_matches(source) # type: ignore[attr-defined] + return completions, start class PyodideConsole(Console): @@ -12,9 +460,7 @@ class PyodideConsole(Console): async def runcode(self, source: str, code: CodeRunner) -> ConsoleFuture: """Execute a code object. - All exceptions are caught except SystemExit, which is reraised. - Returns ------- The return value is a dependent sum type with the following possibilities: @@ -26,3 +472,21 @@ async def runcode(self, source: str, code: CodeRunner) -> ConsoleFuture: await loadPackagesFromImports(source) return await super().runcode(source, code) + + +def repr_shorten( + value: Any, limit: int = 1000, split: int | None = None, separator: str = "..." +) -> str: + """Compute the string representation of ``value`` and shorten it + if necessary. + + If it is longer than ``limit`` then return the firsts ``split`` + characters and the last ``split`` characters separated by '...'. + Default value for ``split`` is `limit // 2`. + """ + if split is None: + split = limit // 2 + text = repr(value) + if len(text) > limit: + text = f"{text[:split]}{separator}{text[-split:]}" + return text diff --git a/src/py/pyodide/http.py b/src/py/pyodide/http.py index 3f85cd6c17c..0e4c5e2239b 100644 --- a/src/py/pyodide/http.py +++ b/src/py/pyodide/http.py @@ -1,8 +1,8 @@ +import json from io import StringIO +from typing import Any, BinaryIO, TextIO + from ._core import JsProxy, to_js -from typing import Any -import json -from io import IOBase try: from js import XMLHttpRequest @@ -10,7 +10,7 @@ pass from ._core import IN_BROWSER -from ._util import unpack_buffer_archive +from ._package_loader import unpack_buffer __all__ = [ "open_url", @@ -65,7 +65,7 @@ def __init__(self, url: str, js_response: JsProxy): def body_used(self) -> bool: """Has the response been used yet? - (If so, attempting to retreive the body again will raise an OSError.) + (If so, attempting to retrieve the body again will raise an OSError.) """ return self.js_response.bodyUsed @@ -149,7 +149,7 @@ async def bytes(self) -> bytes: self._raise_if_failed() return (await self.buffer()).to_bytes() - async def _into_file(self, f: IOBase): + async def _into_file(self, f: TextIO | BinaryIO): """Write the data into an empty file with no copy. Warning: should only be used when f is an empty file, otherwise it may @@ -178,7 +178,7 @@ async def _create_file(self, path: str): an ``OSError`` """ with open(path, "x") as f: - await self._into_file(f) # type: ignore + await self._into_file(f) async def unpack_archive(self, *, extract_dir=None, format=None): """Treat the data as an archive and unpack it into target directory. @@ -202,13 +202,11 @@ async def unpack_archive(self, *, extract_dir=None, format=None): """ buf = await self.buffer() filename = self._url.rsplit("/", -1)[-1] - unpack_buffer_archive( - buf, filename=filename, format=format, extract_dir=extract_dir - ) + unpack_buffer(buf, filename=filename, format=format, extract_dir=extract_dir) async def pyfetch(url: str, **kwargs) -> FetchResponse: - """Fetch the url and return the response. + r"""Fetch the url and return the response. This functions provides a similar API to the JavaScript `fetch function `_ however it is @@ -226,7 +224,8 @@ async def pyfetch(url: str, **kwargs) -> FetchResponse: `_. """ if IN_BROWSER: - from js import fetch as _jsfetch, Object + from js import Object + from js import fetch as _jsfetch return FetchResponse( url, await _jsfetch(url, to_js(kwargs, dict_converter=Object.fromEntries)) diff --git a/src/py/pyodide/webloop.py b/src/py/pyodide/webloop.py index e2a822299a0..846d2238de9 100644 --- a/src/py/pyodide/webloop.py +++ b/src/py/pyodide/webloop.py @@ -5,8 +5,7 @@ import traceback from typing import Callable - -from ._core import create_once_callable, IN_BROWSER +from ._core import IN_BROWSER, create_once_callable if IN_BROWSER: from js import setTimeout @@ -96,7 +95,9 @@ async def wrapper(): # Scheduling methods: use browser.setTimeout to schedule tasks on the browser event loop. # - def call_soon(self, callback: Callable, *args, context: contextvars.Context = None): + def call_soon( + self, callback: Callable, *args, context: contextvars.Context | None = None + ): """Arrange for a callback to be called as soon as possible. Any positional arguments after the callback will be passed to @@ -108,7 +109,7 @@ def call_soon(self, callback: Callable, *args, context: contextvars.Context = No return self.call_later(delay, callback, *args, context=context) def call_soon_threadsafe( - self, callback: Callable, *args, context: contextvars.Context = None + self, callback: Callable, *args, context: contextvars.Context | None = None ): """Like ``call_soon()``, but thread-safe. @@ -121,7 +122,7 @@ def call_later( delay: float, callback: Callable, *args, - context: contextvars.Context = None, + context: contextvars.Context | None = None, ): """Arrange for a callback to be called at a given time. @@ -157,7 +158,7 @@ def call_at( when: float, callback: Callable, *args, - context: contextvars.Context = None, + context: contextvars.Context | None = None, ): """Like ``call_later()``, but uses an absolute time. @@ -217,14 +218,14 @@ def create_task(self, coro, *, name=None): self._check_closed() if self._task_factory is None: task = asyncio.tasks.Task(coro, loop=self, name=name) - if task._source_traceback: + if task._source_traceback: # type: ignore[attr-defined] # Added comment: # this only happens if get_debug() returns True. # In that case, remove create_task from _source_traceback. - del task._source_traceback[-1] + del task._source_traceback[-1] # type: ignore[attr-defined] else: task = self._task_factory(self, coro) - asyncio.tasks._set_task_name(task, name) + asyncio.tasks._set_task_name(task, name) # type: ignore[attr-defined] return task @@ -374,7 +375,8 @@ def call_exception_handler(self, context): traceback.print_exc() -class WebLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore +# Type issue fixed in next release of mypy (0.940) +class WebLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[misc, valid-type] """ A simple event loop policy for managing WebLoop based event loops. """ @@ -390,7 +392,7 @@ def get_event_loop(self): def new_event_loop(self): """Create a new event loop""" - self._default_loop = WebLoop() + self._default_loop = WebLoop() # type: ignore[abstract] return self._default_loop def set_event_loop(self, loop: asyncio.AbstractEventLoop): diff --git a/src/py/setup.cfg b/src/py/setup.cfg index 54638a4829a..998627f2340 100644 --- a/src/py/setup.cfg +++ b/src/py/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pyodide -version = 0.19.0 +version = 0.20.0 author = Pyodide developers description = "A Python package providing core interpreter functionality for Pyodide." long_description = file: README.md diff --git a/src/templates/console.html b/src/templates/console.html index c5c3ef0293c..b6cdb30d1cb 100644 --- a/src/templates/console.html +++ b/src/templates/console.html @@ -3,9 +3,10 @@ - + + @@ -24,9 +25,7 @@ } async function main() { - globalThis.pyodide = await loadPyodide({ - indexURL: "{{ PYODIDE_BASE_URL }}", - }); + globalThis.pyodide = await loadPyodide(); let namespace = pyodide.globals.get("dict")(); pyodide.runPython( ` @@ -45,7 +44,7 @@ def clear_console(): pyconsole.buffer = [] `, - namespace + { globals: namespace } ); let repr_shorten = namespace.get("repr_shorten"); let banner = namespace.get("BANNER"); @@ -68,7 +67,7 @@ async function interpreter(command) { let unlock = await lock(); term.pause(); - // multiline should be splitted (useful when pasting) + // multiline should be split (useful when pasting) for (const c of command.split("\n")) { let fut = pyconsole.push(c); term.set_prompt(fut.syntax_check === "incomplete" ? ps2 : ps1); @@ -128,11 +127,20 @@ keymap: { "CTRL+C": async function (event, original) { clear_console(); - term.echo_command(); + term.enter(); term.echo("KeyboardInterrupt"); term.set_command(""); term.set_prompt(ps1); }, + TAB: (event, original) => { + const command = term.before_cursor(); + // Disable completion for whitespaces. + if (command.trim() === "") { + term.insert("\t"); + return false; + } + return original(event); + }, }, }); window.term = term; @@ -141,7 +149,7 @@ term.error(s.trimEnd()); }; term.ready = Promise.resolve(); - pyodide._module.on_fatal = async (e) => { + pyodide._api.on_fatal = async (e) => { term.error( "Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers." ); diff --git a/src/templates/module_test.html b/src/templates/module_test.html new file mode 100644 index 00000000000..6b4ad3dbbce --- /dev/null +++ b/src/templates/module_test.html @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/src/templates/module_webworker.js b/src/templates/module_webworker.js new file mode 100644 index 00000000000..0fd899d6660 --- /dev/null +++ b/src/templates/module_webworker.js @@ -0,0 +1,26 @@ +import { loadPyodide } from "./pyodide.mjs"; + +onmessage = async function (e) { + try { + const data = e.data; + for (let key of Object.keys(data)) { + if (key !== "python") { + // Keys other than python must be arguments for the python script. + // Set them on self, so that `from js import key` works. + self[key] = data[key]; + } + } + + if (!loadPyodide.inProgress) { + self.pyodide = await loadPyodide(); + } + await self.pyodide.loadPackagesFromImports(data.python); + let results = await self.pyodide.runPythonAsync(data.python); + self.postMessage({ results }); + } catch (e) { + // if you prefer messages with the error + self.postMessage({ error: e.message + "\n" + e.stack }); + // if you prefer onerror events + // setTimeout(() => { throw err; }); + } +}; diff --git a/src/templates/test.html b/src/templates/test.html index 09915ecfa8f..e5de020eb92 100644 --- a/src/templates/test.html +++ b/src/templates/test.html @@ -2,6 +2,7 @@ + pyodide