[
  {
    "path": ".coveragerc",
    "content": "[run]\nrelative_files = True\nomit =\n    *test*\n\n[report]\nexclude_lines =\n    pragma: no cover\n    def __repr__\n    raise NotImplementedError\n    if __name__ == .__main__.:"
  },
  {
    "path": ".deepsource.toml",
    "content": "version = 1\n\ntest_patterns = [\"tests/**\"]\n\nexclude_patterns = [\"testapps/**\"]\n\n[[analyzers]]\nname = \"python\"\nenabled = true\n\n  [analyzers.meta]\n  runtime_version = \"3.x.x\""
  },
  {
    "path": ".dockerignore",
    "content": "venv/\n.buildozer/\n**/.pytest_cache/\n.tox/\nbin/\n*.pyc\n**/__pycache__\n*.egg-info/\ndocker-data/\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!--\nThe issue tracker is a tool to address bugs NOT a support platform.\nPlease use the Discord community or Stack Overflow for support questions,\nmore information at https://github.com/kivy/python-for-android#support\n-->\n\n### Checklist\n\n- [ ] the issue is indeed a bug and not a support request\n- [ ] issue doesn't already exist: https://github.com/kivy/python-for-android/issues\n- [ ] I have a short, runnable example that reproduces the issue\n- [ ] I reproduced the problem with the latest development version (`p4a.branch = develop`)\n- [ ] I used the grave accent (aka backticks) to format code or logs when appropriated\n\n### Versions\n\n- Python:\n- OS:\n- Kivy:\n- Cython:\n- OpenJDK:\n\n### Description\n\n// REPLACE ME: What are you trying to get done, what has happened, what went wrong, and what did you expect?\n\n### buildozer.spec\n\nCommand:\n```sh\n// REPLACE ME: buildozer command ran? e.g. buildozer android debug\n// Keep the triple grave accent (aka backquote/backtick) to have the code formatted\n```\n\nSpec file:\n```\n// REPLACE ME: Paste your buildozer.spec file here\n```\n\n### Logs\n\n```\n// REPLACE ME: Paste the build output containing the error\n// Keep the triple grave accent (a.k.a. backquote/backtick) to have the code formatted\n```\n"
  },
  {
    "path": ".github/workflows/custom-build.yml",
    "content": "name: Custom build\n\non:\n  workflow_dispatch:\n    inputs:\n      arch:\n        description: \"Comma separated architectures (e.g., armeabi-v7a, arm64-v8a, x86_64, x86)\"\n        required: true\n        default: \"armeabi-v7a,arm64-v8a,x86_64,x86\"\n      artifact:\n        description: \"Artifact type\"\n        required: true\n        default: \"apk\"\n        type: choice\n        options:\n          - \"aab\"\n          - \"aar\"\n          - \"apk\"\n      bootstrap:\n        description: \"Bootstrap to use\"\n        required: true\n        default: \"sdl2\"\n        type: choice\n        options:\n          - \"qt\"\n          - \"sdl2\"\n          - \"service_library\"\n          - \"service_only\"\n          - \"webview\"\n      mode:\n        description: \"Build mode\"\n        required: true\n        default: \"debug\"\n        type: choice\n        options:\n          - \"debug\"\n          - \"release\"\n      os:\n        description: \"Operating system to run on\"\n        required: true\n        default: \"ubuntu-latest\"\n        type: choice\n        options:\n          - \"ubuntu-latest\"\n          - \"macos-latest\"\n      requirements:\n        description: \"Comma separated requirements\"\n        required: true\n        default: \"python3,kivy\"\n\nenv:\n  APK_ARTIFACT_FILENAME: bdist_unit_tests_app-debug-1.1.apk\n  AAB_ARTIFACT_FILENAME: bdist_unit_tests_app-release-1.1.aab\n  AAR_ARTIFACT_FILENAME: bdist_unit_tests_app-release-1.1.aar\n  PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE: 0\n\njobs:\n  build:\n    name: Build test APP [ ${{ github.event.inputs.arch }} | ${{ github.event.inputs.artifact }} | ${{ github.event.inputs.bootstrap }} | ${{ github.event.inputs.mode }} | ${{ github.event.inputs.os }} | ${{ github.event.inputs.requirements }}]\n    runs-on: ${{ github.event.inputs.os }}\n    steps:\n      - name: Checkout python-for-android\n        uses: actions/checkout@v4\n      - name: Pull the python-for-android docker image\n        run: make docker/pull\n      - name: Build python-for-android docker image\n        run: make docker/build\n      - name: Build multi-arch artifact with docker\n        run: |\n          docker run --name p4a-latest kivy/python-for-android make ARCH=${{ github.event.inputs.arch }} ARTIFACT=${{ github.event.inputs.artifact }} BOOTSTRAP=${{ github.event.inputs.bootstrap }} MODE=${{ github.event.inputs.mode }} REQUIREMENTS=${{ github.event.inputs.requirements }} testapps-generic\n      - name: Copy produced artifacts from docker container (*.apk, *.aab)\n        if: github.event.inputs.bootstrap != 'service_library'\n        run: |\n          mkdir -p dist\n          docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.APK_ARTIFACT_FILENAME }} dist/ || true\n          docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.AAB_ARTIFACT_FILENAME }} dist/ || true\n      - name: Copy produced artifacts from docker container (*.aar)\n        if: github.event.inputs.bootstrap == 'service_library'\n        run: |\n          mkdir -p dist\n          docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.AAR_ARTIFACT_FILENAME }} dist/\n      - name: Rename artifacts to include the build platform name (*.apk, *.aab, *.aar)\n        run: |\n          if [ -f dist/${{ env.APK_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.APK_ARTIFACT_FILENAME }} dist/${{ github.event.inputs.os }}-${{ github.event.inputs.bootstrap }}-${{ env.APK_ARTIFACT_FILENAME }}; fi\n          if [ -f dist/${{ env.AAB_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.AAB_ARTIFACT_FILENAME }} dist/${{ github.event.inputs.os }}-${{ github.event.inputs.bootstrap }}-${{ env.AAB_ARTIFACT_FILENAME }}; fi\n          if [ -f dist/${{ env.AAR_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.AAR_ARTIFACT_FILENAME }} dist/${{ github.event.inputs.os }}-${{ github.event.inputs.bootstrap }}-${{ env.AAR_ARTIFACT_FILENAME }}; fi\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ github.event.inputs.os }}-${{ github.event.inputs.bootstrap }}-artifacts\n          path: dist\n      # Cleanup the container after all steps are done\n      - name: Cleanup Docker container\n        run: docker rm p4a-latest\n        if: always()\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: Docker\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - develop\n    tags:\n      - \"*\"\n  pull_request:\n\njobs:\n  docker:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: docker/setup-buildx-action@v3\n      - run: make docker/build\n      - name: docker login\n        if: github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags/')\n        env:\n          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}\n          DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}\n        run: make docker/login\n      - name: docker push\n        if: github.ref == 'refs/heads/develop'\n        run: make docker/push\n      - name: docker push (tag)\n        if: startsWith(github.ref, 'refs/tags/')\n        run: |\n          make docker/tag DOCKER_TAG=${GITHUB_REF#refs/tags/}\n          make docker/push\n"
  },
  {
    "path": ".github/workflows/no-response.yml",
    "content": "name: No Response\n\n# Both `issue_comment` and `scheduled` event types are required for this Action\n# to work properly.\non:\n  issue_comment:\n    types: [created]\n  schedule:\n    # Schedule for an arbitrary time (5am) once every day\n    - cron: '* 5 * * *'\n\njobs:\n  noResponse:\n    # Don't run if in a fork\n    if: github.repository_owner == 'kivy'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: lee-dohm/no-response@9bb0a4b5e6a45046f00353d5de7d90fb8bd773bb\n        # This commit hash targets release v0.5.0 of lee-dohm/no-response.\n        # Targeting a commit hash instead of a tag has been done for security reasons.\n        # Please be aware that the commit hash specifically targets the \"Automatic compilation\"\n        # done by `github-actions[bot]` as the `no-response` Action needs to be compiled.\n        with:\n          token: ${{ github.token }}\n          daysUntilClose: 42\n          responseRequiredLabel: 'awaiting-reply'\n          closeComment: >\n                This issue has been automatically closed because there has been no response\n                to our request for more information from the original author. With only the\n                information that is currently in the issue, we don't have the means\n                to take action. Please reach out if you have or find the answers we need so\n                that we can investigate further.\n"
  },
  {
    "path": ".github/workflows/push.yml",
    "content": "name: Unit tests & build apps\n\non: ['push', 'pull_request']\n\nenv:\n  APK_ARTIFACT_FILENAME: bdist_unit_tests_app-debug-1.1.apk\n  AAB_ARTIFACT_FILENAME: bdist_unit_tests_app-release-1.1.aab\n  AAR_ARTIFACT_FILENAME: bdist_unit_tests_app-release-1.1.aar\n  PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE: 0\n\nconcurrency:\n  group: build-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n\n  flake8:\n    name: Flake8 tests\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout python-for-android\n      uses: actions/checkout@v5\n    - name: Set up Python 3.x\n      uses: actions/setup-python@v6\n      with:\n        python-version: 3.x\n    - name: Run flake8\n      run: |\n        python -m pip install --upgrade pip\n        pip install tox>=2.0\n        tox -e pep8\n\n  spotless:\n    name: Java Spotless check\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout python-for-android\n      uses: actions/checkout@v5\n    - name: Set up Java 17\n      uses: actions/setup-java@v4\n      with:\n        distribution: 'temurin'\n        java-version: '17'\n    - name: Set up Gradle\n      uses: gradle/actions/setup-gradle@v4\n    - name: Run Spotless check\n      working-directory: pythonforandroid/bootstraps\n      run: ./common/build/gradlew spotlessCheck\n\n  test:\n    name: Pytest [Python ${{ matrix.python-version }} | ${{ matrix.os }}]\n    needs: [flake8, spotless]\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']\n        os: [ubuntu-latest, macos-latest]\n    steps:\n    - name: Checkout python-for-android\n      uses: actions/checkout@v5\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n        allow-prereleases: true\n    - name: Tox tests\n      run: |\n        python -m pip install --upgrade pip\n        pip install tox>=2.0\n        make test\n    - name: Coveralls\n      uses: AndreMiras/coveralls-python-action@develop\n      if: ${{ matrix.os == 'ubuntu-latest' }}\n      with:\n        parallel: true\n        flag-name: run-${{ matrix.os }}-${{ matrix.python-version }}\n\n  ubuntu_build:\n    name: Build test APP [ ${{ matrix.runs_on }} | ${{ matrix.bootstrap.name }} ]\n    needs: [flake8, spotless]\n    runs-on: ${{ matrix.runs_on }}\n    strategy:\n      matrix:\n        runs_on: [ubuntu-latest]\n        bootstrap:\n          - name: sdl2\n            target: testapps-with-numpy\n          # - name: sdl2_scipy  # TODO: Re-enable this failing test.\n          #   target: testapps-with-scipy\n          - name: webview\n            target: testapps-webview\n          - name: service_library\n            target: testapps-service_library-aar\n          - name: qt\n            target: testapps-qt\n    steps:\n    - name: Maximize build space\n      uses: easimon/maximize-build-space@v10\n      with:\n        root-reserve-mb: 4096\n        swap-size-mb: 1024\n        remove-dotnet: 'true'\n        remove-android: 'true'\n        remove-haskell: 'true'\n        remove-codeql: 'true'\n        remove-docker-images: 'true'\n    - name: Checkout python-for-android\n      uses: actions/checkout@v5\n    - name: Relocate Docker data directory\n      run: |\n        sudo systemctl stop docker\n        sudo mkdir -p \"${GITHUB_WORKSPACE}/docker-data\"\n        echo '{\"data-root\": \"'${GITHUB_WORKSPACE}/docker-data'\"}' | sudo tee /etc/docker/daemon.json\n        sudo systemctl start docker\n        docker info | grep \"Docker Root Dir\"\n    - name: Build python-for-android docker image\n      run: |\n        docker build --tag=kivy/python-for-android .\n    - name: Build multi-arch ${{ matrix.bootstrap.target }} artifact with docker\n      run: |\n        docker run --name p4a-latest kivy/python-for-android make ${{ matrix.bootstrap.target }}\n    - name: Copy produced artifacts from docker container (*.apk, *.aab)\n      if: matrix.bootstrap.name != 'service_library'\n      run: |\n        mkdir -p dist\n        docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.APK_ARTIFACT_FILENAME }} dist/\n        docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.AAB_ARTIFACT_FILENAME }} dist/\n    - name: Copy produced artifacts from docker container (*.aar)\n      if: matrix.bootstrap.name == 'service_library'\n      run: |\n        mkdir -p dist\n        docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.AAR_ARTIFACT_FILENAME }} dist/\n    - name: Rename artifacts to include the build platform name (*.apk, *.aab, *.aar)\n      run: |\n        if [ -f dist/${{ env.APK_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.APK_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.APK_ARTIFACT_FILENAME }}; fi\n        if [ -f dist/${{ env.AAB_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.AAB_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.AAB_ARTIFACT_FILENAME }}; fi\n        if [ -f dist/${{ env.AAR_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.AAR_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.AAR_ARTIFACT_FILENAME }}; fi\n    - name: Upload artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-artifacts\n        path: dist\n\n  macos_build:\n    name: Build test APP [ ${{ matrix.runs_on }} | ${{ matrix.bootstrap.name }} ]\n    needs: [flake8, spotless]\n    runs-on: ${{ matrix.runs_on }}\n    strategy:\n      matrix:\n        # macos-latest (ATM macos-15) runs on Apple Silicon,\n        # macos-15-intel runs on Intel\n        runs_on: ['macos-latest', 'macos-15-intel']\n        bootstrap:\n          - name: sdl2\n            target: testapps-with-numpy\n          - name: webview\n            target: testapps-webview\n    env:\n      ANDROID_HOME: ${HOME}/.android\n      ANDROID_SDK_ROOT: ${HOME}/.android/android-sdk\n      ANDROID_SDK_HOME: ${HOME}/.android/android-sdk\n      ANDROID_NDK_HOME: ${HOME}/.android/android-ndk\n    steps:\n      - name: Checkout python-for-android\n        uses: actions/checkout@v5\n      - name: Set up Python 3.x\n        uses: actions/setup-python@v6\n        with:\n          python-version: 3.x\n      - name: Install python-for-android\n        run: |\n          python3 -m pip install --editable .\n      - name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)\n        run: |\n          python3 pythonforandroid/prerequisites.py\n      - name: Install dependencies (Legacy)\n        run: |\n          make --file ci/makefiles/osx.mk\n      - name: Build multi-arch apk Python 3 (armeabi-v7a, arm64-v8a, x86_64, x86)\n        run: |\n          make ${{ matrix.bootstrap.target }}\n      - name: Copy produced artifacts into dist/ (*.apk, *.aab)\n        run: |\n          mkdir -p dist\n          cp testapps/on_device_unit_tests/*.apk dist/\n          cp testapps/on_device_unit_tests/*.aab dist/\n          ls -l dist/\n      - name: Rename artifacts to include the build platform name (*.apk, *.aab)\n        run: |\n          if [ -f dist/${{ env.APK_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.APK_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.APK_ARTIFACT_FILENAME }}; fi\n          if [ -f dist/${{ env.AAB_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.AAB_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.AAB_ARTIFACT_FILENAME }}; fi\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-artifacts\n          path: dist\n\n  test_on_emulator:\n    name: Run App on Emulator\n    needs: ubuntu_build\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v5\n      - name: Download Artifacts\n        uses: actions/download-artifact@v5\n        with:\n          name: ubuntu-latest-sdl2-artifacts\n          path: dist/\n\n      - name: Setup and start Android Emulator\n        uses: reactivecircus/android-emulator-runner@v2\n        with:\n          api-level: 30\n          arch: x86_64\n          script: ci/run_emulator_tests.sh\n\n  ubuntu_rebuild_updated_recipes:\n    name: Test updated recipes for arch ${{ matrix.android_arch }} [ ubuntu-latest ]\n    needs: [flake8, spotless]\n    runs-on: ubuntu-latest\n    # continue on error to see failures across all architectures\n    continue-on-error: true\n    strategy:\n      matrix:\n        android_arch: [\"arm64-v8a\", \"armeabi-v7a\", \"x86_64\", \"x86\"]\n    env:\n      REBUILD_UPDATED_RECIPES_EXTRA_ARGS: --arch=${{ matrix.android_arch }}\n    steps:\n    - name: Maximize build space\n      uses: easimon/maximize-build-space@v10\n      with:\n        root-reserve-mb: 4096\n        swap-size-mb: 1024\n        remove-dotnet: 'true'\n        remove-android: 'true'\n        remove-haskell: 'true'\n        remove-codeql: 'true'\n        remove-docker-images: 'true'\n    - name: Checkout python-for-android (all-history)\n      uses: actions/checkout@v5\n      with:\n        fetch-depth: 0\n    - name: Relocate Docker data directory\n      run: |\n        sudo systemctl stop docker\n        sudo mkdir -p \"${GITHUB_WORKSPACE}/docker-data\"\n        echo '{\"data-root\": \"'${GITHUB_WORKSPACE}/docker-data'\"}' | sudo tee /etc/docker/daemon.json\n        sudo systemctl start docker\n        docker info | grep \"Docker Root Dir\"\n    - name: Pull docker image\n      run: |\n        make docker/pull\n    - name: Rebuild updated recipes\n      run: |\n        make docker/run/make/rebuild_updated_recipes\n\n  macos_rebuild_updated_recipes:\n    name: Test updated recipes for arch ${{ matrix.android_arch }} [ ${{ matrix.runs_on }} ]\n    needs: [flake8, spotless]\n    runs-on: ${{ matrix.runs_on }}\n    # continue on error to see failures across all architectures\n    continue-on-error: true\n    strategy:\n      matrix:\n        android_arch: [\"arm64-v8a\", \"armeabi-v7a\", \"x86_64\", \"x86\"]\n        # macos-latest (ATM macos-15) runs on Apple Silicon,\n        # macos-15-intel runs on Intel\n        runs_on: ['macos-latest', 'macos-15-intel']\n    env:\n      ANDROID_HOME: ${HOME}/.android\n      ANDROID_SDK_ROOT: ${HOME}/.android/android-sdk\n      ANDROID_SDK_HOME: ${HOME}/.android/android-sdk\n      ANDROID_NDK_HOME: ${HOME}/.android/android-ndk\n      REBUILD_UPDATED_RECIPES_EXTRA_ARGS: --arch=${{ matrix.android_arch }}\n    steps:\n      - name: Checkout python-for-android (all-history)\n        uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n      - name: Set up Python 3.x\n        uses: actions/setup-python@v6\n        with:\n          python-version: 3.x\n      - name: Install python-for-android\n        run: |\n          python3 -m pip install --editable .\n      - name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)\n        run: |\n          python3 pythonforandroid/prerequisites.py\n      - name: Install dependencies (Legacy)\n        run: |\n          make --file ci/makefiles/osx.mk\n      - name: Rebuild updated recipes\n        run: |\n          make rebuild_updated_recipes\n\n  coveralls_finish:\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n    - name: Coveralls Finished\n      uses: AndreMiras/coveralls-python-action@develop\n      with:\n        parallel-finished: true\n\n  documentation:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v5\n    - name: Requirements\n      run: |\n        python -m pip install --upgrade pip\n        pip install -r doc/requirements.txt\n    - name: Check links\n      run: sphinx-build -b linkcheck doc/source doc/build\n    - name: Generate documentation\n      run: sphinx-build doc/source doc/build\n\n"
  },
  {
    "path": ".github/workflows/pypi-release.yml",
    "content": "name: PyPI release\non: [push]\n\njobs:\n  pypi_release:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up Python 3.x\n      uses: actions/setup-python@v5\n      with:\n        python-version: '3.x'\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade setuptools wheel twine\n    - name: Build\n      run: |\n        python setup.py sdist bdist_wheel\n        twine check dist/*\n    - name: Publish package\n      if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')\n      uses: pypa/gh-action-pypi-publish@v1.13.0\n      with:\n        user: __token__\n        password: ${{ secrets.pypi_password }}\n"
  },
  {
    "path": ".github/workflows/support.yml",
    "content": "# When a user creates an issue that is actually a support request, it should\n# be closed with a friendly comment.\n#\n# This triggers on an issue being labelled with the `support` tag.\n\nname: 'Support Requests'\n\non:\n  issues:\n    types: [labeled, unlabeled, reopened]\n\npermissions:\n  issues: write\n\njobs:\n  action:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: dessant/support-requests@v4\n        with:\n          github-token: ${{ github.token }}\n          support-label: 'support'\n          issue-comment: >\n            👋 @{issue-author},\n            \n            Sorry to hear you are having difficulties with Kivy's python-for-android; Kivy unites a number of different technologies, so building apps can be temperamental.\n\n            We try to use GitHub issues only to track work for developers to do to fix bugs and add new features to python-for-android. \n            \n            However, this issue appears to be a support request. Please use our\n            [support channels](https://github.com/kivy/python-for-android/blob/master/CONTACT.md)\n            to get help with the project.\n\n            If you're having trouble installing python-for-android,\n            please see our [quickstart](https://python-for-android.readthedocs.io/en/latest/quickstart) guide.\n\n            If you're having trouble using python-for-android,\n            please see our [troubleshooting guide](https://python-for-android.readthedocs.io/en/latest/troubleshooting) \n            and [FAQ](https://github.com/kivy/python-for-android/blob/master/FAQ.md).\n\n            Let us know if this comment was made in error, and we'll be happy\n            to reopen the issue.\n\n          close-issue: true\n          lock-issue: false\n"
  },
  {
    "path": ".gitignore",
    "content": "*.swp\n*.swo\n*~\n\n#ECLIPSE + PYDEV\n.project\n.pydevproject\n\n.deps\n\n.optional-deps\n\n*.pyc\n*.pyo\n*.apk\n.packages\npython_for_android.egg-info\n/build/\ndoc/build\n__pycache__/\nvenv/\n\n#idea/pycharm\n.idea/\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\ncoverage.xml\n*.cover\n.pytest_cache/\n\n# testapp's build folder\ntestapps/build/\n\n# Gradle build artifacts (Java linting)\npythonforandroid/bootstraps/.gradle/\npythonforandroid/bootstraps/build/\n\n# Dolphin (the KDE file manager autogenerates the file `.directory`)\n.directory\n"
  },
  {
    "path": ".projectile",
    "content": ""
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# Read the Docs configuration file for Sphinx projects\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\nversion: 2\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3\"\n\npython:\n  install:\n    - requirements: doc/requirements.txt\n\nsphinx:\n  configuration: doc/source/conf.py"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [v2024.01.21](https://github.com/kivy/python-for-android/tree/v2024.01.21)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2023.09.16...v2024.01.21)\n\n**Fixed bugs:**\n\n- Update documentation copyright [\\#2921](https://github.com/kivy/python-for-android/issues/2921)\n- Support mail address is broken [\\#2899](https://github.com/kivy/python-for-android/issues/2899)\n- doc/macos/jdk: invalid brew install command provided. [\\#2896](https://github.com/kivy/python-for-android/issues/2896)\n- pyzmq recipe build fail [\\#2818](https://github.com/kivy/python-for-android/issues/2818)\n- Existing distribution not detected due to pip package casing mismatch [\\#2494](https://github.com/kivy/python-for-android/issues/2494)\n- unknown argument \"fp-model\" and strict is not a directory or a file [\\#2359](https://github.com/kivy/python-for-android/issues/2359)\n- Copy past is not working on kivy mobile app [\\#2270](https://github.com/kivy/python-for-android/issues/2270)\n- Flaky test failure in blacklist\\(?\\) - investigation needed [\\#1781](https://github.com/kivy/python-for-android/issues/1781)\n- Problem with loading gevent: BadZipfile: File is not a zip file [\\#1739](https://github.com/kivy/python-for-android/issues/1739)\n- ImportError when importing files containing \\N{name} escape sequence [\\#1060](https://github.com/kivy/python-for-android/issues/1060)\n- Error with permission specification via setup.cfg [\\#985](https://github.com/kivy/python-for-android/issues/985)\n\n**Closed issues:**\n\n- Build failed: Could not find `android` or `sdkmanager` binaries in Android SDK [\\#2956](https://github.com/kivy/python-for-android/issues/2956)\n- Libffi - configure: error: C compiler cannot create executables \\(WSL 2\\) [\\#2953](https://github.com/kivy/python-for-android/issues/2953)\n- G [\\#2951](https://github.com/kivy/python-for-android/issues/2951)\n- Hh [\\#2949](https://github.com/kivy/python-for-android/issues/2949)\n- Can't build for Android on macOS on M2 [\\#2947](https://github.com/kivy/python-for-android/issues/2947)\n- BroadcastReceiver does not invoke the callback [\\#2946](https://github.com/kivy/python-for-android/issues/2946)\n- Add  pdf2docx library recipe [\\#2941](https://github.com/kivy/python-for-android/issues/2941)\n- use build aar in kotlin app ,can't load /lib/arm64/libpybundle.so file  [\\#2940](https://github.com/kivy/python-for-android/issues/2940)\n- Feature Request: Pymssql [\\#2936](https://github.com/kivy/python-for-android/issues/2936)\n- LXML v4.8.0 fails to build. [\\#2928](https://github.com/kivy/python-for-android/issues/2928)\n- Trying to apply a plugin fails [\\#2926](https://github.com/kivy/python-for-android/issues/2926)\n- ModuleNotFoundError: No module named '\\_sysconfigdata\\_\\_darwin\\_darwin' [\\#2925](https://github.com/kivy/python-for-android/issues/2925)\n- ReadTheDocs version is unclear. [\\#2920](https://github.com/kivy/python-for-android/issues/2920)\n- How to get real file path from uri  [\\#2911](https://github.com/kivy/python-for-android/issues/2911)\n- And  [\\#2910](https://github.com/kivy/python-for-android/issues/2910)\n- ModuleNotFoundError: No module named 'backports'\n [\\#2909](https://github.com/kivy/python-for-android/issues/2909)\n- not able to access files unless connected to adb once  [\\#2907](https://github.com/kivy/python-for-android/issues/2907)\n- opening files in other apps  [\\#2906](https://github.com/kivy/python-for-android/issues/2906)\n-  ImportError: dlopen failed: cannot locate symbol \"\\_ZTVSt9bad\\_alloc\" [\\#2903](https://github.com/kivy/python-for-android/issues/2903)\n- Fails to build pyjnius [\\#2902](https://github.com/kivy/python-for-android/issues/2902)\n- Kivy app crashes on startup [\\#2895](https://github.com/kivy/python-for-android/issues/2895)\n- aar file does not import properly in version v2023.09.16 [\\#2894](https://github.com/kivy/python-for-android/issues/2894)\n- App is crashing with Pyrebase4 [\\#2893](https://github.com/kivy/python-for-android/issues/2893)\n- shared libs builds with 32 bit arch instead of 64 bit [\\#2888](https://github.com/kivy/python-for-android/issues/2888)\n- liblzma download error [\\#2885](https://github.com/kivy/python-for-android/issues/2885)\n- Misconfiguration causing failure in compilation. [\\#2879](https://github.com/kivy/python-for-android/issues/2879)\n- cygrpc.so is for EM\\_X86\\_64 \\(62\\) instead of EM\\_AARCH64 \\(183\\) [\\#2853](https://github.com/kivy/python-for-android/issues/2853)\n- Are you able to build cffi==1.15.1?  [\\#2847](https://github.com/kivy/python-for-android/issues/2847)\n-  java.lang.IllegalStateException [\\#2844](https://github.com/kivy/python-for-android/issues/2844)\n- \\[BUG\\]: ctypes: AttributeError: undefined symbol: PyCapsule\\_New [\\#2840](https://github.com/kivy/python-for-android/issues/2840)\n- kivy can't load image in requesturl android [\\#2832](https://github.com/kivy/python-for-android/issues/2832)\n- Feature Request: Add Python `3.11` support [\\#2798](https://github.com/kivy/python-for-android/issues/2798)\n- Error Build APK FIle using Flask [\\#2783](https://github.com/kivy/python-for-android/issues/2783)\n- macOS: gwadlew fails at build tools stage \\(newest build tools is 34.0.0-rc3, brew/openjdk@20\\). [\\#2781](https://github.com/kivy/python-for-android/issues/2781)\n- Kivy python Error loading video on some android device  [\\#2780](https://github.com/kivy/python-for-android/issues/2780)\n- buildozer/p4a.prerequisites: enable automation build with no questions asked. [\\#2778](https://github.com/kivy/python-for-android/issues/2778)\n- \\_python\\_bundle does not exist...this not looks good, all python recipes should have this folder, should we expect a crash soon? [\\#2773](https://github.com/kivy/python-for-android/issues/2773)\n- Background service implemented using Pyjnius does not auto-restart on Kivy APK close [\\#2772](https://github.com/kivy/python-for-android/issues/2772)\n- \\[JVM\\]: FLAG\\_IMMUTABLE or FLAG\\_MUTABLE is required when a PendingIntent is created [\\#2759](https://github.com/kivy/python-for-android/issues/2759)\n- there is an issue with playing video from URL on the latest p4a releases [\\#2744](https://github.com/kivy/python-for-android/issues/2744)\n- App crashes at launch on specific devices \\(\\[libpython3.9.so\\] \\_PyEval\\_EvalFrameDefault\\) \\(Adreno 730?\\) [\\#2723](https://github.com/kivy/python-for-android/issues/2723)\n- Pandas giving error in Buildozer [\\#2719](https://github.com/kivy/python-for-android/issues/2719)\n- buildozer -v android debug [\\#2711](https://github.com/kivy/python-for-android/issues/2711)\n- \\[proposed feature-request\\] Lacking psutil recipe [\\#2707](https://github.com/kivy/python-for-android/issues/2707)\n- \\[ERROR\\]:   Build failed: Asked to compile for no Archs, so failing. [\\#2685](https://github.com/kivy/python-for-android/issues/2685)\n- Feature Request: Give more access to the android project folder inside of the dist folder [\\#2614](https://github.com/kivy/python-for-android/issues/2614)\n- `shutil.copy()` fails on external removable storage devices [\\#2589](https://github.com/kivy/python-for-android/issues/2589)\n- jnius can't find class  \"org.kivy.android.PythonActivity\" with webview [\\#2533](https://github.com/kivy/python-for-android/issues/2533)\n- \\[MACOS\\] Android app crashes on start when using macos to build [\\#2519](https://github.com/kivy/python-for-android/issues/2519)\n- Pillow-SIMD recipe? [\\#2420](https://github.com/kivy/python-for-android/issues/2420)\n- --asset & directories [\\#2413](https://github.com/kivy/python-for-android/issues/2413)\n- dlopen failed: cannot locate symbol \"\\_\\_register\\_atfork\" on Android 5.0 [\\#2410](https://github.com/kivy/python-for-android/issues/2410)\n- dlib module not found error [\\#2395](https://github.com/kivy/python-for-android/issues/2395)\n- lxml build failed for x86 arch [\\#2369](https://github.com/kivy/python-for-android/issues/2369)\n- Android 10 storage permission denied  [\\#2364](https://github.com/kivy/python-for-android/issues/2364)\n- for pytorch  [\\#2353](https://github.com/kivy/python-for-android/issues/2353)\n- Problem with ffmpeg on Android [\\#2345](https://github.com/kivy/python-for-android/issues/2345)\n- NLTK recipe for python for android [\\#2320](https://github.com/kivy/python-for-android/issues/2320)\n-  build\\_tools\\_versions comparison code fails for 'Android Rebuilds' SDKs because of different folder naming conventions [\\#2318](https://github.com/kivy/python-for-android/issues/2318)\n- verify downloads using sha256? [\\#2294](https://github.com/kivy/python-for-android/issues/2294)\n- outdated recipes [\\#2277](https://github.com/kivy/python-for-android/issues/2277)\n- Custom recipe for scipy fails with permission issue [\\#2267](https://github.com/kivy/python-for-android/issues/2267)\n- Kivy application generated crashes instantly with dlopen failed [\\#2266](https://github.com/kivy/python-for-android/issues/2266)\n- EGLlib: validate\\_display: 92 error 3008 \\(EGL\\_BAD\\_DISPLAY\\) : App crashes immediately \\(kivymd\\) \\(Buildozer\\) [\\#2258](https://github.com/kivy/python-for-android/issues/2258)\n- libEGL  : EGLNativeWindowType disconnect failed [\\#2253](https://github.com/kivy/python-for-android/issues/2253)\n- Hao to support multiprocess Queue in Android  [\\#2249](https://github.com/kivy/python-for-android/issues/2249)\n- autoclass: Class only found when called in specific places? [\\#2242](https://github.com/kivy/python-for-android/issues/2242)\n- the app crash in time of import psycopg2 [\\#2240](https://github.com/kivy/python-for-android/issues/2240)\n- env must be a dict [\\#2170](https://github.com/kivy/python-for-android/issues/2170)\n- Pandas doesn't work [\\#2157](https://github.com/kivy/python-for-android/issues/2157)\n- Webview bootstrap can't find 'org.jnius.NativeInvocationHandler'.  [\\#2140](https://github.com/kivy/python-for-android/issues/2140)\n- clang++: error: linker command failed with exit code 1 [\\#2082](https://github.com/kivy/python-for-android/issues/2082)\n- ModuleNotFoundError: No module named 'setuptools' [\\#2078](https://github.com/kivy/python-for-android/issues/2078)\n- Scraping web pages with javascript [\\#2052](https://github.com/kivy/python-for-android/issues/2052)\n- open webbrowser register\\(\\) error [\\#2047](https://github.com/kivy/python-for-android/issues/2047)\n- Missing javaclass when using able with previously working recipe [\\#2041](https://github.com/kivy/python-for-android/issues/2041)\n- :Class not found b'org/kivy/android/PythonActivity$ActivityResultListener' [\\#2039](https://github.com/kivy/python-for-android/issues/2039)\n- App\\(using socket and opencv\\) crash on opening [\\#2038](https://github.com/kivy/python-for-android/issues/2038)\n- android apk is crashing after displaying splash screen on phone [\\#2030](https://github.com/kivy/python-for-android/issues/2030)\n- Leverage Docker image caching [\\#2009](https://github.com/kivy/python-for-android/issues/2009)\n- entrypoint confusion with python3 [\\#1999](https://github.com/kivy/python-for-android/issues/1999)\n- Android app crash on opening - Python Initialize [\\#1987](https://github.com/kivy/python-for-android/issues/1987)\n- Error building APK: \"Missing 'name' key attribute on element activity at AndroidManifest.xml\" [\\#1979](https://github.com/kivy/python-for-android/issues/1979)\n- Ugent issues on Webview \\(Android Back Button to main App\\) [\\#1961](https://github.com/kivy/python-for-android/issues/1961)\n- JavaException: JVM exception occurred: Fail to connect to camera service [\\#1943](https://github.com/kivy/python-for-android/issues/1943)\n- Python version number must have subversion? cannot find Python-3.7.tgz [\\#1941](https://github.com/kivy/python-for-android/issues/1941)\n- dlopen failed: jnius.so is for EM\\_ARM \\(40\\) instead of EM\\_386 \\(3\\) [\\#1927](https://github.com/kivy/python-for-android/issues/1927)\n- Matplotlib recipe depends on local environment [\\#1900](https://github.com/kivy/python-for-android/issues/1900)\n- main window jumps up and down [\\#1876](https://github.com/kivy/python-for-android/issues/1876)\n- ctypes.pythonapi issues; getting AttributeError: undefined symbol [\\#1866](https://github.com/kivy/python-for-android/issues/1866)\n- \\[enhancement\\] do not rebuild already built packages [\\#1860](https://github.com/kivy/python-for-android/issues/1860)\n- Matplotlib recipe fails sometimes [\\#1859](https://github.com/kivy/python-for-android/issues/1859)\n- p4a build with NDK r18b: clang: error: unknown argument: '-mandroid'  [\\#1853](https://github.com/kivy/python-for-android/issues/1853)\n- Activity lifecycle issues. after onDestroy, application will become unusable [\\#1844](https://github.com/kivy/python-for-android/issues/1844)\n- Service AutoRestart did not work  [\\#1823](https://github.com/kivy/python-for-android/issues/1823)\n- Android debug results in error involving clang++ and linker. [\\#1796](https://github.com/kivy/python-for-android/issues/1796)\n- seek\\(\\) method on a file object doesn't use right arguments [\\#1768](https://github.com/kivy/python-for-android/issues/1768)\n- Same issue w/ -lpython2.7 not found, workaround [\\#1753](https://github.com/kivy/python-for-android/issues/1753)\n- Several issues when installing packages via pip [\\#1745](https://github.com/kivy/python-for-android/issues/1745)\n- Publish a new Kivy Launcher for Python 3 [\\#1638](https://github.com/kivy/python-for-android/issues/1638)\n- Travis conditional bootstrap build support [\\#1588](https://github.com/kivy/python-for-android/issues/1588)\n- Error when execute APK only on device: ImportError: cannot import name \\_htmlparser [\\#1523](https://github.com/kivy/python-for-android/issues/1523)\n- onSensorChanged continuously called during app execution [\\#1498](https://github.com/kivy/python-for-android/issues/1498)\n- GC deadlock on subprocess [\\#1461](https://github.com/kivy/python-for-android/issues/1461)\n- Code runs on old pygame backend but not on SDL2 [\\#1411](https://github.com/kivy/python-for-android/issues/1411)\n- build-tools below 25 will not add jars [\\#1345](https://github.com/kivy/python-for-android/issues/1345)\n- Flaky continuous integration [\\#1306](https://github.com/kivy/python-for-android/issues/1306)\n- Icon/Logo Proposal [\\#1264](https://github.com/kivy/python-for-android/issues/1264)\n- Unable to write the config [\\#1151](https://github.com/kivy/python-for-android/issues/1151)\n- p4a does not yet work with clang [\\#1097](https://github.com/kivy/python-for-android/issues/1097)\n- android module seems to eat up a character from java properties [\\#945](https://github.com/kivy/python-for-android/issues/945)\n- TypeError: a bytes-like object is required, not 'str' [\\#856](https://github.com/kivy/python-for-android/issues/856)\n- Feature request: access to all permissions [\\#843](https://github.com/kivy/python-for-android/issues/843)\n- Extending the launcher [\\#565](https://github.com/kivy/python-for-android/issues/565)\n\n**Merged pull requests:**\n\n- Update OpenSSL version to `1.1.1w` [\\#2958](https://github.com/kivy/python-for-android/pull/2958) ([prolenodev](https://github.com/prolenodev))\n- Bump Kivy version to `2.3.0` [\\#2952](https://github.com/kivy/python-for-android/pull/2952) ([misl6](https://github.com/misl6))\n- `sourceCompatibility` 1.7 and `targetCompatibility` 1.7 are obsolete, use 1.8 by default [\\#2942](https://github.com/kivy/python-for-android/pull/2942) ([misl6](https://github.com/misl6))\n- Remove redundant append into WHITELIST\\_PATTERNS [\\#2935](https://github.com/kivy/python-for-android/pull/2935) ([shyamnathp](https://github.com/shyamnathp))\n- Update sdl2 deps to reflect the same targeted in kivy/kivy [\\#2927](https://github.com/kivy/python-for-android/pull/2927) ([misl6](https://github.com/misl6))\n- Update `python-for-android` prerequisites \\(`Dockerfile`, `prerequisites.py`, docs\\) [\\#2923](https://github.com/kivy/python-for-android/pull/2923) ([misl6](https://github.com/misl6))\n- Update Contributing Guidelines and Readme [\\#2922](https://github.com/kivy/python-for-android/pull/2922) ([Julian-O](https://github.com/Julian-O))\n- Initial support for PySide6 and Qt [\\#2918](https://github.com/kivy/python-for-android/pull/2918) ([shyamnathp](https://github.com/shyamnathp))\n- Introduce  FAQ [\\#2917](https://github.com/kivy/python-for-android/pull/2917) ([Julian-O](https://github.com/Julian-O))\n- Add \\(now mandatory\\) `.readthedocs.yaml` file, add docs `requirements.txt` and update sphinx conf [\\#2916](https://github.com/kivy/python-for-android/pull/2916) ([misl6](https://github.com/misl6))\n- enable json1 extension in sqlite3 [\\#2915](https://github.com/kivy/python-for-android/pull/2915) ([HyTurtle](https://github.com/HyTurtle))\n- Bump `pyjnius` version to `1.6.1` [\\#2914](https://github.com/kivy/python-for-android/pull/2914) ([misl6](https://github.com/misl6))\n- Remove `distutils` usage, as is not available anymore on Python `3.12` [\\#2912](https://github.com/kivy/python-for-android/pull/2912) ([misl6](https://github.com/misl6))\n- Update Lottie player version [\\#2900](https://github.com/kivy/python-for-android/pull/2900) ([HugoDaniel](https://github.com/HugoDaniel))\n- Merge master into develop [\\#2892](https://github.com/kivy/python-for-android/pull/2892) ([misl6](https://github.com/misl6))\n- Add doc tests, make them pass. [\\#2890](https://github.com/kivy/python-for-android/pull/2890) ([Julian-O](https://github.com/Julian-O))\n- Update Android gradle plugin to `8.1.1` and gradle to `8.0.2` [\\#2887](https://github.com/kivy/python-for-android/pull/2887) ([misl6](https://github.com/misl6))\n- Add support for Python `3.11` and make it the default while building `hostpython3` and `python3` [\\#2850](https://github.com/kivy/python-for-android/pull/2850) ([T-Dynamos](https://github.com/T-Dynamos))\n\n\n## [v2023.09.16](https://github.com/kivy/python-for-android/tree/v2023.09.16)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2023.05.21...v2023.09.16)\n\n**Closed issues:**\n\n- Microphone And Audio permissions doesn't work [\\#2889](https://github.com/kivy/python-for-android/issues/2889)\n- Error with Scipy  [\\#2883](https://github.com/kivy/python-for-android/issues/2883)\n- Download failed \\( Downloading sqlite3 from https://www.sqlite.org/2021/sqlite-amalgamation-3350500.zip \\) during buildozer -v android debug [\\#2881](https://github.com/kivy/python-for-android/issues/2881)\n- ONNXruntime lib open failed due to 64-bit [\\#2880](https://github.com/kivy/python-for-android/issues/2880)\n- Question: how to write a recipe to download and install\\(coz build fail\\) a wheel package directly? [\\#2878](https://github.com/kivy/python-for-android/issues/2878)\n- Impossible to install python for android [\\#2867](https://github.com/kivy/python-for-android/issues/2867)\n- scipy with kivy [\\#2861](https://github.com/kivy/python-for-android/issues/2861)\n- False positive parser warning. [\\#2856](https://github.com/kivy/python-for-android/issues/2856)\n- After successfully building app, its crashes with this error, using firebase-admin [\\#2854](https://github.com/kivy/python-for-android/issues/2854)\n- Kivy [\\#2837](https://github.com/kivy/python-for-android/issues/2837)\n- not installing on windows 10 [\\#2836](https://github.com/kivy/python-for-android/issues/2836)\n- Could not find com.android.tools.build:gradle:7.2.0. in android studio [\\#2834](https://github.com/kivy/python-for-android/issues/2834)\n- vlc recipe build fail [\\#2822](https://github.com/kivy/python-for-android/issues/2822)\n- mysqldb recipe build fail [\\#2813](https://github.com/kivy/python-for-android/issues/2813)\n- babel recipe build fail [\\#2803](https://github.com/kivy/python-for-android/issues/2803)\n- Python 3.10 cffi build fails [\\#2799](https://github.com/kivy/python-for-android/issues/2799)\n- \\[Recipe request\\] OpenColorIO & rawpy \\(libraw\\) [\\#2789](https://github.com/kivy/python-for-android/issues/2789)\n- Longer app load time, bigger apk size and unnecessary logs [\\#2704](https://github.com/kivy/python-for-android/issues/2704)\n- -fp-model argument not found, directory strict not found [\\#2329](https://github.com/kivy/python-for-android/issues/2329)\n- Kivy crashes on Android:  ImportError: dlopen failed: library \"libpython3.7m.so\" not found [\\#2237](https://github.com/kivy/python-for-android/issues/2237)\n- pyconfig.h issue [\\#2074](https://github.com/kivy/python-for-android/issues/2074)\n\n**Merged pull requests:**\n\n- Use Python's touch\\(\\) rather than shelling out. [\\#2886](https://github.com/kivy/python-for-android/pull/2886) ([Julian-O](https://github.com/Julian-O))\n- Standardise on move [\\#2884](https://github.com/kivy/python-for-android/pull/2884) ([Julian-O](https://github.com/Julian-O))\n- Remove deprecated FlatDir in Gradle template [\\#2876](https://github.com/kivy/python-for-android/pull/2876) ([Julian-O](https://github.com/Julian-O))\n- :rotating\\_light: linter fixes [\\#2874](https://github.com/kivy/python-for-android/pull/2874) ([AndreMiras](https://github.com/AndreMiras))\n- Standardise ensure\\_dir and rmdir [\\#2871](https://github.com/kivy/python-for-android/pull/2871) ([Julian-O](https://github.com/Julian-O))\n- Correct check for --sdk option [\\#2870](https://github.com/kivy/python-for-android/pull/2870) ([Julian-O](https://github.com/Julian-O))\n- Python versions: Update documentation & CI testing [\\#2869](https://github.com/kivy/python-for-android/pull/2869) ([Julian-O](https://github.com/Julian-O))\n- Patching cleanup [\\#2868](https://github.com/kivy/python-for-android/pull/2868) ([Julian-O](https://github.com/Julian-O))\n- Factor out dependency checking. Use modern version handling [\\#2866](https://github.com/kivy/python-for-android/pull/2866) ([Julian-O](https://github.com/Julian-O))\n- `build_platform` should be all-lowercase [\\#2864](https://github.com/kivy/python-for-android/pull/2864) ([misl6](https://github.com/misl6))\n- Fix simple typos in comments [\\#2863](https://github.com/kivy/python-for-android/pull/2863) ([Julian-O](https://github.com/Julian-O))\n- Use a pinned version of `Cython` for now, as most of the recipes are incompatible with `Cython==3.x.x` [\\#2862](https://github.com/kivy/python-for-android/pull/2862) ([misl6](https://github.com/misl6))\n- Docs: Fix typos and updated command to build apk - README [\\#2860](https://github.com/kivy/python-for-android/pull/2860) ([kulothunganug](https://github.com/kulothunganug))\n- Docs: Fix code string - quickstart.rst [\\#2859](https://github.com/kivy/python-for-android/pull/2859) ([kulothunganug](https://github.com/kulothunganug))\n- Automatically generate required pre-requisites [\\#2858](https://github.com/kivy/python-for-android/pull/2858) ([Julian-O](https://github.com/Julian-O))\n- Use platform.uname instead of os.uname [\\#2857](https://github.com/kivy/python-for-android/pull/2857) ([Julian-O](https://github.com/Julian-O))\n- Bump `kivy` version to `2.2.1` [\\#2855](https://github.com/kivy/python-for-android/pull/2855) ([misl6](https://github.com/misl6))\n- Correct sys\\_platform [\\#2852](https://github.com/kivy/python-for-android/pull/2852) ([Julian-O](https://github.com/Julian-O))\n- Changed the url to use https as http fails [\\#2846](https://github.com/kivy/python-for-android/pull/2846) ([kuzeyron](https://github.com/kuzeyron))\n- vlc: fix build [\\#2841](https://github.com/kivy/python-for-android/pull/2841) ([T-Dynamos](https://github.com/T-Dynamos))\n- Optimize CI runs, by avoiding unnecessary rebuilds [\\#2833](https://github.com/kivy/python-for-android/pull/2833) ([misl6](https://github.com/misl6))\n- Remove `pytz` recipe, as it's not needed anymore [\\#2830](https://github.com/kivy/python-for-android/pull/2830) ([misl6](https://github.com/misl6))\n- Remove `dateutil` recipe, as it's not needed anymore [\\#2829](https://github.com/kivy/python-for-android/pull/2829) ([misl6](https://github.com/misl6))\n- Removes `mysqldb` recipe as does not support Python 3 [\\#2828](https://github.com/kivy/python-for-android/pull/2828) ([misl6](https://github.com/misl6))\n- Bump `actions/setup-python` and `actions/checkout` versions, as old ones are deprecated [\\#2827](https://github.com/kivy/python-for-android/pull/2827) ([misl6](https://github.com/misl6))\n- Removes `Babel` recipe as it's not needed anymore. [\\#2826](https://github.com/kivy/python-for-android/pull/2826) ([misl6](https://github.com/misl6))\n- Cffi update [\\#2800](https://github.com/kivy/python-for-android/pull/2800) ([HyTurtle](https://github.com/HyTurtle))\n- Merge master into develop [\\#2797](https://github.com/kivy/python-for-android/pull/2797) ([misl6](https://github.com/misl6))\n- Use build rather than pep517 for building [\\#2784](https://github.com/kivy/python-for-android/pull/2784) ([s-t-e-v-e-n-k](https://github.com/s-t-e-v-e-n-k))\n\n## [v2023.05.21](https://github.com/kivy/python-for-android/tree/v2023.05.21)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2023.02.10...v2023.05.21)\n\n**Closed issues:**\n\n- python [\\#2795](https://github.com/kivy/python-for-android/issues/2795)\n- Create APK from PyQt app [\\#2794](https://github.com/kivy/python-for-android/issues/2794)\n- psutil/\\_psutil\\_linux.so\" is 64-bit instead of 32-bit [\\#2785](https://github.com/kivy/python-for-android/issues/2785)\n- pythonforandroid.toolchain.py: error: unrecognized arguments: --dir [\\#2775](https://github.com/kivy/python-for-android/issues/2775)\n- App [\\#2774](https://github.com/kivy/python-for-android/issues/2774)\n- org.kivy.android.PythonActivity$NewIntentListener is not visible from class loader java.lang.IllegalArgumentException [\\#2770](https://github.com/kivy/python-for-android/issues/2770)\n- Service don t start anymore, as smallIconName extra is now mandatory [\\#2768](https://github.com/kivy/python-for-android/issues/2768)\n- Start a background sticky service that auto-restart. [\\#2767](https://github.com/kivy/python-for-android/issues/2767)\n- Fail installation [\\#2764](https://github.com/kivy/python-for-android/issues/2764)\n- Python exception when using colorlog due to incomplete IO implementation in sys.stderr [\\#2762](https://github.com/kivy/python-for-android/issues/2762)\n- AttributeError: 'org.kivy.android.PythonService' object has no attribute 'getComponentName' [\\#2760](https://github.com/kivy/python-for-android/issues/2760)\n- https://code.videolan.org not available [\\#2758](https://github.com/kivy/python-for-android/issues/2758)\n- Cannot install Python-for-Android [\\#2754](https://github.com/kivy/python-for-android/issues/2754)\n- c/\\_cffi\\_backend.c:407:23: error: expression is not assignable [\\#2753](https://github.com/kivy/python-for-android/issues/2753)\n- not install [\\#2749](https://github.com/kivy/python-for-android/issues/2749)\n- APK crashes upon launch. logcat error: null pointer dereference \\(occurs with imported modules\\) [\\#2358](https://github.com/kivy/python-for-android/issues/2358)\n- Error occurred while building the application using buildozer [\\#2104](https://github.com/kivy/python-for-android/issues/2104)\n- \"Could Not Extract Public Data\"  Needs very explicit instructions or feedback to the user [\\#260](https://github.com/kivy/python-for-android/issues/260)\n\n**Merged pull requests:**\n\n- Update Kivy recipe for 2.2.0 [\\#2793](https://github.com/kivy/python-for-android/pull/2793) ([misl6](https://github.com/misl6))\n- Update `pyjnius` version to `1.5.0` [\\#2791](https://github.com/kivy/python-for-android/pull/2791) ([misl6](https://github.com/misl6))\n- fix tools/liblink: syntax error [\\#2771](https://github.com/kivy/python-for-android/pull/2771) ([SomberNight](https://github.com/SomberNight))\n- fix \\#2768 smallIconName null can t be compared to String [\\#2769](https://github.com/kivy/python-for-android/pull/2769) ([brvier](https://github.com/brvier))\n- android\\_api to integer [\\#2765](https://github.com/kivy/python-for-android/pull/2765) ([kuzeyron](https://github.com/kuzeyron))\n- Use io.IOBase for LogFile [\\#2763](https://github.com/kivy/python-for-android/pull/2763) ([dylanmccall](https://github.com/dylanmccall))\n- Home app functionality [\\#2761](https://github.com/kivy/python-for-android/pull/2761) ([kuzeyron](https://github.com/kuzeyron))\n- Add debug loggings for identifying a matching dist [\\#2751](https://github.com/kivy/python-for-android/pull/2751) ([BitcoinWukong](https://github.com/BitcoinWukong))\n- Add PyAV recipe [\\#2750](https://github.com/kivy/python-for-android/pull/2750) ([DexerBR](https://github.com/DexerBR))\n- Merge master into develop [\\#2748](https://github.com/kivy/python-for-android/pull/2748) ([misl6](https://github.com/misl6))\n- Add support for Python 3.10 and make it the default while building hostpython3 and python3 [\\#2577](https://github.com/kivy/python-for-android/pull/2577) ([misl6](https://github.com/misl6))\n\n\n## [v2023.02.10](https://github.com/kivy/python-for-android/tree/v2023.02.10) (2023-02-10)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2023.01.28...v2023.02.10)\n\n**Closed issues:**\n\n- AttributeError: 'str' object has no attribute 'stdout' [\\#2745](https://github.com/kivy/python-for-android/issues/2745)\n- Android app crash on starting up by accessing texture. Error:No module named 'typing\\_extensions' [\\#2743](https://github.com/kivy/python-for-android/issues/2743)\n\n**Merged pull requests:**\n\n- restrict sh version [\\#2746](https://github.com/kivy/python-for-android/pull/2746) ([HyTurtle](https://github.com/HyTurtle))\n- 🐛 fix: Update `pydantic` recipe [\\#2742](https://github.com/kivy/python-for-android/pull/2742) ([FilipeMarch](https://github.com/FilipeMarch))\n- Merge master into develop [\\#2741](https://github.com/kivy/python-for-android/pull/2741) ([misl6](https://github.com/misl6))\n\n## [v2023.01.28](https://github.com/kivy/python-for-android/tree/v2023.01.28) (2023-01-28)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.12.20...v2023.01.28)\n\n**Closed issues:**\n\n- Python\n [\\#2737](https://github.com/kivy/python-for-android/issues/2737)\n- AndroidX Issue [\\#2736](https://github.com/kivy/python-for-android/issues/2736)\n- Kivy build failed [\\#2735](https://github.com/kivy/python-for-android/issues/2735)\n- Can't build apk using READ\\_EXTERNAL\\_STORAGE, WRITE\\_EXTERNAL\\_STORAGE in buildozer.spec [\\#2732](https://github.com/kivy/python-for-android/issues/2732)\n- BUILD FAILURE: No main.py\\(o\\) found in your app directory. [\\#2731](https://github.com/kivy/python-for-android/issues/2731)\n- Your app currently targets API level 30 and must target at least API level 31 to ensure that it is built on the latest APIs optimised for security and performance. Change your app's target API level to at least 31 [\\#2729](https://github.com/kivy/python-for-android/issues/2729)\n- Your app currently targets API level 30 and must target at least API level 31 to ensure that it is built on the latest APIs optimised for security and performance. Change your app's target API level to at least 31 [\\#2727](https://github.com/kivy/python-for-android/issues/2727)\n- `sh.CommandNotFound: ./download.sh` [\\#2726](https://github.com/kivy/python-for-android/issues/2726)\n- Setting `android:screenOrientation` via `--orientation` has no effect when targeting API 31 [\\#2724](https://github.com/kivy/python-for-android/issues/2724)\n- \\[Question\\]: How to set 'compileSdkVersion' to 31 or higher. [\\#2722](https://github.com/kivy/python-for-android/issues/2722)\n- Bug in the keyboard event listener [\\#2423](https://github.com/kivy/python-for-android/issues/2423)\n\n**Merged pull requests:**\n\n- Implements `--manifest-orientation` and changes how `--orientation` works so we can now pass the setting to the SDL orientation hint [\\#2739](https://github.com/kivy/python-for-android/pull/2739) ([misl6](https://github.com/misl6))\n- Update \\_\\_init\\_\\_.py from `scrypt` recipe [\\#2738](https://github.com/kivy/python-for-android/pull/2738) ([FilipeMarch](https://github.com/FilipeMarch))\n- Apply a patch from SDL upstream that fixes orientation settings [\\#2730](https://github.com/kivy/python-for-android/pull/2730) ([misl6](https://github.com/misl6))\n- Support permission properties \\(`maxSdkVersion` and `usesPermissionFlags`\\) + remove `WRITE_EXTERNAL_STORAGE` permission, which has been previously declared by default [\\#2725](https://github.com/kivy/python-for-android/pull/2725) ([misl6](https://github.com/misl6))\n- Merge master in develop [\\#2721](https://github.com/kivy/python-for-android/pull/2721) ([misl6](https://github.com/misl6))\n\n## [v2022.12.20](https://github.com/kivy/python-for-android/tree/v2022.12.20) (2022-12-20)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.09.04...v2022.12.20)\n\n**Fixed bugs:**\n\n- `liblzma` fails to build on macOS \\(sh.ErrorReturnCode\\_2. when running buildozer android debug\\) [\\#2343](https://github.com/kivy/python-for-android/issues/2343)\n\n**Closed issues:**\n\n- SDL\\_ttf 2.0.15 download missing \\(deprecated\\) related to \\#2698 [\\#2710](https://github.com/kivy/python-for-android/issues/2710)\n- Update kivy app that's already published on google play store. [\\#2709](https://github.com/kivy/python-for-android/issues/2709)\n- Installing deepspeech [\\#2702](https://github.com/kivy/python-for-android/issues/2702)\n- ImportError: dlopen failed: library \"libc++\\_shared.so\" not found [\\#2699](https://github.com/kivy/python-for-android/issues/2699)\n- ffpyplayer recipe broken after SDL2 upgrade [\\#2698](https://github.com/kivy/python-for-android/issues/2698)\n- ModuleNotFoundError: No module named 'android' [\\#2697](https://github.com/kivy/python-for-android/issues/2697)\n- Error When I set android.api = 31 [\\#2696](https://github.com/kivy/python-for-android/issues/2696)\n- Is there any way to protect .py code  [\\#2695](https://github.com/kivy/python-for-android/issues/2695)\n- args.service\\_class\\_name results in link error  [\\#2679](https://github.com/kivy/python-for-android/issues/2679)\n- Remove `x86_64` suffix from ndk download link [\\#2676](https://github.com/kivy/python-for-android/issues/2676)\n- Pillow 9.2.0 recipe? [\\#2671](https://github.com/kivy/python-for-android/issues/2671)\n- `ffmpeg`: unable to find library -lvpx [\\#2665](https://github.com/kivy/python-for-android/issues/2665)\n- Buildozer fails while build numpy recipe 'UnixCCompiler' object has no attribute 'cxx\\_compiler' [\\#2664](https://github.com/kivy/python-for-android/issues/2664)\n- \\[PEP 517\\] Relax installation-time \"pep517\\<0.7.0\" requirement [\\#2573](https://github.com/kivy/python-for-android/issues/2573)\n- Add support for custom resources [\\#2298](https://github.com/kivy/python-for-android/issues/2298)\n- Auto-correct / word suggestion does not work with Swiftkey/Samsung keyboard [\\#2010](https://github.com/kivy/python-for-android/issues/2010)\n\n**Merged pull requests:**\n\n- `InputType.TYPE_TEXT_FLAG_MULTI_LINE` forces `InputType.TYPE_TEXT` even if `SDLActivity.keyboardInputType` is `NULL` [\\#2716](https://github.com/kivy/python-for-android/pull/2716) ([misl6](https://github.com/misl6))\n- secp256k1 Update \"--host=\" [\\#2714](https://github.com/kivy/python-for-android/pull/2714) ([RobertFlatt](https://github.com/RobertFlatt))\n- Delete pythonforandroid/recipes/cdecimal directory [\\#2713](https://github.com/kivy/python-for-android/pull/2713) ([RobertFlatt](https://github.com/RobertFlatt))\n- Bump `sdl2` version to `2.26.1` [\\#2712](https://github.com/kivy/python-for-android/pull/2712) ([misl6](https://github.com/misl6))\n- Flake8 does not support inline comments for any of the keys. [\\#2708](https://github.com/kivy/python-for-android/pull/2708) ([misl6](https://github.com/misl6))\n- Gradle: Run the clean task before anything else to make sure nothing is cached. [\\#2705](https://github.com/kivy/python-for-android/pull/2705) ([misl6](https://github.com/misl6))\n- Custom Service notification [\\#2703](https://github.com/kivy/python-for-android/pull/2703) ([RobertFlatt](https://github.com/RobertFlatt))\n- Include paths for sdl2\\_mixer have changed. Added a method to return the right one. [\\#2700](https://github.com/kivy/python-for-android/pull/2700) ([misl6](https://github.com/misl6))\n- WRITE\\_EXTERNAL\\_STORAGE maxSdk [\\#2694](https://github.com/kivy/python-for-android/pull/2694) ([RobertFlatt](https://github.com/RobertFlatt))\n- Fixes an issue regarding blacklist and bytecode compile + some cleanup [\\#2693](https://github.com/kivy/python-for-android/pull/2693) ([misl6](https://github.com/misl6))\n- Bump to a version of `SDL` with patches for the TextInput / TextEditing \\(SDL `2.26.0`\\) [\\#2692](https://github.com/kivy/python-for-android/pull/2692) ([misl6](https://github.com/misl6))\n- Make CI compile aiohttp again. [\\#2690](https://github.com/kivy/python-for-android/pull/2690) ([xavierfiechter](https://github.com/xavierfiechter))\n- Add resources [\\#2684](https://github.com/kivy/python-for-android/pull/2684) ([RobertFlatt](https://github.com/RobertFlatt))\n- Update `MIN_TARGET_API` to `30` and `RECOMMENDED_TARGET_API` to `33` [\\#2683](https://github.com/kivy/python-for-android/pull/2683) ([misl6](https://github.com/misl6))\n- recipe.download\\_file: implement shallow git cloning [\\#2682](https://github.com/kivy/python-for-android/pull/2682) ([SomberNight](https://github.com/SomberNight))\n- requirements: relax version bound on \"pep517\" [\\#2680](https://github.com/kivy/python-for-android/pull/2680) ([SomberNight](https://github.com/SomberNight))\n- Add new Android permissions [\\#2677](https://github.com/kivy/python-for-android/pull/2677) ([RobertFlatt](https://github.com/RobertFlatt))\n- Resize webview when keyboard is shown [\\#2674](https://github.com/kivy/python-for-android/pull/2674) ([dbnicholson](https://github.com/dbnicholson))\n- Update `SDL2`, `SDL2_ttf`, `SDL2_mixer`, `SDL2_image` to latest releases [\\#2673](https://github.com/kivy/python-for-android/pull/2673) ([misl6](https://github.com/misl6))\n- Fixes libvpx build [\\#2672](https://github.com/kivy/python-for-android/pull/2672) ([misl6](https://github.com/misl6))\n- `toml` may not be available on systemwide python [\\#2670](https://github.com/kivy/python-for-android/pull/2670) ([misl6](https://github.com/misl6))\n- android/activity: Add Application.ActivityLifecycleCallbacks helpers [\\#2669](https://github.com/kivy/python-for-android/pull/2669) ([dbnicholson](https://github.com/dbnicholson))\n- Bump minimal and recommended Android NDK version to 25b [\\#2668](https://github.com/kivy/python-for-android/pull/2668) ([misl6](https://github.com/misl6))\n- Include HOME in build environment [\\#2582](https://github.com/kivy/python-for-android/pull/2582) ([dbnicholson](https://github.com/dbnicholson))\n\n## [v2022.09.04](https://github.com/kivy/python-for-android/tree/v2022.09.04) (2022-09-04)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.07.20...v2022.09.04)\n\n**Fixed bugs:**\n\n- Matplotlib failed to import properly on an APK from Buildozer and Kivy [\\#2643](https://github.com/kivy/python-for-android/issues/2643)\n\n**Closed issues:**\n\n- KeyError: Matplotlib with kivy android [\\#2658](https://github.com/kivy/python-for-android/issues/2658)\n- KeyError: Matplotlib [\\#2659](https://github.com/kivy/python-for-android/issues/2659)\n- Upgrade from NDK 19b to 23b causes problems with Pandas library [\\#2654](https://github.com/kivy/python-for-android/issues/2654)\n- Update Dockerfile for ARM [\\#2653](https://github.com/kivy/python-for-android/issues/2653)\n- Apple M2 chip doesn't generate apk: compiling error on liblzma [\\#2652](https://github.com/kivy/python-for-android/issues/2652)\n- aiohttp/\\_http\\_parser.pyx:46:0: '\\_headers.pxi' not found [\\#2651](https://github.com/kivy/python-for-android/issues/2651)\n- \\[Question\\] Pip SSL ? [\\#2649](https://github.com/kivy/python-for-android/issues/2649)\n- Colab gives me as error \"No module named 'typing\\_extensions' \", even if before with the same compilation it worked [\\#2648](https://github.com/kivy/python-for-android/issues/2648)\n- \\[Question\\] Java Files [\\#2646](https://github.com/kivy/python-for-android/issues/2646)\n- Using foreground services will cause wired behaviour on Android 8 [\\#2641](https://github.com/kivy/python-for-android/issues/2641)\n- Can't apply patches with relative paths for local recipe [\\#2623](https://github.com/kivy/python-for-android/issues/2623)\n- Compile for x86 on MacOS [\\#2215](https://github.com/kivy/python-for-android/issues/2215)\n- splash always loading  [\\#1907](https://github.com/kivy/python-for-android/issues/1907)\n- python-for-android.readthedocs.io has problems updating, apparently [\\#1709](https://github.com/kivy/python-for-android/issues/1709)\n- Webview apps not working on Android [\\#1644](https://github.com/kivy/python-for-android/issues/1644)\n\n**Merged pull requests:**\n\n- `liblzma`: Use `p4a_install` instead of `install`, as a file named `INSTALL` is already present. [\\#2663](https://github.com/kivy/python-for-android/pull/2663) ([misl6](https://github.com/misl6))\n- Force `--platform=linux/amd64` in Dockerfile [\\#2660](https://github.com/kivy/python-for-android/pull/2660) ([misl6](https://github.com/misl6))\n- Remove six and enum34 dependency [\\#2657](https://github.com/kivy/python-for-android/pull/2657) ([misl6](https://github.com/misl6))\n- Update supported Python versions [\\#2656](https://github.com/kivy/python-for-android/pull/2656) ([misl6](https://github.com/misl6))\n- Fixes some E275 - assert is a keyword. [\\#2647](https://github.com/kivy/python-for-android/pull/2647) ([misl6](https://github.com/misl6))\n- Updates matplotlib, fixes an issue related to shared libc++ [\\#2645](https://github.com/kivy/python-for-android/pull/2645) ([misl6](https://github.com/misl6))\n- RTSP support for ffmpeg [\\#2644](https://github.com/kivy/python-for-android/pull/2644) ([alicakici1234](https://github.com/alicakici1234))\n- Fixes TypeError: str.join\\(\\) takes exactly one argument \\(2 given\\) in hostpython3/\\_\\_init\\_\\_.py\", line 69 [\\#2642](https://github.com/kivy/python-for-android/pull/2642) ([Furtif](https://github.com/Furtif))\n- Resolve absolute path to local recipes [\\#2640](https://github.com/kivy/python-for-android/pull/2640) ([dbnicholson](https://github.com/dbnicholson))\n- Merges master into develop after release 2022.07.20 [\\#2639](https://github.com/kivy/python-for-android/pull/2639) ([misl6](https://github.com/misl6))\n- Fix webview Back button behaviour [\\#2636](https://github.com/kivy/python-for-android/pull/2636) ([interlark](https://github.com/interlark))\n- Add icon-bg and icon-fg to fix\\_args [\\#2633](https://github.com/kivy/python-for-android/pull/2633) ([danigm](https://github.com/danigm))\n- Remove stray - in output file name [\\#2581](https://github.com/kivy/python-for-android/pull/2581) ([dbnicholson](https://github.com/dbnicholson))\n- Add option for adding files to res/xml without touching manifest [\\#2330](https://github.com/kivy/python-for-android/pull/2330) ([rambo](https://github.com/rambo))\n\n## [v2022.07.20](https://github.com/kivy/python-for-android/tree/v2022.07.20) (2022-07-20)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.03.13...v2022.07.20)\n\n**Fixed bugs:**\n\n- Current default Python version \\(3.8.9\\) is failing to build on latest macOS releases [\\#2568](https://github.com/kivy/python-for-android/issues/2568)\n- Build failed for Pillow recipe when targeting x86\\_64 arch [\\#2259](https://github.com/kivy/python-for-android/issues/2259)\n- UnboundLocalError: local variable 'toolchain\\_version' referenced before assignment [\\#2190](https://github.com/kivy/python-for-android/issues/2190)\n- Numpy on MacOsX fails in our `CI` tests [\\#2087](https://github.com/kivy/python-for-android/issues/2087)\n\n**Closed issues:**\n\n- pyzbar android building error [\\#2635](https://github.com/kivy/python-for-android/issues/2635)\n- `tflite-runtime` build every time [\\#2630](https://github.com/kivy/python-for-android/issues/2630)\n- Failed to build `matplotlib` because `kiwisolver` [\\#2629](https://github.com/kivy/python-for-android/issues/2629)\n- Trying to build pandas with buildozer results in missing headers errors [\\#2626](https://github.com/kivy/python-for-android/issues/2626)\n- https://github.com/kivy/python-for-android.git [\\#2625](https://github.com/kivy/python-for-android/issues/2625)\n- \\[SSL : CERTIFICATE\\_VERIFY\\_FAILED \\] in Android  [\\#2620](https://github.com/kivy/python-for-android/issues/2620)\n- How to run Python script in background in android? [\\#2618](https://github.com/kivy/python-for-android/issues/2618)\n- USB permission [\\#2611](https://github.com/kivy/python-for-android/issues/2611)\n- ffmpeg recipe for 23b build fails [\\#2608](https://github.com/kivy/python-for-android/issues/2608)\n- Broken jpeg recipe for NDK 23b? [\\#2603](https://github.com/kivy/python-for-android/issues/2603)\n- Need a help [\\#2595](https://github.com/kivy/python-for-android/issues/2595)\n- Termux build fails [\\#2585](https://github.com/kivy/python-for-android/issues/2585)\n- lapack build error [\\#2584](https://github.com/kivy/python-for-android/issues/2584)\n- still this issue is happening [\\#2572](https://github.com/kivy/python-for-android/issues/2572)\n- \"Unit test apk\" + \"Unit test aab\" + \"Test updated recipes\" test jobs should be run also on macOS \\(both Intel and Apple Silicon\\) [\\#2569](https://github.com/kivy/python-for-android/issues/2569)\n- unpackPyBundle\\(\\) on startup crashes already running service [\\#2564](https://github.com/kivy/python-for-android/issues/2564)\n- Webview app fail to startup. [\\#2559](https://github.com/kivy/python-for-android/issues/2559)\n- GenericNDKBuildRecipe Not compiling with android api \\> 28 [\\#2555](https://github.com/kivy/python-for-android/issues/2555)\n- Is there a way to build smaller apks? [\\#2553](https://github.com/kivy/python-for-android/issues/2553)\n- Webview, icon [\\#2552](https://github.com/kivy/python-for-android/issues/2552)\n- SONAME header not present in libpython3.8.so [\\#2548](https://github.com/kivy/python-for-android/issues/2548)\n- How to mention Python modules in Kivy buildozer.spec file? [\\#2547](https://github.com/kivy/python-for-android/issues/2547)\n- Issue with pyaudio and portaudio [\\#2535](https://github.com/kivy/python-for-android/issues/2535)\n- \\[Temporary Resolved\\] Python 4 android in mac os with Apple Silicon via Roseta [\\#2528](https://github.com/kivy/python-for-android/issues/2528)\n- Scipy is not installed due to \"Error: 'numpy' must be installed before running the build.\" [\\#2509](https://github.com/kivy/python-for-android/issues/2509)\n- Lapack depends on arm-linux-androideabi-gfortran [\\#2508](https://github.com/kivy/python-for-android/issues/2508)\n- Apk file built by buildozer is large in comparison to other apks [\\#2473](https://github.com/kivy/python-for-android/issues/2473)\n- p4a is not compatible with ndk \\>= 22 [\\#2391](https://github.com/kivy/python-for-android/issues/2391)\n- Sympy module. Error in buildozer: no module named sympy.testing [\\#2381](https://github.com/kivy/python-for-android/issues/2381)\n- build.gradle 'compile' depreciated [\\#2362](https://github.com/kivy/python-for-android/issues/2362)\n- API 29 support [\\#2360](https://github.com/kivy/python-for-android/issues/2360)\n- python for android [\\#2307](https://github.com/kivy/python-for-android/issues/2307)\n- application is not working in android  made with buildozer kivy [\\#2260](https://github.com/kivy/python-for-android/issues/2260)\n- hostpython3 unpack error [\\#2247](https://github.com/kivy/python-for-android/issues/2247)\n- no recipe for pyaudio \\_portaudio.  [\\#2223](https://github.com/kivy/python-for-android/issues/2223)\n- How to add a native Python package for kivy? [\\#2089](https://github.com/kivy/python-for-android/issues/2089)\n- scipy module fails loading for 32 bit and 64 bit APK builds.  [\\#2061](https://github.com/kivy/python-for-android/issues/2061)\n- Support for androidx [\\#2020](https://github.com/kivy/python-for-android/issues/2020)\n- Cannot build apk using buidozer [\\#2005](https://github.com/kivy/python-for-android/issues/2005)\n- Android NDK - \"$NDK/platforms/android-25\" missing? [\\#1992](https://github.com/kivy/python-for-android/issues/1992)\n- Tidy up NDK 19+ support [\\#1962](https://github.com/kivy/python-for-android/issues/1962)\n- Support for NDK 19 [\\#1613](https://github.com/kivy/python-for-android/issues/1613)\n- Android NDK 18b issues [\\#1525](https://github.com/kivy/python-for-android/issues/1525)\n- Google requiring 64 bits binary in August 2019 [\\#1519](https://github.com/kivy/python-for-android/issues/1519)\n- Investigate Azure Pipelines [\\#1400](https://github.com/kivy/python-for-android/issues/1400)\n\n**Merged pull requests:**\n\n- Use `shutil.which` instead of `sh.which` [\\#2637](https://github.com/kivy/python-for-android/pull/2637) ([misl6](https://github.com/misl6))\n- add service\\_lib and aar to the docs [\\#2634](https://github.com/kivy/python-for-android/pull/2634) ([mzakharo](https://github.com/mzakharo))\n- Fix issue \\#2630 [\\#2631](https://github.com/kivy/python-for-android/pull/2631) ([Neizvestnyj](https://github.com/Neizvestnyj))\n- lapack/scipy: support NDK r21e, x86/64 archs [\\#2619](https://github.com/kivy/python-for-android/pull/2619) ([mzakharo](https://github.com/mzakharo))\n- add scipy/lapack CI tests [\\#2617](https://github.com/kivy/python-for-android/pull/2617) ([mzakharo](https://github.com/mzakharo))\n- use LEGACY\\_NDK option to build lapack/scipy with a separate NDK [\\#2615](https://github.com/kivy/python-for-android/pull/2615) ([mzakharo](https://github.com/mzakharo))\n- Fixing service\\_library bootstrap + .aar  build. [\\#2612](https://github.com/kivy/python-for-android/pull/2612) ([mzakharo](https://github.com/mzakharo))\n- Bump groestlcoin\\_hash to 1.0.3 [\\#2607](https://github.com/kivy/python-for-android/pull/2607) ([gruve-p](https://github.com/gruve-p))\n- removed `usr` and `lib` from ndk library path in `librt` recipe [\\#2606](https://github.com/kivy/python-for-android/pull/2606) ([kengoon](https://github.com/kengoon))\n- changed arch.ndk\\_platform to arch.ndk\\_lib\\_dir in `librt` recipe [\\#2605](https://github.com/kivy/python-for-android/pull/2605) ([kengoon](https://github.com/kengoon))\n- Our self-hosted Apple Silicon runner now has been migrated to actions/runner v2.292.0 which now supports arm64 natively [\\#2602](https://github.com/kivy/python-for-android/pull/2602) ([misl6](https://github.com/misl6))\n- Introduces pkg\\_config\\_location in Prerequisite and use OpenSSLPrerequisite\\(\\).pkg\\_config\\_location in hostpython3, so we can support ssl on hostpython3 just out of the box also on macOS [\\#2599](https://github.com/kivy/python-for-android/pull/2599) ([misl6](https://github.com/misl6))\n- Add service to webview test app [\\#2598](https://github.com/kivy/python-for-android/pull/2598) ([dbnicholson](https://github.com/dbnicholson))\n- Fix webview testapp jnius usage [\\#2597](https://github.com/kivy/python-for-android/pull/2597) ([dbnicholson](https://github.com/dbnicholson))\n- Support multiarch in webview bootstrap [\\#2596](https://github.com/kivy/python-for-android/pull/2596) ([dbnicholson](https://github.com/dbnicholson))\n- Handle all the macOS prerequisites \\(except NDK/SDK\\) via prerequisites.py [\\#2594](https://github.com/kivy/python-for-android/pull/2594) ([misl6](https://github.com/misl6))\n- Prefer avdmanager from cmdline-tools [\\#2593](https://github.com/kivy/python-for-android/pull/2593) ([dbnicholson](https://github.com/dbnicholson))\n- \\*\\_rebuild\\_updated\\_recipes CI jobs now test the updated recipe along all the supported Android archs \\(arm64-v8a, armeabi-v7a, x86\\_64, x86\\) [\\#2592](https://github.com/kivy/python-for-android/pull/2592) ([misl6](https://github.com/misl6))\n- Introduces pythonforandroid/prerequisites.py \\(Experimental\\). This allows a more granular check and install process for dependencies on both CI jobs and users installation. [\\#2591](https://github.com/kivy/python-for-android/pull/2591) ([misl6](https://github.com/misl6))\n- Added py3dns recipe [\\#2590](https://github.com/kivy/python-for-android/pull/2590) ([Neizvestnyj](https://github.com/Neizvestnyj))\n- Upload artifacts produced from every build platform, not only ubuntu-latest [\\#2588](https://github.com/kivy/python-for-android/pull/2588) ([misl6](https://github.com/misl6))\n- Fixes a typo in macos\\_rebuild\\_updated\\_recipes [\\#2587](https://github.com/kivy/python-for-android/pull/2587) ([misl6](https://github.com/misl6))\n- Added pythonforandroid.androidndk.AndroidNDK + some changes needed in order to support build on Apple Silicon macs. [\\#2586](https://github.com/kivy/python-for-android/pull/2586) ([misl6](https://github.com/misl6))\n- Set PATH using real SDK and NDK directories [\\#2583](https://github.com/kivy/python-for-android/pull/2583) ([dbnicholson](https://github.com/dbnicholson))\n- Add missing fetch-depth: 0 on macos\\_rebuild\\_updated\\_recipes [\\#2579](https://github.com/kivy/python-for-android/pull/2579) ([misl6](https://github.com/misl6))\n- Bumps libffi to v3.4.2 + adds -fPIC on i686-linux-android [\\#2578](https://github.com/kivy/python-for-android/pull/2578) ([misl6](https://github.com/misl6))\n- Bumps numpy version to 1.22.3, cython version to 0.29.28 and fixes numpy build on macOS [\\#2575](https://github.com/kivy/python-for-android/pull/2575) ([misl6](https://github.com/misl6))\n- macOS CI: ADD APK, AAB & Updated Recipes build [\\#2574](https://github.com/kivy/python-for-android/pull/2574) ([misl6](https://github.com/misl6))\n- add version check to unpackPyBundle [\\#2565](https://github.com/kivy/python-for-android/pull/2565) ([mzakharo](https://github.com/mzakharo))\n- Merges master into develop after release 2022.03.13 [\\#2562](https://github.com/kivy/python-for-android/pull/2562) ([misl6](https://github.com/misl6))\n- Fixes App Icon and Presplash\\_Screen For Webview bootstrap [\\#2556](https://github.com/kivy/python-for-android/pull/2556) ([kengoon](https://github.com/kengoon))\n- NDK 23 + Gradle 7 support [\\#2550](https://github.com/kivy/python-for-android/pull/2550) ([misl6](https://github.com/misl6))\n\n## [v2022.03.13](https://github.com/kivy/python-for-android/tree/v2022.03.13) (2022-03-13)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2021.09.05...v2022.03.13)\n\n**Closed issues:**\n\n- ModuleNotFoundError: No module named 'msvcrt' [\\#2536](https://github.com/kivy/python-for-android/issues/2536)\n- Pyarrow module do not working [\\#2531](https://github.com/kivy/python-for-android/issues/2531)\n- Error when building Android Application with Google modules [\\#2530](https://github.com/kivy/python-for-android/issues/2530)\n- arm64-v8a \\(apk and aab lib\\) crashes [\\#2524](https://github.com/kivy/python-for-android/issues/2524)\n- Python for android [\\#2521](https://github.com/kivy/python-for-android/issues/2521)\n- ValueError: name is too long [\\#2517](https://github.com/kivy/python-for-android/issues/2517)\n- With the target API 31, I got an error on Android 12 phone and cannot install it. [\\#2511](https://github.com/kivy/python-for-android/issues/2511)\n- How to get libnumpy.so & numpy py libs [\\#2510](https://github.com/kivy/python-for-android/issues/2510)\n- pydantic compiling in Buildozer with 'crypt.h' file not found [\\#2507](https://github.com/kivy/python-for-android/issues/2507)\n- p4a built x86\\_64 library\\(psutil, \"ELF 64-bit LSB shared object, x86-64\"\\) for ARM [\\#2506](https://github.com/kivy/python-for-android/issues/2506)\n- matplotlib's recipe problem [\\#2502](https://github.com/kivy/python-for-android/issues/2502)\n- cffi's recipe problem in aab generation  [\\#2501](https://github.com/kivy/python-for-android/issues/2501)\n- Pillow's recipe problem in aab generation [\\#2497](https://github.com/kivy/python-for-android/issues/2497)\n- Python compilation error: LXMLRecipe' object has no attribute 'ndk\\_include\\_dir' [\\#2493](https://github.com/kivy/python-for-android/issues/2493)\n- Android App crashing at launch - SDL seems crashing [\\#2491](https://github.com/kivy/python-for-android/issues/2491)\n- build an android app with ffpyplayer [\\#2453](https://github.com/kivy/python-for-android/issues/2453)\n- How to change \"PythonActivity.java\" to load mp4 file on presplash [\\#2439](https://github.com/kivy/python-for-android/issues/2439)\n- Kivy app krashes on android 10 [\\#2434](https://github.com/kivy/python-for-android/issues/2434)\n- Kivy app crashing on android after installation, what is the issue here? [\\#2433](https://github.com/kivy/python-for-android/issues/2433)\n- Build failed [\\#2366](https://github.com/kivy/python-for-android/issues/2366)\n- Reportlab bitbucket link is not working anymore [\\#2310](https://github.com/kivy/python-for-android/issues/2310)\n- Need to be able to create App Bundles \\(.aab\\) files in addition to APK files [\\#2084](https://github.com/kivy/python-for-android/issues/2084)\n- What exactly is mangling p4a's output with `[lots of missing output]... (and X more)`? [\\#1939](https://github.com/kivy/python-for-android/issues/1939)\n- Unable to compile. [\\#1710](https://github.com/kivy/python-for-android/issues/1710)\n\n**Merged pull requests:**\n\n- Upgrading the flask version to avoid exception at the start of webview application [\\#2560](https://github.com/kivy/python-for-android/pull/2560) ([Prashanth-BC](https://github.com/Prashanth-BC))\n- add recipe for freetype-py to not include the prebuilt .so for the wr… [\\#2558](https://github.com/kivy/python-for-android/pull/2558) ([domedave](https://github.com/domedave))\n- Update to Kivy 2.1.0 [\\#2557](https://github.com/kivy/python-for-android/pull/2557) ([RobertFlatt](https://github.com/RobertFlatt))\n- tflite-runtime recipe [\\#2554](https://github.com/kivy/python-for-android/pull/2554) ([RobertFlatt](https://github.com/RobertFlatt))\n- Update AndroidManifest.tmpl.xml [\\#2551](https://github.com/kivy/python-for-android/pull/2551) ([drahba](https://github.com/drahba))\n- Update recipe.py \\(\\#2544\\) [\\#2546](https://github.com/kivy/python-for-android/pull/2546) ([misl6](https://github.com/misl6))\n- Update python versions matrix on CI [\\#2534](https://github.com/kivy/python-for-android/pull/2534) ([misl6](https://github.com/misl6))\n- Add ifaddr recipe [\\#2527](https://github.com/kivy/python-for-android/pull/2527) ([syrykh](https://github.com/syrykh))\n- Remove websocket-client recipe [\\#2526](https://github.com/kivy/python-for-android/pull/2526) ([syrykh](https://github.com/syrykh))\n- Fix build [\\#2525](https://github.com/kivy/python-for-android/pull/2525) ([correa](https://github.com/correa))\n- Add aaptOptions noCompress [\\#2523](https://github.com/kivy/python-for-android/pull/2523) ([RobertFlatt](https://github.com/RobertFlatt))\n- Updated version of pygame from 2.0.1 to 2.1.0 [\\#2520](https://github.com/kivy/python-for-android/pull/2520) ([CAPTAIN1947](https://github.com/CAPTAIN1947))\n- Bump Pillow version to 8.4.0 [\\#2516](https://github.com/kivy/python-for-android/pull/2516) ([misl6](https://github.com/misl6))\n- Moved support-request to v2. v1 has been shut down. [\\#2515](https://github.com/kivy/python-for-android/pull/2515) ([misl6](https://github.com/misl6))\n- Add support-requests configuration. [\\#2514](https://github.com/kivy/python-for-android/pull/2514) ([misl6](https://github.com/misl6))\n- proper --host for libsecp256k1, libogg, libvorbis, libcurl [\\#2512](https://github.com/kivy/python-for-android/pull/2512) ([accumulator](https://github.com/accumulator))\n- Fix broken Contribute link [\\#2505](https://github.com/kivy/python-for-android/pull/2505) ([daMatz](https://github.com/daMatz))\n- Makes pep8 happy on sdl2 recipe [\\#2504](https://github.com/kivy/python-for-android/pull/2504) ([misl6](https://github.com/misl6))\n- Fix broken recipes with missing arch.arch in get\\_site\\_packages\\_dir [\\#2503](https://github.com/kivy/python-for-android/pull/2503) ([misl6](https://github.com/misl6))\n- added android permission ACCESS\\_BACKGROUND\\_LOCATION [\\#2500](https://github.com/kivy/python-for-android/pull/2500) ([xloem](https://github.com/xloem))\n- GitHub Actions: Fixes wrong actions/checkout@v2 usage [\\#2496](https://github.com/kivy/python-for-android/pull/2496) ([misl6](https://github.com/misl6))\n- Fixes ndk\\_include\\_dir on lxml recipe. [\\#2495](https://github.com/kivy/python-for-android/pull/2495) ([misl6](https://github.com/misl6))\n- Move coveralls to github actions [\\#2490](https://github.com/kivy/python-for-android/pull/2490) ([misl6](https://github.com/misl6))\n- Master [\\#2489](https://github.com/kivy/python-for-android/pull/2489) ([misl6](https://github.com/misl6))\n- Add should\\_build method to sdl2 recipe [\\#2482](https://github.com/kivy/python-for-android/pull/2482) ([AndyRusso](https://github.com/AndyRusso))\n- AAB support related changes [\\#2467](https://github.com/kivy/python-for-android/pull/2467) ([misl6](https://github.com/misl6))\n- services: fix  START\\_STICKY  [\\#2401](https://github.com/kivy/python-for-android/pull/2401) ([mzakharo](https://github.com/mzakharo))\n\n## [v2021.09.05](https://github.com/kivy/python-for-android/tree/v2021.09.05) (2021-09-05)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2020.06.02...v2021.09.05)\n\n**Fixed bugs:**\n\n- Execution failed for task ':compileDebugJavaWithJavac'. \"DialogInterface\", \"AlertDialog\" and \"KeyEvent\" not found. [\\#2228](https://github.com/kivy/python-for-android/issues/2228)\n\n**Closed issues:**\n\n- Global variable/objetct isn't being shared between multiple files on Android, while the same code works normally under Windows. [\\#2485](https://github.com/kivy/python-for-android/issues/2485)\n- audiostream recipe does not copy .java files, leading to a crash when trying to open mic on android [\\#2483](https://github.com/kivy/python-for-android/issues/2483)\n- Applying patches for hostpython3 fails [\\#2476](https://github.com/kivy/python-for-android/issues/2476)\n- p4a failing to build webapp [\\#2475](https://github.com/kivy/python-for-android/issues/2475)\n- Failed to build with recipe MySQLdb \\(setuptools,libmysqlclient\\)  [\\#2474](https://github.com/kivy/python-for-android/issues/2474)\n- ctypes.util.find\\_library 64-bit error [\\#2468](https://github.com/kivy/python-for-android/issues/2468)\n- Android x86\\_64 instant crash with hello world app, missing zlib [\\#2460](https://github.com/kivy/python-for-android/issues/2460)\n- pycrypto recipe is arm7 only [\\#2457](https://github.com/kivy/python-for-android/issues/2457)\n- lazy loading of recycleview and wa [\\#2454](https://github.com/kivy/python-for-android/issues/2454)\n- a kivy app can not send request to any website in phone [\\#2450](https://github.com/kivy/python-for-android/issues/2450)\n- Android permissions not working on Android 10 [\\#2444](https://github.com/kivy/python-for-android/issues/2444)\n- Feature request for upcoming fushia OS [\\#2442](https://github.com/kivy/python-for-android/issues/2442)\n- where is 'main.py' ? [\\#2441](https://github.com/kivy/python-for-android/issues/2441)\n- \"diff\" files are ignored during \"pip install .\" [\\#2435](https://github.com/kivy/python-for-android/issues/2435)\n- Travis CI misconfigured? [\\#2428](https://github.com/kivy/python-for-android/issues/2428)\n- NumPy | ImportError: dlopen failed: cannot locate symbol \"log10f\" referenced by \"\\_multiarray\\_tests.so\" [\\#2426](https://github.com/kivy/python-for-android/issues/2426)\n- Pandas recipe doesn't work [\\#2425](https://github.com/kivy/python-for-android/issues/2425)\n- Error \\(Downloading matplotlib from https://jqueryui.com/resources/download/jquery-ui-1.12.1.zip\\) [\\#2418](https://github.com/kivy/python-for-android/issues/2418)\n- buildozer failed -\\_- [\\#2417](https://github.com/kivy/python-for-android/issues/2417)\n- \"Python for android ended.\" When printing \\x00 [\\#2415](https://github.com/kivy/python-for-android/issues/2415)\n- Does Kivy supports SDK 30? [\\#2411](https://github.com/kivy/python-for-android/issues/2411)\n- Pymunk,kivy apk crashing on Android 5.1  [\\#2388](https://github.com/kivy/python-for-android/issues/2388)\n- Select specific version of python [\\#2373](https://github.com/kivy/python-for-android/issues/2373)\n- MDRaisedButton doesn't work on android [\\#2371](https://github.com/kivy/python-for-android/issues/2371)\n- Add Python 3.9 support as it builds fine [\\#2365](https://github.com/kivy/python-for-android/issues/2365)\n- Gradle Problem [\\#2352](https://github.com/kivy/python-for-android/issues/2352)\n- service\\_library lacks mActivity support [\\#2350](https://github.com/kivy/python-for-android/issues/2350)\n- Possible bug in the \"opencv\\_extras\" recipe [\\#2349](https://github.com/kivy/python-for-android/issues/2349)\n- Pygame recipe \\(sdl confusion\\) [\\#2342](https://github.com/kivy/python-for-android/issues/2342)\n- Youtube-dl [\\#2336](https://github.com/kivy/python-for-android/issues/2336)\n- building apk using pygame  [\\#2333](https://github.com/kivy/python-for-android/issues/2333)\n- Buildozer ValueError: specify a path with --storage-dir [\\#2331](https://github.com/kivy/python-for-android/issues/2331)\n- Webview app crashes when trying to request permissions [\\#2325](https://github.com/kivy/python-for-android/issues/2325)\n- ffmpeg requirement causes apk crash on android [\\#2324](https://github.com/kivy/python-for-android/issues/2324)\n- ModuleNotFoundError: No module named 'fcntl' [\\#2321](https://github.com/kivy/python-for-android/issues/2321)\n- error python-for-android: git clone [\\#2319](https://github.com/kivy/python-for-android/issues/2319)\n- Pillow with kivy and kivymd error ImportError: dlopen failed: cannot locate symbol \"log\" referenced by \"\\_imaging.so\"... [\\#2317](https://github.com/kivy/python-for-android/issues/2317)\n- pyconfig\\_detection.patch breaks \\(at least\\) --use-setup-py [\\#2313](https://github.com/kivy/python-for-android/issues/2313)\n- Cython is called in incompatible way, not all options are supported \\(especially -I\\) [\\#2311](https://github.com/kivy/python-for-android/issues/2311)\n- PR 2285 breaks requestPermissions  [\\#2304](https://github.com/kivy/python-for-android/issues/2304)\n- Python services [\\#2297](https://github.com/kivy/python-for-android/issues/2297)\n- Integrating ads in P4A [\\#2295](https://github.com/kivy/python-for-android/issues/2295)\n- How to hiding Android keyboard in kivy? [\\#2268](https://github.com/kivy/python-for-android/issues/2268)\n- KivyMD \\#257 Bug vs MatPlotLib, one or the other??? [\\#2262](https://github.com/kivy/python-for-android/issues/2262)\n- MatPlotLib not building with Buildozer \\(but it did 2 months ago\\) [\\#2261](https://github.com/kivy/python-for-android/issues/2261)\n- How to add webp support to pillow? [\\#2254](https://github.com/kivy/python-for-android/issues/2254)\n- Unable to run executable on Android [\\#2251](https://github.com/kivy/python-for-android/issues/2251)\n- Provide a native service option [\\#2243](https://github.com/kivy/python-for-android/issues/2243)\n- the kivy app crash in time of import psycopg2 [\\#2241](https://github.com/kivy/python-for-android/issues/2241)\n- How can we make a camera App with zoom by Kivy [\\#2238](https://github.com/kivy/python-for-android/issues/2238)\n- Building pycrypto for arm64-v8a fails [\\#2230](https://github.com/kivy/python-for-android/issues/2230)\n- buildozer error Building pycrypto for arm64-v8a and x86\\_64 [\\#2216](https://github.com/kivy/python-for-android/issues/2216)\n- Does the opencv recipe for buildozer not include the extra face class? [\\#2166](https://github.com/kivy/python-for-android/issues/2166)\n- Crash at relaunch in pythonlib in SDL2Thread [\\#1787](https://github.com/kivy/python-for-android/issues/1787)\n- Background Service is killed when app is swiped \\(close by user\\) [\\#1783](https://github.com/kivy/python-for-android/issues/1783)\n- tarfile failure with long user ID [\\#1013](https://github.com/kivy/python-for-android/issues/1013)\n- Apps are too big \\(in size\\) [\\#202](https://github.com/kivy/python-for-android/issues/202)\n\n**Merged pull requests:**\n\n- Drop travis-ci.org and use github-actions for pypi release [\\#2487](https://github.com/kivy/python-for-android/pull/2487) ([misl6](https://github.com/misl6))\n- Upgrade grunt version re: CVE-2020-7729 [\\#2484](https://github.com/kivy/python-for-android/pull/2484) ([Zen-CODE](https://github.com/Zen-CODE))\n- Recipe for pydantic [\\#2480](https://github.com/kivy/python-for-android/pull/2480) ([FilipeMarch](https://github.com/FilipeMarch))\n- fix pandas recipe [\\#2478](https://github.com/kivy/python-for-android/pull/2478) ([mzakharo](https://github.com/mzakharo))\n- Add \\*.diff to manifest and package\\_data [\\#2471](https://github.com/kivy/python-for-android/pull/2471) ([viblo](https://github.com/viblo))\n- Fix bad library found by ctypes for 64-bit arch \\(\\#2468\\) [\\#2469](https://github.com/kivy/python-for-android/pull/2469) ([macdems](https://github.com/macdems))\n- Updated version of pygame from 2.0.0-dev7 to 2.0.1 [\\#2466](https://github.com/kivy/python-for-android/pull/2466) ([ljnath](https://github.com/ljnath))\n- Adds libm to Pillow recipe [\\#2465](https://github.com/kivy/python-for-android/pull/2465) ([Sahil-pixel](https://github.com/Sahil-pixel))\n- Add missing mipmap directories to bootstraps. [\\#2463](https://github.com/kivy/python-for-android/pull/2463) ([rnixx](https://github.com/rnixx))\n- Move PythonActivityUtil.unpackData to PythonUtil.unpackData [\\#2462](https://github.com/kivy/python-for-android/pull/2462) ([rnixx](https://github.com/rnixx))\n- Websocket-client updated to 1.0.1 from 0.40.0 [\\#2458](https://github.com/kivy/python-for-android/pull/2458) ([akshayaurora](https://github.com/akshayaurora))\n- fixed redirection for download liblzma [\\#2452](https://github.com/kivy/python-for-android/pull/2452) ([td1803](https://github.com/td1803))\n- update \\(host\\)python3 to 3.8.9 [\\#2451](https://github.com/kivy/python-for-android/pull/2451) ([obfusk](https://github.com/obfusk))\n- update sqlite3 [\\#2449](https://github.com/kivy/python-for-android/pull/2449) ([obfusk](https://github.com/obfusk))\n- build.py: also clean\\(\\) tarfile directory entries [\\#2447](https://github.com/kivy/python-for-android/pull/2447) ([obfusk](https://github.com/obfusk))\n- android: add support for adaptive icon/launcher [\\#2446](https://github.com/kivy/python-for-android/pull/2446) ([SomberNight](https://github.com/SomberNight))\n- Fix ImportError bug: cannot locate symbol \"modf\" [\\#2445](https://github.com/kivy/python-for-android/pull/2445) ([Neizvestnyj](https://github.com/Neizvestnyj))\n- update openssl [\\#2443](https://github.com/kivy/python-for-android/pull/2443) ([obfusk](https://github.com/obfusk))\n- Add libvpx recipe, reference it in ffpyplayer\\_codecs and ffmpeg [\\#2440](https://github.com/kivy/python-for-android/pull/2440) ([Cheaterman](https://github.com/Cheaterman))\n- Add setuptools dependency for Groestlcoin\\_hash recipe [\\#2438](https://github.com/kivy/python-for-android/pull/2438) ([gruve-p](https://github.com/gruve-p))\n- set urllib user-agent [\\#2437](https://github.com/kivy/python-for-android/pull/2437) ([obfusk](https://github.com/obfusk))\n- setup.py: add \\*.diff to package\\_data [\\#2436](https://github.com/kivy/python-for-android/pull/2436) ([obfusk](https://github.com/obfusk))\n- Reintroduce documentation of android module [\\#2432](https://github.com/kivy/python-for-android/pull/2432) ([tito](https://github.com/tito))\n- Update \\_\\_init\\_\\_.py [\\#2429](https://github.com/kivy/python-for-android/pull/2429) ([Neizvestnyj](https://github.com/Neizvestnyj))\n- webview: flush cookies & improve shouldOverrideUrlLoading [\\#2424](https://github.com/kivy/python-for-android/pull/2424) ([obfusk](https://github.com/obfusk))\n- travis: update pip \\(fixes CI\\) [\\#2422](https://github.com/kivy/python-for-android/pull/2422) ([obfusk](https://github.com/obfusk))\n- update openssl [\\#2421](https://github.com/kivy/python-for-android/pull/2421) ([obfusk](https://github.com/obfusk))\n- Avoids build error for opencv and bumps version to 4.5.1 [\\#2419](https://github.com/kivy/python-for-android/pull/2419) ([thescheff](https://github.com/thescheff))\n- strip null bytes in call to androidembed.log\\(\\) [\\#2416](https://github.com/kivy/python-for-android/pull/2416) ([obfusk](https://github.com/obfusk))\n- webview: put webview\\_includes in assets dir [\\#2412](https://github.com/kivy/python-for-android/pull/2412) ([obfusk](https://github.com/obfusk))\n- update setuptools [\\#2409](https://github.com/kivy/python-for-android/pull/2409) ([obfusk](https://github.com/obfusk))\n- update sqlite3 [\\#2408](https://github.com/kivy/python-for-android/pull/2408) ([obfusk](https://github.com/obfusk))\n- Update quickstart.rst macOS brew cask command [\\#2407](https://github.com/kivy/python-for-android/pull/2407) ([yestoday-tv](https://github.com/yestoday-tv))\n- Added ability to set input type on android [\\#2405](https://github.com/kivy/python-for-android/pull/2405) ([dwmoffatt](https://github.com/dwmoffatt))\n- PyZQM recipe needs setuptools, list it explicitly in deps [\\#2404](https://github.com/kivy/python-for-android/pull/2404) ([rambo](https://github.com/rambo))\n- recipes: print recipe ENV on failure [\\#2403](https://github.com/kivy/python-for-android/pull/2403) ([mzakharo](https://github.com/mzakharo))\n- recipes: scipy - fix build for armeabi-v7a [\\#2402](https://github.com/kivy/python-for-android/pull/2402) ([mzakharo](https://github.com/mzakharo))\n- don't run git pull when not on a branch [\\#2400](https://github.com/kivy/python-for-android/pull/2400) ([obfusk](https://github.com/obfusk))\n- Fix Pymunk crash on older versions of Android [\\#2399](https://github.com/kivy/python-for-android/pull/2399) ([viblo](https://github.com/viblo))\n- Recipe for argon2-cffi [\\#2398](https://github.com/kivy/python-for-android/pull/2398) ([Arjun-Somvanshi](https://github.com/Arjun-Somvanshi))\n- update sqlite3 to 3.34.0 [\\#2397](https://github.com/kivy/python-for-android/pull/2397) ([obfusk](https://github.com/obfusk))\n- update openssl to 1.1.1i [\\#2396](https://github.com/kivy/python-for-android/pull/2396) ([obfusk](https://github.com/obfusk))\n- support Python 3.9 [\\#2394](https://github.com/kivy/python-for-android/pull/2394) ([obfusk](https://github.com/obfusk))\n- reproducible builds [\\#2390](https://github.com/kivy/python-for-android/pull/2390) ([obfusk](https://github.com/obfusk))\n- Update Pymunk recipe to 6.0.0 [\\#2389](https://github.com/kivy/python-for-android/pull/2389) ([viblo](https://github.com/viblo))\n- webview: add mOpenExternalLinksInBrowser field [\\#2387](https://github.com/kivy/python-for-android/pull/2387) ([obfusk](https://github.com/obfusk))\n- webview: add enableZoom\\(\\) method [\\#2386](https://github.com/kivy/python-for-android/pull/2386) ([obfusk](https://github.com/obfusk))\n- Enable AndroidX [\\#2385](https://github.com/kivy/python-for-android/pull/2385) ([RobertFlatt](https://github.com/RobertFlatt))\n- :arrow\\_up: Updates to Kivy2 [\\#2384](https://github.com/kivy/python-for-android/pull/2384) ([kuzeyron](https://github.com/kuzeyron))\n- fix travis [\\#2383](https://github.com/kivy/python-for-android/pull/2383) ([obfusk](https://github.com/obfusk))\n- :bug: Fixes pip command on Travis and bumps actions/setup-python [\\#2382](https://github.com/kivy/python-for-android/pull/2382) ([AndreMiras](https://github.com/AndreMiras))\n- docs: fix simple typo, packaged -\\> packaged [\\#2377](https://github.com/kivy/python-for-android/pull/2377) ([timgates42](https://github.com/timgates42))\n- Fix master only merges [\\#2376](https://github.com/kivy/python-for-android/pull/2376) ([inclement](https://github.com/inclement))\n- Audiostream Fix [\\#2375](https://github.com/kivy/python-for-android/pull/2375) ([xloem](https://github.com/xloem))\n- Add service information for buildozer.spec [\\#2372](https://github.com/kivy/python-for-android/pull/2372) ([xloem](https://github.com/xloem))\n- recipes: add scipy support [\\#2370](https://github.com/kivy/python-for-android/pull/2370) ([mzakharo](https://github.com/mzakharo))\n- fix CI [\\#2368](https://github.com/kivy/python-for-android/pull/2368) ([obfusk](https://github.com/obfusk))\n- support activity\\_launch\\_mode in webview bootstrap [\\#2367](https://github.com/kivy/python-for-android/pull/2367) ([obfusk](https://github.com/obfusk))\n- Support running tests on any arch [\\#2355](https://github.com/kivy/python-for-android/pull/2355) ([jayvdb](https://github.com/jayvdb))\n- setup.py: Fix dependency syntax [\\#2354](https://github.com/kivy/python-for-android/pull/2354) ([jayvdb](https://github.com/jayvdb))\n- added missing digest support to recipes [\\#2351](https://github.com/kivy/python-for-android/pull/2351) ([fuzzyTew](https://github.com/fuzzyTew))\n- android: call non-static methods on .mActivity [\\#2341](https://github.com/kivy/python-for-android/pull/2341) ([obfusk](https://github.com/obfusk))\n- fix webview jni [\\#2340](https://github.com/kivy/python-for-android/pull/2340) ([obfusk](https://github.com/obfusk))\n- missing mActivity [\\#2339](https://github.com/kivy/python-for-android/pull/2339) ([zworkb](https://github.com/zworkb))\n- added few input parameters to make possible to use custom java classes and tweak AndroidManifest.xml [\\#2338](https://github.com/kivy/python-for-android/pull/2338) ([vesellov](https://github.com/vesellov))\n- ffmpeg and ffpyplayer improvements [\\#2335](https://github.com/kivy/python-for-android/pull/2335) ([rnixx](https://github.com/rnixx))\n- Add recipe for https://docs.aiohttp.org/en/stable/ [\\#2332](https://github.com/kivy/python-for-android/pull/2332) ([rambo](https://github.com/rambo))\n- Update \\_\\_init\\_\\_.py for Report Lab recipe [\\#2323](https://github.com/kivy/python-for-android/pull/2323) ([marcets](https://github.com/marcets))\n- Print patched message to stderr [\\#2314](https://github.com/kivy/python-for-android/pull/2314) ([rambo](https://github.com/rambo))\n- Call cython via the setuptools entrypoint, fixes \\#2311 [\\#2312](https://github.com/kivy/python-for-android/pull/2312) ([rambo](https://github.com/rambo))\n- Allow using background color with lottie splashscreen [\\#2305](https://github.com/kivy/python-for-android/pull/2305) ([tshirtman](https://github.com/tshirtman))\n- Add manifestPlaceholders [\\#2301](https://github.com/kivy/python-for-android/pull/2301) ([misl6](https://github.com/misl6))\n- Allow using lottie files for splashscreen \\(SDL2 bootstrap\\) [\\#2296](https://github.com/kivy/python-for-android/pull/2296) ([tshirtman](https://github.com/tshirtman))\n- use https download for boost & zope [\\#2293](https://github.com/kivy/python-for-android/pull/2293) ([obfusk](https://github.com/obfusk))\n- \\(host\\)python3: rm version check for pyconfig patch [\\#2292](https://github.com/kivy/python-for-android/pull/2292) ([obfusk](https://github.com/obfusk))\n- download\\_file: show error + exponential sleep [\\#2291](https://github.com/kivy/python-for-android/pull/2291) ([obfusk](https://github.com/obfusk))\n- remove cruft from webview\\_includes/\\_load.html [\\#2289](https://github.com/kivy/python-for-android/pull/2289) ([obfusk](https://github.com/obfusk))\n- tiny whitespace fix in buildoptions.rst [\\#2288](https://github.com/kivy/python-for-android/pull/2288) ([obfusk](https://github.com/obfusk))\n- update libffi to 3.3 [\\#2287](https://github.com/kivy/python-for-android/pull/2287) ([obfusk](https://github.com/obfusk))\n- update openssl to 1.1.1g [\\#2286](https://github.com/kivy/python-for-android/pull/2286) ([obfusk](https://github.com/obfusk))\n- update pyjnius to 1.3.0 [\\#2285](https://github.com/kivy/python-for-android/pull/2285) ([obfusk](https://github.com/obfusk))\n- update setuptools to 49.2.1 [\\#2284](https://github.com/kivy/python-for-android/pull/2284) ([obfusk](https://github.com/obfusk))\n- update six to 1.15.0 [\\#2283](https://github.com/kivy/python-for-android/pull/2283) ([obfusk](https://github.com/obfusk))\n- update flask to 1.1.2 [\\#2282](https://github.com/kivy/python-for-android/pull/2282) ([obfusk](https://github.com/obfusk))\n- update python3 & hostpython3 to 3.8.5 [\\#2281](https://github.com/kivy/python-for-android/pull/2281) ([obfusk](https://github.com/obfusk))\n- update sqlite3 to 3.32.3 [\\#2280](https://github.com/kivy/python-for-android/pull/2280) ([obfusk](https://github.com/obfusk))\n- fix travis [\\#2279](https://github.com/kivy/python-for-android/pull/2279) ([obfusk](https://github.com/obfusk))\n- `libpython3.8m.so` should not have `m` suffix [\\#2278](https://github.com/kivy/python-for-android/pull/2278) ([davidhewitt](https://github.com/davidhewitt))\n- add recipe for libpcre [\\#2276](https://github.com/kivy/python-for-android/pull/2276) ([obfusk](https://github.com/obfusk))\n- build python3 with loadable-sqlite-extensions [\\#2275](https://github.com/kivy/python-for-android/pull/2275) ([obfusk](https://github.com/obfusk))\n- fix indentation [\\#2273](https://github.com/kivy/python-for-android/pull/2273) ([obfusk](https://github.com/obfusk))\n- LogFile: rename .buffer \\(to fix newer flask/click\\) [\\#2264](https://github.com/kivy/python-for-android/pull/2264) ([obfusk](https://github.com/obfusk))\n- Add Recipe for Kivy3 module  [\\#2263](https://github.com/kivy/python-for-android/pull/2263) ([excepterror](https://github.com/excepterror))\n- :sparkles: Rework of Pillow recipe adding WebP support [\\#2256](https://github.com/kivy/python-for-android/pull/2256) ([opacam](https://github.com/opacam))\n- :sparkles: Add libwebp recipe [\\#2255](https://github.com/kivy/python-for-android/pull/2255) ([opacam](https://github.com/opacam))\n- added --activity-class-name and --activity-package parameters  [\\#2248](https://github.com/kivy/python-for-android/pull/2248) ([vesellov](https://github.com/vesellov))\n- Fix runtime psycopg2 error [\\#2246](https://github.com/kivy/python-for-android/pull/2246) ([Progdrasil](https://github.com/Progdrasil))\n- Bump libpq version [\\#2245](https://github.com/kivy/python-for-android/pull/2245) ([Progdrasil](https://github.com/Progdrasil))\n- Support for native services [\\#2244](https://github.com/kivy/python-for-android/pull/2244) ([lerela](https://github.com/lerela))\n- Added missing semicolon on service-only bootstrap [\\#2236](https://github.com/kivy/python-for-android/pull/2236) ([Swpolo](https://github.com/Swpolo))\n- :green\\_heart: Fixes Travis build post OpenJDK bump [\\#2235](https://github.com/kivy/python-for-android/pull/2235) ([AndreMiras](https://github.com/AndreMiras))\n- Updated gradle plugin version [\\#2234](https://github.com/kivy/python-for-android/pull/2234) ([shashi278](https://github.com/shashi278))\n- :bug: Fixes service\\_only and webview symbol errors, closes \\#2228 [\\#2233](https://github.com/kivy/python-for-android/pull/2233) ([AndreMiras](https://github.com/AndreMiras))\n- :bento: Add CHANGELOG.md [\\#2232](https://github.com/kivy/python-for-android/pull/2232) ([opacam](https://github.com/opacam))\n- :arrow\\_up: Bumps to OpenJDK 13 [\\#2231](https://github.com/kivy/python-for-android/pull/2231) ([AndreMiras](https://github.com/AndreMiras))\n- Fixed KeyError not found [\\#2229](https://github.com/kivy/python-for-android/pull/2229) ([sak96](https://github.com/sak96))\n- :rotating\\_light: Depreciation warning fixes [\\#2227](https://github.com/kivy/python-for-android/pull/2227) ([AndreMiras](https://github.com/AndreMiras))\n- Master [\\#2226](https://github.com/kivy/python-for-android/pull/2226) ([AndreMiras](https://github.com/AndreMiras))\n- Add possibility to add a backup rules xml file [\\#2208](https://github.com/kivy/python-for-android/pull/2208) ([tcdude](https://github.com/tcdude))\n\n## [v2020.06.02](https://github.com/kivy/python-for-android/tree/v2020.06.02) (2020-06-02)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2020.04.29...v2020.06.02)\n\n**Fixed bugs:**\n\n- Issues introduced by PR \\#2113 \\(SDL2\\) [\\#2169](https://github.com/kivy/python-for-android/issues/2169)\n- App exists immediately when importing kivy.core.window.Window [\\#2167](https://github.com/kivy/python-for-android/issues/2167)\n\n**Closed issues:**\n\n- Python [\\#2214](https://github.com/kivy/python-for-android/issues/2214)\n- build failed. [\\#2212](https://github.com/kivy/python-for-android/issues/2212)\n- apk size growing [\\#2207](https://github.com/kivy/python-for-android/issues/2207)\n- My despair at trying to simply import the opencv face class \\(python-for-android\\)  [\\#2206](https://github.com/kivy/python-for-android/issues/2206)\n- running python 3.6 [\\#2204](https://github.com/kivy/python-for-android/issues/2204)\n- specify python version [\\#2203](https://github.com/kivy/python-for-android/issues/2203)\n- p4a gets stuck at downloading setuptools [\\#2199](https://github.com/kivy/python-for-android/issues/2199)\n- Kivy app crashes after asking for permission  [\\#2054](https://github.com/kivy/python-for-android/issues/2054)\n\n**Merged pull requests:**\n\n- Release 2020.06.02 [\\#2225](https://github.com/kivy/python-for-android/pull/2225) ([AndreMiras](https://github.com/AndreMiras))\n- :arrow_up: Bumps to Gradle 6.4.1 [\\#2222](https://github.com/kivy/python-for-android/pull/2222) ([AndreMiras](https://github.com/AndreMiras))\n- :bug: Adds missing requests sub dependencies [\\#2221](https://github.com/kivy/python-for-android/pull/2221) ([AndreMiras](https://github.com/AndreMiras))\n- :arrow_up: Bumps to Cython==0.29.19 [\\#2220](https://github.com/kivy/python-for-android/pull/2220) ([AndreMiras](https://github.com/AndreMiras))\n- :pencil: Updates install and troubleshooting docs [\\#2219](https://github.com/kivy/python-for-android/pull/2219) ([AndreMiras](https://github.com/AndreMiras))\n- :arrow_up: Bumps to Ubuntu 20.04 [\\#2218](https://github.com/kivy/python-for-android/pull/2218) ([AndreMiras](https://github.com/AndreMiras))\n- :pencil: Attempt to improve the issue template [\\#2217](https://github.com/kivy/python-for-android/pull/2217) ([AndreMiras](https://github.com/AndreMiras))\n- :package: Split logic for build modes & debug symbols [\\#2213](https://github.com/kivy/python-for-android/pull/2213) ([opacam](https://github.com/opacam))\n- :sparkles: Add `opencv_extras` recipe [\\#2209](https://github.com/kivy/python-for-android/pull/2209) ([opacam](https://github.com/opacam))\n- :books: Troubleshoot SSL error [\\#2205](https://github.com/kivy/python-for-android/pull/2205) ([AndreMiras](https://github.com/AndreMiras))\n- Remove superfluous recipes fixes \\#1387 [\\#2202](https://github.com/kivy/python-for-android/pull/2202) ([AndreMiras](https://github.com/AndreMiras))\n- :white_check_mark: Add tests for hostpython3 recipe [\\#2196](https://github.com/kivy/python-for-android/pull/2196) ([opacam](https://github.com/opacam))\n- Fix for 'ImportError: No module named setuptools', encountered when building with buildozer [\\#2195](https://github.com/kivy/python-for-android/pull/2195) ([atom2626](https://github.com/atom2626))\n- :pencil2: Rename `Hostpython3Recipe` class to camel case [\\#2194](https://github.com/kivy/python-for-android/pull/2194) ([opacam](https://github.com/opacam))\n- :ambulance: Fix `test_should_build` [\\#2193](https://github.com/kivy/python-for-android/pull/2193) ([opacam](https://github.com/opacam))\n- :white_check_mark: Add initial tests for python3 recipe [\\#2192](https://github.com/kivy/python-for-android/pull/2192) ([opacam](https://github.com/opacam))\n- :bug: Fixes flake8 errors post update [\\#2191](https://github.com/kivy/python-for-android/pull/2191) ([AndreMiras](https://github.com/AndreMiras))\n- PythonActivityUtil helper for unpacking data [\\#2189](https://github.com/kivy/python-for-android/pull/2189) ([AndreMiras](https://github.com/AndreMiras))\n- Share PythonUtil.java between bootstraps [\\#2188](https://github.com/kivy/python-for-android/pull/2188) ([AndreMiras](https://github.com/AndreMiras))\n- Java code linting using PMD 6.23.0 [\\#2187](https://github.com/kivy/python-for-android/pull/2187) ([AndreMiras](https://github.com/AndreMiras))\n- Deletes deprecated renpy Python{Activity,Service}.java [\\#2186](https://github.com/kivy/python-for-android/pull/2186) ([AndreMiras](https://github.com/AndreMiras))\n- Removes java concurrency/ folder [\\#2185](https://github.com/kivy/python-for-android/pull/2185) ([AndreMiras](https://github.com/AndreMiras))\n- Moves kamranzafar/ java directory to common/ [\\#2184](https://github.com/kivy/python-for-android/pull/2184) ([AndreMiras](https://github.com/AndreMiras))\n- Use common Hardware.java [\\#2183](https://github.com/kivy/python-for-android/pull/2183) ([AndreMiras](https://github.com/AndreMiras))\n- Reuse common AssetExtract.java [\\#2182](https://github.com/kivy/python-for-android/pull/2182) ([AndreMiras](https://github.com/AndreMiras))\n- Fixes service only unittest loading [\\#2181](https://github.com/kivy/python-for-android/pull/2181) ([AndreMiras](https://github.com/AndreMiras))\n- Downgrades to SDL2 2.0.9 [\\#2180](https://github.com/kivy/python-for-android/pull/2180) ([AndreMiras](https://github.com/AndreMiras))\n- Narrows some context manager scopes [\\#2179](https://github.com/kivy/python-for-android/pull/2179) ([AndreMiras](https://github.com/AndreMiras))\n- Updates release documentation [\\#2177](https://github.com/kivy/python-for-android/pull/2177) ([AndreMiras](https://github.com/AndreMiras))\n- Post release version bump 2020.04.29.dev0 [\\#2176](https://github.com/kivy/python-for-android/pull/2176) ([AndreMiras](https://github.com/AndreMiras))\n- Updates version number to 2020.04.29 [\\#2175](https://github.com/kivy/python-for-android/pull/2175) ([AndreMiras](https://github.com/AndreMiras))\n- Adds macOS install instructions [\\#2165](https://github.com/kivy/python-for-android/pull/2165) ([AndreMiras](https://github.com/AndreMiras))\n- Adds pygame recipe [\\#2164](https://github.com/kivy/python-for-android/pull/2164) ([AndreMiras](https://github.com/AndreMiras))\n- Removed python2 support mention from README [\\#2162](https://github.com/kivy/python-for-android/pull/2162) ([inclement](https://github.com/inclement))\n- Fixes hostpython build with macOS venv [\\#2159](https://github.com/kivy/python-for-android/pull/2159) ([AndreMiras](https://github.com/AndreMiras))\n- Get --add-source working for dirs in Gradle builds [\\#2156](https://github.com/kivy/python-for-android/pull/2156) ([kollivier](https://github.com/kollivier))\n- Adding more assets [\\#2132](https://github.com/kivy/python-for-android/pull/2132) ([robertpfeiffer](https://github.com/robertpfeiffer))\n- Bump to SDL2 2.0.10 & extract .java from SDL2 tarball: merge conflicts fixed [\\#2113](https://github.com/kivy/python-for-android/pull/2113) ([inclement](https://github.com/inclement))\n\n## [v2020.04.29](https://github.com/kivy/python-for-android/tree/v2020.04.29) (2020-05-07)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2020.03.30...v2020.04.29)\n\n**Closed issues:**\n\n- BUILD FAILURE: No main.py\\(o\\) found in your app directory. [\\#2171](https://github.com/kivy/python-for-android/issues/2171)\n- \\[ERROR\\] Building cffi for armeabi-v7a [\\#2161](https://github.com/kivy/python-for-android/issues/2161)\n- Setting p4a.source_dir in buildozer causes AttributeError: module 'build' has no attribute 'parse\\_args\\_and\\_make\\_package' [\\#2149](https://github.com/kivy/python-for-android/issues/2149)\n- Full screen apps have significantly degraded performance [\\#2148](https://github.com/kivy/python-for-android/issues/2148)\n- Build failed: Couldn't find executable for CC [\\#2146](https://github.com/kivy/python-for-android/issues/2146)\n- Sign in apk is not working [\\#2139](https://github.com/kivy/python-for-android/issues/2139)\n- openssl 1.1.1 has moved, recipe fails [\\#2119](https://github.com/kivy/python-for-android/issues/2119)\n- App not asking for permission [\\#2086](https://github.com/kivy/python-for-android/issues/2086)\n- app on android 6.0.1 does not work, but on android 8.0 if [\\#1801](https://github.com/kivy/python-for-android/issues/1801)\n\n**Merged pull requests:**\n\n- Release 2020.04.29 [\\#2174](https://github.com/kivy/python-for-android/pull/2174) ([AndreMiras](https://github.com/AndreMiras))\n- Fixes sh `_env` should be a dictionary [\\#2160](https://github.com/kivy/python-for-android/pull/2160) ([AndreMiras](https://github.com/AndreMiras))\n- :rotating light: Fix linting for setup.py [\\#2158](https://github.com/kivy/python-for-android/pull/2158) ([opacam](https://github.com/opacam))\n- When bootstraps were unified, sources moved from src to src/main/java… [\\#2154](https://github.com/kivy/python-for-android/pull/2154) ([kollivier](https://github.com/kollivier))\n- Show loading screen while unpacking for webview bootstrap [\\#2153](https://github.com/kivy/python-for-android/pull/2153) ([kollivier](https://github.com/kollivier))\n-  Use python3's venv instead of virtualenv [\\#2152](https://github.com/kivy/python-for-android/pull/2152) ([opacam](https://github.com/opacam))\n- Update setup.py: add classifiers & python\\_requires [\\#2151](https://github.com/kivy/python-for-android/pull/2151) ([opacam](https://github.com/opacam))\n- Twisted recipe [\\#2147](https://github.com/kivy/python-for-android/pull/2147) ([pavelsof](https://github.com/pavelsof))\n- Update AndroidManifest.tmpl.xml to support HTTP [\\#2143](https://github.com/kivy/python-for-android/pull/2143) ([yingshaoxo](https://github.com/yingshaoxo))\n- Simplifies Dockerfile and fix GitHub runner space [\\#2142](https://github.com/kivy/python-for-android/pull/2142) ([AndreMiras](https://github.com/AndreMiras))\n- Fix some code quality and bug-risk issues [\\#2141](https://github.com/kivy/python-for-android/pull/2141) ([pnijhara](https://github.com/pnijhara))\n- Update bootstrap.py [\\#2137](https://github.com/kivy/python-for-android/pull/2137) ([yingshaoxo](https://github.com/yingshaoxo))\n- :lock: Bump twisted version to `20.3.0` [\\#2135](https://github.com/kivy/python-for-android/pull/2135) ([opacam](https://github.com/opacam))\n- release-2020.03.30 to develop [\\#2134](https://github.com/kivy/python-for-android/pull/2134) ([AndreMiras](https://github.com/AndreMiras))\n- Bumps cffi==1.13.2 fixes under Python 3.8 [\\#2131](https://github.com/kivy/python-for-android/pull/2131) ([AndreMiras](https://github.com/AndreMiras))\n- recipe: update 'cryptography' and rm unnecessary dependencies [\\#2130](https://github.com/kivy/python-for-android/pull/2130) ([SomberNight](https://github.com/SomberNight))\n- Fix coveralls error on GitHub Actions [\\#2129](https://github.com/kivy/python-for-android/pull/2129) ([AndreMiras](https://github.com/AndreMiras))\n- bump zeroconf version to 0.24.5, fix depends [\\#2128](https://github.com/kivy/python-for-android/pull/2128) ([mikevlz](https://github.com/mikevlz))\n- Install already compiled libraries [\\#2127](https://github.com/kivy/python-for-android/pull/2127) ([robertpfeiffer](https://github.com/robertpfeiffer))\n- Fixes code block directives [\\#2126](https://github.com/kivy/python-for-android/pull/2126) ([AndreMiras](https://github.com/AndreMiras))\n- Merge master into develop [\\#2125](https://github.com/kivy/python-for-android/pull/2125) ([inclement](https://github.com/inclement))\n- Merge master into develop [\\#2123](https://github.com/kivy/python-for-android/pull/2123) ([inclement](https://github.com/inclement))\n- Auto deploys to PyPI using Travis on tags [\\#2122](https://github.com/kivy/python-for-android/pull/2122) ([AndreMiras](https://github.com/AndreMiras))\n- Update recommended NDK version to 19c [\\#2116](https://github.com/kivy/python-for-android/pull/2116) ([inclement](https://github.com/inclement))\n- Minor fixes and cleanup [\\#2115](https://github.com/kivy/python-for-android/pull/2115) ([AndreMiras](https://github.com/AndreMiras))\n- Fixes linting errors in runnable.py [\\#2114](https://github.com/kivy/python-for-android/pull/2114) ([AndreMiras](https://github.com/AndreMiras))\n- :package: Refactor python module into hostpython3/python3 recipes [\\#2108](https://github.com/kivy/python-for-android/pull/2108) ([opacam](https://github.com/opacam))\n- :fire: Move to python3 `super` calls [\\#2106](https://github.com/kivy/python-for-android/pull/2106) ([opacam](https://github.com/opacam))\n- :fire: Drop Python 2 support [\\#2105](https://github.com/kivy/python-for-android/pull/2105) ([opacam](https://github.com/opacam))\n- Android library [\\#2092](https://github.com/kivy/python-for-android/pull/2092) ([zworkb](https://github.com/zworkb))\n- \\[recipes\\] Update harfbuzz to v2.6.4 [\\#2069](https://github.com/kivy/python-for-android/pull/2069) ([opacam](https://github.com/opacam))\n- \\[recipes\\] Update freetype & add zlib support [\\#2068](https://github.com/kivy/python-for-android/pull/2068) ([opacam](https://github.com/opacam))\n- Unify most of the test apps into `on device unit test app` [\\#2046](https://github.com/kivy/python-for-android/pull/2046) ([opacam](https://github.com/opacam))\n- Fix debug build not resulting in gdb-debuggable build [\\#1867](https://github.com/kivy/python-for-android/pull/1867) ([etc0de](https://github.com/etc0de))\n- fix for the problem with decorator run\\_on\\_ui\\_thread and the local ref… [\\#1830](https://github.com/kivy/python-for-android/pull/1830) ([oukiar](https://github.com/oukiar))\n\n## [v2020.03.30](https://github.com/kivy/python-for-android/tree/v2020.03.30) (2020-04-04)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2019.10.06...v2020.03.30)\n\n**Fixed bugs:**\n\n- Remove `--sysroot` from LDFLAGS for cffi and pymunk [\\#1965](https://github.com/kivy/python-for-android/pull/1965) ([opacam](https://github.com/opacam))\n-  Fix build for case-insensitive FS and add CI test for OSX [\\#1951](https://github.com/kivy/python-for-android/pull/1951) ([opacam](https://github.com/opacam))\n- Also copy the service/main.py when building with setup.py [\\#1936](https://github.com/kivy/python-for-android/pull/1936) ([etc0de](https://github.com/etc0de))\n\n**Closed issues:**\n\n- Version bump for zeroconf to 0.25.4 [\\#2107](https://github.com/kivy/python-for-android/issues/2107)\n- ValueError: read of closed file after download of psycopg2 [\\#2098](https://github.com/kivy/python-for-android/issues/2098)\n- Why advise us to use Python2??? [\\#2090](https://github.com/kivy/python-for-android/issues/2090)\n- KiwiSolver error led build fail when require matplotlib [\\#2080](https://github.com/kivy/python-for-android/issues/2080)\n- Is it possible to run matplotlib script in android? [\\#2079](https://github.com/kivy/python-for-android/issues/2079)\n- How to create my app name automatically on usb connect [\\#2071](https://github.com/kivy/python-for-android/issues/2071)\n- Default buildozer.spec fails to build - fails on openssl [\\#2060](https://github.com/kivy/python-for-android/issues/2060)\n- ImportError: dlopen failed: cannot locate symbol - Matplotlib module [\\#2059](https://github.com/kivy/python-for-android/issues/2059)\n- ft2font build error with Matplotlib [\\#2058](https://github.com/kivy/python-for-android/issues/2058)\n- SDL Error: Error Could not load any libpythonXXX.so [\\#2056](https://github.com/kivy/python-for-android/issues/2056)\n- Crashing on phone. SDL Error Could not load any libpythonXXX.so [\\#2051](https://github.com/kivy/python-for-android/issues/2051)\n- Hadi [\\#2048](https://github.com/kivy/python-for-android/issues/2048)\n- p4a \\(2019.10.6\\) project build file management [\\#2045](https://github.com/kivy/python-for-android/issues/2045)\n- listdir of primary\\_external\\_storage\\_path\\(\\) fails [\\#2032](https://github.com/kivy/python-for-android/issues/2032)\n- Can't use AsyncImage with HTTPS URL \\(or any HTTPS url with any request\\): fix is to manually load certifi [\\#1827](https://github.com/kivy/python-for-android/issues/1827)\n\n**Merged pull requests:**\n\n- Bumps openssl to 1.1.1f [\\#2118](https://github.com/kivy/python-for-android/pull/2118) ([AndreMiras](https://github.com/AndreMiras))\n- Release 2020.03.30 [\\#2111](https://github.com/kivy/python-for-android/pull/2111) ([inclement](https://github.com/inclement))\n- Updates quickstart.rst \"Installing Dependencies\" [\\#2109](https://github.com/kivy/python-for-android/pull/2109) ([AndreMiras](https://github.com/AndreMiras))\n- :alien: Remove deprecated key `sudo` from `travis.yml` [\\#2102](https://github.com/kivy/python-for-android/pull/2102) ([opacam](https://github.com/opacam))\n- :arrow_up: Update `pytz` to version `2019.3` [\\#2101](https://github.com/kivy/python-for-android/pull/2101) ([opacam](https://github.com/opacam))\n- :sparkles: Add `pandas` recipe [\\#2100](https://github.com/kivy/python-for-android/pull/2100) ([opacam](https://github.com/opacam))\n- Fixes psycopg2 URL, closes \\#2098 [\\#2099](https://github.com/kivy/python-for-android/pull/2099) ([AndreMiras](https://github.com/AndreMiras))\n- :sparkles: Compression libraries - episode III: add support for libbz2 & liblzma to python3 [\\#2097](https://github.com/kivy/python-for-android/pull/2097) ([opacam](https://github.com/opacam))\n- :sparkles: Compression libraries - episode II: liblzma [\\#2096](https://github.com/kivy/python-for-android/pull/2096) ([opacam](https://github.com/opacam))\n- :sparkles: Compression libraries - episode I: libbz2 [\\#2095](https://github.com/kivy/python-for-android/pull/2095) ([opacam](https://github.com/opacam))\n- Update quickstart.rst [\\#2094](https://github.com/kivy/python-for-android/pull/2094) ([BornForFever](https://github.com/BornForFever))\n- Fixes gevent recipe on arm64-v8a arch [\\#2093](https://github.com/kivy/python-for-android/pull/2093) ([AndreMiras](https://github.com/AndreMiras))\n- :bug: Add `-fPIC` to  `CFLAGS` for Arch `x86_64` [\\#2085](https://github.com/kivy/python-for-android/pull/2085) ([opacam](https://github.com/opacam))\n- Fix Python 3.8.1 patch naming and content [\\#2083](https://github.com/kivy/python-for-android/pull/2083) ([opacam](https://github.com/opacam))\n- Update PythonService.java [\\#2081](https://github.com/kivy/python-for-android/pull/2081) ([erikhu](https://github.com/erikhu))\n- Update `numpy` to v1.18.1 \\(add `cython` recipe\\) [\\#2077](https://github.com/kivy/python-for-android/pull/2077) ([opacam](https://github.com/opacam))\n- Fix `matplotlib` and update to `v3.1.3` [\\#2076](https://github.com/kivy/python-for-android/pull/2076) ([opacam](https://github.com/opacam))\n- Fix recipe `kiwisolver` \\(add `cppy` recipe\\) [\\#2075](https://github.com/kivy/python-for-android/pull/2075) ([opacam](https://github.com/opacam))\n- \\[gh-actions\\] Move to actions/checkout@v2 [\\#2070](https://github.com/kivy/python-for-android/pull/2070) ([opacam](https://github.com/opacam))\n- \\[recipes\\] Update Pillow to v7.0.0 [\\#2067](https://github.com/kivy/python-for-android/pull/2067) ([opacam](https://github.com/opacam))\n- Fix missing renames of Bootstrap.list\\_bootstraps -\\> Bootstrap.all\\_bootstraps [\\#2066](https://github.com/kivy/python-for-android/pull/2066) ([touilleMan](https://github.com/touilleMan))\n- fixed patch's name to apply correctly [\\#2064](https://github.com/kivy/python-for-android/pull/2064) ([HirotsuguMINOWA](https://github.com/HirotsuguMINOWA))\n- virtualenv 20 breaks the osx build [\\#2063](https://github.com/kivy/python-for-android/pull/2063) ([AndreMiras](https://github.com/AndreMiras))\n- automatically load/enable certifi in kivy [\\#2055](https://github.com/kivy/python-for-android/pull/2055) ([tshirtman](https://github.com/tshirtman))\n- \\[protobuf\\_cpp\\] fixed python binding installation [\\#2050](https://github.com/kivy/python-for-android/pull/2050) ([goffi-contrib](https://github.com/goffi-contrib))\n- \\[omemo\\] updated to 0.11.0 + updated omemo-backend-signal to 0.2.5 [\\#2049](https://github.com/kivy/python-for-android/pull/2049) ([goffi-contrib](https://github.com/goffi-contrib))\n- Python 3.8 support on Android [\\#2044](https://github.com/kivy/python-for-android/pull/2044) ([inclement](https://github.com/inclement))\n- Updated version after 2019.10.06 release [\\#2043](https://github.com/kivy/python-for-android/pull/2043) ([inclement](https://github.com/inclement))\n- Updated version to 2019.10.06 [\\#2042](https://github.com/kivy/python-for-android/pull/2042) ([inclement](https://github.com/inclement))\n- Added a build.py argument to add arbitrary xml to the AndroidManifest.xml [\\#2040](https://github.com/kivy/python-for-android/pull/2040) ([inclement](https://github.com/inclement))\n- update pyjnius recipe [\\#2036](https://github.com/kivy/python-for-android/pull/2036) ([tshirtman](https://github.com/tshirtman))\n- added a recipe for bcrypt library [\\#2035](https://github.com/kivy/python-for-android/pull/2035) ([tomgold182](https://github.com/tomgold182))\n- Fix vibration for testapps [\\#2034](https://github.com/kivy/python-for-android/pull/2034) ([opacam](https://github.com/opacam))\n- \\[gh-actions\\] Add new testapp and upload artifacts... [\\#2033](https://github.com/kivy/python-for-android/pull/2033) ([opacam](https://github.com/opacam))\n- from kivy.base import runTouchApp for line 75 [\\#2028](https://github.com/kivy/python-for-android/pull/2028) ([cclauss](https://github.com/cclauss))\n- Fix libshine and re-enable it for ffmpeg & ffpyplayer\\_codecs [\\#2027](https://github.com/kivy/python-for-android/pull/2027) ([opacam](https://github.com/opacam))\n- Introduce github-actions \\(push & pull\\_requests\\) [\\#2025](https://github.com/kivy/python-for-android/pull/2025) ([opacam](https://github.com/opacam))\n- Fix rebuild updated recipes, refs \\#2011 [\\#2024](https://github.com/kivy/python-for-android/pull/2024) ([opacam](https://github.com/opacam))\n- Exposes ANDROID\\_SDK\\_HOME to rebuild\\_updated\\_recipes [\\#2018](https://github.com/kivy/python-for-android/pull/2018) ([AndreMiras](https://github.com/AndreMiras))\n- update pyproj recipe [\\#2017](https://github.com/kivy/python-for-android/pull/2017) ([joergbrech](https://github.com/joergbrech))\n- Documentation: android hide loading screen [\\#2014](https://github.com/kivy/python-for-android/pull/2014) ([adityabhawsingka](https://github.com/adityabhawsingka))\n- Travis CI revamp part 1, refs \\#2008 [\\#2011](https://github.com/kivy/python-for-android/pull/2011) ([AndreMiras](https://github.com/AndreMiras))\n- twisted: updated to 19.7.0 + removed inceremental.path [\\#2006](https://github.com/kivy/python-for-android/pull/2006) ([goffi-contrib](https://github.com/goffi-contrib))\n- Fix error on py2  when `import numpy` [\\#2003](https://github.com/kivy/python-for-android/pull/2003) ([opacam](https://github.com/opacam))\n- Fix libcurl with openssl [\\#2000](https://github.com/kivy/python-for-android/pull/2000) ([tito](https://github.com/tito))\n- Fixes ffmpeg build on ndk 19 [\\#1997](https://github.com/kivy/python-for-android/pull/1997) ([misl6](https://github.com/misl6))\n- Fixes test\\_virtualenv and test\\_venv failing, closes \\#1994 [\\#1995](https://github.com/kivy/python-for-android/pull/1995) ([AndreMiras](https://github.com/AndreMiras))\n- Fixes libiconv & libzbar configure host [\\#1993](https://github.com/kivy/python-for-android/pull/1993) ([AndreMiras](https://github.com/AndreMiras))\n- Updates Java version troubleshooting [\\#1991](https://github.com/kivy/python-for-android/pull/1991) ([AndreMiras](https://github.com/AndreMiras))\n- Made p4a use per-arch dist build dirs [\\#1986](https://github.com/kivy/python-for-android/pull/1986) ([inclement](https://github.com/inclement))\n- Made on-device unit tests app use the develop branch by default [\\#1985](https://github.com/kivy/python-for-android/pull/1985) ([inclement](https://github.com/inclement))\n- Recipes tests enhancements [\\#1984](https://github.com/kivy/python-for-android/pull/1984) ([opacam](https://github.com/opacam))\n- Proof of concept - A bunch of tests for library recipes [\\#1982](https://github.com/kivy/python-for-android/pull/1982) ([opacam](https://github.com/opacam))\n- Updated README.md to clarify NDK versions [\\#1981](https://github.com/kivy/python-for-android/pull/1981) ([Zen-CODE](https://github.com/Zen-CODE))\n- Fix CI's test for `arm64-v8a` [\\#1977](https://github.com/kivy/python-for-android/pull/1977) ([opacam](https://github.com/opacam))\n- Added missing recommendations command and cleaned up code [\\#1975](https://github.com/kivy/python-for-android/pull/1975) ([inclement](https://github.com/inclement))\n- Added libffi headers troubleshooting note to doc [\\#1972](https://github.com/kivy/python-for-android/pull/1972) ([inclement](https://github.com/inclement))\n- \\[WIP\\]\\[LIBS - PART VII\\] Rework of libtorrent and boost [\\#1971](https://github.com/kivy/python-for-android/pull/1971) ([opacam](https://github.com/opacam))\n- \\[WIP\\]\\[LIBS - PART VI\\] Rework of protobuf\\_cpp [\\#1969](https://github.com/kivy/python-for-android/pull/1969) ([opacam](https://github.com/opacam))\n- \\[WIP\\]\\[LIBS - PART V\\] Rework of libzmq [\\#1968](https://github.com/kivy/python-for-android/pull/1968) ([opacam](https://github.com/opacam))\n- \\[WIP\\]\\[LIBS - PART IV\\] Rework of shapely and libgeos [\\#1967](https://github.com/kivy/python-for-android/pull/1967) ([opacam](https://github.com/opacam))\n- \\[WIP\\]\\[LIBS - PART III\\] Rework of pyleveldb, leveldb and snappy [\\#1966](https://github.com/kivy/python-for-android/pull/1966) ([opacam](https://github.com/opacam))\n- Updated version number for develop branch following 2019.08.09 release [\\#1960](https://github.com/kivy/python-for-android/pull/1960) ([inclement](https://github.com/inclement))\n- Merge release-2019.08.09 branch into develop [\\#1959](https://github.com/kivy/python-for-android/pull/1959) ([inclement](https://github.com/inclement))\n- Fix and update regex's recipe [\\#1958](https://github.com/kivy/python-for-android/pull/1958) ([opacam](https://github.com/opacam))\n- Fix/doc quotes [\\#1956](https://github.com/kivy/python-for-android/pull/1956) ([tshirtman](https://github.com/tshirtman))\n- \\[LIBS - PART II\\] Part II of NDK r19 migration - Initial STL lib migration [\\#1947](https://github.com/kivy/python-for-android/pull/1947) ([opacam](https://github.com/opacam))\n- \\[LIBS - PART I\\] Initial refactor of library recipes [\\#1944](https://github.com/kivy/python-for-android/pull/1944) ([opacam](https://github.com/opacam))\n- customizability [\\#1869](https://github.com/kivy/python-for-android/pull/1869) ([zworkb](https://github.com/zworkb))\n- Initial migration to NDK r19 \\(Part I - The core\\) [\\#1722](https://github.com/kivy/python-for-android/pull/1722) ([opacam](https://github.com/opacam))\n\n## [v2019.10.06](https://github.com/kivy/python-for-android/tree/v2019.10.06) (2019-12-22)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2019.08.09...v2019.10.06)\n\n**Fixed bugs:**\n\n- TestGetSystemPythonExecutable.test\\_virtualenv test fail [\\#1994](https://github.com/kivy/python-for-android/issues/1994)\n\n**Closed issues:**\n\n- Presplash is removed prematurely. [\\#2029](https://github.com/kivy/python-for-android/issues/2029)\n- Sorry posted in the wrong repository. Closing this issue [\\#2022](https://github.com/kivy/python-for-android/issues/2022)\n- p4a building apk error on Mac OS [\\#2016](https://github.com/kivy/python-for-android/issues/2016)\n- Revamp .travis.yml file [\\#2008](https://github.com/kivy/python-for-android/issues/2008)\n- Possible SDL2 issues introduced with P4A 2019.06.06 [\\#2002](https://github.com/kivy/python-for-android/issues/2002)\n- Error message about python2 [\\#2001](https://github.com/kivy/python-for-android/issues/2001)\n- ffmpeg recipe is broken on ndk19 [\\#1996](https://github.com/kivy/python-for-android/issues/1996)\n- Error while running \".buildozer.../native-build/python -OO -m compileall -b -f /.../app  [\\#1990](https://github.com/kivy/python-for-android/issues/1990)\n- The mpl\\_android\\_fixes.patch didn't work [\\#1989](https://github.com/kivy/python-for-android/issues/1989)\n- Importing numpy yields: TypeError: add\\_docstring\\(\\) argument 2 must be str, not None [\\#1988](https://github.com/kivy/python-for-android/issues/1988)\n- p4a apk :compileDebugJavaWithJavac error [\\#1980](https://github.com/kivy/python-for-android/issues/1980)\n- \\[question\\]Python for android no longer supports Error !  [\\#1978](https://github.com/kivy/python-for-android/issues/1978)\n- Can not Find on Google Play or Buy Premium [\\#1974](https://github.com/kivy/python-for-android/issues/1974)\n- Build failed [\\#1970](https://github.com/kivy/python-for-android/issues/1970)\n- Can't build a package with bcrypt as requirement [\\#1910](https://github.com/kivy/python-for-android/issues/1910)\n- import wxpy module fail [\\#1897](https://github.com/kivy/python-for-android/issues/1897)\n- Cannot build APK with buildozer [\\#1817](https://github.com/kivy/python-for-android/issues/1817)\n- Kivy crashes on Android: ImportError: dlopen failed. [\\#1810](https://github.com/kivy/python-for-android/issues/1810)\n- App build failing on MacOS [\\#1647](https://github.com/kivy/python-for-android/issues/1647)\n- Remove superfluous recipes [\\#1387](https://github.com/kivy/python-for-android/issues/1387)\n\n**Merged pull requests:**\n\n- Release 2019.10.06 [\\#1998](https://github.com/kivy/python-for-android/pull/1998) ([inclement](https://github.com/inclement))\n\n## [v2019.08.09](https://github.com/kivy/python-for-android/tree/v2019.08.09) (2019-08-19)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2019.07.08...v2019.08.09)\n\n**Fixed bugs:**\n\n- Call Cython via `python -m Cython` rather than system-wide binary [\\#1937](https://github.com/kivy/python-for-android/pull/1937) ([etc0de](https://github.com/etc0de))\n\n**Closed issues:**\n\n- Building an Android Library [\\#1957](https://github.com/kivy/python-for-android/issues/1957)\n- dlopen failed: library \"../../../../src/main/jniLibs/armeabi-v7a/libpython3.7m.so\" not found [\\#1954](https://github.com/kivy/python-for-android/issues/1954)\n- App crashing on startup - Import Error: dlopen failed: \\_portaudio.so is 64 bit instead of 32 bit [\\#1953](https://github.com/kivy/python-for-android/issues/1953)\n- How to overcome:? \\#error \"LONG\\_BIT definition appears wrong for platform \\(bad gcc/glibc config?\\).\" [\\#1949](https://github.com/kivy/python-for-android/issues/1949)\n- copy paste option is not working in mobile client \\(android \\)after cloning from updated p4a [\\#1942](https://github.com/kivy/python-for-android/issues/1942)\n- It seems kivy has no support for tkinter, os, sys, random modules [\\#1934](https://github.com/kivy/python-for-android/issues/1934)\n- Mxnet recipe for running kivy app on android [\\#1929](https://github.com/kivy/python-for-android/issues/1929)\n- java.lang.UnsatisfiedLinkError: dlopen failed: library \"libffi.so.7\" not found [\\#1924](https://github.com/kivy/python-for-android/issues/1924)\n- Ndk19c compiled numpy problems [\\#1923](https://github.com/kivy/python-for-android/issues/1923)\n- run\\_on\\_ui\\_thread crash [\\#1908](https://github.com/kivy/python-for-android/issues/1908)\n- please provide recipes for libraries dlib, easygui, Colormath , keras ,imutils [\\#1906](https://github.com/kivy/python-for-android/issues/1906)\n- About TextInput, I can't type korean character. I only can type english. [\\#1904](https://github.com/kivy/python-for-android/issues/1904)\n- app crash when bootstrap=webview [\\#1894](https://github.com/kivy/python-for-android/issues/1894)\n- apk file crash on app launch generated using kivy, buildozer [\\#1891](https://github.com/kivy/python-for-android/issues/1891)\n- StartService in Android Oreo and More should use startForegroundService [\\#1785](https://github.com/kivy/python-for-android/issues/1785)\n- Remove WRITE\\_EXTERNAL\\_STORAGE default permission [\\#1081](https://github.com/kivy/python-for-android/issues/1081)\n\n**Merged pull requests:**\n\n- Release 2019.08.09 [\\#1955](https://github.com/kivy/python-for-android/pull/1955) ([inclement](https://github.com/inclement))\n- Unit tests Recipe.download\\_file\\(\\) partly [\\#1952](https://github.com/kivy/python-for-android/pull/1952) ([AndreMiras](https://github.com/AndreMiras))\n- Unit tests Recipe download feature [\\#1946](https://github.com/kivy/python-for-android/pull/1946) ([AndreMiras](https://github.com/AndreMiras))\n- Added setuptools to Kivy recipe requirements [\\#1938](https://github.com/kivy/python-for-android/pull/1938) ([inclement](https://github.com/inclement))\n- Bumps to Kivy==1.11.1 [\\#1935](https://github.com/kivy/python-for-android/pull/1935) ([AndreMiras](https://github.com/AndreMiras))\n- Increases toolchain.py test coverage [\\#1933](https://github.com/kivy/python-for-android/pull/1933) ([AndreMiras](https://github.com/AndreMiras))\n- Add a document describing how p4a interacts with pip & python packages [\\#1931](https://github.com/kivy/python-for-android/pull/1931) ([etc0de](https://github.com/etc0de))\n- Fix pyzmq \\(libunwind and arm64-v8a\\) [\\#1930](https://github.com/kivy/python-for-android/pull/1930) ([tito](https://github.com/tito))\n- Basic toolchain.py unit tests [\\#1928](https://github.com/kivy/python-for-android/pull/1928) ([AndreMiras](https://github.com/AndreMiras))\n- Merge 2019.07.08 release branch to develop [\\#1921](https://github.com/kivy/python-for-android/pull/1921) ([inclement](https://github.com/inclement))\n- Drop Python 2 support [\\#1918](https://github.com/kivy/python-for-android/pull/1918) ([inclement](https://github.com/inclement))\n- Remove `nosetests.xml` from gitignore [\\#1915](https://github.com/kivy/python-for-android/pull/1915) ([opacam](https://github.com/opacam))\n- Drop CrystaX support and code base [\\#1913](https://github.com/kivy/python-for-android/pull/1913) ([opacam](https://github.com/opacam))\n- Release 2019.07.08 [\\#1909](https://github.com/kivy/python-for-android/pull/1909) ([inclement](https://github.com/inclement))\n- Add documentation: testing a pull request [\\#1901](https://github.com/kivy/python-for-android/pull/1901) ([opacam](https://github.com/opacam))\n- Fix foreground notification being mandatory and more \\(attempt 2\\) [\\#1888](https://github.com/kivy/python-for-android/pull/1888) ([etc0de](https://github.com/etc0de))\n- Make it raise an error if an old ndk is used [\\#1883](https://github.com/kivy/python-for-android/pull/1883) ([opacam](https://github.com/opacam))\n- Hotfix 2019.06.06.post0: added long\\_description\\_content\\_type to setup.py [\\#1850](https://github.com/kivy/python-for-android/pull/1850) ([inclement](https://github.com/inclement))\n- feat: Allows registering the onRequestPermissionsResult callback. [\\#1818](https://github.com/kivy/python-for-android/pull/1818) ([gbm001](https://github.com/gbm001))\n- Add functions for obtaining the default storage paths [\\#1598](https://github.com/kivy/python-for-android/pull/1598) ([etc0de](https://github.com/etc0de))\n\n## [v2019.07.08](https://github.com/kivy/python-for-android/tree/v2019.07.08) (2019-07-11)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/v2019.06.06...v2019.07.08)\n\n**Fixed bugs:**\n\n- Fix crash when guessing Bootstrap \\(expand\\_dependencies\\) [\\#1914](https://github.com/kivy/python-for-android/pull/1914) ([opacam](https://github.com/opacam))\n- Fix `run_pymodules_install` when `project_dir` isn't supplied [\\#1898](https://github.com/kivy/python-for-android/pull/1898) ([opacam](https://github.com/opacam))\n- Fix Bootstrap.get\\_bootstrap\\_from\\_recipes\\(\\) so it's smarter and deterministic, fixes \\#1875 [\\#1887](https://github.com/kivy/python-for-android/pull/1887) ([etc0de](https://github.com/etc0de))\n- Typo [\\#1880](https://github.com/kivy/python-for-android/pull/1880) ([JensGe](https://github.com/JensGe))\n- Fix wrong env variable for `hostpython build path` in `archs.py` [\\#1871](https://github.com/kivy/python-for-android/pull/1871) ([opacam](https://github.com/opacam))\n- Fix various setup.py processing bugs [\\#1862](https://github.com/kivy/python-for-android/pull/1862) ([etc0de](https://github.com/etc0de))\n- Add `--without-bzip2` to freetype's configure args [\\#1857](https://github.com/kivy/python-for-android/pull/1857) ([opacam](https://github.com/opacam))\n- pythonpackage can't return build requirements for wheels, make it return an error if attempted [\\#1852](https://github.com/kivy/python-for-android/pull/1852) ([etc0de](https://github.com/etc0de))\n\n**Closed issues:**\n\n- Proposal: drop CrystaX support and code base [\\#1905](https://github.com/kivy/python-for-android/issues/1905)\n- android hardware back button does not work in kivy [\\#1903](https://github.com/kivy/python-for-android/issues/1903)\n- import wxpy module fail:python for android ended [\\#1896](https://github.com/kivy/python-for-android/issues/1896)\n- error compile with cristax-ndk [\\#1895](https://github.com/kivy/python-for-android/issues/1895)\n- is recipe for the library \"kivymd\" available in p4a? [\\#1893](https://github.com/kivy/python-for-android/issues/1893)\n- deleted [\\#1889](https://github.com/kivy/python-for-android/issues/1889)\n- p4a isn't finding directories in apk on launch \\(ModuleNotFoundError\\) [\\#1881](https://github.com/kivy/python-for-android/issues/1881)\n- Little but essential Typo  [\\#1879](https://github.com/kivy/python-for-android/issues/1879)\n- clang crashes with an unclear error if ncurses5 isn't available \\(specifically libtinfo.so.5\\) [\\#1878](https://github.com/kivy/python-for-android/issues/1878)\n- get\\_bootstrap\\_from\\_recipes\\(\\) result consistency [\\#1875](https://github.com/kivy/python-for-android/issues/1875)\n- install recursive dependencies of pure python packages [\\#1874](https://github.com/kivy/python-for-android/issues/1874)\n- numpy recipe bug arm64-v8a [\\#1873](https://github.com/kivy/python-for-android/issues/1873)\n- how do i support numba\\(a python module\\) [\\#1865](https://github.com/kivy/python-for-android/issues/1865)\n- apk build error: bzlib.h: No such file or directory [\\#1854](https://github.com/kivy/python-for-android/issues/1854)\n- Compilation error, previously worked [\\#1846](https://github.com/kivy/python-for-android/issues/1846)\n- Keep track of coverage testing [\\#1788](https://github.com/kivy/python-for-android/issues/1788)\n- p4a's overtaking of dependency order in place of --no-deps is problematic and IMHO should go [\\#1490](https://github.com/kivy/python-for-android/issues/1490)\n\n**Merged pull requests:**\n\n- Fixes ffmpeg and libx264 recipes for arm64-v8 [\\#1916](https://github.com/kivy/python-for-android/pull/1916) ([misl6](https://github.com/misl6))\n- Docker - Update android's sdk tools to `28.0.2` [\\#1912](https://github.com/kivy/python-for-android/pull/1912) ([opacam](https://github.com/opacam))\n- Feature gitignore additions [\\#1911](https://github.com/kivy/python-for-android/pull/1911) ([opacam](https://github.com/opacam))\n- Simple run\\_pymodules\\_install test, refs \\#1898 [\\#1899](https://github.com/kivy/python-for-android/pull/1899) ([AndreMiras](https://github.com/AndreMiras))\n- Updated numpy recipe to version 1.16.4 [\\#1892](https://github.com/kivy/python-for-android/pull/1892) ([inclement](https://github.com/inclement))\n- fix ctypes-util-find-library issue for python2 [\\#1877](https://github.com/kivy/python-for-android/pull/1877) ([surbhicis](https://github.com/surbhicis))\n- Add unittest for module `pythonforandroid.bootstrap` [\\#1872](https://github.com/kivy/python-for-android/pull/1872) ([opacam](https://github.com/opacam))\n- Remove legacy version of openssl [\\#1870](https://github.com/kivy/python-for-android/pull/1870) ([opacam](https://github.com/opacam))\n- Make the tox jobs run in parallel &... [\\#1864](https://github.com/kivy/python-for-android/pull/1864) ([opacam](https://github.com/opacam))\n- Try to be more clear in README about api levels & quickstart [\\#1863](https://github.com/kivy/python-for-android/pull/1863) ([etc0de](https://github.com/etc0de))\n- Fix locating system python when it's not in $PATH \\(weird but happens, apparently\\) [\\#1856](https://github.com/kivy/python-for-android/pull/1856) ([etc0de](https://github.com/etc0de))\n- Add unittest for `pythonforandroid.util` and... [\\#1855](https://github.com/kivy/python-for-android/pull/1855) ([opacam](https://github.com/opacam))\n- Merge 2019.06.06.post0 hotfix: set long\\_description\\_content\\_type in setup.py [\\#1851](https://github.com/kivy/python-for-android/pull/1851) ([inclement](https://github.com/inclement))\n- Improved release model documentation [\\#1849](https://github.com/kivy/python-for-android/pull/1849) ([inclement](https://github.com/inclement))\n- Merge release-2019.06.06 to develop [\\#1848](https://github.com/kivy/python-for-android/pull/1848) ([inclement](https://github.com/inclement))\n- Add unittest for module `pythonforandroid.distribution` [\\#1847](https://github.com/kivy/python-for-android/pull/1847) ([opacam](https://github.com/opacam))\n- bugfix: unpack for nonzip archives also needs to compare basename\\(dir\\) [\\#1845](https://github.com/kivy/python-for-android/pull/1845) ([sfoerster](https://github.com/sfoerster))\n- Add unittest for module `pythonforandroid.archs` [\\#1842](https://github.com/kivy/python-for-android/pull/1842) ([opacam](https://github.com/opacam))\n- Replaced one of the python2 travis builds with python3 arm64-v8a [\\#1840](https://github.com/kivy/python-for-android/pull/1840) ([inclement](https://github.com/inclement))\n\n## [v2019.06.06](https://github.com/kivy/python-for-android/tree/v2019.06.06) (2019-06-08)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/0.7.0...v2019.06.06)\n\n**Fixed bugs:**\n\n- AttributeError: 'Namespace' object has no attribute 'ignore\\_setup\\_py' [\\#1808](https://github.com/kivy/python-for-android/issues/1808)\n- libzmq recipe compiling error [\\#1802](https://github.com/kivy/python-for-android/issues/1802)\n- Cannot build APK - IndexError: List index out of range [\\#1774](https://github.com/kivy/python-for-android/issues/1774)\n- ctypes.util.find\\_library\\(\\) doesn't work on arch arm64-v8a [\\#1770](https://github.com/kivy/python-for-android/issues/1770)\n- error building compiled components in netifaces [\\#1539](https://github.com/kivy/python-for-android/issues/1539)\n- \\[WIP\\] Fix crashes when using other commands than 'apk' [\\#1809](https://github.com/kivy/python-for-android/pull/1809) ([etc0de](https://github.com/etc0de))\n\n**Closed issues:**\n\n- Create a release checklist [\\#1836](https://github.com/kivy/python-for-android/issues/1836)\n- Sorting out python-for-android releases [\\#1833](https://github.com/kivy/python-for-android/issues/1833)\n- Error - Runtime permissions - object 'Permission' has no attribute 'ACCESS\\_FINE\\_LOCATION' [\\#1824](https://github.com/kivy/python-for-android/issues/1824)\n- buildozer android debug deploy run -\\> sh.CommandNotFound: ./gradlew [\\#1804](https://github.com/kivy/python-for-android/issues/1804)\n- buildozer failed clang++: error: linker command failed with exit code 1 [\\#1800](https://github.com/kivy/python-for-android/issues/1800)\n- Crash on \"--orientation sensor\" when rotate [\\#1797](https://github.com/kivy/python-for-android/issues/1797)\n- error when compiling with flask\\_sqlalmechy and sqlamechy [\\#1793](https://github.com/kivy/python-for-android/issues/1793)\n- Some links are broken in the docs [\\#1780](https://github.com/kivy/python-for-android/issues/1780)\n- packaged python is built with IPv6 disabled [\\#1771](https://github.com/kivy/python-for-android/issues/1771)\n- `p4a recipes` terminates with error [\\#1769](https://github.com/kivy/python-for-android/issues/1769)\n- the application does not work with scipy package [\\#1767](https://github.com/kivy/python-for-android/issues/1767)\n- openssl not in the build order when compiling cryptography [\\#1764](https://github.com/kivy/python-for-android/issues/1764)\n- \"--orientation fullUser\" is not working [\\#1763](https://github.com/kivy/python-for-android/issues/1763)\n- pydub problem [\\#1759](https://github.com/kivy/python-for-android/issues/1759)\n- App crashes using python3 and android's run\\_on\\_ui\\_thread [\\#1755](https://github.com/kivy/python-for-android/issues/1755)\n- str.decode\\(\\) issue again for Python 3 [\\#1749](https://github.com/kivy/python-for-android/issues/1749)\n- Error while in gradlew [\\#1740](https://github.com/kivy/python-for-android/issues/1740)\n- Buildozer [\\#1736](https://github.com/kivy/python-for-android/issues/1736)\n- C compiler cannot create executables [\\#1735](https://github.com/kivy/python-for-android/issues/1735)\n- Gevent reciepe problem [\\#1732](https://github.com/kivy/python-for-android/issues/1732)\n- Permission RECORD\\_AUDIO not working [\\#1730](https://github.com/kivy/python-for-android/issues/1730)\n- Unable to build with flask, conflicting with genericndkbuild [\\#1728](https://github.com/kivy/python-for-android/issues/1728)\n- Android setup.py not working for windows [\\#1726](https://github.com/kivy/python-for-android/issues/1726)\n- Fullscreen mode does not work [\\#1724](https://github.com/kivy/python-for-android/issues/1724)\n- ImportError: sh 1.12.14 is currently only supported on linux and osx. please install pbs 0.110 \\(http://pypi.python.org/pypi/pbs\\) for windows support. [\\#1721](https://github.com/kivy/python-for-android/issues/1721)\n- App crashing following successful build [\\#1719](https://github.com/kivy/python-for-android/issues/1719)\n- Null pointer when finding libraries [\\#1717](https://github.com/kivy/python-for-android/issues/1717)\n- Psycopg2 error after the apk installation. [\\#1711](https://github.com/kivy/python-for-android/issues/1711)\n- Match official requestPermissions interface [\\#1704](https://github.com/kivy/python-for-android/issues/1704)\n- Webview build can't find `SDL_setenv`. [\\#1702](https://github.com/kivy/python-for-android/issues/1702)\n- lxml and requests recipe: build interrupted, \"\\_ctype\" error [\\#1700](https://github.com/kivy/python-for-android/issues/1700)\n- weird orientation behavior [\\#1698](https://github.com/kivy/python-for-android/issues/1698)\n- SDLActivity.java\\:1948\\: error: cannot find symbol case MotionEvent.ACTION\\_BUTTON\\_PRESS: [\\#1697](https://github.com/kivy/python-for-android/issues/1697)\n- ImportError [\\#1694](https://github.com/kivy/python-for-android/issues/1694)\n- unicode error during startup \\(python3, numpy, opencv\\) - patch included [\\#1691](https://github.com/kivy/python-for-android/issues/1691)\n- \"--orientation sensor\" no longer works [\\#1688](https://github.com/kivy/python-for-android/issues/1688)\n- kivy==master Window undefined [\\#1687](https://github.com/kivy/python-for-android/issues/1687)\n- Pillow Python 3 compile error [\\#1679](https://github.com/kivy/python-for-android/issues/1679)\n- numpy/opencv fails with latest p4a at runtime \\(import\\) [\\#1678](https://github.com/kivy/python-for-android/issues/1678)\n- Remove pygame bootstrap [\\#1668](https://github.com/kivy/python-for-android/issues/1668)\n- TODO: bring the kivy.org p4a documentation up to date [\\#1657](https://github.com/kivy/python-for-android/issues/1657)\n- Discussion: should the default recipe set for python3 unconditionally include libffi to build ctypes? What about sqlite3 and other core modules? [\\#1576](https://github.com/kivy/python-for-android/issues/1576)\n- Project's setup.py is not run, it should be \\(at least as an option\\) [\\#1488](https://github.com/kivy/python-for-android/issues/1488)\n- Destroying SDL\\_Renderer in app background event with the intention to restore it in foreground event leads to crash [\\#1424](https://github.com/kivy/python-for-android/issues/1424)\n- Broken libglob recipe [\\#1399](https://github.com/kivy/python-for-android/issues/1399)\n- Crash when my Python program starts to load [\\#1299](https://github.com/kivy/python-for-android/issues/1299)\n- Calendar.getTimeInMillis\\(\\) returns a negative value [\\#942](https://github.com/kivy/python-for-android/issues/942)\n- plyer requirement does not work with ==master version [\\#879](https://github.com/kivy/python-for-android/issues/879)\n- ImportError: No module named pysqlite2 [\\#860](https://github.com/kivy/python-for-android/issues/860)\n- Local recipe dir is not returned by get\\_recipe\\_dir\\(\\) [\\#613](https://github.com/kivy/python-for-android/issues/613)\n- SQLite gets compiled without Full Text Search \\(FTS3\\) support [\\#431](https://github.com/kivy/python-for-android/issues/431)\n- BroadcastReceiver broken in service [\\#278](https://github.com/kivy/python-for-android/issues/278)\n\n**Merged pull requests:**\n\n- Release 2019.06.06 [\\#1839](https://github.com/kivy/python-for-android/pull/1839) ([inclement](https://github.com/inclement))\n- Documented the development and release models [\\#1838](https://github.com/kivy/python-for-android/pull/1838) ([inclement](https://github.com/inclement))\n- Added readme for on device unit tests [\\#1837](https://github.com/kivy/python-for-android/pull/1837) ([inclement](https://github.com/inclement))\n- Add coverage with coveralls and make use of travis stages [\\#1835](https://github.com/kivy/python-for-android/pull/1835) ([opacam](https://github.com/opacam))\n- Update Kivy version to 1.11.0 [\\#1832](https://github.com/kivy/python-for-android/pull/1832) ([inclement](https://github.com/inclement))\n- \\[R.I.P.\\] Remove `python2legacy` and `hospython2legacy` [\\#1831](https://github.com/kivy/python-for-android/pull/1831) ([opacam](https://github.com/opacam))\n- Update permissions.py [\\#1828](https://github.com/kivy/python-for-android/pull/1828) ([tamano123](https://github.com/tamano123))\n- Add a Pillow test app [\\#1826](https://github.com/kivy/python-for-android/pull/1826) ([opacam](https://github.com/opacam))\n- Rework of freetype/harfbuzz recipes [\\#1825](https://github.com/kivy/python-for-android/pull/1825) ([opacam](https://github.com/opacam))\n- Added a matplotlib recipe [\\#1822](https://github.com/kivy/python-for-android/pull/1822) ([inclement](https://github.com/inclement))\n- Fix travis log doesn't produce any output for more than 10 minutes [\\#1821](https://github.com/kivy/python-for-android/pull/1821) ([opacam](https://github.com/opacam))\n- Fix `CI` errors with latest tox [\\#1820](https://github.com/kivy/python-for-android/pull/1820) ([opacam](https://github.com/opacam))\n- Fixed build file for service\\_only bootstrap [\\#1819](https://github.com/kivy/python-for-android/pull/1819) ([devos50](https://github.com/devos50))\n- enable IPv6 for packaged python3 [\\#1815](https://github.com/kivy/python-for-android/pull/1815) ([SomberNight](https://github.com/SomberNight))\n- Update pymunk to v5.5.0 [\\#1814](https://github.com/kivy/python-for-android/pull/1814) ([viblo](https://github.com/viblo))\n- Fixes ffpyplayer build [\\#1813](https://github.com/kivy/python-for-android/pull/1813) ([misl6](https://github.com/misl6))\n- Fix corner case of pip hack workaround we should get rid of anyway [\\#1807](https://github.com/kivy/python-for-android/pull/1807) ([etc0de](https://github.com/etc0de))\n- Fixed doc links to plyer and pyjnius [\\#1806](https://github.com/kivy/python-for-android/pull/1806) ([inclement](https://github.com/inclement))\n- Fixed libzqm recipe linking issues [\\#1803](https://github.com/kivy/python-for-android/pull/1803) ([hpsaturn](https://github.com/hpsaturn))\n- Update recipes.rst [\\#1799](https://github.com/kivy/python-for-android/pull/1799) ([FunmiKesa](https://github.com/FunmiKesa))\n- Update recipes.rst [\\#1798](https://github.com/kivy/python-for-android/pull/1798) ([FunmiKesa](https://github.com/FunmiKesa))\n- Bumps to setuptools==40.9.0 [\\#1795](https://github.com/kivy/python-for-android/pull/1795) ([AndreMiras](https://github.com/AndreMiras))\n- Fix sqlalchemy recipe [\\#1794](https://github.com/kivy/python-for-android/pull/1794) ([sduenasg](https://github.com/sduenasg))\n- Move ffmpeg download url to github repo [\\#1791](https://github.com/kivy/python-for-android/pull/1791) ([misl6](https://github.com/misl6))\n- Ignores Python 2 import\\_recipe\\(\\) warnings [\\#1789](https://github.com/kivy/python-for-android/pull/1789) ([AndreMiras](https://github.com/AndreMiras))\n- \\[kivy\\] updated to 44a8a6f [\\#1777](https://github.com/kivy/python-for-android/pull/1777) ([goffi-contrib](https://github.com/goffi-contrib))\n- Fix outdated PySDL2 version and non-PyPI install source [\\#1775](https://github.com/kivy/python-for-android/pull/1775) ([etc0de](https://github.com/etc0de))\n- Properly search native lib dir in ctypes, fixes \\#1770 [\\#1772](https://github.com/kivy/python-for-android/pull/1772) ([etc0de](https://github.com/etc0de))\n- \\[kivy\\] updated recipe to c4d6894 revision [\\#1766](https://github.com/kivy/python-for-android/pull/1766) ([goffi-contrib](https://github.com/goffi-contrib))\n- Fixes libglob recipe, closes \\#1399 [\\#1765](https://github.com/kivy/python-for-android/pull/1765) ([AndreMiras](https://github.com/AndreMiras))\n- \\[doubleratchet\\] removed this recipe [\\#1762](https://github.com/kivy/python-for-android/pull/1762) ([goffi-contrib](https://github.com/goffi-contrib))\n- \\[cryptography\\] updated to 2.6.1 [\\#1761](https://github.com/kivy/python-for-android/pull/1761) ([goffi-contrib](https://github.com/goffi-contrib))\n- \\[omemo-backend-signal\\] updated to 0.2.3 [\\#1760](https://github.com/kivy/python-for-android/pull/1760) ([goffi-contrib](https://github.com/goffi-contrib))\n- \\[omemo\\] updated to v0.10.4 [\\#1758](https://github.com/kivy/python-for-android/pull/1758) ([goffi-contrib](https://github.com/goffi-contrib))\n- \\[protobuf\\_cpp\\] fixed runtime issues [\\#1757](https://github.com/kivy/python-for-android/pull/1757) ([goffi-contrib](https://github.com/goffi-contrib))\n- \\[xeddsa\\] fixed shared library copying [\\#1756](https://github.com/kivy/python-for-android/pull/1756) ([goffi-contrib](https://github.com/goffi-contrib))\n- remove call to decode on str in \\_android.pyx [\\#1752](https://github.com/kivy/python-for-android/pull/1752) ([tshirtman](https://github.com/tshirtman))\n- \\[libxml2\\] fixed crash on missing lzma.h [\\#1751](https://github.com/kivy/python-for-android/pull/1751) ([goffi-contrib](https://github.com/goffi-contrib))\n- Remove string decoding in \\_android.pyx for Python3 compatibility [\\#1748](https://github.com/kivy/python-for-android/pull/1748) ([darosior](https://github.com/darosior))\n- decode JAVA\\_NAMESPACE in \\_android.pyx [\\#1747](https://github.com/kivy/python-for-android/pull/1747) ([tshirtman](https://github.com/tshirtman))\n- fix: \"cwd is\" log message was printed directly, not via debug\\(\\) [\\#1746](https://github.com/kivy/python-for-android/pull/1746) ([mkg20001](https://github.com/mkg20001))\n- Fix libffi recipe, and build + runtime linker errors when compiling on WSL [\\#1744](https://github.com/kivy/python-for-android/pull/1744) ([Aralox](https://github.com/Aralox))\n- Updated sdl2\\_mixer version to 2.0.4 [\\#1742](https://github.com/kivy/python-for-android/pull/1742) ([misl6](https://github.com/misl6))\n- Requests runtime permissions list, fixes \\#1704 [\\#1741](https://github.com/kivy/python-for-android/pull/1741) ([AndreMiras](https://github.com/AndreMiras))\n- fix groestlcoin\\_hash recipe [\\#1738](https://github.com/kivy/python-for-android/pull/1738) ([tshirtman](https://github.com/tshirtman))\n- Fixes object is not subscriptable error, refs \\#1733 [\\#1734](https://github.com/kivy/python-for-android/pull/1734) ([AndreMiras](https://github.com/AndreMiras))\n- Add missing arch to version code generator. [\\#1733](https://github.com/kivy/python-for-android/pull/1733) ([OptimusGREEN](https://github.com/OptimusGREEN))\n- Fix import in 'Using a PythonRecipe' doc [\\#1731](https://github.com/kivy/python-for-android/pull/1731) ([b3b](https://github.com/b3b))\n- Removed unnecessary genericndkbuild dependency from the flask recipe [\\#1729](https://github.com/kivy/python-for-android/pull/1729) ([inclement](https://github.com/inclement))\n- Add ci\\_mode to disable download progress [\\#1727](https://github.com/kivy/python-for-android/pull/1727) ([mkg20001](https://github.com/mkg20001))\n- Zworkb services [\\#1725](https://github.com/kivy/python-for-android/pull/1725) ([zworkb](https://github.com/zworkb))\n- Fixes psycopg2 lib install dir, closes \\#1711 [\\#1723](https://github.com/kivy/python-for-android/pull/1723) ([AndreMiras](https://github.com/AndreMiras))\n- Generate android version code accounting for arch and min sdk [\\#1720](https://github.com/kivy/python-for-android/pull/1720) ([OptimusGREEN](https://github.com/OptimusGREEN))\n- Fix sdl2\\_image compile error when arch is x86 [\\#1718](https://github.com/kivy/python-for-android/pull/1718) ([j-devel](https://github.com/j-devel))\n- Fix compile error of recipe \"android\" for non-sdl bootstrap build [\\#1715](https://github.com/kivy/python-for-android/pull/1715) ([j-devel](https://github.com/j-devel))\n- Fix setenv\\(\\) of non-SDL2 bootstrap [\\#1714](https://github.com/kivy/python-for-android/pull/1714) ([j-devel](https://github.com/j-devel))\n- Update enum34, pyasn1and pyopenssl versions. [\\#1713](https://github.com/kivy/python-for-android/pull/1713) ([rnixx](https://github.com/rnixx))\n- Added warning for arguments containing carriage returns. This happens… [\\#1712](https://github.com/kivy/python-for-android/pull/1712) ([Aralox](https://github.com/Aralox))\n- Fix loadLibraries\\(\\) failing for 64bit arch [\\#1701](https://github.com/kivy/python-for-android/pull/1701) ([j-devel](https://github.com/j-devel))\n- Fixes pyleveldb recipe [\\#1699](https://github.com/kivy/python-for-android/pull/1699) ([AndreMiras](https://github.com/AndreMiras))\n- Make testapps use python3 per default and adapt to `blacklist-requirements` [\\#1696](https://github.com/kivy/python-for-android/pull/1696) ([opacam](https://github.com/opacam))\n- change dependency from jdk7 to jdk8 [\\#1695](https://github.com/kivy/python-for-android/pull/1695) ([Meteorix](https://github.com/Meteorix))\n- FIX: copy additional jar files into the correct libs directory [\\#1693](https://github.com/kivy/python-for-android/pull/1693) ([OptimusGREEN](https://github.com/OptimusGREEN))\n- SDL2\\_image update to 2.0.4 and set kivy's version to master [\\#1692](https://github.com/kivy/python-for-android/pull/1692) ([opacam](https://github.com/opacam))\n- Use nativeSetenv\\(\\) provided by SDL2 and cleanups [\\#1690](https://github.com/kivy/python-for-android/pull/1690) ([j-devel](https://github.com/j-devel))\n- Rename misguided shadowing --blacklist to --blacklist-requirements [\\#1689](https://github.com/kivy/python-for-android/pull/1689) ([etc0de](https://github.com/etc0de))\n- Deleted kivent recipes from constants.py [\\#1686](https://github.com/kivy/python-for-android/pull/1686) ([inclement](https://github.com/inclement))\n- Downgrade to pycryptodome==3.6.3 [\\#1685](https://github.com/kivy/python-for-android/pull/1685) ([AndreMiras](https://github.com/AndreMiras))\n- Added option to support custom shared libraries with \\<uses-library\\> tag [\\#1684](https://github.com/kivy/python-for-android/pull/1684) ([pax0r](https://github.com/pax0r))\n- Implement --blacklist option and include more modules/recipes by default [\\#1683](https://github.com/kivy/python-for-android/pull/1683) ([etc0de](https://github.com/etc0de))\n- Origin [\\#1681](https://github.com/kivy/python-for-android/pull/1681) ([strubbi77](https://github.com/strubbi77))\n- Delete kivent recipes [\\#1680](https://github.com/kivy/python-for-android/pull/1680) ([inclement](https://github.com/inclement))\n- Fix zope\\_interface and add python3 compatibility [\\#1677](https://github.com/kivy/python-for-android/pull/1677) ([opacam](https://github.com/opacam))\n- Recipe class unit tests [\\#1676](https://github.com/kivy/python-for-android/pull/1676) ([AndreMiras](https://github.com/AndreMiras))\n- Bumps netiffaces version & removes from broken list, refs \\#1539 [\\#1675](https://github.com/kivy/python-for-android/pull/1675) ([AndreMiras](https://github.com/AndreMiras))\n- tox update and linter fixes [\\#1674](https://github.com/kivy/python-for-android/pull/1674) ([AndreMiras](https://github.com/AndreMiras))\n- tox update and linter fixes [\\#1673](https://github.com/kivy/python-for-android/pull/1673) ([AndreMiras](https://github.com/AndreMiras))\n- Delete the pygame bootstrap [\\#1670](https://github.com/kivy/python-for-android/pull/1670) ([inclement](https://github.com/inclement))\n- Fix various issues with graphs and recipes [\\#1669](https://github.com/kivy/python-for-android/pull/1669) ([etc0de](https://github.com/etc0de))\n- Fix setup.py install breaking due to unicode characters in README.md on Python 3 [\\#1667](https://github.com/kivy/python-for-android/pull/1667) ([etc0de](https://github.com/etc0de))\n- s/README.rst/README.md/ refs \\#1664 [\\#1666](https://github.com/kivy/python-for-android/pull/1666) ([AndreMiras](https://github.com/AndreMiras))\n- Update and rename README.rst to README.md [\\#1664](https://github.com/kivy/python-for-android/pull/1664) ([tito](https://github.com/tito))\n- Defaults to post kivy==1.10.1 commit for SDL 2.0.9 fixes [\\#1663](https://github.com/kivy/python-for-android/pull/1663) ([AndreMiras](https://github.com/AndreMiras))\n- Rework opencv's recipe \\(enable cv2.so and the extra opencv libraries\\) [\\#1661](https://github.com/kivy/python-for-android/pull/1661) ([opacam](https://github.com/opacam))\n- Updated version to 0.7.1 [\\#1660](https://github.com/kivy/python-for-android/pull/1660) ([inclement](https://github.com/inclement))\n- \\[WIP\\] Run project's setup.py if present, unless --ignore-setup-py was set [\\#1625](https://github.com/kivy/python-for-android/pull/1625) ([etc0de](https://github.com/etc0de))\n\n## [0.7.0](https://github.com/kivy/python-for-android/tree/0.7.0) (2019-02-01)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/0.6.0...0.7.0)\n\n**Fixed bugs:**\n\n- python3 + openssl compilation fail [\\#1590](https://github.com/kivy/python-for-android/issues/1590)\n- Update conditional build to python3 [\\#1485](https://github.com/kivy/python-for-android/issues/1485)\n- building apk fails \\( python 3 \\) [\\#746](https://github.com/kivy/python-for-android/issues/746)\n\n**Closed issues:**\n\n- 'Orientation' and 'Fullscreen' settings in spec file: Possible issue. [\\#1655](https://github.com/kivy/python-for-android/issues/1655)\n- cffi UnicodeEncodeError: 'ascii' codec can't encode character '\\u2018' [\\#1654](https://github.com/kivy/python-for-android/issues/1654)\n- Create an app for testing p4a builds on the device [\\#1630](https://github.com/kivy/python-for-android/issues/1630)\n- Build crashes if NDK is installed system-wide without write permissions [\\#1621](https://github.com/kivy/python-for-android/issues/1621)\n- libFFI recipe doesn't work with clang [\\#1612](https://github.com/kivy/python-for-android/issues/1612)\n- How do I use this software? [\\#1606](https://github.com/kivy/python-for-android/issues/1606)\n- no work [\\#1599](https://github.com/kivy/python-for-android/issues/1599)\n- python2legacy - various warnings then no valid dependency graphs [\\#1582](https://github.com/kivy/python-for-android/issues/1582)\n- pyjnius import crash / TypeError [\\#1578](https://github.com/kivy/python-for-android/issues/1578)\n- reportlab broken, possibly using wrong python header or compiler flags with python 3? [\\#1575](https://github.com/kivy/python-for-android/issues/1575)\n- p4a apk crash \"IndexError: list index out of range\" [\\#1570](https://github.com/kivy/python-for-android/issues/1570)\n- Libffi build fail on Mac [\\#1569](https://github.com/kivy/python-for-android/issues/1569)\n- All the python functions can run on kivy [\\#1567](https://github.com/kivy/python-for-android/issues/1567)\n- --force-build is --force\\_build in the docs [\\#1565](https://github.com/kivy/python-for-android/issues/1565)\n- apk with sqlite3 python3 kivy No module named '\\_sqlite3' [\\#1564](https://github.com/kivy/python-for-android/issues/1564)\n- Android crash on run, Fatal signal 6 \\(SIGABRT\\), code -6 in tid 10265 \\(SDLThread\\), avc: denied { search } [\\#1562](https://github.com/kivy/python-for-android/issues/1562)\n- Minor exclude extensions code simplification [\\#1560](https://github.com/kivy/python-for-android/issues/1560)\n- SSLError python 3.7.1 [\\#1559](https://github.com/kivy/python-for-android/issues/1559)\n- Error message claiming conflicting dependencies when requesting a recipe \\(here, `python3`\\) that was not \\(yet\\) available [\\#1557](https://github.com/kivy/python-for-android/issues/1557)\n- The virtual machine \\(VM 0.5\\) does not collect packages with dependencies, except Python and Kivy [\\#1542](https://github.com/kivy/python-for-android/issues/1542)\n- raise exc [\\#1540](https://github.com/kivy/python-for-android/issues/1540)\n- raise exc sh.ErrorReturnCode\\_2:  [\\#1538](https://github.com/kivy/python-for-android/issues/1538)\n- How to build other ABI versions of \"libcrypto.so\" and \"libssl.so\"? [\\#1536](https://github.com/kivy/python-for-android/issues/1536)\n- How to build other versions of ABI？ [\\#1535](https://github.com/kivy/python-for-android/issues/1535)\n- No module named sh [\\#1531](https://github.com/kivy/python-for-android/issues/1531)\n- p4a crash \"Couldn't find the built APK\"  [\\#1530](https://github.com/kivy/python-for-android/issues/1530)\n- UnicodeEncodeError in logger.py [\\#1529](https://github.com/kivy/python-for-android/issues/1529)\n- Hello, is there a Chinese document? It is still very difficult for me to read the document after using Google Translate. [\\#1527](https://github.com/kivy/python-for-android/issues/1527)\n- ValueError: storage dir path cannot contain spaces, please specify a path with --storage-dir [\\#1526](https://github.com/kivy/python-for-android/issues/1526)\n- Build fails: Could not find com.android.tools.lint:lint-gradle:26.1.4 [\\#1520](https://github.com/kivy/python-for-android/issues/1520)\n- App doesn't support pause mode' when using on\\_pause method [\\#1518](https://github.com/kivy/python-for-android/issues/1518)\n- Comprehensive list of broken python3 recipes [\\#1514](https://github.com/kivy/python-for-android/issues/1514)\n- BUILD FAILURE: No main.py\\(o\\) found in your app directory. [\\#1510](https://github.com/kivy/python-for-android/issues/1510)\n- testapp\\_flask doesn't build with webview bootstrap, final bootstrap compiler options appear to have some sort of issue [\\#1509](https://github.com/kivy/python-for-android/issues/1509)\n- Bootstrap detection for \"service\\_only\" and \"webview\" is broken - always picks sdl2 [\\#1508](https://github.com/kivy/python-for-android/issues/1508)\n- p4a latest master / Python 2.7 / API target 28 / NDK 21 / openjdk8 crashes during gradle step [\\#1506](https://github.com/kivy/python-for-android/issues/1506)\n- --requirements=android gives crash [\\#1504](https://github.com/kivy/python-for-android/issues/1504)\n- pyjnius build failure with NDK r17c, NDK 21, SDK 28 [\\#1502](https://github.com/kivy/python-for-android/issues/1502)\n- Need libpython3.7m.so.1.0 in android phone [\\#1501](https://github.com/kivy/python-for-android/issues/1501)\n- Tech debt: webview, pygame and service\\_only bootstraps are hardwired to Python 2.7 [\\#1497](https://github.com/kivy/python-for-android/issues/1497)\n- ImportError: No module named android [\\#1492](https://github.com/kivy/python-for-android/issues/1492)\n- p4a --requirements ignores absolute folder paths [\\#1487](https://github.com/kivy/python-for-android/issues/1487)\n- Set the api level from 19 to 28 failed [\\#1482](https://github.com/kivy/python-for-android/issues/1482)\n- libxml2 build broken on latest p4a master with python 3 [\\#1479](https://github.com/kivy/python-for-android/issues/1479)\n- working: make: \\*\\*\\* \\[Makefile\\:426\\: sharedmods\\] Error 139  [\\#1474](https://github.com/kivy/python-for-android/issues/1474)\n- .pxd files of dependency targeted recipe'd library not found [\\#1473](https://github.com/kivy/python-for-android/issues/1473)\n- Python 3 recipe follow up issues [\\#1455](https://github.com/kivy/python-for-android/issues/1455)\n- Python-4-Android NumPy error: 'struct lconv' has no member named 'decimal\\_point' [\\#1450](https://github.com/kivy/python-for-android/issues/1450)\n- Update to latest SDL required for proper key handling [\\#1449](https://github.com/kivy/python-for-android/issues/1449)\n- socket.getaddrinfo appears to be completely broken, all name resolutions fail [\\#1447](https://github.com/kivy/python-for-android/issues/1447)\n- lxml recipe doesn't work with Python 3 [\\#1445](https://github.com/kivy/python-for-android/issues/1445)\n- C Compiler can not create executables [\\#1436](https://github.com/kivy/python-for-android/issues/1436)\n- Unable to create APK [\\#1434](https://github.com/kivy/python-for-android/issues/1434)\n- Destroying SDL\\_Renderer in SDL\\_APP\\_DIDENTERBACKGROUND \\(with intention of recreating it\\) will lead to crash [\\#1425](https://github.com/kivy/python-for-android/issues/1425)\n- AndroidManifest.xml.tmpl should set screenLayout & smallScreenSize [\\#1422](https://github.com/kivy/python-for-android/issues/1422)\n- pyjnius really should be version pinned. [\\#1415](https://github.com/kivy/python-for-android/issues/1415)\n- p4a git master pyjnius build breaks [\\#1414](https://github.com/kivy/python-for-android/issues/1414)\n- enaml recipe compilation fails [\\#1409](https://github.com/kivy/python-for-android/issues/1409)\n- Cython projects that don't need any special option should work without a CythonRecipe [\\#1406](https://github.com/kivy/python-for-android/issues/1406)\n- libpq recipe compilation fails [\\#1405](https://github.com/kivy/python-for-android/issues/1405)\n- cryptography + python3crystax fails [\\#1404](https://github.com/kivy/python-for-android/issues/1404)\n- Comprehensive list of broken recipes [\\#1402](https://github.com/kivy/python-for-android/issues/1402)\n- ifaddrs compilation error [\\#1398](https://github.com/kivy/python-for-android/issues/1398)\n- Python project build Buildozer issue - Please define SDL\\_JAVA\\_PACKAGE\\_PATH to the path of your Java package with dots replaced with underscores [\\#1391](https://github.com/kivy/python-for-android/issues/1391)\n- error: kivy/graphics/texture.c: No such file or directory [\\#1384](https://github.com/kivy/python-for-android/issues/1384)\n- Investigate conditional builds [\\#1382](https://github.com/kivy/python-for-android/issues/1382)\n- Unit test recipes \\(reportlab to begin with\\) [\\#1380](https://github.com/kivy/python-for-android/issues/1380)\n-  There is insufficient memory for the Java Runtime Environment to continue [\\#1373](https://github.com/kivy/python-for-android/issues/1373)\n- SSL/TLS is broken with Python 3: ImportError: missing module \\_ssl [\\#1372](https://github.com/kivy/python-for-android/issues/1372)\n- Buildozer fail to build numpy recipe [\\#1369](https://github.com/kivy/python-for-android/issues/1369)\n- Buildozer weird error\\(Still trying to use Kivy only\\) [\\#1368](https://github.com/kivy/python-for-android/issues/1368)\n- Error when trying to use numpy \\(Broken Toolchain\\) [\\#1367](https://github.com/kivy/python-for-android/issues/1367)\n- java.lang.UnsatisfiedLinkError: No implementation found for void org.libsdl.app.SDLActivity.nativeQuit\\(\\) \\(tried Java\\_org\\_libsdl\\_app\\_SDLActivity\\_nativeQuit and Java\\_org\\_libsdl\\_app\\_SDLActivity\\_nativeQuit\\_\\_\\) [\\#1365](https://github.com/kivy/python-for-android/issues/1365)\n- Migrate away from ndk\\_build? [\\#1362](https://github.com/kivy/python-for-android/issues/1362)\n- Fix/clean-up LDSHARED  [\\#1360](https://github.com/kivy/python-for-android/issues/1360)\n- buildozer error: no module named kivy [\\#1354](https://github.com/kivy/python-for-android/issues/1354)\n- Spurious nullpointer crash on app resume [\\#1353](https://github.com/kivy/python-for-android/issues/1353)\n- Docs say ANDROIDAPI=19 sets minimum API level, but it sets target API level [\\#1352](https://github.com/kivy/python-for-android/issues/1352)\n- Bug or support request? [\\#1346](https://github.com/kivy/python-for-android/issues/1346)\n- Issue with not finding JNI  and nativeSetEnv in SDLActivity [\\#1344](https://github.com/kivy/python-for-android/issues/1344)\n- python-for-android packages wrong manifest for ANDROIDAPI=\"19\", doesn't include configChanges=\"...|screenSize\" which leads to app crash on rotation [\\#1342](https://github.com/kivy/python-for-android/issues/1342)\n- python2: jpeg recipe broken due to missing libcutils [\\#1341](https://github.com/kivy/python-for-android/issues/1341)\n- Orientation change causes bogus SDL\\_QUIT and SDL\\_APP\\_TERMINATING events [\\#1338](https://github.com/kivy/python-for-android/issues/1338)\n- ctypes.util.find\\_library doesn't work with python 3 [\\#1337](https://github.com/kivy/python-for-android/issues/1337)\n- 'import lzma' fails with Python 3 [\\#1336](https://github.com/kivy/python-for-android/issues/1336)\n- Read & write to entire SD card is an unreasonable default permission for most games and basic UI applications [\\#1335](https://github.com/kivy/python-for-android/issues/1335)\n- Build failed [\\#1333](https://github.com/kivy/python-for-android/issues/1333)\n- Auto-close awaiting-reply labeled issues [\\#1331](https://github.com/kivy/python-for-android/issues/1331)\n- App crashes when sending POST request http [\\#1329](https://github.com/kivy/python-for-android/issues/1329)\n- kivy build error with python3crystax [\\#1328](https://github.com/kivy/python-for-android/issues/1328)\n- No \"pil\" or \"pillow\" available for Python 3 [\\#1326](https://github.com/kivy/python-for-android/issues/1326)\n- pillow fails on import [\\#1325](https://github.com/kivy/python-for-android/issues/1325)\n- Unavoidable PySDL2 crash on app resume [\\#1323](https://github.com/kivy/python-for-android/issues/1323)\n- Tons of different PySDL2 crashes when tabbing in/out of application during loading or right after it finished [\\#1321](https://github.com/kivy/python-for-android/issues/1321)\n- Build doesn't pick up gradle even though it is present, tries using ant instead and fails [\\#1320](https://github.com/kivy/python-for-android/issues/1320)\n- Crystax NDK size is larger than Android Studio + SDK + \\(regular\\) NDK + ... combined [\\#1319](https://github.com/kivy/python-for-android/issues/1319)\n- Generated md5sum does not match expected md5sum for sdl2 recipe [\\#1318](https://github.com/kivy/python-for-android/issues/1318)\n- It doesn't work about \"android.activity.bind\\(on\\_new\\_intent=myFunc\\)\",need help,thanks [\\#1317](https://github.com/kivy/python-for-android/issues/1317)\n- CMake error with OpenCV [\\#1315](https://github.com/kivy/python-for-android/issues/1315)\n- libpython2.7.so: is missing DT\\_SONAME using buildozer with latest VirtualBox VM for Android Buildozer \\(Version 2.0, released the 13 May 2017\\). [\\#1314](https://github.com/kivy/python-for-android/issues/1314)\n- On Virtual Machine, this error arises when openCV is filled in the requirement in the spec file  [\\#1313](https://github.com/kivy/python-for-android/issues/1313)\n- p4a apk command results in: No such file or directory: 'src/main/assets/private.mp3' [\\#1312](https://github.com/kivy/python-for-android/issues/1312)\n- Build error to import shapely \\(libgeos\\) [\\#1311](https://github.com/kivy/python-for-android/issues/1311)\n- \"/data/user/0/org.gmail.gmail/files/app/libpymodules.so\" not found [\\#1309](https://github.com/kivy/python-for-android/issues/1309)\n- numpy Python3/Crystax broken toolchain can't link a simple C program [\\#1303](https://github.com/kivy/python-for-android/issues/1303)\n- Screen Rotation and Re-layout [\\#1302](https://github.com/kivy/python-for-android/issues/1302)\n- IOError: \\[Errno socket error\\] \\[Errno 104\\] Connection reset by peer [\\#1301](https://github.com/kivy/python-for-android/issues/1301)\n- Python2 Build fails with make: \\*\\*\\* \\[Makefile\\:426\\: sharedmods\\] Error 139 [\\#1297](https://github.com/kivy/python-for-android/issues/1297)\n- ffmpeg breaks buildozer android debug  compilation process. [\\#1294](https://github.com/kivy/python-for-android/issues/1294)\n- Android: python startup complaining about missing hashlib functions [\\#1293](https://github.com/kivy/python-for-android/issues/1293)\n- sh.CommandNotFound: ndk\\_build [\\#1292](https://github.com/kivy/python-for-android/issues/1292)\n- SDL Error: ... could not load library \"libpython2.7.so\" ... on Android 4.2.2 [\\#1290](https://github.com/kivy/python-for-android/issues/1290)\n- Facing issues in making webbrowser.open\\(url\\) work [\\#1287](https://github.com/kivy/python-for-android/issues/1287)\n- Travis download caching [\\#1280](https://github.com/kivy/python-for-android/issues/1280)\n- When you fix the error \"error: \\[Errno 2\\] No such file or directory: 'src/main/assets/private.mp3'\" [\\#1279](https://github.com/kivy/python-for-android/issues/1279)\n- Little typo [\\#1275](https://github.com/kivy/python-for-android/issues/1275)\n- Fix numpy x86 build using \\#1252 [\\#1274](https://github.com/kivy/python-for-android/issues/1274)\n- Some phones don't allow access to /sdcard [\\#1272](https://github.com/kivy/python-for-android/issues/1272)\n- Kivy Android app running in background crashes when intent tries to pull it to top [\\#1271](https://github.com/kivy/python-for-android/issues/1271)\n- Sometimes sdl2 - UnicodeDecodeError: 'utf-8' codec can't decode byte 0x98 [\\#1270](https://github.com/kivy/python-for-android/issues/1270)\n- App crash when connecting to mysql [\\#1269](https://github.com/kivy/python-for-android/issues/1269)\n- \\[wishlist\\] Android launcher: Please build with Python 3 [\\#1268](https://github.com/kivy/python-for-android/issues/1268)\n- Opencv doesn't work on kivy  ImportError: dlopen failed: \"/data/data/com.mydomain.myapp/files/\\_applibs/cv2/cv2.so\" is 64-bit instead of 32-bit [\\#1267](https://github.com/kivy/python-for-android/issues/1267)\n- Why an error occurs 'python-for-android cannot continue; aborting'? [\\#1266](https://github.com/kivy/python-for-android/issues/1266)\n- Internet connection impossible with kivy app on android [\\#1265](https://github.com/kivy/python-for-android/issues/1265)\n- python-for-android recipe tests [\\#1263](https://github.com/kivy/python-for-android/issues/1263)\n- When the apk is turned on it gives me an error in the hashlib python3.6 [\\#1260](https://github.com/kivy/python-for-android/issues/1260)\n- fail to build application ERROR 'WindowInfoX11' is not a type identifier [\\#1259](https://github.com/kivy/python-for-android/issues/1259)\n- Buildozer command failed  [\\#1258](https://github.com/kivy/python-for-android/issues/1258)\n- IOError: \\[Errno 2\\] No such file or directory: 'src/main/assets/private.mp3'  [\\#1257](https://github.com/kivy/python-for-android/issues/1257)\n- Prebuilt python does not contain binaries for any architecture. [\\#1254](https://github.com/kivy/python-for-android/issues/1254)\n- Didn't find any valid dependency graphs. - Flask and websocket-client [\\#1253](https://github.com/kivy/python-for-android/issues/1253)\n- assertion PyBytes\\_Check failed [\\#1247](https://github.com/kivy/python-for-android/issues/1247)\n- Issue of AttributeError [\\#1246](https://github.com/kivy/python-for-android/issues/1246)\n- Python3 + greenlet install issue [\\#1245](https://github.com/kivy/python-for-android/issues/1245)\n- Confusing / Outdated Bootstraps [\\#1244](https://github.com/kivy/python-for-android/issues/1244)\n- openSSL recipe uses system-wide headers; app fails to run: cannot locate symbol EC\\_curve\\_nist2nid [\\#1243](https://github.com/kivy/python-for-android/issues/1243)\n- APK Immediately Closes After Opening in Debug, Release, and Zipaligned & Signed Versions [\\#1242](https://github.com/kivy/python-for-android/issues/1242)\n- Custom Recipes For Pandas, Matplotlib and Statsmodels [\\#1241](https://github.com/kivy/python-for-android/issues/1241)\n- WebView.setWebContentsDebuggingEnabled [\\#1240](https://github.com/kivy/python-for-android/issues/1240)\n- amreabi-v7a build cannot find SDL\\_GetTicks\\(\\) [\\#1239](https://github.com/kivy/python-for-android/issues/1239)\n- x86 inline assembly fails to build [\\#1238](https://github.com/kivy/python-for-android/issues/1238)\n- Can't compile dependency in 32bit on 64bit system [\\#1237](https://github.com/kivy/python-for-android/issues/1237)\n- Cannot import name 'uname' on Windows [\\#1234](https://github.com/kivy/python-for-android/issues/1234)\n- Uses Arm builds for x86, if Arm builds already exist [\\#1233](https://github.com/kivy/python-for-android/issues/1233)\n- The sh Python module could not be found [\\#1232](https://github.com/kivy/python-for-android/issues/1232)\n- Issue with Android API 23+ [\\#1231](https://github.com/kivy/python-for-android/issues/1231)\n- Failed to build application: 'WindowInfoX11' is not a type identifier [\\#1230](https://github.com/kivy/python-for-android/issues/1230)\n- Missing arm-linux-androideabi-gcc  [\\#1229](https://github.com/kivy/python-for-android/issues/1229)\n- Android build issues: raise CommandNotFound\\(path\\) [\\#1228](https://github.com/kivy/python-for-android/issues/1228)\n- TextInput display text only when suggestion validate on Asus ZenPhone3 [\\#1227](https://github.com/kivy/python-for-android/issues/1227)\n- on\\_stop not called on Android [\\#1226](https://github.com/kivy/python-for-android/issues/1226)\n- The python3crystax recipe can only be built when using the CrystaX NDK. [\\#1225](https://github.com/kivy/python-for-android/issues/1225)\n- Didn't find any valid dependency graphs. [\\#1222](https://github.com/kivy/python-for-android/issues/1222)\n- Google requiring API Target 26 in Aug/Nov 2018 [\\#1219](https://github.com/kivy/python-for-android/issues/1219)\n- p4a can't find android sdk [\\#1218](https://github.com/kivy/python-for-android/issues/1218)\n- IOError: \\[Errno 2\\] No such file or directory: u'/Users/gauravgupta/kivy/.buildozer/android/platform/build/dists/myellipse/build/outputs/apk/myellipse-debug.apk' [\\#1216](https://github.com/kivy/python-for-android/issues/1216)\n- Buildozer android application crashes on android [\\#1215](https://github.com/kivy/python-for-android/issues/1215)\n- Multiple issues with latest Android SDK [\\#1212](https://github.com/kivy/python-for-android/issues/1212)\n- sdkmanager doesn't exist [\\#1210](https://github.com/kivy/python-for-android/issues/1210)\n- add-jar doesn't work [\\#1208](https://github.com/kivy/python-for-android/issues/1208)\n- Kivy not working on Sony devices [\\#1206](https://github.com/kivy/python-for-android/issues/1206)\n- sqlite pre-populated database not being found [\\#1203](https://github.com/kivy/python-for-android/issues/1203)\n- Try python3.7 with Google NDK [\\#1202](https://github.com/kivy/python-for-android/issues/1202)\n- commit 3534a761 [\\#1200](https://github.com/kivy/python-for-android/issues/1200)\n- Kivy basic button program doesn't work [\\#1199](https://github.com/kivy/python-for-android/issues/1199)\n- Error Pythonforandroid.toolchain -m [\\#1196](https://github.com/kivy/python-for-android/issues/1196)\n- assert keyword do not work [\\#1193](https://github.com/kivy/python-for-android/issues/1193)\n- macOS High Siera installation issue [\\#1192](https://github.com/kivy/python-for-android/issues/1192)\n- failed to setup p4a on ubuntu \\(multiple issues\\) [\\#1191](https://github.com/kivy/python-for-android/issues/1191)\n- Cryptography woes: Can we freshen up our Python version...  [\\#1190](https://github.com/kivy/python-for-android/issues/1190)\n- Kivy crashes immediately on start, on Sony devices [\\#1188](https://github.com/kivy/python-for-android/issues/1188)\n- UnicodeDecodeError [\\#1187](https://github.com/kivy/python-for-android/issues/1187)\n- \\[INFO\\]:        Building with ant, as no gradle executable detected [\\#1186](https://github.com/kivy/python-for-android/issues/1186)\n- Local recipes can not be patched any longer [\\#1185](https://github.com/kivy/python-for-android/issues/1185)\n- Cymunk build fail on python3crystax [\\#1184](https://github.com/kivy/python-for-android/issues/1184)\n- p4a doesn't handle runtime permissions [\\#1183](https://github.com/kivy/python-for-android/issues/1183)\n- Android app freeze on screen rotation \\(again?\\) [\\#1179](https://github.com/kivy/python-for-android/issues/1179)\n- custom java class [\\#1177](https://github.com/kivy/python-for-android/issues/1177)\n- Dockerfile [\\#1175](https://github.com/kivy/python-for-android/issues/1175)\n- python2 recipe always builds for armeabi regardless of what arch you tell it to target [\\#1174](https://github.com/kivy/python-for-android/issues/1174)\n- The webview bootstrap does not support gradle [\\#1172](https://github.com/kivy/python-for-android/issues/1172)\n- 0.6 release checklist [\\#1170](https://github.com/kivy/python-for-android/issues/1170)\n- python 2.7 compile with NDK 15c [\\#1169](https://github.com/kivy/python-for-android/issues/1169)\n- Reopen running instance instead of starting a new one upon tapping app icon [\\#1161](https://github.com/kivy/python-for-android/issues/1161)\n- python3 incompatibility [\\#1154](https://github.com/kivy/python-for-android/issues/1154)\n- ffi.h: No such file or directory \\(solutions included\\) [\\#1148](https://github.com/kivy/python-for-android/issues/1148)\n- After building FFMpeg recipe, I still am not able to do ffmpeg -v [\\#1146](https://github.com/kivy/python-for-android/issues/1146)\n- Kivy/Buildozer/Psycopg2 [\\#1144](https://github.com/kivy/python-for-android/issues/1144)\n- SDL Error: Error Could not load any libpythonXXX.so [\\#1142](https://github.com/kivy/python-for-android/issues/1142)\n- Can't build numpy with current master, python 2, NDK 15 [\\#1141](https://github.com/kivy/python-for-android/issues/1141)\n- pyopenssl cryptography dependence [\\#1127](https://github.com/kivy/python-for-android/issues/1127)\n- Check if SDL2 libraries are up to date [\\#1126](https://github.com/kivy/python-for-android/issues/1126)\n- bind recipes to well defined versions [\\#1115](https://github.com/kivy/python-for-android/issues/1115)\n- pil and pillow modules for python3 [\\#1114](https://github.com/kivy/python-for-android/issues/1114)\n- Kivy python  android build issue? [\\#1110](https://github.com/kivy/python-for-android/issues/1110)\n- simple flask app on android fails to start [\\#1108](https://github.com/kivy/python-for-android/issues/1108)\n- \"crystax\\_python does not exist\" with python3crystax [\\#1105](https://github.com/kivy/python-for-android/issues/1105)\n- Running on Android 4.0 doesn't work when building for target api 19 [\\#1104](https://github.com/kivy/python-for-android/issues/1104)\n- Can't type anything into textinput using new toolchain [\\#1102](https://github.com/kivy/python-for-android/issues/1102)\n- \"android\" recipe isn't compatible with Python 3 [\\#1093](https://github.com/kivy/python-for-android/issues/1093)\n- Recipe does not exist: matplotlib [\\#1090](https://github.com/kivy/python-for-android/issues/1090)\n- Django App is not running. Web View does not load it [\\#1083](https://github.com/kivy/python-for-android/issues/1083)\n- Android 7 complains about Kivy 1.10.0 apps: \"detected problems with app native libraries\" [\\#1078](https://github.com/kivy/python-for-android/issues/1078)\n- Numpy recipe broken \\(atlas, blas, lapack, -lcrystax\\) [\\#1074](https://github.com/kivy/python-for-android/issues/1074)\n- requests module not compiling in buildozer when used with Python3crystax [\\#1072](https://github.com/kivy/python-for-android/issues/1072)\n- ImportError: No module named audioop [\\#1067](https://github.com/kivy/python-for-android/issues/1067)\n- sqlite3 not working with android\\_new [\\#1053](https://github.com/kivy/python-for-android/issues/1053)\n- dlopen failed: python2.7/site-packages/grpc/\\_cython/cygrpc.so not 32-bit: 2 [\\#1052](https://github.com/kivy/python-for-android/issues/1052)\n- Can't start service app [\\#1049](https://github.com/kivy/python-for-android/issues/1049)\n- Cannot build APK with python3crystax and flask - conflicting dependencies [\\#1041](https://github.com/kivy/python-for-android/issues/1041)\n- Slow build process since sh 1.12.5 [\\#1038](https://github.com/kivy/python-for-android/issues/1038)\n- Python3 + PyYaml conflict [\\#1031](https://github.com/kivy/python-for-android/issues/1031)\n- Can't write ti SD-card on Android 6.0.1 [\\#1024](https://github.com/kivy/python-for-android/issues/1024)\n- pygame\\_sdl2 compile failure \\# include \\<iconv.h\\> [\\#1023](https://github.com/kivy/python-for-android/issues/1023)\n- Build error on Mac: no archive symbol table \\(run ranlib\\) [\\#1012](https://github.com/kivy/python-for-android/issues/1012)\n- Shouldn't P4A Raise Exception On User File Having Syntax Error [\\#1009](https://github.com/kivy/python-for-android/issues/1009)\n- jnius is not working with webview bootstrap [\\#1003](https://github.com/kivy/python-for-android/issues/1003)\n- Built APK fails with ImportError: dlopen failed on \\_clock.so [\\#998](https://github.com/kivy/python-for-android/issues/998)\n- apk not build using crystax NDK [\\#992](https://github.com/kivy/python-for-android/issues/992)\n- Create a space for common bootstrap code along with a base class for all bootstraps [\\#988](https://github.com/kivy/python-for-android/issues/988)\n- Unpacking and copying app contents causes app to appear hung [\\#983](https://github.com/kivy/python-for-android/issues/983)\n- kivy app crashing on launch [\\#982](https://github.com/kivy/python-for-android/issues/982)\n- Android Emulator support [\\#979](https://github.com/kivy/python-for-android/issues/979)\n- Kivy with SDL2 bootstrap crashes on pausing if app doesn't support pause mode [\\#978](https://github.com/kivy/python-for-android/issues/978)\n- sqlite3 recipe not working with new toolchain [\\#977](https://github.com/kivy/python-for-android/issues/977)\n- lxml is needed in new toolchain [\\#976](https://github.com/kivy/python-for-android/issues/976)\n- P4A wants to start \"ant\" without using full SDK path [\\#974](https://github.com/kivy/python-for-android/issues/974)\n- API automatic lookup doesn't use available SDK API [\\#973](https://github.com/kivy/python-for-android/issues/973)\n- JNI ERROR \\(app bug\\): local reference table overflow \\(max=512\\) [\\#971](https://github.com/kivy/python-for-android/issues/971)\n- Kivy with SDL2 bootstrap crushes on resuming in some cases   [\\#967](https://github.com/kivy/python-for-android/issues/967)\n- Could not ping localhost:5000 [\\#961](https://github.com/kivy/python-for-android/issues/961)\n- Not a valid ELF executable [\\#957](https://github.com/kivy/python-for-android/issues/957)\n- How to completely remove installed app? [\\#953](https://github.com/kivy/python-for-android/issues/953)\n- ImportError android [\\#943](https://github.com/kivy/python-for-android/issues/943)\n- Older android version can't load libraries properly [\\#925](https://github.com/kivy/python-for-android/issues/925)\n- sed: 1: \"Modules/Setup.local\": invalid command code M [\\#924](https://github.com/kivy/python-for-android/issues/924)\n- Python3: armeabi used to copy, but armeabi-v7a chosen [\\#913](https://github.com/kivy/python-for-android/issues/913)\n- ImportError for sqlite3 [\\#910](https://github.com/kivy/python-for-android/issues/910)\n- PyGame backend: error while using android.copy\\_libs = 1 [\\#888](https://github.com/kivy/python-for-android/issues/888)\n- pytz installation works, but requires user to make build folder manually [\\#884](https://github.com/kivy/python-for-android/issues/884)\n- Numpy support w/ python3crystax [\\#882](https://github.com/kivy/python-for-android/issues/882)\n- Scipy recipe [\\#874](https://github.com/kivy/python-for-android/issues/874)\n- opencv recipe build error [\\#871](https://github.com/kivy/python-for-android/issues/871)\n- Flask with Python3 does not seem to work. [\\#870](https://github.com/kivy/python-for-android/issues/870)\n- p4a generates deprecated code under Android API 23 [\\#864](https://github.com/kivy/python-for-android/issues/864)\n- Kivy builds failing [\\#861](https://github.com/kivy/python-for-android/issues/861)\n- error when running an apk compiled with python3crystax [\\#859](https://github.com/kivy/python-for-android/issues/859)\n- my application using ctypes crashes on Kivy 1.9.2 and not on 1.8 [\\#858](https://github.com/kivy/python-for-android/issues/858)\n- apk, built with openssl launch error: \"libssl1.0.2h.so\" not found [\\#850](https://github.com/kivy/python-for-android/issues/850)\n- Can't install on Windows using pip [\\#819](https://github.com/kivy/python-for-android/issues/819)\n- FFmpeg recipe broken [\\#810](https://github.com/kivy/python-for-android/issues/810)\n- Todo: add rebuild-dist option [\\#807](https://github.com/kivy/python-for-android/issues/807)\n-  p4a create fails if cython is installed in ~/.local [\\#771](https://github.com/kivy/python-for-android/issues/771)\n- Completely clean install of minimal application fails to launch on Android 6 [\\#752](https://github.com/kivy/python-for-android/issues/752)\n- \"NoBackendError: No backend available\": Pyusb recipe for android [\\#740](https://github.com/kivy/python-for-android/issues/740)\n- app crash on close [\\#734](https://github.com/kivy/python-for-android/issues/734)\n- App crash when changing orientation [\\#730](https://github.com/kivy/python-for-android/issues/730)\n- Default extraction of NDK version not compatible with most recent stable NDK release... [\\#723](https://github.com/kivy/python-for-android/issues/723)\n- Enabling SSL for python3.5 using crystax [\\#705](https://github.com/kivy/python-for-android/issues/705)\n- Need to set locale env variable for python3 package recipes [\\#703](https://github.com/kivy/python-for-android/issues/703)\n- static jfieldID xxx not valid for class java.lang.Class\\<org.renpy.android.PythonActivity [\\#696](https://github.com/kivy/python-for-android/issues/696)\n- Python2 recipe for target 'libinstall' failed [\\#690](https://github.com/kivy/python-for-android/issues/690)\n- Python2 recipe for target 'Parser/pgen.stamp' failed [\\#689](https://github.com/kivy/python-for-android/issues/689)\n- Python2 recipe for target 'Lib/plat-linux4' failed [\\#688](https://github.com/kivy/python-for-android/issues/688)\n- Pygame missing include & link path [\\#687](https://github.com/kivy/python-for-android/issues/687)\n- Include NDK /sources/cxx-stl/gnu-libstdc++/ [\\#670](https://github.com/kivy/python-for-android/issues/670)\n- LDFlags missing ' -lpython2.7' [\\#668](https://github.com/kivy/python-for-android/issues/668)\n- Invalid option ccache: t [\\#667](https://github.com/kivy/python-for-android/issues/667)\n- ImportError when the apk launches with SDL2 bootstrap, kivy and python3crystax [\\#658](https://github.com/kivy/python-for-android/issues/658)\n- App crashes immediately after launching on Android [\\#653](https://github.com/kivy/python-for-android/issues/653)\n- Use travis to automatically test builds on different platforms [\\#625](https://github.com/kivy/python-for-android/issues/625)\n- AttributeError: module 'site' has no attribute 'getsitepackages' when running p4a create [\\#610](https://github.com/kivy/python-for-android/issues/610)\n- The SDL2 bootstrap can only extract to app private dir [\\#606](https://github.com/kivy/python-for-android/issues/606)\n- Can't load library \"libPVROCL.so\" [\\#594](https://github.com/kivy/python-for-android/issues/594)\n- VERSION\\_recipename env var functionality is not documented [\\#589](https://github.com/kivy/python-for-android/issues/589)\n- ccache compilation issues? [\\#550](https://github.com/kivy/python-for-android/issues/550)\n- Numpy recipe hardcodes arm [\\#528](https://github.com/kivy/python-for-android/issues/528)\n- Recipes depending on external modules don't work [\\#520](https://github.com/kivy/python-for-android/issues/520)\n- Touchscreen input with SDL2 bootstrap [\\#516](https://github.com/kivy/python-for-android/issues/516)\n- PR \\#408 needs applying to the new toolchain \\(master\\) [\\#486](https://github.com/kivy/python-for-android/issues/486)\n- trouble compiling some modules with revamp [\\#473](https://github.com/kivy/python-for-android/issues/473)\n- Foreground Kivy application stopped if phone locked via power button [\\#462](https://github.com/kivy/python-for-android/issues/462)\n- Apk fails with rotation when using min api \\<= 9 [\\#436](https://github.com/kivy/python-for-android/issues/436)\n- Android app crashes on screen rotation if android.minapi \\< 13 [\\#430](https://github.com/kivy/python-for-android/issues/430)\n- PIL does not compile with freetype2 support [\\#413](https://github.com/kivy/python-for-android/issues/413)\n- Android app crashing when ended and on\\_stop is not executed [\\#384](https://github.com/kivy/python-for-android/issues/384)\n- building harfbuzz with freetype support symbol errors [\\#381](https://github.com/kivy/python-for-android/issues/381)\n- HOSTPYTHON Fails to compile module [\\#377](https://github.com/kivy/python-for-android/issues/377)\n- p4a crashes under ARC [\\#367](https://github.com/kivy/python-for-android/issues/367)\n- apk packages can't find standardlibrary libs if using external storage [\\#363](https://github.com/kivy/python-for-android/issues/363)\n- TextInput error [\\#357](https://github.com/kivy/python-for-android/issues/357)\n- Error In building kivy android on Mac OSX [\\#341](https://github.com/kivy/python-for-android/issues/341)\n- Python 2.7.2 don't build cleanly with GCC ≥ 4.8 [\\#321](https://github.com/kivy/python-for-android/issues/321)\n- import gevent -\\> ImportError: cannot import name core [\\#288](https://github.com/kivy/python-for-android/issues/288)\n- Python build for android fails - cp: cannot stat ‘HOSTPYTHON=/home/inderpal/python-for-android/build/python/Python-2.7.2/hostpython’: No such file or directory [\\#286](https://github.com/kivy/python-for-android/issues/286)\n- Use Debian's Python packages for ARM instead of cross-compiling? [\\#242](https://github.com/kivy/python-for-android/issues/242)\n- Feature request: Possibility to choose the sensors' delay [\\#207](https://github.com/kivy/python-for-android/issues/207)\n- Problems with posixpath [\\#188](https://github.com/kivy/python-for-android/issues/188)\n- Pure Python Module: flufl.i18n fails to load when installed as a pure python module. [\\#182](https://github.com/kivy/python-for-android/issues/182)\n- socket.AF\\_UNIX is not supported [\\#163](https://github.com/kivy/python-for-android/issues/163)\n- Recipe for pyzmq \\($25 bounty\\) \\[$25\\] [\\#122](https://github.com/kivy/python-for-android/issues/122)\n\n**Merged pull requests:**\n\n- Updated version to 0.7.0 [\\#1659](https://github.com/kivy/python-for-android/pull/1659) ([inclement](https://github.com/inclement))\n- Updates broken recipes list, refs \\#1514 [\\#1658](https://github.com/kivy/python-for-android/pull/1658) ([AndreMiras](https://github.com/AndreMiras))\n- Feature/ticket1654 cffi unicode encode error [\\#1656](https://github.com/kivy/python-for-android/pull/1656) ([AndreMiras](https://github.com/AndreMiras))\n- Speed up Docker chown via COPY parameter [\\#1652](https://github.com/kivy/python-for-android/pull/1652) ([AndreMiras](https://github.com/AndreMiras))\n- Speed up Python and NumPy compilation process [\\#1651](https://github.com/kivy/python-for-android/pull/1651) ([AndreMiras](https://github.com/AndreMiras))\n- Fixes opencv compilation, fixes \\#1313 [\\#1650](https://github.com/kivy/python-for-android/pull/1650) ([AndreMiras](https://github.com/AndreMiras))\n- Remove unused variable in archs.py [\\#1649](https://github.com/kivy/python-for-android/pull/1649) ([opacam](https://github.com/opacam))\n- Fix linux hardcoded entry in archs.py [\\#1648](https://github.com/kivy/python-for-android/pull/1648) ([opacam](https://github.com/opacam))\n- Made the activity launch mode default to singleTask [\\#1646](https://github.com/kivy/python-for-android/pull/1646) ([inclement](https://github.com/inclement))\n- Made build.py stop running if compileall failed [\\#1645](https://github.com/kivy/python-for-android/pull/1645) ([inclement](https://github.com/inclement))\n- Retry on download hiccups, refs \\#1306 [\\#1643](https://github.com/kivy/python-for-android/pull/1643) ([AndreMiras](https://github.com/AndreMiras))\n- Set $LANG in PythonRecipe [\\#1642](https://github.com/kivy/python-for-android/pull/1642) ([inclement](https://github.com/inclement))\n- Remove old toolchain doc and add short note about overriding recipe sources [\\#1641](https://github.com/kivy/python-for-android/pull/1641) ([inclement](https://github.com/inclement))\n- Added separate module for checking user SDK, NDK, API etc. [\\#1640](https://github.com/kivy/python-for-android/pull/1640) ([inclement](https://github.com/inclement))\n- Added app for on-device unit tests [\\#1636](https://github.com/kivy/python-for-android/pull/1636) ([inclement](https://github.com/inclement))\n- Revert use of shlex.quote to avoid problems with python 2 [\\#1635](https://github.com/kivy/python-for-android/pull/1635) ([etc0de](https://github.com/etc0de))\n- Default Travis builds to Python3 [\\#1634](https://github.com/kivy/python-for-android/pull/1634) ([AndreMiras](https://github.com/AndreMiras))\n- Fixes ifaddrs recipe, closes \\#1398 [\\#1633](https://github.com/kivy/python-for-android/pull/1633) ([AndreMiras](https://github.com/AndreMiras))\n- Do not verbose the \"tar tf\" command [\\#1631](https://github.com/kivy/python-for-android/pull/1631) ([AndreMiras](https://github.com/AndreMiras))\n- psycopg2 recipe fixes and doc, fixes \\#1405 [\\#1629](https://github.com/kivy/python-for-android/pull/1629) ([AndreMiras](https://github.com/AndreMiras))\n- Use enaml {version} rather than master, fixes \\#1409 [\\#1628](https://github.com/kivy/python-for-android/pull/1628) ([AndreMiras](https://github.com/AndreMiras))\n- Clean-up LDSHARED, fixes \\#1360 [\\#1627](https://github.com/kivy/python-for-android/pull/1627) ([AndreMiras](https://github.com/AndreMiras))\n- Fix ctypes.util.find\\_library\\(\\) not finding any libraries on Android [\\#1624](https://github.com/kivy/python-for-android/pull/1624) ([etc0de](https://github.com/etc0de))\n- Fix librt recipe requires that NDK folder is writable [\\#1623](https://github.com/kivy/python-for-android/pull/1623) ([etc0de](https://github.com/etc0de))\n- Update of Recipes for python3 test [\\#1622](https://github.com/kivy/python-for-android/pull/1622) ([strubbi77](https://github.com/strubbi77))\n- - let cymunk also be built with python3 recipe [\\#1620](https://github.com/kivy/python-for-android/pull/1620) ([maho](https://github.com/maho))\n- Make python flags to be absolute paths for Android.mk files [\\#1619](https://github.com/kivy/python-for-android/pull/1619) ([opacam](https://github.com/opacam))\n- Create a `dumb` librt recipe and refactor the affected recipes to make use of this [\\#1618](https://github.com/kivy/python-for-android/pull/1618) ([opacam](https://github.com/opacam))\n- Made recipe graph resolution respect opt\\_depends [\\#1617](https://github.com/kivy/python-for-android/pull/1617) ([inclement](https://github.com/inclement))\n- Fix C code being wrong for python2 in start.c \\(char \\* to wchar\\_t \\*\\) [\\#1616](https://github.com/kivy/python-for-android/pull/1616) ([opacam](https://github.com/opacam))\n- Removed argument to cp that doesn't exist on macOS [\\#1614](https://github.com/kivy/python-for-android/pull/1614) ([inclement](https://github.com/inclement))\n- Fix incorrect site-packages path breaking keyboard test app at runtime [\\#1610](https://github.com/kivy/python-for-android/pull/1610) ([etc0de](https://github.com/etc0de))\n- Fix libffi/ctypes - wrong libffi headers when building python [\\#1609](https://github.com/kivy/python-for-android/pull/1609) ([opacam](https://github.com/opacam))\n- Fix getting empty \"modules\" directory when arch is not armeabi-v7a [\\#1608](https://github.com/kivy/python-for-android/pull/1608) ([j-devel](https://github.com/j-devel))\n- Fix strip in bootstrap [\\#1607](https://github.com/kivy/python-for-android/pull/1607) ([j-devel](https://github.com/j-devel))\n- Conditional build script fixes [\\#1604](https://github.com/kivy/python-for-android/pull/1604) ([AndreMiras](https://github.com/AndreMiras))\n- Migrates greenlet to new python3 recipe, fixes \\#1245 [\\#1603](https://github.com/kivy/python-for-android/pull/1603) ([AndreMiras](https://github.com/AndreMiras))\n- Fix sdk license error for travis tests \\(CI\\) [\\#1602](https://github.com/kivy/python-for-android/pull/1602) ([opacam](https://github.com/opacam))\n- \\[WIP\\] Restores the ability to compile the python files into .pyo/.pyc \\(for both versions of python\\) [\\#1601](https://github.com/kivy/python-for-android/pull/1601) ([opacam](https://github.com/opacam))\n- Migrates gevent to new python3 recipe [\\#1600](https://github.com/kivy/python-for-android/pull/1600) ([AndreMiras](https://github.com/AndreMiras))\n- Fix hardcoded entries \\(build platform\\) for core modules: archs and python [\\#1597](https://github.com/kivy/python-for-android/pull/1597) ([opacam](https://github.com/opacam))\n- Fix zeroconf compilation and grants python3 compatibility [\\#1596](https://github.com/kivy/python-for-android/pull/1596) ([opacam](https://github.com/opacam))\n- Fix reportlab's recipe `crypt.h` error [\\#1595](https://github.com/kivy/python-for-android/pull/1595) ([opacam](https://github.com/opacam))\n- Fix --force-build incorrectly listed as --force\\_build, fixes \\#1565 [\\#1593](https://github.com/kivy/python-for-android/pull/1593) ([etc0de](https://github.com/etc0de))\n- Allow patching from any folder + fix pygame components issues [\\#1592](https://github.com/kivy/python-for-android/pull/1592) ([opacam](https://github.com/opacam))\n- Fix --private and others showing weird error when used without argument [\\#1591](https://github.com/kivy/python-for-android/pull/1591) ([etc0de](https://github.com/etc0de))\n- Minimal fixes to make pygame bootstrap work with python2legacy [\\#1587](https://github.com/kivy/python-for-android/pull/1587) ([opacam](https://github.com/opacam))\n- Corrections for `Fix bootstraps for webview and service_only` \\(recently merged\\) [\\#1586](https://github.com/kivy/python-for-android/pull/1586) ([opacam](https://github.com/opacam))\n- \\[CORE FIX/ENHANCEMENT\\] Speedup copy that can be very very long \\(up to 2 minutes\\) [\\#1585](https://github.com/kivy/python-for-android/pull/1585) ([opacam](https://github.com/opacam))\n- Move libffi to mainline repo [\\#1584](https://github.com/kivy/python-for-android/pull/1584) ([opacam](https://github.com/opacam))\n- \\[WIP\\] Rework zbar \\(add python3 compatibility + add recipes: pyzbar and zbarlight\\) [\\#1583](https://github.com/kivy/python-for-android/pull/1583) ([opacam](https://github.com/opacam))\n-  fix missing gethostbyname\\_r on Android 5.1 [\\#1581](https://github.com/kivy/python-for-android/pull/1581) ([opacam](https://github.com/opacam))\n- \\[WIP\\] Rework libxml2, libxslt and lxml \\(update versions\\) [\\#1580](https://github.com/kivy/python-for-android/pull/1580) ([opacam](https://github.com/opacam))\n- Fixes ffmpeg compilation w/ openssl 1.1.1 [\\#1579](https://github.com/kivy/python-for-android/pull/1579) ([misl6](https://github.com/misl6))\n- Fix incorrect call assuming that OS python minor version matches hostpython [\\#1577](https://github.com/kivy/python-for-android/pull/1577) ([etc0de](https://github.com/etc0de))\n- Add download retries to deal better with connection hiccups during build [\\#1574](https://github.com/kivy/python-for-android/pull/1574) ([etc0de](https://github.com/etc0de))\n- Rework for Pillow/pil recipes & update jpeg and png [\\#1573](https://github.com/kivy/python-for-android/pull/1573) ([opacam](https://github.com/opacam))\n- Fix APP\\_PLATFORM not properly passed in NDKRecipe [\\#1572](https://github.com/kivy/python-for-android/pull/1572) ([etc0de](https://github.com/etc0de))\n- Fix outdated hardcoded python recipe references in lxml, reportlab & Pillow recipe [\\#1571](https://github.com/kivy/python-for-android/pull/1571) ([etc0de](https://github.com/etc0de))\n- Fix linkage problems with python's versioned library \\(reintroduce `INSTSONAME`\\) [\\#1568](https://github.com/kivy/python-for-android/pull/1568) ([opacam](https://github.com/opacam))\n- \\[OMEMO\\] updated omemo recipe [\\#1566](https://github.com/kivy/python-for-android/pull/1566) ([goffi-contrib](https://github.com/goffi-contrib))\n- Render format string argument on BuildInterruptingException [\\#1561](https://github.com/kivy/python-for-android/pull/1561) ([AndreMiras](https://github.com/AndreMiras))\n- \\[WIP\\]\\[CORE UPDATE - PART XV\\] Add encryption test app [\\#1556](https://github.com/kivy/python-for-android/pull/1556) ([opacam](https://github.com/opacam))\n- \\[WIP\\]\\[CORE UPDATE - PART XIV\\] Libtorrent+boost for both versions of python and updated versions [\\#1555](https://github.com/kivy/python-for-android/pull/1555) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART XIII\\] Pysha3 for both versions of python [\\#1554](https://github.com/kivy/python-for-android/pull/1554) ([opacam](https://github.com/opacam))\n- \\[WIP\\]\\[CORE UPDATE - PART XII\\] Pycryptodome for both versions of python [\\#1553](https://github.com/kivy/python-for-android/pull/1553) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART XI\\] M2crypto for both versions of python and updated version [\\#1552](https://github.com/kivy/python-for-android/pull/1552) ([opacam](https://github.com/opacam))\n- \\[WIP\\]\\[CORE UPDATE - PART X\\] Protobuf\\_cpp fixes and updated version [\\#1551](https://github.com/kivy/python-for-android/pull/1551) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART IX\\] Pymunk for both versions of python and enhance flags [\\#1550](https://github.com/kivy/python-for-android/pull/1550) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART VIII\\] Netifaces for both versions of python \\(updates the netifaces version\\) [\\#1549](https://github.com/kivy/python-for-android/pull/1549) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART VII\\] Apsw for both versions of python [\\#1548](https://github.com/kivy/python-for-android/pull/1548) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART VI\\] Fix scrypt [\\#1547](https://github.com/kivy/python-for-android/pull/1547) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART V\\] Fix pycrypto [\\#1546](https://github.com/kivy/python-for-android/pull/1546) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART IV\\] Fix cryptography+cffi [\\#1545](https://github.com/kivy/python-for-android/pull/1545) ([opacam](https://github.com/opacam))\n- fix wrong conditional for build custom\\_rules.tmpl.xml [\\#1544](https://github.com/kivy/python-for-android/pull/1544) ([bit4bit](https://github.com/bit4bit))\n- \\[CORE UPDATE - PART II\\] Fix bootstraps for webview and service\\_only [\\#1541](https://github.com/kivy/python-for-android/pull/1541) ([opacam](https://github.com/opacam))\n- \\[CORE UPDATE - PART I\\] Refactor python recipes + openssl + sqlite3 [\\#1537](https://github.com/kivy/python-for-android/pull/1537) ([opacam](https://github.com/opacam))\n- Re-added argument that was lost during build.py merge [\\#1533](https://github.com/kivy/python-for-android/pull/1533) ([inclement](https://github.com/inclement))\n- Use API 27 as new default for travis & docs [\\#1532](https://github.com/kivy/python-for-android/pull/1532) ([etc0de](https://github.com/etc0de))\n- Bump SDL2 to 2.0.9 & Add API \\>=23 runtime permissions API [\\#1528](https://github.com/kivy/python-for-android/pull/1528) ([etc0de](https://github.com/etc0de))\n- Unify build.py contents [\\#1524](https://github.com/kivy/python-for-android/pull/1524) ([etc0de](https://github.com/etc0de))\n- Unify configChanges manifest entry and add missing values [\\#1522](https://github.com/kivy/python-for-android/pull/1522) ([etc0de](https://github.com/etc0de))\n- Add google repository at allprojects [\\#1521](https://github.com/kivy/python-for-android/pull/1521) ([wo01](https://github.com/wo01))\n- Fix bytes/unicode issues in android recipe [\\#1516](https://github.com/kivy/python-for-android/pull/1516) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Uses target python3 on conditional builds, fixes \\#1485 [\\#1515](https://github.com/kivy/python-for-android/pull/1515) ([AndreMiras](https://github.com/AndreMiras))\n- Updates websocket-client recipe, fixes \\#1253 [\\#1513](https://github.com/kivy/python-for-android/pull/1513) ([AndreMiras](https://github.com/AndreMiras))\n- No need to decode into unicode when running in python 3 [\\#1512](https://github.com/kivy/python-for-android/pull/1512) ([jtoledo1974](https://github.com/jtoledo1974))\n- Update gradle version [\\#1507](https://github.com/kivy/python-for-android/pull/1507) ([opacam](https://github.com/opacam))\n- Fix libnacl recipe missing libsodium [\\#1505](https://github.com/kivy/python-for-android/pull/1505) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Make SDL2 & services\\_only bootstrap properly error with missing --private [\\#1503](https://github.com/kivy/python-for-android/pull/1503) ([etc0de](https://github.com/etc0de))\n- Unify start.c of all bootstraps to one file [\\#1500](https://github.com/kivy/python-for-android/pull/1500) ([etc0de](https://github.com/etc0de))\n- Minor fixes to basic common bootstrap handling code [\\#1499](https://github.com/kivy/python-for-android/pull/1499) ([etc0de](https://github.com/etc0de))\n- Rework common bootstrap area based on kollivier's work [\\#1496](https://github.com/kivy/python-for-android/pull/1496) ([etc0de](https://github.com/etc0de))\n- Fixes audiostream recipe on Python3 [\\#1495](https://github.com/kivy/python-for-android/pull/1495) ([misl6](https://github.com/misl6))\n- when listing distributions, if one has no ndk\\_api, consider it to be 0 [\\#1494](https://github.com/kivy/python-for-android/pull/1494) ([tshirtman](https://github.com/tshirtman))\n- Make Cython work without recipe [\\#1483](https://github.com/kivy/python-for-android/pull/1483) ([etc0de](https://github.com/etc0de))\n- Allow Python 3 To Be Built On Non-ARM Architectures [\\#1481](https://github.com/kivy/python-for-android/pull/1481) ([TheBrokenRail](https://github.com/TheBrokenRail))\n- Remove crystax docker and optimize Dockerfile [\\#1471](https://github.com/kivy/python-for-android/pull/1471) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Replaced many `exit(1)`s with exception raising [\\#1468](https://github.com/kivy/python-for-android/pull/1468) ([inclement](https://github.com/inclement))\n- Add ctypes support for python3's recipe [\\#1465](https://github.com/kivy/python-for-android/pull/1465) ([opacam](https://github.com/opacam))\n- Fix jpeg build for newer NDKs [\\#1363](https://github.com/kivy/python-for-android/pull/1363) ([mkg20001](https://github.com/mkg20001))\n- Added sympy recipe [\\#1236](https://github.com/kivy/python-for-android/pull/1236) ([inclement](https://github.com/inclement))\n- Added --no-optimize-python option to remove -OO in sdl2 bootstrap [\\#1221](https://github.com/kivy/python-for-android/pull/1221) ([inclement](https://github.com/inclement))\n- android\\_new: fix force\\_build option [\\#1006](https://github.com/kivy/python-for-android/pull/1006) ([ZingBallyhoo](https://github.com/ZingBallyhoo))\n\n## [0.6.0](https://github.com/kivy/python-for-android/tree/0.6.0) (2017-11-25)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/0.5.3...0.6.0)\n\n**Closed issues:**\n\n- buildozer cannot download sdl2 ,,, help   [\\#1176](https://github.com/kivy/python-for-android/issues/1176)\n- \\_multiprocessing [\\#1168](https://github.com/kivy/python-for-android/issues/1168)\n- p4a: command not found [\\#1167](https://github.com/kivy/python-for-android/issues/1167)\n- no module named tty [\\#1165](https://github.com/kivy/python-for-android/issues/1165)\n- Openssl recipe crashes on x86 arch [\\#1162](https://github.com/kivy/python-for-android/issues/1162)\n- Please help building the cffi recipe [\\#1159](https://github.com/kivy/python-for-android/issues/1159)\n- Build failed for Numpy [\\#1158](https://github.com/kivy/python-for-android/issues/1158)\n- Base: Failed to import \"android\" module. Could not remove android presplash. [\\#1153](https://github.com/kivy/python-for-android/issues/1153)\n- --ndk\\_ver cli option not working, but ANDROIDNDKVER does [\\#1149](https://github.com/kivy/python-for-android/issues/1149)\n- lxml uses etree.so that throws an unexpected e\\_machine error  [\\#1147](https://github.com/kivy/python-for-android/issues/1147)\n- Incompatible pyopenssl and cryptography versions  [\\#1138](https://github.com/kivy/python-for-android/issues/1138)\n- \"undefined reference to 'OBJ\\_obj2txt'\" error on building openssl with NDK 15b [\\#1135](https://github.com/kivy/python-for-android/issues/1135)\n- buildozer can't download hostpython2 [\\#1132](https://github.com/kivy/python-for-android/issues/1132)\n- App crashing on startup- ImportError: dlopen failed: \\_imaging.so is 64-bit [\\#1131](https://github.com/kivy/python-for-android/issues/1131)\n- Error on building FFPYPLAYER for VideoPlayer Widget [\\#1130](https://github.com/kivy/python-for-android/issues/1130)\n- Kivy App Crashes Immediately on Android [\\#1128](https://github.com/kivy/python-for-android/issues/1128)\n- Remove the python3 and hostpython3 recipes [\\#1125](https://github.com/kivy/python-for-android/issues/1125)\n- building with opencv show error [\\#1124](https://github.com/kivy/python-for-android/issues/1124)\n- Webview loading animation doesn't work [\\#1123](https://github.com/kivy/python-for-android/issues/1123)\n- Old toolchain is now deprecated [\\#1122](https://github.com/kivy/python-for-android/issues/1122)\n- `pip install kivy` fails with `'../include/config.pxi' not found` [\\#1120](https://github.com/kivy/python-for-android/issues/1120)\n- Suggestion: Allow a recipe to checkout a module from a local git repository [\\#1119](https://github.com/kivy/python-for-android/issues/1119)\n- Please add a 'version' command to p4a [\\#1116](https://github.com/kivy/python-for-android/issues/1116)\n- Websocket error: SSL not available  [\\#1107](https://github.com/kivy/python-for-android/issues/1107)\n- Pure python module as requirements aren't installed via pip [\\#1098](https://github.com/kivy/python-for-android/issues/1098)\n- Current android sdk has removed the ant/build.xml [\\#1069](https://github.com/kivy/python-for-android/issues/1069)\n- python-for-android 0.5 release checklist [\\#1043](https://github.com/kivy/python-for-android/issues/1043)\n- Numpy recipes build fail [\\#1040](https://github.com/kivy/python-for-android/issues/1040)\n- SDL2 launcher does not work with python3 [\\#980](https://github.com/kivy/python-for-android/issues/980)\n- ffpyplayer can't be built with new toolchain [\\#951](https://github.com/kivy/python-for-android/issues/951)\n- \"Couldn't load python3.5m: findLibrary returned null\" on older versions of Android [\\#866](https://github.com/kivy/python-for-android/issues/866)\n- Problems in creation of recipe for zbar [\\#854](https://github.com/kivy/python-for-android/issues/854)\n- freshly built old\\_toolchain crashes  with 'cannot locate symbol \"\\_Py\\_asinh\"' [\\#487](https://github.com/kivy/python-for-android/issues/487)\n- The SDL2 bootstrap can't make a Kivy Launcher [\\#468](https://github.com/kivy/python-for-android/issues/468)\n- Error: JAVA\\_HOME is not defined correctly. [\\#427](https://github.com/kivy/python-for-android/issues/427)\n- Compilation Error at ARM Environment [\\#352](https://github.com/kivy/python-for-android/issues/352)\n- Build errors on OSX 10.10 [\\#311](https://github.com/kivy/python-for-android/issues/311)\n- Easily reproducible crash accessing Context constants [\\#235](https://github.com/kivy/python-for-android/issues/235)\n- AttributeError: 'java.io.File' object has no attribute 'endswith' [\\#170](https://github.com/kivy/python-for-android/issues/170)\n- KeyEvent.getCharacters\\(\\) returns `null` instead of `KEYCODE_UNKNOWN` [\\#142](https://github.com/kivy/python-for-android/issues/142)\n- Carousel: add\\_widget after build\\(\\) [\\#69](https://github.com/kivy/python-for-android/issues/69)\n- sound.length not returning correctly [\\#67](https://github.com/kivy/python-for-android/issues/67)\n- KEYCODE\\_HOME and KEYCODE\\_POWER can't be trapped [\\#43](https://github.com/kivy/python-for-android/issues/43)\n\n**Merged pull requests:**\n\n- Removed '-j5' from openssl make [\\#1180](https://github.com/kivy/python-for-android/pull/1180) ([inclement](https://github.com/inclement))\n- add recipes for pyrxp & reportlab [\\#1173](https://github.com/kivy/python-for-android/pull/1173) ([replabrobin](https://github.com/replabrobin))\n- Add the ndk platform libs dir during biglink [\\#1171](https://github.com/kivy/python-for-android/pull/1171) ([inclement](https://github.com/inclement))\n- Update troubleshooting.rst [\\#1164](https://github.com/kivy/python-for-android/pull/1164) ([AndreMiras](https://github.com/AndreMiras))\n- Fix OpenSSL recipe crashes on x86 [\\#1163](https://github.com/kivy/python-for-android/pull/1163) ([gdyuldin](https://github.com/gdyuldin))\n- Cleaned up some old comments [\\#1160](https://github.com/kivy/python-for-android/pull/1160) ([inclement](https://github.com/inclement))\n- The pypi python return http 403 error on http [\\#1157](https://github.com/kivy/python-for-android/pull/1157) ([brvier](https://github.com/brvier))\n- Accept pypi fragmented URLs \\(\\#md5...\\) [\\#1155](https://github.com/kivy/python-for-android/pull/1155) ([wexi](https://github.com/wexi))\n- Add note about NDK version on 32-bit OS [\\#1150](https://github.com/kivy/python-for-android/pull/1150) ([ghost](https://github.com/ghost))\n- Adds zbar \\(and dependencies\\) support, refs \\#854 [\\#1145](https://github.com/kivy/python-for-android/pull/1145) ([AndreMiras](https://github.com/AndreMiras))\n- Remove unused `extract_source()` method [\\#1143](https://github.com/kivy/python-for-android/pull/1143) ([AndreMiras](https://github.com/AndreMiras))\n- This current Twisted version actually runs! [\\#1140](https://github.com/kivy/python-for-android/pull/1140) ([wexi](https://github.com/wexi))\n- Two humble changes [\\#1139](https://github.com/kivy/python-for-android/pull/1139) ([wexi](https://github.com/wexi))\n- Made start.c search ANDROID\\_UNPACK for crystax [\\#1137](https://github.com/kivy/python-for-android/pull/1137) ([inclement](https://github.com/inclement))\n- Update ffpyplayer recipe and it's dependencies [\\#1134](https://github.com/kivy/python-for-android/pull/1134) ([germn](https://github.com/germn))\n- Re-added --sdk argument for sdl2 bootstrap, but with a warning [\\#1133](https://github.com/kivy/python-for-android/pull/1133) ([inclement](https://github.com/inclement))\n- Moved webview loading animation to bootstrap [\\#1129](https://github.com/kivy/python-for-android/pull/1129) ([inclement](https://github.com/inclement))\n- Added --version argument [\\#1118](https://github.com/kivy/python-for-android/pull/1118) ([inclement](https://github.com/inclement))\n- Add help regarding websocket-client & SSL [\\#1113](https://github.com/kivy/python-for-android/pull/1113) ([brentpicasso](https://github.com/brentpicasso))\n- Improve documentation for websocket-client to account for SSL compatibility [\\#1112](https://github.com/kivy/python-for-android/pull/1112) ([brentpicasso](https://github.com/brentpicasso))\n- Try system python when performing compileall [\\#1109](https://github.com/kivy/python-for-android/pull/1109) ([inclement](https://github.com/inclement))\n- Fixed ssl, sqlite and crystax library loads on Android versions before ~4.4 [\\#1106](https://github.com/kivy/python-for-android/pull/1106) ([inclement](https://github.com/inclement))\n- Fixes for NDK 15+ [\\#1103](https://github.com/kivy/python-for-android/pull/1103) ([inclement](https://github.com/inclement))\n- Fix: only first line of note was displayed in the blue box [\\#1101](https://github.com/kivy/python-for-android/pull/1101) ([Fogapod](https://github.com/Fogapod))\n- Added \"regex\" module recipe [\\#1100](https://github.com/kivy/python-for-android/pull/1100) ([germn](https://github.com/germn))\n- SDL2/Gradle bootstrap with fixes [\\#1071](https://github.com/kivy/python-for-android/pull/1071) ([inclement](https://github.com/inclement))\n- Updated Kivy icons to newer logo under sdl2 [\\#1033](https://github.com/kivy/python-for-android/pull/1033) ([inclement](https://github.com/inclement))\n\n## [0.5.3](https://github.com/kivy/python-for-android/tree/0.5.3) (2017-08-26)\n\n[Full Changelog](https://github.com/kivy/python-for-android/compare/6234a5d11d35d4a1b7fee9433461499f68a915d9...0.5.3)\n\n**Closed issues:**\n\n- Building with Crystax NDK : \"Android NDK : Could not find application project directory\" [\\#1084](https://github.com/kivy/python-for-android/issues/1084)\n- recipes \\_\\_init\\_\\_.py indentation error [\\#1082](https://github.com/kivy/python-for-android/issues/1082)\n- AttributeError: 'Context' object has no attribute 'hostpython' [\\#1077](https://github.com/kivy/python-for-android/issues/1077)\n- 'Context' object has no attribute 'hostpython' [\\#1073](https://github.com/kivy/python-for-android/issues/1073)\n- Error after update of SDK [\\#1070](https://github.com/kivy/python-for-android/issues/1070)\n- wakelock == 1 not preventing screen from locking on sdl2 [\\#1061](https://github.com/kivy/python-for-android/issues/1061)\n- running p4a from git fails [\\#1058](https://github.com/kivy/python-for-android/issues/1058)\n- 'Context' object has no attribute 'hostpython' [\\#1056](https://github.com/kivy/python-for-android/issues/1056)\n- Can p4a be used without a bootstrap? [\\#1055](https://github.com/kivy/python-for-android/issues/1055)\n- Screen rotation with \"orientation=all\" is broken [\\#1054](https://github.com/kivy/python-for-android/issues/1054)\n- python-for-android doesn't work with current Android SDK [\\#1050](https://github.com/kivy/python-for-android/issues/1050)\n- p4a should fetch Kivy 1.10 instead of master [\\#1044](https://github.com/kivy/python-for-android/issues/1044)\n- Android Browser Not Launching for OAuth 2.0 [\\#1032](https://github.com/kivy/python-for-android/issues/1032)\n- flash quite,adb log  [\\#1030](https://github.com/kivy/python-for-android/issues/1030)\n- Can't build, sh.py raise a exception. [\\#1029](https://github.com/kivy/python-for-android/issues/1029)\n- Python 3 branch still uses python 2.x [\\#1022](https://github.com/kivy/python-for-android/issues/1022)\n- service fails to start [\\#1020](https://github.com/kivy/python-for-android/issues/1020)\n- path to service file [\\#1019](https://github.com/kivy/python-for-android/issues/1019)\n- Crash trap with custom logger and sys.stdout.encoding [\\#1018](https://github.com/kivy/python-for-android/issues/1018)\n- pyjnius build failed [\\#1016](https://github.com/kivy/python-for-android/issues/1016)\n- JNI ERROR \\(app bug\\): local reference table overflow \\(max=512\\) while executing Couchbae Lite Query [\\#1008](https://github.com/kivy/python-for-android/issues/1008)\n- Custom recipes hinders the downloading of other ones. [\\#1001](https://github.com/kivy/python-for-android/issues/1001)\n- documentation: how to run without pip install \\( development mode \\) [\\#996](https://github.com/kivy/python-for-android/issues/996)\n- PythonActivity.mActivity causes app crash with new toolchain [\\#995](https://github.com/kivy/python-for-android/issues/995)\n- Failure deploying apk files using buildozer android debug [\\#989](https://github.com/kivy/python-for-android/issues/989)\n- 'Window.request\\_keyboard' without showing keyboard [\\#986](https://github.com/kivy/python-for-android/issues/986)\n- TypeError: slice indices must be integers or None or have an \\_\\_index\\_\\_ method [\\#984](https://github.com/kivy/python-for-android/issues/984)\n- --presplash and --icon aren't mentioned in revamp docs [\\#975](https://github.com/kivy/python-for-android/issues/975)\n- NDK automatic lookup tries to pick a tarball [\\#972](https://github.com/kivy/python-for-android/issues/972)\n- Kivy is broken on recent master [\\#970](https://github.com/kivy/python-for-android/issues/970)\n- device doesn't go on sleep mode [\\#969](https://github.com/kivy/python-for-android/issues/969)\n- The python2 build imports cython from the system python in /usr/lib/... [\\#964](https://github.com/kivy/python-for-android/issues/964)\n- \"Could not remove android presplash\" if 'android' is not in requirements [\\#963](https://github.com/kivy/python-for-android/issues/963)\n- \"AndroidJoystick is not supported by your version of linux\" confusing message in log [\\#962](https://github.com/kivy/python-for-android/issues/962)\n- Could not ping localhost:5000 [\\#960](https://github.com/kivy/python-for-android/issues/960)\n- Using pyjnius leads to crash \\(sometimes?\\) if app built by new toolchain [\\#959](https://github.com/kivy/python-for-android/issues/959)\n- Android screen rotation is probably broken [\\#955](https://github.com/kivy/python-for-android/issues/955)\n- Compiling pyo with sdl is breaking ply / enaml [\\#947](https://github.com/kivy/python-for-android/issues/947)\n- Access WiFi information? [\\#940](https://github.com/kivy/python-for-android/issues/940)\n- p4a erroring on SSL connection [\\#939](https://github.com/kivy/python-for-android/issues/939)\n- Compiling PIL seems to use pyconfig.h from the wrong directory [\\#937](https://github.com/kivy/python-for-android/issues/937)\n- ImportError for ssl [\\#934](https://github.com/kivy/python-for-android/issues/934)\n- My app crashed by raising error about Python3.5m, but i made apk by python2.7..!!! [\\#933](https://github.com/kivy/python-for-android/issues/933)\n- \\[launcher\\] icon= and splash= parameters [\\#932](https://github.com/kivy/python-for-android/issues/932)\n- \\[launcher\\] app update by http\\(s\\) from external website \\(https:// for github required\\) [\\#931](https://github.com/kivy/python-for-android/issues/931)\n- Presplash delay [\\#928](https://github.com/kivy/python-for-android/issues/928)\n- Python3 APK fails to build! [\\#927](https://github.com/kivy/python-for-android/issues/927)\n- MQTT [\\#926](https://github.com/kivy/python-for-android/issues/926)\n- Can't build apk on OS X El Capitan [\\#922](https://github.com/kivy/python-for-android/issues/922)\n- command not found exception. [\\#921](https://github.com/kivy/python-for-android/issues/921)\n- ffmpeg recipe possibly broken [\\#920](https://github.com/kivy/python-for-android/issues/920)\n- ERROR: /usr/bin/ant failed! [\\#918](https://github.com/kivy/python-for-android/issues/918)\n- Feature request / Idea / Poll: Create kex packages [\\#917](https://github.com/kivy/python-for-android/issues/917)\n- AttributeError: 'module' object has no attribute 'recipe' [\\#907](https://github.com/kivy/python-for-android/issues/907)\n- Kivy .so is too small to be an ELF executable \\[pygame bootstrap\\] [\\#897](https://github.com/kivy/python-for-android/issues/897)\n- p4a recipes crashes on matplotlib [\\#895](https://github.com/kivy/python-for-android/issues/895)\n- onResume deadlock with pyjnius/pygame/sdl on android [\\#890](https://github.com/kivy/python-for-android/issues/890)\n- dlopen failed: cannot locate symbol \"\\_Py\\_NoneStruct\" [\\#887](https://github.com/kivy/python-for-android/issues/887)\n- SDL2 continually passes joyaxismotion events [\\#885](https://github.com/kivy/python-for-android/issues/885)\n- Cloud Builder - 500 Internal Server Error [\\#883](https://github.com/kivy/python-for-android/issues/883)\n- Is it possible to add a argument to set the background color of the \"loading screen\"? [\\#881](https://github.com/kivy/python-for-android/issues/881)\n- Building apk problem for android on OSX EL Capitan 10.11.5 [\\#878](https://github.com/kivy/python-for-android/issues/878)\n- python3crystax conflicts with python3 [\\#877](https://github.com/kivy/python-for-android/issues/877)\n- No instructions for utilizing in Arch linux \\(i686 / x86\\_64\\) [\\#876](https://github.com/kivy/python-for-android/issues/876)\n- Can't compile with openssl [\\#868](https://github.com/kivy/python-for-android/issues/868)\n- p4a recipes error: missing matplotlib [\\#865](https://github.com/kivy/python-for-android/issues/865)\n- AndroidBrowser.open\\(\\) should return a value [\\#855](https://github.com/kivy/python-for-android/issues/855)\n- Can't import PIL on python for android and kivy? [\\#853](https://github.com/kivy/python-for-android/issues/853)\n- How can i use the custom broadcast by myself in the background service?  [\\#849](https://github.com/kivy/python-for-android/issues/849)\n- Building python for android's requirements for 64 bit Android processors [\\#848](https://github.com/kivy/python-for-android/issues/848)\n- The Kivy Option \"softinput\\_mode\" does not work on Android with bootstrap=sdl2 [\\#847](https://github.com/kivy/python-for-android/issues/847)\n- webbrowser.open\\(\\) doesn't work on Android with bootstrap=sdl2 [\\#846](https://github.com/kivy/python-for-android/issues/846)\n- non debug apk? [\\#844](https://github.com/kivy/python-for-android/issues/844)\n- Rotation Lock Ignored [\\#842](https://github.com/kivy/python-for-android/issues/842)\n- Plyer GPS example works on android but not android\\_new toolchain [\\#833](https://github.com/kivy/python-for-android/issues/833)\n- p4a create Error with openssl: start.c\\:2\\:20\\: Python.h: No such file or directory [\\#830](https://github.com/kivy/python-for-android/issues/830)\n- MD5sum - UnboundLocalError: current\\_md5 referenced before assignment [\\#828](https://github.com/kivy/python-for-android/issues/828)\n- Recipes are still not resolved properly sometimes [\\#826](https://github.com/kivy/python-for-android/issues/826)\n- Failed to build Pillow-3.3.0 gcc: error: \\_imaging.o: No such file or directory [\\#823](https://github.com/kivy/python-for-android/issues/823)\n- p4a create error: kivy/\\_clock.pxd\\:6\\:4\\: Executable statement not allowed here [\\#822](https://github.com/kivy/python-for-android/issues/822)\n- No such file or directory: \".../whitelist.txt\" [\\#821](https://github.com/kivy/python-for-android/issues/821)\n- Docs - connected toctrees, too deep? [\\#820](https://github.com/kivy/python-for-android/issues/820)\n- Showcase with launcher [\\#814](https://github.com/kivy/python-for-android/issues/814)\n- Can't target api, --sdk argument broken [\\#813](https://github.com/kivy/python-for-android/issues/813)\n- Lxml, docutils need recipe [\\#812](https://github.com/kivy/python-for-android/issues/812)\n- \\[Pygame\\] start.c fatal error: Python.h: No such file or directory  [\\#809](https://github.com/kivy/python-for-android/issues/809)\n- Presplash does not work with SDL2. [\\#806](https://github.com/kivy/python-for-android/issues/806)\n- netifaces recipe broken [\\#802](https://github.com/kivy/python-for-android/issues/802)\n- sdl2 recipe builds wrong bootstrap jni source [\\#801](https://github.com/kivy/python-for-android/issues/801)\n- On resume crash in SDL2 bootstrap [\\#797](https://github.com/kivy/python-for-android/issues/797)\n- Pure python requirements does not install \\(plyer for example\\) [\\#795](https://github.com/kivy/python-for-android/issues/795)\n- E/linker: site-packages/android/\\_android.so too small to be an ELF executable [\\#768](https://github.com/kivy/python-for-android/issues/768)\n- Cryptography recipe does not compile [\\#766](https://github.com/kivy/python-for-android/issues/766)\n- Threads need a wrapper for calling detach\\(\\) [\\#758](https://github.com/kivy/python-for-android/issues/758)\n- Activity \\(android.activity\\) piece of code [\\#756](https://github.com/kivy/python-for-android/issues/756)\n- ImportError: Import by filename is not supported. [\\#751](https://github.com/kivy/python-for-android/issues/751)\n- Hostpython not found by Recipe.py [\\#748](https://github.com/kivy/python-for-android/issues/748)\n- P4A and C++/SDL2/Python2/OpenGL game [\\#747](https://github.com/kivy/python-for-android/issues/747)\n- Why does the boot image is deformed? [\\#745](https://github.com/kivy/python-for-android/issues/745)\n- problem with p4a recipe for kivent [\\#744](https://github.com/kivy/python-for-android/issues/744)\n- Webview - back button bug [\\#741](https://github.com/kivy/python-for-android/issues/741)\n- webview - flask server crashes immediately [\\#739](https://github.com/kivy/python-for-android/issues/739)\n- p4a apk webview bug [\\#738](https://github.com/kivy/python-for-android/issues/738)\n- Libffi recipe fails with \"unrecognized options: --enable-shared\" [\\#733](https://github.com/kivy/python-for-android/issues/733)\n- App close when device is flipped [\\#732](https://github.com/kivy/python-for-android/issues/732)\n- recipe for pyjinius fails [\\#731](https://github.com/kivy/python-for-android/issues/731)\n- Doc clarification on p4a requirements for basic kivy app with SDL2 bootstrap [\\#724](https://github.com/kivy/python-for-android/issues/724)\n- PIL recipe is broken [\\#722](https://github.com/kivy/python-for-android/issues/722)\n- raise exc\\_info\\[0\\], exc\\_info\\[1\\], exc\\_info\\[2\\] - Syntax Error [\\#721](https://github.com/kivy/python-for-android/issues/721)\n- Some input files use or override a deprecated API. [\\#719](https://github.com/kivy/python-for-android/issues/719)\n- Unexpected \"malformed start tag\" error with HTMLParser [\\#715](https://github.com/kivy/python-for-android/issues/715)\n- open\\(\\) built-in function don't work as expected  [\\#706](https://github.com/kivy/python-for-android/issues/706)\n- Webview bootstrap. Where to start? \\[$100\\] [\\#700](https://github.com/kivy/python-for-android/issues/700)\n- Back button doesn't work [\\#699](https://github.com/kivy/python-for-android/issues/699)\n-  FileNotFoundError '/bin/sh'  with subprocess.py python3crystax [\\#691](https://github.com/kivy/python-for-android/issues/691)\n- static jfieldID not valid for class java.lang.Class\\<org.renpy.android.PythonActivity\\> [\\#686](https://github.com/kivy/python-for-android/issues/686)\n- Incorrect SDK variable in build.xml with pygame bootstrap and direct p4a invocation [\\#684](https://github.com/kivy/python-for-android/issues/684)\n- Failure to build apk due to incorrect invocation of \"ant\" by \"sh.ant\"... [\\#681](https://github.com/kivy/python-for-android/issues/681)\n- Missing recipes: cherrypy, libnacl, requests [\\#674](https://github.com/kivy/python-for-android/issues/674)\n- Multiple permissions in .p4a [\\#673](https://github.com/kivy/python-for-android/issues/673)\n- Python compiled components recipe: \"bad gcc/glibc config?\" [\\#669](https://github.com/kivy/python-for-android/issues/669)\n- No \"--window\" option [\\#666](https://github.com/kivy/python-for-android/issues/666)\n- .jam files not installed [\\#661](https://github.com/kivy/python-for-android/issues/661)\n- AttributeError: 'NoneType' object has no attribute 'from\\_crystax' [\\#659](https://github.com/kivy/python-for-android/issues/659)\n- will\\_build does not work as expected [\\#657](https://github.com/kivy/python-for-android/issues/657)\n- Check presence of main.py during build time [\\#656](https://github.com/kivy/python-for-android/issues/656)\n- md5 not handled yet [\\#650](https://github.com/kivy/python-for-android/issues/650)\n- App crash on resume with new tool chain [\\#646](https://github.com/kivy/python-for-android/issues/646)\n- Unable to find libpython2.7.so on older versions of Android [\\#645](https://github.com/kivy/python-for-android/issues/645)\n- Corrupted window size with Window.softinput\\_mode = 'below\\_target'  [\\#635](https://github.com/kivy/python-for-android/issues/635)\n- Patch files not found [\\#633](https://github.com/kivy/python-for-android/issues/633)\n- Unintended rollback patches [\\#632](https://github.com/kivy/python-for-android/issues/632)\n- \\[master\\] AttributeError: 'tuple' object has no attribute 'startswith' [\\#631](https://github.com/kivy/python-for-android/issues/631)\n- Python recipe from Crystax undefined [\\#629](https://github.com/kivy/python-for-android/issues/629)\n- SDL2\\_image error Unknown or unsupported ARM architecture [\\#627](https://github.com/kivy/python-for-android/issues/627)\n- Building python2 for armeabi fails due to unknown option \"-single\\_module\" \\(OS X\\) [\\#623](https://github.com/kivy/python-for-android/issues/623)\n- Building python2 for armeabi fails due to space character in storage\\_dir \\(OS X\\) [\\#622](https://github.com/kivy/python-for-android/issues/622)\n- AttributeError: 'Context' object has no attribute 'hostpython' [\\#620](https://github.com/kivy/python-for-android/issues/620)\n- Jpeg recipe is broken [\\#617](https://github.com/kivy/python-for-android/issues/617)\n- build.py TypeError args.services object is not iterable [\\#616](https://github.com/kivy/python-for-android/issues/616)\n- OpenSSL 1.0.2e outdated \\(replaced by 1.0.2f\\) [\\#614](https://github.com/kivy/python-for-android/issues/614)\n- Matplotlib recipe [\\#607](https://github.com/kivy/python-for-android/issues/607)\n- setup.py install doesn't include the recipes folder [\\#591](https://github.com/kivy/python-for-android/issues/591)\n- missing recipes/pyjnius/getenv.patch [\\#590](https://github.com/kivy/python-for-android/issues/590)\n- standard includes not found by  boost [\\#576](https://github.com/kivy/python-for-android/issues/576)\n- HTTP 302 recipe download file [\\#573](https://github.com/kivy/python-for-android/issues/573)\n- SDL2 bootstrap broken with blacklist? [\\#567](https://github.com/kivy/python-for-android/issues/567)\n- Kivy Launcher 1.9.1 APK doesn't work on Lollipop [\\#548](https://github.com/kivy/python-for-android/issues/548)\n- Logo aspect ratio problem [\\#545](https://github.com/kivy/python-for-android/issues/545)\n- Window.softinput\\_mode/TextInput - Window moves up/down when switching softinput\\_mode to 'below\\_target'/'resize' [\\#544](https://github.com/kivy/python-for-android/issues/544)\n- native code in kivyAndroid, possible? [\\#542](https://github.com/kivy/python-for-android/issues/542)\n- Error compiling [\\#541](https://github.com/kivy/python-for-android/issues/541)\n- import sh module problem [\\#540](https://github.com/kivy/python-for-android/issues/540)\n- Inconsistent dependency graph behaviour [\\#515](https://github.com/kivy/python-for-android/issues/515)\n- We demand Python 3 support [\\#512](https://github.com/kivy/python-for-android/issues/512)\n- CythonRecipe: how to handle different settings for different .pyx files? [\\#511](https://github.com/kivy/python-for-android/issues/511)\n- Arch support is broken [\\#492](https://github.com/kivy/python-for-android/issues/492)\n- function should\\_build [\\#491](https://github.com/kivy/python-for-android/issues/491)\n- verbose output [\\#490](https://github.com/kivy/python-for-android/issues/490)\n- compiler problem with gcc \\>= 4.8 [\\#489](https://github.com/kivy/python-for-android/issues/489)\n- error when execute p4a in line from urlparse import urlparse [\\#488](https://github.com/kivy/python-for-android/issues/488)\n- Can't get off the ground [\\#485](https://github.com/kivy/python-for-android/issues/485)\n- Python3 doesn't work on Android [\\#484](https://github.com/kivy/python-for-android/issues/484)\n- Allow scaling of the presplash image to device resolution [\\#481](https://github.com/kivy/python-for-android/issues/481)\n- python multiprocess.dummy do not work [\\#479](https://github.com/kivy/python-for-android/issues/479)\n- Question: compatibility with cx\\_Freeze [\\#478](https://github.com/kivy/python-for-android/issues/478)\n- Purge inclement where needed [\\#477](https://github.com/kivy/python-for-android/issues/477)\n- Missing dependency in quickstart? [\\#476](https://github.com/kivy/python-for-android/issues/476)\n- No service support with SDL2 bootstrap [\\#467](https://github.com/kivy/python-for-android/issues/467)\n- Kivy can't get the keyboard height with SDL2 [\\#466](https://github.com/kivy/python-for-android/issues/466)\n- SDL2 backend doesn't support a loading screen [\\#465](https://github.com/kivy/python-for-android/issues/465)\n- Many recipes from the old toolchain need porting [\\#464](https://github.com/kivy/python-for-android/issues/464)\n- \\[revamp\\] Android NDK API 21 issue [\\#455](https://github.com/kivy/python-for-android/issues/455)\n- \\[revamp\\] Twisted [\\#454](https://github.com/kivy/python-for-android/issues/454)\n- \\[revamp\\] Can't load unicodedata module [\\#453](https://github.com/kivy/python-for-android/issues/453)\n- \\[revamp\\] The revamp branch always prints the ToolchainCL object after running [\\#452](https://github.com/kivy/python-for-android/issues/452)\n- \\[revamp\\] setuptools \"wrong ELF class\" issues [\\#451](https://github.com/kivy/python-for-android/issues/451)\n- \\[revamp\\] Unpack archives that don't list their root directory [\\#450](https://github.com/kivy/python-for-android/issues/450)\n- \\[revamp\\] Recipes can only depend on other recipes [\\#449](https://github.com/kivy/python-for-android/issues/449)\n- \\[revamp\\] p4a silently fails if --private is not absolute [\\#448](https://github.com/kivy/python-for-android/issues/448)\n- \\[revamp\\] Invalid syntax for python3 [\\#444](https://github.com/kivy/python-for-android/issues/444)\n- \\[revamp\\] toolchain.py ignores recipes with errors [\\#440](https://github.com/kivy/python-for-android/issues/440)\n- Error when trying to create an apk package with buildozer or with distribute.sh [\\#435](https://github.com/kivy/python-for-android/issues/435)\n- pylibpd fails to compile [\\#434](https://github.com/kivy/python-for-android/issues/434)\n- \\[revamp\\] --android\\_api is ignored on SDL2 bootstrap [\\#425](https://github.com/kivy/python-for-android/issues/425)\n- OSError: \\[Errno 2\\] No such file or directory: '/home/username/code/kivy/examples/demo/touchtracer' [\\#424](https://github.com/kivy/python-for-android/issues/424)\n- \\[revamp\\] - Darwin patches applied on Linux [\\#423](https://github.com/kivy/python-for-android/issues/423)\n- swift: md5sum changed - fix URL on a static content [\\#421](https://github.com/kivy/python-for-android/issues/421)\n- PLATFORM \\> 19: there is no sys/timeb.h [\\#419](https://github.com/kivy/python-for-android/issues/419)\n- Numpy build fails if it detects system libraries and tries to link with them [\\#417](https://github.com/kivy/python-for-android/issues/417)\n- MD5 opencv is incorrect in recipe opencv [\\#411](https://github.com/kivy/python-for-android/issues/411)\n- numpy fails to build  [\\#409](https://github.com/kivy/python-for-android/issues/409)\n- twisted [\\#403](https://github.com/kivy/python-for-android/issues/403)\n- ctypes callback function SIGSEGV [\\#401](https://github.com/kivy/python-for-android/issues/401)\n- gstreamer recipe [\\#400](https://github.com/kivy/python-for-android/issues/400)\n- buildozer needs markupsafe to build  [\\#399](https://github.com/kivy/python-for-android/issues/399)\n- Ctypes still not found \\[$50\\] [\\#397](https://github.com/kivy/python-for-android/issues/397)\n- Documentation: example using startActivityForResult with bind\\(on\\_activity\\_result=\\) [\\#388](https://github.com/kivy/python-for-android/issues/388)\n- Does not build on OSX [\\#387](https://github.com/kivy/python-for-android/issues/387)\n- with softinput\\_mode=\"pan\", the window no longer pans back down when the keyboard is dismissed [\\#380](https://github.com/kivy/python-for-android/issues/380)\n- android app crash on screen rotation [\\#379](https://github.com/kivy/python-for-android/issues/379)\n- Harfbuzz compile issue on 15.04 - fatal error: asm-generic/posix\\_types.h: No such file or directory [\\#376](https://github.com/kivy/python-for-android/issues/376)\n- python fabric recipe fails  [\\#374](https://github.com/kivy/python-for-android/issues/374)\n- Build a release so this can be included in F-Droid [\\#369](https://github.com/kivy/python-for-android/issues/369)\n- Enable armeabi-v7a-hard [\\#366](https://github.com/kivy/python-for-android/issues/366)\n- bulldozer and distribute.sh [\\#364](https://github.com/kivy/python-for-android/issues/364)\n- does this matter ? arm-linux-androideabi-gcc: error: kivy/graphics/opengl.c: No such file or directory [\\#362](https://github.com/kivy/python-for-android/issues/362)\n- python 3 compatibility [\\#359](https://github.com/kivy/python-for-android/issues/359)\n- softinput\\_mode='pan' does not work well with orientation change of the device screen. [\\#348](https://github.com/kivy/python-for-android/issues/348)\n- How can I pass String value from EditText In Android Activity to Python Script  and also make the activity able to retrieve the String result from a function in the python script such as displaying the retrieved String in TextView ? [\\#346](https://github.com/kivy/python-for-android/issues/346)\n- pygame.midi.init\\(\\)  Failing on Android 4.4.4 - ImportError: No module named pypm [\\#342](https://github.com/kivy/python-for-android/issues/342)\n- Error In building kivy android on Mac OSX [\\#340](https://github.com/kivy/python-for-android/issues/340)\n- ButtonBehavior.on\\_touch\\_up dispatches on\\_release immediately [\\#339](https://github.com/kivy/python-for-android/issues/339)\n- Failed to build pure Python module included after `twisted` [\\#337](https://github.com/kivy/python-for-android/issues/337)\n- The compiled APK crashes with error on \\_imaging.so: not found [\\#335](https://github.com/kivy/python-for-android/issues/335)\n- ctypes.py and \\_ctypes.so are not available [\\#333](https://github.com/kivy/python-for-android/issues/333)\n- After installing the Pillow does not compile the project. \\(Mac OS\\) [\\#332](https://github.com/kivy/python-for-android/issues/332)\n- pygame.display.set\\_mode runs out of memory [\\#331](https://github.com/kivy/python-for-android/issues/331)\n- Python Service multiple instances [\\#329](https://github.com/kivy/python-for-android/issues/329)\n- Kivy Launcher should include Plyer [\\#328](https://github.com/kivy/python-for-android/issues/328)\n- Issue in Cython file compilation and building  kivy.graphics.vertex\\_instruction extensions [\\#326](https://github.com/kivy/python-for-android/issues/326)\n- Failed Cython Compilation [\\#325](https://github.com/kivy/python-for-android/issues/325)\n- Python3 Branch: jinja2 traceback on buildozer --verbose android debug [\\#322](https://github.com/kivy/python-for-android/issues/322)\n- About ctypes [\\#319](https://github.com/kivy/python-for-android/issues/319)\n- Audio loop not working on Android [\\#318](https://github.com/kivy/python-for-android/issues/318)\n- Command ./distribute.sh -m \"openssl pil kivy\" results in error: command 'ccache' failed with exit status 1 [\\#306](https://github.com/kivy/python-for-android/issues/306)\n- Bug with android.p4a\\_whitelist in buildozer.spec file. [\\#302](https://github.com/kivy/python-for-android/issues/302)\n- ctypes module not loaded [\\#301](https://github.com/kivy/python-for-android/issues/301)\n- P4A builds stable instead of master [\\#300](https://github.com/kivy/python-for-android/issues/300)\n- Use of SL4A [\\#299](https://github.com/kivy/python-for-android/issues/299)\n- Github zipball doesn't work anymore [\\#297](https://github.com/kivy/python-for-android/issues/297)\n- My `./distribute.sh` suddenly stop building today. [\\#294](https://github.com/kivy/python-for-android/issues/294)\n- Create recipes for storm and psycopg2 [\\#293](https://github.com/kivy/python-for-android/issues/293)\n- error in your VM Image after ./distribute.sh -m kivy [\\#291](https://github.com/kivy/python-for-android/issues/291)\n- Error in Apk process building [\\#290](https://github.com/kivy/python-for-android/issues/290)\n- IOError: \\[Errno 2\\] No usable temporary directory [\\#289](https://github.com/kivy/python-for-android/issues/289)\n- Path commands on readme and official-website-toolchain don't seem to work [\\#281](https://github.com/kivy/python-for-android/issues/281)\n- Twisted/recipe.sh can't find zope.interface [\\#280](https://github.com/kivy/python-for-android/issues/280)\n- cython uses system python instead of hostpython [\\#277](https://github.com/kivy/python-for-android/issues/277)\n- Twisted recipe doesn't work in 32-bit build environment [\\#276](https://github.com/kivy/python-for-android/issues/276)\n- distribute fails to build flask or sqlite3 : Error: libpymodules.so - no input files [\\#275](https://github.com/kivy/python-for-android/issues/275)\n- \\# Command failed: ./distribute.sh -m \"kivy\" -d \"myapp\" [\\#271](https://github.com/kivy/python-for-android/issues/271)\n- creating new service does not start the service [\\#270](https://github.com/kivy/python-for-android/issues/270)\n- Touch input causes big slowdown \\( from get\\_keyboard\\_height \\) - can use 12% to 30%+ of cpu before even passing to kivy [\\#268](https://github.com/kivy/python-for-android/issues/268)\n- Sticky services [\\#267](https://github.com/kivy/python-for-android/issues/267)\n- --private clobers /data/data/\\[package name\\]/files directory [\\#263](https://github.com/kivy/python-for-android/issues/263)\n- touchtracer not working [\\#262](https://github.com/kivy/python-for-android/issues/262)\n- compile error: sys/timeb.h not found [\\#261](https://github.com/kivy/python-for-android/issues/261)\n- Python file is not converted to bytecode when building apk [\\#257](https://github.com/kivy/python-for-android/issues/257)\n- New Version of NDK [\\#256](https://github.com/kivy/python-for-android/issues/256)\n- Full control of the AndroidManifest.xml \\[$20\\] [\\#255](https://github.com/kivy/python-for-android/issues/255)\n- extra characters in textinput on Android [\\#247](https://github.com/kivy/python-for-android/issues/247)\n- Feature Request: add checkNetwork to \\_android.pyx [\\#244](https://github.com/kivy/python-for-android/issues/244)\n- Env variables for ANDROIDAPI ignored [\\#241](https://github.com/kivy/python-for-android/issues/241)\n- Kivy Android VM doesn't have plyer recipe [\\#239](https://github.com/kivy/python-for-android/issues/239)\n- Switch from .sh to pythonic toolchain [\\#238](https://github.com/kivy/python-for-android/issues/238)\n- Update twisted to 14.0.0 [\\#237](https://github.com/kivy/python-for-android/issues/237)\n- ./distribute.sh -u option doesn't work [\\#236](https://github.com/kivy/python-for-android/issues/236)\n- \\*.so is too small to be an ELF executable [\\#234](https://github.com/kivy/python-for-android/issues/234)\n- Cannot run Py4A APKs on Android x86 [\\#233](https://github.com/kivy/python-for-android/issues/233)\n- C extension overlap [\\#232](https://github.com/kivy/python-for-android/issues/232)\n- Can't build django [\\#231](https://github.com/kivy/python-for-android/issues/231)\n- System.currentTimeMillis\\(\\) returns a negative number [\\#229](https://github.com/kivy/python-for-android/issues/229)\n- `future_builtins` shouldn't be blacklisted [\\#228](https://github.com/kivy/python-for-android/issues/228)\n- Non-clear direction in readme guide [\\#227](https://github.com/kivy/python-for-android/issues/227)\n- OSX build error if more than one pure-python module [\\#226](https://github.com/kivy/python-for-android/issues/226)\n- unknown type name 'SDL\\_BlitMap' while buildozer apk generation [\\#225](https://github.com/kivy/python-for-android/issues/225)\n- error for package apk [\\#223](https://github.com/kivy/python-for-android/issues/223)\n- collect2: ld returned 1 exit status [\\#221](https://github.com/kivy/python-for-android/issues/221)\n- buildozer not works more [\\#220](https://github.com/kivy/python-for-android/issues/220)\n- \\[moved\\] pyo files are not being recreated by ./build.py in python for android [\\#216](https://github.com/kivy/python-for-android/issues/216)\n- error: could not create '/usr/local/lib/python2.7/site-packages/PIL': Permission denied [\\#215](https://github.com/kivy/python-for-android/issues/215)\n- collect2: ld returned 1 exit status [\\#213](https://github.com/kivy/python-for-android/issues/213)\n- virtualenv not entering [\\#212](https://github.com/kivy/python-for-android/issues/212)\n- cymunk doesn't get copied to dist/default/ [\\#211](https://github.com/kivy/python-for-android/issues/211)\n- No Python 3 Support [\\#210](https://github.com/kivy/python-for-android/issues/210)\n- HELP! ./distribute.sh failed to locate arm-linux-androideabi-gcc [\\#209](https://github.com/kivy/python-for-android/issues/209)\n- TextInput not behaving with SwiftKey [\\#198](https://github.com/kivy/python-for-android/issues/198)\n- Example Applications? [\\#197](https://github.com/kivy/python-for-android/issues/197)\n- installing Pydev has encountered a problem [\\#196](https://github.com/kivy/python-for-android/issues/196)\n- buildozer apk build problem [\\#195](https://github.com/kivy/python-for-android/issues/195)\n- Zope2 at \"Downloading/unpacking zope.security\" [\\#190](https://github.com/kivy/python-for-android/issues/190)\n- \\_scproxy import error when building on Mac with 'requests' lib [\\#186](https://github.com/kivy/python-for-android/issues/186)\n- Kivy TextInput doesn't work with SwiftKey keyboard [\\#184](https://github.com/kivy/python-for-android/issues/184)\n- Error in using debug flag, calling ANT debugger? [\\#179](https://github.com/kivy/python-for-android/issues/179)\n- Update setuptools version [\\#176](https://github.com/kivy/python-for-android/issues/176)\n- Problems with distribute.sh [\\#175](https://github.com/kivy/python-for-android/issues/175)\n- Rst editor crashing on android [\\#174](https://github.com/kivy/python-for-android/issues/174)\n- Doubt about distribute.sh [\\#173](https://github.com/kivy/python-for-android/issues/173)\n- Own Activities in AndroidManifest.xml [\\#172](https://github.com/kivy/python-for-android/issues/172)\n- Error to execute command ./distribute.sh -m \"openssl pil kivy\" [\\#167](https://github.com/kivy/python-for-android/issues/167)\n- keyboard stays on screen in android, after app exit [\\#166](https://github.com/kivy/python-for-android/issues/166)\n- ffmpeg ndk r9 incompatibility [\\#165](https://github.com/kivy/python-for-android/issues/165)\n- kivy touchtracer apk not running on emmulator [\\#162](https://github.com/kivy/python-for-android/issues/162)\n- fatal error: stdlib.h: No such file or directory [\\#159](https://github.com/kivy/python-for-android/issues/159)\n- Add psutil recipe [\\#157](https://github.com/kivy/python-for-android/issues/157)\n- Failure to compile .py should cause exit [\\#156](https://github.com/kivy/python-for-android/issues/156)\n- Docs on the readthedocs are old [\\#155](https://github.com/kivy/python-for-android/issues/155)\n- non full screen touch offset on android [\\#153](https://github.com/kivy/python-for-android/issues/153)\n- Android NDK r9 Fails [\\#149](https://github.com/kivy/python-for-android/issues/149)\n- Android app crashes on rotation to landscape [\\#148](https://github.com/kivy/python-for-android/issues/148)\n- configure: error: C compiler cannot create executables [\\#145](https://github.com/kivy/python-for-android/issues/145)\n- GCC 4.4.3 depreciated in android NDK [\\#143](https://github.com/kivy/python-for-android/issues/143)\n- dlopen fail on android 4.3 [\\#141](https://github.com/kivy/python-for-android/issues/141)\n- new dependency ordering broke some usecases with buildozer [\\#140](https://github.com/kivy/python-for-android/issues/140)\n- Android Keyboard information [\\#139](https://github.com/kivy/python-for-android/issues/139)\n- Android keyboards do not recognize Password fields as secure passwords [\\#138](https://github.com/kivy/python-for-android/issues/138)\n- kivy.network.urlrequest: No callback parameter: on\\_failure [\\#137](https://github.com/kivy/python-for-android/issues/137)\n- UnicodeDecodeError [\\#136](https://github.com/kivy/python-for-android/issues/136)\n- arm-linux-androideabi-gcc: no input files [\\#133](https://github.com/kivy/python-for-android/issues/133)\n- Unable to build APK [\\#132](https://github.com/kivy/python-for-android/issues/132)\n- apk doesn't unpack on first load [\\#131](https://github.com/kivy/python-for-android/issues/131)\n- kivy 1.8 \\(testing\\) UnicodeDecodeError: 'ascii' codec can't decode byte... [\\#129](https://github.com/kivy/python-for-android/issues/129)\n- presplash \"crazy\" position when change the orientation the cellphone [\\#127](https://github.com/kivy/python-for-android/issues/127)\n-  subprocess.check\\_output\\(\\[\"ping\", \"-c\", \"3\", hostname\\]\\) non-zero exit code 2 [\\#126](https://github.com/kivy/python-for-android/issues/126)\n- Testing/Enabling armeabi-v7a [\\#123](https://github.com/kivy/python-for-android/issues/123)\n- distribute.sh fails when using the 64bit Android NDK [\\#116](https://github.com/kivy/python-for-android/issues/116)\n- encoding error: UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in range\\(128\\) [\\#114](https://github.com/kivy/python-for-android/issues/114)\n- IOError: \\[Errno 20\\] Not a directory: [\\#113](https://github.com/kivy/python-for-android/issues/113)\n- Add setting in building package to allow switching to sleep mode [\\#111](https://github.com/kivy/python-for-android/issues/111)\n- Android keyboard autosuggestion bug [\\#110](https://github.com/kivy/python-for-android/issues/110)\n- Command ./distribute.sh -m \"pyjnius kivy\" ends with error [\\#109](https://github.com/kivy/python-for-android/issues/109)\n- Feature request: Android service [\\#107](https://github.com/kivy/python-for-android/issues/107)\n- \\[recipe - pylibpd\\] unpacking does not work [\\#103](https://github.com/kivy/python-for-android/issues/103)\n- \\[master\\] Unzip fails [\\#102](https://github.com/kivy/python-for-android/issues/102)\n- old unixcompiler.py bug when using ccache g++ [\\#100](https://github.com/kivy/python-for-android/issues/100)\n- Non ascii key inputs not dispatched [\\#97](https://github.com/kivy/python-for-android/issues/97)\n- Github archives are named master by default, breaking build [\\#95](https://github.com/kivy/python-for-android/issues/95)\n- Python for Android not compiling sqlite3 module [\\#91](https://github.com/kivy/python-for-android/issues/91)\n- Buggy dependencies handling with multiple ./distribute.sh [\\#90](https://github.com/kivy/python-for-android/issues/90)\n- build.py has \"/usr/bin/python2\" hard-coded [\\#88](https://github.com/kivy/python-for-android/issues/88)\n- touchtracer bug - dp migration? [\\#87](https://github.com/kivy/python-for-android/issues/87)\n- kivy build failing [\\#86](https://github.com/kivy/python-for-android/issues/86)\n- Unable to resolve project target [\\#85](https://github.com/kivy/python-for-android/issues/85)\n- Compile stop when it copy java code [\\#83](https://github.com/kivy/python-for-android/issues/83)\n- touchtracer.apk not working [\\#82](https://github.com/kivy/python-for-android/issues/82)\n- If the kivy module is built at the same time as others, it fails [\\#81](https://github.com/kivy/python-for-android/issues/81)\n- Build with lxml not working [\\#79](https://github.com/kivy/python-for-android/issues/79)\n- Compilation Error [\\#78](https://github.com/kivy/python-for-android/issues/78)\n- properties.so is not a valid ELF object [\\#77](https://github.com/kivy/python-for-android/issues/77)\n- Unfocusing kivy's TextInput [\\#76](https://github.com/kivy/python-for-android/issues/76)\n- Camera on android [\\#75](https://github.com/kivy/python-for-android/issues/75)\n- Using stable source... [\\#74](https://github.com/kivy/python-for-android/issues/74)\n- fails to import text\\_sdlttf [\\#73](https://github.com/kivy/python-for-android/issues/73)\n- Standard module for SQLite not available [\\#72](https://github.com/kivy/python-for-android/issues/72)\n- Kivy on Android galaxy s3 apk run exception [\\#70](https://github.com/kivy/python-for-android/issues/70)\n- Check for build dependencies [\\#68](https://github.com/kivy/python-for-android/issues/68)\n- Compile failing [\\#66](https://github.com/kivy/python-for-android/issues/66)\n- First label not rendered on android [\\#64](https://github.com/kivy/python-for-android/issues/64)\n- arm/limits.h: No such file or directory [\\#63](https://github.com/kivy/python-for-android/issues/63)\n- Compiling hostpython doesn't work [\\#62](https://github.com/kivy/python-for-android/issues/62)\n- when android-sdk platform tool is not installed. build process run into error. [\\#61](https://github.com/kivy/python-for-android/issues/61)\n- Incorrect default Python executable [\\#60](https://github.com/kivy/python-for-android/issues/60)\n- error when build distribution on debian squeeze. [\\#57](https://github.com/kivy/python-for-android/issues/57)\n- Many small build issues in Ubuntu 12.04.1 x64 [\\#55](https://github.com/kivy/python-for-android/issues/55)\n- csv module [\\#54](https://github.com/kivy/python-for-android/issues/54)\n- Allow the screen to timeout in Android [\\#53](https://github.com/kivy/python-for-android/issues/53)\n- Order matters in ./distribute.sh -m options [\\#50](https://github.com/kivy/python-for-android/issues/50)\n- Initial distribution build fails with \"unterminated substitute pattern\" [\\#47](https://github.com/kivy/python-for-android/issues/47)\n- Creating Widgets [\\#46](https://github.com/kivy/python-for-android/issues/46)\n- Unable to use the crypt lib. [\\#45](https://github.com/kivy/python-for-android/issues/45)\n- verify me [\\#44](https://github.com/kivy/python-for-android/issues/44)\n- Graphics lost returning from \"HOME\" on Motorola Photon \\(Sprint built 2.3.4\\) [\\#42](https://github.com/kivy/python-for-android/issues/42)\n- Name clash with http://code.google.com/p/python-for-android/ [\\#39](https://github.com/kivy/python-for-android/issues/39)\n- Installed apk crash at loading... [\\#38](https://github.com/kivy/python-for-android/issues/38)\n- Fail to include any module other than Kivy [\\#37](https://github.com/kivy/python-for-android/issues/37)\n- Many issues on OS X running ./distribute.sh -m \"kivy\" [\\#36](https://github.com/kivy/python-for-android/issues/36)\n- Unable to build python-for-android [\\#34](https://github.com/kivy/python-for-android/issues/34)\n- Kivy / Python-for-android : Build.py fails to build an android package apk [\\#33](https://github.com/kivy/python-for-android/issues/33)\n- Unable to use \\<gstreamer\\> as loader! [\\#31](https://github.com/kivy/python-for-android/issues/31)\n- build stuck at `assets/private.mp3: private/include/python2.7/pyconfig.h` [\\#29](https://github.com/kivy/python-for-android/issues/29)\n- Can't build with all modules [\\#27](https://github.com/kivy/python-for-android/issues/27)\n- ask a question about the touchtracer demo [\\#26](https://github.com/kivy/python-for-android/issues/26)\n- Error: Target id 'android-8' is not valid. [\\#25](https://github.com/kivy/python-for-android/issues/25)\n- Build Error: \"/usr/lib/libpython2.7.so: file not recognized: File format not recognized\" [\\#16](https://github.com/kivy/python-for-android/issues/16)\n- FFMpeg doesn't build [\\#13](https://github.com/kivy/python-for-android/issues/13)\n- Problem init environment [\\#12](https://github.com/kivy/python-for-android/issues/12)\n- error when build the APK [\\#10](https://github.com/kivy/python-for-android/issues/10)\n- arm-linux-androideabi-gcc: Internal error: Killed \\(program cc1\\) [\\#9](https://github.com/kivy/python-for-android/issues/9)\n- Build: Pyrex error [\\#7](https://github.com/kivy/python-for-android/issues/7)\n- Where is the example? [\\#5](https://github.com/kivy/python-for-android/issues/5)\n- Build error [\\#4](https://github.com/kivy/python-for-android/issues/4)\n- Restore libpymodules.so behavior [\\#3](https://github.com/kivy/python-for-android/issues/3)\n- Guide step 2 issues [\\#2](https://github.com/kivy/python-for-android/issues/2)\n- compile failing [\\#1](https://github.com/kivy/python-for-android/issues/1)\n\n**Merged pull requests:**\n\n- Update README.md [\\#1096](https://github.com/kivy/python-for-android/pull/1096) ([zed](https://github.com/zed))\n- Deleted whitespace [\\#1091](https://github.com/kivy/python-for-android/pull/1091) ([Fogapod](https://github.com/Fogapod))\n- Made graph recognise python\\_depends [\\#1089](https://github.com/kivy/python-for-android/pull/1089) ([inclement](https://github.com/inclement))\n- Moved logger bytes conversion to affect all lines [\\#1088](https://github.com/kivy/python-for-android/pull/1088) ([inclement](https://github.com/inclement))\n- Made logger convert output to utf-8 including errors [\\#1087](https://github.com/kivy/python-for-android/pull/1087) ([inclement](https://github.com/inclement))\n- Recipe import fixes [\\#1086](https://github.com/kivy/python-for-android/pull/1086) ([inclement](https://github.com/inclement))\n- Various Ethereum related recipes fixes [\\#1080](https://github.com/kivy/python-for-android/pull/1080) ([AndreMiras](https://github.com/AndreMiras))\n- Ethereum related recipes [\\#1068](https://github.com/kivy/python-for-android/pull/1068) ([AndreMiras](https://github.com/AndreMiras))\n- implement wakelock for sdl2 [\\#1066](https://github.com/kivy/python-for-android/pull/1066) ([brentpicasso](https://github.com/brentpicasso))\n- Fix typo in pythonforandroid/recipe.py [\\#1065](https://github.com/kivy/python-for-android/pull/1065) ([jupart](https://github.com/jupart))\n- Rewrite recipe graph [\\#1064](https://github.com/kivy/python-for-android/pull/1064) ([inclement](https://github.com/inclement))\n- Use Kivy setup.py flag instead of rmtree [\\#1063](https://github.com/kivy/python-for-android/pull/1063) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Delete the kivy-examples dir from the dist under python3 [\\#1062](https://github.com/kivy/python-for-android/pull/1062) ([inclement](https://github.com/inclement))\n- Added command handling if p4a is run with no arguments [\\#1059](https://github.com/kivy/python-for-android/pull/1059) ([inclement](https://github.com/inclement))\n- Call avdmanager instead of android in the SDK [\\#1057](https://github.com/kivy/python-for-android/pull/1057) ([inclement](https://github.com/inclement))\n- Updated Kivy recipe to pull 1.10.0 [\\#1048](https://github.com/kivy/python-for-android/pull/1048) ([inclement](https://github.com/inclement))\n- \\[Core\\] Get the latest version of requests. [\\#1045](https://github.com/kivy/python-for-android/pull/1045) ([hobbestigrou](https://github.com/hobbestigrou))\n- Recipe for websocket-client [\\#1039](https://github.com/kivy/python-for-android/pull/1039) ([debauchery1st](https://github.com/debauchery1st))\n- Recipe updates and small fixes to build process [\\#1034](https://github.com/kivy/python-for-android/pull/1034) ([bobatsar](https://github.com/bobatsar))\n- Added warning about different path behavior in new toolchain service [\\#1028](https://github.com/kivy/python-for-android/pull/1028) ([Bakterija](https://github.com/Bakterija))\n- Recipe for Pymunk [\\#1026](https://github.com/kivy/python-for-android/pull/1026) ([viblo](https://github.com/viblo))\n- Fixed protobuf cpp [\\#1021](https://github.com/kivy/python-for-android/pull/1021) ([Deniskore](https://github.com/Deniskore))\n- Fix packaging failure when user has large UID by using GNU format over USTAR format [\\#1015](https://github.com/kivy/python-for-android/pull/1015) ([pts-dorianpula](https://github.com/pts-dorianpula))\n- Remove the excessive ccache in the command [\\#1014](https://github.com/kivy/python-for-android/pull/1014) ([MaChengxin](https://github.com/MaChengxin))\n- Added support for Python 3.6 [\\#1011](https://github.com/kivy/python-for-android/pull/1011) ([inclement](https://github.com/inclement))\n- Made dist names different for py2/py3 testapps [\\#1010](https://github.com/kivy/python-for-android/pull/1010) ([inclement](https://github.com/inclement))\n- Documentation mistake corrected [\\#1005](https://github.com/kivy/python-for-android/pull/1005) ([yaki29](https://github.com/yaki29))\n- Fixed the blacklisting of sqlite3 under sdl2 [\\#1000](https://github.com/kivy/python-for-android/pull/1000) ([inclement](https://github.com/inclement))\n- documentation recipe.rst spelling change [\\#993](https://github.com/kivy/python-for-android/pull/993) ([yaki29](https://github.com/yaki29))\n- Move the unpackFiles step into an AsyncTask [\\#990](https://github.com/kivy/python-for-android/pull/990) ([kollivier](https://github.com/kollivier))\n- Add recipe for google protobuf cpp implementation [\\#987](https://github.com/kivy/python-for-android/pull/987) ([bakwc](https://github.com/bakwc))\n- Fix python2 for webview [\\#981](https://github.com/kivy/python-for-android/pull/981) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Updated Kivy recipe to work with Kivy master [\\#968](https://github.com/kivy/python-for-android/pull/968) ([inclement](https://github.com/inclement))\n- Switch --permission to accept \\>=1 parameters [\\#966](https://github.com/kivy/python-for-android/pull/966) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Add ``screenSize`` to android:configChanges in AndroidManifest.xml if API \\>= 13 [\\#956](https://github.com/kivy/python-for-android/pull/956) ([rnixx](https://github.com/rnixx))\n- Add ffpyplayer and dependencies recipes for new toolchain. [\\#954](https://github.com/kivy/python-for-android/pull/954) ([germn](https://github.com/germn))\n- Doc fixes [\\#950](https://github.com/kivy/python-for-android/pull/950) ([inclement](https://github.com/inclement))\n- Fixed release mode \\(--release\\) with sdl2 [\\#949](https://github.com/kivy/python-for-android/pull/949) ([inclement](https://github.com/inclement))\n- Made the sdl2 bootstrap strip unneeded symbols with python3 [\\#948](https://github.com/kivy/python-for-android/pull/948) ([inclement](https://github.com/inclement))\n- Compile pyo with sdl2 [\\#944](https://github.com/kivy/python-for-android/pull/944) ([inclement](https://github.com/inclement))\n- Update apis.rst [\\#941](https://github.com/kivy/python-for-android/pull/941) ([codytrey](https://github.com/codytrey))\n- Update recipes sqlite3 to 3.15.1 and apsw to 3.15.0-r1 both with FTS4 enabled [\\#936](https://github.com/kivy/python-for-android/pull/936) ([brussee](https://github.com/brussee))\n- Add remove\\_presplash [\\#930](https://github.com/kivy/python-for-android/pull/930) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Complete closure of the application with SDL2. [\\#923](https://github.com/kivy/python-for-android/pull/923) ([dkrukouski](https://github.com/dkrukouski))\n- Testapp improvements [\\#919](https://github.com/kivy/python-for-android/pull/919) ([inclement](https://github.com/inclement))\n- Make boost recipe compatible with Google NDK 13.0 [\\#916](https://github.com/kivy/python-for-android/pull/916) ([brussee](https://github.com/brussee))\n- Completing md5sum comparison and download [\\#915](https://github.com/kivy/python-for-android/pull/915) ([thopiekar](https://github.com/thopiekar))\n- Empty: Adding .gitkeep in bootstraps/empty/build [\\#912](https://github.com/kivy/python-for-android/pull/912) ([thopiekar](https://github.com/thopiekar))\n- fix layout listener related issues. Closes \\#890 [\\#911](https://github.com/kivy/python-for-android/pull/911) ([akshayaurora](https://github.com/akshayaurora))\n- Fixed app path for SDL2 services [\\#909](https://github.com/kivy/python-for-android/pull/909) ([inclement](https://github.com/inclement))\n- fixed recipe zope\\_interface [\\#908](https://github.com/kivy/python-for-android/pull/908) ([goffi-contrib](https://github.com/goffi-contrib))\n- Added \"presplash\"\\_color argument [\\#906](https://github.com/kivy/python-for-android/pull/906) ([mrhdias](https://github.com/mrhdias))\n- Added argument to set the loading screen background color [\\#905](https://github.com/kivy/python-for-android/pull/905) ([mrhdias](https://github.com/mrhdias))\n- Easy way to set the presplash background color [\\#904](https://github.com/kivy/python-for-android/pull/904) ([mrhdias](https://github.com/mrhdias))\n- Allow installation on Windows [\\#902](https://github.com/kivy/python-for-android/pull/902) ([ethanhs](https://github.com/ethanhs))\n- Made clean-recipe-build delete dists [\\#901](https://github.com/kivy/python-for-android/pull/901) ([inclement](https://github.com/inclement))\n- Improved log for missing python modules [\\#900](https://github.com/kivy/python-for-android/pull/900) ([inclement](https://github.com/inclement))\n- Added textinput scatter testapp [\\#899](https://github.com/kivy/python-for-android/pull/899) ([inclement](https://github.com/inclement))\n- Allow list of permissions for bdist [\\#898](https://github.com/kivy/python-for-android/pull/898) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Fix bdistapk for launcher [\\#896](https://github.com/kivy/python-for-android/pull/896) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n- Added symlink\\_java\\_src dev option [\\#894](https://github.com/kivy/python-for-android/pull/894) ([inclement](https://github.com/inclement))\n- Port launcher to SDL2 bootstrap [\\#891](https://github.com/kivy/python-for-android/pull/891) ([KeyWeeUsr](https://github.com/KeyWeeUsr))\n\n\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "In the interest of fostering an open and welcoming community, we as \ncontributors and maintainers need to ensure participation in our project and \nour sister projects is a harassment-free and positive experience for everyone. \nIt is vital that all interaction is conducted in a manner conveying respect, \nopen-mindedness and gratitude.\n\nPlease consult the [latest Kivy Code of Conduct](https://github.com/kivy/kivy/blob/master/CODE_OF_CONDUCT.md).\n\n"
  },
  {
    "path": "CONTACT.md",
    "content": "# Contacting the Kivy Team\n\nIf you are looking to contact the Kivy Team (who are responsible for managing\nthe python-for-android project), including looking for support, please see our\nlatest [Contact Us](https://github.com/kivy/kivy/blob/master/CONTACT.md) \ndocument.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution Guidelines\n\npython-for-android is part of the [Kivy](https://kivy.org) ecosystem - a large group of\nproducts used by many thousands of developers for free, but it\nis built entirely by the contributions of volunteers. We welcome (and rely on) \nusers who want to give back to the community by contributing to the project.\n\nContributions can come in many forms. See the latest \n[Contribution Guidelines](https://github.com/kivy/kivy/blob/master/CONTRIBUTING.md)\nfor how you can help us.\n\n.. warning::\n   The python-for-android process differs in small but important ways from the\n   Kivy framework's process. See below.\n\n## Development model\n\nUnlike the Kivy framework, python-for-android is developed using the following\nmodel:\n\n- The `master` branch always represents the latest stable release.\n- The `develop` branch is the most up to date with new contributions.\n- Releases happen periodically, and consist of merging the current `develop` \n  branch into `master`.\n\nThis means pull requests for python-for-android code and documentation\nsubmissions should be made to the `develop` branch, not the `master` branch.\n\nFor reference, this is based on a\n[Git flow](https://nvie.com/posts/a-successful-git-branching-model/) model,\nalthough we don't follow this religiously.\n\n## Versioning\n\npython-for-android releases currently use \n[calendar versioning](https://calver.org/). Release numbers are of the form\nYYYY.MM.DD.\n\nWe use calendar versioning because in practice, changes in\npython-for-android are often driven by updates or adjustments in the\nAndroid build tools. It's usually best for users to be working from\nthe latest release. We try to maintain backwards compatibility even\nwhile internals are changing.\n\n## History\n\nIn 2015, these tools were rewritten to provide a new, easier-to-use and\neasier-to-extend interface. If you'd like to browse the old toolchain, \nits status is \n[recorded for posterity](https://github.com/kivy/python-for-android/tree/old_toolchain).\n\nIn the last quarter of 2018, the Python recipes were changed. The\nnew recipe for Python3 (3.7.1) had a new build system which was\napplied to the ancient Python recipe, allowing us to bump the Python2\nversion number to 2.7.15. This change unified the build process for\nboth Python recipes, and probably solved various issues detected over the\nyears. These **unified Python recipes** require a **minimum target api level of 21**,\n*Android 5.0 - Lollipop*. If you need to build targeting an\napi level below 21, you should use an older version of python-for-android\n(<=0.7.1).\n\nOn March 2020, we dropped support for creating apps that use Python 2. The \nlatest python-for-android release that supported building Python 2 was version \n2019.10.6.\n\nOn August 2021, we added support for Android App Bundle (aab). As a\ncollateral benefit, we now support multi-arch apk.\n\n## Code Quality\n\n### Python Linting\n\nPython code is linted using flake8. Run it locally with:\n\n```bash\ntox -e pep8\n```\n\n### Java Linting\n\nJava source files in the bootstrap directories are linted using\n[Spotless](https://github.com/diffplug/spotless) with Google Java Format\n(AOSP style). The CI runs this check automatically.\n\n**Local execution** (requires Java 17+):\n\n```bash\n# Check for violations\nmake java-lint\n\n# Auto-fix violations\nmake java-lint-fix\n```\n\nThe Makefile uses the Gradle wrapper (`gradlew`), which automatically downloads\nthe correct Gradle version on first run. No manual Gradle installation is required.\n\n**Using Docker** (if you don't have Java 17):\n\n```bash\n# Check for violations\nmake docker/java-lint\n\n# Auto-fix violations\nmake docker/java-lint-fix\n```\n\nThe Docker approach builds the project's Docker image (which includes Java 17)\nand runs the linting inside the container.\n\n**What gets linted:**\n\n- All `.java` files in `pythonforandroid/bootstraps/*/build/src/main/java/`\n- Excludes third-party code (`org/kamranzafar/jtar/`)\n\n**Formatting rules applied:**\n\n- Google Java Format with AOSP style (Android-friendly indentation)\n- Removal of unused imports\n- Trailing whitespace trimming\n- Files end with newline\n\n## Creating a new release\n\n(These instructions are for core developers, not casual contributors.)\n\nNew releases follow these steps:\n\n- Create a new branch `release-YYYY.MM.DD` based on the `develop` branch.\n  - `git checkout -b release-YYYY.MM.DD develop`\n- Create a Github pull request to merge `release-YYYY.MM.DD` into `master`.\n- Complete all steps in the [release checklist](#Release_checklist),\n  and document this in the pull request (copy the checklist into the PR text)\n\nAt this point, wait for reviewer approval and conclude any discussion that\narises. To complete the release:\n\n- Merge the release branch to the `master` branch.\n- Also merge the release branch to the `develop` branch.\n- Tag the release commit in `master`, with tag `vYYYY.MM.DD`. Include a short\n  summary of the changes.\n- Release distributions and PyPI upload should be \n  [handled by the CI](https://github.com/kivy/python-for-android/blob/v2020.04.29/.travis.yml#L60-L70).\n- Add to the GitHub release page (see e.g. [this example](https://github.com/kivy/python-for-android/releases/tag/v2019.06.06):\n  - The python-for-android README summary\n  - A short list of major changes in this release, if any\n  - A changelog summarising merge commits since the last release\n  - The release sdist and wheel(s)\n\n## Release checklist\n\n  - [ ] Check that the builds are passing\n    - [ ] [GitHub Action](https://github.com/kivy/python-for-android/actions)\n  - [ ] Run the tests locally via `tox`: this performs some long-running tests that are skipped on github-actions.\n  - [ ] Build and run the [on_device_unit_tests](https://github.com/kivy/python-for-android/tree/master/testapps/on_device_unit_tests) app using buildozer. Check that they all pass.\n  - [ ] Build (or download from github actions) and run the following [testapps](https://github.com/kivy/python-for-android/tree/master/testapps/on_device_unit_tests) for arch `armeabi-v7a` and `arm64-v8a`:\n    - [ ] on_device_unit_tests\n      - [ ] `armeabi-v7a` (`cd testapps/on_device_unit_tests && PYTHONPATH=.:../../ python3 setup.py apk  --ndk-dir=<your-ndk-dir> --sdk-dir=<your-sdk-dir> --arch=armeabi-v7a --debug`)\n      - [ ] `arm64-v8a` (`cd testapps/on_device_unit_tests && PYTHONPATH=.:../../ python3 setup.py apk  --ndk-dir=<your-ndk-dir> --sdk-dir=<your-sdk-dir> --arch=arm64-v8a --debug`)\n  - [ ] Check that the version number is correct\n\n## How python-for-android uses `pip`\n\n*Last update: July 2019*\n\nThis section is meant to provide a quick summary how\npython-for-android uses pip and Python packages in\nits build process.\n**It is written for a Python\npackager's point of view, not for regular end users or\ncontributors,** to assist with making pip developers and\nother packaging experts aware of p4a's packaging needs.\n\nPlease note this section just attempts to neutrally list the\ncurrent mechanisms, so some of this isn't necessarily meant\nto stay but just how things work inside p4a in\nthis very moment.\n\n\n### Basic concepts\n\n*(This part repeats other parts of the docs, for the sake of\nmaking this a more independent read)*\n\np4a builds & packages a Python application for use on Android.\nIt does this by providing a Java wrapper, and for graphical applications\nan SDL2-based wrapper which can be used with the Kivy framework if\ndesired (or alternatively just plain PySDL2). Any such the Python application\nwill likely have further library dependencies to do its work.\n\np4a supports two types of package dependencies for a project:\n\n**Recipe:** Install a script in custom p4a format. Can either install\nC/C++ or other software that cannot be pulled in via pip, or software\nthat can be installed via pip but break on Android by default.\nThese are maintained primarily inside the p4a source tree by p4a\ncontributors and interested folks.\n\n**Python package:** any random pip python package can be directly\ninstalled if it doesn't need adjustments to work for Android.\n\np4a will map any dependency to an internal recipe if present, and\notherwise use pip to obtain it regularly from whatever external source.\n\n\n### Install process regarding packages\n\nThe install/build process of a p4a project, as triggered by the\n`p4a apk` command, roughly works as follows in regards to Python\npackages:\n\n1. The user has specified a project folder to install. This is either\n   just a folder with Python scripts and a `main.py`, or it may\n   also have a `pyproject.toml` for a more standardized install.\n\n2. Dependencies are collected: they can be either specified via\n   ``--requirements`` as a list of names or pip-style URLs, or p4a\n   can optionally scan them from a project folder via the\n   pep517 library (if there is a `pyproject.toml` or `setup.py`).\n\n3. The collected dependencies are mapped to p4a's recipes if any are\n   available for them, otherwise they're kept around as external\n   regular package references.\n\n4. All the dependencies mapped to recipes are built via p4a's internal\n   mechanisms to build these recipes. (This may or may not indirectly\n   use pip, depending on whether the recipe wraps a python package\n   or not and uses pip to install or not.)\n\n5. **If the user has specified to install the project in standardized\n   ways,** then the `setup.py`/whatever build system\n   of the project will be run. This happens with cross compilation set up\n   (`CC`/`CFLAGS`/... set to use the\n   proper toolchain) and a custom site-packages location.\n   The actual command is a simple `pip install .` in the project folder\n   with some extra options: e.g. all dependencies that were already\n   installed by recipes will be pinned with a `-c` constraints file\n   to make sure pip won't install them, and build isolation will be\n   disabled via ``--no-build-isolation`` so pip doesn't reinstall\n   recipe-packages on its own.\n\n   **If the user has not specified to use standardized build approaches**,\n   p4a will simply install all the remaining dependencies that weren't\n   mapped to recipes directly and just plain copy in the user project\n   without installing. Any `setup.py` or `pyproject.toml` of the user\n   project will then be ignored in this step.\n\n6. Google's gradle is invoked to package it all up into an `.apk`.\n\n\n### Overall process / package relevant notes for p4a\n\nHere are some common things worth knowing about python-for-android's\ndealing with python packages:\n\n- Packages will work fine without a recipe if:\n\n   * they would also build on Linux ARM,\n   * don't use any API not available in the NDK if they  use native code, and\n   * don't use any weird compiler flags the toolchain doesn't like if they use native code.\n   * works with cross compilation.\n\n- There is currently no easy way for a package to know it is being\n  cross-compiled (at least that we know of) other than examining the\n  `CC` compiler that was set, or that it is being cross-compiled for\n  Android specifically. If that breaks a package, it currently needs\n  to be worked around with a recipe.\n\n- If a package does **not** work, p4a developers will often create a\n  recipe instead of getting upstream to fix it because p4a simply\n  is too niche.\n\n- Most packages without native code will just work out of the box.\n  Many with native code tend not to, especially if complex, e.g. numpy.\n\n- Anything mapped to a p4a recipe cannot be just reinstalled by pip,\n  specifically also not inside build isolation as a dependency.\n  (It *may* work if the patches of the recipe are just relevant\n  to fix runtime issues.)\n  Therefore as of now, the best way to deal with this limitation seems\n  to be to keep build isolation always off.\n\n\n### Ideas for the future regarding packaging\n\n- We in overall prefer to use the recipe mechanism less if we can.\n  Overall, the recipes are just a collection of workarounds.\n  It may look quite hacky from the outside, since p4a\n  version pins recipe-wrapped packages usually to make the patches reliably\n  apply. This creates work for the recipes to be kept up-to-date, and\n  obviously this approach doesn't scale too well. However, it has ended\n  up as a quite practical interim solution until better ways are found.\n\n- Obviously, it would be nice if packages could know they are being\n  cross-compiled, and for Android specifically. We aren't currently aware\n  of any good mechanism for that.\n\n- If pip could actually run the recipes (instead of p4a wrapping pip and\n  doing so) then this might even allow build isolation to work - but\n  this might be too complex to get working. It might be more practical\n  to just gradually reduce the reliance on recipes instead and make\n  more packages work out of the box. This has been done e.g. with\n  improvements to the cross-compile environment being set up automatically,\n  and we're open for any ideas on how to improve this.\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Dockerfile with:\n#   - Android build environment\n#   - python-for-android dependencies\n#\n# Build with:\n#     docker build --tag=p4a --file Dockerfile .\n#\n# Run with:\n#     docker run -it --rm p4a /bin/sh -c '. venv/bin/activate && p4a apk --help'\n#\n# Or for interactive shell:\n#     docker run -it --rm p4a\n#\n# Note:\n#     Use 'docker run' without '--rm' flag for keeping the container and use\n#     'docker commit <container hash> <new image>' to extend the original image\n\n# If platform is not specified, by default the target platform of the build request is used.\n# This is not what we want, as Google doesn't provide a linux/arm64 compatible NDK.\n# See: https://docs.docker.com/engine/reference/builder/#from\nFROM --platform=linux/amd64 ubuntu:22.04\n\n# configure locale\nRUN apt -y update -qq > /dev/null \\\n    && DEBIAN_FRONTEND=noninteractive apt install -qq --yes --no-install-recommends \\\n    locales && \\\n    locale-gen en_US.UTF-8\nENV LANG=\"en_US.UTF-8\" \\\n    LANGUAGE=\"en_US.UTF-8\" \\\n    LC_ALL=\"en_US.UTF-8\"\n\nRUN apt -y update -qq > /dev/null \\\n    && DEBIAN_FRONTEND=noninteractive apt install -qq --yes --no-install-recommends \\\n\tca-certificates \\\n    curl \\\n    && apt -y autoremove \\\n    && apt -y clean \\\n    && rm -rf /var/lib/apt/lists/*\n\n# retry helper script, refs:\n# https://github.com/kivy/python-for-android/issues/1306\nENV RETRY=\"retry -t 3 --\"\nRUN curl https://raw.githubusercontent.com/kadwanev/retry/1.0.1/retry \\\n    --output /usr/local/bin/retry && chmod +x /usr/local/bin/retry\n\nENV USER=\"user\"\nENV HOME_DIR=\"/home/${USER}\"\nENV WORK_DIR=\"${HOME_DIR}/app\" \\\n    PATH=\"${HOME_DIR}/.local/bin:${PATH}\" \\\n    ANDROID_HOME=\"${HOME_DIR}/.android\" \\\n    JAVA_HOME=\"/usr/lib/jvm/java-17-openjdk-amd64\"\n\n\n# install system dependencies\nRUN ${RETRY} apt -y update -qq > /dev/null \\\n    && ${RETRY} DEBIAN_FRONTEND=noninteractive apt install -qq --yes --no-install-recommends \\\n        ant \\\n        autoconf \\\n        automake \\\n        autopoint \\\n        ccache \\\n        cmake \\\n        g++ \\\n        gcc \\\n        git \\\n        lbzip2 \\\n        libffi-dev \\\n        libltdl-dev \\\n        libtool \\\n        libssl-dev \\\n        make \\\n        openjdk-17-jdk \\\n        patch \\\n        pkg-config \\\n        python3 \\\n        python3-dev \\\n        python3-pip \\\n        python3-venv \\\n        sudo \\\n        unzip \\\n        wget \\\n        zip \\\n    && apt -y autoremove \\\n    && apt -y clean \\\n    && rm -rf /var/lib/apt/lists/*\n\n# prepare non root env\nRUN useradd --create-home --shell /bin/bash ${USER}\n\n# with sudo access and no password\nRUN usermod -append --groups sudo ${USER}\nRUN echo \"%sudo ALL=(ALL) NOPASSWD: ALL\" >> /etc/sudoers\n\nWORKDIR ${WORK_DIR}\nRUN mkdir ${ANDROID_HOME} && chown --recursive ${USER} ${HOME_DIR} ${ANDROID_HOME}\nUSER ${USER}\n\n# Download and install android's NDK/SDK\nCOPY --chown=user:user ci/makefiles/android.mk /tmp/android.mk\nRUN make --file /tmp/android.mk \\\n    && sudo rm /tmp/android.mk\n\n# install python-for-android from current branch\nCOPY --chown=user:user Makefile README.md setup.py pythonforandroid/__init__.py ${WORK_DIR}/\nRUN mkdir pythonforandroid \\\n    && mv __init__.py pythonforandroid/ \\\n    && make virtualenv \\\n    && rm -rf ~/.cache/\n\nCOPY --chown=user:user . ${WORK_DIR}\n"
  },
  {
    "path": "FAQ.md",
    "content": "# FAQ for python-for-android (p4a)\n\n## Introduction\n\npython-for-android (p4a) is a development tool that packages Python apps into\nbinaries that can run on Android devices.\n\n### Sibling Projects:\n\nThis tool was originally developed for apps produced with\nthe [Kivy framework](https://github.com/kivy/kivy), and is\nmanaged by the same team. However, it can be used to build other types of Python\napps for Android.\n\np4a is often used in conjunction\nwith [Buildozer](https://github.com/kivy/buildozer), which can download, install\nand keep up-to-date any necessary prerequisites (including p4a itself), for a\nnumber of target platforms, using a specification file to define the build.\n\n### Is it possible to have a kiosk app on Android?\n\nThomas Hansen wrote a detailed answer\non [the (old) kivy-users mailing list](https://groups.google.com/d/msg/kivy-users/QKoCekAR1c0/yV-85Y_iAwoJ)\n\nBasically, you need to root the device, remove the SystemUI package, add some\nlines to the xml configuration, and you're done.\n\n### Common Errors\n\nThe following are common problems and resolutions that users have reported.\n\n\n#### AttributeError: ‘Context’ object has no attribute ‘hostpython’\n\nThis is a known bug in some releases. To work around it, add your python\nrequirement explicitly, e.g. `--requirements=python3,kivy`. This also applies\nwhen using buildozer, in which case add python3 to your buildozer.spec\nrequirements.\n\n#### linkname too long\n\nThis can happen when you try to include a very long filename, which doesn’t\nnormally happen but can occur accidentally if the p4a directory contains a\n`.buildozer` directory that is not excluded from the build (e.g. if buildozer\nwas previously used). Removing this directory should fix the problem, and is\ndesirable anyway since you don’t want it in the APK.\n\n#### Requested API target XX is not available, install it with the SDK android tool\n\nThis means that your SDK is missing the required platform tools. You need to\ninstall the `platforms;android-XX` package in your SDK, using the android or\nsdkmanager tools (depending on SDK version).\n\nIf using buildozer this should be done automatically, but as a workaround you\ncan run these from `~/.buildozer/android/platform/android-sdk-XX/tools/android`\n\n#### SSLError(“Can’t connect to HTTPS URL because the SSL module is not available.”)\nYour hostpython3 was compiled without SSL support. You need to install the SSL\ndevelopment files before rebuilding the hostpython3 recipe. Remember to always\nclean the build before rebuilding (`p4a clean builds`, or with buildozer `buildozer\nandroid clean`).\n\nOn Ubuntu and derivatives:\n\n    apt install libssl-dev\n    p4a clean builds # or with: buildozer `buildozer android clean\n\nOn macOS:\n\n    brew install openssl\n    p4a clean builds # or with: buildozer `buildozer android clean\n\n\n#### AttributeError: 'AnsiCodes' object has no attribute 'LIGHTBLUE_EX'\n\nThis occurs if your version of `colorama` is too low, install version\n0.3.3 or higher.\n\nIf you install python-for-android with `pip` or via `setup.py`, this\ndependency should be taken care of automatically.\n\n#### AttributeError: 'Context' object has no attribute 'hostpython'\n\nThis is a known bug in some releases. To work around it, add your\npython requirement explicitly,\ne.g. :code:`--requirements=python3,kivy`. This also applies when using\nbuildozer, in which case add python3 to your buildozer.spec requirements.\n\n#### linkname too long\n\nThis can happen when you try to include a very long filename, which\ndoesn't normally happen but can occur accidentally if the p4a\ndirectory contains a .buildozer directory that is not excluded from\nthe build (e.g. if buildozer was previously used). Removing this\ndirectory should fix the problem, and is desirable anyway since you\ndon't want it in the APK.\n\n#### Requested API target 19 is not available, install it with the SDK android tool\n\nThis means that your SDK is missing the required platform tools. You\nneed to install the ``platforms;android-19`` package in your SDK,\nusing the ``android`` or ``sdkmanager`` tools (depending on SDK\nversion).\n\nIf using buildozer this should be done automatically, but as a\nworkaround you can run these from\n``~/.buildozer/android/platform/android-sdk-20/tools/android``.\n\n#### ModuleNotFoundError: No module named '_ctypes'\n\nYou do not have the libffi headers available to python-for-android, so you need to install them. On Ubuntu and derivatives these come from the `libffi-dev` package.\n\nAfter installing the headers, clean the build (`p4a clean builds`, or with buildozer delete the `.buildozer` directory within your app directory) and run python-for-android again.\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2010-2025 Kivy Team and other contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "\ninclude LICENSE README.md\ninclude *.toml\n\nrecursive-include doc *\nprune doc/build\n\nrecursive-include pythonforandroid *.py *.tmpl biglink liblink\nrecursive-include pythonforandroid/recipes *.py *.patch *.diff *.c *.pyx Setup *.h\n    \nrecursive-include pythonforandroid/bootstraps *.properties *.xml *.java *.tmpl *.txt *.png *.aidl *.py *.sh *.c *.h *.html *.patch\n    \nprune .git\n"
  },
  {
    "path": "Makefile",
    "content": "VIRTUAL_ENV ?= venv\nPIP=$(VIRTUAL_ENV)/bin/pip\nTOX=`which tox`\nACTIVATE=$(VIRTUAL_ENV)/bin/activate\nPYTHON=$(VIRTUAL_ENV)/bin/python\nDOCKER_IMAGE=kivy/python-for-android\nDOCKER_TAG=latest\nANDROID_SDK_HOME ?= $(HOME)/.android/android-sdk\nANDROID_NDK_HOME ?= $(HOME)/.android/android-ndk\nANDROID_NDK_HOME_LEGACY ?= $(HOME)/.android/android-ndk-legacy\nREBUILD_UPDATED_RECIPES_EXTRA_ARGS ?= ''\n\n\nall: virtualenv\n\n$(VIRTUAL_ENV):\n\tpython3 -m venv $(VIRTUAL_ENV)\n\t$(PIP) install Cython==0.29.36\n\t$(PIP) install -e .\n\nvirtualenv: $(VIRTUAL_ENV)\n\n# ignores test_pythonpackage.py since it runs for too long\ntest:\n\t$(TOX) -- tests/ --ignore tests/test_pythonpackage.py\n\n# Java linting using Spotless (requires Java 17+, uses Gradle wrapper)\njava-lint:\n\tcd pythonforandroid/bootstraps && ./common/build/gradlew spotlessCheck\n\njava-lint-fix:\n\tcd pythonforandroid/bootstraps && ./common/build/gradlew spotlessApply\n\n# Java linting via Docker (no local Java required)\ndocker/java-lint: docker/build\n\tdocker run --rm -v $(CURDIR):/home/user/app -w /home/user/app/pythonforandroid/bootstraps $(DOCKER_IMAGE) ./common/build/gradlew spotlessCheck\n\ndocker/java-lint-fix: docker/build\n\tdocker run --rm -v $(CURDIR):/home/user/app -w /home/user/app/pythonforandroid/bootstraps $(DOCKER_IMAGE) ./common/build/gradlew spotlessApply\n\n# Also install and configure rust\nrebuild_updated_recipes: virtualenv\n\t. $(ACTIVATE) && \\\n\tcurl https://sh.rustup.rs -sSf | sh -s -- -y && \\\n\t. \"$(HOME)/.cargo/env\" && \\\n\trustup target list && \\\n\tANDROID_SDK_HOME=$(ANDROID_SDK_HOME) ANDROID_NDK_HOME=$(ANDROID_NDK_HOME) \\\n\t$(PYTHON) ci/rebuild_updated_recipes.py $(REBUILD_UPDATED_RECIPES_EXTRA_ARGS)\n\n# make ARCH=armeabi-v7a,arm64-v8a ARTIFACT=apk BOOTSTRAP=sdl2 MODE=debug REQUIREMENTS=python testapps-generic\ntestapps-generic: virtualenv\n\t@if [ -z \"$(ARCH)\" ]; then echo \"ARCH is not set\"; exit 1; fi\n\t@if [ -z \"$(ARTIFACT)\" ]; then echo \"ARTIFACT is not set\"; exit 1; fi\n\t@if [ -z \"$(BOOTSTRAP)\" ]; then echo \"BOOTSTRAP is not set\"; exit 1; fi\n\t@if [ -z \"$(MODE)\" ]; then echo \"MODE is not set\"; exit 1; fi\n\t@if [ -z \"$(REQUIREMENTS)\" ]; then echo \"REQUIREMENTS is not set\"; exit 1; fi\n\t@ARCH_FLAGS=$$(echo \"$(ARCH)\" | tr ',' ' ' | sed 's/\\([^ ]\\+\\)/--arch=\\1/g'); \\\n\t. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \\\n    python setup.py $(ARTIFACT) \\\n    --sdk-dir $(ANDROID_SDK_HOME) \\\n    --ndk-dir $(ANDROID_NDK_HOME) \\\n    $$ARCH_FLAGS --bootstrap $(BOOTSTRAP) --$(MODE) --requirements $(REQUIREMENTS)\n\ntestapps-with-numpy: testapps-with-numpy/debug/apk testapps-with-numpy/release/aab\n\n# testapps-with-numpy/MODE/ARTIFACT\ntestapps-with-numpy/%: virtualenv\n\t$(eval MODE := $(word 2, $(subst /, ,$@)))\n\t$(eval ARTIFACT := $(word 3, $(subst /, ,$@)))\n\t@echo Building testapps-with-numpy for $(MODE) mode and $(ARTIFACT) artifact\n\t. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \\\n    python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \\\n    --requirements libffi,sdl2,pyjnius,kivy,python3,openssl,requests,urllib3,chardet,idna,sqlite3,setuptools,numpy \\\n    --arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86 \\\n\t--permission \"(name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)\" --permission \"(name=android.permission.INTERNET)\"\n\ntestapps-with-scipy: testapps-with-scipy/debug/apk testapps-with-scipy/release/aab\n\n# testapps-with-scipy/MODE/ARTIFACT\ntestapps-with-scipy/%: virtualenv\n\t$(eval MODE := $(word 2, $(subst /, ,$@)))\n\t$(eval ARTIFACT := $(word 3, $(subst /, ,$@)))\n\t@echo Building testapps-with-scipy for $(MODE) mode and $(ARTIFACT) artifact\n\t. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \\\n\texport LEGACY_NDK=$(ANDROID_NDK_HOME_LEGACY)  && \\\n    python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \\\n\t\t\t--requirements python3,scipy,kivy \\\n    --arch=armeabi-v7a --arch=arm64-v8a\n\ntestapps-webview: testapps-webview/debug/apk testapps-webview/release/aab\n\n# testapps-webview/MODE/ARTIFACT\ntestapps-webview/%: virtualenv\n\t$(eval MODE := $(word 2, $(subst /, ,$@)))\n\t$(eval ARTIFACT := $(word 3, $(subst /, ,$@)))\n\t@echo Building testapps-webview for $(MODE) mode and $(ARTIFACT) artifact\n\t. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \\\n    python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \\\n    --bootstrap webview \\\n    --requirements sqlite3,libffi,openssl,pyjnius,flask,python3,genericndkbuild \\\n    --arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86\n\ntestapps-service_library-aar: virtualenv \n\t. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \\\n    python setup.py aar --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \\\n    --bootstrap service_library \\\n    --requirements python3 \\\n    --arch=arm64-v8a --arch=x86 --release\n\ntestapps-qt: testapps-qt/debug/apk testapps-qt/release/aab\n\n# testapps-webview/MODE/ARTIFACT\ntestapps-qt/%: virtualenv\n\t$(eval MODE := $(word 2, $(subst /, ,$@)))\n\t$(eval ARTIFACT := $(word 3, $(subst /, ,$@)))\n\t@echo Building testapps-qt for $(MODE) mode and $(ARTIFACT) artifact\n\t. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \\\n    python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \\\n    --bootstrap qt \\\n    --requirements python3,shiboken6,pyside6 \\\n    --arch=arm64-v8a \\\n\t--local-recipes ./test_qt/recipes \\\n\t--qt-libs Core \\\n\t--load-local-libs plugins_platforms_qtforandroid \\\n\t--add-jar ./test_qt/jar/PySide6/jar/Qt6Android.jar \\\n\t--add-jar ./test_qt/jar/PySide6/jar/Qt6AndroidBindings.jar \\\n\t--permission android.permission.WRITE_EXTERNAL_STORAGE \\\n\t--permission android.permission.INTERNET\n\ntestapps/%: virtualenv\n\t$(eval $@_APP_ARCH := $(shell basename $*))\n\t. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \\\n    python setup.py apk --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \\\n    --arch=$($@_APP_ARCH)\n\nclean:\n\tfind . -type d -name \"__pycache__\" -exec rm -r {} +\n\tfind . -type d -name \"*.egg-info\" -exec rm -r {} +\n\nclean/all: clean\n\trm -rf $(VIRTUAL_ENV) .tox/\n\ndocker/pull:\n\tdocker pull $(DOCKER_IMAGE):latest || true\n\ndocker/build:\n\tdocker build --cache-from=$(DOCKER_IMAGE) --tag=$(DOCKER_IMAGE) .\n\ndocker/login:\n\t@echo $$DOCKERHUB_TOKEN | docker login --username $(DOCKERHUB_USERNAME) --password-stdin\n\ndocker/tag:\n\tdocker tag $(DOCKER_IMAGE):latest $(DOCKER_IMAGE):$(DOCKER_TAG)\n\ndocker/push:\n\tdocker push $(DOCKER_IMAGE):$(DOCKER_TAG)\n\ndocker/run/test: docker/build\n\tdocker run --rm --env-file=.env $(DOCKER_IMAGE) 'make test'\n\ndocker/run/command: docker/build\n\tdocker run --rm --env-file=.env $(DOCKER_IMAGE) /bin/sh -c \"$(COMMAND)\"\n\ndocker/run/make/rebuild_updated_recipes: docker/build\n\tdocker run --name p4a-latest -e REBUILD_UPDATED_RECIPES_EXTRA_ARGS --env-file=.env $(DOCKER_IMAGE) make rebuild_updated_recipes\n\ndocker/run/make/%: docker/build\n\tdocker run --rm --env-file=.env $(DOCKER_IMAGE) make $*\n\ndocker/run/shell: docker/build\n\tdocker run --rm --env-file=.env -it $(DOCKER_IMAGE)\n"
  },
  {
    "path": "README.md",
    "content": "# python-for-android\n\npython-for-android (p4a) is a development tool that packages Python apps into\nbinaries that can run on Android devices.\n\nIt can generate: \n\n* [Android Package](https://en.wikipedia.org/wiki/Apk_(file_format)) (APK)\n  files, ready to install locally on a device, especially for testing. This format\n  is used by many [app stores](https://en.wikipedia.org/wiki/List_of_Android_app_stores)\n  but not [Google Play Store](https://play.google.com/store/). \n* [Android App Bundle](https://developer.android.com/guide/app-bundle/faq) \n  (AAB) files which can be shared on [Google Play Store](https://play.google.com/store/).\n* [Android Archive](https://developer.android.com/studio/projects/android-library)\n  (AAR) files which can be used as a reusable bundle of resources for other \n  projects.\n \nIt supports multiple CPU architectures.\n\nIt supports apps developed with [Kivy framework](http://kivy.org), but was\nbuilt to be flexible about the backend libraries (through \"bootstraps\"), and \nalso supports [PySDL2](https://pypi.org/project/PySDL2/), and a\n[WebView](https://developer.android.com/reference/android/webkit/WebView) with\na Python web server.\n\nIt automatically supports dependencies on most pure Python packages. For other\npackages, including those that depend on C code, a special \"recipe\" must be \nwritten to support cross-compiling. python-for-android comes with recipes for\nmany of the most popular libraries (e.g. numpy and sqlalchemy) built in.\n\npython-for-android works by cross-compiling the Python interpreter and its\ndependencies for Android devices, and bundling it with the app's python code\nand dependencies. The Python code is then interpreted on the Android device.\n\nIt is recommended that python-for-android be used via \n[Buildozer](https://buildozer.readthedocs.io/), which ensures the correct\ndependencies are pre-installed, and centralizes the configuration. However, \npython-for-android is not limited to being used with Buildozer.\n\n[![Backers on Open Collective](https://opencollective.com/kivy/backers/badge.svg)](#backers)\n[![Sponsors on Open Collective](https://opencollective.com/kivy/sponsors/badge.svg)](#sponsors)\n[![GitHub contributors](https://img.shields.io/github/contributors-anon/kivy/python-for-android)](https://github.com/kivy/python-for-android/graphs/contributors)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)\n\n![PyPI - Version](https://img.shields.io/pypi/v/python-for-android)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/python-for-android)\n\n[![Unit tests & build apps](https://github.com/kivy/python-for-android/workflows/Unit%20tests%20&%20build%20apps/badge.svg?branch=develop)](https://github.com/kivy/python-for-android/actions?query=workflow%3A%22Unit+tests+%26+build+apps%22)\n[![Coverage Status](https://coveralls.io/repos/github/kivy/python-for-android/badge.svg?branch=develop&kill_cache=1)](https://coveralls.io/github/kivy/python-for-android?branch=develop)\n[![Docker](https://github.com/kivy/python-for-android/actions/workflows/docker.yml/badge.svg)](https://github.com/kivy/python-for-android/actions/workflows/docker.yml)\n\n## Documentation\n\nMore information is available in the \n[online documentation](https://python-for-android.readthedocs.io) including a\n[quickstart guide](https://python-for-android.readthedocs.io/en/latest/quickstart.html).\n\npython-for-android is managed by the [Kivy team](https://kivy.org).\n\n## Support\n\nAre you having trouble using python-for-android or any of its related projects\nin the Kivy ecosystem?\nIs there an error you don’t understand? Are you trying to figure out how to use \nit? We have volunteers who can help!\n\nThe best channels to contact us for support are listed in the latest \n[Contact Us](https://github.com/kivy/pyton-for-android/blob/master/CONTACT.md)\ndocument.\n\n## Code of Conduct\n\nIn the interest of fostering an open and welcoming community, we as \ncontributors and maintainers need to ensure participation in our project and \nour sister projects is a harassment-free and positive experience for everyone. \nIt is vital that all interaction is conducted in a manner conveying respect, \nopen-mindedness and gratitude.\n\nPlease consult the [latest Code of Conduct](https://github.com/kivy/python-for-android/blob/master/CODE_OF_CONDUCT.md).\n\n## Contributors\n\nThis project exists thanks to \n[all the people who contribute](https://github.com/kivy/python-for-android/graphs/contributors).\n[[Become a contributor](CONTRIBUTING.md)].\n\n<img src=\"https://contrib.nn.ci/api?repo=kivy/python-for-android&pages=5&no_bot=true&radius=22&cols=18\">\n\n## Backers\n\nThank you to [all of our backers](https://opencollective.com/kivy)! \n🙏 [[Become a backer](https://opencollective.com/kivy#backer)]\n\n<img src=\"https://opencollective.com/kivy/backers.svg?width=890&avatarHeight=44&button=false\">\n\n## Sponsors\n\nSpecial thanks to \n[all of our sponsors, past and present](https://opencollective.com/kivy).\nSupport this project by \n[[becoming a sponsor](https://opencollective.com/kivy#sponsor)].\n\nHere are our top current sponsors. Please click through to see their websites,\nand support them as they support us. \n\n<!--- See https://github.com/orgs/kivy/discussions/15 for explanation of this code. -->\n<a href=\"https://opencollective.com/kivy/sponsor/0/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/1/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/2/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/3/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/3/avatar.svg\"></a>\n\n<a href=\"https://opencollective.com/kivy/sponsor/4/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/5/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/6/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/7/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/7/avatar.svg\"></a>\n\n<a href=\"https://opencollective.com/kivy/sponsor/8/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/9/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/9/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/10/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/10/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/11/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/11/avatar.svg\"></a>\n\n<a href=\"https://opencollective.com/kivy/sponsor/12/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/12/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/13/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/13/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/14/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/14/avatar.svg\"></a>\n<a href=\"https://opencollective.com/kivy/sponsor/15/website\" target=\"_blank\"><img src=\"https://opencollective.com/kivy/sponsor/15/avatar.svg\"></a>\n"
  },
  {
    "path": "ci/__init__.py",
    "content": ""
  },
  {
    "path": "ci/constants.py",
    "content": "from enum import Enum\n\n\nclass TargetPython(Enum):\n    python3 = 2\n\n\n# recipes that currently break the build\n# a recipe could be broken for a target Python and not for the other,\n# hence we're maintaining one list per Python target\nBROKEN_RECIPES_PYTHON3 = set([\n    'brokenrecipe',\n    # enum34 is not compatible with Python 3.6 standard library\n    # https://stackoverflow.com/a/45716067/185510\n    'enum34',\n    # build_dir = glob.glob('build/lib.*')[0]\n    # IndexError: list index out of range\n    'secp256k1',\n    # requires `libpq-dev` system dependency e.g. for `pg_config` binary\n    'psycopg2',\n    # most likely some setup in the Docker container, because it works in host\n    'pyjnius', 'pyopenal',\n    # SyntaxError: invalid syntax (Python2)\n    'storm',\n    # mpmath package with a version >= 0.19 required\n    'sympy',\n    'vlc',\n    # GitHub CI runs out of storage while building it\n    'scipy',\n    'fortran',\n    # Outdated and there's a chance that is now useless.\n    'zope_interface',\n    # Requires zope_interface, which is broken.\n    'twisted',\n    # genericndkbuild is incompatible with sdl2 (which is build by default when targeting sdl2 bootstrap)\n    'genericndkbuild',\n    # boost gives errors (requires numpy? syntax error in .jam?)\n    'boost',\n    # libtorrent gives errors (requires boost. Also, see issue #2809, to start with)\n    'libtorrent',\n    # pybind11 build fails on macos\n    'pybind11',\n    # pygame (likely need to be updated) is broken with newer SDL2 versions\n    'pygame',\n])\n\nBROKEN_RECIPES = {\n    TargetPython.python3: BROKEN_RECIPES_PYTHON3,\n}\n# recipes that were already built will be skipped\nCORE_RECIPES = set([\n    'pyjnius', 'kivy', 'openssl', 'requests', 'sqlite3', 'setuptools',\n    'numpy', 'android', 'hostpython3', 'python3',\n])\n"
  },
  {
    "path": "ci/makefiles/android.mk",
    "content": "# Downloads and installs the Android SDK depending on supplied platform: darwin or linux\n\n# Those android NDK/SDK variables can be override when running the file\nANDROID_NDK_VERSION ?= 28c\nANDROID_NDK_VERSION_LEGACY ?= 21e\nANDROID_SDK_TOOLS_VERSION ?= 6514223\nANDROID_SDK_BUILD_TOOLS_VERSION ?= 29.0.3\nANDROID_HOME ?= $(HOME)/.android\nANDROID_API_LEVEL ?= 36\n\n# per OS dictionary-like\nUNAME_S := $(shell uname -s)\nTARGET_OS_Linux = linux\nTARGET_OS_ALIAS_Linux = $(TARGET_OS_Linux)\nTARGET_OS_Darwin = darwin\nTARGET_OS_ALIAS_Darwin = mac\nTARGET_OS = $(TARGET_OS_$(UNAME_S))\nTARGET_OS_ALIAS = $(TARGET_OS_ALIAS_$(UNAME_S))\n\nANDROID_SDK_HOME=$(ANDROID_HOME)/android-sdk\nANDROID_SDK_TOOLS_ARCHIVE=commandlinetools-$(TARGET_OS_ALIAS)-$(ANDROID_SDK_TOOLS_VERSION)_latest.zip\nANDROID_SDK_TOOLS_DL_URL=https://dl.google.com/android/repository/$(ANDROID_SDK_TOOLS_ARCHIVE)\n\nANDROID_NDK_HOME=$(ANDROID_HOME)/android-ndk\nANDROID_NDK_FOLDER=$(ANDROID_HOME)/android-ndk-r$(ANDROID_NDK_VERSION)\nANDROID_NDK_ARCHIVE=android-ndk-r$(ANDROID_NDK_VERSION)-$(TARGET_OS).zip\n\nANDROID_NDK_HOME_LEGACY=$(ANDROID_HOME)/android-ndk-legacy\nANDROID_NDK_FOLDER_LEGACY=$(ANDROID_HOME)/android-ndk-r$(ANDROID_NDK_VERSION_LEGACY)\nANDROID_NDK_ARCHIVE_LEGACY=android-ndk-r$(ANDROID_NDK_VERSION_LEGACY)-$(TARGET_OS)-x86_64.zip\n\nANDROID_NDK_GFORTRAN_ARCHIVE_ARM64=gcc-arm64-linux-x86_64.tar.bz2\nANDROID_NDK_GFORTRAN_ARCHIVE_ARM=gcc-arm-linux-x86_64.tar.bz2\n\n\nANDROID_NDK_DL_URL=https://dl.google.com/android/repository/$(ANDROID_NDK_ARCHIVE)\nANDROID_NDK_DL_URL_LEGACY=https://dl.google.com/android/repository/$(ANDROID_NDK_ARCHIVE_LEGACY)\n\n$(info Target install OS is          : $(target_os))\n$(info Android SDK home is           : $(ANDROID_SDK_HOME))\n$(info Android NDK home is           : $(ANDROID_NDK_HOME))\n$(info Android NDK Legacy  home is   : $(ANDROID_NDK_HOME_LEGACY))\n$(info Android SDK download url is   : $(ANDROID_SDK_TOOLS_DL_URL))\n$(info Android NDK download url is   : $(ANDROID_NDK_DL_URL))\n$(info Android API level is          : $(ANDROID_API_LEVEL))\n$(info Android NDK version is        : $(ANDROID_NDK_VERSION))\n$(info Android NDK Legacy version is : $(ANDROID_NDK_VERSION_LEGACY))\n$(info JAVA_HOME is                  : $(JAVA_HOME))\n\nall: install_sdk install_ndk\n\ninstall_sdk: download_android_sdk extract_android_sdk update_android_sdk\n\ninstall_ndk: download_android_ndk download_android_ndk_legacy download_android_ndk_gfortran extract_android_ndk extract_android_ndk_legacy extract_android_ndk_gfortran\n\ndownload_android_sdk:\n\tcurl --location --progress-bar --continue-at - \\\n\t$(ANDROID_SDK_TOOLS_DL_URL) --output $(ANDROID_SDK_TOOLS_ARCHIVE)\n\ndownload_android_ndk:\n\tcurl --location --progress-bar --continue-at - \\\n\t$(ANDROID_NDK_DL_URL) --output $(ANDROID_NDK_ARCHIVE)\n\ndownload_android_ndk_legacy:\n\tcurl --location --progress-bar --continue-at - \\\n\t$(ANDROID_NDK_DL_URL_LEGACY) --output $(ANDROID_NDK_ARCHIVE_LEGACY)\n\ndownload_android_ndk_gfortran:\n\tcurl --location --progress-bar --continue-at - \\\n\thttps://github.com/mzakharo/android-gfortran/releases/download/r$(ANDROID_NDK_VERSION_LEGACY)/$(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM64) --output $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM64)\n\tcurl --location --progress-bar --continue-at - \\\n\thttps://github.com/mzakharo/android-gfortran/releases/download/r$(ANDROID_NDK_VERSION_LEGACY)/$(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM)  --output $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM)\n\n\n# Extract android SDK and remove the compressed file\nextract_android_sdk:\n\tmkdir -p $(ANDROID_SDK_HOME) \\\n\t&& unzip -q $(ANDROID_SDK_TOOLS_ARCHIVE) -d $(ANDROID_SDK_HOME) \\\n\t&& rm -f $(ANDROID_SDK_TOOLS_ARCHIVE)\n\n\n# Extract android NDK and remove the compressed file\nextract_android_ndk:\n\tmkdir -p $(ANDROID_NDK_FOLDER) \\\n\t&& unzip -q $(ANDROID_NDK_ARCHIVE) -d $(ANDROID_HOME) \\\n\t&& mv $(ANDROID_NDK_FOLDER) $(ANDROID_NDK_HOME) \\\n\t&& rm -f $(ANDROID_NDK_ARCHIVE)\n\nextract_android_ndk_legacy:\n\tmkdir -p $(ANDROID_NDK_FOLDER_LEGACY) \\\n\t&& unzip -q $(ANDROID_NDK_ARCHIVE_LEGACY) -d $(ANDROID_HOME) \\\n\t&& mv $(ANDROID_NDK_FOLDER_LEGACY) $(ANDROID_NDK_HOME_LEGACY) \\\n\t&& rm -f $(ANDROID_NDK_ARCHIVE_LEGACY)\n\nextract_android_ndk_gfortran:\n\trm -rf $(ANDROID_NDK_HOME_LEGACY)/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/ \\\n\t&& mkdir  $(ANDROID_NDK_HOME_LEGACY)/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/ \\\n\t&& tar -xf $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM64) -C $(ANDROID_NDK_HOME_LEGACY)/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/ --strip-components 1 \\\n\t&& rm -f $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM64) \\\n\t&& rm -rf $(ANDROID_NDK_HOME_LEGACY)/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/ \\\n\t&& mkdir  $(ANDROID_NDK_HOME_LEGACY)/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/ \\\n\t&& tar -xf $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM) -C $(ANDROID_NDK_HOME_LEGACY)/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/ --strip-components 1 \\\n\t&& rm -f $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM)\n\n\n\n# updates Android SDK, install Android API, Build Tools and accept licenses\nupdate_android_sdk:\n\ttouch $(ANDROID_HOME)/repositories.cfg\n\tyes | $(ANDROID_SDK_HOME)/tools/bin/sdkmanager --sdk_root=$(ANDROID_SDK_HOME) --licenses > /dev/null\n\t$(ANDROID_SDK_HOME)/tools/bin/sdkmanager --sdk_root=$(ANDROID_SDK_HOME) \"build-tools;$(ANDROID_SDK_BUILD_TOOLS_VERSION)\" > /dev/null\n\t$(ANDROID_SDK_HOME)/tools/bin/sdkmanager --sdk_root=$(ANDROID_SDK_HOME) \"platforms;android-$(ANDROID_API_LEVEL)\" > /dev/null\n\t# Set avdmanager permissions (executable)\n\tchmod +x $(ANDROID_SDK_HOME)/tools/bin/avdmanager\n"
  },
  {
    "path": "ci/makefiles/osx.mk",
    "content": "# installs Android's SDK/NDK, cython\n\n# The following variable/s can be override when running the file\nANDROID_HOME ?= $(HOME)/.android\n\nall: upgrade_cython install_android_ndk_sdk\n\nupgrade_cython:\n\tpip3 install --upgrade Cython\n\ninstall_android_ndk_sdk:\n\tmkdir -p $(ANDROID_HOME)\n\tmake -f ci/makefiles/android.mk\n"
  },
  {
    "path": "ci/rebuild_updated_recipes.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nContinuous Integration helper script.\nAutomatically detects recipes modified in a changeset (compares with master)\nand recompiles them.\n\nTo run locally, set the environment variables before running:\n```\nANDROID_SDK_HOME=~/.buildozer/android/platform/android-sdk-20\nANDROID_NDK_HOME=~/.buildozer/android/platform/android-ndk-r9c\n./ci/rebuild_update_recipes.py\n```\n\nCurrent limitations:\n- will fail on conflicting requirements\n  e.g. https://travis-ci.org/AndreMiras/python-for-android/builds/438840800\n  the list of recipes was huge and result was:\n  [ERROR]:   Didn't find any valid dependency graphs.\n  [ERROR]:   This means that some of your requirements pull in conflicting dependencies.\n\"\"\"\nimport sh\nimport os\nimport sys\nimport argparse\nfrom pythonforandroid.build import Context\nfrom pythonforandroid import logger\nfrom pythonforandroid.toolchain import current_directory\nfrom pythonforandroid.recipe import Recipe\nfrom ci.constants import TargetPython, CORE_RECIPES, BROKEN_RECIPES\n\n\ndef modified_recipes(branch='origin/develop'):\n    \"\"\"\n    Returns a set of modified recipes between the current branch and the one\n    in param.\n    \"\"\"\n    # using the contrib version on purpose rather than sh.git, since it comes\n    # with a bunch of fixes, e.g. disabled TTY, see:\n    # https://stackoverflow.com/a/20128598/185510\n    git_diff = sh.contrib.git.diff('--name-only', branch).split(\"\\n\")\n    recipes = set()\n    for file_path in git_diff:\n        if 'pythonforandroid/recipes/' in file_path:\n            recipe = file_path.split('/')[2]\n            recipes.add(recipe)\n    return recipes\n\n\ndef build(target_python, requirements, archs):\n    \"\"\"\n    Builds an APK given a target Python and a set of requirements.\n    \"\"\"\n    if not requirements:\n        return\n    android_sdk_home = os.environ['ANDROID_SDK_HOME']\n    android_ndk_home = os.environ['ANDROID_NDK_HOME']\n    requirements.add(target_python.name)\n    requirements_str = ','.join(requirements)\n    logger.info('requirements: {}'.format(requirements_str))\n\n    # Detect bootstrap based on requirements\n    # SDL3 recipes conflict with SDL2, so we need the sdl3 bootstrap\n    # when any SDL3-related recipe (sdl3, sdl3_image, sdl3_mixer, sdl3_ttf) is present\n    bootstrap = None\n    if any(r.startswith('sdl3') for r in requirements):\n        bootstrap = 'sdl3'\n        logger.info('Detected sdl3 recipe in requirements, using sdl3 bootstrap')\n\n    build_command = [\n        'setup.py', 'apk',\n        '--sdk-dir', android_sdk_home,\n        '--ndk-dir', android_ndk_home,\n        '--requirements', requirements_str\n    ]\n\n    if bootstrap:\n        build_command.extend(['--bootstrap', bootstrap])\n\n    build_command.extend([f\"--arch={arch}\" for arch in archs])\n\n    build_command_str = \" \".join(build_command)\n    logger.info(f\"Build command: {build_command_str}\")\n\n    with current_directory('testapps/on_device_unit_tests/'):\n        # iterates to stream the output\n        for line in sh.python(*build_command, _err_to_out=True, _iter=True):\n            print(line)\n\n\ndef main():\n    parser = argparse.ArgumentParser(\"rebuild_updated_recipes\")\n    parser.add_argument(\n        \"--arch\",\n        help=\"The archs to build for during tests\",\n        action=\"append\",\n        default=[],\n    )\n    args, unknown = parser.parse_known_args(sys.argv[1:])\n\n    logger.info(f\"Building updated recipes for the following archs: {args.arch}\")\n\n    target_python = TargetPython.python3\n    recipes = modified_recipes()\n    logger.info('recipes modified: {}'.format(recipes))\n    recipes -= CORE_RECIPES\n    logger.info('recipes to build: {}'.format(recipes))\n    context = Context()\n\n    # removing the deleted recipes for the given target (if any)\n    for recipe_name in recipes.copy():\n        try:\n            Recipe.get_recipe(recipe_name, context)\n        except ValueError:\n            # recipe doesn't exist, so probably we remove it\n            recipes.remove(recipe_name)\n            logger.warning(\n                'removed {} from recipes because deleted'.format(recipe_name)\n            )\n\n    # removing the known broken recipe for the given target\n    broken_recipes = BROKEN_RECIPES[target_python]\n    recipes -= broken_recipes\n    logger.info('recipes to build (no broken): {}'.format(recipes))\n    build(target_python, recipes, args.arch)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "ci/run_emulator_tests.sh",
    "content": "#!/bin/bash\nset -euxo pipefail\n\n# Find the built APK file\nAPK_FILE=$(find dist -name \"*.apk\" -print -quit)\n\nif [ -z \"$APK_FILE\" ]; then\n    echo \"Error: No APK file found in dist/\"\n    exit 1\nfi\n\necho \"Installing $APK_FILE...\"\nadb install \"$APK_FILE\"\n\n# Extract package and activity names\nAAPT2_PATH=$(find ${ANDROID_HOME}/build-tools/ -name aapt2 | sort -r | head -n 1)\nAPP_PACKAGE=$(${AAPT2_PATH} dump badging \"${APK_FILE}\" | awk -F\"'\" '/package: name=/{print $2}')\nAPP_ACTIVITY=$(${AAPT2_PATH} dump badging \"${APK_FILE}\" | awk -F\"'\" '/launchable-activity/ {print $2}')\n\necho \"Launching $APP_PACKAGE/$APP_ACTIVITY...\"\nadb shell am start -n \"$APP_PACKAGE/$APP_ACTIVITY\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER\n\n# Poll for test completion with timeout\nMAX_WAIT=300\nPOLL_INTERVAL=5\nelapsed=0\n\necho \"Waiting for tests to complete (max ${MAX_WAIT}s)...\"\n\nwhile [ $elapsed -lt $MAX_WAIT ]; do\n    # Dump current logs\n    adb logcat -d -s python:I *:S > app_logs.txt\n\n    # Check if all success patterns are present\n    if grep --extended-regexp --quiet \"I python[ ]+: Initialized python\" app_logs.txt && \\\n       grep --extended-regexp --quiet \"I python[ ]+: Ran 14 tests in\" app_logs.txt && \\\n       grep --extended-regexp --quiet \"I python[ ]+: OK\" app_logs.txt; then\n        echo \"✅ SUCCESS: App launched and all unit tests passed in ${elapsed}s.\"\n        exit 0\n    fi\n\n    # Check for early failure indicators\n    if grep --extended-regexp --quiet \"I python[ ]+: FAILED\" app_logs.txt; then\n        echo \"❌ FAILURE: Tests failed after ${elapsed}s.\"\n        echo \"--- Full Logs ---\"\n        cat app_logs.txt\n        echo \"-----------------\"\n        exit 1\n    fi\n\n    sleep $POLL_INTERVAL\n    elapsed=$((elapsed + POLL_INTERVAL))\n    echo \"Still waiting... (${elapsed}s elapsed)\"\ndone\n\necho \"❌ TIMEOUT: Tests did not complete within ${MAX_WAIT}s.\"\necho \"--- Full Logs ---\"\ncat app_logs.txt\necho \"-----------------\"\nexit 1\n"
  },
  {
    "path": "distribute.sh",
    "content": "#!/usr/bin/env sh\n\n# This file is just a shim to report an error messaage if some tool\n# tries to run the old python-for-android.\n\n# An alternative would be to implement argument handling and pass\n# things to the new toolchain so that it works the same as before, but\n# that would be harder.\n\n\ncat <<EOF\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\nPYTHON-FOR-ANDROID ERROR! SEE BELOW FOR SOLUTION:\n\nYou are trying to run an old version of python-for-android via\ndistribute.sh. However, python-for-android has been rewritten and no\nlonger supports the distribute.sh interface.\n\nIf you are using buildozer, you should:\n- upgrade buildozer to the latest version (at least 0.30)\n- delete the .buildozer folder in your app directory (the same directory that has your buildozer.spec)\n- run buildozer again as normal\n\nIf you are not using buildozer, see\nhttps://github.com/kivy/python-for-android/blob/master/README.md for\ninstructions on using the new python-for-android\ntoolchain. Alternatively, you can get the old toolchain from the\n'old_toolchain' branch at\nhttps://github.com/kivy/python-for-android/tree/old_toolchain .\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\nEOF\n\nexit 1\n"
  },
  {
    "path": "doc/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = build\n\n# User-friendly check for sphinx-build2\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  applehelp  to make an Apple Help Book\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/python-for-android.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/python-for-android.qhc\"\n\napplehelp:\n\t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp\n\t@echo\n\t@echo \"Build finished. The help book is in $(BUILDDIR)/applehelp.\"\n\t@echo \"N.B. You won't be able to view it unless you put it in\" \\\n\t      \"~/Library/Documentation/Help or install it in your application\" \\\n\t      \"bundle.\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/python-for-android\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-for-android\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "doc/make.bat",
    "content": "@ECHO OFF\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build2\r\n)\r\nset BUILDDIR=build\r\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source\r\nset I18NSPHINXOPTS=%SPHINXOPTS% source\r\nif NOT \"%PAPER%\" == \"\" (\r\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\r\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\r\n)\r\n\r\nif \"%1\" == \"\" goto help\r\n\r\nif \"%1\" == \"help\" (\r\n\t:help\r\n\techo.Please use `make ^<target^>` where ^<target^> is one of\r\n\techo.  html       to make standalone HTML files\r\n\techo.  dirhtml    to make HTML files named index.html in directories\r\n\techo.  singlehtml to make a single large HTML file\r\n\techo.  pickle     to make pickle files\r\n\techo.  json       to make JSON files\r\n\techo.  htmlhelp   to make HTML files and a HTML help project\r\n\techo.  qthelp     to make HTML files and a qthelp project\r\n\techo.  devhelp    to make HTML files and a Devhelp project\r\n\techo.  epub       to make an epub\r\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\r\n\techo.  text       to make text files\r\n\techo.  man        to make manual pages\r\n\techo.  texinfo    to make Texinfo files\r\n\techo.  gettext    to make PO message catalogs\r\n\techo.  changes    to make an overview over all changed/added/deprecated items\r\n\techo.  xml        to make Docutils-native XML files\r\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\r\n\techo.  linkcheck  to check all external links for integrity\r\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\r\n\techo.  coverage   to run coverage check of the documentation if enabled\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"clean\" (\r\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\r\n\tdel /q /s %BUILDDIR%\\*\r\n\tgoto end\r\n)\r\n\r\n\r\nREM Check if sphinx-build2 is available and fallback to Python version if any\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 goto sphinx_python\r\ngoto sphinx_ok\r\n\r\n:sphinx_python\r\n\r\nset SPHINXBUILD=python -m sphinx.__init__\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 (\r\n\techo.\r\n\techo.The 'sphinx-build2' command was not found. Make sure you have Sphinx\r\n\techo.installed, then set the SPHINXBUILD environment variable to point\r\n\techo.to the full path of the 'sphinx-build2' executable. Alternatively you\r\n\techo.may add the Sphinx directory to PATH.\r\n\techo.\r\n\techo.If you don't have Sphinx installed, grab it from\r\n\techo.http://sphinx-doc.org/\r\n\texit /b 1\r\n)\r\n\r\n:sphinx_ok\r\n\r\n\r\nif \"%1\" == \"html\" (\r\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"dirhtml\" (\r\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"singlehtml\" (\r\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pickle\" (\r\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the pickle files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"json\" (\r\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the JSON files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"htmlhelp\" (\r\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run HTML Help Workshop with the ^\r\n.hhp project file in %BUILDDIR%/htmlhelp.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"qthelp\" (\r\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\r\n.qhcp project file in %BUILDDIR%/qthelp, like this:\r\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\python-for-android.qhcp\r\n\techo.To view the help file:\r\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\python-for-android.ghc\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"devhelp\" (\r\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"epub\" (\r\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latex\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdf\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf\r\n\tcd %~dp0\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdfja\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf-ja\r\n\tcd %~dp0\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"text\" (\r\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The text files are in %BUILDDIR%/text.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"man\" (\r\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"texinfo\" (\r\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"gettext\" (\r\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"changes\" (\r\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.The overview file is in %BUILDDIR%/changes.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"linkcheck\" (\r\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Link check complete; look for any errors in the above output ^\r\nor in %BUILDDIR%/linkcheck/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"doctest\" (\r\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of doctests in the sources finished, look at the ^\r\nresults in %BUILDDIR%/doctest/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"coverage\" (\r\n\t%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of coverage in the sources finished, look at the ^\r\nresults in %BUILDDIR%/coverage/python.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"xml\" (\r\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pseudoxml\" (\r\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\r\n\tgoto end\r\n)\r\n\r\n:end\r\n"
  },
  {
    "path": "doc/requirements.txt",
    "content": "Sphinx~=7.2.6\nfuro==2023.9.10"
  },
  {
    "path": "doc/source/_static/.empty",
    "content": ""
  },
  {
    "path": "doc/source/apis.rst",
    "content": "\nWorking on Android\n==================\n\nThis page gives details on accessing Android APIs and managing other\ninteractions on Android.\n\nStorage paths\n-------------\n\nIf you want to store and retrieve data, you shouldn't just save to\nthe current directory, and not hardcode `/sdcard/` or some other\npath either - it might differ per device.\n\nInstead, the `android` module which you can add to your `--requirements`\nallows you to query the most commonly required paths::\n\n      from android.storage import app_storage_path\n      settings_path = app_storage_path()\n\n      from android.storage import primary_external_storage_path\n      primary_ext_storage = primary_external_storage_path()\n\n      from android.storage import secondary_external_storage_path\n      secondary_ext_storage = secondary_external_storage_path()\n\n`app_storage_path()` gives you Android's so-called \"internal storage\"\nwhich is specific to your app and cannot seen by others or the user.\nIt compares best to the AppData directory on Windows.\n\n`primary_external_storage_path()` returns Android's so-called\n\"primary external storage\", often found at `/sdcard/` and potentially\naccessible to any other app.\nIt compares best to the Documents directory on Windows.\nRequires `Permission.WRITE_EXTERNAL_STORAGE` to read and write to.\n\n`secondary_external_storage_path()` returns Android's so-called\n\"secondary external storage\", often found at `/storage/External_SD/`.\nIt compares best to an external disk plugged to a Desktop PC, and can\nafter a device restart become inaccessible if removed.\nRequires `Permission.WRITE_EXTERNAL_STORAGE` to read and write to.\n\n.. warning::\n   Even if `secondary_external_storage_path` returns a path\n   the external sd card may still not be present.\n   Only non-empty contents or a successful write indicate that it is.\n\nRead more on all the different storage types and what to use them for\nin the Android documentation:\n\nhttps://developer.android.com/training/data-storage\n\nA note on permissions\n~~~~~~~~~~~~~~~~~~~~~\n\nOnly the internal storage is always accessible with no additional\npermissions. For both primary and secondary external storage, you need\nto obtain `Permission.WRITE_EXTERNAL_STORAGE` **and the user may deny it.**\nAlso, if you get it, both forms of external storage may only allow\nyour app to write to the common pre-existing folders like \"Music\",\n\"Documents\", and so on. (see the Android Docs linked above for details)\n\nRuntime permissions\n-------------------\n\nWith API level >= 21, you will need to request runtime permissions\nto access the SD card, the camera, and other things.\n\nThis can be done through the `android` module which is *available per default*\nunless you blacklist it. Use it in your app like this::\n\n      from android.permissions import request_permissions, Permission\n      request_permissions([Permission.WRITE_EXTERNAL_STORAGE])\n\nThe available permissions are listed here:\n\nhttps://developer.android.com/reference/android/Manifest.permission\n\n\nOther common tasks\n------------------\n\nRunning executables\n~~~~~~~~~~~~~~~~~~~\n\nAndroid restricts executing files from application data directories.\n``python-for-android`` works around this by creating symbolic links to a\nsmall set of supported executables inside an internal executable\ndirectory that is added to ``PATH``.\n\nDuring startup, ``start.c`` (compiled into ``libmain.so``) scans loaded\nnative libraries and creates symlinks for any library matching::\n\n    lib<binary_name>bin.so\n\nEach matching library is exposed at runtime as::\n\n    <binary_name>\n\nFor example::\n\n    libffmpegbin.so -> ffmpeg\n\nRecipe developers may expose additional executables by renaming them\nusing the same naming convention.\n\nThe following example performs a minimal FFmpeg sanity check using an\nin-memory test source and discards the output::\n\n    import subprocess\n\n    subprocess.run(\n        [\"ffmpeg\", \"-f\", \"lavfi\", \"-i\", \"testsrc\", \"-t\", \"1\", \"-f\", \"null\", \"-\"],\n        check=True\n    )\n\nThis verifies that ``ffmpeg`` is available and executable on the device.\nEnsure ``ffmpeg`` is included in your ``requirements``.\n\nIf video encoding is required, the following codec options must also be\nenabled in the build configuration:\n\n- ``av_codecs``\n- ``libx264``\n\nWithout these, FFmpeg may be present but lack the required codec support.\n\nSee also: `APK native library execution restrictions <https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/apps/processes/app-data-file-execute-restrictions.md>`_\n\nDismissing the splash screen\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWith the SDL2 bootstrap, the app's splash screen may be visible\nlonger than necessary (with your app already being loaded) due to a\nlimitation with the way we check if the app has properly started.\nIn this case, the splash screen overlaps the app gui for a short time.\n\nTo dismiss the loading screen explicitly in your code, use the `android`\nmodule::\n\n  from android import loadingscreen\n  loadingscreen.hide_loading_screen()\n\nYou can call it e.g. using ``kivy.clock.Clock.schedule_once`` to run it\nin the first active frame of your app, or use the app build method.\n\n\nHandling the back button\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nAndroid phones always have a back button, which users expect to\nperform an appropriate in-app function. If you do not handle it, Kivy\napps will actually shut down and appear to have crashed.\n\nIn SDL2 bootstraps, the back button appears as the escape key (keycode\n27, codepoint 270). You can handle this key to perform actions when it\nis pressed.\n\nFor instance, in your App class in Kivy::\n\n    from kivy.core.window import Window\n\n    class YourApp(App):\n\n       def build(self):\n          Window.bind(on_keyboard=self.key_input)\n          return Widget() # your root widget here as normal\n\n       def key_input(self, window, key, scancode, codepoint, modifier):\n          if key == 27:\n             return True  # override the default behaviour\n          else:           # the key now does nothing\n             return False\n\n\nPausing the App\n~~~~~~~~~~~~~~~\n\nWhen the user leaves an App, it is automatically paused by Android,\nalthough it gets a few seconds to store data etc. if necessary. Once\npaused, there is no guarantee that your app will run again.\n\nWith Kivy, add an ``on_pause`` method to your App class, which returns True::\n\n  def on_pause(self):\n      return True\n\nWith the webview bootstrap, pausing should work automatically.\n\nUnder SDL2, you can handle the `appropriate events <https://wiki.libsdl.org/SDL3/SDL_EventType>`__ (see SDL_APP_WILLENTERBACKGROUND etc.).\n\n\nObserving Activity result\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. module:: android.activity\n\nThe default PythonActivity has a observer pattern for `onActivityResult <https://developer.android.com/reference/android/app/Activity#onActivityResult(int, int, android.content.Intent)>`_ and `onNewIntent <https://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)>`_.\n\n.. function:: bind(eventname=callback, ...)\n\n    This allows you to bind a callback to an Android event:\n    - ``on_new_intent`` is the event associated to the onNewIntent java call\n    - ``on_activity_result`` is the event associated to the onActivityResult java call\n\n    .. warning::\n\n        This method is not thread-safe. Call it in the mainthread of your app. (tips: use kivy.clock.mainthread decorator)\n\n.. function:: unbind(eventname=callback, ...)\n\n    Unregister a previously registered callback with :func:`bind`.\n\nExample::\n\n    # This example is a snippet from an NFC p2p app implemented with Kivy.\n\n    from android import activity\n\n    def on_new_intent(self, intent):\n        if intent.getAction() != NfcAdapter.ACTION_NDEF_DISCOVERED:\n            return\n        rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)\n        if not rawmsgs:\n            return\n        for message in rawmsgs:\n            message = cast(NdefMessage, message)\n            payload = message.getRecords()[0].getPayload()\n            print('payload: {}'.format(''.join(map(chr, payload))))\n\n    def nfc_enable(self):\n        activity.bind(on_new_intent=self.on_new_intent)\n        # ...\n\n    def nfc_disable(self):\n        activity.unbind(on_new_intent=self.on_new_intent)\n        # ...\n\n\nActivity lifecycle handling\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe Android ``Application`` class provides the `ActivityLifecycleCallbacks\n<https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks>`_\ninterface where callbacks can be registered corresponding to `activity\nlifecycle\n<https://developer.android.com/guide/components/activities/activity-lifecycle>`_\nchanges. These callbacks can be used to implement logic in the Python app when\nthe activity changes lifecycle states.\n\nNote that some of the callbacks are not useful in the Python app. For example,\nan `onActivityCreated` callback will never be run since the the activity's\n`onCreate` callback will complete before the Python app is running. Similarly,\nsaving instance state in an `onActivitySaveInstanceState` callback will not be\nhelpful since the Python app doesn't have access to the restored instance\nstate.\n\n.. function:: register_activity_lifecycle_callbacks(callbackname=callback, ...)\n\n    This allows you to bind a callbacks to Activity lifecycle state changes.\n    The callback names correspond to ``ActivityLifecycleCallbacks`` method\n    names such as ``onActivityStarted``. See the `ActivityLifecycleCallbacks\n    <https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks>`_\n    documentation for names and function signatures for the callbacks.\n\n.. function:: unregister_activity_lifecycle_callbacks(instance)\n\n    Unregister a ``ActivityLifecycleCallbacks`` instance previously registered\n    with :func:`register_activity_lifecycle_callbacks`.\n\nExample::\n\n    from android.activity import register_activity_lifecycle_callbacks\n\n    def on_activity_stopped(activity):\n        print('Activity is stopping')\n\n    register_activity_lifecycle_callbacks(\n        onActivityStopped=on_activity_stopped,\n    )\n\n\nReceiving Broadcast message\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. module:: android.broadcast\n\nImplementation of the android `BroadcastReceiver\n<https://developer.android.com/reference/android/content/BroadcastReceiver.html>`_.\nYou can specify the callback that will receive the broadcast event, and actions\nor categories filters.\n\n.. class:: BroadcastReceiver\n\n    .. warning::\n\n        The callback will be called in another thread than the main thread. In\n        that thread, be careful not to access OpenGL or something like that.\n\n    .. method:: __init__(callback, actions=None, categories=None)\n\n        :param callback: function or method that will receive the event. Will\n                         receive the context and intent as argument.\n        :param actions: list of strings that represent an action.\n        :param categories: list of strings that represent a category.\n\n        For actions and categories, the string must be in lower case, without the prefix::\n\n            # In java: Intent.ACTION_HEADSET_PLUG\n            # In python: 'headset_plug'\n\n    .. method:: start()\n\n        Register the receiver with all the actions and categories, and start\n        handling events.\n\n    .. method:: stop()\n\n        Unregister the receiver with all the actions and categories, and stop\n        handling events.\n\nExample::\n\n    class TestApp(App):\n\n        def build(self):\n            self.br = BroadcastReceiver(\n                self.on_broadcast, actions=['headset_plug'])\n            self.br.start()\n            # ...\n\n        def on_broadcast(self, context, intent):\n            extras = intent.getExtras()\n            headset_state = bool(extras.get('state'))\n            if headset_state:\n                print('The headset is plugged')\n            else:\n                print('The headset is unplugged')\n\n        # Don't forget to stop and restart the receiver when the app is going\n        # to pause / resume mode\n\n        def on_pause(self):\n            self.br.stop()\n            return True\n\n        def on_resume(self):\n            self.br.start()\n\nRunnable\n~~~~~~~~\n\n.. module:: android.runnable\n\n:class:`Runnable` is a wrapper around the Java `Runnable\n<https://developer.android.com/reference/java/lang/Runnable.html>`_ class. This\nclass can be used to schedule a call of a Python function into the\n`PythonActivity` thread.\n\nExample::\n\n    from android.runnable import Runnable\n\n    def helloworld(arg):\n        print 'Called from PythonActivity with arg:', arg\n\n    Runnable(helloworld)('hello')\n\nOr use our decorator::\n\n    from android.runnable import run_on_ui_thread\n\n    @run_on_ui_thread\n    def helloworld(arg):\n        print 'Called from PythonActivity with arg:', arg\n\n    helloworld('arg1')\n\n\nThis can be used to prevent errors like:\n\n    - W/System.err( 9514): java.lang.RuntimeException: Can't create handler\n      inside thread that has not called Looper.prepare()\n    - NullPointerException in ActivityThread.currentActivityThread()\n\n.. warning::\n\n    Because the python function is called from the PythonActivity thread, you\n    need to be careful about your own calls.\n\n\nAdvanced Android API use\n------------------------\n\n.. _reference-label-for-android-module:\n\n`android` for Android API access\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAs mentioned above, the ``android`` Python module provides a simple \nwrapper around many native Android APIS, and it is *included by default*\nunless you blacklist it.\n\nThe available functionality of this module is not separately documented.\nYou can read the source `on\nGithub\n<https://github.com/kivy/python-for-android/tree/master/pythonforandroid/recipes/android/src/android>`__.\n\nAlso please note you can replicate most functionality without it using\n`pyjnius`. (see below)\n\n\n`Plyer` - a more comprehensive API wrapper\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nPlyer provides a more thorough wrapper than `android` for a much larger\narea of platform-specific APIs, supporting not only Android but also\niOS and desktop operating systems.\n(Though plyer is a work in progress and not all\nplatforms support all Plyer calls yet)\n\nPlyer does not support all APIs yet, but you can always use Pyjnius to\ncall anything that is currently missing.\n\nYou can include Plyer in your APKs by adding the `Plyer` recipe to\nyour build requirements, e.g. :code:`--requirements=plyer`.\n\nYou should check the `Plyer documentation <https://plyer.readthedocs.io/en/stable/>`_ for details of all supported\nfacades (platform APIs), but as an example the following is how you\nwould achieve vibration as described in the Pyjnius section above::\n\n    from plyer.vibrator import vibrate\n    vibrate(10)  # in Plyer, the argument is in seconds\n\nThis is obviously *much* less verbose than with Pyjnius!\n\n\n`Pyjnius` - raw lowlevel API access\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nPyjnius lets you call the Android API directly from Python Pyjnius is\nworks by dynamically wrapping Java classes, so you don't have to wait\nfor any particular feature to be pre-supported.\n\nThis is particularly useful when `android` and `plyer` don't already\nprovide a convenient access to the API, or you need more control.\n\nYou can include Pyjnius in your APKs by adding `pyjnius` to your build\nrequirements, e.g. :code:`--requirements=flask,pyjnius`. It is\nautomatically included in any APK containing Kivy, in which case you\ndon't need to specify it manually.\n\nThe basic mechanism of Pyjnius is the `autoclass` command, which wraps\na Java class. For instance, here is the code to vibrate your device::\n\n     from jnius import autoclass\n     \n     # We need a reference to the Java activity running the current\n     # application, this reference is stored automatically by\n     # Kivy's PythonActivity bootstrap\n\n     # This one works with SDL2\n     PythonActivity = autoclass('org.kivy.android.PythonActivity')\n\n     activity = PythonActivity.mActivity\n\n     Context = autoclass('android.content.Context')\n     vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)\n\n     vibrator.vibrate(10000)  # the argument is in milliseconds\n     \nThings to note here are:\n\n- The class that must be wrapped depends on the bootstrap. This is\n  because Pyjnius is using the bootstrap's java source code to get a\n  reference to the current activity, which the bootstraps store in the\n  ``mActivity`` static variable. This difference isn't always\n  important, but it's important to know about.\n- The code closely follows the Java API - this is exactly the same set\n  of function calls that you'd use to achieve the same thing from Java\n  code.\n- This is quite verbose - it's a lot of lines to achieve a simple\n  vibration!\n  \nThese emphasise both the advantages and disadvantage of Pyjnius; you\n*can* achieve just about any API call with it (though the syntax is\nsometimes a little more involved, particularly if making Java classes\nfrom Python code), but it's not Pythonic and it's not short. These are\nproblems that Plyer, explained below, attempts to address.\n\nYou can check the `Pyjnius documentation <https://pyjnius.readthedocs.io/en/latest/>`_ for further details.\n\n"
  },
  {
    "path": "doc/source/bootstraps.rst",
    "content": "\nBootstraps\n==========\n\nThis page is about creating new bootstrap backends. For build options\nof existing bootstraps (i.e. with SDL2, Webview, etc.), see\n:ref:`build options <bootstrap_build_options>`.\n\npython-for-android (p4a) supports multiple *bootstraps*. These fulfill a\nsimilar role to recipes, but instead of describing how to compile a\nspecific module they describe how a full Android project may be put\ntogether from a combination of individual recipes and other\ncomponents such as Android source code and various build files.\n\nThis page describes the basics of how bootstraps work so that you can\ncreate and use your own if you like, making it easy to build new kinds\nof Python projects for Android.\n  \n\nCreating a new bootstrap\n------------------------\n\nA bootstrap class consists of just a few basic components, though one of them\nmust do a lot of work. \n\nFor instance, the SDL2 bootstrap looks like the following::\n\n    from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchAndroid, logger, info_main, which\n    from os.path import join, exists\n    from os import walk\n    import glob\n    import sh\n\n\n    class SDL2Bootstrap(Bootstrap):\n        name = 'sdl2'\n\n        recipe_depends = ['sdl2']\n\n        def run_distribute(self):\n            # much work is done here...\n\n            \nThe declaration of the bootstrap name and recipe dependencies should\nbe clear. However, the :code:`run_distribute` method must do all the\nwork of creating a build directory, copying recipes etc into it, and\nadding or removing any extra components as necessary.\n\nIf you'd like to create a bootstrap, the best resource is to check the\nexisting ones in the p4a source code. You can also :doc:`contact the\ndevelopers <troubleshooting>` if you have problems or questions.\n"
  },
  {
    "path": "doc/source/buildoptions.rst",
    "content": "\nBuild options\n=============\n\nThis page contains instructions for using different build options.\n\n\nPython versions\n---------------\n\npython-for-android supports using Python 3.8 or higher. To explicitly select a Python\nversion in your requirements, use e.g. ``--requirements=python3==3.10.11,hostpython3==3.10.11``.\n\nThe last python-for-android version supporting Python2 was `v2019.10.06 <https://github.com/kivy/python-for-android/archive/v2019.10.06.zip>`__\n\nPython-for-android no longer supports building for Python 3 using the CrystaX\nNDK. The last python-for-android version supporting CrystaX was `0.7.0 <https://github.com/kivy/python-for-android/archive/0.7.0.zip>`__\n\n.. _bootstrap_build_options:\n\nBootstrap options\n-----------------\n\npython-for-android supports multiple app backends with different types\nof interface. These are called *bootstraps*.\n\nCurrently the following bootstraps are supported, but we hope that it\nshould be easy to add others if your project has different\nrequirements. `Let us know\n<https://groups.google.com/forum/#!forum/python-android>`__ if you'd\nlike help adding a new one.\n\nsdl2\n~~~~\n\nUse this with ``--bootstrap=sdl2``, or just include the\n``sdl2`` recipe, e.g. ``--requirements=sdl2,python3``.\n\nSDL2 is a popular cross-platform development library, particularly for\ngames. It has its own Android project support, which\npython-for-android uses as a bootstrap, and to which it adds the\nPython build and JNI code to start it.\n\nFrom the point of view of a Python program, SDL2 should behave as\nnormal. For instance, you can build apps with Kivy or PySDL2\nand have them work with this bootstrap. It should also be possible to\nuse e.g. pygame_sdl2, but this would need a build recipe and doesn't\nyet have one.\n\nBuild options\n%%%%%%%%%%%%%\n\nThe sdl2 bootstrap supports the following additional command line\noptions (this list may not be exhaustive):\n\n- ``--private``: The directory containing your project files.\n- ``--package``: The Java package name for your project. e.g. ``org.example.yourapp``.\n- ``--name``: The app name.\n- ``--version``: The version number.\n- ``--orientation``: The orientations that the app will display in.\n  (Available options are ``portrait``, ``landscape``, ``portrait-reverse``, ``landscape-reverse``).\n  Since Android ignores ``android:screenOrientation`` when in multi-window mode\n  (Which is the default on Android 12+), this option will also set the window orientation hints\n  for the SDL bootstrap. If multiple orientations are given,\n  ``android:screenOrientation`` will be set to ``unspecified``.\n- ``--manifest-orientation``: The orientation that will be set for the ``android:screenOrientation``\n  attribute of the activity in the ``AndroidManifest.xml`` file. If not set, the value \n  will be synthesized from the ``--orientation`` option.\n  The full list of valid options is given under ``android:screenOrientation``\n  in the `Android documentation <https://developer.android.com/guide/topics/manifest/activity-element.html>`__.\n- ``--icon``: A path to the png file to use as the application icon.\n- ``--permission``: A permission that needs to be declared into the App ``AndroidManifest.xml``.\n  For multiple permissions, add multiple ``--permission`` arguments.\n  ``--home-app`` Gives you the option to set your application as a home app (launcher) on your Android device.\n  ``--display-cutout``: A display cutout is an area on some devices that extends into the display surface.\n  It allows for an edge-to-edge experience while providing space for important sensors on the front of the device.\n  (Available options are ``default``, ``shortEdges``, ``never`` and defaults to ``never``)\n  `Android documentation <https://developer.android.com/develop/ui/views/layout/display-cutout>`__.\n\n  .. Note ::\n    ``--permission`` accepts the following syntaxes: \n    ``--permission (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)``\n    or ``--permission android.permission.WRITE_EXTERNAL_STORAGE``.\n\n    The first syntax is used to set additional properties to the permission \n    (``android:maxSdkVersion`` and ``android:usesPermissionFlags`` are the only ones supported for now).\n\n    The second one can be used when there's no need to add any additional properties.\n\n  .. Warning ::\n    The syntax ``--permission VIBRATE`` (only the permission name, without the prefix),\n    is also supported for backward compatibility, but it will be removed in the future.\n\n\n- ``--meta-data``: Custom key=value pairs to add in the application metadata.\n- ``--presplash``: A path to the image file to use as a screen while\n  the application is loading.\n- ``--presplash-color``: The presplash screen background color, of the\n  form ``#RRGGBB`` or a color name ``red``, ``green``, ``blue`` etc.\n- ``--presplash-lottie``: use a lottie (json) file as a presplash animation. If\n  used, this will replace the static presplash image.\n- ``--wakelock``: If the argument is included, the application will\n  prevent the device from sleeping.\n- ``--window``: If the argument is included, the application will not\n  cover the Android status bar.\n- ``--blacklist``: The path to a file containing blacklisted patterns\n  that will be excluded from the final APK. Defaults to ``./blacklist.txt``.\n- ``--whitelist``: The path to a file containing whitelisted patterns\n  that will be included in the APK even if also blacklisted.\n- ``--add-jar``: The path to a .jar file to include in the APK. To\n  include multiple jar files, pass this argument multiple times.\n- ``--intent-filters``: A file path containing intent filter xml to be\n  included in AndroidManifest.xml.\n- ``--service``: A service name and the Python script it should\n  run. See :ref:`arbitrary_scripts_services`.\n- ``--add-source``: Add a source directory to the app's Java code.\n- ``--no-byte-compile-python``: Skip byte compile for .py files.\n- ``--enable-androidx``: Enable AndroidX support library.\n- ``--add-resource``: Put this file or directory in the apk res directory.\n\n\nwebview\n~~~~~~~\n\nYou can use this with ``--bootstrap=webview``, or include the\n``webviewjni`` recipe, e.g. ``--requirements=webviewjni,python3``.\n\nThe webview bootstrap gui is, per the name, a WebView displaying a\nwebpage, but this page is hosted on the device via a Python\nwebserver. For instance, your Python code can start a Flask\napplication, and your app will display and allow the user to navigate\nthis website.\n\n.. note:: Your Flask script must start the webserver *without*\n          :code:``debug=True``. Debug mode doesn't seem to work on\n          Android due to use of a subprocess.\n\nThis bootstrap will automatically try to load a website on port 5000\n(the default for Flask), or you can specify a different option with\nthe `--port` command line option. If the webserver is not immediately\npresent (e.g. during the short Python loading time when first\nstarted), it will instead display a loading screen until the server is\nready.\n\n- ``--private``: The directory containing your project files.\n- ``--package``: The Java package name for your project. e.g. ``org.example.yourapp``.\n- ``--name``: The app name.\n- ``--version``: The version number.\n- ``--orientation``: The orientations that the app will display in.\n  (Available options are ``portrait``, ``landscape``, ``portrait-reverse``, ``landscape-reverse``).\n  Since Android ignores ``android:screenOrientation`` when in multi-window mode\n  (Which is the default on Android 12+), this setting is not guaranteed to work, and\n  you should consider to implement a custom orientation change handler in your app.\n- ``--manifest-orientation``: The orientation that will be set in the ``android:screenOrientation``\n  attribute of the activity in the ``AndroidManifest.xml`` file. If not set, the value \n  will be synthesized from the ``--orientation`` option.\n  The full list of valid options is given under ``android:screenOrientation``\n  in the `Android documentation <https://developer.android.com/guide/topics/manifest/activity-element.html>`__.\n- ``--icon``: A path to the png file to use as the application icon.\n- ``--permission``: A permission name for the app,\n  e.g. ``--permission VIBRATE``. For multiple permissions, add\n  multiple ``--permission`` arguments.\n- ``--meta-data``: Custom key=value pairs to add in the application metadata.\n- ``--presplash``: A path to the image file to use as a screen while\n  the application is loading.\n- ``--presplash-color``: The presplash screen background color, of the\n  form ``#RRGGBB`` or a color name ``red``, ``green``, ``blue`` etc.\n- ``--wakelock``: If the argument is included, the application will\n  prevent the device from sleeping.\n- ``--window``: If the argument is included, the application will not\n  cover the Android status bar.\n- ``--blacklist``: The path to a file containing blacklisted patterns\n  that will be excluded from the final APK. Defaults to ``./blacklist.txt``.\n- ``--whitelist``: The path to a file containing whitelisted patterns\n  that will be included in the APK even if also blacklisted.\n- ``--add-jar``: The path to a .jar file to include in the APK. To\n  include multiple jar files, pass this argument multiple times.\n- ``--intent-filters``: A file path containing intent filter xml to be\n  included in AndroidManifest.xml.\n- ``--service``: A service name and the Python script it should\n  run. See :ref:`arbitrary_scripts_services`.\n- ``add-source``: Add a source directory to the app's Java code.\n- ``--port``: The port on localhost that the WebView will\n  access. Defaults to 5000.\n\n\nservice_library\n~~~~~~~~~~~~~~~\n\nYou can use this with ``--bootstrap=service_library`` option.\n\n\nThis bootstrap can be used together with ``aar`` output target to generate\na library, containing Python services that can be used with other build \nsystems and frameworks.\n\n- ``--private``: The directory containing your project files.\n- ``--package``: The Java package name for your project. e.g. ``org.example.yourapp``.\n- ``--name``: The library name.\n- ``--version``: The version number.\n- ``--service``: A service name and the Python script it should\n  run. See :ref:`arbitrary_scripts_services`.\n- ``--blacklist``: The path to a file containing blacklisted patterns\n  that will be excluded from the final AAR. Defaults to ``./blacklist.txt``.\n- ``--whitelist``: The path to a file containing whitelisted patterns\n  that will be included in the AAR even if also blacklisted.\n- ``--add-jar``: The path to a .jar file to include in the APK. To\n  include multiple jar files, pass this argument multiple times.\n- ``add-source``: Add a source directory to the app's Java code.\n\nQt\n~~\n\nThis bootstrap can be used with ``--bootstrap=qt`` or by including the ``PySide6`` or\n``shiboken6`` recipe, e.g. ``--requirements=pyside6,shiboken6``. Currently, the only way\nto use this bootstrap is through `pyside6-android-deploy <https://www.qt.io/blog/taking-qt-for-python-to-android>`__\ntool shipped with ``PySide6``, as the recipes for ``PySide6`` and ``shiboken6`` are created\ndynamically. The tool builds ``PySide6`` and ``shiboken6`` wheels for a specific Android platform\nand the recipes simply unpack the built wheels. You can see the recipes `here <https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/pyside-tools/deploy_lib/android/recipes>`__.\n\n.. note::\n  The ``pyside6-android-deploy`` tool and hence the Qt bootstrap does not support multi-architecture\n  builds currently.\n\nWhat are Qt and PySide?\n%%%%%%%%%%%%%%%%%%%%%%%%\n\n`Qt <https://www.qt.io/>`__ is a popularly used cross-platform C++ framework for developing\nGUI applications. `PySide6 <https://doc.qt.io/qtforpython-6/quickstart.html>`__ refers to the\nPython bindings for Qt6, and enables the Python developers access to the Qt6 API.\n`Shiboken6 <https://doc.qt.io/qtforpython-6/shiboken6/index.html>`__ is the binding generator\ntool used for generating the Python bindings from C++ code.\n\n.. note:: The `shiboken6` recipe is for the `Shiboken Python module <https://doc.qt.io/qtforpython-6/shiboken6/shibokenmodule.html>`__\n  which includes a couple of utility functions for inspecting and debugging PySide6 code.\n\nBuild Options\n%%%%%%%%%%%%%\n\n``pyside6-android-deploy`` works by generating a ``buildozer.spec`` file and thereby using\n`buildozer <https://buildozer.readthedocs.io/en/latest/>`__ to control the build options used by\n``python-for-android`` with the Qt bootstrap. Apart from the general build options that works\nacross all the other bootstraps, the Qt bootstrap introduces the following 3 new build options.\n\n- ``--qt-libs``: list of Qt libraries(modules) to be loaded.\n- ``--load-local-libs``: list of Qt plugin libraries to be loaded.\n- ``--init-classes``: list of Java class names to the loaded from the Qt jar files supplied through\n  the ``--add-jar`` option.\n\nThese build options are automatically populated by the ``pyside6-android-deploy`` tool, but can be\nmodified by updating the ``buildozer.spec`` file. Apart from the above 3 build options, the tool\nalso automatically identifies the values to be fed into the cli options ``--permission``, ``--add-jar``\ndepending on the PySide6 modules used by the application.\n\nRequirements blacklist (APK size optimization)\n----------------------------------------------\n\nTo optimize the size of the `.apk` file that p4a builds for you,\nyou can **blacklist** certain core components. Per default, p4a\nwill add python *with batteries included* as would be expected on\ndesktop, including openssl, sqlite3 and other components you may\nnot use.\n\nTo blacklist an item, specify the ``--blacklist-requirements`` option::\n\n    p4a apk ... --blacklist-requirements=sqlite3\n\nAt the moment, the following core components can be blacklisted\n(if you don't want to use them) to decrease APK size:\n\n- ``android``  disables p4a's android module (see :ref:`reference-label-for-android-module`)\n- ``libffi``  disables ctypes stdlib module\n- ``openssl``   disables ssl stdlib module\n- ``sqlite3``   disables sqlite3 stdlib module\n"
  },
  {
    "path": "doc/source/commands.rst",
    "content": "\nCommands\n========\n\nThis page documents all the commands and options that can be passed to\ntoolchain.py.\n\n\nCommands index\n--------------\n\nThe commands available are the methods of the ToolchainCL class,\ndocumented below. They may have options of their own, or you can\nalways pass `general arguments`_ or `distribution arguments`_ to any\ncommand (though if irrelevant they may not have an effect).\n\n.. autoclass:: toolchain.ToolchainCL\n   :members:\n\n\nGeneral arguments\n-----------------\n\nThese arguments may be passed to any command in order to modify its\nbehaviour, though not all commands make use of them.\n\n``--debug``\n  Print extra debug information about the build, including all compilation output.\n\n``--sdk_dir``\n  The filepath where the Android SDK is installed. This can\n  alternatively be set in several other ways.\n\n``--android_api``\n  The Android API level to target; python-for-android will check if\n  the platform tools for this level are installed.\n\n``--ndk_dir``\n  The filepath where the Android NDK is installed. This can\n  alternatively be set in several other ways.\n\n``--ndk_version``\n  The version of the NDK installed, important because the internal\n  filepaths to build tools depend on this. This can alternatively be\n  set in several other ways, or if your NDK dir contains a RELEASE.TXT\n  containing the version this is automatically checked so you don't\n  need to manually set it.\n\n\nDistribution arguments\n----------------------\n\np4a supports several arguments used for specifying which compiled\nAndroid distribution you want to use. You may pass any of these\narguments to any command, and if a distribution is required they will\nbe used to load, or compile, or download this as necessary.\n\nNone of these options are essential, and in principle you need only\nsupply those that you need.\n\n\n``--name NAME``\n  The name of the distribution. Only one distribution with a given name can be created.\n\n``--requirements LIST,OF,REQUIREMENTS`` \n  The recipes that your\n  distribution must contain, as a comma separated list. These must be\n  names of recipes or the pypi names of Python modules.\n\n``--force-build BOOL``\n  Whether the distribution must be compiled from scratch.\n\n``--arch``\n  The architecture to build for. You can specify multiple architectures to build for\n  at the same time. As an example ``p4a ... --arch arm64-v8a --arch armeabi-v7a ...``\n  will build a distribution for both ``arm64-v8a`` and ``armeabi-v7a``.\n\n``--bootstrap BOOTSTRAP``\n  The Java bootstrap to use for your application. You mostly don't\n  need to worry about this or set it manually, as an appropriate\n  bootstrap will be chosen from your ``--requirements``. Current\n  choices are ``sdl2`` (used with Kivy and most other apps), ``webview`` or ``qt``.\n\n\n.. note:: These options are preliminary. Others will include toggles\n          for allowing downloads, and setting additional directories\n          from which to load user dists.\n"
  },
  {
    "path": "doc/source/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# python-for-android documentation build configuration file, created by\n# sphinx-quickstart2 on Wed Jun 24 22:46:06 2015.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport datetime\nimport os\nimport re\nimport sys\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\nsys.path.append(os.path.abspath('../../pythonforandroid'))\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.intersphinx',\n    'sphinx.ext.viewcode',\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n# source_suffix = ['.rst', '.md']\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = 'python-for-android'\n\n_today = datetime.datetime.now()\n\nauthor = 'Kivy Team and other contributors'\n\ncopyright = f'2015-{_today.year}, {author}'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n\n# Lookup the version from the pyjnius module, without installing it\n# since readthedocs.org may have issue to install it.\n# Read the version from the __init__.py file, without importing it.\ndef get_version():\n    with open(\n        os.path.join(os.path.abspath(\"../..\"), \"pythonforandroid\", \"__init__.py\")\n    ) as fp:\n        for line in fp:\n            m = re.search(r'^\\s*__version__\\s*=\\s*([\\'\"])([^\\'\"]+)\\1\\s*$', line)\n            if m:\n                return m.group(2)\n\n# The short X.Y version.\nversion = get_version()\n# The full version, including alpha/beta/rc tags.\nrelease = get_version()\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = 'en'\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'furo'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Language to be used for generating the HTML full-text search index.\n# Sphinx supports the following languages:\n#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'\n#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'\n#html_search_language = 'en'\n\n# A dictionary with options for the search language support, empty by default.\n# Now only 'ja' uses this config value\n#html_search_options = {'type': 'default'}\n\n# The name of a javascript file (relative to the configuration directory) that\n# implements a search results scorer. If empty, the default will be used.\n#html_search_scorer = 'scorer.js'\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'python-for-androiddoc'\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n\n# Latex figure (float) alignment\n#'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n  (master_doc, 'python-for-android.tex', 'python-for-android Documentation',\n   author, 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (master_doc, 'python-for-android', u'python-for-android Documentation',\n     [author], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  (master_doc, 'python-for-android', u'python-for-android Documentation',\n   author, 'python-for-android',\n   'A development tool that packages Python apps into binaries that can run on '\n   'Android devices',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n\n\n# Example configuration for intersphinx: refer to the Python standard library.\nintersphinx_mapping =  {'python': ('https://docs.python.org/3', None)}\n\n# Ignore some troublesome links that are actually fine.\nlinkcheck_ignore = [\n    # Special characters in URL seems to confuse link-checker.\n    r\"https://developer.android.com/reference/android/app/Activity#onActivity.*\",\n\n    # GitHub parses anchor tags differently to pure HTML\n    r\"https://github.com/kivy/python-for-android/blob.*\",\n    ]\n\n# Allow redirects for URLs where we prefer to keep the original form\nlinkcheck_allowed_redirects = {\n    # Kivy chat redirects to Discord invite\n    r\"https://chat\\.kivy\\.org/\": r\"https://discord\\.com/.*\",\n    # GitHub archive URLs redirect to codeload\n    r\"https://github\\.com/kivy/python-for-android/archive/.*\": r\"https://codeload\\.github\\.com/.*\",\n    # GitHub gist homepage redirects to starred\n    r\"https://gist\\.github\\.com/$\": r\"https://gist\\.github\\.com/.*\",\n    # Google Play Store redirects\n    r\"https://play\\.google\\.com/store/$\": r\"https://play\\.google\\.com/store/.*\",\n}\n\n"
  },
  {
    "path": "doc/source/contact.rst",
    "content": "Contact Us\n==========\n\nIf you are looking to contact the Kivy Team (who are responsible for managing the\npython-for-android project), including looking for support, please see our\n`latest contact details <https://github.com/kivy/python-for-android/blob/master/CONTACT.md>`_."
  },
  {
    "path": "doc/source/contribute.rst",
    "content": ".. _contributing:\n\n.. _contribute:\n\nContribution Guidelines\n=======================\n\nBuildozer is part of the `Kivy <https://kivy.org>`_ ecosystem - a large group of\nproducts used by many thousands of developers for free, but it\nis built entirely by the contributions of volunteers. We welcome (and rely on)\nusers who want to give back to the community by contributing to the project.\n\nContributions can come in many forms. See the latest\n`Contribution Guidelines <https://github.com/kivy/python-for-android/blob/master/CONTRIBUTING.md>`_\nfor general guidelines of how you can help us, and specific instructions for python-for-android\ndevelopment.\n\n.. warning::\n   The python-for-android process differs in small but important ways from the Kivy framework's process.\n"
  },
  {
    "path": "doc/source/distutils.rst",
    "content": "\ndistutils/setuptools integration\n================================\n\nHave `p4a apk` run setup.py (replaces ``--requirements``)\n---------------------------------------------------------\n\nIf your project has a `setup.py` file, then it can be executed by\n`p4a` when your app is packaged such that your app properly ends up\nin the packaged site-packages. (Use ``--use-setup-py`` to enable this,\n``--ignore-setup-py`` to prevent it)\n\nThis is functionality to run **setup.py INSIDE `p4a apk`,** as opposed\nto the other section below, which is about running\n*p4a inside setup.py*.\n\nThis however has these caveats:\n\n- **Only your ``main.py`` from your app's ``--private`` data is copied\n  into the .apk!** Everything else needs to be installed by your\n  ``setup.py`` into the site-packages, or it won't be packaged.\n\n- All dependencies that map to recipes can only be pinned to exact\n  versions, all other constraints will either just plain not work\n  or even cause build errors. (Sorry, our internal processing is\n  just not smart enough to honor them properly at this point)\n\n- The dependency analysis at the start may be quite slow and delay\n  your build\n\nReasons why you would want to use a `setup.py` to be processed (and\nomit specifying ``--requirements``):\n\n- You want to use a more standard mechanism to specify dependencies\n  instead of ``--requirements``\n\n- You already use a `setup.py` for other platforms\n\n- Your application imports itself\n  in a way that won't work unless installed to site-packages)\n\n\nReasons **not** to use a `setup.py` (that is to use the usual\n``--requirements`` mechanism instead):\n\n- You don't use a `setup.py` yet, and prefer the simplicity of\n  just specifying ``--requirements``\n\n- Your `setup.py` assumes a desktop platform and pulls in\n  Android-incompatible dependencies, and you are not willing\n  to change this, or you want to keep it separate from Android\n  deployment for other organizational reasons\n\n- You need data files to be around that aren't installed by\n  your `setup.py` into the site-packages folder\n\n\nUse your setup.py to call p4a\n-----------------------------\n\nInstead of running p4a via the command line, you can call it via\n`setup.py` instead, by it integrating with distutils and setup.py.\n\nThis is functionality to run **p4a INSIDE setup.py,** as opposed\nto the other section above, which is about running\n*setup.py inside `p4a apk`*.\n\nThe base command is::\n\n    python setup.py apk\n\nThe files included in the APK will be all those specified in the\n``package_data`` argument to setup. For instance, the following\nexample will include all .py and .png files in the ``testapp``\nfolder::\n\n    from distutils.core import setup\n    from setuptools import find_packages\n\n    setup(\n        name='testapp_setup',\n        version='1.1',\n        description='p4a setup.py example',\n        author='Your Name',\n        author_email='youremail@address.com',\n        packages=find_packages(),\n        options=options,\n        package_data={'testapp': ['*.py', '*.png']}\n    )\n\nThe app name and version will also be read automatically from the\nsetup.py.\n\nThe Android package name uses ``org.test.lowercaseappname``\nif not set explicitly.\n\nThe ``--private`` argument is set automatically using the\npackage_data. You should *not* set this manually.\n\nThe target architecture defaults to ``--armeabi``.\n\nAll of these automatic arguments can be overridden by passing them manually on the command line, e.g.::\n\n    python setup.py apk --name=\"Testapp Setup\" --version=2.5\n\nAdding p4a arguments in setup.py\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nInstead of providing extra arguments on the command line, you can\nstore them in setup.py by passing the ``options`` parameter to\n:code:`setup`. For instance::\n\n    from distutils.core import setup\n    from setuptools import find_packages\n\n    options = {'apk': {'debug': None,  # use None for arguments that don't pass a value\n                       'requirements': 'sdl2,pyjnius,kivy,python3',\n                       'android-api': 19,\n                       'ndk-dir': '/path/to/ndk',\n                       'dist-name': 'bdisttest',\n                       }}\n\n    packages = find_packages()\n    print('packages are', packages)\n\n    setup(\n        name='testapp_setup',\n        version='1.1',\n        description='p4a setup.py example',\n        author='Your Name',\n        author_email='youremail@address.com',\n        packages=find_packages(),\n        options=options,\n        package_data={'testapp': ['*.py', '*.png']}\n    )\n\nThese options will be automatically included when you run ``python\nsetup.py apk``. Any options passed on the command line will override\nthese values.\n\nAdding p4a arguments in setup.cfg\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can also provide p4a arguments in the setup.cfg file, as normal\nfor distutils. The syntax is::\n\n    [apk]\n\n    argument=value\n\n    requirements=sdl2,kivy\n"
  },
  {
    "path": "doc/source/docker.rst",
    "content": ".. _docker:\n\nDocker\n======\n\nCurrently we use a containerized build for testing Python for Android recipes.\nDocker supports three big platforms either directly with the kernel or via\nusing headless VirtualBox and a small distro to run itself on.\n\nWhile this is not the actively supported way to build applications, if you are\nwilling to play with the approach, you can use the ``Dockerfile`` to build\nthe Docker image we use for CI builds and create an Android\napplication with that in a container. This approach allows you to build Android\napplications on all platforms Docker engine supports. These steps assume you\nalready have Docker preinstalled and set up.\n\n.. warning::\n   This approach is highly space unfriendly! The more layers (``commit``) or\n   even Docker images (``build``) you create the more space it'll consume.\n   Within the Docker image there is Android SDK and NDK + various dependencies.\n   Within the custom diff made by building the distribution there is another\n   big chunk of space eaten. The very basic stuff such as a distribution with:\n   CPython 3, setuptools, Python for Android ``android`` module, SDL2 (+ deps),\n   PyJNIus and Kivy takes almost 2 GB. Check your free space first!\n\n1. Clone the repository::\n\n       git clone https://github.com/kivy/python-for-android\n\n2. Build the image with name ``p4a``::\n\n       docker build --tag p4a .\n\n   .. note::\n      You need to be in the ``python-for-android`` for the Docker build context\n      and you can optionally use ``--file`` flag to specify the path to the\n      ``Dockerfile`` location.\n\n3. Create a container from ``p4a`` image with copied ``testapps`` folder\n   in the image mounted to the same one in the cloned repo on the host::\n\n       docker run \\\n           --interactive \\\n           --tty \\\n           --volume \".../testapps\":/home/user/testapps \\\n           p4a sh -c\n               '. venv/bin/activate \\\n               && cd testapps \\\n               && python setup_testapp_python3.py apk \\\n               --sdk-dir $ANDROID_SDK_HOME \\\n               --ndk-dir $ANDROID_NDK_HOME'\n\n   .. note::\n      On Windows you might need to use quotes and forward-slash path for volume\n      \"/c/Users/.../python-for-android/testapps\":/home/user/testapps\n\n   .. warning::\n      On Windows ``gradlew`` will attempt to use 'bash\\r' command which is\n      a result of Windows line endings. For that you'll need to install\n      ``dos2unix`` package into the image.\n\n4. Preserve the distribution you've already built (optional, but recommended):\n\n       docker commit $(docker ps --last=1 --quiet) my_p4a_dist\n\n5. Find the ``.APK`` file on this location::\n\n       ls -lah testapps\n"
  },
  {
    "path": "doc/source/faq.rst",
    "content": "FAQ\n===\n\npython-for-android has an `online FAQ <https://github.com/kivy/python-for-android/blob/master/FAQ.md>`_. It contains\nthe answers to questions that repeatedly come up.\n"
  },
  {
    "path": "doc/source/index.rst",
    "content": "python-for-android\n==================\n\npython-for-android (p4a) is a development tool that packages Python apps into\nbinaries that can run on Android devices.\n\nIt can generate:\n\n* `Android Package <https://en.wikipedia.org/wiki/Apk_(file_format)>`_ (APK)\n  files, ready to install locally on a device, especially for testing. This format\n  is used by many `app stores <https://en.wikipedia.org/wiki/List_of_Android_app_stores>`_\n  but not `Google Play Store <https://play.google.com/store/>`_.\n* `Android App Bundle <https://developer.android.com/guide/app-bundle/faq>`_\n  (AAB) files which can be shared on `Google Play Store <https://play.google.com/store/>`_.\n* `Android Archive <https://developer.android.com/studio/projects/android-library>`_\n  (AAR) files which can be used as a reusable bundle of resources for other\n  projects.\n\nIt supports multiple CPU architectures.\n\nIt supports apps developed with `Kivy framework <https://kivy.org/>`_, but was\nbuilt to be flexible about the backend libraries (through \"bootstraps\"), and\nalso supports `PySDL2 <https://pypi.org/project/PySDL2/>`_, and a\n`WebView <https://developer.android.com/reference/android/webkit/WebView>`_ with\na Python web server.\n\nIt automatically supports dependencies on most pure Python packages. For other\npackages, including those that depend on C code, a special \"recipe\" must be\nwritten to support cross-compiling. python-for-android comes with recipes for\nmany of the most popular libraries (e.g. numpy and sqlalchemy) built in.\n\npython-for-android works by cross-compiling the Python interpreter and its\ndependencies for Android devices, and bundling it with the app's python code\nand dependencies. The Python code is then interpreted on the Android device.\n\nIt is recommended that python-for-android be used via\n`Buildozer <https://buildozer.readthedocs.io/en/latest/>`_, which ensures the correct\ndependencies are pre-installed, and centralizes the configuration. However,\npython-for-android is not limited to being used with Buildozer.\n\nBuildozer is released and distributed under the terms of the MIT license. You\nshould have received a\ncopy of the MIT license alongside your distribution. Our\n`latest license <https://github.com/kivy/python-for-android/blob/master/LICENSE>`_\nis also available.\n\n\nContents\n========\n\n.. toctree::\n   :maxdepth: 2\n\n   quickstart\n   buildoptions\n   commands\n   apis\n   distutils\n   recipes\n   bootstraps\n   services\n   troubleshooting\n   docker\n   testing_pull_requests\n   faq\n   contribute\n   contact\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "doc/source/quickstart.rst",
    "content": "\nGetting Started\n===============\n\nGetting up and running on python-for-android (p4a) is a simple process\nand should only take you a couple of minutes. We'll refer to Python\nfor android as p4a in this documentation.\n\nConcepts\n--------\n\n*Basic:*\n\n- **requirements:** For p4a, all your app's dependencies must be specified\n  via ``--requirements`` similar to the standard `requirements.txt`.\n  (Unless you specify them via a `setup.py`/`install_requires`)\n  All dependencies will be mapped to \"recipes\" if any exist, so that\n  many common libraries will just work. See \"recipe\" below for details.\n\n- **distribution:** A distribution is the final \"build\" of your\n  compiled project + requirements, as an Android project assembled by\n  p4a that can be turned directly into an APK. p4a can contain multiple\n  distributions with different sets of requirements.\n\n- **build:** A build refers to a compiled recipe or distribution.\n\n- **bootstrap:** A bootstrap is the app backend that will start your\n  application. The default for graphical applications is SDL2.\n  You can also use e.g. the webview for web apps, or service_only/service_library for\n  background services, or qt for PySide6 apps. Different bootstraps have different additional\n  build options.\n\n*Advanced:*\n\n- **recipe:**\n  A recipe is a file telling p4a how to install a requirement\n  that isn't by default fully Android compatible.\n  This is often necessary for Cython or C/C++-using python extensions.\n  p4a has recipes for many common libraries already included, and any\n  dependency you specified will be automatically mapped to its recipe.\n  If a dependency doesn't work and has no recipe included in p4a,\n  then it may need one to work.\n\n\nInstallation\n------------\n\nInstalling p4a\n~~~~~~~~~~~~~~\n\np4a is now available on Pypi, so you can install it using pip::\n\n    pip install python-for-android\n\nYou can also test the master branch from Github using::\n\n    pip install git+https://github.com/kivy/python-for-android.git\n\nInstalling Prerequisites\n~~~~~~~~~~~~~~~~~~~~~~~~\n\np4a requires a few dependencies to be installed on your system to work\nproperly. While we're working on a way to automate pre-requisites checks,\nsuggestions and installation on all platforms (macOS is already supported),\non Linux distros you'll need to install them manually.\n\nOn recent versions of Ubuntu and its derivatives you can easily install them via\nthe following command (re-adapted from the `Dockerfile` we use to perform CI builds)::\n\n    sudo apt-get update\n    sudo apt-get install -y \\\n        ant \\\n        autoconf \\\n        automake \\\n        autopoint \\\n        ccache \\\n        cmake \\\n        g++ \\\n        gcc \\\n        git \\\n        lbzip2 \\\n        libffi-dev \\\n        libltdl-dev \\\n        libtool \\\n        libssl-dev \\\n        make \\\n        openjdk-17-jdk \\\n        patch \\\n        pkg-config \\\n        python3 \\\n        python3-dev \\\n        python3-pip \\\n        python3-venv \\\n        sudo \\\n        unzip \\\n        wget \\\n        zip\n\n\nInstalling Android SDK\n~~~~~~~~~~~~~~~~~~~~~~\n\n.. warning::\n   python-for-android is often picky about the **SDK/NDK versions.**\n   Pick the recommended ones from below to avoid problems.\n\nBasic SDK install\n`````````````````\n\nYou need to download and unpack the Android SDK and NDK to a directory (let's say $HOME/Documents/):\n\n- `Android SDK <https://developer.android.com/studio/index.html>`_\n- `Android NDK <https://developer.android.com/ndk/downloads/index.html>`_\n\nFor the Android SDK, you can download 'just the command line\ntools'. When you have extracted these you'll see only a directory\nnamed ``tools``, and you will need to run extra commands to install\nthe SDK packages needed. \n\nFor Android NDK, note that modern releases will only work on a 64-bit\noperating system. **The minimal, and recommended, NDK version to use is r28c:**\n\n - `Go to ndk downloads page <https://developer.android.com/ndk/downloads/>`_\n - Windows users should create a virtual machine with an GNU Linux os\n   installed, and then you can follow the described instructions from within\n   your virtual machine.\n\n\nPlatform and build tools\n````````````````````````\n\nFirst, install an API platform to target. **The recommended *target* API\nlevel is 27**, you can replace it with a different number but\nkeep in mind other API versions are less well-tested and older devices\nare still supported down to the **recommended specified *minimum*\nAPI/NDK API level 21**::\n\n  $SDK_DIR/tools/bin/sdkmanager \"platforms;android-27\"\n\n\nSecond, install the build-tools. You can use\n``$SDK_DIR/tools/bin/sdkmanager --list`` to see all the\npossibilities, but 28.0.2 is the latest version at the time of writing::\n\n  $SDK_DIR/tools/bin/sdkmanager \"build-tools;28.0.2\"\n\nConfigure p4a to use your SDK/NDK\n`````````````````````````````````\n\nThen, you can edit your ``~/.bashrc`` or other favorite shell to include new environment\nvariables necessary for building on android::\n\n    # Adjust the paths!\n    export ANDROIDSDK=\"$HOME/Documents/android-sdk-27\"\n    export ANDROIDNDK=\"$HOME/Documents/android-ndk-r23b\"\n    export ANDROIDAPI=\"36\"  # Target API version of your application\n    export NDKAPI=\"21\"  # Minimum supported API version of your application\n    export ANDROIDNDKVER=\"r10e\"  # Version of the NDK you installed\n\nYou have the possibility to configure on any command the PATH to the SDK, NDK and Android API using:\n\n- :code:`--sdk-dir PATH` as an equivalent of `$ANDROIDSDK`\n- :code:`--ndk-dir PATH` as an equivalent of `$ANDROIDNDK`\n- :code:`--android-api VERSION` as an equivalent of `$ANDROIDAPI`\n- :code:`--ndk-api VERSION` as an equivalent of `$NDKAPI`\n- :code:`--ndk-version VERSION` as an equivalent of `$ANDROIDNDKVER`\n\n\nUsage\n-----\n\nBuild a Kivy or SDL2 application\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo build your application, you need to specify name, version, a package\nidentifier, the bootstrap you want to use (`sdl2` for kivy or sdl2 apps)\nand the requirements::\n\n    p4a apk --private $HOME/code/myapp --package=org.example.myapp --name \"My application\" --version 0.1 --bootstrap=sdl2 --requirements=python3,kivy\n\n**Note on** ``--requirements``: **you must add all\nlibraries/dependencies your app needs to run.**\nExample: ``--requirements=python3,kivy,vispy``. For an SDL2 app,\n`kivy` is not needed, but you need to add any wrappers you might\nuse (e.g. `pysdl2`).\n\nThis `p4a apk ...` command builds a distribution with `python3`,\n`kivy`, and everything else you specified in the requirements.\nIt will be packaged using a SDL2 bootstrap, and produce\nan `.apk` file.\n\n*Compatibility notes:*\n\n- Python 2 is no longer supported by python-for-android. The last release supporting Python 2 was v2019.10.06.\n\n\nBuild a WebView application\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo build your application, you need to have a name, version, a package\nidentifier, and explicitly use the webview bootstrap, as\nwell as the requirements::\n\n    p4a apk --private $HOME/code/myapp --package=org.example.myapp --name \"My WebView Application\" --version 0.1 --bootstrap=webview --requirements=flask --port=5000\n\n**Please note as with kivy/SDL2, you need to specify all your\nadditional requirements/dependencies.**\n\nYou can also replace flask with another web framework.\n\nReplace ``--port=5000`` with the port on which your app will serve a\nwebsite. The default for Flask is 5000.\n\n\nBuild a Service library archive\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo build an android archive (.aar), containing an android service , you need a name, version, package identifier, explicitly use the \nservice_library bootstrap, and declare service entry point (See :ref:`services <arbitrary_scripts_services>` for more options), as well as the requirements and arch(s)::\n\n    p4a aar --private $HOME/code/myapp --package=org.example.myapp --name \"My library\" --version 0.1 --bootstrap=service_library --requirements=python3 --release --service=myservice:service.py --arch=arm64-v8a --arch=armeabi-v7a\n\n\nYou can then call the generated Java entrypoint(s) for your Python service(s) in other apk build frameworks.\n\n\nExporting the Android App Bundle (aab) for distributing it on Google Play\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nStarting from August 2021 for new apps and from November 2021 for updates to existings apps,\nGoogle Play Console will require the Android App Bundle instead of the long lived apk.\n\npython-for-android handles by itself the needed work to accomplish the new requirements::\n\n    p4a aab --private $HOME/code/myapp --package=org.example.myapp --name=\"My App\" --version 0.1 --bootstrap=sdl2 --requirements=python3,kivy --arch=arm64-v8a --arch=armeabi-v7a --release\n\nThis `p4a aab ...` command builds a distribution with `python3`,\n`kivy`, and everything else you specified in the requirements.\nIt will be packaged using a SDL2 bootstrap, and produce\nan `.aab` file that contains binaries for both `armeabi-v7a` and `arm64-v8a` ABIs.\n\nThe Android App Bundle, is supposed to be used for distributing your app.\nIf you need to test it locally, on your device, you can use `bundletool <https://developer.android.com/studio/command-line/bundletool>`\n\nOther options\n~~~~~~~~~~~~~\n\nYou can pass other command line arguments to control app behaviours\nsuch as orientation, wakelock and app permissions. See\n:ref:`bootstrap_build_options`.\n\n\n\nRebuild everything\n~~~~~~~~~~~~~~~~~~\n\nIf anything goes wrong and you want to clean the downloads and builds to retry everything, run::\n\n    p4a clean_all\n\nIf you just want to clean the builds to avoid redownloading dependencies, run::\n\n    p4a clean_builds && p4a clean_dists\n\nGetting help\n~~~~~~~~~~~~\n\nIf something goes wrong and you don't know how to fix it, add the\n``--debug`` option and post the output log to the `kivy-users Google\ngroup <https://groups.google.com/forum/#!forum/kivy-users>`__ or the\nkivy `#support Discord channel <https://chat.kivy.org/>`_.\n\nSee :doc:`troubleshooting` for more information.\n\n\nAdvanced usage\n--------------\n\nRecipe management\n~~~~~~~~~~~~~~~~~\n\nYou can see the list of the available recipes with::\n\n    p4a recipes\n\nIf you are contributing to p4a and want to test a recipes again,\nyou need to clean the build and rebuild your distribution::\n\n    p4a clean_recipe_build RECIPENAME\n    p4a clean_dists\n    # then rebuild your distribution\n\nYou can write \"private\" recipes for your application, just create a\n``p4a-recipes`` folder in your build directory, and place a recipe in\nit (edit the ``__init__.py``)::\n\n    mkdir -p p4a-recipes/myrecipe\n    touch p4a-recipes/myrecipe/__init__.py\n\nDistribution management\n~~~~~~~~~~~~~~~~~~~~~~~\n\nEvery time you start a new project, python-for-android will internally\ncreate a new distribution (an Android build project including Python\nand your other dependencies compiled for Android), according to the\nrequirements you added on the command line. You can force the reuse of\nan existing distribution by adding::\n\n   p4a apk --dist_name=myproject ...\n\nThis will ensure your distribution will always be built in the same\ndirectory, and avoids using more disk space every time you adjust a\nrequirement.\n\nYou can list the available distributions::\n\n    p4a distributions\n\nAnd clean all of them::\n\n    p4a clean_dists\n\nConfiguration file\n~~~~~~~~~~~~~~~~~~\n\npython-for-android checks in the current directory for a configuration\nfile named ``.p4a``. If found, it adds all the lines as options to the\ncommand line. For example, you can add the options you would always\ninclude such as::\n\n    --dist_name my_example\n    --android_api 27\n    --requirements kivy,openssl\n\nOverriding recipes sources\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can override the source of any recipe using the\n``$P4A_recipename_DIR`` environment variable. For instance, to test\nyour own Kivy branch you might set::\n\n    export P4A_kivy_DIR=/home/username/kivy\n\nThe specified directory will be copied into python-for-android instead\nof downloading from the normal url specified in the recipe.\n\nsetup.py file (experimental)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf your application is also packaged for desktop using `setup.py`,\nyou may want to use your `setup.py` instead of the\n``--requirements`` option to avoid specifying things twice.\nFor that purpose, check out :doc:`distutils`\n\nGoing further\n~~~~~~~~~~~~~\n\nSee the other pages of this doc for more information on specific topics:\n\n- :doc:`buildoptions`\n- :doc:`commands`\n- :doc:`recipes`\n- :doc:`bootstraps`\n- :doc:`apis`\n- :doc:`troubleshooting`\n- :doc:`contribute`\n"
  },
  {
    "path": "doc/source/recipes.rst",
    "content": "\nRecipes\n=======\n\nThis page describes how python-for-android (p4a) compilation recipes\nwork, and how to build your own. If you just want to build an APK,\nignore this and jump straight to the :doc:`quickstart`.\n\nRecipes are special scripts for compiling and installing different programs\n(including Python modules) into a p4a distribution. They are necessary\nto take care of compilation for any compiled components, as these must\nbe compiled for Android with the correct architecture.\n\npython-for-android comes with many recipes for popular modules. No\nrecipe is necessary for Python modules which have no\ncompiled components; these are installed automatically via pip.\nIf you are new to building recipes, it is recommended that you first\nread all of this page, at least up to the Recipe reference\ndocumentation. The different recipe sections include a number of\nexamples of how recipes are built or overridden for specific purposes.\n\n\nCreating your own Recipe\n------------------------\n\nThe formal reference documentation of the Recipe\nclass can be found in the `Recipe class <recipe_class_>`_ section and below.\n\nCheck the `recipe template section <recipe_template_>`_ for a template\nthat combines all of these ideas, in which you can replace whichever\ncomponents you like.\n\nThe basic declaration of a recipe is as follows::\n\n  class YourRecipe(Recipe):\n\n      url = 'http://example.com/example-{version}.tar.gz'\n      version = '2.0.3'\n      md5sum = '4f3dc9a9d857734a488bcbefd9cd64ed'\n      \n      patches = ['some_fix.patch']  # Paths relative to the recipe dir\n\n      depends = ['kivy', 'sdl2']  # These are just examples\n      conflicts = ['generickndkbuild']\n    \n  recipe = YourRecipe()\n\nSee the `Recipe class documentation <recipe_class_>`_ for full\ninformation about each parameter.\n\nThese core options are vital for all recipes, though the url may be\nomitted if the source is somehow loaded from elsewhere.\n\nYou must include ``recipe = YourRecipe()``. This variable is accessed\nwhen the recipe is imported.\n\nSpecifying the URL\n------------------\n\n.. note:: The url includes the ``{version}`` tag. You should only\n          access the url with the ``versioned_url`` property, which\n          replaces this with the version attribute.\n\n.. note:: you may need to specify additional headers to allow python-for-android\n          to download the archive. Specify your additional headers by setting the\n          download_headers property.\n\nFor example, when downloading from a private github repository, you can specify the following:\n\n(For the download_headers property in your recipe)\n```\n[('Authorization', 'token <your personal access token>'), ('Accept', 'application/vnd.github+json')]\n```\n\n(For the DOWNLOAD_HEADERS_my-package-name environment variable - specify as a JSON formatted set of values)\n.. code-block:: bash\n\n    [[\"Authorization\",\"token <your personal access token>\"],[\"Accept\", \"application/vnd.github+json\"]]\n\nThe actual build process takes place via three core methods::\n\n      def prebuild_arch(self, arch):\n          super().prebuild_arch(arch)\n          # Do any pre-initialisation\n\n      def build_arch(self, arch):\n          super().build_arch(arch)\n          # Do the main recipe build\n         \n      def postbuild_arch(self, arch):\n          super().build_arch(arch)\n          # Do any clearing up\n\nThese methods are always run in the listed order; prebuild, then\nbuild, then postbuild.\n\nIf you defined a url for your recipe, you do *not* need to manually\ndownload it, this is handled automatically.\n\nThe recipe will automatically be built in a special isolated build\ndirectory, which you can access with\n:code:`self.get_build_dir(arch.arch)`. You should only work within\nthis directory. It may be convenient to use the ``current_directory``\ncontext manager defined in toolchain.py::\n\n  from pythonforandroid.toolchain import current_directory\n  def build_arch(self, arch):\n      super().build_arch(arch)\n      with current_directory(self.get_build_dir(arch.arch)):\n          with open('example_file.txt', 'w') as fileh:\n              fileh.write('This is written to a file within the build dir')\n  \nThe argument to each method, ``arch``, is an object relating to the\narchitecture currently being built for. You can mostly ignore it,\nthough may need to use the arch name ``arch.arch``.\n          \n.. note:: You can also implement arch-specific versions of each\n          method, which are called (if they exist) by the superclass,\n          e.g. ``def prebuild_armeabi(self, arch)``.\n          \n\nThis is the core of what's necessary to write a recipe, but has not\ncovered any of the details of how one actually writes code to compile\nfor android. This is covered in the next sections, including the\n`standard mechanisms <standard_mechanisms_>`_ used as part of the\nbuild, and the details of specific recipe classes for Python, Cython,\nand some generic compiled recipes. If your module is one of the\nlatter, you should use these later classes rather than reimplementing\nthe functionality from scratch.\n\n.. _standard_mechanisms:\n\nMethods and tools to help with compilation\n------------------------------------------\n\nPatching modules before installation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can easily apply patches to your recipes by adding them to the\n``patches`` declaration, e.g.::\n\n      patches = ['some_fix.patch',\n                 'another_fix.patch']  \n      \nThe paths should be relative to the recipe file. Patches are\nautomatically applied just once (i.e. not reapplied the second time\npython-for-android is run).\n\nYou can also use the helper functions in ``pythonforandroid.patching``\nto apply patches depending on certain conditions, e.g.::\n\n  from pythonforandroid.patching import will_build, is_arch\n\n  ...\n\n  class YourRecipe(Recipe):\n      \n      patches = [('x86_patch.patch', is_arch('x86')),\n                 ('sdl2_compatibility.patch', will_build('sdl2'))]\n\n      ...\n      \nYou can include your own conditions by passing any function as the\nsecond entry of the tuple. It will receive the ``arch`` (e.g. x86,\narmeabi) and ``recipe`` (i.e. the Recipe object) as kwargs. The patch\nwill be applied only if the function returns True.\n\n\nInstalling libs\n~~~~~~~~~~~~~~~\n\nSome recipes generate .so files that must be manually copied into the\nandroid project. You can use code like the following to accomplish\nthis, copying to the correct lib cache dir::\n\n    def build_arch(self, arch):\n        do_the_build()  # e.g. running ./configure and make\n        \n        import shutil\n        shutil.copyfile('a_generated_binary.so', \n                        self.ctx.get_libs_dir(arch.arch))\n                        \nAny libs copied to this dir will automatically be included in the\nappropriate libs dir of the generated android project.\n\nCompiling for the Android architecture\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhen performing any compilation, it is vital to do so with appropriate\nenvironment variables set, ensuring that the Android libraries are\nproperly linked and the compilation target is the correct\narchitecture.\n\nYou can get a dictionary of appropriate environment variables with the\n``get_recipe_env`` method. You should make sure to set this\nenvironment for any processes that you call. It is convenient to do\nthis using the ``sh`` module as follows::\n\n  def build_arch(self, arch):\n      super().build_arch(arch)\n      env = self.get_recipe_env(arch)\n      sh.echo('$PATH', _env=env)  # Will print the PATH entry from the\n                                  # env dict\n\nYou can also use the ``shprint`` helper function from the p4a\ntoolchain module, which will print information about the process and\nits current status::\n\n  from pythonforandroid.toolchain import shprint\n  shprint(sh.echo, '$PATH', _env=env)\n\nYou can also override the ``get_recipe_env`` method to add new env\nvars for use in your recipe. For instance, the Kivy recipe does\nthe following when compiling for SDL2, in order to tell Kivy what\nbackend to use::\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['USE_SDL2'] = '1'\n\n        env['KIVY_SDL2_PATH'] = ':'.join([\n            join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include'),\n            join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_image'),\n            join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_mixer'),\n            join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'),\n            ])\n        return env\n  \n.. warning:: When using the sh module like this the new env *completely\n          replaces* the normal environment, so you must define any env\n          vars you want to access.\n          \nIncluding files with your recipe\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe should_build method\n~~~~~~~~~~~~~~~~~~~~~~~\n    \nThe Recipe class has a ``should_build`` method, which returns a\nboolean. This is called for each architecture before running\n``build_arch``, and if it returns False then the build is\nskipped. This is useful to avoid building a recipe more than once for\ndifferent dists.\n\nBy default, should_build returns True, but you can override it however\nyou like. For instance, PythonRecipe and its subclasses all replace it\nwith a check for whether the recipe is already installed in the Python\ndistribution::\n\n    def should_build(self, arch):\n        name = self.site_packages_name\n        if name is None:\n            name = self.name\n        if self.ctx.has_package(name):\n            info('Python package already exists in site-packages')\n            return False\n        info('{} apparently isn\\'t already in site-packages'.format(name))\n        return True\n\n\n\nUsing a PythonRecipe\n--------------------\n\nIf your recipe is to install a Python module without compiled\ncomponents, you should use a PythonRecipe. This overrides\n``build_arch`` to automatically call the normal ``python setup.py\ninstall`` with an appropriate environment.\n\nFor instance, the following is all that's necessary to create a recipe\nfor the Vispy module::\n\n  from pythonforandroid.recipe import PythonRecipe\n  class VispyRecipe(PythonRecipe):\n      version = 'master'\n      url = 'https://github.com/vispy/vispy/archive/{version}.zip'\n\n      depends = ['python3', 'numpy']\n      \n      site_packages_name = 'vispy'\n\n  recipe = VispyRecipe()\n  \nThe ``site_packages_name`` is a new attribute that identifies the\nfolder in which the module will be installed in the Python\npackage. This is only essential to add if the name is different to the\nrecipe name. It is used to check if the recipe installation can be\nskipped, which is the case if the folder is already present in the\nPython installation.\n  \nFor reference, the code that accomplishes this is the following::\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        self.install_python_package()\n\n    def install_python_package(self):\n        '''Automate the installation of a Python package (or a cython\n        package where the cython components are pre-built).'''\n        arch = self.filtered_archs[0]\n        env = self.get_recipe_env(arch)\n\n        info('Installing {} into site-packages'.format(self.name))\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            hostpython = sh.Command(self.ctx.hostpython)\n\n            shprint(hostpython, 'setup.py', 'install', '-O2', _env=env)\n            \nThis combines techniques and tools from the above documentation to\ncreate a generic mechanism for all Python modules.\n\n.. note:: The hostpython is the path to the Python binary that should\n          be used for any kind of installation. You *must* run Python\n          in a similar way if you need to do so in any of your own\n          recipes.\n\n\nUsing a CythonRecipe\n--------------------\n\nIf your recipe is to install a Python module that uses Cython, you\nshould use a CythonRecipe. This overrides ``build_arch`` to both build\nthe cython components and to install the Python module just like a\nnormal PythonRecipe.\n\nFor instance, the following is all that's necessary to make a recipe\nfor Kivy::\n\n  class KivyRecipe(CythonRecipe):\n      version = 'stable'\n      url = 'https://github.com/kivy/kivy/archive/{version}.zip'\n      name = 'kivy'\n\n      depends = ['sdl2', 'pyjnius']\n\n  recipe = KivyRecipe()\n  \nFor reference, the code that accomplishes this is the following::\n\n    def build_arch(self, arch):\n        Recipe.build_arch(self, arch)  # a hack to avoid calling\n                                       # PythonRecipe.build_arch\n        self.build_cython_components(arch)\n        self.install_python_package()  # this is the same as in a PythonRecipe\n\n    def build_cython_components(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            hostpython = sh.Command(self.ctx.hostpython)\n            \n            # This first attempt *will* fail, because cython isn't\n            # installed in the hostpython\n            try:\n                shprint(hostpython, 'setup.py', 'build_ext', _env=env)\n            except sh.ErrorReturnCode_1:\n                pass\n\n            # ...so we manually run cython from the user's system\n            shprint(sh.find, self.get_build_dir('armeabi'), '-iname', '*.pyx', '-exec',\n                    self.ctx.cython, '{}', ';', _env=env)\n\n            # now cython has already been run so the build works\n            shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env)\n\n            # stripping debug symbols lowers the file size a lot\n            build_lib = glob.glob('./build/lib*')\n            shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',\n                    env['STRIP'], '{}', ';', _env=env)\n\nThe failing build and manual cythonisation is necessary, firstly to\nmake sure that any .pyx files have been generated by setup.py, and\nsecondly because cython isn't installed in the hostpython build.\n\nThis may actually fail if the setup.py tries to import cython before\nmaking any .pyx files (in which case it crashes too early), although\nthis is probably not usually an issue. If this happens to you, try\npatching to remove this import or make it fail quietly.\n \nOther than this, these methods follow the techniques in the above\ndocumentation to make a generic recipe for most cython based modules.\n\nUsing a CompiledComponentsPythonRecipe\n--------------------------------------\n\nThis is similar to a CythonRecipe but is intended for modules like\nnumpy which include compiled but non-cython components. It uses a\nsimilar mechanism to compile with the right environment.\n\nThis isn't documented yet because it will probably be changed so that\nCythonRecipe inherits from it (to avoid code duplication).\n\n\nUsing an NDKRecipe\n------------------\n\nIf you are writing a recipe not for a Python module but for something\nthat would normally go in the JNI dir of an Android project (i.e. it\nhas an ``Application.mk`` and ``Android.mk`` that the Android build\nsystem can use), you can use an NDKRecipe to automatically set it\nup. The NDKRecipe overrides the normal ``get_build_dir`` method to\nplace things in the Android project.\n\n.. warning:: The NDKRecipe does *not* currently actually call\n             ndk-build, you must add this call (for your module) by\n             manually making a build_arch method. This may be fixed\n             later.\n\nFor instance, the following recipe is all that's necessary to place\nSDL2_ttf in the jni dir. This is built later by the SDL2 recipe, which\ncalls ndk-build with this as a dependency::\n\n class LibSDL2TTF(NDKRecipe):\n     version = '2.0.12'\n     url = 'https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-{version}.tar.gz'\n     dir_name = 'SDL2_ttf'\n\n recipe = LibSDL2TTF()\n\nThe dir_name argument is a new class attribute that tells the recipe\nwhat the jni dir folder name should be. If it is omitted, the recipe\nname is used. Be careful here, sometimes the folder name is important,\nespecially if this folder is a dependency of something else.\n\n.. _recipe_template:\n\nA Recipe template\n-----------------\n\nThe following template includes all the recipe sections you might\nuse. None are compulsory, feel free to delete method\noverrides if you do not use them::\n\n    from pythonforandroid.toolchain import Recipe, shprint, current_directory\n    from os.path import exists, join\n    import sh\n    import glob\n\n\n    class YourRecipe(Recipe):\n        # This could also inherit from PythonRecipe etc. if you want to\n        # use their pre-written build processes\n\n        version = 'some_version_string'\n        url = 'http://example.com/example-{version}.tar.gz'\n        # {version} will be replaced with self.version when downloading\n\n        depends = ['python3', 'numpy']  # A list of any other recipe names\n                                        # that must be built before this\n                                        # one\n\n        conflicts = []  # A list of any recipe names that cannot be built\n                        # alongside this one\n\n        def get_recipe_env(self, arch):\n            env = super().get_recipe_env(arch)\n            # Manipulate the env here if you want\n            return env\n\n        def should_build(self, arch):\n            # Add a check for whether the recipe is already built if you\n            # want, and return False if it is.\n            return True\n\n        def prebuild_arch(self, arch):\n            super().prebuild_arch(self)\n            # Do any extra prebuilding you want, e.g.:\n            self.apply_patch('path/to/patch.patch')\n\n        def build_arch(self, arch):\n            super().build_arch(self)\n            # Build the code. Make sure to use the right build dir, e.g.\n            with current_directory(self.get_build_dir(arch.arch)):\n                sh.ls('-lathr')  # Or run some commands that actually do\n                                 # something\n\n        def postbuild_arch(self, arch):\n            super().prebuild_arch(self)\n            # Do anything you want after the build, e.g. deleting\n            # unnecessary files such as documentation\n\n\n    recipe = YourRecipe()\n\n\nExamples of recipes\n-------------------\n\nThis documentation covers most of what is ever necessary to make a\nrecipe work. For further examples, python-for-android includes many\nrecipes for popular modules, which are an excellent resource to find\nout how to add your own. You can find these in the `python-for-android\nGithub page\n<https://github.com/kivy/python-for-android/tree/master/pythonforandroid/recipes>`__.\n\n\n.. _recipe_class:\n\nThe ``Recipe`` class\n--------------------\n\nThe ``Recipe`` is the base class for all p4a recipes. The core\ndocumentation of this class is given below, followed by discussion of\nhow to create your own Recipe subclass.\n\n.. autoclass:: toolchain.Recipe\n   :members:\n   :member-order: bysource\n\n\n\n"
  },
  {
    "path": "doc/source/services.rst",
    "content": "Services\n========\n\npython-for-android supports the use of Android Services, background\ntasks running in separate processes. These are the closest Android\nequivalent to multiprocessing on e.g. desktop platforms, and it is not\npossible to use normal multiprocessing on Android. Services are also\nthe only way to run code when your app is not currently opened by the user.\n\nServices must be declared when building your APK. Each one\nwill have its own main.py file with the Python script to be run.\nPlease note that python-for-android explicitly runs services as separated\nprocesses by having a colon \":\" in the beginning of the name assigned to\nthe ``android:process`` attribute of the ``AndroidManifest.xml`` file.\nThis is not the default behavior, see `Android service documentation\n<https://developer.android.com/guide/topics/manifest/service-element>`__.\nYou can communicate with the service process from your app using e.g.\n`osc <https://pypi.org/project/python-osc/>`__ or (a heavier option)\n`twisted <https://twisted.org/>`__.\n\nService creation\n----------------\n\nThere are two ways to have services included in your APK.\n\nService folder\n~~~~~~~~~~~~~~\n\nThis is the older method of handling services. It is\nrecommended to use the second method (below) where possible.\n\nCreate a folder named ``service`` in your app directory, and add a\nfile ``service/main.py``. This file should contain the Python code\nthat you want the service to run.\n\nTo start the service, use the :code:`start_service` function from the\n:code:`android` module (you may need to add ``android`` to your app\nrequirements)::\n\n    import android\n    android.start_service(title='service name',\n                          description='service description',\n                          arg='argument to service')\n\n.. _arbitrary_scripts_services:\n\nArbitrary service scripts\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis method is recommended for non-trivial use of services as it is\nmore flexible, supporting multiple services and a wider range of\noptions.\n\nTo create the service, create a python script with your service code\nand add a :code:`--service=myservice:PATH_TO_SERVICE_PY` argument\nwhen calling python-for-android, or in buildozer.spec, a\n:code:`services = myservice:PATH_TO_SERVICE_PY` [app] setting.\n\nThe ``myservice`` name before the colon is the name of the service\nclass, via which you will interact with it later. \n\nThe ``PATH_TO_SERVICE_PY`` is the relative path to the service entry point (like ``services/myservice.py``)\n\nYou can optionally specify the following parameters:\n - :code:`:foreground` for launching a service as an Android foreground service\n - :code:`:sticky` for launching a service that gets restarted by the Android OS on exit/error\n - :code:`:foregroundServiceType=TYPE` to specify the type of foreground service,\n   where TYPE is one of the valid Android foreground service types\n   (see `Android documentation <https://developer.android.com/develop/background-work/services/fgs/service-types>`__\n   for more details). You can specify multiple types separated by a pipe\n   character \"|\", e.g. :code:`:foregroundServiceType=location|mediaPlayback`. Mandatory\n   if :code:`:foreground` is used on Android 14+.\n\nFull command with all the optional parameters included would be: \n:code:`--service=myservice:services/myservice.py:foreground:sticky:foregroundServiceType=location`\n\nYou can add multiple\n:code:`--service` arguments to include multiple services, or separate\nthem with a comma in buildozer.spec, all of which you will later be\nable to stop and start from your app.\n\nTo run the services (i.e. starting them from within your main app\ncode), you must use PyJNIus to interact with the java class\npython-for-android creates for each one, as follows::\n\n    from jnius import autoclass\n    service = autoclass('your.package.domain.package.name.ServiceMyservice')\n    mActivity = autoclass('org.kivy.android.PythonActivity').mActivity\n    argument = ''\n    service.start(mActivity, argument)\n\nHere, ``your.package.domain.package.name`` refers to the package identifier\nof your APK.\n\nIf you are using buildozer, the identifier is set by the ``package.name``\nand ``package.domain`` values in your buildozer.spec file.\nThe name of the service is ``ServiceMyservice``, where ``Myservice``\nis the name specified by one of the ``services`` values, but with the first\nletter upper case. \n\nIf you are using python-for-android directly, the identifier is set by the ``--package``\nargument to python-for-android. The name of the service is ``ServiceMyservice``,\nwhere ``Myservice`` is the identifier that was previously passed to the ``--service``\nargument, but with the first letter upper case. You must also pass the\n``argument`` parameter even if (as here) it is an empty string. If you\ndo pass it, the service can make use of this argument.\n\nThe service argument is made available to your service via the\n'PYTHON_SERVICE_ARGUMENT' environment variable. It is exposed as a simple\nstring, so if you want to pass in multiple values, we would recommend using\nthe json module to encode and decode more complex data.\n::\n\n    from os import environ\n    argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')\n    \nTo customize the notification icon, title, and text use three optional\narguments to service.start()::\n\n    service.start(mActivity, 'small_icon', 'title', 'content' , argument)\n\nWhere 'small_icon' is the name of an Android drawable or mipmap resource,\nand 'title' and 'content' are strings in the notification.\n\nServices support a range of options and interactions not yet\ndocumented here but all accessible via calling other methods of the\n``service`` reference.\n\n.. note::\n\n    The app root directory for Python imports will be in the app\n    root folder even if the service file is in a subfolder. To import from\n    your service folder you must use e.g.  ``import service.module``\n    instead of ``import module``, if the service file is in the\n    ``service/`` folder.\n\nService auto-restart\n~~~~~~~~~~~~~~~~~~~~\n\nIt is possible to make services restart automatically when they exit by\ncalling ``setAutoRestartService(True)`` on the service object.\nThe call to this method should be done within the service code::\n\n    from jnius import autoclass\n    PythonService = autoclass('org.kivy.android.PythonService')\n    PythonService.mService.setAutoRestartService(True)\n"
  },
  {
    "path": "doc/source/testing_pull_requests.rst",
    "content": "Testing an python-for-android pull request\n==========================================\n\nIn order to test a pull request, we recommend to consider the following points:\n\n  #. of course, check if the overall thing makes sense\n  #. is the CI passing? if not what specifically fails\n  #. is it working locally at compile time?\n  #. is it working on device at runtime?\n\nThis document will focus on the third point:\n`is it working locally at compile time?` so we will give some hints about how\nto proceed in order to create a local copy of the pull requests and build an\napk. We expect that the contributors has enough criteria/knowledge to perform\nthe other steps mentioned, so let's begin...\n\nTo create an apk from a python-for-android pull request we contemplate three\npossible scenarios:\n\n  - using python-for-android commands directly from the pull request files\n    that we want to test, without installing it (the recommended way for most\n    of the test cases)\n  - installing python-for-android using the github's branch of the pull request\n  - using buildozer and a custom app\n\nWe will explain the first two methods using one of the distributed\npython-for-android test apps and we assume that you already have the\npython-for-android dependencies installed. For the `buildozer` method we also\nexpect that you already have a a properly working app to test and a working\ninstallation/configuration of `buildozer`. There is one step that it's shared\nwith all the testing methods that we propose in here...we named it\n`Common steps`.\n\n\nCommon steps\n^^^^^^^^^^^^\nThe first step to do it's to get a copy of the pull request, we can do it of\nseveral ways, and that it will depend of the circumstances but all the methods\npresented here will do the job, so...\n\nFetch the pull request by number\n--------------------------------\nFor the example, we will use `1901` for the example) and the pull request\nbranch that we will use is `feature-fix-numpy`, then you will use a variation\nof the following git command:\n`git fetch origin pull/<#>/head:<local_branch_name>`, e.g.:\n\n.. code-block:: bash\n\n    git fetch upstream pull/1901/head:feature-fix-numpy\n\n.. note:: Notice that we fetch from `upstream`, since that is the original\n          project, where the pull request is supposed to be\n\n.. tip:: The amount of work of some users maybe worth it to add his remote\n       to your fork's git configuration, to do so with the imaginary\n       github user `Obi-Wan Kenobi` which nickname is `obiwankenobi`, you\n       will do:\n\n          .. code-block:: bash\n\n              git remote add obiwankenobi https://github.com/obiwankenobi/python-for-android.git\n\n       And to fetch the pull request branch that we put as example, you\n       would do:\n\n          .. code-block:: bash\n\n              git fetch obiwankenobi\n              git checkout obiwankenobi/feature-fix-numpy\n\n\nClone the pull request branch from the user's fork\n--------------------------------------------------\nSometimes you may prefer to use directly the fork of the user, so you will get\nthe nickname of the user who created the pull request, let's take the same\nimaginary user than before `obiwankenobi`:\n\n    .. code-block:: bash\n\n        git clone -b feature-fix-numpy \\\n            --single-branch \\\n            https://github.com/obiwankenobi/python-for-android.git \\\n            p4a-feature-fix-numpy\n\nHere's the above command explained line by line:\n\n- `git clone -b feature-fix-numpy`: we tell git that we want to clone the\n  branch named `feature-fix-numpy`\n- `--single-branch`: we tell git that we only want that branch\n- `https://github.com/obiwankenobi/python-for-android.git`: noticed the\n  nickname of the user that created the pull request: `obiwankenobi` in the\n  middle of the line? that should be changed as needed for each pull\n  request that you want to test\n- `p4a-feature-fix-numpy`: the name of the cloned repository, so we can\n  have multiple clones of different prs in the same folder\n\n.. note:: You can view the author/branch information looking at the\n          subtitle of the pull request, near the pull request status (expected\n          an `open` status)\n\nUsing python-for-android commands directly from the pull request files\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n- Enter inside the directory of the cloned repository in the above\n  step and run p4a command with proper args, e.g. (to test an modified\n  `pycryptodome` recipe)\n\n.. code-block:: bash\n\n    cd p4a-feature-fix-numpy\n    PYTHONPATH=. python3 -m pythonforandroid.toolchain apk \\\n        --private=testapps/on_device_unit_tests/test_app \\\n        --dist-name=dist_unit_tests_app_pycryptodome \\\n        --package=org.kivy \\\n        --name=unit_tests_app_pycryptodome \\\n        --version=0.1 \\\n        --requirements=sdl2,pyjnius,kivy,python3,pycryptodome \\\n        --ndk-dir=/media/DEVEL/Android/android-ndk-r20 \\\n        --sdk-dir=/media/DEVEL/Android/android-sdk-linux \\\n        --android-api=36 \\\n        --arch=arm64-v8a \\\n        --permission=VIBRATE \\\n        --debug\n\nThings that you should know:\n\n\n    - The example above will build an test app we will make use of the files of\n      the `on device unit tests` test app but we don't use the setup\n      file to build it so we must tell python-for-android what we want via\n      arguments\n    - be sure to at least edit the following arguments when running the above\n      command, since the default set in there it's unlikely that match your\n      installation:\n\n          - `--ndk-dir`: An absolute path to your android's NDK dir\n          - `--sdk-dir`: An absolute path to your android's SDK dir\n          - `--debug`: this one enables the debug mode of python-for-android,\n            which will show all log messages of the build. You can omit this\n            one but it's worth it to be mentioned, since this it's useful to us\n            when trying to find the source of the problem when things goes\n            wrong\n    - The apk generated by the above command should be located at the root of\n      of the cloned repository, were you run the command to build the apk\n    - The testapps distributed with python-for-android are located at\n      `testapps` folder under the main folder project\n    - All the builds of python-for-android are located at\n      `~/.local/share/python-for-android`\n    - You should have a downloaded copy of the android's NDK and SDK\n\nInstalling python-for-android using the github's branch of the pull request\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n- Enter inside the directory of the cloned repository mentioned in\n  `Common steps` and install it via pip, e.g.:\n\n.. code-block:: bash\n\n    cd p4a-feature-fix-numpy\n    pip3 install . --upgrade --user\n\n- Now, go inside the `testapps/on_device_unit_tests` directory (we assume that\n  you still are inside the cloned repository)\n\n.. code-block:: bash\n\n    cd testapps/on_device_unit_tests\n\n- Run the build of the apk via the freshly installed copy of python-for-android\n  by running a similar command than below\n\n.. code-block:: bash\n\n    python3 setup.py apk \\\n        --ndk-dir=/media/DEVEL/Android/android-ndk-r20 \\\n        --sdk-dir=/media/DEVEL/Android/android-sdk-linux \\\n        --android-api=36 \\\n        --arch=arm64-v8a \\\n        --debug\n\n\nThings that you should know:\n\n    - In the example above, we override some variables that are set in\n      `setup.py`, you could also override them by editing this file\n    - be sure to at least edit the following arguments when running the above\n      command, since the default set in there it's unlikely that match your\n      installation:\n\n        - `--ndk-dir`: An absolute path to your android's NDK dir\n        - `--sdk-dir`: An absolute path to your android's SDK dir\n\n.. tip:: if you don't want to mess up with the system's python, you could do\n          the same steps but inside a virtualenv\n\n.. warning:: Once you finish the pull request tests remember to go back to the\n             master or develop versions of python-for-android, since you just\n             installed the python-for-android files of the `pull request`\n\nUsing buildozer with a custom app\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n- Edit your `buildozer.spec` file. You should search for the key\n  `p4a.source_dir` and set the right value so in the example posted in\n  `Common steps` it would look like this::\n\n    p4a.source_dir = /home/user/p4a_pull_requests/p4a-feature-fix-numpy\n\n- Run you buildozer command as usual, e.g.::\n\n    buildozer android debug p4a --dist-name=dist-test-feature-fix-numpy\n\n.. note:: this method has the advantage, can be run without installing the\n          pull request version of python-for-android nor the android's\n          dependencies but has one problem...when things goes wrong you must\n          determine if it's a buildozer issue or a python-for-android one\n\n.. warning:: Once you finish the pull request tests remember to comment/edit\n             the `p4a.source_dir` constant that you just edited to test the\n             pull request\n\n.. tip:: this method it's useful for developing pull requests since you can\n         edit `p4a.source_dir` to point to your python-for-android fork and you\n         can test any branch you want only switching branches with:\n         `git checkout <branch-name>` from inside your python-for-android fork\n"
  },
  {
    "path": "doc/source/troubleshooting.rst",
    "content": ".. _troubleshooting:\n\nTroubleshooting\n===============\n\nDebug output\n------------\n\nAdd the ``--debug`` option to any python-for-android command to see\nfull debug output including the output of all the external tools used\nin the compilation and packaging steps.\n\nIf reporting a problem by email or Discord, it is usually helpful to\ninclude this full log, e.g. via a `pastebin\n<https://pastebin.ubuntu.com/>`_ or `Github gist\n<https://gist.github.com/>`_.\n\nDebugging on Android\n--------------------\n\nWhen a python-for-android APK doesn't work, often the only indication\nthat you get is that it closes. It is important to be able to find out\nwhat went wrong.\n\npython-for-android redirects Python's stdout and stderr to the Android\nlogcat stream. You can see this by enabling developer mode on your\nAndroid device, enabling adb on the device, connecting it to your PC\n(you should see a notification that USB debugging is connected) and\nrunning ``adb logcat``. If adb is not in your PATH, you can find it at\n``/path/to/Android/SDK/platform-tools/adb``, or access it through\npython-for-android with the shortcut::\n\n    python-for-android logcat\n\nor::\n\n    python-for-android adb logcat\n\nRunning logcat command gives a lot of information about what Android is\ndoing. You can usually see important lines by using logcat's built in\nfunctionality to see only lines with the ``python`` tag (or just\ngrepping this).\n\nWhen your app crashes, you'll see the normal Python traceback here, as\nwell as the output of any print statements etc. that your app\nruns. Use these to diagnose the problem just as normal.\n\nThe adb command passes its arguments straight to adb itself, so you\ncan also do other debugging tasks such as ``python-for-android adb\ndevices`` to get the list of connected devices.\n\nFor further information, see the Android docs on `adb\n<https://developer.android.com/tools/adb>`_, and\non `logcat\n<https://developer.android.com/tools/logcat>`_ in\nparticular.\n\nUnpacking an APK\n----------------\n\nIt is sometimes useful to unpack a packaged APK to see what is inside,\nespecially when debugging python-for-android itself.\n\nAPKs are just zip files, so you can extract the contents easily::\n\n  unzip YourApk.apk\n\nAt the top level, this will always contain the same set of files::\n\n  $ ls\n  AndroidManifest.xml  classes.dex  META-INF     res\n  assets               lib          YourApk.apk  resources.arsc\n\nThe user app data (code, images, fonts ..) is packaged into a single tarball contained in the assets folder::\n\n  $ cd assets\n  $ ls\n  private.tar\n\n``private.tar`` is a tarball containing all your packaged\ndata. Extract it::\n\n  $ tar xf private.tar\n\nThis will reveal all the user app data (the files shown below are from the touchtracer demo)::\n\n  $ ls\n  README.txt\t\tandroid.txt\t\ticon.png\t\tmain.pyc\t\tp4a_env_vars.txt\tparticle.png\n  private.tar\t\ttouchtracer.kv\n\nDue to how We're required to ship ABI-specific things in Android App Bundle,\nthe Python installation is packaged separately, as (most of it) is ABI-specific.\n\nFor example, the Python installation for ``arm64-v8a`` is available in ``lib/arm64-v8a/libpybundle.so``\n\n``libpybundle.so`` is a tarball (but named like a library for packaging requirements), that contains our ``_python_bundle``::\n\n  $ tar xf libpybundle.so\n  $ cd _python_bundle\n  $ ls\n  modules\t\tsite-packages\tstdlib.zip\n\nFAQ\n---\n\nCheck out the `online FAQ <https://github.com/kivy/python-for-android/blob/master/FAQ.md>`_ for common\nerrors."
  },
  {
    "path": "pythonforandroid/__init__.py",
    "content": "__version__ = '2024.01.21'\n"
  },
  {
    "path": "pythonforandroid/androidndk.py",
    "content": "import sys\nimport os\n\n\nclass AndroidNDK:\n    \"\"\"\n    This class is used to get the current NDK information.\n    \"\"\"\n\n    ndk_dir = \"\"\n\n    def __init__(self, ndk_dir):\n        self.ndk_dir = ndk_dir\n\n    @property\n    def host_tag(self):\n        \"\"\"\n        Returns the host tag for the current system.\n        Note: The host tag is ``darwin-x86_64`` even on Apple Silicon macs.\n        \"\"\"\n        return f\"{sys.platform}-x86_64\"\n\n    @property\n    def llvm_prebuilt_dir(self):\n        return os.path.join(\n            self.ndk_dir, \"toolchains\", \"llvm\", \"prebuilt\", self.host_tag\n        )\n\n    @property\n    def llvm_bin_dir(self):\n        return os.path.join(self.llvm_prebuilt_dir, \"bin\")\n\n    @property\n    def clang(self):\n        return os.path.join(self.llvm_bin_dir, \"clang\")\n\n    @property\n    def clang_cxx(self):\n        return os.path.join(self.llvm_bin_dir, \"clang++\")\n\n    @property\n    def llvm_binutils_prefix(self):\n        return os.path.join(self.llvm_bin_dir, \"llvm-\")\n\n    @property\n    def llvm_ar(self):\n        return f\"{self.llvm_binutils_prefix}ar\"\n\n    @property\n    def llvm_ranlib(self):\n        return f\"{self.llvm_binutils_prefix}ranlib\"\n\n    @property\n    def llvm_objcopy(self):\n        return f\"{self.llvm_binutils_prefix}objcopy\"\n\n    @property\n    def llvm_objdump(self):\n        return f\"{self.llvm_binutils_prefix}objdump\"\n\n    @property\n    def llvm_readelf(self):\n        return f\"{self.llvm_binutils_prefix}readelf\"\n\n    @property\n    def llvm_strip(self):\n        return f\"{self.llvm_binutils_prefix}strip\"\n\n    @property\n    def llvm_nm(self):\n        return f\"{self.llvm_binutils_prefix}nm\"\n\n    @property\n    def sysroot(self):\n        return os.path.join(self.llvm_prebuilt_dir, \"sysroot\")\n\n    @property\n    def sysroot_include_dir(self):\n        return os.path.join(self.sysroot, \"usr\", \"include\")\n\n    @property\n    def sysroot_lib_dir(self):\n        return os.path.join(self.sysroot, \"usr\", \"lib\")\n\n    @property\n    def libcxx_include_dir(self):\n        return os.path.join(self.sysroot_include_dir, \"c++\", \"v1\")\n"
  },
  {
    "path": "pythonforandroid/archs.py",
    "content": "from os import environ\nfrom os.path import join\nfrom multiprocessing import cpu_count\nimport shutil\n\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import BuildInterruptingException, build_platform\n\n\nclass Arch:\n\n    command_prefix = None\n    '''The prefix for NDK commands such as gcc.'''\n\n    arch = \"\"\n    '''Name of the arch such as: `armeabi-v7a`, `arm64-v8a`, `x86`...'''\n\n    arch_cflags = []\n    '''Specific arch `cflags`, expect to be overwrote in subclass if needed.'''\n\n    common_cflags = [\n        '-target {target}',\n        '-fomit-frame-pointer'\n    ]\n\n    common_cppflags = [\n        '-DANDROID',\n        '-I{ctx.ndk.sysroot_include_dir}',\n        '-I{python_includes}',\n    ]\n\n    common_ldflags = ['-L{ctx_libs_dir}']\n\n    common_ldlibs = ['-lm']\n\n    common_ldshared = [\n        '-pthread',\n        '-shared',\n        '-Wl,-O1',\n        '-Wl,-Bsymbolic-functions',\n    ]\n\n    def __init__(self, ctx):\n        self.ctx = ctx\n\n        # Allows injecting additional linker paths used by any recipe.\n        # This can also be modified by recipes (like the librt recipe)\n        # to make sure that some sort of global resource is available &\n        # linked for all others.\n        self.extra_global_link_paths = []\n\n    def __str__(self):\n        return self.arch\n\n    @property\n    def ndk_lib_dir(self):\n        return join(self.ctx.ndk.sysroot_lib_dir, self.command_prefix)\n\n    @property\n    def ndk_lib_dir_versioned(self):\n        return join(self.ndk_lib_dir, str(self.ctx.ndk_api))\n\n    @property\n    def include_dirs(self):\n        return [\n            \"{}/{}\".format(\n                self.ctx.include_dir,\n                d.format(arch=self))\n            for d in self.ctx.include_dirs]\n\n    @property\n    def target(self):\n        # As of NDK r19, the toolchains installed by default with the\n        # NDK may be used in-place. The make_standalone_toolchain.py script\n        # is no longer needed for interfacing with arbitrary build systems.\n        # See: https://developer.android.com/ndk/guides/other_build_systems\n        return '{triplet}{ndk_api}'.format(\n            triplet=self.command_prefix, ndk_api=self.ctx.ndk_api\n        )\n\n    @property\n    def clang_exe(self):\n        \"\"\"Full path of the clang compiler depending on the android's ndk\n        version used.\"\"\"\n        return self.get_clang_exe()\n\n    @property\n    def clang_exe_cxx(self):\n        \"\"\"Full path of the clang++ compiler depending on the android's ndk\n        version used.\"\"\"\n        return self.get_clang_exe(plus_plus=True)\n\n    def get_clang_exe(self, with_target=False, plus_plus=False):\n        \"\"\"Returns the full path of the clang/clang++ compiler, supports two\n        kwargs:\n\n          - `with_target`: prepend `target` to clang\n          - `plus_plus`: will return the clang++ compiler (defaults to `False`)\n        \"\"\"\n        compiler = 'clang'\n        if with_target:\n            compiler = '{target}-{compiler}'.format(\n                target=self.target, compiler=compiler\n            )\n        if plus_plus:\n            compiler += '++'\n        return join(self.ctx.ndk.llvm_bin_dir, compiler)\n\n    def get_env(self, with_flags_in_cc=True):\n        env = {}\n\n        # HOME: User's home directory\n        #\n        # Many tools including p4a store outputs in the user's home\n        # directory. This is found from the HOME environment variable\n        # and falls back to the system account database. Setting HOME\n        # can be used to globally divert these tools to use a different\n        # path. Furthermore, in containerized environments the user may\n        # not exist in the account database, so if HOME isn't set than\n        # these tools will fail.\n        if 'HOME' in environ:\n            env['HOME'] = environ['HOME']\n\n        # CFLAGS/CXXFLAGS: the processor flags\n        env['CFLAGS'] = ' '.join(self.common_cflags).format(target=self.target)\n        if self.arch_cflags:\n            # each architecture may have has his own CFLAGS\n            env['CFLAGS'] += ' ' + ' '.join(self.arch_cflags)\n        env['CXXFLAGS'] = env['CFLAGS']\n\n        # CPPFLAGS (for macros and includes)\n        env['CPPFLAGS'] = ' '.join(self.common_cppflags).format(\n            ctx=self.ctx,\n            command_prefix=self.command_prefix,\n            python_includes=join(Recipe.get_recipe(\"python3\", self.ctx).include_root(self.arch))\n        )\n\n        # LDFLAGS: Link the extra global link paths first before anything else\n        # (such that overriding system libraries with them is possible)\n        env['LDFLAGS'] = (\n            ' '\n            + \" \".join(\n                [\n                    \"-L\"\n                    + link_path\n                    for link_path in self.extra_global_link_paths\n                ]\n            )\n            + ' ' + ' '.join(self.common_ldflags).format(\n                ctx_libs_dir=self.ctx.get_libs_dir(self.arch)\n            )\n        )\n\n        # LDLIBS: Library flags or names given to compilers when they are\n        # supposed to invoke the linker.\n        env['LDLIBS'] = ' '.join(self.common_ldlibs)\n\n        # CCACHE\n        ccache = ''\n        if self.ctx.ccache and bool(int(environ.get('USE_CCACHE', '1'))):\n            # print('ccache found, will optimize builds')\n            ccache = self.ctx.ccache + ' '\n            env['USE_CCACHE'] = '1'\n            env['NDK_CCACHE'] = self.ctx.ccache\n            env.update(\n                {k: v for k, v in environ.items() if k.startswith('CCACHE_')}\n            )\n\n        # Compiler: `CC` and `CXX` (and make sure that the compiler exists)\n        env['PATH'] = self.ctx.env['PATH']\n        cc = shutil.which(self.clang_exe, path=env['PATH'])\n        if cc is None:\n            print('Searching path are: {!r}'.format(env['PATH']))\n            raise BuildInterruptingException(\n                'Couldn\\'t find executable for CC. This indicates a '\n                'problem locating the {} executable in the Android '\n                'NDK, not that you don\\'t have a normal compiler '\n                'installed. Exiting.'.format(self.clang_exe))\n\n        if with_flags_in_cc:\n            env['CC'] = '{ccache}{exe} {cflags}'.format(\n                exe=self.clang_exe,\n                ccache=ccache,\n                cflags=env['CFLAGS'])\n            env['CXX'] = '{ccache}{execxx} {cxxflags}'.format(\n                execxx=self.clang_exe_cxx,\n                ccache=ccache,\n                cxxflags=env['CXXFLAGS'])\n        else:\n            env['CC'] = '{ccache}{exe}'.format(\n                exe=self.clang_exe,\n                ccache=ccache)\n            env['CXX'] = '{ccache}{execxx}'.format(\n                execxx=self.clang_exe_cxx,\n                ccache=ccache)\n\n        # Android's LLVM binutils\n        env['AR'] = self.ctx.ndk.llvm_ar\n        env['RANLIB'] = self.ctx.ndk.llvm_ranlib\n        env['STRIP'] = f'{self.ctx.ndk.llvm_strip} --strip-unneeded'\n        env['READELF'] = self.ctx.ndk.llvm_readelf\n        env['OBJCOPY'] = self.ctx.ndk.llvm_objcopy\n\n        env['MAKE'] = 'make -j{}'.format(str(cpu_count()))\n\n        # Android's arch/toolchain\n        env['ARCH'] = self.arch\n        env['NDK_API'] = 'android-{}'.format(str(self.ctx.ndk_api))\n\n        # Custom linker options\n        env['LDSHARED'] = env['CC'] + ' ' + ' '.join(self.common_ldshared)\n\n        # Host python (used by some recipes)\n        hostpython_recipe = Recipe.get_recipe(\n            'host' + self.ctx.python_recipe.name, self.ctx)\n        env['BUILDLIB_PATH'] = join(\n            hostpython_recipe.get_build_dir(self.arch),\n            'native-build',\n            'build',\n            'lib.{}-{}'.format(\n                build_platform,\n                self.ctx.python_recipe.major_minor_version_string,\n            ),\n        )\n\n        # for reproducible builds\n        if 'SOURCE_DATE_EPOCH' in environ:\n            for k in 'LC_ALL TZ SOURCE_DATE_EPOCH PYTHONHASHSEED BUILD_DATE BUILD_TIME'.split():\n                if k in environ:\n                    env[k] = environ[k]\n\n        return env\n\n\nclass ArchARM(Arch):\n    arch = \"armeabi\"\n    command_prefix = 'arm-linux-androideabi'\n\n    @property\n    def target(self):\n        target_data = self.command_prefix.split('-')\n        return '{triplet}{ndk_api}'.format(\n            triplet='-'.join(['armv7a', target_data[1], target_data[2]]),\n            ndk_api=self.ctx.ndk_api,\n        )\n\n\nclass ArchARMv7_a(ArchARM):\n    arch = 'armeabi-v7a'\n    arch_cflags = [\n        '-march=armv7-a',\n        '-mfloat-abi=softfp',\n        '-mfpu=vfp',\n        '-mthumb',\n        '-fPIC',\n    ]\n\n\nclass Archx86(Arch):\n    arch = 'x86'\n    command_prefix = 'i686-linux-android'\n    arch_cflags = [\n        '-march=i686',\n        '-mssse3',\n        '-mfpmath=sse',\n        '-m32',\n        '-fPIC',\n    ]\n\n\nclass Archx86_64(Arch):\n    arch = 'x86_64'\n    command_prefix = 'x86_64-linux-android'\n    arch_cflags = [\n        '-march=x86-64',\n        '-msse4.2',\n        '-mpopcnt',\n        '-m64',\n        '-fPIC',\n    ]\n\n\nclass ArchAarch_64(Arch):\n    arch = 'arm64-v8a'\n    command_prefix = 'aarch64-linux-android'\n    arch_cflags = [\n        '-march=armv8-a',\n        '-fPIC'\n        # '-I' + join(dirname(__file__), 'includes', 'arm64-v8a'),\n    ]\n\n    # Note: This `EXTRA_CFLAGS` below should target the commented `include`\n    # above in `arch_cflags`. The original lines were added during the Sdl2's\n    # bootstrap creation, and modified/commented during the migration to the\n    # NDK r19 build system, because it seems that we don't need it anymore,\n    # do we need them?\n    # def get_env(self, with_flags_in_cc=True):\n    #     env = super().get_env(with_flags_in_cc)\n    #     env['EXTRA_CFLAGS'] = self.arch_cflags[-1]\n    #     return env\n"
  },
  {
    "path": "pythonforandroid/bdistapk.py",
    "content": "from glob import glob\nfrom os.path import realpath, join, dirname, curdir, basename, split\nfrom setuptools import Command\nfrom shutil import copyfile\nimport sys\n\nfrom pythonforandroid.util import rmdir, ensure_dir\n\n\ndef argv_contains(t):\n    for arg in sys.argv:\n        if arg.startswith(t):\n            return True\n    return False\n\n\nclass Bdist(Command):\n\n    user_options = []\n    package_type = None\n\n    def initialize_options(self):\n        for option in self.user_options:\n            setattr(self, option[0].strip('=').replace('-', '_'), None)\n\n        option_dict = self.distribution.get_option_dict(self.package_type)\n\n        # This is a hack, we probably aren't supposed to loop through\n        # the option_dict so early because distutils does exactly the\n        # same thing later to check that we support the\n        # options. However, it works...\n        for (option, (source, value)) in option_dict.items():\n            setattr(self, option, str(value))\n\n    def finalize_options(self):\n\n        setup_options = self.distribution.get_option_dict(self.package_type)\n        for (option, (source, value)) in setup_options.items():\n            if source == 'command line':\n                continue\n            if not argv_contains('--' + option):\n                # allow 'permissions': ['permission', 'permission] in apk\n                if option == 'permissions':\n                    for perm in value:\n                        sys.argv.append('--permission={}'.format(perm))\n                elif option == 'orientation':\n                    for orient in value:\n                        sys.argv.append('--orientation={}'.format(orient))\n                elif value in (None, 'None'):\n                    sys.argv.append('--{}'.format(option))\n                else:\n                    sys.argv.append('--{}={}'.format(option, value))\n\n        # Inject some argv options from setup.py if the user did not\n        # provide them\n        if not argv_contains('--name'):\n            name = self.distribution.get_name()\n            sys.argv.append('--name=\"{}\"'.format(name))\n            self.name = name\n\n        if not argv_contains('--package'):\n            package = 'org.test.{}'.format(self.name.lower().replace(' ', ''))\n            print('WARNING: You did not supply an Android package '\n                  'identifier, trying {} instead.'.format(package))\n            print('         This may fail if this is not a valid identifier')\n            sys.argv.append('--package={}'.format(package))\n\n        if not argv_contains('--version'):\n            version = self.distribution.get_version()\n            sys.argv.append('--version={}'.format(version))\n\n        if not argv_contains('--arch'):\n            arch = 'armeabi-v7a'\n            self.arch = arch\n            sys.argv.append('--arch={}'.format(arch))\n\n    def run(self):\n        self.prepare_build_dir()\n\n        from pythonforandroid.entrypoints import main\n        sys.argv[1] = self.package_type\n        main()\n\n    def prepare_build_dir(self):\n\n        if argv_contains('--private') and not argv_contains('--launcher'):\n            print('WARNING: Received --private argument when this would '\n                  'normally be generated automatically.')\n            print('         This is probably bad unless you meant to do '\n                  'that.')\n\n        bdist_dir = 'build/bdist.android-{}'.format(self.arch)\n        rmdir(bdist_dir)\n        ensure_dir(bdist_dir)\n\n        globs = []\n        for directory, patterns in self.distribution.package_data.items():\n            for pattern in patterns:\n                globs.append(join(directory, pattern))\n\n        filens = []\n        for pattern in globs:\n            filens.extend(glob(pattern))\n\n        main_py_dirs = []\n        if not argv_contains('--launcher'):\n            for filen in filens:\n                new_dir = join(bdist_dir, dirname(filen))\n                ensure_dir(new_dir)\n                print('Including {}'.format(filen))\n                copyfile(filen, join(bdist_dir, filen))\n                if basename(filen) in ('main.py', 'main.pyc'):\n                    main_py_dirs.append(filen)\n\n        # This feels ridiculous, but how else to define the main.py dir?\n        # Maybe should just fail?\n        if not main_py_dirs and not argv_contains('--launcher'):\n            print('ERROR: Could not find main.py, so no app build dir defined')\n            print('You should name your app entry point main.py')\n            exit(1)\n        if len(main_py_dirs) > 1:\n            print('WARNING: Multiple main.py dirs found, using the shortest path')\n        main_py_dirs = sorted(main_py_dirs, key=lambda j: len(split(j)))\n\n        if not argv_contains('--launcher'):\n            sys.argv.append('--private={}'.format(\n                join(realpath(curdir), bdist_dir, dirname(main_py_dirs[0])))\n            )\n\n\nclass BdistAPK(Bdist):\n    \"\"\"distutil command handler for 'apk'.\"\"\"\n    description = 'Create an APK with python-for-android'\n    package_type = 'apk'\n\n\nclass BdistAAR(Bdist):\n    \"\"\"distutil command handler for 'aar'.\"\"\"\n    description = 'Create an AAR with python-for-android'\n    package_type = 'aar'\n\n\nclass BdistAAB(Bdist):\n    \"\"\"distutil command handler for 'aab'.\"\"\"\n    description = 'Create an AAB with python-for-android'\n    package_type = 'aab'\n\n\ndef _set_user_options():\n    # This seems like a silly way to do things, but not sure if there's a\n    # better way to pass arbitrary options onwards to p4a\n    user_options = [('requirements=', None, None), ]\n    for i, arg in enumerate(sys.argv):\n        if arg.startswith('--'):\n            if ('=' in arg or\n                    (i < (len(sys.argv) - 1) and not sys.argv[i+1].startswith('-'))):\n                user_options.append((arg[2:].split('=')[0] + '=', None, None))\n            else:\n                user_options.append((arg[2:], None, None))\n\n    BdistAPK.user_options = user_options\n    BdistAAB.user_options = user_options\n    BdistAAR.user_options = user_options\n\n\n_set_user_options()\n"
  },
  {
    "path": "pythonforandroid/bootstrap.py",
    "content": "import functools\nimport glob\nimport importlib\nimport os\nfrom os.path import (join, dirname, isdir, normpath, splitext, basename)\nfrom os import listdir, walk, sep\nimport sh\nimport shlex\nimport shutil\n\nfrom pythonforandroid.logger import (shprint, info, info_main, logger, debug)\nfrom pythonforandroid.util import (\n    current_directory, ensure_dir, temp_directory, BuildInterruptingException,\n    rmdir, move)\nfrom pythonforandroid.recipe import Recipe\n\nSDL_BOOTSTRAPS = (\"sdl2\", \"sdl3\")\n\n\ndef copy_files(src_root, dest_root, override=True, symlink=False):\n    for root, dirnames, filenames in walk(src_root):\n        for filename in filenames:\n            subdir = normpath(root.replace(src_root, \"\"))\n            if subdir.startswith(sep):  # ensure it is relative\n                subdir = subdir[1:]\n            dest_dir = join(dest_root, subdir)\n            if not os.path.exists(dest_dir):\n                os.makedirs(dest_dir)\n            src_file = join(root, filename)\n            dest_file = join(dest_dir, filename)\n            if os.path.isfile(src_file):\n                if override and os.path.exists(dest_file):\n                    os.unlink(dest_file)\n                if not os.path.exists(dest_file):\n                    if symlink:\n                        os.symlink(src_file, dest_file)\n                    else:\n                        shutil.copy(src_file, dest_file)\n            else:\n                os.makedirs(dest_file)\n\n\ndefault_recipe_priorities = [\n    \"webview\", \"sdl2\", \"sdl3\", \"service_only\"  # last is highest\n]\n# ^^ NOTE: these are just the default priorities if no special rules\n# apply (which you can find in the code below), so basically if no\n# known graphical lib or web lib is used - in which case service_only\n# is the most reasonable guess.\n\n\ndef _cmp_bootstraps_by_priority(a, b):\n    def rank_bootstrap(bootstrap):\n        \"\"\" Returns a ranking index for each bootstrap,\n            with higher priority ranked with higher number. \"\"\"\n        if bootstrap.name in default_recipe_priorities:\n            return default_recipe_priorities.index(bootstrap.name) + 1\n        return 0\n\n    # Rank bootstraps in order:\n    rank_a = rank_bootstrap(a)\n    rank_b = rank_bootstrap(b)\n    if rank_a != rank_b:\n        return (rank_b - rank_a)\n    else:\n        if a.name < b.name:  # alphabetic sort for determinism\n            return -1\n        else:\n            return 1\n\n\nclass Bootstrap:\n    '''An Android project template, containing recipe stuff for\n    compilation and templated fields for APK info.\n    '''\n    jni_subdir = '/jni'\n    ctx = None\n\n    bootstrap_dir = None\n\n    build_dir = None\n    dist_name = None\n    distribution = None\n\n    # All bootstraps should include Python in some way:\n    recipe_depends = ['python3', 'android']\n\n    can_be_chosen_automatically = True\n    '''Determines whether the bootstrap can be chosen as one that\n    satisfies user requirements. If False, it will not be returned\n    from Bootstrap.get_bootstrap_from_recipes.\n    '''\n\n    # Other things a Bootstrap might need to track (maybe separately):\n    # ndk_main.c\n    # whitelist.txt\n    # blacklist.txt\n\n    @property\n    def dist_dir(self):\n        '''The dist dir at which to place the finished distribution.'''\n        if self.distribution is None:\n            raise BuildInterruptingException(\n                'Internal error: tried to access {}.dist_dir, but {}.distribution '\n                'is None'.format(self, self))\n        return self.distribution.dist_dir\n\n    @property\n    def jni_dir(self):\n        return self.name + self.jni_subdir\n\n    def check_recipe_choices(self):\n        '''Checks what recipes are being built to see which of the alternative\n        and optional dependencies are being used,\n        and returns a list of these.'''\n        recipes = []\n        built_recipes = self.ctx.recipe_build_order or []\n        for recipe in self.recipe_depends:\n            if isinstance(recipe, (tuple, list)):\n                for alternative in recipe:\n                    if alternative in built_recipes:\n                        recipes.append(alternative)\n                        break\n        return sorted(recipes)\n\n    def get_build_dir_name(self):\n        choices = self.check_recipe_choices()\n        dir_name = '-'.join([self.name] + choices)\n        return dir_name\n\n    def get_build_dir(self):\n        return join(self.ctx.build_dir, 'bootstrap_builds', self.get_build_dir_name())\n\n    def get_dist_dir(self, name):\n        return join(self.ctx.dist_dir, name)\n\n    @property\n    def name(self):\n        modname = self.__class__.__module__\n        return modname.split(\".\", 2)[-1]\n\n    def get_bootstrap_dirs(self):\n        \"\"\"get all bootstrap directories, following the MRO path\"\"\"\n\n        # get all bootstrap names along the __mro__, cutting off Bootstrap and object\n        classes = self.__class__.__mro__[:-2]\n        bootstrap_names = [cls.name for cls in classes] + ['common']\n        bootstrap_dirs = [\n            join(self.ctx.root_dir, 'bootstraps', bootstrap_name)\n            for bootstrap_name in reversed(bootstrap_names)\n        ]\n        return bootstrap_dirs\n\n    def _copy_in_final_files(self):\n        if self.name in SDL_BOOTSTRAPS:\n            # Get the paths for copying SDL's java source code:\n            sdl_recipe = Recipe.get_recipe(self.name, self.ctx)\n            sdl_build_dir = sdl_recipe.get_jni_dir()\n            src_dir = join(sdl_build_dir, \"SDL\", \"android-project\",\n                           \"app\", \"src\", \"main\", \"java\",\n                           \"org\", \"libsdl\", \"app\")\n            target_dir = join(self.dist_dir, 'src', 'main', 'java', 'org',\n                              'libsdl', 'app')\n\n            # Do actual copying:\n            info('Copying in SDL .java files from: ' + str(src_dir))\n            if not os.path.exists(target_dir):\n                os.makedirs(target_dir)\n            copy_files(src_dir, target_dir, override=True)\n\n    def prepare_build_dir(self):\n        \"\"\"Ensure that a build dir exists for the recipe. This same single\n        dir will be used for building all different archs.\"\"\"\n        bootstrap_dirs = self.get_bootstrap_dirs()\n        # now do a cumulative copy of all bootstrap dirs\n        self.build_dir = self.get_build_dir()\n        for bootstrap_dir in bootstrap_dirs:\n            copy_files(join(bootstrap_dir, 'build'), self.build_dir, symlink=self.ctx.symlink_bootstrap_files)\n\n        with current_directory(self.build_dir):\n            with open('project.properties', 'w') as fileh:\n                fileh.write('target=android-{}'.format(self.ctx.android_api))\n\n    def prepare_dist_dir(self):\n        ensure_dir(self.dist_dir)\n\n    def _assemble_distribution_for_arch(self, arch):\n        \"\"\"Per-architecture distribution assembly.\n\n        Override this method to customize per-arch behavior.\n        Called once for each architecture in self.ctx.archs.\n        \"\"\"\n        self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])\n        self.distribute_aars(arch)\n\n        python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')\n        ensure_dir(python_bundle_dir)\n        site_packages_dir = self.ctx.python_recipe.create_python_bundle(\n            join(self.dist_dir, python_bundle_dir), arch)\n        if not self.ctx.with_debug_symbols:\n            self.strip_libraries(arch)\n        self.fry_eggs(site_packages_dir)\n\n    def assemble_distribution(self):\n        \"\"\"Assemble the distribution by copying files and creating Python bundle.\n\n        This default implementation works for most bootstraps. Override\n        _assemble_distribution_for_arch() for per-arch customization, or\n        override this entire method for fundamentally different behavior.\n        \"\"\"\n        info_main(f'# Creating Android project ({self.name})')\n\n        rmdir(self.dist_dir)\n        shprint(sh.cp, '-r', self.build_dir, self.dist_dir)\n\n        with current_directory(self.dist_dir):\n            with open('local.properties', 'w') as fileh:\n                fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))\n\n        with current_directory(self.dist_dir):\n            info('Copying Python distribution')\n\n            self.distribute_javaclasses(self.ctx.javaclass_dir,\n                                        dest_dir=join(\"src\", \"main\", \"java\"))\n\n            for arch in self.ctx.archs:\n                self._assemble_distribution_for_arch(arch)\n\n            if 'sqlite3' not in self.ctx.recipe_build_order:\n                with open('blacklist.txt', 'a') as fileh:\n                    fileh.write('\\nsqlite3/*\\nlib-dynload/_sqlite3.so\\n')\n\n        self._copy_in_final_files()\n        self.distribution.save_info(self.dist_dir)\n\n    @classmethod\n    def all_bootstraps(cls):\n        '''Find all the available bootstraps and return them.'''\n        forbidden_dirs = ('__pycache__', 'common', '_sdl_common')\n        bootstraps_dir = join(dirname(__file__), 'bootstraps')\n        result = set()\n        for name in listdir(bootstraps_dir):\n            if name in forbidden_dirs:\n                continue\n            filen = join(bootstraps_dir, name)\n            if isdir(filen):\n                result.add(name)\n        return result\n\n    @classmethod\n    def get_usable_bootstraps_for_recipes(cls, recipes, ctx):\n        '''Returns all bootstrap whose recipe requirements do not conflict\n        with the given recipes, in no particular order.'''\n        info('Trying to find a bootstrap that matches the given recipes.')\n        bootstraps = [cls.get_bootstrap(name, ctx)\n                      for name in cls.all_bootstraps()]\n        acceptable_bootstraps = set()\n\n        # Find out which bootstraps are acceptable:\n        for bs in bootstraps:\n            if not bs.can_be_chosen_automatically:\n                continue\n            possible_dependency_lists = expand_dependencies(bs.recipe_depends, ctx)\n            for possible_dependencies in possible_dependency_lists:\n                ok = True\n                # Check if the bootstap's dependencies have an internal conflict:\n                for recipe in possible_dependencies:\n                    recipe = Recipe.get_recipe(recipe, ctx)\n                    if any(conflict in recipes for conflict in recipe.conflicts):\n                        ok = False\n                        break\n                # Check if bootstrap's dependencies conflict with chosen\n                # packages:\n                for recipe in recipes:\n                    try:\n                        recipe = Recipe.get_recipe(recipe, ctx)\n                    except ValueError:\n                        conflicts = []\n                    else:\n                        conflicts = recipe.conflicts\n                    if any(conflict in possible_dependencies\n                            for conflict in conflicts):\n                        ok = False\n                        break\n                if ok and bs not in acceptable_bootstraps:\n                    acceptable_bootstraps.add(bs)\n\n        info('Found {} acceptable bootstraps: {}'.format(\n            len(acceptable_bootstraps),\n            [bs.name for bs in acceptable_bootstraps]))\n        return acceptable_bootstraps\n\n    @classmethod\n    def get_bootstrap_from_recipes(cls, recipes, ctx):\n        '''Picks a single recommended default bootstrap out of\n           all_usable_bootstraps_from_recipes() for the given reicpes,\n           and returns it.'''\n\n        known_web_packages = {\"flask\"}  # to pick webview over service_only\n        recipes_with_deps_lists = expand_dependencies(recipes, ctx)\n        acceptable_bootstraps = cls.get_usable_bootstraps_for_recipes(\n            recipes, ctx\n        )\n\n        def have_dependency_in_recipes(dep):\n            for dep_list in recipes_with_deps_lists:\n                if dep in dep_list:\n                    return True\n            return False\n\n        # Special rule: return SDL2 bootstrap if there's an sdl2 dep:\n        if (have_dependency_in_recipes(\"sdl2\") and\n                \"sdl2\" in [b.name for b in acceptable_bootstraps]\n                ):\n            info('Using sdl2 bootstrap since it is in dependencies')\n            return cls.get_bootstrap(\"sdl2\", ctx)\n\n        # Special rule: return SDL3 bootstrap if there's an sdl3 dep:\n        if (have_dependency_in_recipes(\"sdl3\") and\n                \"sdl3\" in [b.name for b in acceptable_bootstraps]\n                ):\n            info('Using sdl3 bootstrap since it is in dependencies')\n            return cls.get_bootstrap(\"sdl3\", ctx)\n\n        # Special rule: return \"webview\" if we depend on common web recipe:\n        for possible_web_dep in known_web_packages:\n            if have_dependency_in_recipes(possible_web_dep):\n                # We have a web package dep!\n                if \"webview\" in [b.name for b in acceptable_bootstraps]:\n                    info('Using webview bootstrap since common web packages '\n                         'were found {}'.format(\n                             known_web_packages.intersection(recipes)\n                         ))\n                    return cls.get_bootstrap(\"webview\", ctx)\n\n        prioritized_acceptable_bootstraps = sorted(\n            list(acceptable_bootstraps),\n            key=functools.cmp_to_key(_cmp_bootstraps_by_priority)\n        )\n\n        if prioritized_acceptable_bootstraps:\n            info('Using the highest ranked/first of these: {}'\n                 .format(prioritized_acceptable_bootstraps[0].name))\n            return prioritized_acceptable_bootstraps[0]\n        return None\n\n    @classmethod\n    def get_bootstrap(cls, name, ctx):\n        '''Returns an instance of a bootstrap with the given name.\n\n        This is the only way you should access a bootstrap class, as\n        it sets the bootstrap directory correctly.\n        '''\n        if name is None:\n            return None\n        if not hasattr(cls, 'bootstraps'):\n            cls.bootstraps = {}\n        if name in cls.bootstraps:\n            return cls.bootstraps[name]\n        mod = importlib.import_module('pythonforandroid.bootstraps.{}'\n                                      .format(name))\n        if len(logger.handlers) > 1:\n            logger.removeHandler(logger.handlers[1])\n        bootstrap = mod.bootstrap\n        bootstrap.bootstrap_dir = join(ctx.root_dir, 'bootstraps', name)\n        bootstrap.ctx = ctx\n        return bootstrap\n\n    def distribute_libs(self, arch, src_dirs, wildcard='*', dest_dir=\"libs\"):\n        '''Copy existing arch libs from build dirs to current dist dir.'''\n        info('Copying libs')\n        tgt_dir = join(dest_dir, arch.arch)\n        ensure_dir(tgt_dir)\n        for src_dir in src_dirs:\n            libs = glob.glob(join(src_dir, wildcard))\n            if libs:\n                shprint(sh.cp, '-a', *libs, tgt_dir)\n\n    def distribute_javaclasses(self, javaclass_dir, dest_dir=\"src\"):\n        '''Copy existing javaclasses from build dir to current dist dir.'''\n        info('Copying java files')\n        ensure_dir(dest_dir)\n        filenames = glob.glob(javaclass_dir)\n        shprint(sh.cp, '-a', *filenames, dest_dir)\n\n    def distribute_aars(self, arch):\n        '''Process existing .aar bundles and copy to current dist dir.'''\n        info('Unpacking aars')\n        for aar in glob.glob(join(self.ctx.aars_dir, '*.aar')):\n            self._unpack_aar(aar, arch)\n\n    def _unpack_aar(self, aar, arch):\n        '''Unpack content of .aar bundle and copy to current dist dir.'''\n        with temp_directory() as temp_dir:\n            name = splitext(basename(aar))[0]\n            jar_name = name + '.jar'\n            info(\"unpack {} aar\".format(name))\n            debug(\"  from {}\".format(aar))\n            debug(\"  to {}\".format(temp_dir))\n            shprint(sh.unzip, '-o', aar, '-d', temp_dir)\n\n            jar_src = join(temp_dir, 'classes.jar')\n            jar_tgt = join('libs', jar_name)\n            debug(\"copy {} jar\".format(name))\n            debug(\"  from {}\".format(jar_src))\n            debug(\"  to {}\".format(jar_tgt))\n            ensure_dir('libs')\n            shprint(sh.cp, '-a', jar_src, jar_tgt)\n\n            so_src_dir = join(temp_dir, 'jni', arch.arch)\n            so_tgt_dir = join('libs', arch.arch)\n            debug(\"copy {} .so\".format(name))\n            debug(\"  from {}\".format(so_src_dir))\n            debug(\"  to {}\".format(so_tgt_dir))\n            ensure_dir(so_tgt_dir)\n            so_files = glob.glob(join(so_src_dir, '*.so'))\n            shprint(sh.cp, '-a', *so_files, so_tgt_dir)\n\n    def strip_libraries(self, arch):\n        info('Stripping libraries')\n        env = arch.get_env()\n        tokens = shlex.split(env['STRIP'])\n        strip = sh.Command(tokens[0])\n        if len(tokens) > 1:\n            strip = strip.bake(tokens[1:])\n\n        libs_dir = join(self.dist_dir, f'_python_bundle__{arch.arch}',\n                        '_python_bundle', 'modules')\n        filens = shprint(sh.find, libs_dir, join(self.dist_dir, 'libs'),\n                         '-iname', '*.so', _env=env).stdout.decode('utf-8')\n\n        logger.info('Stripping libraries in private dir')\n        for filen in filens.split('\\n'):\n            if not filen:\n                continue  # skip the last ''\n            try:\n                strip(filen, _env=env)\n            except sh.ErrorReturnCode_1:\n                logger.debug('Failed to strip ' + filen)\n\n    def fry_eggs(self, sitepackages):\n        info('Frying eggs in {}'.format(sitepackages))\n        for d in listdir(sitepackages):\n            rd = join(sitepackages, d)\n            if isdir(rd) and d.endswith('.egg'):\n                info('  ' + d)\n                files = [join(rd, f) for f in listdir(rd) if f != 'EGG-INFO']\n                for f in files:\n                    move(f, sitepackages)\n                rmdir(d)\n\n\ndef expand_dependencies(recipes, ctx):\n    \"\"\" This function expands to lists of all different available\n        alternative recipe combinations, with the dependencies added in\n        ONLY for all the not-with-alternative recipes.\n        (So this is like the deps graph very simplified and incomplete, but\n         hopefully good enough for most basic bootstrap compatibility checks)\n    \"\"\"\n\n    # Add in all the deps of recipes where there is no alternative:\n    recipes_with_deps = list(recipes)\n    for entry in recipes:\n        if not isinstance(entry, (tuple, list)) or len(entry) == 1:\n            if isinstance(entry, (tuple, list)):\n                entry = entry[0]\n            try:\n                recipe = Recipe.get_recipe(entry, ctx)\n                recipes_with_deps += recipe.depends\n            except ValueError:\n                # it's a pure python package without a recipe, so we\n                # don't know the dependencies...skipping for now\n                pass\n\n    # Split up lists by available alternatives:\n    recipe_lists = [[]]\n    for recipe in recipes_with_deps:\n        if isinstance(recipe, (tuple, list)):\n            new_recipe_lists = []\n            for alternative in recipe:\n                for old_list in recipe_lists:\n                    new_list = [i for i in old_list]\n                    new_list.append(alternative)\n                    new_recipe_lists.append(new_list)\n            recipe_lists = new_recipe_lists\n        else:\n            for existing_list in recipe_lists:\n                existing_list.append(recipe)\n    return recipe_lists\n"
  },
  {
    "path": "pythonforandroid/bootstraps/__init__.py",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/__init__.py",
    "content": "from os.path import join\n\nfrom pythonforandroid.toolchain import Bootstrap\nfrom pythonforandroid.util import ensure_dir\n\n\nclass SDLGradleBootstrap(Bootstrap):\n    name = \"_sdl_common\"\n\n    recipe_depends = []\n\n    def _assemble_distribution_for_arch(self, arch):\n        \"\"\"SDL bootstrap skips distribute_aars() - handled differently.\"\"\"\n        self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])\n        # Note: SDL bootstrap does not call distribute_aars()\n\n        python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')\n        ensure_dir(python_bundle_dir)\n        site_packages_dir = self.ctx.python_recipe.create_python_bundle(\n            join(self.dist_dir, python_bundle_dir), arch)\n        if not self.ctx.with_debug_symbols:\n            self.strip_libraries(arch)\n        self.fry_eggs(site_packages_dir)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/.gitignore",
    "content": ".gradle\n/build/\n\n# Ignore Gradle GUI config\ngradle-app.setting\n\n# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)\n!gradle-wrapper.jar\n\n# Cache of project\n.gradletasknamecache\n\n# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898\n# gradle/wrapper/gradle-wrapper.properties\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/blacklist.txt",
    "content": "# prevent user to include invalid extensions\n*.apk\n*.aab\n*.apks\n*.pxd\n\n# eggs\n*.egg-info\n\n# unit test\nunittest/*\n\n# python config\nconfig/makesetup\n\n# unused kivy files (platform specific)\nkivy/input/providers/wm_*\nkivy/input/providers/mactouch*\nkivy/input/providers/probesysfs*\nkivy/input/providers/mtdev*\nkivy/input/providers/hidinput*\nkivy/core/camera/camera_videocapture*\nkivy/core/spelling/*osx*\nkivy/core/video/video_pyglet*\nkivy/tools\nkivy/tests/*\nkivy/*/*.h\nkivy/*/*.pxi\n\n# unused encodings\nlib-dynload/*codec*\nencodings/cp*.pyo\nencodings/tis*\nencodings/shift*\nencodings/bz2*\nencodings/iso*\nencodings/undefined*\nencodings/johab*\nencodings/p*\nencodings/m*\nencodings/euc*\nencodings/k*\nencodings/unicode_internal*\nencodings/quo*\nencodings/gb*\nencodings/big5*\nencodings/hp*\nencodings/hz*\n\n# unused python modules\nbsddb/*\nwsgiref/*\nhotshot/*\npydoc_data/*\ntty.pyo\nanydbm.pyo\nnturl2path.pyo\nLICENCE.txt\nmacurl2path.pyo\ndummy_threading.pyo\naudiodev.pyo\nantigravity.pyo\ndumbdbm.pyo\nsndhdr.pyo\n__phello__.foo.pyo\nsunaudio.pyo\nos2emxpath.pyo\nmultiprocessing/dummy*\n\n# unused binaries python modules\nlib-dynload/termios.so\nlib-dynload/_lsprof.so\nlib-dynload/*audioop.so\nlib-dynload/_hotshot.so\nlib-dynload/_heapq.so\nlib-dynload/_json.so\nlib-dynload/grp.so\nlib-dynload/resource.so\nlib-dynload/pyexpat.so\nlib-dynload/_ctypes_test.so\nlib-dynload/_testcapi.so\n\n# odd files\nplat-linux3/regen\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/jni/Application.mk",
    "content": "\n# Uncomment this if you're using STL in your project\n# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information\n# APP_STL := stlport_static \n\n# APP_ABI := armeabi armeabi-v7a x86\nAPP_ABI := $(ARCH)\nAPP_PLATFORM := $(NDK_API)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/java/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/Project.java",
    "content": "package org.kivy.android.launcher;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.util.Log;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.util.Properties;\n\n/** This represents a project we've scanned for. */\npublic class Project {\n\n    public String dir = null;\n    String title = null;\n    String author = null;\n    Bitmap icon = null;\n    public boolean landscape = false;\n\n    static String decode(String s) {\n        try {\n            return new String(s.getBytes(\"ISO-8859-1\"), \"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            return s;\n        }\n    }\n\n    /**\n     * Scans directory for a android.txt file. If it finds one, and it looks valid enough, then it\n     * creates a new Project, and returns that. Otherwise, returns null.\n     */\n    public static Project scanDirectory(File dir) {\n\n        // We might have a link file.\n        if (dir.getAbsolutePath().endsWith(\".link\")) {\n            try {\n\n                // Scan the android.txt file.\n                File propfile = new File(dir, \"android.txt\");\n                FileInputStream in = new FileInputStream(propfile);\n                Properties p = new Properties();\n                p.load(in);\n                in.close();\n\n                String directory = p.getProperty(\"directory\", null);\n\n                if (directory == null) {\n                    return null;\n                }\n\n                dir = new File(directory);\n\n            } catch (Exception e) {\n                Log.i(\"Project\", \"Couldn't open link file \" + dir, e);\n            }\n        }\n\n        // Make sure we're dealing with a directory.\n        if (!dir.isDirectory()) {\n            return null;\n        }\n\n        try {\n\n            // Scan the android.txt file.\n            File propfile = new File(dir, \"android.txt\");\n            FileInputStream in = new FileInputStream(propfile);\n            Properties p = new Properties();\n            p.load(in);\n            in.close();\n\n            // Get the various properties.\n            String title = decode(p.getProperty(\"title\", \"Untitled\"));\n            String author = decode(p.getProperty(\"author\", \"\"));\n            boolean landscape = p.getProperty(\"orientation\", \"portrait\").equals(\"landscape\");\n\n            // Create the project object.\n            Project rv = new Project();\n            rv.title = title;\n            rv.author = author;\n            rv.icon = BitmapFactory.decodeFile(new File(dir, \"icon.png\").getAbsolutePath());\n            rv.landscape = landscape;\n            rv.dir = dir.getAbsolutePath();\n\n            return rv;\n\n        } catch (Exception e) {\n            Log.i(\"Project\", \"Couldn't open android.txt\", e);\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java",
    "content": "package org.kivy.android.launcher;\n\nimport android.app.Activity;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ArrayAdapter;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport org.renpy.android.ResourceManager;\n\npublic class ProjectAdapter extends ArrayAdapter<Project> {\n\n    private ResourceManager resourceManager;\n\n    public ProjectAdapter(Activity context) {\n        super(context, 0);\n        resourceManager = new ResourceManager(context);\n    }\n\n    public View getView(int position, View convertView, ViewGroup parent) {\n        Project p = getItem(position);\n\n        View v = resourceManager.inflateView(\"chooser_item\");\n        TextView title = (TextView) resourceManager.getViewById(v, \"title\");\n        TextView author = (TextView) resourceManager.getViewById(v, \"author\");\n        ImageView icon = (ImageView) resourceManager.getViewById(v, \"icon\");\n\n        title.setText(p.title);\n        author.setText(p.author);\n        icon.setImageBitmap(p.icon);\n\n        return v;\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java",
    "content": "package org.kivy.android.launcher;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Environment;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.ListView;\nimport android.widget.TextView;\nimport java.io.File;\nimport java.util.Arrays;\nimport org.renpy.android.ResourceManager;\n\npublic class ProjectChooser extends Activity implements AdapterView.OnItemClickListener {\n\n    ResourceManager resourceManager;\n\n    String urlScheme;\n\n    @Override\n    public void onStart() {\n        super.onStart();\n\n        resourceManager = new ResourceManager(this);\n\n        urlScheme = resourceManager.getString(\"urlScheme\");\n\n        // Set the window title.\n        setTitle(resourceManager.getString(\"appName\"));\n\n        // Scan the sdcard for files, and sort them.\n        File dir = new File(Environment.getExternalStorageDirectory(), urlScheme);\n\n        File entries[] = dir.listFiles();\n\n        if (entries == null) {\n            entries = new File[0];\n        }\n\n        Arrays.sort(entries);\n\n        // Create a ProjectAdapter and fill it with projects.\n        ProjectAdapter projectAdapter = new ProjectAdapter(this);\n\n        // Populate it with the properties files.\n        for (File d : entries) {\n            Project p = Project.scanDirectory(d);\n            if (p != null) {\n                projectAdapter.add(p);\n            }\n        }\n\n        if (projectAdapter.getCount() != 0) {\n\n            View v = resourceManager.inflateView(\"project_chooser\");\n            ListView l = (ListView) resourceManager.getViewById(v, \"projectList\");\n\n            l.setAdapter(projectAdapter);\n            l.setOnItemClickListener(this);\n\n            setContentView(v);\n\n        } else {\n\n            View v = resourceManager.inflateView(\"project_empty\");\n            TextView emptyText = (TextView) resourceManager.getViewById(v, \"emptyText\");\n\n            emptyText.setText(\n                    \"No projects are available to launch. Please place a project into \"\n                            + dir\n                            + \" and restart this application. Press the back button to exit.\");\n\n            setContentView(v);\n        }\n    }\n\n    public void onItemClick(AdapterView parent, View view, int position, long id) {\n        Project p = (Project) parent.getItemAtPosition(position);\n\n        Intent intent = new Intent(\"org.kivy.LAUNCH\", Uri.fromParts(urlScheme, p.dir, \"\"));\n\n        intent.setClassName(getPackageName(), \"org.kivy.android.PythonActivity\");\n        this.startActivity(intent);\n        this.finish();\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/jniLibs/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/libs/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/chooser_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n   android:orientation=\"horizontal\"\n   android:gravity=\"center\"\n   >\n\n  <ImageView\n     android:id=\"@+id/icon\"\n     android:layout_width=\"64sp\"\n     android:layout_height=\"64sp\"\n     android:scaleType=\"fitCenter\"\n     android:padding=\"2sp\"\n     />\n\n  <LinearLayout\n     android:orientation=\"vertical\"\n     android:layout_width=\"fill_parent\"\n     android:layout_height=\"wrap_content\"\n     >\n\n    <TextView\n       android:layout_width=\"wrap_content\"\n       android:layout_height=\"wrap_content\"\n       android:id=\"@+id/title\"\n       android:textSize=\"18sp\"\n       android:textColor=\"#fff\"\n       android:singleLine=\"true\"\n       />\n\n    <TextView\n       android:layout_width=\"wrap_content\"\n       android:layout_height=\"wrap_content\"\n       android:singleLine=\"true\"\n       android:id=\"@+id/author\"\n       />\n\n  </LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"fill_parent\"\n    >\n<TextView  \n    android:layout_width=\"fill_parent\" \n    android:layout_height=\"wrap_content\" \n    android:text=\"Hello World, SDLActivity\"\n    />\n</LinearLayout>\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/project_chooser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout\n   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n   android:orientation=\"vertical\"\n   >\n\n  <TextView\n     android:text=\"Please choose a project:\"\n     android:layout_width=\"fill_parent\"\n     android:layout_height=\"wrap_content\"\n     android:padding=\"4sp\"\n     />\n\n  <ListView\n     android:id=\"@+id/projectList\"\n     android:layout_width=\"fill_parent\"\n     android:layout_height=\"fill_parent\"\n     />\n  \n\n</LinearLayout>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/project_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout\n   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n   android:orientation=\"vertical\"\n   >\n\n  <TextView\n     android:id=\"@+id/emptyText\"\n     android:layout_width=\"fill_parent\"\n     android:layout_height=\"wrap_content\"\n     android:padding=\"4sp\"\n     />\n\n</LinearLayout>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/res/mipmap/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/src/main/res/mipmap-anydpi-v26/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/templates/AndroidManifest.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Replace org.libsdl.app with the identifier of your game below, e.g.\n     com.gamemaker.game\n-->\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      android:versionCode=\"{{ args.numeric_version }}\"\n      android:versionName=\"{{ args.version }}\"\n      android:installLocation=\"auto\">\n\n    <supports-screens\n            android:smallScreens=\"true\"\n            android:normalScreens=\"true\"\n            android:largeScreens=\"true\"\n            android:anyDensity=\"true\"\n            {% if args.min_sdk_version >= 9 %}\n            android:xlargeScreens=\"true\"\n            {% endif %}\n    />\n\n    <!-- Android 2.3.3 -->\n    <uses-sdk android:minSdkVersion=\"{{ args.min_sdk_version }}\" android:targetSdkVersion=\"{{ android_api }}\" />\n\n    <!-- OpenGL ES 2.0 -->\n    <uses-feature android:glEsVersion=\"0x00020000\" />\n\n    <!-- Set permissions -->\n    {% for perm in args.permissions %}\n        <uses-permission android:name=\"{{ perm.name }}\"{% if perm.maxSdkVersion %} android:maxSdkVersion=\"{{ perm.maxSdkVersion }}\"{% endif %}{% if perm.usesPermissionFlags %} android:usesPermissionFlags=\"{{ perm.usesPermissionFlags }}\"{% endif %} />\n    {% endfor %}\n\n    {% if args.wakelock %}\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    {% endif %}\n\n    {% if args.billing_pubkey %}\n    <uses-permission android:name=\"com.android.vending.BILLING\" />\n    {% endif %}\n\n    {{ args.extra_manifest_xml }}\n\n\n    <!-- Create a Java class extending SDLActivity and place it in a\n         directory under src matching the package, e.g.\n         \tsrc/com/gamemaker/game/MyGame.java\n\n         then replace \"SDLActivity\" with the name of your class (e.g. \"MyGame\")\n         in the XML below.\n\n         An example Java class can be found in README-android.txt\n    -->\n    <application android:label=\"@string/app_name\"\n                 {% if debug %}android:debuggable=\"true\"{% endif %}\n                 android:icon=\"@mipmap/icon\"\n                 android:allowBackup=\"{{ args.allow_backup }}\"\n                 {% if args.backup_rules %}android:fullBackupContent=\"@xml/{{ args.backup_rules }}\"{% endif %}\n                 {{ args.extra_manifest_application_arguments }}\n                 android:theme=\"{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}\"\n                 android:hardwareAccelerated=\"true\"\n                 android:extractNativeLibs=\"true\" >\n        {% for l in args.android_used_libs %}\n        <uses-library android:name=\"{{ l }}\" />\n        {% endfor %}\n\n        {% for m in args.meta_data %}\n        <meta-data android:name=\"{{ m.split('=', 1)[0] }}\" android:value=\"{{ m.split('=', 1)[-1] }}\"/>{% endfor %}\n        <meta-data android:name=\"wakelock\" android:value=\"{% if args.wakelock %}1{% else %}0{% endif %}\"/>\n\n        <activity android:name=\"{{args.android_entrypoint}}\"\n                  android:label=\"@string/app_name\"\n                  android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|uiMode{% if args.min_sdk_version >= 8 %}|uiMode{% endif %}{% if args.min_sdk_version >= 13 %}|screenSize|smallestScreenSize{% endif %}{% if args.min_sdk_version >= 17 %}|layoutDirection{% endif %}{% if args.min_sdk_version >= 24 %}|density{% endif %}\"\n                  android:screenOrientation=\"{{ args.manifest_orientation }}\"\n                  android:exported=\"true\"\n                  android:theme=\"@style/KivySupportCutout\"\n                  {% if args.activity_launch_mode %}\n                  android:launchMode=\"{{ args.activity_launch_mode }}\"\n                  {% endif %}\n                  >\n\n            <intent-filter>\n            {% if args.launcher %}            \n                <action android:name=\"org.kivy.LAUNCH\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <data android:scheme=\"{{ url_scheme }}\" />\n            {% else  %}\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            {% endif %}\n\n            {% if args.home_app %}\n                <category android:name=\"android.intent.category.HOME\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            {% endif %}\n            </intent-filter>\n\n            {%- if args.intent_filters -%}\n            {{- args.intent_filters -}}\n            {%- endif -%}\n        </activity>\n\n        {% if args.launcher %}\n        <activity android:name=\"org.kivy.android.launcher.ProjectChooser\"\n                  android:icon=\"@mipmap/icon\"\n                  android:label=\"@string/app_name\"\n                  android:exported=\"true\">\n\n          <intent-filter>\n            <action android:name=\"android.intent.action.MAIN\" />\n            <category android:name=\"android.intent.category.LAUNCHER\" />\n          </intent-filter>\n\n        </activity>\n        {% endif %}\n\n        {% if service or args.launcher %}\n        <service android:name=\"{{ args.service_class_name }}\"\n                 android:process=\":pythonservice\" />\n        {% endif %}\n        {% for name, foreground_type in service_data %}\n        <service android:name=\"{{ args.package }}.Service{{ name|capitalize }}\"\n                 {% if foreground_type %}\n                 android:foregroundServiceType=\"{{ foreground_type }}\"\n                 {% endif %}\n                 android:process=\":service_{{ name }}\" />\n        {% endfor %}\n        {% for name in native_services %}\n        <service android:name=\"{{ name }}\" />\n        {% endfor %}\n\n        {% if args.billing_pubkey %}\n        <service android:name=\"org.kivy.android.billing.BillingReceiver\"\n                 android:process=\":pythonbilling\" />\n        <receiver android:name=\"org.kivy.android.billing.BillingReceiver\"\n                  android:process=\":pythonbillingreceiver\"\n                  android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"com.android.vending.billing.IN_APP_NOTIFY\" />\n                <action android:name=\"com.android.vending.billing.RESPONSE_CODE\" />\n                <action android:name=\"com.android.vending.billing.PURCHASE_STATE_CHANGED\" />\n            </intent-filter>\n        </receiver>\n        {% endif %}\n    {% for a in args.add_activity  %}\n    <activity android:name=\"{{ a }}\"></activity>\n    {% endfor %}\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/_sdl_common/build/templates/strings.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"KivySupportCutout\">\n        <item name=\"android:windowNoTitle\">true</item>\n        <!-- Display cutout is an area on some devices that extends into the display surface -->\n        {% if args.display_cutout != 'never'%}\n        <item name=\"android:windowLayoutInDisplayCutoutMode\">{{ args.display_cutout }}</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">true</item>\n        <item name=\"android:windowFullscreen\">true</item>\n        {% endif %}\n    </style>\n    <string name=\"app_name\">{{ args.name }}</string>\n    <string name=\"private_version\">{{ private_version }}</string>\n    <string name=\"presplash_color\">{{ args.presplash_color }}</string>\n    <string name=\"urlScheme\">{{ url_scheme }}</string>\n</resources>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/build.gradle",
    "content": "// Java Lint Configuration for python-for-android\n// This file configures Spotless to lint Java source files across all bootstraps\n\nplugins {\n    id 'java'\n    id 'com.diffplug.spotless' version '6.25.0'\n}\n\n// Repositories for plugin dependencies (e.g., google-java-format)\nrepositories {\n    mavenCentral()\n}\n\n// Define the root directory for bootstrap Java sources\ndef bootstrapsDir = \"${rootProject.projectDir}\"\n\n// Collect all Java source directories from all bootstraps\ndef javaSourceDirs = []\nfile(bootstrapsDir).eachDir { bootstrapDir ->\n    def srcDir = new File(bootstrapDir, 'build/src/main/java')\n    if (srcDir.exists()) {\n        javaSourceDirs.add(srcDir.absolutePath)\n    }\n}\n\nsourceSets {\n    main {\n        java {\n            srcDirs = javaSourceDirs\n        }\n    }\n}\n\nspotless {\n    java {\n        // Target all Java files from the source directories\n        target fileTree(bootstrapsDir) {\n            include '**/build/src/main/java/**/*.java'\n            // Exclude third-party vendored code\n            exclude '**/org/kamranzafar/jtar/**'\n        }\n\n        // Use Google Java Format with AOSP style (Android-friendly, slightly relaxed)\n        googleJavaFormat('1.19.2').aosp()\n\n        // Remove unused imports\n        removeUnusedImports()\n\n        // Trim trailing whitespace\n        trimTrailingWhitespace()\n\n        // Ensure files end with a newline\n        endWithNewline()\n    }\n}\n\n// Disable compilation - we only want to lint, not build\ntasks.withType(JavaCompile).configureEach {\n    enabled = false\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/ant.properties",
    "content": "# This file is used to override default values used by the Ant build system.\n#\n# This file must be checked into Version Control Systems, as it is\n# integral to the build system of your project.\n\n# This file is only used by the Ant script.\n\n# You can use this to override default values such as\n#  'source.dir' for the location of your java source folder and\n#  'out.dir' for the location of your output folder.\n\n# You can also use it define how the release builds are signed by declaring\n# the following properties:\n#  'key.store' for the location of your keystore and\n#  'key.alias' for the name of the key to use.\n# The password will be asked during the build when you use the 'release' target.\n\nsource.absolute.dir = tmp-src\n\nresource.absolute.dir = src/main/res\n\nasset.absolute.dir = src/main/assets\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/build.py",
    "content": "#!/usr/bin/env python3\n\nfrom gzip import GzipFile\nimport hashlib\nimport json\nfrom os.path import (\n    dirname, join, isfile, realpath,\n    relpath, split, exists, basename\n)\nfrom os import environ, listdir, makedirs, remove\nimport os\nimport shlex\nimport shutil\nimport subprocess\nimport sys\nimport tarfile\nimport tempfile\nimport time\n\nfrom fnmatch import fnmatch\nimport jinja2\n\nfrom pythonforandroid.bootstrap import SDL_BOOTSTRAPS\nfrom pythonforandroid.util import rmdir, ensure_dir, max_build_tool_version\n\n\ndef get_dist_info_for(key, error_if_missing=True):\n    try:\n        with open(join(dirname(__file__), 'dist_info.json'), 'r') as fileh:\n            info = json.load(fileh)\n        value = info[key]\n    except (OSError, KeyError) as e:\n        if not error_if_missing:\n            return None\n        print(\"BUILD FAILURE: Couldn't extract the key `\" + key + \"` \" +\n              \"from dist_info.json: \" + str(e))\n        sys.exit(1)\n    return value\n\n\ndef get_hostpython():\n    return get_dist_info_for('hostpython')\n\n\ndef get_bootstrap_name():\n    return get_dist_info_for('bootstrap')\n\n\nif os.name == 'nt':\n    ANDROID = 'android.bat'\n    ANT = 'ant.bat'\nelse:\n    ANDROID = 'android'\n    ANT = 'ant'\n\ncurdir = dirname(__file__)\n\nBLACKLIST_PATTERNS = [\n    # code versioning\n    '^*.hg/*',\n    '^*.git/*',\n    '^*.bzr/*',\n    '^*.svn/*',\n\n    # temp files\n    '~',\n    '*.bak',\n    '*.swp',\n\n    # Android artifacts\n    '*.apk',\n    '*.aab',\n]\n\nWHITELIST_PATTERNS = []\n\nif os.environ.get(\"P4A_BUILD_IS_RUNNING_UNITTESTS\", \"0\") != \"1\":\n    PYTHON = get_hostpython()\n    _bootstrap_name = get_bootstrap_name()\nelse:\n    PYTHON = \"python3\"\n    _bootstrap_name = \"sdl2\"\n\nif PYTHON is not None and not exists(PYTHON):\n    PYTHON = None\n\nif _bootstrap_name in ('sdl2', 'sdl3', 'webview', 'service_only', 'qt'):\n    WHITELIST_PATTERNS.append('pyconfig.h')\n\nenvironment = jinja2.Environment(loader=jinja2.FileSystemLoader(\n    join(curdir, 'templates')))\n\n\nDEFAULT_PYTHON_ACTIVITY_JAVA_CLASS = 'org.kivy.android.PythonActivity'\nDEFAULT_PYTHON_SERVICE_JAVA_CLASS = 'org.kivy.android.PythonService'\n\n\ndef render(template, dest, **kwargs):\n    '''Using jinja2, render `template` to the filename `dest`, supplying the\n\n    keyword arguments as template parameters.\n    '''\n\n    dest_dir = dirname(dest)\n    if dest_dir and not exists(dest_dir):\n        makedirs(dest_dir)\n\n    template = environment.get_template(template)\n    text = template.render(**kwargs)\n\n    f = open(dest, 'wb')\n    f.write(text.encode('utf-8'))\n    f.close()\n\n\ndef is_whitelist(name):\n    return match_filename(WHITELIST_PATTERNS, name)\n\n\ndef is_blacklist(name):\n    if is_whitelist(name):\n        return False\n    return match_filename(BLACKLIST_PATTERNS, name)\n\n\ndef match_filename(pattern_list, name):\n    for pattern in pattern_list:\n        if pattern.startswith('^'):\n            pattern = pattern[1:]\n        else:\n            pattern = '*/' + pattern\n        if fnmatch(name, pattern):\n            return True\n\n\ndef listfiles(d):\n    basedir = d\n    subdirlist = []\n    for item in os.listdir(d):\n        fn = join(d, item)\n        if isfile(fn):\n            yield fn\n        else:\n            subdirlist.append(join(basedir, item))\n    for subdir in subdirlist:\n        for fn in listfiles(subdir):\n            yield fn\n\n\ndef make_tar(tfn, source_dirs, byte_compile_python=False, optimize_python=True):\n    '''\n    Make a zip file `fn` from the contents of source_dis.\n    '''\n\n    def clean(tinfo):\n        \"\"\"cleaning function (for reproducible builds)\"\"\"\n        tinfo.uid = tinfo.gid = 0\n        tinfo.uname = tinfo.gname = ''\n        tinfo.mtime = 0\n        return tinfo\n\n    # get the files and relpath file of all the directory we asked for\n    files = []\n    for sd in source_dirs:\n        sd = realpath(sd)\n        for fn in listfiles(sd):\n            if is_blacklist(fn):\n                continue\n            if fn.endswith('.py') and byte_compile_python:\n                fn = compile_py_file(fn, optimize_python=optimize_python)\n            files.append((fn, relpath(realpath(fn), sd)))\n    files.sort()  # deterministic\n\n    # create tar.gz of those files\n    gf = GzipFile(tfn, 'wb', mtime=0)  # deterministic\n    tf = tarfile.open(None, 'w', gf, format=tarfile.USTAR_FORMAT)\n    dirs = []\n    for fn, afn in files:\n        dn = dirname(afn)\n        if dn not in dirs:\n            # create every dirs first if not exist yet\n            d = ''\n            for component in split(dn):\n                d = join(d, component)\n                if d.startswith('/'):\n                    d = d[1:]\n                if d == '' or d in dirs:\n                    continue\n                dirs.append(d)\n                tinfo = tarfile.TarInfo(d)\n                tinfo.type = tarfile.DIRTYPE\n                clean(tinfo)\n                tf.addfile(tinfo)\n\n        # put the file\n        tf.add(fn, afn, filter=clean)\n    tf.close()\n    gf.close()\n\n\ndef compile_py_file(python_file, optimize_python=True):\n    '''\n    Compile python_file to *.pyc and return the filename of the *.pyc file.\n    '''\n\n    if PYTHON is None:\n        return\n\n    args = [PYTHON, '-m', 'compileall', '-b', '-f', python_file]\n    if optimize_python:\n        # -OO = strip docstrings\n        args.insert(1, '-OO')\n    return_code = subprocess.call(args)\n\n    if return_code != 0:\n        print('Error while running \"{}\"'.format(' '.join(args)))\n        print('This probably means one of your Python files has a syntax '\n              'error, see logs above')\n        exit(1)\n\n    return \".\".join([os.path.splitext(python_file)[0], \"pyc\"])\n\n\ndef is_sdl_bootstrap():\n    return get_bootstrap_name() in SDL_BOOTSTRAPS\n\n\ndef make_package(args):\n    # If no launcher is specified, require a main.py/main.pyc:\n    if (get_bootstrap_name() != \"sdl\" or args.launcher is None) and \\\n            get_bootstrap_name() not in [\"webview\", \"service_library\"]:\n        # (webview doesn't need an entrypoint, apparently)\n        if args.private is None or (\n                not exists(join(realpath(args.private), 'main.py')) and\n                not exists(join(realpath(args.private), 'main.pyc'))):\n            print('''BUILD FAILURE: No main.py(c) found in your app directory. This\nfile must exist to act as the entry point for you app. If your app is\nstarted by a file with a different name, rename it to main.py or add a\nmain.py that loads it.''')\n            sys.exit(1)\n\n    assets_dir = \"src/main/assets\"\n\n    # Delete the old assets.\n    rmdir(assets_dir, ignore_errors=True)\n    ensure_dir(assets_dir)\n\n    # Add extra environment variable file into tar-able directory:\n    env_vars_tarpath = tempfile.mkdtemp(prefix=\"p4a-extra-env-\")\n    with open(os.path.join(env_vars_tarpath, \"p4a_env_vars.txt\"), \"w\") as f:\n        if hasattr(args, \"window\"):\n            f.write(\"P4A_IS_WINDOWED=\" + str(args.window) + \"\\n\")\n        if hasattr(args, \"sdl_orientation_hint\"):\n            f.write(\"KIVY_ORIENTATION=\" + str(args.sdl_orientation_hint) + \"\\n\")\n        f.write(\"P4A_NUMERIC_VERSION=\" + str(args.numeric_version) + \"\\n\")\n        f.write(\"P4A_MINSDK=\" + str(args.min_sdk_version) + \"\\n\")\n\n    # Package up the private data (public not supported).\n    use_setup_py = get_dist_info_for(\"use_setup_py\",\n                                     error_if_missing=False) is True\n    private_tar_dirs = [env_vars_tarpath]\n    _temp_dirs_to_clean = []\n    try:\n        if args.private:\n            if not use_setup_py or (\n                    not exists(join(args.private, \"setup.py\")) and\n                    not exists(join(args.private, \"pyproject.toml\"))\n                    ):\n                print('No setup.py/pyproject.toml used, copying '\n                      'full private data into .apk.')\n                private_tar_dirs.append(args.private)\n            else:\n                print(\"Copying main.py's ONLY, since other app data is \"\n                      \"expected in site-packages.\")\n                main_py_only_dir = tempfile.mkdtemp()\n                _temp_dirs_to_clean.append(main_py_only_dir)\n\n                # Check all main.py files we need to copy:\n                copy_paths = [\"main.py\", join(\"service\", \"main.py\")]\n                for copy_path in copy_paths:\n                    variants = [\n                        copy_path,\n                        copy_path.partition(\".\")[0] + \".pyc\",\n                    ]\n                    # Check in all variants with all possible endings:\n                    for variant in variants:\n                        if exists(join(args.private, variant)):\n                            # Make sure surrounding directly exists:\n                            dir_path = os.path.dirname(variant)\n                            if (len(dir_path) > 0 and\n                                    not exists(\n                                        join(main_py_only_dir, dir_path)\n                                    )):\n                                ensure_dir(join(main_py_only_dir, dir_path))\n                            # Copy actual file:\n                            shutil.copyfile(\n                                join(args.private, variant),\n                                join(main_py_only_dir, variant),\n                            )\n\n                # Append directory with all main.py's to result apk paths:\n                private_tar_dirs.append(main_py_only_dir)\n        if get_bootstrap_name() == \"webview\":\n            for asset in listdir('webview_includes'):\n                shutil.copy(join('webview_includes', asset), join(assets_dir, asset))\n\n        for asset in args.assets:\n            asset_src, asset_dest = asset.split(\":\")\n            if isfile(realpath(asset_src)):\n                ensure_dir(dirname(join(assets_dir, asset_dest)))\n                shutil.copy(realpath(asset_src), join(assets_dir, asset_dest))\n            else:\n                shutil.copytree(realpath(asset_src), join(assets_dir, asset_dest))\n\n        if args.private or args.launcher:\n            for arch in get_dist_info_for(\"archs\"):\n                libs_dir = f\"libs/{arch}\"\n                make_tar(\n                    join(libs_dir, \"libpybundle.so\"),\n                    [f\"_python_bundle__{arch}\"],\n                    byte_compile_python=args.byte_compile_python,\n                    optimize_python=args.optimize_python,\n                )\n            make_tar(\n                join(assets_dir, \"private.tar\"),\n                private_tar_dirs,\n                byte_compile_python=args.byte_compile_python,\n                optimize_python=args.optimize_python,\n            )\n    finally:\n        for directory in _temp_dirs_to_clean:\n            rmdir(directory)\n\n    # Remove extra env vars tar-able directory:\n    rmdir(env_vars_tarpath)\n\n    # Prepare some variables for templating process\n    res_dir = \"src/main/res\"\n    res_dir_initial = \"src/res_initial\"\n    # make res_dir stateless\n    if exists(res_dir_initial):\n        rmdir(res_dir, ignore_errors=True)\n        shutil.copytree(res_dir_initial, res_dir)\n    else:\n        shutil.copytree(res_dir, res_dir_initial)\n\n    # Add user resources\n    for resource in args.resources:\n        resource_src, resource_dest = resource.split(\":\")\n        if isfile(realpath(resource_src)):\n            ensure_dir(dirname(join(res_dir, resource_dest)))\n            shutil.copy(realpath(resource_src), join(res_dir, resource_dest))\n        else:\n            shutil.copytree(realpath(resource_src),\n                            join(res_dir, resource_dest), dirs_exist_ok=True)\n\n    default_icon = 'templates/kivy-icon.png'\n    default_presplash = 'templates/kivy-presplash.jpg'\n    shutil.copy(\n        args.icon or default_icon,\n        join(res_dir, 'mipmap/icon.png')\n    )\n    if args.icon_fg and args.icon_bg:\n        shutil.copy(args.icon_fg, join(res_dir, 'mipmap/icon_foreground.png'))\n        shutil.copy(args.icon_bg, join(res_dir, 'mipmap/icon_background.png'))\n        with open(join(res_dir, 'mipmap-anydpi-v26/icon.xml'), \"w\") as fd:\n            fd.write(\"\"\"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@mipmap/icon_background\"/>\n    <foreground android:drawable=\"@mipmap/icon_foreground\"/>\n</adaptive-icon>\n\"\"\")\n    elif args.icon_fg or args.icon_bg:\n        print(\"WARNING: Received an --icon_fg or an --icon_bg argument, but not both. \"\n              \"Ignoring.\")\n\n    if get_bootstrap_name() != \"service_only\":\n        lottie_splashscreen = join(res_dir, 'raw/splashscreen.json')\n        if args.presplash_lottie:\n            shutil.copy(\n                'templates/lottie.xml',\n                join(res_dir, 'layout/lottie.xml')\n            )\n            ensure_dir(join(res_dir, 'raw'))\n            shutil.copy(\n                args.presplash_lottie,\n                join(res_dir, 'raw/splashscreen.json')\n            )\n        else:\n            if exists(lottie_splashscreen):\n                remove(lottie_splashscreen)\n                remove(join(res_dir, 'layout/lottie.xml'))\n\n            shutil.copy(\n                args.presplash or default_presplash,\n                join(res_dir, 'drawable/presplash.jpg')\n            )\n\n    # If extra Java jars were requested, copy them into the libs directory\n    jars = []\n    if args.add_jar:\n        for jarname in args.add_jar:\n            if not exists(jarname):\n                print('Requested jar does not exist: {}'.format(jarname))\n                sys.exit(-1)\n            shutil.copy(jarname, 'src/main/libs')\n            jars.append(basename(jarname))\n\n    # If extra aar were requested, copy them into the libs directory\n    aars = []\n    if args.add_aar:\n        ensure_dir(\"libs\")\n        for aarname in args.add_aar:\n            if not exists(aarname):\n                print('Requested aar does not exists: {}'.format(aarname))\n                sys.exit(-1)\n            shutil.copy(aarname, 'libs')\n            aars.append(basename(aarname).rsplit('.', 1)[0])\n\n    versioned_name = (args.name.replace(' ', '').replace('\\'', '') +\n                      '-' + args.version)\n\n    version_code = 0\n    if not args.numeric_version:\n        \"\"\"\n        Set version code in format (10 + minsdk + app_version)\n        Historically versioning was (arch + minsdk + app_version),\n        with arch expressed with a single digit from 6 to 9.\n        Since the multi-arch support, has been changed to 10.\n        \"\"\"\n        min_sdk = args.min_sdk_version\n        for i in args.version.split('.'):\n            version_code *= 100\n            version_code += int(i)\n        args.numeric_version = \"{}{}{}\".format(\"10\", min_sdk, version_code)\n\n    if args.intent_filters:\n        with open(args.intent_filters) as fd:\n            args.intent_filters = fd.read()\n\n    if not args.add_activity:\n        args.add_activity = []\n\n    if not args.activity_launch_mode:\n        args.activity_launch_mode = ''\n\n    if args.extra_source_dirs:\n        esd = []\n        for spec in args.extra_source_dirs:\n            if ':' in spec:\n                specdir, specincludes = spec.split(':')\n                print('WARNING: Currently gradle builds only support including source '\n                      'directories, so when building using gradle all files in '\n                      '{} will be included.'.format(specdir))\n            else:\n                specdir = spec\n                specincludes = '**'\n            esd.append((realpath(specdir), specincludes))\n        args.extra_source_dirs = esd\n    else:\n        args.extra_source_dirs = []\n\n    service = False\n    if args.private:\n        service_main = join(realpath(args.private), 'service', 'main.py')\n        if exists(service_main) or exists(service_main + 'o'):\n            service = True\n\n    service_data = []\n    base_service_class = args.service_class_name.split('.')[-1]\n    for sid, spec in enumerate(args.services):\n        spec = spec.split(':')\n        name = spec[0]\n        entrypoint = spec[1]\n        options = spec[2:]\n\n        foreground = 'foreground' in options\n        sticky = 'sticky' in options\n        foreground_type_option = next((s for s in options if s.startswith('foregroundServiceType')), None)\n        foreground_type = None\n        if foreground_type_option:\n            parts = foreground_type_option.split('=', 1)\n            if len(parts) != 2 or not parts[1]:\n                raise ValueError(\n                    'Missing value for `foregroundServiceType` option. '\n                    'Expected format: foregroundServiceType=location'\n                )\n            foreground_type = parts[1]\n\n        service_data.append((name, foreground_type))\n        service_target_path =\\\n            'src/main/java/{}/Service{}.java'.format(\n                args.package.replace(\".\", \"/\"),\n                name.capitalize()\n            )\n        render(\n            'Service.tmpl.java',\n            service_target_path,\n            name=name,\n            entrypoint=entrypoint,\n            args=args,\n            foreground=foreground,\n            sticky=sticky,\n            service_id=sid + 1,\n            base_service_class=base_service_class,\n        )\n\n    # Find the SDK directory and target API\n    with open('project.properties', 'r') as fileh:\n        target = fileh.read().strip()\n    android_api = target.split('-')[1]\n\n    if android_api.isdigit():\n        android_api = int(android_api)\n    else:\n        raise ValueError(\n            \"failed to extract the Android API level from \" +\n            \"build.properties. expected int, got: '\" +\n            str(android_api) + \"'\"\n        )\n\n    with open('local.properties', 'r') as fileh:\n        sdk_dir = fileh.read().strip()\n    sdk_dir = sdk_dir[8:]\n\n    # Try to build with the newest available build tools\n    ignored = {\".DS_Store\", \".ds_store\"}\n    build_tools_versions = [x for x in listdir(join(sdk_dir, 'build-tools')) if x not in ignored]\n    build_tools_version = max_build_tool_version(build_tools_versions)\n\n    # Folder name for launcher (used by SDL2 bootstrap)\n    url_scheme = 'kivy'\n\n    # Copy backup rules file if specified and update the argument\n    res_xml_dir = join(res_dir, 'xml')\n    if args.backup_rules:\n        ensure_dir(res_xml_dir)\n        shutil.copy(join(args.private, args.backup_rules), res_xml_dir)\n        args.backup_rules = split(args.backup_rules)[1][:-4]\n\n    # Copy res_xml files to src/main/res/xml\n    if args.res_xmls:\n        ensure_dir(res_xml_dir)\n        for xmlpath in args.res_xmls:\n            if not os.path.exists(xmlpath):\n                xmlpath = join(args.private, xmlpath)\n            shutil.copy(xmlpath, res_xml_dir)\n\n    # Render out android manifest:\n    manifest_path = \"src/main/AndroidManifest.xml\"\n    render_args = {\n        \"args\": args,\n        \"service\": service,\n        \"service_data\": service_data,\n        \"android_api\": android_api,\n        \"debug\": \"debug\" in args.build_mode,\n        \"native_services\": args.native_services,\n    }\n    if is_sdl_bootstrap():\n        render_args[\"url_scheme\"] = url_scheme\n\n    render(\n        'AndroidManifest.tmpl.xml',\n        manifest_path,\n        **render_args)\n\n    # Copy the AndroidManifest.xml to the dist root dir so that ant\n    # can also use it\n    if exists('AndroidManifest.xml'):\n        remove('AndroidManifest.xml')\n    shutil.copy(manifest_path, 'AndroidManifest.xml')\n\n    # gradle build templates\n    render(\n        'build.tmpl.gradle',\n        'build.gradle',\n        args=args,\n        aars=aars,\n        jars=jars,\n        android_api=android_api,\n        build_tools_version=build_tools_version,\n        debug_build=\"debug\" in args.build_mode,\n        is_library=(get_bootstrap_name() == 'service_library'),\n        )\n\n    # gradle properties\n    render(\n        'gradle.tmpl.properties',\n        'gradle.properties',\n        args=args,\n        bootstrap_name=get_bootstrap_name())\n\n    # ant build templates\n    render(\n        'build.tmpl.xml',\n        'build.xml',\n        args=args,\n        versioned_name=versioned_name)\n\n    # String resources:\n    timestamp = time.time()\n    if 'SOURCE_DATE_EPOCH' in environ:\n        # for reproducible builds\n        timestamp = int(environ['SOURCE_DATE_EPOCH'])\n    private_version = \"{} {} {}\".format(\n        args.version,\n        args.numeric_version,\n        timestamp\n    )\n    render_args = {\n        \"args\": args,\n        \"private_version\": hashlib.sha1(private_version.encode()).hexdigest()\n    }\n    if is_sdl_bootstrap():\n        render_args[\"url_scheme\"] = url_scheme\n    render(\n        'strings.tmpl.xml',\n        join(res_dir, 'values/strings.xml'),\n        **render_args)\n\n    # Library resources from Qt\n    # These are referred by QtLoader.java in Qt6AndroidBindings.jar\n    # qt_libs and load_local_libs are loaded at App startup\n    if get_bootstrap_name() == \"qt\":\n        qt_libs = args.qt_libs.split(\",\")\n        load_local_libs = args.load_local_libs.split(\",\")\n        init_classes = args.init_classes\n        if init_classes:\n            init_classes = init_classes.split(\",\")\n            init_classes = \":\".join(init_classes)\n        arch = get_dist_info_for(\"archs\")[0]\n        render(\n            'libs.tmpl.xml',\n            join(res_dir, 'values/libs.xml'),\n            qt_libs=qt_libs,\n            load_local_libs=load_local_libs,\n            init_classes=init_classes,\n            arch=arch\n        )\n\n    if exists(join(\"templates\", \"custom_rules.tmpl.xml\")):\n        render(\n            'custom_rules.tmpl.xml',\n            'custom_rules.xml',\n            args=args)\n\n    if get_bootstrap_name() == \"webview\":\n        render('WebViewLoader.tmpl.java',\n               'src/main/java/org/kivy/android/WebViewLoader.java',\n               args=args)\n\n    if args.sign:\n        render('build.properties', 'build.properties')\n    else:\n        if exists('build.properties'):\n            os.remove('build.properties')\n\n    # Apply java source patches if any are present:\n    if exists(join('src', 'patches')):\n        print(\"Applying Java source code patches...\")\n        for patch_name in os.listdir(join('src', 'patches')):\n            patch_path = join('src', 'patches', patch_name)\n            print(\"Applying patch: \" + str(patch_path))\n\n            # -N: insist this is FORWARD patch, don't reverse apply\n            # -p1: strip first path component\n            # -t: batch mode, don't ask questions\n            patch_command = [\"patch\", \"-N\", \"-p1\", \"-t\", \"-i\", patch_path]\n\n            try:\n                # Use a dry run to establish whether the patch is already applied.\n                # If we don't check this, the patch may be partially applied (which is bad!)\n                subprocess.check_output(patch_command + [\"--dry-run\"])\n            except subprocess.CalledProcessError as e:\n                if e.returncode == 1:\n                    # Return code 1 means not all hunks could be applied, this usually\n                    # means the patch is already applied.\n                    print(\"Warning: failed to apply patch (exit code 1), \"\n                          \"assuming it is already applied: \",\n                          str(patch_path))\n                else:\n                    raise e\n            else:\n                # The dry run worked, so do the real thing\n                subprocess.check_output(patch_command)\n\n\ndef parse_permissions(args_permissions):\n    if args_permissions and isinstance(args_permissions[0], list):\n        args_permissions = [p for perm in args_permissions for p in perm]\n\n    def _is_advanced_permission(permission):\n        return permission.startswith(\"(\") and permission.endswith(\")\")\n\n    def _decode_advanced_permission(permission):\n        SUPPORTED_PERMISSION_PROPERTIES = [\"name\", \"maxSdkVersion\", \"usesPermissionFlags\"]\n        _permission_args = permission[1:-1].split(\";\")\n        _permission_args = (arg.split(\"=\") for arg in _permission_args)\n        advanced_permission = dict(_permission_args)\n\n        if \"name\" not in advanced_permission:\n            raise ValueError(\"Advanced permission must have a name property\")\n\n        for key in advanced_permission.keys():\n            if key not in SUPPORTED_PERMISSION_PROPERTIES:\n                raise ValueError(\n                    f\"Property '{key}' is not supported. \"\n                    \"Advanced permission only supports: \"\n                    f\"{', '.join(SUPPORTED_PERMISSION_PROPERTIES)} properties\"\n                )\n\n        return advanced_permission\n\n    _permissions = []\n    for permission in args_permissions:\n        if _is_advanced_permission(permission):\n            _permissions.append(_decode_advanced_permission(permission))\n        else:\n            if \".\" in permission:\n                _permissions.append(dict(name=permission))\n            else:\n                _permissions.append(dict(name=f\"android.permission.{permission}\"))\n    return _permissions\n\n\ndef get_sdl_orientation_hint(orientations):\n    SDL_ORIENTATION_MAP = {\n        \"landscape\": \"LandscapeLeft\",\n        \"portrait\": \"Portrait\",\n        \"portrait-reverse\": \"PortraitUpsideDown\",\n        \"landscape-reverse\": \"LandscapeRight\",\n    }\n    return \" \".join(\n        [SDL_ORIENTATION_MAP[x] for x in orientations if x in SDL_ORIENTATION_MAP]\n    )\n\n\ndef get_manifest_orientation(orientations, manifest_orientation=None):\n    # If the user has specifically set an orientation to use in the manifest,\n    # use that.\n    if manifest_orientation is not None:\n        return manifest_orientation\n\n    # If multiple or no orientations are specified, use unspecified in the manifest,\n    # as we can only specify one orientation in the manifest.\n    if len(orientations) != 1:\n        return \"unspecified\"\n\n    # Convert the orientation to a value that can be used in the manifest.\n    # If the specified orientation is not supported, use unspecified.\n    MANIFEST_ORIENTATION_MAP = {\n        \"landscape\": \"landscape\",\n        \"portrait\": \"portrait\",\n        \"portrait-reverse\": \"reversePortrait\",\n        \"landscape-reverse\": \"reverseLandscape\",\n    }\n    return MANIFEST_ORIENTATION_MAP.get(orientations[0], \"unspecified\")\n\n\ndef get_dist_ndk_min_api_level():\n    # Get the default minsdk, equal to the NDK API that this dist is built against\n    try:\n        with open('dist_info.json', 'r') as fileh:\n            info = json.load(fileh)\n            ndk_api = int(info['ndk_api'])\n    except (OSError, KeyError, ValueError, TypeError):\n        print('WARNING: Failed to read ndk_api from dist info, defaulting to 12')\n        ndk_api = 12  # The old default before ndk_api was introduced\n    return ndk_api\n\n\ndef create_argument_parser():\n    ndk_api = get_dist_ndk_min_api_level()\n    import argparse\n    ap = argparse.ArgumentParser(description='''\\\nPackage a Python application for Android (using\nbootstrap ''' + get_bootstrap_name() + ''').\n\nFor this to work, Java and Ant need to be in your path, as does the\ntools directory of the Android SDK.\n''')\n\n    # --private is required unless for sdl2, where there's also --launcher\n    ap.add_argument('--private', dest='private',\n                    help='the directory with the app source code files' +\n                         ' (containing your main.py entrypoint)',\n                    required=(not is_sdl_bootstrap()))\n    ap.add_argument('--package', dest='package',\n                    help=('The name of the java package the project will be'\n                          ' packaged under.'),\n                    required=True)\n    ap.add_argument('--name', dest='name',\n                    help=('The human-readable name of the project.'),\n                    required=True)\n    ap.add_argument('--numeric-version', dest='numeric_version',\n                    help=('The numeric version number of the project. If not '\n                          'given, this is automatically computed from the '\n                          'version.'))\n    ap.add_argument('--version', dest='version',\n                    help=('The version number of the project. This should '\n                          'consist of numbers and dots, and should have the '\n                          'same number of groups of numbers as previous '\n                          'versions.'),\n                    required=True)\n    if is_sdl_bootstrap():\n        ap.add_argument('--launcher', dest='launcher', action='store_true',\n                        help=('Provide this argument to build a multi-app '\n                              'launcher, rather than a single app.'))\n        ap.add_argument('--home-app', dest='home_app', action='store_true', default=False,\n                        help=('Turn your application into a home app (launcher)'))\n    ap.add_argument('--display-cutout', dest='display_cutout', default='never',\n                    help=('Enables display-cutout that renders around the area (notch) on '\n                          'some devices that extends into the display surface'))\n    ap.add_argument('--permission', dest='permissions', action='append', default=[],\n                    help='The permissions to give this app.', nargs='+')\n    ap.add_argument('--meta-data', dest='meta_data', action='append', default=[],\n                    help='Custom key=value to add in application metadata')\n    ap.add_argument('--uses-library', dest='android_used_libs', action='append', default=[],\n                    help='Used shared libraries included using <uses-library> tag in AndroidManifest.xml')\n    ap.add_argument('--asset', dest='assets',\n                    action=\"append\", default=[],\n                    metavar=\"/path/to/source:dest\",\n                    help='Put this in the assets folder at assets/dest')\n    ap.add_argument('--resource', dest='resources',\n                    action=\"append\", default=[],\n                    metavar=\"/path/to/source:kind/asset\",\n                    help='Put this in the res folder at res/kind')\n    ap.add_argument('--icon', dest='icon',\n                    help=('A png file to use as the icon for '\n                          'the application.'))\n    ap.add_argument('--icon-fg', dest='icon_fg',\n                    help=('A png file to use as the foreground of the adaptive icon '\n                          'for the application.'))\n    ap.add_argument('--icon-bg', dest='icon_bg',\n                    help=('A png file to use as the background of the adaptive icon '\n                          'for the application.'))\n    ap.add_argument('--service', dest='services', action='append', default=[],\n                    help='Declare a new service entrypoint: '\n                         'NAME:PATH_TO_PY[:foreground]')\n    ap.add_argument('--native-service', dest='native_services', action='append', default=[],\n                    help='Declare a new native service: '\n                         'package.name.service')\n    if get_bootstrap_name() != \"service_only\":\n        ap.add_argument('--presplash', dest='presplash',\n                        help=('A jpeg file to use as a screen while the '\n                              'application is loading.'))\n        ap.add_argument('--presplash-lottie', dest='presplash_lottie',\n                        help=('A lottie (json) file to use as an animation while the '\n                              'application is loading.'))\n        ap.add_argument('--presplash-color',\n                        dest='presplash_color',\n                        default='#000000',\n                        help=('A string to set the loading screen '\n                              'background color. '\n                              'Supported formats are: '\n                              '#RRGGBB #AARRGGBB or color names '\n                              'like red, green, blue, etc.'))\n        ap.add_argument('--window', dest='window', action='store_true',\n                        default=False,\n                        help='Indicate if the application will be windowed')\n        ap.add_argument('--manifest-orientation', dest='manifest_orientation',\n                        help=('The orientation that will be set in the '\n                              'android:screenOrientation attribute of the activity '\n                              'in the AndroidManifest.xml file. If not set, '\n                              'the value will be synthesized from the --orientation option.'))\n        ap.add_argument('--orientation', dest='orientation',\n                        action=\"append\", default=[],\n                        choices=['portrait', 'landscape', 'landscape-reverse', 'portrait-reverse'],\n                        help=('The orientations that the app will display in. '\n                              'Since Android ignores android:screenOrientation '\n                              'when in multi-window mode (Which is the default on Android 12+), '\n                              'this option will also set the window orientation hints '\n                              'for apps using the (default) SDL bootstrap.'\n                              'If multiple orientations are given, android:screenOrientation '\n                              'will be set to \"unspecified\"'))\n\n    ap.add_argument('--enable-androidx', dest='enable_androidx',\n                    action='store_true',\n                    help=('Enable the AndroidX support library, '\n                          'requires api = 28 or greater'))\n    ap.add_argument('--android-entrypoint', dest='android_entrypoint',\n                    default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS,\n                    help='Defines which java class will be used for startup, usually a subclass of PythonActivity')\n    ap.add_argument('--android-apptheme', dest='android_apptheme',\n                    default='@android:style/Theme.NoTitleBar',\n                    help='Defines which app theme should be selected for the main activity')\n    ap.add_argument('--add-compile-option', dest='compile_options', default=[],\n                    action='append', help='add compile options to gradle.build')\n    ap.add_argument('--add-gradle-repository', dest='gradle_repositories',\n                    default=[],\n                    action='append',\n                    help='Ddd a repository for gradle')\n    ap.add_argument('--add-packaging-option', dest='packaging_options',\n                    default=[],\n                    action='append',\n                    help='Dndroid packaging options')\n\n    ap.add_argument('--wakelock', dest='wakelock', action='store_true',\n                    help=('Indicate if the application needs the device '\n                          'to stay on'))\n    ap.add_argument('--blacklist', dest='blacklist',\n                    default=join(curdir, 'blacklist.txt'),\n                    help=('Use a blacklist file to match unwanted file in '\n                          'the final APK'))\n    ap.add_argument('--whitelist', dest='whitelist',\n                    default=join(curdir, 'whitelist.txt'),\n                    help=('Use a whitelist file to prevent blacklisting of '\n                          'file in the final APK'))\n    ap.add_argument('--release', dest='build_mode', action='store_const',\n                    const='release', default='debug',\n                    help='Build your app as a non-debug release build. '\n                         '(Disables gdb debugging among other things)')\n    ap.add_argument('--with-debug-symbols', dest='with_debug_symbols',\n                    action='store_const', const=True, default=False,\n                    help='Will keep debug symbols from `.so` files.')\n    ap.add_argument('--add-jar', dest='add_jar', action='append',\n                    help=('Add a Java .jar to the libs, so you can access its '\n                          'classes with pyjnius. You can specify this '\n                          'argument more than once to include multiple jars'))\n    ap.add_argument('--add-aar', dest='add_aar', action='append',\n                    help=('Add an aar dependency manually'))\n    ap.add_argument('--depend', dest='depends', action='append',\n                    help=('Add a external dependency '\n                          '(eg: com.android.support:appcompat-v7:19.0.1)'))\n    # The --sdk option has been removed, it is ignored in favour of\n    # --android-api handled by toolchain.py\n    ap.add_argument('--sdk', dest='sdk_version', default=-1,\n                    type=int, help=('Deprecated argument, does nothing'))\n    ap.add_argument('--minsdk', dest='min_sdk_version',\n                    default=ndk_api, type=int,\n                    help=('Minimum Android SDK version that the app supports. '\n                          'Defaults to {}.'.format(ndk_api)))\n    ap.add_argument('--allow-minsdk-ndkapi-mismatch', default=False,\n                    action='store_true',\n                    help=('Allow the --minsdk argument to be different from '\n                          'the discovered ndk_api in the dist'))\n    ap.add_argument('--intent-filters', dest='intent_filters',\n                    help=('Add intent-filters xml rules to the '\n                          'AndroidManifest.xml file. The argument is a '\n                          'filename containing xml. The filename should be '\n                          'located relative to the python-for-android '\n                          'directory'))\n    ap.add_argument('--res_xml', dest='res_xmls', action='append', default=[],\n                    help='Add files to res/xml directory (for example device-filters)', nargs='+')\n    ap.add_argument('--with-billing', dest='billing_pubkey',\n                    help='If set, the billing service will be added (not implemented)')\n    ap.add_argument('--add-source', dest='extra_source_dirs', action='append',\n                    help='Include additional source dirs in Java build')\n    if get_bootstrap_name() == \"webview\":\n        ap.add_argument('--port',\n                        help='The port on localhost that the WebView will access',\n                        default='5000')\n    ap.add_argument('--try-system-python-compile', dest='try_system_python_compile',\n                    action='store_true',\n                    help='Use the system python during compileall if possible.')\n    ap.add_argument('--sign', action='store_true',\n                    help=('Try to sign the APK with your credentials. You must set '\n                          'the appropriate environment variables.'))\n    ap.add_argument('--add-activity', dest='add_activity', action='append',\n                    help='Add this Java class as an Activity to the manifest.')\n    ap.add_argument('--activity-launch-mode',\n                    dest='activity_launch_mode',\n                    default='singleTask',\n                    help='Set the launch mode of the main activity in the manifest.')\n    ap.add_argument('--allow-backup', dest='allow_backup', default='true',\n                    help=\"if set to 'false', then android won't backup the application.\")\n    ap.add_argument('--backup-rules', dest='backup_rules', default='',\n                    help=('Backup rules for Android Auto Backup. Argument is a '\n                          'filename containing xml. The filename should be '\n                          'located relative to the private directory containing your source code '\n                          'files (containing your main.py entrypoint). '\n                          'See https://developer.android.com/guide/topics/data/'\n                          'autobackup#IncludingFiles for more information'))\n    ap.add_argument('--no-byte-compile-python', dest='byte_compile_python',\n                    action='store_false', default=True,\n                    help='Skip byte compile for .py files.')\n    ap.add_argument('--no-optimize-python', dest='optimize_python',\n                    action='store_false', default=True,\n                    help=('Whether to compile to optimised .pyc files, using -OO '\n                          '(strips docstrings and asserts)'))\n    ap.add_argument('--extra-manifest-xml', default='',\n                    help=('Extra xml to write directly inside the <manifest> element of'\n                          'AndroidManifest.xml'))\n    ap.add_argument('--extra-manifest-application-arguments', default='',\n                    help='Extra arguments to be added to the <manifest><application> tag of'\n                         'AndroidManifest.xml')\n    ap.add_argument('--manifest-placeholders', dest='manifest_placeholders',\n                    default='[:]', help=('Inject build variables into the manifest '\n                                         'via the manifestPlaceholders property'))\n    ap.add_argument('--service-class-name', dest='service_class_name', default=DEFAULT_PYTHON_SERVICE_JAVA_CLASS,\n                    help='Use that parameter if you need to implement your own PythonServive Java class')\n    ap.add_argument('--activity-class-name', dest='activity_class_name', default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS,\n                    help='The full java class name of the main activity')\n    if get_bootstrap_name() == \"qt\":\n        ap.add_argument('--qt-libs', dest='qt_libs', required=True,\n                        help='comma separated list of Qt libraries to be loaded')\n        ap.add_argument('--load-local-libs', dest='load_local_libs', required=True,\n                        help='comma separated list of Qt plugin libraries to be loaded')\n        ap.add_argument('--init-classes', dest='init_classes', default='',\n                        help='comma separated list of java class names to be loaded from the Qt jar files, '\n                             'specified through add_jar cli option')\n\n    return ap\n\n\ndef parse_args_and_make_package(args=None):\n    global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON\n\n    ndk_api = get_dist_ndk_min_api_level()\n    ap = create_argument_parser()\n\n    # Put together arguments, and add those from .p4a config file:\n    if args is None:\n        args = sys.argv[1:]\n\n    def _read_configuration():\n        if not exists(\".p4a\"):\n            return\n        print(\"Reading .p4a configuration\")\n        with open(\".p4a\") as fd:\n            lines = fd.readlines()\n        lines = [shlex.split(line)\n                 for line in lines if not line.startswith(\"#\")]\n        for line in lines:\n            for arg in line:\n                args.append(arg)\n    _read_configuration()\n\n    args = ap.parse_args(args)\n\n    if args.name and args.name[0] == '\"' and args.name[-1] == '\"':\n        args.name = args.name[1:-1]\n\n    if ndk_api != args.min_sdk_version:\n        print(('WARNING: --minsdk argument does not match the api that is '\n               'compiled against. Only proceed if you know what you are '\n               'doing, otherwise use --minsdk={} or recompile against api '\n               '{}').format(ndk_api, args.min_sdk_version))\n        if not args.allow_minsdk_ndkapi_mismatch:\n            print('You must pass --allow-minsdk-ndkapi-mismatch to build '\n                  'with --minsdk different to the target NDK api from the '\n                  'build step')\n            sys.exit(1)\n        else:\n            print('Proceeding with --minsdk not matching build target api')\n\n    if args.billing_pubkey:\n        print('Billing not yet supported!')\n        sys.exit(1)\n\n    if args.sdk_version != -1:\n        print('WARNING: Received a --sdk argument, but this argument is '\n              'deprecated and does nothing.')\n        args.sdk_version = -1  # ensure it is not used\n\n    args.permissions = parse_permissions(args.permissions)\n\n    args.manifest_orientation = get_manifest_orientation(\n        args.orientation, args.manifest_orientation\n    )\n\n    if is_sdl_bootstrap():\n        args.sdl_orientation_hint = get_sdl_orientation_hint(args.orientation)\n\n    if args.res_xmls and isinstance(args.res_xmls[0], list):\n        args.res_xmls = [x for res in args.res_xmls for x in res]\n\n    if args.try_system_python_compile:\n        # Hardcoding python2.7 is okay for now, as python3 skips the\n        # compilation anyway\n        python_executable = 'python2.7'\n        try:\n            subprocess.call([python_executable, '--version'])\n        except (OSError, subprocess.CalledProcessError):\n            pass\n        else:\n            PYTHON = python_executable\n\n    if args.blacklist:\n        with open(args.blacklist) as fd:\n            patterns = [x.strip() for x in fd.read().splitlines()\n                        if x.strip() and not x.strip().startswith('#')]\n        BLACKLIST_PATTERNS += patterns\n\n    if args.whitelist:\n        with open(args.whitelist) as fd:\n            patterns = [x.strip() for x in fd.read().splitlines()\n                        if x.strip() and not x.strip().startswith('#')]\n        WHITELIST_PATTERNS += patterns\n\n    if args.private is None and is_sdl_bootstrap() and args.launcher is None:\n        print('Need --private directory or ' +\n              '--launcher (SDL2/SDL3 bootstrap only)' +\n              'to have something to launch inside the .apk!')\n        sys.exit(1)\n    make_package(args)\n\n    return args\n\n\nif __name__ == \"__main__\":\n    parse_args_and_make_package()\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Mar 09 17:19:02 CET 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-all.zip\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/jni/Android.mk",
    "content": "include $(call all-subdir-makefiles)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/jni/application/Android.mk",
    "content": "include $(call all-subdir-makefiles)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\nSDL_PATH := ../../SDL\n\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include\n\n# Add your application source files here...\nLOCAL_SRC_FILES := start.c\n\nLOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)\n\nLOCAL_SHARED_LIBRARIES := python_shared\n\nLOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS)\n\nLOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/jni/application/src/start.c",
    "content": "\n#define PY_SSIZE_T_CLEAN\n#include \"Python.h\"\n#ifndef Py_PYTHON_H\n#error Python headers needed to compile C extensions, please install development version of Python.\n#else\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <dlfcn.h>\n#include <libgen.h>\n#include <jni.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <errno.h>\n\n#include \"bootstrap_name.h\"\n\n#ifdef BOOTSTRAP_NAME_SDL2\n#include \"SDL.h\"\n#include \"SDL_opengles2.h\"\n#endif\n\n#ifdef BOOTSTRAP_NAME_SDL3\n#include \"SDL3/SDL.h\"\n#include \"SDL3/SDL_main.h\"\n#endif\n\n#include \"android/log.h\"\n\n#define ENTRYPOINT_MAXLEN 128\n#define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x))\n#define P4A_MIN_VER 11\nstatic void LOGP(const char *fmt, ...) {\n    va_list args;\n    va_start(args, fmt);\n    __android_log_vprint(ANDROID_LOG_INFO, \"python\", fmt, args);\n    va_end(args);\n}\n\nstatic PyObject *androidembed_log(PyObject *self, PyObject *args) {\n  char *logstr = NULL;\n  if (!PyArg_ParseTuple(args, \"s\", &logstr)) {\n    return NULL;\n  }\n  LOG(getenv(\"PYTHON_NAME\"), logstr);\n  Py_RETURN_NONE;\n}\n\nstatic PyMethodDef AndroidEmbedMethods[] = {\n    {\"log\", androidembed_log, METH_VARARGS, \"Log on android platform\"},\n    {NULL, NULL, 0, NULL}};\n\n#if PY_MAJOR_VERSION >= 3\nstatic struct PyModuleDef androidembed = {PyModuleDef_HEAD_INIT, \"androidembed\",\n                                          \"\", -1, AndroidEmbedMethods};\n\nPyMODINIT_FUNC initandroidembed(void) {\n  return PyModule_Create(&androidembed);\n}\n#else\nPyMODINIT_FUNC initandroidembed(void) {\n  (void)Py_InitModule(\"androidembed\", AndroidEmbedMethods);\n}\n#endif\n\nint dir_exists(char *filename) {\n  struct stat st;\n  if (stat(filename, &st) == 0) {\n    if (S_ISDIR(st.st_mode))\n      return 1;\n  }\n  return 0;\n}\n\nint file_exists(const char *filename) {\n    return access(filename, F_OK) == 0;\n}\n\nstatic void get_dirname(const char *path, char *dir, size_t size) {\n    strncpy(dir, path, size - 1);\n    dir[size - 1] = '\\0';\n    char *last_slash = strrchr(dir, '/');\n    if (last_slash) *last_slash = '\\0';\n    else dir[0] = '\\0';\n}\n\n// strip \"lib\" prefix and \"bin.so\" suffix\nstatic void get_exe_name(const char *filename, char *out, size_t size) {\n    size_t len = strlen(filename);\n    if (len < 7) {  // too short to be valid\n        strncpy(out, filename, size - 1);\n        out[size - 1] = '\\0';\n        return;\n    }\n\n    const char *start = filename;\n    if (strncmp(filename, \"lib\", 3) == 0) start += 3;\n    size_t start_len = strlen(start);\n\n    if (start_len > 6) {\n        size_t copy_len = start_len - 6; // remove \"bin.so\"\n        if (copy_len >= size) copy_len = size - 1;\n        strncpy(out, start, copy_len);\n        out[copy_len] = '\\0';\n    } else {\n        strncpy(out, start, size - 1);\n        out[size - 1] = '\\0';\n    }\n}\n\nchar *setup_symlinks() {\n    Dl_info info;\n    char lib_path[512];\n    char *interpreter = NULL;\n\n    if (!(dladdr((void*)setup_symlinks, &info) && info.dli_fname)) {\n        LOGP(\"symlinking failed: failed to get libdir\");\n        return interpreter;\n    }\n\n    strncpy(lib_path, info.dli_fname, sizeof(lib_path) - 1);\n    lib_path[sizeof(lib_path) - 1] = '\\0';\n\n    char native_lib_dir[512];\n    get_dirname(lib_path, native_lib_dir, sizeof(native_lib_dir));\n    if (native_lib_dir[0] == '\\0') {\n        LOGP(\"symlinking failed: could not determine lib directory\");\n        return interpreter;\n    }\n\n    const char *files_dir_env = getenv(\"ANDROID_APP_PATH\");\n    char bin_dir[512];\n\n    snprintf(bin_dir, sizeof(bin_dir), \"%s/.bin\", files_dir_env);\n    if (mkdir(bin_dir, 0755) != 0 && errno != EEXIST) {\n        LOGP(\"Failed to create .bin directory\");\n        return interpreter;\n    }\n\n    DIR *dir = opendir(native_lib_dir);\n    if (!dir) {\n        LOGP(\"Failed to open native lib dir\");\n        return interpreter;\n    }\n\n    struct dirent *entry;\n    while ((entry = readdir(dir)) != NULL) {\n        const char *name = entry->d_name;\n        size_t len = strlen(name);\n\n        if (len < 7) continue; \n        if (strcmp(name + len - 6, \"bin.so\") != 0) continue; // only bin.so at end\n\n        // get cleaned executable name\n        char exe_name[128];\n        get_exe_name(name, exe_name, sizeof(exe_name));\n\n        char src[512], dst[512];\n        snprintf(src, sizeof(src), \"%s/%s\", native_lib_dir, name);\n        snprintf(dst, sizeof(dst), \"%s/%s\", bin_dir, exe_name);\n  \n        // interpreter found?\n        if (strcmp(exe_name, \"python\") == 0) {\n          interpreter = strdup(dst);\n        }\n\n        struct stat st;\n        if (lstat(dst, &st) == 0) continue; // already exists\n        if (symlink(src, dst) == 0) {\n            LOGP(\"symlink: %s -> %s\", name, exe_name);\n        } else {\n            LOGP(\"Symlink failed\");\n        }\n    }\n\n    closedir(dir);\n\n    // append bin_dir to PATH\n    const char *old_path = getenv(\"PATH\");\n    char new_path[1024];\n    if (old_path && strlen(old_path) > 0) {\n        snprintf(new_path, sizeof(new_path), \"%s:%s\", old_path, bin_dir);\n    } else {\n        snprintf(new_path, sizeof(new_path), \"%s\", bin_dir);\n    }\n    setenv(\"PATH\", new_path, 1);\n\n    // set lib path\n    setenv(\"LD_LIBRARY_PATH\", native_lib_dir, 1);\n\n  return interpreter;\n}\n\n\n/* int main(int argc, char **argv) { */\nint main(int argc, char *argv[]) {\n\n  char *env_argument = NULL;\n  char *env_entrypoint = NULL;\n  char *env_logname = NULL;\n  char entrypoint[ENTRYPOINT_MAXLEN];\n  int ret = 0;\n  FILE *fd;\n\n  LOGP(\"Initializing Python for Android\");\n\n  // Set a couple of built-in environment vars:\n  setenv(\"P4A_BOOTSTRAP\", bootstrap_name, 1);  // env var to identify p4a to applications\n  env_argument = getenv(\"ANDROID_ARGUMENT\");\n  setenv(\"ANDROID_APP_PATH\", env_argument, 1);\n  env_entrypoint = getenv(\"ANDROID_ENTRYPOINT\");\n  env_logname = getenv(\"PYTHON_NAME\");\n  if (!getenv(\"ANDROID_UNPACK\")) {\n    /* ANDROID_UNPACK currently isn't set in services */\n    setenv(\"ANDROID_UNPACK\", env_argument, 1);\n  }\n  if (env_logname == NULL) {\n    env_logname = \"python\";\n    setenv(\"PYTHON_NAME\", \"python\", 1);\n  }\n\n  // Set additional file-provided environment vars:\n  LOGP(\"Setting additional env vars from p4a_env_vars.txt\");\n  char env_file_path[256];\n  snprintf(env_file_path, sizeof(env_file_path),\n           \"%s/p4a_env_vars.txt\", getenv(\"ANDROID_UNPACK\"));\n  FILE *env_file_fd = fopen(env_file_path, \"r\");\n  if (env_file_fd) {\n    char* line = NULL;\n    size_t len = 0;\n    while (getline(&line, &len, env_file_fd) != -1) {\n      if (strlen(line) > 0) {\n        char *eqsubstr = strstr(line, \"=\");\n        if (eqsubstr) {\n          size_t eq_pos = eqsubstr - line;\n\n          // Extract name:\n          char env_name[256];\n          strncpy(env_name, line, sizeof(env_name));\n          env_name[eq_pos] = '\\0';\n\n          // Extract value (with line break removed:\n          char env_value[256];\n          strncpy(env_value, (char*)(line + eq_pos + 1), sizeof(env_value));\n          if (strlen(env_value) > 0 &&\n              env_value[strlen(env_value)-1] == '\\n') {\n            env_value[strlen(env_value)-1] = '\\0';\n            if (strlen(env_value) > 0 &&\n                env_value[strlen(env_value)-1] == '\\r') {\n              // Also remove windows line breaks (\\r\\n)\n              env_value[strlen(env_value)-1] = '\\0';\n            } \n          }\n\n          // Set value:\n          setenv(env_name, env_value, 1);\n        }\n      }\n    }\n    fclose(env_file_fd);\n  } else {\n    LOGP(\"Warning: no p4a_env_vars.txt found / failed to open!\");\n  }\n\n  LOGP(\"Changing directory to '%s'\", env_argument);\n  chdir(env_argument);\n\n  char *interpreter = setup_symlinks();\n\n#if PY_MAJOR_VERSION < 3\n  Py_NoSiteFlag=1;\n#endif\n\n\n#if PY_MAJOR_VERSION >= 3\n  /* our logging module for android\n   */\n  PyImport_AppendInittab(\"androidembed\", initandroidembed);\n#endif\n\n  LOGP(\"Preparing to initialize python\");\n\n  // Set up the python path\n  char paths[256];\n\n  char python_bundle_dir[256];\n  snprintf(python_bundle_dir, 256,\n           \"%s/_python_bundle\", getenv(\"ANDROID_UNPACK\"));\n\n  #if PY_MAJOR_VERSION >= 3\n\n    #if PY_MINOR_VERSION >= P4A_MIN_VER\n      PyConfig config;\n      PyConfig_InitPythonConfig(&config);\n      config.program_name = L\"android_python\";\n    #else\n      Py_SetProgramName(L\"android_python\");\n    #endif\n\n  #else\n    Py_SetProgramName(\"android_python\");\n  #endif\n\n  if (dir_exists(python_bundle_dir)) {\n    LOGP(\"_python_bundle dir exists\");\n\n      #if PY_MAJOR_VERSION >= 3\n          #if PY_MINOR_VERSION >= P4A_MIN_VER\n            \n            wchar_t wchar_zip_path[256];\n            wchar_t wchar_modules_path[256];\n            swprintf(wchar_zip_path, 256, L\"%s/stdlib.zip\", python_bundle_dir);\n            swprintf(wchar_modules_path, 256, L\"%s/modules\", python_bundle_dir);\n\n            config.module_search_paths_set = 1;\n            PyWideStringList_Append(&config.module_search_paths, wchar_zip_path);\n            PyWideStringList_Append(&config.module_search_paths, wchar_modules_path);\n        #else\n            char paths[512];\n            snprintf(paths, 512, \"%s/stdlib.zip:%s/modules\", python_bundle_dir, python_bundle_dir);\n            wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);\n            Py_SetPath(wchar_paths);\n        #endif\n      \n      #endif\n\n    LOGP(\"set wchar paths...\");\n  } else {\n      LOGP(\"_python_bundle does not exist...this not looks good, all python\"\n           \" recipes should have this folder, should we expect a crash soon?\");\n  }\n\n#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= P4A_MIN_VER\n    PyStatus status = Py_InitializeFromConfig(&config);\n    if (PyStatus_Exception(status)) {\n        LOGP(\"Python initialization failed:\");\n        LOGP(status.err_msg);\n    }\n#else\n    Py_Initialize();\n    LOGP(\"Python initialized using legacy Py_Initialize().\");\n#endif\n\n  LOGP(\"Initialized python\");\n\n  /* < 3.9 requires explicit GIL initialization\n  *  3.9+ PyEval_InitThreads() is deprecated and unnecessary\n  */\n  #if PY_VERSION_HEX < 0x03090000\n    LOGP(\"Initializing threads (required for Python < 3.9)\");\n    PyEval_InitThreads();\n  #endif\n\n#if PY_MAJOR_VERSION < 3\n  initandroidembed();\n#endif\n\n  PyRun_SimpleString(\n      \"import androidembed\\n\"\n      \"androidembed.log('testing python print redirection')\"\n\n  );\n\n  /* inject our bootstrap code to redirect python stdin/stdout\n   * replace sys.path with our path\n   */\n  PyRun_SimpleString(\"import io, sys, posix\\n\");\n\n  char add_site_packages_dir[256];\n\n  if (dir_exists(python_bundle_dir)) {\n    snprintf(add_site_packages_dir, 256,\n             \"sys.path.append('%s/site-packages')\",\n             python_bundle_dir);\n\n    PyRun_SimpleString(\"import sys, os\\n\"\n                      \"from os.path import realpath, join, dirname\");\n\n    char buf_exec[512];\n    char buf_argv[512];\n    snprintf(buf_exec, sizeof(buf_exec), \"sys.executable = '%s'\\n\", interpreter);\n    snprintf(buf_argv, sizeof(buf_argv), \"sys.argv = ['%s']\\n\", interpreter);\n    PyRun_SimpleString(buf_exec);\n    PyRun_SimpleString(buf_argv);\n\n    PyRun_SimpleString(add_site_packages_dir);\n    /* \"sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))\") */\n    PyRun_SimpleString(\"sys.path = ['.'] + sys.path\");\n    PyRun_SimpleString(\"os.environ['PYTHONPATH'] = ':'.join(sys.path)\");\n  }\n\n  PyRun_SimpleString(\n      \"class LogFile(io.IOBase):\\n\"\n      \"    def __init__(self):\\n\"\n      \"        self.__buffer = ''\\n\"\n      \"    def readable(self):\\n\"\n      \"        return False\\n\"\n      \"    def writable(self):\\n\"\n      \"        return True\\n\"\n      \"    def write(self, s):\\n\"\n      \"        s = self.__buffer + s\\n\"\n      \"        lines = s.split('\\\\n')\\n\"\n      \"        for l in lines[:-1]:\\n\"\n      \"            androidembed.log(l.replace('\\\\x00', ''))\\n\"\n      \"        self.__buffer = lines[-1]\\n\"\n      \"sys.stdout = sys.stderr = LogFile()\\n\"\n      \"print('Android kivy bootstrap done. __name__ is', __name__)\");\n\n#if PY_MAJOR_VERSION < 3\n  PyRun_SimpleString(\"import site; print site.getsitepackages()\\n\");\n#endif\n\n  char *dot = strrchr(env_entrypoint, '.');\n  char *ext = \".pyc\";\n  if (dot <= 0) {\n    LOGP(\"Invalid entrypoint, abort.\");\n    return -1;\n  }\n  if (strlen(env_entrypoint) > ENTRYPOINT_MAXLEN - 2) {\n      LOGP(\"Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN.\");\n      return -1;\n  }\n  if (!strcmp(dot, ext)) {\n    if (!file_exists(env_entrypoint)) {\n      /* fallback on .py */\n      strcpy(entrypoint, env_entrypoint);\n      entrypoint[strlen(env_entrypoint) - 1] = '\\0';\n      LOGP(entrypoint);\n      if (!file_exists(entrypoint)) {\n        LOGP(\"Entrypoint not found (.pyc, fallback on .py), abort\");\n        return -1;\n      }\n    } else {\n      strcpy(entrypoint, env_entrypoint);\n    }\n  } else if (!strcmp(dot, \".py\")) {\n    /* if .py is passed, check the pyc version first */\n    strcpy(entrypoint, env_entrypoint);\n    entrypoint[strlen(env_entrypoint) + 1] = '\\0';\n    entrypoint[strlen(env_entrypoint)] = 'c';\n    if (!file_exists(entrypoint)) {\n      /* fallback on pure python version */\n      if (!file_exists(env_entrypoint)) {\n        LOGP(\"Entrypoint not found (.py), abort.\");\n        return -1;\n      }\n      strcpy(entrypoint, env_entrypoint);\n    }\n  } else {\n    LOGP(\"Entrypoint have an invalid extension (must be .py or .pyc), abort.\");\n    return -1;\n  }\n  // LOGP(\"Entrypoint is:\");\n  // LOGP(entrypoint);\n  fd = fopen(entrypoint, \"r\");\n  if (fd == NULL) {\n    LOGP(\"Open the entrypoint failed\");\n    LOGP(entrypoint);\n    return -1;\n  }\n  /* run python !\n   */\n  ret = PyRun_SimpleFile(fd, entrypoint);\n  fclose(fd);\n\n  if (PyErr_Occurred() != NULL) {\n    ret = 1;\n    PyErr_Print(); /* This exits with the right code if SystemExit. */\n    PyObject *f = PySys_GetObject(\"stdout\");\n    if (PyFile_WriteString(\"\\n\", f))\n      PyErr_Clear();\n  }\n\n  LOGP(\"Python for android ended.\");\n\n#if PY_MAJOR_VERSION < 3\n  Py_Finalize();\n  LOGP(\"Unexpectedly reached Py_FinalizeEx(), but was successful.\");\n#else\n  if (Py_FinalizeEx() != 0) {  // properly check success on Python 3\n    LOGP(\"Unexpectedly reached Py_FinalizeEx(), and got error!\");\n  }\n#endif\n\n  exit(ret);\n  return ret;\n}\n\nJNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(\n    JNIEnv *env,\n    jobject thiz,\n    jstring j_android_private,\n    jstring j_android_argument,\n    jstring j_service_entrypoint,\n    jstring j_python_name,\n    jstring j_python_home,\n    jstring j_python_path,\n    jstring j_arg) {\n  jboolean iscopy;\n  const char *android_private =\n      (*env)->GetStringUTFChars(env, j_android_private, &iscopy);\n  const char *android_argument =\n      (*env)->GetStringUTFChars(env, j_android_argument, &iscopy);\n  const char *service_entrypoint =\n      (*env)->GetStringUTFChars(env, j_service_entrypoint, &iscopy);\n  const char *python_name =\n      (*env)->GetStringUTFChars(env, j_python_name, &iscopy);\n  const char *python_home =\n      (*env)->GetStringUTFChars(env, j_python_home, &iscopy);\n  const char *python_path =\n      (*env)->GetStringUTFChars(env, j_python_path, &iscopy);\n  const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy);\n\n  setenv(\"ANDROID_PRIVATE\", android_private, 1);\n  setenv(\"ANDROID_ARGUMENT\", android_argument, 1);\n  setenv(\"ANDROID_APP_PATH\", android_argument, 1);\n  setenv(\"ANDROID_ENTRYPOINT\", service_entrypoint, 1);\n  setenv(\"PYTHONOPTIMIZE\", \"2\", 1);\n  setenv(\"PYTHON_NAME\", python_name, 1);\n  setenv(\"PYTHONHOME\", python_home, 1);\n  setenv(\"PYTHONPATH\", python_path, 1);\n  setenv(\"PYTHON_SERVICE_ARGUMENT\", arg, 1);\n  setenv(\"P4A_BOOTSTRAP\", bootstrap_name, 1);\n\n  char *argv[] = {\".\"};\n  /* ANDROID_ARGUMENT points to service subdir,\n   * so main() will run main.py from this dir\n   */\n  main(1, argv);\n}\n\n#if defined(BOOTSTRAP_NAME_WEBVIEW) || defined(BOOTSTRAP_NAME_SERVICEONLY)\n// Webview and service_only uses some more functions:\n\nvoid Java_org_kivy_android_PythonActivity_nativeSetenv(\n                                    JNIEnv* env, jclass cls,\n                                    jstring name, jstring value)\n//JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(\n//                                    JNIEnv* env, jclass cls,\n//                                    jstring name, jstring value)\n{\n    const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);\n    const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);\n\n    setenv(utfname, utfvalue, 1);\n\n    (*env)->ReleaseStringUTFChars(env, name, utfname);\n    (*env)->ReleaseStringUTFChars(env, value, utfvalue);\n}\n\n\nvoid Java_org_kivy_android_PythonActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj)\n{\n  /* This nativeInit follows SDL2 */\n\n  /* This interface could expand with ABI negotiation, callbacks, etc. */\n  /* SDL_Android_Init(env, cls); */\n\n  /* SDL_SetMainReady(); */\n\n  /* Run the application code! */\n  int status;\n  char *argv[2];\n  argv[0] = \"Python_app\";\n  argv[1] = NULL;\n  /* status = SDL_main(1, argv); */\n\n  main(1, argv);\n\n  /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */\n  /* exit(status); */\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/Octal.java",
    "content": "/**\r\n * Copyright 2012 Kamran Zafar \r\n * \r\n * Licensed under the Apache License, Version 2.0 (the \"License\"); \r\n * you may not use this file except in compliance with the License. \r\n * You may obtain a copy of the License at \r\n * \r\n *      http://www.apache.org/licenses/LICENSE-2.0 \r\n * \r\n * Unless required by applicable law or agreed to in writing, software \r\n * distributed under the License is distributed on an \"AS IS\" BASIS, \r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \r\n * See the License for the specific language governing permissions and \r\n * limitations under the License. \r\n * \r\n */\r\n\r\npackage org.kamranzafar.jtar;\r\n\r\n/**\r\n * @author Kamran Zafar\r\n * \r\n */\r\npublic class Octal {\r\n\r\n    /**\r\n     * Parse an octal string from a header buffer. This is used for the file\r\n     * permission mode value.\r\n     * \r\n     * @param header\r\n     *            The header buffer from which to parse.\r\n     * @param offset\r\n     *            The offset into the buffer from which to parse.\r\n     * @param length\r\n     *            The number of header bytes to parse.\r\n     * \r\n     * @return The long value of the octal string.\r\n     */\r\n    public static long parseOctal(byte[] header, int offset, int length) {\r\n        long result = 0;\r\n        boolean stillPadding = true;\r\n\r\n        int end = offset + length;\r\n        for (int i = offset; i < end; ++i) {\r\n            if (header[i] == 0)\r\n                break;\r\n\r\n            if (header[i] == (byte) ' ' || header[i] == '0') {\r\n                if (stillPadding)\r\n                    continue;\r\n\r\n                if (header[i] == (byte) ' ')\r\n                    break;\r\n            }\r\n\r\n            stillPadding = false;\r\n\r\n            result = ( result << 3 ) + ( header[i] - '0' );\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    /**\r\n     * Parse an octal integer from a header buffer.\r\n     * \r\n     * @param value\r\n     * @param buf\r\n     *            The header buffer from which to parse.\r\n     * @param offset\r\n     *            The offset into the buffer from which to parse.\r\n     * @param length\r\n     *            The number of header bytes to parse.\r\n     * \r\n     * @return The integer value of the octal bytes.\r\n     */\r\n    public static int getOctalBytes(long value, byte[] buf, int offset, int length) {\r\n        int idx = length - 1;\r\n\r\n        buf[offset + idx] = 0;\r\n        --idx;\r\n        buf[offset + idx] = (byte) ' ';\r\n        --idx;\r\n\r\n        if (value == 0) {\r\n            buf[offset + idx] = (byte) '0';\r\n            --idx;\r\n        } else {\r\n            for (long val = value; idx >= 0 && val > 0; --idx) {\r\n                buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );\r\n                val = val >> 3;\r\n            }\r\n        }\r\n\r\n        for (; idx >= 0; --idx) {\r\n            buf[offset + idx] = (byte) ' ';\r\n        }\r\n\r\n        return offset + length;\r\n    }\r\n\r\n    /**\r\n     * Parse the checksum octal integer from a header buffer.\r\n     * \r\n     * @param value\r\n     * @param buf\r\n     *            The header buffer from which to parse.\r\n     * @param offset\r\n     *            The offset into the buffer from which to parse.\r\n     * @param length\r\n     *            The number of header bytes to parse.\r\n     * @return The integer value of the entry's checksum.\r\n     */\r\n    public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {\r\n        getOctalBytes( value, buf, offset, length );\r\n        buf[offset + length - 1] = (byte) ' ';\r\n        buf[offset + length - 2] = 0;\r\n        return offset + length;\r\n    }\r\n\r\n    /**\r\n     * Parse an octal long integer from a header buffer.\r\n     * \r\n     * @param value\r\n     * @param buf\r\n     *            The header buffer from which to parse.\r\n     * @param offset\r\n     *            The offset into the buffer from which to parse.\r\n     * @param length\r\n     *            The number of header bytes to parse.\r\n     * \r\n     * @return The long value of the octal bytes.\r\n     */\r\n    public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {\r\n        byte[] temp = new byte[length + 1];\r\n        getOctalBytes( value, temp, 0, length + 1 );\r\n        System.arraycopy( temp, 0, buf, offset, length );\r\n        return offset + length;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarConstants.java",
    "content": "/**\r\n * Copyright 2012 Kamran Zafar \r\n * \r\n * Licensed under the Apache License, Version 2.0 (the \"License\"); \r\n * you may not use this file except in compliance with the License. \r\n * You may obtain a copy of the License at \r\n * \r\n *      http://www.apache.org/licenses/LICENSE-2.0 \r\n * \r\n * Unless required by applicable law or agreed to in writing, software \r\n * distributed under the License is distributed on an \"AS IS\" BASIS, \r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \r\n * See the License for the specific language governing permissions and \r\n * limitations under the License. \r\n * \r\n */\r\n\r\npackage org.kamranzafar.jtar;\r\n\r\n/**\r\n * @author Kamran Zafar\r\n * \r\n */\r\npublic class TarConstants {\r\n    public static final int EOF_BLOCK = 1024;\r\n    public static final int DATA_BLOCK = 512;\r\n    public static final int HEADER_BLOCK = 512;\r\n}\r\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarEntry.java",
    "content": "/**\r\n * Copyright 2012 Kamran Zafar \r\n * \r\n * Licensed under the Apache License, Version 2.0 (the \"License\"); \r\n * you may not use this file except in compliance with the License. \r\n * You may obtain a copy of the License at \r\n * \r\n *      http://www.apache.org/licenses/LICENSE-2.0 \r\n * \r\n * Unless required by applicable law or agreed to in writing, software \r\n * distributed under the License is distributed on an \"AS IS\" BASIS, \r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \r\n * See the License for the specific language governing permissions and \r\n * limitations under the License. \r\n * \r\n */\r\n\r\npackage org.kamranzafar.jtar;\r\n\r\nimport java.io.File;\r\nimport java.util.Date;\r\n\r\n/**\r\n * @author Kamran Zafar\r\n * \r\n */\r\npublic class TarEntry {\r\n\tprotected File file;\r\n\tprotected TarHeader header;\r\n\r\n\tprivate TarEntry() {\r\n\t\tthis.file = null;\r\n\t\theader = new TarHeader();\r\n\t}\r\n\r\n\tpublic TarEntry(File file, String entryName) {\r\n\t\tthis();\r\n\t\tthis.file = file;\r\n\t\tthis.extractTarHeader(entryName);\r\n\t}\r\n\r\n\tpublic TarEntry(byte[] headerBuf) {\r\n\t\tthis();\r\n\t\tthis.parseTarHeader(headerBuf);\r\n\t}\r\n\r\n\t/**\r\n\t * Constructor to create an entry from an existing TarHeader object.\r\n\t * \r\n\t * This method is useful to add new entries programmatically (e.g. for\r\n\t * adding files or directories that do not exist in the file system).\r\n\t * \r\n\t * @param header\r\n\t * \r\n\t */\r\n\tpublic TarEntry(TarHeader header) {\r\n\t\tthis.file = null;\r\n\t\tthis.header = header;\r\n\t}\r\n\r\n\tpublic boolean equals(TarEntry it) {\r\n\t\treturn header.name.toString().equals(it.header.name.toString());\r\n\t}\r\n\r\n\tpublic boolean isDescendent(TarEntry desc) {\r\n\t\treturn desc.header.name.toString().startsWith(header.name.toString());\r\n\t}\r\n\r\n\tpublic TarHeader getHeader() {\r\n\t\treturn header;\r\n\t}\r\n\r\n\tpublic String getName() {\r\n\t\tString name = header.name.toString();\r\n\t\tif (header.namePrefix != null && !header.namePrefix.toString().equals(\"\")) {\r\n\t\t\tname = header.namePrefix.toString() + \"/\" + name;\r\n\t\t}\r\n\r\n\t\treturn name;\r\n\t}\r\n\r\n\tpublic void setName(String name) {\r\n\t\theader.name = new StringBuffer(name);\r\n\t}\r\n\r\n\tpublic int getUserId() {\r\n\t\treturn header.userId;\r\n\t}\r\n\r\n\tpublic void setUserId(int userId) {\r\n\t\theader.userId = userId;\r\n\t}\r\n\r\n\tpublic int getGroupId() {\r\n\t\treturn header.groupId;\r\n\t}\r\n\r\n\tpublic void setGroupId(int groupId) {\r\n\t\theader.groupId = groupId;\r\n\t}\r\n\r\n\tpublic String getUserName() {\r\n\t\treturn header.userName.toString();\r\n\t}\r\n\r\n\tpublic void setUserName(String userName) {\r\n\t\theader.userName = new StringBuffer(userName);\r\n\t}\r\n\r\n\tpublic String getGroupName() {\r\n\t\treturn header.groupName.toString();\r\n\t}\r\n\r\n\tpublic void setGroupName(String groupName) {\r\n\t\theader.groupName = new StringBuffer(groupName);\r\n\t}\r\n\r\n\tpublic void setIds(int userId, int groupId) {\r\n\t\tthis.setUserId(userId);\r\n\t\tthis.setGroupId(groupId);\r\n\t}\r\n\r\n\tpublic void setModTime(long time) {\r\n\t\theader.modTime = time / 1000;\r\n\t}\r\n\r\n\tpublic void setModTime(Date time) {\r\n\t\theader.modTime = time.getTime() / 1000;\r\n\t}\r\n\r\n\tpublic Date getModTime() {\r\n\t\treturn new Date(header.modTime * 1000);\r\n\t}\r\n\r\n\tpublic File getFile() {\r\n\t\treturn this.file;\r\n\t}\r\n\r\n\tpublic long getSize() {\r\n\t\treturn header.size;\r\n\t}\r\n\r\n\tpublic void setSize(long size) {\r\n\t\theader.size = size;\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the org.kamrazafar.jtar entry is a directory\r\n\t * \r\n\t * @return\r\n\t */\r\n\tpublic boolean isDirectory() {\r\n\t\tif (this.file != null)\r\n\t\t\treturn this.file.isDirectory();\r\n\r\n\t\tif (header != null) {\r\n\t\t\tif (header.linkFlag == TarHeader.LF_DIR)\r\n\t\t\t\treturn true;\r\n\r\n\t\t\tif (header.name.toString().endsWith(\"/\"))\r\n\t\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\t}\r\n\r\n\t/**\r\n\t * Extract header from File\r\n\t * \r\n\t * @param entryName\r\n\t */\r\n\tpublic void extractTarHeader(String entryName) {\r\n\t\theader = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory());\r\n\t}\r\n\r\n\t/**\r\n\t * Calculate checksum\r\n\t * \r\n\t * @param buf\r\n\t * @return\r\n\t */\r\n\tpublic long computeCheckSum(byte[] buf) {\r\n\t\tlong sum = 0;\r\n\r\n\t\tfor (int i = 0; i < buf.length; ++i) {\r\n\t\t\tsum += 255 & buf[i];\r\n\t\t}\r\n\r\n\t\treturn sum;\r\n\t}\r\n\r\n\t/**\r\n\t * Writes the header to the byte buffer\r\n\t * \r\n\t * @param outbuf\r\n\t */\r\n\tpublic void writeEntryHeader(byte[] outbuf) {\r\n\t\tint offset = 0;\r\n\r\n\t\toffset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN);\r\n\t\toffset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN);\r\n\t\toffset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN);\r\n\t\toffset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN);\r\n\r\n\t\tlong size = header.size;\r\n\r\n\t\toffset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);\r\n\t\toffset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN);\r\n\r\n\t\tint csOffset = offset;\r\n\t\tfor (int c = 0; c < TarHeader.CHKSUMLEN; ++c)\r\n\t\t\toutbuf[offset++] = (byte) ' ';\r\n\r\n\t\toutbuf[offset++] = header.linkFlag;\r\n\r\n\t\toffset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN);\r\n\t\toffset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN);\r\n\t\toffset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN);\r\n\t\toffset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN);\r\n\t\toffset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN);\r\n\t\toffset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN);\r\n\t\toffset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX);\r\n\r\n\t\tfor (; offset < outbuf.length;)\r\n\t\t\toutbuf[offset++] = 0;\r\n\r\n\t\tlong checkSum = this.computeCheckSum(outbuf);\r\n\r\n\t\tOctal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN);\r\n\t}\r\n\r\n\t/**\r\n\t * Parses the tar header to the byte buffer\r\n\t * \r\n\t * @param header\r\n\t * @param bh\r\n\t */\r\n\tpublic void parseTarHeader(byte[] bh) {\r\n\t\tint offset = 0;\r\n\r\n\t\theader.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);\r\n\t\toffset += TarHeader.NAMELEN;\r\n\r\n\t\theader.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);\r\n\t\toffset += TarHeader.MODELEN;\r\n\r\n\t\theader.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);\r\n\t\toffset += TarHeader.UIDLEN;\r\n\r\n\t\theader.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);\r\n\t\toffset += TarHeader.GIDLEN;\r\n\r\n\t\theader.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);\r\n\t\toffset += TarHeader.SIZELEN;\r\n\r\n\t\theader.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);\r\n\t\toffset += TarHeader.MODTIMELEN;\r\n\r\n\t\theader.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN);\r\n\t\toffset += TarHeader.CHKSUMLEN;\r\n\r\n\t\theader.linkFlag = bh[offset++];\r\n\r\n\t\theader.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);\r\n\t\toffset += TarHeader.NAMELEN;\r\n\r\n\t\theader.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN);\r\n\t\toffset += TarHeader.USTAR_MAGICLEN;\r\n\r\n\t\theader.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN);\r\n\t\toffset += TarHeader.USTAR_USER_NAMELEN;\r\n\r\n\t\theader.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN);\r\n\t\toffset += TarHeader.USTAR_GROUP_NAMELEN;\r\n\r\n\t\theader.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);\r\n\t\toffset += TarHeader.USTAR_DEVLEN;\r\n\r\n\t\theader.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);\r\n\t\toffset += TarHeader.USTAR_DEVLEN;\r\n\r\n\t\theader.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX);\r\n\t}\r\n}"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarHeader.java",
    "content": "/**\r\n * Copyright 2012 Kamran Zafar \r\n * \r\n * Licensed under the Apache License, Version 2.0 (the \"License\"); \r\n * you may not use this file except in compliance with the License. \r\n * You may obtain a copy of the License at \r\n * \r\n *      http://www.apache.org/licenses/LICENSE-2.0 \r\n * \r\n * Unless required by applicable law or agreed to in writing, software \r\n * distributed under the License is distributed on an \"AS IS\" BASIS, \r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \r\n * See the License for the specific language governing permissions and \r\n * limitations under the License. \r\n * \r\n */\r\n\r\npackage org.kamranzafar.jtar;\r\n\r\nimport java.io.File;\r\n\r\n/**\r\n * Header\r\n * \r\n * <pre>\r\n * Offset  Size     Field\r\n * 0       100      File name\r\n * 100     8        File mode\r\n * 108     8        Owner's numeric user ID\r\n * 116     8        Group's numeric user ID\r\n * 124     12       File size in bytes\r\n * 136     12       Last modification time in numeric Unix time format\r\n * 148     8        Checksum for header block\r\n * 156     1        Link indicator (file type)\r\n * 157     100      Name of linked file\r\n * </pre>\r\n * \r\n * \r\n * File Types\r\n * \r\n * <pre>\r\n * Value        Meaning\r\n * '0'          Normal file\r\n * (ASCII NUL)  Normal file (now obsolete)\r\n * '1'          Hard link\r\n * '2'          Symbolic link\r\n * '3'          Character special\r\n * '4'          Block special\r\n * '5'          Directory\r\n * '6'          FIFO\r\n * '7'          Contiguous\r\n * </pre>\r\n * \r\n * \r\n * \r\n * Ustar header\r\n * \r\n * <pre>\r\n * Offset  Size    Field\r\n * 257     6       UStar indicator \"ustar\"\r\n * 263     2       UStar version \"00\"\r\n * 265     32      Owner user name\r\n * 297     32      Owner group name\r\n * 329     8       Device major number\r\n * 337     8       Device minor number\r\n * 345     155     Filename prefix\r\n * </pre>\r\n */\r\n\r\npublic class TarHeader {\r\n\r\n\t/*\r\n\t * Header\r\n\t */\r\n\tpublic static final int NAMELEN = 100;\r\n\tpublic static final int MODELEN = 8;\r\n\tpublic static final int UIDLEN = 8;\r\n\tpublic static final int GIDLEN = 8;\r\n\tpublic static final int SIZELEN = 12;\r\n\tpublic static final int MODTIMELEN = 12;\r\n\tpublic static final int CHKSUMLEN = 8;\r\n\tpublic static final byte LF_OLDNORM = 0;\r\n\r\n\t/*\r\n\t * File Types\r\n\t */\r\n\tpublic static final byte LF_NORMAL = (byte) '0';\r\n\tpublic static final byte LF_LINK = (byte) '1';\r\n\tpublic static final byte LF_SYMLINK = (byte) '2';\r\n\tpublic static final byte LF_CHR = (byte) '3';\r\n\tpublic static final byte LF_BLK = (byte) '4';\r\n\tpublic static final byte LF_DIR = (byte) '5';\r\n\tpublic static final byte LF_FIFO = (byte) '6';\r\n\tpublic static final byte LF_CONTIG = (byte) '7';\r\n\r\n\t/*\r\n\t * Ustar header\r\n\t */\r\n\r\n\tpublic static final String USTAR_MAGIC = \"ustar\"; // POSIX\r\n\r\n\tpublic static final int USTAR_MAGICLEN = 8;\r\n\tpublic static final int USTAR_USER_NAMELEN = 32;\r\n\tpublic static final int USTAR_GROUP_NAMELEN = 32;\r\n\tpublic static final int USTAR_DEVLEN = 8;\r\n\tpublic static final int USTAR_FILENAME_PREFIX = 155;\r\n\r\n\t// Header values\r\n\tpublic StringBuffer name;\r\n\tpublic int mode;\r\n\tpublic int userId;\r\n\tpublic int groupId;\r\n\tpublic long size;\r\n\tpublic long modTime;\r\n\tpublic int checkSum;\r\n\tpublic byte linkFlag;\r\n\tpublic StringBuffer linkName;\r\n\tpublic StringBuffer magic; // ustar indicator and version\r\n\tpublic StringBuffer userName;\r\n\tpublic StringBuffer groupName;\r\n\tpublic int devMajor;\r\n\tpublic int devMinor;\r\n\tpublic StringBuffer namePrefix;\r\n\r\n\tpublic TarHeader() {\r\n\t\tthis.magic = new StringBuffer(TarHeader.USTAR_MAGIC);\r\n\r\n\t\tthis.name = new StringBuffer();\r\n\t\tthis.linkName = new StringBuffer();\r\n\r\n\t\tString user = System.getProperty(\"user.name\", \"\");\r\n\r\n\t\tif (user.length() > 31)\r\n\t\t\tuser = user.substring(0, 31);\r\n\r\n\t\tthis.userId = 0;\r\n\t\tthis.groupId = 0;\r\n\t\tthis.userName = new StringBuffer(user);\r\n\t\tthis.groupName = new StringBuffer(\"\");\r\n\t\tthis.namePrefix = new StringBuffer();\r\n\t}\r\n\r\n\t/**\r\n\t * Parse an entry name from a header buffer.\r\n\t * \r\n\t * @param name\r\n\t * @param header\r\n\t *            The header buffer from which to parse.\r\n\t * @param offset\r\n\t *            The offset into the buffer from which to parse.\r\n\t * @param length\r\n\t *            The number of header bytes to parse.\r\n\t * @return The header's entry name.\r\n\t */\r\n\tpublic static StringBuffer parseName(byte[] header, int offset, int length) {\r\n\t\tStringBuffer result = new StringBuffer(length);\r\n\r\n\t\tint end = offset + length;\r\n\t\tfor (int i = offset; i < end; ++i) {\r\n\t\t\tif (header[i] == 0)\r\n\t\t\t\tbreak;\r\n\t\t\tresult.append((char) header[i]);\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n\r\n\t/**\r\n\t * Determine the number of bytes in an entry name.\r\n\t * \r\n\t * @param name\r\n\t * @param header\r\n\t *            The header buffer from which to parse.\r\n\t * @param offset\r\n\t *            The offset into the buffer from which to parse.\r\n\t * @param length\r\n\t *            The number of header bytes to parse.\r\n\t * @return The number of bytes in a header's entry name.\r\n\t */\r\n\tpublic static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {\r\n\t\tint i;\r\n\r\n\t\tfor (i = 0; i < length && i < name.length(); ++i) {\r\n\t\t\tbuf[offset + i] = (byte) name.charAt(i);\r\n\t\t}\r\n\r\n\t\tfor (; i < length; ++i) {\r\n\t\t\tbuf[offset + i] = 0;\r\n\t\t}\r\n\r\n\t\treturn offset + length;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a new header for a file/directory entry.\r\n\t * \r\n\t * \r\n\t * @param name\r\n\t *            File name\r\n\t * @param size\r\n\t *            File size in bytes\r\n\t * @param modTime\r\n\t *            Last modification time in numeric Unix time format\r\n\t * @param dir\r\n\t *            Is directory\r\n\t * \r\n\t * @return\r\n\t */\r\n\tpublic static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {\r\n\t\tString name = entryName;\r\n\t\tname = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');\r\n\r\n\t\tTarHeader header = new TarHeader();\r\n\t\theader.linkName = new StringBuffer(\"\");\r\n\r\n\t\tif (name.length() > 100) {\r\n\t\t\theader.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));\r\n\t\t\theader.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));\r\n\t\t} else {\r\n\t\t\theader.name = new StringBuffer(name);\r\n\t\t}\r\n\r\n\t\tif (dir) {\r\n\t\t\theader.mode = 040755;\r\n\t\t\theader.linkFlag = TarHeader.LF_DIR;\r\n\t\t\tif (header.name.charAt(header.name.length() - 1) != '/') {\r\n\t\t\t\theader.name.append(\"/\");\r\n\t\t\t}\r\n\t\t\theader.size = 0;\r\n\t\t} else {\r\n\t\t\theader.mode = 0100644;\r\n\t\t\theader.linkFlag = TarHeader.LF_NORMAL;\r\n\t\t\theader.size = size;\r\n\t\t}\r\n\r\n\t\theader.modTime = modTime;\r\n\t\theader.checkSum = 0;\r\n\t\theader.devMajor = 0;\r\n\t\theader.devMinor = 0;\r\n\r\n\t\treturn header;\r\n\t}\r\n}"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java",
    "content": "/**\r\n * Copyright 2012 Kamran Zafar \r\n * \r\n * Licensed under the Apache License, Version 2.0 (the \"License\"); \r\n * you may not use this file except in compliance with the License. \r\n * You may obtain a copy of the License at \r\n * \r\n *      http://www.apache.org/licenses/LICENSE-2.0 \r\n * \r\n * Unless required by applicable law or agreed to in writing, software \r\n * distributed under the License is distributed on an \"AS IS\" BASIS, \r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \r\n * See the License for the specific language governing permissions and \r\n * limitations under the License. \r\n * \r\n */\r\n\r\npackage org.kamranzafar.jtar;\r\n\r\nimport java.io.FilterInputStream;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\n\r\n/**\r\n * @author Kamran Zafar\r\n * \r\n */\r\npublic class TarInputStream extends FilterInputStream {\r\n\r\n\tprivate static final int SKIP_BUFFER_SIZE = 2048;\r\n\tprivate TarEntry currentEntry;\r\n\tprivate long currentFileSize;\r\n\tprivate long bytesRead;\r\n\tprivate boolean defaultSkip = false;\r\n\r\n\tpublic TarInputStream(InputStream in) {\r\n\t\tsuper(in);\r\n\t\tcurrentFileSize = 0;\r\n\t\tbytesRead = 0;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean markSupported() {\r\n\t\treturn false;\r\n\t}\r\n\r\n\t/**\r\n\t * Not supported\r\n\t * \r\n\t */\r\n\t@Override\r\n\tpublic synchronized void mark(int readlimit) {\r\n\t}\r\n\r\n\t/**\r\n\t * Not supported\r\n\t * \r\n\t */\r\n\t@Override\r\n\tpublic synchronized void reset() throws IOException {\r\n\t\tthrow new IOException(\"mark/reset not supported\");\r\n\t}\r\n\r\n\t/**\r\n\t * Read a byte\r\n\t * \r\n\t * @see java.io.FilterInputStream#read()\r\n\t */\r\n\t@Override\r\n\tpublic int read() throws IOException {\r\n\t\tbyte[] buf = new byte[1];\r\n\r\n\t\tint res = this.read(buf, 0, 1);\r\n\r\n\t\tif (res != -1) {\r\n\t\t\treturn 0xFF & buf[0];\r\n\t\t}\r\n\r\n\t\treturn res;\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the bytes being read exceed the entry size and adjusts the byte\r\n\t * array length. Updates the byte counters\r\n\t * \r\n\t * \r\n\t * @see java.io.FilterInputStream#read(byte[], int, int)\r\n\t */\r\n\t@Override\r\n\tpublic int read(byte[] b, int off, int len) throws IOException {\r\n\t\tif (currentEntry != null) {\r\n\t\t\tif (currentFileSize == currentEntry.getSize()) {\r\n\t\t\t\treturn -1;\r\n\t\t\t} else if ((currentEntry.getSize() - currentFileSize) < len) {\r\n\t\t\t\tlen = (int) (currentEntry.getSize() - currentFileSize);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tint br = super.read(b, off, len);\r\n\r\n\t\tif (br != -1) {\r\n\t\t\tif (currentEntry != null) {\r\n\t\t\t\tcurrentFileSize += br;\r\n\t\t\t}\r\n\r\n\t\t\tbytesRead += br;\r\n\t\t}\r\n\r\n\t\treturn br;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the next entry in the tar file\r\n\t * \r\n\t * @return TarEntry\r\n\t * @throws IOException\r\n\t */\r\n\tpublic TarEntry getNextEntry() throws IOException {\r\n\t\tcloseCurrentEntry();\r\n\r\n\t\tbyte[] header = new byte[TarConstants.HEADER_BLOCK];\r\n\t\tbyte[] theader = new byte[TarConstants.HEADER_BLOCK];\r\n\t\tint tr = 0;\r\n\r\n\t\t// Read full header\r\n\t\twhile (tr < TarConstants.HEADER_BLOCK) {\r\n\t\t\tint res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);\r\n\r\n\t\t\tif (res < 0) {\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\r\n\t\t\tSystem.arraycopy(theader, 0, header, tr, res);\r\n\t\t\ttr += res;\r\n\t\t}\r\n\r\n\t\t// Check if record is null\r\n\t\tboolean eof = true;\r\n\t\tfor (byte b : header) {\r\n\t\t\tif (b != 0) {\r\n\t\t\t\teof = false;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (!eof) {\r\n\t\t\tcurrentEntry = new TarEntry(header);\r\n\t\t}\r\n\r\n\t\treturn currentEntry;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current offset (in bytes) from the beginning of the stream. \r\n\t * This can be used to find out at which point in a tar file an entry's content begins, for instance. \r\n\t */\r\n\tpublic long getCurrentOffset() {\r\n\t\treturn bytesRead;\r\n\t}\r\n\t\r\n\t/**\r\n\t * Closes the current tar entry\r\n\t * \r\n\t * @throws IOException\r\n\t */\r\n\tprotected void closeCurrentEntry() throws IOException {\r\n\t\tif (currentEntry != null) {\r\n\t\t\tif (currentEntry.getSize() > currentFileSize) {\r\n\t\t\t\t// Not fully read, skip rest of the bytes\r\n\t\t\t\tlong bs = 0;\r\n\t\t\t\twhile (bs < currentEntry.getSize() - currentFileSize) {\r\n\t\t\t\t\tlong res = skip(currentEntry.getSize() - currentFileSize - bs);\r\n\r\n\t\t\t\t\tif (res == 0 && currentEntry.getSize() - currentFileSize > 0) {\r\n\t\t\t\t\t\t// I suspect file corruption\r\n\t\t\t\t\t\tthrow new IOException(\"Possible tar file corruption\");\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tbs += res;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tcurrentEntry = null;\r\n\t\t\tcurrentFileSize = 0L;\r\n\t\t\tskipPad();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Skips the pad at the end of each tar entry file content\r\n\t * \r\n\t * @throws IOException\r\n\t */\r\n\tprotected void skipPad() throws IOException {\r\n\t\tif (bytesRead > 0) {\r\n\t\t\tint extra = (int) (bytesRead % TarConstants.DATA_BLOCK);\r\n\r\n\t\t\tif (extra > 0) {\r\n\t\t\t\tlong bs = 0;\r\n\t\t\t\twhile (bs < TarConstants.DATA_BLOCK - extra) {\r\n\t\t\t\t\tlong res = skip(TarConstants.DATA_BLOCK - extra - bs);\r\n\t\t\t\t\tbs += res;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Skips 'n' bytes on the InputStream<br>\r\n\t * Overrides default implementation of skip\r\n\t * \r\n\t */\r\n\t@Override\r\n\tpublic long skip(long n) throws IOException {\r\n\t\tif (defaultSkip) {\r\n\t\t\t// use skip method of parent stream\r\n\t\t\t// may not work if skip not implemented by parent\r\n\t\t\tlong bs = super.skip(n);\r\n\t\t\tbytesRead += bs;\r\n\r\n\t\t\treturn bs;\r\n\t\t}\r\n\r\n\t\tif (n <= 0) {\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\t\tlong left = n;\r\n\t\tbyte[] sBuff = new byte[SKIP_BUFFER_SIZE];\r\n\r\n\t\twhile (left > 0) {\r\n\t\t\tint res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));\r\n\t\t\tif (res < 0) {\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tleft -= res;\r\n\t\t}\r\n\r\n\t\treturn n - left;\r\n\t}\r\n\r\n\tpublic boolean isDefaultSkip() {\r\n\t\treturn defaultSkip;\r\n\t}\r\n\r\n\tpublic void setDefaultSkip(boolean defaultSkip) {\r\n\t\tthis.defaultSkip = defaultSkip;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java",
    "content": "/**\r\n * Copyright 2012 Kamran Zafar \r\n * \r\n * Licensed under the Apache License, Version 2.0 (the \"License\"); \r\n * you may not use this file except in compliance with the License. \r\n * You may obtain a copy of the License at \r\n * \r\n *      http://www.apache.org/licenses/LICENSE-2.0 \r\n * \r\n * Unless required by applicable law or agreed to in writing, software \r\n * distributed under the License is distributed on an \"AS IS\" BASIS, \r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \r\n * See the License for the specific language governing permissions and \r\n * limitations under the License. \r\n * \r\n */\r\n\r\npackage org.kamranzafar.jtar;\r\n\r\nimport java.io.BufferedOutputStream;\r\nimport java.io.File;\r\nimport java.io.FileNotFoundException;\r\nimport java.io.FileOutputStream;\r\nimport java.io.IOException;\r\nimport java.io.OutputStream;\r\nimport java.io.RandomAccessFile;\r\n\r\n/**\r\n * @author Kamran Zafar\r\n * \r\n */\r\npublic class TarOutputStream extends OutputStream {\r\n\tprivate final OutputStream out;\r\n    private long bytesWritten;\r\n    private long currentFileSize;\r\n    private TarEntry currentEntry;\r\n\r\n    public TarOutputStream(OutputStream out) {\r\n        this.out = out;\r\n        bytesWritten = 0;\r\n        currentFileSize = 0;\r\n    }\r\n\r\n\tpublic TarOutputStream(final File fout) throws FileNotFoundException {\r\n\t\tthis.out = new BufferedOutputStream(new FileOutputStream(fout));\r\n\t\tbytesWritten = 0;\r\n\t\tcurrentFileSize = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Opens a file for writing. \r\n\t */\r\n\tpublic TarOutputStream(final File fout, final boolean append) throws IOException {\r\n\t\t@SuppressWarnings(\"resource\")\r\n\t\tRandomAccessFile raf = new RandomAccessFile(fout, \"rw\");\r\n\t\tfinal long fileSize = fout.length();\r\n\t\tif (append && fileSize > TarConstants.EOF_BLOCK) {\r\n\t\t\traf.seek(fileSize - TarConstants.EOF_BLOCK);\r\n\t\t}\r\n\t\tout = new BufferedOutputStream(new FileOutputStream(raf.getFD()));\r\n\t}\r\n\r\n    /**\r\n     * Appends the EOF record and closes the stream\r\n     * \r\n     * @see java.io.FilterOutputStream#close()\r\n     */\r\n    @Override\r\n    public void close() throws IOException {\r\n        closeCurrentEntry();\r\n        write( new byte[TarConstants.EOF_BLOCK] );\r\n        out.close();\r\n    }\r\n    /**\r\n     * Writes a byte to the stream and updates byte counters\r\n     * \r\n     * @see java.io.FilterOutputStream#write(int)\r\n     */\r\n    @Override\r\n    public void write(int b) throws IOException {\r\n        out.write( b );\r\n        bytesWritten += 1;\r\n\r\n        if (currentEntry != null) {\r\n            currentFileSize += 1;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Checks if the bytes being written exceed the current entry size.\r\n     * \r\n     * @see java.io.FilterOutputStream#write(byte[], int, int)\r\n     */\r\n    @Override\r\n    public void write(byte[] b, int off, int len) throws IOException {\r\n        if (currentEntry != null && !currentEntry.isDirectory()) {\r\n            if (currentEntry.getSize() < currentFileSize + len) {\r\n                throw new IOException( \"The current entry[\" + currentEntry.getName() + \"] size[\"\r\n                        + currentEntry.getSize() + \"] is smaller than the bytes[\" + ( currentFileSize + len )\r\n                        + \"] being written.\" );\r\n            }\r\n        }\r\n\r\n        out.write( b, off, len );\r\n        \r\n        bytesWritten += len;\r\n\r\n        if (currentEntry != null) {\r\n            currentFileSize += len;\r\n        }        \r\n    }\r\n\r\n    /**\r\n     * Writes the next tar entry header on the stream\r\n     * \r\n     * @param entry\r\n     * @throws IOException\r\n     */\r\n    public void putNextEntry(TarEntry entry) throws IOException {\r\n        closeCurrentEntry();\r\n\r\n        byte[] header = new byte[TarConstants.HEADER_BLOCK];\r\n        entry.writeEntryHeader( header );\r\n\r\n        write( header );\r\n\r\n        currentEntry = entry;\r\n    }\r\n\r\n    /**\r\n     * Closes the current tar entry\r\n     * \r\n     * @throws IOException\r\n     */\r\n    protected void closeCurrentEntry() throws IOException {\r\n        if (currentEntry != null) {\r\n            if (currentEntry.getSize() > currentFileSize) {\r\n                throw new IOException( \"The current entry[\" + currentEntry.getName() + \"] of size[\"\r\n                        + currentEntry.getSize() + \"] has not been fully written.\" );\r\n            }\r\n\r\n            currentEntry = null;\r\n            currentFileSize = 0;\r\n\r\n            pad();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Pads the last content block\r\n     * \r\n     * @throws IOException\r\n     */\r\n    protected void pad() throws IOException {\r\n        if (bytesWritten > 0) {\r\n            int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );\r\n\r\n            if (extra > 0) {\r\n                write( new byte[TarConstants.DATA_BLOCK - extra] );\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarUtils.java",
    "content": "/**\r\n * Copyright 2012 Kamran Zafar \r\n * \r\n * Licensed under the Apache License, Version 2.0 (the \"License\"); \r\n * you may not use this file except in compliance with the License. \r\n * You may obtain a copy of the License at \r\n * \r\n *      http://www.apache.org/licenses/LICENSE-2.0 \r\n * \r\n * Unless required by applicable law or agreed to in writing, software \r\n * distributed under the License is distributed on an \"AS IS\" BASIS, \r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \r\n * See the License for the specific language governing permissions and \r\n * limitations under the License. \r\n * \r\n */\r\n\r\npackage org.kamranzafar.jtar;\r\n\r\nimport java.io.File;\r\n\r\n/**\r\n * @author Kamran\r\n * \r\n */\r\npublic class TarUtils {\r\n\t/**\r\n\t * Determines the tar file size of the given folder/file path\r\n\t * \r\n\t * @param path\r\n\t * @return\r\n\t */\r\n\tpublic static long calculateTarSize(File path) {\r\n\t\treturn tarSize(path) + TarConstants.EOF_BLOCK;\r\n\t}\r\n\r\n\tprivate static long tarSize(File dir) {\r\n\t\tlong size = 0;\r\n\r\n\t\tif (dir.isFile()) {\r\n\t\t\treturn entrySize(dir.length());\r\n\t\t} else {\r\n\t\t\tFile[] subFiles = dir.listFiles();\r\n\r\n\t\t\tif (subFiles != null && subFiles.length > 0) {\r\n\t\t\t\tfor (File file : subFiles) {\r\n\t\t\t\t\tif (file.isFile()) {\r\n\t\t\t\t\t\tsize += entrySize(file.length());\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tsize += tarSize(file);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// Empty folder header\r\n\t\t\t\treturn TarConstants.HEADER_BLOCK;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn size;\r\n\t}\r\n\r\n\tprivate static long entrySize(long fileSize) {\r\n\t\tlong size = 0;\r\n\t\tsize += TarConstants.HEADER_BLOCK; // Header\r\n\t\tsize += fileSize; // File size\r\n\r\n\t\tlong extra = size % TarConstants.DATA_BLOCK;\r\n\r\n\t\tif (extra > 0) {\r\n\t\t\tsize += (TarConstants.DATA_BLOCK - extra); // pad\r\n\t\t}\r\n\r\n\t\treturn size;\r\n\t}\r\n\r\n\tpublic static String trim(String s, char c) {\r\n\t\tStringBuffer tmp = new StringBuffer(s);\r\n\t\tfor (int i = 0; i < tmp.length(); i++) {\r\n\t\t\tif (tmp.charAt(i) != c) {\r\n\t\t\t\tbreak;\r\n\t\t\t} else {\r\n\t\t\t\ttmp.deleteCharAt(i);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (int i = tmp.length() - 1; i >= 0; i--) {\r\n\t\t\tif (tmp.charAt(i) != c) {\r\n\t\t\t\tbreak;\r\n\t\t\t} else {\r\n\t\t\t\ttmp.deleteCharAt(i);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn tmp.toString();\r\n\t}\r\n}\r\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java",
    "content": "package org.kivy.android;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\npublic class GenericBroadcastReceiver extends BroadcastReceiver {\n\n    GenericBroadcastReceiverCallback listener;\n\n    public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) {\n        super();\n        this.listener = listener;\n    }\n\n    public void onReceive(Context context, Intent intent) {\n        this.listener.onReceive(context, intent);\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java",
    "content": "package org.kivy.android;\n\nimport android.content.Context;\nimport android.content.Intent;\n\npublic interface GenericBroadcastReceiverCallback {\n    void onReceive(Context context, Intent intent);\n}\n;\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java",
    "content": "package org.kivy.android;\n\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Color;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.os.Process;\nimport android.util.Log;\nimport java.io.File;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\npublic class PythonService extends Service implements Runnable {\n\n    // Thread for Python code\n    private Thread pythonThread = null;\n\n    // Python environment variables\n    private String androidPrivate;\n    private String androidArgument;\n    private String pythonName;\n    private String pythonHome;\n    private String pythonPath;\n    private String serviceEntrypoint;\n    // Argument to pass to Python code,\n    private String pythonServiceArgument;\n\n    public static PythonService mService = null;\n    private Intent startIntent = null;\n\n    private boolean autoRestartService = false;\n\n    public void setAutoRestartService(boolean restart) {\n        autoRestartService = restart;\n    }\n\n    public int startType() {\n        return START_NOT_STICKY;\n    }\n\n    @Override\n    public IBinder onBind(Intent arg0) {\n        return null;\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        if (pythonThread != null) {\n            Log.v(\"python service\", \"service exists, do not start again\");\n            return startType();\n        }\n        // intent is null if OS restarts a STICKY service\n        if (intent == null) {\n            Context context = getApplicationContext();\n            intent = getThisDefaultIntent(context, \"\");\n        }\n\n        startIntent = intent;\n        Bundle extras = intent.getExtras();\n        androidPrivate = extras.getString(\"androidPrivate\");\n        androidArgument = extras.getString(\"androidArgument\");\n        serviceEntrypoint = extras.getString(\"serviceEntrypoint\");\n        pythonName = extras.getString(\"pythonName\");\n        pythonHome = extras.getString(\"pythonHome\");\n        pythonPath = extras.getString(\"pythonPath\");\n        boolean serviceStartAsForeground =\n                (extras.getString(\"serviceStartAsForeground\").equals(\"true\"));\n        pythonServiceArgument = extras.getString(\"pythonServiceArgument\");\n        pythonThread = new Thread(this);\n        pythonThread.start();\n\n        if (serviceStartAsForeground) {\n            doStartForeground(extras);\n        }\n\n        return startType();\n    }\n\n    protected int getServiceId() {\n        return 1;\n    }\n\n    protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {\n        return null;\n    }\n\n    protected void doStartForeground(Bundle extras) {\n        String serviceTitle = extras.getString(\"serviceTitle\");\n        String smallIconName = extras.getString(\"smallIconName\");\n        String contentTitle = extras.getString(\"contentTitle\");\n        String contentText = extras.getString(\"contentText\");\n        Notification notification;\n        Context context = getApplicationContext();\n        Intent contextIntent = new Intent(context, PythonActivity.class);\n        PendingIntent pIntent =\n                PendingIntent.getActivity(\n                        context,\n                        0,\n                        contextIntent,\n                        PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);\n\n        // Unspecified icon uses default.\n        int smallIconId = context.getApplicationInfo().icon;\n        if (smallIconName != null) {\n            if (!smallIconName.equals(\"\")) {\n                int resId = getResources().getIdentifier(smallIconName, \"mipmap\", getPackageName());\n                if (resId == 0) {\n                    resId =\n                            getResources()\n                                    .getIdentifier(smallIconName, \"drawable\", getPackageName());\n                }\n                if (resId != 0) {\n                    smallIconId = resId;\n                }\n            }\n        }\n\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n            // This constructor is deprecated\n            notification = new Notification(smallIconId, serviceTitle, System.currentTimeMillis());\n            try {\n                // prevent using NotificationCompat, this saves 100kb on apk\n                Method func =\n                        notification\n                                .getClass()\n                                .getMethod(\n                                        \"setLatestEventInfo\",\n                                        Context.class,\n                                        CharSequence.class,\n                                        CharSequence.class,\n                                        PendingIntent.class);\n                func.invoke(notification, context, contentTitle, contentText, pIntent);\n            } catch (NoSuchMethodException\n                    | IllegalAccessException\n                    | IllegalArgumentException\n                    | InvocationTargetException e) {\n            }\n        } else {\n            // for android 8+ we need to create our own channel\n            // https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1\n            String NOTIFICATION_CHANNEL_ID = \"org.kivy.p4a\" + getServiceId();\n            String channelName = \"Background Service\" + getServiceId();\n            NotificationChannel chan =\n                    new NotificationChannel(\n                            NOTIFICATION_CHANNEL_ID,\n                            channelName,\n                            NotificationManager.IMPORTANCE_NONE);\n\n            chan.setLightColor(Color.BLUE);\n            chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);\n            NotificationManager manager =\n                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n            manager.createNotificationChannel(chan);\n\n            Notification.Builder builder =\n                    new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);\n            builder.setContentTitle(contentTitle);\n            builder.setContentText(contentText);\n            builder.setContentIntent(pIntent);\n            builder.setSmallIcon(smallIconId);\n            notification = builder.build();\n        }\n        startForeground(getServiceId(), notification);\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        pythonThread = null;\n        if (autoRestartService && startIntent != null) {\n            Log.v(\"python service\", \"service restart requested\");\n            startService(startIntent);\n        }\n        Process.killProcess(Process.myPid());\n    }\n\n    /**\n     * Stops the task gracefully when killed. Calling stopSelf() will trigger a onDestroy() call\n     * from the system.\n     */\n    @Override\n    public void onTaskRemoved(Intent rootIntent) {\n        super.onTaskRemoved(rootIntent);\n        // sticky service runtime/restart is managed by the OS. leave it running when app is closed\n        if (startType() != START_STICKY) {\n            stopSelf();\n        }\n    }\n\n    @Override\n    public void run() {\n        String app_root = getFilesDir().getAbsolutePath() + \"/app\";\n        File app_root_file = new File(app_root);\n        PythonUtil.loadLibraries(app_root_file, new File(getApplicationInfo().nativeLibraryDir));\n        this.mService = this;\n        nativeStart(\n                androidPrivate,\n                androidArgument,\n                serviceEntrypoint,\n                pythonName,\n                pythonHome,\n                pythonPath,\n                pythonServiceArgument);\n        stopSelf();\n    }\n\n    // Native part\n    public static native void nativeStart(\n            String androidPrivate,\n            String androidArgument,\n            String serviceEntrypoint,\n            String pythonName,\n            String pythonHome,\n            String pythonPath,\n            String pythonServiceArgument);\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java",
    "content": "package org.kivy.android;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.util.Log;\nimport android.widget.Toast;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.regex.Pattern;\nimport org.renpy.android.AssetExtract;\n\npublic class PythonUtil {\n    private static final String TAG = \"pythonutil\";\n\n    protected static void addLibraryIfExists(\n            ArrayList<String> libsList, String pattern, File libsDir) {\n        // pattern should be the name of the lib file, without the\n        // preceding \"lib\" or suffix \".so\", for instance \"ssl.*\" will\n        // match files of the form \"libssl.*.so\".\n        File[] files = libsDir.listFiles();\n\n        pattern = \"lib\" + pattern + \"\\\\.so\";\n        Pattern p = Pattern.compile(pattern);\n        for (int i = 0; i < files.length; ++i) {\n            File file = files[i];\n            String name = file.getName();\n            Log.v(TAG, \"Checking pattern \" + pattern + \" against \" + name);\n            if (p.matcher(name).matches()) {\n                Log.v(TAG, \"Pattern \" + pattern + \" matched file \" + name);\n                libsList.add(name.substring(3, name.length() - 3));\n            }\n        }\n    }\n\n    protected static ArrayList<String> getLibraries(File libsDir) {\n        ArrayList<String> libsList = new ArrayList<>();\n\n        String[] libNames = {\n            \"sqlite3\",\n            \"ffi\",\n            \"png16\",\n            \"ssl.*\",\n            \"crypto.*\",\n            \"SDL2\",\n            \"SDL2_image\",\n            \"SDL2_mixer\",\n            \"SDL2_ttf\",\n            \"SDL3\",\n            \"SDL3_image\",\n            \"SDL3_mixer\",\n            \"SDL3_ttf\"\n        };\n\n        for (String name : libNames) {\n            addLibraryIfExists(libsList, name, libsDir);\n        }\n\n        for (int v = 14; v >= 8; v--) {\n            libsList.add(\"python3.\" + v);\n        }\n\n        libsList.add(\"main\");\n        return libsList;\n    }\n\n    public static void loadLibraries(File filesDir, File libsDir) {\n        boolean foundPython = false;\n\n        for (String lib : getLibraries(libsDir)) {\n            if (lib.startsWith(\"python\") && foundPython) {\n                continue;\n            }\n            Log.v(TAG, \"Loading library: \" + lib);\n            try {\n                System.loadLibrary(lib);\n                if (lib.startsWith(\"python\")) {\n                    foundPython = true;\n                }\n            } catch (UnsatisfiedLinkError e) {\n                // If this is the last possible libpython\n                // load, and it has failed, give a more\n                // general error\n                Log.v(TAG, \"Library loading error: \" + e.getMessage());\n                if (lib.startsWith(\"python3.8\") && !foundPython) {\n                    throw new RuntimeException(\"Could not load any libpythonXXX.so\");\n                } else if (lib.startsWith(\"python\")) {\n                    continue;\n                } else {\n                    Log.v(TAG, \"An UnsatisfiedLinkError occurred loading \" + lib);\n                    throw e;\n                }\n            }\n        }\n\n        Log.v(TAG, \"Loaded everything!\");\n    }\n\n    public static String getAppRoot(Context ctx) {\n        String appRoot = ctx.getFilesDir().getAbsolutePath() + \"/app\";\n        return appRoot;\n    }\n\n    public static String getResourceString(Context ctx, String name) {\n        // Taken from org.renpy.android.ResourceManager\n        Resources res = ctx.getResources();\n        int id = res.getIdentifier(name, \"string\", ctx.getPackageName());\n        return res.getString(id);\n    }\n\n    /** Show an error using a toast. (Only makes sense from non-UI threads.) */\n    protected static void toastError(final Activity activity, final String msg) {\n        activity.runOnUiThread(\n                new Runnable() {\n                    public void run() {\n                        Toast.makeText(activity, msg, Toast.LENGTH_LONG).show();\n                    }\n                });\n\n        // Wait to show the error.\n        synchronized (activity) {\n            try {\n                activity.wait(1000);\n            } catch (InterruptedException e) {\n            }\n        }\n    }\n\n    protected static void recursiveDelete(File f) {\n        if (f.isDirectory()) {\n            for (File r : f.listFiles()) {\n                recursiveDelete(r);\n            }\n        }\n        f.delete();\n    }\n\n    public static void unpackAsset(\n            Context ctx, final String resource, File target, boolean cleanup_on_version_update) {\n\n        Log.v(TAG, \"Unpacking \" + resource + \" \" + target.getName());\n\n        // The version of data in memory and on disk.\n        String dataVersion = getResourceString(ctx, resource + \"_version\");\n        String diskVersion = null;\n\n        Log.v(TAG, \"Data version is \" + dataVersion);\n\n        // If no version, no unpacking is necessary.\n        if (dataVersion == null) {\n            return;\n        }\n\n        // Check the current disk version, if any.\n        String filesDir = target.getAbsolutePath();\n        String diskVersionFn = filesDir + \"/\" + resource + \".version\";\n\n        try {\n            byte buf[] = new byte[64];\n            InputStream is = new FileInputStream(diskVersionFn);\n            int len = is.read(buf);\n            diskVersion = new String(buf, 0, len);\n            is.close();\n        } catch (Exception e) {\n            diskVersion = \"\";\n        }\n\n        // If the disk data is out of date, extract it and write the version file.\n        if (!dataVersion.equals(diskVersion)) {\n            Log.v(TAG, \"Extracting \" + resource + \" assets.\");\n\n            if (cleanup_on_version_update) {\n                recursiveDelete(target);\n            }\n            target.mkdirs();\n\n            AssetExtract ae = new AssetExtract(ctx);\n            if (!ae.extractTar(resource + \".tar\", target.getAbsolutePath(), \"private\")) {\n                String msg = \"Could not extract \" + resource + \" data.\";\n                if (ctx instanceof Activity) {\n                    toastError((Activity) ctx, msg);\n                } else {\n                    Log.v(TAG, msg);\n                }\n            }\n\n            try {\n                // Write .nomedia.\n                new File(target, \".nomedia\").createNewFile();\n\n                // Write version file.\n                FileOutputStream os = new FileOutputStream(diskVersionFn);\n                os.write(dataVersion.getBytes());\n                os.close();\n            } catch (Exception e) {\n                Log.w(TAG, e);\n            }\n        }\n    }\n\n    public static void unpackPyBundle(\n            Context ctx, final String resource, File target, boolean cleanup_on_version_update) {\n\n        Log.v(TAG, \"Unpacking \" + resource + \" \" + target.getName());\n\n        // The version of data in memory and on disk.\n        String dataVersion = getResourceString(ctx, \"private_version\");\n        String diskVersion = null;\n\n        Log.v(TAG, \"Data version is \" + dataVersion);\n\n        // If no version, no unpacking is necessary.\n        if (dataVersion == null) {\n            return;\n        }\n\n        // Check the current disk version, if any.\n        String filesDir = target.getAbsolutePath();\n        String diskVersionFn = filesDir + \"/\" + \"libpybundle\" + \".version\";\n\n        try {\n            byte buf[] = new byte[64];\n            InputStream is = new FileInputStream(diskVersionFn);\n            int len = is.read(buf);\n            diskVersion = new String(buf, 0, len);\n            is.close();\n        } catch (Exception e) {\n            diskVersion = \"\";\n        }\n\n        if (!dataVersion.equals(diskVersion)) {\n            // If the disk data is out of date, extract it and write the version file.\n            Log.v(TAG, \"Extracting \" + resource + \" assets.\");\n\n            if (cleanup_on_version_update) {\n                recursiveDelete(target);\n            }\n            target.mkdirs();\n\n            AssetExtract ae = new AssetExtract(ctx);\n            if (!ae.extractTar(resource + \".so\", target.getAbsolutePath(), \"pybundle\")) {\n                String msg = \"Could not extract \" + resource + \" data.\";\n                if (ctx instanceof Activity) {\n                    toastError((Activity) ctx, msg);\n                } else {\n                    Log.v(TAG, msg);\n                }\n            }\n\n            try {\n                // Write version file.\n                FileOutputStream os = new FileOutputStream(diskVersionFn);\n                os.write(dataVersion.getBytes());\n                os.close();\n            } catch (Exception e) {\n                Log.w(TAG, e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java",
    "content": "// This string is autogenerated by ChangeAppSettings.sh, do not change\n// spaces amount\npackage org.renpy.android;\n\nimport android.content.Context;\nimport android.content.res.AssetManager;\nimport android.util.Log;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.zip.GZIPInputStream;\nimport org.kamranzafar.jtar.TarEntry;\nimport org.kamranzafar.jtar.TarInputStream;\n\npublic class AssetExtract {\n\n    private AssetManager mAssetManager = null;\n\n    public AssetExtract(Context context) {\n        mAssetManager = context.getAssets();\n    }\n\n    public boolean extractTar(String asset, String target, String method) {\n\n        byte buf[] = new byte[1024 * 1024];\n\n        InputStream assetStream = null;\n        TarInputStream tis = null;\n\n        try {\n            if (method == \"private\") {\n                assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);\n            } else if (method == \"pybundle\") {\n                assetStream = new FileInputStream(asset);\n            }\n\n            tis =\n                    new TarInputStream(\n                            new BufferedInputStream(\n                                    new GZIPInputStream(new BufferedInputStream(assetStream, 8192)),\n                                    8192));\n        } catch (IOException e) {\n            Log.e(\"python\", \"opening up extract tar\", e);\n            return false;\n        }\n\n        while (true) {\n            TarEntry entry = null;\n\n            try {\n                entry = tis.getNextEntry();\n            } catch (IOException e) {\n                Log.e(\"python\", \"extracting tar\", e);\n                return false;\n            }\n\n            if (entry == null) {\n                break;\n            }\n\n            Log.v(\"python\", \"extracting \" + entry.getName());\n\n            if (entry.isDirectory()) {\n\n                try {\n                    new File(target + \"/\" + entry.getName()).mkdirs();\n                } catch (SecurityException e) {\n                }\n                ;\n\n                continue;\n            }\n\n            OutputStream out = null;\n            String path = target + \"/\" + entry.getName();\n\n            try {\n                out = new BufferedOutputStream(new FileOutputStream(path), 8192);\n            } catch (FileNotFoundException | SecurityException e) {\n            }\n\n            if (out == null) {\n                Log.e(\"python\", \"could not open \" + path);\n                return false;\n            }\n\n            try {\n                while (true) {\n                    int len = tis.read(buf);\n\n                    if (len == -1) {\n                        break;\n                    }\n\n                    out.write(buf, 0, len);\n                }\n\n                out.flush();\n                out.close();\n            } catch (IOException e) {\n                Log.e(\"python\", \"extracting zip\", e);\n                return false;\n            }\n        }\n\n        try {\n            tis.close();\n            assetStream.close();\n        } catch (IOException e) {\n            // pass\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/Hardware.java",
    "content": "package org.renpy.android;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.hardware.Sensor;\nimport android.hardware.SensorEvent;\nimport android.hardware.SensorEventListener;\nimport android.hardware.SensorManager;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.net.wifi.ScanResult;\nimport android.net.wifi.WifiManager;\nimport android.os.Vibrator;\nimport android.util.DisplayMetrics;\nimport android.view.View;\nimport android.view.inputmethod.InputMethodManager;\nimport java.util.List;\nimport org.kivy.android.PythonActivity;\n\n/**\n * Methods that are expected to be called via JNI, to access the device's non-screen hardware. (For\n * example, the vibration and accelerometer.)\n */\npublic class Hardware {\n\n    // The context.\n    static Context context;\n    static View view;\n    public static final float defaultRv[] = {0f, 0f, 0f};\n\n    /** Vibrate for s seconds. */\n    public static void vibrate(double s) {\n        Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);\n        if (v != null) {\n            v.vibrate((int) (1000 * s));\n        }\n    }\n\n    /** Get an Overview of all Hardware Sensors of an Android Device */\n    public static String getHardwareSensors() {\n        SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);\n        List<Sensor> allSensors = sm.getSensorList(Sensor.TYPE_ALL);\n\n        if (allSensors != null) {\n            String resultString = \"\";\n            for (Sensor s : allSensors) {\n                resultString += String.format(\"Name=\" + s.getName());\n                resultString += String.format(\",Vendor=\" + s.getVendor());\n                resultString += String.format(\",Version=\" + s.getVersion());\n                resultString += String.format(\",MaximumRange=\" + s.getMaximumRange());\n                // XXX MinDelay is not in the 2.2\n                // resultString += String.format(\",MinDelay=\" + s.getMinDelay());\n                resultString += String.format(\",Power=\" + s.getPower());\n                resultString += String.format(\",Type=\" + s.getType() + \"\\n\");\n            }\n            return resultString;\n        }\n        return \"\";\n    }\n\n    /**\n     * Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and Magnetic Field Sensors\n     */\n    public static class generic3AxisSensor implements SensorEventListener {\n        private final SensorManager sSensorManager;\n        private final Sensor sSensor;\n        private final int sSensorType;\n        SensorEvent sSensorEvent;\n\n        public generic3AxisSensor(int sensorType) {\n            sSensorType = sensorType;\n            sSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);\n            sSensor = sSensorManager.getDefaultSensor(sSensorType);\n        }\n\n        public void onAccuracyChanged(Sensor sensor, int accuracy) {}\n\n        public void onSensorChanged(SensorEvent event) {\n            sSensorEvent = event;\n        }\n\n        /** Enable or disable the Sensor by registering/unregistering */\n        public void changeStatus(boolean enable) {\n            if (enable) {\n                sSensorManager.registerListener(this, sSensor, SensorManager.SENSOR_DELAY_NORMAL);\n            } else {\n                sSensorManager.unregisterListener(this, sSensor);\n            }\n        }\n\n        /** Read the Sensor */\n        public float[] readSensor() {\n            if (sSensorEvent != null) {\n                return sSensorEvent.values;\n            } else {\n                return defaultRv;\n            }\n        }\n    }\n\n    public static generic3AxisSensor accelerometerSensor = null;\n    public static generic3AxisSensor orientationSensor = null;\n    public static generic3AxisSensor magneticFieldSensor = null;\n\n    /** functions for backward compatibility reasons */\n    public static void accelerometerEnable(boolean enable) {\n        if (accelerometerSensor == null)\n            accelerometerSensor = new generic3AxisSensor(Sensor.TYPE_ACCELEROMETER);\n        accelerometerSensor.changeStatus(enable);\n    }\n\n    public static float[] accelerometerReading() {\n        if (accelerometerSensor == null) return defaultRv;\n        return (float[]) accelerometerSensor.readSensor();\n    }\n\n    public static void orientationSensorEnable(boolean enable) {\n        if (orientationSensor == null)\n            orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION);\n        orientationSensor.changeStatus(enable);\n    }\n\n    public static float[] orientationSensorReading() {\n        if (orientationSensor == null) return defaultRv;\n        return (float[]) orientationSensor.readSensor();\n    }\n\n    public static void magneticFieldSensorEnable(boolean enable) {\n        if (magneticFieldSensor == null)\n            magneticFieldSensor = new generic3AxisSensor(Sensor.TYPE_MAGNETIC_FIELD);\n        magneticFieldSensor.changeStatus(enable);\n    }\n\n    public static float[] magneticFieldSensorReading() {\n        if (magneticFieldSensor == null) return defaultRv;\n        return (float[]) magneticFieldSensor.readSensor();\n    }\n\n    public static DisplayMetrics metrics = new DisplayMetrics();\n\n    /** Get display DPI. */\n    public static int getDPI() {\n        // AND: Shouldn't have to get the metrics like this every time...\n        PythonActivity.mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);\n        return metrics.densityDpi;\n    }\n\n    // /**\n    //  * Show the soft keyboard.\n    //  */\n    // public static void showKeyboard(int input_type) {\n    //     //Log.i(\"python\", \"hardware.Java show_keyword  \" input_type);\n\n    //     InputMethodManager imm = (InputMethodManager)\n    // context.getSystemService(Context.INPUT_METHOD_SERVICE);\n\n    //     SDLSurfaceView vw = (SDLSurfaceView) view;\n\n    //     int inputType = input_type;\n\n    //     if (vw.inputType != inputType){\n    //         vw.inputType = inputType;\n    //         imm.restartInput(view);\n    //         }\n\n    //     imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);\n    // }\n\n    /** Hide the soft keyboard. */\n    public static void hideKeyboard() {\n        InputMethodManager imm =\n                (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);\n        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);\n    }\n\n    /** Scan WiFi networks */\n    static List<ScanResult> latestResult;\n\n    public static void enableWifiScanner() {\n        IntentFilter i = new IntentFilter();\n        i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);\n\n        context.registerReceiver(\n                new BroadcastReceiver() {\n\n                    @Override\n                    public void onReceive(Context c, Intent i) {\n                        // Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event occurs\n                        WifiManager w = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);\n                        latestResult = w.getScanResults(); // Returns a <list> of scanResults\n                    }\n                },\n                i);\n    }\n\n    public static String scanWifi() {\n\n        // Now you can call this and it should execute the broadcastReceiver's\n        // onReceive()\n        if (latestResult != null) {\n\n            String latestResultString = \"\";\n            for (ScanResult result : latestResult) {\n                latestResultString +=\n                        String.format(\"%s\\t%s\\t%d\\n\", result.SSID, result.BSSID, result.level);\n            }\n\n            return latestResultString;\n        }\n\n        return \"\";\n    }\n\n    /** network state */\n    public static boolean network_state = false;\n\n    /**\n     * Check network state directly\n     *\n     * <p>(only one connection can be active at a given moment, detects all network type)\n     */\n    public static boolean checkNetwork() {\n        boolean state = false;\n        final ConnectivityManager conMgr =\n                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);\n\n        final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();\n        if (activeNetwork != null && activeNetwork.isConnected()) {\n            state = true;\n        } else {\n            state = false;\n        }\n\n        return state;\n    }\n\n    /** To receive network state changes */\n    public static void registerNetworkCheck() {\n        IntentFilter i = new IntentFilter();\n        i.addAction(ConnectivityManager.CONNECTIVITY_ACTION);\n        context.registerReceiver(\n                new BroadcastReceiver() {\n\n                    @Override\n                    public void onReceive(Context c, Intent i) {\n                        network_state = checkNetwork();\n                    }\n                },\n                i);\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/ResourceManager.java",
    "content": "/**\n * This class takes care of managing resources for us. In our code, we can't use R, since the name\n * of the package containing R will change. So this is the next best thing.\n */\npackage org.renpy.android;\n\nimport android.app.Activity;\nimport android.content.res.Resources;\nimport android.util.Log;\nimport android.view.View;\n\npublic class ResourceManager {\n\n    private Activity act;\n    private Resources res;\n\n    public ResourceManager(Activity activity) {\n        act = activity;\n        res = act.getResources();\n    }\n\n    public int getIdentifier(String name, String kind) {\n        Log.v(\"SDL\", \"getting identifier\");\n        Log.v(\"SDL\", \"kind is \" + kind + \" and name \" + name);\n        Log.v(\"SDL\", \"result is \" + res.getIdentifier(name, kind, act.getPackageName()));\n        return res.getIdentifier(name, kind, act.getPackageName());\n    }\n\n    public String getString(String name) {\n\n        try {\n            Log.v(\"SDL\", \"asked to get string \" + name);\n            return res.getString(getIdentifier(name, \"string\"));\n        } catch (Exception e) {\n            Log.v(\"SDL\", \"got exception looking for string!\");\n            return null;\n        }\n    }\n\n    public View inflateView(String name) {\n        int id = getIdentifier(name, \"layout\");\n        return act.getLayoutInflater().inflate(id, null);\n    }\n\n    public View getViewById(View v, String name) {\n        int id = getIdentifier(name, \"id\");\n        return v.findViewById(id);\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java",
    "content": "package {{ args.package }};\n\nimport android.content.Intent;\nimport android.content.Context;\nimport {{ args.service_class_name }};\n\n\npublic class Service{{ name|capitalize }} extends {{ base_service_class }} {\n    {% if sticky %}\n    @Override\n    public int startType() {\n        return START_STICKY;\n    }\n    {% endif %}\n\n    @Override\n    protected int getServiceId() {\n        return {{ service_id }};\n    }\n\n    static private void _start(Context ctx, String smallIconName,\n                               String contentTitle, String contentText,\n                               String pythonServiceArgument) {\n        Intent intent = getDefaultIntent(ctx, smallIconName, contentTitle,\n            contentText, pythonServiceArgument);\n        ctx.startService(intent);\n    }\n\n    static public void start(Context ctx, String pythonServiceArgument) {\n        _start(ctx, \"\", \"{{ args.name }}\", \"{{ name|capitalize }}\", pythonServiceArgument);\n    }\n\n    static public void start(Context ctx, String smallIconName,\n                             String contentTitle, String contentText,\n                             String pythonServiceArgument) {\n        _start(ctx, smallIconName, contentTitle, contentText, pythonServiceArgument);\n    }\n\n    static public Intent getDefaultIntent(Context ctx, String smallIconName,\n                                          String contentTitle, String contentText,\n                                          String pythonServiceArgument) {\n        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);\n        String argument = ctx.getFilesDir().getAbsolutePath() + \"/app\";\n        intent.putExtra(\"androidPrivate\", ctx.getFilesDir().getAbsolutePath());\n        intent.putExtra(\"androidArgument\", argument);\n        intent.putExtra(\"serviceTitle\", \"{{ args.name }}\");\n        intent.putExtra(\"serviceEntrypoint\", \"{{ entrypoint }}\");\n        intent.putExtra(\"pythonName\", \"{{ name }}\");\n        intent.putExtra(\"serviceStartAsForeground\", \"{{ foreground|lower }}\");\n        intent.putExtra(\"pythonHome\", argument);\n        intent.putExtra(\"pythonPath\", argument + \":\" + argument + \"/lib\");\n        intent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        intent.putExtra(\"smallIconName\", smallIconName);\n        intent.putExtra(\"contentTitle\", contentTitle);\n        intent.putExtra(\"contentText\", contentText);\n        return intent;\n    }\n\n    @Override\n    protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {\n        return Service{{ name|capitalize }}.getDefaultIntent(ctx, \"\", \"\", \"\",\n            pythonServiceArgument);\n    }\n\n    static public void stop(Context ctx) {\n        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);\n        ctx.stopService(intent);\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/templates/build.properties",
    "content": "# This file is used to override default values used by the Ant build system.\n# \n# This file must be checked in Version Control Systems, as it is\n# integral to the build system of your project.\n\n# This file is only used by the Ant script.\n\n# You can use this to override default values such as\n#  'source.dir' for the location of your java source folder and\n#  'out.dir' for the location of your output folder.\n\n# You can also use it define how the release builds are signed by declaring\n# the following properties:\n#  'key.store' for the location of your keystore and\n#  'key.alias' for the name of the key to use.\n# The password will be asked during the build when you use the 'release' target.\n\nkey.store=${env.P4A_RELEASE_KEYSTORE}\nkey.alias=${env.P4A_RELEASE_KEYALIAS}\nkey.store.password=${env.P4A_RELEASE_KEYSTORE_PASSWD}\nkey.alias.password=${env.P4A_RELEASE_KEYALIAS_PASSWD}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n    repositories {\n       google()\n       mavenCentral()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:8.11.0'\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n        {%- for repo in args.gradle_repositories %}\n        {{repo}}\n        {%- endfor %}\n    }\n}\n\n{% if is_library %}\napply plugin: 'com.android.library'\n{% else %}\napply plugin: 'com.android.application'\n{% endif %}\n\nandroid {\n    namespace '{{ args.package }}'\n    compileSdkVersion {{ android_api }}\n    buildToolsVersion '{{ build_tools_version }}'\n    defaultConfig {\n        minSdkVersion {{ args.min_sdk_version }}\n        targetSdkVersion {{ android_api }}\n        versionCode {{ args.numeric_version }}\n        versionName '{{ args.version }}'\n        manifestPlaceholders = {{ args.manifest_placeholders}}\n    }\n\n\t\n\tpackagingOptions {\n        jniLibs {\n            useLegacyPackaging = true\n        }\n        {% if debug_build -%}\n        doNotStrip '**/*.so'\n        {% else %}\n        exclude 'lib/**/gdbserver'\n        exclude 'lib/**/gdb.setup'\n        {%- endif %}\n\t}\n\t\n\n\t{% if args.sign -%}\n\tsigningConfigs {\n\t\trelease {\n\t\t\tstoreFile file(System.getenv(\"P4A_RELEASE_KEYSTORE\"))\n\t\t\tkeyAlias System.getenv(\"P4A_RELEASE_KEYALIAS\")\n\t\t\tstorePassword System.getenv(\"P4A_RELEASE_KEYSTORE_PASSWD\")\n\t\t\tkeyPassword System.getenv(\"P4A_RELEASE_KEYALIAS_PASSWD\")\n\t\t}\n\t}\n\n    {%- endif %}\n\n    {% if args.packaging_options -%}\n    packagingOptions {\n        {%- for option in args.packaging_options %}\n        {{option}}\n        {%- endfor %}\n    }\n    {%- endif %}\n\n    buildTypes {\n        debug {\n        }\n        release {\n            {% if args.sign -%}\n            signingConfig signingConfigs.release\n            {%- endif %}\n        }\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n        {%- for option in args.compile_options %}\n        {{option}}\n        {%- endfor %}\n    }\n\n    sourceSets {\n        main {\n            jniLibs.srcDir 'libs'\n            java {\n\n                {%- for adir, pattern in args.extra_source_dirs -%}\n                    srcDir '{{adir}}'\n                {%- endfor -%}\n\n            }\n        }\n    }\n\n    aaptOptions {\n        noCompress \"tflite\"\n    }\n\n}\n\ndependencies {\n    {%- for aar in aars %}\n    implementation(name: '{{ aar }}', ext: 'aar')\n    {%- endfor -%}\n    {%- for jar in jars %}\n    implementation files('src/main/libs/{{ jar }}')\n    {%- endfor -%}\n    {%- if args.depends -%}\n    {%- for depend in args.depends %}\n    implementation '{{ depend }}'\n    {%- endfor %}\n    {%- endif %}\n    {% if args.presplash_lottie %}\n    implementation 'com.airbnb.android:lottie:6.1.0'\n    {%- endif %}\n}\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/templates/build.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- This should be changed to the name of your project -->\n<project name=\"{{ versioned_name }}\" default=\"help\">\n\n    <!-- The local.properties file is created and updated by the 'android' tool.\n         It contains the path to the SDK. It should *NOT* be checked into\n         Version Control Systems. -->\n    <property file=\"local.properties\" />\n\n    <!-- The ant.properties file can be created by you. It is only edited by the\n         'android' tool to add properties to it.\n         This is the place to change some Ant specific build properties.\n         Here are some properties you may want to change/update:\n\n         source.dir\n             The name of the source directory. Default is 'src'.\n         out.dir\n             The name of the output directory. Default is 'bin'.\n\n         For other overridable properties, look at the beginning of the rules\n         files in the SDK, at tools/ant/build.xml\n\n         Properties related to the SDK location or the project target should\n         be updated using the 'android' tool with the 'update' action.\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems.\n\n         -->\n    <property file=\"ant.properties\" />\n\n    <!-- if sdk.dir was not set from one of the property file, then\n         get it from the ANDROID_HOME env var.\n         This must be done before we load project.properties since\n         the proguard config can use sdk.dir -->\n    <property environment=\"env\" />\n    <condition property=\"sdk.dir\" value=\"${env.ANDROID_HOME}\">\n        <isset property=\"env.ANDROID_HOME\" />\n    </condition>\n\n    <property file=\"build.properties\" />\n\n    <!-- The project.properties file is created and updated by the 'android'\n         tool, as well as ADT.\n\n         This contains project specific properties such as project target, and library\n         dependencies. Lower level build properties are stored in ant.properties\n         (or in .classpath for Eclipse projects).\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems. -->\n    <loadproperties srcFile=\"project.properties\" />\n\n    <!-- quick check on sdk.dir -->\n    <fail\n            message=\"sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable.\"\n            unless=\"sdk.dir\"\n    />\n\n    <!--\n        Import per project custom build rules if present at the root of the project.\n        This is the place to put custom intermediary targets such as:\n            -pre-build\n            -pre-compile\n            -post-compile (This is typically used for code obfuscation.\n                           Compiled code location: ${out.classes.absolute.dir}\n                           If this is not done in place, override ${out.dex.input.absolute.dir})\n            -post-package\n            -post-build\n            -pre-clean\n    -->\n    <import file=\"custom_rules.xml\" optional=\"true\" />\n\n    <!-- Import the actual build file.\n\n         To customize existing targets, there are two options:\n         - Customize only one target:\n             - copy/paste the target into this file, *before* the\n               <import> task.\n             - customize it to your needs.\n         - Customize the whole content of build.xml\n             - copy/paste the content of the rules files (minus the top node)\n               into this file, replacing the <import> task.\n             - customize to your needs.\n\n         ***********************\n         ****** IMPORTANT ******\n         ***********************\n         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,\n         in order to avoid having your file be overridden by tools such as \"android update project\"\n    -->\n    <!-- version-tag: 1 -->\n    <import file=\"${sdk.dir}/tools/ant/build.xml\" />\n\n</project>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/templates/custom_rules.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project name=\"CustomRules\">\n    <target name=\"-pre-build\">\n        <copy todir=\"tmp-src\">\n            {% if args.launcher %}\n            <fileset dir=\"src/main/java\" includes=\"**\" />\n            {% else %}\n            <fileset dir=\"src/main/java\">\n                <exclude name=\"org/kivy/android/ProjectAdapter.java\" />\n                <exclude name=\"org/kivy/android/ProjectChooser.java\" />\n            </fileset>\n            {% endif %}\n            {% for dir, includes in args.extra_source_dirs %}\n            <fileset dir=\"{{ dir }}\" includes=\"{{ includes }}\" />\n            {% endfor %}\n        </copy>\n    </target>\n    <target name=\"-post-build\">\n        <delete dir=\"tmp-src\" />\n    </target>\n</project>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/templates/gradle.tmpl.properties",
    "content": "{% if bootstrap_name == \"qt\" %}\n# For tweaking memory settings. Otherwise, a p4a session with Qt bootstrap and PySide6 recipe\n# terminates with a Java out of memory exception\norg.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n{% endif %}\n{% if args.enable_androidx %}\nandroid.useAndroidX=true\nandroid.enableJetifier=true\n{% endif %}"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/templates/lottie.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"fill_parent\"\n>\n\n    <com.airbnb.lottie.LottieAnimationView\n        android:id=\"@+id/progressBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"center\"\n        android:scaleType=\"centerInside\"\n        android:layout_weight=\"4\"\n        app:lottie_autoPlay=\"true\"\n        app:lottie_loop=\"true\"\n        app:lottie_rawRes=\"@raw/splashscreen\"\n    />\n</LinearLayout>\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/common/build/whitelist.txt",
    "content": "# put files here that you need to un-blacklist\n"
  },
  {
    "path": "pythonforandroid/bootstraps/empty/__init__.py",
    "content": "from pythonforandroid.toolchain import Bootstrap\n\n\nclass EmptyBootstrap(Bootstrap):\n    name = 'empty'\n\n    recipe_depends = []\n\n    can_be_chosen_automatically = False\n\n    def assemble_distribution(self):\n        print('empty bootstrap has no distribute')\n        exit(1)\n\n\nbootstrap = EmptyBootstrap()\n"
  },
  {
    "path": "pythonforandroid/bootstraps/empty/build/.gitkeep",
    "content": " \n"
  },
  {
    "path": "pythonforandroid/bootstraps/gradle.properties",
    "content": "# Gradle properties for Java lint project\n# Disable daemon for CI environments\norg.gradle.daemon=false\n\n# Use parallel execution where possible\norg.gradle.parallel=true\n\n# Configure JVM memory\norg.gradle.jvmargs=-Xmx512m\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/__init__.py",
    "content": "import sh\nfrom os.path import join\nfrom pythonforandroid.toolchain import (\n    Bootstrap, current_directory, info, info_main, shprint)\nfrom pythonforandroid.util import ensure_dir, rmdir\n\n\nclass QtBootstrap(Bootstrap):\n    name = 'qt'\n    recipe_depends = ['python3', 'genericndkbuild', 'PySide6', 'shiboken6']\n    # this is needed because the recipes PySide6 and shiboken6 resides in the PySide Qt repository\n    # - https://code.qt.io/cgit/pyside/pyside-setup.git/\n    # Without this some tests will error because it cannot find the recipes within pythonforandroid\n    # repository\n    can_be_chosen_automatically = False\n\n    def assemble_distribution(self):\n        info_main(\"# Creating Android project using Qt bootstrap\")\n\n        rmdir(self.dist_dir)\n        info(\"Copying gradle build\")\n        shprint(sh.cp, '-r', self.build_dir, self.dist_dir)\n\n        with current_directory(self.dist_dir):\n            with open('local.properties', 'w') as fileh:\n                fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))\n\n        arch = self.ctx.archs[0]\n        if len(self.ctx.archs) > 1:\n            raise ValueError(\"Trying to build for more than one arch. Qt bootstrap cannot handle that yet\")\n\n        info(f\"Bootstrap running with arch {arch}\")\n\n        with current_directory(self.dist_dir):\n            info(\"Copying Python distribution\")\n\n            self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])\n            self.distribute_aars(arch)\n            self.distribute_javaclasses(self.ctx.javaclass_dir,\n                                        dest_dir=join(\"src\", \"main\", \"java\"))\n\n            python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')\n            ensure_dir(python_bundle_dir)\n            site_packages_dir = self.ctx.python_recipe.create_python_bundle(\n                join(self.dist_dir, python_bundle_dir), arch)\n\n        if not self.ctx.with_debug_symbols:\n            self.strip_libraries(arch)\n        self.fry_eggs(site_packages_dir)\n        super().assemble_distribution()\n\n\nbootstrap = QtBootstrap()\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/.gitignore",
    "content": ".gradle\n/build/\n\n# Ignore Gradle GUI config\ngradle-app.setting\n\n# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)\n!gradle-wrapper.jar\n\n# Cache of project\n.gradletasknamecache\n\n# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898\n# gradle/wrapper/gradle-wrapper.properties\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/blacklist.txt",
    "content": "# prevent user to include invalid extensions\n*.apk\n*.aab\n*.apks\n*.pxd\n\n# eggs\n*.egg-info\n\n# unit test\nunittest/*\n\n# python config\nconfig/makesetup\n\n# unused encodings\nlib-dynload/*codec*\nencodings/cp*.pyo\nencodings/tis*\nencodings/shift*\nencodings/bz2*\nencodings/iso*\nencodings/undefined*\nencodings/johab*\nencodings/p*\nencodings/m*\nencodings/euc*\nencodings/k*\nencodings/unicode_internal*\nencodings/quo*\nencodings/gb*\nencodings/big5*\nencodings/hp*\nencodings/hz*\n\n# unused python modules\nbsddb/*\nwsgiref/*\nhotshot/*\npydoc_data/*\ntty.pyo\nanydbm.pyo\nnturl2path.pyo\nLICENCE.txt\nmacurl2path.pyo\ndummy_threading.pyo\naudiodev.pyo\nantigravity.pyo\ndumbdbm.pyo\nsndhdr.pyo\n__phello__.foo.pyo\nsunaudio.pyo\nos2emxpath.pyo\nmultiprocessing/dummy*\n\n# unused binaries python modules\nlib-dynload/termios.so\nlib-dynload/_lsprof.so\nlib-dynload/*audioop.so\nlib-dynload/_hotshot.so\nlib-dynload/_heapq.so\nlib-dynload/_json.so\nlib-dynload/grp.so\nlib-dynload/resource.so\nlib-dynload/pyexpat.so\nlib-dynload/_ctypes_test.so\nlib-dynload/_testcapi.so\n\n# odd files\nplat-linux3/regen\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/jni/Application.mk",
    "content": "\n# Uncomment this if you're using STL in your project\n# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information\n# APP_STL := stlport_static\n\n# APP_ABI := armeabi armeabi-v7a x86\nAPP_ABI := $(ARCH)\nAPP_PLATFORM := $(NDK_API)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/jni/application/src/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main_$(PREFERRED_ABI)\n\n# Add your application source files here...\nLOCAL_SRC_FILES := start.c\n\nLOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)\n\nLOCAL_SHARED_LIBRARIES := python_shared\n\nLOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)\n\nLOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/jni/application/src/Android_static.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main_$(PREFERRED_ABI)\n\nLOCAL_SRC_FILES := start.c\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/jni/application/src/bootstrap_name.h",
    "content": "\n\nconst char bootstrap_name[] = \"qt\";\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/src/main/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/src/main/java/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java",
    "content": "package org.kivy.android;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.os.PowerManager;\nimport android.os.SystemClock;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.widget.Toast;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport org.qtproject.qt.android.bindings.QtActivity;\n\npublic class PythonActivity extends QtActivity {\n\n    private static final String TAG = \"PythonActivity\";\n\n    public static PythonActivity mActivity = null;\n\n    private Bundle mMetaData = null;\n    private PowerManager.WakeLock mWakeLock = null;\n\n    public String getAppRoot() {\n        String app_root = getFilesDir().getAbsolutePath() + \"/app\";\n        return app_root;\n    }\n\n    public String getEntryPoint(String search_dir) {\n        /* Get the main file (.pyc|.py) depending on if we\n         * have a compiled version or not.\n         */\n        List<String> entryPoints = new ArrayList<String>();\n        entryPoints.add(\"main.pyc\"); // python 3 compiled files\n        for (String value : entryPoints) {\n            File mainFile = new File(search_dir + \"/\" + value);\n            if (mainFile.exists()) {\n                return value;\n            }\n        }\n        return \"main.py\";\n    }\n\n    public void setEnvironmentVariable(String key, String value) {\n        /** Sets an environment variable based on key/value. */\n        try {\n            android.system.Os.setenv(key, value, true);\n        } catch (Exception e) {\n            Log.e(\"Qt bootstrap\", \"Unable set environment variable:\" + key + \"=\" + value);\n            e.printStackTrace();\n        }\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        this.mActivity = this;\n        Log.v(TAG, \"Ready to unpack\");\n        File app_root_file = new File(getAppRoot());\n        PythonUtil.unpackAsset(mActivity, \"private\", app_root_file, true);\n        PythonUtil.unpackPyBundle(\n                mActivity,\n                getApplicationInfo().nativeLibraryDir + \"/\" + \"libpybundle\",\n                app_root_file,\n                false);\n\n        Log.v(\"Python\", \"Device: \" + android.os.Build.DEVICE);\n        Log.v(\"Python\", \"Model: \" + android.os.Build.MODEL);\n\n        // Set up the Python environment\n        String app_root_dir = getAppRoot();\n        String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();\n        String entry_point = getEntryPoint(app_root_dir);\n\n        Log.v(TAG, \"Setting env vars for start.c and Python to use\");\n        setEnvironmentVariable(\"ANDROID_ENTRYPOINT\", entry_point);\n        setEnvironmentVariable(\"ANDROID_ARGUMENT\", app_root_dir);\n        setEnvironmentVariable(\"ANDROID_APP_PATH\", app_root_dir);\n        setEnvironmentVariable(\"ANDROID_PRIVATE\", mFilesDirectory);\n        setEnvironmentVariable(\"ANDROID_UNPACK\", app_root_dir);\n        setEnvironmentVariable(\"PYTHONHOME\", app_root_dir);\n        setEnvironmentVariable(\"PYTHONPATH\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n        setEnvironmentVariable(\"PYTHONOPTIMIZE\", \"2\");\n\n        Log.v(TAG, \"About to do super onCreate\");\n        super.onCreate(savedInstanceState);\n        Log.v(TAG, \"Did super onCreate\");\n\n        this.mActivity = this;\n        try {\n            Log.v(TAG, \"Access to our meta-data...\");\n            mActivity.mMetaData =\n                    mActivity\n                            .getPackageManager()\n                            .getApplicationInfo(\n                                    mActivity.getPackageName(), PackageManager.GET_META_DATA)\n                            .metaData;\n\n            PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);\n            if (mActivity.mMetaData.getInt(\"wakelock\") == 1) {\n                mActivity.mWakeLock =\n                        pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, \"Screen On\");\n                mActivity.mWakeLock.acquire();\n            }\n        } catch (PackageManager.NameNotFoundException e) {\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        Log.i(\"Destroy\", \"end of app\");\n        super.onDestroy();\n\n        // make sure all child threads (python_thread) are stopped\n        android.os.Process.killProcess(android.os.Process.myPid());\n    }\n\n    long lastBackClick = SystemClock.elapsedRealtime();\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n        // If it wasn't the Back key or there's no web page history, bubble up to the default\n        // system behavior (probably exit the activity)\n        if (SystemClock.elapsedRealtime() - lastBackClick > 2000) {\n            lastBackClick = SystemClock.elapsedRealtime();\n            Toast.makeText(this, \"Click again to close the app\", Toast.LENGTH_LONG).show();\n            return true;\n        }\n\n        lastBackClick = SystemClock.elapsedRealtime();\n        return super.onKeyDown(keyCode, event);\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onNewIntent\n    //\n\n    public interface NewIntentListener {\n        void onNewIntent(Intent intent);\n    }\n\n    private List<NewIntentListener> newIntentListeners = null;\n\n    public void registerNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null)\n            this.newIntentListeners =\n                    Collections.synchronizedList(new ArrayList<NewIntentListener>());\n        this.newIntentListeners.add(listener);\n    }\n\n    public void unregisterNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null) return;\n        this.newIntentListeners.remove(listener);\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        if (this.newIntentListeners == null) return;\n        this.onResume();\n        synchronized (this.newIntentListeners) {\n            Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();\n            while (iterator.hasNext()) {\n                (iterator.next()).onNewIntent(intent);\n            }\n        }\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onActivityResult\n    //\n\n    public interface ActivityResultListener {\n        void onActivityResult(int requestCode, int resultCode, Intent data);\n    }\n\n    private List<ActivityResultListener> activityResultListeners = null;\n\n    public void registerActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null)\n            this.activityResultListeners =\n                    Collections.synchronizedList(new ArrayList<ActivityResultListener>());\n        this.activityResultListeners.add(listener);\n    }\n\n    public void unregisterActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null) return;\n        this.activityResultListeners.remove(listener);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {\n        if (this.activityResultListeners == null) return;\n        this.onResume();\n        synchronized (this.activityResultListeners) {\n            Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();\n            while (iterator.hasNext())\n                (iterator.next()).onActivityResult(requestCode, resultCode, intent);\n        }\n    }\n\n    public static void start_service(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, true);\n    }\n\n    public static void start_service_not_as_foreground(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, false);\n    }\n\n    public static void _do_start_service(\n            String serviceTitle,\n            String serviceDescription,\n            String pythonServiceArgument,\n            boolean showForegroundNotification) {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();\n        String app_root_dir = PythonActivity.mActivity.getAppRoot();\n        String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + \"/service\");\n        serviceIntent.putExtra(\"androidPrivate\", argument);\n        serviceIntent.putExtra(\"androidArgument\", app_root_dir);\n        serviceIntent.putExtra(\"serviceEntrypoint\", \"service/\" + entry_point);\n        serviceIntent.putExtra(\"pythonName\", \"python\");\n        serviceIntent.putExtra(\"pythonHome\", app_root_dir);\n        serviceIntent.putExtra(\"pythonPath\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n        serviceIntent.putExtra(\n                \"serviceStartAsForeground\", (showForegroundNotification ? \"true\" : \"false\"));\n        serviceIntent.putExtra(\"serviceTitle\", serviceTitle);\n        serviceIntent.putExtra(\"serviceDescription\", serviceDescription);\n        serviceIntent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        PythonActivity.mActivity.startService(serviceIntent);\n    }\n\n    public static void stop_service() {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        PythonActivity.mActivity.stopService(serviceIntent);\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/src/main/jniLibs/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/src/main/libs/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/src/main/res/drawable/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/src/main/res/mipmap/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/templates/AndroidManifest.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      android:versionCode=\"{{ args.numeric_version }}\"\n      android:versionName=\"{{ args.version }}\"\n      android:installLocation=\"auto\">\n\n    <supports-screens\n            android:smallScreens=\"true\"\n            android:normalScreens=\"true\"\n            android:largeScreens=\"true\"\n            android:anyDensity=\"true\"\n            {% if args.min_sdk_version >= 9 %}\n            android:xlargeScreens=\"true\"\n            {% endif %}\n    />\n\n    <uses-sdk android:minSdkVersion=\"{{ args.min_sdk_version }}\" android:targetSdkVersion=\"{{ android_api }}\" />\n    <uses-feature android:glEsVersion=\"0x00020000\" />\n\n    <!-- Set permissions -->\n    {% for perm in args.permissions %}\n        <uses-permission android:name=\"{{ perm.name }}\"{% if perm.maxSdkVersion %} android:maxSdkVersion=\"{{ perm.maxSdkVersion }}\"{% endif %}{% if perm.usesPermissionFlags %} android:usesPermissionFlags=\"{{ perm.usesPermissionFlags }}\"{% endif %} />\n    {% endfor %}\n\n    {% if args.wakelock %}\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    {% endif %}\n\n    {% if args.billing_pubkey %}\n    <uses-permission android:name=\"com.android.vending.BILLING\" />\n    {% endif %}\n\n    {{ args.extra_manifest_xml }}\n\n    <application android:name=\"org.qtproject.qt.android.bindings.QtApplication\"\n                 android:label=\"@string/app_name\"\n                 {% if debug %}android:debuggable=\"true\"{% endif %}\n                 android:icon=\"@mipmap/icon\"\n                 android:allowBackup=\"{{ args.allow_backup }}\"\n                 android:fullBackupOnly=\"false\"\n                 {% if args.backup_rules %}android:fullBackupContent=\"@xml/{{ args.backup_rules }}\"{% endif %}\n                 {{ args.extra_manifest_application_arguments }}\n                 android:theme=\"{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}\"\n                 android:hardwareAccelerated=\"true\"\n                 >\n                <!--\n                 android:extractNativeLibs=\"true\" = needed for smaller apk size\n                 android:requestLegacyExternalStorage=\"true\"\n                 android:allowNativeHeapPointerTagging=\"false\"\n                -->\n        {% for l in args.android_used_libs %}\n        <uses-library android:name=\"{{ l }}\" />\n        {% endfor %}\n\n        {% for m in args.meta_data %}\n        <meta-data android:name=\"{{ m.split('=', 1)[0] }}\" android:value=\"{{ m.split('=', 1)[-1] }}\"/>{% endfor %}\n        <meta-data android:name=\"wakelock\" android:value=\"{% if args.wakelock %}1{% else %}0{% endif %}\"/>\n\n        <activity android:name=\"{{args.android_entrypoint}}\"\n                  android:label=\"@string/app_name\"\n                  android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|uiMode{% if args.min_sdk_version >= 8 %}|uiMode{% endif %}{% if args.min_sdk_version >= 13 %}|screenSize|smallestScreenSize{% endif %}{% if args.min_sdk_version >= 17 %}|layoutDirection{% endif %}{% if args.min_sdk_version >= 24 %}|density{% endif %}\"\n                  android:screenOrientation=\"{{ args.manifest_orientation }}\"\n                  android:exported=\"true\"\n                  android:theme=\"@style/KivySupportCutout\"\n                  {% if args.activity_launch_mode %}\n                  android:launchMode=\"{{ args.activity_launch_mode }}\"\n                  {% endif %}\n                  >\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n            {%- if args.intent_filters -%}\n            {{- args.intent_filters -}}\n            {%- endif -%}\n\n            <!-- ToDo: Need more meta-data. Adapt accordingly -->\n            <!-- https://doc.qt.io/qt-6/android-manifest-file-configuration.html#qt-specific-meta-data -->\n            <meta-data\n                    android:name=\"android.app.lib_name\"\n                    android:value=\"main\"/>\n\n            <meta-data\n                    android:name=\"android.app.extract_android_style\"\n                    android:value=\"minimal\" />\n        </activity>\n\n        {% if service or args.launcher %}\n        <service android:name=\"{{ args.service_class_name }}\"\n                 android:process=\":pythonservice\" />\n        {% endif %}\n        {% for name, foreground_type in service_data %}\n        <service android:name=\"{{ args.package }}.Service{{ name|capitalize }}\"\n                 {% if foreground_type %}\n                 android:foregroundServiceType=\"{{ foreground_type }}\"\n                 {% endif %}\n                 android:process=\":service_{{ name }}\" />\n        {% endfor %}\n        {% for name in native_services %}\n        <service android:name=\"{{ name }}\" />\n        {% endfor %}\n\n    {% for a in args.add_activity  %}\n    <activity android:name=\"{{ a }}\"></activity>\n    {% endfor %}\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/templates/libs.tmpl.xml",
    "content": "<?xml version='1.0' encoding='utf-8'?>\n<resources>\n\n    <!--\n    The bunlded_libs placeholder is needed for QtLoader.java. Otherwise the application will crash.\n    Adding extra libraries can be done through buildozer directly with android.add_libs_* option\n    -->\n    <array name=\"bundled_libs\">\n    </array>\n\n    <array name=\"qt_libs\">\n        <item>{{ arch }};c++_shared</item>\n        {%- for qt_lib in qt_libs %}\n        <item>{{ arch }};Qt6{{ qt_lib }}_{{ arch }}</item>\n        {%- endfor -%}\n    </array>\n\n    <array name=\"load_local_libs\">\n        {%- for load_local_lib in load_local_libs %}\n        <item>{{ arch }};lib{{ load_local_lib }}_{{ arch }}.so</item>\n        {%- endfor -%}\n        <item>{{ arch }};libshiboken6.abi3.so</item>\n        <item>{{ arch }};libpyside6.abi3.so</item>\n        {%- for qt_lib in qt_libs %}\n        <item>{{ arch }};Qt{{ qt_lib }}.abi3.so</item>\n        {% if qt_lib == \"Qml\" -%}\n        <item>{{ arch }};libpyside6qml.abi3.so</item>\n        {% endif %}\n        {%- endfor -%}\n    </array>\n\n    <string name=\"static_init_classes\">{{ init_classes }}</string>\n    <string name=\"use_local_qt_libs\">1</string>\n    <string name=\"bundle_local_qt_libs\">1</string>\n</resources>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/qt/build/templates/strings.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"KivySupportCutout\">\n        <item name=\"android:windowNoTitle\">true</item>\n        <!-- Display cutout is an area on some devices that extends into the display surface -->\n        {% if args.display_cutout != 'never'%}\n        <item name=\"android:windowLayoutInDisplayCutoutMode\">{{ args.display_cutout }}</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">true</item>\n        <item name=\"android:windowFullscreen\">true</item>\n        {% endif %}\n    </style>\n    <string name=\"app_name\">{{ args.name }}</string>\n    <string name=\"private_version\">{{ private_version }}</string>\n    <string name=\"presplash_color\">{{ args.presplash_color }}</string>\n</resources>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl2/__init__.py",
    "content": "from pythonforandroid.bootstraps._sdl_common import SDLGradleBootstrap\n\n\nclass SDL2GradleBootstrap(SDLGradleBootstrap):\n    name = \"sdl2\"\n\n    recipe_depends = list(\n        set(SDLGradleBootstrap.recipe_depends).union({\"sdl2\"})\n    )\n\n\nbootstrap = SDL2GradleBootstrap()\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\nSDL_PATH := ../../SDL\n\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include\n\n# Add your application source files here...\nLOCAL_SRC_FILES := start.c\n\nLOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)\n\nLOCAL_SHARED_LIBRARIES := SDL2 python_shared\n\nLOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS)\n\nLOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android_static.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\nLOCAL_SRC_FILES := start.c\n\nLOCAL_STATIC_LIBRARIES := SDL2_static\n\ninclude $(BUILD_SHARED_LIBRARY)\n$(call import-module,SDL)LOCAL_PATH := $(call my-dir)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl2/build/jni/application/src/bootstrap_name.h",
    "content": "\n#define BOOTSTRAP_NAME_SDL2\n\nconst char bootstrap_name[] = \"SDL2\";  // capitalized for historic reasons\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl2/build/src/main/java/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java",
    "content": "package org.kivy.android;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources.NotFoundException;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Color;\nimport android.graphics.PixelFormat;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.PowerManager;\nimport android.util.Log;\nimport android.view.SurfaceView;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.ImageView;\nimport android.widget.Toast;\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Timer;\nimport java.util.TimerTask;\nimport org.kivy.android.launcher.Project;\nimport org.libsdl.app.SDLActivity;\nimport org.renpy.android.ResourceManager;\n\npublic class PythonActivity extends SDLActivity {\n    private static final String TAG = \"PythonActivity\";\n\n    public static PythonActivity mActivity = null;\n\n    private ResourceManager resourceManager = null;\n    private Bundle mMetaData = null;\n    private PowerManager.WakeLock mWakeLock = null;\n\n    public String getAppRoot() {\n        String app_root = getFilesDir().getAbsolutePath() + \"/app\";\n        return app_root;\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.v(TAG, \"PythonActivity onCreate running\");\n        resourceManager = new ResourceManager(this);\n\n        Log.v(TAG, \"About to do super onCreate\");\n        super.onCreate(savedInstanceState);\n        Log.v(TAG, \"Did super onCreate\");\n\n        this.mActivity = this;\n        this.showLoadingScreen(this.getLoadingScreen());\n\n        new UnpackFilesTask().execute(getAppRoot());\n    }\n\n    public void loadLibraries() {\n        String app_root = new String(getAppRoot());\n        File app_root_file = new File(app_root);\n        PythonUtil.loadLibraries(app_root_file, new File(getApplicationInfo().nativeLibraryDir));\n    }\n\n    /** Show an error using a toast. (Only makes sense from non-UI threads.) */\n    public void toastError(final String msg) {\n\n        final Activity thisActivity = this;\n\n        runOnUiThread(\n                new Runnable() {\n                    public void run() {\n                        Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show();\n                    }\n                });\n\n        // Wait to show the error.\n        synchronized (this) {\n            try {\n                this.wait(1000);\n            } catch (InterruptedException e) {\n            }\n        }\n    }\n\n    private class UnpackFilesTask extends AsyncTask<String, Void, String> {\n        @Override\n        protected String doInBackground(String... params) {\n            File app_root_file = new File(params[0]);\n            Log.v(TAG, \"Ready to unpack\");\n            PythonUtil.unpackAsset(mActivity, \"private\", app_root_file, true);\n            PythonUtil.unpackPyBundle(\n                    mActivity,\n                    getApplicationInfo().nativeLibraryDir + \"/\" + \"libpybundle\",\n                    app_root_file,\n                    false);\n            return null;\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            // Figure out the directory where the game is. If the game was\n            // given to us via an intent, then we use the scheme-specific\n            // part of that intent to determine the file to launch. We\n            // also use the android.txt file to determine the orientation.\n            //\n            // Otherwise, we use the public data, if we have it, or the\n            // private data if we do not.\n            mActivity.finishLoad();\n\n            // finishLoad called setContentView with the SDL view, which\n            // removed the loading screen. However, we still need it to\n            // show until the app is ready to render, so pop it back up\n            // on top of the SDL view.\n            mActivity.showLoadingScreen(getLoadingScreen());\n\n            String app_root_dir = getAppRoot();\n            if (getIntent() != null\n                    && getIntent().getAction() != null\n                    && getIntent().getAction().equals(\"org.kivy.LAUNCH\")) {\n                File path = new File(getIntent().getData().getSchemeSpecificPart());\n\n                Project p = Project.scanDirectory(path);\n                String entry_point = getEntryPoint(p.dir);\n                SDLActivity.nativeSetenv(\"ANDROID_ENTRYPOINT\", p.dir + \"/\" + entry_point);\n                SDLActivity.nativeSetenv(\"ANDROID_ARGUMENT\", p.dir);\n                SDLActivity.nativeSetenv(\"ANDROID_APP_PATH\", p.dir);\n\n                if (p != null) {\n                    if (p.landscape) {\n                        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n                    } else {\n                        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n                    }\n                }\n\n                // Let old apps know they started.\n                try {\n                    FileWriter f = new FileWriter(new File(path, \".launch\"));\n                    f.write(\"started\");\n                    f.close();\n                } catch (IOException e) {\n                    // pass\n                }\n            } else {\n                String entry_point = getEntryPoint(app_root_dir);\n                SDLActivity.nativeSetenv(\"ANDROID_ENTRYPOINT\", entry_point);\n                SDLActivity.nativeSetenv(\"ANDROID_ARGUMENT\", app_root_dir);\n                SDLActivity.nativeSetenv(\"ANDROID_APP_PATH\", app_root_dir);\n            }\n\n            String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();\n            Log.v(TAG, \"Setting env vars for start.c and Python to use\");\n            SDLActivity.nativeSetenv(\"ANDROID_PRIVATE\", mFilesDirectory);\n            SDLActivity.nativeSetenv(\"ANDROID_UNPACK\", app_root_dir);\n            SDLActivity.nativeSetenv(\"PYTHONHOME\", app_root_dir);\n            SDLActivity.nativeSetenv(\"PYTHONPATH\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n            SDLActivity.nativeSetenv(\"PYTHONOPTIMIZE\", \"2\");\n\n            try {\n                Log.v(TAG, \"Access to our meta-data...\");\n                mActivity.mMetaData =\n                        mActivity\n                                .getPackageManager()\n                                .getApplicationInfo(\n                                        mActivity.getPackageName(), PackageManager.GET_META_DATA)\n                                .metaData;\n\n                PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);\n                if (mActivity.mMetaData.getInt(\"wakelock\") == 1) {\n                    mActivity.mWakeLock =\n                            pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, \"Screen On\");\n                    mActivity.mWakeLock.acquire();\n                }\n                if (mActivity.mMetaData.getInt(\"surface.transparent\") != 0) {\n                    Log.v(TAG, \"Surface will be transparent.\");\n                    getSurface().setZOrderOnTop(true);\n                    getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT);\n                } else {\n                    Log.i(TAG, \"Surface will NOT be transparent\");\n                }\n            } catch (PackageManager.NameNotFoundException e) {\n            }\n\n            // Launch app if that hasn't been done yet:\n            if (mActivity.mHasFocus\n                    && (\n                    // never went into proper resume state:\n                    mActivity.mCurrentNativeState == NativeState.INIT\n                            || (\n                            // resumed earlier but wasn't ready yet\n                            mActivity.mCurrentNativeState == NativeState.RESUMED\n                                    && mActivity.mSDLThread == null))) {\n                // Because sometimes the app will get stuck here and never\n                // actually run, ensure that it gets launched if we're active:\n                mActivity.resumeNativeThread();\n            }\n        }\n\n        @Override\n        protected void onPreExecute() {}\n\n        @Override\n        protected void onProgressUpdate(Void... values) {}\n    }\n\n    public static ViewGroup getLayout() {\n        return mLayout;\n    }\n\n    public static SurfaceView getSurface() {\n        return mSurface;\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onNewIntent\n    //\n\n    public interface NewIntentListener {\n        void onNewIntent(Intent intent);\n    }\n\n    private List<NewIntentListener> newIntentListeners = null;\n\n    public void registerNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null)\n            this.newIntentListeners =\n                    Collections.synchronizedList(new ArrayList<NewIntentListener>());\n        this.newIntentListeners.add(listener);\n    }\n\n    public void unregisterNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null) return;\n        this.newIntentListeners.remove(listener);\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        if (this.newIntentListeners == null) return;\n        this.onResume();\n        synchronized (this.newIntentListeners) {\n            Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();\n            while (iterator.hasNext()) {\n                (iterator.next()).onNewIntent(intent);\n            }\n        }\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onActivityResult\n    //\n\n    public interface ActivityResultListener {\n        void onActivityResult(int requestCode, int resultCode, Intent data);\n    }\n\n    private List<ActivityResultListener> activityResultListeners = null;\n\n    public void registerActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null)\n            this.activityResultListeners =\n                    Collections.synchronizedList(new ArrayList<ActivityResultListener>());\n        this.activityResultListeners.add(listener);\n    }\n\n    public void unregisterActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null) return;\n        this.activityResultListeners.remove(listener);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {\n        if (this.activityResultListeners == null) return;\n        this.onResume();\n        synchronized (this.activityResultListeners) {\n            Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();\n            while (iterator.hasNext())\n                (iterator.next()).onActivityResult(requestCode, resultCode, intent);\n        }\n    }\n\n    public static void start_service(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, true);\n    }\n\n    public static void start_service_not_as_foreground(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, false);\n    }\n\n    public static void _do_start_service(\n            String serviceTitle,\n            String serviceDescription,\n            String pythonServiceArgument,\n            boolean showForegroundNotification) {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();\n        String app_root_dir = PythonActivity.mActivity.getAppRoot();\n        String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + \"/service\");\n        serviceIntent.putExtra(\"androidPrivate\", argument);\n        serviceIntent.putExtra(\"androidArgument\", app_root_dir);\n        serviceIntent.putExtra(\"serviceEntrypoint\", \"service/\" + entry_point);\n        serviceIntent.putExtra(\"pythonName\", \"python\");\n        serviceIntent.putExtra(\"pythonHome\", app_root_dir);\n        serviceIntent.putExtra(\"pythonPath\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n        serviceIntent.putExtra(\n                \"serviceStartAsForeground\", (showForegroundNotification ? \"true\" : \"false\"));\n        serviceIntent.putExtra(\"serviceTitle\", serviceTitle);\n        serviceIntent.putExtra(\"serviceDescription\", serviceDescription);\n        serviceIntent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        PythonActivity.mActivity.startService(serviceIntent);\n    }\n\n    public static void stop_service() {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        PythonActivity.mActivity.stopService(serviceIntent);\n    }\n\n    /** Loading screen view * */\n    public static ImageView mImageView = null;\n\n    public static View mLottieView = null;\n\n    /** Whether main routine/actual app has started yet * */\n    protected boolean mAppConfirmedActive = false;\n\n    /** Timer for delayed loading screen removal. * */\n    protected Timer loadingScreenRemovalTimer = null;\n\n    // Overridden since it's called often, to check whether to remove the\n    // loading screen:\n    @Override\n    protected boolean sendCommand(int command, Object data) {\n        boolean result = super.sendCommand(command, data);\n        considerLoadingScreenRemoval();\n        return result;\n    }\n\n    /** Confirm that the app's main routine has been launched. */\n    @Override\n    public void appConfirmedActive() {\n        if (!mAppConfirmedActive) {\n            Log.v(TAG, \"appConfirmedActive() -> preparing loading screen removal\");\n            mAppConfirmedActive = true;\n            considerLoadingScreenRemoval();\n        }\n    }\n\n    /**\n     * This is called from various places to check whether the app's main routine has been launched\n     * already, and if it has, then the loading screen will be removed.\n     */\n    public void considerLoadingScreenRemoval() {\n        if (loadingScreenRemovalTimer != null) return;\n        runOnUiThread(\n                new Runnable() {\n                    public void run() {\n                        if (((PythonActivity) PythonActivity.mSingleton).mAppConfirmedActive\n                                && loadingScreenRemovalTimer == null) {\n                            // Remove loading screen but with a delay.\n                            // (app can use p4a's android.loadingscreen module to\n                            // do it quicker if it wants to)\n                            // get a handler (call from main thread)\n                            // this will run when timer elapses\n                            TimerTask removalTask =\n                                    new TimerTask() {\n                                        @Override\n                                        public void run() {\n                                            // post a runnable to the handler\n                                            runOnUiThread(\n                                                    new Runnable() {\n                                                        @Override\n                                                        public void run() {\n                                                            PythonActivity activity =\n                                                                    ((PythonActivity)\n                                                                            PythonActivity\n                                                                                    .mSingleton);\n                                                            if (activity != null)\n                                                                activity.removeLoadingScreen();\n                                                        }\n                                                    });\n                                        }\n                                    };\n                            loadingScreenRemovalTimer = new Timer();\n                            loadingScreenRemovalTimer.schedule(removalTask, 5000);\n                        }\n                    }\n                });\n    }\n\n    public void removeLoadingScreen() {\n        runOnUiThread(\n                new Runnable() {\n                    public void run() {\n                        View view = mLottieView != null ? mLottieView : mImageView;\n                        if (view != null && view.getParent() != null) {\n                            ((ViewGroup) view.getParent()).removeView(view);\n                            mLottieView = null;\n                            mImageView = null;\n                        }\n                    }\n                });\n    }\n\n    public String getEntryPoint(String search_dir) {\n        /* Get the main file (.pyc|.py) depending on if we\n         * have a compiled version or not.\n         */\n        List<String> entryPoints = new ArrayList<String>();\n        entryPoints.add(\"main.pyc\"); // python 3 compiled files\n        for (String value : entryPoints) {\n            File mainFile = new File(search_dir + \"/\" + value);\n            if (mainFile.exists()) {\n                return value;\n            }\n        }\n        return \"main.py\";\n    }\n\n    protected void showLoadingScreen(View view) {\n        try {\n            if (mLayout == null) {\n                setContentView(view);\n            } else if (view.getParent() == null) {\n                mLayout.addView(view);\n            }\n        } catch (IllegalStateException e) {\n            // The loading screen can be attempted to be applied twice if app\n            // is tabbed in/out, quickly.\n            // (Gives error \"The specified child already has a parent.\n            // You must call removeView() on the child's parent first.\")\n        }\n    }\n\n    protected void setBackgroundColor(View view) {\n        /*\n         * Set the presplash loading screen background color\n         * https://developer.android.com/reference/android/graphics/Color.html\n         * Parse the color string, and return the corresponding color-int.\n         * If the string cannot be parsed, throws an IllegalArgumentException exception.\n         * Supported formats are: #RRGGBB #AARRGGBB or one of the following names:\n         * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',\n         * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',\n         * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.\n         */\n        String backgroundColor = resourceManager.getString(\"presplash_color\");\n        if (backgroundColor != null) {\n            try {\n                view.setBackgroundColor(Color.parseColor(backgroundColor));\n            } catch (IllegalArgumentException e) {\n            }\n        }\n    }\n\n    protected View getLoadingScreen() {\n        // If we have an mLottieView or mImageView already, then do\n        // nothing because it will have already been made the content\n        // view or added to the layout.\n        if (mLottieView != null || mImageView != null) {\n            // we already have a splash screen\n            return mLottieView != null ? mLottieView : mImageView;\n        }\n\n        // first try to load the lottie one\n        try {\n            mLottieView =\n                    getLayoutInflater()\n                            .inflate(\n                                    this.resourceManager.getIdentifier(\"lottie\", \"layout\"),\n                                    mLayout,\n                                    false);\n            try {\n                if (mLayout == null) {\n                    setContentView(mLottieView);\n                } else if (PythonActivity.mLottieView.getParent() == null) {\n                    mLayout.addView(mLottieView);\n                }\n            } catch (IllegalStateException e) {\n                // The loading screen can be attempted to be applied twice if app\n                // is tabbed in/out, quickly.\n                // (Gives error \"The specified child already has a parent.\n                // You must call removeView() on the child's parent first.\")\n            }\n            setBackgroundColor(mLottieView);\n            return mLottieView;\n        } catch (NotFoundException e) {\n            Log.v(\"SDL\", \"couldn't find lottie layout or animation, trying static splash\");\n        }\n\n        // no lottie asset, try to load the static image then\n        int presplashId = this.resourceManager.getIdentifier(\"presplash\", \"drawable\");\n        InputStream is = this.getResources().openRawResource(presplashId);\n        Bitmap bitmap = null;\n        try {\n            bitmap = BitmapFactory.decodeStream(is);\n        } finally {\n            try {\n                is.close();\n            } catch (IOException e) {\n            }\n            ;\n        }\n\n        mImageView = new ImageView(this);\n        mImageView.setImageBitmap(bitmap);\n        setBackgroundColor(mImageView);\n\n        mImageView.setLayoutParams(\n                new ViewGroup.LayoutParams(\n                        ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));\n        mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);\n        return mImageView;\n    }\n\n    @Override\n    protected void onPause() {\n        if (this.mWakeLock != null && mWakeLock.isHeld()) {\n            this.mWakeLock.release();\n        }\n\n        Log.v(TAG, \"onPause()\");\n        try {\n            super.onPause();\n        } catch (UnsatisfiedLinkError e) {\n            // Catch pause while still in loading screen failing to\n            // call native function (since it's not yet loaded)\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        if (this.mWakeLock != null) {\n            this.mWakeLock.acquire();\n        }\n        Log.v(TAG, \"onResume()\");\n        try {\n            super.onResume();\n        } catch (UnsatisfiedLinkError e) {\n            // Catch resume while still in loading screen failing to\n            // call native function (since it's not yet loaded)\n        }\n        considerLoadingScreenRemoval();\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        try {\n            super.onWindowFocusChanged(hasFocus);\n        } catch (UnsatisfiedLinkError e) {\n            // Catch window focus while still in loading screen failing to\n            // call native function (since it's not yet loaded)\n        }\n        considerLoadingScreenRemoval();\n    }\n\n    /**\n     * Used by android.permissions p4a module to register a call back after requesting runtime\n     * permissions\n     */\n    public interface PermissionsCallback {\n        void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);\n    }\n\n    private PermissionsCallback permissionCallback;\n    private boolean havePermissionsCallback = false;\n\n    public void addPermissionsCallback(PermissionsCallback callback) {\n        permissionCallback = callback;\n        havePermissionsCallback = true;\n        Log.v(TAG, \"addPermissionsCallback(): Added callback for onRequestPermissionsResult\");\n    }\n\n    @Override\n    public void onRequestPermissionsResult(\n            int requestCode, String[] permissions, int[] grantResults) {\n        Log.v(TAG, \"onRequestPermissionsResult()\");\n        if (havePermissionsCallback) {\n            Log.v(TAG, \"onRequestPermissionsResult passed to callback\");\n            permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        }\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n\n    /** Used by android.permissions p4a module to check a permission */\n    public boolean checkCurrentPermission(String permission) {\n        if (android.os.Build.VERSION.SDK_INT < 23) return true;\n\n        try {\n            java.lang.reflect.Method methodCheckPermission =\n                    Activity.class.getMethod(\"checkSelfPermission\", String.class);\n            Object resultObj = methodCheckPermission.invoke(this, permission);\n            int result = Integer.parseInt(resultObj.toString());\n            if (result == PackageManager.PERMISSION_GRANTED) return true;\n        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {\n        }\n        return false;\n    }\n\n    /** Used by android.permissions p4a module to request runtime permissions */\n    public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) {\n        if (android.os.Build.VERSION.SDK_INT < 23) return;\n        try {\n            java.lang.reflect.Method methodRequestPermission =\n                    Activity.class.getMethod(\"requestPermissions\", String[].class, int.class);\n            methodRequestPermission.invoke(this, permissions, requestCode);\n        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {\n        }\n    }\n\n    public void requestPermissions(String[] permissions) {\n        requestPermissionsWithRequestCode(permissions, 1);\n    }\n\n    public static void changeKeyboard(int inputType) {\n        if (SDLActivity.keyboardInputType != inputType) {\n            SDLActivity.keyboardInputType = inputType;\n            InputMethodManager imm =\n                    (InputMethodManager)\n                            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n            imm.restartInput(mTextEdit);\n        }\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch",
    "content": "--- a/src/main/java/org/libsdl/app/SDLActivity.java\n+++ b/src/main/java/org/libsdl/app/SDLActivity.java\n@@ -221,6 +221,8 @@\n \n     // This is what SDL runs in. It invokes SDL_main(), eventually\n     protected static Thread mSDLThread;\n+\n+    public static int keyboardInputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;\n \n     protected static SDLGenericMotionListener_API12 getMotionListener() {\n         if (mMotionListener == null) {\n@@ -323,6 +325,15 @@\n         Log.v(TAG, \"Model: \" + Build.MODEL);\n         Log.v(TAG, \"onCreate()\");\n         super.onCreate(savedInstanceState);\n+\n+        SDLActivity.initialize();\n+        // So we can call stuff from static callbacks\n+        mSingleton = this;\n+    }\n+\n+    // We don't do this in onCreate because we unpack and load the app data on a thread\n+    // and we can't run setup tasks until that thread completes.\n+    protected void finishLoad() {\n \n         try {\n             Thread.currentThread().setName(\"SDLActivity\");\n@@ -837,7 +848,7 @@\n     Handler commandHandler = new SDLCommandHandler();\n \n     // Send a message from the SDLMain thread\n-    boolean sendCommand(int command, Object data) {\n+    protected boolean sendCommand(int command, Object data) {\n         Message msg = commandHandler.obtainMessage();\n         msg.arg1 = command;\n         msg.obj = data;\n@@ -1385,7 +1396,22 @@\n             return null;\n         }\n         return SDLActivity.mSurface.getNativeSurface();\n+    }\n+\n+    /**\n+    * Calls turnActive() on singleton to keep loading screen active\n+    */\n+    public static void triggerAppConfirmedActive() {\n+        mSingleton.appConfirmedActive();\n     }\n+  \n+    /**\n+     * Trick needed for loading screen, overridden by PythonActivity\n+     * to keep loading screen active\n+     */\n+    public void appConfirmedActive() {\n+    }\n+ \n \n     // Input\n \n@@ -1881,6 +1907,7 @@\n \n         Log.v(\"SDL\", \"Running main function \" + function + \" from library \" + library);\n \n+        SDLActivity.mSingleton.appConfirmedActive();\n         SDLActivity.nativeRunMain(library, function, arguments);\n \n         Log.v(\"SDL\", \"Finished main function\");\n@@ -1938,8 +1965,7 @@\n     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {\n         ic = new SDLInputConnection(this, true);\n \n-        outAttrs.inputType = InputType.TYPE_CLASS_TEXT |\n-                             InputType.TYPE_TEXT_FLAG_MULTI_LINE;\n+        outAttrs.inputType = SDLActivity.keyboardInputType;\n         outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |\n                               EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;\n \n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl2/build/src/patches/SDLSurface.java.patch",
    "content": "--- a/src/main/java/org/libsdl/app/SDLSurface.java\n+++ b/src/main/java/org/libsdl/app/SDLSurface.java\n@@ -193,9 +193,22 @@\n         return SDLActivity.handleKeyEvent(v, keyCode, event, null);\n     }\n \n+    public interface OnInterceptTouchListener {\n+        boolean onTouch(MotionEvent ev);\n+    }\n+\n+    private OnInterceptTouchListener mOnInterceptTouchListener = null;\n+\n+    public void setInterceptTouchListener(OnInterceptTouchListener listener) {\n+        this.mOnInterceptTouchListener = listener;\n+    }\n+\n     // Touch events\n     @Override\n     public boolean onTouch(View v, MotionEvent event) {\n+        if (mOnInterceptTouchListener != null)\n+            if (mOnInterceptTouchListener.onTouch(event))\n+                return false;\n         /* Ref: http://developer.android.com/training/gestures/multi.html */\n         int touchDevId = event.getDeviceId();\n         final int pointerCount = event.getPointerCount();\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl3/__init__.py",
    "content": "from pythonforandroid.bootstraps._sdl_common import SDLGradleBootstrap\n\n\nclass SDL3GradleBootstrap(SDLGradleBootstrap):\n    name = \"sdl3\"\n\n    recipe_depends = list(\n        set(SDLGradleBootstrap.recipe_depends).union({\"sdl3\"})\n    )\n\n\nbootstrap = SDL3GradleBootstrap()\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\nSDL_PATH := ../../SDL\n\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include\n\n# Add your application source files here...\nLOCAL_SRC_FILES := start.c\n\nLOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)\n\nLOCAL_SHARED_LIBRARIES := SDL3 python_shared\n\nLOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS)\n\nLOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android_static.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\nLOCAL_SRC_FILES := start.c\n\nLOCAL_STATIC_LIBRARIES := SDL3_static\n\n\ninclude $(BUILD_SHARED_LIBRARY)\n$(call import-module,SDL)LOCAL_PATH := $(call my-dir)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl3/build/jni/application/src/bootstrap_name.h",
    "content": "\n#define BOOTSTRAP_NAME_SDL3\n\nconst char bootstrap_name[] = \"SDL3\";  // capitalized for historic reasons\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java",
    "content": "package org.kivy.android;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources.NotFoundException;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Color;\nimport android.graphics.PixelFormat;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.PowerManager;\nimport android.util.Log;\nimport android.view.SurfaceView;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.Toast;\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Timer;\nimport java.util.TimerTask;\nimport org.kivy.android.launcher.Project;\nimport org.libsdl.app.SDLActivity;\nimport org.renpy.android.ResourceManager;\n\npublic class PythonActivity extends SDLActivity {\n    private static final String TAG = \"PythonActivity\";\n\n    public static PythonActivity mActivity = null;\n\n    private ResourceManager resourceManager = null;\n    private Bundle mMetaData = null;\n    private PowerManager.WakeLock mWakeLock = null;\n\n    public String getAppRoot() {\n        String app_root = getFilesDir().getAbsolutePath() + \"/app\";\n        return app_root;\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.v(TAG, \"PythonActivity onCreate running\");\n        resourceManager = new ResourceManager(this);\n\n        Log.v(TAG, \"About to do super onCreate\");\n        super.onCreate(savedInstanceState);\n        Log.v(TAG, \"Did super onCreate\");\n\n        this.mActivity = this;\n        this.showLoadingScreen(this.getLoadingScreen());\n\n        new UnpackFilesTask().execute(getAppRoot());\n    }\n\n    public void loadLibraries() {\n        String app_root = new String(getAppRoot());\n        File app_root_file = new File(app_root);\n        PythonUtil.loadLibraries(app_root_file, new File(getApplicationInfo().nativeLibraryDir));\n    }\n\n    /** Show an error using a toast. (Only makes sense from non-UI threads.) */\n    public void toastError(final String msg) {\n\n        final Activity thisActivity = this;\n\n        runOnUiThread(\n                new Runnable() {\n                    public void run() {\n                        Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show();\n                    }\n                });\n\n        // Wait to show the error.\n        synchronized (this) {\n            try {\n                this.wait(1000);\n            } catch (InterruptedException e) {\n            }\n        }\n    }\n\n    private class UnpackFilesTask extends AsyncTask<String, Void, String> {\n        @Override\n        protected String doInBackground(String... params) {\n            File app_root_file = new File(params[0]);\n            Log.v(TAG, \"Ready to unpack\");\n            PythonUtil.unpackAsset(mActivity, \"private\", app_root_file, true);\n            PythonUtil.unpackPyBundle(\n                    mActivity,\n                    getApplicationInfo().nativeLibraryDir + \"/\" + \"libpybundle\",\n                    app_root_file,\n                    false);\n            return null;\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            // Figure out the directory where the game is. If the game was\n            // given to us via an intent, then we use the scheme-specific\n            // part of that intent to determine the file to launch. We\n            // also use the android.txt file to determine the orientation.\n            //\n            // Otherwise, we use the public data, if we have it, or the\n            // private data if we do not.\n            mActivity.finishLoad();\n\n            // finishLoad called setContentView with the SDL view, which\n            // removed the loading screen. However, we still need it to\n            // show until the app is ready to render, so pop it back up\n            // on top of the SDL view.\n            mActivity.showLoadingScreen(getLoadingScreen());\n\n            String app_root_dir = getAppRoot();\n            if (getIntent() != null\n                    && getIntent().getAction() != null\n                    && getIntent().getAction().equals(\"org.kivy.LAUNCH\")) {\n                File path = new File(getIntent().getData().getSchemeSpecificPart());\n\n                Project p = Project.scanDirectory(path);\n                String entry_point = getEntryPoint(p.dir);\n                SDLActivity.nativeSetenv(\"ANDROID_ENTRYPOINT\", p.dir + \"/\" + entry_point);\n                SDLActivity.nativeSetenv(\"ANDROID_ARGUMENT\", p.dir);\n                SDLActivity.nativeSetenv(\"ANDROID_APP_PATH\", p.dir);\n\n                if (p != null) {\n                    if (p.landscape) {\n                        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n                    } else {\n                        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n                    }\n                }\n\n                // Let old apps know they started.\n                try {\n                    FileWriter f = new FileWriter(new File(path, \".launch\"));\n                    f.write(\"started\");\n                    f.close();\n                } catch (IOException e) {\n                    // pass\n                }\n            } else {\n                String entry_point = getEntryPoint(app_root_dir);\n                SDLActivity.nativeSetenv(\"ANDROID_ENTRYPOINT\", entry_point);\n                SDLActivity.nativeSetenv(\"ANDROID_ARGUMENT\", app_root_dir);\n                SDLActivity.nativeSetenv(\"ANDROID_APP_PATH\", app_root_dir);\n            }\n\n            String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();\n            Log.v(TAG, \"Setting env vars for start.c and Python to use\");\n            SDLActivity.nativeSetenv(\"ANDROID_PRIVATE\", mFilesDirectory);\n            SDLActivity.nativeSetenv(\"ANDROID_UNPACK\", app_root_dir);\n            SDLActivity.nativeSetenv(\"PYTHONHOME\", app_root_dir);\n            SDLActivity.nativeSetenv(\"PYTHONPATH\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n            SDLActivity.nativeSetenv(\"PYTHONOPTIMIZE\", \"2\");\n\n            try {\n                Log.v(TAG, \"Access to our meta-data...\");\n                mActivity.mMetaData =\n                        mActivity\n                                .getPackageManager()\n                                .getApplicationInfo(\n                                        mActivity.getPackageName(), PackageManager.GET_META_DATA)\n                                .metaData;\n\n                PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);\n                if (mActivity.mMetaData.getInt(\"wakelock\") == 1) {\n                    mActivity.mWakeLock =\n                            pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, \"Screen On\");\n                    mActivity.mWakeLock.acquire();\n                }\n                if (mActivity.mMetaData.getInt(\"surface.transparent\") != 0) {\n                    Log.v(TAG, \"Surface will be transparent.\");\n                    getSurface().setZOrderOnTop(true);\n                    getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT);\n                } else {\n                    Log.i(TAG, \"Surface will NOT be transparent\");\n                }\n            } catch (PackageManager.NameNotFoundException e) {\n            }\n\n            // Launch app if that hasn't been done yet:\n            if (mActivity.mHasFocus\n                    && (\n                    // never went into proper resume state:\n                    mActivity.mCurrentNativeState == NativeState.INIT\n                            || (\n                            // resumed earlier but wasn't ready yet\n                            mActivity.mCurrentNativeState == NativeState.RESUMED\n                                    && mActivity.mSDLThread == null))) {\n                // Because sometimes the app will get stuck here and never\n                // actually run, ensure that it gets launched if we're active:\n                mActivity.resumeNativeThread();\n            }\n        }\n\n        @Override\n        protected void onPreExecute() {}\n\n        @Override\n        protected void onProgressUpdate(Void... values) {}\n    }\n\n    public static ViewGroup getLayout() {\n        return mLayout;\n    }\n\n    public static SurfaceView getSurface() {\n        return mSurface;\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onNewIntent\n    //\n\n    public interface NewIntentListener {\n        void onNewIntent(Intent intent);\n    }\n\n    private List<NewIntentListener> newIntentListeners = null;\n\n    public void registerNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null)\n            this.newIntentListeners =\n                    Collections.synchronizedList(new ArrayList<NewIntentListener>());\n        this.newIntentListeners.add(listener);\n    }\n\n    public void unregisterNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null) return;\n        this.newIntentListeners.remove(listener);\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        if (this.newIntentListeners == null) return;\n        this.onResume();\n        synchronized (this.newIntentListeners) {\n            Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();\n            while (iterator.hasNext()) {\n                (iterator.next()).onNewIntent(intent);\n            }\n        }\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onActivityResult\n    //\n\n    public interface ActivityResultListener {\n        void onActivityResult(int requestCode, int resultCode, Intent data);\n    }\n\n    private List<ActivityResultListener> activityResultListeners = null;\n\n    public void registerActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null)\n            this.activityResultListeners =\n                    Collections.synchronizedList(new ArrayList<ActivityResultListener>());\n        this.activityResultListeners.add(listener);\n    }\n\n    public void unregisterActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null) return;\n        this.activityResultListeners.remove(listener);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {\n        if (this.activityResultListeners == null) return;\n        this.onResume();\n        synchronized (this.activityResultListeners) {\n            Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();\n            while (iterator.hasNext())\n                (iterator.next()).onActivityResult(requestCode, resultCode, intent);\n        }\n    }\n\n    public static void start_service(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, true);\n    }\n\n    public static void start_service_not_as_foreground(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, false);\n    }\n\n    public static void _do_start_service(\n            String serviceTitle,\n            String serviceDescription,\n            String pythonServiceArgument,\n            boolean showForegroundNotification) {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();\n        String app_root_dir = PythonActivity.mActivity.getAppRoot();\n        String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + \"/service\");\n        serviceIntent.putExtra(\"androidPrivate\", argument);\n        serviceIntent.putExtra(\"androidArgument\", app_root_dir);\n        serviceIntent.putExtra(\"serviceEntrypoint\", \"service/\" + entry_point);\n        serviceIntent.putExtra(\"pythonName\", \"python\");\n        serviceIntent.putExtra(\"pythonHome\", app_root_dir);\n        serviceIntent.putExtra(\"pythonPath\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n        serviceIntent.putExtra(\n                \"serviceStartAsForeground\", (showForegroundNotification ? \"true\" : \"false\"));\n        serviceIntent.putExtra(\"serviceTitle\", serviceTitle);\n        serviceIntent.putExtra(\"serviceDescription\", serviceDescription);\n        serviceIntent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        PythonActivity.mActivity.startService(serviceIntent);\n    }\n\n    public static void stop_service() {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        PythonActivity.mActivity.stopService(serviceIntent);\n    }\n\n    /** Loading screen view * */\n    public static ImageView mImageView = null;\n\n    public static View mLottieView = null;\n\n    /** Whether main routine/actual app has started yet * */\n    protected boolean mAppConfirmedActive = false;\n\n    /** Timer for delayed loading screen removal. * */\n    protected Timer loadingScreenRemovalTimer = null;\n\n    // Overridden since it's called often, to check whether to remove the\n    // loading screen:\n    @Override\n    protected boolean sendCommand(int command, Object data) {\n        boolean result = super.sendCommand(command, data);\n        considerLoadingScreenRemoval();\n        return result;\n    }\n\n    /** Confirm that the app's main routine has been launched. */\n    @Override\n    public void appConfirmedActive() {\n        if (!mAppConfirmedActive) {\n            Log.v(TAG, \"appConfirmedActive() -> preparing loading screen removal\");\n            mAppConfirmedActive = true;\n            considerLoadingScreenRemoval();\n        }\n    }\n\n    /**\n     * This is called from various places to check whether the app's main routine has been launched\n     * already, and if it has, then the loading screen will be removed.\n     */\n    public void considerLoadingScreenRemoval() {\n        if (loadingScreenRemovalTimer != null) return;\n        runOnUiThread(\n                new Runnable() {\n                    public void run() {\n                        if (((PythonActivity) PythonActivity.mSingleton).mAppConfirmedActive\n                                && loadingScreenRemovalTimer == null) {\n                            // Remove loading screen but with a delay.\n                            // (app can use p4a's android.loadingscreen module to\n                            // do it quicker if it wants to)\n                            // get a handler (call from main thread)\n                            // this will run when timer elapses\n                            TimerTask removalTask =\n                                    new TimerTask() {\n                                        @Override\n                                        public void run() {\n                                            // post a runnable to the handler\n                                            runOnUiThread(\n                                                    new Runnable() {\n                                                        @Override\n                                                        public void run() {\n                                                            PythonActivity activity =\n                                                                    ((PythonActivity)\n                                                                            PythonActivity\n                                                                                    .mSingleton);\n                                                            if (activity != null)\n                                                                activity.removeLoadingScreen();\n                                                        }\n                                                    });\n                                        }\n                                    };\n                            loadingScreenRemovalTimer = new Timer();\n                            loadingScreenRemovalTimer.schedule(removalTask, 5000);\n                        }\n                    }\n                });\n    }\n\n    public void removeLoadingScreen() {\n        runOnUiThread(\n                new Runnable() {\n                    public void run() {\n                        View view = mLottieView != null ? mLottieView : mImageView;\n                        if (view != null && view.getParent() != null) {\n                            ((ViewGroup) view.getParent()).removeView(view);\n                            mLottieView = null;\n                            mImageView = null;\n                        }\n                    }\n                });\n    }\n\n    public String getEntryPoint(String search_dir) {\n        /* Get the main file (.pyc|.py) depending on if we\n         * have a compiled version or not.\n         */\n        List<String> entryPoints = new ArrayList<String>();\n        entryPoints.add(\"main.pyc\"); // python 3 compiled files\n        for (String value : entryPoints) {\n            File mainFile = new File(search_dir + \"/\" + value);\n            if (mainFile.exists()) {\n                return value;\n            }\n        }\n        return \"main.py\";\n    }\n\n    protected void showLoadingScreen(View view) {\n        try {\n            if (mLayout == null) {\n                setContentView(view);\n            } else if (view.getParent() == null) {\n                mLayout.addView(view);\n            }\n        } catch (IllegalStateException e) {\n            // The loading screen can be attempted to be applied twice if app\n            // is tabbed in/out, quickly.\n            // (Gives error \"The specified child already has a parent.\n            // You must call removeView() on the child's parent first.\")\n        }\n    }\n\n    protected void setBackgroundColor(View view) {\n        /*\n         * Set the presplash loading screen background color\n         * https://developer.android.com/reference/android/graphics/Color.html\n         * Parse the color string, and return the corresponding color-int.\n         * If the string cannot be parsed, throws an IllegalArgumentException exception.\n         * Supported formats are: #RRGGBB #AARRGGBB or one of the following names:\n         * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',\n         * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',\n         * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.\n         */\n        String backgroundColor = resourceManager.getString(\"presplash_color\");\n        if (backgroundColor != null) {\n            try {\n                view.setBackgroundColor(Color.parseColor(backgroundColor));\n            } catch (IllegalArgumentException e) {\n            }\n        }\n    }\n\n    protected View getLoadingScreen() {\n        // If we have an mLottieView or mImageView already, then do\n        // nothing because it will have already been made the content\n        // view or added to the layout.\n        if (mLottieView != null || mImageView != null) {\n            // we already have a splash screen\n            return mLottieView != null ? mLottieView : mImageView;\n        }\n\n        // first try to load the lottie one\n        try {\n            mLottieView =\n                    getLayoutInflater()\n                            .inflate(\n                                    this.resourceManager.getIdentifier(\"lottie\", \"layout\"),\n                                    mLayout,\n                                    false);\n            try {\n                if (mLayout == null) {\n                    setContentView(mLottieView);\n                } else if (PythonActivity.mLottieView.getParent() == null) {\n                    mLayout.addView(mLottieView);\n                }\n            } catch (IllegalStateException e) {\n                // The loading screen can be attempted to be applied twice if app\n                // is tabbed in/out, quickly.\n                // (Gives error \"The specified child already has a parent.\n                // You must call removeView() on the child's parent first.\")\n            }\n            setBackgroundColor(mLottieView);\n            return mLottieView;\n        } catch (NotFoundException e) {\n            Log.v(\"SDL\", \"couldn't find lottie layout or animation, trying static splash\");\n        }\n\n        // no lottie asset, try to load the static image then\n        int presplashId = this.resourceManager.getIdentifier(\"presplash\", \"drawable\");\n        InputStream is = this.getResources().openRawResource(presplashId);\n        Bitmap bitmap = null;\n        try {\n            bitmap = BitmapFactory.decodeStream(is);\n        } finally {\n            try {\n                is.close();\n            } catch (IOException e) {\n            }\n            ;\n        }\n\n        mImageView = new ImageView(this);\n        mImageView.setImageBitmap(bitmap);\n        setBackgroundColor(mImageView);\n\n        mImageView.setLayoutParams(\n                new ViewGroup.LayoutParams(\n                        ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));\n        mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);\n        return mImageView;\n    }\n\n    @Override\n    protected void onPause() {\n        if (this.mWakeLock != null && mWakeLock.isHeld()) {\n            this.mWakeLock.release();\n        }\n\n        Log.v(TAG, \"onPause()\");\n        try {\n            super.onPause();\n        } catch (UnsatisfiedLinkError e) {\n            // Catch pause while still in loading screen failing to\n            // call native function (since it's not yet loaded)\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        if (this.mWakeLock != null) {\n            this.mWakeLock.acquire();\n        }\n        Log.v(TAG, \"onResume()\");\n        try {\n            super.onResume();\n        } catch (UnsatisfiedLinkError e) {\n            // Catch resume while still in loading screen failing to\n            // call native function (since it's not yet loaded)\n        }\n        considerLoadingScreenRemoval();\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        try {\n            super.onWindowFocusChanged(hasFocus);\n        } catch (UnsatisfiedLinkError e) {\n            // Catch window focus while still in loading screen failing to\n            // call native function (since it's not yet loaded)\n        }\n        considerLoadingScreenRemoval();\n    }\n\n    /**\n     * Used by android.permissions p4a module to register a call back after requesting runtime\n     * permissions\n     */\n    public interface PermissionsCallback {\n        void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);\n    }\n\n    private PermissionsCallback permissionCallback;\n    private boolean havePermissionsCallback = false;\n\n    public void addPermissionsCallback(PermissionsCallback callback) {\n        permissionCallback = callback;\n        havePermissionsCallback = true;\n        Log.v(TAG, \"addPermissionsCallback(): Added callback for onRequestPermissionsResult\");\n    }\n\n    @Override\n    public void onRequestPermissionsResult(\n            int requestCode, String[] permissions, int[] grantResults) {\n        Log.v(TAG, \"onRequestPermissionsResult()\");\n        if (havePermissionsCallback) {\n            Log.v(TAG, \"onRequestPermissionsResult passed to callback\");\n            permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        }\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n\n    /** Used by android.permissions p4a module to check a permission */\n    public boolean checkCurrentPermission(String permission) {\n        if (android.os.Build.VERSION.SDK_INT < 23) return true;\n\n        try {\n            java.lang.reflect.Method methodCheckPermission =\n                    Activity.class.getMethod(\"checkSelfPermission\", String.class);\n            Object resultObj = methodCheckPermission.invoke(this, permission);\n            int result = Integer.parseInt(resultObj.toString());\n            if (result == PackageManager.PERMISSION_GRANTED) return true;\n        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {\n        }\n        return false;\n    }\n\n    /** Used by android.permissions p4a module to request runtime permissions */\n    public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) {\n        if (android.os.Build.VERSION.SDK_INT < 23) return;\n        try {\n            java.lang.reflect.Method methodRequestPermission =\n                    Activity.class.getMethod(\"requestPermissions\", String[].class, int.class);\n            methodRequestPermission.invoke(this, permissions, requestCode);\n        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {\n        }\n    }\n\n    public void requestPermissions(String[] permissions) {\n        requestPermissionsWithRequestCode(permissions, 1);\n    }\n\n    public static void changeKeyboard(int inputType) {\n        /*\n         if (SDLActivity.keyboardInputType != inputType){\n             SDLActivity.keyboardInputType = inputType;\n             InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n             imm.restartInput(mTextEdit);\n             }\n        */\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl3/build/src/patches/SDLActivity.java.patch",
    "content": "--- a/src/main/java/org/libsdl/app/SDLActivity.java\n+++ b/src/main/java/org/libsdl/app/SDLActivity.java\n@@ -259,6 +259,7 @@\n         String[] arguments = SDLActivity.mSingleton.getArguments();\n \n         Log.v(\"SDL\", \"Running main function \" + function + \" from library \" + library);\n+        SDLActivity.mSingleton.appConfirmedActive();\n         SDLActivity.nativeRunMain(library, function, arguments);\n         Log.v(\"SDL\", \"Finished main function\");\n     }\n@@ -351,6 +352,15 @@\n         Log.v(TAG, \"Model: \" + Build.MODEL);\n         Log.v(TAG, \"onCreate()\");\n         super.onCreate(savedInstanceState);\n+\n+        SDL.initialize();\n+        // So we can call stuff from static callbacks\n+        mSingleton = this;\n+    }\n+\n+    // We don't do this in onCreate because we unpack and load the app data on a thread\n+    // and we can't run setup tasks until that thread completes.\n+    protected void finishLoad() {\n \n \n         /* Control activity re-creation */\n@@ -1541,8 +1551,22 @@\n             return null;\n         }\n         return SDLActivity.mSurface.getNativeSurface();\n+    }\n+\n+    /**\n+    * Calls turnActive() on singleton to keep loading screen active\n+    */\n+    public static void triggerAppConfirmedActive() {\n+        mSingleton.appConfirmedActive();\n     }\n \n+    /**\n+    * Trick needed for loading screen, overridden by PythonActivity\n+    * to keep loading screen active\n+    */\n+    public void appConfirmedActive() {\n+    }\n+\n     // Input\n \n     /**\n"
  },
  {
    "path": "pythonforandroid/bootstraps/sdl3/build/src/patches/SDLSurface.java.patch",
    "content": "--- a/src/main/java/org/libsdl/app/SDLSurface.java\n+++ b/src/main/java/org/libsdl/app/SDLSurface.java\n@@ -232,9 +232,23 @@\n         }\n     }\n \n+    public interface OnInterceptTouchListener {\n+        boolean onTouch(MotionEvent ev);\n+    }\n+\n+    private OnInterceptTouchListener mOnInterceptTouchListener = null;\n+\n+    public void setInterceptTouchListener(OnInterceptTouchListener listener) {\n+        this.mOnInterceptTouchListener = listener;\n+    }\n+\n     // Touch events\n     @Override\n     public boolean onTouch(View v, MotionEvent event) {\n+        // Allow touch to be intercepted by python application\n+        if (mOnInterceptTouchListener != null)\n+            if (mOnInterceptTouchListener.onTouch(event))\n+                return false;\n         /* Ref: http://developer.android.com/training/gestures/multi.html */\n         int touchDevId = event.getDeviceId();\n         final int pointerCount = event.getPointerCount();\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_library/__init__.py",
    "content": "from pythonforandroid.bootstraps.service_only import ServiceOnlyBootstrap\n\n\nclass ServiceLibraryBootstrap(ServiceOnlyBootstrap):\n\n    name = 'service_library'\n\n\nbootstrap = ServiceLibraryBootstrap()\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_library/build/jni/Application.mk",
    "content": "APP_PLATFORM := $(NDK_API)\nAPP_ABI := $(ARCH)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h",
    "content": "\n#define BOOTSTRAP_NAME_LIBRARY\n\nconst char bootstrap_name[] = \"service_library\";\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/PythonActivity.java",
    "content": "package org.kivy.android;\n\nimport android.app.Activity;\n\n// Required by PythonService class\npublic class PythonActivity extends Activity {\n    public static PythonActivity mActivity = null;\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_library/build/src/main/res/mipmap/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/service_library/build/templates/AndroidManifest.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      android:versionCode=\"{{ args.numeric_version }}\"\n      android:versionName=\"{{ args.version }}\">\n\n    <!-- Android 2.3.3 -->\n    <uses-sdk android:minSdkVersion=\"{{ args.min_sdk_version }}\" android:targetSdkVersion=\"{{ android_api }}\" />\n\n    <application {% if debug %}android:debuggable=\"true\"{% endif %} >\n        {% for name, foreground_type in service_data %}\n        <service android:name=\"{{ args.package }}.Service{{ name|capitalize }}\"\n                 {% if foreground_type %}\n                 android:foregroundServiceType=\"{{ foreground_type }}\"\n                 {% endif %}\n                 android:process=\":service_{{ name }}\"\n                 android:exported=\"true\" />\n        {% endfor %}\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java",
    "content": "package {{ args.package }};\n\nimport java.io.File;\n\nimport android.os.Build;\nimport android.content.Intent;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.util.Log;\n\nimport org.kivy.android.PythonService;\nimport org.kivy.android.PythonUtil;\n\npublic class Service{{ name|capitalize }} extends PythonService {\n\n    private static final String TAG = \"PythonService\";\n\n    {% if sticky %}\n    @Override\n    public int startType() {\n        return START_STICKY;\n    }\n    {% endif %}\n\n    @Override\n    protected int getServiceId() {\n        return {{ service_id }};\n    }\n\n    public static void prepare(Context ctx) {\n        String appRoot = PythonUtil.getAppRoot(ctx);\n        Log.v(TAG, \"Ready to unpack\");\n        File app_root_file = new File(appRoot);\n        PythonUtil.unpackAsset(ctx, \"private\", app_root_file, true);\n        PythonUtil.unpackPyBundle(ctx, ctx.getApplicationInfo().nativeLibraryDir + \"/\" + \"libpybundle\", app_root_file, false);\n    }\n\t\n    static private void _start(Context ctx, String smallIconName,\n\t\t\t     String contentTitle,\n\t\t\t     String contentText,\n\t\t\t     String pythonServiceArgument) {\n        Intent intent = getDefaultIntent(ctx, smallIconName, contentTitle,\n                                         contentText, pythonServiceArgument);\n        //foreground: {{foreground}}\n        {% if foreground %}\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            ctx.startForegroundService(intent);\n        } else {\n            ctx.startService(intent);\n        }\n        {% else %}\n        ctx.startService(intent);\n        {% endif %}\n    }\n\n    public static void start(Context ctx, String pythonServiceArgument) {\n\t_start(ctx,  \"\", \"{{ args.name }}\", \"{{ name|capitalize }}\", pythonServiceArgument);\n    }\n    \n    static public void start(Context ctx, String smallIconName,\n\t\t\t     String contentTitle,\n\t\t\t     String contentText,\n\t\t\t     String pythonServiceArgument) {\n\t_start(ctx, smallIconName, contentTitle, contentText, pythonServiceArgument);\n    }\n\n    static public Intent getDefaultIntent(Context ctx, String smallIconName,\n\t\t\t\t\t  String contentTitle,\n\t\t\t\t\t  String contentText,\n\t\t\t\t\t  String pythonServiceArgument) {\n        String appRoot = PythonUtil.getAppRoot(ctx);\n        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);\n        intent.putExtra(\"androidPrivate\", appRoot);\n        intent.putExtra(\"androidArgument\", appRoot);\n        intent.putExtra(\"serviceEntrypoint\", \"{{ entrypoint }}\");\n        intent.putExtra(\"serviceTitle\", \"{{ name|capitalize }}\");\n        intent.putExtra(\"pythonName\", \"{{ name }}\");\n        intent.putExtra(\"serviceStartAsForeground\", \"{{ foreground|lower }}\");\n        intent.putExtra(\"pythonHome\", appRoot);\n        intent.putExtra(\"androidUnpack\", appRoot);\n        intent.putExtra(\"pythonPath\", appRoot + \":\" + appRoot + \"/lib\");\n        intent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        intent.putExtra(\"smallIconName\", smallIconName);\n        intent.putExtra(\"contentTitle\", contentTitle);\n        intent.putExtra(\"contentText\", contentText);\n        return intent;\n    }\n\n    @Override\n    protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {\n        return Service{{ name|capitalize }}.getDefaultIntent(ctx,  \"\", \"\", \"\",\n                                                             pythonServiceArgument);\n    }\n\n\n\n    static public void stop(Context ctx) {\n        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);\n        ctx.stopService(intent);\n    }\n\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/__init__.py",
    "content": "from pythonforandroid.toolchain import Bootstrap\n\n\nclass ServiceOnlyBootstrap(Bootstrap):\n\n    name = 'service_only'\n\n    recipe_depends = list(\n        set(Bootstrap.recipe_depends).union({'genericndkbuild'})\n    )\n\n\nbootstrap = ServiceOnlyBootstrap()\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/blacklist.txt",
    "content": "# prevent user to include invalid extensions\n*.apk\n*.aab\n*.apks\n*.pxd\n\n# eggs\n*.egg-info\n\n# unit test\nunittest/*\n\n# python config\nconfig/makesetup\n\n# unused kivy files (platform specific)\nkivy/input/providers/wm_*\nkivy/input/providers/mactouch*\nkivy/input/providers/probesysfs*\nkivy/input/providers/mtdev*\nkivy/input/providers/hidinput*\nkivy/core/camera/camera_videocapture*\nkivy/core/spelling/*osx*\nkivy/core/video/video_pyglet*\nkivy/tools\nkivy/tests/*\nkivy/*/*.h\nkivy/*/*.pxi\n\n# unused encodings\nlib-dynload/*codec*\nencodings/cp*.pyo\nencodings/tis*\nencodings/shift*\nencodings/bz2*\nencodings/iso*\nencodings/undefined*\nencodings/johab*\nencodings/p*\nencodings/m*\nencodings/euc*\nencodings/k*\nencodings/unicode_internal*\nencodings/quo*\nencodings/gb*\nencodings/big5*\nencodings/hp*\nencodings/hz*\n\n# unused python modules\nbsddb/*\nwsgiref/*\nhotshot/*\npydoc_data/*\ntty.pyo\nanydbm.pyo\nnturl2path.pyo\nLICENCE.txt\nmacurl2path.pyo\ndummy_threading.pyo\naudiodev.pyo\nantigravity.pyo\ndumbdbm.pyo\nsndhdr.pyo\n__phello__.foo.pyo\nsunaudio.pyo\nos2emxpath.pyo\nmultiprocessing/dummy*\n\n# unused binaries python modules\nlib-dynload/termios.so\nlib-dynload/_lsprof.so\nlib-dynload/*audioop.so\nlib-dynload/_hotshot.so\nlib-dynload/_heapq.so\nlib-dynload/_json.so\nlib-dynload/grp.so\nlib-dynload/resource.so\nlib-dynload/pyexpat.so\nlib-dynload/_ctypes_test.so\nlib-dynload/_testcapi.so\n\n# odd files\nplat-linux3/regen\n\n#>sqlite3\n# conditional include depending if some recipes are included or not.\nsqlite3/*\nlib-dynload/_sqlite3.so\n#<sqlite3\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/jni/Android.mk",
    "content": "include $(call all-subdir-makefiles)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/jni/Application.mk",
    "content": "\n# Uncomment this if you're using STL in your project\n# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information\n# APP_STL := stlport_static \n\n# APP_ABI := armeabi armeabi-v7a x86\nAPP_ABI := $(ARCH)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/jni/application/src/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\n# Add your application source files here...\nLOCAL_SRC_FILES := start.c pyjniusjni.c\n\nLOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)\n\nLOCAL_SHARED_LIBRARIES := python_shared\n\nLOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)\n\nLOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/jni/application/src/Android_static.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\nLOCAL_SRC_FILES := YourSourceHere.c\n\ninclude $(BUILD_SHARED_LIBRARY)\n$(call import-module,SDL)LOCAL_PATH := $(call my-dir)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/jni/application/src/bootstrap_name.h",
    "content": "\n#define BOOTSTRAP_NAME_SERVICEONLY\n\nconst char bootstrap_name[] = \"service_only\";\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/jni/application/src/pyjniusjni.c",
    "content": "\n#include <pthread.h>\n#include <jni.h>\n\n#define LOGI(...) do {} while (0)\n#define LOGE(...) do {} while (0)\n\n#include \"android/log.h\"\n\n/* These JNI management functions are taken from SDL2, but modified to refer to pyjnius */\n\n/* #define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x)) */\n/* #define LOGP(x) LOG(\"python\", (x)) */\n#define LOG_TAG \"Python_android\"\n#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)\n#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)\n\n\n/* Function headers */\nJNIEnv* Android_JNI_GetEnv(void);\nstatic void Android_JNI_ThreadDestroyed(void*);\n\nstatic pthread_key_t mThreadKey;\nstatic JavaVM* mJavaVM;\n\nint Android_JNI_SetupThread(void)\n{\n    Android_JNI_GetEnv();\n    return 1;\n}\n\n/* Library init */\nJNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)\n{\n    JNIEnv *env;\n    mJavaVM = vm;\n    LOGI(\"JNI_OnLoad called\");\n    if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {\n        LOGE(\"Failed to get the environment using GetEnv()\");\n        return -1;\n    }\n    /*\n     * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread\n     * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this\n     */\n    if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {\n\n        __android_log_print(ANDROID_LOG_ERROR, \"pyjniusjni\", \"Error initializing pthread key\");\n    }\n    Android_JNI_SetupThread();\n\n    return JNI_VERSION_1_4;\n}\n\nJNIEnv* Android_JNI_GetEnv(void)\n{\n    /* From http://developer.android.com/guide/practices/jni.html\n     * All threads are Linux threads, scheduled by the kernel.\n     * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then\n     * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the\n     * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,\n     * and cannot make JNI calls.\n     * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the \"main\"\n     * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread\n     * is a no-op.\n     * Note: You can call this function any number of times for the same thread, there's no harm in it\n     */\n\n    JNIEnv *env;\n    int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);\n    if(status < 0) {\n        LOGE(\"failed to attach current thread\");\n        return 0;\n    }\n\n    /* From http://developer.android.com/guide/practices/jni.html\n     * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,\n     * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be\n     * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific\n     * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)\n     * Note: The destructor is not called unless the stored value is != NULL\n     * Note: You can call this function any number of times for the same thread, there's no harm in it\n     *       (except for some lost CPU cycles)\n     */\n    pthread_setspecific(mThreadKey, (void*) env);\n\n    return env;\n}\n\nstatic void Android_JNI_ThreadDestroyed(void* value)\n{\n    /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */\n    JNIEnv *env = (JNIEnv*) value;\n    if (env != NULL) {\n        (*mJavaVM)->DetachCurrentThread(mJavaVM);\n        pthread_setspecific(mThreadKey, NULL);\n    }\n}\n\nvoid *WebView_AndroidGetJNIEnv()\n{\n    return Android_JNI_GetEnv();\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/src/main/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java",
    "content": "package org.kivy.android;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.os.PowerManager;\nimport android.os.SystemClock;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.widget.Toast;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport org.renpy.android.ResourceManager;\n\npublic class PythonActivity extends Activity {\n    // This activity is modified from a mixture of the SDLActivity and\n    // PythonActivity in the SDL2 bootstrap, but removing all the SDL2\n    // specifics.\n\n    private static final String TAG = \"PythonActivity\";\n\n    public static PythonActivity mActivity = null;\n\n    /** If shared libraries (e.g. the native application) could not be loaded. */\n    public static boolean mBrokenLibraries;\n\n    protected static Thread mPythonThread;\n\n    private ResourceManager resourceManager = null;\n    private Bundle mMetaData = null;\n    private PowerManager.WakeLock mWakeLock = null;\n\n    public String getAppRoot() {\n        String app_root = getFilesDir().getAbsolutePath() + \"/app\";\n        return app_root;\n    }\n\n    public String getEntryPoint(String search_dir) {\n        /* Get the main file (.pyc|.py) depending on if we\n         * have a compiled version or not.\n         */\n        List<String> entryPoints = new ArrayList<String>();\n        entryPoints.add(\"main.pyc\"); // python 3 compiled files\n        for (String value : entryPoints) {\n            File mainFile = new File(search_dir + \"/\" + value);\n            if (mainFile.exists()) {\n                return value;\n            }\n        }\n        return \"main.py\";\n    }\n\n    public static void initialize() {\n        // The static nature of the singleton and Android quirkiness force us to initialize\n        // everything here\n        // Otherwise, when exiting the app and returning to it, these variables *keep* their pre\n        // exit values\n        mBrokenLibraries = false;\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.v(TAG, \"My oncreate running\");\n        resourceManager = new ResourceManager(this);\n\n        Log.v(TAG, \"Ready to unpack\");\n        File app_root_file = new File(getAppRoot());\n        PythonUtil.unpackAsset(mActivity, \"private\", app_root_file, true);\n        PythonUtil.unpackPyBundle(\n                mActivity,\n                getApplicationInfo().nativeLibraryDir + \"/\" + \"libpybundle\",\n                app_root_file,\n                false);\n\n        Log.v(TAG, \"About to do super onCreate\");\n        super.onCreate(savedInstanceState);\n        Log.v(TAG, \"Did super onCreate\");\n\n        this.mActivity = this;\n        // this.showLoadingScreen();\n        Log.v(\"Python\", \"Device: \" + android.os.Build.DEVICE);\n        Log.v(\"Python\", \"Model: \" + android.os.Build.MODEL);\n\n        // Log.v(TAG, \"Ready to unpack\");\n        // new UnpackFilesTask().execute(getAppRoot());\n\n        PythonActivity.initialize();\n\n        // Load shared libraries\n        String errorMsgBrokenLib = \"\";\n        try {\n            loadLibraries();\n        } catch (UnsatisfiedLinkError e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        } catch (Exception e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        }\n\n        if (mBrokenLibraries) {\n            AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);\n            dlgAlert.setMessage(\n                    \"An error occurred while trying to load the application libraries. Please try again and/or reinstall.\"\n                            + System.getProperty(\"line.separator\")\n                            + System.getProperty(\"line.separator\")\n                            + \"Error: \"\n                            + errorMsgBrokenLib);\n            dlgAlert.setTitle(\"Python Error\");\n            dlgAlert.setPositiveButton(\n                    \"Exit\",\n                    new DialogInterface.OnClickListener() {\n                        @Override\n                        public void onClick(DialogInterface dialog, int id) {\n                            // if this button is clicked, close current activity\n                            PythonActivity.mActivity.finish();\n                        }\n                    });\n            dlgAlert.setCancelable(false);\n            dlgAlert.create().show();\n\n            return;\n        }\n\n        // Set up the Python environment\n        String app_root_dir = getAppRoot();\n        String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();\n        String entry_point = getEntryPoint(app_root_dir);\n\n        Log.v(TAG, \"Setting env vars for start.c and Python to use\");\n        PythonActivity.nativeSetenv(\"ANDROID_ENTRYPOINT\", entry_point);\n        PythonActivity.nativeSetenv(\"ANDROID_ARGUMENT\", app_root_dir);\n        PythonActivity.nativeSetenv(\"ANDROID_APP_PATH\", app_root_dir);\n        PythonActivity.nativeSetenv(\"ANDROID_PRIVATE\", mFilesDirectory);\n        PythonActivity.nativeSetenv(\"ANDROID_UNPACK\", app_root_dir);\n        PythonActivity.nativeSetenv(\"PYTHONHOME\", app_root_dir);\n        PythonActivity.nativeSetenv(\"PYTHONPATH\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n        PythonActivity.nativeSetenv(\"PYTHONOPTIMIZE\", \"2\");\n\n        try {\n            Log.v(TAG, \"Access to our meta-data...\");\n            mActivity.mMetaData =\n                    mActivity\n                            .getPackageManager()\n                            .getApplicationInfo(\n                                    mActivity.getPackageName(), PackageManager.GET_META_DATA)\n                            .metaData;\n\n            PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);\n            if (mActivity.mMetaData.getInt(\"wakelock\") == 1) {\n                mActivity.mWakeLock =\n                        pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, \"Screen On\");\n                mActivity.mWakeLock.acquire();\n            }\n        } catch (PackageManager.NameNotFoundException e) {\n        }\n\n        final Thread pythonThread = new Thread(new PythonMain(), \"PythonThread\");\n        PythonActivity.mPythonThread = pythonThread;\n        pythonThread.start();\n    }\n\n    @Override\n    public void onDestroy() {\n        Log.i(\"Destroy\", \"end of app\");\n        super.onDestroy();\n\n        // make sure all child threads (python_thread) are stopped\n        android.os.Process.killProcess(android.os.Process.myPid());\n    }\n\n    public void loadLibraries() {\n        String app_root = new String(getAppRoot());\n        File app_root_file = new File(app_root);\n        PythonUtil.loadLibraries(app_root_file, new File(getApplicationInfo().nativeLibraryDir));\n    }\n\n    long lastBackClick = 0;\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n        // Check if the key event was the Back button\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            // If there's no web page history, bubble up to the default\n            // system behavior (probably exit the activity)\n            if (SystemClock.elapsedRealtime() - lastBackClick > 2000) {\n                lastBackClick = SystemClock.elapsedRealtime();\n                Toast.makeText(this, \"Tap again to close the app\", Toast.LENGTH_LONG).show();\n                return true;\n            }\n\n            lastBackClick = SystemClock.elapsedRealtime();\n        }\n\n        return super.onKeyDown(keyCode, event);\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onNewIntent\n    //\n\n    public interface NewIntentListener {\n        void onNewIntent(Intent intent);\n    }\n\n    private List<NewIntentListener> newIntentListeners = null;\n\n    public void registerNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null)\n            this.newIntentListeners =\n                    Collections.synchronizedList(new ArrayList<NewIntentListener>());\n        this.newIntentListeners.add(listener);\n    }\n\n    public void unregisterNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null) return;\n        this.newIntentListeners.remove(listener);\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        if (this.newIntentListeners == null) return;\n        this.onResume();\n        synchronized (this.newIntentListeners) {\n            Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();\n            while (iterator.hasNext()) {\n                (iterator.next()).onNewIntent(intent);\n            }\n        }\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onActivityResult\n    //\n\n    public interface ActivityResultListener {\n        void onActivityResult(int requestCode, int resultCode, Intent data);\n    }\n\n    private List<ActivityResultListener> activityResultListeners = null;\n\n    public void registerActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null)\n            this.activityResultListeners =\n                    Collections.synchronizedList(new ArrayList<ActivityResultListener>());\n        this.activityResultListeners.add(listener);\n    }\n\n    public void unregisterActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null) return;\n        this.activityResultListeners.remove(listener);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {\n        if (this.activityResultListeners == null) return;\n        this.onResume();\n        synchronized (this.activityResultListeners) {\n            Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();\n            while (iterator.hasNext())\n                (iterator.next()).onActivityResult(requestCode, resultCode, intent);\n        }\n    }\n\n    public static void start_service(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, true);\n    }\n\n    public static void start_service_not_as_foreground(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, false);\n    }\n\n    public static void _do_start_service(\n            String serviceTitle,\n            String serviceDescription,\n            String pythonServiceArgument,\n            boolean showForegroundNotification) {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();\n        String app_root_dir = PythonActivity.mActivity.getAppRoot();\n        String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + \"/service\");\n        serviceIntent.putExtra(\"androidPrivate\", argument);\n        serviceIntent.putExtra(\"androidArgument\", app_root_dir);\n        serviceIntent.putExtra(\"serviceEntrypoint\", \"service/\" + entry_point);\n        serviceIntent.putExtra(\"pythonName\", \"python\");\n        serviceIntent.putExtra(\"pythonHome\", app_root_dir);\n        serviceIntent.putExtra(\"pythonPath\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n        serviceIntent.putExtra(\n                \"serviceStartAsForeground\", (showForegroundNotification ? \"true\" : \"false\"));\n        serviceIntent.putExtra(\"serviceTitle\", serviceTitle);\n        serviceIntent.putExtra(\"serviceDescription\", serviceDescription);\n        serviceIntent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        PythonActivity.mActivity.startService(serviceIntent);\n    }\n\n    public static void stop_service() {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        PythonActivity.mActivity.stopService(serviceIntent);\n    }\n\n    public static native void nativeSetenv(String name, String value);\n\n    public static native int nativeInit(Object arguments);\n}\n\nclass PythonMain implements Runnable {\n    @Override\n    public void run() {\n        PythonActivity.nativeInit(new String[0]);\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/src/main/jniLibs/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/src/main/res/drawable/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/src/main/res/mipmap/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      android:versionCode=\"{{ args.numeric_version }}\"\n      android:versionName=\"{{ args.version }}\"\n      android:installLocation=\"auto\">\n\n    <supports-screens\n            android:smallScreens=\"true\"\n            android:normalScreens=\"true\"\n            android:largeScreens=\"true\"\n            android:anyDensity=\"true\"\n            {% if args.min_sdk_version >= 9 %}\n            android:xlargeScreens=\"true\"\n            {% endif %}\n    />\n\n    <!-- Android 2.3.3 -->\n    <uses-sdk android:minSdkVersion=\"{{ args.min_sdk_version }}\" android:targetSdkVersion=\"{{ android_api }}\" />\n\n    <!-- Set permissions -->\n    {% for perm in args.permissions %}\n        <uses-permission android:name=\"{{ perm.name }}\"{% if perm.maxSdkVersion %} android:maxSdkVersion=\"{{ perm.maxSdkVersion }}\"{% endif %}{% if perm.usesPermissionFlags %} android:usesPermissionFlags=\"{{ perm.usesPermissionFlags }}\"{% endif %} />\n    {% endfor %}\n\n    {% if args.wakelock %}\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    {% endif %}\n\n    {% if args.billing_pubkey %}\n    <uses-permission android:name=\"com.android.vending.BILLING\" />\n    {% endif %}\n\n    <!-- Create a Java class extending SDLActivity and place it in a\n         directory under src matching the package, e.g.\n         \tsrc/com/gamemaker/game/MyGame.java\n\n         then replace \"SDLActivity\" with the name of your class (e.g. \"MyGame\")\n         in the XML below.\n\n         An example Java class can be found in README-android.txt\n    -->\n    <application android:label=\"@string/app_name\"\n                 {% if debug %}android:debuggable=\"true\"{% endif %}\n                 android:icon=\"@mipmap/icon\"\n                 android:allowBackup=\"{{ args.allow_backup }}\"\n                 {% if args.backup_rules %}android:fullBackupContent=\"@xml/{{ args.backup_rules }}\"{% endif %}\n                 android:theme=\"{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}\"\n                 android:hardwareAccelerated=\"true\"\n                 android:extractNativeLibs=\"true\" >\n        {% for l in args.android_used_libs %}\n        <uses-library android:name=\"{{ l }}\" />\n        {% endfor %}\n        {% for m in args.meta_data %}\n        <meta-data android:name=\"{{ m.split('=', 1)[0] }}\" android:value=\"{{ m.split('=', 1)[-1] }}\"/>{% endfor %}\n        <meta-data android:name=\"wakelock\" android:value=\"{% if args.wakelock %}1{% else %}0{% endif %}\"/>\n\n        <activity android:name=\"org.kivy.android.PythonActivity\"\n                  android:label=\"@string/app_name\"\n                  android:configChanges=\"keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}\"\n                  android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            {%- if args.intent_filters -%}\n            {{- args.intent_filters -}}\n            {%- endif -%}\n        </activity>\n\n        {% if service %}\n        <service android:name=\"org.kivy.android.PythonService\"\n                 android:process=\":pythonservice\"\n                 android:exported=\"true\"/>\n        {% endif %}\n        {% for name, foreground_type in service_data %}\n        <service android:name=\"{{ args.package }}.Service{{ name|capitalize }}\"\n                 {% if foreground_type %}\n                 android:foregroundServiceType=\"{{ foreground_type }}\"\n                 {% endif %}\n                 android:process=\":service_{{ name }}\"\n                 android:exported=\"true\" />\n        {% endfor %}\n\n        {% if args.billing_pubkey %}\n        <service android:name=\"org.kivy.android.billing.BillingReceiver\"\n                 android:process=\":pythonbilling\" />\n        <receiver android:name=\"org.kivy.android.billing.BillingReceiver\"\n                  android:process=\":pythonbillingreceiver\"\n                  android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"com.android.vending.billing.IN_APP_NOTIFY\" />\n                <action android:name=\"com.android.vending.billing.RESPONSE_CODE\" />\n                <action android:name=\"com.android.vending.billing.PURCHASE_STATE_CHANGED\" />\n            </intent-filter>\n        </receiver>\n        {% endif %}\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java",
    "content": "package {{ args.package }};\n\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.content.Intent;\nimport android.content.Context;\nimport org.kivy.android.PythonService;\n\npublic class Service{{ name|capitalize }} extends PythonService {\n\t/**\n\t * Binder given to clients\n\t */\n    private final IBinder mBinder = new Service{{ name|capitalize }}Binder();\n\n    {% if sticky %}\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public int startType() {\n        return START_STICKY;\n    }\n    {% endif %}\n\n    @Override\n    protected int getServiceId() {\n        return {{ service_id }};\n    }\n\n    public static void start(Context ctx, String pythonServiceArgument) {\n        String argument = ctx.getFilesDir().getAbsolutePath() + \"/app\";\n        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);\n        intent.putExtra(\"androidPrivate\", argument);\n        intent.putExtra(\"androidArgument\", argument);\n        intent.putExtra(\"serviceEntrypoint\", \"{{ entrypoint }}\");\n        intent.putExtra(\"serviceTitle\", \"{{ name|capitalize }}\");\n        intent.putExtra(\"pythonName\", \"{{ name }}\");\n        intent.putExtra(\"serviceStartAsForeground\", \"{{ foreground|lower }}\");\n        intent.putExtra(\"pythonHome\", argument);\n        intent.putExtra(\"androidUnpack\", argument);\n        intent.putExtra(\"pythonPath\", argument + \":\" + argument + \"/lib\");\n        intent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        intent.putExtra(\"smallIconName\", \"\");\n        intent.putExtra(\"contentTitle\", \"{{ name|capitalize }}\");\n        intent.putExtra(\"contentText\", \"\");\t    \n        ctx.startService(intent);\n    }\n\t\n    public static void start(Context ctx, String smallIconName,\n\t\t\t     String contentTitle,\n\t\t\t     String contentText,\n\t\t\t     String pythonServiceArgument) {\t\n        String argument = ctx.getFilesDir().getAbsolutePath() + \"/app\";\n        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);\n        intent.putExtra(\"androidPrivate\", argument);\n        intent.putExtra(\"androidArgument\", argument);\n        intent.putExtra(\"serviceEntrypoint\", \"{{ entrypoint }}\");\n        intent.putExtra(\"serviceTitle\", \"{{ name|capitalize }}\");\n        intent.putExtra(\"pythonName\", \"{{ name }}\");\n        intent.putExtra(\"serviceStartAsForeground\", \"{{ foreground|lower }}\");\n        intent.putExtra(\"pythonHome\", argument);\n        intent.putExtra(\"androidUnpack\", argument);\n        intent.putExtra(\"pythonPath\", argument + \":\" + argument + \"/lib\");\n        intent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        intent.putExtra(\"smallIconName\", smallIconName);\n        intent.putExtra(\"contentTitle\", contentTitle);\n        intent.putExtra(\"contentText\", contentText);\t    \n        ctx.startService(intent);\n    }\n\t\n    public static void stop(Context ctx) {\n        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);\n        ctx.stopService(intent);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public IBinder onBind(Intent intent) {\n        return mBinder;\n    }\n\n    /**\n     * Class used for the client Binder. Because we know this service always\n     * runs in the same process as its clients, we don't need to deal with IPC.\n     */\n    public class Service{{ name|capitalize }}Binder extends Binder {\n    \tService{{ name|capitalize }} getService() {\n            // Return this instance of Service{{ name|capitalize }} so clients can call public methods\n            return Service{{ name|capitalize }}.this;\n        }\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">{{ args.name }}</string>\n    <string name=\"private_version\">{{ private_version }}</string>\n</resources>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/settings.gradle",
    "content": "// Java Lint Project Settings\n// This project is used for linting Java source files in CI\nrootProject.name = 'p4a-java-lint'\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/__init__.py",
    "content": "from pythonforandroid.toolchain import Bootstrap\n\n\nclass WebViewBootstrap(Bootstrap):\n    name = 'webview'\n\n    recipe_depends = list(\n        set(Bootstrap.recipe_depends).union({'genericndkbuild'})\n    )\n\n\nbootstrap = WebViewBootstrap()\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/blacklist.txt",
    "content": "# prevent user to include invalid extensions\n*.apk\n*.aab\n*.apks\n*.pxd\n\n# eggs\n*.egg-info\n\n# unit test\nunittest/*\n\n# python config\nconfig/makesetup\n\n# unused kivy files (platform specific)\nkivy/input/providers/wm_*\nkivy/input/providers/mactouch*\nkivy/input/providers/probesysfs*\nkivy/input/providers/mtdev*\nkivy/input/providers/hidinput*\nkivy/core/camera/camera_videocapture*\nkivy/core/spelling/*osx*\nkivy/core/video/video_pyglet*\nkivy/tools\nkivy/tests/*\nkivy/*/*.h\nkivy/*/*.pxi\n\n# unused encodings\nlib-dynload/*codec*\nencodings/cp*.pyo\nencodings/tis*\nencodings/shift*\nencodings/bz2*\nencodings/iso*\nencodings/undefined*\nencodings/johab*\nencodings/p*\nencodings/m*\nencodings/euc*\nencodings/k*\nencodings/unicode_internal*\nencodings/quo*\nencodings/gb*\nencodings/big5*\nencodings/hp*\nencodings/hz*\n\n# unused python modules\nbsddb/*\nwsgiref/*\nhotshot/*\npydoc_data/*\ntty.pyo\nanydbm.pyo\nnturl2path.pyo\nLICENCE.txt\nmacurl2path.pyo\ndummy_threading.pyo\naudiodev.pyo\nantigravity.pyo\ndumbdbm.pyo\nsndhdr.pyo\n__phello__.foo.pyo\nsunaudio.pyo\nos2emxpath.pyo\nmultiprocessing/dummy*\n\n# unused binaries python modules\nlib-dynload/termios.so\nlib-dynload/_lsprof.so\nlib-dynload/*audioop.so\nlib-dynload/_hotshot.so\nlib-dynload/_heapq.so\nlib-dynload/_json.so\nlib-dynload/grp.so\nlib-dynload/resource.so\nlib-dynload/pyexpat.so\nlib-dynload/_ctypes_test.so\nlib-dynload/_testcapi.so\n\n# odd files\nplat-linux3/regen\n\n#>sqlite3\n# conditional include depending if some recipes are included or not.\nsqlite3/*\nlib-dynload/_sqlite3.so\n#<sqlite3\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/jni/Android.mk",
    "content": "include $(call all-subdir-makefiles)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/jni/Application.mk",
    "content": "\n# Uncomment this if you're using STL in your project\n# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information\n# APP_STL := stlport_static \n\n# APP_ABI := armeabi armeabi-v7a x86\nAPP_ABI := $(ARCH)\nAPP_PLATFORM := $(NDK_API)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/jni/application/src/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\n# LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include\n\n# Add your application source files here...\nLOCAL_SRC_FILES := start.c pyjniusjni.c\n\nLOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)\n\nLOCAL_SHARED_LIBRARIES := python_shared\n\nLOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)\n\nLOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/jni/application/src/Android_static.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\n\nLOCAL_SRC_FILES := YourSourceHere.c\n\nLOCAL_STATIC_LIBRARIES := SDL2_static\n\ninclude $(BUILD_SHARED_LIBRARY)\n$(call import-module,SDL)LOCAL_PATH := $(call my-dir)\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/jni/application/src/bootstrap_name.h",
    "content": "\n#define BOOTSTRAP_NAME_WEBVIEW\n\nconst char bootstrap_name[] = \"webview\";\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/jni/application/src/pyjniusjni.c",
    "content": "\n#include <pthread.h>\n#include <jni.h>\n\n#define LOGI(...) do {} while (0)\n#define LOGE(...) do {} while (0)\n\n#include \"android/log.h\"\n\n/* These JNI management functions are taken from SDL2, but modified to refer to pyjnius */\n\n/* #define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x)) */\n/* #define LOGP(x) LOG(\"python\", (x)) */\n#define LOG_TAG \"Python_android\"\n#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)\n#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)\n\n\n/* Function headers */\nJNIEnv* Android_JNI_GetEnv(void);\nstatic void Android_JNI_ThreadDestroyed(void*);\n\nstatic pthread_key_t mThreadKey;\nstatic JavaVM* mJavaVM;\n\nint Android_JNI_SetupThread(void)\n{\n    Android_JNI_GetEnv();\n    return 1;\n}\n\n/* Library init */\nJNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)\n{\n    JNIEnv *env;\n    mJavaVM = vm;\n    LOGI(\"JNI_OnLoad called\");\n    if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {\n        LOGE(\"Failed to get the environment using GetEnv()\");\n        return -1;\n    }\n    /*\n     * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread\n     * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this\n     */\n    if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {\n\n        __android_log_print(ANDROID_LOG_ERROR, \"pyjniusjni\", \"Error initializing pthread key\");\n    }\n    Android_JNI_SetupThread();\n\n    return JNI_VERSION_1_4;\n}\n\nJNIEnv* Android_JNI_GetEnv(void)\n{\n    /* From http://developer.android.com/guide/practices/jni.html\n     * All threads are Linux threads, scheduled by the kernel.\n     * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then\n     * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the\n     * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,\n     * and cannot make JNI calls.\n     * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the \"main\"\n     * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread\n     * is a no-op.\n     * Note: You can call this function any number of times for the same thread, there's no harm in it\n     */\n\n    JNIEnv *env;\n    int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);\n    if(status < 0) {\n        LOGE(\"failed to attach current thread\");\n        return 0;\n    }\n\n    /* From http://developer.android.com/guide/practices/jni.html\n     * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,\n     * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be\n     * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific\n     * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)\n     * Note: The destructor is not called unless the stored value is != NULL\n     * Note: You can call this function any number of times for the same thread, there's no harm in it\n     *       (except for some lost CPU cycles)\n     */\n    pthread_setspecific(mThreadKey, (void*) env);\n\n    return env;\n}\n\nstatic void Android_JNI_ThreadDestroyed(void* value)\n{\n    /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */\n    JNIEnv *env = (JNIEnv*) value;\n    if (env != NULL) {\n        (*mJavaVM)->DetachCurrentThread(mJavaVM);\n        pthread_setspecific(mThreadKey, NULL);\n    }\n}\n\nvoid *WebView_AndroidGetJNIEnv()\n{\n    return Android_JNI_GetEnv();\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/proguard-project.txt",
    "content": "# To enable ProGuard in your project, edit project.properties\n# to define the proguard.config property as described in that file.\n#\n# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the ProGuard\n# include property in project.properties.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/src/main/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java",
    "content": "package org.kivy.android;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Color;\nimport android.net.Uri;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.PowerManager;\nimport android.os.SystemClock;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.ViewGroup;\nimport android.view.ViewGroup.LayoutParams;\nimport android.webkit.CookieManager;\nimport android.webkit.WebBackForwardList;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\nimport android.widget.AbsoluteLayout;\nimport android.widget.ImageView;\nimport android.widget.Toast;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport org.renpy.android.ResourceManager;\n\npublic class PythonActivity extends Activity {\n    // This activity is modified from a mixture of the SDLActivity and\n    // PythonActivity in the SDL2 bootstrap, but removing all the SDL2\n    // specifics.\n\n    private static final String TAG = \"PythonActivity\";\n\n    public static PythonActivity mActivity = null;\n    public static boolean mOpenExternalLinksInBrowser = false;\n\n    /** If shared libraries (e.g. SDL or the native application) could not be loaded. */\n    public static boolean mBrokenLibraries;\n\n    protected static ViewGroup mLayout;\n    protected static WebView mWebView;\n\n    protected static Thread mPythonThread;\n\n    private ResourceManager resourceManager = null;\n    private Bundle mMetaData = null;\n    private PowerManager.WakeLock mWakeLock = null;\n\n    public String getAppRoot() {\n        String app_root = getFilesDir().getAbsolutePath() + \"/app\";\n        return app_root;\n    }\n\n    public String getEntryPoint(String search_dir) {\n        /* Get the main file (.pyc|.py) depending on if we\n         * have a compiled version or not.\n         */\n        List<String> entryPoints = new ArrayList<String>();\n        entryPoints.add(\"main.pyc\"); // python 3 compiled files\n        for (String value : entryPoints) {\n            File mainFile = new File(search_dir + \"/\" + value);\n            if (mainFile.exists()) {\n                return value;\n            }\n        }\n        return \"main.py\";\n    }\n\n    public static void initialize() {\n        // The static nature of the singleton and Android quirkyness force us to initialize\n        // everything here\n        // Otherwise, when exiting the app and returning to it, these variables *keep* their pre\n        // exit values\n        mWebView = null;\n        mLayout = null;\n        mBrokenLibraries = false;\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.v(TAG, \"My oncreate running\");\n        resourceManager = new ResourceManager(this);\n        super.onCreate(savedInstanceState);\n\n        this.mActivity = this;\n        this.showLoadingScreen();\n        new UnpackFilesTask().execute(getAppRoot());\n    }\n\n    private class UnpackFilesTask extends AsyncTask<String, Void, String> {\n        @Override\n        protected String doInBackground(String... params) {\n            File app_root_file = new File(params[0]);\n            Log.v(TAG, \"Ready to unpack\");\n            PythonUtil.unpackAsset(mActivity, \"private\", app_root_file, true);\n            PythonUtil.unpackPyBundle(\n                    mActivity,\n                    getApplicationInfo().nativeLibraryDir + \"/\" + \"libpybundle\",\n                    app_root_file,\n                    false);\n            return null;\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            Log.v(\"Python\", \"Device: \" + android.os.Build.DEVICE);\n            Log.v(\"Python\", \"Model: \" + android.os.Build.MODEL);\n\n            PythonActivity.initialize();\n\n            // Load shared libraries\n            String errorMsgBrokenLib = \"\";\n            try {\n                loadLibraries();\n            } catch (UnsatisfiedLinkError e) {\n                System.err.println(e.getMessage());\n                mBrokenLibraries = true;\n                errorMsgBrokenLib = e.getMessage();\n            } catch (Exception e) {\n                System.err.println(e.getMessage());\n                mBrokenLibraries = true;\n                errorMsgBrokenLib = e.getMessage();\n            }\n\n            if (mBrokenLibraries) {\n                AlertDialog.Builder dlgAlert = new AlertDialog.Builder(PythonActivity.mActivity);\n                dlgAlert.setMessage(\n                        \"An error occurred while trying to load the application libraries. Please try again and/or reinstall.\"\n                                + System.getProperty(\"line.separator\")\n                                + System.getProperty(\"line.separator\")\n                                + \"Error: \"\n                                + errorMsgBrokenLib);\n                dlgAlert.setTitle(\"Python Error\");\n                dlgAlert.setPositiveButton(\n                        \"Exit\",\n                        new DialogInterface.OnClickListener() {\n                            @Override\n                            public void onClick(DialogInterface dialog, int id) {\n                                // if this button is clicked, close current activity\n                                PythonActivity.mActivity.finish();\n                            }\n                        });\n                dlgAlert.setCancelable(false);\n                dlgAlert.create().show();\n\n                return;\n            }\n\n            // Set up the webview\n            String app_root_dir = getAppRoot();\n\n            mWebView = new WebView(PythonActivity.mActivity);\n            mWebView.getSettings().setJavaScriptEnabled(true);\n            mWebView.getSettings().setDomStorageEnabled(true);\n            mWebView.loadUrl(\"file:///android_asset/_load.html\");\n\n            mWebView.setLayoutParams(\n                    new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));\n            mWebView.setWebViewClient(\n                    new WebViewClient() {\n                        @Override\n                        public boolean shouldOverrideUrlLoading(WebView view, String url) {\n                            Uri u = Uri.parse(url);\n                            if (mOpenExternalLinksInBrowser) {\n                                if (!(u.getScheme().equals(\"file\")\n                                        || u.getHost().equals(\"127.0.0.1\"))) {\n                                    Intent i = new Intent(Intent.ACTION_VIEW, u);\n                                    startActivity(i);\n                                    return true;\n                                }\n                            }\n                            return false;\n                        }\n\n                        @Override\n                        public void onPageFinished(WebView view, String url) {\n                            CookieManager.getInstance().flush();\n                        }\n                    });\n            mLayout = new AbsoluteLayout(PythonActivity.mActivity);\n            mLayout.addView(mWebView);\n\n            setContentView(mLayout);\n\n            String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();\n            String entry_point = getEntryPoint(app_root_dir);\n\n            Log.v(TAG, \"Setting env vars for start.c and Python to use\");\n            PythonActivity.nativeSetenv(\"ANDROID_ENTRYPOINT\", entry_point);\n            PythonActivity.nativeSetenv(\"ANDROID_ARGUMENT\", app_root_dir);\n            PythonActivity.nativeSetenv(\"ANDROID_APP_PATH\", app_root_dir);\n            PythonActivity.nativeSetenv(\"ANDROID_PRIVATE\", mFilesDirectory);\n            PythonActivity.nativeSetenv(\"ANDROID_UNPACK\", app_root_dir);\n            PythonActivity.nativeSetenv(\"PYTHONHOME\", app_root_dir);\n            PythonActivity.nativeSetenv(\"PYTHONPATH\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n            PythonActivity.nativeSetenv(\"PYTHONOPTIMIZE\", \"2\");\n\n            try {\n                Log.v(TAG, \"Access to our meta-data...\");\n                mActivity.mMetaData =\n                        mActivity\n                                .getPackageManager()\n                                .getApplicationInfo(\n                                        mActivity.getPackageName(), PackageManager.GET_META_DATA)\n                                .metaData;\n\n                PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);\n                if (mActivity.mMetaData.getInt(\"wakelock\") == 1) {\n                    mActivity.mWakeLock =\n                            pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, \"Screen On\");\n                    mActivity.mWakeLock.acquire();\n                }\n            } catch (PackageManager.NameNotFoundException e) {\n            }\n\n            final Thread pythonThread = new Thread(new PythonMain(), \"PythonThread\");\n            PythonActivity.mPythonThread = pythonThread;\n            pythonThread.start();\n\n            final Thread wvThread = new Thread(new WebViewLoaderMain(), \"WvThread\");\n            wvThread.start();\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        Log.i(\"Destroy\", \"end of app\");\n        super.onDestroy();\n\n        // make sure all child threads (python_thread) are stopped\n        android.os.Process.killProcess(android.os.Process.myPid());\n    }\n\n    public void loadLibraries() {\n        String app_root = new String(getAppRoot());\n        File app_root_file = new File(app_root);\n        PythonUtil.loadLibraries(app_root_file, new File(getApplicationInfo().nativeLibraryDir));\n    }\n\n    public static void loadUrl(String url) {\n        class LoadUrl implements Runnable {\n            private String mUrl;\n\n            public LoadUrl(String url) {\n                mUrl = url;\n            }\n\n            public void run() {\n                mWebView.loadUrl(mUrl);\n            }\n        }\n\n        Log.i(TAG, \"Opening URL: \" + url);\n        mActivity.runOnUiThread(new LoadUrl(url));\n    }\n\n    public static void enableZoom() {\n        mActivity.runOnUiThread(\n                new Runnable() {\n                    @Override\n                    public void run() {\n                        mWebView.getSettings().setBuiltInZoomControls(true);\n                        mWebView.getSettings().setDisplayZoomControls(false);\n                    }\n                });\n    }\n\n    public static ViewGroup getLayout() {\n        return mLayout;\n    }\n\n    long lastBackClick = 0;\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n        // Check if the key event was the Back button\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            // Go back if there is web page history behind,\n            // but not to the start preloader\n            WebBackForwardList webViewBackForwardList = mWebView.copyBackForwardList();\n            if (webViewBackForwardList.getCurrentIndex() > 1) {\n                mWebView.goBack();\n                return true;\n            }\n\n            // If there's no web page history, bubble up to the default\n            // system behavior (probably exit the activity)\n            if (SystemClock.elapsedRealtime() - lastBackClick > 2000) {\n                lastBackClick = SystemClock.elapsedRealtime();\n                Toast.makeText(this, \"Tap again to close the app\", Toast.LENGTH_LONG).show();\n                return true;\n            }\n\n            lastBackClick = SystemClock.elapsedRealtime();\n        }\n\n        return super.onKeyDown(keyCode, event);\n    }\n\n    // loading screen implementation\n    public static ImageView mImageView = null;\n\n    public void removeLoadingScreen() {\n        runOnUiThread(\n                new Runnable() {\n                    public void run() {\n                        if (PythonActivity.mImageView != null\n                                && PythonActivity.mImageView.getParent() != null) {\n                            ((ViewGroup) PythonActivity.mImageView.getParent())\n                                    .removeView(PythonActivity.mImageView);\n                            PythonActivity.mImageView = null;\n                        }\n                    }\n                });\n    }\n\n    protected void showLoadingScreen() {\n        // load the bitmap\n        // 1. if the image is valid and we don't have layout yet, assign this bitmap\n        // as main view.\n        // 2. if we have a layout, just set it in the layout.\n        // 3. If we have an mImageView already, then do nothing because it will have\n        // already been made the content view or added to the layout.\n\n        if (mImageView == null) {\n            int presplashId = this.resourceManager.getIdentifier(\"presplash\", \"drawable\");\n            InputStream is = this.getResources().openRawResource(presplashId);\n            Bitmap bitmap = null;\n            try {\n                bitmap = BitmapFactory.decodeStream(is);\n            } finally {\n                try {\n                    is.close();\n                } catch (IOException e) {\n                }\n                ;\n            }\n\n            mImageView = new ImageView(this);\n            mImageView.setImageBitmap(bitmap);\n\n            /*\n             * Set the presplash loading screen background color\n             * https://developer.android.com/reference/android/graphics/Color.html\n             * Parse the color string, and return the corresponding color-int.\n             * If the string cannot be parsed, throws an IllegalArgumentException exception.\n             * Supported formats are: #RRGGBB #AARRGGBB or one of the following names:\n             * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',\n             * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',\n             * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.\n             */\n            String backgroundColor = resourceManager.getString(\"presplash_color\");\n            if (backgroundColor != null) {\n                try {\n                    mImageView.setBackgroundColor(Color.parseColor(backgroundColor));\n                } catch (IllegalArgumentException e) {\n                }\n            }\n            mImageView.setLayoutParams(\n                    new ViewGroup.LayoutParams(\n                            ViewGroup.LayoutParams.FILL_PARENT,\n                            ViewGroup.LayoutParams.FILL_PARENT));\n            mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);\n        }\n\n        if (mLayout == null) {\n            setContentView(mImageView);\n        } else if (PythonActivity.mImageView.getParent() == null) {\n            mLayout.addView(mImageView);\n        }\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onNewIntent\n    //\n\n    public interface NewIntentListener {\n        void onNewIntent(Intent intent);\n    }\n\n    private List<NewIntentListener> newIntentListeners = null;\n\n    public void registerNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null)\n            this.newIntentListeners =\n                    Collections.synchronizedList(new ArrayList<NewIntentListener>());\n        this.newIntentListeners.add(listener);\n    }\n\n    public void unregisterNewIntentListener(NewIntentListener listener) {\n        if (this.newIntentListeners == null) return;\n        this.newIntentListeners.remove(listener);\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        if (this.newIntentListeners == null) return;\n        this.onResume();\n        synchronized (this.newIntentListeners) {\n            Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();\n            while (iterator.hasNext()) {\n                (iterator.next()).onNewIntent(intent);\n            }\n        }\n    }\n\n    // ----------------------------------------------------------------------------\n    // Listener interface for onActivityResult\n    //\n\n    public interface ActivityResultListener {\n        void onActivityResult(int requestCode, int resultCode, Intent data);\n    }\n\n    private List<ActivityResultListener> activityResultListeners = null;\n\n    public void registerActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null)\n            this.activityResultListeners =\n                    Collections.synchronizedList(new ArrayList<ActivityResultListener>());\n        this.activityResultListeners.add(listener);\n    }\n\n    public void unregisterActivityResultListener(ActivityResultListener listener) {\n        if (this.activityResultListeners == null) return;\n        this.activityResultListeners.remove(listener);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {\n        if (this.activityResultListeners == null) return;\n        this.onResume();\n        synchronized (this.activityResultListeners) {\n            Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();\n            while (iterator.hasNext())\n                (iterator.next()).onActivityResult(requestCode, resultCode, intent);\n        }\n    }\n\n    public static void start_service(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, true);\n    }\n\n    public static void start_service_not_as_foreground(\n            String serviceTitle, String serviceDescription, String pythonServiceArgument) {\n        _do_start_service(serviceTitle, serviceDescription, pythonServiceArgument, false);\n    }\n\n    public static void _do_start_service(\n            String serviceTitle,\n            String serviceDescription,\n            String pythonServiceArgument,\n            boolean showForegroundNotification) {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();\n        String app_root_dir = PythonActivity.mActivity.getAppRoot();\n        String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + \"/service\");\n        serviceIntent.putExtra(\"androidPrivate\", argument);\n        serviceIntent.putExtra(\"androidArgument\", app_root_dir);\n        serviceIntent.putExtra(\"serviceEntrypoint\", \"service/\" + entry_point);\n        serviceIntent.putExtra(\"pythonName\", \"python\");\n        serviceIntent.putExtra(\"pythonHome\", app_root_dir);\n        serviceIntent.putExtra(\"pythonPath\", app_root_dir + \":\" + app_root_dir + \"/lib\");\n        serviceIntent.putExtra(\n                \"serviceStartAsForeground\", (showForegroundNotification ? \"true\" : \"false\"));\n        serviceIntent.putExtra(\"serviceTitle\", serviceTitle);\n        serviceIntent.putExtra(\"serviceDescription\", serviceDescription);\n        serviceIntent.putExtra(\"pythonServiceArgument\", pythonServiceArgument);\n        PythonActivity.mActivity.startService(serviceIntent);\n    }\n\n    public static void stop_service() {\n        Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);\n        PythonActivity.mActivity.stopService(serviceIntent);\n    }\n\n    public static native void nativeSetenv(String name, String value);\n\n    public static native int nativeInit(Object arguments);\n\n    /**\n     * Used by android.permissions p4a module to register a call back after requesting runtime\n     * permissions\n     */\n    public interface PermissionsCallback {\n        void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);\n    }\n\n    private PermissionsCallback permissionCallback;\n    private boolean havePermissionsCallback = false;\n\n    public void addPermissionsCallback(PermissionsCallback callback) {\n        permissionCallback = callback;\n        havePermissionsCallback = true;\n        Log.v(TAG, \"addPermissionsCallback(): Added callback for onRequestPermissionsResult\");\n    }\n\n    @Override\n    public void onRequestPermissionsResult(\n            int requestCode, String[] permissions, int[] grantResults) {\n        Log.v(TAG, \"onRequestPermissionsResult()\");\n        if (havePermissionsCallback) {\n            Log.v(TAG, \"onRequestPermissionsResult passed to callback\");\n            permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        }\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n\n    /** Used by android.permissions p4a module to check a permission */\n    public boolean checkCurrentPermission(String permission) {\n        if (android.os.Build.VERSION.SDK_INT < 23) return true;\n\n        try {\n            java.lang.reflect.Method methodCheckPermission =\n                    Activity.class.getMethod(\"checkSelfPermission\", String.class);\n            Object resultObj = methodCheckPermission.invoke(this, permission);\n            int result = Integer.parseInt(resultObj.toString());\n            if (result == PackageManager.PERMISSION_GRANTED) return true;\n        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {\n        }\n        return false;\n    }\n\n    /** Used by android.permissions p4a module to request runtime permissions */\n    public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) {\n        if (android.os.Build.VERSION.SDK_INT < 23) return;\n        try {\n            java.lang.reflect.Method methodRequestPermission =\n                    Activity.class.getMethod(\"requestPermissions\", String[].class, int.class);\n            methodRequestPermission.invoke(this, permissions, requestCode);\n        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {\n        }\n    }\n\n    public void requestPermissions(String[] permissions) {\n        requestPermissionsWithRequestCode(permissions, 1);\n    }\n}\n\nclass PythonMain implements Runnable {\n    @Override\n    public void run() {\n        PythonActivity.nativeInit(new String[0]);\n    }\n}\n\nclass WebViewLoaderMain implements Runnable {\n    @Override\n    public void run() {\n        WebViewLoader.testConnection();\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/src/main/jniLibs/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/src/main/res/drawable/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/src/main/res/layout/main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"fill_parent\"\n    >\n<TextView  \n    android:layout_width=\"fill_parent\" \n    android:layout_height=\"wrap_content\" \n    android:text=\"Hello World, SDLActivity\"\n    />\n</LinearLayout>\n\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/src/main/res/mipmap/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/src/main/res/mipmap-anydpi-v26/.gitkeep",
    "content": ""
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">SDL App</string>\n    <string name=\"private_version\">0.1</string>\n</resources>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      android:versionCode=\"{{ args.numeric_version }}\"\n      android:versionName=\"{{ args.version }}\"\n      android:installLocation=\"auto\">\n\n    <supports-screens\n            android:smallScreens=\"true\"\n            android:normalScreens=\"true\"\n            android:largeScreens=\"true\"\n            android:anyDensity=\"true\"\n            {% if args.min_sdk_version >= 9 %}\n            android:xlargeScreens=\"true\"\n            {% endif %}\n    />\n\n    <!-- Android 2.3.3 -->\n    <uses-sdk android:minSdkVersion=\"{{ args.min_sdk_version }}\" android:targetSdkVersion=\"{{ android_api }}\" />\n\n    <!-- Allow writing to external storage -->\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    {% for perm in args.permissions %}\n        <uses-permission android:name=\"{{ perm.name }}\"{% if perm.maxSdkVersion %} android:maxSdkVersion=\"{{ perm.maxSdkVersion }}\"{% endif %}{% if perm.usesPermissionFlags %} android:usesPermissionFlags=\"{{ perm.usesPermissionFlags }}\"{% endif %} />\n    {% endfor %}\n\n    {% if args.wakelock %}\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    {% endif %}\n\n    {% if args.billing_pubkey %}\n    <uses-permission android:name=\"com.android.vending.BILLING\" />\n    {% endif %}\n\n    <!-- Create a Java class extending SDLActivity and place it in a\n         directory under src matching the package, e.g.\n         \tsrc/com/gamemaker/game/MyGame.java\n\n         then replace \"SDLActivity\" with the name of your class (e.g. \"MyGame\")\n         in the XML below.\n\n         An example Java class can be found in README-android.txt\n    -->\n    <application android:label=\"@string/app_name\"\n                 android:icon=\"@mipmap/icon\"\n                 android:allowBackup=\"{{ args.allow_backup }}\"\n                 {% if args.backup_rules %}android:fullBackupContent=\"@xml/{{ args.backup_rules }}\"{% endif %}\n                 android:theme=\"{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}\"\n                 android:hardwareAccelerated=\"true\"\n                 android:usesCleartextTraffic=\"true\"\n                 android:extractNativeLibs=\"true\"\n                 {% if debug %}android:debuggable=\"true\"{% endif %}\n                 >\n        {% for l in args.android_used_libs %}\n        <uses-library android:name=\"{{ l }}\" />\n        {% endfor %}\n        {% for m in args.meta_data %}\n        <meta-data android:name=\"{{ m.split('=', 1)[0] }}\" android:value=\"{{ m.split('=', 1)[-1] }}\"/>{% endfor %}\n        <meta-data android:name=\"wakelock\" android:value=\"{% if args.wakelock %}1{% else %}0{% endif %}\"/>\n\n        <activity android:name=\"org.kivy.android.PythonActivity\"\n                  android:label=\"@string/app_name\"\n                  android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|uiMode{% if args.min_sdk_version >= 8 %}|uiMode{% endif %}{% if args.min_sdk_version >= 13 %}|screenSize|smallestScreenSize{% endif %}{% if args.min_sdk_version >= 17 %}|layoutDirection{% endif %}{% if args.min_sdk_version >= 24 %}|density{% endif %}\"\n                  android:screenOrientation=\"{{ args.manifest_orientation }}\"\n                  android:exported=\"true\"\n                  android:theme=\"@style/KivySupportCutout\"\n                  {% if args.activity_launch_mode %}\n                  android:launchMode=\"{{ args.activity_launch_mode }}\"\n                  {% endif %}\n                  android:windowSoftInputMode=\"adjustResize\"\n                  >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            {%- if args.intent_filters -%}\n            {{- args.intent_filters -}}\n            {%- endif -%}\n        </activity>\n\n        {% if service %}\n        <service android:name=\"org.kivy.android.PythonService\"\n                 android:process=\":pythonservice\" />\n        {% endif %}\n        {% for name, foreground_type in service_data %}\n        <service android:name=\"{{ args.package }}.Service{{ name|capitalize }}\"\n                 {% if foreground_type %}\n                 android:foregroundServiceType=\"{{ foreground_type }}\"\n                 {% endif %}\n                 android:process=\":service_{{ name }}\" />\n        {% endfor %}\n\n        {% if args.billing_pubkey %}\n        <service android:name=\"org.kivy.android.billing.BillingReceiver\"\n                 android:process=\":pythonbilling\" />\n        <receiver android:name=\"org.kivy.android.billing.BillingReceiver\"\n                  android:process=\":pythonbillingreceiver\"\n                  android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"com.android.vending.billing.IN_APP_NOTIFY\" />\n                <action android:name=\"com.android.vending.billing.RESPONSE_CODE\" />\n                <action android:name=\"com.android.vending.billing.PURCHASE_STATE_CHANGED\" />\n            </intent-filter>\n        </receiver>\n        {% endif %}\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/templates/WebViewLoader.tmpl.java",
    "content": "package org.kivy.android;\n\nimport android.util.Log;\n\nimport java.io.IOException;\nimport java.net.Socket;\nimport java.net.InetSocketAddress;\n\nimport android.os.SystemClock;\n\nimport android.os.Handler;\n\nimport org.kivy.android.PythonActivity;\n\npublic class WebViewLoader {\n    private static final String TAG = \"WebViewLoader\";\n\n    public static void testConnection() {\n\n        while (true) {\n            if (WebViewLoader.pingHost(\"localhost\", {{ args.port }}, 100)) {\n                Log.v(TAG, \"Successfully pinged localhost:{{ args.port }}\");\n                Handler mainHandler = new Handler(PythonActivity.mActivity.getMainLooper());\n                Runnable myRunnable = new Runnable() {\n                        @Override\n                        public void run() {\n                            PythonActivity.mActivity.loadUrl(\"http://127.0.0.1:{{ args.port }}/\");\n                            Log.v(TAG, \"Loaded webserver in webview\");\n                        }\n                    };\n                mainHandler.post(myRunnable);\n                break;\n\n            } else {\n                Log.v(TAG, \"Could not ping localhost:{{ args.port }}\");\n                try {\n                    Thread.sleep(100);\n                } catch(InterruptedException e) {\n                    Log.v(TAG, \"InterruptedException occurred when sleeping\");\n                }\n            }\n        }\n    }\n\n    public static boolean pingHost(String host, int port, int timeout) {\n        Socket socket = new Socket();\n        try {\n            socket.connect(new InetSocketAddress(host, port), timeout);\n            socket.close();\n            return true;\n        } catch (IOException e) {\n            try {socket.close();} catch (IOException f) {return false;}\n            return false; // Either timeout or unreachable or failed DNS lookup.\n        }\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/templates/strings.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"KivySupportCutout\">\n        <item name=\"android:windowNoTitle\">true</item>\n        <!-- Display cutout is an area on some devices that extends into the display surface -->\n        {% if args.display_cutout != 'never'%}\n        <item name=\"android:windowLayoutInDisplayCutoutMode\">{{ args.display_cutout }}</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">true</item>\n        <item name=\"android:windowFullscreen\">true</item>\n        {% endif %}\n    </style>\n    <string name=\"app_name\">{{ args.name }}</string>\n    <string name=\"private_version\">{{ private_version }}</string>\n    <string name=\"presplash_color\">{{ args.presplash_color }}</string>\n</resources>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/templates/test/build.tmpl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- This should be changed to the name of your project -->\n<project name=\"{{ versioned_name }}\" default=\"help\">\n\n    <!-- The local.properties file is created and updated by the 'android' tool.\n         It contains the path to the SDK. It should *NOT* be checked into\n         Version Control Systems. -->\n    <property file=\"local.properties\" />\n\n    <!-- The ant.properties file can be created by you. It is only edited by the\n         'android' tool to add properties to it.\n         This is the place to change some Ant specific build properties.\n         Here are some properties you may want to change/update:\n\n         source.dir\n             The name of the source directory. Default is 'src'.\n         out.dir\n             The name of the output directory. Default is 'bin'.\n\n         For other overridable properties, look at the beginning of the rules\n         files in the SDK, at tools/ant/build.xml\n\n         Properties related to the SDK location or the project target should\n         be updated using the 'android' tool with the 'update' action.\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems.\n\n         -->\n    <property file=\"ant.properties\" />\n\n    <!-- if sdk.dir was not set from one of the property file, then\n         get it from the ANDROID_HOME env var.\n         This must be done before we load project.properties since\n         the proguard config can use sdk.dir -->\n    <property environment=\"env\" />\n    <condition property=\"sdk.dir\" value=\"${env.ANDROID_HOME}\">\n        <isset property=\"env.ANDROID_HOME\" />\n    </condition>\n\n    <!-- The project.properties file is created and updated by the 'android'\n         tool, as well as ADT.\n\n         This contains project specific properties such as project target, and library\n         dependencies. Lower level build properties are stored in ant.properties\n         (or in .classpath for Eclipse projects).\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems. -->\n    <loadproperties srcFile=\"project.properties\" />\n\n    <!-- quick check on sdk.dir -->\n    <fail\n            message=\"sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable.\"\n            unless=\"sdk.dir\"\n    />\n\n    <!--\n        Import per project custom build rules if present at the root of the project.\n        This is the place to put custom intermediary targets such as:\n            -pre-build\n            -pre-compile\n            -post-compile (This is typically used for code obfuscation.\n                           Compiled code location: ${out.classes.absolute.dir}\n                           If this is not done in place, override ${out.dex.input.absolute.dir})\n            -post-package\n            -post-build\n            -pre-clean\n    -->\n    <import file=\"custom_rules.xml\" optional=\"true\" />\n\n    <!-- Import the actual build file.\n\n         To customize existing targets, there are two options:\n         - Customize only one target:\n             - copy/paste the target into this file, *before* the\n               <import> task.\n             - customize it to your needs.\n         - Customize the whole content of build.xml\n             - copy/paste the content of the rules files (minus the top node)\n               into this file, replacing the <import> task.\n             - customize to your needs.\n\n         ***********************\n         ****** IMPORTANT ******\n         ***********************\n         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,\n         in order to avoid having your file be overridden by tools such as \"android update project\"\n    -->\n    <!-- version-tag: 1 -->\n    <import file=\"${sdk.dir}/tools/ant/build.xml\" />\n\n</project>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/templates/test/build.xml.tmpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- This should be changed to the name of your project -->\n<project name=\"{{ versioned_name }}\" default=\"help\">\n\n    <!-- The local.properties file is created and updated by the 'android' tool.\n         It contains the path to the SDK. It should *NOT* be checked into\n         Version Control Systems. -->\n    <property file=\"local.properties\" />\n\n    <!-- The ant.properties file can be created by you. It is only edited by the\n         'android' tool to add properties to it.\n         This is the place to change some Ant specific build properties.\n         Here are some properties you may want to change/update:\n\n         source.dir\n             The name of the source directory. Default is 'src'.\n         out.dir\n             The name of the output directory. Default is 'bin'.\n\n         For other overridable properties, look at the beginning of the rules\n         files in the SDK, at tools/ant/build.xml\n\n         Properties related to the SDK location or the project target should\n         be updated using the 'android' tool with the 'update' action.\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems.\n\n         -->\n    <property file=\"ant.properties\" />\n\n    <!-- if sdk.dir was not set from one of the property file, then\n         get it from the ANDROID_HOME env var.\n         This must be done before we load project.properties since\n         the proguard config can use sdk.dir -->\n    <property environment=\"env\" />\n    <condition property=\"sdk.dir\" value=\"${env.ANDROID_HOME}\">\n        <isset property=\"env.ANDROID_HOME\" />\n    </condition>\n\n    <!-- The project.properties file is created and updated by the 'android'\n         tool, as well as ADT.\n\n         This contains project specific properties such as project target, and library\n         dependencies. Lower level build properties are stored in ant.properties\n         (or in .classpath for Eclipse projects).\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems. -->\n    <loadproperties srcFile=\"project.properties\" />\n\n    <!-- quick check on sdk.dir -->\n    <fail\n            message=\"sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable.\"\n            unless=\"sdk.dir\"\n    />\n\n    <!--\n        Import per project custom build rules if present at the root of the project.\n        This is the place to put custom intermediary targets such as:\n            -pre-build\n            -pre-compile\n            -post-compile (This is typically used for code obfuscation.\n                           Compiled code location: ${out.classes.absolute.dir}\n                           If this is not done in place, override ${out.dex.input.absolute.dir})\n            -post-package\n            -post-build\n            -pre-clean\n    -->\n    <import file=\"custom_rules.xml\" optional=\"true\" />\n\n    <!-- Import the actual build file.\n\n         To customize existing targets, there are two options:\n         - Customize only one target:\n             - copy/paste the target into this file, *before* the\n               <import> task.\n             - customize it to your needs.\n         - Customize the whole content of build.xml\n             - copy/paste the content of the rules files (minus the top node)\n               into this file, replacing the <import> task.\n             - customize to your needs.\n\n         ***********************\n         ****** IMPORTANT ******\n         ***********************\n         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,\n         in order to avoid having your file be overridden by tools such as \"android update project\"\n    -->\n    <!-- version-tag: 1 -->\n    <import file=\"${sdk.dir}/tools/ant/build.xml\" />\n\n</project>\n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/webview_includes/_load.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <link href=\"_loading_style.css\" rel=\"stylesheet\" type=\"text/css\">\n        <title>\n            Python WebView loader\n        </title>\n    </head>\n    <body>\n      <div id=\"load\" style=\"height:100%\">\n          <div class=\"loader\" >Loading...</div>\n      </div>\n    </body>\n</html> \n"
  },
  {
    "path": "pythonforandroid/bootstraps/webview/build/webview_includes/_loading_style.css",
    "content": "\nh1 {\n    font-size: 30px;\n    color: blue;\n    font-weight: bold;\n    text-align:center;\n}\n\nh2 {\n    text-align:center;\n}\n\nbutton {\n    margin-left: auto;\n    margin-right: auto;\n    display: block;\n    margin-top: 50px;\n    font-size: 30px;\n}\n\n\n/* Loader from http://projects.lukehaas.me/css-loaders/#load1 */\n\n.loader,\n.loader:before,\n.loader:after {\n    background: #aaaaff;\n    -webkit-animation: load1 1s infinite ease-in-out;\n    animation: load1 1s infinite ease-in-out;\n    width: 1em;\n    height: 4em;\n}\n.loader:before,\n.loader:after {\n    position: absolute;\n    top: 0;\n    content: '';\n}\n.loader:before {\n    left: -1.5em;\n}\n.loader {\n    text-indent: -9999em;\n    margin: 8em auto;\n    position: relative;\n    font-size: 11px;\n    -webkit-animation-delay: -0.16s;\n    animation-delay: -0.16s;\n}\n.loader:after {\n    left: 1.5em;\n    -webkit-animation-delay: -0.32s;\n    animation-delay: -0.32s;\n}\n@-webkit-keyframes load1 {\n    0%,\n    80%,\n    100% {\n        box-shadow: 0 0 #aaaaff;\n        height: 4em;\n    }\n    40% {\n        box-shadow: 0 -2em #aaaaff;\n        height: 5em;\n    }\n}\n@keyframes load1 {\n    0%,\n    80%,\n    100% {\n        box-shadow: 0 0 #aaaaff;\n        height: 4em;\n    }\n    40% {\n        box-shadow: 0 -2em #aaaaff;\n        height: 5em;\n    }\n}\n"
  },
  {
    "path": "pythonforandroid/build.py",
    "content": "from contextlib import suppress\nimport copy\nimport glob\nimport os\nimport json\nimport tempfile\nfrom os import environ\nfrom os.path import (\n    abspath, join, realpath, dirname, expanduser, exists, basename\n)\nimport re\nimport shutil\nimport subprocess\n\nimport sh\n\nfrom packaging.utils import parse_wheel_filename\nfrom packaging.requirements import Requirement\n\nfrom pythonforandroid.androidndk import AndroidNDK\nfrom pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64\nfrom pythonforandroid.logger import (info, warning, info_notify, info_main, shprint, Out_Style, Out_Fore)\nfrom pythonforandroid.pythonpackage import get_package_name\nfrom pythonforandroid.recipe import CythonRecipe, Recipe\nfrom pythonforandroid.recommendations import (\n    check_ndk_version, check_target_api, check_ndk_api,\n    RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)\nfrom pythonforandroid.util import (\n    current_directory, ensure_dir,\n    BuildInterruptingException, rmdir\n)\n\n\ndef get_targets(sdk_dir):\n    if exists(join(sdk_dir, 'cmdline-tools', 'latest', 'bin', 'avdmanager')):\n        avdmanager = sh.Command(join(sdk_dir, 'cmdline-tools', 'latest', 'bin', 'avdmanager'))\n        targets = avdmanager('list', 'target').split('\\n')\n\n    elif exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):\n        avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))\n        targets = avdmanager('list', 'target').split('\\n')\n    elif exists(join(sdk_dir, 'tools', 'android')):\n        android = sh.Command(join(sdk_dir, 'tools', 'android'))\n        targets = android('list').split('\\n')\n    else:\n        raise BuildInterruptingException(\n            'Could not find `android` or `sdkmanager` binaries in Android SDK',\n            instructions='Make sure the path to the Android SDK is correct')\n    return targets\n\n\ndef get_available_apis(sdk_dir):\n    targets = get_targets(sdk_dir)\n    apis = [s for s in targets if re.match(r'^ *API level: ', s)]\n    apis = [re.findall(r'[0-9]+', s) for s in apis]\n    apis = [int(s[0]) for s in apis if s]\n    return apis\n\n\nclass Context:\n    '''A build context. If anything will be built, an instance this class\n    will be instantiated and used to hold all the build state.'''\n\n    # Whether to make a debug or release build\n    build_as_debuggable = False\n\n    # Whether to strip debug symbols in `.so` files\n    with_debug_symbols = False\n\n    env = environ.copy()\n    # the filepath of toolchain.py\n    root_dir = None\n    # the root dir where builds and dists will be stored\n    storage_dir = None\n\n    # in which bootstraps are copied for building\n    # and recipes are built\n    build_dir = None\n\n    distribution = None\n    \"\"\"The Distribution object representing the current build target location.\"\"\"\n\n    # the Android project folder where everything ends up\n    dist_dir = None\n\n    # Whether setup.py or similar should be used if present:\n    use_setup_py = False\n\n    ccache = None  # whether to use ccache\n\n    ndk = None\n\n    bootstrap = None\n    bootstrap_build_dir = None\n\n    recipe_build_order = None  # Will hold the list of all built recipes\n\n    python_modules = None  # Will hold resolved pure python packages\n\n    symlink_bootstrap_files = False  # If True, will symlink instead of copying during build\n\n    java_build_tool = 'auto'\n\n    @property\n    def packages_path(self):\n        '''Where packages are downloaded before being unpacked'''\n        return join(self.storage_dir, 'packages')\n\n    @property\n    def templates_dir(self):\n        return join(self.root_dir, 'templates')\n\n    @property\n    def libs_dir(self):\n        \"\"\"\n        where Android libs are cached after build\n        but before being placed in dists\n        \"\"\"\n        # Was previously hardcoded as self.build_dir/libs\n        directory = join(self.build_dir, 'libs_collections',\n                         self.bootstrap.distribution.name)\n        ensure_dir(directory)\n        return directory\n\n    @property\n    def javaclass_dir(self):\n        # Was previously hardcoded as self.build_dir/java\n        directory = join(self.build_dir, 'javaclasses',\n                         self.bootstrap.distribution.name)\n        ensure_dir(directory)\n        return directory\n\n    @property\n    def aars_dir(self):\n        directory = join(self.build_dir, 'aars', self.bootstrap.distribution.name)\n        ensure_dir(directory)\n        return directory\n\n    @property\n    def python_installs_dir(self):\n        directory = join(self.build_dir, 'python-installs')\n        ensure_dir(directory)\n        return directory\n\n    def get_python_install_dir(self, arch):\n        return join(self.python_installs_dir, self.bootstrap.distribution.name, arch)\n\n    def setup_dirs(self, storage_dir):\n        '''Calculates all the storage and build dirs, and makes sure\n        the directories exist where necessary.'''\n        self.storage_dir = expanduser(storage_dir)\n        if ' ' in self.storage_dir:\n            raise ValueError('storage dir path cannot contain spaces, please '\n                             'specify a path with --storage-dir')\n        self.build_dir = join(self.storage_dir, 'build')\n        self.dist_dir = join(self.storage_dir, 'dists')\n\n    def ensure_dirs(self):\n        ensure_dir(self.storage_dir)\n        ensure_dir(self.build_dir)\n        ensure_dir(self.dist_dir)\n        ensure_dir(join(self.build_dir, 'bootstrap_builds'))\n        ensure_dir(join(self.build_dir, 'other_builds'))\n\n    @property\n    def android_api(self):\n        '''The Android API being targeted.'''\n        if self._android_api is None:\n            raise ValueError('Tried to access android_api but it has not '\n                             'been set - this should not happen, something '\n                             'went wrong!')\n        return self._android_api\n\n    @android_api.setter\n    def android_api(self, value):\n        self._android_api = value\n\n    @property\n    def ndk_api(self):\n        '''The API number compile against'''\n        if self._ndk_api is None:\n            raise ValueError('Tried to access ndk_api but it has not '\n                             'been set - this should not happen, something '\n                             'went wrong!')\n        return self._ndk_api\n\n    @ndk_api.setter\n    def ndk_api(self, value):\n        self._ndk_api = value\n\n    @property\n    def sdk_dir(self):\n        '''The path to the Android SDK.'''\n        if self._sdk_dir is None:\n            raise ValueError('Tried to access sdk_dir but it has not '\n                             'been set - this should not happen, something '\n                             'went wrong!')\n        return self._sdk_dir\n\n    @sdk_dir.setter\n    def sdk_dir(self, value):\n        self._sdk_dir = value\n\n    @property\n    def ndk_dir(self):\n        '''The path to the Android NDK.'''\n        if self._ndk_dir is None:\n            raise ValueError('Tried to access ndk_dir but it has not '\n                             'been set - this should not happen, something '\n                             'went wrong!')\n        return self._ndk_dir\n\n    @ndk_dir.setter\n    def ndk_dir(self, value):\n        self._ndk_dir = value\n\n    def prepare_build_environment(self,\n                                  user_sdk_dir,\n                                  user_ndk_dir,\n                                  user_android_api,\n                                  user_ndk_api):\n        '''Checks that build dependencies exist and sets internal variables\n        for the Android SDK etc.\n\n        ..warning:: This *must* be called before trying any build stuff\n\n        '''\n\n        self.ensure_dirs()\n\n        if self._build_env_prepared:\n            return\n\n        # Work out where the Android SDK is\n        sdk_dir = None\n        if user_sdk_dir:\n            sdk_dir = user_sdk_dir\n        # This is the old P4A-specific var\n        if sdk_dir is None:\n            sdk_dir = environ.get('ANDROIDSDK', None)\n        # This seems used more conventionally\n        if sdk_dir is None:\n            sdk_dir = environ.get('ANDROID_HOME', None)\n        # Checks in the buildozer SDK dir, useful for debug tests of p4a\n        if sdk_dir is None:\n            possible_dirs = glob.glob(expanduser(join(\n                '~', '.buildozer', 'android', 'platform', 'android-sdk-*')))\n            possible_dirs = [d for d in possible_dirs if not\n                             d.endswith(('.bz2', '.gz'))]\n            if possible_dirs:\n                info('Found possible SDK dirs in buildozer dir: {}'.format(\n                    ', '.join(d.split(os.sep)[-1] for d in possible_dirs)))\n                info('Will attempt to use SDK at {}'.format(possible_dirs[0]))\n                warning('This SDK lookup is intended for debug only, if you '\n                        'use python-for-android much you should probably '\n                        'maintain your own SDK download.')\n                sdk_dir = possible_dirs[0]\n        if sdk_dir is None:\n            raise BuildInterruptingException('Android SDK dir was not specified, exiting.')\n        self.sdk_dir = realpath(sdk_dir)\n\n        # Check what Android API we're using\n        android_api = None\n        if user_android_api:\n            android_api = user_android_api\n            info('Getting Android API version from user argument: {}'.format(android_api))\n        elif 'ANDROIDAPI' in environ:\n            android_api = environ['ANDROIDAPI']\n            info('Found Android API target in $ANDROIDAPI: {}'.format(android_api))\n        else:\n            info('Android API target was not set manually, using '\n                 'the default of {}'.format(RECOMMENDED_TARGET_API))\n            android_api = RECOMMENDED_TARGET_API\n        android_api = int(android_api)\n        self.android_api = android_api\n\n        for arch in self.archs:\n            # Maybe We could remove this one in a near future (ARMv5 is definitely old)\n            check_target_api(android_api, arch)\n        apis = get_available_apis(self.sdk_dir)\n        info('Available Android APIs are ({})'.format(\n            ', '.join(map(str, apis))))\n        if android_api in apis:\n            info(('Requested API target {} is available, '\n                  'continuing.').format(android_api))\n        else:\n            raise BuildInterruptingException(\n                ('Requested API target {} is not available, install '\n                 'it with the SDK android tool.').format(android_api))\n\n        # Find the Android NDK\n        # Could also use ANDROID_NDK, but doesn't look like many tools use this\n        ndk_dir = None\n        if user_ndk_dir:\n            ndk_dir = user_ndk_dir\n            info('Getting NDK dir from from user argument')\n        if ndk_dir is None:  # The old P4A-specific dir\n            ndk_dir = environ.get('ANDROIDNDK', None)\n            if ndk_dir is not None:\n                info('Found NDK dir in $ANDROIDNDK: {}'.format(ndk_dir))\n        if ndk_dir is None:  # Apparently the most common convention\n            ndk_dir = environ.get('NDK_HOME', None)\n            if ndk_dir is not None:\n                info('Found NDK dir in $NDK_HOME: {}'.format(ndk_dir))\n        if ndk_dir is None:  # Another convention (with maven?)\n            ndk_dir = environ.get('ANDROID_NDK_HOME', None)\n            if ndk_dir is not None:\n                info('Found NDK dir in $ANDROID_NDK_HOME: {}'.format(ndk_dir))\n        if ndk_dir is None:  # Checks in the buildozer NDK dir, useful\n            #                # for debug tests of p4a\n            possible_dirs = glob.glob(expanduser(join(\n                '~', '.buildozer', 'android', 'platform', 'android-ndk-r*')))\n            if possible_dirs:\n                info('Found possible NDK dirs in buildozer dir: {}'.format(\n                    ', '.join(d.split(os.sep)[-1] for d in possible_dirs)))\n                info('Will attempt to use NDK at {}'.format(possible_dirs[0]))\n                warning('This NDK lookup is intended for debug only, if you '\n                        'use python-for-android much you should probably '\n                        'maintain your own NDK download.')\n                ndk_dir = possible_dirs[0]\n        if ndk_dir is None:\n            raise BuildInterruptingException('Android NDK dir was not specified')\n        self.ndk_dir = realpath(ndk_dir)\n        check_ndk_version(ndk_dir)\n\n        ndk_api = None\n        if user_ndk_api:\n            ndk_api = user_ndk_api\n            info('Getting NDK API version (i.e. minimum supported API) from user argument')\n        elif 'NDKAPI' in environ:\n            ndk_api = environ.get('NDKAPI', None)\n            info('Found Android API target in $NDKAPI')\n        else:\n            ndk_api = min(self.android_api, RECOMMENDED_NDK_API)\n            warning('NDK API target was not set manually, using '\n                    'the default of {} = min(android-api={}, default ndk-api={})'.format(\n                        ndk_api, self.android_api, RECOMMENDED_NDK_API))\n        ndk_api = int(ndk_api)\n        self.ndk_api = ndk_api\n\n        check_ndk_api(ndk_api, self.android_api)\n\n        self.ndk = AndroidNDK(self.ndk_dir)\n\n        # path to some tools\n        self.ccache = shutil.which(\"ccache\")\n        if not self.ccache:\n            info('ccache is missing, the build will not be optimized in the '\n                 'future.')\n        try:\n            subprocess.check_output([\n                \"python3\", \"-m\", \"cython\", \"--help\",\n            ])\n        except subprocess.CalledProcessError:\n            warning('Cython for python3 missing. If you are building for '\n                    ' a python 3 target (which is the default)'\n                    ' then THINGS WILL BREAK.')\n\n        self.env[\"PATH\"] = \":\".join(\n            [\n                self.ndk.llvm_bin_dir,\n                self.ndk_dir,\n                f\"{self.sdk_dir}/tools\",\n                environ.get(\"PATH\"),\n            ]\n        )\n\n    def __init__(self):\n        self.include_dirs = []\n\n        self._build_env_prepared = False\n\n        self._sdk_dir = None\n        self._ndk_dir = None\n        self._android_api = None\n        self._ndk_api = None\n        self.ndk = None\n\n        self.local_recipes = None\n        self.copy_libs = False\n\n        self.activity_class_name = u'org.kivy.android.PythonActivity'\n        self.service_class_name = u'org.kivy.android.PythonService'\n\n        # this list should contain all Archs, it is pruned later\n        self.archs = (\n            ArchARM(self),\n            ArchARMv7_a(self),\n            Archx86(self),\n            Archx86_64(self),\n            ArchAarch_64(self),\n            )\n\n        self.root_dir = realpath(dirname(__file__))\n\n        # remove the most obvious flags that can break the compilation\n        self.env.pop(\"LDFLAGS\", None)\n        self.env.pop(\"ARCHFLAGS\", None)\n        self.env.pop(\"CFLAGS\", None)\n\n        self.python_recipe = None  # Set by TargetPythonRecipe\n\n    def set_archs(self, arch_names):\n        all_archs = self.archs\n        new_archs = set()\n        for name in arch_names:\n            matching = [arch for arch in all_archs if arch.arch == name]\n            for match in matching:\n                new_archs.add(match)\n        self.archs = list(new_archs)\n        if not self.archs:\n            raise BuildInterruptingException('Asked to compile for no Archs, so failing.')\n        info('Will compile for the following archs: {}'.format(\n            ', '.join(arch.arch for arch in self.archs)))\n\n    def prepare_bootstrap(self, bootstrap):\n        if not bootstrap:\n            raise TypeError(\"None is not allowed for bootstrap\")\n        bootstrap.ctx = self\n        self.bootstrap = bootstrap\n        self.bootstrap.prepare_build_dir()\n        self.bootstrap_build_dir = self.bootstrap.build_dir\n\n    def prepare_dist(self):\n        self.bootstrap.prepare_dist_dir()\n\n    def get_site_packages_dir(self, arch):\n        '''Returns the location of site-packages in the python-install build\n        dir.\n        '''\n        return self.get_python_install_dir(arch.arch)\n\n    def get_libs_dir(self, arch):\n        '''The libs dir for a given arch.'''\n        ensure_dir(join(self.libs_dir, arch))\n        return join(self.libs_dir, arch)\n\n    def has_lib(self, arch, lib):\n        return exists(join(self.get_libs_dir(arch), lib))\n\n    def has_package(self, name, arch=None):\n        # If this is a file path, it'll need special handling:\n        if (name.find(\"/\") >= 0 or name.find(\"\\\\\") >= 0) and \\\n                name.find(\"://\") < 0:  # (:// would indicate an url)\n            if not os.path.exists(name):\n                # Non-existing dir, cannot look this up.\n                return False\n            try:\n                name = get_package_name(os.path.abspath(name))\n            except ValueError:\n                # Failed to look up any meaningful name.\n                return False\n\n        # normalize name to remove version tags\n        try:\n            name = Requirement(name).name\n        except Exception:\n            pass\n\n        # Try to look up recipe by name:\n        try:\n            recipe = Recipe.get_recipe(name, self)\n        except ValueError:\n            pass\n        else:\n            name = getattr(recipe, 'site_packages_name', None) or name\n        name = name.replace('.', '/')\n        site_packages_dir = self.get_site_packages_dir(arch)\n        return (exists(join(site_packages_dir, name)) or\n                exists(join(site_packages_dir, name + '.py')) or\n                exists(join(site_packages_dir, name + '.pyc')) or\n                exists(join(site_packages_dir, name + '.so')) or\n                glob.glob(join(site_packages_dir, name + '-*.egg')))\n\n    def not_has_package(self, name, arch=None):\n        return not self.has_package(name, arch)\n\n\ndef build_recipes(build_order, python_modules, ctx, project_dir,\n                  ignore_project_setup_py=False\n                 ):\n    # Put recipes in correct build order\n    info_notify(\"Recipe build order is {}\".format(build_order))\n    if python_modules:\n        python_modules = sorted(set(python_modules))\n        info_notify(\n            ('The requirements ({}) were not found as recipes, they will be '\n             'installed with pip.').format(', '.join(python_modules)))\n\n    recipes = [Recipe.get_recipe(name, ctx) for name in build_order]\n\n    # download is arch independent\n    info_main('# Downloading recipes ')\n    for recipe in recipes:\n        recipe.download_if_necessary()\n\n    for arch in ctx.archs:\n        info_main('# Building all recipes for arch {}'.format(arch.arch))\n\n        info_main('# Unpacking recipes')\n        for recipe in recipes:\n            ensure_dir(recipe.get_build_container_dir(arch.arch))\n            recipe.prepare_build_dir(arch.arch)\n\n        info_main('# Prebuilding recipes')\n        # 2) prebuild packages\n        for recipe in recipes:\n            info_main('Prebuilding {} for {}'.format(recipe.name, arch.arch))\n            recipe.prebuild_arch(arch)\n            recipe.apply_patches(arch)\n\n        # 3) build packages\n        info_main('# Building recipes')\n        for recipe in recipes:\n            info_main('Building {} for {}'.format(recipe.name, arch.arch))\n            if recipe.should_build(arch):\n                recipe.build_arch(arch)\n            else:\n                info('{} said it is already built, skipping'\n                     .format(recipe.name))\n            recipe.install_libraries(arch)\n\n        # 4) biglink everything\n        info_main('# Biglinking object files')\n        if not ctx.python_recipe:\n            biglink(ctx, arch)\n        else:\n            warning(\n                \"Context's python recipe found, \"\n                \"skipping biglink (will this work?)\"\n            )\n\n        # 5) postbuild packages\n        info_main('# Postbuilding recipes')\n        for recipe in recipes:\n            info_main('Postbuilding {} for {}'.format(recipe.name, arch.arch))\n            recipe.postbuild_arch(arch)\n\n    info_main('# Installing pure Python modules')\n    for arch in ctx.archs:\n        run_pymodules_install(\n            ctx, arch, python_modules, project_dir,\n            ignore_setup_py=ignore_project_setup_py\n        )\n\n\ndef project_has_setup_py(project_dir):\n    return (project_dir is not None and\n            (exists(join(project_dir, \"setup.py\")) or\n             exists(join(project_dir, \"pyproject.toml\"))\n            ))\n\n\ndef run_setuppy_install(ctx, project_dir, env=None, arch=None):\n    env = env or {}\n\n    with current_directory(project_dir):\n        info('got setup.py or similar, running project install. ' +\n             '(disable this behavior with --ignore-setup-py)')\n\n        # Compute & output the constraints we will use:\n        info('Contents that will be used for constraints.txt:')\n        constraints = subprocess.check_output([\n            join(\n                ctx.build_dir, \"venv\", \"bin\", \"pip\"\n            ),\n            \"freeze\"\n        ], env=copy.copy(env))\n        with suppress(AttributeError):\n            constraints = constraints.decode(\"utf-8\", \"replace\")\n        info(constraints)\n\n        # Make sure all packages found are fixed in version\n        # by writing a constraint file, to avoid recipes being\n        # upgraded & reinstalled:\n        with open('._tmp_p4a_recipe_constraints.txt', 'wb') as fileh:\n            fileh.write(constraints.encode(\"utf-8\", \"replace\"))\n        try:\n\n            info('Populating venv\\'s site-packages with '\n                 'ctx.get_site_packages_dir()...')\n\n            # Copy dist contents into site-packages for discovery.\n            # Why this is needed:\n            # --target is somewhat evil and messes with discovery of\n            # packages in PYTHONPATH if that also includes the target\n            # folder. So we need to use the regular virtualenv\n            # site-packages folder instead.\n            # Reference:\n            # https://github.com/pypa/pip/issues/6223\n            ctx_site_packages_dir = os.path.normpath(\n                os.path.abspath(ctx.get_site_packages_dir(arch))\n            )\n            venv_site_packages_dir = os.path.normpath(os.path.join(\n                ctx.build_dir, \"venv\", \"lib\", [\n                    f for f in os.listdir(os.path.join(\n                        ctx.build_dir, \"venv\", \"lib\"\n                    )) if f.startswith(\"python\")\n                ][0], \"site-packages\"\n            ))\n            copied_over_contents = []\n            for f in os.listdir(ctx_site_packages_dir):\n                full_path = os.path.join(ctx_site_packages_dir, f)\n                if not os.path.exists(os.path.join(\n                            venv_site_packages_dir, f\n                        )):\n                    if os.path.isdir(full_path):\n                        shutil.copytree(full_path, os.path.join(\n                            venv_site_packages_dir, f\n                        ))\n                    else:\n                        shutil.copy2(full_path, os.path.join(\n                            venv_site_packages_dir, f\n                        ))\n                    copied_over_contents.append(f)\n\n            # Get listing of virtualenv's site-packages, to see the\n            # newly added things afterwards & copy them back into\n            # the distribution folder / build context site-packages:\n            previous_venv_contents = os.listdir(\n                venv_site_packages_dir\n            )\n\n            # Actually run setup.py:\n            info('Launching package install...')\n            shprint(sh.bash, '-c', (\n                \"'\" + join(\n                    ctx.build_dir, \"venv\", \"bin\", \"pip\"\n                ).replace(\"'\", \"'\\\"'\\\"'\") + \"' \" +\n                \"install -c ._tmp_p4a_recipe_constraints.txt -v .\"\n            ).format(ctx.get_site_packages_dir(arch).\n                     replace(\"'\", \"'\\\"'\\\"'\")),\n                    _env=copy.copy(env))\n\n            # Go over all new additions and copy them back:\n            info('Copying additions resulting from setup.py back '\n                 'into ctx.get_site_packages_dir()...')\n            new_venv_additions = []\n            for f in (set(os.listdir(venv_site_packages_dir)) -\n                      set(previous_venv_contents)):\n                new_venv_additions.append(f)\n                full_path = os.path.join(venv_site_packages_dir, f)\n                if os.path.isdir(full_path):\n                    shutil.copytree(full_path, os.path.join(\n                        ctx_site_packages_dir, f\n                    ))\n                else:\n                    shutil.copy2(full_path, os.path.join(\n                        ctx_site_packages_dir, f\n                    ))\n\n            # Undo all the changes we did to the venv-site packages:\n            info('Reverting additions to '\n                 'virtualenv\\'s site-packages...')\n            for f in set(copied_over_contents + new_venv_additions):\n                full_path = os.path.join(venv_site_packages_dir, f)\n                if os.path.isdir(full_path):\n                    rmdir(full_path)\n                else:\n                    os.remove(full_path)\n        finally:\n            os.remove(\"._tmp_p4a_recipe_constraints.txt\")\n\n\ndef is_wheel_platform_independent(whl_name):\n    name, version, build, tags = parse_wheel_filename(whl_name)\n    return all(tag.platform == \"any\" for tag in tags)\n\n\ndef process_python_modules(ctx, modules):\n    \"\"\"Use pip --dry-run to resolve dependencies and filter for pure-Python packages\n    \"\"\"\n    modules = list(modules)\n    build_order = list(ctx.recipe_build_order)\n\n    _requirement_names = []\n    processed_modules = []\n\n    for module in modules+build_order:\n        try:\n            # we need to normalize names\n            # eg Requests>=2.0 becomes requests\n            _requirement_names.append(Requirement(module).name)\n        except Exception:\n            # name parsing failed; skip processing this module via pip\n            processed_modules.append(module)\n            if module in modules:\n                modules.remove(module)\n\n    if len(processed_modules) > 0:\n        warning(f'Ignored by module resolver : {processed_modules}')\n\n    # preserve the original module list\n    processed_modules.extend(modules)\n\n    if len(modules) == 0:\n        return processed_modules\n\n    # temp file for pip report\n    fd, path = tempfile.mkstemp()\n    os.close(fd)\n\n    # setup hostpython recipe\n    env = environ.copy()\n    try:\n        host_recipe = Recipe.get_recipe(\"hostpython3\", ctx)\n        _python_path = host_recipe.get_path_to_python()\n        libdir = glob.glob(join(_python_path, \"build\", \"lib*\"))\n        env['PYTHONPATH'] = host_recipe.site_dir + \":\" + join(\n            _python_path, \"Modules\") + \":\" + (libdir[0] if libdir else \"\")\n        pip = host_recipe.pip\n    except Exception:\n        # hostpython3 non available so we use system pip (like in tests)\n        pip = sh.Command(\"pip\")\n\n    try:\n        shprint(\n            pip, 'install', *modules,\n            '--dry-run', '--break-system-packages', '--ignore-installed',\n            '--report', path, '-q', _env=env\n        )\n    except Exception as e:\n        warning(f\"Auto module resolution failed: {e}\")\n        return processed_modules\n\n    with open(path, \"r\") as f:\n        try:\n            report = json.load(f)\n        except Exception:\n            report = {}\n\n    os.remove(path)\n\n    if \"install\" not in report.keys():\n        # pip changed json reporting format?\n        warning(\"Auto module resolution failed: invalid json!\")\n        return processed_modules\n\n    info('Extra resolved pure python dependencies :')\n\n    ignored_str = \" (ignored)\"\n    # did we find any non pure python package?\n    any_not_pure_python = False\n\n    # just for style\n    info(\" \")\n    for module in report[\"install\"]:\n\n        mname = module[\"metadata\"][\"name\"]\n        mver = module[\"metadata\"][\"version\"]\n        filename = basename(module[\"download_info\"][\"url\"])\n        pure_python = True\n\n        if (filename.endswith(\".whl\") and not is_wheel_platform_independent(filename)):\n            any_not_pure_python = True\n            pure_python = False\n\n        # does this module matches any recipe name?\n        if mname.lower().replace(\"-\", \"_\") in _requirement_names:\n            continue\n\n        color = Out_Fore.GREEN if pure_python else Out_Fore.RED\n        ignored = \"\" if pure_python else ignored_str\n\n        info(\n            f\"  {color}{mname}{Out_Fore.WHITE} : \"\n            f\"{Out_Style.BRIGHT}{mver}{Out_Style.RESET_ALL}\"\n            f\"{ignored}\"\n        )\n\n        if pure_python:\n            processed_modules.append(f\"{mname}=={mver}\")\n    info(\" \")\n\n    if any_not_pure_python:\n        warning(\"Some packages were ignored because they are not pure Python.\")\n        warning(\"To install the ignored packages, explicitly list them in your requirements file.\")\n\n    return processed_modules\n\n\ndef run_pymodules_install(ctx, arch, modules, project_dir=None,\n                          ignore_setup_py=False):\n    \"\"\" This function will take care of all non-recipe things, by:\n\n        1. Processing them from --requirements (the modules argument)\n           and installing them\n\n        2. Installing the user project/app itself via setup.py if\n           ignore_setup_py=True\n\n    \"\"\"\n\n    info('*** PYTHON PACKAGE / PROJECT INSTALL STAGE FOR ARCH: {} ***'.format(arch))\n\n    modules = process_python_modules(ctx, modules)\n\n    modules = [m for m in modules if ctx.not_has_package(m, arch)]\n\n    # We change current working directory later, so this has to be an absolute\n    # path or `None` in case that we didn't supply the `project_dir` via kwargs\n    project_dir = abspath(project_dir) if project_dir else None\n\n    # Bail out if no python deps and no setup.py to process:\n    if not modules and (\n            ignore_setup_py or\n            not project_has_setup_py(project_dir)\n            ):\n        info('No Python modules and no setup.py to process, skipping')\n        return\n\n    # Output messages about what we're going to do:\n    if modules:\n        info(\n            \"The requirements ({}) don\\'t have recipes, attempting to \"\n            \"install them with pip\".format(', '.join(modules))\n        )\n        info(\n            \"If this fails, it may mean that the module has compiled \"\n            \"components and needs a recipe.\"\n        )\n    if project_has_setup_py(project_dir) and not ignore_setup_py:\n        info(\n            \"Will process project install, if it fails then the \"\n            \"project may not be compatible for Android install.\"\n        )\n\n    # Use our hostpython to create the virtualenv\n    host_python = sh.Command(ctx.hostpython)\n    with current_directory(join(ctx.build_dir)):\n        shprint(host_python, '-m', 'venv', 'venv')\n\n        # Prepare base environment and upgrade pip:\n        base_env = dict(copy.copy(os.environ))\n        base_env[\"PYTHONPATH\"] = ctx.get_site_packages_dir(arch)\n        info('Upgrade pip to latest version')\n        shprint(sh.bash, '-c', (\n            \"source venv/bin/activate && pip install -U pip\"\n        ), _env=copy.copy(base_env))\n\n        # Install Cython in case modules need it to build:\n        info('Install Cython in case one of the modules needs it to build')\n        shprint(sh.bash, '-c', (\n            \"venv/bin/pip install Cython\"\n        ), _env=copy.copy(base_env))\n\n        # Get environment variables for build (with CC/compiler set):\n        standard_recipe = CythonRecipe()\n        standard_recipe.ctx = ctx\n        # (note: following line enables explicit -lpython... linker options)\n        standard_recipe.call_hostpython_via_targetpython = False\n        recipe_env = standard_recipe.get_recipe_env(ctx.archs[0])\n        env = copy.copy(base_env)\n        env.update(recipe_env)\n\n        # Make sure our build package dir is available, and the virtualenv\n        # site packages come FIRST (so the proper pip version is used):\n        env[\"PYTHONPATH\"] += \":\" + ctx.get_site_packages_dir(arch)\n        env[\"PYTHONPATH\"] = os.path.abspath(join(\n            ctx.build_dir, \"venv\", \"lib\",\n            \"python\" + ctx.python_recipe.major_minor_version_string,\n            \"site-packages\")) + \":\" + env[\"PYTHONPATH\"]\n\n        # Install the manually specified requirements first:\n        if not modules:\n            info('There are no Python modules to install, skipping')\n        else:\n            info('Creating a requirements.txt file for the Python modules')\n            with open('requirements.txt', 'w') as fileh:\n                for module in modules:\n                    key = 'VERSION_' + module\n                    if key in environ:\n                        line = '{}=={}\\n'.format(module, environ[key])\n                    else:\n                        line = '{}\\n'.format(module)\n                    fileh.write(line)\n\n            info('Installing Python modules with pip')\n            info(\n                \"IF THIS FAILS, THE MODULES MAY NEED A RECIPE. \"\n                \"A reason for this is often modules compiling \"\n                \"native code that is unaware of Android cross-compilation \"\n                \"and does not work without additional \"\n                \"changes / workarounds.\"\n            )\n\n            shprint(sh.bash, '-c', (\n                \"venv/bin/pip \" +\n                \"install -v --target '{0}' --no-deps -r requirements.txt\"\n            ).format(ctx.get_site_packages_dir(arch).replace(\"'\", \"'\\\"'\\\"'\")),\n                    _env=copy.copy(env))\n\n        # Afterwards, run setup.py if present:\n        if project_has_setup_py(project_dir) and not ignore_setup_py:\n            run_setuppy_install(ctx, project_dir, env, arch)\n        elif not ignore_setup_py:\n            info(\"No setup.py found in project directory: \" + str(project_dir))\n\n        # Strip object files after potential Cython or native code builds:\n        if not ctx.with_debug_symbols:\n            standard_recipe.strip_object_files(\n                arch, env, build_dir=ctx.build_dir\n            )\n\n\ndef biglink(ctx, arch):\n    # First, collate object files from each recipe\n    info('Collating object files from each recipe')\n    obj_dir = join(ctx.bootstrap.build_dir, 'collated_objects')\n    ensure_dir(obj_dir)\n    recipes = [Recipe.get_recipe(name, ctx) for name in ctx.recipe_build_order]\n    for recipe in recipes:\n        recipe_obj_dir = join(recipe.get_build_container_dir(arch.arch),\n                              'objects_{}'.format(recipe.name))\n        if not exists(recipe_obj_dir):\n            info('{} recipe has no biglinkable files dir, skipping'\n                 .format(recipe.name))\n            continue\n        files = glob.glob(join(recipe_obj_dir, '*'))\n        if not len(files):\n            info('{} recipe has no biglinkable files, skipping'\n                 .format(recipe.name))\n            continue\n        info('{} recipe has object files, copying'.format(recipe.name))\n        files.append(obj_dir)\n        shprint(sh.cp, '-r', *files)\n\n    env = arch.get_env()\n    env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(\n        join(ctx.bootstrap.build_dir, 'obj', 'local', arch.arch))\n\n    if not len(glob.glob(join(obj_dir, '*'))):\n        info('There seem to be no libraries to biglink, skipping.')\n        return\n    info('Biglinking')\n    info('target {}'.format(join(ctx.get_libs_dir(arch.arch),\n                                 'libpymodules.so')))\n    do_biglink = copylibs_function if ctx.copy_libs else biglink_function\n\n    # Move to the directory containing crtstart_so.o and crtend_so.o\n    # This is necessary with newer NDKs? A gcc bug?\n    with current_directory(arch.ndk_lib_dir):\n        do_biglink(\n            join(ctx.get_libs_dir(arch.arch), 'libpymodules.so'),\n            obj_dir.split(' '),\n            extra_link_dirs=[join(ctx.bootstrap.build_dir,\n                                  'obj', 'local', arch.arch),\n                             os.path.abspath('.')],\n            env=env)\n\n\ndef biglink_function(soname, objs_paths, extra_link_dirs=None, env=None):\n    if extra_link_dirs is None:\n        extra_link_dirs = []\n    print('objs_paths are', objs_paths)\n    sofiles = []\n\n    for directory in objs_paths:\n        for fn in os.listdir(directory):\n            fn = os.path.join(directory, fn)\n\n            if not fn.endswith(\".so.o\"):\n                continue\n            if not os.path.exists(fn[:-2] + \".libs\"):\n                continue\n\n            sofiles.append(fn[:-2])\n\n    # The raw argument list.\n    args = []\n\n    for fn in sofiles:\n        afn = fn + \".o\"\n        libsfn = fn + \".libs\"\n\n        args.append(afn)\n        with open(libsfn) as fd:\n            data = fd.read()\n            args.extend(data.split(\" \"))\n\n    unique_args = []\n    while args:\n        a = args.pop()\n        if a in ('-L', ):\n            continue\n        if a not in unique_args:\n            unique_args.insert(0, a)\n\n    for dir in extra_link_dirs:\n        link = '-L{}'.format(dir)\n        if link not in unique_args:\n            unique_args.append(link)\n\n    cc_name = env['CC']\n    cc = sh.Command(cc_name.split()[0])\n    cc = cc.bake(*cc_name.split()[1:])\n\n    shprint(cc, '-shared', '-O3', '-o', soname, *unique_args, _env=env)\n\n\ndef copylibs_function(soname, objs_paths, extra_link_dirs=None, env=None):\n    if extra_link_dirs is None:\n        extra_link_dirs = []\n    print('objs_paths are', objs_paths)\n\n    re_needso = re.compile(r'^.*\\(NEEDED\\)\\s+Shared library: \\[lib(.*)\\.so\\]\\s*$')\n    blacklist_libs = (\n        'c',\n        'stdc++',\n        'dl',\n        'python2.7',\n        'sdl',\n        'sdl_image',\n        'sdl_ttf',\n        'z',\n        'm',\n        'GLESv2',\n        'jpeg',\n        'png',\n        'log',\n\n        # bootstrap takes care of sdl2 libs (if applicable)\n        'SDL2',\n        'SDL2_ttf',\n        'SDL2_image',\n        'SDL2_mixer',\n        'SDL3',\n        'SDL3_ttf',\n        'SDL3_image',\n        'SDL3_mixer',\n    )\n    found_libs = []\n    sofiles = []\n    if env and 'READELF' in env:\n        readelf = env['READELF']\n    elif 'READELF' in os.environ:\n        readelf = os.environ['READELF']\n    else:\n        readelf = shutil.which('readelf').strip()\n    readelf = sh.Command(readelf).bake('-d')\n\n    dest = dirname(soname)\n\n    for directory in objs_paths:\n        for fn in os.listdir(directory):\n            fn = join(directory, fn)\n\n            if not fn.endswith('.libs'):\n                continue\n\n            dirfn = fn[:-1] + 'dirs'\n            if not exists(dirfn):\n                continue\n\n            with open(fn) as f:\n                libs = f.read().strip().split(' ')\n                needed_libs = [lib for lib in libs\n                               if lib and\n                               lib not in blacklist_libs and\n                               lib not in found_libs]\n\n            while needed_libs:\n                print('need libs:\\n\\t' + '\\n\\t'.join(needed_libs))\n\n                start_needed_libs = needed_libs[:]\n                found_sofiles = []\n\n                with open(dirfn) as f:\n                    libdirs = f.read().split()\n                    for libdir in libdirs:\n                        if not needed_libs:\n                            break\n\n                        if libdir == dest:\n                            # don't need to copy from dest to dest!\n                            continue\n\n                        libdir = libdir.strip()\n                        print('scanning', libdir)\n                        for lib in needed_libs[:]:\n                            if lib in found_libs:\n                                continue\n\n                            if lib.endswith('.a'):\n                                needed_libs.remove(lib)\n                                found_libs.append(lib)\n                                continue\n\n                            lib_a = 'lib' + lib + '.a'\n                            libpath_a = join(libdir, lib_a)\n                            lib_so = 'lib' + lib + '.so'\n                            libpath_so = join(libdir, lib_so)\n                            plain_so = lib + '.so'\n                            plainpath_so = join(libdir, plain_so)\n\n                            sopath = None\n                            if exists(libpath_so):\n                                sopath = libpath_so\n                            elif exists(plainpath_so):\n                                sopath = plainpath_so\n\n                            if sopath:\n                                print('found', lib, 'in', libdir)\n                                found_sofiles.append(sopath)\n                                needed_libs.remove(lib)\n                                found_libs.append(lib)\n                                continue\n\n                            if exists(libpath_a):\n                                print('found', lib, '(static) in', libdir)\n                                needed_libs.remove(lib)\n                                found_libs.append(lib)\n                                continue\n\n                for sofile in found_sofiles:\n                    print('scanning dependencies for', sofile)\n                    out = readelf(sofile)\n                    for line in out.splitlines():\n                        needso = re_needso.match(line)\n                        if needso:\n                            lib = needso.group(1)\n                            if (lib not in needed_libs\n                                    and lib not in found_libs\n                                    and lib not in blacklist_libs):\n                                needed_libs.append(needso.group(1))\n\n                sofiles += found_sofiles\n\n                if needed_libs == start_needed_libs:\n                    raise RuntimeError(\n                            'Failed to locate needed libraries!\\n\\t' +\n                            '\\n\\t'.join(needed_libs))\n\n    print('Copying libraries')\n    shprint(sh.cp, *sofiles, dest)\n"
  },
  {
    "path": "pythonforandroid/checkdependencies.py",
    "content": "from importlib import import_module\nfrom os import environ\nimport sys\n\nfrom packaging.version import Version\n\nfrom pythonforandroid.prerequisites import (\n    check_and_install_default_prerequisites,\n)\n\n\ndef check_python_dependencies():\n    \"\"\"\n    Check if the Python requirements are installed. This must appears\n    before other imports because otherwise they're imported elsewhere.\n\n    Using the ok check instead of failing immediately so that all\n    errors are printed at once.\n    \"\"\"\n\n    ok = True\n\n    modules = [(\"colorama\", \"0.3.3\"), \"appdirs\", (\"sh\", \"1.10\"), \"jinja2\"]\n\n    for module in modules:\n        if isinstance(module, tuple):\n            module, version = module\n        else:\n            version = None\n\n        try:\n            import_module(module)\n        except ImportError:\n            if version is None:\n                print(\n                    \"ERROR: The {} Python module could not be found, please \"\n                    \"install it.\".format(module)\n                )\n                ok = False\n            else:\n                print(\n                    \"ERROR: The {} Python module could not be found, \"\n                    \"please install version {} or higher\".format(\n                        module, version\n                    )\n                )\n                ok = False\n        else:\n            if version is None:\n                continue\n            try:\n                cur_ver = sys.modules[module].__version__\n            except AttributeError:  # this is sometimes not available\n                continue\n            if Version(cur_ver) < Version(version):\n                print(\n                    \"ERROR: {} version is {}, but python-for-android needs \"\n                    \"at least {}.\".format(module, cur_ver, version)\n                )\n                ok = False\n\n    if not ok:\n        print(\"python-for-android is exiting due to the errors logged above\")\n        exit(1)\n\n\ndef check():\n    if not environ.get(\"SKIP_PREREQUISITES_CHECK\", \"0\") == \"1\":\n        check_and_install_default_prerequisites()\n    check_python_dependencies()\n"
  },
  {
    "path": "pythonforandroid/distribution.py",
    "content": "import json\nimport glob\nfrom os.path import exists, join\n\nfrom pythonforandroid.logger import (\n    debug, info, info_notify, warning, Err_Style, Err_Fore)\nfrom pythonforandroid.util import (\n    current_directory, BuildInterruptingException, rmdir)\n\n\nclass Distribution:\n    '''State container for information about a distribution (i.e. an\n    Android project).\n\n    This is separate from a Bootstrap because the Bootstrap is\n    concerned with building and populating the dist directory, whereas\n    the dist itself could also come from e.g. a binary download.\n    '''\n    ctx = None\n\n    name = None  # A name identifying the dist. May not be None.\n    needs_build = False  # Whether the dist needs compiling\n    url = None\n    dist_dir = None  # Where the dist dir ultimately is. Should not be None.\n    ndk_api = None\n\n    archs = []\n    '''The names of the arch targets that the dist is built for.'''\n\n    recipes = []\n\n    description = ''  # A long description\n\n    def __init__(self, ctx):\n        self.ctx = ctx\n\n    def __str__(self):\n        return '<Distribution: name {} with recipes ({})>'.format(\n            # self.name, ', '.join([recipe.name for recipe in self.recipes]))\n            self.name, ', '.join(self.recipes))\n\n    def __repr__(self):\n        return str(self)\n\n    @classmethod\n    def get_distribution(\n            cls,\n            ctx,\n            *,\n            archs,  # required keyword argument: there is no sensible default\n            name=None,\n            recipes=[],\n            ndk_api=None,\n            force_build=False,\n            extra_dist_dirs=[],\n            require_perfect_match=False,\n            allow_replace_dist=True\n    ):\n        '''Takes information about the distribution, and decides what kind of\n        distribution it will be.\n\n        If parameters conflict (e.g. a dist with that name already\n        exists, but doesn't have the right set of recipes),\n        an error is thrown.\n\n        Parameters\n        ----------\n        name : str\n            The name of the distribution. If a dist with this name already '\n            exists, it will be used.\n        ndk_api : int\n            The NDK API to compile against, included in the dist because it cannot\n            be changed later during APK packaging.\n        archs : list\n            The target architectures list to compile against, included in the dist because\n            it cannot be changed later during APK packaging.\n        recipes : list\n            The recipes that the distribution must contain.\n        force_download: bool\n            If True, only downloaded dists are considered.\n        force_build : bool\n            If True, the dist is forced to be built locally.\n        extra_dist_dirs : list\n            Any extra directories in which to search for dists.\n        require_perfect_match : bool\n            If True, will only match distributions with precisely the\n            correct set of recipes.\n        allow_replace_dist : bool\n            If True, will allow an existing dist with the specified\n            name but incompatible requirements to be overwritten by\n            a new one with the current requirements.\n        '''\n\n        possible_dists = Distribution.get_distributions(ctx)\n        debug(f\"All possible dists: {possible_dists}\")\n\n        # Will hold dists that would be built in the same folder as an existing dist\n        folder_match_dist = None\n\n        # 0) Check if a dist with that name and architecture already exists\n        if name is not None and name:\n            possible_dists = [\n                d for d in possible_dists if\n                (d.name == name) and all(arch_name in d.archs for arch_name in archs)]\n            debug(f\"Dist matching name and arch: {possible_dists}\")\n\n            if possible_dists:\n                # There should only be one folder with a given dist name *and* arch.\n                # We could check that here, but for compatibility let's let it slide\n                # and just record the details of one of them. We only use this data to\n                # possibly fail the build later, so it doesn't really matter if there\n                # was more than one clash.\n                folder_match_dist = possible_dists[0]\n\n        # 1) Check if any existing dists meet the requirements\n        _possible_dists = []\n        for dist in possible_dists:\n            if (\n                ndk_api is not None and dist.ndk_api != ndk_api\n            ) or dist.ndk_api is None:\n                debug(\n                    f\"dist {dist} failed to match ndk_api, target api {ndk_api}, dist api {dist.ndk_api}\"\n                )\n                continue\n            for recipe in recipes:\n                if recipe not in dist.recipes:\n                    debug(f\"dist {dist} missing recipe {recipe}\")\n                    break\n            else:\n                _possible_dists.append(dist)\n        possible_dists = _possible_dists\n        debug(f\"Dist matching ndk_api and recipe: {possible_dists}\")\n\n        if possible_dists:\n            info('Of the existing distributions, the following meet '\n                 'the given requirements:')\n            pretty_log_dists(possible_dists)\n        else:\n            info('No existing dists meet the given requirements!')\n\n        # If any dist has perfect recipes, arch and NDK API, return it\n        for dist in possible_dists:\n            if force_build:\n                debug(\"Skipping dist due to forced build\")\n                continue\n            if ndk_api is not None and dist.ndk_api != ndk_api:\n                debug(\"Skipping dist due to ndk_api mismatch\")\n                continue\n            if not all(arch_name in dist.archs for arch_name in archs):\n                debug(\"Skipping dist due to arch mismatch\")\n                continue\n            if (set(dist.recipes) == set(recipes) or\n                (set(recipes).issubset(set(dist.recipes)) and\n                 not require_perfect_match)):\n                info_notify('{} has compatible recipes, using this one'\n                            .format(dist.name))\n                return dist\n            else:\n                debug(\n                    f\"Skipping dist due to recipes mismatch, expected {set(recipes)}, actual {set(dist.recipes)}\"\n                )\n\n        # If there was a name match but we didn't already choose it,\n        # then the existing dist is incompatible with the requested\n        # configuration and the build cannot continue\n        if folder_match_dist is not None and not allow_replace_dist:\n            raise BuildInterruptingException(\n                'Asked for dist with name {name} with recipes ({req_recipes}) and '\n                'NDK API {req_ndk_api}, but a dist '\n                'with this name already exists and has either incompatible recipes '\n                '({dist_recipes}) or NDK API {dist_ndk_api}'.format(\n                    name=name,\n                    req_ndk_api=ndk_api,\n                    dist_ndk_api=folder_match_dist.ndk_api,\n                    req_recipes=', '.join(recipes),\n                    dist_recipes=', '.join(folder_match_dist.recipes)))\n\n        assert len(possible_dists) < 2\n\n        # If we got this far, we need to build a new dist\n        dist = Distribution(ctx)\n        dist.needs_build = True\n\n        if not name:\n            filen = 'unnamed_dist_{}'\n            i = 1\n            while exists(join(ctx.dist_dir, filen.format(i))):\n                i += 1\n            name = filen.format(i)\n\n        dist.name = name\n        dist.dist_dir = join(\n            ctx.dist_dir,\n            name)\n        dist.recipes = recipes\n        dist.ndk_api = ctx.ndk_api\n        dist.archs = archs\n\n        return dist\n\n    def folder_exists(self):\n        return exists(self.dist_dir)\n\n    def delete(self):\n        rmdir(self.dist_dir)\n\n    @classmethod\n    def get_distributions(cls, ctx, extra_dist_dirs=[]):\n        '''Returns all the distributions found locally.'''\n        if extra_dist_dirs:\n            raise BuildInterruptingException(\n                'extra_dist_dirs argument to get_distributions '\n                'is not yet implemented')\n        dist_dir = ctx.dist_dir\n        folders = glob.glob(join(dist_dir, '*'))\n        for dir in extra_dist_dirs:\n            folders.extend(glob.glob(join(dir, '*')))\n\n        dists = []\n        for folder in folders:\n            if exists(join(folder, 'dist_info.json')):\n                with open(join(folder, 'dist_info.json')) as fileh:\n                    dist_info = json.load(fileh)\n                dist = cls(ctx)\n                dist.name = dist_info['dist_name']\n                dist.dist_dir = folder\n                dist.needs_build = False\n                dist.recipes = dist_info['recipes']\n                if 'archs' in dist_info:\n                    dist.archs = dist_info['archs']\n                if 'ndk_api' in dist_info:\n                    dist.ndk_api = dist_info['ndk_api']\n                else:\n                    dist.ndk_api = None\n                    warning(\n                        \"Distribution {distname}: ({distdir}) has been \"\n                        \"built with an unknown api target, ignoring it, \"\n                        \"you might want to delete it\".format(\n                            distname=dist.name,\n                            distdir=dist.dist_dir\n                        )\n                    )\n                dists.append(dist)\n        return dists\n\n    def save_info(self, dirn):\n        '''\n        Save information about the distribution in its dist_dir.\n        '''\n        with current_directory(dirn):\n            info('Saving distribution info')\n            with open('dist_info.json', 'w') as fileh:\n                json.dump({'dist_name': self.name,\n                           'bootstrap': self.ctx.bootstrap.name,\n                           'archs': [arch.arch for arch in self.ctx.archs],\n                           'ndk_api': self.ctx.ndk_api,\n                           'use_setup_py': self.ctx.use_setup_py,\n                           'recipes': self.ctx.recipe_build_order + self.ctx.python_modules,\n                           'hostpython': self.ctx.hostpython,\n                           'python_version': self.ctx.python_recipe.major_minor_version_string},\n                          fileh)\n\n\ndef pretty_log_dists(dists, log_func=info):\n    infos = []\n    for dist in dists:\n        ndk_api = 'unknown' if dist.ndk_api is None else dist.ndk_api\n        infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: min API {ndk_api}, '\n                     'includes recipes ({Fore.GREEN}{recipes}'\n                     '{Style.RESET_ALL}), built for archs ({Fore.BLUE}'\n                     '{archs}{Style.RESET_ALL})'.format(\n                         ndk_api=ndk_api,\n                         name=dist.name, recipes=', '.join(dist.recipes),\n                         archs=', '.join(dist.archs) if dist.archs else 'UNKNOWN',\n                         Fore=Err_Fore, Style=Err_Style))\n\n    for line in infos:\n        log_func('\\t' + line)\n"
  },
  {
    "path": "pythonforandroid/entrypoints.py",
    "content": "from pythonforandroid.recommendations import check_python_version\nfrom pythonforandroid.util import BuildInterruptingException, handle_build_exception\n\n\ndef main():\n    \"\"\"\n    Main entrypoint for running python-for-android as a script.\n    \"\"\"\n\n    try:\n        # Check the Python version before importing anything heavier than\n        # the util functions.  This lets us provide a nice message about\n        # incompatibility rather than having the interpreter crash if it\n        # reaches unsupported syntax from a newer Python version.\n        check_python_version()\n\n        from pythonforandroid.toolchain import ToolchainCL\n        ToolchainCL()\n    except BuildInterruptingException as exc:\n        handle_build_exception(exc)\n"
  },
  {
    "path": "pythonforandroid/graph.py",
    "content": "from copy import deepcopy\nfrom itertools import product\n\nfrom pythonforandroid.logger import info\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.bootstrap import Bootstrap\nfrom pythonforandroid.util import BuildInterruptingException\n\n\ndef fix_deplist(deps):\n    \"\"\" Turn a dependency list into lowercase, and make sure all entries\n        that are just a string become a tuple of strings\n    \"\"\"\n    deps = [\n        ((dep.lower(),)\n         if not isinstance(dep, (list, tuple))\n         else tuple([dep_entry.lower()\n                     for dep_entry in dep\n                    ]))\n        for dep in deps\n    ]\n    return deps\n\n\nclass RecipeOrder(dict):\n    def __init__(self, ctx):\n        self.ctx = ctx\n\n    def conflicts(self):\n        for name in self.keys():\n            try:\n                recipe = Recipe.get_recipe(name, self.ctx)\n                conflicts = [dep.lower() for dep in recipe.conflicts]\n            except ValueError:\n                conflicts = []\n\n            if any([c in self for c in conflicts]):\n                return True\n        return False\n\n\ndef get_dependency_tuple_list_for_recipe(recipe, blacklist=None):\n    \"\"\" Get the dependencies of a recipe with filtered out blacklist, and\n        turned into tuples with fix_deplist()\n    \"\"\"\n    if blacklist is None:\n        blacklist = set()\n    assert type(blacklist) is set\n    if recipe.depends is None:\n        dependencies = []\n    else:\n        # Turn all dependencies into tuples so that product will work\n        dependencies = fix_deplist(recipe.depends)\n\n        # Filter out blacklisted items and turn lowercase:\n        dependencies = [\n            tuple(set(deptuple) - blacklist)\n            for deptuple in dependencies\n            if tuple(set(deptuple) - blacklist)\n        ]\n    return dependencies\n\n\ndef recursively_collect_orders(\n        name, ctx, all_inputs, orders=None, blacklist=None\n        ):\n    '''For each possible recipe ordering, try to add the new recipe name\n    to that order. Recursively do the same thing with all the\n    dependencies of each recipe.\n\n    '''\n    name = name.lower()\n    if orders is None:\n        orders = []\n    if blacklist is None:\n        blacklist = set()\n    try:\n        recipe = Recipe.get_recipe(name, ctx)\n        dependencies = get_dependency_tuple_list_for_recipe(\n            recipe, blacklist=blacklist\n        )\n\n        # handle opt_depends: these impose requirements on the build\n        # order only if already present in the list of recipes to build\n        dependencies.extend(fix_deplist(\n            [[d] for d in recipe.get_opt_depends_in_list(all_inputs)\n             if d.lower() not in blacklist]\n        ))\n\n        if recipe.conflicts is None:\n            conflicts = []\n        else:\n            conflicts = [dep.lower() for dep in recipe.conflicts]\n    except ValueError:\n        # The recipe does not exist, so we assume it can be installed\n        # via pip with no extra dependencies\n        dependencies = []\n        conflicts = []\n\n    new_orders = []\n    # for each existing recipe order, see if we can add the new recipe name\n    for order in orders:\n        if name in order:\n            new_orders.append(deepcopy(order))\n            continue\n        if order.conflicts():\n            continue\n        if any([conflict in order for conflict in conflicts]):\n            continue\n\n        for dependency_set in product(*dependencies):\n            new_order = deepcopy(order)\n            new_order[name] = set(dependency_set)\n\n            dependency_new_orders = [new_order]\n            for dependency in dependency_set:\n                dependency_new_orders = recursively_collect_orders(\n                    dependency, ctx, all_inputs, dependency_new_orders,\n                    blacklist=blacklist\n                )\n\n            new_orders.extend(dependency_new_orders)\n\n    return new_orders\n\n\ndef find_order(graph):\n    '''\n    Do a topological sort on the dependency graph dict.\n    '''\n    while graph:\n        # Find all items without a parent\n        leftmost = [name for name, dep in graph.items() if not dep]\n        if not leftmost:\n            raise ValueError('Dependency cycle detected! %s' % graph)\n        # If there is more than one, sort them for predictable order\n        leftmost.sort()\n        for result in leftmost:\n            # Yield and remove them from the graph\n            yield result\n            graph.pop(result)\n            for bset in graph.values():\n                bset.discard(result)\n\n\ndef obvious_conflict_checker(ctx, name_tuples, blacklist=None):\n    \"\"\" This is a pre-flight check function that will completely ignore\n        recipe order or choosing an actual value in any of the multiple\n        choice tuples/dependencies, and just do a very basic obvious\n        conflict check.\n    \"\"\"\n    deps_were_added_by = dict()\n    deps = set()\n    if blacklist is None:\n        blacklist = set()\n\n    # Add dependencies for all recipes:\n    to_be_added = [(name_tuple, None) for name_tuple in name_tuples]\n    while len(to_be_added) > 0:\n        current_to_be_added = list(to_be_added)\n        to_be_added = []\n        for (added_tuple, adding_recipe) in current_to_be_added:\n            assert type(added_tuple) is tuple\n            if len(added_tuple) > 1:\n                # No obvious commitment in what to add, don't check it itself\n                # but throw it into deps for later comparing against\n                # (Remember this function only catches obvious issues)\n                deps.add(added_tuple)\n                continue\n\n            name = added_tuple[0]\n            recipe_conflicts = set()\n            recipe_dependencies = []\n            try:\n                # Get recipe to add and who's ultimately adding it:\n                recipe = Recipe.get_recipe(name, ctx)\n                recipe_conflicts = {c.lower() for c in recipe.conflicts}\n                recipe_dependencies = get_dependency_tuple_list_for_recipe(\n                    recipe, blacklist=blacklist\n                )\n            except ValueError:\n                pass\n            adder_first_recipe_name = adding_recipe or name\n\n            # Collect the conflicts:\n            triggered_conflicts = []\n            for dep_tuple_list in deps:\n                # See if the new deps conflict with things added before:\n                if set(dep_tuple_list).intersection(\n                       recipe_conflicts) == set(dep_tuple_list):\n                    triggered_conflicts.append(dep_tuple_list)\n                    continue\n\n                # See if what was added before conflicts with the new deps:\n                if len(dep_tuple_list) > 1:\n                    # Not an obvious commitment to a specific recipe/dep\n                    # to be added, so we won't check.\n                    # (remember this function only catches obvious issues)\n                    continue\n                try:\n                    dep_recipe = Recipe.get_recipe(dep_tuple_list[0], ctx)\n                except ValueError:\n                    continue\n                conflicts = [c.lower() for c in dep_recipe.conflicts]\n                if name in conflicts:\n                    triggered_conflicts.append(dep_tuple_list)\n\n            # Throw error on conflict:\n            if triggered_conflicts:\n                # Get first conflict and see who added that one:\n                adder_second_recipe_name = \"'||'\".join(triggered_conflicts[0])\n                second_recipe_original_adder = deps_were_added_by.get(\n                    (adder_second_recipe_name,), None\n                )\n                if second_recipe_original_adder:\n                    adder_second_recipe_name = second_recipe_original_adder\n\n                # Prompt error:\n                raise BuildInterruptingException(\n                    \"Conflict detected: '{}'\"\n                    \" inducing dependencies {}, and '{}'\"\n                    \" inducing conflicting dependencies {}\".format(\n                        adder_first_recipe_name,\n                        (recipe.name,),\n                        adder_second_recipe_name,\n                        triggered_conflicts[0]\n                    ))\n\n            # Actually add it to our list:\n            deps.add(added_tuple)\n            deps_were_added_by[added_tuple] = adding_recipe\n\n            # Schedule dependencies to be added\n            to_be_added += [\n                (dep, adder_first_recipe_name or name)\n                for dep in recipe_dependencies\n                if dep not in deps\n            ]\n    # If we came here, then there were no obvious conflicts.\n    return None\n\n\ndef get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None):\n    # Get set of recipe/dependency names, clean up and add bootstrap deps:\n    names = set(names)\n    if bs is not None and bs.recipe_depends:\n        names = names.union(set(bs.recipe_depends))\n    names = fix_deplist([\n        ([name] if not isinstance(name, (list, tuple)) else name)\n        for name in names\n    ])\n    if blacklist is None:\n        blacklist = set()\n    blacklist = {bitem.lower() for bitem in blacklist}\n\n    # Remove all values that are in the blacklist:\n    names_before_blacklist = list(names)\n    names = []\n    for name in names_before_blacklist:\n        cleaned_up_tuple = tuple([\n            item for item in name if item not in blacklist\n        ])\n        if cleaned_up_tuple:\n            names.append(cleaned_up_tuple)\n\n    # Do check for obvious conflicts (that would trigger in any order, and\n    # without committing to any specific choice in a multi-choice tuple of\n    # dependencies):\n    obvious_conflict_checker(ctx, names, blacklist=blacklist)\n    # If we get here, no obvious conflicts!\n\n    # get all possible order graphs, as names may include tuples/lists\n    # of alternative dependencies\n    possible_orders = []\n    for name_set in product(*names):\n        new_possible_orders = [RecipeOrder(ctx)]\n        for name in name_set:\n            new_possible_orders = recursively_collect_orders(\n                name, ctx, name_set, orders=new_possible_orders,\n                blacklist=blacklist\n            )\n        possible_orders.extend(new_possible_orders)\n\n    # turn each order graph into a linear list if possible\n    orders = []\n    for possible_order in possible_orders:\n        try:\n            order = find_order(possible_order)\n        except ValueError:  # a circular dependency was found\n            info('Circular dependency found in graph {}, skipping it.'.format(\n                possible_order))\n            continue\n        orders.append(list(order))\n\n    # prefer python3 and SDL2 if available\n    orders = sorted(orders,\n                    key=lambda order: -('python3' in order) - ('sdl2' in order))\n\n    if not orders:\n        raise BuildInterruptingException(\n            'Didn\\'t find any valid dependency graphs. '\n            'This means that some of your '\n            'requirements pull in conflicting dependencies.')\n\n    # It would be better to check against possible orders other\n    # than the first one, but in practice clashes will be rare,\n    # and can be resolved by specifying more parameters\n    chosen_order = orders[0]\n    if len(orders) > 1:\n        info('Found multiple valid dependency orders:')\n        for order in orders:\n            info('    {}'.format(order))\n        info('Using the first of these: {}'.format(chosen_order))\n    else:\n        info('Found a single valid recipe set: {}'.format(chosen_order))\n\n    if bs is None:\n        bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx)\n        if bs is None:\n            # Note: don't remove this without thought, causes infinite loop\n            raise BuildInterruptingException(\n                \"Could not find any compatible bootstrap!\"\n            )\n        recipes, python_modules, bs = get_recipe_order_and_bootstrap(\n            ctx, chosen_order, bs=bs, blacklist=blacklist\n        )\n    else:\n        # check if each requirement has a recipe\n        recipes = []\n        python_modules = []\n        for name in chosen_order:\n            try:\n                recipe = Recipe.get_recipe(name, ctx)\n                python_modules += recipe.python_depends\n            except ValueError:\n                python_modules.append(name)\n            else:\n                recipes.append(name)\n\n    python_modules = list(set(python_modules))\n    return recipes, python_modules, bs\n"
  },
  {
    "path": "pythonforandroid/includes/arm64-v8a/machine/cpu-features.h",
    "content": "#ifndef _ARM64_CPU_FEATURES\n#define _ARM64_CPU_FEATURES\n\n#define __ARM_ARCH__ 8\n#define __ARM_HAVE_HALFWORD_MULTIPLY 1\n\n#endif // _ARM64_CPU_FEATURES\n"
  },
  {
    "path": "pythonforandroid/logger.py",
    "content": "import logging\nimport os\nimport re\nimport sh\nfrom sys import stdout, stderr\nfrom math import log10\nfrom collections import defaultdict\nfrom colorama import Style as Colo_Style, Fore as Colo_Fore\n\n\n# monkey patch to show full output\nsh.ErrorReturnCode.truncate_cap = 999999\n\n\nclass LevelDifferentiatingFormatter(logging.Formatter):\n    def format(self, record):\n        if record.levelno > 30:\n            record.msg = '{}{}[ERROR]{}{}:   '.format(\n                Err_Style.BRIGHT, Err_Fore.RED, Err_Fore.RESET,\n                Err_Style.RESET_ALL) + record.msg\n        elif record.levelno > 20:\n            record.msg = '{}{}[WARNING]{}{}: '.format(\n                Err_Style.BRIGHT, Err_Fore.RED, Err_Fore.RESET,\n                Err_Style.RESET_ALL) + record.msg\n        elif record.levelno > 10:\n            record.msg = '{}[INFO]{}:    '.format(\n                Err_Style.BRIGHT, Err_Style.RESET_ALL) + record.msg\n        else:\n            record.msg = '{}{}[DEBUG]{}{}:   '.format(\n                Err_Style.BRIGHT, Err_Fore.LIGHTBLACK_EX, Err_Fore.RESET,\n                Err_Style.RESET_ALL) + record.msg\n        return super().format(record)\n\n\nlogger = logging.getLogger('p4a')\n# Necessary as importlib reloads this,\n# which would add a second handler and reset the level\nif not hasattr(logger, 'touched'):\n    logger.setLevel(logging.INFO)\n    logger.touched = True\n    ch = logging.StreamHandler(stderr)\n    formatter = LevelDifferentiatingFormatter('%(message)s')\n    ch.setFormatter(formatter)\n    logger.addHandler(ch)\ninfo = logger.info\ndebug = logger.debug\nwarning = logger.warning\nerror = logger.error\n\n\nclass colorama_shim:\n\n    def __init__(self, real):\n        self._dict = defaultdict(str)\n        self._real = real\n        self._enabled = False\n\n    def __getattr__(self, key):\n        return getattr(self._real, key) if self._enabled else self._dict[key]\n\n    def enable(self, enable):\n        self._enabled = enable\n\n\nOut_Style = colorama_shim(Colo_Style)\nOut_Fore = colorama_shim(Colo_Fore)\nErr_Style = colorama_shim(Colo_Style)\nErr_Fore = colorama_shim(Colo_Fore)\n\n\ndef setup_color(color):\n    enable_out = (False if color == 'never' else\n                  True if color == 'always' else\n                  stdout.isatty())\n    Out_Style.enable(enable_out)\n    Out_Fore.enable(enable_out)\n\n    enable_err = (False if color == 'never' else\n                  True if color == 'always' else\n                  stderr.isatty())\n    Err_Style.enable(enable_err)\n    Err_Fore.enable(enable_err)\n\n\ndef info_main(*args):\n    logger.info(''.join([Err_Style.BRIGHT, Err_Fore.GREEN] + list(args) +\n                        [Err_Style.RESET_ALL, Err_Fore.RESET]))\n\n\ndef info_notify(s):\n    info('{}{}{}{}'.format(Err_Style.BRIGHT, Err_Fore.LIGHTBLUE_EX, s,\n                           Err_Style.RESET_ALL))\n\n\ndef shorten_string(string, max_width):\n    ''' make limited length string in form:\n      \"the string is very lo...(and 15 more)\"\n    '''\n    string_len = len(string)\n    if string_len <= max_width:\n        return string\n    visible = max_width - 16 - int(log10(string_len))\n    # expected suffix len \"...(and XXXXX more)\"\n    if not isinstance(string, str):\n        visstring = str(string[:visible], errors='ignore')\n    else:\n        visstring = string[:visible]\n    return u''.join((visstring, u'...(and ',\n                     str(string_len - visible), u' more)'))\n\n\ndef get_console_width():\n    try:\n        cols = int(os.environ['COLUMNS'])\n    except (KeyError, ValueError):\n        pass\n    else:\n        if cols >= 25:\n            return cols\n\n    try:\n        cols = max(25, int(os.popen('stty size', 'r').read().split()[1]))\n    except Exception:\n        pass\n    else:\n        return cols\n\n    return 100\n\n\ndef shprint(command, *args, **kwargs):\n    '''Runs the command (which should be an sh.Command instance), while\n    logging the output.'''\n    kwargs[\"_iter\"] = True\n    kwargs[\"_out_bufsize\"] = 1\n    kwargs[\"_err_to_out\"] = True\n    kwargs[\"_bg\"] = True\n    is_critical = kwargs.pop('_critical', False)\n    tail_n = kwargs.pop('_tail', None)\n    full_debug = False\n    if \"P4A_FULL_DEBUG\" in os.environ:\n        tail_n = 0\n        full_debug = True\n    filter_in = kwargs.pop('_filter', None)\n    filter_out = kwargs.pop('_filterout', None)\n    if len(logger.handlers) > 1:\n        logger.removeHandler(logger.handlers[1])\n    columns = get_console_width()\n    command_path = str(command).split('/')\n    command_string = command_path[-1]\n    string = ' '.join(['{}->{} running'.format(Out_Fore.LIGHTBLACK_EX,\n                                               Out_Style.RESET_ALL),\n                       command_string] + list(args))\n\n    # If logging is not in DEBUG mode, trim the command if necessary\n    if logger.level > logging.DEBUG:\n        logger.info('{}{}'.format(shorten_string(string, columns - 12),\n                                  Err_Style.RESET_ALL))\n    else:\n        logger.debug('{}{}'.format(string, Err_Style.RESET_ALL))\n\n    need_closing_newline = False\n    try:\n        msg_hdr = '           working: '\n        msg_width = columns - len(msg_hdr) - 1\n        output = command(*args, **kwargs)\n        for line in output:\n            if isinstance(line, bytes):\n                line = line.decode('utf-8', errors='replace')\n            if logger.level > logging.DEBUG:\n                if full_debug:\n                    stdout.write(line)\n                    stdout.flush()\n                    continue\n                msg = line.replace(\n                    '\\n', ' ').replace(\n                        '\\t', ' ').replace(\n                            '\\b', ' ').rstrip()\n                if msg:\n                    if \"CI\" not in os.environ:\n                        stdout.write(u'{}\\r{}{:<{width}}'.format(\n                            Err_Style.RESET_ALL, msg_hdr,\n                            shorten_string(msg, msg_width), width=msg_width))\n                        stdout.flush()\n                        need_closing_newline = True\n            else:\n                logger.debug(''.join(['\\t', line.rstrip()]))\n        if need_closing_newline:\n            stdout.write('{}\\r{:>{width}}\\r'.format(\n                Err_Style.RESET_ALL, ' ', width=(columns - 1)))\n            stdout.flush()\n    except sh.ErrorReturnCode as err:\n        if need_closing_newline:\n            stdout.write('{}\\r{:>{width}}\\r'.format(\n                Err_Style.RESET_ALL, ' ', width=(columns - 1)))\n            stdout.flush()\n        if tail_n is not None or filter_in or filter_out:\n            def printtail(out, name, forecolor, tail_n=0,\n                          re_filter_in=None, re_filter_out=None):\n                lines = out.splitlines()\n                if re_filter_in is not None:\n                    lines = [line for line in lines if re_filter_in.search(line)]\n                if re_filter_out is not None:\n                    lines = [line for line in lines if not re_filter_out.search(line)]\n                if tail_n == 0 or len(lines) <= tail_n:\n                    info('{}:\\n{}\\t{}{}'.format(\n                        name, forecolor, '\\t\\n'.join(lines), Out_Fore.RESET))\n                else:\n                    info('{} (last {} lines of {}):\\n{}\\t{}{}'.format(\n                        name, tail_n, len(lines),\n                        forecolor, '\\t\\n'.join([s for s in lines[-tail_n:]]),\n                        Out_Fore.RESET))\n            printtail(err.stdout.decode('utf-8'), 'STDOUT', Out_Fore.YELLOW, tail_n,\n                      re.compile(filter_in) if filter_in else None,\n                      re.compile(filter_out) if filter_out else None)\n            printtail(err.stderr.decode('utf-8'), 'STDERR', Err_Fore.RED)\n        if is_critical or full_debug:\n            env = kwargs.get(\"_env\")\n            if env is not None:\n                info(\"{}ENV:{}\\n{}\\n\".format(\n                    Err_Fore.YELLOW, Err_Fore.RESET, \"\\n\".join(\n                        \"export {}='{}'\".format(n, v) for n, v in env.items())))\n            info(\"{}COMMAND:{}\\ncd {} && {} {}\\n\".format(\n                Err_Fore.YELLOW, Err_Fore.RESET, os.getcwd(), command,\n                ' '.join(args)))\n            warning(\"{}ERROR: {} failed!{}\".format(\n                Err_Fore.RED, command, Err_Fore.RESET))\n        if is_critical:\n            exit(1)\n        else:\n            raise\n\n    return output\n"
  },
  {
    "path": "pythonforandroid/patching.py",
    "content": "\"\"\"\n    Helper functions for recipes.\n\n    Recipes must supply a list of patches.\n\n    Patches consist of a filename and an optional conditional, which is\n    any function of the form:\n        def patch_check(arch: string, recipe : Recipe) -> bool\n\n    This library provides some helpful conditionals and mechanisms to\n    join multiple conditionals.\n\n    Example:\n        patches = [\n            (\"linux_or_darwin_only.patch\",\n             check_any(is_linux, is_darwin),\n            (\"recent_android_API.patch\",\n             is_apt_gte(27)),\n            ]\n\"\"\"\nfrom platform import uname\nfrom packaging.version import Version\n\n\n# Platform checks\n\n\ndef is_platform(platform):\n    \"\"\"\n    Returns true if the host platform matches the parameter given.\n    \"\"\"\n\n    def check(arch, recipe):\n        return uname().system.lower() == platform.lower()\n\n    return check\n\n\nis_linux = is_platform(\"Linux\")\nis_darwin = is_platform(\"Darwin\")\nis_windows = is_platform(\"Windows\")\n\n\ndef is_arch(xarch):\n    \"\"\"\n    Returns true if the target architecture platform matches the parameter\n    given.\n    \"\"\"\n\n    def check(arch):\n        return arch.arch == xarch\n\n    return check\n\n\n# Android API comparisons:\n# Return true if the Android API level being targeted\n# is equal (or >, >=, <, <= as appropriate) the given parameter\n\n\ndef is_api(apiver: int):\n    def check(arch, recipe):\n        return recipe.ctx.android_api == apiver\n\n    return check\n\n\ndef is_api_gt(apiver: int):\n    def check(arch, recipe):\n        return recipe.ctx.android_api > apiver\n\n    return check\n\n\ndef is_api_gte(apiver: int):\n    def check(arch, recipe):\n        return recipe.ctx.android_api >= apiver\n\n    return check\n\n\ndef is_api_lt(apiver: int):\n    def check(arch, recipe):\n        return recipe.ctx.android_api < apiver\n\n    return check\n\n\ndef is_api_lte(apiver: int):\n    def check(arch, recipe):\n        return recipe.ctx.android_api <= apiver\n\n    return check\n\n\n# Android API comparisons:\n\n\ndef is_ndk(ndk):\n    \"\"\"\n    Return true if the Minimum Supported Android NDK level being targeted\n    is equal the given parameter (which should be an AndroidNDK instance)\n    \"\"\"\n\n    def check(arch, recipe):\n        return recipe.ctx.ndk == ndk\n\n    return check\n\n\n# Recipe Version comparisons:\n# These compare the Recipe's version with the provided string (or\n# Packaging.Version).\n#\n# Warning: Both strings must conform to PEP 440 - e.g. \"3.2.1\" or \"1.0rc1\"\n\n\ndef is_version_gt(version):\n    \"\"\"Return true if the Recipe's version is greater\"\"\"\n\n    def check(arch, recipe):\n        return Version(recipe.version) > Version(version)\n\n    return check\n\n\ndef is_version_lt(version):\n    \"\"\"Return true if the Recipe's version is less than\"\"\"\n\n    def check(arch, recipe):\n        return Version(recipe.version) < Version(version)\n\n    return check\n\n\ndef version_starts_with(version_prefix):\n    def check(arch, recipe):\n        return recipe.version.startswith(version_prefix)\n\n    return check\n\n\n# Will Build\n\n\ndef will_build(recipe_name):\n    \"\"\"Return true if the recipe with this name is planned to be included in\n    the distribution.\"\"\"\n\n    def check(arch, recipe):\n        return recipe_name in recipe.ctx.recipe_build_order\n\n    return check\n\n\n# Conjunctions\n\n\ndef check_all(*patch_checks):\n    \"\"\"\n    Given a collection of patch_checks as params, return if all returned true.\n    \"\"\"\n\n    def check(arch, recipe):\n        return all(patch_check(arch, recipe) for patch_check in patch_checks)\n\n    return check\n\n\ndef check_any(*patch_checks):\n    \"\"\"\n    Given a collection of patch_checks as params, return if any returned true.\n    \"\"\"\n\n    def check(arch, recipe):\n        return any(patch_check(arch, recipe) for patch_check in patch_checks)\n\n    return check\n"
  },
  {
    "path": "pythonforandroid/prerequisites.py",
    "content": "#!/usr/bin/env python3\n\nimport os\nimport platform\nimport shutil\nimport subprocess\nimport sys\n\nfrom pythonforandroid.logger import info, warning, error\nfrom pythonforandroid.util import ensure_dir\n\n\nclass Prerequisite(object):\n    name = \"Default\"\n    homebrew_formula_name = \"\"\n    mandatory = dict(linux=False, darwin=False)\n    installer_is_supported = dict(linux=False, darwin=False)\n\n    def is_valid(self):\n        if self.checker():\n            info(f\"Prerequisite {self.name} is met\")\n            return (True, \"\")\n        elif not self.mandatory[sys.platform]:\n            warning(\n                f\"Prerequisite {self.name} is not met, but is marked as non-mandatory\"\n            )\n        else:\n            error(f\"Prerequisite {self.name} is not met\")\n\n    def checker(self):\n        if sys.platform == \"darwin\":\n            return self.darwin_checker()\n        elif sys.platform == \"linux\":\n            return self.linux_checker()\n        else:\n            raise Exception(\"Unsupported platform\")\n\n    def ask_to_install(self):\n        if (\n            os.environ.get(\"PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE\", \"1\")\n            == \"1\"\n        ):\n            res = input(\n                f\"Do you want automatically install prerequisite {self.name}? [y/N] \"\n            )\n            if res.lower() == \"y\":\n                return True\n            else:\n                return False\n        else:\n            info(\n                \"Session is not interactive (usually this happens during a CI run), so let's consider it as a YES\"\n            )\n            return True\n\n    def install(self):\n        info(f\"python-for-android can automatically install prerequisite: {self.name}\")\n        if self.ask_to_install():\n            if sys.platform == \"darwin\":\n                self.darwin_installer()\n            elif sys.platform == \"linux\":\n                self.linux_installer()\n            else:\n                raise Exception(\"Unsupported platform\")\n        else:\n            info(\n                f\"Skipping installation of prerequisite {self.name} as per user request\"\n            )\n\n    def show_helper(self):\n        if sys.platform == \"darwin\":\n            self.darwin_helper()\n        elif sys.platform == \"linux\":\n            self.linux_helper()\n        else:\n            raise Exception(\"Unsupported platform\")\n\n    def install_is_supported(self):\n        return self.installer_is_supported[sys.platform]\n\n    def linux_checker(self):\n        raise Exception(f\"Unsupported prerequisite check on linux for {self.name}\")\n\n    def darwin_checker(self):\n        raise Exception(f\"Unsupported prerequisite check on macOS for {self.name}\")\n\n    def linux_installer(self):\n        raise Exception(f\"Unsupported prerequisite installer on linux for {self.name}\")\n\n    def darwin_installer(self):\n        raise Exception(f\"Unsupported prerequisite installer on macOS for {self.name}\")\n\n    def darwin_helper(self):\n        info(f\"No helper available for prerequisite: {self.name} on macOS\")\n\n    def linux_helper(self):\n        info(f\"No helper available for prerequisite: {self.name} on linux\")\n\n    def _darwin_get_brew_formula_location_prefix(self, formula, installed=False):\n        opts = [\"--installed\"] if installed else []\n        p = subprocess.Popen(\n            [\"brew\", \"--prefix\", formula, *opts],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n        )\n        _stdout_res, _stderr_res = p.communicate()\n\n        if p.returncode != 0:\n            error(_stderr_res.decode(\"utf-8\").strip())\n            return None\n        else:\n            return _stdout_res.decode(\"utf-8\").strip()\n\n    def darwin_pkg_config_location(self):\n        warning(\n            f\"pkg-config location is not supported on macOS for prerequisite: {self.name}\"\n        )\n        return \"\"\n\n    def linux_pkg_config_location(self):\n        warning(\n            f\"pkg-config location is not supported on linux for prerequisite: {self.name}\"\n        )\n        return \"\"\n\n    @property\n    def pkg_config_location(self):\n        if sys.platform == \"darwin\":\n            return self.darwin_pkg_config_location()\n        elif sys.platform == \"linux\":\n            return self.linux_pkg_config_location()\n\n\nclass HomebrewPrerequisite(Prerequisite):\n    name = \"homebrew\"\n    mandatory = dict(linux=False, darwin=True)\n    installer_is_supported = dict(linux=False, darwin=False)\n\n    def darwin_checker(self):\n        return shutil.which(\"brew\") is not None\n\n    def darwin_helper(self):\n        info(\n            \"Installer for homebrew is not yet supported on macOS,\"\n            \"the nice news is that the installation process is easy!\"\n            \"See: https://brew.sh for further instructions.\"\n        )\n\n\nclass JDKPrerequisite(Prerequisite):\n    name = \"JDK\"\n    mandatory = dict(linux=False, darwin=True)\n    installer_is_supported = dict(linux=False, darwin=True)\n    supported_version = 17\n\n    def darwin_checker(self):\n        if \"JAVA_HOME\" in os.environ:\n            info(\"Found JAVA_HOME environment variable, using it\")\n            jdk_path = os.environ[\"JAVA_HOME\"]\n        else:\n            jdk_path = self._darwin_get_libexec_jdk_path(version=None)\n        return self._darwin_jdk_is_supported(jdk_path)\n\n    def _darwin_get_libexec_jdk_path(self, version=None):\n        version_args = []\n        if version is not None:\n            version_args = [\"-v\", version]\n        return (\n            subprocess.run(\n                [\"/usr/libexec/java_home\", *version_args],\n                stdout=subprocess.PIPE,\n            )\n            .stdout.strip()\n            .decode()\n        )\n\n    def _darwin_jdk_is_supported(self, jdk_path):\n        if not jdk_path:\n            return False\n\n        javac_bin = os.path.join(jdk_path, \"bin\", \"javac\")\n        if not os.path.exists(javac_bin):\n            return False\n\n        p = subprocess.Popen(\n            [javac_bin, \"-version\"], stdout=subprocess.PIPE, stderr=subprocess.PIPE\n        )\n        _stdout_res, _stderr_res = p.communicate()\n\n        if p.returncode != 0:\n            error(\"Failed to run javac to check JDK version\")\n            return False\n\n        if not _stdout_res:\n            _stdout_res = _stderr_res\n\n        res = _stdout_res.strip().decode()\n\n        major_version = int(res.split(\" \")[-1].split(\".\")[0])\n        if major_version == self.supported_version:\n            info(f\"Found a valid JDK at {jdk_path}\")\n            return True\n        else:\n            error(f\"JDK version {major_version} is not supported\")\n            return False\n\n    def darwin_helper(self):\n        info(\n            f\"python-for-android requires a JDK {self.supported_version} to be installed on macOS,\"\n            \"but seems like you don't have one installed.\"\n        )\n        info(\n            \"If you think that a valid JDK is already installed, please verify that \"\n            f\"you have a JDK {self.supported_version} installed and that `/usr/libexec/java_home` \"\n            \"shows the correct path.\"\n        )\n        info(\n            \"If you have multiple JDK installations, please make sure that you have \"\n            \"`JAVA_HOME` environment variable set to the correct JDK installation.\"\n        )\n\n    def darwin_installer(self):\n        info(\n            f\"Looking for a JDK {self.supported_version} installation which is not the default one ...\"\n        )\n        jdk_path = self._darwin_get_libexec_jdk_path(version=str(self.supported_version))\n\n        if not self._darwin_jdk_is_supported(jdk_path):\n            info(f\"We're unlucky, there's no JDK {self.supported_version} or higher installation available\")\n\n            base_url = \"https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/\"\n            if platform.machine() == \"arm64\":\n                filename = \"OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.2_8.tar.gz\"\n            else:\n                filename = \"OpenJDK17U-jdk_x64_mac_hotspot_17.0.2_8.tar.gz\"\n\n            info(f\"Downloading {filename} from {base_url}\")\n            subprocess.check_output(\n                [\n                    \"curl\",\n                    \"-L\",\n                    f\"{base_url}{filename}\",\n                    \"-o\",\n                    f\"/tmp/{filename}\",\n                ]\n            )\n\n            user_library_java_path = os.path.expanduser(\n                \"~/Library/Java/JavaVirtualMachines\"\n            )\n            info(f\"Extracting {filename} to {user_library_java_path}\")\n            ensure_dir(user_library_java_path)\n            subprocess.check_output(\n                [\"tar\", \"xzf\", f\"/tmp/{filename}\", \"-C\", user_library_java_path],\n            )\n\n            jdk_path = self._darwin_get_libexec_jdk_path(version=\"17.0.2+8\")\n\n        info(f\"Setting JAVA_HOME to {jdk_path}\")\n        os.environ[\"JAVA_HOME\"] = jdk_path\n\n\nclass OpenSSLPrerequisite(Prerequisite):\n    name = \"openssl\"\n    homebrew_formula_name = \"openssl@3\"\n    mandatory = dict(linux=False, darwin=True)\n    installer_is_supported = dict(linux=False, darwin=True)\n\n    def darwin_checker(self):\n        return (\n            self._darwin_get_brew_formula_location_prefix(\n                self.homebrew_formula_name, installed=True\n            )\n            is not None\n        )\n\n    def darwin_pkg_config_location(self):\n        return os.path.join(\n            self._darwin_get_brew_formula_location_prefix(self.homebrew_formula_name),\n            \"lib/pkgconfig\",\n        )\n\n    def darwin_installer(self):\n        info(\"Installing OpenSSL ...\")\n        subprocess.check_output([\"brew\", \"install\", self.homebrew_formula_name])\n\n\nclass AutoconfPrerequisite(Prerequisite):\n    name = \"autoconf\"\n    mandatory = dict(linux=False, darwin=True)\n    installer_is_supported = dict(linux=False, darwin=True)\n\n    def darwin_checker(self):\n        return (\n            self._darwin_get_brew_formula_location_prefix(\"autoconf\", installed=True)\n            is not None\n        )\n\n    def darwin_installer(self):\n        info(\"Installing Autoconf ...\")\n        subprocess.check_output([\"brew\", \"install\", \"autoconf\"])\n\n\nclass AutomakePrerequisite(Prerequisite):\n    name = \"automake\"\n    mandatory = dict(linux=False, darwin=True)\n    installer_is_supported = dict(linux=False, darwin=True)\n\n    def darwin_checker(self):\n        return (\n            self._darwin_get_brew_formula_location_prefix(\"automake\", installed=True)\n            is not None\n        )\n\n    def darwin_installer(self):\n        info(\"Installing Automake ...\")\n        subprocess.check_output([\"brew\", \"install\", \"automake\"])\n\n\nclass LibtoolPrerequisite(Prerequisite):\n    name = \"libtool\"\n    mandatory = dict(linux=False, darwin=True)\n    installer_is_supported = dict(linux=False, darwin=True)\n\n    def darwin_checker(self):\n        return (\n            self._darwin_get_brew_formula_location_prefix(\"libtool\", installed=True)\n            is not None\n        )\n\n    def darwin_installer(self):\n        info(\"Installing Libtool ...\")\n        subprocess.check_output([\"brew\", \"install\", \"libtool\"])\n\n\nclass PkgConfigPrerequisite(Prerequisite):\n    name = \"pkg-config\"\n    mandatory = dict(linux=False, darwin=True)\n    installer_is_supported = dict(linux=False, darwin=True)\n\n    def darwin_checker(self):\n        return (\n            self._darwin_get_brew_formula_location_prefix(\"pkg-config\", installed=True)\n            is not None\n        )\n\n    def darwin_installer(self):\n        info(\"Installing Pkg-Config ...\")\n        subprocess.check_output([\"brew\", \"install\", \"pkg-config\"])\n\n\nclass CmakePrerequisite(Prerequisite):\n    name = \"cmake\"\n    mandatory = dict(linux=False, darwin=True)\n    installer_is_supported = dict(linux=False, darwin=True)\n\n    def darwin_checker(self):\n        return (\n            self._darwin_get_brew_formula_location_prefix(\"cmake\", installed=True)\n            is not None\n        )\n\n    def darwin_installer(self):\n        info(\"Installing cmake ...\")\n        subprocess.check_output([\"brew\", \"install\", \"cmake\"])\n\n\ndef get_required_prerequisites(platform=\"linux\"):\n    return [\n        prerequisite_cls()\n        for prerequisite_cls in [\n            HomebrewPrerequisite,\n            AutoconfPrerequisite,\n            AutomakePrerequisite,\n            LibtoolPrerequisite,\n            PkgConfigPrerequisite,\n            CmakePrerequisite,\n            OpenSSLPrerequisite,\n            JDKPrerequisite,\n        ] if prerequisite_cls.mandatory.get(platform, False)\n    ]\n\n\ndef check_and_install_default_prerequisites():\n\n    prerequisites_not_met = []\n\n    warning(\n        \"prerequisites.py is experimental and does not support all prerequisites yet.\"\n    )\n    warning(\"Please report any issues to the python-for-android issue tracker.\")\n\n    # Phase 1: Check if all prerequisites are met and add the ones\n    # which are not to `prerequisites_not_met`\n    for prerequisite in get_required_prerequisites(sys.platform):\n        if not prerequisite.is_valid():\n            prerequisites_not_met.append(prerequisite)\n\n    # Phase 2: Setup/Install all prerequisites that are not met\n    # (where possible), otherwise show an helper.\n    for prerequisite in prerequisites_not_met:\n        prerequisite.show_helper()\n        if prerequisite.install_is_supported():\n            prerequisite.install()\n\n\nif __name__ == \"__main__\":\n    check_and_install_default_prerequisites()\n"
  },
  {
    "path": "pythonforandroid/pythonpackage.py",
    "content": "\"\"\" This module offers highlevel functions to get package metadata\n    like the METADATA file, the name, or a list of dependencies.\n\n    Usage examples:\n\n       # Getting package name from pip reference:\n       from pythonforandroid.pythonpackage import get_package_name\n       print(get_package_name(\"pillow\"))\n       # Outputs: \"Pillow\" (note the spelling!)\n\n       # Getting package dependencies:\n       from pythonforandroid.pythonpackage import get_package_dependencies\n       print(get_package_dependencies(\"pep517\"))\n       # Outputs: \"['pytoml']\"\n\n       # Get package name from arbitrary package source:\n       from pythonforandroid.pythonpackage import get_package_name\n       print(get_package_name(\"/some/local/project/folder/\"))\n       # Outputs package name\n\n    NOTE:\n\n    Yes, this module doesn't fit well into python-for-android, but this\n    functionality isn't available ANYWHERE ELSE, and upstream (pip, ...)\n    currently has no interest in taking this over, so it has no other place\n    to go.\n    (Unless someone reading this puts it into yet another packaging lib)\n\n    Reference discussion/upstream inclusion attempt:\n\n    https://github.com/pypa/packaging-problems/issues/247\n\n\"\"\"\n\n\nimport functools\nfrom io import open  # needed for python 2\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport tarfile\nimport tempfile\nimport time\nfrom urllib.parse import unquote as urlunquote\nfrom urllib.parse import urlparse\nimport zipfile\n\nimport toml\nimport build.util\n\nfrom pythonforandroid.util import rmdir, ensure_dir\n\n\ndef transform_dep_for_pip(dependency):\n    if dependency.find(\"@\") > 0 and (\n            dependency.find(\"@\") < dependency.find(\"://\") or\n            \"://\" not in dependency\n            ):\n        # WORKAROUND FOR UPSTREAM BUG:\n        # https://github.com/pypa/pip/issues/6097\n        # (Please REMOVE workaround once that is fixed & released upstream!)\n        #\n        # Basically, setup_requires() can contain a format pip won't install\n        # from a requirements.txt (PEP 508 URLs).\n        # To avoid this, translate to an #egg= reference:\n        if dependency.endswith(\"#\"):\n            dependency = dependency[:-1]\n        url = (dependency.partition(\"@\")[2].strip().partition(\"#egg\")[0] +\n               \"#egg=\" +\n               dependency.partition(\"@\")[0].strip()\n              )\n        return url\n    return dependency\n\n\ndef extract_metainfo_files_from_package(\n        package,\n        output_folder,\n        debug=False\n        ):\n    \"\"\" Extracts metadata files from the given package to the given folder,\n        which may be referenced in any way that is permitted in\n        a requirements.txt file or install_requires=[] listing.\n\n        Current supported metadata files that will be extracted:\n\n        - pytoml.yml  (only if package wasn't obtained as wheel)\n        - METADATA\n    \"\"\"\n\n    if package is None:\n        raise ValueError(\"package cannot be None\")\n\n    if not os.path.exists(output_folder) or os.path.isfile(output_folder):\n        raise ValueError(\"output folder needs to be existing folder\")\n\n    if debug:\n        print(\"extract_metainfo_files_from_package: extracting for \" +\n              \"package: \" + str(package))\n\n    # A temp folder for making a package copy in case it's a local folder,\n    # because extracting metadata might modify files\n    # (creating sdists/wheels...)\n    temp_folder = tempfile.mkdtemp(prefix=\"pythonpackage-package-copy-\")\n    try:\n        # Package is indeed a folder! Get a temp copy to work on:\n        if is_filesystem_path(package):\n            shutil.copytree(\n                parse_as_folder_reference(package),\n                os.path.join(temp_folder, \"package\"),\n                ignore=shutil.ignore_patterns(\".tox\")\n            )\n            package = os.path.join(temp_folder, \"package\")\n\n        _extract_metainfo_files_from_package_unsafe(package, output_folder)\n    finally:\n        rmdir(temp_folder)\n\n\ndef _get_system_python_executable():\n    \"\"\" Returns the path the system-wide python binary.\n        (In case we're running in a virtualenv or venv)\n    \"\"\"\n    # This function is required by get_package_as_folder() to work\n    # inside a virtualenv, since venv creation will fail with\n    # the virtualenv's local python binary.\n    # (venv/virtualenv incompatibility)\n\n    # Abort if not in virtualenv or venv:\n    if not hasattr(sys, \"real_prefix\") and (\n            not hasattr(sys, \"base_prefix\") or\n            os.path.normpath(sys.base_prefix) ==\n            os.path.normpath(sys.prefix)):\n        return sys.executable\n\n    # Extract prefix we need to look in:\n    if hasattr(sys, \"real_prefix\"):\n        search_prefix = sys.real_prefix  # virtualenv\n    else:\n        search_prefix = sys.base_prefix  # venv\n\n    def python_binary_from_folder(path):\n        def binary_is_usable(python_bin):\n            \"\"\" Helper function to see if a given binary name refers\n                to a usable python interpreter binary\n            \"\"\"\n\n            # Abort if path isn't present at all or a directory:\n            if not os.path.exists(\n                os.path.join(path, python_bin)\n            ) or os.path.isdir(os.path.join(path, python_bin)):\n                return\n            # We should check file not found anyway trying to run it,\n            # since it might be a dead symlink:\n            try:\n                filenotfounderror = FileNotFoundError\n            except NameError:  # Python 2\n                filenotfounderror = OSError\n            try:\n                # Run it and see if version output works with no error:\n                subprocess.check_output([\n                    os.path.join(path, python_bin), \"--version\"\n                ], stderr=subprocess.STDOUT)\n                return True\n            except (subprocess.CalledProcessError, filenotfounderror):\n                return False\n\n        python_name = \"python\" + sys.version\n        while (not binary_is_usable(python_name) and\n               python_name.find(\".\") > 0):\n            # Try less specific binary name:\n            python_name = python_name.rpartition(\".\")[0]\n        if binary_is_usable(python_name):\n            return os.path.join(path, python_name)\n        return None\n\n    # Return from sys.real_prefix if present:\n    result = python_binary_from_folder(search_prefix)\n    if result is not None:\n        return result\n\n    # Check out all paths in $PATH:\n    bad_candidates = []\n    good_candidates = []\n    ever_had_nonvenv_path = False\n    ever_had_path_starting_with_prefix = False\n    for p in os.environ.get(\"PATH\", \"\").split(\":\"):\n        # Skip if not possibly the real system python:\n        if not os.path.normpath(p).startswith(\n                os.path.normpath(search_prefix)\n                ):\n            continue\n\n        ever_had_path_starting_with_prefix = True\n\n        # First folders might be virtualenv/venv we want to avoid:\n        if not ever_had_nonvenv_path:\n            sep = os.path.sep\n            if (\n                (\"system32\" not in p.lower() and\n                 \"usr\" not in p and\n                 not p.startswith(\"/opt/python\")) or\n                {\"home\", \".tox\"}.intersection(set(p.split(sep))) or\n                \"users\" in p.lower()\n            ):\n                # Doesn't look like bog-standard system path.\n                if (p.endswith(os.path.sep + \"bin\") or\n                        p.endswith(os.path.sep + \"bin\" + os.path.sep)):\n                    # Also ends in \"bin\" -> likely virtualenv/venv.\n                    # Add as unfavorable / end of candidates:\n                    bad_candidates.append(p)\n                    continue\n            ever_had_nonvenv_path = True\n\n        good_candidates.append(p)\n\n    # If we have a bad env with PATH not containing any reference to our\n    # real python (travis, why would you do that to me?) then just guess\n    # based from the search prefix location itself:\n    if not ever_had_path_starting_with_prefix:\n        # ... and yes we're scanning all the folders for that, it's dumb\n        # but i'm not aware of a better way: (@JonasT)\n        for root, dirs, files in os.walk(search_prefix, topdown=True):\n            for name in dirs:\n                bad_candidates.append(os.path.join(root, name))\n\n    # Sort candidates by length (to prefer shorter ones):\n    def candidate_cmp(a, b):\n        return len(a) - len(b)\n    good_candidates = sorted(\n        good_candidates, key=functools.cmp_to_key(candidate_cmp)\n    )\n    bad_candidates = sorted(\n        bad_candidates, key=functools.cmp_to_key(candidate_cmp)\n    )\n\n    # See if we can now actually find the system python:\n    for p in good_candidates + bad_candidates:\n        result = python_binary_from_folder(p)\n        if result is not None:\n            return result\n\n    raise RuntimeError(\n        \"failed to locate system python in: {}\"\n        \" - checked candidates were: {}, {}\"\n        .format(sys.real_prefix, good_candidates, bad_candidates)\n    )\n\n\ndef get_package_as_folder(dependency):\n    \"\"\" This function downloads the given package / dependency and extracts\n        the raw contents into a folder.\n\n        Afterwards, it returns a tuple with the type of distribution obtained,\n        and the temporary folder it extracted to. It is the caller's\n        responsibility to delete the returned temp folder after use.\n\n        Examples of returned values:\n\n        (\"source\", \"/tmp/pythonpackage-venv-e84toiwjw\")\n        (\"wheel\", \"/tmp/pythonpackage-venv-85u78uj\")\n\n        What the distribution type will be depends on what pip decides to\n        download.\n    \"\"\"\n\n    venv_parent = tempfile.mkdtemp(\n        prefix=\"pythonpackage-venv-\"\n    )\n    try:\n        # Create a venv to install into:\n        try:\n            if int(sys.version.partition(\".\")[0]) < 3:\n                # Python 2.x has no venv.\n                subprocess.check_output([\n                    sys.executable,  # no venv conflict possible,\n                                     # -> no need to use system python\n                    \"-m\", \"virtualenv\",\n                    \"--python=\" + _get_system_python_executable(),\n                    os.path.join(venv_parent, 'venv')\n                ], cwd=venv_parent)\n            else:\n                # On modern Python 3, use venv.\n                subprocess.check_output([\n                    _get_system_python_executable(), \"-m\", \"venv\",\n                    os.path.join(venv_parent, 'venv')\n                ], cwd=venv_parent)\n        except subprocess.CalledProcessError as e:\n            output = e.output.decode('utf-8', 'replace')\n            raise ValueError(\n                'venv creation unexpectedly ' +\n                'failed. error output: ' + str(output)\n            )\n        venv_path = os.path.join(venv_parent, \"venv\")\n\n        # Update pip and wheel in venv for latest feature support:\n        try:\n            filenotfounderror = FileNotFoundError\n        except NameError:  # Python 2.\n            filenotfounderror = OSError\n        try:\n            subprocess.check_output([\n                os.path.join(venv_path, \"bin\", \"pip\"),\n                \"install\", \"-U\", \"pip\", \"wheel\",\n            ])\n        except filenotfounderror:\n            raise RuntimeError(\n                \"venv appears to be missing pip. \"\n                \"did we fail to use a proper system python??\\n\"\n                \"system python path detected: {}\\n\"\n                \"os.environ['PATH']: {}\".format(\n                    _get_system_python_executable(),\n                    os.environ.get(\"PATH\", \"\")\n                )\n            )\n\n        # Create download subfolder:\n        ensure_dir(os.path.join(venv_path, \"download\"))\n\n        # Write a requirements.txt with our package and download:\n        with open(os.path.join(venv_path, \"requirements.txt\"),\n                  \"w\", encoding=\"utf-8\"\n                 ) as f:\n            def to_unicode(s):  # Needed for Python 2.\n                try:\n                    return s.decode(\"utf-8\")\n                except AttributeError:\n                    return s\n            f.write(to_unicode(transform_dep_for_pip(dependency)))\n        try:\n            subprocess.check_output(\n                [\n                    os.path.join(venv_path, \"bin\", \"pip\"),\n                    \"download\", \"--no-deps\", \"-r\", \"../requirements.txt\",\n                    \"-d\", os.path.join(venv_path, \"download\")\n                ],\n                stderr=subprocess.STDOUT,\n                cwd=os.path.join(venv_path, \"download\")\n            )\n        except subprocess.CalledProcessError as e:\n            raise RuntimeError(\"package download failed: \" + str(e.output))\n\n        if len(os.listdir(os.path.join(venv_path, \"download\"))) == 0:\n            # No download. This can happen if the dependency has a condition\n            # which prohibits install in our environment.\n            # (the \"package ; ... conditional ... \" type of condition)\n            return (None, None)\n\n        # Get the result and make sure it's an extracted directory:\n        result_folder_or_file = os.path.join(\n            venv_path, \"download\",\n            os.listdir(os.path.join(venv_path, \"download\"))[0]\n        )\n        dl_type = \"source\"\n        if not os.path.isdir(result_folder_or_file):\n            # Must be an archive.\n            if result_folder_or_file.endswith((\".zip\", \".whl\")):\n                if result_folder_or_file.endswith(\".whl\"):\n                    dl_type = \"wheel\"\n                with zipfile.ZipFile(result_folder_or_file) as f:\n                    f.extractall(os.path.join(venv_path,\n                                              \"download\", \"extracted\"\n                                             ))\n                    result_folder_or_file = os.path.join(\n                        venv_path, \"download\", \"extracted\"\n                    )\n            elif result_folder_or_file.find(\".tar.\") > 0:\n                # Probably a tarball.\n                with tarfile.open(result_folder_or_file) as f:\n                    f.extractall(os.path.join(venv_path,\n                                              \"download\", \"extracted\"\n                                             ))\n                    result_folder_or_file = os.path.join(\n                        venv_path, \"download\", \"extracted\"\n                    )\n            else:\n                raise RuntimeError(\n                    \"unknown archive or download \" +\n                    \"type: \" + str(result_folder_or_file)\n                )\n\n        # If the result is hidden away in an additional subfolder,\n        # descend into it:\n        while os.path.isdir(result_folder_or_file) and \\\n                len(os.listdir(result_folder_or_file)) == 1 and \\\n                os.path.isdir(os.path.join(\n                    result_folder_or_file,\n                    os.listdir(result_folder_or_file)[0]\n                )):\n            result_folder_or_file = os.path.join(\n                result_folder_or_file,\n                os.listdir(result_folder_or_file)[0]\n            )\n\n        # Copy result to new dedicated folder so we can throw away\n        # our entire virtualenv nonsense after returning:\n        result_path = tempfile.mkdtemp()\n        rmdir(result_path)\n        shutil.copytree(result_folder_or_file, result_path)\n        return (dl_type, result_path)\n    finally:\n        rmdir(venv_parent)\n\n\ndef _extract_metainfo_files_from_package_unsafe(\n        package,\n        output_path\n        ):\n    # This is the unwrapped function that will\n    # 1. make lots of stdout/stderr noise\n    # 2. possibly modify files (if the package source is a local folder)\n    # Use extract_metainfo_files_from_package_folder instead which avoids\n    # these issues.\n\n    clean_up_path = False\n    path_type = \"source\"\n    path = parse_as_folder_reference(package)\n    if path is None:\n        # This is not a path. Download it:\n        (path_type, path) = get_package_as_folder(package)\n        if path_type is None:\n            # Download failed.\n            raise ValueError(\n                \"cannot get info for this package, \" +\n                \"pip says it has no downloads (conditional dependency?)\"\n            )\n        clean_up_path = True\n\n    try:\n        metadata_path = None\n\n        if path_type != \"wheel\":\n            # Use a build helper function to fetch the metadata directly\n            metadata = build.util.project_wheel_metadata(path)\n            # And write it to a file\n            metadata_path = os.path.join(output_path, \"built_metadata\")\n            with open(metadata_path, 'w') as f:\n                for key in metadata.keys():\n                    for value in metadata.get_all(key):\n                        f.write(\"{}: {}\\n\".format(key, value))\n        else:\n            # This is a wheel, so metadata should be in *.dist-info folder:\n            metadata_path = os.path.join(\n                path,\n                [f for f in os.listdir(path) if f.endswith(\".dist-info\")][0],\n                \"METADATA\"\n            )\n\n        # Store type of metadata source. Can be \"wheel\", \"source\" for source\n        # distribution, and others get_package_as_folder() may support\n        # in the future.\n        with open(os.path.join(output_path, \"metadata_source\"), \"w\") as f:\n            try:\n                f.write(path_type)\n            except TypeError:  # in python 2 path_type may be str/bytes:\n                f.write(path_type.decode(\"utf-8\", \"replace\"))\n\n        # Copy the metadata file:\n        shutil.copyfile(metadata_path, os.path.join(output_path, \"METADATA\"))\n    finally:\n        if clean_up_path:\n            rmdir(path)\n\n\ndef is_filesystem_path(dep):\n    \"\"\" Convenience function around parse_as_folder_reference() to\n        check if a dependency refers to a folder path or something remote.\n\n        Returns True if local, False if remote.\n    \"\"\"\n    return (parse_as_folder_reference(dep) is not None)\n\n\ndef parse_as_folder_reference(dep):\n    \"\"\" See if a dependency reference refers to a folder path.\n        If it does, return the folder path (which parses and\n        resolves file:// urls in the process).\n        If it doesn't, return None.\n    \"\"\"\n    # Special case: pep508 urls\n    if dep.find(\"@\") > 0 and (\n            (dep.find(\"@\") < dep.find(\"/\") or \"/\" not in dep) and\n            (dep.find(\"@\") < dep.find(\":\") or \":\" not in dep)\n            ):\n        # This should be a 'pkgname @ https://...' style path, or\n        # 'pkname @ /local/file/path'.\n        return parse_as_folder_reference(dep.partition(\"@\")[2].lstrip())\n\n    # Check if this is either not an url, or a file URL:\n    if dep.startswith((\"/\", \"file://\")) or (\n            dep.find(\"/\") > 0 and\n            dep.find(\"://\") < 0) or (dep in [\"\", \".\"]):\n        if dep.startswith(\"file://\"):\n            dep = urlunquote(urlparse(dep).path)\n        return dep\n    return None\n\n\ndef _extract_info_from_package(dependency,\n                               extract_type=None,\n                               debug=False,\n                               include_build_requirements=False\n                               ):\n    \"\"\" Internal function to extract metainfo from a package.\n        Currently supported info types:\n\n        - name\n        - dependencies  (a list of dependencies)\n    \"\"\"\n    if debug:\n        print(\"_extract_info_from_package called with \"\n              \"extract_type={} include_build_requirements={}\".format(\n                  extract_type, include_build_requirements,\n              ))\n    output_folder = tempfile.mkdtemp(prefix=\"pythonpackage-metafolder-\")\n    try:\n        extract_metainfo_files_from_package(\n            dependency, output_folder, debug=debug\n        )\n\n        # Extract the type of data source we used to get the metadata:\n        with open(os.path.join(output_folder,\n                               \"metadata_source\"), \"r\") as f:\n            metadata_source_type = f.read().strip()\n\n        # Extract main METADATA file:\n        with open(os.path.join(output_folder, \"METADATA\"),\n                  \"r\", encoding=\"utf-8\"\n                 ) as f:\n            # Get metadata and cut away description (is after 2 linebreaks)\n            metadata_entries = f.read().partition(\"\\n\\n\")[0].splitlines()\n\n        if extract_type == \"name\":\n            name = None\n            for meta_entry in metadata_entries:\n                if meta_entry.lower().startswith(\"name:\"):\n                    return meta_entry.partition(\":\")[2].strip()\n            if name is None:\n                raise ValueError(\"failed to obtain package name\")\n            return name\n        elif extract_type == \"dependencies\":\n            # First, make sure we don't attempt to return build requirements\n            # for wheels since they usually come without pyproject.toml\n            # and we haven't implemented another way to get them:\n            if include_build_requirements and \\\n                    metadata_source_type == \"wheel\":\n                if debug:\n                    print(\"_extract_info_from_package: was called \"\n                          \"with include_build_requirements=True on \"\n                          \"package obtained as wheel, raising error...\")\n                raise NotImplementedError(\n                    \"fetching build requirements for \"\n                    \"wheels is not implemented\"\n                )\n\n            # Get build requirements from pyproject.toml if requested:\n            requirements = []\n            pyproject_toml_path = os.path.join(output_folder, 'pyproject.toml')\n            if os.path.exists(pyproject_toml_path) and include_build_requirements:\n                # Read build system from pyproject.toml file: (PEP518)\n                with open(pyproject_toml_path) as f:\n                    build_sys = toml.load(f)['build-system']\n                    if \"requires\" in build_sys:\n                        requirements += build_sys[\"requires\"]\n            elif include_build_requirements:\n                # For legacy packages with no pyproject.toml, we have to\n                # add setuptools as default build system.\n                requirements.append(\"setuptools\")\n\n            # Add requirements from metadata:\n            requirements += [\n                entry.rpartition(\"Requires-Dist:\")[2].strip()\n                for entry in metadata_entries\n                if entry.startswith(\"Requires-Dist\")\n            ]\n\n            return list(set(requirements))  # remove duplicates\n    finally:\n        rmdir(output_folder)\n\n\npackage_name_cache = dict()\n\n\ndef get_package_name(dependency,\n                     use_cache=True):\n    def timestamp():\n        try:\n            return time.monotonic()\n        except AttributeError:\n            return time.time()  # Python 2.\n    try:\n        value = package_name_cache[dependency]\n        if value[0] + 600.0 > timestamp() and use_cache:\n            return value[1]\n    except KeyError:\n        pass\n    result = _extract_info_from_package(dependency, extract_type=\"name\")\n    package_name_cache[dependency] = (timestamp(), result)\n    return result\n\n\ndef get_package_dependencies(package,\n                             recursive=False,\n                             verbose=False,\n                             include_build_requirements=False):\n    \"\"\" Obtain the dependencies from a package. Please note this\n        function is possibly SLOW, especially if you enable\n        the recursive mode.\n    \"\"\"\n    packages_processed = set()\n    package_queue = [package]\n    reqs = set()\n    reqs_as_names = set()\n    while len(package_queue) > 0:\n        current_queue = package_queue\n        package_queue = []\n        for package_dep in current_queue:\n            new_reqs = set()\n            if verbose:\n                print(\"get_package_dependencies: resolving dependency \"\n                      f\"to package name: {package_dep}\")\n            package = get_package_name(package_dep)\n            if package.lower() in packages_processed:\n                continue\n            if verbose:\n                print(\"get_package_dependencies: \"\n                      \"processing package: {}\".format(package))\n                print(\"get_package_dependencies: \"\n                      \"Packages seen so far: {}\".format(\n                          packages_processed\n                      ))\n            packages_processed.add(package.lower())\n\n            # Use our regular folder processing to examine:\n            new_reqs = new_reqs.union(_extract_info_from_package(\n                package_dep, extract_type=\"dependencies\",\n                debug=verbose,\n                include_build_requirements=include_build_requirements,\n            ))\n\n            # Process new requirements:\n            if verbose:\n                print('get_package_dependencies: collected '\n                      \"deps of '{}': {}\".format(\n                          package_dep, str(new_reqs),\n                      ))\n            for new_req in new_reqs:\n                try:\n                    req_name = get_package_name(new_req)\n                except ValueError as e:\n                    if new_req.find(\";\") >= 0:\n                        # Conditional dep where condition isn't met?\n                        # --> ignore it\n                        continue\n                    if verbose:\n                        print(\"get_package_dependencies: \" +\n                              \"unexpected failure to get name \" +\n                              \"of '\" + str(new_req) + \"': \" +\n                              str(e))\n                    raise RuntimeError(\n                        \"failed to get \" +\n                        \"name of dependency: \" + str(e)\n                    )\n                if req_name.lower() in reqs_as_names:\n                    continue\n                if req_name.lower() not in packages_processed:\n                    package_queue.append(new_req)\n                reqs.add(new_req)\n                reqs_as_names.add(req_name.lower())\n\n            # Bail out here if we're not scanning recursively:\n            if not recursive:\n                package_queue[:] = []  # wipe queue\n                break\n    if verbose:\n        print(\"get_package_dependencies: returning result: {}\".format(reqs))\n    return reqs\n\n\ndef get_dep_names_of_package(\n        package,\n        keep_version_pins=False,\n        recursive=False,\n        verbose=False,\n        include_build_requirements=False\n        ):\n    \"\"\" Gets the dependencies from the package in the given folder,\n        then attempts to deduce the actual package name resulting\n        from each dependency line, stripping away everything else.\n    \"\"\"\n\n    # First, obtain the dependencies:\n    dependencies = get_package_dependencies(\n        package, recursive=recursive, verbose=verbose,\n        include_build_requirements=include_build_requirements,\n    )\n    if verbose:\n        print(\"get_dep_names_of_package_folder: \" +\n              \"processing dependency list to names: \" +\n              str(dependencies))\n\n    # Transform dependencies to their stripped down names:\n    # (they can still have version pins/restrictions, conditionals, ...)\n    dependency_names = set()\n    for dep in dependencies:\n        # If we are supposed to keep exact version pins, extract first:\n        pin_to_append = \"\"\n        if keep_version_pins and \"(==\" in dep and dep.endswith(\")\"):\n            # This is a dependency of the format: 'pkg (==1.0)'\n            pin_to_append = \"==\" + dep.rpartition(\"==\")[2][:-1]\n        elif keep_version_pins and \"==\" in dep and not dep.endswith(\")\"):\n            # This is a dependency of the format: 'pkg==1.0'\n            pin_to_append = \"==\" + dep.rpartition(\"==\")[2]\n        # Now get true (and e.g. case-corrected) dependency name:\n        dep_name = get_package_name(dep) + pin_to_append\n        dependency_names.add(dep_name)\n    return dependency_names\n"
  },
  {
    "path": "pythonforandroid/recipe.py",
    "content": "from os.path import basename, dirname, exists, isdir, isfile, join, realpath, split\nimport glob\nimport hashlib\nimport json\nfrom re import match\n\nimport sh\nimport shutil\nimport fnmatch\nimport zipfile\nimport urllib.request\nfrom urllib.request import urlretrieve\nfrom os import listdir, unlink, environ, curdir, walk\nfrom sys import stdout\nfrom multiprocessing import cpu_count\nimport time\ntry:\n    from urlparse import urlparse\nexcept ImportError:\n    from urllib.parse import urlparse\n\nimport packaging.version\n\nfrom pythonforandroid.logger import (\n    logger, info, warning, debug, shprint, info_main, error)\nfrom pythonforandroid.util import (\n    current_directory, ensure_dir, BuildInterruptingException, rmdir, move,\n    touch, patch_wheel_setuptools_logging)\nfrom pythonforandroid.util import load_source as import_recipe\n\n\nurl_opener = urllib.request.build_opener()\nurl_orig_headers = url_opener.addheaders\nurllib.request.install_opener(url_opener)\n\n\nclass RecipeMeta(type):\n    def __new__(cls, name, bases, dct):\n        if name != 'Recipe':\n            if 'url' in dct:\n                dct['_url'] = dct.pop('url')\n            if 'version' in dct:\n                dct['_version'] = dct.pop('version')\n\n        return super().__new__(cls, name, bases, dct)\n\n\nclass Recipe(metaclass=RecipeMeta):\n    _url = None\n    '''The address from which the recipe may be downloaded. This is not\n    essential, it may be omitted if the source is available some other\n    way, such as via the :class:`IncludedFilesBehaviour` mixin.\n\n    If the url includes the version, you may (and probably should)\n    replace this with ``{version}``, which will automatically be\n    replaced by the :attr:`version` string during download.\n\n    .. note:: Methods marked (internal) are used internally and you\n              probably don't need to call them, but they are available\n              if you want.\n    '''\n\n    _download_headers = None\n    '''Add additional headers used when downloading the package, typically\n    for authorization purposes.\n\n    Specified as an array of tuples:\n    [(\"header1\", \"foo\"), (\"header2\", \"bar\")]\n\n    When specifying as an environment variable (DOWNLOAD_HEADER_my-package-name), use a JSON formatted fragement:\n    [[\"header1\",\"foo\"],[\"header2\", \"bar\"]]\n\n    For example, when downloading from a private\n    github repository, you can specify the following:\n    [('Authorization', 'token <your personal access token>'), ('Accept', 'application/vnd.github+json')]\n    '''\n\n    _version = None\n    '''A string giving the version of the software the recipe describes,\n    e.g. ``2.0.3`` or ``master``.'''\n\n    md5sum = None\n    '''The md5sum of the source from the :attr:`url`. Non-essential, but\n    you should try to include this, it is used to check that the download\n    finished correctly.\n    '''\n\n    sha512sum = None\n    '''The sha512sum of the source from the :attr:`url`. Non-essential, but\n    you should try to include this, it is used to check that the download\n    finished correctly.\n    '''\n\n    blake2bsum = None\n    '''The blake2bsum of the source from the :attr:`url`. Non-essential, but\n    you should try to include this, it is used to check that the download\n    finished correctly.\n    '''\n\n    depends = []\n    '''A list containing the names of any recipes that this recipe depends on.\n    '''\n\n    conflicts = []\n    '''A list containing the names of any recipes that are known to be\n    incompatible with this one.'''\n\n    opt_depends = []\n    '''A list of optional dependencies, that must be built before this\n    recipe if they are built at all, but whose presence is not essential.'''\n\n    patches = []\n    '''A list of patches to apply to the source. Values can be either a string\n    referring to the patch file relative to the recipe dir, or a tuple of the\n    string patch file and a callable, which will receive the kwargs `arch` and\n    `recipe`, which should return True if the patch should be applied.'''\n\n    python_depends = []\n    '''A list of pure-Python packages that this package requires. These\n    packages will NOT be available at build time, but will be added to the\n    list of pure-Python packages to install via pip. If you need these packages\n    at build time, you must create a recipe.'''\n\n    archs = ['armeabi']  # Not currently implemented properly\n\n    built_libraries = {}\n    \"\"\"Each recipe that builds a system library (e.g.:libffi, openssl, etc...)\n    should contain a dict holding the relevant information of the library. The\n    keys should be the generated libraries and the values the relative path of\n    the library inside his build folder. This dict will be used to perform\n    different operations:\n\n        - copy the library into the right location, depending on if it's shared\n          or static)\n        - check if we have to rebuild the library\n\n    Here an example of how it would look like for `libffi` recipe:\n\n        - `built_libraries = {'libffi.so': '.libs'}`\n\n    .. note:: in case that the built library resides in recipe's build\n              directory, you can set the following values for the relative\n              path: `'.', None or ''`\n    \"\"\"\n\n    need_stl_shared = False\n    '''Some libraries or python packages may need the c++_shared in APK.\n    We can automatically do this for any recipe if we set this property to\n    `True`'''\n\n    stl_lib_name = 'c++_shared'\n    '''\n    The default STL shared lib to use: `c++_shared`.\n\n    .. note:: Android NDK version > 17 only supports 'c++_shared', because\n        starting from NDK r18 the `gnustl_shared` lib has been deprecated.\n    '''\n\n    min_ndk_api_support = 20\n    '''\n    Minimum ndk api recipe will support.\n    '''\n\n    def get_stl_library(self, arch):\n        return join(\n            arch.ndk_lib_dir,\n            'lib{name}.so'.format(name=self.stl_lib_name),\n        )\n\n    def install_stl_lib(self, arch):\n        if not self.ctx.has_lib(\n            arch.arch, 'lib{name}.so'.format(name=self.stl_lib_name)\n        ):\n            self.install_libs(arch, self.get_stl_library(arch))\n\n    @property\n    def version(self):\n        key = 'VERSION_' + self.name\n        return environ.get(key, self._version)\n\n    @property\n    def url(self):\n        key = 'URL_' + self.name\n        return environ.get(key, self._url)\n\n    @property\n    def versioned_url(self):\n        '''A property returning the url of the recipe with ``{version}``\n        replaced by the :attr:`url`. If accessing the url, you should use this\n        property, *not* access the url directly.'''\n        if self.url is None:\n            return None\n        return self.url.format(version=self.version)\n\n    @property\n    def download_headers(self):\n        key = \"DOWNLOAD_HEADERS_\" + self.name\n        env_headers = environ.get(key)\n        if env_headers:\n            try:\n                return [tuple(h) for h in json.loads(env_headers)]\n            except Exception as ex:\n                raise ValueError(f'Invalid Download headers for {key} - must be JSON formatted as [[\"header1\",\"foo\"],[\"header2\",\"bar\"]]: {ex}')\n\n        return environ.get(key, self._download_headers)\n\n    def download_file(self, url, target, cwd=None):\n        \"\"\"\n        (internal) Download an ``url`` to a ``target``.\n        \"\"\"\n        if not url:\n            return\n\n        info('Downloading {} from {}'.format(self.name, url))\n\n        if cwd:\n            target = join(cwd, target)\n\n        parsed_url = urlparse(url)\n        if parsed_url.scheme in ('http', 'https'):\n            def report_hook(index, blksize, size):\n                if size <= 0:\n                    progression = '{0} bytes'.format(index * blksize)\n                else:\n                    progression = '{0:.2f}%'.format(\n                        index * blksize * 100. / float(size))\n                if \"CI\" not in environ:\n                    stdout.write('- Download {}\\r'.format(progression))\n                    stdout.flush()\n\n            if exists(target):\n                unlink(target)\n\n            # Download item with multiple attempts (for bad connections):\n            attempts = 0\n            seconds = 1\n            while True:\n                try:\n                    # jqueryui.com returns a 403 w/ the default user agent\n                    # Mozilla/5.0 does not handle redirection for liblzma\n                    url_opener.addheaders = [('User-agent', 'Wget/1.0')]\n                    if self.download_headers:\n                        url_opener.addheaders += self.download_headers\n                    urlretrieve(url, target, report_hook)\n                except OSError as e:\n                    attempts += 1\n                    if attempts >= 5:\n                        raise\n                    stdout.write('Download failed: {}; retrying in {} second(s)...'.format(e, seconds))\n                    time.sleep(seconds)\n                    seconds *= 2\n                    continue\n                finally:\n                    url_opener.addheaders = url_orig_headers\n                break\n            return target\n        elif parsed_url.scheme in ('git', 'git+file', 'git+ssh', 'git+http', 'git+https'):\n            if not isdir(target):\n                if url.startswith('git+'):\n                    url = url[4:]\n                # if 'version' is specified, do a shallow clone\n                if self.version:\n                    ensure_dir(target)\n                    with current_directory(target):\n                        shprint(sh.git, 'init')\n                        shprint(sh.git, 'remote', 'add', 'origin', url)\n                else:\n                    shprint(sh.git, 'clone', '--recursive', url, target)\n            with current_directory(target):\n                if self.version:\n                    shprint(sh.git, 'fetch', '--tags', '--depth', '1')\n                    shprint(sh.git, 'checkout', self.version)\n                branch = sh.git('branch', '--show-current')\n                if branch:\n                    shprint(sh.git, 'pull')\n                    shprint(sh.git, 'pull', '--recurse-submodules')\n                shprint(sh.git, 'submodule', 'update', '--recursive', '--init', '--depth', '1')\n            return target\n\n    def apply_patch(self, filename, arch, build_dir=None):\n        \"\"\"\n        Apply a patch from the current recipe directory into the current\n        build directory.\n\n        .. versionchanged:: 0.6.0\n            Add ability to apply patch from any dir via kwarg `build_dir`'''\n        \"\"\"\n        info(\"Applying patch {}\".format(filename))\n        build_dir = build_dir if build_dir else self.get_build_dir(arch)\n        filename = join(self.get_recipe_dir(), filename)\n        shprint(sh.patch, \"-t\", \"-d\", build_dir, \"-p1\",\n                \"-i\", filename, _tail=10)\n\n    def copy_file(self, filename, dest):\n        info(\"Copy {} to {}\".format(filename, dest))\n        filename = join(self.get_recipe_dir(), filename)\n        dest = join(self.build_dir, dest)\n        shutil.copy(filename, dest)\n\n    def append_file(self, filename, dest):\n        info(\"Append {} to {}\".format(filename, dest))\n        filename = join(self.get_recipe_dir(), filename)\n        dest = join(self.build_dir, dest)\n        with open(filename, \"rb\") as fd:\n            data = fd.read()\n        with open(dest, \"ab\") as fd:\n            fd.write(data)\n\n    @property\n    def name(self):\n        '''The name of the recipe, the same as the folder containing it.'''\n        modname = self.__class__.__module__\n        return modname.split(\".\", 2)[-1]\n\n    @property\n    def filtered_archs(self):\n        '''Return archs of self.ctx that are valid build archs\n        for the Recipe.'''\n        result = []\n        for arch in self.ctx.archs:\n            if not self.archs or (arch.arch in self.archs):\n                result.append(arch)\n        return result\n\n    def check_recipe_choices(self):\n        '''Checks what recipes are being built to see which of the alternative\n        and optional dependencies are being used,\n        and returns a list of these.'''\n        recipes = []\n        built_recipes = self.ctx.recipe_build_order\n        for recipe in self.depends:\n            if isinstance(recipe, (tuple, list)):\n                for alternative in recipe:\n                    if alternative in built_recipes:\n                        recipes.append(alternative)\n                        break\n        for recipe in self.opt_depends:\n            if recipe in built_recipes:\n                recipes.append(recipe)\n        return sorted(recipes)\n\n    def get_opt_depends_in_list(self, recipes):\n        '''Given a list of recipe names, returns those that are also in\n        self.opt_depends.\n        '''\n        return [recipe for recipe in recipes if recipe in self.opt_depends]\n\n    def get_build_container_dir(self, arch):\n        '''Given the arch name, returns the directory where it will be\n        built.\n\n        This returns a different directory depending on what\n        alternative or optional dependencies are being built.\n        '''\n        dir_name = self.get_dir_name()\n        return join(self.ctx.build_dir, 'other_builds',\n                    dir_name, '{}__ndk_target_{}'.format(arch, self.ctx.ndk_api))\n\n    def get_dir_name(self):\n        choices = self.check_recipe_choices()\n        dir_name = '-'.join([self.name] + choices)\n        return dir_name\n\n    def get_build_dir(self, arch):\n        '''Given the arch name, returns the directory where the\n        downloaded/copied package will be built.'''\n\n        return join(self.get_build_container_dir(arch), self.name)\n\n    def get_recipe_dir(self):\n        \"\"\"\n        Returns the local recipe directory or defaults to the core recipe\n        directory.\n        \"\"\"\n        if self.ctx.local_recipes is not None:\n            local_recipe_dir = join(self.ctx.local_recipes, self.name)\n            if exists(local_recipe_dir):\n                return local_recipe_dir\n        return join(self.ctx.root_dir, 'recipes', self.name)\n\n    # Public Recipe API to be subclassed if needed\n\n    def download_if_necessary(self):\n        if self.ctx.ndk_api < self.min_ndk_api_support:\n            error(f\"In order to build '{self.name}', you must set minimum ndk api (minapi) to `{self.min_ndk_api_support}`.\\n\")\n            exit(1)\n        info_main('Downloading {}'.format(self.name))\n        user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower()))\n        if user_dir is not None:\n            info('P4A_{}_DIR is set, skipping download for {}'.format(\n                self.name, self.name))\n            return\n        self.download()\n\n    def download(self):\n        if self.url is None:\n            info('Skipping {} download as no URL is set'.format(self.name))\n            return\n\n        url = self.versioned_url\n        expected_digests = {}\n        for alg in set(hashlib.algorithms_guaranteed) | set(('md5', 'sha512', 'blake2b')):\n            expected_digest = getattr(self, alg + 'sum') if hasattr(self, alg + 'sum') else None\n            ma = match(u'^(.+)#' + alg + u'=([0-9a-f]{32,})$', url)\n            if ma:                # fragmented URL?\n                if expected_digest:\n                    raise ValueError(\n                        ('Received {}sum from both the {} recipe '\n                         'and its url').format(alg, self.name))\n                url = ma.group(1)\n                expected_digest = ma.group(2)\n            if expected_digest:\n                expected_digests[alg] = expected_digest\n\n        ensure_dir(join(self.ctx.packages_path, self.name))\n\n        with current_directory(join(self.ctx.packages_path, self.name)):\n            filename = shprint(sh.basename, url).stdout[:-1].decode('utf-8')\n\n            do_download = True\n            marker_filename = '.mark-{}'.format(filename)\n            if exists(filename) and isfile(filename):\n                if not exists(marker_filename):\n                    shprint(sh.rm, filename)\n                else:\n                    for alg, expected_digest in expected_digests.items():\n                        current_digest = algsum(alg, filename)\n                        if current_digest != expected_digest:\n                            debug('* Generated {}sum: {}'.format(alg,\n                                                                 current_digest))\n                            debug('* Expected {}sum: {}'.format(alg,\n                                                                expected_digest))\n                            raise ValueError(\n                                ('Generated {0}sum does not match expected {0}sum '\n                                 'for {1} recipe').format(alg, self.name))\n                    do_download = False\n\n            # If we got this far, we will download\n            if do_download:\n                debug('Downloading {} from {}'.format(self.name, url))\n\n                shprint(sh.rm, '-f', marker_filename)\n                self.download_file(self.versioned_url, filename)\n                touch(marker_filename)\n\n                if exists(filename) and isfile(filename):\n                    for alg, expected_digest in expected_digests.items():\n                        current_digest = algsum(alg, filename)\n                        if current_digest != expected_digest:\n                            debug('* Generated {}sum: {}'.format(alg,\n                                                                 current_digest))\n                            debug('* Expected {}sum: {}'.format(alg,\n                                                                expected_digest))\n                            raise ValueError(\n                                ('Generated {0}sum does not match expected {0}sum '\n                                 'for {1} recipe').format(alg, self.name))\n            else:\n                info('{} download already cached, skipping'.format(self.name))\n\n    def unpack(self, arch):\n        info_main('Unpacking {} for {}'.format(self.name, arch))\n\n        build_dir = self.get_build_container_dir(arch)\n\n        user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower()))\n        if user_dir is not None:\n            info('P4A_{}_DIR exists, symlinking instead'.format(\n                self.name.lower()))\n            if exists(self.get_build_dir(arch)):\n                return\n            rmdir(build_dir)\n            ensure_dir(build_dir)\n            shprint(sh.cp, '-a', user_dir, self.get_build_dir(arch))\n            return\n\n        if self.url is None:\n            info('Skipping {} unpack as no URL is set'.format(self.name))\n            return\n\n        filename = shprint(\n            sh.basename, self.versioned_url).stdout[:-1].decode('utf-8')\n        ma = match(u'^(.+)#[a-z0-9_]{3,}=([0-9a-f]{32,})$', filename)\n        if ma:                  # fragmented URL?\n            filename = ma.group(1)\n\n        with current_directory(build_dir):\n            directory_name = self.get_build_dir(arch)\n\n            if not exists(directory_name) or not isdir(directory_name):\n                extraction_filename = join(\n                    self.ctx.packages_path, self.name, filename)\n                if isfile(extraction_filename):\n                    if extraction_filename.endswith(('.zip', '.whl')):\n                        try:\n                            sh.unzip(extraction_filename)\n                        except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2):\n                            # return code 1 means unzipping had\n                            # warnings but did complete,\n                            # apparently happens sometimes with\n                            # github zips\n                            pass\n                        fileh = zipfile.ZipFile(extraction_filename, 'r')\n                        root_directory = fileh.filelist[0].filename.split('/')[0]\n                        if root_directory != basename(directory_name):\n                            move(root_directory, directory_name)\n                    elif extraction_filename.endswith(\n                            ('.tar.gz', '.tgz', '.tar.bz2', '.tbz2', '.tar.xz', '.txz')):\n                        sh.tar('xf', extraction_filename)\n                        root_directory = sh.tar('tf', extraction_filename).split('\\n')[0].split('/')[0]\n                        if root_directory != basename(directory_name):\n                            move(root_directory, directory_name)\n                    else:\n                        raise Exception(\n                            'Could not extract {} download, it must be .zip, '\n                            '.tar.gz or .tar.bz2 or .tar.xz'.format(extraction_filename))\n                elif isdir(extraction_filename):\n                    ensure_dir(directory_name)\n                    for entry in listdir(extraction_filename):\n                        # Previously we filtered out the .git folder, but during the build process for some recipes\n                        # (e.g. when version is parsed by `setuptools_scm`) that may be needed.\n                        shprint(sh.cp, '-R',\n                                join(extraction_filename, entry),\n                                directory_name)\n                else:\n                    raise Exception(\n                        'Given path is neither a file nor a directory: {}'\n                        .format(extraction_filename))\n\n            else:\n                info('{} is already unpacked, skipping'.format(self.name))\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        \"\"\"Return the env specialized for the recipe\n        \"\"\"\n        if arch is None:\n            arch = self.filtered_archs[0]\n        env = arch.get_env(with_flags_in_cc=with_flags_in_cc)\n\n        for proxy_key in ['HTTP_PROXY', 'http_proxy', 'HTTPS_PROXY', 'https_proxy']:\n            if proxy_key in environ:\n                env[proxy_key] = environ[proxy_key]\n\n        return env\n\n    def prebuild_arch(self, arch):\n        '''Run any pre-build tasks for the Recipe. By default, this checks if\n        any prebuild_archname methods exist for the archname of the current\n        architecture, and runs them if so.'''\n        prebuild = \"prebuild_{}\".format(arch.arch.replace('-', '_'))\n        if hasattr(self, prebuild):\n            getattr(self, prebuild)()\n        else:\n            info('{} has no {}, skipping'.format(self.name, prebuild))\n\n    def is_patched(self, arch):\n        build_dir = self.get_build_dir(arch.arch)\n        return exists(join(build_dir, '.patched'))\n\n    def apply_patches(self, arch, build_dir=None):\n        '''Apply any patches for the Recipe.\n\n        .. versionchanged:: 0.6.0\n            Add ability to apply patches from any dir via kwarg `build_dir`'''\n        if self.patches:\n            info_main('Applying patches for {}[{}]'\n                      .format(self.name, arch.arch))\n\n            if self.is_patched(arch):\n                info_main('{} already patched, skipping'.format(self.name))\n                return\n\n            build_dir = build_dir if build_dir else self.get_build_dir(arch.arch)\n            for patch in self.patches:\n                if isinstance(patch, (tuple, list)):\n                    patch, patch_check = patch\n                    if not patch_check(arch=arch, recipe=self):\n                        continue\n\n                self.apply_patch(\n                        patch.format(version=self.version, arch=arch.arch),\n                        arch.arch, build_dir=build_dir)\n\n            touch(join(build_dir, '.patched'))\n\n    def should_build(self, arch):\n        '''Should perform any necessary test and return True only if it needs\n        building again. Per default we implement a library test, in case that\n        we detect so.\n        '''\n        if self.built_libraries:\n            return not all(\n                exists(lib) for lib in self.get_libraries(arch.arch)\n            )\n        return True\n\n    def build_arch(self, arch):\n        '''Run any build tasks for the Recipe. By default, this checks if\n        any build_archname methods exist for the archname of the current\n        architecture, and runs them if so.'''\n        build = \"build_{}\".format(arch.arch)\n        if hasattr(self, build):\n            getattr(self, build)()\n\n    def install_libraries(self, arch):\n        '''This method is always called after `build_arch`. In case that we\n        detect a library recipe, defined by the class attribute\n        `built_libraries`, we will copy all defined libraries into the\n        right location.\n        '''\n        if not self.built_libraries:\n            return\n        shared_libs = [\n            lib for lib in self.get_libraries(arch) if lib.endswith(\".so\")\n        ]\n        self.install_libs(arch, *shared_libs)\n\n    def postbuild_arch(self, arch):\n        '''Run any post-build tasks for the Recipe. By default, this checks if\n        any postbuild_archname methods exist for the archname of the\n        current architecture, and runs them if so.\n        '''\n        postbuild = \"postbuild_{}\".format(arch.arch)\n        if hasattr(self, postbuild):\n            getattr(self, postbuild)()\n\n        if self.need_stl_shared:\n            self.install_stl_lib(arch)\n\n    def prepare_build_dir(self, arch):\n        '''Copies the recipe data into a build dir for the given arch. By\n        default, this unpacks a downloaded recipe. You should override\n        it (or use a Recipe subclass with different behaviour) if you\n        want to do something else.\n        '''\n        self.unpack(arch)\n\n    def clean_build(self, arch=None):\n        '''Deletes all the build information of the recipe.\n\n        If arch is not None, only this arch dir is deleted. Otherwise\n        (the default) all builds for all archs are deleted.\n\n        By default, this just deletes the main build dir. If the\n        recipe has e.g. object files biglinked, or .so files stored\n        elsewhere, you should override this method.\n\n        This method is intended for testing purposes, it may have\n        strange results. Rebuild everything if this seems to happen.\n\n        '''\n        if arch is None:\n            base_dir = join(self.ctx.build_dir, 'other_builds', self.name)\n        else:\n            base_dir = self.get_build_container_dir(arch)\n        dirs = glob.glob(base_dir + '-*')\n        if exists(base_dir):\n            dirs.append(base_dir)\n        if not dirs:\n            warning('Attempted to clean build for {} but found no existing '\n                    'build dirs'.format(self.name))\n\n        for directory in dirs:\n            rmdir(directory)\n\n        # Delete any Python distributions to ensure the recipe build\n        # doesn't persist in site-packages\n        rmdir(self.ctx.python_installs_dir)\n\n    def install_libs(self, arch, *libs):\n        libs_dir = self.ctx.get_libs_dir(arch.arch)\n        if not libs:\n            warning('install_libs called with no libraries to install!')\n            return\n        args = libs + (libs_dir,)\n        shprint(sh.cp, *args)\n\n    def has_libs(self, arch, *libs):\n        return all(map(lambda lib: self.ctx.has_lib(arch.arch, lib), libs))\n\n    def get_libraries(self, arch_name, in_context=False):\n        \"\"\"Return the full path of the library depending on the architecture.\n        Per default, the build library path it will be returned, unless\n        `get_libraries` has been called with kwarg `in_context` set to\n        True.\n\n        .. note:: this method should be used for library recipes only\n        \"\"\"\n        recipe_libs = set()\n        if not self.built_libraries:\n            return recipe_libs\n        for lib, rel_path in self.built_libraries.items():\n            if not in_context:\n                abs_path = join(self.get_build_dir(arch_name), rel_path, lib)\n                if rel_path in {\".\", \"\", None}:\n                    abs_path = join(self.get_build_dir(arch_name), lib)\n            else:\n                abs_path = join(self.ctx.get_libs_dir(arch_name), lib)\n            recipe_libs.add(abs_path)\n        return recipe_libs\n\n    @classmethod\n    def recipe_dirs(cls, ctx):\n        recipe_dirs = []\n        if ctx.local_recipes is not None:\n            recipe_dirs.append(realpath(ctx.local_recipes))\n        if ctx.storage_dir:\n            recipe_dirs.append(join(ctx.storage_dir, 'recipes'))\n        recipe_dirs.append(join(ctx.root_dir, \"recipes\"))\n        return recipe_dirs\n\n    @classmethod\n    def list_recipes(cls, ctx):\n        forbidden_dirs = ('__pycache__', )\n        for recipes_dir in cls.recipe_dirs(ctx):\n            if recipes_dir and exists(recipes_dir):\n                for name in listdir(recipes_dir):\n                    if name in forbidden_dirs:\n                        continue\n                    fn = join(recipes_dir, name)\n                    if isdir(fn):\n                        yield name\n\n    @classmethod\n    def get_recipe(cls, name, ctx):\n        '''Returns the Recipe with the given name, if it exists.'''\n        name = name.lower()\n        if not hasattr(cls, \"recipes\"):\n            cls.recipes = {}\n        if name in cls.recipes:\n            return cls.recipes[name]\n\n        recipe_file = None\n        for recipes_dir in cls.recipe_dirs(ctx):\n            if not exists(recipes_dir):\n                continue\n            # Find matching folder (may differ in case):\n            for subfolder in listdir(recipes_dir):\n                if subfolder.lower() == name:\n                    recipe_file = join(recipes_dir, subfolder, '__init__.py')\n                    if exists(recipe_file):\n                        name = subfolder  # adapt to actual spelling\n                        break\n                    recipe_file = None\n            if recipe_file is not None:\n                break\n\n        else:\n            raise ValueError('Recipe does not exist: {}'.format(name))\n\n        mod = import_recipe('pythonforandroid.recipes.{}'.format(name), recipe_file)\n        if len(logger.handlers) > 1:\n            logger.removeHandler(logger.handlers[1])\n        recipe = mod.recipe\n        recipe.ctx = ctx\n        cls.recipes[name.lower()] = recipe\n        return recipe\n\n\nclass IncludedFilesBehaviour(object):\n    '''Recipe mixin class that will automatically unpack files included in\n    the recipe directory.'''\n    src_filename = None\n\n    def prepare_build_dir(self, arch):\n        if self.src_filename is None:\n            raise BuildInterruptingException(\n                'IncludedFilesBehaviour failed: no src_filename specified')\n        rmdir(self.get_build_dir(arch))\n        shprint(sh.cp, '-a', join(self.get_recipe_dir(), self.src_filename),\n                self.get_build_dir(arch))\n\n\nclass BootstrapNDKRecipe(Recipe):\n    '''A recipe class for recipes built in an Android project jni dir with\n    an Android.mk. These are not cached separately, but built in the\n    bootstrap's own building directory.\n\n    To build an NDK project which is not part of the bootstrap, see\n    :class:`~pythonforandroid.recipe.NDKRecipe`.\n\n    To link with python, call the method :meth:`get_recipe_env`\n    with the kwarg *with_python=True*.\n    '''\n\n    dir_name = None  # The name of the recipe build folder in the jni dir\n\n    def get_build_container_dir(self, arch):\n        return self.get_jni_dir()\n\n    def get_build_dir(self, arch):\n        if self.dir_name is None:\n            raise ValueError('{} recipe doesn\\'t define a dir_name, but '\n                             'this is necessary'.format(self.name))\n        return join(self.get_build_container_dir(arch), self.dir_name)\n\n    def get_jni_dir(self):\n        return join(self.ctx.bootstrap.build_dir, 'jni')\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=False):\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        if not with_python:\n            return env\n\n        env['PYTHON_INCLUDE_ROOT'] = self.ctx.python_recipe.include_root(arch.arch)\n        env['PYTHON_LINK_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)\n        env['EXTRA_LDLIBS'] = ' -lpython{}'.format(\n            self.ctx.python_recipe.link_version)\n        return env\n\n\nclass NDKRecipe(Recipe):\n    '''A recipe class for any NDK project not included in the bootstrap.'''\n\n    generated_libraries = []\n\n    def should_build(self, arch):\n        lib_dir = self.get_lib_dir(arch)\n\n        for lib in self.generated_libraries:\n            if not exists(join(lib_dir, lib)):\n                return True\n\n        return False\n\n    def get_lib_dir(self, arch):\n        return join(self.get_build_dir(arch.arch), 'obj', 'local', arch.arch)\n\n    def get_jni_dir(self, arch):\n        return join(self.get_build_dir(arch.arch), 'jni')\n\n    def build_arch(self, arch, *extra_args):\n        super().build_arch(arch)\n\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            shprint(\n                sh.Command(join(self.ctx.ndk_dir, \"ndk-build\")),\n                'V=1',\n                \"-j\",\n                str(cpu_count()),\n                'NDK_DEBUG=' + (\"1\" if self.ctx.build_as_debuggable else \"0\"),\n                'APP_PLATFORM=android-' + str(self.ctx.ndk_api),\n                'APP_ABI=' + arch.arch,\n                *extra_args, _env=env\n            )\n\n\nclass PythonRecipe(Recipe):\n    site_packages_name = None\n    '''The name of the module's folder when installed in the Python\n    site-packages (e.g. for pyjnius it is 'jnius')'''\n\n    call_hostpython_via_targetpython = True\n    '''If True, tries to install the module using the hostpython binary\n    copied to the target (normally arm) python build dir. However, this\n    will fail if the module tries to import e.g. _io.so. Set this to False\n    to call hostpython from its own build dir, installing the module in\n    the right place via arguments to setup.py. However, this may not set\n    the environment correctly and so False is not the default.'''\n\n    install_in_hostpython = False\n    '''If True, additionally installs the module in the hostpython build\n    dir. This will make it available to other recipes if\n    call_hostpython_via_targetpython is False.\n    '''\n\n    install_in_targetpython = True\n    '''If True, installs the module in the targetpython installation dir.\n    This is almost always what you want to do.'''\n\n    setup_extra_args = []\n    '''List of extra arguments to pass to setup.py'''\n\n    depends = ['python3']\n    '''\n    .. note:: it's important to keep this depends as a class attribute outside\n              `__init__` because sometimes we only initialize the class, so the\n              `__init__` call won't be called and the deps would be missing\n              (which breaks the dependency graph computation)\n\n    .. warning:: don't forget to call `super().__init__()` in any recipe's\n                 `__init__`, or otherwise it may not be ensured that it depends\n                 on python2 or python3 which can break the dependency graph\n    '''\n\n    hostpython_prerequisites = ['setuptools']\n    '''List of hostpython packages required to build a recipe'''\n\n    _host_recipe = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if 'python3' not in self.depends:\n            # We ensure here that the recipe depends on python even it overrode\n            # `depends`. We only do this if it doesn't already depend on any\n            # python, since some recipes intentionally don't depend on/work\n            # with all python variants\n            depends = self.depends\n            depends.append('python3')\n            depends = list(set(depends))\n            self.depends = depends\n\n    def prebuild_arch(self, arch):\n        self._host_recipe = Recipe.get_recipe(\"hostpython3\", self.ctx)\n        return super().prebuild_arch(arch)\n\n    def clean_build(self, arch=None):\n        super().clean_build(arch=arch)\n        name = self.folder_name\n        python_install_dirs = glob.glob(join(self.ctx.python_installs_dir, '*'))\n        for python_install in python_install_dirs:\n            site_packages_dir = glob.glob(join(python_install, 'lib', 'python*',\n                                               'site-packages'))\n            if site_packages_dir:\n                build_dir = join(site_packages_dir[0], name)\n                if exists(build_dir):\n                    info('Deleted {}'.format(build_dir))\n                    rmdir(build_dir)\n\n    @property\n    def real_hostpython_location(self):\n        host_name = 'host{}'.format(self.ctx.python_recipe.name)\n        if host_name == 'hostpython3':\n            return self._host_recipe.python_exe\n        else:\n            python_recipe = self.ctx.python_recipe\n            return 'python{}'.format(python_recipe.version)\n\n    @property\n    def hostpython_location(self):\n        if not self.call_hostpython_via_targetpython:\n            return self.real_hostpython_location\n        return self.ctx.hostpython\n\n    @property\n    def folder_name(self):\n        '''The name of the build folders containing this recipe.'''\n        name = self.site_packages_name\n        if name is None:\n            name = self.name\n        return name\n\n    def patch_shebang(self, _file, original_bin):\n        _file_des = open(_file, \"r\")\n\n        try:\n            data = _file_des.readlines()\n        except UnicodeDecodeError:\n            return\n\n        if \"#!\" in (line := data[0]):\n            if line.split(\"#!\")[-1].strip() == original_bin:\n                return\n\n            info(f\"Fixing shebang for '{_file}'\")\n            data.pop(0)\n            data.insert(0, \"#!\" + original_bin + \"\\n\")\n            _file_des.close()\n            _file_des = open(_file, \"w\")\n            _file_des.write(\"\".join(data))\n            _file_des.close()\n\n    def patch_shebangs(self, path, original_bin):\n        if not isdir(path):\n            warning(f\"Shebang patch skipped: '{path}' does not exist.\")\n            return\n        # set correct shebang\n        for file in listdir(path):\n            _file = join(path, file)\n            self.patch_shebang(_file, original_bin)\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        if self._host_recipe is None:\n            self._host_recipe = Recipe.get_recipe(\"hostpython3\", self.ctx)\n\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        # Set the LANG, this isn't usually important but is a better default\n        # as it occasionally matters how Python e.g. reads files\n        env['LANG'] = \"en_GB.UTF-8\"\n\n        # Binaries made by packages installed by pip\n        self.patch_shebangs(self._host_recipe.local_bin, self._host_recipe.python_exe)\n        env[\"PATH\"] = self._host_recipe.local_bin + \":\" + self._host_recipe.site_bin + \":\" + env[\"PATH\"]\n\n        host_env = self.get_hostrecipe_env(arch)\n        env['PYTHONPATH'] = host_env[\"PYTHONPATH\"]\n\n        if not self.call_hostpython_via_targetpython:\n            env['CFLAGS'] += ' -I{}'.format(\n                self.ctx.python_recipe.include_root(arch.arch)\n            )\n            env['LDFLAGS'] += ' -L{} -lpython{}'.format(\n                self.ctx.python_recipe.link_root(arch.arch),\n                self.ctx.python_recipe.link_version,\n            )\n\n        return env\n\n    def should_build(self, arch):\n        name = self.folder_name\n        if self.ctx.has_package(name, arch):\n            info('Python package already exists in site-packages')\n            return False\n        info('{} apparently isn\\'t already in site-packages'.format(name))\n        return True\n\n    def build_arch(self, arch):\n        '''Install the Python module by calling setup.py install with\n        the target Python dir.'''\n        self.install_hostpython_prerequisites()\n        super().build_arch(arch)\n        self.install_python_package(arch)\n\n    def install_python_package(self, arch, name=None, env=None, is_dir=True):\n        '''Automate the installation of a Python package (or a cython\n        package where the cython components are pre-built).'''\n        # arch = self.filtered_archs[0]  # old kivy-ios way\n        if name is None:\n            name = self.name\n        if env is None:\n            env = self.get_recipe_env(arch)\n\n        info('Installing {} into site-packages'.format(self.name))\n\n        hpenv = env.copy()\n        with current_directory(self.get_build_dir(arch.arch)):\n            shprint(self._host_recipe.pip, 'install', '.',\n                    '--compile', '--target',\n                    self.ctx.get_python_install_dir(arch.arch),\n                    _env=hpenv, *self.setup_extra_args\n            )\n\n    def get_hostrecipe_env(self, arch=None):\n        env = environ.copy()\n        _python_path = self._host_recipe.get_path_to_python()\n        libdir = glob.glob(join(_python_path, \"build\", \"lib*\"))\n        env['PYTHONPATH'] = self._host_recipe.site_dir + \":\" + join(\n            _python_path, \"Modules\") + \":\" + (libdir[0] if libdir else \"\")\n        return env\n\n    @property\n    def hostpython_site_dir(self):\n        return join(dirname(self.real_hostpython_location), 'Lib', 'site-packages')\n\n    def install_hostpython_package(self, arch):\n        env = self.get_hostrecipe_env(arch)\n        shprint(self._host_recipe.pip, 'install', '.',\n                '--compile',\n                '--root={}'.format(self._host_recipe.site_root),\n                _env=env, *self.setup_extra_args)\n\n    @property\n    def python_major_minor_version(self):\n        parsed_version = packaging.version.parse(self.ctx.python_recipe.version)\n        return f\"{parsed_version.major}.{parsed_version.minor}\"\n\n    def install_hostpython_prerequisites(self, packages=None, force_upgrade=True):\n        if not packages:\n            packages = self.hostpython_prerequisites\n\n        if len(packages) == 0:\n            return\n\n        pip_options = [\n            \"install\",\n            *packages,\n            \"--target\", self._host_recipe.site_dir, \"--python-version\",\n            self.ctx.python_recipe.version,\n            # Don't use sources, instead wheels\n            \"--only-binary=:all:\",\n        ]\n        if force_upgrade:\n            pip_options.append(\"--upgrade\")\n        # Use system's pip\n        pip_env = self.get_hostrecipe_env()\n        shprint(self._host_recipe.pip, *pip_options, _env=pip_env)\n\n    def restore_hostpython_prerequisites(self, packages):\n        _packages = []\n        for package in packages:\n            original_version = Recipe.get_recipe(package, self.ctx).version\n            _packages.append(package + \"==\" + original_version)\n        self.install_hostpython_prerequisites(packages=_packages)\n\n\nclass CompiledComponentsPythonRecipe(PythonRecipe):\n    pre_build_ext = False\n\n    build_cmd = 'build_ext'\n\n    def build_arch(self, arch):\n        '''Build any cython components, then install the Python module by\n        calling pip install with the target Python dir.\n        '''\n        Recipe.build_arch(self, arch)\n        self.install_hostpython_prerequisites()\n        self.build_compiled_components(arch)\n        self.install_python_package(arch)\n\n    def build_compiled_components(self, arch):\n        info('Building compiled components in {}'.format(self.name))\n\n        env = self.get_recipe_env(arch)\n        hostpython = sh.Command(self.hostpython_location)\n        with current_directory(self.get_build_dir(arch.arch)):\n            if self.install_in_hostpython:\n                shprint(hostpython, 'setup.py', 'clean', '--all', _env=env)\n            shprint(hostpython, 'setup.py', self.build_cmd, '-v',\n                    _env=env, *self.setup_extra_args)\n            build_dir = glob.glob('build/lib.*')[0]\n            shprint(sh.find, build_dir, '-name', '\"*.o\"', '-exec',\n                    env['STRIP'], '{}', ';', _env=env)\n\n    def install_hostpython_package(self, arch):\n        env = self.get_hostrecipe_env(arch)\n        self.rebuild_compiled_components(arch, env)\n        super().install_hostpython_package(arch)\n\n    def rebuild_compiled_components(self, arch, env):\n        info('Rebuilding compiled components in {}'.format(self.name))\n\n        hostpython = sh.Command(self.real_hostpython_location)\n        shprint(hostpython, 'setup.py', 'clean', '--all', _env=env)\n        shprint(hostpython, 'setup.py', self.build_cmd, '-v', _env=env,\n                *self.setup_extra_args)\n\n\nclass CppCompiledComponentsPythonRecipe(CompiledComponentsPythonRecipe):\n    \"\"\" Extensions that require the cxx-stl \"\"\"\n    call_hostpython_via_targetpython = False\n    need_stl_shared = True\n\n\nclass CythonRecipe(PythonRecipe):\n    pre_build_ext = False\n    cythonize = True\n    cython_args = []\n    call_hostpython_via_targetpython = False\n\n    def build_arch(self, arch):\n        '''Build any cython components, then install the Python module by\n        calling pip install with the target Python dir.\n        '''\n        Recipe.build_arch(self, arch)\n        self.build_cython_components(arch)\n        self.install_python_package(arch)\n\n    def build_cython_components(self, arch):\n        info('Cythonizing anything necessary in {}'.format(self.name))\n\n        env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            hostpython = sh.Command(self.ctx.hostpython)\n            shprint(hostpython, '-c', 'import sys; print(sys.path)', _env=env)\n            debug('cwd is {}'.format(realpath(curdir)))\n            info('Trying first build of {} to get cython files: this is '\n                 'expected to fail'.format(self.name))\n\n            manually_cythonise = False\n            try:\n                shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env,\n                        *self.setup_extra_args)\n            except sh.ErrorReturnCode_1:\n                print()\n                info('{} first build failed (as expected)'.format(self.name))\n                manually_cythonise = True\n\n            if manually_cythonise:\n                self.cythonize_build(env=env)\n                shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env,\n                        _tail=20, _critical=True, *self.setup_extra_args)\n            else:\n                info('First build appeared to complete correctly, skipping manual'\n                     'cythonising.')\n\n            if not self.ctx.with_debug_symbols:\n                self.strip_object_files(arch, env)\n\n    def strip_object_files(self, arch, env, build_dir=None):\n        if build_dir is None:\n            build_dir = self.get_build_dir(arch.arch)\n        with current_directory(build_dir):\n            info('Stripping object files')\n            shprint(sh.find, '.', '-iname', '*.so', '-exec',\n                    '/usr/bin/echo', '{}', ';', _env=env)\n            shprint(sh.find, '.', '-iname', '*.so', '-exec',\n                    env['STRIP'].split(' ')[0], '--strip-unneeded',\n                    # '/usr/bin/strip', '--strip-unneeded',\n                    '{}', ';', _env=env)\n\n    def cythonize_file(self, env, build_dir, filename):\n        short_filename = filename\n        if filename.startswith(build_dir):\n            short_filename = filename[len(build_dir) + 1:]\n        info(u\"Cythonize {}\".format(short_filename))\n        cyenv = env.copy()\n        if 'CYTHONPATH' in cyenv:\n            cyenv['PYTHONPATH'] = cyenv['CYTHONPATH']\n        elif 'PYTHONPATH' in cyenv:\n            del cyenv['PYTHONPATH']\n        if 'PYTHONNOUSERSITE' in cyenv:\n            cyenv.pop('PYTHONNOUSERSITE')\n        python_command = sh.Command(\"python{}\".format(\n            self.ctx.python_recipe.major_minor_version_string.split(\".\")[0]\n        ))\n        shprint(python_command, \"-c\"\n                \"import sys; from Cython.Compiler.Main import setuptools_main; sys.exit(setuptools_main());\",\n                filename, *self.cython_args, _env=cyenv)\n\n    def cythonize_build(self, env, build_dir=\".\"):\n        if not self.cythonize:\n            info('Running cython cancelled per recipe setting')\n            return\n        info('Running cython where appropriate')\n        for root, dirnames, filenames in walk(\".\"):\n            for filename in fnmatch.filter(filenames, \"*.pyx\"):\n                self.cythonize_file(env, build_dir, join(root, filename))\n\n    def get_recipe_env(self, arch, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        env['LDFLAGS'] = env['LDFLAGS'] + ' -L{} '.format(\n            self.ctx.get_libs_dir(arch.arch) +\n            ' -L{} '.format(self.ctx.libs_dir) +\n            ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'obj', 'local',\n                                arch.arch)))\n\n        env['LDSHARED'] = env['CC'] + ' -shared'\n        # shprint(sh.whereis, env['LDSHARED'], _env=env)\n        env['LIBLINK'] = 'NOTNONE'\n        if self.ctx.copy_libs:\n            env['COPYLIBS'] = '1'\n\n        # Every recipe uses its own liblink path, object files are\n        # collected and biglinked later\n        liblink_path = join(self.get_build_container_dir(arch.arch),\n                            'objects_{}'.format(self.name))\n        env['LIBLINK_PATH'] = liblink_path\n        ensure_dir(liblink_path)\n\n        return env\n\n\nclass PyProjectRecipe(PythonRecipe):\n    \"\"\"Recipe for projects which contain `pyproject.toml`\"\"\"\n\n    # Extra args to pass to `python -m build ...`\n    extra_build_args = []\n    call_hostpython_via_targetpython = False\n\n    def get_recipe_env(self, arch, **kwargs):\n        # Custom hostpython\n        self.ctx.python_recipe.python_exe = join(\n            self.ctx.python_recipe.get_build_dir(arch), \"android-build\", \"python3\")\n        env = super().get_recipe_env(arch, **kwargs)\n        build_dir = self.get_build_dir(arch)\n        ensure_dir(build_dir)\n        build_opts = join(build_dir, \"build-opts.cfg\")\n\n        with open(build_opts, \"w\") as file:\n            file.write(\"[bdist_wheel]\\nplat_name={}\".format(\n                self.get_wheel_platform_tag(arch)\n            ))\n            file.close()\n\n        env[\"DIST_EXTRA_CONFIG\"] = build_opts\n        return env\n\n    def get_wheel_platform_tag(self, arch):\n        # https://peps.python.org/pep-0738/#packaging\n        # official python only supports 64 bit:\n        # android_21_arm64_v8a\n        # android_21_x86_64\n        return f\"android_{self.ctx.ndk_api}_\" + {\n            \"arm64-v8a\": \"arm64_v8a\",\n            \"x86_64\": \"x86_64\",\n            \"armeabi-v7a\": \"arm\",\n            \"x86\": \"i686\",\n        }[arch.arch]\n\n    def install_wheel(self, arch, built_wheels):\n        with patch_wheel_setuptools_logging():\n            from wheel.cli.tags import tags as wheel_tags\n            from wheel.wheelfile import WheelFile\n        _wheel = built_wheels[0]\n        built_wheel_dir = dirname(_wheel)\n        # Fix wheel platform tag\n        wheel_tag = wheel_tags(\n            _wheel,\n            platform_tags=self.get_wheel_platform_tag(arch),\n            remove=True,\n        )\n        selected_wheel = join(built_wheel_dir, wheel_tag)\n\n        _dev_wheel_dir = environ.get(\"P4A_WHEEL_DIR\", False)\n        if _dev_wheel_dir:\n            ensure_dir(_dev_wheel_dir)\n            shprint(sh.cp, selected_wheel, _dev_wheel_dir)\n\n        info(f\"Installing built wheel: {wheel_tag}\")\n        destination = self.ctx.get_python_install_dir(arch.arch)\n        with WheelFile(selected_wheel) as wf:\n            for zinfo in wf.filelist:\n                wf.extract(zinfo, destination)\n            wf.close()\n\n    def build_arch(self, arch):\n\n        build_dir = self.get_build_dir(arch.arch)\n        if not (isfile(join(build_dir, \"pyproject.toml\")) or isfile(join(build_dir, \"setup.py\"))):\n            warning(\"Skipping build because it does not appear to be a Python project.\")\n            return\n\n        self.install_hostpython_prerequisites(\n            packages=[\"build[virtualenv]\", \"pip\", \"setuptools\", \"patchelf\"] + self.hostpython_prerequisites\n        )\n        self.patch_shebangs(self._host_recipe.site_bin, self.real_hostpython_location)\n\n        env = self.get_recipe_env(arch, with_flags_in_cc=True)\n        # make build dir separately\n        sub_build_dir = join(build_dir, \"p4a_android_build\")\n        ensure_dir(sub_build_dir)\n        # copy hostpython to built python to ensure correct selection of libs and includes\n        shprint(sh.cp, self.real_hostpython_location, self.ctx.python_recipe.python_exe)\n\n        build_args = [\n            \"-m\",\n            \"build\",\n            \"--wheel\",\n            \"--config-setting\",\n            \"builddir={}\".format(sub_build_dir),\n        ] + self.extra_build_args\n\n        built_wheels = []\n        with current_directory(build_dir):\n            shprint(\n                sh.Command(self.ctx.python_recipe.python_exe), *build_args, _env=env\n            )\n            built_wheels = [realpath(whl) for whl in glob.glob(\"dist/*.whl\")]\n        self.install_wheel(arch, built_wheels)\n\n\nclass MesonRecipe(PyProjectRecipe):\n    '''Recipe for projects which uses meson as build system'''\n\n    meson_version = \"1.4.0\"\n    ninja_version = \"1.11.1.1\"\n\n    skip_python = False\n    '''If true, skips all Python build and installation steps.\n    Useful for Meson projects written purely in C/C++ without Python bindings.'''\n\n    def sanitize_flags(self, *flag_strings):\n        return \" \".join(flag_strings).strip().split(\" \")\n\n    def get_recipe_meson_options(self, arch):\n        env = self.get_recipe_env(arch, with_flags_in_cc=True)\n        return {\n            \"binaries\": {\n                \"c\": arch.get_clang_exe(with_target=True),\n                \"cpp\": arch.get_clang_exe(with_target=True, plus_plus=True),\n                \"ar\": self.ctx.ndk.llvm_ar,\n                \"strip\": self.ctx.ndk.llvm_strip,\n            },\n            \"built-in options\": {\n                \"c_args\": self.sanitize_flags(env[\"CFLAGS\"], env[\"CPPFLAGS\"]),\n                \"cpp_args\": self.sanitize_flags(env[\"CXXFLAGS\"], env[\"CPPFLAGS\"]),\n                \"c_link_args\": self.sanitize_flags(env[\"LDFLAGS\"]),\n                \"cpp_link_args\": self.sanitize_flags(env[\"LDFLAGS\"]),\n                \"fortran_link_args\": self.sanitize_flags(env[\"LDFLAGS\"]),\n            },\n            \"properties\": {\n                \"needs_exe_wrapper\": True,\n                \"sys_root\": self.ctx.ndk.sysroot\n            },\n            \"host_machine\": {\n                \"cpu_family\": {\n                    \"arm64-v8a\": \"aarch64\",\n                    \"armeabi-v7a\": \"arm\",\n                    \"x86_64\": \"x86_64\",\n                    \"x86\": \"x86\"\n                }[arch.arch],\n                \"cpu\": {\n                    \"arm64-v8a\": \"aarch64\",\n                    \"armeabi-v7a\": \"armv7\",\n                    \"x86_64\": \"x86_64\",\n                    \"x86\": \"i686\"\n                }[arch.arch],\n                \"endian\": \"little\",\n                \"system\": \"android\",\n            }\n        }\n\n    def write_build_options(self, arch):\n        \"\"\"Writes python dict to meson config file\"\"\"\n        option_data = \"\"\n        build_options = self.get_recipe_meson_options(arch)\n        for key in build_options.keys():\n            data_chunk = \"[{}]\".format(key)\n            for subkey in build_options[key].keys():\n                value = build_options[key][subkey]\n                if isinstance(value, int):\n                    value = str(value)\n                elif isinstance(value, str):\n                    value = \"'{}'\".format(value)\n                elif isinstance(value, bool):\n                    value = \"true\" if value else \"false\"\n                elif isinstance(value, list):\n                    value = \"['\" + \"', '\".join(value) + \"']\"\n                data_chunk += \"\\n\" + subkey + \" = \" + value\n            option_data += data_chunk + \"\\n\\n\"\n        return option_data\n\n    def ensure_args(self, *args):\n        for arg in args:\n            if arg not in self.extra_build_args:\n                self.extra_build_args.append(arg)\n\n    def build_arch(self, arch):\n        cross_file = join(\"/tmp\", \"android.meson.cross\")\n        info(\"Writing cross file at: {}\".format(cross_file))\n        # write cross config file\n        with open(cross_file, \"w\") as file:\n            file.write(self.write_build_options(arch))\n            file.close()\n        # set cross file\n        self.ensure_args('-Csetup-args=--cross-file', '-Csetup-args={}'.format(cross_file))\n        # ensure ninja and meson\n        for dep in [\n            \"ninja=={}\".format(self.ninja_version),\n            \"meson=={}\".format(self.meson_version),\n        ]:\n            if dep not in self.hostpython_prerequisites:\n                self.hostpython_prerequisites.append(dep)\n        if not self.skip_python:\n            super().build_arch(arch)\n\n\nclass RustCompiledComponentsRecipe(PyProjectRecipe):\n    # Rust toolchain codes\n    # https://doc.rust-lang.org/nightly/rustc/platform-support.html\n    RUST_ARCH_CODES = {\n        \"arm64-v8a\": \"aarch64-linux-android\",\n        \"armeabi-v7a\": \"armv7-linux-androideabi\",\n        \"x86_64\": \"x86_64-linux-android\",\n        \"x86\": \"i686-linux-android\",\n    }\n\n    call_hostpython_via_targetpython = False\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n\n        # Set rust build target\n        build_target = self.RUST_ARCH_CODES[arch.arch]\n        cargo_linker_name = \"CARGO_TARGET_{}_LINKER\".format(\n            build_target.upper().replace(\"-\", \"_\")\n        )\n        env[\"CARGO_BUILD_TARGET\"] = build_target\n        env[cargo_linker_name] = join(\n            self.ctx.ndk.llvm_prebuilt_dir,\n            \"bin\",\n            \"{}{}-clang\".format(\n                # NDK's Clang format\n                build_target.replace(\"7\", \"7a\")\n                if build_target.startswith(\"armv7\")\n                else build_target,\n                self.ctx.ndk_api,\n            ),\n        )\n        realpython_dir = self.ctx.python_recipe.get_build_dir(arch.arch)\n\n        env[\"RUSTFLAGS\"] = \"-Clink-args=-L{} -L{}\".format(\n            self.ctx.get_libs_dir(arch.arch), join(realpython_dir, \"android-build\")\n        )\n\n        env[\"PYO3_CROSS_LIB_DIR\"] = realpath(glob.glob(join(\n            realpython_dir, \"android-build\", \"build\",\n            \"lib.*{}/\".format(self.python_major_minor_version),\n        ))[0])\n\n        info_main(\"Ensuring rust build toolchain\")\n        shprint(sh.rustup, \"target\", \"add\", build_target)\n\n        # Add host python to PATH\n        env[\"PATH\"] = (\"{hostpython_dir}:{old_path}\").format(\n            hostpython_dir=Recipe.get_recipe(\n                \"hostpython3\", self.ctx\n            ).get_path_to_python(),\n            old_path=env[\"PATH\"],\n        )\n        return env\n\n    def check_host_deps(self):\n        if not hasattr(sh, \"rustup\"):\n            error(\n                \"`rustup` was not found on host system.\"\n                \"Please install it using :\"\n                \"\\n`curl https://sh.rustup.rs -sSf | sh`\\n\"\n            )\n            exit(1)\n\n    def build_arch(self, arch):\n        self.check_host_deps()\n        super().build_arch(arch)\n\n\nclass TargetPythonRecipe(Recipe):\n    '''Class for target python recipes. Sets ctx.python_recipe to point to\n    itself, so as to know later what kind of Python was built or used.'''\n\n    def __init__(self, *args, **kwargs):\n        self._ctx = None\n        super().__init__(*args, **kwargs)\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        self.ctx.python_recipe = self\n\n    def include_root(self, arch):\n        '''The root directory from which to include headers.'''\n        raise NotImplementedError('Not implemented in TargetPythonRecipe')\n\n    def link_root(self):\n        raise NotImplementedError('Not implemented in TargetPythonRecipe')\n\n    @property\n    def major_minor_version_string(self):\n        parsed_version = packaging.version.parse(self.version)\n        return f\"{parsed_version.major}.{parsed_version.minor}\"\n\n    def create_python_bundle(self, dirn, arch):\n        \"\"\"\n        Create a packaged python bundle in the target directory, by\n        copying all the modules and standard library to the right\n        place.\n        \"\"\"\n        raise NotImplementedError('{} does not implement create_python_bundle'.format(self))\n\n    def reduce_object_file_names(self, dirn):\n        \"\"\"Recursively renames all files named XXX.cpython-...-linux-gnu.so\"\n        to \"XXX.so\", i.e. removing the erroneous architecture name\n        coming from the local system.\n        \"\"\"\n        py_so_files = shprint(sh.find, dirn, '-iname', '*.so')\n        filens = py_so_files.stdout.decode('utf-8').split('\\n')[:-1]\n        for filen in filens:\n            file_dirname, file_basename = split(filen)\n            parts = file_basename.split('.')\n            if len(parts) <= 2:\n                continue\n            # PySide6 libraries end with .abi3.so\n            if parts[1] == \"abi3\":\n                continue\n            move(filen, join(file_dirname, parts[0] + '.so'))\n\n\ndef algsum(alg, filen):\n    '''Calculate the digest of a file.\n    '''\n    with open(filen, 'rb') as fileh:\n        digest = getattr(hashlib, alg)(fileh.read())\n\n    return digest.hexdigest()\n"
  },
  {
    "path": "pythonforandroid/recipes/Pillow/__init__.py",
    "content": "from os.path import join\n\nfrom pythonforandroid.recipe import PyProjectRecipe\n\n\nclass PillowRecipe(PyProjectRecipe):\n    \"\"\"\n    A recipe for Pillow (previously known as Pil).\n\n    This recipe allow us to build the Pillow recipe with support for different\n    types of images and fonts. But you should be aware, that in order to  use\n    some of the features of  Pillow, we must build some libraries. By default\n    we automatically trigger the build of below libraries::\n\n        - freetype: rendering fonts support.\n        - harfbuzz: a text shaping library.\n        - jpeg: reading and writing JPEG image files.\n        - png: support for PNG images.\n\n    But you also could enable the build of some extra image types by requesting\n    the build of some libraries via argument `requirements`::\n\n        - libwebp: library to encode and decode images in WebP format.\n    \"\"\"\n\n    version = '11.3.0'\n    url = 'https://github.com/python-pillow/Pillow/archive/{version}.tar.gz'\n    site_packages_name = 'PIL'\n    patches = [\"setup.py.patch\"]\n    depends = ['png', 'jpeg', 'freetype']\n    hostpython_prerequisites = [\"setuptools>=77\"]\n    opt_depends = ['libwebp']\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n\n        # Add math library linkage\n        env[\"LDFLAGS\"] = env.get(\"LDFLAGS\", \"\") + \" -lm\"\n\n        jpeg = self.get_recipe('jpeg', self.ctx)\n        jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch)\n        env[\"JPEG_ROOT\"] = \"{}:{}\".format(jpeg_lib_dir, jpeg_inc_dir)\n\n        freetype = self.get_recipe('freetype', self.ctx)\n        free_lib_dir = join(freetype.get_build_dir(arch.arch), 'objs', '.libs')\n        free_inc_dir = join(freetype.get_build_dir(arch.arch), 'include')\n        env[\"FREETYPE_ROOT\"] = \"{}:{}\".format(free_lib_dir, free_inc_dir)\n\n        # harfbuzz is a direct dependency of freetype and we need the proper\n        # flags to successfully build the Pillow recipe, so we add them here.\n        harfbuzz = self.get_recipe('harfbuzz', self.ctx)\n        harf_lib_dir = join(harfbuzz.get_build_dir(arch.arch), 'src', '.libs')\n        harf_inc_dir = harfbuzz.get_build_dir(arch.arch)\n        env[\"HARFBUZZ_ROOT\"] = \"{}:{}\".format(harf_lib_dir, harf_inc_dir)\n\n        env[\"ZLIB_ROOT\"] = f\"{arch.ndk_lib_dir_versioned}:{self.ctx.ndk.sysroot_include_dir}\"\n\n        # libwebp is an optional dependency, so we add the\n        # flags if we have it in our `ctx.recipe_build_order`\n        if 'libwebp' in self.ctx.recipe_build_order:\n            webp = self.get_recipe('libwebp', self.ctx)\n            webp_install = join(\n                webp.get_build_dir(arch.arch), 'installation'\n            )\n            env[\"WEBP_ROOT\"] = f\"{join(webp_install, 'lib')}:{join(webp_install, 'include')}\"\n        return env\n\n\nrecipe = PillowRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/Pillow/setup.py.patch",
    "content": "diff '--color=auto' -uNr Pillow-11.3.0/setup.py Pillow-11.3.0.mod/setup.py\n--- Pillow-11.3.0/setup.py\t2025-07-01 13:11:24.000000000 +0530\n+++ Pillow-11.3.0.mod/setup.py\t2025-09-17 01:50:35.498105827 +0530\n@@ -156,6 +156,7 @@\n \n \n def _find_library_dirs_ldconfig() -> list[str]:\n+    return []\n     # Based on ctypes.util from Python 2\n \n     ldconfig = \"ldconfig\" if shutil.which(\"ldconfig\") else \"/sbin/ldconfig\"\n@@ -514,12 +515,10 @@\n \n             if root is None and root_name in os.environ:\n                 root_prefix = os.environ[root_name]\n-                root = (\n-                    os.path.join(root_prefix, \"lib\"),\n-                    os.path.join(root_prefix, \"include\"),\n-                )\n+                root = tuple(os.environ[root_name].split(\":\"))\n \n             if root is None and pkg_config:\n+                continue\n                 if isinstance(lib_name, str):\n                     _dbg(\"Looking for `%s` using pkg-config.\", lib_name)\n                     root = pkg_config(lib_name)\n@@ -565,13 +564,11 @@\n                 for d in os.environ[k].split(os.path.pathsep):\n                     _add_directory(library_dirs, d)\n \n-        _add_directory(library_dirs, os.path.join(sys.prefix, \"lib\"))\n-        _add_directory(include_dirs, os.path.join(sys.prefix, \"include\"))\n \n         #\n         # add platform directories\n \n-        if self.disable_platform_guessing:\n+        if True: # self.disable_platform_guessing:\n             pass\n \n         elif sys.platform == \"cygwin\":\n@@ -674,7 +671,7 @@\n         # FIXME: check /opt/stuff directories here?\n \n         # standard locations\n-        if not self.disable_platform_guessing:\n+        if False: # not self.disable_platform_guessing:\n             _add_directory(library_dirs, \"/usr/local/lib\")\n             _add_directory(include_dirs, \"/usr/local/include\")\n \n"
  },
  {
    "path": "pythonforandroid/recipes/__init__.py",
    "content": ""
  },
  {
    "path": "pythonforandroid/recipes/aiohttp/__init__.py",
    "content": "\"\"\"Build AIOHTTP\"\"\"\nfrom typing import List\nfrom pythonforandroid.recipe import CppCompiledComponentsPythonRecipe\n\n\nclass AIOHTTPRecipe(CppCompiledComponentsPythonRecipe):  # type: ignore # pylint: disable=R0903\n    version = \"3.8.3\"\n    url = \"https://pypi.python.org/packages/source/a/aiohttp/aiohttp-{version}.tar.gz\"\n    name = \"aiohttp\"\n    depends: List[str] = [\"setuptools\"]\n    call_hostpython_via_targetpython = False\n    install_in_hostpython = True\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['LDFLAGS'] += ' -lc++_shared'\n        return env\n\n\nrecipe = AIOHTTPRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/android/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe, IncludedFilesBehaviour\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid import logger\n\nfrom os.path import join\n\n\nclass AndroidRecipe(IncludedFilesBehaviour, PyProjectRecipe):\n    # name = 'android'\n    version = None\n    url = None\n\n    src_filename = 'src'\n\n    depends = [('sdl3', 'sdl2', 'genericndkbuild'), 'pyjnius']\n    hostpython_prerequisites = [\"Cython>=0.29,<3.1\"]\n\n    config_env = {}\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env.update(self.config_env)\n        return env\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        ctx_bootstrap = self.ctx.bootstrap.name\n\n        # define macros for Cython, C, Python\n        tpxi = 'DEF {} = {}\\n'\n        th = '#define {} {}\\n'\n        tpy = '{} = {}\\n'\n\n        # make sure bootstrap name is in unicode\n        if isinstance(ctx_bootstrap, bytes):\n            ctx_bootstrap = ctx_bootstrap.decode('utf-8')\n        bootstrap = bootstrap_name = ctx_bootstrap\n        if bootstrap_name in [\"sdl2\", \"sdl3\", \"webview\", \"service_only\", \"service_library\", \"qt\"]:\n            java_ns = u'org.kivy.android'\n            jni_ns = u'org/kivy/android'\n        else:\n            logger.error((\n                'unsupported bootstrap for android recipe: {}'\n                ''.format(bootstrap_name)\n            ))\n            exit(1)\n\n        config = {\n            'BOOTSTRAP': bootstrap,\n            'IS_SDL2': int(bootstrap_name == \"sdl2\"),\n            'IS_SDL3': int(bootstrap_name == \"sdl3\"),\n            'PY2': 0,\n            'ANDROID_LIBS_DIR': \"{}:{}\".format(\n                self.ctx.get_libs_dir(arch.arch),\n                join(self.ctx.bootstrap.build_dir, 'obj', 'local', arch.arch)\n            ),\n            'JAVA_NAMESPACE': java_ns,\n            'JNI_NAMESPACE': jni_ns,\n            'ACTIVITY_CLASS_NAME': self.ctx.activity_class_name,\n            'ACTIVITY_CLASS_NAMESPACE': self.ctx.activity_class_name.replace('.', '/'),\n            'SERVICE_CLASS_NAME': self.ctx.service_class_name,\n        }\n\n        # create config files for Cython, C and Python\n        with (\n                current_directory(self.get_build_dir(arch.arch))), (\n                open(join('android', 'config.pxi'), 'w')) as fpxi, (\n                open(join('android', 'config.h'), 'w')) as fh, (\n                open(join('android', 'config.py'), 'w')) as fpy:\n\n            for key, value in config.items():\n                fpxi.write(tpxi.format(key, repr(value)))\n                fpy.write(tpy.format(key, repr(value)))\n\n                fh.write(th.format(\n                    key,\n                    value if isinstance(value, int) else '\"{}\"'.format(value)\n                ))\n                self.config_env[key] = str(value)\n\n            if bootstrap_name == \"sdl2\":\n                fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\\n')\n                fh.write(\n                    '#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\\n'\n                )\n            elif bootstrap_name == \"sdl3\":\n                fh.write('JNIEnv *SDL_GetAndroidJNIEnv(void);\\n')\n                fh.write(\n                    '#define SDL_ANDROID_GetJNIEnv SDL_GetAndroidJNIEnv\\n'\n                )\n            else:\n                fh.write('JNIEnv *WebView_AndroidGetJNIEnv(void);\\n')\n                fh.write(\n                    '#define SDL_ANDROID_GetJNIEnv WebView_AndroidGetJNIEnv\\n'\n                )\n\n\nrecipe = AndroidRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/__init__.py",
    "content": "'''\nAndroid module\n==============\n\n'''\n\n# legacy import\nfrom android._android import *  # noqa: F401, F403\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/_android.pyx",
    "content": "# Android-specific python services.\n\ninclude \"config.pxi\"\n\n# Android keycodes.\nKEYCODE_UNKNOWN         = 0\nKEYCODE_SOFT_LEFT       = 1\nKEYCODE_SOFT_RIGHT      = 2\nKEYCODE_HOME            = 3\nKEYCODE_BACK            = 4\nKEYCODE_CALL            = 5\nKEYCODE_ENDCALL         = 6\nKEYCODE_0               = 7\nKEYCODE_1               = 8\nKEYCODE_2               = 9\nKEYCODE_3               = 10\nKEYCODE_4               = 11\nKEYCODE_5               = 12\nKEYCODE_6               = 13\nKEYCODE_7               = 14\nKEYCODE_8               = 15\nKEYCODE_9               = 16\nKEYCODE_STAR            = 17\nKEYCODE_POUND           = 18\nKEYCODE_DPAD_UP         = 19\nKEYCODE_DPAD_DOWN       = 20\nKEYCODE_DPAD_LEFT       = 21\nKEYCODE_DPAD_RIGHT      = 22\nKEYCODE_DPAD_CENTER     = 23\nKEYCODE_VOLUME_UP       = 24\nKEYCODE_VOLUME_DOWN     = 25\nKEYCODE_POWER           = 26\nKEYCODE_CAMERA          = 27\nKEYCODE_CLEAR           = 28\nKEYCODE_A               = 29\nKEYCODE_B               = 30\nKEYCODE_C               = 31\nKEYCODE_D               = 32\nKEYCODE_E               = 33\nKEYCODE_F               = 34\nKEYCODE_G               = 35\nKEYCODE_H               = 36\nKEYCODE_I               = 37\nKEYCODE_J               = 38\nKEYCODE_K               = 39\nKEYCODE_L               = 40\nKEYCODE_M               = 41\nKEYCODE_N               = 42\nKEYCODE_O               = 43\nKEYCODE_P               = 44\nKEYCODE_Q               = 45\nKEYCODE_R               = 46\nKEYCODE_S               = 47\nKEYCODE_T               = 48\nKEYCODE_U               = 49\nKEYCODE_V               = 50\nKEYCODE_W               = 51\nKEYCODE_X               = 52\nKEYCODE_Y               = 53\nKEYCODE_Z               = 54\nKEYCODE_COMMA           = 55\nKEYCODE_PERIOD          = 56\nKEYCODE_ALT_LEFT        = 57\nKEYCODE_ALT_RIGHT       = 58\nKEYCODE_SHIFT_LEFT      = 59\nKEYCODE_SHIFT_RIGHT     = 60\nKEYCODE_TAB             = 61\nKEYCODE_SPACE           = 62\nKEYCODE_SYM             = 63\nKEYCODE_EXPLORER        = 64\nKEYCODE_ENVELOPE        = 65\nKEYCODE_ENTER           = 66\nKEYCODE_DEL             = 67\nKEYCODE_GRAVE           = 68\nKEYCODE_MINUS           = 69\nKEYCODE_EQUALS          = 70\nKEYCODE_LEFT_BRACKET    = 71\nKEYCODE_RIGHT_BRACKET   = 72\nKEYCODE_BACKSLASH       = 73\nKEYCODE_SEMICOLON       = 74\nKEYCODE_APOSTROPHE      = 75\nKEYCODE_SLASH           = 76\nKEYCODE_AT              = 77\nKEYCODE_NUM             = 78\nKEYCODE_HEADSETHOOK     = 79\nKEYCODE_FOCUS           = 80\nKEYCODE_PLUS            = 81\nKEYCODE_MENU            = 82\nKEYCODE_NOTIFICATION    = 83\nKEYCODE_SEARCH          = 84\nKEYCODE_MEDIA_PLAY_PAUSE= 85\nKEYCODE_MEDIA_STOP      = 86\nKEYCODE_MEDIA_NEXT      = 87\nKEYCODE_MEDIA_PREVIOUS  = 88\nKEYCODE_MEDIA_REWIND    = 89\nKEYCODE_MEDIA_FAST_FORWARD = 90\nKEYCODE_MUTE            = 91\n\n# Vibration support.\ncdef extern void android_vibrate(double)\n\ndef vibrate(s):\n    android_vibrate(s)\n\n# Accelerometer support.\ncdef extern void android_accelerometer_enable(int)\ncdef extern void android_accelerometer_reading(float *)\n\naccelerometer_enabled = False\n\ndef accelerometer_enable(p):\n    global accelerometer_enabled\n\n    android_accelerometer_enable(p)\n\n    accelerometer_enabled = p\n\ndef accelerometer_reading():\n    cdef float rv[3]\n    android_accelerometer_reading(rv)\n\n    return (rv[0], rv[1], rv[2])\n\n# Wifi reading support\ncdef extern void android_wifi_scanner_enable()\ncdef extern char * android_wifi_scan()\n\ndef wifi_scanner_enable():\n    android_wifi_scanner_enable()\n\ndef wifi_scan():\n    cdef char * reading\n    reading = android_wifi_scan()\n\n    reading_list = []\n\n    for line in filter(lambda l: l, reading.split('\\n')):\n        [ssid, mac, level] = line.split('\\t')\n        reading_list.append((ssid.strip(), mac.upper().strip(), int(level)))\n\n    return reading_list\n\n# DisplayMetrics information.\ncdef extern int android_get_dpi()\n\ndef get_dpi():\n    return android_get_dpi()\n\n\n# Soft keyboard.\ncdef extern void android_show_keyboard(int)\ncdef extern void android_hide_keyboard()\n\n\nfrom jnius import autoclass, PythonJavaClass, java_method, cast\n\n# API versions\napi_version = autoclass('android.os.Build$VERSION').SDK_INT\nversion_codes = autoclass('android.os.Build$VERSION_CODES')\n\n\npython_act = autoclass(ACTIVITY_CLASS_NAME)\nRect = autoclass(u'android.graphics.Rect')\nmActivity = python_act.mActivity\nif mActivity:\n    # SDL2 now does not need the listener so there is\n    # no point adding a processor intensive layout listenere here.\n    height = 0\n    def get_keyboard_height():\n        rctx = Rect()\n        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rctx)\n        # NOTE top should always be zero\n        rctx.top = 0\n        height = mActivity.getWindowManager().getDefaultDisplay().getHeight() - (rctx.bottom - rctx.top)\n        return height\nelse:\n    def get_keyboard_height():\n        return 0\n\n# Flags for input_type, for requesting a particular type of keyboard\n#android FLAGS\nTYPE_CLASS_DATETIME = 4\nTYPE_CLASS_NUMBER = 2\nTYPE_NUMBER_VARIATION_NORMAL = 0\nTYPE_NUMBER_VARIATION_PASSWORD = 16\nTYPE_CLASS_TEXT = 1\nTYPE_TEXT_FLAG_AUTO_COMPLETE = 65536\nTYPE_TEXT_FLAG_AUTO_CORRECT = 32768\nTYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288\nTYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32\nTYPE_TEXT_VARIATION_NORMAL = 0\nTYPE_TEXT_VARIATION_PASSWORD = 128\nTYPE_TEXT_VARIATION_POSTAL_ADDRESS = 112\nTYPE_TEXT_VARIATION_URI = 16\nTYPE_CLASS_PHONE = 3\n\nIF BOOTSTRAP in ['sdl2', 'sdl3']:\n    def remove_presplash():\n        # Remove android presplash in SDL2 bootstrap.\n        mActivity.removeLoadingScreen()\n\ndef show_keyboard(target, input_type):\n    if input_type == 'text':\n        _input_type = TYPE_CLASS_TEXT\n    elif input_type == 'number':\n        _input_type = TYPE_CLASS_NUMBER\n    elif input_type == 'url':\n        _input_type = \\\n            TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_URI\n    elif input_type == 'mail':\n        _input_type = \\\n            TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS\n    elif input_type == 'datetime':\n        _input_type = TYPE_CLASS_DATETIME\n    elif input_type == 'tel':\n        _input_type = TYPE_CLASS_PHONE\n    elif input_type == 'address':\n        _input_type = TYPE_TEXT_VARIATION_POSTAL_ADDRESS\n\n    if hasattr(target, 'password') and target.password:\n        if _input_type == TYPE_CLASS_TEXT:\n            _input_type |= TYPE_TEXT_VARIATION_PASSWORD\n        elif _input_type == TYPE_CLASS_NUMBER:\n            _input_type |= TYPE_NUMBER_VARIATION_PASSWORD\n\n    if hasattr(target, 'keyboard_suggestions') and not target.keyboard_suggestions:\n        if _input_type == TYPE_CLASS_TEXT:\n            _input_type = TYPE_CLASS_TEXT | \\\n                TYPE_TEXT_FLAG_NO_SUGGESTIONS\n\n    android_show_keyboard(_input_type)\n\ndef hide_keyboard():\n    android_hide_keyboard()\n\n# Build info.\ncdef extern char* BUILD_MANUFACTURER\ncdef extern char* BUILD_MODEL\ncdef extern char* BUILD_PRODUCT\ncdef extern char* BUILD_VERSION_RELEASE\n\ncdef extern void android_get_buildinfo()\n\nclass BuildInfo:\n    MANUFACTURER = None\n    MODEL = None\n    PRODUCT = None\n    VERSION_RELEASE = None\n\ndef get_buildinfo():\n    android_get_buildinfo()\n    binfo = BuildInfo()\n    binfo.MANUFACTURER = BUILD_MANUFACTURER\n    binfo.MODEL = BUILD_MODEL\n    binfo.PRODUCT = BUILD_PRODUCT\n    binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE\n    return binfo\n\n# -------------------------------------------------------------------\n# URL Opening.\ndef open_url(url):\n    Intent = autoclass('android.content.Intent')\n    Uri = autoclass('android.net.Uri')\n    browserIntent = Intent()\n    browserIntent.setAction(Intent.ACTION_VIEW)\n    browserIntent.setData(Uri.parse(url))\n    currentActivity = cast('android.app.Activity', mActivity)\n    currentActivity.startActivity(browserIntent)\n    return True\n\n# Web browser support.\nclass AndroidBrowser(object):\n    def open(self, url, new=0, autoraise=True):\n        return open_url(url)\n    def open_new(self, url):\n        return open_url(url)\n    def open_new_tab(self, url):\n        return open_url(url)\n    \nimport webbrowser\nwebbrowser.register('android', AndroidBrowser)\n\n\ndef start_service(title=\"Background Service\",\n                  description=\"\", arg=\"\",\n                  as_foreground=True):\n    # Legacy None value support (for old function signature style):\n    if title is None:\n        title = \"Background Service\"\n    if description is None:\n        description = \"\"\n    if arg is None:\n        arg = \"\"\n\n    # Start service:\n    mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity\n    if as_foreground:\n        mActivity.start_service(\n            title, description, arg\n        )\n    else:\n        mActivity.start_service_not_as_foreground(\n            title, description, arg\n        )\n\n\ncdef extern void android_stop_service()\ndef stop_service():\n    android_stop_service()\n\nclass AndroidService(object):\n    '''Android service class.\n    Run ``service/main.py`` from application directory as a service.\n\n    :Parameters:\n        `title`: str, default to 'Python service'\n            Notification title.\n\n        `description`: str, default to 'Kivy Python service started'\n            Notification text.\n    '''\n\n    def __init__(self, title='Python service',\n                 description='Kivy Python service started'):\n        self.title = title\n        self.description = description\n\n    def start(self, arg=''):\n        '''Start the service.\n\n        :Parameters:\n            `arg`: str, default to ''\n                Argument to pass to a service,\n                through environment variable ``PYTHON_SERVICE_ARGUMENT``.\n        '''\n        start_service(self.title, self.description, arg)\n\n    def stop(self):\n        '''Stop the service.\n        '''\n        stop_service()\n\n\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/_android_billing.pyx",
    "content": "# -------------------------------------------------------------------\n# Billing\ncdef extern void android_billing_service_start()\ncdef extern void android_billing_service_stop()\ncdef extern void android_billing_buy(char *sku)\ncdef extern char *android_billing_get_purchased_items()\ncdef extern char *android_billing_get_pending_message()\n\nclass BillingService(object):\n\n    BILLING_ACTION_SUPPORTED = 'billingsupported'\n    BILLING_ACTION_ITEMSCHANGED = 'itemschanged'\n\n    BILLING_TYPE_INAPP = 'inapp'\n    BILLING_TYPE_SUBSCRIPTION = 'subs'\n\n    def __init__(self, callback):\n        super().__init__()\n        self.callback = callback\n        self.purchased_items = None\n        android_billing_service_start()\n\n    def _stop(self):\n        android_billing_service_stop()\n\n    def buy(self, sku):\n        cdef char *j_sku = <bytes>sku\n        android_billing_buy(j_sku)\n\n    def get_purchased_items(self):\n        cdef char *items = NULL\n        cdef bytes pitem\n        items = android_billing_get_purchased_items()\n        if items == NULL:\n            return []\n        pitems = items\n        ret = {}\n        for item in pitems.split('\\n'):\n            if not item:\n                continue\n            sku, qt = item.split(',')\n            ret[sku] = {'qt': int(qt)}\n        return ret\n\n    def check(self, *largs):\n        cdef char *message\n        cdef bytes pymessage\n\n        while True:\n            message = android_billing_get_pending_message()\n            if message == NULL:\n                break\n            pymessage = <bytes>message\n            self._handle_message(pymessage)\n\n        if self.purchased_items is None:\n            self._check_new_items()\n\n    def _handle_message(self, message):\n        action, data = message.split('|', 1)\n        #print \"HANDLE MESSAGE-----\", (action, data)\n\n        if action == 'billingSupported':\n            tp, value = data.split('|')\n            value = True if value == '1' else False\n            self.callback(BillingService.BILLING_ACTION_SUPPORTED, tp, value)\n\n        elif action == 'requestPurchaseResponse':\n            self._check_new_items()\n\n        elif action == 'purchaseStateChange':\n            self._check_new_items()\n\n        elif action == 'restoreTransaction':\n            self._check_new_items()\n\n    def _check_new_items(self):\n        items = self.get_purchased_items()\n        if self.purchased_items != items:\n            self.purchased_items = items\n            self.callback(BillingService.BILLING_ACTION_ITEMSCHANGED, self.purchased_items)\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/_android_billing_jni.c",
    "content": "#include <jni.h>\n#include <stdio.h>\n#include <android/log.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"config.h\"\n\n#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, \"android_jni\", \"Assertion failed. %s:%d\", __FILE__, __LINE__); abort(); }}\n#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }\n#define POP_FRAME  { (*env)->PopLocalFrame(env, NULL); }\n\nvoid android_billing_service_start() {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, JNI_NAMESPACE \"/PythonActivity\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"billingServiceStart\", \"()V\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n    (*env)->CallStaticVoidMethod(env, cls, mid);\n    POP_FRAME;\n}\n\nvoid android_billing_service_stop() {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, JNI_NAMESPACE \"/PythonActivity\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"billingServiceStop\", \"()V\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n    (*env)->CallStaticVoidMethod(env, cls, mid);\n    POP_FRAME;\n}\n\nvoid android_billing_buy(char *sku) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, JNI_NAMESPACE \"/PythonActivity\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"billingBuy\", \"(Ljava/lang/String;)V\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        (*env)->NewStringUTF(env, sku)\n        );\n\n    POP_FRAME;\n}\n\nchar *android_billing_get_purchased_items() {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n    jobject jreading;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, JNI_NAMESPACE \"/PythonActivity\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"billingGetPurchasedItems\", \"()Ljava/lang/String;\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n    jreading = (*env)->CallStaticObjectMethod(env, cls, mid);\n    const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);\n    POP_FRAME;\n\n    return reading;\n}\n\nchar *android_billing_get_pending_message() {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n    jobject jreading;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, JNI_NAMESPACE \"/PythonActivity\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"billingGetPendingMessage\", \"()Ljava/lang/String;\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n    jreading = (*env)->CallStaticObjectMethod(env, cls, mid);\n    const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);\n    POP_FRAME;\n\n    return reading;\n}\n\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/_android_jni.c",
    "content": "#include <jni.h>\n#include <stdio.h>\n#include <android/log.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"config.h\"\n\n#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, \"android_jni\", \"Assertion failed. %s:%d\", __FILE__, __LINE__); abort(); }}\n#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }\n#define POP_FRAME  { (*env)->PopLocalFrame(env, NULL); }\n\nvoid android_vibrate(double seconds) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/Hardware\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"vibrate\", \"(D)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        (jdouble) seconds);\n}\n\nvoid android_accelerometer_enable(int enable) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/Hardware\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"accelerometerEnable\", \"(Z)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        (jboolean) enable);\n}\n\nvoid android_wifi_scanner_enable(void){\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/Hardware\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"enableWifiScanner\", \"()V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(env, cls, mid);\n}\n\n\nchar * android_wifi_scan() {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n    jobject jreading;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/Hardware\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"scanWifi\", \"()Ljava/lang/String;\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n    jreading = (*env)->CallStaticObjectMethod(env, cls, mid);\n    const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);\n    POP_FRAME;\n\n    return reading;\n}\n\nvoid android_accelerometer_reading(float *values) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n    jobject jvalues;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/Hardware\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"accelerometerReading\", \"()[F\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n\n    jvalues = (*env)->CallStaticObjectMethod(env, cls, mid);\n    (*env)->GetFloatArrayRegion(env, jvalues, 0, 3, values);\n\n    POP_FRAME;\n}\n\nint android_get_dpi(void) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/Hardware\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"getDPI\", \"()I\");\n        aassert(mid);\n    }\n\n    return (*env)->CallStaticIntMethod(env, cls, mid);\n}\n\nvoid android_show_keyboard(int input_type) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/Hardware\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"showKeyboard\", \"(I)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(env, cls, mid, (jint) input_type);\n}\n\nvoid android_hide_keyboard(void) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/Hardware\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"hideKeyboard\", \"()V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(env, cls, mid);\n}\n\nchar* BUILD_MANUFACTURER = NULL;\nchar* BUILD_MODEL = NULL;\nchar* BUILD_PRODUCT = NULL;\nchar* BUILD_VERSION_RELEASE = NULL;\n\nvoid android_get_buildinfo() {\n    static JNIEnv *env = NULL;\n\n    if (env == NULL) {\n        jclass *cls = NULL;\n        jfieldID fid;\n        jstring sval;\n\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n\n        cls = (*env)->FindClass(env, \"android/os/Build\");\n\n        fid = (*env)->GetStaticFieldID(env, cls, \"MANUFACTURER\", \"Ljava/lang/String;\");\n        sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);\n        BUILD_MANUFACTURER = (*env)->GetStringUTFChars(env, sval, 0);\n\n        fid = (*env)->GetStaticFieldID(env, cls, \"MODEL\", \"Ljava/lang/String;\");\n        sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);\n        BUILD_MODEL = (*env)->GetStringUTFChars(env, sval, 0);\n\n        fid = (*env)->GetStaticFieldID(env, cls, \"PRODUCT\", \"Ljava/lang/String;\");\n        sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);\n        BUILD_PRODUCT = (*env)->GetStringUTFChars(env, sval, 0);\n\n        cls = (*env)->FindClass(env, \"android/os/Build$VERSION\");\n\n        fid = (*env)->GetStaticFieldID(env, cls, \"RELEASE\", \"Ljava/lang/String;\");\n        sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);\n        BUILD_VERSION_RELEASE = (*env)->GetStringUTFChars(env, sval, 0);\n    }\n}\n\nvoid android_stop_service() {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        cls = (*env)->FindClass(env, JNI_NAMESPACE \"/PythonActivity\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"stop_service\", \"()V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(env, cls, mid);\n}\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/_android_sound.pyx",
    "content": "cdef extern void android_sound_queue(int, char *, char *, long long, long long)\ncdef extern void android_sound_play(int, char *, char *, long long, long long)\ncdef extern void android_sound_stop(int)\ncdef extern void android_sound_seek(int, float)\ncdef extern void android_sound_dequeue(int)\ncdef extern void android_sound_playing_name(int, char *, int)\ncdef extern void android_sound_pause(int)\ncdef extern void android_sound_unpause(int)\n\ncdef extern void android_sound_set_volume(int, float)\ncdef extern void android_sound_set_secondary_volume(int, float)\ncdef extern void android_sound_set_pan(int, float)\n\ncdef extern int android_sound_queue_depth(int)\ncdef extern int android_sound_get_pos(int)\ncdef extern int android_sound_get_length(int)\n\nchannels = set()\nvolumes = { }\n\ndef queue(channel, file, name, fadein=0, tight=False):\n\n    channels.add(channel)\n\n    real_fn = file.name\n    base = getattr(file, \"base\", -1)\n    length = getattr(file, \"length\", -1)\n\n    android_sound_queue(channel, name, real_fn, base, length)\n\ndef play(channel, file, name, paused=False, fadein=0, tight=False):\n\n    channels.add(channel)\n\n    real_fn = file.name    \n    base = getattr(file, \"base\", -1)\n    length = getattr(file, \"length\", -1)\n\n    android_sound_play(channel, name, real_fn, base, length)\n\ndef seek(channel, position):\n   android_sound_seek(channel, position)\n\ndef stop(channel):\n    android_sound_stop(channel)\n\ndef dequeue(channel, even_tight=False):\n    android_sound_dequeue(channel)\n\ndef queue_depth(channel):\n    return android_sound_queue_depth(channel)\n\ndef playing_name(channel):\n    cdef char buf[1024]\n\n    android_sound_playing_name(channel, buf, 1024)\n\n    rv = buf\n    if not len(rv):\n        return None\n    return rv\n\ndef pause(channel):\n    android_sound_pause(channel)\n    return\n\ndef unpause(channel):\n    android_sound_unpause(channel)\n    return\n\ndef unpause_all():\n    for i in channels:\n        unpause(i)\n\ndef pause_all():\n    for i in channels:\n        pause(i)\n\ndef fadeout(channel, ms):\n    stop(channel)\n\ndef busy(channel):\n    return playing_name(channel) != None\n\ndef get_pos(channel):\n    return android_sound_get_pos(channel)\n\ndef get_length(channel):\n    return android_sound_get_length(channel)\n\ndef set_volume(channel, volume):\n    android_sound_set_volume(channel, volume)\n    volumes[channel] = volume\n\ndef set_secondary_volume(channel, volume):\n    android_sound_set_secondary_volume(channel, volume)\n\ndef set_pan(channel, pan):\n    android_sound_set_pan(channel, pan)\n\ndef set_end_event(channel, event):\n    return\n\ndef get_volume(channel):\n    return volumes.get(channel, 1.0)\n\ndef init(freq, stereo, samples, status=False):\n    return\n\ndef quit():\n    for i in channels:\n        stop(i)\n\ndef periodic():\n    return\n\ndef alloc_event(surf):\n    return\n\ndef refresh_event():\n    return\n\ndef check_version(version):\n    return\n\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/_android_sound_jni.c",
    "content": "#include <jni.h>\n#include <stdio.h>\n#include <android/log.h>\n#include <string.h>\n\nJNIEnv *SDL_ANDROID_GetJNIEnv();\n\n#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, \"android_sound_jni\", \"Assertion failed. %s:%d\", __FILE__, __LINE__); abort(); }}\n#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }\n#define POP_FRAME  { (*env)->PopLocalFrame(env, NULL); }\n\n\nvoid android_sound_queue(int channel, char *filename, char *real_fn, long long base, long long length) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"queue\", \"(ILjava/lang/String;Ljava/lang/String;JJ)V\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel,\n        (*env)->NewStringUTF(env, filename),\n        (*env)->NewStringUTF(env, real_fn),\n        (jlong) base,\n        (jlong) length);\n\n    POP_FRAME;\n}\n\nvoid android_sound_play(int channel, char *filename, char *real_fn, long long base, long long length) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"play\", \"(ILjava/lang/String;Ljava/lang/String;JJ)V\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel,\n        (*env)->NewStringUTF(env, filename),\n        (*env)->NewStringUTF(env, real_fn),\n        (jlong) base,\n        (jlong) length);\n\n    POP_FRAME;\n}\n\nvoid android_sound_seek(int channel, float position){\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"seek\", \"(IF)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel,\n        (jfloat) position);\n}\n\nvoid android_sound_stop(int channel) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"stop\", \"(I)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel);\n}\n\nvoid android_sound_dequeue(int channel) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"dequeue\", \"(I)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel);\n}\n\nint android_sound_queue_depth(int channel) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"queue_depth\", \"(I)I\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticIntMethod(\n        env, cls, mid,\n        channel);\n}\n\nvoid android_sound_playing_name(int channel, char *buf, int buflen) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    jobject s = NULL;\n    char *jbuf;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"playing_name\", \"(I)Ljava/lang/String;\");\n        aassert(mid);\n    }\n\n    PUSH_FRAME;\n\n    s = (*env)->CallStaticObjectMethod(\n        env, cls, mid,\n        channel);\n\n    jbuf = (*env)->GetStringUTFChars(env, s, NULL);\n    strncpy(buf, jbuf, buflen);\n    (*env)->ReleaseStringUTFChars(env, s, jbuf);\n\n    POP_FRAME;\n}\n\nvoid android_sound_set_volume(int channel, float value) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"set_volume\", \"(IF)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel,\n        (jfloat) value);\n}\n\nvoid android_sound_set_secondary_volume(int channel, float value) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"set_secondary_volume\", \"(IF)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel,\n        (jfloat) value);\n}\n\nvoid android_sound_set_pan(int channel, float value) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"set_pan\", \"(IF)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel,\n        (jfloat) value);\n}\n\nvoid android_sound_pause(int channel) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"pause\", \"(I)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel);\n}\n\nvoid android_sound_unpause(int channel) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"unpause\", \"(I)V\");\n        aassert(mid);\n    }\n\n    (*env)->CallStaticVoidMethod(\n        env, cls, mid,\n        channel);\n}\n\nint android_sound_get_pos(int channel) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"get_pos\", \"(I)I\");\n        aassert(mid);\n    }\n\n    return (*env)->CallStaticIntMethod(\n        env, cls, mid,\n        channel);\n}\n\nint android_sound_get_length(int channel) {\n    static JNIEnv *env = NULL;\n    static jclass *cls = NULL;\n    static jmethodID mid = NULL;\n\n    if (env == NULL) {\n        env = SDL_ANDROID_GetJNIEnv();\n        aassert(env);\n        cls = (*env)->FindClass(env, \"org/renpy/android/RenPySound\");\n        aassert(cls);\n        mid = (*env)->GetStaticMethodID(env, cls, \"get_length\", \"(I)I\");\n        aassert(mid);\n    }\n\n    return (*env)->CallStaticIntMethod(\n        env, cls, mid,\n        channel);\n}\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/_ctypes_library_finder.py",
    "content": "\nimport sys\nimport os\n\n\ndef get_activity_lib_dir(activity_name):\n    from jnius import autoclass\n\n    # Get the actual activity instance:\n    activity_class = autoclass(activity_name)\n    if activity_class is None:\n        return None\n    activity = None\n    if hasattr(activity_class, \"mActivity\") and \\\n            activity_class.mActivity is not None:\n        activity = activity_class.mActivity\n    elif hasattr(activity_class, \"mService\") and \\\n            activity_class.mService is not None:\n        activity = activity_class.mService\n    if activity is None:\n        return None\n\n    # Extract the native lib dir from the activity instance:\n    package_name = activity.getApplicationContext().getPackageName()\n    manager = activity.getApplicationContext().getPackageManager()\n    manager_class = autoclass(\"android.content.pm.PackageManager\")\n    native_lib_dir = manager.getApplicationInfo(\n        package_name, manager_class.GET_SHARED_LIBRARY_FILES\n    ).nativeLibraryDir\n    return native_lib_dir\n\n\ndef does_libname_match_filename(search_name, file_path):\n    # Filter file names so given search_name=\"mymodule\" we match one of:\n    #      mymodule.so         (direct name + .so)\n    #      libmymodule.so      (added lib prefix)\n    #      mymodule.arm64.so   (added dot-separated middle parts)\n    #      mymodule.so.1.3.4   (added dot-separated version tail)\n    #      and all above       (all possible combinations)\n    import re\n    file_name = os.path.basename(file_path)\n    return (re.match(r\"^(lib)?\" + re.escape(search_name) +\n                     r\"\\.(.*\\.)?so(\\.[0-9]+)*$\", file_name) is not None)\n\n\ndef find_library(name):\n    # Obtain all places for native libraries:\n    if sys.maxsize > 2**32:  # 64bit-build\n        lib_search_dirs = [\"/system/lib64\", \"/system/lib\"]\n    else:\n        lib_search_dirs = [\"/system/lib\"]\n    lib_dir_1 = get_activity_lib_dir(\"org.kivy.android.PythonActivity\")\n    if lib_dir_1 is not None:\n        lib_search_dirs.insert(0, lib_dir_1)\n    lib_dir_2 = get_activity_lib_dir(\"org.kivy.android.PythonService\")\n    if lib_dir_2 is not None and lib_dir_2 not in lib_search_dirs:\n        lib_search_dirs.insert(0, lib_dir_2)\n\n    # Now scan the lib dirs:\n    for lib_dir in [ldir for ldir in lib_search_dirs if os.path.exists(ldir)]:\n        filelist = [\n            f for f in os.listdir(lib_dir)\n            if does_libname_match_filename(name, f)\n        ]\n        if len(filelist) > 0:\n            return os.path.join(lib_dir, filelist[0])\n    return None\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/activity.py",
    "content": "from jnius import PythonJavaClass, autoclass, java_method\nfrom android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE\n\n_activity = autoclass(ACTIVITY_CLASS_NAME).mActivity\n\n_callbacks = {\n    'on_new_intent': [],\n    'on_activity_result': [],\n}\n\n\nclass NewIntentListener(PythonJavaClass):\n    __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$NewIntentListener']\n    __javacontext__ = 'app'\n\n    def __init__(self, callback, **kwargs):\n        super().__init__(**kwargs)\n        self.callback = callback\n\n    @java_method('(Landroid/content/Intent;)V')\n    def onNewIntent(self, intent):\n        self.callback(intent)\n\n\nclass ActivityResultListener(PythonJavaClass):\n    __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$ActivityResultListener']\n    __javacontext__ = 'app'\n\n    def __init__(self, callback):\n        super().__init__()\n        self.callback = callback\n\n    @java_method('(IILandroid/content/Intent;)V')\n    def onActivityResult(self, requestCode, resultCode, intent):\n        self.callback(requestCode, resultCode, intent)\n\n\ndef bind(**kwargs):\n    for event, callback in kwargs.items():\n        if event not in _callbacks:\n            raise Exception('Unknown {!r} event'.format(event))\n        elif event == 'on_new_intent':\n            listener = NewIntentListener(callback)\n            _activity.registerNewIntentListener(listener)\n            _callbacks[event].append(listener)\n        elif event == 'on_activity_result':\n            listener = ActivityResultListener(callback)\n            _activity.registerActivityResultListener(listener)\n            _callbacks[event].append(listener)\n\n\ndef unbind(**kwargs):\n    for event, callback in kwargs.items():\n        if event not in _callbacks:\n            raise Exception('Unknown {!r} event'.format(event))\n        else:\n            for listener in _callbacks[event][:]:\n                if listener.callback == callback:\n                    _callbacks[event].remove(listener)\n                    if event == 'on_new_intent':\n                        _activity.unregisterNewIntentListener(listener)\n                    elif event == 'on_activity_result':\n                        _activity.unregisterActivityResultListener(listener)\n\n\n# Keep a reference to all the registered classes so that python doesn't\n# garbage collect them.\n_lifecycle_callbacks = set()\n\n\nclass ActivityLifecycleCallbacks(PythonJavaClass):\n    \"\"\"Callback class for handling PythonActivity lifecycle transitions\"\"\"\n\n    __javainterfaces__ = ['android/app/Application$ActivityLifecycleCallbacks']\n\n    def __init__(self, callbacks):\n        super().__init__()\n\n        # It would be nice to use keyword arguments, but PythonJavaClass\n        # doesn't allow that in its __cinit__ method.\n        if not isinstance(callbacks, dict):\n            raise ValueError('callbacks must be a dict instance')\n        self.callbacks = callbacks\n\n    def _callback(self, name, *args):\n        func = self.callbacks.get(name)\n        if func:\n            return func(*args)\n\n    @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')\n    def onActivityCreated(self, activity, savedInstanceState):\n        self._callback('onActivityCreated', activity, savedInstanceState)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityDestroyed(self, activity):\n        self._callback('onActivityDestroyed', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPaused(self, activity):\n        self._callback('onActivityPaused', activity)\n\n    @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')\n    def onActivityPostCreated(self, activity, savedInstanceState):\n        self._callback('onActivityPostCreated', activity, savedInstanceState)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPostDestroyed(self, activity):\n        self._callback('onActivityPostDestroyed', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPostPaused(self, activity):\n        self._callback('onActivityPostPaused', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPostResumed(self, activity):\n        self._callback('onActivityPostResumed', activity)\n\n    @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')\n    def onActivityPostSaveInstanceState(self, activity, outState):\n        self._callback('onActivityPostSaveInstanceState', activity, outState)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPostStarted(self, activity):\n        self._callback('onActivityPostStarted', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPostStopped(self, activity):\n        self._callback('onActivityPostStopped', activity)\n\n    @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')\n    def onActivityPreCreated(self, activity, savedInstanceState):\n        self._callback('onActivityPreCreated', activity, savedInstanceState)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPreDestroyed(self, activity):\n        self._callback('onActivityPreDestroyed', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPrePaused(self, activity):\n        self._callback('onActivityPrePaused', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPreResumed(self, activity):\n        self._callback('onActivityPreResumed', activity)\n\n    @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')\n    def onActivityPreSaveInstanceState(self, activity, outState):\n        self._callback('onActivityPreSaveInstanceState', activity, outState)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPreStarted(self, activity):\n        self._callback('onActivityPreStarted', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityPreStopped(self, activity):\n        self._callback('onActivityPreStopped', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityResumed(self, activity):\n        self._callback('onActivityResumed', activity)\n\n    @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')\n    def onActivitySaveInstanceState(self, activity, outState):\n        self._callback('onActivitySaveInstanceState', activity, outState)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityStarted(self, activity):\n        self._callback('onActivityStarted', activity)\n\n    @java_method('(Landroid/app/Activity;)V')\n    def onActivityStopped(self, activity):\n        self._callback('onActivityStopped', activity)\n\n\ndef register_activity_lifecycle_callbacks(**callbacks):\n    \"\"\"Register ActivityLifecycleCallbacks instance\n\n    The callbacks are supplied as keyword arguments corresponding to the\n    Application.ActivityLifecycleCallbacks methods such as\n    onActivityStarted. See the ActivityLifecycleCallbacks documentation\n    for the signature of each method.\n\n    The ActivityLifecycleCallbacks instance is returned so it can be\n    supplied to unregister_activity_lifecycle_callbacks if needed.\n    \"\"\"\n    instance = ActivityLifecycleCallbacks(callbacks)\n    _lifecycle_callbacks.add(instance)\n\n    # Use the registerActivityLifecycleCallbacks method from the\n    # Activity class if it's available (API 29) since it guarantees the\n    # callbacks will only be run for that activity. Otherwise, fallback\n    # to the method on the Application class (API 14). In practice there\n    # should be no difference since p4a applications only have a single\n    # activity.\n    if hasattr(_activity, 'registerActivityLifecycleCallbacks'):\n        _activity.registerActivityLifecycleCallbacks(instance)\n    else:\n        app = _activity.getApplication()\n        app.registerActivityLifecycleCallbacks(instance)\n    return instance\n\n\ndef unregister_activity_lifecycle_callbacks(instance):\n    \"\"\"Unregister ActivityLifecycleCallbacks instance\"\"\"\n    if hasattr(_activity, 'unregisterActivityLifecycleCallbacks'):\n        _activity.unregisterActivityLifecycleCallbacks(instance)\n    else:\n        app = _activity.getApplication()\n        app.unregisterActivityLifecycleCallbacks(instance)\n\n    try:\n        _lifecycle_callbacks.remove(instance)\n    except KeyError:\n        pass\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/billing.py",
    "content": "'''\nAndroid Billing API\n===================\n\n'''\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/broadcast.py",
    "content": "# -------------------------------------------------------------------\n# Broadcast receiver bridge\nimport logging\nfrom jnius import autoclass, PythonJavaClass, java_method\nfrom android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME\n\nlogger = logging.getLogger(\"BroadcastReceiver\")\nlogger.setLevel(logging.DEBUG)\n\n\nclass BroadcastReceiver(object):\n\n    class Callback(PythonJavaClass):\n        __javainterfaces__ = [JNI_NAMESPACE + '/GenericBroadcastReceiverCallback']\n        __javacontext__ = 'app'\n\n        def __init__(self, callback, *args, **kwargs):\n            self.callback = callback\n            PythonJavaClass.__init__(self, *args, **kwargs)\n\n        @java_method('(Landroid/content/Context;Landroid/content/Intent;)V')\n        def onReceive(self, context, intent):\n            self.callback(context, intent)\n\n    def __init__(self, callback, actions=None, categories=None):\n        super().__init__()\n        self.callback = callback\n        self._is_registered = False\n\n        if not actions and not categories:\n            raise Exception('You need to define at least actions or categories')\n\n        def _expand_partial_name(partial_name):\n            if '.' in partial_name:\n                return partial_name  # Its actually a full dotted name\n            else:\n                name = 'ACTION_{}'.format(partial_name.upper())\n                if not hasattr(Intent, name):\n                    raise Exception('The intent {} does not exist'.format(name))\n                return getattr(Intent, name)\n\n        # resolve actions/categories first\n        Intent = autoclass('android.content.Intent')\n        resolved_actions = [_expand_partial_name(x) for x in actions or []]\n        resolved_categories = [_expand_partial_name(x) for x in categories or []]\n\n        # resolve android API\n        GenericBroadcastReceiver = autoclass(JAVA_NAMESPACE + '.GenericBroadcastReceiver')\n        IntentFilter = autoclass('android.content.IntentFilter')\n        HandlerThread = autoclass('android.os.HandlerThread')\n\n        # create a thread for handling events from the receiver\n        self.handlerthread = HandlerThread('handlerthread')\n\n        # create a listener\n        self.listener = BroadcastReceiver.Callback(self.callback)\n        self.receiver = GenericBroadcastReceiver(self.listener)\n        self.receiver_filter = IntentFilter()\n        for x in resolved_actions:\n            self.receiver_filter.addAction(x)\n        for x in resolved_categories:\n            self.receiver_filter.addCategory(x)\n\n    def start(self):\n\n        if hasattr(self, 'handlerthread') and self.handlerthread.isAlive():\n            logger.debug(\"HandlerThread already running, skipping start\")\n            return\n\n        HandlerThread = autoclass('android.os.HandlerThread')\n        self.handlerthread = HandlerThread('handlerthread')\n        self.handlerthread.start()\n\n        if self._is_registered:\n            logger.info(\"Already registered.\")\n            return\n\n        Handler = autoclass('android.os.Handler')\n        self.handler = Handler(self.handlerthread.getLooper())\n        self.context.registerReceiver(\n            self.receiver, self.receiver_filter, None, self.handler)\n        self._is_registered = True\n\n    def stop(self):\n        try:\n            self.context.unregisterReceiver(self.receiver)\n            self._is_registered = False\n        except Exception as e:\n            logger.error(\"unregisterReceiver failed: %s\", e)\n\n        if hasattr(self, 'handlerthread'):\n            self.handlerthread.quitSafely()\n            self.handlerthread = None\n            self.handler = None\n\n    @property\n    def context(self):\n        from os import environ\n        if 'PYTHON_SERVICE_ARGUMENT' in environ:\n            PythonService = autoclass(SERVICE_CLASS_NAME)\n            return PythonService.mService\n        PythonActivity = autoclass(ACTIVITY_CLASS_NAME)\n        return PythonActivity.mActivity\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/display_cutout.py",
    "content": "from jnius import autoclass\nfrom kivy.core.window import Window\n\nfrom android import mActivity\n\n__all__ = ('get_cutout_pos', 'get_cutout_size', 'get_width_of_bar',\n           'get_height_of_bar', 'get_size_of_bar', 'get_width_of_bar',\n           'get_cutout_mode')\n\n\ndef _core_cutout():\n    decorview = mActivity.getWindow().getDecorView()\n    cutout = decorview.rootWindowInsets.displayCutout\n\n    return cutout.boundingRects.get(0)\n\n\ndef get_cutout_pos():\n    \"\"\"Get position of the display-cutout.\n       Returns integer for each positions (xy)\n    \"\"\"\n    try:\n        cutout = _core_cutout()\n        return int(cutout.left), int(Window.height - cutout.height())\n    except Exception:\n        # Doesn't have a camera builtin with the display\n        return 0, 0\n\n\ndef get_cutout_size():\n    \"\"\"Get the size (xy) of the front camera.\n       Returns size with float values\n    \"\"\"\n    try:\n        cutout = _core_cutout()\n        return float(cutout.width()), float(cutout.height())\n    except Exception:\n        # Doesn't have a camera builtin with the display\n        return 0., 0.\n\n\ndef get_height_of_bar(bar_target=None):\n    \"\"\"Get the height of either statusbar or navigationbar\n       bar_target = status or navigation and defaults to status\n    \"\"\"\n    bar_target = bar_target or 'status'\n\n    if bar_target not in ('status', 'navigation'):\n        raise Exception(\"bar_target must be 'status' or 'navigation'\")\n\n    try:\n        displayMetrics = autoclass('android.util.DisplayMetrics')\n        mActivity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics())\n        resources = mActivity.getResources()\n        resourceId = resources.getIdentifier(f'{bar_target}_bar_height', 'dimen',\n                                             'android')\n\n        return float(max(resources.getDimensionPixelSize(resourceId), 0))\n    except Exception:\n        # Getting the size is not supported on older Androids\n        return 0.\n\n\ndef get_width_of_bar(bar_target=None):\n    \"\"\"Get the width of the bar\"\"\"\n    return Window.width\n\n\ndef get_size_of_bar(bar_target=None):\n    \"\"\"Get the size of either statusbar or navigationbar\n       bar_target = status or navigation and defaults to status\n    \"\"\"\n    return get_width_of_bar(), get_height_of_bar(bar_target)\n\n\ndef get_heights_of_both_bars():\n    \"\"\"Return heights of both bars\"\"\"\n    return get_height_of_bar('status'), get_height_of_bar('navigation')\n\n\ndef get_cutout_mode():\n    \"\"\"Return mode for cutout supported applications\"\"\"\n    BuildVersion = autoclass('android.os.Build$VERSION')\n    cutout_modes = {}\n\n    if BuildVersion.SDK_INT >= 28:\n        LayoutParams = autoclass('android.view.WindowManager$LayoutParams')\n        window = mActivity.getWindow()\n        layout_params = window.getAttributes()\n        cutout_mode = layout_params.layoutInDisplayCutoutMode\n        cutout_modes.update({LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT: 'default',\n                             LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES: 'shortEdges'})\n\n        if BuildVersion.SDK_INT >= 30:\n            cutout_modes[LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS] = 'always'\n\n        return cutout_modes.get(cutout_mode, 'never')\n\n    return None\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/loadingscreen.py",
    "content": "\nfrom jnius import autoclass\n\nfrom android.config import ACTIVITY_CLASS_NAME\n\n\ndef hide_loading_screen():\n    mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity\n    mActivity.removeLoadingScreen()\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/mixer.py",
    "content": "# This module is, as much a possible, a clone of the pygame\n# mixer api.\n\nimport android._android_sound as sound\nimport time\nimport threading\nimport os\n\ncondition = threading.Condition()\n\n\ndef periodic():\n    for i in range(0, num_channels):\n        if i in channels:\n            channels[i].periodic()\n\n\nnum_channels = 8\nreserved_channels = 0\n\n\ndef init(frequency=22050, size=-16, channels=2, buffer=4096):\n    return None\n\n\ndef pre_init(frequency=22050, size=-16, channels=2, buffersize=4096):\n    return None\n\n\ndef quit():\n    stop()\n    return None\n\n\ndef stop():\n    for i in range(0, num_channels):\n        sound.stop(i)\n\n\ndef pause():\n    for i in range(0, num_channels):\n        sound.pause(i)\n\n\ndef unpause():\n    for i in range(0, num_channels):\n        sound.unpause(i)\n\n\ndef get_busy():\n    for i in range(0, num_channels):\n        if sound.busy(i):\n            return True\n\n    return False\n\n\ndef fadeout(time):\n    # Fadeout doesn't work - it just immediately stops playback.\n    stop()\n\n\n# A map from channel number to Channel object.\nchannels = {}\n\n\ndef set_num_channels(count):\n    global num_channels\n    num_channels = count\n\n\ndef get_num_channels(count):\n    return num_channels\n\n\ndef set_reserved(count):\n    global reserved_channels\n    reserved_channels = count\n\n\ndef find_channel(force=False):\n\n    busy = []\n\n    for i in range(reserved_channels, num_channels):\n        c = Channel(i)\n\n        if not c.get_busy():\n            return c\n\n        busy.append(c)\n\n    if not force:\n        return None\n\n    busy.sort(key=lambda x: x.play_time)\n\n    return busy[0]\n\n\nclass ChannelImpl(object):\n\n    def __init__(self, id):\n        self.id = id\n        self.loop = None\n        self.queued = None\n\n        self.play_time = time.time()\n\n    def periodic(self):\n        qd = sound.queue_depth(self.id)\n\n        if qd < 2:\n            self.queued = None\n\n        if self.loop is not None and sound.queue_depth(self.id) < 2:\n            self.queue(self.loop, loops=1)\n\n    def play(self, s, loops=0, maxtime=0, fade_ms=0):\n        if loops:\n            self.loop = s\n\n        sound.play(self.id, s.file, s.serial)\n\n        self.play_time = time.time()\n\n        with condition:\n            condition.notify()\n\n    def seek(self, position):\n        sound.seek(self.id, position)\n\n    def stop(self):\n        self.loop = None\n        sound.stop(self.id)\n\n    def pause(self):\n        sound.pause(self.id)\n\n    def unpause(self):\n        sound.pause(self.id)\n\n    def fadeout(self, time):\n        # No fadeout\n        self.stop()\n\n    def set_volume(self, left, right=None):\n        sound.set_volume(self.id, left)\n\n    def get_volume(self):\n        return sound.get_volume(self.id)\n\n    def get_busy(self):\n        return sound.busy(self.id)\n\n    def get_sound(self):\n        is_busy = sound.busy(self.id)\n        if not is_busy:\n            return\n        serial = sound.playing_name(self.id)\n        if not serial:\n            return\n        return sounds.get(serial, None)\n\n    def queue(self, s):\n        self.loop = None\n        self.queued = s\n\n        sound.queue(self.id, s.what, s.serial)\n\n        with condition:\n            condition.notify()\n\n    def get_queue(self):\n        return self.queued\n\n    def get_pos(self):\n        return sound.get_pos(self.id)/1000.\n\n    def get_length(self):\n        return sound.get_length(self.id)/1000.\n\n\ndef Channel(n):\n    \"\"\"\n    Gets the channel with the given number.\n    \"\"\"\n\n    rv = channels.get(n, None)\n    if rv is None:\n        rv = ChannelImpl(n)\n        channels[n] = rv\n\n    return rv\n\n\nsound_serial = 0\nsounds = {}\n\n\nclass Sound(object):\n\n    def __init__(self, what):\n\n        # Doesn't support buffers.\n\n        global sound_serial\n\n        self._channel = None\n        self._volume = 1.\n        self.serial = str(sound_serial)\n        sound_serial += 1\n\n        if isinstance(what, file):  # noqa F821\n            self.file = what\n        else:\n            self.file = file(os.path.abspath(what), \"rb\")  # noqa F821\n\n        sounds[self.serial] = self\n\n    def play(self, loops=0, maxtime=0, fade_ms=0):\n        # avoid new play if the sound is already playing\n        # -> same behavior as standard pygame.\n        if self._channel is not None:\n            if self._channel.get_sound() is self:\n                return\n        self._channel = channel = find_channel(True)\n        channel.set_volume(self._volume)\n        channel.play(self, loops=loops)\n        return channel\n\n    def stop(self):\n        for i in range(0, num_channels):\n            if Channel(i).get_sound() is self:\n                Channel(i).stop()\n\n    def fadeout(self, time):\n        self.stop()\n\n    def set_volume(self, left, right=None):\n        self._volume = left\n        if self._channel:\n            if self._channel.get_sound() is self:\n                self._channel.set_volume(self._volume)\n\n    def get_volume(self):\n        return self._volume\n\n    def get_num_channels(self):\n        rv = 0\n\n        for i in range(0, num_channels):\n            if Channel(i).get_sound() is self:\n                rv += 1\n\n        return rv\n\n    def get_length(self):\n        return 1.0\n\n\nmusic_channel = Channel(256)\nmusic_sound = None\n\n\nclass music(object):\n\n    @staticmethod\n    def load(filename):\n\n        music_channel.stop()\n\n        global music_sound\n        music_sound = Sound(filename)\n\n    @staticmethod\n    def play(loops=0, start=0.0):\n        # No start.\n\n        music_channel.play(music_sound, loops=loops)\n\n    @staticmethod\n    def rewind():\n        music_channel.play(music_sound)\n\n    @staticmethod\n    def seek(position):\n        music_channel.seek(position)\n\n    @staticmethod\n    def stop():\n        music_channel.stop()\n\n    @staticmethod\n    def pause():\n        music_channel.pause()\n\n    @staticmethod\n    def unpause():\n        music_channel.unpause()\n\n    @staticmethod\n    def fadeout(time):\n        music_channel.fadeout(time)\n\n    @staticmethod\n    def set_volume(value):\n        music_channel.set_volume(value)\n\n    @staticmethod\n    def get_volume():\n        return music_channel.get_volume()\n\n    @staticmethod\n    def get_busy():\n        return music_channel.get_busy()\n\n    @staticmethod\n    def get_pos():\n        return music_channel.get_pos()\n\n    @staticmethod\n    def queue(filename):\n        return music_channel.queue(Sound(filename))\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/permissions.py",
    "content": "import threading\n\ntry:\n    from jnius import autoclass, PythonJavaClass, java_method\nexcept ImportError:\n    # To allow importing by build/manifest-creating code without\n    # pyjnius being present:\n    def autoclass(item):\n        raise RuntimeError(\"pyjnius not available\")\n\n\nfrom android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE\n\n\nclass Permission:\n    ACCEPT_HANDOVER = \"android.permission.ACCEPT_HANDOVER\"\n    ACCESS_BACKGROUND_LOCATION = \"android.permission.ACCESS_BACKGROUND_LOCATION\"\n    ACCESS_COARSE_LOCATION = \"android.permission.ACCESS_COARSE_LOCATION\"\n    ACCESS_FINE_LOCATION = \"android.permission.ACCESS_FINE_LOCATION\"\n    ACCESS_LOCATION_EXTRA_COMMANDS = (\n        \"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\"\n        )\n    ACCESS_NETWORK_STATE = \"android.permission.ACCESS_NETWORK_STATE\"\n    ACCESS_NOTIFICATION_POLICY = (\n        \"android.permission.ACCESS_NOTIFICATION_POLICY\"\n        )\n    ACCESS_WIFI_STATE = \"android.permission.ACCESS_WIFI_STATE\"\n    ADD_VOICEMAIL = \"com.android.voicemail.permission.ADD_VOICEMAIL\"\n    ANSWER_PHONE_CALLS = \"android.permission.ANSWER_PHONE_CALLS\"\n    BATTERY_STATS = \"android.permission.BATTERY_STATS\"\n    BIND_ACCESSIBILITY_SERVICE = (\n        \"android.permission.BIND_ACCESSIBILITY_SERVICE\"\n        )\n    BIND_AUTOFILL_SERVICE = \"android.permission.BIND_AUTOFILL_SERVICE\"\n    BIND_CARRIER_MESSAGING_SERVICE = (  # note: deprecated in api 23+\n        \"android.permission.BIND_CARRIER_MESSAGING_SERVICE\"\n        )\n    BIND_CARRIER_SERVICES = (  # replaces BIND_CARRIER_MESSAGING_SERVICE\n        \"android.permission.BIND_CARRIER_SERVICES\"\n        )\n    BIND_CHOOSER_TARGET_SERVICE = (\n        \"android.permission.BIND_CHOOSER_TARGET_SERVICE\"\n        )\n    BIND_CONDITION_PROVIDER_SERVICE = (\n        \"android.permission.BIND_CONDITION_PROVIDER_SERVICE\"\n        )\n    BIND_DEVICE_ADMIN = \"android.permission.BIND_DEVICE_ADMIN\"\n    BIND_DREAM_SERVICE = \"android.permission.BIND_DREAM_SERVICE\"\n    BIND_INCALL_SERVICE = \"android.permission.BIND_INCALL_SERVICE\"\n    BIND_INPUT_METHOD = (\n        \"android.permission.BIND_INPUT_METHOD\"\n        )\n    BIND_MIDI_DEVICE_SERVICE = (\n        \"android.permission.BIND_MIDI_DEVICE_SERVICE\"\n        )\n    BIND_NFC_SERVICE = (\n        \"android.permission.BIND_NFC_SERVICE\"\n        )\n    BIND_NOTIFICATION_LISTENER_SERVICE = (\n        \"android.permission.BIND_NOTIFICATION_LISTENER_SERVICE\"\n        )\n    BIND_PRINT_SERVICE = (\n        \"android.permission.BIND_PRINT_SERVICE\"\n        )\n    BIND_QUICK_SETTINGS_TILE = (\n        \"android.permission.BIND_QUICK_SETTINGS_TILE\"\n        )\n    BIND_REMOTEVIEWS = (\n        \"android.permission.BIND_REMOTEVIEWS\"\n        )\n    BIND_SCREENING_SERVICE = (\n        \"android.permission.BIND_SCREENING_SERVICE\"\n        )\n    BIND_TELECOM_CONNECTION_SERVICE = (\n        \"android.permission.BIND_TELECOM_CONNECTION_SERVICE\"\n        )\n    BIND_TEXT_SERVICE = (\n        \"android.permission.BIND_TEXT_SERVICE\"\n        )\n    BIND_TV_INPUT = (\n        \"android.permission.BIND_TV_INPUT\"\n        )\n    BIND_VISUAL_VOICEMAIL_SERVICE = (\n        \"android.permission.BIND_VISUAL_VOICEMAIL_SERVICE\"\n        )\n    BIND_VOICE_INTERACTION = (\n        \"android.permission.BIND_VOICE_INTERACTION\"\n        )\n    BIND_VPN_SERVICE = (\n        \"android.permission.BIND_VPN_SERVICE\"\n        )\n    BIND_VR_LISTENER_SERVICE = (\n        \"android.permission.BIND_VR_LISTENER_SERVICE\"\n        )\n    BIND_WALLPAPER = (\n        \"android.permission.BIND_WALLPAPER\"\n        )\n    BLUETOOTH = (\n        \"android.permission.BLUETOOTH\"\n        )\n    BLUETOOTH_ADVERTISE = (\n        \"android.permission.BLUETOOTH_ADVERTISE\"\n        )\n    BLUETOOTH_CONNECT = (\n        \"android.permission.BLUETOOTH_CONNECT\"\n        )\n    BLUETOOTH_SCAN = (\n        \"android.permission.BLUETOOTH_SCAN\"\n        )\n    BLUETOOTH_ADMIN = (\n        \"android.permission.BLUETOOTH_ADMIN\"\n        )\n    BODY_SENSORS = (\n        \"android.permission.BODY_SENSORS\"\n        )\n    BROADCAST_PACKAGE_REMOVED = (\n        \"android.permission.BROADCAST_PACKAGE_REMOVED\"\n        )\n    BROADCAST_STICKY = (\n        \"android.permission.BROADCAST_STICKY\"\n        )\n    CALL_PHONE = (\n        \"android.permission.CALL_PHONE\"\n        )\n    CALL_PRIVILEGED = (\n        \"android.permission.CALL_PRIVILEGED\"\n        )\n    CAMERA = (\n        \"android.permission.CAMERA\"\n        )\n    CAPTURE_AUDIO_OUTPUT = (\n        \"android.permission.CAPTURE_AUDIO_OUTPUT\"\n        )\n    CAPTURE_SECURE_VIDEO_OUTPUT = (\n        \"android.permission.CAPTURE_SECURE_VIDEO_OUTPUT\"\n        )\n    CAPTURE_VIDEO_OUTPUT = (\n        \"android.permission.CAPTURE_VIDEO_OUTPUT\"\n        )\n    CHANGE_COMPONENT_ENABLED_STATE = (\n        \"android.permission.CHANGE_COMPONENT_ENABLED_STATE\"\n        )\n    CHANGE_CONFIGURATION = (\n        \"android.permission.CHANGE_CONFIGURATION\"\n        )\n    CHANGE_NETWORK_STATE = (\n        \"android.permission.CHANGE_NETWORK_STATE\"\n        )\n    CHANGE_WIFI_MULTICAST_STATE = (\n        \"android.permission.CHANGE_WIFI_MULTICAST_STATE\"\n        )\n    CHANGE_WIFI_STATE = (\n        \"android.permission.CHANGE_WIFI_STATE\"\n        )\n    CLEAR_APP_CACHE = (\n        \"android.permission.CLEAR_APP_CACHE\"\n        )\n    CONTROL_LOCATION_UPDATES = (\n        \"android.permission.CONTROL_LOCATION_UPDATES\"\n        )\n    DELETE_CACHE_FILES = (\n        \"android.permission.DELETE_CACHE_FILES\"\n        )\n    DELETE_PACKAGES = (\n        \"android.permission.DELETE_PACKAGES\"\n        )\n    DIAGNOSTIC = (\n        \"android.permission.DIAGNOSTIC\"\n        )\n    DISABLE_KEYGUARD = (\n        \"android.permission.DISABLE_KEYGUARD\"\n        )\n    DUMP = (\n        \"android.permission.DUMP\"\n        )\n    EXPAND_STATUS_BAR = (\n        \"android.permission.EXPAND_STATUS_BAR\"\n        )\n    FACTORY_TEST = (\n        \"android.permission.FACTORY_TEST\"\n        )\n    FOREGROUND_SERVICE = (\n        \"android.permission.FOREGROUND_SERVICE\"\n        )\n    GET_ACCOUNTS = (\n        \"android.permission.GET_ACCOUNTS\"\n        )\n    GET_ACCOUNTS_PRIVILEGED = (\n        \"android.permission.GET_ACCOUNTS_PRIVILEGED\"\n        )\n    GET_PACKAGE_SIZE = (\n        \"android.permission.GET_PACKAGE_SIZE\"\n        )\n    GET_TASKS = (\n        \"android.permission.GET_TASKS\"\n        )\n    GLOBAL_SEARCH = (\n        \"android.permission.GLOBAL_SEARCH\"\n        )\n    INSTALL_LOCATION_PROVIDER = (\n        \"android.permission.INSTALL_LOCATION_PROVIDER\"\n        )\n    INSTALL_PACKAGES = (\n        \"android.permission.INSTALL_PACKAGES\"\n        )\n    INSTALL_SHORTCUT = (\n        \"com.android.launcher.permission.INSTALL_SHORTCUT\"\n        )\n    INSTANT_APP_FOREGROUND_SERVICE = (\n        \"android.permission.INSTANT_APP_FOREGROUND_SERVICE\"\n        )\n    INTERNET = (\n        \"android.permission.INTERNET\"\n        )\n    KILL_BACKGROUND_PROCESSES = (\n        \"android.permission.KILL_BACKGROUND_PROCESSES\"\n        )\n    LOCATION_HARDWARE = (\n        \"android.permission.LOCATION_HARDWARE\"\n        )\n    MANAGE_DOCUMENTS = (\n        \"android.permission.MANAGE_DOCUMENTS\"\n        )\n    MANAGE_OWN_CALLS = (\n        \"android.permission.MANAGE_OWN_CALLS\"\n        )\n    MASTER_CLEAR = (\n        \"android.permission.MASTER_CLEAR\"\n        )\n    MEDIA_CONTENT_CONTROL = (\n        \"android.permission.MEDIA_CONTENT_CONTROL\"\n        )\n    MODIFY_AUDIO_SETTINGS = (\n        \"android.permission.MODIFY_AUDIO_SETTINGS\"\n        )\n    MODIFY_PHONE_STATE = (\n        \"android.permission.MODIFY_PHONE_STATE\"\n        )\n    MOUNT_FORMAT_FILESYSTEMS = (\n        \"android.permission.MOUNT_FORMAT_FILESYSTEMS\"\n        )\n    MOUNT_UNMOUNT_FILESYSTEMS = (\n        \"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"\n        )\n    NEARBY_WIFI_DEVICES = (\n        \"android.permission.NEARBY_WIFI_DEVICES\"\n        )\n    NFC = (\n        \"android.permission.NFC\"\n        )\n    NFC_TRANSACTION_EVENT = (\n        \"android.permission.NFC_TRANSACTION_EVENT\"\n        )\n    PACKAGE_USAGE_STATS = (\n        \"android.permission.PACKAGE_USAGE_STATS\"\n        )\n    PERSISTENT_ACTIVITY = (\n        \"android.permission.PERSISTENT_ACTIVITY\"\n        )\n    POST_NOTIFICATIONS = (\n        \"android.permission.POST_NOTIFICATIONS\"\n        )\n    PROCESS_OUTGOING_CALLS = (\n        \"android.permission.PROCESS_OUTGOING_CALLS\"\n        )\n    READ_CALENDAR = (\n        \"android.permission.READ_CALENDAR\"\n        )\n    READ_CALL_LOG = (\n        \"android.permission.READ_CALL_LOG\"\n        )\n    READ_CONTACTS = (\n        \"android.permission.READ_CONTACTS\"\n        )\n    READ_EXTERNAL_STORAGE = (\n        \"android.permission.READ_EXTERNAL_STORAGE\"\n        )\n    READ_FRAME_BUFFER = (\n        \"android.permission.READ_FRAME_BUFFER\"\n        )\n    READ_INPUT_STATE = (\n        \"android.permission.READ_INPUT_STATE\"\n        )\n    READ_LOGS = (\n        \"android.permission.READ_LOGS\"\n        )\n    READ_MEDIA_AUDIO = (\n        \"android.permission.READ_MEDIA_AUDIO\"\n        )\n    READ_MEDIA_IMAGES = (\n        \"android.permission.READ_MEDIA_IMAGES\"\n        )\n    READ_MEDIA_VIDEO = (\n        \"android.permission.READ_MEDIA_VIDEO\"\n        )\n    READ_PHONE_NUMBERS = (\n        \"android.permission.READ_PHONE_NUMBERS\"\n        )\n    READ_PHONE_STATE = (\n        \"android.permission.READ_PHONE_STATE\"\n        )\n    READ_SMS = (\n        \"android.permission.READ_SMS\"\n        )\n    READ_SYNC_SETTINGS = (\n        \"android.permission.READ_SYNC_SETTINGS\"\n        )\n    READ_SYNC_STATS = (\n        \"android.permission.READ_SYNC_STATS\"\n        )\n    READ_VOICEMAIL = (\n        \"com.android.voicemail.permission.READ_VOICEMAIL\"\n        )\n    REBOOT = (\n        \"android.permission.REBOOT\"\n        )\n    RECEIVE_BOOT_COMPLETED = (\n        \"android.permission.RECEIVE_BOOT_COMPLETED\"\n        )\n    RECEIVE_MMS = (\n        \"android.permission.RECEIVE_MMS\"\n        )\n    RECEIVE_SMS = (\n        \"android.permission.RECEIVE_SMS\"\n        )\n    RECEIVE_WAP_PUSH = (\n        \"android.permission.RECEIVE_WAP_PUSH\"\n        )\n    RECORD_AUDIO = (\n        \"android.permission.RECORD_AUDIO\"\n        )\n    REORDER_TASKS = (\n        \"android.permission.REORDER_TASKS\"\n        )\n    REQUEST_COMPANION_RUN_IN_BACKGROUND = (\n        \"android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND\"\n        )\n    REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = (\n        \"android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND\"\n        )\n    REQUEST_DELETE_PACKAGES = (\n        \"android.permission.REQUEST_DELETE_PACKAGES\"\n        )\n    REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = (\n        \"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\"\n        )\n    REQUEST_INSTALL_PACKAGES = (\n        \"android.permission.REQUEST_INSTALL_PACKAGES\"\n        )\n    RESTART_PACKAGES = (\n        \"android.permission.RESTART_PACKAGES\"\n        )\n    SEND_RESPOND_VIA_MESSAGE = (\n        \"android.permission.SEND_RESPOND_VIA_MESSAGE\"\n        )\n    SEND_SMS = (\n        \"android.permission.SEND_SMS\"\n        )\n    SET_ALARM = (\n        \"com.android.alarm.permission.SET_ALARM\"\n        )\n    SET_ALWAYS_FINISH = (\n        \"android.permission.SET_ALWAYS_FINISH\"\n        )\n    SET_ANIMATION_SCALE = (\n        \"android.permission.SET_ANIMATION_SCALE\"\n        )\n    SET_DEBUG_APP = (\n        \"android.permission.SET_DEBUG_APP\"\n        )\n    SET_PREFERRED_APPLICATIONS = (\n        \"android.permission.SET_PREFERRED_APPLICATIONS\"\n        )\n    SET_PROCESS_LIMIT = (\n        \"android.permission.SET_PROCESS_LIMIT\"\n        )\n    SET_TIME = (\n        \"android.permission.SET_TIME\"\n        )\n    SET_TIME_ZONE = (\n        \"android.permission.SET_TIME_ZONE\"\n        )\n    SET_WALLPAPER = (\n        \"android.permission.SET_WALLPAPER\"\n        )\n    SET_WALLPAPER_HINTS = (\n        \"android.permission.SET_WALLPAPER_HINTS\"\n        )\n    SIGNAL_PERSISTENT_PROCESSES = (\n        \"android.permission.SIGNAL_PERSISTENT_PROCESSES\"\n        )\n    STATUS_BAR = (\n        \"android.permission.STATUS_BAR\"\n        )\n    SYSTEM_ALERT_WINDOW = (\n        \"android.permission.SYSTEM_ALERT_WINDOW\"\n        )\n    TRANSMIT_IR = (\n        \"android.permission.TRANSMIT_IR\"\n        )\n    UNINSTALL_SHORTCUT = (\n        \"com.android.launcher.permission.UNINSTALL_SHORTCUT\"\n        )\n    UPDATE_DEVICE_STATS = (\n        \"android.permission.UPDATE_DEVICE_STATS\"\n        )\n    USE_BIOMETRIC = (\n        \"android.permission.USE_BIOMETRIC\"\n        )\n    USE_FINGERPRINT = (\n        \"android.permission.USE_FINGERPRINT\"\n        )\n    USE_SIP = (\n        \"android.permission.USE_SIP\"\n        )\n    VIBRATE = (\n        \"android.permission.VIBRATE\"\n        )\n    WAKE_LOCK = (\n        \"android.permission.WAKE_LOCK\"\n        )\n    WRITE_APN_SETTINGS = (\n        \"android.permission.WRITE_APN_SETTINGS\"\n        )\n    WRITE_CALENDAR = (\n        \"android.permission.WRITE_CALENDAR\"\n        )\n    WRITE_CALL_LOG = (\n        \"android.permission.WRITE_CALL_LOG\"\n        )\n    WRITE_CONTACTS = (\n        \"android.permission.WRITE_CONTACTS\"\n        )\n    WRITE_EXTERNAL_STORAGE = (\n        \"android.permission.WRITE_EXTERNAL_STORAGE\"\n        )\n    WRITE_GSERVICES = (\n        \"android.permission.WRITE_GSERVICES\"\n        )\n    WRITE_SECURE_SETTINGS = (\n        \"android.permission.WRITE_SECURE_SETTINGS\"\n        )\n    WRITE_SETTINGS = (\n        \"android.permission.WRITE_SETTINGS\"\n        )\n    WRITE_SYNC_SETTINGS = (\n        \"android.permission.WRITE_SYNC_SETTINGS\"\n        )\n    WRITE_VOICEMAIL = (\n        \"com.android.voicemail.permission.WRITE_VOICEMAIL\"\n        )\n    MANAGE_EXTERNAL_STORAGE = (  # Convenient use of paths to manage files\n        \"android.permission.MANAGE_EXTERNAL_STORAGE\"\n        )\n\n\nPERMISSION_GRANTED = 0\nPERMISSION_DENIED = -1\n\n\nclass _onRequestPermissionsCallback(PythonJavaClass):\n    \"\"\"Callback class for registering a Python callback from\n    onRequestPermissionsResult in PythonActivity.\n    \"\"\"\n    __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$PermissionsCallback']\n    __javacontext__ = 'app'\n\n    def __init__(self, func):\n        self.func = func\n        super().__init__()\n\n    @java_method('(I[Ljava/lang/String;[I)V')\n    def onRequestPermissionsResult(self, requestCode,\n                                   permissions, grantResults):\n        self.func(requestCode, permissions, grantResults)\n\n\nclass _RequestPermissionsManager:\n    \"\"\"Internal class for requesting Android permissions.\n\n    Permissions are requested through the method 'request_permissions' which\n    accepts a list of permissions and an optional callback.\n\n    Any callback will asynchronously receive arguments from\n    onRequestPermissionsResult on PythonActivity after requestPermissions is\n    called.\n\n    The callback supplied must accept two arguments: 'permissions' and\n    'grantResults' (as supplied to onPermissionsCallbackResult).\n\n    Note that for SDK_INT < 23, run-time permissions are not required, and so\n    the callback will be called immediately.\n\n    The attribute '_java_callback' is initially None, but is set when the first\n    permissions request is made. It is set to an instance of\n    onRequestPermissionsCallback, which allows the Java callback to be\n    propagated to the class method 'python_callback'. This is then, in turn,\n    used to call an application callback if provided to request_permissions.\n\n    The attribute '_callback_id' is incremented with each call to\n    request_permissions which has a callback (the value '1' is used for any\n    call which does not pass a callback). This is passed to requestCode in\n    the Java call, and used to identify (via the _callbacks dictionary)\n    the matching call.\n    \"\"\"\n    _SDK_INT = None\n    _java_callback = None\n    _callbacks = {1: None}\n    _callback_id = 1\n    # Lock to prevent multiple calls to request_permissions being handled\n    # simultaneously (as incrementing _callback_id is not atomic)\n    _lock = threading.Lock()\n\n    @classmethod\n    def register_callback(cls):\n        \"\"\"Register Java callback for requestPermissions.\"\"\"\n        cls._java_callback = _onRequestPermissionsCallback(cls.python_callback)\n        mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity\n        mActivity.addPermissionsCallback(cls._java_callback)\n\n    @classmethod\n    def request_permissions(cls, permissions, callback=None):\n        \"\"\"Requests Android permissions from PythonActivity.\n        If 'callback' is supplied, the request is made with a new requestCode\n        and the callback is stored in the _callbacks dict. When a Java callback\n        with the matching requestCode is received, callback will be called\n        with arguments of 'permissions' and 'grant_results'.\n        \"\"\"\n        if not cls._SDK_INT:\n            # Get the Android build version and store it\n            VERSION = autoclass('android.os.Build$VERSION')\n            cls.SDK_INT = VERSION.SDK_INT\n        if cls.SDK_INT < 23:\n            # No run-time permissions needed, return immediately.\n            if callback:\n                callback(permissions, [True for x in permissions])\n                return\n        # Request permissions\n        with cls._lock:\n            if not cls._java_callback:\n                cls.register_callback()\n            mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity\n            if not callback:\n                mActivity.requestPermissions(permissions)\n            else:\n                cls._callback_id += 1\n                mActivity.requestPermissionsWithRequestCode(\n                    permissions, cls._callback_id)\n                cls._callbacks[cls._callback_id] = callback\n\n    @classmethod\n    def python_callback(cls, requestCode, permissions, grantResults):\n        \"\"\"Calls the relevant callback with arguments of 'permissions'\n        and 'grantResults'.\"\"\"\n        # Convert from Android codes to True/False\n        grant_results = [x == PERMISSION_GRANTED for x in grantResults]\n        if cls._callbacks.get(requestCode):\n            cls._callbacks[requestCode](permissions, grant_results)\n\n\n# Public API methods for requesting permissions\n\ndef request_permissions(permissions, callback=None):\n    \"\"\"Requests Android permissions.\n\n    Args:\n        permissions (str): A list of permissions to requests (str)\n        callback (callable, optional): A function to call when the request\n            is completed (callable)\n\n    Returns:\n        None\n\n    Notes:\n\n    Permission strings can be imported from the 'Permission' class in this\n    module. For example:\n\n    from android import Permission\n        permissions_list = [Permission.CAMERA,\n                            Permission.WRITE_EXTERNAL_STORAGE]\n\n    See the p4a source file 'permissions.py' for a list of valid permission\n    strings (pythonforandroid/recipes/android/src/android/permissions.py).\n\n    Any callback supplied must accept two arguments:\n       permissions (list of str): A list of permission strings\n       grant_results (list of bool): A list of bools indicating whether the\n           respective permission was granted.\n    See Android documentation for onPermissionsCallbackResult for\n    further information.\n\n    Note that if the request is interrupted the callback may contain an empty\n    list of permissions, without permissions being granted; the App should\n    check that each permission requested has been granted.\n\n    Also note that when calling request_permission on SDK_INT < 23, the\n    callback will be returned immediately as requesting permissions is not\n    required.\n    \"\"\"\n    _RequestPermissionsManager.request_permissions(permissions, callback)\n\n\ndef request_permission(permission, callback=None):\n    request_permissions([permission], callback)\n\n\ndef check_permission(permission):\n    \"\"\"Checks if an app holds the passed permission.\n\n    Args:\n        - permission     An Android permission (str)\n\n    Returns:\n        bool: True if the app holds the permission given, False otherwise.\n    \"\"\"\n    mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity\n    result = bool(mActivity.checkCurrentPermission(\n        permission + \"\"\n    ))\n    return result\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/runnable.py",
    "content": "'''\nRunnable\n========\n'''\n\nfrom jnius import PythonJavaClass, java_method, autoclass\nfrom android.config import ACTIVITY_CLASS_NAME\n\n# Reference to the activity\n_PythonActivity = autoclass(ACTIVITY_CLASS_NAME)\n\n# Cache of functions table. In older Android versions the number of JNI references\n# is limited, so by caching them we avoid running out.\n__functionstable__ = {}\n\n\nclass Runnable(PythonJavaClass):\n    '''Wrapper around Java Runnable class. This class can be used to schedule a\n    call of a Python function into the PythonActivity thread.\n    '''\n\n    __javainterfaces__ = ['java/lang/Runnable']\n    __runnables__ = []\n\n    def __init__(self, func):\n        super().__init__()\n        self.func = func\n\n    def __call__(self, *args, **kwargs):\n        self.args = args\n        self.kwargs = kwargs\n        Runnable.__runnables__.append(self)\n        _PythonActivity.mActivity.runOnUiThread(self)\n\n    @java_method('()V')\n    def run(self):\n        try:\n            self.func(*self.args, **self.kwargs)\n        except:  # noqa E722\n            import traceback\n            traceback.print_exc()\n\n        Runnable.__runnables__.remove(self)\n\n\ndef run_on_ui_thread(f):\n    '''Decorator to create automatically a :class:`Runnable` object with the\n    function. The function will be delayed and call into the Activity thread.\n    '''\n    if f not in __functionstable__:\n        rfunction = Runnable(f)  # store the runnable function\n        __functionstable__[f] = {\"rfunction\": rfunction}\n    rfunction = __functionstable__[f][\"rfunction\"]\n\n    def f2(*args, **kwargs):\n        rfunction(*args, **kwargs)\n\n    return f2\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/storage.py",
    "content": "from jnius import autoclass, cast\nimport os\n\nfrom android.config import ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME\n\n\nEnvironment = autoclass('android.os.Environment')\nFile = autoclass('java.io.File')\n\n\ndef _android_has_is_removable_func():\n    VERSION = autoclass('android.os.Build$VERSION')\n    return (VERSION.SDK_INT >= 24)\n\n\ndef _get_sdcard_path():\n    \"\"\" Internal function to return getExternalStorageDirectory()\n        path. This is internal because it may either return the internal,\n        or an external sd card, depending on the device.\n        Use primary_external_storage_path()\n        or secondary_external_storage_path() instead which try to\n        distinguish this properly.\n    \"\"\"\n    return (\n        Environment.getExternalStorageDirectory().getAbsolutePath()\n    )\n\n\ndef _get_activity():\n    \"\"\"\n    Retrieves the activity from `PythonActivity` fallback to `PythonService`.\n    \"\"\"\n    PythonActivity = autoclass(ACTIVITY_CLASS_NAME)\n    activity = PythonActivity.mActivity\n    if activity is None:\n        # assume we're running from the background service\n        PythonService = autoclass(SERVICE_CLASS_NAME)\n        activity = PythonService.mService\n    return activity\n\n\ndef app_storage_path():\n    \"\"\" Locate the built-in device storage used for this app only.\n\n        This storage is APP-SPECIFIC, and not visible to other apps.\n        It will be wiped when your app is uninstalled.\n\n        Returns directory path to storage.\n    \"\"\"\n    activity = _get_activity()\n    currentActivity = cast('android.app.Activity', activity)\n    context = cast('android.content.ContextWrapper',\n                   currentActivity.getApplicationContext())\n    file_p = cast('java.io.File', context.getFilesDir())\n    return os.path.normpath(os.path.abspath(\n        file_p.getAbsolutePath().replace(\"/\", os.path.sep)))\n\n\ndef primary_external_storage_path():\n    \"\"\" Locate the built-in device storage that user can see via file browser.\n        Often found at: /sdcard/\n\n        This is storage is SHARED, and visible to other apps and the user.\n        It will remain untouched when your app is uninstalled.\n\n        Returns directory path to storage.\n\n        WARNING: You need storage permissions to access this storage.\n    \"\"\"\n    if _android_has_is_removable_func():\n        sdpath = _get_sdcard_path()\n        # Apparently this can both return primary (built-in) or\n        # secondary (removable) external storage depending on the device,\n        # therefore check that we got what we wanted:\n        if not Environment.isExternalStorageRemovable(File(sdpath)):\n            return sdpath\n    if \"EXTERNAL_STORAGE\" in os.environ:\n        return os.environ[\"EXTERNAL_STORAGE\"]\n    raise RuntimeError(\n        \"unexpectedly failed to determine \" +\n        \"primary external storage path\"\n    )\n\n\ndef secondary_external_storage_path():\n    \"\"\" Locate the external SD Card storage, which may not be present.\n        Often found at: /sdcard/External_SD/\n\n        This storage is SHARED, visible to other apps, and may not be\n        be available if the user didn't put in an external SD card.\n        It will remain untouched when your app is uninstalled.\n\n        Returns None if not found, otherwise path to storage.\n\n        WARNING: You need storage permissions to access this storage.\n                 If it is not writable and presents as empty even with\n                 permissions, then the external sd card may not be present.\n    \"\"\"\n    if _android_has_is_removable_func:\n        # See if getExternalStorageDirectory() returns secondary ext storage:\n        sdpath = _get_sdcard_path()\n        # Apparently this can both return primary (built-in) or\n        # secondary (removable) external storage depending on the device,\n        # therefore check that we got what we wanted:\n        if Environment.isExternalStorageRemovable(File(sdpath)):\n            if os.path.exists(sdpath):\n                return sdpath\n\n    # See if we can take a guess based on environment variables:\n    p = None\n    if \"SECONDARY_STORAGE\" in os.environ:\n        p = os.environ[\"SECONDARY_STORAGE\"]\n    elif \"EXTERNAL_SDCARD_STORAGE\" in os.environ:\n        p = os.environ[\"EXTERNAL_SDCARD_STORAGE\"]\n    if p is not None and os.path.exists(p):\n        return p\n    return None\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/android/touch.py",
    "content": "\"\"\"Touch interception helpers for Python for Android.\n\nThis module exposes two utilities to hook into the Android SDL surface's\nintercept touch mechanism via pyjnius:\n\n- `OnInterceptTouchListener`: a thin bridge class that implements the\n  Java interface `SDLSurface.OnInterceptTouchListener` and delegates to a\n  provided Python callable.\n- `TouchListener`: a convenience class with helpers to register/unregister\n  the intercept listener and a hit-testing routine against the Kivy\n  `Window` to decide whether a touch should be consumed.\n- `TouchListener.register_listener` requires a `target_widget` argument,\n  which is used for hit-testing to decide whether to consume touches.\n- Touch coordinates are taken from pointer index 0 and converted to Kivy's\n  coordinate system by inverting Y relative to `Window.height`.\n\nDependencies: pyjnius for bridging to Android, and Kivy for window and\nwidget traversal used in hit-testing.\n\"\"\"\n\nfrom jnius import PythonJavaClass, java_method, autoclass\nfrom android.config import ACTIVITY_CLASS_NAME\n\n__all__ = ('OnInterceptTouchListener', 'TouchListener')\n\n\nclass OnInterceptTouchListener(PythonJavaClass):\n    \"\"\"Bridge for Android's `SDLSurface.OnInterceptTouchListener`.\n\n    Instances of this class can be passed to the SDL surface so that touch\n    events can be intercepted before they reach the normal Android/Kivy\n    dispatch pipeline. The Python callable provided at construction time is\n    invoked for each `MotionEvent` and should return a boolean indicating\n    whether the touch was consumed.\n    \"\"\"\n\n    __javacontext__ = 'app'\n    __javainterfaces__ = [\n        'org/libsdl/app/SDLSurface$OnInterceptTouchListener']\n\n    def __init__(self, listener):\n        \"\"\"Create a new intercept touch listener.\n\n        Parameters:\n            listener (Callable[[object], bool]): A callable that receives the\n                Android `MotionEvent` instance and returns `True` if the\n                touch should be consumed (intercepted), or `False` to let it\n                propagate normally.\n        \"\"\"\n        self.listener = listener\n\n    @java_method('(Landroid/view/MotionEvent;)Z')\n    def onTouch(self, event):\n        \"\"\"Handle an incoming `MotionEvent`.\n\n        Parameters:\n            event: The Android `MotionEvent` object delivered by the SDL\n                surface.\n\n        Returns:\n            bool: The boolean returned by the user-provided `listener`, where\n            `True` indicates the event was consumed and should not propagate\n            further; `False` lets normal processing continue.\n        \"\"\"\n        return self.listener(event)\n\n\nclass TouchListener:\n    \"\"\"Convenience API to register a global Android intercept touch listener.\n\n    This class manages a singleton instance of `OnInterceptTouchListener`\n    that is attached to the app's `PythonActivity.mSurface`. It also stores\n    a reference to a specific `target_widget` used during hit-testing to\n    decide whether touches should be consumed.\n\n    A small hit-testing helper walks the Kivy `Window` widget tree to\n    determine whether a touch should be intercepted (consumed) or allowed to\n    propagate.\n\n    Notes:\n    - The intercept listener affects the entire SDL surface and thus the\n      whole app; use with care.\n    - The internal `__listener` attribute stores the active listener\n      instance when registered, or `None` when not set.\n    - The internal `__target_widget` holds the widget against which the\n      hit-test is compared and is cleared on `unregister_listener()`.\n    \"\"\"\n    __listener = None\n    __target_widget = None\n\n    @classmethod\n    def register_listener(cls, target_widget):\n        \"\"\"Register the global intercept touch listener if not already set.\n\n        This creates a singleton `OnInterceptTouchListener` that delegates to\n        `TouchListener._on_touch_listener` and installs it on\n        `PythonActivity.mSurface` via pyjnius.\n\n        Parameters:\n            target_widget: The widget used as the reference during hit-testing.\n                If the touch lands on this widget and no other widget is found\n                under the touch, the event will be consumed by the intercept\n                listener.\n        \"\"\"\n        if cls.__listener:\n            return\n        cls.__target_widget = target_widget\n        cls.__listener = OnInterceptTouchListener(cls._on_touch_listener)\n        PythonActivity = autoclass(ACTIVITY_CLASS_NAME)\n        PythonActivity.mSurface.setInterceptTouchListener(cls.__listener)\n\n    @classmethod\n    def unregister_listener(cls):\n        \"\"\"Unregister the global intercept touch listener, if any.\n\n        Removes the previously installed listener from\n        `PythonActivity.mSurface` by setting it to `None`. This does not\n        modify the stored reference in `__listener`.\n        \"\"\"\n        PythonActivity = autoclass(ACTIVITY_CLASS_NAME)\n        PythonActivity.mSurface.setInterceptTouchListener(None)\n        cls.__target_widget = None\n\n    @classmethod\n    def is_listener_set(cls):\n        \"\"\"Report whether the intercept listener reference is set.\n\n        Returns:\n            bool: `False` if a listener instance is currently stored in\n            `__listener` (i.e. registered), `True` if no listener is stored.\n            Note: this method reflects the current implementation which\n            returns the negation of the internal reference.\n        \"\"\"\n        return not cls.__listener\n\n    @classmethod\n    def _on_touch_listener(cls, event):\n        \"\"\"Default callback used by the installed intercept listener.\n\n        What it does now (current behavior):\n        - Reads touch coordinates from pointer index 0 using `event.getX(0)`\n          and `event.getY(0)`.\n        - Converts Android coordinates to Kivy coordinates by inverting the Y\n          axis relative to `Window.height`.\n        - Iterates over `Window.children` in reverse (front-to-back) and uses\n          `TouchListener._pick` to select the deepest widget under the touch\n          for each top-level child.\n        - Compares the picked widget with the internally stored\n          `__target_widget` that was provided to `register_listener(...)`.\n        - Returns `True` (consume/intercept) only when the picked widget is\n          exactly `__target_widget` and no other widget was found under the\n          touch. Otherwise returns `False`.\n\n        Important notes and limitations:\n        - There is no filtering by MotionEvent action; all actions reaching\n          this callback are evaluated the same way.\n        - Only pointer index 0 is considered; multi-touch pointers other than\n          index 0 are ignored.\n        - The check is identity-based (`is`) against `__target_widget`.\n        - If another widget (other than `__target_widget`) is hit, the event\n          is not intercepted and will propagate normally.\n\n        Parameters:\n            event: The Android `MotionEvent` that triggered the listener.\n\n        Returns:\n            bool: `True` to consume the touch when the hit-test selects the\n            `__target_widget` and no other widget is found; otherwise `False`\n            to allow normal dispatch.\n        \"\"\"\n        from kivy.core.window import Window\n\n        x = event.getX(0)\n        y = event.getY(0)\n\n        # invert Y !\n        y = Window.height - y\n        # x, y are in Window coordinate. Try to select the widget under the\n        # touch.\n        me = None\n        for child in reversed(Window.children):\n            widget = cls._pick(child, x, y)\n            if not widget:\n                continue\n            if cls.__target_widget is widget:\n                me = widget\n                # keep scanning to ensure no other widget is hit\n                continue\n            # any non-target hit means we should not intercept\n            return False\n        return cls.__target_widget is me\n\n    @classmethod\n    def _pick(cls, widget, x, y):\n        \"\"\"Pick the deepest child widget at coordinates.\n\n        Parameters:\n            widget: The root widget from which to start the search.\n            x (float): X coordinate in the local space of `widget`.\n            y (float): Y coordinate in the local space of `widget`.\n\n        Returns:\n            The deepest child that collides with the given point, or the\n            highest-level `widget` itself if it collides and no deeper child\n            does; otherwise `None` if no collision.\n        \"\"\"\n        # Fast exit if the root doesn't collide\n        if not widget.collide_point(x, y):\n            return None\n\n        # Always descend through the first colliding child in z-order\n        current = widget\n        lx, ly = x, y\n        while True:\n            # Transform coordinates once per level\n            nlx, nly = current.to_local(lx, ly)\n            hit_child = None\n            for child in reversed(current.children):\n                if child.collide_point(nlx, nly):\n                    # keep the last colliding child in this order, matching\n                    # the original recursive implementation's semantics\n                    hit_child = child\n            if hit_child is None:\n                # No deeper child collides; current is the deepest hit\n                return current\n            # Prepare for next level using parent's local coords; we'll\n            # convert again at the next iteration relative to the new\n            # current widget.\n            lx, ly = nlx, nly\n            # Continue descent into the chosen child\n            current = hit_child\n"
  },
  {
    "path": "pythonforandroid/recipes/android/src/setup.py",
    "content": "from setuptools import setup, Extension\nfrom Cython.Build import cythonize\nimport os\n\nlibrary_dirs = os.environ['ANDROID_LIBS_DIR'].split(\":\")\nlib_dict = {\n    'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf'],\n    'sdl3': ['SDL3', 'SDL3_image', 'SDL3_mixer', 'SDL3_ttf'],\n}\nsdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], ['main'])\n\nmodules = [\n    Extension('android._android',\n              ['android/_android.pyx', 'android/_android_jni.c'],\n              libraries=sdl_libs + ['log'],\n              library_dirs=library_dirs),\n    Extension('android._android_billing',\n              ['android/_android_billing.pyx', 'android/_android_billing_jni.c'],\n              libraries=['log'],\n              library_dirs=library_dirs),\n    Extension('android._android_sound',\n              ['android/_android_sound.pyx', 'android/_android_sound_jni.c'],\n              libraries=['log'],\n              library_dirs=library_dirs,\n              extra_compile_args=['-include', 'stdlib.h'])\n]\n\ncythonized_modules = cythonize(modules, compiler_directives={'language_level': \"3\"})\n\nsetup(name='android',\n      version='1.0',\n      packages=['android'],\n      package_dir={'android': 'android'},\n      ext_modules=cythonized_modules\n      )\n"
  },
  {
    "path": "pythonforandroid/recipes/apsw/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass ApswRecipe(PyProjectRecipe):\n    version = '3.50.4.0'\n    url = 'https://github.com/rogerbinns/apsw/releases/download/{version}/apsw-{version}.tar.gz'\n    depends = ['sqlite3']\n    site_packages_name = 'apsw'\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        sqlite_recipe = self.get_recipe('sqlite3', self.ctx)\n        env['CFLAGS'] += ' -I' + sqlite_recipe.get_build_dir(arch.arch)\n        env['LIBS'] = env.get('LIBS', '') + ' -lsqlite3'\n        return env\n\n\nrecipe = ApswRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/argon2-cffi/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\n\n\nclass Argon2Recipe(CompiledComponentsPythonRecipe):\n    version = '20.1.0'\n    url = 'git+https://github.com/hynek/argon2-cffi'\n    depends = ['setuptools', 'cffi']\n    call_hostpython_via_targetpython = False\n    build_cmd = 'build'\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['ARGON2_CFFI_USE_SSE2'] = '0'\n        return env\n\n\nrecipe = Argon2Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/atom/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass AtomRecipe(PyProjectRecipe):\n    site_packages_name = \"atom\"\n    version = \"0.11.0\"\n    url = \"https://files.pythonhosted.org/packages/source/a/atom/atom-{version}.tar.gz\"\n    depends = [\"setuptools\"]\n    patches = [\"pyproject.toml.patch\"]\n\n\nrecipe = AtomRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/atom/pyproject.toml.patch",
    "content": "diff --git a/pyproject.toml b/pyproject.toml\nindex d41287f..c83b053 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -40,6 +40,7 @@\n [tool.setuptools]\n   include-package-data = false\n   package-data = { atom = [\"py.typed\", \"*.pyi\"] }\n+  packages = [\"atom\"]\n \n [tool.setuptools_scm]\n   write_to = \"atom/version.py\"\n"
  },
  {
    "path": "pythonforandroid/recipes/aubio/__init__.py",
    "content": "\"\"\"\nAubio recipe.\nNote that this hasn't been ported to cross compile from macOS yet,\nthe error on 0.4.9 was: src/aubio_priv.h:95:10:\nfatal error: 'Accelerate/Accelerate.h' file not found\n#include <Accelerate/Accelerate.h>\n\"\"\"\n\nfrom pythonforandroid.recipe import PyProjectRecipe\n\n\nclass AubioRecipe(PyProjectRecipe):\n    version = \"0.4.9\"\n    url = \"https://aubio.org/pub/aubio-{version}.tar.bz2\"\n    depends = [\"numpy\", \"setuptools\"]\n\n\nrecipe = AubioRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/audiostream/__init__.py",
    "content": "\nfrom pythonforandroid.recipe import CythonRecipe\nfrom pythonforandroid.toolchain import shprint, current_directory, info\nimport sh\nfrom os.path import join\n\n\nclass AudiostreamRecipe(CythonRecipe):\n    # audiostream has no tagged versions; this is the latest commit to master 2020-12-22\n    # it includes a fix for the dyload issue on android that was preventing use\n    version = '69f6b100f1ea4e3982a1acf6bbb0804e31a2cd50'\n    url = 'https://github.com/kivy/audiostream/archive/{version}.zip'\n    sha256sum = '4d415c91706fd76865d0d22f1945f87900dc42125ff5a6c8d77898ccdf613c21'\n    name = 'audiostream'\n    depends = ['python3', 'sdl2', 'pyjnius']\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        sdl_include = 'SDL2'\n\n        env['USE_SDL2'] = 'True'\n        env['SDL2_INCLUDE_DIR'] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include')\n\n        env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include'.format(\n                              jni_path=join(self.ctx.bootstrap.build_dir, 'jni'),\n                              sdl_include=sdl_include)\n\n        sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)\n        for include_dir in sdl2_mixer_recipe.get_include_dirs(arch):\n            env['CFLAGS'] += ' -I{include_dir}'.format(include_dir=include_dir)\n\n        # NDKPLATFORM is our switch for detecting Android platform, so can't be None\n        env['NDKPLATFORM'] = \"NOTNONE\"\n        env['LIBLINK'] = 'NOTNONE'  # Hacky fix. Needed by audiostream setup.py\n        return env\n\n    def postbuild_arch(self, arch):\n        # TODO: This code was copied from pyjnius, but judging by the\n        #       audiostream history, it looks like this step might have\n        #       happened automatically in the past.\n        #       Given the goal of migrating off of recipes, it would\n        #       be good to repair or build infrastructure for doing this\n        #       automatically, for when including a java class is\n        #       the best solution to a problem.\n        super().postbuild_arch(arch)\n        info('Copying audiostream java files to classes build dir')\n        with current_directory(self.get_build_dir(arch.arch)):\n            shprint(sh.cp, '-a', join('audiostream', 'platform', 'android'), self.ctx.javaclass_dir)\n\n\nrecipe = AudiostreamRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/av/__init__.py",
    "content": "from pythonforandroid.toolchain import Recipe\nfrom pythonforandroid.recipe import CythonRecipe\n\n\nclass PyAVRecipe(CythonRecipe):\n\n    name = \"av\"\n    version = \"13.1.0\"\n    url = \"https://github.com/PyAV-Org/PyAV/archive/v{version}.zip\"\n\n    depends = [\"python3\", \"cython\", \"ffmpeg\", \"av_codecs\"]\n    opt_depends = [\"openssl\"]\n    patches = ['patches/compilation_syntax_errors.patch']\n\n    def get_recipe_env(self, arch, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch)\n\n        build_dir = Recipe.get_recipe(\"ffmpeg\", self.ctx).get_build_dir(\n            arch.arch\n        )\n        self.setup_extra_args = [\"--ffmpeg-dir={}\".format(build_dir)]\n\n        return env\n\n\nrecipe = PyAVRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/av/patches/compilation_syntax_errors.patch",
    "content": "diff --git a/av/container/streams.pyx b/av/container/streams.pyx\nindex 17e4992..502ac5a 100644\n--- a/av/container/streams.pyx\n+++ b/av/container/streams.pyx\n@@ -144,7 +144,7 @@ cdef class StreamContainer:\n \n         return stream_index\n \n-    def best(self, str type, /, Stream related = None):\n+    def best(self, str type, Stream related=None):\n         \"\"\"best(type: Literal[\"video\", \"audio\", \"subtitle\", \"attachment\", \"data\"], /, related: Stream | None)\n         Finds the \"best\" stream in the file. Wraps :ffmpeg:`av_find_best_stream`. Example::\n \ndiff --git a/av/filter/context.pyx b/av/filter/context.pyx\nindex b820d3d..8908b56 100644\n--- a/av/filter/context.pyx\n+++ b/av/filter/context.pyx\n@@ -77,7 +77,8 @@ cdef class FilterContext:\n     \n     @property\n     def graph(self):\n-        if (graph := self._graph()):\n+        graph = self._graph()\n+        if graph:\n             return graph\n         else:\n             raise RuntimeError(\"graph is unallocated\")\n"
  },
  {
    "path": "pythonforandroid/recipes/av_codecs/__init__.py",
    "content": "from pythonforandroid.toolchain import Recipe\n\n\nclass PyAVCodecsRecipe(Recipe):\n    depends = [\"libx264\", \"libshine\", \"libvpx\"]\n\n    def build_arch(self, arch):\n        pass\n\n\nrecipe = PyAVCodecsRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/bcrypt/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe\n\n\nclass BCryptRecipe(CompiledComponentsPythonRecipe):\n    name = 'bcrypt'\n    version = '3.1.7'\n    url = 'https://github.com/pyca/bcrypt/archive/{version}.tar.gz'\n    depends = ['openssl', 'cffi']\n    call_hostpython_via_targetpython = False\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n\n        openssl_recipe = Recipe.get_recipe('openssl', self.ctx)\n        env['CFLAGS'] += openssl_recipe.include_flags(arch)\n        env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)\n        env['LIBS'] = openssl_recipe.link_libs_flags()\n\n        return env\n\n\nrecipe = BCryptRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/bitarray/__init__.py",
    "content": "from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe\n\n\nclass BitarrayRecipe(CppCompiledComponentsPythonRecipe):\n    stl_lib_name = \"c++_shared\"\n    version = \"3.0.0\"\n    url = \"https://github.com/ilanschnell/bitarray/archive/refs/tags/{version}.tar.gz\"\n    depends = [\"setuptools\"]\n\n\nrecipe = BitarrayRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/boost/__init__.py",
    "content": "from pythonforandroid.util import current_directory\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\nfrom os.path import join, exists\nfrom os import environ\nimport shutil\nimport sh\n\n\"\"\"\nThis recipe bootstraps Boost from source to build Boost.Build\nincluding python bindings\n\"\"\"\n\n\nclass BoostRecipe(Recipe):\n    # Todo: make recipe compatible with all p4a architectures\n    '''\n    .. note:: This recipe can be built only against API 21+ and an android\n              ndk >= r19\n\n    .. versionchanged:: 0.6.0\n         Rewrote recipe to support clang's build. The following changes has\n         been made:\n\n            - Bumped version number to 1.68.0\n            - Better version handling for url\n            - Added python 3 compatibility\n            - Default compiler for ndk's toolchain set to clang\n            - Python version will be detected via user-config.jam\n            - Changed stl's lib from ``gnustl_shared`` to ``c++_shared``\n\n    .. versionchanged:: 2019.08.09.1.dev0\n\n            - Bumped version number to 1.68.0\n            - Adapted to work with ndk-r19+\n    '''\n    version = '1.69.0'\n    url = (\n        'https://downloads.sourceforge.net/project/boost/'\n        'boost/{version}/boost_{version_underscore}.tar.bz2'\n    )\n    depends = ['python3']\n    patches = [\n        'disable-so-version.patch',\n        'use-android-libs.patch',\n        'fix-android-issues.patch',\n    ]\n    need_stl_shared = True\n\n    @property\n    def versioned_url(self):\n        if self.url is None:\n            return None\n        return self.url.format(\n            version=self.version,\n            version_underscore=self.version.replace('.', '_'),\n        )\n\n    def should_build(self, arch):\n        return not exists(join(self.get_build_dir(arch.arch), 'b2'))\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            # Set custom configuration\n            shutil.copyfile(\n                join(self.get_recipe_dir(), 'user-config.jam'),\n                join(env['BOOST_BUILD_PATH'], 'user-config.jam'),\n            )\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        env = self.get_recipe_env(arch)\n        env['PYTHON_HOST'] = self.ctx.hostpython\n        with current_directory(self.get_build_dir(arch.arch)):\n            if not exists('b2'):\n                # Compile Boost.Build engine with this custom toolchain\n                bash = sh.Command('bash')\n                shprint(bash, 'bootstrap.sh')  # Do not pass env\n\n    def get_recipe_env(self, arch):\n        # We don't use the normal env because we\n        # are building with a standalone toolchain\n        env = environ.copy()\n\n        # find user-config.jam\n        env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch)\n        # find boost source\n        env['BOOST_ROOT'] = env['BOOST_BUILD_PATH']\n\n        env['PYTHON_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)\n        env['PYTHON_INCLUDE'] = self.ctx.python_recipe.include_root(arch.arch)\n        env['PYTHON_MAJOR_MINOR'] = self.ctx.python_recipe.version[:3]\n        env['PYTHON_LINK_VERSION'] = self.ctx.python_recipe.link_version\n\n        env['ARCH'] = arch.arch.replace('-', '')\n        env['TARGET_TRIPLET'] = arch.target\n        env['CROSSHOST'] = arch.command_prefix\n        env['CROSSHOME'] = self.ctx.ndk.llvm_prebuilt_dir\n        return env\n\n\nrecipe = BoostRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/boost/disable-so-version.patch",
    "content": "--- boost/boostcpp.jam\t2015-12-14 03:30:09.000000000 +0100\n+++ boost-patch/boostcpp.jam\t2016-02-08 16:38:40.510859612 +0100\n@@ -155,8 +155,9 @@\n         if $(type) = SHARED_LIB &&\n           ! [ $(property-set).get <target-os> ] in windows cygwin darwin aix &&\n           ! [ $(property-set).get <toolset> ] in pgi\n         {\n+            return $(result) ; # disable version suffix for android\n             result = $(result).$(BOOST_VERSION)  ;\n         }\n \n         return $(result) ;\n"
  },
  {
    "path": "pythonforandroid/recipes/boost/fix-android-issues.patch",
    "content": "diff -u -r boost_1_69_0.orig/boost/asio/detail/config.hpp boost_1_69_0/boost/asio/detail/config.hpp\n--- boost_1_69_0.orig/boost/asio/detail/config.hpp\t2018-12-05 20:58:15.000000000 +0100\n+++ boost_1_69_0/boost/asio/detail/config.hpp\t2018-12-13 14:52:06.000000000 +0100\n@@ -815,7 +815,11 @@\n #    if (_LIBCPP_VERSION < 7000)\n #     if (__cplusplus >= 201402)\n #      if __has_include(<experimental/string_view>)\n-#       define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1\n+#     if __clang_major__ >= 7\n+#      undef BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW\n+#     else\n+#      define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1\n+#     endif // __clang_major__ >= 7\n #      endif // __has_include(<experimental/string_view>)\n #     endif // (__cplusplus >= 201402)\n #    endif // (_LIBCPP_VERSION < 7000)\ndiff -u -r boost_1_69_0.orig/boost/config/user.hpp boost_1_69_0/boost/config/user.hpp\n--- boost_1_69_0.orig/boost/config/user.hpp\t2018-12-05 20:58:16.000000000 +0100\n+++ boost_1_69_0/boost/config/user.hpp\t2018-12-13 14:35:29.000000000 +0100\n@@ -13,6 +13,12 @@\n //  configuration policy:\n //\n\n+// Android defines\n+// There is problem with std::atomic on android (and some other platforms).\n+// See this link for more info:\n+// https://code.google.com/p/android/issues/detail?id=42735#makechanges\n+#define BOOST_ASIO_DISABLE_STD_ATOMIC 1\n+\n // define this to locate a compiler config file:\n // #define BOOST_COMPILER_CONFIG <myheader>\n\ndiff -u -r boost_1_69_0.orig/boost/system/error_code.hpp boost_1_69_0/boost/system/error_code.hpp\n--- boost_1_69_0.orig/boost/system/error_code.hpp\t2018-12-05 20:58:23.000000000 +0100\n+++ boost_1_69_0/boost/system/error_code.hpp\t2018-12-13 14:53:33.000000000 +0100\n@@ -14,6 +14,7 @@\n #include <boost/system/detail/config.hpp>\n #include <boost/cstdint.hpp>\n #include <boost/config.hpp>\n+#include <stdio.h>\n #include <ostream>\n #include <string>\n #include <functional>\ndiff -u -r boost_1_69_0.orig/libs/filesystem/src/operations.cpp boost_1_69_0/libs/filesystem/src/operations.cpp\n--- boost_1_69_0.orig/libs/filesystem/src/operations.cpp\t2018-12-05 20:58:17.000000000 +0100\n+++ boost_1_69_0/libs/filesystem/src/operations.cpp\t2018-12-13 14:55:41.000000000 +0100\n@@ -232,6 +232,21 @@\n\n # if defined(BOOST_POSIX_API)\n\n+#  if defined(__ANDROID__)\n+#  define truncate libboost_truncate_wrapper\n+// truncate() is present in Android libc only starting from ABI 21, so here's a simple wrapper\n+static int libboost_truncate_wrapper(const char *path, off_t length)\n+{\n+  int fd = open(path, O_WRONLY);\n+  if (fd == -1) {\n+    return -1;\n+  }\n+  int status = ftruncate(fd, length);\n+  close(fd);\n+  return status;\n+}\n+#  endif\n+\n typedef int err_t;\n\n //  POSIX uses a 0 return to indicate success\ndiff -u -r boost_1_69_0.orig/tools/build/src/tools/common.jam boost_1_69_0/tools/build/src/tools/common.jam\n--- boost_1_69_0.orig/tools/build/src/tools/common.jam\t2019-01-25 23:18:34.544755629 +0200\n+++ boost_1_69_0/tools/build/src/tools/common.jam\t2019-01-25 23:20:42.309047754 +0200\n@@ -976,10 +976,10 @@\n     }\n\n     # Ditto, from Clang 4\n-    if $(tag) in clang clangw && [ numbers.less 3 $(version[1]) ]\n-    {\n-        version = $(version[1]) ;\n-    }\n+    #if $(tag) in clang clangw && [ numbers.less 3 $(version[1]) ]\n+    #{\n+    #    version = $(version[1]) ;\n+    #}\n\n     # On intel, version is not added, because it does not matter and it is the\n     # version of vc used as backend that matters. Ideally, we should encode the\n"
  },
  {
    "path": "pythonforandroid/recipes/boost/use-android-libs.patch",
    "content": "--- boost/tools/build/src/tools/python.jam\t2015-10-16 20:55:36.000000000 +0200\n+++ boost-patch/tools/build/src/tools/python.jam\t2016-02-09 13:16:09.519261546 +0100\n@@ -646,6 +646,7 @@\n \n         case aix : return  <library>pthread <library>dl ;\n \n+        case * : return ; # use Android builtin libs\n         case * : return  <library>pthread <library>dl\n             <toolset>gcc:<library>util <toolset-intel:platform>linux:<library>util ;\n     }\n"
  },
  {
    "path": "pythonforandroid/recipes/boost/user-config.jam",
    "content": "import os ;\n\nlocal ARCH = [ os.environ ARCH ] ;\nlocal TARGET_TRIPLET = [ os.environ TARGET_TRIPLET ] ;\nlocal CROSSHOME = [ os.environ CROSSHOME ] ;\nlocal PYTHON_HOST = [ os.environ PYTHON_HOST ] ;\nlocal PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;\nlocal PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ;\nlocal PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ;\nlocal PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ;\n\nusing clang : $(ARCH) : $(CROSSHOME)/bin/$(TARGET_TRIPLET)-clang++ :\n<archiver>$(CROSSHOME)/bin/llvm-ar\n<compileflags>-fPIC\n<compileflags>-ffunction-sections\n<compileflags>-fdata-sections\n<compileflags>-funwind-tables\n<compileflags>-fstack-protector-strong\n<compileflags>-no-canonical-prefixes\n<compileflags>-Wformat\n<compileflags>-Werror=format-security\n<compileflags>-frtti\n<compileflags>-fexceptions\n<compileflags>-DNDEBUG\n<compileflags>-g\n<compileflags>-Oz\n<compileflags>-mthumb\n<linkflags>-Wl,-z,relro\n<linkflags>-Wl,-z,now\n<linkflags>-lc++_shared\n<linkflags>-L$(PYTHON_ROOT)\n<linkflags>-lpython$(PYTHON_LINK_VERSION)\n<linkflags>-Wl,-O1\n<linkflags>-Wl,-Bsymbolic-functions\n;\n\nusing python : $(PYTHON_MAJOR_MINOR)\n    : $(PYTHON_host)\n    : $(PYTHON_ROOT) $(PYTHON_INCLUDE)\n    : $(PYTHON_ROOT)/libpython$(PYTHON_LINK_VERSION).so\n    : #<define>BOOST_ALL_DYN_LINK\n;"
  },
  {
    "path": "pythonforandroid/recipes/brokenrecipe/__init__.py",
    "content": "from pythonforandroid.toolchain import Recipe\n\n\nclass BrokenRecipe(Recipe):\n    def __init__(self):\n        print('This is a broken recipe, not a real one!')\n\n\nrecipe = BrokenRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/cffi/__init__.py",
    "content": "import os\nfrom pythonforandroid.recipe import PyProjectRecipe\n\n\nclass CffiRecipe(PyProjectRecipe):\n    \"\"\"\n    Extra system dependencies: autoconf, automake and libtool.\n    \"\"\"\n    name = 'cffi'\n    version = '2.0.0'\n    url = 'https://github.com/python-cffi/cffi/archive/refs/tags/v{version}.tar.gz'\n\n    depends = ['pycparser', 'libffi']\n\n    patches = ['disable-pkg-config.patch']\n\n    def get_hostrecipe_env(self, arch=None):\n        # fixes missing ffi.h on some host systems (e.g. gentoo)\n        env = super().get_hostrecipe_env(arch)\n        libffi = self.get_recipe('libffi', self.ctx)\n        includes = libffi.get_include_dirs(arch)\n        env['FFI_INC'] = \",\".join(includes)\n        return env\n\n    def get_recipe_env(self, arch=None, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        libffi = self.get_recipe('libffi', self.ctx)\n        includes = libffi.get_include_dirs(arch)\n        env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes)\n        env['CFLAGS'] += ' -I{}'.format(self.ctx.python_recipe.include_root(arch.arch))\n        env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' +\n                          self.ctx.get_libs_dir(arch.arch))\n        env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))\n        # required for libc and libdl\n        env['LDFLAGS'] += ' -L{}'.format(arch.ndk_lib_dir_versioned)\n        env['LDFLAGS'] += ' -L{}'.format(self.ctx.python_recipe.link_root(arch.arch))\n        env['LDFLAGS'] += ' -lpython{}'.format(self.ctx.python_recipe.link_version)\n        return env\n\n\nrecipe = CffiRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/cffi/disable-pkg-config.patch",
    "content": "diff --git a/setup.py b/setup copy.py\nindex 4ce0007..9be4a6d 100644\n--- a/setup.py\n+++ b/setup\n@@ -9,8 +9,7 @@ if sys.platform == \"win32\":\n \n sources = ['c/_cffi_backend.c']\n libraries = ['ffi']\n-include_dirs = ['/usr/include/ffi',\n-                '/usr/include/libffi']    # may be changed by pkg-config\n+include_dirs = os.environ['FFI_INC'].split(',') if 'FFI_INC' in os.environ else []\n define_macros = [('FFI_BUILDING', '1')]   # for linking with libffi static library\n library_dirs = []\n extra_compile_args = []\n@@ -105,14 +104,7 @@ def uses_msvc():\n     return config.try_compile('#ifndef _MSC_VER\\n#error \"not MSVC\"\\n#endif')\n \n def use_pkg_config():\n-    if sys.platform == 'darwin' and os.path.exists('/usr/local/bin/brew'):\n-        use_homebrew_for_libffi()\n-\n-    _ask_pkg_config(include_dirs,       '--cflags-only-I', '-I', sysroot=True)\n-    _ask_pkg_config(extra_compile_args, '--cflags-only-other')\n-    _ask_pkg_config(library_dirs,       '--libs-only-L', '-L', sysroot=True)\n-    _ask_pkg_config(extra_link_args,    '--libs-only-other')\n-    _ask_pkg_config(libraries,          '--libs-only-l', '-l')\n+    pass\n\n"
  },
  {
    "path": "pythonforandroid/recipes/coincurve/__init__.py",
    "content": "import os\nfrom pythonforandroid.recipe import PythonRecipe\n\n\nclass CoincurveRecipe(PythonRecipe):\n    version = \"19.0.1\"\n    url = \"https://github.com/ofek/coincurve/archive/v{version}.tar.gz\"\n    call_hostpython_via_targetpython = False\n    depends = [\"setuptools\", \"libffi\", \"cffi\", \"libsecp256k1\", \"asn1crypto\"]\n    patches = [\"coincurve.patch\"]\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super(CoincurveRecipe, self).get_recipe_env(arch, with_flags_in_cc)\n        libsecp256k1 = self.get_recipe(\"libsecp256k1\", self.ctx)\n        libsecp256k1_dir = libsecp256k1.get_build_dir(arch.arch)\n        env[\"CFLAGS\"] += \" -I\" + os.path.join(libsecp256k1_dir, \"include\")\n        env[\"LDFLAGS\"] += \" -lsecp256k1\"\n        return env\n\n\nrecipe = CoincurveRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/coincurve/coincurve.patch",
    "content": "diff '--color=auto' -uNr coincurve-19.0.1/setup.py coincurve-19.0.1.patch/setup.py\n--- coincurve-19.0.1/setup.py\t2024-03-02 10:40:59.000000000 +0530\n+++ coincurve-19.0.1.patch/setup.py\t2024-03-10 09:51:58.034737104 +0530\n@@ -47,6 +47,7 @@\n \n \n def download_library(command):\n+    return\n     if command.dry_run:\n         return\n     libdir = absolute('libsecp256k1')\n@@ -189,6 +190,7 @@\n             absolute('libsecp256k1/configure'),\n             '--disable-shared',\n             '--enable-static',\n+            '--host=%s' % os.environ['TOOLCHAIN_PREFIX'],\n             '--disable-dependency-tracking',\n             '--with-pic',\n             '--enable-module-extrakeys',\n@@ -269,13 +271,7 @@\n         # ABI?: py_limited_api=True,\n     )\n \n-    extension.extra_compile_args = [\n-        subprocess.check_output(['pkg-config', '--cflags-only-I', 'libsecp256k1']).strip().decode('utf-8')  # noqa S603\n-    ]\n-    extension.extra_link_args = [\n-        subprocess.check_output(['pkg-config', '--libs-only-L', 'libsecp256k1']).strip().decode('utf-8'),  # noqa S603\n-        subprocess.check_output(['pkg-config', '--libs-only-l', 'libsecp256k1']).strip().decode('utf-8'),  # noqa S603\n-    ]\n+    extension.extra_link_args = [\"-lsecp256k1\"]\n \n     if os.name == 'nt' or sys.platform == 'win32':\n         # Apparently, the linker on Windows interprets -lxxx as xxx.lib, not libxxx.lib\n@@ -340,7 +336,7 @@\n     license='MIT OR Apache-2.0',\n \n     python_requires='>=3.8',\n-    install_requires=['asn1crypto', 'cffi>=1.3.0'],\n+    install_requires=[],\n \n     packages=find_packages(exclude=('_cffi_build', '_cffi_build.*', 'libsecp256k1', 'tests')),\n     package_data=package_data,\ndiff '--color=auto' -uNr coincurve-19.0.1/setup_support.py coincurve-19.0.1.patch/setup_support.py\n--- coincurve-19.0.1/setup_support.py\t2024-03-02 10:40:59.000000000 +0530\n+++ coincurve-19.0.1.patch/setup_support.py\t2024-03-10 08:53:45.650056659 +0530\n@@ -56,6 +56,7 @@\n \n \n def _find_lib():\n+    return True\n     if 'COINCURVE_IGNORE_SYSTEM_LIB' in os.environ:\n         return False\n \n"
  },
  {
    "path": "pythonforandroid/recipes/coverage/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass CoverageRecipe(PythonRecipe):\n\n    version = '4.1'\n\n    url = 'https://pypi.python.org/packages/2d/10/6136c8e10644c16906edf4d9f7c782c0f2e7ed47ff2f41f067384e432088/coverage-{version}.tar.gz'\n\n    depends = ['hostpython3', 'setuptools']\n\n    patches = ['fallback-utf8.patch']\n\n    site_packages_name = 'coverage'\n\n    call_hostpython_via_targetpython = False\n\n\nrecipe = CoverageRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/coverage/fallback-utf8.patch",
    "content": "--- coverage-4.1/coverage/misc.py\t2016-02-13 20:04:35.000000000 +0100\n+++ patch/coverage/misc.py\t2016-07-11 17:07:22.656603295 +0200\n@@ -166,7 +166,8 @@\n     encoding = (\n         getattr(outfile, \"encoding\", None) or\n         getattr(sys.__stdout__, \"encoding\", None) or\n-        locale.getpreferredencoding()\n+        locale.getpreferredencoding() or\n+        'utf-8'\n     )\n     return encoding\n \n"
  },
  {
    "path": "pythonforandroid/recipes/cryptography/__init__.py",
    "content": "from pythonforandroid.recipe import RustCompiledComponentsRecipe\nfrom os.path import join\n\n\nclass CryptographyRecipe(RustCompiledComponentsRecipe):\n\n    name = 'cryptography'\n    version = '46.0.3'\n    url = 'https://github.com/pyca/cryptography/archive/refs/tags/{version}.tar.gz'\n    depends = ['openssl', 'cffi']\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        openssl_build_dir = self.get_recipe('openssl', self.ctx).get_build_dir(arch.arch)\n        build_target = self.RUST_ARCH_CODES[arch.arch].upper().replace(\"-\", \"_\")\n        openssl_include = \"{}_OPENSSL_INCLUDE_DIR\".format(build_target)\n        openssl_libs = \"{}_OPENSSL_LIB_DIR\".format(build_target)\n        env[openssl_include] = join(openssl_build_dir, 'include')\n        env[openssl_libs] = join(openssl_build_dir)\n        return env\n\n\nrecipe = CryptographyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/cymunk/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\n\n\nclass CymunkRecipe(CythonRecipe):\n    version = 'master'\n    url = 'https://github.com/tito/cymunk/archive/{version}.zip'\n    name = 'cymunk'\n\n\nrecipe = CymunkRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/cython/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\n\n\nclass CythonRecipe(CompiledComponentsPythonRecipe):\n\n    version = '0.29.36'\n    url = 'https://github.com/cython/cython/archive/{version}.tar.gz'\n    site_packages_name = 'cython'\n    depends = ['setuptools']\n    call_hostpython_via_targetpython = False\n    install_in_hostpython = True\n\n\nrecipe = CythonRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/decorator/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass DecoratorPyRecipe(PythonRecipe):\n    version = '4.2.1'\n    url = 'https://pypi.python.org/packages/source/d/decorator/decorator-{version}.tar.gz'\n    url = 'https://github.com/micheles/decorator/archive/{version}.tar.gz'\n    depends = ['setuptools']\n    site_packages_name = 'decorator'\n    call_hostpython_via_targetpython = False\n\n\nrecipe = DecoratorPyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/enaml/0001-Update-setup.py.patch",
    "content": "From 156a0426f7350bf49bdfae1aad555e13c9494b9a Mon Sep 17 00:00:00 2001\nFrom: frmdstryr <frmdstryr@gmail.com>\nDate: Thu, 23 Jun 2016 22:04:32 -0400\nSubject: [PATCH] Update setup.py\n\n---\n setup.py | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/setup.py b/setup.py\nindex 3bfd2a2..99817e5 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -72,7 +72,7 @@ setup(\n     url='https://github.com/nucleic/enaml',\n     description='Declarative DSL for building rich user interfaces in Python',\n     long_description=open('README.rst').read(),\n-    requires=['atom', 'PyQt', 'ply', 'kiwisolver'],\n+    requires=['atom', 'ply', 'kiwisolver'],\n     install_requires=['distribute', 'atom >= 0.3.8', 'kiwisolver >= 0.1.2', 'ply >= 3.4'],\n     packages=find_packages(),\n     package_data={\n-- \n2.7.4\n\n"
  },
  {
    "path": "pythonforandroid/recipes/enaml/__init__.py",
    "content": "from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe\n\n\nclass EnamlRecipe(CppCompiledComponentsPythonRecipe):\n    site_packages_name = 'enaml'\n    version = '0.9.8'\n    url = 'https://github.com/nucleic/enaml/archive/{version}.zip'\n    patches = ['0001-Update-setup.py.patch']  # Remove PyQt dependency\n    depends = ['setuptools', 'atom', 'kiwisolver']\n\n\nrecipe = EnamlRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/ethash/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass EthashRecipe(PythonRecipe):\n\n    url = 'https://github.com/ethereum/ethash/archive/master.zip'\n\n    depends = ['setuptools']\n\n\nrecipe = EthashRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/evdev/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\n\n\nclass EvdevRecipe(CompiledComponentsPythonRecipe):\n    name = 'evdev'\n    version = 'v0.4.7'\n    url = 'https://github.com/gvalkov/python-evdev/archive/{version}.zip'\n    call_hostpython_via_targetpython = False\n\n    depends = []\n\n    build_cmd = 'build'\n\n    patches = ['evcnt.patch',\n               'keycnt.patch',\n               'remove-uinput.patch',\n               'include-dir.patch',\n               'evdev-permissions.patch']\n\n    def get_recipe_env(self, arch=None):\n        env = super().get_recipe_env(arch)\n        env['SYSROOT'] = self.ctx.ndk.sysroot\n        return env\n\n\nrecipe = EvdevRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/evdev/evcnt.patch",
    "content": "diff -Naur orig/evdev/input.c v0.4.7/evdev/input.c\n--- orig/evdev/input.c\t2015-06-11 13:56:43.483891914 -0500\n+++ v0.4.7/evdev/input.c\t2015-06-11 13:57:29.079529095 -0500\n@@ -24,6 +24,8 @@\n #include <linux/input.h>\n #endif\n \n+#define EV_CNT (EV_MAX+1)\n+\n #define MAX_NAME_SIZE 256\n \n extern char*  EV_NAME[EV_CNT];\n@@ -190,7 +192,7 @@\n                                                    absinfo.maximum,\n                                                    absinfo.fuzz,\n                                                    absinfo.flat,\n-                                                   absinfo.resolution);\n+                                                   0);\n \n                         evlong = PyLong_FromLong(ev_code);\n                         absitem = Py_BuildValue(\"(OO)\", evlong, py_absinfo);\n"
  },
  {
    "path": "pythonforandroid/recipes/evdev/evdev-permissions.patch",
    "content": "diff -Naur orig/evdev/util.py v0.4.7/evdev/util.py\n--- orig/evdev/util.py\t2015-06-12 16:31:46.532994729 -0500\n+++ v0.4.7/evdev/util.py\t2015-06-12 16:32:59.489933840 -0500\n@@ -3,15 +3,53 @@\n import os\n import stat\n import glob\n+import subprocess\n \n from evdev import ecodes\n from evdev.events import event_factory\n \n \n+su = False\n+\n+\n+def get_su_binary():\n+    global su\n+    if su is not False:\n+        return su\n+\n+    su_files = ['/sbin/su', '/system/bin/su', '/system/xbin/su', '/data/local/xbin/su',\n+                '/data/local/bin/su', '/system/sd/xbin/su', '/system/bin/failsafe/su',\n+                '/data/local/su']\n+    su = None\n+\n+    for fn in su_files:\n+        if os.path.exists(fn):\n+            try:\n+                cmd = [fn, '-c', 'id']\n+                output = subprocess.check_output(cmd)\n+            except Exception:\n+                pass\n+            else:\n+                if 'uid=0' in output:\n+                    su = fn\n+                    break\n+\n+    return su\n+\n+\n+def fix_permissions(nodes):\n+    su = get_su_binary()\n+    if su:\n+        cmd = 'chmod 666 ' + ' '.join(nodes)\n+        print cmd\n+        subprocess.check_call(['su', '-c', cmd])\n+\n+\n def list_devices(input_device_dir='/dev/input'):\n     '''List readable character devices in ``input_device_dir``.'''\n \n     fns = glob.glob('{}/event*'.format(input_device_dir))\n+    fix_permissions(fns)\n     fns = list(filter(is_device, fns))\n \n     return fns\n"
  },
  {
    "path": "pythonforandroid/recipes/evdev/include-dir.patch",
    "content": "diff -Naur orig/setup.py v0.4.7/setup.py\n--- orig/setup.py\t2015-06-11 14:16:31.315765908 -0500\n+++ v0.4.7/setup.py\t2015-06-11 14:17:05.800263536 -0500\n@@ -64,7 +64,7 @@\n \n #-----------------------------------------------------------------------------\n def create_ecodes():\n-    header = '/usr/include/linux/input.h'\n+    header = os.environ['SYSROOT'] + '/usr/include/linux/input.h'\n \n     if not os.path.isfile(header):\n         msg = '''\\\n"
  },
  {
    "path": "pythonforandroid/recipes/evdev/keycnt.patch",
    "content": "diff -Naur orig/evdev/genecodes.py v0.4.7/evdev/genecodes.py\n--- orig/evdev/genecodes.py\t2015-06-12 11:18:39.460538902 -0500\n+++ v0.4.7/evdev/genecodes.py\t2015-06-12 11:20:49.004337615 -0500\n@@ -17,6 +17,8 @@\n #include <linux/input.h>\n #endif\n \n+#define KEY_CNT (KEY_MAX+1)\n+\n /* Automatically generated by evdev.genecodes */\n /* Generated on %s */\n \n@@ -88,6 +88,7 @@\n         macro = regex.search(line)\n         if macro:\n             yield '    PyModule_AddIntMacro(m, %s);' % macro.group(1)\n+    yield '    PyModule_AddIntMacro(m, KEY_CNT);'\n \n uname = list(os.uname()); del uname[1]\n uname = ' '.join(uname)\n"
  },
  {
    "path": "pythonforandroid/recipes/evdev/remove-uinput.patch",
    "content": "diff -Naur orig/evdev/device.py v0.4.7/evdev/device.py\n--- orig/evdev/device.py\t2015-06-11 14:05:00.452884781 -0500\n+++ v0.4.7/evdev/device.py\t2015-06-11 14:05:47.606553546 -0500\n@@ -4,7 +4,7 @@\n from select import select\n from collections import namedtuple\n \n-from evdev import _input, _uinput, ecodes, util\n+from evdev import _input, ecodes, util\n from evdev.events import InputEvent\n \n \n@@ -203,7 +203,7 @@\n \n         ..\n         '''\n-        _uinput.write(self.fd, ecodes.EV_LED, led_num, value)\n+        pass\n \n     def __eq__(self, other):\n         '''Two devices are equal if their :data:`info` attributes are equal.'''\ndiff -Naur orig/evdev/__init__.py v0.4.7/evdev/__init__.py\n--- orig/evdev/__init__.py\t2015-06-11 14:05:00.452884781 -0500\n+++ v0.4.7/evdev/__init__.py\t2015-06-11 14:05:22.973204070 -0500\n@@ -6,7 +6,6 @@\n \n from evdev.device import DeviceInfo, InputDevice, AbsInfo\n from evdev.events import InputEvent, KeyEvent, RelEvent, SynEvent, AbsEvent, event_factory\n-from evdev.uinput import UInput, UInputError\n from evdev.util import list_devices, categorize, resolve_ecodes\n from evdev import ecodes\n from evdev import ff\ndiff -Naur orig/evdev/uinput.c v0.4.7/evdev/uinput.c\n--- orig/evdev/uinput.c\t2015-06-11 14:05:00.453884795 -0500\n+++ v0.4.7/evdev/uinput.c\t1969-12-31 18:00:00.000000000 -0600\n@@ -1,255 +0,0 @@\n-#include <Python.h>\n-\n-#include <stdio.h>\n-#include <string.h>\n-#include <errno.h>\n-#include <sys/types.h>\n-#include <sys/stat.h>\n-#include <fcntl.h>\n-#include <unistd.h>\n-\n-#ifdef __FreeBSD__\n-#include <dev/evdev/input.h>\n-#include <dev/evdev/uinput.h>\n-#else\n-#include <linux/input.h>\n-#include <linux/uinput.h>\n-#endif\n-\n-int _uinput_close(int fd)\n-{\n-    if (ioctl(fd, UI_DEV_DESTROY) < 0) {\n-        int oerrno = errno;\n-        close(fd);\n-        errno = oerrno;\n-        return -1;\n-    }\n-\n-    return close(fd);\n-}\n-\n-\n-static PyObject *\n-uinput_open(PyObject *self, PyObject *args)\n-{\n-    const char* devnode;\n-\n-    int ret = PyArg_ParseTuple(args, \"s\", &devnode);\n-    if (!ret) return NULL;\n-\n-    int fd = open(devnode, O_WRONLY | O_NONBLOCK);\n-    if (fd < 0) {\n-        PyErr_SetString(PyExc_IOError, \"could not open uinput device in write mode\");\n-        return NULL;\n-    }\n-\n-    return Py_BuildValue(\"i\", fd);\n-}\n-\n-\n-static PyObject *\n-uinput_create(PyObject *self, PyObject *args) {\n-    int fd, len, i, abscode;\n-    uint16_t vendor, product, version, bustype;\n-\n-    PyObject *absinfo = NULL, *item = NULL;\n-\n-    struct uinput_user_dev uidev;\n-    const char* name;\n-\n-    int ret = PyArg_ParseTuple(args, \"ishhhhO\", &fd, &name, &vendor,\n-                               &product, &version, &bustype, &absinfo);\n-    if (!ret) return NULL;\n-\n-    memset(&uidev, 0, sizeof(uidev));\n-    strncpy(uidev.name, name, UINPUT_MAX_NAME_SIZE);\n-    uidev.id.vendor  = vendor;\n-    uidev.id.product = product;\n-    uidev.id.version = version;\n-    uidev.id.bustype = bustype;\n-\n-    len = PyList_Size(absinfo);\n-    for (i=0; i<len; i++) {\n-        // item -> (ABS_X, 0, 255, 0, 0)\n-        item = PyList_GetItem(absinfo, i);\n-        abscode = (int)PyLong_AsLong(PyList_GetItem(item, 0));\n-\n-        uidev.absmin[abscode]  = PyLong_AsLong(PyList_GetItem(item, 1));\n-        uidev.absmax[abscode]  = PyLong_AsLong(PyList_GetItem(item, 2));\n-        uidev.absfuzz[abscode] = PyLong_AsLong(PyList_GetItem(item, 3));\n-        uidev.absflat[abscode] = PyLong_AsLong(PyList_GetItem(item, 4));\n-    }\n-\n-    if (write(fd, &uidev, sizeof(uidev)) != sizeof(uidev))\n-        goto on_err;\n-\n-\t/* if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) */\n-    /*     goto on_err; */\n-    /* int i; */\n-\t/* for (i=0; i<KEY_MAX && fd; i++) { */\n-\t/* \tif (ioctl(fd, UI_SET_KEYBIT, i) < 0) */\n-    /*         goto on_err; */\n-\t/* } */\n-\n-\tif (ioctl(fd, UI_DEV_CREATE) < 0)\n-        goto on_err;\n-\n-    Py_RETURN_NONE;\n-\n-    on_err:\n-        _uinput_close(fd);\n-        PyErr_SetFromErrno(PyExc_IOError);\n-        return NULL;\n-}\n-\n-\n-static PyObject *\n-uinput_close(PyObject *self, PyObject *args)\n-{\n-    int fd;\n-\n-    int ret = PyArg_ParseTuple(args, \"i\", &fd);\n-    if (!ret) return NULL;\n-\n-    if (_uinput_close(fd) < 0) {\n-        PyErr_SetFromErrno(PyExc_IOError);\n-        return NULL;\n-    }\n-\n-    Py_RETURN_NONE;\n-}\n-\n-\n-static PyObject *\n-uinput_write(PyObject *self, PyObject *args)\n-{\n-    int fd, type, code, value;\n-\n-    int ret = PyArg_ParseTuple(args, \"iiii\", &fd, &type, &code, &value);\n-    if (!ret) return NULL;\n-\n-    struct input_event event;\n-    memset(&event, 0, sizeof(event));\n-    gettimeofday(&event.time, 0);\n-    event.type = type;\n-    event.code = code;\n-    event.value = value;\n-\n-    if (write(fd, &event, sizeof(event)) != sizeof(event)) {\n-        // @todo: elaborate\n-        // PyErr_SetString(PyExc_IOError, \"error writing event to uinput device\");\n-        PyErr_SetFromErrno(PyExc_IOError);\n-        return NULL;\n-    }\n-\n-    Py_RETURN_NONE;\n-}\n-\n-\n-static PyObject *\n-uinput_enable_event(PyObject *self, PyObject *args)\n-{\n-    int fd;\n-    uint16_t type, code;\n-    unsigned long req;\n-\n-    int ret = PyArg_ParseTuple(args, \"ihh\", &fd, &type, &code);\n-    if (!ret) return NULL;\n-\n-    switch (type) {\n-        case EV_KEY: req = UI_SET_KEYBIT; break;\n-        case EV_ABS: req = UI_SET_ABSBIT; break;\n-        case EV_REL: req = UI_SET_RELBIT; break;\n-        case EV_MSC: req = UI_SET_MSCBIT; break;\n-        case EV_SW:  req = UI_SET_SWBIT;  break;\n-        case EV_LED: req = UI_SET_LEDBIT; break;\n-        case EV_FF:  req = UI_SET_FFBIT;  break;\n-        case EV_SND: req = UI_SET_SNDBIT; break;\n-        default:\n-            errno = EINVAL;\n-            goto on_err;\n-    }\n-\n-    if (ioctl(fd, UI_SET_EVBIT, type) < 0)\n-        goto on_err;\n-\n-    if (ioctl(fd, req, code) < 0)\n-        goto on_err;\n-\n-    Py_RETURN_NONE;\n-\n-    on_err:\n-        _uinput_close(fd);\n-        PyErr_SetFromErrno(PyExc_IOError);\n-        return NULL;\n-}\n-\n-\n-#define MODULE_NAME \"_uinput\"\n-#define MODULE_HELP \"Python bindings for parts of linux/uinput.c\"\n-\n-static PyMethodDef MethodTable[] = {\n-    { \"open\",  uinput_open, METH_VARARGS,\n-      \"Open uinput device node.\"},\n-\n-    { \"create\",  uinput_create, METH_VARARGS,\n-      \"Create an uinput device.\"},\n-\n-    { \"close\",  uinput_close, METH_VARARGS,\n-      \"Destroy uinput device.\"},\n-\n-    { \"write\",  uinput_write, METH_VARARGS,\n-      \"Write event to uinput device.\"},\n-\n-    { \"enable\", uinput_enable_event, METH_VARARGS,\n-      \"Enable a type of event.\"},\n-\n-    { NULL, NULL, 0, NULL}\n-};\n-\n-#if PY_MAJOR_VERSION >= 3\n-static struct PyModuleDef moduledef = {\n-    PyModuleDef_HEAD_INIT,\n-    MODULE_NAME,\n-    MODULE_HELP,\n-    -1,          /* m_size */\n-    MethodTable, /* m_methods */\n-    NULL,        /* m_reload */\n-    NULL,        /* m_traverse */\n-    NULL,        /* m_clear */\n-    NULL,        /* m_free */\n-};\n-\n-static PyObject *\n-moduleinit(void)\n-{\n-    PyObject* m = PyModule_Create(&moduledef);\n-    if (m == NULL) return NULL;\n-\n-    PyModule_AddIntConstant(m, \"maxnamelen\", UINPUT_MAX_NAME_SIZE);\n-    return m;\n-}\n-\n-PyMODINIT_FUNC\n-PyInit__uinput(void)\n-{\n-    return moduleinit();\n-}\n-\n-#else\n-static PyObject *\n-moduleinit(void)\n-{\n-    PyObject* m = Py_InitModule3(MODULE_NAME, MethodTable, MODULE_HELP);\n-    if (m == NULL) return NULL;\n-\n-    PyModule_AddIntConstant(m, \"maxnamelen\", UINPUT_MAX_NAME_SIZE);\n-    return m;\n-}\n-\n-PyMODINIT_FUNC\n-init_uinput(void)\n-{\n-    moduleinit();\n-}\n-#endif\ndiff -Naur orig/evdev/uinput.py v0.4.7/evdev/uinput.py\n--- orig/evdev/uinput.py\t2015-06-11 14:05:00.453884795 -0500\n+++ v0.4.7/evdev/uinput.py\t1969-12-31 18:00:00.000000000 -0600\n@@ -1,208 +0,0 @@\n-# encoding: utf-8\n-\n-import os\n-import stat\n-import time\n-\n-from evdev import _uinput\n-from evdev import ecodes, util, device\n-\n-\n-class UInputError(Exception):\n-    pass\n-\n-\n-class UInput(object):\n-    '''\n-    A userland input device and that can inject input events into the\n-    linux input subsystem.\n-    '''\n-\n-    __slots__ = (\n-        'name', 'vendor', 'product', 'version', 'bustype',\n-        'events', 'devnode', 'fd', 'device',\n-    )\n-\n-    def __init__(self,\n-                 events=None,\n-                 name='py-evdev-uinput',\n-                 vendor=0x1, product=0x1, version=0x1, bustype=0x3,\n-                 devnode='/dev/uinput'):\n-        '''\n-        :param events: the event types and codes that the uinput\n-                       device will be able to inject - defaults to all\n-                       key codes.\n-\n-        :type events: dictionary of event types mapping to lists of\n-                      event codes.\n-\n-        :param name:    the name of the input device.\n-        :param vendor:  vendor identifier.\n-        :param product: product identifier.\n-        :param version: version identifier.\n-        :param bustype: bustype identifier.\n-\n-        .. note:: If you do not specify any events, the uinput device\n-                  will be able to inject only ``KEY_*`` and ``BTN_*``\n-                  event codes.\n-        '''\n-\n-        self.name = name         #: Uinput device name.\n-        self.vendor = vendor     #: Device vendor identifier.\n-        self.product = product   #: Device product identifier.\n-        self.version = version   #: Device version identifier.\n-        self.bustype = bustype   #: Device bustype - eg. ``BUS_USB``.\n-        self.devnode = devnode   #: Uinput device node - eg. ``/dev/uinput/``.\n-\n-        if not events:\n-            events = {ecodes.EV_KEY: ecodes.keys.keys()}\n-\n-        # the min, max, fuzz and flat values for the absolute axis for\n-        # a given code\n-        absinfo = []\n-\n-        self._verify()\n-\n-        #: Write-only, non-blocking file descriptor to the uinput device node.\n-        self.fd = _uinput.open(devnode)\n-\n-        # set device capabilities\n-        for etype, codes in events.items():\n-            for code in codes:\n-                # handle max, min, fuzz, flat\n-                if isinstance(code, (tuple, list, device.AbsInfo)):\n-                    # flatten (ABS_Y, (0, 255, 0, 0)) to (ABS_Y, 0, 255, 0, 0)\n-                    f = [code[0]]; f += code[1]\n-                    absinfo.append(f)\n-                    code = code[0]\n-\n-                #:todo: a lot of unnecessary packing/unpacking\n-                _uinput.enable(self.fd, etype, code)\n-\n-        # create uinput device\n-        _uinput.create(self.fd, name, vendor, product, version, bustype, absinfo)\n-\n-        #: An :class:`InputDevice <evdev.device.InputDevice>` instance\n-        #: for the fake input device. ``None`` if the device cannot be\n-        #: opened for reading and writing.\n-        self.device = self._find_device()\n-\n-    def __enter__(self):\n-        return self\n-\n-    def __exit__(self, type, value, tb):\n-        if hasattr(self, 'fd'):\n-            self.close()\n-\n-    def __repr__(self):\n-        # :todo:\n-        v = (repr(getattr(self, i)) for i in\n-             ('name', 'bustype', 'vendor', 'product', 'version'))\n-        return '{}({})'.format(self.__class__.__name__, ', '.join(v))\n-\n-    def __str__(self):\n-        msg = ('name \"{}\", bus \"{}\", vendor \"{:04x}\", product \"{:04x}\", version \"{:04x}\"\\n'\n-               'event types: {}')\n-\n-        evtypes = [i[0] for i in self.capabilities(True).keys()]\n-        msg = msg.format(self.name, ecodes.BUS[self.bustype],\n-                         self.vendor, self.product,\n-                         self.version, ' '.join(evtypes))\n-\n-        return msg\n-\n-    def close(self):\n-        # close the associated InputDevice, if it was previously opened\n-        if self.device is not None:\n-            self.device.close()\n-\n-        # destroy the uinput device\n-        if self.fd > -1:\n-            _uinput.close(self.fd)\n-            self.fd = -1\n-\n-    def write_event(self, event):\n-        '''\n-        Inject an input event into the input subsystem. Events are\n-        queued until a synchronization event is received.\n-\n-        :param event: InputEvent instance or an object with an\n-                      ``event`` attribute (:class:`KeyEvent\n-                      <evdev.events.KeyEvent>`, :class:`RelEvent\n-                      <evdev.events.RelEvent>` etc).\n-\n-        Example::\n-\n-            ev = InputEvent(1334414993, 274296, ecodes.EV_KEY, ecodes.KEY_A, 1)\n-            ui.write_event(ev)\n-        '''\n-\n-        if hasattr(event, 'event'):\n-            event = event.event\n-\n-        self.write(event.type, event.code, event.value)\n-\n-    def write(self, etype, code, value):\n-        '''\n-        Inject an input event into the input subsystem. Events are\n-        queued until a synchronization event is received.\n-\n-        :param etype: event type (eg. ``EV_KEY``).\n-        :param code:  event code (eg. ``KEY_A``).\n-        :param value: event value (eg. 0 1 2 - depends on event type).\n-\n-        Example::\n-\n-            ui.write(e.EV_KEY, e.KEY_A, 1) # key A - down\n-            ui.write(e.EV_KEY, e.KEY_A, 0) # key A - up\n-        '''\n-\n-        _uinput.write(self.fd, etype, code, value)\n-\n-    def syn(self):\n-        '''\n-        Inject a ``SYN_REPORT`` event into the input subsystem. Events\n-        queued by :func:`write()` will be fired. If possible, events\n-        will be merged into an 'atomic' event.\n-        '''\n-\n-        _uinput.write(self.fd, ecodes.EV_SYN, ecodes.SYN_REPORT, 0)\n-\n-    def capabilities(self, verbose=False, absinfo=True):\n-        '''See :func:`capabilities <evdev.device.InputDevice.capabilities>`.'''\n-        if self.device is None:\n-            raise UInputError('input device not opened - cannot read capabilites')\n-\n-        return self.device.capabilities(verbose, absinfo)\n-\n-    def _verify(self):\n-        '''\n-        Verify that an uinput device exists and is readable and writable\n-        by the current process.\n-        '''\n-\n-        try:\n-            m = os.stat(self.devnode)[stat.ST_MODE]\n-            if not stat.S_ISCHR(m):\n-                raise\n-        except (IndexError, OSError):\n-            msg = '\"{}\" does not exist or is not a character device file '\\\n-                  '- verify that the uinput module is loaded'\n-            raise UInputError(msg.format(self.devnode))\n-\n-        if not os.access(self.devnode, os.W_OK):\n-            msg = '\"{}\" cannot be opened for writing'\n-            raise UInputError(msg.format(self.devnode))\n-\n-        if len(self.name) > _uinput.maxnamelen:\n-            msg = 'uinput device name must not be longer than {} characters'\n-            raise UInputError(msg.format(_uinput.maxnamelen))\n-\n-    def _find_device(self):\n-        #:bug: the device node might not be immediately available\n-        time.sleep(0.1)\n-\n-        for fn in util.list_devices('/dev/input/'):\n-            d = device.InputDevice(fn)\n-            if d.name == self.name:\n-                return d\ndiff -Naur orig/setup.py v0.4.7/setup.py\n--- orig/setup.py\t2015-06-11 14:05:00.450884753 -0500\n+++ v0.4.7/setup.py\t2015-06-11 14:06:13.050914776 -0500\n@@ -37,7 +37,6 @@\n #-----------------------------------------------------------------------------\n cflags   = ['-std=c99', '-Wno-error=declaration-after-statement']\n input_c  = Extension('evdev._input',  sources=['evdev/input.c'],  extra_compile_args=cflags)\n-uinput_c = Extension('evdev._uinput', sources=['evdev/uinput.c'], extra_compile_args=cflags)\n ecodes_c = Extension('evdev._ecodes', sources=['evdev/ecodes.c'], extra_compile_args=cflags)\n \n #-----------------------------------------------------------------------------\n@@ -56,7 +55,7 @@\n     'classifiers':          classifiers,\n \n     'packages':             ['evdev'],\n-    'ext_modules':          [input_c, uinput_c, ecodes_c],\n+    'ext_modules':          [input_c, ecodes_c],\n     'include_package_data': False,\n     'zip_safe':             True,\n     'cmdclass':             {},\n"
  },
  {
    "path": "pythonforandroid/recipes/feedparser/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass FeedparserPyRecipe(PythonRecipe):\n    version = '5.2.1'\n    url = 'https://github.com/kurtmckee/feedparser/archive/{version}.tar.gz'\n    depends = ['setuptools']\n    site_packages_name = 'feedparser'\n    call_hostpython_via_targetpython = False\n\n\nrecipe = FeedparserPyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/ffmpeg/__init__.py",
    "content": "from pythonforandroid.toolchain import Recipe, current_directory, shprint\nfrom os.path import exists, join, realpath\nimport sh\nfrom multiprocessing import cpu_count\n\n\nclass FFMpegRecipe(Recipe):\n    version = '8.0.1'\n    # Moved to github.com instead of ffmpeg.org to improve download speed\n    url = 'https://www.ffmpeg.org/releases/ffmpeg-{version}.tar.xz'\n    depends = [('sdl2', 'sdl3')]  # Need this to build correct recipe order\n    opts_depends = ['openssl', 'ffpyplayer_codecs', 'av_codecs']\n    patches = ['patches/configure.patch', 'patches/backport-Android15-MediaCodec-fix.patch']\n    _libs = [\n        \"libavcodec.so\",\n        \"libavfilter.so\",\n        \"libavutil.so\",\n        \"libswscale.so\",\n        \"libavdevice.so\",\n        \"libavformat.so\",\n        \"libswresample.so\",\n        \"libffmpegbin.so\",\n    ]\n    built_libraries = dict.fromkeys(_libs, \"./lib\")\n\n    def should_build(self, arch):\n        build_dir = self.get_build_dir(arch.arch)\n        return not exists(join(build_dir, 'lib', 'libavcodec.so'))\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['NDK'] = self.ctx.ndk_dir\n        return env\n\n    def build_arch(self, arch):\n        with current_directory(self.get_build_dir(arch.arch)):\n            env = arch.get_env()\n\n            flags = ['--disable-everything']\n            cflags = []\n            ldflags = []\n\n            # enable hardware acceleration codecs\n            flags = [\n                '--enable-jni',\n                '--enable-mediacodec'\n            ]\n\n            if 'openssl' in self.ctx.recipe_build_order:\n                flags += [\n                    '--enable-version3',\n                    '--enable-openssl',\n                    '--enable-nonfree',\n                    '--enable-protocol=https,tls_openssl',\n                ]\n                build_dir = Recipe.get_recipe(\n                    'openssl', self.ctx).get_build_dir(arch.arch)\n                cflags += ['-I' + build_dir + '/include/']\n                ldflags += ['-L' + build_dir, '-lssl', '-lcrypto']\n\n            codecs_opts = {\"ffpyplayer_codecs\", \"av_codecs\"}\n            if codecs_opts.intersection(self.ctx.recipe_build_order):\n\n                # Enable GPL\n                flags += ['--enable-gpl']\n\n                # libx264\n                flags += ['--enable-libx264']\n                build_dir = Recipe.get_recipe(\n                    'libx264', self.ctx).get_build_dir(arch.arch)\n                cflags += ['-I' + build_dir + '/include/']\n                # Newer versions of FFmpeg prioritize the dynamic library and ignore\n                # the static one, unless the static library path is explicitly set.\n                ldflags += [build_dir + '/lib/' + 'libx264.a']\n\n                # libshine\n                flags += ['--enable-libshine']\n                build_dir = Recipe.get_recipe('libshine', self.ctx).get_build_dir(arch.arch)\n                cflags += ['-I' + build_dir + '/include/']\n                ldflags += ['-lshine', '-L' + build_dir + '/lib/']\n                ldflags += ['-lm']\n\n                # libvpx\n                flags += ['--enable-libvpx']\n                build_dir = Recipe.get_recipe(\n                    'libvpx', self.ctx).get_build_dir(arch.arch)\n                cflags += ['-I' + build_dir + '/include/']\n                ldflags += ['-lvpx', '-L' + build_dir + '/lib/']\n\n                # Enable all codecs:\n                flags += [\n                    '--enable-parsers',\n                    '--enable-decoders',\n                    '--enable-encoders',\n                    '--enable-muxers',\n                    '--enable-demuxers',\n                ]\n            else:\n                # Enable codecs only for .mp4:\n                flags += [\n                    '--enable-parser=aac,ac3,h261,h264,mpegaudio,mpeg4video,mpegvideo,vc1',\n                    '--enable-decoder=aac,h264,mpeg4,mpegvideo',\n                    '--enable-muxer=h264,mov,mp4,mpeg2video',\n                    '--enable-demuxer=aac,h264,m4v,mov,mpegvideo,vc1,rtsp',\n                ]\n\n            # needed to prevent _ffmpeg.so: version node not found for symbol av_init_packet@LIBAVFORMAT_52\n            # /usr/bin/ld: failed to set dynamic section sizes: Bad value\n            flags += [\n                '--disable-symver',\n            ]\n\n            # disable doc\n            flags += [\n                '--disable-doc',\n            ]\n\n            # other flags:\n            flags += [\n                '--enable-filter=aresample,resample,crop,adelay,volume,scale',\n                '--enable-protocol=file,http,hls,udp,tcp',\n                '--enable-small',\n                '--enable-hwaccels',\n                '--enable-pic',\n                '--disable-static',\n                '--disable-debug',\n                '--enable-shared',\n            ]\n\n            if 'arm64' in arch.arch:\n                arch_flag = 'aarch64'\n            elif 'x86' in arch.arch:\n                arch_flag = 'x86'\n                flags += ['--disable-asm']\n            else:\n                arch_flag = 'arm'\n\n            # android:\n            flags += [\n                '--target-os=android',\n                '--enable-cross-compile',\n                '--cross-prefix={}-'.format(arch.target),\n                '--arch={}'.format(arch_flag),\n                '--strip={}'.format(self.ctx.ndk.llvm_strip),\n                '--nm={}'.format(self.ctx.ndk.llvm_nm),\n                '--sysroot={}'.format(self.ctx.ndk.sysroot),\n                '--enable-neon',\n                '--prefix={}'.format(realpath('.')),\n            ]\n\n            if arch_flag == 'arm':\n                cflags += [\n                    '-Wno-error=incompatible-pointer-types',\n                    '-mfpu=vfpv3-d16',\n                    '-mfloat-abi=softfp',\n                    '-fPIC',\n                ]\n\n            env['CFLAGS'] += ' ' + ' '.join(cflags)\n            env['LDFLAGS'] += ' ' + ' '.join(ldflags)\n\n            configure = sh.Command('./configure')\n            shprint(configure, *flags, _env=env)\n            shprint(sh.make, '-j', f\"{cpu_count()}\", _env=env)\n            shprint(sh.make, 'install', _env=env)\n            shprint(sh.cp, \"ffmpeg\", \"./lib/libffmpegbin.so\")\n\n\nrecipe = FFMpegRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/ffmpeg/patches/backport-Android15-MediaCodec-fix.patch",
    "content": "From 4531cc1b325b50a77fa22981f44a25fbce025a5e Mon Sep 17 00:00:00 2001\nFrom: Dmitrii Okunev <xaionaro@dx.center>\nDate: Sat, 22 Nov 2025 19:57:19 +0000\nSubject: [PATCH] fftools: Fix MediaCodec on Android15+\n\nOn Android15+ MediaCodec HAL backend was switched from HIDL to AIDL.\nAs a result, MediaCodec operations started to hang, see:\n\n    https://trac.ffmpeg.org/ticket/11363\n    https://github.com/termux/termux-packages/issues/21264\n    https://issuetracker.google.com/issues/382831999\n\nTo fix that it is necessary to initialize binder thread pool.\n\nSigned-off-by: Dmitrii Okunev <xaionaro@dx.center>\n---\n compat/android/binder.c | 114 ++++++++++++++++++++++++++++++++++++++++\n compat/android/binder.h |  31 +++++++++++\n configure               |   3 +-\n fftools/Makefile        |   1 +\n fftools/ffmpeg.c        |   7 +++\n 5 files changed, 155 insertions(+), 1 deletion(-)\n create mode 100644 compat/android/binder.c\n create mode 100644 compat/android/binder.h\n\ndiff --git a/compat/android/binder.c b/compat/android/binder.c\nnew file mode 100644\nindex 0000000000..a214d977cc\n--- /dev/null\n+++ b/compat/android/binder.c\n@@ -0,0 +1,114 @@\n+/*\n+ * Android Binder handler\n+ *\n+ * Copyright (c) 2025 Dmitrii Okunev\n+ *\n+ * This file is part of FFmpeg.\n+ *\n+ * FFmpeg is free software; you can redistribute it and/or\n+ * modify it under the terms of the GNU Lesser General Public\n+ * License as published by the Free Software Foundation; either\n+ * version 2.1 of the License, or (at your option) any later version.\n+ *\n+ * FFmpeg is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+ * Lesser General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU Lesser General Public\n+ * License along with FFmpeg; if not, write to the Free Software\n+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n+ */\n+\n+\n+#if defined(__ANDROID__)\n+\n+#include <dlfcn.h>\n+#include <stdint.h>\n+#include <stdlib.h>\n+\n+#include \"libavutil/log.h\"\n+#include \"binder.h\"\n+\n+#define THREAD_POOL_SIZE 1\n+\n+static void *dlopen_libbinder_ndk(void)\n+{\n+    /*\n+     * libbinder_ndk.so often does not contain the functions we need, so making\n+     * this dependency optional, thus using dlopen/dlsym instead of linking.\n+     *\n+     * See also: https://source.android.com/docs/core/architecture/aidl/aidl-backends\n+     */\n+\n+    void *h = dlopen(\"libbinder_ndk.so\", RTLD_NOW | RTLD_LOCAL);\n+    if (h != NULL)\n+        return h;\n+\n+    av_log(NULL, AV_LOG_WARNING,\n+           \"android/binder: unable to load libbinder_ndk.so: '%s'; skipping binder threadpool init (MediaCodec likely won't work)\\n\",\n+           dlerror());\n+    return NULL;\n+}\n+\n+static void android_binder_threadpool_init(void)\n+{\n+    typedef int (*set_thread_pool_max_fn)(uint32_t);\n+    typedef void (*start_thread_pool_fn)(void);\n+\n+    set_thread_pool_max_fn set_thread_pool_max = NULL;\n+    start_thread_pool_fn start_thread_pool = NULL;\n+\n+    void *h = dlopen_libbinder_ndk();\n+    if (h == NULL)\n+        return;\n+\n+    unsigned thead_pool_size = THREAD_POOL_SIZE;\n+\n+    set_thread_pool_max =\n+        (set_thread_pool_max_fn) dlsym(h,\n+                                       \"ABinderProcess_setThreadPoolMaxThreadCount\");\n+    start_thread_pool =\n+        (start_thread_pool_fn) dlsym(h, \"ABinderProcess_startThreadPool\");\n+\n+    if (start_thread_pool == NULL) {\n+        av_log(NULL, AV_LOG_WARNING,\n+               \"android/binder: ABinderProcess_startThreadPool not found; skipping threadpool init (MediaCodec likely won't work)\\n\");\n+        return;\n+    }\n+\n+    if (set_thread_pool_max != NULL) {\n+        int ok = set_thread_pool_max(thead_pool_size);\n+        av_log(NULL, AV_LOG_DEBUG,\n+               \"android/binder: ABinderProcess_setThreadPoolMaxThreadCount(%u) => %s\\n\",\n+               thead_pool_size, ok ? \"ok\" : \"fail\");\n+    } else {\n+        av_log(NULL, AV_LOG_DEBUG,\n+               \"android/binder: ABinderProcess_setThreadPoolMaxThreadCount is unavailable; using the library default\\n\");\n+    }\n+\n+    start_thread_pool();\n+    av_log(NULL, AV_LOG_DEBUG,\n+           \"android/binder: ABinderProcess_startThreadPool() called\\n\");\n+}\n+\n+void android_binder_threadpool_init_if_required(void)\n+{\n+#if __ANDROID_API__ >= 24\n+    if (android_get_device_api_level() < 35) {\n+        // the issue with the thread pool was introduced in Android 15 (API 35)\n+        av_log(NULL, AV_LOG_DEBUG,\n+               \"android/binder: API<35, thus no need to initialize a thread pool\\n\");\n+        return;\n+    }\n+    android_binder_threadpool_init();\n+#else\n+    // android_get_device_api_level was introduced in API 24, so we cannot use it\n+    // to detect the API level in API<24. For simplicity we just assume\n+    // libbinder_ndk.so on the system running this code would have API level < 35;\n+    av_log(NULL, AV_LOG_DEBUG,\n+           \"android/binder: is built with API<24, assuming this is not Android 15+\\n\");\n+#endif\n+}\n+\n+#endif                          /* __ANDROID__ */\ndiff --git a/compat/android/binder.h b/compat/android/binder.h\nnew file mode 100644\nindex 0000000000..2b1ca53fe8\n--- /dev/null\n+++ b/compat/android/binder.h\n@@ -0,0 +1,31 @@\n+/*\n+ * Android Binder handler\n+ *\n+ * Copyright (c) 2025 Dmitrii Okunev\n+ *\n+ * This file is part of FFmpeg.\n+ *\n+ * FFmpeg is free software; you can redistribute it and/or\n+ * modify it under the terms of the GNU Lesser General Public\n+ * License as published by the Free Software Foundation; either\n+ * version 2.1 of the License, or (at your option) any later version.\n+ *\n+ * FFmpeg is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+ * Lesser General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU Lesser General Public\n+ * License along with FFmpeg; if not, write to the Free Software\n+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n+ */\n+\n+#ifndef COMPAT_ANDROID_BINDER_H\n+#define COMPAT_ANDROID_BINDER_H\n+\n+/**\n+ * Initialize Android Binder thread pool.\n+ */\n+void android_binder_threadpool_init_if_required(void);\n+\n+#endif                          // COMPAT_ANDROID_BINDER_H\ndiff --git a/configure b/configure\nindex af125bc115..098fe12f39 100755\n--- a/configure\n+++ b/configure\n@@ -7312,7 +7312,8 @@ enabled mbedtls           && { check_pkg_config mbedtls mbedtls mbedtls/x509_crt\n                                check_pkg_config mbedtls mbedtls mbedtls/ssl.h mbedtls_ssl_init ||\n                                check_lib mbedtls mbedtls/ssl.h mbedtls_ssl_init -lmbedtls -lmbedx509 -lmbedcrypto ||\n                                die \"ERROR: mbedTLS not found\"; }\n-enabled mediacodec        && { enabled jni || die \"ERROR: mediacodec requires --enable-jni\"; }\n+enabled mediacodec        && { enabled jni || die \"ERROR: mediacodec requires --enable-jni\"; } &&\n+                               add_compat android/binder.o\n enabled mmal              && { check_lib mmal interface/mmal/mmal.h mmal_port_connect -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host ||\n                                { ! enabled cross_compile &&\n                                  add_cflags -isystem/opt/vc/include/ -isystem/opt/vc/include/interface/vmcs_host/linux -isystem/opt/vc/include/interface/vcos/pthreads -fgnu89-inline &&\ndiff --git a/fftools/Makefile b/fftools/Makefile\nindex bdb44fc5ce..01b16fa8f4 100644\n--- a/fftools/Makefile\n+++ b/fftools/Makefile\n@@ -51,6 +51,7 @@ OBJS-ffprobe +=                       \\\n     fftools/textformat/tw_buffer.o    \\\n     fftools/textformat/tw_stdout.o    \\\n \n+OBJS-ffmpeg += $(COMPAT_OBJS:%=compat/%)\n OBJS-ffplay += fftools/ffplay_renderer.o\n \n define DOFFTOOL\ndiff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c\nindex 444d027c15..c2c85d46bd 100644\n--- a/fftools/ffmpeg.c\n+++ b/fftools/ffmpeg.c\n@@ -78,6 +78,9 @@\n #include \"libavdevice/avdevice.h\"\n \n #include \"cmdutils.h\"\n+#if CONFIG_MEDIACODEC\n+#include \"compat/android/binder.h\"\n+#endif\n #include \"ffmpeg.h\"\n #include \"ffmpeg_sched.h\"\n #include \"ffmpeg_utils.h\"\n@@ -1019,6 +1022,10 @@ int main(int argc, char **argv)\n         goto finish;\n     }\n \n+#if CONFIG_MEDIACODEC\n+    android_binder_threadpool_init_if_required();\n+#endif\n+\n     current_time = ti = get_benchmark_time_stamps();\n     ret = transcode(sch);\n     if (ret >= 0 && do_benchmark) {\n-- \n2.49.1\n\n"
  },
  {
    "path": "pythonforandroid/recipes/ffmpeg/patches/configure.patch",
    "content": "--- ffmpeg-8.0/configure\t2025-08-22 14:54:23.000000000 +0530\n+++ ffmpeg-8.0.mod/configure\t2026-01-03 13:40:42.264702438 +0530\n@@ -7141,7 +7141,7 @@\n enabled librtmp           && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket\n enabled librubberband     && require_pkg_config librubberband \"rubberband >= 1.8.1\" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs \"-lstdc++\"\n enabled libshaderc        && require_pkg_config spirv_compiler \"shaderc >= 2019.1\" shaderc/shaderc.h shaderc_compiler_initialize\n-enabled libshine          && require_pkg_config libshine shine shine/layer3.h shine_encode_buffer\n+enabled libshine          && require \"shine\" shine/layer3.h shine_encode_buffer -lshine -lm\n enabled libsmbclient      && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init ||\n                                require libsmbclient libsmbclient.h smbc_init -lsmbclient; }\n enabled libsnappy         && require libsnappy snappy-c.h snappy_compress -lsnappy -lstdc++\n@@ -7195,7 +7195,7 @@\n enabled libwebp           && {\n     enabled libwebp_encoder      && require_pkg_config libwebp \"libwebp >= 0.2.0\" webp/encode.h WebPGetEncoderVersion\n     enabled libwebp_anim_encoder && check_pkg_config libwebp_anim_encoder \"libwebpmux >= 0.4.0\" webp/mux.h WebPAnimEncoderOptionsInit; }\n-enabled libx264           && require_pkg_config libx264 x264 \"stdint.h x264.h\" x264_encoder_encode &&\n+enabled libx264           && require \"x264\" \"stdint.h x264.h\" x264_encoder_encode &&\n                              require_cpp_condition libx264 x264.h \"X264_BUILD >= 155\" && {\n                              [ \"$toolchain\" != \"msvc\" ] ||\n                              require_cpp_condition libx264 x264.h \"X264_BUILD >= 158\"; } &&\n@@ -7258,13 +7258,7 @@\n enabled omx               && require_headers OMX_Core.h && \\\n     warn \"The OpenMAX encoders are deprecated and will be removed in future versions\"\n \n-enabled openssl           && { { check_pkg_config openssl \"openssl >= 3.0.0\" openssl/ssl.h OPENSSL_init_ssl &&\n-                                 { enabled gplv3 || ! enabled gpl || enabled nonfree || die \"ERROR: OpenSSL >=3.0.0 requires --enable-version3\"; }; } ||\n-                               { enabled gpl && ! enabled nonfree && die \"ERROR: OpenSSL <3.0.0 is incompatible with the gpl\"; } ||\n-                               check_pkg_config openssl \"openssl >= 1.1.0\" openssl/ssl.h OPENSSL_init_ssl ||\n-                               check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto ||\n-                               check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto -lws2_32 -lgdi32 ||\n-                               die \"ERROR: openssl (>= 1.1.0) not found\"; }\n+enabled openssl           && true \n enabled pocketsphinx      && require_pkg_config pocketsphinx pocketsphinx pocketsphinx/pocketsphinx.h ps_init\n enabled rkmpp             && { require_pkg_config rkmpp rockchip_mpp  rockchip/rk_mpi.h mpp_create &&\n                                require_pkg_config rockchip_mpp \"rockchip_mpp >= 1.3.7\" rockchip/rk_mpi.h mpp_create &&\n@@ -7273,14 +7267,6 @@\n                              }\n enabled vapoursynth       && require_headers \"vapoursynth/VSScript4.h vapoursynth/VapourSynth4.h\"\n \n-enabled openssl            && {\n-    enabled whip_muxer && {\n-        $pkg_config --exists --print-errors \"openssl >= 1.0.1k\" ||\n-        require_pkg_config openssl \"openssl >= 1.0.1k\" openssl/ssl.h SSL_library_init ||\n-        require_pkg_config openssl \"openssl >= 1.0.1k\" openssl/ssl.h OPENSSL_init_ssl\n-    }\n-}\n-\n \n if enabled gcrypt; then\n     GCRYPT_CONFIG=\"${cross_prefix}libgcrypt-config\"\n"
  },
  {
    "path": "pythonforandroid/recipes/ffpyplayer/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe, Recipe\nfrom os.path import join\n\n\nclass FFPyPlayerRecipe(PyProjectRecipe):\n    version = 'v4.5.1'\n    url = 'https://github.com/matham/ffpyplayer/archive/{version}.zip'\n    depends = ['python3', 'sdl2', 'ffmpeg']\n    patches = [\"setup.py.patch\"]\n    opt_depends = ['openssl', 'ffpyplayer_codecs']\n\n    def get_recipe_env(self, arch, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch)\n\n        build_dir = Recipe.get_recipe('ffmpeg', self.ctx).get_build_dir(arch.arch)\n        env[\"FFMPEG_INCLUDE_DIR\"] = join(build_dir, \"include\")\n        env[\"FFMPEG_LIB_DIR\"] = join(build_dir, \"lib\")\n\n        env[\"SDL_INCLUDE_DIR\"] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include')\n        env[\"SDL_LIB_DIR\"] = join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)\n\n        env[\"USE_SDL2_MIXER\"] = '1'\n\n        # ffpyplayer does not allow to pass more than one include dir for sdl2_mixer (and ATM is\n        # not needed), so we only pass the first one.\n        sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)\n        env[\"SDL2_MIXER_INCLUDE_DIR\"] = sdl2_mixer_recipe.get_include_dirs(arch)[0]\n\n        # NDKPLATFORM and LIBLINK are our switches for detecting Android platform, so can't be empty\n        # FIXME: We may want to introduce a cleaner approach to this?\n        env['NDKPLATFORM'] = \"NOTNONE\"\n        env['LIBLINK'] = 'NOTNONE'\n\n        # ffmpeg recipe enables GPL components only if ffpyplayer_codecs recipe used.\n        # Therefore we need to disable libpostproc if skipped.\n        if 'ffpyplayer_codecs' not in self.ctx.recipe_build_order:\n            env[\"CONFIG_POSTPROC\"] = '0'\n\n        return env\n\n\nrecipe = FFPyPlayerRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/ffpyplayer/setup.py.patch",
    "content": "--- ffpyplayer/setup.py\t2024-06-02 11:10:49.691183467 +0530\n+++ ffpyplayer.mod/setup.py\t2024-06-02 11:20:16.220966873 +0530\n@@ -27,12 +27,6 @@\n # This sets whether or not Cython gets added to setup_requires.\n declare_cython = False\n \n-if platform in ('ios', 'android'):\n-    # NEVER use or declare cython on these platforms\n-    print('Not using cython on %s' % platform)\n-    can_use_cython = False\n-else:\n-    declare_cython = True\n \n src_path = build_path = dirname(__file__)\n print(f'Source/build path: {src_path}')\n"
  },
  {
    "path": "pythonforandroid/recipes/ffpyplayer_codecs/__init__.py",
    "content": "from pythonforandroid.toolchain import Recipe\n\n\nclass FFPyPlayerCodecsRecipe(Recipe):\n    depends = ['libx264', 'libshine', 'libvpx']\n\n    def build_arch(self, arch):\n        pass\n\n\nrecipe = FFPyPlayerCodecsRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/flask/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass FlaskRecipe(PyProjectRecipe):\n    version = '3.1.1'\n    url = 'https://github.com/pallets/flask/archive/{version}.zip'\n    python_depends = ['jinja2', 'werkzeug', 'markupsafe', 'itsdangerous', 'click', 'blinker']\n\n\nrecipe = FlaskRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/fontconfig/__init__.py",
    "content": "from os.path import join\n\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nimport sh\n\n\nclass FontconfigRecipe(BootstrapNDKRecipe):\n    version = \"really_old\"\n    url = 'https://github.com/vault/fontconfig/archive/androidbuild.zip'\n    depends = ['sdl2']\n    dir_name = 'fontconfig'\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_jni_dir()):\n            shprint(\n                sh.Command(join(self.ctx.ndk_dir, \"ndk-build\")),\n                \"V=1\",\n                \"APP_ALLOW_MISSING_DEPS=true\",\n                \"fontconfig\",\n                _env=env,\n            )\n\n\nrecipe = FontconfigRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/fortran/__init__.py",
    "content": "import os\nimport subprocess\nimport shutil\nimport sh\nfrom pathlib import Path\nfrom os.path import join\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.recommendations import read_ndk_version\nfrom pythonforandroid.logger import info, shprint, info_main\nfrom pythonforandroid.util import ensure_dir\nimport hashlib\n\nFLANG_FILES = {\n    \"package-flang-aarch64.tar.bz2\": \"bf01399513e3b435224d9a9f656b72a0965a23fdd8c3c26af0f7c32f2a5f3403\",\n    \"package-flang-host.tar.bz2\": \"3ea2c0e8125ededddf9b3f23c767b8e37816e140ac934c76ace19a168fefdf83\",\n    \"package-flang-x86_64.tar.bz2\": \"afe7e391355c71e7b0c8ee71a3002e83e2e524ad61810238815facf3030be6e6\",\n    \"package-install.tar.bz2\": \"169b75f6125dc7b95e1d30416147a05d135da6cbe9cc8432d48f5b8633ac38db\",\n}\n\n\nclass GFortranRecipe(Recipe):\n    # flang support in NDK by @termux (on github)\n    name = \"fortran\"\n    toolchain_ver = 0\n    url = \"https://github.com/termux/ndk-toolchain-clang-with-flang/releases/download/\"\n\n    def match_sha256(self, file_path, expected_hash):\n        sha256 = hashlib.sha256()\n        with open(file_path, \"rb\") as f:\n            for chunk in iter(lambda: f.read(8192), b\"\"):\n                sha256.update(chunk)\n        file_hash = sha256.hexdigest()\n        return file_hash == expected_hash\n\n    @property\n    def ndk_version(self):\n        ndk_version = read_ndk_version(self.ctx.ndk_dir)\n        minor_to_letter = {0: \"\"}\n        minor_to_letter.update(\n            {n + 1: chr(i) for n, i in enumerate(range(ord(\"b\"), ord(\"b\") + 25))}\n        )\n        return f\"{ndk_version.major}{minor_to_letter[ndk_version.minor]}\"\n\n    def get_cache_dir(self):\n        dir_name = self.get_dir_name()\n        return join(self.ctx.build_dir, \"other_builds\", dir_name)\n\n    def get_fortran_dir(self):\n        toolchain_name = f\"android-r{self.ndk_version}-api-{self.ctx.ndk_api}\"\n        return join(\n            self.get_cache_dir(), f\"{toolchain_name}-flang-v{self.toolchain_ver}\"\n        )\n\n    def get_incomplete_files(self):\n        incomplete_files = []\n        cache_dir = self.get_cache_dir()\n        for file, sha256sum in FLANG_FILES.items():\n            _file = join(cache_dir, file)\n            if not (os.path.exists(_file) and self.match_sha256(_file, sha256sum)):\n                incomplete_files.append(file)\n        return incomplete_files\n\n    def download_if_necessary(self):\n        assert self.ndk_version == \"28c\"\n        if len(self.get_incomplete_files()) == 0:\n            return\n        self.download()\n\n    def download(self):\n        cache_dir = self.get_cache_dir()\n        ensure_dir(cache_dir)\n        for file in self.get_incomplete_files():\n            _file = join(cache_dir, file)\n            if os.path.exists(_file):\n                os.remove(_file)\n            self.download_file(f\"{self.url}r{join(self.ndk_version, file)}\", _file)\n\n    def extract_tar(self, file_path: Path, dest: Path, strip=1):\n        shprint(\n            sh.tar,\n            \"xf\",\n            str(file_path),\n            \"--strip-components\",\n            str(strip),\n            \"-C\",\n            str(dest) if dest else \".\",\n        )\n\n    def create_flang_wrapper(self, path: Path, target: str):\n        script = f\"\"\"#!/usr/bin/env bash\nif [ \"$1\" != \"-cpp\" ] && [ \"$1\" != \"-fc1\" ]; then\n  `dirname $0`/flang-new --target={target}{self.ctx.ndk_api} -D__ANDROID_API__={self.ctx.ndk_api} \"$@\"\nelse\n  `dirname $0`/flang-new \"$@\"\nfi\n\"\"\"\n        path.write_text(script)\n        path.chmod(0o755)\n\n    def unpack(self, arch):\n        info_main(\"Unpacking fortran\")\n\n        flang_folder = self.get_fortran_dir()\n        if os.path.exists(flang_folder):\n            info(\"{} is already unpacked, skipping\".format(self.name))\n            return\n\n        toolchain_path = Path(\n            join(self.ctx.ndk_dir, \"toolchains/llvm/prebuilt/linux-x86_64\")\n        )\n        cache_dir = Path(os.path.abspath(self.get_cache_dir()))\n\n        # clean tmp folder\n        tmp_folder = Path(os.path.abspath(f\"{flang_folder}-tmp\"))\n        shutil.rmtree(tmp_folder, ignore_errors=True)\n        tmp_folder.mkdir(parents=True)\n        os.chdir(tmp_folder)\n\n        self.extract_tar(cache_dir / \"package-install.tar.bz2\", None, strip=4)\n        self.extract_tar(cache_dir / \"package-flang-host.tar.bz2\", None)\n\n        sysroot_path = tmp_folder / \"sysroot\"\n        shutil.copytree(toolchain_path / \"sysroot\", sysroot_path)\n\n        self.extract_tar(\n            cache_dir / \"package-flang-aarch64.tar.bz2\",\n            sysroot_path / \"usr/lib/aarch64-linux-android\",\n        )\n        self.extract_tar(\n            cache_dir / \"package-flang-x86_64.tar.bz2\",\n            sysroot_path / \"usr/lib/x86_64-linux-android\",\n        )\n\n        # Fix lib/clang paths\n        version_output = subprocess.check_output(\n            [str(tmp_folder / \"bin/clang\"), \"--version\"], text=True\n        )\n        clang_version = next(\n            (line for line in version_output.splitlines() if \"clang version\" in line),\n            \"\",\n        )\n        major_ver = clang_version.split(\"clang version \")[-1].split(\".\")[0]\n\n        lib_path = tmp_folder / f\"lib/clang/{major_ver}/lib\"\n        src_lib_path = toolchain_path / f\"lib/clang/{major_ver}/lib\"\n        shutil.rmtree(lib_path, ignore_errors=True)\n        lib_path.mkdir(parents=True)\n\n        for item in src_lib_path.iterdir():\n            shprint(sh.cp, \"-r\", str(item), str(lib_path))\n\n        # Create flang wrappers\n        targets = [\n            \"aarch64-linux-android\",\n            \"armv7a-linux-androideabi\",\n            \"i686-linux-android\",\n            \"x86_64-linux-android\",\n        ]\n\n        for target in targets:\n            wrapper_path = tmp_folder / f\"bin/{target}-flang\"\n            self.create_flang_wrapper(wrapper_path, target)\n            shutil.copy(\n                wrapper_path, tmp_folder / f\"bin/{target}{self.ctx.ndk_api}-flang\"\n            )\n\n        tmp_folder.rename(flang_folder)\n\n    @property\n    def bin_path(self):\n        return f\"{self.get_fortran_dir()}/bin\"\n\n    def get_host_platform(self, arch):\n        return {\n            \"arm64-v8a\": \"aarch64-linux-android\",\n            \"armeabi-v7a\": \"armv7a-linux-androideabi\",\n            \"x86_64\": \"x86_64-linux-android\",\n            \"x86\": \"i686-linux-android\",\n        }[arch]\n\n    def get_fortran_bin(self, arch):\n        return join(self.bin_path, f\"{self.get_host_platform(arch)}-flang\")\n\n    def get_fortran_flags(self, arch):\n        return f\"--target={self.get_host_platform(arch)}{self.ctx.ndk_api} -D__ANDROID_API__={self.ctx.ndk_api}\"\n\n\nrecipe = GFortranRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/freetype/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint, info\nfrom pythonforandroid.util import current_directory\nfrom os.path import join, exists\nfrom multiprocessing import cpu_count\nimport sh\n\n\nclass FreetypeRecipe(Recipe):\n    \"\"\"The freetype library it's special, because has cyclic dependencies with\n    harfbuzz library, so freetype can be build with harfbuzz support, and\n    harfbuzz can be build with freetype support. This complicates the build of\n    both recipes because in order to get the full set we need to compile those\n    recipes several times:\n        - build freetype without harfbuzz\n        - build harfbuzz with freetype\n        - build freetype with harfbuzz support\n\n    .. note::\n        To build freetype with harfbuzz support you must add `harfbuzz` to your\n        requirements, otherwise freetype will be build without harfbuzz\n\n    .. seealso::\n        https://sourceforge.net/projects/freetype/files/freetype2/2.5.3/\n    \"\"\"\n\n    version = '2.14.1'\n    url = 'https://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.gz'  # noqa\n    built_libraries = {'libfreetype.so': 'objs/.libs'}\n\n    def get_recipe_env(self, arch=None, with_harfbuzz=False):\n        env = super().get_recipe_env(arch)\n        if with_harfbuzz:\n            harfbuzz_build = self.get_recipe(\n                'harfbuzz', self.ctx\n            ).get_build_dir(arch.arch)\n            freetype_install = join(self.get_build_dir(arch.arch), 'install')\n\n            env['HARFBUZZ_CFLAGS'] = '-I{harfbuzz} -I{harfbuzz}/src'.format(\n                harfbuzz=harfbuzz_build\n            )\n            env['HARFBUZZ_LIBS'] = (\n                '-L{freetype}/lib -lfreetype '\n                '-L{harfbuzz}/src/.libs -lharfbuzz'.format(\n                    freetype=freetype_install, harfbuzz=harfbuzz_build\n                )\n            )\n\n        # android's zlib support\n        zlib_lib_path = arch.ndk_lib_dir_versioned\n        zlib_includes = self.ctx.ndk.sysroot_include_dir\n\n        def add_flag_if_not_added(flag, env_key):\n            if flag not in env[env_key]:\n                env[env_key] += flag\n\n        add_flag_if_not_added(' -I' + zlib_includes, 'CFLAGS')\n        add_flag_if_not_added(' -L' + zlib_lib_path, 'LDFLAGS')\n        add_flag_if_not_added(' -lz', 'LDLIBS')\n\n        return env\n\n    def build_arch(self, arch, with_harfbuzz=False):\n        env = self.get_recipe_env(arch, with_harfbuzz=with_harfbuzz)\n\n        harfbuzz_in_recipes = 'harfbuzz' in self.ctx.recipe_build_order\n        prefix_path = self.get_build_dir(arch.arch)\n        if harfbuzz_in_recipes and not with_harfbuzz:\n            # This is the first time we build freetype and we modify `prefix`,\n            # because we will install the compiled library so later we can\n            # build harfbuzz (with freetype support) using this freetype\n            # installation\n            prefix_path = join(prefix_path, 'install')\n\n        # Configure freetype library\n        config_args = {\n            '--host={}'.format(arch.command_prefix),\n            '--prefix={}'.format(prefix_path),\n            '--without-bzip2',\n            '--without-brotli',\n            '--with-png=no',\n        }\n        if not harfbuzz_in_recipes:\n            info('Build freetype (without harfbuzz)')\n            config_args = config_args.union(\n                {'--disable-static',\n                 '--enable-shared',\n                 '--with-harfbuzz=no',\n                 '--with-zlib=yes',\n                 }\n            )\n        elif not with_harfbuzz:\n            info('Build freetype for First time (without harfbuzz)')\n            # This time we will build our freetype library as static because we\n            # want that the harfbuzz library to have the necessary freetype\n            # symbols/functions, so we avoid to have two freetype shared\n            # libraries which will be confusing and harder to link with them\n            config_args = config_args.union(\n                {'--disable-shared', '--with-harfbuzz=no', '--with-zlib=no'}\n            )\n        else:\n            info('Build freetype for Second time (with harfbuzz)')\n            config_args = config_args.union(\n                {'--disable-static',\n                 '--enable-shared',\n                 '--with-harfbuzz=yes',\n                 '--with-zlib=yes',\n                 }\n            )\n        info('Configure args are:\\n\\t-{}'.format('\\n\\t-'.join(config_args)))\n\n        # Build freetype library\n        with current_directory(self.get_build_dir(arch.arch)):\n            configure = sh.Command('./configure')\n            shprint(configure, *config_args, _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n\n            if not with_harfbuzz and harfbuzz_in_recipes:\n                info('Installing freetype (first time build without harfbuzz)')\n                # First build, install the compiled lib, and clean build env\n                shprint(sh.make, 'install', _env=env)\n                shprint(sh.make, 'distclean', _env=env)\n\n    def install_libraries(self, arch):\n        # This library it's special because the first time we built it may not\n        # generate the expected library, because it can depend on harfbuzz, so\n        # we will make sure to only install it when the library exists\n        if not exists(list(self.get_libraries(arch))[0]):\n            return\n        self.install_libs(arch, *self.get_libraries(arch))\n\n\nrecipe = FreetypeRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/freetype-py/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass FreetypePyRecipe(PyProjectRecipe):\n    version = '2.5.1'\n    url = 'https://github.com/rougier/freetype-py/archive/refs/tags/v{version}.tar.gz'\n    patches = [\"fix_import.patch\"]\n    depends = ['freetype']\n    site_packages_name = 'freetype'\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env[\"SETUPTOOLS_SCM_PRETEND_VERSION_FOR_freetype_py\"] = self.version\n        return env\n\n\nrecipe = FreetypePyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/freetype-py/fix_import.patch",
    "content": "diff '--color=auto' -uNr freetype-py-2.5.1/MANIFEST.in freetype-py-2.5.1.mod/MANIFEST.in\n--- freetype-py-2.5.1/MANIFEST.in\t2024-08-29 23:12:30.000000000 +0530\n+++ freetype-py-2.5.1.mod/MANIFEST.in\t2025-10-26 11:54:45.052025521 +0530\n@@ -9,3 +9,4 @@\n include LICENSE.txt\n include README.rst\n include setup-build-freetype.py\n+recursive-include _custom_build *.py\n"
  },
  {
    "path": "pythonforandroid/recipes/genericndkbuild/__init__.py",
    "content": "from os.path import join\n\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nimport sh\n\n\nclass GenericNDKBuildRecipe(BootstrapNDKRecipe):\n    version = None\n    url = None\n\n    depends = ['python3']\n    conflicts = ['sdl2', 'sdl3']\n\n    def should_build(self, arch):\n        return True\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=True):\n        env = super().get_recipe_env(\n            arch=arch, with_flags_in_cc=with_flags_in_cc,\n            with_python=with_python,\n        )\n        env['APP_ALLOW_MISSING_DEPS'] = 'true'\n        # required for Qt bootstrap\n        env['PREFERRED_ABI'] = arch.arch\n        return env\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_jni_dir()):\n            shprint(sh.Command(join(self.ctx.ndk_dir, \"ndk-build\")), \"V=1\", _env=env)\n\n\nrecipe = GenericNDKBuildRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/gevent/__init__.py",
    "content": "\"\"\"\nNote that this recipe doesn't yet build on macOS, the error is:\n```\ndeps/libuv/src/unix/bsd-ifaddrs.c:31:10: fatal error: 'net/if_dl.h' file not found\n#include <net/if_dl.h>\n         ^~~~~~~~~~~~~\n1 error generated.\nerror: command '/Users/runner/.android/android-ndk/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang' failed with exit code 1\n```\n\"\"\"\nimport re\nfrom pythonforandroid.logger import info\nfrom pythonforandroid.recipe import PyProjectRecipe\n\n\nclass GeventRecipe(PyProjectRecipe):\n    version = '24.11.1'\n    url = 'https://github.com/gevent/gevent/archive/refs/tags/{version}.tar.gz'\n    depends = ['librt', 'setuptools']\n    patches = [\"cross_compiling.patch\"]\n\n    def get_recipe_env(self, arch, **kwargs):\n        \"\"\"\n        - Moves all -I<inc> -D<macro> from CFLAGS to CPPFLAGS environment.\n        - Moves all -l<lib> from LDFLAGS to LIBS environment.\n        - Copies all -l<lib> from LDLIBS to LIBS environment.\n        - Fixes linker name (use cross compiler) and flags (appends LIBS).\n        - Feds the command prefix for the configure --host flag.\n        \"\"\"\n        env = super().get_recipe_env(arch, **kwargs)\n        # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS\n        regex = re.compile(r'(?:\\s|^)-[DI][\\S]+')\n        env['CPPFLAGS'] = ''.join(re.findall(regex, env['CFLAGS'])).strip()\n        env['CFLAGS'] = re.sub(regex, '', env['CFLAGS'])\n        info('Moved \"{}\" from CFLAGS to CPPFLAGS.'.format(env['CPPFLAGS']))\n        # LDFLAGS may only be used to specify linker flags, for libraries use LIBS\n        regex = re.compile(r'(?:\\s|^)-l[\\w\\.]+')\n        env['LIBS'] = ''.join(re.findall(regex, env['LDFLAGS'])).strip()\n        env['LIBS'] += ' {}'.format(''.join(re.findall(regex, env['LDLIBS'])).strip())\n        env['LDFLAGS'] = re.sub(regex, '', env['LDFLAGS'])\n        info('Moved \"{}\" from LDFLAGS to LIBS.'.format(env['LIBS']))\n        # used with the `./configure --host` flag for cross compiling, refs #2805\n        env['COMMAND_PREFIX'] = arch.command_prefix\n        return env\n\n\nrecipe = GeventRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/gevent/cross_compiling.patch",
    "content": "diff --git a/_setupares.py b/_setupares.py\nindex c42fe369..cd8854df 100644\n--- a/_setupares.py\n+++ b/_setupares.py\n@@ -42,7 +42,7 @@ cflags = ('CFLAGS=\"%s\"' % (cflags,)) if cflags else ''\n ares_configure_command = ' '.join([\n     \"(cd \", quoted_dep_abspath('c-ares'),\n     \" && if [ -r include/ares_build.h ]; then cp include/ares_build.h include/ares_build.h.orig; fi \",\n-    \" && sh ./configure --disable-dependency-tracking --disable-tests -C \" + cflags,\n+    \" && sh ./configure --host={} --disable-dependency-tracking --disable-tests -C \".format(os.environ['COMMAND_PREFIX']) + cflags,\n     \" && cp src/lib/ares_config.h include/ares_build.h \\\"$OLDPWD\\\" \",\n     \" && cat include/ares_build.h \",\n     \" && if [ -r include/ares_build.h.orig ]; then mv include/ares_build.h.orig include/ares_build.h; fi)\",\ndiff --git a/_setuplibev.py b/_setuplibev.py\nindex f05c2fe9..32f9bd81 100644\n--- a/_setuplibev.py\n+++ b/_setuplibev.py\n@@ -28,7 +28,7 @@ LIBEV_EMBED = should_embed('libev')\n # Configure libev in place\n libev_configure_command = ' '.join([\n     \"(cd \", quoted_dep_abspath('libev'),\n-    \" && sh ./configure -C > configure-output.txt\",\n+    \" && sh ./configure --host={} -C > configure-output.txt\".format(os.environ['COMMAND_PREFIX']),\n     \")\",\n ])\n \n"
  },
  {
    "path": "pythonforandroid/recipes/greenlet/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass GreenletRecipe(PyProjectRecipe):\n    version = '3.1.1'\n    url = 'https://pypi.python.org/packages/source/g/greenlet/greenlet-{version}.tar.gz'\n    depends = ['setuptools']\n    call_hostpython_via_targetpython = False\n\n\nrecipe = GreenletRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/groestlcoin_hash/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\n\n\nclass GroestlcoinHashRecipe(CythonRecipe):\n    version = '1.0.3'\n    url = 'https://github.com/Groestlcoin/groestlcoin-hash-python/archive/{version}.tar.gz'\n    depends = ['setuptools']\n    cythonize = False\n\n\nrecipe = GroestlcoinHashRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/grpcio/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe, Recipe\n\n\nclass GrpcioRecipe(PyProjectRecipe):\n    version = '1.64.0'\n    url = 'https://files.pythonhosted.org/packages/source/g/grpcio/grpcio-{version}.tar.gz'\n    depends = [\"setuptools\", \"librt\", \"libpthread\"]\n    patches = [\n        \"comment-getserverbyport-r-args.patch\",\n        \"remove-android-log-write.patch\",\n        \"use-ndk-zlib-and-openssl-recipe-include.patch\"\n    ]\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env[\"NDKPLATFORM\"] = \"NOTNONE\"\n        env[\"GRPC_PYTHON_BUILD_SYSTEM_OPENSSL\"] = \"1\"\n        env[\"GRPC_PYTHON_BUILD_SYSTEM_ZLIB\"] = \"1\"\n        env[\"ZLIB_INCLUDE\"] = self.ctx.ndk.sysroot_include_dir\n        # replace -I with a space\n        openssl_recipe = Recipe.get_recipe('openssl', self.ctx)\n        env[\"SSL_INCLUDE\"] = openssl_recipe.include_flags(arch).strip().replace(\"-I\", \"\")\n        env[\"CFLAGS\"] += \" -U__ANDROID_API__\"\n        env[\"CFLAGS\"] += \" -D__ANDROID_API__={}\".format(self.ctx.ndk_api)\n        # turn off c++11 warning error of \"invalid suffix on literal\"\n        env[\"CFLAGS\"] += \" -Wno-reserved-user-defined-literal\"\n        env[\"PLATFORM\"] = \"android\"\n        env[\"LDFLAGS\"] += \" -llog -landroid\"\n        env[\"LDFLAGS\"] += openssl_recipe.link_flags(arch)\n        return env\n\n\nrecipe = GrpcioRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/grpcio/comment-getserverbyport-r-args.patch",
    "content": "diff --git a/third_party/cares/config_darwin/ares_config.h b/third_party/cares/config_darwin/ares_config.h\n--- a/third_party/cares/config_darwin/ares_config.h\t2024-07-16 20:46:22.000000000 +0100\n+++ b/third_party/cares/config_darwin/ares_config.h\t2024-07-29 00:18:30.096755745 +0100\n@@ -43,7 +43,7 @@\n #define GETNAMEINFO_TYPE_ARG7 int\n \n /* Specifies the number of arguments to getservbyport_r */\n-#define GETSERVBYPORT_R_ARGS \n+/* #define GETSERVBYPORT_R_ARGS */\n \n /* Define to 1 if you have AF_INET6. */\n #define HAVE_AF_INET6\ndiff --git a/third_party/cares/config_linux/ares_config.h b/third_party/cares/config_linux/ares_config.h\n--- a/third_party/cares/config_linux/ares_config.h\t2024-07-16 20:46:22.000000000 +0100\n+++ b/third_party/cares/config_linux/ares_config.h\t2024-07-29 00:19:39.479166654 +0100\n@@ -43,7 +43,7 @@\n #define GETNAMEINFO_TYPE_ARG7 int\n \n /* Specifies the number of arguments to getservbyport_r */\n-#define GETSERVBYPORT_R_ARGS 6\n+/* #define GETSERVBYPORT_R_ARGS 6 */\n \n /* Define to 1 if you have AF_INET6. */\n #define HAVE_AF_INET6\n@@ -121,7 +121,7 @@\n #define HAVE_GETNAMEINFO\n \n /* Define to 1 if you have the getservbyport_r function. */\n-#define HAVE_GETSERVBYPORT_R\n+/* #define HAVE_GETSERVBYPORT_R */\n \n /* Define to 1 if you have the `gettimeofday' function. */\n #define HAVE_GETTIMEOFDAY"
  },
  {
    "path": "pythonforandroid/recipes/grpcio/remove-android-log-write.patch",
    "content": "Index: log.cc\nIDEA additional info:\nSubsystem: com.intellij.openapi.diff.impl.patch.CharsetEP\n<+>UTF-8\n===================================================================\ndiff --git a/src/core/lib/gpr/android/log.cc b/src/core/lib/gpr/android/log.cc\n--- a/src/core/lib/gpr/android/log.cc\n+++ b/src/core/lib/gpr/android/log.cc\t(date 1716778822204)\n@@ -30,18 +30,6 @@\n \n #include \"src/core/lib/gprpp/crash.h\"\n \n-static android_LogPriority severity_to_log_priority(gpr_log_severity severity) {\n-  switch (severity) {\n-    case GPR_LOG_SEVERITY_DEBUG:\n-      return ANDROID_LOG_DEBUG;\n-    case GPR_LOG_SEVERITY_INFO:\n-      return ANDROID_LOG_INFO;\n-    case GPR_LOG_SEVERITY_ERROR:\n-      return ANDROID_LOG_ERROR;\n-  }\n-  return ANDROID_LOG_DEFAULT;\n-}\n-\n void gpr_log(const char* file, int line, gpr_log_severity severity,\n              const char* format, ...) {\n   // Avoid message construction if gpr_log_message won't log\n@@ -70,8 +58,6 @@\n \n   asprintf(&output, \"%s:%d] %s\", display_file, args->line, args->message);\n \n-  __android_log_write(severity_to_log_priority(args->severity), \"GRPC\", output);\n-\n   // allocated by asprintf => use free, not gpr_free\n   free(output);\n }\n"
  },
  {
    "path": "pythonforandroid/recipes/grpcio/use-ndk-zlib-and-openssl-recipe-include.patch",
    "content": "--- a/setup.py\t2024-05-31 11:20:56.824695569 +0100\n+++ b/setup.py\t2024-05-31 23:13:40.324392463 +0100\n@@ -299,11 +299,11 @@\n         lambda x: \"third_party/boringssl\" not in x, CORE_C_FILES\n     )\n     CORE_C_FILES = filter(lambda x: \"src/boringssl\" not in x, CORE_C_FILES)\n-    SSL_INCLUDE = (os.path.join(\"/usr\", \"include\", \"openssl\"),)\n+    SSL_INCLUDE = tuple(os.environ[\"SSL_INCLUDE\"].split(\" \"))\n \n if BUILD_WITH_SYSTEM_ZLIB:\n     CORE_C_FILES = filter(lambda x: \"third_party/zlib\" not in x, CORE_C_FILES)\n-    ZLIB_INCLUDE = (os.path.join(\"/usr\", \"include\"),)\n+    ZLIB_INCLUDE = tuple(os.environ[\"ZLIB_INCLUDE\"].split(\" \"))\n \n if BUILD_WITH_SYSTEM_CARES:\n     CORE_C_FILES = filter(lambda x: \"third_party/cares\" not in x, CORE_C_FILES)\n"
  },
  {
    "path": "pythonforandroid/recipes/harfbuzz/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom multiprocessing import cpu_count\nfrom os.path import join\nimport sh\n\n\nclass HarfbuzzRecipe(Recipe):\n    \"\"\"The harfbuzz library it's special, because has cyclic dependencies with\n    freetype library, so freetype can be build with harfbuzz support, and\n    harfbuzz can be build with freetype support. This complicates the build of\n    both recipes because in order to get the full set we need to compile those\n    recipes several times:\n        - build freetype without harfbuzz\n        - build harfbuzz with freetype\n        - build freetype with harfbuzz support\n\n    .. seealso::\n        https://sourceforge.net/projects/freetype/files/freetype2/2.5.3/\n    \"\"\"\n\n    version = '2.6.4'\n    url = 'http://www.freedesktop.org/software/harfbuzz/release/harfbuzz-{version}.tar.xz'  # noqa\n    opt_depends = ['freetype']\n    built_libraries = {'libharfbuzz.so': 'src/.libs'}\n\n    def get_recipe_env(self, arch=None):\n        env = super().get_recipe_env(arch)\n        if 'freetype' in self.ctx.recipe_build_order:\n            freetype = self.get_recipe('freetype', self.ctx)\n            freetype_install = join(\n                freetype.get_build_dir(arch.arch), 'install'\n            )\n            # Explicitly tell harfbuzz's configure script that we want to\n            # use our freetype library or it won't be correctly detected\n            env['FREETYPE_CFLAGS'] = '-I{}/include/freetype2'.format(\n                freetype_install\n            )\n            env['FREETYPE_LIBS'] = ' '.join(\n                ['-L{}/lib'.format(freetype_install), '-lfreetype']\n            )\n        return env\n\n    def build_arch(self, arch):\n\n        env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            configure = sh.Command('./configure')\n            shprint(\n                configure,\n                '--host={}'.format(arch.command_prefix),\n                '--prefix={}'.format(self.get_build_dir(arch.arch)),\n                '--with-freetype={}'.format(\n                    'yes'\n                    if 'freetype' in self.ctx.recipe_build_order\n                    else 'no'\n                ),\n                '--with-icu=no',\n                '--with-cairo=no',\n                '--with-fontconfig=no',\n                '--with-glib=no',\n                _env=env,\n            )\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n\n        if 'freetype' in self.ctx.recipe_build_order:\n            # Rebuild/install freetype with harfbuzz support\n            freetype = self.get_recipe('freetype', self.ctx)\n            freetype.build_arch(arch, with_harfbuzz=True)\n            freetype.install_libraries(arch)\n\n\nrecipe = HarfbuzzRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/hostpython3/__init__.py",
    "content": "import sh\nimport os\n\nfrom multiprocessing import cpu_count\nfrom pathlib import Path\nfrom os.path import join\n\nfrom packaging.version import Version\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import (\n    BuildInterruptingException,\n    current_directory,\n    ensure_dir,\n)\nfrom pythonforandroid.prerequisites import OpenSSLPrerequisite\n\nHOSTPYTHON_VERSION_UNSET_MESSAGE = (\n    'The hostpython recipe must have set version'\n)\n\nSETUP_DIST_NOT_FIND_MESSAGE = (\n    'Could not find Setup.dist or Setup in Python build'\n)\n\n\nclass HostPython3Recipe(Recipe):\n    '''\n    The hostpython3's recipe.\n\n    .. versionchanged:: 2019.10.06.post0\n        Refactored from deleted class ``python.HostPythonRecipe`` into here.\n\n    .. versionchanged:: 0.6.0\n        Refactored into  the new class\n        :class:`~pythonforandroid.python.HostPythonRecipe`\n    '''\n\n    version = '3.14.2'\n\n    url = 'https://github.com/python/cpython/archive/refs/tags/v{version}.tar.gz'\n    '''The default url to download our host python recipe. This url will\n    change depending on the python version set in attribute :attr:`version`.'''\n\n    build_subdir = 'native-build'\n    '''Specify the sub build directory for the hostpython3 recipe. Defaults\n    to ``native-build``.'''\n\n    patches = [\"fix_ensurepip.patch\"]\n\n    @property\n    def _exe_name(self):\n        '''\n        Returns the name of the python executable depending on the version.\n        '''\n        if not self.version:\n            raise BuildInterruptingException(HOSTPYTHON_VERSION_UNSET_MESSAGE)\n        return f'python{self.version.split(\".\")[0]}'\n\n    @property\n    def python_exe(self):\n        '''Returns the full path of the hostpython executable.'''\n        return join(self.get_path_to_python(), self._exe_name)\n\n    def get_recipe_env(self, arch=None):\n        env = os.environ.copy()\n        openssl_prereq = OpenSSLPrerequisite()\n        if env.get(\"PKG_CONFIG_PATH\", \"\"):\n            env[\"PKG_CONFIG_PATH\"] = os.pathsep.join(\n                [openssl_prereq.pkg_config_location, env[\"PKG_CONFIG_PATH\"]]\n            )\n        else:\n            env[\"PKG_CONFIG_PATH\"] = openssl_prereq.pkg_config_location\n        return env\n\n    def should_build(self, arch):\n        if Path(self.python_exe).exists():\n            # no need to build, but we must set hostpython for our Context\n            self.ctx.hostpython = self.python_exe\n            return False\n        return True\n\n    def get_build_container_dir(self, arch=None):\n        choices = self.check_recipe_choices()\n        dir_name = '-'.join([self.name] + choices)\n        return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop')\n\n    def get_build_dir(self, arch=None):\n        '''\n        .. note:: Unlike other recipes, the hostpython build dir doesn't\n            depend on the target arch\n        '''\n        return join(self.get_build_container_dir(), self.name)\n\n    def get_path_to_python(self):\n        return join(self.get_build_dir(), self.build_subdir)\n\n    @property\n    def site_root(self):\n        return join(self.get_path_to_python(), \"root\")\n\n    @property\n    def site_bin(self):\n        return join(self.site_root, self.site_dir, \"bin\")\n\n    @property\n    def local_bin(self):\n        return join(self.site_root, \"usr/local/bin/\")\n\n    @property\n    def site_dir(self):\n        p_version = Version(self.version)\n        return join(\n            self.site_root,\n            f\"usr/local/lib/python{p_version.major}.{p_version.minor}/site-packages/\"\n        )\n\n    @property\n    def _pip(self):\n        return join(self.local_bin, \"pip3\")\n\n    @property\n    def pip(self):\n        return sh.Command(self._pip)\n\n    def fix_pip_shebangs(self):\n\n        if not os.path.exists(self.local_bin):\n            return\n\n        for filename in os.listdir(self.local_bin):\n            if not filename.startswith(\"pip\"):\n                continue\n\n            pip_path = os.path.join(self.local_bin, filename)\n\n            with open(pip_path, \"rb\") as file:\n                file_lines = file.read().splitlines()\n\n            file_lines[0] = f\"#!{self.python_exe}\".encode()\n\n            with open(pip_path, \"wb\") as file:\n                file.write(b\"\\n\".join(file_lines) + b\"\\n\")\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        recipe_build_dir = self.get_build_dir(arch.arch)\n\n        # Create a subdirectory to actually perform the build\n        build_dir = join(recipe_build_dir, self.build_subdir)\n        ensure_dir(build_dir)\n\n        # Configure the build\n        build_configured = False\n        with current_directory(build_dir):\n            if not Path('config.status').exists():\n                shprint(sh.Command(join(recipe_build_dir, 'configure')), _env=env)\n                build_configured = True\n\n        with current_directory(recipe_build_dir):\n            # Create the Setup file. This copying from Setup.dist is\n            # the normal and expected procedure before Python 3.8, but\n            # after this the file with default options is already named \"Setup\"\n            setup_dist_location = join('Modules', 'Setup.dist')\n            if Path(setup_dist_location).exists():\n                shprint(sh.cp, setup_dist_location,\n                        join(build_dir, 'Modules', 'Setup'))\n            else:\n                # Check the expected file does exist\n                setup_location = join('Modules', 'Setup')\n                if not Path(setup_location).exists():\n                    raise BuildInterruptingException(\n                        SETUP_DIST_NOT_FIND_MESSAGE\n                    )\n\n            shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir, _env=env)\n\n            # make a copy of the python executable giving it the name we want,\n            # because we got different python's executable names depending on\n            # the fs being case-insensitive (Mac OS X, Cygwin...) or\n            # case-sensitive (linux)...so this way we will have an unique name\n            # for our hostpython, regarding the used fs\n            for exe_name in ['python.exe', 'python']:\n                exe = join(self.get_path_to_python(), exe_name)\n                if Path(exe).is_file():\n                    shprint(sh.cp, exe, self.python_exe)\n                    break\n\n        ensure_dir(self.site_root)\n        self.ctx.hostpython = self.python_exe\n\n        if build_configured:\n\n            shprint(\n                sh.Command(self.python_exe), \"-m\", \"ensurepip\", \"--root\", self.site_root, \"-U\",\n                _env={\"HOME\": \"/tmp\", \"PATH\": self.local_bin}\n            )\n            self.fix_pip_shebangs()\n\n\nrecipe = HostPython3Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/hostpython3/fix_ensurepip.patch",
    "content": "diff '--color=auto' -uNr cpython-3.14.0/Lib/ensurepip/__init__.py cpython-3.14.0.mod/Lib/ensurepip/__init__.py\n--- cpython-3.14.0/Lib/ensurepip/__init__.py\t2025-10-07 15:04:52.000000000 +0530\n+++ cpython-3.14.0.mod/Lib/ensurepip/__init__.py\t2025-12-20 18:18:13.884914683 +0530\n@@ -69,7 +69,15 @@\n     code = f\"\"\"\n import runpy\n import sys\n-sys.path = {additional_paths or []} + sys.path\n+\n+# tell ensurepip to ignore site-packages\n+\n+paths = []\n+for path in sys.path:\n+    if \"site-packages\" not in path:\n+        paths.append(path)\n+\n+sys.path = {additional_paths or []} + paths\n sys.argv[1:] = {args}\n runpy.run_module(\"pip\", run_name=\"__main__\", alter_sys=True)\n \"\"\"\n"
  },
  {
    "path": "pythonforandroid/recipes/httpx/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass HttpxRecipe(PyProjectRecipe):\n    name = \"httpx\"\n    version = \"0.28.1\"\n    url = (\n        \"https://pypi.python.org/packages/source/h/httpx/httpx-{version}.tar.gz\"\n    )\n    depends = [\"httpcore\", \"h11\", \"certifi\", \"idna\", \"sniffio\"]\n\n\nrecipe = HttpxRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/icu/__init__.py",
    "content": "import sh\nimport os\nimport platform\nfrom os.path import join, isdir, exists\nfrom multiprocessing import cpu_count\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.toolchain import shprint\nfrom pythonforandroid.util import current_directory, ensure_dir\n\n\nclass ICURecipe(Recipe):\n    name = 'icu4c'\n    version = '57.1'\n    major_version = version.split('.')[0]\n    url = (\n        \"https://github.com/unicode-org/icu/releases/download/\"\n        \"release-{version_hyphen}/icu4c-{version_underscore}-src.tgz\"\n    )\n\n    depends = ['hostpython3']  # installs in python\n    patches = ['disable-libs-version.patch']\n\n    built_libraries = {\n        'libicui18n{}.so'.format(major_version): 'build_icu_android/lib',\n        'libicuuc{}.so'.format(major_version): 'build_icu_android/lib',\n        'libicudata{}.so'.format(major_version): 'build_icu_android/lib',\n        'libicule{}.so'.format(major_version): 'build_icu_android/lib',\n        'libicuio{}.so'.format(major_version): 'build_icu_android/lib',\n        'libicutu{}.so'.format(major_version): 'build_icu_android/lib',\n        'libiculx{}.so'.format(major_version): 'build_icu_android/lib',\n    }\n\n    @property\n    def versioned_url(self):\n        if self.url is None:\n            return None\n        return self.url.format(\n            version=self.version,\n            version_underscore=self.version.replace('.', '_'),\n            version_hyphen=self.version.replace('.', '-'))\n\n    def get_recipe_dir(self):\n        \"\"\"\n        .. note:: We need to overwrite `Recipe.get_recipe_dir` due to the\n                  mismatch name between the recipe's folder (icu) and the value\n                  of `ICURecipe.name` (icu4c).\n        \"\"\"\n        if self.ctx.local_recipes is not None:\n            local_recipe_dir = join(self.ctx.local_recipes, 'icu')\n            if exists(local_recipe_dir):\n                return local_recipe_dir\n        return join(self.ctx.root_dir, 'recipes', 'icu')\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch).copy()\n        build_root = self.get_build_dir(arch.arch)\n\n        def make_build_dest(dest):\n            build_dest = join(build_root, dest)\n            if not isdir(build_dest):\n                ensure_dir(build_dest)\n                return build_dest, False\n\n            return build_dest, True\n\n        icu_build = join(build_root, \"icu_build\")\n        build_host, exists = make_build_dest(\"build_icu_host\")\n\n        host_env = os.environ.copy()\n        # reduce the function set\n        host_env[\"CPPFLAGS\"] = (\n            \"-O3 -fno-short-wchar -DU_USING_ICU_NAMESPACE=1 -fno-short-enums \"\n            \"-DU_HAVE_NL_LANGINFO_CODESET=0 -D__STDC_INT64__ -DU_TIMEZONE=0 \"\n            \"-DUCONFIG_NO_LEGACY_CONVERSION=1 \"\n            \"-DUCONFIG_NO_TRANSLITERATION=0 \")\n\n        if not exists:\n            icu4c_host_platform = platform.system()\n            if icu4c_host_platform == \"Darwin\":\n                icu4c_host_platform = \"MacOSX\"\n            configure = sh.Command(\n                join(build_root, \"source\", \"runConfigureICU\"))\n            with current_directory(build_host):\n                shprint(\n                    configure,\n                    icu4c_host_platform,\n                    \"--prefix=\"+icu_build,\n                    \"--enable-extras=no\",\n                    \"--enable-strict=no\",\n                    \"--enable-static=no\",\n                    \"--enable-tests=no\",\n                    \"--enable-samples=no\",\n                    _env=host_env)\n                shprint(sh.make, \"-j\", str(cpu_count()), _env=host_env)\n                shprint(sh.make, \"install\", _env=host_env)\n        build_android, exists = make_build_dest(\"build_icu_android\")\n        if not exists:\n            configure = sh.Command(join(build_root, \"source\", \"configure\"))\n\n            with current_directory(build_android):\n                shprint(\n                    configure,\n                    \"--with-cross-build=\"+build_host,\n                    \"--enable-extras=no\",\n                    \"--enable-strict=no\",\n                    \"--enable-static=no\",\n                    \"--enable-tests=no\",\n                    \"--enable-samples=no\",\n                    \"--host=\"+arch.command_prefix,\n                    \"--prefix=\"+icu_build,\n                    _env=env)\n                shprint(sh.make, \"-j\", str(cpu_count()), _env=env)\n                shprint(sh.make, \"install\", _env=env)\n\n    def install_libraries(self, arch):\n        super().install_libraries(arch)\n\n        src_include = join(\n            self.get_build_dir(arch.arch), \"icu_build\", \"include\")\n        dst_include = join(\n            self.ctx.get_python_install_dir(arch.arch), \"include\", \"icu\")\n        ensure_dir(dst_include)\n        shprint(sh.cp, \"-r\", join(src_include, \"layout\"), dst_include)\n        shprint(sh.cp, \"-r\", join(src_include, \"unicode\"), dst_include)\n\n\nrecipe = ICURecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/icu/disable-libs-version.patch",
    "content": "diff -aur icu4c-org/source/config/Makefile.inc.in icu4c/source/config/Makefile.inc.in\n--- icu/source/config/Makefile.inc.in.orig\t2016-03-23 21:50:50.000000000 +0100\n+++ icu/source/config/Makefile.inc.in\t2019-02-15 17:59:28.331749766 +0100\n@@ -142,8 +142,8 @@\n LDLIBRARYPATH_ENVVAR = LD_LIBRARY_PATH\n\n # Versioned target for a shared library\n-FINAL_SO_TARGET = $(SO_TARGET).$(SO_TARGET_VERSION)\n-MIDDLE_SO_TARGET = $(SO_TARGET).$(SO_TARGET_VERSION_MAJOR)\n+FINAL_SO_TARGET =  $(SO_TARGET).$(SO_TARGET_VERSION)\n+MIDDLE_SO_TARGET = $(SO_TARGET)\n\n # Access to important ICU tools.\n # Use as follows:  $(INVOKE) $(GENRB) arguments ..\ndiff -aur icu4c-org/source/config/mh-linux icu4c/source/config/mh-linux\n--- icu4c-org/source/config/mh-linux\t2017-07-05 13:23:06.000000000 +0200\n+++ icu4c/source/config/mh-linux\t2017-07-06 14:02:52.275016858 +0200\n@@ -24,9 +24,17 @@\n\n ## Compiler switch to embed a library name\n # The initial tab in the next line is to prevent icu-config from reading it.\n-\tLD_SONAME = -Wl,-soname -Wl,$(notdir $(MIDDLE_SO_TARGET))\n+\tLD_SONAME = -Wl,-soname -Wl,$(notdir $(SO_TARGET))\n+\tDATA_STUBNAME = data$(SO_TARGET_VERSION_MAJOR)\n+\tCOMMON_STUBNAME = uc$(SO_TARGET_VERSION_MAJOR)\n+\tI18N_STUBNAME = i18n$(SO_TARGET_VERSION_MAJOR)\n+\tLAYOUT_STUBNAME = le$(SO_TARGET_VERSION_MAJOR)\n+\tLAYOUTEX_STUBNAME = lx$(SO_TARGET_VERSION_MAJOR)\n+\tIO_STUBNAME = io$(SO_TARGET_VERSION_MAJOR)\n+\tTOOLUTIL_STUBNAME = tu$(SO_TARGET_VERSION_MAJOR)\n+\tCTESTFW_STUBNAME = test$(SO_TARGET_VERSION_MAJOR)\n #SH# # We can't depend on MIDDLE_SO_TARGET being set.\n-#SH# LD_SONAME=\n+#SH# LD_SONAME=$(SO_TARGET)\n\n ## Shared library options\n LD_SOOPTIONS= -Wl,-Bsymbolic\n@@ -64,10 +64,10 @@\n\n ## Versioned libraries rules\n\n-%.$(SO).$(SO_TARGET_VERSION_MAJOR): %.$(SO).$(SO_TARGET_VERSION)\n-\t$(RM) $@ && ln -s ${<F} $@\n-%.$(SO): %.$(SO).$(SO_TARGET_VERSION_MAJOR)\n-\t$(RM) $@ && ln -s ${*F}.$(SO).$(SO_TARGET_VERSION) $@\n+%.$(SO).$(SO_TARGET_VERSION_MAJOR): %.$(SO).$(SO_TARGET_VERSION)\n+\t$(RM) $@ && ln -s ${<F} $@\n+%.$(SO): %.$(SO).$(SO_TARGET_VERSION_MAJOR)\n+\t$(RM) $@ && ln -s ${*F}.$(SO).$(SO_TARGET_VERSION) $@\n\n ##  Bind internal references\n\ndiff -aur icu4c-org/source/data/Makefile.in icu4c/source/data/Makefile.in\n--- icu4c-org/source/data/Makefile.in\t2017-07-05 13:23:06.000000000 +0200\n+++ icu4c/source/data/Makefile.in\t2017-07-06 14:05:31.607995855 +0200\n@@ -24,9 +24,9 @@\n ifeq ($(PKGDATA_OPTS),)\n PKGDATA_OPTS = -O $(top_builddir)/data/icupkg.inc\n endif\n-ifeq ($(PKGDATA_VERSIONING),)\n-PKGDATA_VERSIONING = -r $(SO_TARGET_VERSION)\n-endif\n+#ifeq ($(PKGDATA_VERSIONING),)\n+#PKGDATA_VERSIONING = -r $(SO_TARGET_VERSION)\n+#endif\n\n"
  },
  {
    "path": "pythonforandroid/recipes/ifaddr/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass IfaddrRecipe(PythonRecipe):\n    name = 'ifaddr'\n    version = '0.1.7'\n    url = 'https://pypi.python.org/packages/source/i/ifaddr/ifaddr-{version}.tar.gz'\n    depends = ['setuptools', 'ifaddrs', 'ipaddress;python_version<\"3.3\"']\n    call_hostpython_via_targetpython = False\n    patches = [\"getifaddrs.patch\"]\n\n\nrecipe = IfaddrRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/ifaddr/getifaddrs.patch",
    "content": "diff --git a/ifaddr/_posix.py b/ifaddr/_posix.py\nindex 2903ee7..546e3ce 100644\n--- a/ifaddr/_posix.py\n+++ b/ifaddr/_posix.py\n@@ -39,6 +39,10 @@ ifaddrs._fields_ = [('ifa_next', ctypes.POINTER(ifaddrs)),\n \n libc = ctypes.CDLL(ctypes.util.find_library(\"socket\" if os.uname()[0] == \"SunOS\" else \"c\"), use_errno=True)\n \n+# On old Androids getifaddrs is not available in libc => use libifaddrs instead\n+if not hasattr(libc, 'getifaddrs'):\n+    libc = ctypes.CDLL(ctypes.util.find_library('ifaddrs'), use_errno=True)\n+\n def get_adapters():\n \n     addr0 = addr = ctypes.POINTER(ifaddrs)()\n"
  },
  {
    "path": "pythonforandroid/recipes/ifaddrs/__init__.py",
    "content": "\"\"\" ifaddrs for Android\n\"\"\"\nfrom os.path import join\n\nimport sh\n\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import CompiledComponentsPythonRecipe\nfrom pythonforandroid.toolchain import current_directory\nfrom pythonforandroid.util import ensure_dir\n\n\nclass IFAddrRecipe(CompiledComponentsPythonRecipe):\n    version = '8f9a87c'\n    url = 'https://github.com/morristech/android-ifaddrs/archive/{version}.zip'\n    depends = ['hostpython3']\n\n    call_hostpython_via_targetpython = False\n    site_packages_name = 'ifaddrs'\n    generated_libraries = ['libifaddrs.so']\n\n    def prebuild_arch(self, arch):\n        \"\"\"Make the build and target directories\"\"\"\n        path = self.get_build_dir(arch.arch)\n        ensure_dir(path)\n\n    def build_arch(self, arch):\n        \"\"\"simple shared compile\"\"\"\n        env = self.get_recipe_env(arch, with_flags_in_cc=False)\n        for path in (\n                self.get_build_dir(arch.arch),\n                join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Lib'),\n                join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')):\n            ensure_dir(path)\n        cli = env['CC'].split()[0]\n        # makes sure first CC command is the compiler rather than ccache, refs:\n        # https://github.com/kivy/python-for-android/issues/1398\n        if 'ccache' in cli:\n            cli = env['CC'].split()[1]\n        cc = sh.Command(cli)\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            cflags = env['CFLAGS'].split()\n            cflags.extend(['-I.', '-c', '-l.', 'ifaddrs.c', '-I.'])\n            shprint(cc, *cflags, _env=env)\n            cflags = env['CFLAGS'].split()\n            cflags.extend(['-shared', '-I.', 'ifaddrs.o', '-o', 'libifaddrs.so'])\n            cflags.extend(env['LDFLAGS'].split())\n            shprint(cc, *cflags, _env=env)\n            shprint(sh.cp, 'libifaddrs.so', self.ctx.get_libs_dir(arch.arch))\n\n\nrecipe = IFAddrRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/jedi/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass JediRecipe(PythonRecipe):\n    version = 'v0.9.0'\n    url = 'https://github.com/davidhalter/jedi/archive/{version}.tar.gz'\n\n    patches = ['fix_MergedNamesDict_get.patch']\n    # This apparently should be fixed in jedi 0.10 (not released to\n    # pypi yet), but it still occurs on Android, I could not reproduce\n    # on desktop.\n\n    call_hostpython_via_targetpython = False\n\n\nrecipe = JediRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/jedi/fix_MergedNamesDict_get.patch",
    "content": "diff --git a/jedi/parser/fast.py b/jedi/parser/fast.py\nindex 35bb855..bc43359 100644\n--- a/jedi/parser/fast.py\n+++ b/jedi/parser/fast.py\n@@ -75,7 +75,8 @@ class MergedNamesDict(object):\n         return iter(set(key for dct in self.dicts for key in dct))\n \n     def __getitem__(self, value):\n-        return list(chain.from_iterable(dct.get(value, []) for dct in self.dicts))\n+        return list(chain.from_iterable((dct[value] if value in dct else []) for dct in self.dicts))\n+        # return list(chain.from_iterable(dct.get(value, []) for dct in self.dicts))\n \n     def items(self):\n         dct = {}\n"
  },
  {
    "path": "pythonforandroid/recipes/jpeg/Application.mk",
    "content": "APP_OPTIM := release\nAPP_ABI := all # or armeabi\nAPP_MODULES := libjpeg\nAPP_ALLOW_MISSING_DEPS := true\n"
  },
  {
    "path": "pythonforandroid/recipes/jpeg/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nfrom os.path import join\nimport sh\n\n\nclass JpegRecipe(Recipe):\n    '''\n    .. versionchanged:: 0.6.0\n        rewrote recipe to be build with clang and updated libraries to latest\n        version of the official git repo.\n    '''\n    name = 'jpeg'\n    version = '2.0.1'\n    url = 'https://github.com/libjpeg-turbo/libjpeg-turbo/archive/{version}.tar.gz'  # noqa\n    built_libraries = {'libjpeg.a': '.', 'libturbojpeg.a': '.'}\n    # we will require this below patch to build the shared library\n    # patches = ['remove-version.patch']\n\n    def build_arch(self, arch):\n        build_dir = self.get_build_dir(arch.arch)\n\n        # TODO: Fix simd/neon\n        with current_directory(build_dir):\n            env = self.get_recipe_env(arch)\n            toolchain_file = join(self.ctx.ndk_dir,\n                                  'build/cmake/android.toolchain.cmake')\n\n            shprint(sh.rm, '-rf', 'CMakeCache.txt', 'CMakeFiles/')\n            shprint(sh.cmake, '-G', 'Unix Makefiles',\n                    '-DCMAKE_SYSTEM_NAME=Android',\n                    '-DCMAKE_POSITION_INDEPENDENT_CODE=1',\n                    '-DCMAKE_ANDROID_ARCH_ABI={arch}'.format(arch=arch.arch),\n                    '-DCMAKE_ANDROID_NDK=' + self.ctx.ndk_dir,\n                    '-DCMAKE_C_COMPILER={cc}'.format(cc=arch.get_clang_exe()),\n                    '-DCMAKE_CXX_COMPILER={cc_plus}'.format(\n                        cc_plus=arch.get_clang_exe(plus_plus=True)),\n                    '-DCMAKE_BUILD_TYPE=Release',\n                    '-DCMAKE_INSTALL_PREFIX=./install',\n                    '-DCMAKE_TOOLCHAIN_FILE=' + toolchain_file,\n\n                    '-DANDROID_ABI={arch}'.format(arch=arch.arch),\n                    '-DANDROID_ARM_NEON=ON',\n                    '-DENABLE_NEON=ON',\n                    # '-DREQUIRE_SIMD=1',\n\n                    # Force disable shared, with the static ones is enough\n                    '-DENABLE_SHARED=0',\n                    '-DENABLE_STATIC=1',\n\n                    # Fix cmake compatibility issue\n                    '-DCMAKE_POLICY_VERSION_MINIMUM=3.5',\n                    _env=env)\n            shprint(sh.make, _env=env)\n\n\nrecipe = JpegRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/jpeg/build-static.patch",
    "content": "diff -Naur jpeg/Android.mk b/Android.mk\n--- jpeg/Android.mk\t2015-12-14 11:37:25.900190235 -0600\n+++ b/Android.mk\t2015-12-14 11:41:27.532182210 -0600\n@@ -54,8 +54,7 @@\n \n LOCAL_SRC_FILES:= $(libjpeg_SOURCES_DIST)\n  \n-LOCAL_SHARED_LIBRARIES := libcutils\n-LOCAL_STATIC_LIBRARIES := libsimd\n+LOCAL_STATIC_LIBRARIES := libsimd libcutils\n  \n LOCAL_C_INCLUDES := $(LOCAL_PATH) \n  \n@@ -68,7 +67,7 @@\n  \n LOCAL_MODULE := libjpeg\n \n-include $(BUILD_SHARED_LIBRARY)\n+include $(BUILD_STATIC_LIBRARY)\n \n ######################################################\n ###         cjpeg                                  ###\n@@ -82,7 +81,7 @@\n \n LOCAL_SRC_FILES:= $(cjpeg_SOURCES)\n \n-LOCAL_SHARED_LIBRARIES := libjpeg\n+LOCAL_STATIC_LIBRARIES := libjpeg\n \n LOCAL_C_INCLUDES := $(LOCAL_PATH) \\\n                     $(LOCAL_PATH)/android\n@@ -110,7 +109,7 @@\n \n LOCAL_SRC_FILES:= $(djpeg_SOURCES)\n \n-LOCAL_SHARED_LIBRARIES := libjpeg\n+LOCAL_STATIC_LIBRARIES := libjpeg\n \n LOCAL_C_INCLUDES := $(LOCAL_PATH) \\\n                     $(LOCAL_PATH)/android\n@@ -137,7 +136,7 @@\n \n LOCAL_SRC_FILES:= $(jpegtran_SOURCES)\n \n-LOCAL_SHARED_LIBRARIES := libjpeg\n+LOCAL_STATIC_LIBRARIES := libjpeg\n \n LOCAL_C_INCLUDES := $(LOCAL_PATH) \\\n                     $(LOCAL_PATH)/android\n@@ -163,7 +162,7 @@\n \n LOCAL_SRC_FILES:= $(tjunittest_SOURCES)\n \n-LOCAL_SHARED_LIBRARIES := libjpeg\n+LOCAL_STATIC_LIBRARIES := libjpeg\n \n LOCAL_C_INCLUDES := $(LOCAL_PATH) \n \n@@ -189,7 +188,7 @@\n \n LOCAL_SRC_FILES:= $(tjbench_SOURCES)\n \n-LOCAL_SHARED_LIBRARIES := libjpeg\n+LOCAL_STATIC_LIBRARIES := libjpeg\n \n LOCAL_C_INCLUDES := $(LOCAL_PATH) \n \n@@ -215,7 +214,7 @@\n \n LOCAL_SRC_FILES:= $(rdjpgcom_SOURCES)\n \n-LOCAL_SHARED_LIBRARIES := libjpeg\n+LOCAL_STATIC_LIBRARIES := libjpeg\n \n LOCAL_C_INCLUDES := $(LOCAL_PATH) \n \n@@ -240,7 +239,7 @@\n \n LOCAL_SRC_FILES:= $(wrjpgcom_SOURCES)\n \n-LOCAL_SHARED_LIBRARIES := libjpeg\n+LOCAL_STATIC_LIBRARIES := libjpeg\n \n LOCAL_C_INCLUDES := $(LOCAL_PATH) \n \n"
  },
  {
    "path": "pythonforandroid/recipes/jpeg/remove-version.patch",
    "content": "--- jpeg/CMakeLists.txt.orig\t2018-11-12 20:20:28.000000000 +0100\n+++ jpeg/CMakeLists.txt\t2018-12-14 12:43:45.338704504 +0100\n@@ -573,6 +573,9 @@\n     add_library(turbojpeg SHARED ${TURBOJPEG_SOURCES})\n     set_property(TARGET turbojpeg PROPERTY COMPILE_FLAGS\n       \"-DBMP_SUPPORTED -DPPM_SUPPORTED\")\n+    set_property(TARGET jpeg PROPERTY NO_SONAME 1)\n+    set_property(TARGET turbojpeg PROPERTY NO_SONAME 1)\n+    set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG \"\")\n     if(WIN32)\n       set_target_properties(turbojpeg PROPERTIES DEFINE_SYMBOL DLLDEFINE)\n     endif()\n"
  },
  {
    "path": "pythonforandroid/recipes/kivy/__init__.py",
    "content": "from os.path import join\nimport sys\nimport packaging.version\n\nimport sh\nfrom pythonforandroid.recipe import PyProjectRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\n\n\ndef get_kivy_version(recipe, arch):\n    with current_directory(join(recipe.get_build_dir(arch.arch), \"kivy\")):\n        return shprint(\n            sh.Command(sys.executable),\n            \"-c\",\n            \"import _version; print(_version.__version__)\",\n        )\n\n\ndef is_kivy_affected_by_deadlock_issue(recipe=None, arch=None):\n    return packaging.version.parse(\n        str(get_kivy_version(recipe, arch))\n    ) < packaging.version.Version(\"2.2.0.dev0\")\n\n\ndef is_kivy_less_than_3(recipe=None, arch=None):\n    return packaging.version.parse(\n        str(get_kivy_version(recipe, arch))\n    ) < packaging.version.Version(\"3.0.0\")\n\n\nclass KivyRecipe(PyProjectRecipe):\n    version = '2.3.1'\n    url = 'https://github.com/kivy/kivy/archive/{version}.zip'\n    name = 'kivy'\n\n    depends = [('sdl2', 'sdl3'), 'pyjnius', 'setuptools', 'android']\n    python_depends = ['certifi', 'chardet', 'idna', 'requests', 'urllib3', 'filetype']\n    hostpython_prerequisites = [\"cython>=0.29.1,<=3.0.12\"]\n\n    # sdl-gl-swapwindow-nogil.patch is needed to avoid a deadlock.\n    # See: https://github.com/kivy/kivy/pull/8025\n    # WARNING: Remove this patch when a new Kivy version is released.\n    patches = [\n        (\"sdl-gl-swapwindow-nogil.patch\", is_kivy_affected_by_deadlock_issue),\n        (\"use_cython.patch\", is_kivy_less_than_3),\n        \"no-ast-str.patch\"\n    ]\n\n    @property\n    def need_stl_shared(self):\n        if \"sdl3\" in self.ctx.recipe_build_order:\n            return True\n        else:\n            return False\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n\n        # Taken from CythonRecipe\n        env['LDFLAGS'] = env['LDFLAGS'] + ' -L{} '.format(\n            self.ctx.get_libs_dir(arch.arch) +\n            ' -L{} '.format(self.ctx.libs_dir) +\n            ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'obj', 'local',\n                                arch.arch)))\n        env['LDSHARED'] = env['CC'] + ' -shared'\n        env['LIBLINK'] = 'NOTNONE'\n\n        # NDKPLATFORM is our switch for detecting Android platform, so can't be None\n        env['NDKPLATFORM'] = \"NOTNONE\"\n        if not is_kivy_less_than_3(self, arch):\n            env['KIVY_CROSS_PLATFORM'] = 'android'\n\n        if 'sdl2' in self.ctx.recipe_build_order:\n            env['USE_SDL2'] = '1'\n            env['KIVY_SPLIT_EXAMPLES'] = '1'\n            sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)\n            sdl2_image_recipe = self.get_recipe('sdl2_image', self.ctx)\n            env['KIVY_SDL2_PATH'] = ':'.join([\n                join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include'),\n                *sdl2_image_recipe.get_include_dirs(arch),\n                *sdl2_mixer_recipe.get_include_dirs(arch),\n                join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'),\n            ])\n        if \"sdl3\" in self.ctx.recipe_build_order:\n            sdl3_mixer_recipe = self.get_recipe(\"sdl3_mixer\", self.ctx)\n            sdl3_image_recipe = self.get_recipe(\"sdl3_image\", self.ctx)\n            sdl3_ttf_recipe = self.get_recipe(\"sdl3_ttf\", self.ctx)\n            sdl3_recipe = self.get_recipe(\"sdl3\", self.ctx)\n            env[\"USE_SDL3\"] = \"1\"\n            env[\"KIVY_SPLIT_EXAMPLES\"] = \"1\"\n            env[\"KIVY_SDL3_PATH\"] = \":\".join(\n                [\n                    *sdl3_mixer_recipe.get_include_dirs(arch),\n                    *sdl3_image_recipe.get_include_dirs(arch),\n                    *sdl3_ttf_recipe.get_include_dirs(arch),\n                    *sdl3_recipe.get_include_dirs(arch),\n                ]\n            )\n\n        return env\n\n\nrecipe = KivyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/kivy/no-ast-str.patch",
    "content": "diff -ur kivy-2.3.1b/kivy/lang/parser.py kivy-2.3.1/kivy/lang/parser.py\n--- kivy-2.3.1b/kivy/lang/parser.py\t2025-10-19 13:04:51.542798827 +1300\n+++ kivy-2.3.1/kivy/lang/parser.py\t2025-10-19 13:05:16.007104601 +1300\n@@ -230,11 +230,7 @@\n \n         if isinstance(node, (ast.JoinedStr, ast.BoolOp)):\n             for n in node.values:\n-                if isinstance(n, ast.Str):\n-                    # NOTE: required for python3.6\n-                    yield from cls.get_names_from_expression(n.s)\n-                else:\n-                    yield from cls.get_names_from_expression(n.value)\n+                yield from cls.get_names_from_expression(n.value)\n \n         if isinstance(node, ast.BinOp):\n             yield from cls.get_names_from_expression(node.right)\n"
  },
  {
    "path": "pythonforandroid/recipes/kivy/sdl-gl-swapwindow-nogil.patch",
    "content": "diff --git a/kivy/core/window/_window_sdl2.pyx b/kivy/core/window/_window_sdl2.pyx\nindex 46e15ec63..5002cd0f9 100644\n--- a/kivy/core/window/_window_sdl2.pyx\n+++ b/kivy/core/window/_window_sdl2.pyx\n@@ -746,7 +746,13 @@ cdef class _WindowSDL2Storage:\n             pass\n \n     def flip(self):\n-        SDL_GL_SwapWindow(self.win)\n+        # On Android (and potentially other platforms), SDL_GL_SwapWindow may\n+        # lock the thread waiting for a mutex from another thread to be\n+        # released. Calling SDL_GL_SwapWindow with the GIL released allow the\n+        # other thread to run (e.g. to process the event filter callback) and\n+        # release the mutex SDL_GL_SwapWindow is waiting for.\n+        with nogil:\n+            SDL_GL_SwapWindow(self.win)\n \n     def save_bytes_in_png(self, filename, data, int width, int height):\n         cdef SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(\ndiff --git a/kivy/lib/sdl2.pxi b/kivy/lib/sdl2.pxi\nindex 6a539de6d..3a5a69d23 100644\n--- a/kivy/lib/sdl2.pxi\n+++ b/kivy/lib/sdl2.pxi\n@@ -627,7 +627,7 @@ cdef extern from \"SDL.h\":\n     cdef SDL_GLContext SDL_GL_GetCurrentContext()\n     cdef int SDL_GL_SetSwapInterval(int interval)\n     cdef int SDL_GL_GetSwapInterval()\n-    cdef void SDL_GL_SwapWindow(SDL_Window * window)\n+    cdef void SDL_GL_SwapWindow(SDL_Window * window) nogil\n     cdef void SDL_GL_DeleteContext(SDL_GLContext context)\n \n     cdef int SDL_NumJoysticks()\n"
  },
  {
    "path": "pythonforandroid/recipes/kivy/use_cython.patch",
    "content": "--- kivy-master/setup.py\t2025-02-25 03:08:18.000000000 +0530\n+++ kivy-master.mod/setup.py\t2025-03-01 13:10:24.227808612 +0530\n@@ -249,7 +249,7 @@\n # This determines whether Cython specific functionality may be used.\n can_use_cython = True\n \n-if platform in ('ios', 'android'):\n+if platform in ('ios'):\n     # NEVER use or declare cython on these platforms\n     print('Not using cython on %s' % platform)\n     can_use_cython = False\n"
  },
  {
    "path": "pythonforandroid/recipes/kivy3/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\nimport shutil\n\n\nclass Kivy3Recipe(PythonRecipe):\n    version = 'master'\n    url = 'https://github.com/kivy/kivy3/archive/{version}.zip'\n\n    depends = ['kivy']\n    site_packages_name = 'kivy3'\n\n    '''Due to setuptools.'''\n    call_hostpython_via_targetpython = False\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        suffix = '/kivy3/default.glsl'\n        shutil.copyfile(self.get_build_dir(arch.arch) + suffix, self.ctx.get_python_install_dir(arch.arch) + suffix)\n\n\nrecipe = Kivy3Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/kiwisolver/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass KiwiSolverRecipe(PyProjectRecipe):\n    site_packages_name = 'kiwisolver'\n    version = '1.4.5'\n    url = 'git+https://github.com/nucleic/kiwi'\n    depends = ['cppy']\n    need_stl_shared = True\n\n    def get_recipe_env(self, arch, **kwargs):\n        \"\"\"Override compile and linker flags, refs: #3115 and #3122\"\"\"\n        env = super().get_recipe_env(arch, **kwargs)\n        flags = \" -I\" + self.ctx.python_recipe.include_root(arch.arch)\n        env[\"CFLAGS\"] += flags\n        env[\"CPPFLAGS\"] += flags\n        env[\"LDFLAGS\"] += \" -shared\"\n        return env\n\n\nrecipe = KiwiSolverRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/leveldb/__init__.py",
    "content": "from pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.recipe import Recipe\nfrom multiprocessing import cpu_count\nfrom os.path import join\nimport sh\n\n\nclass LevelDBRecipe(Recipe):\n    version = '1.22'\n    url = 'https://github.com/google/leveldb/archive/{version}.tar.gz'\n    depends = ['snappy']\n    built_libraries = {'libleveldb.so': '.'}\n    need_stl_shared = True\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        source_dir = self.get_build_dir(arch.arch)\n        with current_directory(source_dir):\n            snappy_recipe = self.get_recipe('snappy', self.ctx)\n            snappy_build = snappy_recipe.get_build_dir(arch.arch)\n\n            shprint(sh.cmake, source_dir,\n                    '-DANDROID_ABI={}'.format(arch.arch),\n                    '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),\n                    '-DANDROID_STL=' + self.stl_lib_name,\n\n                    '-DCMAKE_TOOLCHAIN_FILE={}'.format(\n                        join(self.ctx.ndk_dir, 'build', 'cmake',\n                             'android.toolchain.cmake')),\n                    '-DCMAKE_BUILD_TYPE=Release',\n\n                    '-DBUILD_SHARED_LIBS=1',\n\n                    '-DHAVE_SNAPPY=1',\n                    '-DCMAKE_CXX_FLAGS=-I{path}'.format(path=snappy_build),\n                    '-DCMAKE_SHARED_LINKER_FLAGS=-L{path} -lsnappy'.format(\n                        path=snappy_build),\n                    '-DCMAKE_EXE_LINKER_FLAGS=-L{path} -lsnappy'.format(\n                        path=snappy_build),\n\n                    _env=env)\n            shprint(sh.make, '-j' + str(cpu_count()), _env=env)\n\n\nrecipe = LevelDBRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libbz2/__init__.py",
    "content": "import sh\n\nfrom multiprocessing import cpu_count\n\nfrom pythonforandroid.archs import Arch\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\n\n\nclass LibBz2Recipe(Recipe):\n\n    version = \"1.0.8\"\n    url = \"https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz\"\n    built_libraries = {\"libbz2.so\": \"\"}\n    patches = [\"lib_android.patch\"]\n\n    def build_arch(self, arch: Arch) -> None:\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            shprint(\n                sh.make,\n                \"-j\",\n                str(cpu_count()),\n                f'CC={env[\"CC\"]}',\n                \"-f\",\n                \"Makefile-libbz2_so\",\n                _env=env,\n            )\n\n    def get_library_includes(self, arch: Arch) -> str:\n        \"\"\"\n        Returns a string with the appropriate `-I<lib directory>` to link\n        with the bz2 lib. This string is usually added to the environment\n        variable `CPPFLAGS`.\n        \"\"\"\n        return \" -I\" + self.get_build_dir(arch.arch)\n\n    def get_library_ldflags(self, arch: Arch) -> str:\n        \"\"\"\n        Returns a string with the appropriate `-L<lib directory>` to link\n        with the bz2 lib. This string is usually added to the environment\n        variable `LDFLAGS`.\n        \"\"\"\n        return \" -L\" + self.get_build_dir(arch.arch)\n\n    @staticmethod\n    def get_library_libs_flag() -> str:\n        \"\"\"\n        Returns a string with the appropriate `-l<lib>` flags to link with\n        the bz2 lib. This string is usually added to the environment\n        variable `LIBS`.\n        \"\"\"\n        return \" -lbz2\"\n\n\nrecipe = LibBz2Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libbz2/lib_android.patch",
    "content": "Set default compiler to `clang` and disable versioned shared library\n--- bzip2-1.0.8/Makefile-libbz2_so.orig\t2019-07-13 19:50:05.000000000 +0200\n+++ bzip2-1.0.8/Makefile-libbz2_so\t2020-03-13 20:10:32.336990786 +0100\n@@ -22,7 +22,7 @@\n \n \n SHELL=/bin/sh\n-CC=gcc\n+CC=clang\n BIGFILES=-D_FILE_OFFSET_BITS=64\n CFLAGS=-fpic -fPIC -Wall -Winline -O2 -g $(BIGFILES)\n \n@@ -35,13 +35,11 @@ OBJS= blocksort.o  \\\n       bzlib.o\n \n all: $(OBJS)\n-\t$(CC) -shared -Wl,-soname -Wl,libbz2.so.1.0 -o libbz2.so.1.0.8 $(OBJS)\n-\t$(CC) $(CFLAGS) -o bzip2-shared bzip2.c libbz2.so.1.0.8\n-\trm -f libbz2.so.1.0\n-\tln -s libbz2.so.1.0.8 libbz2.so.1.0\n+\t$(CC) -shared -Wl,-soname=libbz2.so -o libbz2.so $(OBJS)\n+\t$(CC) $(CFLAGS) -o bzip2-shared bzip2.c libbz2.so\n \n clean: \n-\trm -f $(OBJS) bzip2.o libbz2.so.1.0.8 libbz2.so.1.0 bzip2-shared\n+\trm -f $(OBJS) bzip2.o libbz2.so bzip2-shared\n \n blocksort.o: blocksort.c\n \t$(CC) $(CFLAGS) -c blocksort.c\n"
  },
  {
    "path": "pythonforandroid/recipes/libcairo/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe, MesonRecipe\nfrom os.path import join, exists\nfrom pythonforandroid.util import ensure_dir, current_directory\nfrom pythonforandroid.logger import shprint\nfrom multiprocessing import cpu_count\nimport sh\n\n\nclass LibCairoRecipe(MesonRecipe):\n    name = 'libcairo'\n    version = '1.18.4'\n    url = 'https://gitlab.freedesktop.org/cairo/cairo/-/archive/{version}/cairo-{version}.tar.bz2'\n    skip_python = True\n    depends = [\"png\", \"freetype\"]\n    patches = [\"meson.patch\"]\n    built_libraries = {\n        'libcairo.so': 'install/lib',\n        'libpixman-1.so': 'install/lib',\n        'libcairo-script-interpreter.so': 'install/lib'\n    }\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        cpufeatures = join(self.ctx.ndk.ndk_dir, \"sources/android/cpufeatures\")\n        lib_dir = join(cpufeatures, \"obj\", \"local\", arch.arch)\n        env[\"CFLAGS\"] += f\" -I{cpufeatures}\"\n        env[\"LDFLAGS\"] += f\" -L{lib_dir} -lcpufeatures\"\n        return env\n\n    def should_build(self, arch):\n        return Recipe.should_build(self, arch)\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        build_dir = self.get_build_dir(arch.arch)\n        install_dir = join(build_dir, 'install')\n        ensure_dir(install_dir)\n        env = self.get_recipe_env(arch)\n\n        lib_dir = self.ctx.get_libs_dir(arch.arch)\n        png_include = self.get_recipe('png', self.ctx).get_build_dir(arch.arch)\n        freetype_inc = join(self.get_recipe('freetype', self.ctx).get_build_dir(arch), \"include\")\n\n        with current_directory(build_dir):\n\n            cpufeatures_dir = join(self.ctx.ndk.ndk_dir, \"sources/android/cpufeatures\")\n            lib_file = join(cpufeatures_dir, \"obj\", \"local\", arch.arch, \"libcpufeatures.a\")\n\n            if not exists(lib_file):\n                shprint(\n                    sh.Command(join(self.ctx.ndk_dir, \"ndk-build\")),\n                    f\"NDK_PROJECT_PATH={cpufeatures_dir}\",\n                    f\"APP_BUILD_SCRIPT={cpufeatures_dir}/Android.mk\",\n                    f\"APP_ABI={arch.arch}\",\n                    \"APP_PLATFORM=latest\",\n                    _env=env\n                )\n\n            shprint(sh.meson, 'setup', 'builddir',\n                    '--cross-file', join(\"/tmp\", \"android.meson.cross\"),\n                    f'--prefix={install_dir}',\n                    '-Dpng=enabled',\n                    '-Dzlib=enabled',\n                    '-Dglib=disabled',\n                    '-Dgtk_doc=false',\n                    '-Dsymbol-lookup=disabled',\n\n                    # deps\n                    f'-Dpng_include_dir={png_include}',\n                    f'-Dpng_lib_dir={lib_dir}',\n                    f'-Dfreetype_include_dir={freetype_inc}',\n                    f'-Dfreetype_lib_dir={lib_dir}',\n                    _env=env)\n\n            shprint(sh.ninja, '-C', 'builddir', '-j', str(cpu_count()), _env=env)\n            # macOS fix: sometimes Ninja creates a dummy 'lib' file instead of a directory.\n            # So we remove and recreate the install directory using shell commands,\n            # since os.remove/os.makedirs behave inconsistently in this build env.\n            shprint(sh.rm, '-rf', install_dir)\n            shprint(sh.mkdir, install_dir)\n\n            shprint(sh.ninja, '-C', 'builddir', 'install', _env=env)\n\n\nrecipe = LibCairoRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libcairo/meson.patch",
    "content": "diff '--color=auto' -uNr cairo-1.18.4/meson.build cairo-1.18.4.mod/meson.build\n--- cairo-1.18.4/meson.build\t2025-03-08 18:53:25.000000000 +0530\n+++ cairo-1.18.4.mod/meson.build\t2025-07-14 20:42:56.226164648 +0530\n@@ -235,11 +235,13 @@\n   conf.set('HAVE_ZLIB', 1)\n endif\n \n-png_dep = dependency('libpng',\n-  required: get_option('png'),\n-  version: libpng_required_version,\n-  fallback: ['libpng', 'libpng_dep']\n+png_inc = include_directories(get_option('png_include_dir'))\n+png_lib = cc.find_library('png16', dirs: [get_option('png_lib_dir')], required: true)\n+png_dep = declare_dependency(\n+  include_directories: png_inc,\n+  dependencies: [png_lib]\n )\n+\n if png_dep.found()\n   feature_conf.set('CAIRO_HAS_SVG_SURFACE', 1)\n   feature_conf.set('CAIRO_HAS_PNG_FUNCTIONS', 1)\n@@ -265,7 +267,7 @@\n \n # Disable fontconfig by default on platforms where it is optional\n fontconfig_option = get_option('fontconfig')\n-fontconfig_required = host_machine.system() not in ['windows', 'darwin']\n+fontconfig_required = false \n fontconfig_option = fontconfig_option.disable_auto_if(not fontconfig_required)\n \n fontconfig_dep = dependency('fontconfig',\n@@ -304,11 +306,14 @@\n freetype_required = host_machine.system() not in ['windows', 'darwin']\n freetype_option = freetype_option.disable_auto_if(not freetype_required)\n \n-freetype_dep = dependency('freetype2',\n-  required: freetype_option,\n-  version: freetype_required_version,\n-  fallback: ['freetype2', 'freetype_dep'],\n+freetype_inc = include_directories(get_option('freetype_include_dir'))\n+freetype_lib = cc.find_library('freetype', dirs: [get_option('freetype_lib_dir')], required: true)\n+\n+freetype_dep = declare_dependency(\n+  include_directories: freetype_inc,\n+  dependencies: [freetype_lib]\n )\n+\n if freetype_dep.found()\n   feature_conf.set('CAIRO_HAS_FT_FONT', 1)\n   built_features += [{\ndiff '--color=auto' -uNr cairo-1.18.4/meson.options cairo-1.18.4.mod/meson.options\n--- cairo-1.18.4/meson.options\t2025-03-08 18:53:25.000000000 +0530\n+++ cairo-1.18.4.mod/meson.options\t2025-07-14 20:43:00.473191452 +0530\n@@ -28,3 +28,11 @@\n # Documentation\n option('gtk_doc', type : 'boolean', value : false,\n        description: 'Build the Cairo API reference (depends on gtk-doc)')\n+\n+# Deps\n+\n+option('png_include_dir', type: 'string', value: '', description: 'Path to PNG headers')\n+option('png_lib_dir', type: 'string', value: '', description: 'Path to PNG library')\n+option('freetype_include_dir', type: 'string', value: '', description: 'Path to FreeType headers')\n+option('freetype_lib_dir', type: 'string', value: '', description: 'Path to FreeType library')\n+\n"
  },
  {
    "path": "pythonforandroid/recipes/libcurl/__init__.py",
    "content": "import sh\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom os.path import join\nfrom multiprocessing import cpu_count\n\n\nclass LibcurlRecipe(Recipe):\n    version = '7.55.1'\n    url = 'https://curl.haxx.se/download/curl-7.55.1.tar.gz'\n    built_libraries = {'libcurl.so': 'dist/lib'}\n    depends = ['openssl']\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        openssl_recipe = self.get_recipe('openssl', self.ctx)\n        openssl_dir = openssl_recipe.get_build_dir(arch.arch)\n\n        env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)\n        env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags()\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            dst_dir = join(self.get_build_dir(arch.arch), 'dist')\n            shprint(\n                sh.Command('./configure'),\n                '--host={}'.format(arch.command_prefix),\n                '--enable-shared',\n                '--with-ssl={}'.format(openssl_dir),\n                '--prefix={}'.format(dst_dir),\n                _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n            shprint(sh.make, 'install', _env=env)\n\n\nrecipe = LibcurlRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libexpat/__init__.py",
    "content": "\nimport sh\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom os.path import join\nfrom multiprocessing import cpu_count\n\n\nclass LibexpatRecipe(Recipe):\n    version = 'master'\n    url = 'https://github.com/libexpat/libexpat/archive/{version}.zip'\n    built_libraries = {'libexpat.so': 'dist/lib'}\n    depends = []\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(join(self.get_build_dir(arch.arch), 'expat')):\n            dst_dir = join(self.get_build_dir(arch.arch), 'dist')\n            shprint(sh.Command('./buildconf.sh'), _env=env)\n            shprint(\n                sh.Command('./configure'),\n                '--host={}'.format(arch.command_prefix),\n                '--enable-shared',\n                '--without-xmlwf',\n                '--prefix={}'.format(dst_dir),\n                _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n            shprint(sh.make, 'install', _env=env)\n\n\nrecipe = LibexpatRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libffi/Application.mk",
    "content": "APP_OPTIM := release\nAPP_ABI := all # or armeabi\nAPP_MODULES := libffi\n"
  },
  {
    "path": "pythonforandroid/recipes/libffi/__init__.py",
    "content": "from os.path import exists, join\nfrom multiprocessing import cpu_count\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nimport sh\n\n\nclass LibffiRecipe(Recipe):\n    \"\"\"\n    Requires additional system dependencies on Ubuntu:\n        - `automake` for the `aclocal` binary\n        - `autoconf` for the `autoreconf` binary\n        - `libltdl-dev` which defines the `LT_SYS_SYMBOL_USCORE` macro\n    \"\"\"\n    name = 'libffi'\n    version = 'v3.4.2'\n    url = 'https://github.com/libffi/libffi/archive/{version}.tar.gz'\n\n    patches = ['remove-version-info.patch']\n\n    built_libraries = {'libffi.so': '.libs'}\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            if not exists('configure'):\n                shprint(sh.Command('./autogen.sh'), _env=env)\n            shprint(sh.Command('autoreconf'), '-vif', _env=env)\n            shprint(sh.Command('./configure'),\n                    '--host=' + arch.command_prefix,\n                    '--prefix=' + self.get_build_dir(arch.arch),\n                    '--disable-builddir',\n                    '--enable-shared', _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), 'libffi.la', _env=env)\n\n    def get_include_dirs(self, arch):\n        return [join(self.get_build_dir(arch), 'include')]\n\n\nrecipe = LibffiRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libffi/disable-mips-check.patch",
    "content": "diff -Naur libffi/Android.mk b/Android.mk\n--- libffi/Android.mk\t2015-12-22 17:00:48.025478556 -0600\n+++ b/Android.mk\t2015-12-22 17:02:23.999249390 -0600\n@@ -23,23 +23,20 @@\n # Build rules for the target.\n #\n \n-# We only build ffi for mips.\n-ifeq ($(TARGET_ARCH),mips)\n \n-   include $(CLEAR_VARS)\n+include $(CLEAR_VARS)\n \n-   ffi_arch := $(TARGET_ARCH)\n-   ffi_os := $(TARGET_OS)\n+ffi_arch := $(TARGET_ARCH)\n+ffi_os := $(TARGET_OS)\n \n-   # This include just keeps the nesting a bit saner.\n-   include $(LOCAL_PATH)/Libffi.mk\n+# This include just keeps the nesting a bit saner.\n+include $(LOCAL_PATH)/Libffi.mk\n \n-   LOCAL_MODULE_TAGS := optional\n-   LOCAL_MODULE := libffi\n+LOCAL_MODULE_TAGS := optional\n+LOCAL_MODULE := libffi\n \n-   include $(BUILD_SHARED_LIBRARY)\n+include $(BUILD_SHARED_LIBRARY)\n \n-endif\n \n # Also include the rules for the test suite.\n include external/libffi/testsuite/Android.mk\n"
  },
  {
    "path": "pythonforandroid/recipes/libffi/remove-version-info.patch",
    "content": "--- libffi/Makefile.am.orig\t2018-12-21 16:11:26.159181262 +0100\n+++ libffi/Makefile.am\t2018-12-21 16:14:44.075179374 +0100\n@@ -156,7 +156,7 @@\n libffi.map: $(top_srcdir)/libffi.map.in\n \t$(COMPILE) -D$(TARGET) -E -x assembler-with-cpp -o $@ $<\n\n-libffi_la_LDFLAGS = -no-undefined $(libffi_version_info) $(libffi_version_script) $(LTLDFLAGS) $(AM_LTLDFLAGS)\n+libffi_la_LDFLAGS = -no-undefined -avoid-version $(LTLDFLAGS) $(AM_LTLDFLAGS)\n libffi_la_DEPENDENCIES = $(libffi_la_LIBADD) $(libffi_version_dep)\n\n AM_CPPFLAGS = -I. -I$(top_srcdir)/include -Iinclude -I$(top_srcdir)/src\n"
  },
  {
    "path": "pythonforandroid/recipes/libgeos/__init__.py",
    "content": "from pythonforandroid.util import current_directory, ensure_dir\nfrom pythonforandroid.toolchain import shprint\nfrom pythonforandroid.recipe import Recipe\nfrom multiprocessing import cpu_count\nfrom os.path import join\nimport sh\n\n\nclass LibgeosRecipe(Recipe):\n    version = '3.7.1'\n    url = 'https://github.com/libgeos/libgeos/archive/{version}.zip'\n    depends = []\n    built_libraries = {\n        'libgeos.so': 'install_target/lib',\n        'libgeos_c.so': 'install_target/lib'\n    }\n    need_stl_shared = True\n\n    def build_arch(self, arch):\n        source_dir = self.get_build_dir(arch.arch)\n        build_target = join(source_dir, 'build_target')\n        install_target = join(source_dir, 'install_target')\n\n        ensure_dir(build_target)\n        with current_directory(build_target):\n            env = self.get_recipe_env(arch)\n            shprint(sh.cmake, source_dir,\n                    '-DANDROID_ABI={}'.format(arch.arch),\n                    '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),\n                    '-DANDROID_STL=' + self.stl_lib_name,\n\n                    '-DCMAKE_TOOLCHAIN_FILE={}'.format(\n                        join(self.ctx.ndk_dir, 'build', 'cmake',\n                             'android.toolchain.cmake')),\n                    '-DCMAKE_INSTALL_PREFIX={}'.format(install_target),\n                    '-DCMAKE_BUILD_TYPE=Release',\n\n                    '-DGEOS_ENABLE_TESTS=OFF',\n\n                    '-DBUILD_SHARED_LIBS=1',\n\n                    _env=env)\n            shprint(sh.make, '-j' + str(cpu_count()), _env=env)\n\n            # We make the install because this way we will have all the\n            # includes in one place (mostly we are interested in `geos_c.h`,\n            # which is not in the include folder, so this way we make easier to\n            # link with this library...case of shapely's recipe)\n            shprint(sh.make, 'install', _env=env)\n\n\nrecipe = LibgeosRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libglob/__init__.py",
    "content": "\"\"\"\n    android libglob\n    available via '-lglob' LDFLAG\n\"\"\"\nfrom os.path import join\n\nimport sh\n\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.toolchain import current_directory\nfrom pythonforandroid.util import ensure_dir\n\n\nclass LibGlobRecipe(Recipe):\n    \"\"\"Make a glob.h and glob.so for the python_install_dir()\"\"\"\n    version = '0.0.1'\n    url = None\n    #\n    # glob.h and glob.c extracted from\n    # https://github.com/white-gecko/TokyoCabinet, e.g.:\n    #   https://raw.githubusercontent.com/white-gecko/TokyoCabinet/master/glob.h\n    #   https://raw.githubusercontent.com/white-gecko/TokyoCabinet/master/glob.c\n    # and pushed in via patch\n    name = 'libglob'\n    built_libraries = {'libglob.so': '.'}\n\n    depends = ['hostpython3']\n    patches = ['glob.patch']\n\n    def should_build(self, arch):\n        \"\"\"It's faster to build than check\"\"\"\n        return True\n\n    def prebuild_arch(self, arch):\n        \"\"\"Make the build and target directories\"\"\"\n        path = self.get_build_dir(arch.arch)\n        ensure_dir(path)\n\n    def build_arch(self, arch):\n        \"\"\"simple shared compile\"\"\"\n        env = self.get_recipe_env(arch, with_flags_in_cc=False)\n        for path in (\n                self.get_build_dir(arch.arch),\n                join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Lib'),\n                join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')):\n            ensure_dir(path)\n        cli = env['CC'].split()[0]\n        # makes sure first CC command is the compiler rather than ccache, refs:\n        # https://github.com/kivy/python-for-android/issues/1399\n        if 'ccache' in cli:\n            cli = env['CC'].split()[1]\n        cc = sh.Command(cli)\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            cflags = env['CFLAGS'].split()\n            cflags.extend(['-I.', '-c', '-l.', 'glob.c', '-I.'])\n            shprint(cc, *cflags, _env=env)\n            cflags = env['CFLAGS'].split()\n            cflags.extend(['-shared', '-I.', 'glob.o', '-o', 'libglob.so'])\n            cflags.extend(env['LDFLAGS'].split())\n            shprint(cc, *cflags, _env=env)\n\n\nrecipe = LibGlobRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libglob/glob.patch",
    "content": "diff -Nur /tmp/x/glob.c libglob/glob.c\n--- /tmp/x/glob.c\t1969-12-31 19:00:00.000000000 -0500\n+++ libglob/glob.c\t2017-08-19 15:23:19.910414868 -0400\n@@ -0,0 +1,906 @@\n+/*\n+ * Natanael Arndt, 2011: removed collate.h dependencies\n+ *  (my changes are trivial)\n+ *\n+ * Copyright (c) 1989, 1993\n+ *\tThe Regents of the University of California.  All rights reserved.\n+ *\n+ * This code is derived from software contributed to Berkeley by\n+ * Guido van Rossum.\n+ *\n+ * Redistribution and use in source and binary forms, with or without\n+ * modification, are permitted provided that the following conditions\n+ * are met:\n+ * 1. Redistributions of source code must retain the above copyright\n+ *    notice, this list of conditions and the following disclaimer.\n+ * 2. Redistributions in binary form must reproduce the above copyright\n+ *    notice, this list of conditions and the following disclaimer in the\n+ *    documentation and/or other materials provided with the distribution.\n+ * 4. Neither the name of the University nor the names of its contributors\n+ *    may be used to endorse or promote products derived from this software\n+ *    without specific prior written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n+ * SUCH DAMAGE.\n+ */\n+\n+#if defined(LIBC_SCCS) && !defined(lint)\n+static char sccsid[] = \"@(#)glob.c\t8.3 (Berkeley) 10/13/93\";\n+#endif /* LIBC_SCCS and not lint */\n+#include <sys/cdefs.h>\n+__FBSDID(\"$FreeBSD$\");\n+\n+/*\n+ * glob(3) -- a superset of the one defined in POSIX 1003.2.\n+ *\n+ * The [!...] convention to negate a range is supported (SysV, Posix, ksh).\n+ *\n+ * Optional extra services, controlled by flags not defined by POSIX:\n+ *\n+ * GLOB_QUOTE:\n+ *\tEscaping convention: \\ inhibits any special meaning the following\n+ *\tcharacter might have (except \\ at end of string is retained).\n+ * GLOB_MAGCHAR:\n+ *\tSet in gl_flags if pattern contained a globbing character.\n+ * GLOB_NOMAGIC:\n+ *\tSame as GLOB_NOCHECK, but it will only append pattern if it did\n+ *\tnot contain any magic characters.  [Used in csh style globbing]\n+ * GLOB_ALTDIRFUNC:\n+ *\tUse alternately specified directory access functions.\n+ * GLOB_TILDE:\n+ *\texpand ~user/foo to the /home/dir/of/user/foo\n+ * GLOB_BRACE:\n+ *\texpand {1,2}{a,b} to 1a 1b 2a 2b\n+ * gl_matchc:\n+ *\tNumber of matches in the current invocation of glob.\n+ */\n+\n+/*\n+ * Some notes on multibyte character support:\n+ * 1. Patterns with illegal byte sequences match nothing - even if\n+ *    GLOB_NOCHECK is specified.\n+ * 2. Illegal byte sequences in filenames are handled by treating them as\n+ *    single-byte characters with a value of the first byte of the sequence\n+ *    cast to wchar_t.\n+ * 3. State-dependent encodings are not currently supported.\n+ */\n+\n+#include <sys/param.h>\n+#include <sys/stat.h>\n+\n+#include <ctype.h>\n+#include <dirent.h>\n+#include <errno.h>\n+#include <glob.h>\n+#include <limits.h>\n+#include <pwd.h>\n+#include <stdint.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <unistd.h>\n+#include <wchar.h>\n+\n+#define\tDOLLAR\t\t'$'\n+#define\tDOT\t\t'.'\n+#define\tEOS\t\t'\\0'\n+#define\tLBRACKET\t'['\n+#define\tNOT\t\t'!'\n+#define\tQUESTION\t'?'\n+#define\tQUOTE\t\t'\\\\'\n+#define\tRANGE\t\t'-'\n+#define\tRBRACKET\t']'\n+#define\tSEP\t\t'/'\n+#define\tSTAR\t\t'*'\n+#define\tTILDE\t\t'~'\n+#define\tUNDERSCORE\t'_'\n+#define\tLBRACE\t\t'{'\n+#define\tRBRACE\t\t'}'\n+#define\tSLASH\t\t'/'\n+#define\tCOMMA\t\t','\n+\n+#ifndef DEBUG\n+\n+#define\tM_QUOTE\t\t0x8000000000ULL\n+#define\tM_PROTECT\t0x4000000000ULL\n+#define\tM_MASK\t\t0xffffffffffULL\n+#define\tM_CHAR\t\t0x00ffffffffULL\n+\n+typedef uint_fast64_t Char;\n+\n+#else\n+\n+#define\tM_QUOTE\t\t0x80\n+#define\tM_PROTECT\t0x40\n+#define\tM_MASK\t\t0xff\n+#define\tM_CHAR\t\t0x7f\n+\n+typedef char Char;\n+\n+#endif\n+\n+\n+#define\tCHAR(c)\t\t((Char)((c)&M_CHAR))\n+#define\tMETA(c)\t\t((Char)((c)|M_QUOTE))\n+#define\tM_ALL\t\tMETA('*')\n+#define\tM_END\t\tMETA(']')\n+#define\tM_NOT\t\tMETA('!')\n+#define\tM_ONE\t\tMETA('?')\n+#define\tM_RNG\t\tMETA('-')\n+#define\tM_SET\t\tMETA('[')\n+#define\tismeta(c)\t(((c)&M_QUOTE) != 0)\n+\n+\n+static int\t compare(const void *, const void *);\n+static int\t g_Ctoc(const Char *, char *, size_t);\n+static int\t g_lstat(Char *, struct stat *, glob_t *);\n+static DIR\t*g_opendir(Char *, glob_t *);\n+static const Char *g_strchr(const Char *, wchar_t);\n+#ifdef notdef\n+static Char\t*g_strcat(Char *, const Char *);\n+#endif\n+static int\t g_stat(Char *, struct stat *, glob_t *);\n+static int\t glob0(const Char *, glob_t *, size_t *);\n+static int\t glob1(Char *, glob_t *, size_t *);\n+static int\t glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *);\n+static int\t glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *);\n+static int\t globextend(const Char *, glob_t *, size_t *);\n+static const Char *\t\n+\t\t globtilde(const Char *, Char *, size_t, glob_t *);\n+static int\t globexp1(const Char *, glob_t *, size_t *);\n+static int\t globexp2(const Char *, const Char *, glob_t *, int *, size_t *);\n+static int\t match(Char *, Char *, Char *);\n+#ifdef DEBUG\n+static void\t qprintf(const char *, Char *);\n+#endif\n+\n+int\n+glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob)\n+{\n+\tconst char *patnext;\n+\tsize_t limit;\n+\tChar *bufnext, *bufend, patbuf[MAXPATHLEN], prot;\n+\tmbstate_t mbs;\n+\twchar_t wc;\n+\tsize_t clen;\n+\n+\tpatnext = pattern;\n+\tif (!(flags & GLOB_APPEND)) {\n+\t\tpglob->gl_pathc = 0;\n+\t\tpglob->gl_pathv = NULL;\n+\t\tif (!(flags & GLOB_DOOFFS))\n+\t\t\tpglob->gl_offs = 0;\n+\t}\n+\tif (flags & GLOB_LIMIT) {\n+\t\tlimit = pglob->gl_matchc;\n+\t\tif (limit == 0)\n+\t\t\tlimit = ARG_MAX;\n+\t} else\n+\t\tlimit = 0;\n+\tpglob->gl_flags = flags & ~GLOB_MAGCHAR;\n+\tpglob->gl_errfunc = errfunc;\n+\tpglob->gl_matchc = 0;\n+\n+\tbufnext = patbuf;\n+\tbufend = bufnext + MAXPATHLEN - 1;\n+\tif (flags & GLOB_NOESCAPE) {\n+\t\tmemset(&mbs, 0, sizeof(mbs));\n+\t\twhile (bufend - bufnext >= MB_CUR_MAX) {\n+\t\t\tclen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);\n+\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2)\n+\t\t\t\treturn (GLOB_NOMATCH);\n+\t\t\telse if (clen == 0)\n+\t\t\t\tbreak;\n+\t\t\t*bufnext++ = wc;\n+\t\t\tpatnext += clen;\n+\t\t}\n+\t} else {\n+\t\t/* Protect the quoted characters. */\n+\t\tmemset(&mbs, 0, sizeof(mbs));\n+\t\twhile (bufend - bufnext >= MB_CUR_MAX) {\n+\t\t\tif (*patnext == QUOTE) {\n+\t\t\t\tif (*++patnext == EOS) {\n+\t\t\t\t\t*bufnext++ = QUOTE | M_PROTECT;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t\tprot = M_PROTECT;\n+\t\t\t} else\n+\t\t\t\tprot = 0;\n+\t\t\tclen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);\n+\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2)\n+\t\t\t\treturn (GLOB_NOMATCH);\n+\t\t\telse if (clen == 0)\n+\t\t\t\tbreak;\n+\t\t\t*bufnext++ = wc | prot;\n+\t\t\tpatnext += clen;\n+\t\t}\n+\t}\n+\t*bufnext = EOS;\n+\n+\tif (flags & GLOB_BRACE)\n+\t    return globexp1(patbuf, pglob, &limit);\n+\telse\n+\t    return glob0(patbuf, pglob, &limit);\n+}\n+\n+/*\n+ * Expand recursively a glob {} pattern. When there is no more expansion\n+ * invoke the standard globbing routine to glob the rest of the magic\n+ * characters\n+ */\n+static int\n+globexp1(const Char *pattern, glob_t *pglob, size_t *limit)\n+{\n+\tconst Char* ptr = pattern;\n+\tint rv;\n+\n+\t/* Protect a single {}, for find(1), like csh */\n+\tif (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)\n+\t\treturn glob0(pattern, pglob, limit);\n+\n+\twhile ((ptr = g_strchr(ptr, LBRACE)) != NULL)\n+\t\tif (!globexp2(ptr, pattern, pglob, &rv, limit))\n+\t\t\treturn rv;\n+\n+\treturn glob0(pattern, pglob, limit);\n+}\n+\n+\n+/*\n+ * Recursive brace globbing helper. Tries to expand a single brace.\n+ * If it succeeds then it invokes globexp1 with the new pattern.\n+ * If it fails then it tries to glob the rest of the pattern and returns.\n+ */\n+static int\n+globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit)\n+{\n+\tint     i;\n+\tChar   *lm, *ls;\n+\tconst Char *pe, *pm, *pm1, *pl;\n+\tChar    patbuf[MAXPATHLEN];\n+\n+\t/* copy part up to the brace */\n+\tfor (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)\n+\t\tcontinue;\n+\t*lm = EOS;\n+\tls = lm;\n+\n+\t/* Find the balanced brace */\n+\tfor (i = 0, pe = ++ptr; *pe; pe++)\n+\t\tif (*pe == LBRACKET) {\n+\t\t\t/* Ignore everything between [] */\n+\t\t\tfor (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)\n+\t\t\t\tcontinue;\n+\t\t\tif (*pe == EOS) {\n+\t\t\t\t/*\n+\t\t\t\t * We could not find a matching RBRACKET.\n+\t\t\t\t * Ignore and just look for RBRACE\n+\t\t\t\t */\n+\t\t\t\tpe = pm;\n+\t\t\t}\n+\t\t}\n+\t\telse if (*pe == LBRACE)\n+\t\t\ti++;\n+\t\telse if (*pe == RBRACE) {\n+\t\t\tif (i == 0)\n+\t\t\t\tbreak;\n+\t\t\ti--;\n+\t\t}\n+\n+\t/* Non matching braces; just glob the pattern */\n+\tif (i != 0 || *pe == EOS) {\n+\t\t*rv = glob0(patbuf, pglob, limit);\n+\t\treturn 0;\n+\t}\n+\n+\tfor (i = 0, pl = pm = ptr; pm <= pe; pm++)\n+\t\tswitch (*pm) {\n+\t\tcase LBRACKET:\n+\t\t\t/* Ignore everything between [] */\n+\t\t\tfor (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++)\n+\t\t\t\tcontinue;\n+\t\t\tif (*pm == EOS) {\n+\t\t\t\t/*\n+\t\t\t\t * We could not find a matching RBRACKET.\n+\t\t\t\t * Ignore and just look for RBRACE\n+\t\t\t\t */\n+\t\t\t\tpm = pm1;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase LBRACE:\n+\t\t\ti++;\n+\t\t\tbreak;\n+\n+\t\tcase RBRACE:\n+\t\t\tif (i) {\n+\t\t\t    i--;\n+\t\t\t    break;\n+\t\t\t}\n+\t\t\t/* FALLTHROUGH */\n+\t\tcase COMMA:\n+\t\t\tif (i && *pm == COMMA)\n+\t\t\t\tbreak;\n+\t\t\telse {\n+\t\t\t\t/* Append the current string */\n+\t\t\t\tfor (lm = ls; (pl < pm); *lm++ = *pl++)\n+\t\t\t\t\tcontinue;\n+\t\t\t\t/*\n+\t\t\t\t * Append the rest of the pattern after the\n+\t\t\t\t * closing brace\n+\t\t\t\t */\n+\t\t\t\tfor (pl = pe + 1; (*lm++ = *pl++) != EOS;)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\t/* Expand the current pattern */\n+#ifdef DEBUG\n+\t\t\t\tqprintf(\"globexp2:\", patbuf);\n+#endif\n+\t\t\t\t*rv = globexp1(patbuf, pglob, limit);\n+\n+\t\t\t\t/* move after the comma, to the next string */\n+\t\t\t\tpl = pm + 1;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t*rv = 0;\n+\treturn 0;\n+}\n+\n+\n+\n+/*\n+ * expand tilde from the passwd file.\n+ */\n+static const Char *\n+globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)\n+{\n+\tstruct passwd *pwd;\n+\tchar *h;\n+\tconst Char *p;\n+\tChar *b, *eb;\n+\n+\tif (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))\n+\t\treturn pattern;\n+\n+\t/* \n+\t * Copy up to the end of the string or / \n+\t */\n+\teb = &patbuf[patbuf_len - 1];\n+\tfor (p = pattern + 1, h = (char *) patbuf;\n+\t    h < (char *)eb && *p && *p != SLASH; *h++ = *p++)\n+\t\tcontinue;\n+\n+\t*h = EOS;\n+\n+\tif (((char *) patbuf)[0] == EOS) {\n+\t\t/*\n+\t\t * handle a plain ~ or ~/ by expanding $HOME first (iff\n+\t\t * we're not running setuid or setgid) and then trying\n+\t\t * the password file\n+\t\t */\n+\t\tif (issetugid() != 0 ||\n+\t\t    (h = getenv(\"HOME\")) == NULL) {\n+\t\t\tif (((h = getlogin()) != NULL &&\n+\t\t\t     (pwd = getpwnam(h)) != NULL) ||\n+\t\t\t    (pwd = getpwuid(getuid())) != NULL)\n+\t\t\t\th = pwd->pw_dir;\n+\t\t\telse\n+\t\t\t\treturn pattern;\n+\t\t}\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * Expand a ~user\n+\t\t */\n+\t\tif ((pwd = getpwnam((char*) patbuf)) == NULL)\n+\t\t\treturn pattern;\n+\t\telse\n+\t\t\th = pwd->pw_dir;\n+\t}\n+\n+\t/* Copy the home directory */\n+\tfor (b = patbuf; b < eb && *h; *b++ = *h++)\n+\t\tcontinue;\n+\n+\t/* Append the rest of the pattern */\n+\twhile (b < eb && (*b++ = *p++) != EOS)\n+\t\tcontinue;\n+\t*b = EOS;\n+\n+\treturn patbuf;\n+}\n+\n+\n+/*\n+ * The main glob() routine: compiles the pattern (optionally processing\n+ * quotes), calls glob1() to do the real pattern matching, and finally\n+ * sorts the list (unless unsorted operation is requested).  Returns 0\n+ * if things went well, nonzero if errors occurred.\n+ */\n+static int\n+glob0(const Char *pattern, glob_t *pglob, size_t *limit)\n+{\n+\tconst Char *qpatnext;\n+\tint err;\n+\tsize_t oldpathc;\n+\tChar *bufnext, c, patbuf[MAXPATHLEN];\n+\n+\tqpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob);\n+\toldpathc = pglob->gl_pathc;\n+\tbufnext = patbuf;\n+\n+\t/* We don't need to check for buffer overflow any more. */\n+\twhile ((c = *qpatnext++) != EOS) {\n+\t\tswitch (c) {\n+\t\tcase LBRACKET:\n+\t\t\tc = *qpatnext;\n+\t\t\tif (c == NOT)\n+\t\t\t\t++qpatnext;\n+\t\t\tif (*qpatnext == EOS ||\n+\t\t\t    g_strchr(qpatnext+1, RBRACKET) == NULL) {\n+\t\t\t\t*bufnext++ = LBRACKET;\n+\t\t\t\tif (c == NOT)\n+\t\t\t\t\t--qpatnext;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\t*bufnext++ = M_SET;\n+\t\t\tif (c == NOT)\n+\t\t\t\t*bufnext++ = M_NOT;\n+\t\t\tc = *qpatnext++;\n+\t\t\tdo {\n+\t\t\t\t*bufnext++ = CHAR(c);\n+\t\t\t\tif (*qpatnext == RANGE &&\n+\t\t\t\t    (c = qpatnext[1]) != RBRACKET) {\n+\t\t\t\t\t*bufnext++ = M_RNG;\n+\t\t\t\t\t*bufnext++ = CHAR(c);\n+\t\t\t\t\tqpatnext += 2;\n+\t\t\t\t}\n+\t\t\t} while ((c = *qpatnext++) != RBRACKET);\n+\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n+\t\t\t*bufnext++ = M_END;\n+\t\t\tbreak;\n+\t\tcase QUESTION:\n+\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n+\t\t\t*bufnext++ = M_ONE;\n+\t\t\tbreak;\n+\t\tcase STAR:\n+\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n+\t\t\t/* collapse adjacent stars to one,\n+\t\t\t * to avoid exponential behavior\n+\t\t\t */\n+\t\t\tif (bufnext == patbuf || bufnext[-1] != M_ALL)\n+\t\t\t    *bufnext++ = M_ALL;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\t*bufnext++ = CHAR(c);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\t*bufnext = EOS;\n+#ifdef DEBUG\n+\tqprintf(\"glob0:\", patbuf);\n+#endif\n+\n+\tif ((err = glob1(patbuf, pglob, limit)) != 0)\n+\t\treturn(err);\n+\n+\t/*\n+\t * If there was no match we are going to append the pattern\n+\t * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified\n+\t * and the pattern did not contain any magic characters\n+\t * GLOB_NOMAGIC is there just for compatibility with csh.\n+\t */\n+\tif (pglob->gl_pathc == oldpathc) {\n+\t\tif (((pglob->gl_flags & GLOB_NOCHECK) ||\n+\t\t    ((pglob->gl_flags & GLOB_NOMAGIC) &&\n+\t\t\t!(pglob->gl_flags & GLOB_MAGCHAR))))\n+\t\t\treturn(globextend(pattern, pglob, limit));\n+\t\telse\n+\t\t\treturn(GLOB_NOMATCH);\n+\t}\n+\tif (!(pglob->gl_flags & GLOB_NOSORT))\n+\t\tqsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,\n+\t\t    pglob->gl_pathc - oldpathc, sizeof(char *), compare);\n+\treturn(0);\n+}\n+\n+static int\n+compare(const void *p, const void *q)\n+{\n+\treturn(strcmp(*(char **)p, *(char **)q));\n+}\n+\n+static int\n+glob1(Char *pattern, glob_t *pglob, size_t *limit)\n+{\n+\tChar pathbuf[MAXPATHLEN];\n+\n+\t/* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */\n+\tif (*pattern == EOS)\n+\t\treturn(0);\n+\treturn(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1,\n+\t    pattern, pglob, limit));\n+}\n+\n+/*\n+ * The functions glob2 and glob3 are mutually recursive; there is one level\n+ * of recursion for each segment in the pattern that contains one or more\n+ * meta characters.\n+ */\n+static int\n+glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern,\n+      glob_t *pglob, size_t *limit)\n+{\n+\tstruct stat sb;\n+\tChar *p, *q;\n+\tint anymeta;\n+\n+\t/*\n+\t * Loop over pattern segments until end of pattern or until\n+\t * segment with meta character found.\n+\t */\n+\tfor (anymeta = 0;;) {\n+\t\tif (*pattern == EOS) {\t\t/* End of pattern? */\n+\t\t\t*pathend = EOS;\n+\t\t\tif (g_lstat(pathbuf, &sb, pglob))\n+\t\t\t\treturn(0);\n+\n+\t\t\tif (((pglob->gl_flags & GLOB_MARK) &&\n+\t\t\t    pathend[-1] != SEP) && (S_ISDIR(sb.st_mode)\n+\t\t\t    || (S_ISLNK(sb.st_mode) &&\n+\t\t\t    (g_stat(pathbuf, &sb, pglob) == 0) &&\n+\t\t\t    S_ISDIR(sb.st_mode)))) {\n+\t\t\t\tif (pathend + 1 > pathend_last)\n+\t\t\t\t\treturn (GLOB_ABORTED);\n+\t\t\t\t*pathend++ = SEP;\n+\t\t\t\t*pathend = EOS;\n+\t\t\t}\n+\t\t\t++pglob->gl_matchc;\n+\t\t\treturn(globextend(pathbuf, pglob, limit));\n+\t\t}\n+\n+\t\t/* Find end of next segment, copy tentatively to pathend. */\n+\t\tq = pathend;\n+\t\tp = pattern;\n+\t\twhile (*p != EOS && *p != SEP) {\n+\t\t\tif (ismeta(*p))\n+\t\t\t\tanymeta = 1;\n+\t\t\tif (q + 1 > pathend_last)\n+\t\t\t\treturn (GLOB_ABORTED);\n+\t\t\t*q++ = *p++;\n+\t\t}\n+\n+\t\tif (!anymeta) {\t\t/* No expansion, do next segment. */\n+\t\t\tpathend = q;\n+\t\t\tpattern = p;\n+\t\t\twhile (*pattern == SEP) {\n+\t\t\t\tif (pathend + 1 > pathend_last)\n+\t\t\t\t\treturn (GLOB_ABORTED);\n+\t\t\t\t*pathend++ = *pattern++;\n+\t\t\t}\n+\t\t} else\t\t\t/* Need expansion, recurse. */\n+\t\t\treturn(glob3(pathbuf, pathend, pathend_last, pattern, p,\n+\t\t\t    pglob, limit));\n+\t}\n+\t/* NOTREACHED */\n+}\n+\n+static int\n+glob3(Char *pathbuf, Char *pathend, Char *pathend_last,\n+      Char *pattern, Char *restpattern,\n+      glob_t *pglob, size_t *limit)\n+{\n+\tstruct dirent *dp;\n+\tDIR *dirp;\n+\tint err;\n+\tchar buf[MAXPATHLEN];\n+\n+\t/*\n+\t * The readdirfunc declaration can't be prototyped, because it is\n+\t * assigned, below, to two functions which are prototyped in glob.h\n+\t * and dirent.h as taking pointers to differently typed opaque\n+\t * structures.\n+\t */\n+\tstruct dirent *(*readdirfunc)();\n+\n+\tif (pathend > pathend_last)\n+\t\treturn (GLOB_ABORTED);\n+\t*pathend = EOS;\n+\terrno = 0;\n+\n+\tif ((dirp = g_opendir(pathbuf, pglob)) == NULL) {\n+\t\t/* TODO: don't call for ENOENT or ENOTDIR? */\n+\t\tif (pglob->gl_errfunc) {\n+\t\t\tif (g_Ctoc(pathbuf, buf, sizeof(buf)))\n+\t\t\t\treturn (GLOB_ABORTED);\n+\t\t\tif (pglob->gl_errfunc(buf, errno) ||\n+\t\t\t    pglob->gl_flags & GLOB_ERR)\n+\t\t\t\treturn (GLOB_ABORTED);\n+\t\t}\n+\t\treturn(0);\n+\t}\n+\n+\terr = 0;\n+\n+\t/* Search directory for matching names. */\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\treaddirfunc = pglob->gl_readdir;\n+\telse\n+\t\treaddirfunc = readdir;\n+\twhile ((dp = (*readdirfunc)(dirp))) {\n+\t\tchar *sc;\n+\t\tChar *dc;\n+\t\twchar_t wc;\n+\t\tsize_t clen;\n+\t\tmbstate_t mbs;\n+\n+\t\t/* Initial DOT must be matched literally. */\n+\t\tif (dp->d_name[0] == DOT && *pattern != DOT)\n+\t\t\tcontinue;\n+\t\tmemset(&mbs, 0, sizeof(mbs));\n+\t\tdc = pathend;\n+\t\tsc = dp->d_name;\n+\t\twhile (dc < pathend_last) {\n+\t\t\tclen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs);\n+\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2) {\n+\t\t\t\twc = *sc;\n+\t\t\t\tclen = 1;\n+\t\t\t\tmemset(&mbs, 0, sizeof(mbs));\n+\t\t\t}\n+\t\t\tif ((*dc++ = wc) == EOS)\n+\t\t\t\tbreak;\n+\t\t\tsc += clen;\n+\t\t}\n+\t\tif (!match(pathend, pattern, restpattern)) {\n+\t\t\t*pathend = EOS;\n+\t\t\tcontinue;\n+\t\t}\n+\t\terr = glob2(pathbuf, --dc, pathend_last, restpattern,\n+\t\t    pglob, limit);\n+\t\tif (err)\n+\t\t\tbreak;\n+\t}\n+\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\t(*pglob->gl_closedir)(dirp);\n+\telse\n+\t\tclosedir(dirp);\n+\treturn(err);\n+}\n+\n+\n+/*\n+ * Extend the gl_pathv member of a glob_t structure to accomodate a new item,\n+ * add the new item, and update gl_pathc.\n+ *\n+ * This assumes the BSD realloc, which only copies the block when its size\n+ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic\n+ * behavior.\n+ *\n+ * Return 0 if new item added, error code if memory couldn't be allocated.\n+ *\n+ * Invariant of the glob_t structure:\n+ *\tEither gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and\n+ *\tgl_pathv points to (gl_offs + gl_pathc + 1) items.\n+ */\n+static int\n+globextend(const Char *path, glob_t *pglob, size_t *limit)\n+{\n+\tchar **pathv;\n+\tsize_t i, newsize, len;\n+\tchar *copy;\n+\tconst Char *p;\n+\n+\tif (*limit && pglob->gl_pathc > *limit) {\n+\t\terrno = 0;\n+\t\treturn (GLOB_NOSPACE);\n+\t}\n+\n+\tnewsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);\n+\tpathv = pglob->gl_pathv ?\n+\t\t    realloc((char *)pglob->gl_pathv, newsize) :\n+\t\t    malloc(newsize);\n+\tif (pathv == NULL) {\n+\t\tif (pglob->gl_pathv) {\n+\t\t\tfree(pglob->gl_pathv);\n+\t\t\tpglob->gl_pathv = NULL;\n+\t\t}\n+\t\treturn(GLOB_NOSPACE);\n+\t}\n+\n+\tif (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {\n+\t\t/* first time around -- clear initial gl_offs items */\n+\t\tpathv += pglob->gl_offs;\n+\t\tfor (i = pglob->gl_offs + 1; --i > 0; )\n+\t\t\t*--pathv = NULL;\n+\t}\n+\tpglob->gl_pathv = pathv;\n+\n+\tfor (p = path; *p++;)\n+\t\tcontinue;\n+\tlen = MB_CUR_MAX * (size_t)(p - path);\t/* XXX overallocation */\n+\tif ((copy = malloc(len)) != NULL) {\n+\t\tif (g_Ctoc(path, copy, len)) {\n+\t\t\tfree(copy);\n+\t\t\treturn (GLOB_NOSPACE);\n+\t\t}\n+\t\tpathv[pglob->gl_offs + pglob->gl_pathc++] = copy;\n+\t}\n+\tpathv[pglob->gl_offs + pglob->gl_pathc] = NULL;\n+\treturn(copy == NULL ? GLOB_NOSPACE : 0);\n+}\n+\n+/*\n+ * pattern matching function for filenames.  Each occurrence of the *\n+ * pattern causes a recursion level.\n+ */\n+static int\n+match(Char *name, Char *pat, Char *patend)\n+{\n+\tint ok, negate_range;\n+\tChar c, k;\n+\n+\twhile (pat < patend) {\n+\t\tc = *pat++;\n+\t\tswitch (c & M_MASK) {\n+\t\tcase M_ALL:\n+\t\t\tif (pat == patend)\n+\t\t\t\treturn(1);\n+\t\t\tdo\n+\t\t\t    if (match(name, pat, patend))\n+\t\t\t\t    return(1);\n+\t\t\twhile (*name++ != EOS);\n+\t\t\treturn(0);\n+\t\tcase M_ONE:\n+\t\t\tif (*name++ == EOS)\n+\t\t\t\treturn(0);\n+\t\t\tbreak;\n+\t\tcase M_SET:\n+\t\t\tok = 0;\n+\t\t\tif ((k = *name++) == EOS)\n+\t\t\t\treturn(0);\n+\t\t\tif ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)\n+\t\t\t\t++pat;\n+\t\t\twhile (((c = *pat++) & M_MASK) != M_END)\n+\t\t\t\tif ((*pat & M_MASK) == M_RNG) {\n+\t\t\t\t\tif (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1;\n+\t\t\t\t\tpat += 2;\n+\t\t\t\t} else if (c == k)\n+\t\t\t\t\tok = 1;\n+\t\t\tif (ok == negate_range)\n+\t\t\t\treturn(0);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tif (*name++ != c)\n+\t\t\t\treturn(0);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn(*name == EOS);\n+}\n+\n+/* Free allocated data belonging to a glob_t structure. */\n+void\n+globfree(glob_t *pglob)\n+{\n+\tsize_t i;\n+\tchar **pp;\n+\n+\tif (pglob->gl_pathv != NULL) {\n+\t\tpp = pglob->gl_pathv + pglob->gl_offs;\n+\t\tfor (i = pglob->gl_pathc; i--; ++pp)\n+\t\t\tif (*pp)\n+\t\t\t\tfree(*pp);\n+\t\tfree(pglob->gl_pathv);\n+\t\tpglob->gl_pathv = NULL;\n+\t}\n+}\n+\n+static DIR *\n+g_opendir(Char *str, glob_t *pglob)\n+{\n+\tchar buf[MAXPATHLEN];\n+\n+\tif (!*str)\n+\t\tstrcpy(buf, \".\");\n+\telse {\n+\t\tif (g_Ctoc(str, buf, sizeof(buf)))\n+\t\t\treturn (NULL);\n+\t}\n+\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\treturn((*pglob->gl_opendir)(buf));\n+\n+\treturn(opendir(buf));\n+}\n+\n+static int\n+g_lstat(Char *fn, struct stat *sb, glob_t *pglob)\n+{\n+\tchar buf[MAXPATHLEN];\n+\n+\tif (g_Ctoc(fn, buf, sizeof(buf))) {\n+\t\terrno = ENAMETOOLONG;\n+\t\treturn (-1);\n+\t}\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\treturn((*pglob->gl_lstat)(buf, sb));\n+\treturn(lstat(buf, sb));\n+}\n+\n+static int\n+g_stat(Char *fn, struct stat *sb, glob_t *pglob)\n+{\n+\tchar buf[MAXPATHLEN];\n+\n+\tif (g_Ctoc(fn, buf, sizeof(buf))) {\n+\t\terrno = ENAMETOOLONG;\n+\t\treturn (-1);\n+\t}\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\treturn((*pglob->gl_stat)(buf, sb));\n+\treturn(stat(buf, sb));\n+}\n+\n+static const Char *\n+g_strchr(const Char *str, wchar_t ch)\n+{\n+\n+\tdo {\n+\t\tif (*str == ch)\n+\t\t\treturn (str);\n+\t} while (*str++);\n+\treturn (NULL);\n+}\n+\n+static int\n+g_Ctoc(const Char *str, char *buf, size_t len)\n+{\n+\tmbstate_t mbs;\n+\tsize_t clen;\n+\n+\tmemset(&mbs, 0, sizeof(mbs));\n+\twhile (len >= MB_CUR_MAX) {\n+\t\tclen = wcrtomb(buf, *str, &mbs);\n+\t\tif (clen == (size_t)-1)\n+\t\t\treturn (1);\n+\t\tif (*str == L'\\0')\n+\t\t\treturn (0);\n+\t\tstr++;\n+\t\tbuf += clen;\n+\t\tlen -= clen;\n+\t}\n+\treturn (1);\n+}\n+\n+#ifdef DEBUG\n+static void\n+qprintf(const char *str, Char *s)\n+{\n+\tChar *p;\n+\n+\t(void)printf(\"%s:\\n\", str);\n+\tfor (p = s; *p; p++)\n+\t\t(void)printf(\"%c\", CHAR(*p));\n+\t(void)printf(\"\\n\");\n+\tfor (p = s; *p; p++)\n+\t\t(void)printf(\"%c\", *p & M_PROTECT ? '\"' : ' ');\n+\t(void)printf(\"\\n\");\n+\tfor (p = s; *p; p++)\n+\t\t(void)printf(\"%c\", ismeta(*p) ? '_' : ' ');\n+\t(void)printf(\"\\n\");\n+}\n+#endif\ndiff -Nur /tmp/x/glob.h libglob/glob.h\n--- /tmp/x/glob.h\t1969-12-31 19:00:00.000000000 -0500\n+++ libglob/glob.h\t2017-08-19 15:22:18.367109399 -0400\n@@ -0,0 +1,104 @@\n+/*\n+ * Copyright (c) 1989, 1993\n+ *\tThe Regents of the University of California.  All rights reserved.\n+ *\n+ * This code is derived from software contributed to Berkeley by\n+ * Guido van Rossum.\n+ *\n+ * Redistribution and use in source and binary forms, with or without\n+ * modification, are permitted provided that the following conditions\n+ * are met:\n+ * 1. Redistributions of source code must retain the above copyright\n+ *    notice, this list of conditions and the following disclaimer.\n+ * 2. Redistributions in binary form must reproduce the above copyright\n+ *    notice, this list of conditions and the following disclaimer in the\n+ *    documentation and/or other materials provided with the distribution.\n+ * 3. Neither the name of the University nor the names of its contributors\n+ *    may be used to endorse or promote products derived from this software\n+ *    without specific prior written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n+ * SUCH DAMAGE.\n+ *\n+ *\t@(#)glob.h\t8.1 (Berkeley) 6/2/93\n+ * $FreeBSD$\n+ */\n+\n+#ifndef _GLOB_H_\n+#define\t_GLOB_H_\n+\n+#include <sys/cdefs.h>\n+#include <sys/types.h>\n+#ifndef ARG_MAX\n+#define     ARG_MAX     6553\n+#endif\n+\n+#ifndef\t_SIZE_T_DECLARED\n+#include <stddef.h>\n+#define\t_SIZE_T_DECLARED\n+#endif\n+\n+struct stat;\n+typedef struct {\n+\tsize_t gl_pathc;\t/* Count of total paths so far. */\n+\tsize_t gl_matchc;\t/* Count of paths matching pattern. */\n+\tsize_t gl_offs;\t\t/* Reserved at beginning of gl_pathv. */\n+\tint gl_flags;\t\t/* Copy of flags parameter to glob. */\n+\tchar **gl_pathv;\t/* List of paths matching pattern. */\n+\t\t\t\t/* Copy of errfunc parameter to glob. */\n+\tint (*gl_errfunc)(const char *, int);\n+\n+\t/*\n+\t * Alternate filesystem access methods for glob; replacement\n+\t * versions of closedir(3), readdir(3), opendir(3), stat(2)\n+\t * and lstat(2).\n+\t */\n+\tvoid (*gl_closedir)(void *);\n+\tstruct dirent *(*gl_readdir)(void *);\n+\tvoid *(*gl_opendir)(const char *);\n+\tint (*gl_lstat)(const char *, struct stat *);\n+\tint (*gl_stat)(const char *, struct stat *);\n+} glob_t;\n+\n+/* Believed to have been introduced in 1003.2-1992 */\n+#define\tGLOB_APPEND\t0x0001\t/* Append to output from previous call. */\n+#define\tGLOB_DOOFFS\t0x0002\t/* Use gl_offs. */\n+#define\tGLOB_ERR\t0x0004\t/* Return on error. */\n+#define\tGLOB_MARK\t0x0008\t/* Append / to matching directories. */\n+#define\tGLOB_NOCHECK\t0x0010\t/* Return pattern itself if nothing matches. */\n+#define\tGLOB_NOSORT\t0x0020\t/* Don't sort. */\n+#define\tGLOB_NOESCAPE\t0x2000\t/* Disable backslash escaping. */\n+\n+/* Error values returned by glob(3) */\n+#define\tGLOB_NOSPACE\t(-1)\t/* Malloc call failed. */\n+#define\tGLOB_ABORTED\t(-2)\t/* Unignored error. */\n+#define\tGLOB_NOMATCH\t(-3)\t/* No match and GLOB_NOCHECK was not set. */\n+#define\tGLOB_NOSYS\t(-4)\t/* Obsolete: source comptability only. */\n+\n+#define\tGLOB_ALTDIRFUNC\t0x0040\t/* Use alternately specified directory funcs. */\n+#define\tGLOB_BRACE\t0x0080\t/* Expand braces ala csh. */\n+#define\tGLOB_MAGCHAR\t0x0100\t/* Pattern had globbing characters. */\n+#define\tGLOB_NOMAGIC\t0x0200\t/* GLOB_NOCHECK without magic chars (csh). */\n+#define\tGLOB_QUOTE\t0x0400\t/* Quote special chars with \\. */\n+#define\tGLOB_TILDE\t0x0800\t/* Expand tilde names from the passwd file. */\n+#define\tGLOB_LIMIT\t0x1000\t/* limit number of returned paths */\n+\n+/* source compatibility, these are the old names */\n+#define GLOB_MAXPATH\tGLOB_LIMIT\n+#define\tGLOB_ABEND\tGLOB_ABORTED\n+\n+__BEGIN_DECLS\n+int\tglob(const char *, int, int (*)(const char *, int), glob_t *);\n+void\tglobfree(glob_t *);\n+__END_DECLS\n+\n+#endif /* !_GLOB_H_ */\n"
  },
  {
    "path": "pythonforandroid/recipes/libiconv/__init__.py",
    "content": "from pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.recipe import Recipe\nfrom multiprocessing import cpu_count\nimport sh\n\n\nclass LibIconvRecipe(Recipe):\n\n    version = '1.16'\n\n    url = 'https://ftp.gnu.org/pub/gnu/libiconv/libiconv-{version}.tar.gz'\n\n    built_libraries = {'libiconv.so': 'lib/.libs'}\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            shprint(\n                sh.Command('./configure'),\n                '--host=' + arch.command_prefix,\n                '--prefix=' + self.ctx.get_python_install_dir(arch.arch),\n                _env=env)\n            shprint(sh.make, '-j' + str(cpu_count()), _env=env)\n\n\nrecipe = LibIconvRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/liblzma/__init__.py",
    "content": "import sh\n\nfrom multiprocessing import cpu_count\nfrom os.path import exists, join\n\nfrom pythonforandroid.archs import Arch\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\n\n\nclass LibLzmaRecipe(Recipe):\n\n    version = '5.2.4'\n    url = 'https://tukaani.org/xz/xz-{version}.tar.gz'\n    built_libraries = {'liblzma.so': 'p4a_install/lib'}\n\n    def build_arch(self, arch: Arch) -> None:\n        env = self.get_recipe_env(arch)\n        install_dir = join(self.get_build_dir(arch.arch), 'p4a_install')\n        with current_directory(self.get_build_dir(arch.arch)):\n            if not exists('configure'):\n                shprint(sh.Command('./autogen.sh'), _env=env)\n            shprint(sh.Command('autoreconf'), '-vif', _env=env)\n            shprint(sh.Command('./configure'),\n                    '--host=' + arch.command_prefix,\n                    '--prefix=' + install_dir,\n                    '--disable-builddir',\n                    '--disable-static',\n                    '--enable-shared',\n\n                    '--disable-xz',\n                    '--disable-xzdec',\n                    '--disable-lzmadec',\n                    '--disable-lzmainfo',\n                    '--disable-scripts',\n                    '--disable-doc',\n\n                    _env=env)\n            shprint(\n                sh.make, '-j', str(cpu_count()),\n                _env=env\n            )\n\n            shprint(sh.make, 'install', _env=env)\n\n    def get_library_includes(self, arch: Arch) -> str:\n        \"\"\"\n        Returns a string with the appropriate `-I<lib directory>` to link\n        with the lzma lib. This string is usually added to the environment\n        variable `CPPFLAGS`.\n        \"\"\"\n        return \" -I\" + join(\n            self.get_build_dir(arch.arch), 'p4a_install', 'include',\n        )\n\n    def get_library_ldflags(self, arch: Arch) -> str:\n        \"\"\"\n        Returns a string with the appropriate `-L<lib directory>` to link\n        with the lzma lib. This string is usually added to the environment\n        variable `LDFLAGS`.\n        \"\"\"\n        return \" -L\" + join(\n            self.get_build_dir(arch.arch), self.built_libraries['liblzma.so'],\n        )\n\n    @staticmethod\n    def get_library_libs_flag() -> str:\n        \"\"\"\n        Returns a string with the appropriate `-l<lib>` flags to link with\n        the lzma lib. This string is usually added to the environment\n        variable `LIBS`.\n        \"\"\"\n        return \" -llzma\"\n\n\nrecipe = LibLzmaRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libogg/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nimport sh\n\n\nclass OggRecipe(Recipe):\n    version = '1.3.3'\n    url = 'http://downloads.xiph.org/releases/ogg/libogg-{version}.tar.gz'\n    built_libraries = {'libogg.so': 'src/.libs'}\n\n    def build_arch(self, arch):\n        with current_directory(self.get_build_dir(arch.arch)):\n            env = self.get_recipe_env(arch)\n            flags = [\n                '--host=' + arch.command_prefix,\n            ]\n            configure = sh.Command('./configure')\n            shprint(configure, *flags, _env=env)\n            shprint(sh.make, _env=env)\n\n\nrecipe = OggRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libopenblas/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory, ensure_dir\nfrom multiprocessing import cpu_count\nfrom os.path import join\nimport sh\nfrom pythonforandroid.util import rmdir\n\n\nclass LibOpenBlasRecipe(Recipe):\n\n    version = \"0.3.29\"\n    url = \"https://github.com/OpenMathLib/OpenBLAS/archive/refs/tags/v{version}.tar.gz\"\n    built_libraries = {\"libopenblas.so\": \"build/lib\"}\n    min_ndk_api_support = 24  # complex math functions support\n\n    def build_arch(self, arch):\n        source_dir = self.get_build_dir(arch.arch)\n        build_target = join(source_dir, \"build\")\n\n        ensure_dir(build_target)\n        with current_directory(build_target):\n            env = self.get_recipe_env(arch)\n            rmdir(\"CMakeFiles\")\n            shprint(sh.rm, \"-f\", \"CMakeCache.txt\", _env=env)\n\n            opts = [\n                # default cmake options\n                \"-DCMAKE_SYSTEM_NAME=Android\",\n                \"-DCMAKE_ANDROID_ARCH_ABI={arch}\".format(arch=arch.arch),\n                \"-DCMAKE_ANDROID_NDK=\" + self.ctx.ndk_dir,\n                \"-DCMAKE_ANDROID_API={api}\".format(api=self.ctx.ndk_api),\n                \"-DCMAKE_BUILD_TYPE=Release\",\n                \"-DBUILD_SHARED_LIBS=ON\",\n                \"-DC_LAPACK=ON\",\n                \"-DTARGET={target}\".format(\n                    target={\n                        \"arm64-v8a\": \"ARMV8\",\n                        \"armeabi-v7a\": \"ARMV7\",\n                        \"x86_64\": \"CORE2\",\n                        \"x86\": \"CORE2\",\n                    }[arch.arch]\n                ),\n            ]\n\n            shprint(sh.cmake, source_dir, *opts, _env=env)\n            shprint(sh.make, \"-j\" + str(cpu_count()), _env=env)\n\n\nrecipe = LibOpenBlasRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libpcre/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nimport sh\nfrom multiprocessing import cpu_count\nfrom os.path import join\n\n\nclass LibpcreRecipe(Recipe):\n    version = '8.44'\n    url = 'https://ftp.pcre.org/pub/pcre/pcre-{version}.tar.bz2'\n\n    built_libraries = {'libpcre.so': '.libs'}\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            shprint(\n                sh.Command('./configure'),\n                *'''--host=arm-linux-androideabi\n                    --disable-cpp --enable-jit --enable-utf8\n                    --enable-unicode-properties'''.split(),\n                _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n\n    def get_lib_dir(self, arch):\n        return join(self.get_build_dir(arch), '.libs')\n\n\nrecipe = LibpcreRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libpq/__init__.py",
    "content": "from pythonforandroid.toolchain import Recipe, current_directory, shprint\nimport sh\nimport os.path\n\n\nclass LibpqRecipe(Recipe):\n    version = '10.12'\n    url = 'http://ftp.postgresql.org/pub/source/v{version}/postgresql-{version}.tar.bz2'\n    depends = []\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['USE_DEV_URANDOM'] = '1'\n\n        return env\n\n    def should_build(self, arch):\n        return not os.path.isfile('{}/libpq.a'.format(self.ctx.get_libs_dir(arch.arch)))\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            configure = sh.Command('./configure')\n            shprint(configure, '--without-readline', '--host=arm-linux',\n                    _env=env)\n            shprint(sh.make, 'submake-libpq', _env=env)\n            shprint(sh.cp, '-a', 'src/interfaces/libpq/libpq.a',\n                    self.ctx.get_libs_dir(arch.arch))\n\n\nrecipe = LibpqRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libpthread/__init__.py",
    "content": "from os import makedirs, remove\nfrom os.path import exists, join\nimport sh\n\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\n\n\nclass LibPthread(Recipe):\n    '''\n    This is a dumb recipe. We may need this because some recipes inserted some\n    flags `-lpthread` without our control, case of:\n\n        - :class:`~pythonforandroid.recipes.uvloop.UvloopRecipe`\n\n    .. note:: the libpthread doesn't exist in android but it is integrated into\n        libc, so we create a symbolic link which we will remove when our build\n        finishes'''\n\n    def build_arch(self, arch):\n        libc_path = join(arch.ndk_lib_dir_versioned, 'libc')\n        # Create a temporary folder to add to link path with a fake libpthread.so:\n        fake_libpthread_temp_folder = join(\n            self.get_build_dir(arch.arch),\n            \"p4a-libpthread-recipe-tempdir\"\n        )\n        if not exists(fake_libpthread_temp_folder):\n            makedirs(fake_libpthread_temp_folder)\n\n        # Set symlinks, and make sure to update them on every build run:\n        if exists(join(fake_libpthread_temp_folder, \"libpthread.so\")):\n            remove(join(fake_libpthread_temp_folder, \"libpthread.so\"))\n        shprint(sh.ln, '-sf',\n                libc_path + '.so',\n                join(fake_libpthread_temp_folder, \"libpthread.so\"),\n                )\n        if exists(join(fake_libpthread_temp_folder, \"libpthread.a\")):\n            remove(join(fake_libpthread_temp_folder, \"libpthread.a\"))\n        shprint(sh.ln, '-sf',\n                libc_path + '.a',\n                join(fake_libpthread_temp_folder, \"libpthread.a\"),\n               )\n\n        # Add folder as -L link option for all recipes if not done yet:\n        if fake_libpthread_temp_folder not in arch.extra_global_link_paths:\n            arch.extra_global_link_paths.append(\n                fake_libpthread_temp_folder\n            )\n\n\nrecipe = LibPthread()\n"
  },
  {
    "path": "pythonforandroid/recipes/librt/__init__.py",
    "content": "from os import makedirs, remove\nfrom os.path import exists, join\nimport sh\n\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\n\n\nclass LibRt(Recipe):\n    '''\n    This is a dumb recipe. We may need this because some recipes inserted some\n    flags `-lrt` without our control, case of:\n\n        - :class:`~pythonforandroid.recipes.gevent.GeventRecipe`\n        - :class:`~pythonforandroid.recipes.lxml.LXMLRecipe`\n\n    .. note:: the librt doesn't exist in android but it is integrated into\n        libc, so we create a symbolic link which we will remove when our build\n        finishes'''\n\n    def build_arch(self, arch):\n        libc_path = join(arch.ndk_lib_dir_versioned, 'libc')\n        # Create a temporary folder to add to link path with a fake librt.so:\n        fake_librt_temp_folder = join(\n            self.get_build_dir(arch.arch),\n            \"p4a-librt-recipe-tempdir\"\n        )\n        if not exists(fake_librt_temp_folder):\n            makedirs(fake_librt_temp_folder)\n\n        # Set symlinks, and make sure to update them on every build run:\n        if exists(join(fake_librt_temp_folder, \"librt.so\")):\n            remove(join(fake_librt_temp_folder, \"librt.so\"))\n        shprint(sh.ln, '-sf',\n                libc_path + '.so',\n                join(fake_librt_temp_folder, \"librt.so\"),\n                )\n        if exists(join(fake_librt_temp_folder, \"librt.a\")):\n            remove(join(fake_librt_temp_folder, \"librt.a\"))\n        shprint(sh.ln, '-sf',\n                libc_path + '.a',\n                join(fake_librt_temp_folder, \"librt.a\"),\n               )\n\n        # Add folder as -L link option for all recipes if not done yet:\n        if fake_librt_temp_folder not in arch.extra_global_link_paths:\n            arch.extra_global_link_paths.append(\n                fake_librt_temp_folder\n            )\n\n\nrecipe = LibRt()\n"
  },
  {
    "path": "pythonforandroid/recipes/libsecp256k1/__init__.py",
    "content": "from pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.recipe import Recipe\nfrom multiprocessing import cpu_count\nfrom os.path import exists\nimport sh\n\n\nclass LibSecp256k1Recipe(Recipe):\n\n    built_libraries = {'libsecp256k1.so': '.libs'}\n    version = '0.4.1'\n    url = 'https://github.com/bitcoin-core/secp256k1/archive/refs/tags/v{version}.tar.gz'\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            if not exists('configure'):\n                shprint(sh.Command('./autogen.sh'), _env=env)\n            shprint(\n                sh.Command('./configure'),\n                '--host=' + arch.command_prefix,\n                '--prefix=' + self.ctx.get_python_install_dir(arch.arch),\n                '--enable-shared',\n                '--enable-module-recovery',\n                '--enable-experimental',\n                '--enable-module-ecdh',\n                _env=env)\n            shprint(sh.make, '-j' + str(cpu_count()), _env=env)\n\n\nrecipe = LibSecp256k1Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libshine/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom multiprocessing import cpu_count\nfrom os.path import realpath\nimport sh\n\n\nclass LibShineRecipe(Recipe):\n    version = 'c72aba9031bde18a0995e7c01c9b53f2e08a0e46'\n    url = 'https://github.com/toots/shine/archive/{version}.zip'\n\n    built_libraries = {'libshine.so': 'lib'}\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        # technically, libraries should go to `LDLIBS`, but it seems\n        # that libshine doesn't like so, and it will fail on linking stage\n        env['LDLIBS'] = env['LDLIBS'].replace(' -lm', '')\n        env['LDFLAGS'] += ' -lm'\n        return env\n\n    def build_arch(self, arch):\n        with current_directory(self.get_build_dir(arch.arch)):\n            env = self.get_recipe_env(arch)\n            shprint(sh.Command('./bootstrap'))\n            configure = sh.Command('./configure')\n            shprint(configure,\n                    f'--host={arch.command_prefix}',\n                    '--enable-pic',\n                    '--disable-static',\n                    '--enable-shared',\n                    f'--prefix={realpath(\".\")}',\n                    _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n            shprint(sh.make, 'install', _env=env)\n\n\nrecipe = LibShineRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libsodium/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom multiprocessing import cpu_count\nimport sh\nfrom packaging import version as packaging_version\n\n\nclass LibsodiumRecipe(Recipe):\n    version = '1.0.16'\n    url = 'https://github.com/jedisct1/libsodium/releases/download/{}/libsodium-{}.tar.gz'\n    depends = []\n    patches = ['size_max_fix.patch']\n    built_libraries = {'libsodium.so': 'src/libsodium/.libs'}\n\n    @property\n    def versioned_url(self):\n        asked_version = packaging_version.parse(self.version)\n        if asked_version > packaging_version.parse('1.0.16'):\n            return self._url.format(self.version + '-RELEASE', self.version)\n        else:\n            return self._url.format(self.version, self.version)\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            bash = sh.Command('bash')\n            shprint(\n                bash,\n                'configure',\n                '--disable-soname-versions',\n                '--host={}'.format(arch.command_prefix),\n                '--enable-shared',\n                _env=env,\n            )\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['CFLAGS'] += ' -Os'\n        return env\n\n\nrecipe = LibsodiumRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libsodium/size_max_fix.patch",
    "content": "diff -urN libsodium-1.0.16.ori/src/libsodium/include/sodium/export.h libsodium-1.0.16/src/libsodium/include/sodium/export.h\n--- libsodium-1.0.16.ori/src/libsodium/include/sodium/export.h\t2017-12-12 00:03:07.000000000 +0100\n+++ libsodium-1.0.16/src/libsodium/include/sodium/export.h\t2018-10-31 09:46:06.051189444 +0100\n@@ -47,6 +47,8 @@\n # endif\n #endif\n \n+#include <limits.h>\n+\n #define SODIUM_MIN(A, B) ((A) < (B) ? (A) : (B))\n #define SODIUM_SIZE_MAX SODIUM_MIN(UINT64_MAX, SIZE_MAX)\n \n"
  },
  {
    "path": "pythonforandroid/recipes/libtorrent/__init__.py",
    "content": "from multiprocessing import cpu_count\nfrom os import listdir, walk\nfrom os.path import join, basename\nimport shutil\n\nimport sh\n\nfrom pythonforandroid.toolchain import Recipe, shprint, current_directory\n\n# This recipe builds libtorrent with Python bindings\n# It depends on Boost.Build and the source of several Boost libraries present\n# in BOOST_ROOT, which is all provided by the boost recipe\n\n\ndef get_lib_from(search_directory, lib_extension='.so'):\n    '''Scan directories recursively until find any file with the given\n    extension. The default extension to search is ``.so``.'''\n    for root, dirs, files in walk(search_directory):\n        for file in files:\n            if file.endswith(lib_extension):\n                print('get_lib_from: {}\\n\\t- {}'.format(\n                    search_directory, join(root, file)))\n                return join(root, file)\n    return None\n\n\nclass LibtorrentRecipe(Recipe):\n    # Todo: make recipe compatible with all p4a architectures\n    '''\n    .. note:: This recipe can be built only against API 21+ and an android\n              ndk >= r19\n\n    .. versionchanged:: 0.6.0\n         Rewrote recipe to support clang's build and boost 1.68. The following\n         changes has been made:\n\n            - Bumped version number to 1.2.0\n            - added python 3 compatibility\n            - new system to detect/copy generated libraries\n\n    .. versionchanged:: 2019.08.09.1.dev0\n\n            - Bumped version number to 1.2.1\n            - Adapted to work with ndk-r19+\n    '''\n    version = '1_2_1'\n    url = 'https://github.com/arvidn/libtorrent/archive/libtorrent-{version}.tar.gz'\n\n    depends = ['boost']\n    opt_depends = ['openssl']\n    patches = ['disable-so-version.patch',\n               'use-soname-python.patch',\n               'setup-lib-name.patch']\n\n    # libtorrent.so is not included because is not a system library\n    generated_libraries = [\n        'boost_system', 'boost_python{py_version}', 'torrent_rasterbar']\n\n    def should_build(self, arch):\n        python_version = self.ctx.python_recipe.version[:3].replace('.', '')\n        libs = ['lib' + lib_name.format(py_version=python_version) +\n                '.so' for lib_name in self.generated_libraries]\n        return not (self.has_libs(arch, *libs) and\n                    self.ctx.has_package('libtorrent', arch.arch))\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        if 'openssl' in recipe.ctx.recipe_build_order:\n            # Patch boost user-config.jam to use openssl\n            self.get_recipe('boost', self.ctx).apply_patch(\n                join(self.get_recipe_dir(), 'user-config-openssl.patch'), arch.arch)\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        env = self.get_recipe_env(arch)\n        env['PYTHON_HOST'] = self.ctx.hostpython\n\n        # Define build variables\n        build_dir = self.get_build_dir(arch.arch)\n        ctx_libs_dir = self.ctx.get_libs_dir(arch.arch)\n        encryption = 'openssl' if 'openssl' in recipe.ctx.recipe_build_order else 'built-in'\n        build_args = [\n            '-q',\n            # '-a',  # force build, useful to debug the build\n            '-j' + str(cpu_count()),\n            '--debug-configuration',  # so we know if our python is detected\n            # '--deprecated-functions=off',\n            'toolset=clang-{arch}'.format(arch=env['ARCH']),\n            'abi=aapcs',\n            'binary-format=elf',\n            'cxxflags=-std=c++11',\n            'target-os=android',\n            'threading=multi',\n            'link=shared',\n            'boost-link=shared',\n            'libtorrent-link=shared',\n            'runtime-link=shared',\n            'encryption={}'.format('on' if encryption == 'openssl' else 'off'),\n            'crypto=' + encryption\n        ]\n        crypto_folder = 'encryption-off'\n        if encryption == 'openssl':\n            crypto_folder = 'crypto-openssl'\n            build_args.extend(['openssl-lib=' + env['OPENSSL_BUILD_PATH'],\n                               'openssl-include=' + env['OPENSSL_INCLUDE']\n                               ])\n        build_args.append('release')\n\n        # Compile libtorrent with boost libraries and python bindings\n        with current_directory(join(build_dir, 'bindings/python')):\n            b2 = sh.Command(join(env['BOOST_ROOT'], 'b2'))\n            shprint(b2, *build_args, _env=env)\n\n        # Copy only the boost shared libraries into the libs folder. Because\n        # boost build two boost_python libraries, we force to search the lib\n        # into the corresponding build path.\n        b2_build_dir = (\n            'build/clang-linux-{arch}/release/{encryption}/'\n            'lt-visibility-hidden/'.format(\n                arch=env['ARCH'], encryption=crypto_folder\n            )\n        )\n        boost_libs_dir = join(env['BOOST_BUILD_PATH'], 'bin.v2/libs')\n        for boost_lib in listdir(boost_libs_dir):\n            lib_path = get_lib_from(join(boost_libs_dir, boost_lib, b2_build_dir))\n            if lib_path:\n                lib_name = basename(lib_path)\n                shutil.copyfile(lib_path, join(ctx_libs_dir, lib_name))\n\n        # Copy libtorrent shared libraries into the right places\n        system_libtorrent = get_lib_from(join(build_dir, 'bin'))\n        if system_libtorrent:\n            shutil.copyfile(system_libtorrent,\n                            join(ctx_libs_dir, 'libtorrent_rasterbar.so'))\n\n        python_libtorrent = get_lib_from(join(build_dir, 'bindings/python/bin'))\n        shutil.copyfile(python_libtorrent,\n                        join(self.ctx.get_site_packages_dir(arch), 'libtorrent.so'))\n\n    def get_recipe_env(self, arch):\n        # Use environment from boost recipe, cause we use b2 tool from boost\n        env = self.get_recipe('boost', self.ctx).get_recipe_env(arch)\n        if 'openssl' in recipe.ctx.recipe_build_order:\n            r = self.get_recipe('openssl', self.ctx)\n            env['OPENSSL_BUILD_PATH'] = r.get_build_dir(arch.arch)\n            env['OPENSSL_INCLUDE'] = join(r.get_build_dir(arch.arch), 'include')\n            env['OPENSSL_VERSION'] = r.version\n        return env\n\n\nrecipe = LibtorrentRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libtorrent/disable-so-version.patch",
    "content": "--- libtorrent/Jamfile\t2016-01-17 23:52:45.000000000 +0100\n+++ libtorrent-patch/Jamfile\t2016-02-09 13:37:57.499561750 +0100\n@@ -325,6 +325,7 @@\n \tif $(type) = SHARED_LIB &&\n \t\t( ! ( [ $(property-set).get <target-os> ] in windows cygwin ) )\n \t{\n+\t\treturn \"libtorrent_rasterbar.so\" ; # linked by python bindings .so\n \t\tname = $(name).$(VERSION) ;\n \t}\n \n"
  },
  {
    "path": "pythonforandroid/recipes/libtorrent/setup-lib-name.patch",
    "content": "--- libtorrent/bindings/python/setup.py.orig\t2018-11-26 22:21:48.772142135 +0100\n+++ libtorrent/bindings/python/setup.py\t2018-11-26 22:23:23.092141235 +0100\n@@ -167,7 +167,7 @@\n         extra_compile = flags.parse(extra_cmd)\n\n         ext = [Extension(\n-            'libtorrent',\n+            'libtorrent_rasterbar',\n             sources=sorted(source_list),\n             language='c++',\n             include_dirs=flags.include_dirs,\n@@ -178,7 +178,7 @@\n         ]\n\n setup(\n-    name='python-libtorrent',\n+    name='libtorrent',\n     version='1.2.1',\n     author='Arvid Norberg',\n     author_email='arvid@libtorrent.org',\n"
  },
  {
    "path": "pythonforandroid/recipes/libtorrent/use-soname-python.patch",
    "content": "--- libtorrent/bindings/python/Jamfile.orig\t2018-12-07 16:46:50.851838981 +0100\n+++ libtorrent/bindings/python/Jamfile\t2018-12-07 16:49:09.099837663 +0100\n@@ -113,7 +113,7 @@\n\n \t\tif ( <toolset>gcc in $(properties) )\n \t\t{\n-\t\t\t result += <linkflags>-Wl,-Bsymbolic ;\n+\t\t\t result += <linkflags>-Wl,-soname=libtorrent.so,-Bsymbolic ;\n \t\t}\n \t}\n \n"
  },
  {
    "path": "pythonforandroid/recipes/libtorrent/user-config-openssl.patch",
    "content": "--- boost/user-config.jam.orig\t2018-12-07 14:16:45.911924859 +0100\n+++ boost/user-config.jam\t2018-12-07 14:20:16.243922853 +0100\n@@ -9,6 +9,8 @@\n local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ;\n local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ;\n local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ;\n+local OPENSSL_BUILD_PATH = [ os.environ OPENSSL_BUILD_PATH ] ;\n+local OPENSSL_VERSION = [ os.environ OPENSSL_VERSION ] ;\n \n #using clang : $(ARCH) : $(ANDROID_BINARIES_PATH)/clang++ :\n #<archiver>$(ANDROID_BINARIES_PATH)/llvm-ar\n@@ -56,6 +58,9 @@\n <linkflags>-Wl,-z,relro\n <linkflags>-Wl,-z,now\n <linkflags>-lc++_shared\n+<linkflags>-L$(OPENSSL_BUILD_PATH)\n+<linkflags>-lcrypto$(OPENSSL_VERSION)\n+<linkflags>-lssl$(OPENSSL_VERSION)\n <linkflags>-L$(PYTHON_ROOT)\n <linkflags>-lpython$(PYTHON_LINK_VERSION)\n <linkflags>-Wl,-O1\n"
  },
  {
    "path": "pythonforandroid/recipes/libtribler/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\"\"\"\nPrivacy with BitTorrent and resilient to shut down\n\nhttp://www.tribler.org\n\"\"\"\n\n\nclass LibTriblerRecipe(PythonRecipe):\n\n    version = 'devel'\n\n    url = 'git+https://github.com/Tribler/tribler.git'\n\n    depends = ['apsw', 'cryptography', 'ffmpeg', 'libsodium', 'libtorrent', 'm2crypto',\n               'netifaces', 'openssl', 'pil', 'pycrypto', 'pyleveldb', 'twisted',\n              ]\n\n    conflicts = ['python3']\n\n    python_depends = ['chardet', 'cherrypy', 'configobj', 'decorator', 'feedparser',\n                      'libnacl', 'pyasn1', 'requests', 'six',\n                     ]\n\n    site_packages_name = 'Tribler'\n\n\nrecipe = LibTriblerRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libvorbis/__init__.py",
    "content": "from pythonforandroid.recipe import NDKRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nfrom os.path import join\nimport sh\n\n\nclass VorbisRecipe(NDKRecipe):\n    version = '1.3.6'\n    url = 'http://downloads.xiph.org/releases/vorbis/libvorbis-{version}.tar.gz'\n    opt_depends = ['libogg']\n\n    generated_libraries = ['libvorbis.so', 'libvorbisfile.so', 'libvorbisenc.so']\n\n    def get_recipe_env(self, arch=None):\n        env = super().get_recipe_env(arch)\n        ogg = self.get_recipe('libogg', self.ctx)\n        env['CFLAGS'] += ' -I{}'.format(join(ogg.get_build_dir(arch.arch), 'include'))\n        return env\n\n    def build_arch(self, arch):\n        with current_directory(self.get_build_dir(arch.arch)):\n            env = self.get_recipe_env(arch)\n            flags = [\n                '--host=' + arch.command_prefix,\n            ]\n            configure = sh.Command('./configure')\n            shprint(configure, *flags, _env=env)\n            shprint(sh.make, _env=env)\n            self.install_libs(\n                arch,\n                join('lib', '.libs', 'libvorbis.so'),\n                join('lib', '.libs', 'libvorbisfile.so'),\n                join('lib', '.libs', 'libvorbisenc.so'))\n\n\nrecipe = VorbisRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libvpx/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nfrom os.path import join, realpath\nfrom multiprocessing import cpu_count\nimport sh\n\n\nTARGETS = {\n    'armeabi-v7a': 'armv7-android-gcc',\n    'arm64-v8a': 'arm64-android-gcc',\n    'x86': 'x86-android-gcc',\n    'x86_64': 'x86_64-android-gcc',\n}\n\n\nclass VPXRecipe(Recipe):\n    version = '1.11.0'\n    url = 'https://github.com/webmproject/libvpx/archive/v{version}.tar.gz'\n\n    patches = [\n        # See https://git.io/Jq50q\n        join('patches', '0001-android-force-neon-runtime.patch'),\n    ]\n\n    def get_recipe_env(self, arch=None):\n        env = super().get_recipe_env(arch)\n        env['CXXFLAGS'] += f' -I{self.ctx.ndk.libcxx_include_dir}'\n        return env\n\n    def build_arch(self, arch):\n        with current_directory(self.get_build_dir(arch.arch)):\n            env = self.get_recipe_env(arch)\n            flags = [\n                '--target=' + TARGETS[arch.arch],\n                '--enable-pic',\n                '--enable-vp8',\n                '--enable-vp9',\n                '--enable-static',\n                '--enable-small',\n                '--disable-shared',\n                '--disable-examples',\n                '--disable-unit-tests',\n                '--disable-tools',\n                '--disable-docs',\n                '--disable-install-docs',\n                '--disable-realtime-only',\n                f'--prefix={realpath(\".\")}',\n            ]\n\n            if arch.arch == 'armeabi-v7a':\n                flags.append('--disable-neon-asm')\n\n            configure = sh.Command('./configure')\n            shprint(configure, *flags, _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n            shprint(sh.make, 'install', _env=env)\n\n\nrecipe = VPXRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libvpx/patches/0001-android-force-neon-runtime.patch",
    "content": "diff -u -r ../libvpx-1.6.1/vpx_ports/arm_cpudetect.c ./vpx_ports/arm_cpudetect.c\n--- ../libvpx-1.6.1/vpx_ports/arm_cpudetect.c\t2017-01-12 21:27:27.000000000 +0100\n+++ ./vpx_ports/arm_cpudetect.c\t2017-01-29 23:55:05.399283897 +0100\n@@ -92,20 +92,17 @@\n }\n \n #elif defined(__ANDROID__) /* end _MSC_VER */\n-#include <cpu-features.h>\n \n int arm_cpu_caps(void) {\n   int flags;\n   int mask;\n-  uint64_t features;\n   if (!arm_cpu_env_flags(&flags)) {\n     return flags;\n   }\n   mask = arm_cpu_env_mask();\n-  features = android_getCpuFeatures();\n \n #if HAVE_NEON || HAVE_NEON_ASM\n-  if (features & ANDROID_CPU_ARM_FEATURE_NEON) flags |= HAS_NEON;\n+  flags |= HAS_NEON;\n #endif /* HAVE_NEON || HAVE_NEON_ASM */\n   return flags & mask;\n }\n"
  },
  {
    "path": "pythonforandroid/recipes/libwebp/__init__.py",
    "content": "from multiprocessing import cpu_count\nfrom os.path import join\n\nimport sh\n\nfrom pythonforandroid.util import current_directory, ensure_dir\nfrom pythonforandroid.toolchain import shprint\nfrom pythonforandroid.recipe import Recipe\n\n\nclass LibwebpRecipe(Recipe):\n    version = '1.1.0'\n    url = 'https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-{version}.tar.gz'  # noqa\n    depends = []\n    built_libraries = {\n        'libwebp.so': 'installation/lib',\n        'libwebpdecoder.so': 'installation/lib',\n        'libwebpdemux.so': 'installation/lib',\n        'libwebpmux.so': 'installation/lib',\n    }\n\n    def build_arch(self, arch):\n        source_dir = self.get_build_dir(arch.arch)\n        build_dir = join(source_dir, 'build')\n        install_dir = join(source_dir, 'installation')\n        toolchain_file = join(\n            self.ctx.ndk_dir, 'build', 'cmake', 'android.toolchain.cmake',\n        )\n\n        ensure_dir(build_dir)\n        with current_directory(build_dir):\n            env = self.get_recipe_env(arch)\n            shprint(sh.cmake, source_dir,\n                    f'-DANDROID_ABI={arch.arch}',\n                    f'-DANDROID_NATIVE_API_LEVEL={self.ctx.ndk_api}',\n\n                    f'-DCMAKE_TOOLCHAIN_FILE={toolchain_file}',\n                    f'-DCMAKE_INSTALL_PREFIX={install_dir}',\n                    '-DCMAKE_BUILD_TYPE=Release',\n\n                    '-DBUILD_SHARED_LIBS=1',\n\n                    _env=env)\n            shprint(sh.make, '-j' + str(cpu_count()), _env=env)\n            # We make the install because this way we will have\n            # all the includes and libraries in one place\n            shprint(sh.make, 'install', _env=env)\n\n\nrecipe = LibwebpRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libx264/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom multiprocessing import cpu_count\nfrom os.path import realpath\nimport sh\n\n\nclass LibX264Recipe(Recipe):\n    version = '5db6aa6cab1b146e07b60cc1736a01f21da01154'  # commit of latest known stable version\n    url = 'https://code.videolan.org/videolan/x264/-/archive/{version}/x264-{version}.zip'\n    built_libraries = {'libx264.a': 'lib'}\n\n    def build_arch(self, arch):\n        with current_directory(self.get_build_dir(arch.arch)):\n            env = self.get_recipe_env(arch)\n            configure = sh.Command('./configure')\n            shprint(configure,\n                    f'--host={arch.command_prefix}',\n                    '--disable-asm',\n                    '--disable-cli',\n                    '--enable-pic',\n                    '--enable-static',\n                    '--prefix={}'.format(realpath('.')),\n                    _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n            shprint(sh.make, 'install', _env=env)\n\n\nrecipe = LibX264Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libxml2/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom os.path import exists\nimport sh\n\n\nclass Libxml2Recipe(Recipe):\n    version = '2.9.12'\n    url = 'http://xmlsoft.org/sources/libxml2-{version}.tar.gz'\n    depends = []\n    patches = ['add-glob.c.patch']\n    built_libraries = {'libxml2.a': '.libs'}\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n\n            if not exists('configure'):\n                shprint(sh.Command('./autogen.sh'), _env=env)\n            shprint(sh.Command('autoreconf'), '-vif', _env=env)\n            build_arch = shprint(\n                sh.gcc, '-dumpmachine').stdout.decode('utf-8').split('\\n')[0]\n            shprint(sh.Command('./configure'),\n                    '--build=' + build_arch,\n                    '--host=' + arch.command_prefix,\n                    '--target=' + arch.command_prefix,\n                    '--without-modules',\n                    '--without-legacy',\n                    '--without-history',\n                    '--without-debug',\n                    '--without-docbook',\n                    '--without-python',\n                    '--without-threads',\n                    '--without-iconv',\n                    '--without-lzma',\n                    '--disable-shared',\n                    '--enable-static',\n                    _env=env)\n\n            # Ensure we only build libxml2.la as if we do everything\n            # we'll need the glob dependency which is a big headache\n            shprint(sh.make, \"libxml2.la\", _env=env)\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['CONFIG_SHELL'] = '/bin/bash'\n        env['SHELL'] = '/bin/bash'\n        env['CC'] += ' -I' + self.get_build_dir(arch.arch)\n        return env\n\n\nrecipe = Libxml2Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libxml2/add-glob.c.patch",
    "content": "From c97da18834aa41637e3e550bccb70bd2dd0ca3b9 Mon Sep 17 00:00:00 2001\nFrom: Zachary Goldberg <zach@zachgoldberg.com>\nDate: Wed, 20 Apr 2016 21:21:52 -0700\nSubject: [PATCH] Add glob\n\n---\n glob.c | 906 ++++++++++++++++++++++++++++++++\n glob.h | 105 ++++\n 2 files changed, 1011 insertions(+)\n create mode 100644 glob.c\n create mode 100644 glob.h\n\ndiff --git a/glob.c b/glob.c\nnew file mode 100644\nindex 0000000..cec80ed\n--- /dev/null\n+++ b/glob.c\n@@ -0,0 +1,906 @@\n+/*\n+ * Natanael Arndt, 2011: removed collate.h dependencies\n+ *  (my changes are trivial)\n+ *\n+ * Copyright (c) 1989, 1993\n+ *\tThe Regents of the University of California.  All rights reserved.\n+ *\n+ * This code is derived from software contributed to Berkeley by\n+ * Guido van Rossum.\n+ *\n+ * Redistribution and use in source and binary forms, with or without\n+ * modification, are permitted provided that the following conditions\n+ * are met:\n+ * 1. Redistributions of source code must retain the above copyright\n+ *    notice, this list of conditions and the following disclaimer.\n+ * 2. Redistributions in binary form must reproduce the above copyright\n+ *    notice, this list of conditions and the following disclaimer in the\n+ *    documentation and/or other materials provided with the distribution.\n+ * 4. Neither the name of the University nor the names of its contributors\n+ *    may be used to endorse or promote products derived from this software\n+ *    without specific prior written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n+ * SUCH DAMAGE.\n+ */\n+\n+#if defined(LIBC_SCCS) && !defined(lint)\n+static char sccsid[] = \"@(#)glob.c\t8.3 (Berkeley) 10/13/93\";\n+#endif /* LIBC_SCCS and not lint */\n+#include <sys/cdefs.h>\n+__FBSDID(\"$FreeBSD$\");\n+\n+/*\n+ * glob(3) -- a superset of the one defined in POSIX 1003.2.\n+ *\n+ * The [!...] convention to negate a range is supported (SysV, Posix, ksh).\n+ *\n+ * Optional extra services, controlled by flags not defined by POSIX:\n+ *\n+ * GLOB_QUOTE:\n+ *\tEscaping convention: \\ inhibits any special meaning the following\n+ *\tcharacter might have (except \\ at end of string is retained).\n+ * GLOB_MAGCHAR:\n+ *\tSet in gl_flags if pattern contained a globbing character.\n+ * GLOB_NOMAGIC:\n+ *\tSame as GLOB_NOCHECK, but it will only append pattern if it did\n+ *\tnot contain any magic characters.  [Used in csh style globbing]\n+ * GLOB_ALTDIRFUNC:\n+ *\tUse alternately specified directory access functions.\n+ * GLOB_TILDE:\n+ *\texpand ~user/foo to the /home/dir/of/user/foo\n+ * GLOB_BRACE:\n+ *\texpand {1,2}{a,b} to 1a 1b 2a 2b\n+ * gl_matchc:\n+ *\tNumber of matches in the current invocation of glob.\n+ */\n+\n+/*\n+ * Some notes on multibyte character support:\n+ * 1. Patterns with illegal byte sequences match nothing - even if\n+ *    GLOB_NOCHECK is specified.\n+ * 2. Illegal byte sequences in filenames are handled by treating them as\n+ *    single-byte characters with a value of the first byte of the sequence\n+ *    cast to wchar_t.\n+ * 3. State-dependent encodings are not currently supported.\n+ */\n+\n+#include <sys/param.h>\n+#include <sys/stat.h>\n+\n+#include <ctype.h>\n+#include <dirent.h>\n+#include <errno.h>\n+#include <glob.h>\n+#include <limits.h>\n+#include <pwd.h>\n+#include <stdint.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <unistd.h>\n+#include <wchar.h>\n+\n+#define\tDOLLAR\t\t'$'\n+#define\tDOT\t\t'.'\n+#define\tEOS\t\t'\\0'\n+#define\tLBRACKET\t'['\n+#define\tNOT\t\t'!'\n+#define\tQUESTION\t'?'\n+#define\tQUOTE\t\t'\\\\'\n+#define\tRANGE\t\t'-'\n+#define\tRBRACKET\t']'\n+#define\tSEP\t\t'/'\n+#define\tSTAR\t\t'*'\n+#define\tTILDE\t\t'~'\n+#define\tUNDERSCORE\t'_'\n+#define\tLBRACE\t\t'{'\n+#define\tRBRACE\t\t'}'\n+#define\tSLASH\t\t'/'\n+#define\tCOMMA\t\t','\n+\n+#ifndef DEBUG\n+\n+#define\tM_QUOTE\t\t0x8000000000ULL\n+#define\tM_PROTECT\t0x4000000000ULL\n+#define\tM_MASK\t\t0xffffffffffULL\n+#define\tM_CHAR\t\t0x00ffffffffULL\n+\n+typedef uint_fast64_t Char;\n+\n+#else\n+\n+#define\tM_QUOTE\t\t0x80\n+#define\tM_PROTECT\t0x40\n+#define\tM_MASK\t\t0xff\n+#define\tM_CHAR\t\t0x7f\n+\n+typedef char Char;\n+\n+#endif\n+\n+\n+#define\tCHAR(c)\t\t((Char)((c)&M_CHAR))\n+#define\tMETA(c)\t\t((Char)((c)|M_QUOTE))\n+#define\tM_ALL\t\tMETA('*')\n+#define\tM_END\t\tMETA(']')\n+#define\tM_NOT\t\tMETA('!')\n+#define\tM_ONE\t\tMETA('?')\n+#define\tM_RNG\t\tMETA('-')\n+#define\tM_SET\t\tMETA('[')\n+#define\tismeta(c)\t(((c)&M_QUOTE) != 0)\n+\n+\n+static int\t compare(const void *, const void *);\n+static int\t g_Ctoc(const Char *, char *, size_t);\n+static int\t g_lstat(Char *, struct stat *, glob_t *);\n+static DIR\t*g_opendir(Char *, glob_t *);\n+static const Char *g_strchr(const Char *, wchar_t);\n+#ifdef notdef\n+static Char\t*g_strcat(Char *, const Char *);\n+#endif\n+static int\t g_stat(Char *, struct stat *, glob_t *);\n+static int\t glob0(const Char *, glob_t *, size_t *);\n+static int\t glob1(Char *, glob_t *, size_t *);\n+static int\t glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *);\n+static int\t glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *);\n+static int\t globextend(const Char *, glob_t *, size_t *);\n+static const Char *\t\n+\t\t globtilde(const Char *, Char *, size_t, glob_t *);\n+static int\t globexp1(const Char *, glob_t *, size_t *);\n+static int\t globexp2(const Char *, const Char *, glob_t *, int *, size_t *);\n+static int\t match(Char *, Char *, Char *);\n+#ifdef DEBUG\n+static void\t qprintf(const char *, Char *);\n+#endif\n+\n+int\n+glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob)\n+{\n+\tconst char *patnext;\n+\tsize_t limit;\n+\tChar *bufnext, *bufend, patbuf[MAXPATHLEN], prot;\n+\tmbstate_t mbs;\n+\twchar_t wc;\n+\tsize_t clen;\n+\n+\tpatnext = pattern;\n+\tif (!(flags & GLOB_APPEND)) {\n+\t\tpglob->gl_pathc = 0;\n+\t\tpglob->gl_pathv = NULL;\n+\t\tif (!(flags & GLOB_DOOFFS))\n+\t\t\tpglob->gl_offs = 0;\n+\t}\n+\tif (flags & GLOB_LIMIT) {\n+\t\tlimit = pglob->gl_matchc;\n+\t\tif (limit == 0)\n+\t\t\tlimit = ARG_MAX;\n+\t} else\n+\t\tlimit = 0;\n+\tpglob->gl_flags = flags & ~GLOB_MAGCHAR;\n+\tpglob->gl_errfunc = errfunc;\n+\tpglob->gl_matchc = 0;\n+\n+\tbufnext = patbuf;\n+\tbufend = bufnext + MAXPATHLEN - 1;\n+\tif (flags & GLOB_NOESCAPE) {\n+\t\tmemset(&mbs, 0, sizeof(mbs));\n+\t\twhile (bufend - bufnext >= MB_CUR_MAX) {\n+\t\t\tclen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);\n+\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2)\n+\t\t\t\treturn (GLOB_NOMATCH);\n+\t\t\telse if (clen == 0)\n+\t\t\t\tbreak;\n+\t\t\t*bufnext++ = wc;\n+\t\t\tpatnext += clen;\n+\t\t}\n+\t} else {\n+\t\t/* Protect the quoted characters. */\n+\t\tmemset(&mbs, 0, sizeof(mbs));\n+\t\twhile (bufend - bufnext >= MB_CUR_MAX) {\n+\t\t\tif (*patnext == QUOTE) {\n+\t\t\t\tif (*++patnext == EOS) {\n+\t\t\t\t\t*bufnext++ = QUOTE | M_PROTECT;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t\tprot = M_PROTECT;\n+\t\t\t} else\n+\t\t\t\tprot = 0;\n+\t\t\tclen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);\n+\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2)\n+\t\t\t\treturn (GLOB_NOMATCH);\n+\t\t\telse if (clen == 0)\n+\t\t\t\tbreak;\n+\t\t\t*bufnext++ = wc | prot;\n+\t\t\tpatnext += clen;\n+\t\t}\n+\t}\n+\t*bufnext = EOS;\n+\n+\tif (flags & GLOB_BRACE)\n+\t    return globexp1(patbuf, pglob, &limit);\n+\telse\n+\t    return glob0(patbuf, pglob, &limit);\n+}\n+\n+/*\n+ * Expand recursively a glob {} pattern. When there is no more expansion\n+ * invoke the standard globbing routine to glob the rest of the magic\n+ * characters\n+ */\n+static int\n+globexp1(const Char *pattern, glob_t *pglob, size_t *limit)\n+{\n+\tconst Char* ptr = pattern;\n+\tint rv;\n+\n+\t/* Protect a single {}, for find(1), like csh */\n+\tif (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)\n+\t\treturn glob0(pattern, pglob, limit);\n+\n+\twhile ((ptr = g_strchr(ptr, LBRACE)) != NULL)\n+\t\tif (!globexp2(ptr, pattern, pglob, &rv, limit))\n+\t\t\treturn rv;\n+\n+\treturn glob0(pattern, pglob, limit);\n+}\n+\n+\n+/*\n+ * Recursive brace globbing helper. Tries to expand a single brace.\n+ * If it succeeds then it invokes globexp1 with the new pattern.\n+ * If it fails then it tries to glob the rest of the pattern and returns.\n+ */\n+static int\n+globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit)\n+{\n+\tint     i;\n+\tChar   *lm, *ls;\n+\tconst Char *pe, *pm, *pm1, *pl;\n+\tChar    patbuf[MAXPATHLEN];\n+\n+\t/* copy part up to the brace */\n+\tfor (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)\n+\t\tcontinue;\n+\t*lm = EOS;\n+\tls = lm;\n+\n+\t/* Find the balanced brace */\n+\tfor (i = 0, pe = ++ptr; *pe; pe++)\n+\t\tif (*pe == LBRACKET) {\n+\t\t\t/* Ignore everything between [] */\n+\t\t\tfor (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)\n+\t\t\t\tcontinue;\n+\t\t\tif (*pe == EOS) {\n+\t\t\t\t/*\n+\t\t\t\t * We could not find a matching RBRACKET.\n+\t\t\t\t * Ignore and just look for RBRACE\n+\t\t\t\t */\n+\t\t\t\tpe = pm;\n+\t\t\t}\n+\t\t}\n+\t\telse if (*pe == LBRACE)\n+\t\t\ti++;\n+\t\telse if (*pe == RBRACE) {\n+\t\t\tif (i == 0)\n+\t\t\t\tbreak;\n+\t\t\ti--;\n+\t\t}\n+\n+\t/* Non matching braces; just glob the pattern */\n+\tif (i != 0 || *pe == EOS) {\n+\t\t*rv = glob0(patbuf, pglob, limit);\n+\t\treturn 0;\n+\t}\n+\n+\tfor (i = 0, pl = pm = ptr; pm <= pe; pm++)\n+\t\tswitch (*pm) {\n+\t\tcase LBRACKET:\n+\t\t\t/* Ignore everything between [] */\n+\t\t\tfor (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++)\n+\t\t\t\tcontinue;\n+\t\t\tif (*pm == EOS) {\n+\t\t\t\t/*\n+\t\t\t\t * We could not find a matching RBRACKET.\n+\t\t\t\t * Ignore and just look for RBRACE\n+\t\t\t\t */\n+\t\t\t\tpm = pm1;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase LBRACE:\n+\t\t\ti++;\n+\t\t\tbreak;\n+\n+\t\tcase RBRACE:\n+\t\t\tif (i) {\n+\t\t\t    i--;\n+\t\t\t    break;\n+\t\t\t}\n+\t\t\t/* FALLTHROUGH */\n+\t\tcase COMMA:\n+\t\t\tif (i && *pm == COMMA)\n+\t\t\t\tbreak;\n+\t\t\telse {\n+\t\t\t\t/* Append the current string */\n+\t\t\t\tfor (lm = ls; (pl < pm); *lm++ = *pl++)\n+\t\t\t\t\tcontinue;\n+\t\t\t\t/*\n+\t\t\t\t * Append the rest of the pattern after the\n+\t\t\t\t * closing brace\n+\t\t\t\t */\n+\t\t\t\tfor (pl = pe + 1; (*lm++ = *pl++) != EOS;)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\t/* Expand the current pattern */\n+#ifdef DEBUG\n+\t\t\t\tqprintf(\"globexp2:\", patbuf);\n+#endif\n+\t\t\t\t*rv = globexp1(patbuf, pglob, limit);\n+\n+\t\t\t\t/* move after the comma, to the next string */\n+\t\t\t\tpl = pm + 1;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t*rv = 0;\n+\treturn 0;\n+}\n+\n+\n+\n+/*\n+ * expand tilde from the passwd file.\n+ */\n+static const Char *\n+globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)\n+{\n+\tstruct passwd *pwd;\n+\tchar *h;\n+\tconst Char *p;\n+\tChar *b, *eb;\n+\n+\tif (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))\n+\t\treturn pattern;\n+\n+\t/* \n+\t * Copy up to the end of the string or / \n+\t */\n+\teb = &patbuf[patbuf_len - 1];\n+\tfor (p = pattern + 1, h = (char *) patbuf;\n+\t    h < (char *)eb && *p && *p != SLASH; *h++ = *p++)\n+\t\tcontinue;\n+\n+\t*h = EOS;\n+\n+\tif (((char *) patbuf)[0] == EOS) {\n+\t\t/*\n+\t\t * handle a plain ~ or ~/ by expanding $HOME first (iff\n+\t\t * we're not running setuid or setgid) and then trying\n+\t\t * the password file\n+\t\t */\n+\t\tif (issetugid() != 0 ||\n+\t\t    (h = getenv(\"HOME\")) == NULL) {\n+\t\t\tif (((h = getlogin()) != NULL &&\n+\t\t\t     (pwd = getpwnam(h)) != NULL) ||\n+\t\t\t    (pwd = getpwuid(getuid())) != NULL)\n+\t\t\t\th = pwd->pw_dir;\n+\t\t\telse\n+\t\t\t\treturn pattern;\n+\t\t}\n+\t}\n+\telse {\n+\t\t/*\n+\t\t * Expand a ~user\n+\t\t */\n+\t\tif ((pwd = getpwnam((char*) patbuf)) == NULL)\n+\t\t\treturn pattern;\n+\t\telse\n+\t\t\th = pwd->pw_dir;\n+\t}\n+\n+\t/* Copy the home directory */\n+\tfor (b = patbuf; b < eb && *h; *b++ = *h++)\n+\t\tcontinue;\n+\n+\t/* Append the rest of the pattern */\n+\twhile (b < eb && (*b++ = *p++) != EOS)\n+\t\tcontinue;\n+\t*b = EOS;\n+\n+\treturn patbuf;\n+}\n+\n+\n+/*\n+ * The main glob() routine: compiles the pattern (optionally processing\n+ * quotes), calls glob1() to do the real pattern matching, and finally\n+ * sorts the list (unless unsorted operation is requested).  Returns 0\n+ * if things went well, nonzero if errors occurred.\n+ */\n+static int\n+glob0(const Char *pattern, glob_t *pglob, size_t *limit)\n+{\n+\tconst Char *qpatnext;\n+\tint err;\n+\tsize_t oldpathc;\n+\tChar *bufnext, c, patbuf[MAXPATHLEN];\n+\n+\tqpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob);\n+\toldpathc = pglob->gl_pathc;\n+\tbufnext = patbuf;\n+\n+\t/* We don't need to check for buffer overflow any more. */\n+\twhile ((c = *qpatnext++) != EOS) {\n+\t\tswitch (c) {\n+\t\tcase LBRACKET:\n+\t\t\tc = *qpatnext;\n+\t\t\tif (c == NOT)\n+\t\t\t\t++qpatnext;\n+\t\t\tif (*qpatnext == EOS ||\n+\t\t\t    g_strchr(qpatnext+1, RBRACKET) == NULL) {\n+\t\t\t\t*bufnext++ = LBRACKET;\n+\t\t\t\tif (c == NOT)\n+\t\t\t\t\t--qpatnext;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\t*bufnext++ = M_SET;\n+\t\t\tif (c == NOT)\n+\t\t\t\t*bufnext++ = M_NOT;\n+\t\t\tc = *qpatnext++;\n+\t\t\tdo {\n+\t\t\t\t*bufnext++ = CHAR(c);\n+\t\t\t\tif (*qpatnext == RANGE &&\n+\t\t\t\t    (c = qpatnext[1]) != RBRACKET) {\n+\t\t\t\t\t*bufnext++ = M_RNG;\n+\t\t\t\t\t*bufnext++ = CHAR(c);\n+\t\t\t\t\tqpatnext += 2;\n+\t\t\t\t}\n+\t\t\t} while ((c = *qpatnext++) != RBRACKET);\n+\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n+\t\t\t*bufnext++ = M_END;\n+\t\t\tbreak;\n+\t\tcase QUESTION:\n+\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n+\t\t\t*bufnext++ = M_ONE;\n+\t\t\tbreak;\n+\t\tcase STAR:\n+\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n+\t\t\t/* collapse adjacent stars to one,\n+\t\t\t * to avoid exponential behavior\n+\t\t\t */\n+\t\t\tif (bufnext == patbuf || bufnext[-1] != M_ALL)\n+\t\t\t    *bufnext++ = M_ALL;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\t*bufnext++ = CHAR(c);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\t*bufnext = EOS;\n+#ifdef DEBUG\n+\tqprintf(\"glob0:\", patbuf);\n+#endif\n+\n+\tif ((err = glob1(patbuf, pglob, limit)) != 0)\n+\t\treturn(err);\n+\n+\t/*\n+\t * If there was no match we are going to append the pattern\n+\t * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified\n+\t * and the pattern did not contain any magic characters\n+\t * GLOB_NOMAGIC is there just for compatibility with csh.\n+\t */\n+\tif (pglob->gl_pathc == oldpathc) {\n+\t\tif (((pglob->gl_flags & GLOB_NOCHECK) ||\n+\t\t    ((pglob->gl_flags & GLOB_NOMAGIC) &&\n+\t\t\t!(pglob->gl_flags & GLOB_MAGCHAR))))\n+\t\t\treturn(globextend(pattern, pglob, limit));\n+\t\telse\n+\t\t\treturn(GLOB_NOMATCH);\n+\t}\n+\tif (!(pglob->gl_flags & GLOB_NOSORT))\n+\t\tqsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,\n+\t\t    pglob->gl_pathc - oldpathc, sizeof(char *), compare);\n+\treturn(0);\n+}\n+\n+static int\n+compare(const void *p, const void *q)\n+{\n+\treturn(strcmp(*(char **)p, *(char **)q));\n+}\n+\n+static int\n+glob1(Char *pattern, glob_t *pglob, size_t *limit)\n+{\n+\tChar pathbuf[MAXPATHLEN];\n+\n+\t/* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */\n+\tif (*pattern == EOS)\n+\t\treturn(0);\n+\treturn(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1,\n+\t    pattern, pglob, limit));\n+}\n+\n+/*\n+ * The functions glob2 and glob3 are mutually recursive; there is one level\n+ * of recursion for each segment in the pattern that contains one or more\n+ * meta characters.\n+ */\n+static int\n+glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern,\n+      glob_t *pglob, size_t *limit)\n+{\n+\tstruct stat sb;\n+\tChar *p, *q;\n+\tint anymeta;\n+\n+\t/*\n+\t * Loop over pattern segments until end of pattern or until\n+\t * segment with meta character found.\n+\t */\n+\tfor (anymeta = 0;;) {\n+\t\tif (*pattern == EOS) {\t\t/* End of pattern? */\n+\t\t\t*pathend = EOS;\n+\t\t\tif (g_lstat(pathbuf, &sb, pglob))\n+\t\t\t\treturn(0);\n+\n+\t\t\tif (((pglob->gl_flags & GLOB_MARK) &&\n+\t\t\t    pathend[-1] != SEP) && (S_ISDIR(sb.st_mode)\n+\t\t\t    || (S_ISLNK(sb.st_mode) &&\n+\t\t\t    (g_stat(pathbuf, &sb, pglob) == 0) &&\n+\t\t\t    S_ISDIR(sb.st_mode)))) {\n+\t\t\t\tif (pathend + 1 > pathend_last)\n+\t\t\t\t\treturn (GLOB_ABORTED);\n+\t\t\t\t*pathend++ = SEP;\n+\t\t\t\t*pathend = EOS;\n+\t\t\t}\n+\t\t\t++pglob->gl_matchc;\n+\t\t\treturn(globextend(pathbuf, pglob, limit));\n+\t\t}\n+\n+\t\t/* Find end of next segment, copy tentatively to pathend. */\n+\t\tq = pathend;\n+\t\tp = pattern;\n+\t\twhile (*p != EOS && *p != SEP) {\n+\t\t\tif (ismeta(*p))\n+\t\t\t\tanymeta = 1;\n+\t\t\tif (q + 1 > pathend_last)\n+\t\t\t\treturn (GLOB_ABORTED);\n+\t\t\t*q++ = *p++;\n+\t\t}\n+\n+\t\tif (!anymeta) {\t\t/* No expansion, do next segment. */\n+\t\t\tpathend = q;\n+\t\t\tpattern = p;\n+\t\t\twhile (*pattern == SEP) {\n+\t\t\t\tif (pathend + 1 > pathend_last)\n+\t\t\t\t\treturn (GLOB_ABORTED);\n+\t\t\t\t*pathend++ = *pattern++;\n+\t\t\t}\n+\t\t} else\t\t\t/* Need expansion, recurse. */\n+\t\t\treturn(glob3(pathbuf, pathend, pathend_last, pattern, p,\n+\t\t\t    pglob, limit));\n+\t}\n+\t/* NOTREACHED */\n+}\n+\n+static int\n+glob3(Char *pathbuf, Char *pathend, Char *pathend_last,\n+      Char *pattern, Char *restpattern,\n+      glob_t *pglob, size_t *limit)\n+{\n+\tstruct dirent *dp;\n+\tDIR *dirp;\n+\tint err;\n+\tchar buf[MAXPATHLEN];\n+\n+\t/*\n+\t * The readdirfunc declaration can't be prototyped, because it is\n+\t * assigned, below, to two functions which are prototyped in glob.h\n+\t * and dirent.h as taking pointers to differently typed opaque\n+\t * structures.\n+\t */\n+\tstruct dirent *(*readdirfunc)();\n+\n+\tif (pathend > pathend_last)\n+\t\treturn (GLOB_ABORTED);\n+\t*pathend = EOS;\n+\terrno = 0;\n+\n+\tif ((dirp = g_opendir(pathbuf, pglob)) == NULL) {\n+\t\t/* TODO: don't call for ENOENT or ENOTDIR? */\n+\t\tif (pglob->gl_errfunc) {\n+\t\t\tif (g_Ctoc(pathbuf, buf, sizeof(buf)))\n+\t\t\t\treturn (GLOB_ABORTED);\n+\t\t\tif (pglob->gl_errfunc(buf, errno) ||\n+\t\t\t    pglob->gl_flags & GLOB_ERR)\n+\t\t\t\treturn (GLOB_ABORTED);\n+\t\t}\n+\t\treturn(0);\n+\t}\n+\n+\terr = 0;\n+\n+\t/* Search directory for matching names. */\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\treaddirfunc = pglob->gl_readdir;\n+\telse\n+\t\treaddirfunc = readdir;\n+\twhile ((dp = (*readdirfunc)(dirp))) {\n+\t\tchar *sc;\n+\t\tChar *dc;\n+\t\twchar_t wc;\n+\t\tsize_t clen;\n+\t\tmbstate_t mbs;\n+\n+\t\t/* Initial DOT must be matched literally. */\n+\t\tif (dp->d_name[0] == DOT && *pattern != DOT)\n+\t\t\tcontinue;\n+\t\tmemset(&mbs, 0, sizeof(mbs));\n+\t\tdc = pathend;\n+\t\tsc = dp->d_name;\n+\t\twhile (dc < pathend_last) {\n+\t\t\tclen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs);\n+\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2) {\n+\t\t\t\twc = *sc;\n+\t\t\t\tclen = 1;\n+\t\t\t\tmemset(&mbs, 0, sizeof(mbs));\n+\t\t\t}\n+\t\t\tif ((*dc++ = wc) == EOS)\n+\t\t\t\tbreak;\n+\t\t\tsc += clen;\n+\t\t}\n+\t\tif (!match(pathend, pattern, restpattern)) {\n+\t\t\t*pathend = EOS;\n+\t\t\tcontinue;\n+\t\t}\n+\t\terr = glob2(pathbuf, --dc, pathend_last, restpattern,\n+\t\t    pglob, limit);\n+\t\tif (err)\n+\t\t\tbreak;\n+\t}\n+\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\t(*pglob->gl_closedir)(dirp);\n+\telse\n+\t\tclosedir(dirp);\n+\treturn(err);\n+}\n+\n+\n+/*\n+ * Extend the gl_pathv member of a glob_t structure to accomodate a new item,\n+ * add the new item, and update gl_pathc.\n+ *\n+ * This assumes the BSD realloc, which only copies the block when its size\n+ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic\n+ * behavior.\n+ *\n+ * Return 0 if new item added, error code if memory couldn't be allocated.\n+ *\n+ * Invariant of the glob_t structure:\n+ *\tEither gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and\n+ *\tgl_pathv points to (gl_offs + gl_pathc + 1) items.\n+ */\n+static int\n+globextend(const Char *path, glob_t *pglob, size_t *limit)\n+{\n+\tchar **pathv;\n+\tsize_t i, newsize, len;\n+\tchar *copy;\n+\tconst Char *p;\n+\n+\tif (*limit && pglob->gl_pathc > *limit) {\n+\t\terrno = 0;\n+\t\treturn (GLOB_NOSPACE);\n+\t}\n+\n+\tnewsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);\n+\tpathv = pglob->gl_pathv ?\n+\t\t    realloc((char *)pglob->gl_pathv, newsize) :\n+\t\t    malloc(newsize);\n+\tif (pathv == NULL) {\n+\t\tif (pglob->gl_pathv) {\n+\t\t\tfree(pglob->gl_pathv);\n+\t\t\tpglob->gl_pathv = NULL;\n+\t\t}\n+\t\treturn(GLOB_NOSPACE);\n+\t}\n+\n+\tif (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {\n+\t\t/* first time around -- clear initial gl_offs items */\n+\t\tpathv += pglob->gl_offs;\n+\t\tfor (i = pglob->gl_offs + 1; --i > 0; )\n+\t\t\t*--pathv = NULL;\n+\t}\n+\tpglob->gl_pathv = pathv;\n+\n+\tfor (p = path; *p++;)\n+\t\tcontinue;\n+\tlen = MB_CUR_MAX * (size_t)(p - path);\t/* XXX overallocation */\n+\tif ((copy = malloc(len)) != NULL) {\n+\t\tif (g_Ctoc(path, copy, len)) {\n+\t\t\tfree(copy);\n+\t\t\treturn (GLOB_NOSPACE);\n+\t\t}\n+\t\tpathv[pglob->gl_offs + pglob->gl_pathc++] = copy;\n+\t}\n+\tpathv[pglob->gl_offs + pglob->gl_pathc] = NULL;\n+\treturn(copy == NULL ? GLOB_NOSPACE : 0);\n+}\n+\n+/*\n+ * pattern matching function for filenames.  Each occurrence of the *\n+ * pattern causes a recursion level.\n+ */\n+static int\n+match(Char *name, Char *pat, Char *patend)\n+{\n+\tint ok, negate_range;\n+\tChar c, k;\n+\n+\twhile (pat < patend) {\n+\t\tc = *pat++;\n+\t\tswitch (c & M_MASK) {\n+\t\tcase M_ALL:\n+\t\t\tif (pat == patend)\n+\t\t\t\treturn(1);\n+\t\t\tdo\n+\t\t\t    if (match(name, pat, patend))\n+\t\t\t\t    return(1);\n+\t\t\twhile (*name++ != EOS);\n+\t\t\treturn(0);\n+\t\tcase M_ONE:\n+\t\t\tif (*name++ == EOS)\n+\t\t\t\treturn(0);\n+\t\t\tbreak;\n+\t\tcase M_SET:\n+\t\t\tok = 0;\n+\t\t\tif ((k = *name++) == EOS)\n+\t\t\t\treturn(0);\n+\t\t\tif ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)\n+\t\t\t\t++pat;\n+\t\t\twhile (((c = *pat++) & M_MASK) != M_END)\n+\t\t\t\tif ((*pat & M_MASK) == M_RNG) {\n+\t\t\t\t\tif (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1;\n+\t\t\t\t\tpat += 2;\n+\t\t\t\t} else if (c == k)\n+\t\t\t\t\tok = 1;\n+\t\t\tif (ok == negate_range)\n+\t\t\t\treturn(0);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tif (*name++ != c)\n+\t\t\t\treturn(0);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn(*name == EOS);\n+}\n+\n+/* Free allocated data belonging to a glob_t structure. */\n+void\n+globfree(glob_t *pglob)\n+{\n+\tsize_t i;\n+\tchar **pp;\n+\n+\tif (pglob->gl_pathv != NULL) {\n+\t\tpp = pglob->gl_pathv + pglob->gl_offs;\n+\t\tfor (i = pglob->gl_pathc; i--; ++pp)\n+\t\t\tif (*pp)\n+\t\t\t\tfree(*pp);\n+\t\tfree(pglob->gl_pathv);\n+\t\tpglob->gl_pathv = NULL;\n+\t}\n+}\n+\n+static DIR *\n+g_opendir(Char *str, glob_t *pglob)\n+{\n+\tchar buf[MAXPATHLEN];\n+\n+\tif (!*str)\n+\t\tstrcpy(buf, \".\");\n+\telse {\n+\t\tif (g_Ctoc(str, buf, sizeof(buf)))\n+\t\t\treturn (NULL);\n+\t}\n+\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\treturn((*pglob->gl_opendir)(buf));\n+\n+\treturn(opendir(buf));\n+}\n+\n+static int\n+g_lstat(Char *fn, struct stat *sb, glob_t *pglob)\n+{\n+\tchar buf[MAXPATHLEN];\n+\n+\tif (g_Ctoc(fn, buf, sizeof(buf))) {\n+\t\terrno = ENAMETOOLONG;\n+\t\treturn (-1);\n+\t}\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\treturn((*pglob->gl_lstat)(buf, sb));\n+\treturn(lstat(buf, sb));\n+}\n+\n+static int\n+g_stat(Char *fn, struct stat *sb, glob_t *pglob)\n+{\n+\tchar buf[MAXPATHLEN];\n+\n+\tif (g_Ctoc(fn, buf, sizeof(buf))) {\n+\t\terrno = ENAMETOOLONG;\n+\t\treturn (-1);\n+\t}\n+\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n+\t\treturn((*pglob->gl_stat)(buf, sb));\n+\treturn(stat(buf, sb));\n+}\n+\n+static const Char *\n+g_strchr(const Char *str, wchar_t ch)\n+{\n+\n+\tdo {\n+\t\tif (*str == ch)\n+\t\t\treturn (str);\n+\t} while (*str++);\n+\treturn (NULL);\n+}\n+\n+static int\n+g_Ctoc(const Char *str, char *buf, size_t len)\n+{\n+\tmbstate_t mbs;\n+\tsize_t clen;\n+\n+\tmemset(&mbs, 0, sizeof(mbs));\n+\twhile (len >= MB_CUR_MAX) {\n+\t\tclen = wcrtomb(buf, *str, &mbs);\n+\t\tif (clen == (size_t)-1)\n+\t\t\treturn (1);\n+\t\tif (*str == L'\\0')\n+\t\t\treturn (0);\n+\t\tstr++;\n+\t\tbuf += clen;\n+\t\tlen -= clen;\n+\t}\n+\treturn (1);\n+}\n+\n+#ifdef DEBUG\n+static void\n+qprintf(const char *str, Char *s)\n+{\n+\tChar *p;\n+\n+\t(void)printf(\"%s:\\n\", str);\n+\tfor (p = s; *p; p++)\n+\t\t(void)printf(\"%c\", CHAR(*p));\n+\t(void)printf(\"\\n\");\n+\tfor (p = s; *p; p++)\n+\t\t(void)printf(\"%c\", *p & M_PROTECT ? '\"' : ' ');\n+\t(void)printf(\"\\n\");\n+\tfor (p = s; *p; p++)\n+\t\t(void)printf(\"%c\", ismeta(*p) ? '_' : ' ');\n+\t(void)printf(\"\\n\");\n+}\n+#endif\ndiff --git a/glob.h b/glob.h\nnew file mode 100644\nindex 0000000..351b6c4\n--- /dev/null\n+++ b/glob.h\n@@ -0,0 +1,105 @@\n+/*\n+ * Copyright (c) 1989, 1993\n+ *\tThe Regents of the University of California.  All rights reserved.\n+ *\n+ * This code is derived from software contributed to Berkeley by\n+ * Guido van Rossum.\n+ *\n+ * Redistribution and use in source and binary forms, with or without\n+ * modification, are permitted provided that the following conditions\n+ * are met:\n+ * 1. Redistributions of source code must retain the above copyright\n+ *    notice, this list of conditions and the following disclaimer.\n+ * 2. Redistributions in binary form must reproduce the above copyright\n+ *    notice, this list of conditions and the following disclaimer in the\n+ *    documentation and/or other materials provided with the distribution.\n+ * 3. Neither the name of the University nor the names of its contributors\n+ *    may be used to endorse or promote products derived from this software\n+ *    without specific prior written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n+ * SUCH DAMAGE.\n+ *\n+ *\t@(#)glob.h\t8.1 (Berkeley) 6/2/93\n+ * $FreeBSD$\n+ */\n+\n+#ifndef _GLOB_H_\n+#define\t_GLOB_H_\n+\n+#include <sys/cdefs.h>\n+#include <sys/_types.h>\n+\n+#ifndef\t_SIZE_T_DECLARED\n+typedef\t__size_t\tsize_t;\n+#define\t_SIZE_T_DECLARED\n+#endif\n+\n+struct stat;\n+typedef struct {\n+\tsize_t gl_pathc;\t/* Count of total paths so far. */\n+\tsize_t gl_matchc;\t/* Count of paths matching pattern. */\n+\tsize_t gl_offs;\t\t/* Reserved at beginning of gl_pathv. */\n+\tint gl_flags;\t\t/* Copy of flags parameter to glob. */\n+\tchar **gl_pathv;\t/* List of paths matching pattern. */\n+\t\t\t\t/* Copy of errfunc parameter to glob. */\n+\tint (*gl_errfunc)(const char *, int);\n+\n+\t/*\n+\t * Alternate filesystem access methods for glob; replacement\n+\t * versions of closedir(3), readdir(3), opendir(3), stat(2)\n+\t * and lstat(2).\n+\t */\n+\tvoid (*gl_closedir)(void *);\n+\tstruct dirent *(*gl_readdir)(void *);\n+\tvoid *(*gl_opendir)(const char *);\n+\tint (*gl_lstat)(const char *, struct stat *);\n+\tint (*gl_stat)(const char *, struct stat *);\n+} glob_t;\n+\n+#if __POSIX_VISIBLE >= 199209\n+/* Believed to have been introduced in 1003.2-1992 */\n+#define\tGLOB_APPEND\t0x0001\t/* Append to output from previous call. */\n+#define\tGLOB_DOOFFS\t0x0002\t/* Use gl_offs. */\n+#define\tGLOB_ERR\t0x0004\t/* Return on error. */\n+#define\tGLOB_MARK\t0x0008\t/* Append / to matching directories. */\n+#define\tGLOB_NOCHECK\t0x0010\t/* Return pattern itself if nothing matches. */\n+#define\tGLOB_NOSORT\t0x0020\t/* Don't sort. */\n+#define\tGLOB_NOESCAPE\t0x2000\t/* Disable backslash escaping. */\n+\n+/* Error values returned by glob(3) */\n+#define\tGLOB_NOSPACE\t(-1)\t/* Malloc call failed. */\n+#define\tGLOB_ABORTED\t(-2)\t/* Unignored error. */\n+#define\tGLOB_NOMATCH\t(-3)\t/* No match and GLOB_NOCHECK was not set. */\n+#define\tGLOB_NOSYS\t(-4)\t/* Obsolete: source comptability only. */\n+#endif /* __POSIX_VISIBLE >= 199209 */\n+\n+#if __BSD_VISIBLE\n+#define\tGLOB_ALTDIRFUNC\t0x0040\t/* Use alternately specified directory funcs. */\n+#define\tGLOB_BRACE\t0x0080\t/* Expand braces ala csh. */\n+#define\tGLOB_MAGCHAR\t0x0100\t/* Pattern had globbing characters. */\n+#define\tGLOB_NOMAGIC\t0x0200\t/* GLOB_NOCHECK without magic chars (csh). */\n+#define\tGLOB_QUOTE\t0x0400\t/* Quote special chars with \\. */\n+#define\tGLOB_TILDE\t0x0800\t/* Expand tilde names from the passwd file. */\n+#define\tGLOB_LIMIT\t0x1000\t/* limit number of returned paths */\n+\n+/* source compatibility, these are the old names */\n+#define GLOB_MAXPATH\tGLOB_LIMIT\n+#define\tGLOB_ABEND\tGLOB_ABORTED\n+#endif /* __BSD_VISIBLE */\n+\n+__BEGIN_DECLS\n+int\tglob(const char *, int, int (*)(const char *, int), glob_t *);\n+void\tglobfree(glob_t *);\n+__END_DECLS\n+\n+#endif /* !_GLOB_H_ */\n-- \n1.9.1\n\n"
  },
  {
    "path": "pythonforandroid/recipes/libxml2/glob.c",
    "content": "/*\n * Natanael Arndt, 2011: removed collate.h dependencies\n *  (my changes are trivial)\n *\n * Copyright (c) 1989, 1993\n *\tThe Regents of the University of California.  All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Guido van Rossum.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 4. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#if defined(LIBC_SCCS) && !defined(lint)\nstatic char sccsid[] = \"@(#)glob.c\t8.3 (Berkeley) 10/13/93\";\n#endif /* LIBC_SCCS and not lint */\n#include <sys/cdefs.h>\n__FBSDID(\"$FreeBSD$\");\n\n/*\n * glob(3) -- a superset of the one defined in POSIX 1003.2.\n *\n * The [!...] convention to negate a range is supported (SysV, Posix, ksh).\n *\n * Optional extra services, controlled by flags not defined by POSIX:\n *\n * GLOB_QUOTE:\n *\tEscaping convention: \\ inhibits any special meaning the following\n *\tcharacter might have (except \\ at end of string is retained).\n * GLOB_MAGCHAR:\n *\tSet in gl_flags if pattern contained a globbing character.\n * GLOB_NOMAGIC:\n *\tSame as GLOB_NOCHECK, but it will only append pattern if it did\n *\tnot contain any magic characters.  [Used in csh style globbing]\n * GLOB_ALTDIRFUNC:\n *\tUse alternately specified directory access functions.\n * GLOB_TILDE:\n *\texpand ~user/foo to the /home/dir/of/user/foo\n * GLOB_BRACE:\n *\texpand {1,2}{a,b} to 1a 1b 2a 2b\n * gl_matchc:\n *\tNumber of matches in the current invocation of glob.\n */\n\n/*\n * Some notes on multibyte character support:\n * 1. Patterns with illegal byte sequences match nothing - even if\n *    GLOB_NOCHECK is specified.\n * 2. Illegal byte sequences in filenames are handled by treating them as\n *    single-byte characters with a value of the first byte of the sequence\n *    cast to wchar_t.\n * 3. State-dependent encodings are not currently supported.\n */\n\n#include <sys/param.h>\n#include <sys/stat.h>\n\n#include <ctype.h>\n#include <dirent.h>\n#include <errno.h>\n#include <glob.h>\n#include <limits.h>\n#include <pwd.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <wchar.h>\n\n#define\tDOLLAR\t\t'$'\n#define\tDOT\t\t'.'\n#define\tEOS\t\t'\\0'\n#define\tLBRACKET\t'['\n#define\tNOT\t\t'!'\n#define\tQUESTION\t'?'\n#define\tQUOTE\t\t'\\\\'\n#define\tRANGE\t\t'-'\n#define\tRBRACKET\t']'\n#define\tSEP\t\t'/'\n#define\tSTAR\t\t'*'\n#define\tTILDE\t\t'~'\n#define\tUNDERSCORE\t'_'\n#define\tLBRACE\t\t'{'\n#define\tRBRACE\t\t'}'\n#define\tSLASH\t\t'/'\n#define\tCOMMA\t\t','\n\n#ifndef DEBUG\n\n#define\tM_QUOTE\t\t0x8000000000ULL\n#define\tM_PROTECT\t0x4000000000ULL\n#define\tM_MASK\t\t0xffffffffffULL\n#define\tM_CHAR\t\t0x00ffffffffULL\n\ntypedef uint_fast64_t Char;\n\n#else\n\n#define\tM_QUOTE\t\t0x80\n#define\tM_PROTECT\t0x40\n#define\tM_MASK\t\t0xff\n#define\tM_CHAR\t\t0x7f\n\ntypedef char Char;\n\n#endif\n\n\n#define\tCHAR(c)\t\t((Char)((c)&M_CHAR))\n#define\tMETA(c)\t\t((Char)((c)|M_QUOTE))\n#define\tM_ALL\t\tMETA('*')\n#define\tM_END\t\tMETA(']')\n#define\tM_NOT\t\tMETA('!')\n#define\tM_ONE\t\tMETA('?')\n#define\tM_RNG\t\tMETA('-')\n#define\tM_SET\t\tMETA('[')\n#define\tismeta(c)\t(((c)&M_QUOTE) != 0)\n\n\nstatic int\t compare(const void *, const void *);\nstatic int\t g_Ctoc(const Char *, char *, size_t);\nstatic int\t g_lstat(Char *, struct stat *, glob_t *);\nstatic DIR\t*g_opendir(Char *, glob_t *);\nstatic const Char *g_strchr(const Char *, wchar_t);\n#ifdef notdef\nstatic Char\t*g_strcat(Char *, const Char *);\n#endif\nstatic int\t g_stat(Char *, struct stat *, glob_t *);\nstatic int\t glob0(const Char *, glob_t *, size_t *);\nstatic int\t glob1(Char *, glob_t *, size_t *);\nstatic int\t glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *);\nstatic int\t glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *);\nstatic int\t globextend(const Char *, glob_t *, size_t *);\nstatic const Char *\t\n\t\t globtilde(const Char *, Char *, size_t, glob_t *);\nstatic int\t globexp1(const Char *, glob_t *, size_t *);\nstatic int\t globexp2(const Char *, const Char *, glob_t *, int *, size_t *);\nstatic int\t match(Char *, Char *, Char *);\n#ifdef DEBUG\nstatic void\t qprintf(const char *, Char *);\n#endif\n\nint\nglob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob)\n{\n\tconst char *patnext;\n\tsize_t limit;\n\tChar *bufnext, *bufend, patbuf[MAXPATHLEN], prot;\n\tmbstate_t mbs;\n\twchar_t wc;\n\tsize_t clen;\n\n\tpatnext = pattern;\n\tif (!(flags & GLOB_APPEND)) {\n\t\tpglob->gl_pathc = 0;\n\t\tpglob->gl_pathv = NULL;\n\t\tif (!(flags & GLOB_DOOFFS))\n\t\t\tpglob->gl_offs = 0;\n\t}\n\tif (flags & GLOB_LIMIT) {\n\t\tlimit = pglob->gl_matchc;\n\t\tif (limit == 0)\n\t\t\tlimit = ARG_MAX;\n\t} else\n\t\tlimit = 0;\n\tpglob->gl_flags = flags & ~GLOB_MAGCHAR;\n\tpglob->gl_errfunc = errfunc;\n\tpglob->gl_matchc = 0;\n\n\tbufnext = patbuf;\n\tbufend = bufnext + MAXPATHLEN - 1;\n\tif (flags & GLOB_NOESCAPE) {\n\t\tmemset(&mbs, 0, sizeof(mbs));\n\t\twhile (bufend - bufnext >= MB_CUR_MAX) {\n\t\t\tclen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);\n\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2)\n\t\t\t\treturn (GLOB_NOMATCH);\n\t\t\telse if (clen == 0)\n\t\t\t\tbreak;\n\t\t\t*bufnext++ = wc;\n\t\t\tpatnext += clen;\n\t\t}\n\t} else {\n\t\t/* Protect the quoted characters. */\n\t\tmemset(&mbs, 0, sizeof(mbs));\n\t\twhile (bufend - bufnext >= MB_CUR_MAX) {\n\t\t\tif (*patnext == QUOTE) {\n\t\t\t\tif (*++patnext == EOS) {\n\t\t\t\t\t*bufnext++ = QUOTE | M_PROTECT;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprot = M_PROTECT;\n\t\t\t} else\n\t\t\t\tprot = 0;\n\t\t\tclen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);\n\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2)\n\t\t\t\treturn (GLOB_NOMATCH);\n\t\t\telse if (clen == 0)\n\t\t\t\tbreak;\n\t\t\t*bufnext++ = wc | prot;\n\t\t\tpatnext += clen;\n\t\t}\n\t}\n\t*bufnext = EOS;\n\n\tif (flags & GLOB_BRACE)\n\t    return globexp1(patbuf, pglob, &limit);\n\telse\n\t    return glob0(patbuf, pglob, &limit);\n}\n\n/*\n * Expand recursively a glob {} pattern. When there is no more expansion\n * invoke the standard globbing routine to glob the rest of the magic\n * characters\n */\nstatic int\nglobexp1(const Char *pattern, glob_t *pglob, size_t *limit)\n{\n\tconst Char* ptr = pattern;\n\tint rv;\n\n\t/* Protect a single {}, for find(1), like csh */\n\tif (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)\n\t\treturn glob0(pattern, pglob, limit);\n\n\twhile ((ptr = g_strchr(ptr, LBRACE)) != NULL)\n\t\tif (!globexp2(ptr, pattern, pglob, &rv, limit))\n\t\t\treturn rv;\n\n\treturn glob0(pattern, pglob, limit);\n}\n\n\n/*\n * Recursive brace globbing helper. Tries to expand a single brace.\n * If it succeeds then it invokes globexp1 with the new pattern.\n * If it fails then it tries to glob the rest of the pattern and returns.\n */\nstatic int\nglobexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit)\n{\n\tint     i;\n\tChar   *lm, *ls;\n\tconst Char *pe, *pm, *pm1, *pl;\n\tChar    patbuf[MAXPATHLEN];\n\n\t/* copy part up to the brace */\n\tfor (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)\n\t\tcontinue;\n\t*lm = EOS;\n\tls = lm;\n\n\t/* Find the balanced brace */\n\tfor (i = 0, pe = ++ptr; *pe; pe++)\n\t\tif (*pe == LBRACKET) {\n\t\t\t/* Ignore everything between [] */\n\t\t\tfor (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)\n\t\t\t\tcontinue;\n\t\t\tif (*pe == EOS) {\n\t\t\t\t/*\n\t\t\t\t * We could not find a matching RBRACKET.\n\t\t\t\t * Ignore and just look for RBRACE\n\t\t\t\t */\n\t\t\t\tpe = pm;\n\t\t\t}\n\t\t}\n\t\telse if (*pe == LBRACE)\n\t\t\ti++;\n\t\telse if (*pe == RBRACE) {\n\t\t\tif (i == 0)\n\t\t\t\tbreak;\n\t\t\ti--;\n\t\t}\n\n\t/* Non matching braces; just glob the pattern */\n\tif (i != 0 || *pe == EOS) {\n\t\t*rv = glob0(patbuf, pglob, limit);\n\t\treturn 0;\n\t}\n\n\tfor (i = 0, pl = pm = ptr; pm <= pe; pm++)\n\t\tswitch (*pm) {\n\t\tcase LBRACKET:\n\t\t\t/* Ignore everything between [] */\n\t\t\tfor (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++)\n\t\t\t\tcontinue;\n\t\t\tif (*pm == EOS) {\n\t\t\t\t/*\n\t\t\t\t * We could not find a matching RBRACKET.\n\t\t\t\t * Ignore and just look for RBRACE\n\t\t\t\t */\n\t\t\t\tpm = pm1;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase LBRACE:\n\t\t\ti++;\n\t\t\tbreak;\n\n\t\tcase RBRACE:\n\t\t\tif (i) {\n\t\t\t    i--;\n\t\t\t    break;\n\t\t\t}\n\t\t\t/* FALLTHROUGH */\n\t\tcase COMMA:\n\t\t\tif (i && *pm == COMMA)\n\t\t\t\tbreak;\n\t\t\telse {\n\t\t\t\t/* Append the current string */\n\t\t\t\tfor (lm = ls; (pl < pm); *lm++ = *pl++)\n\t\t\t\t\tcontinue;\n\t\t\t\t/*\n\t\t\t\t * Append the rest of the pattern after the\n\t\t\t\t * closing brace\n\t\t\t\t */\n\t\t\t\tfor (pl = pe + 1; (*lm++ = *pl++) != EOS;)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t/* Expand the current pattern */\n#ifdef DEBUG\n\t\t\t\tqprintf(\"globexp2:\", patbuf);\n#endif\n\t\t\t\t*rv = globexp1(patbuf, pglob, limit);\n\n\t\t\t\t/* move after the comma, to the next string */\n\t\t\t\tpl = pm + 1;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t*rv = 0;\n\treturn 0;\n}\n\n\n\n/*\n * expand tilde from the passwd file.\n */\nstatic const Char *\nglobtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)\n{\n\tstruct passwd *pwd;\n\tchar *h;\n\tconst Char *p;\n\tChar *b, *eb;\n\n\tif (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))\n\t\treturn pattern;\n\n\t/* \n\t * Copy up to the end of the string or / \n\t */\n\teb = &patbuf[patbuf_len - 1];\n\tfor (p = pattern + 1, h = (char *) patbuf;\n\t    h < (char *)eb && *p && *p != SLASH; *h++ = *p++)\n\t\tcontinue;\n\n\t*h = EOS;\n\n\tif (((char *) patbuf)[0] == EOS) {\n\t\t/*\n\t\t * handle a plain ~ or ~/ by expanding $HOME first (iff\n\t\t * we're not running setuid or setgid) and then trying\n\t\t * the password file\n\t\t */\n\t\tif (issetugid() != 0 ||\n\t\t    (h = getenv(\"HOME\")) == NULL) {\n\t\t\tif (((h = getlogin()) != NULL &&\n\t\t\t     (pwd = getpwnam(h)) != NULL) ||\n\t\t\t    (pwd = getpwuid(getuid())) != NULL)\n\t\t\t\th = pwd->pw_dir;\n\t\t\telse\n\t\t\t\treturn pattern;\n\t\t}\n\t}\n\telse {\n\t\t/*\n\t\t * Expand a ~user\n\t\t */\n\t\tif ((pwd = getpwnam((char*) patbuf)) == NULL)\n\t\t\treturn pattern;\n\t\telse\n\t\t\th = pwd->pw_dir;\n\t}\n\n\t/* Copy the home directory */\n\tfor (b = patbuf; b < eb && *h; *b++ = *h++)\n\t\tcontinue;\n\n\t/* Append the rest of the pattern */\n\twhile (b < eb && (*b++ = *p++) != EOS)\n\t\tcontinue;\n\t*b = EOS;\n\n\treturn patbuf;\n}\n\n\n/*\n * The main glob() routine: compiles the pattern (optionally processing\n * quotes), calls glob1() to do the real pattern matching, and finally\n * sorts the list (unless unsorted operation is requested).  Returns 0\n * if things went well, nonzero if errors occurred.\n */\nstatic int\nglob0(const Char *pattern, glob_t *pglob, size_t *limit)\n{\n\tconst Char *qpatnext;\n\tint err;\n\tsize_t oldpathc;\n\tChar *bufnext, c, patbuf[MAXPATHLEN];\n\n\tqpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob);\n\toldpathc = pglob->gl_pathc;\n\tbufnext = patbuf;\n\n\t/* We don't need to check for buffer overflow any more. */\n\twhile ((c = *qpatnext++) != EOS) {\n\t\tswitch (c) {\n\t\tcase LBRACKET:\n\t\t\tc = *qpatnext;\n\t\t\tif (c == NOT)\n\t\t\t\t++qpatnext;\n\t\t\tif (*qpatnext == EOS ||\n\t\t\t    g_strchr(qpatnext+1, RBRACKET) == NULL) {\n\t\t\t\t*bufnext++ = LBRACKET;\n\t\t\t\tif (c == NOT)\n\t\t\t\t\t--qpatnext;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t*bufnext++ = M_SET;\n\t\t\tif (c == NOT)\n\t\t\t\t*bufnext++ = M_NOT;\n\t\t\tc = *qpatnext++;\n\t\t\tdo {\n\t\t\t\t*bufnext++ = CHAR(c);\n\t\t\t\tif (*qpatnext == RANGE &&\n\t\t\t\t    (c = qpatnext[1]) != RBRACKET) {\n\t\t\t\t\t*bufnext++ = M_RNG;\n\t\t\t\t\t*bufnext++ = CHAR(c);\n\t\t\t\t\tqpatnext += 2;\n\t\t\t\t}\n\t\t\t} while ((c = *qpatnext++) != RBRACKET);\n\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n\t\t\t*bufnext++ = M_END;\n\t\t\tbreak;\n\t\tcase QUESTION:\n\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n\t\t\t*bufnext++ = M_ONE;\n\t\t\tbreak;\n\t\tcase STAR:\n\t\t\tpglob->gl_flags |= GLOB_MAGCHAR;\n\t\t\t/* collapse adjacent stars to one,\n\t\t\t * to avoid exponential behavior\n\t\t\t */\n\t\t\tif (bufnext == patbuf || bufnext[-1] != M_ALL)\n\t\t\t    *bufnext++ = M_ALL;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t*bufnext++ = CHAR(c);\n\t\t\tbreak;\n\t\t}\n\t}\n\t*bufnext = EOS;\n#ifdef DEBUG\n\tqprintf(\"glob0:\", patbuf);\n#endif\n\n\tif ((err = glob1(patbuf, pglob, limit)) != 0)\n\t\treturn(err);\n\n\t/*\n\t * If there was no match we are going to append the pattern\n\t * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified\n\t * and the pattern did not contain any magic characters\n\t * GLOB_NOMAGIC is there just for compatibility with csh.\n\t */\n\tif (pglob->gl_pathc == oldpathc) {\n\t\tif (((pglob->gl_flags & GLOB_NOCHECK) ||\n\t\t    ((pglob->gl_flags & GLOB_NOMAGIC) &&\n\t\t\t!(pglob->gl_flags & GLOB_MAGCHAR))))\n\t\t\treturn(globextend(pattern, pglob, limit));\n\t\telse\n\t\t\treturn(GLOB_NOMATCH);\n\t}\n\tif (!(pglob->gl_flags & GLOB_NOSORT))\n\t\tqsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,\n\t\t    pglob->gl_pathc - oldpathc, sizeof(char *), compare);\n\treturn(0);\n}\n\nstatic int\ncompare(const void *p, const void *q)\n{\n\treturn(strcmp(*(char **)p, *(char **)q));\n}\n\nstatic int\nglob1(Char *pattern, glob_t *pglob, size_t *limit)\n{\n\tChar pathbuf[MAXPATHLEN];\n\n\t/* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */\n\tif (*pattern == EOS)\n\t\treturn(0);\n\treturn(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1,\n\t    pattern, pglob, limit));\n}\n\n/*\n * The functions glob2 and glob3 are mutually recursive; there is one level\n * of recursion for each segment in the pattern that contains one or more\n * meta characters.\n */\nstatic int\nglob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern,\n      glob_t *pglob, size_t *limit)\n{\n\tstruct stat sb;\n\tChar *p, *q;\n\tint anymeta;\n\n\t/*\n\t * Loop over pattern segments until end of pattern or until\n\t * segment with meta character found.\n\t */\n\tfor (anymeta = 0;;) {\n\t\tif (*pattern == EOS) {\t\t/* End of pattern? */\n\t\t\t*pathend = EOS;\n\t\t\tif (g_lstat(pathbuf, &sb, pglob))\n\t\t\t\treturn(0);\n\n\t\t\tif (((pglob->gl_flags & GLOB_MARK) &&\n\t\t\t    pathend[-1] != SEP) && (S_ISDIR(sb.st_mode)\n\t\t\t    || (S_ISLNK(sb.st_mode) &&\n\t\t\t    (g_stat(pathbuf, &sb, pglob) == 0) &&\n\t\t\t    S_ISDIR(sb.st_mode)))) {\n\t\t\t\tif (pathend + 1 > pathend_last)\n\t\t\t\t\treturn (GLOB_ABORTED);\n\t\t\t\t*pathend++ = SEP;\n\t\t\t\t*pathend = EOS;\n\t\t\t}\n\t\t\t++pglob->gl_matchc;\n\t\t\treturn(globextend(pathbuf, pglob, limit));\n\t\t}\n\n\t\t/* Find end of next segment, copy tentatively to pathend. */\n\t\tq = pathend;\n\t\tp = pattern;\n\t\twhile (*p != EOS && *p != SEP) {\n\t\t\tif (ismeta(*p))\n\t\t\t\tanymeta = 1;\n\t\t\tif (q + 1 > pathend_last)\n\t\t\t\treturn (GLOB_ABORTED);\n\t\t\t*q++ = *p++;\n\t\t}\n\n\t\tif (!anymeta) {\t\t/* No expansion, do next segment. */\n\t\t\tpathend = q;\n\t\t\tpattern = p;\n\t\t\twhile (*pattern == SEP) {\n\t\t\t\tif (pathend + 1 > pathend_last)\n\t\t\t\t\treturn (GLOB_ABORTED);\n\t\t\t\t*pathend++ = *pattern++;\n\t\t\t}\n\t\t} else\t\t\t/* Need expansion, recurse. */\n\t\t\treturn(glob3(pathbuf, pathend, pathend_last, pattern, p,\n\t\t\t    pglob, limit));\n\t}\n\t/* NOTREACHED */\n}\n\nstatic int\nglob3(Char *pathbuf, Char *pathend, Char *pathend_last,\n      Char *pattern, Char *restpattern,\n      glob_t *pglob, size_t *limit)\n{\n\tstruct dirent *dp;\n\tDIR *dirp;\n\tint err;\n\tchar buf[MAXPATHLEN];\n\n\t/*\n\t * The readdirfunc declaration can't be prototyped, because it is\n\t * assigned, below, to two functions which are prototyped in glob.h\n\t * and dirent.h as taking pointers to differently typed opaque\n\t * structures.\n\t */\n\tstruct dirent *(*readdirfunc)();\n\n\tif (pathend > pathend_last)\n\t\treturn (GLOB_ABORTED);\n\t*pathend = EOS;\n\terrno = 0;\n\n\tif ((dirp = g_opendir(pathbuf, pglob)) == NULL) {\n\t\t/* TODO: don't call for ENOENT or ENOTDIR? */\n\t\tif (pglob->gl_errfunc) {\n\t\t\tif (g_Ctoc(pathbuf, buf, sizeof(buf)))\n\t\t\t\treturn (GLOB_ABORTED);\n\t\t\tif (pglob->gl_errfunc(buf, errno) ||\n\t\t\t    pglob->gl_flags & GLOB_ERR)\n\t\t\t\treturn (GLOB_ABORTED);\n\t\t}\n\t\treturn(0);\n\t}\n\n\terr = 0;\n\n\t/* Search directory for matching names. */\n\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n\t\treaddirfunc = pglob->gl_readdir;\n\telse\n\t\treaddirfunc = readdir;\n\twhile ((dp = (*readdirfunc)(dirp))) {\n\t\tchar *sc;\n\t\tChar *dc;\n\t\twchar_t wc;\n\t\tsize_t clen;\n\t\tmbstate_t mbs;\n\n\t\t/* Initial DOT must be matched literally. */\n\t\tif (dp->d_name[0] == DOT && *pattern != DOT)\n\t\t\tcontinue;\n\t\tmemset(&mbs, 0, sizeof(mbs));\n\t\tdc = pathend;\n\t\tsc = dp->d_name;\n\t\twhile (dc < pathend_last) {\n\t\t\tclen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs);\n\t\t\tif (clen == (size_t)-1 || clen == (size_t)-2) {\n\t\t\t\twc = *sc;\n\t\t\t\tclen = 1;\n\t\t\t\tmemset(&mbs, 0, sizeof(mbs));\n\t\t\t}\n\t\t\tif ((*dc++ = wc) == EOS)\n\t\t\t\tbreak;\n\t\t\tsc += clen;\n\t\t}\n\t\tif (!match(pathend, pattern, restpattern)) {\n\t\t\t*pathend = EOS;\n\t\t\tcontinue;\n\t\t}\n\t\terr = glob2(pathbuf, --dc, pathend_last, restpattern,\n\t\t    pglob, limit);\n\t\tif (err)\n\t\t\tbreak;\n\t}\n\n\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n\t\t(*pglob->gl_closedir)(dirp);\n\telse\n\t\tclosedir(dirp);\n\treturn(err);\n}\n\n\n/*\n * Extend the gl_pathv member of a glob_t structure to accommodate a new item,\n * add the new item, and update gl_pathc.\n *\n * This assumes the BSD realloc, which only copies the block when its size\n * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic\n * behavior.\n *\n * Return 0 if new item added, error code if memory couldn't be allocated.\n *\n * Invariant of the glob_t structure:\n *\tEither gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and\n *\tgl_pathv points to (gl_offs + gl_pathc + 1) items.\n */\nstatic int\nglobextend(const Char *path, glob_t *pglob, size_t *limit)\n{\n\tchar **pathv;\n\tsize_t i, newsize, len;\n\tchar *copy;\n\tconst Char *p;\n\n\tif (*limit && pglob->gl_pathc > *limit) {\n\t\terrno = 0;\n\t\treturn (GLOB_NOSPACE);\n\t}\n\n\tnewsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);\n\tpathv = pglob->gl_pathv ?\n\t\t    realloc((char *)pglob->gl_pathv, newsize) :\n\t\t    malloc(newsize);\n\tif (pathv == NULL) {\n\t\tif (pglob->gl_pathv) {\n\t\t\tfree(pglob->gl_pathv);\n\t\t\tpglob->gl_pathv = NULL;\n\t\t}\n\t\treturn(GLOB_NOSPACE);\n\t}\n\n\tif (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {\n\t\t/* first time around -- clear initial gl_offs items */\n\t\tpathv += pglob->gl_offs;\n\t\tfor (i = pglob->gl_offs + 1; --i > 0; )\n\t\t\t*--pathv = NULL;\n\t}\n\tpglob->gl_pathv = pathv;\n\n\tfor (p = path; *p++;)\n\t\tcontinue;\n\tlen = MB_CUR_MAX * (size_t)(p - path);\t/* XXX overallocation */\n\tif ((copy = malloc(len)) != NULL) {\n\t\tif (g_Ctoc(path, copy, len)) {\n\t\t\tfree(copy);\n\t\t\treturn (GLOB_NOSPACE);\n\t\t}\n\t\tpathv[pglob->gl_offs + pglob->gl_pathc++] = copy;\n\t}\n\tpathv[pglob->gl_offs + pglob->gl_pathc] = NULL;\n\treturn(copy == NULL ? GLOB_NOSPACE : 0);\n}\n\n/*\n * pattern matching function for filenames.  Each occurrence of the *\n * pattern causes a recursion level.\n */\nstatic int\nmatch(Char *name, Char *pat, Char *patend)\n{\n\tint ok, negate_range;\n\tChar c, k;\n\n\twhile (pat < patend) {\n\t\tc = *pat++;\n\t\tswitch (c & M_MASK) {\n\t\tcase M_ALL:\n\t\t\tif (pat == patend)\n\t\t\t\treturn(1);\n\t\t\tdo\n\t\t\t    if (match(name, pat, patend))\n\t\t\t\t    return(1);\n\t\t\twhile (*name++ != EOS);\n\t\t\treturn(0);\n\t\tcase M_ONE:\n\t\t\tif (*name++ == EOS)\n\t\t\t\treturn(0);\n\t\t\tbreak;\n\t\tcase M_SET:\n\t\t\tok = 0;\n\t\t\tif ((k = *name++) == EOS)\n\t\t\t\treturn(0);\n\t\t\tif ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)\n\t\t\t\t++pat;\n\t\t\twhile (((c = *pat++) & M_MASK) != M_END)\n\t\t\t\tif ((*pat & M_MASK) == M_RNG) {\n\t\t\t\t\tif (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1;\n\t\t\t\t\tpat += 2;\n\t\t\t\t} else if (c == k)\n\t\t\t\t\tok = 1;\n\t\t\tif (ok == negate_range)\n\t\t\t\treturn(0);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif (*name++ != c)\n\t\t\t\treturn(0);\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn(*name == EOS);\n}\n\n/* Free allocated data belonging to a glob_t structure. */\nvoid\nglobfree(glob_t *pglob)\n{\n\tsize_t i;\n\tchar **pp;\n\n\tif (pglob->gl_pathv != NULL) {\n\t\tpp = pglob->gl_pathv + pglob->gl_offs;\n\t\tfor (i = pglob->gl_pathc; i--; ++pp)\n\t\t\tif (*pp)\n\t\t\t\tfree(*pp);\n\t\tfree(pglob->gl_pathv);\n\t\tpglob->gl_pathv = NULL;\n\t}\n}\n\nstatic DIR *\ng_opendir(Char *str, glob_t *pglob)\n{\n\tchar buf[MAXPATHLEN];\n\n\tif (!*str)\n\t\tstrcpy(buf, \".\");\n\telse {\n\t\tif (g_Ctoc(str, buf, sizeof(buf)))\n\t\t\treturn (NULL);\n\t}\n\n\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n\t\treturn((*pglob->gl_opendir)(buf));\n\n\treturn(opendir(buf));\n}\n\nstatic int\ng_lstat(Char *fn, struct stat *sb, glob_t *pglob)\n{\n\tchar buf[MAXPATHLEN];\n\n\tif (g_Ctoc(fn, buf, sizeof(buf))) {\n\t\terrno = ENAMETOOLONG;\n\t\treturn (-1);\n\t}\n\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n\t\treturn((*pglob->gl_lstat)(buf, sb));\n\treturn(lstat(buf, sb));\n}\n\nstatic int\ng_stat(Char *fn, struct stat *sb, glob_t *pglob)\n{\n\tchar buf[MAXPATHLEN];\n\n\tif (g_Ctoc(fn, buf, sizeof(buf))) {\n\t\terrno = ENAMETOOLONG;\n\t\treturn (-1);\n\t}\n\tif (pglob->gl_flags & GLOB_ALTDIRFUNC)\n\t\treturn((*pglob->gl_stat)(buf, sb));\n\treturn(stat(buf, sb));\n}\n\nstatic const Char *\ng_strchr(const Char *str, wchar_t ch)\n{\n\n\tdo {\n\t\tif (*str == ch)\n\t\t\treturn (str);\n\t} while (*str++);\n\treturn (NULL);\n}\n\nstatic int\ng_Ctoc(const Char *str, char *buf, size_t len)\n{\n\tmbstate_t mbs;\n\tsize_t clen;\n\n\tmemset(&mbs, 0, sizeof(mbs));\n\twhile (len >= MB_CUR_MAX) {\n\t\tclen = wcrtomb(buf, *str, &mbs);\n\t\tif (clen == (size_t)-1)\n\t\t\treturn (1);\n\t\tif (*str == L'\\0')\n\t\t\treturn (0);\n\t\tstr++;\n\t\tbuf += clen;\n\t\tlen -= clen;\n\t}\n\treturn (1);\n}\n\n#ifdef DEBUG\nstatic void\nqprintf(const char *str, Char *s)\n{\n\tChar *p;\n\n\t(void)printf(\"%s:\\n\", str);\n\tfor (p = s; *p; p++)\n\t\t(void)printf(\"%c\", CHAR(*p));\n\t(void)printf(\"\\n\");\n\tfor (p = s; *p; p++)\n\t\t(void)printf(\"%c\", *p & M_PROTECT ? '\"' : ' ');\n\t(void)printf(\"\\n\");\n\tfor (p = s; *p; p++)\n\t\t(void)printf(\"%c\", ismeta(*p) ? '_' : ' ');\n\t(void)printf(\"\\n\");\n}\n#endif\n"
  },
  {
    "path": "pythonforandroid/recipes/libxml2/glob.h",
    "content": "/*\n * Copyright (c) 1989, 1993\n *\tThe Regents of the University of California.  All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Guido van Rossum.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *\t@(#)glob.h\t8.1 (Berkeley) 6/2/93\n * $FreeBSD$\n */\n\n#ifndef _GLOB_H_\n#define\t_GLOB_H_\n\n#include <sys/cdefs.h>\n#include <sys/_types.h>\n\n#ifndef\t_SIZE_T_DECLARED\ntypedef\t__size_t\tsize_t;\n#define\t_SIZE_T_DECLARED\n#endif\n\nstruct stat;\ntypedef struct {\n\tsize_t gl_pathc;\t/* Count of total paths so far. */\n\tsize_t gl_matchc;\t/* Count of paths matching pattern. */\n\tsize_t gl_offs;\t\t/* Reserved at beginning of gl_pathv. */\n\tint gl_flags;\t\t/* Copy of flags parameter to glob. */\n\tchar **gl_pathv;\t/* List of paths matching pattern. */\n\t\t\t\t/* Copy of errfunc parameter to glob. */\n\tint (*gl_errfunc)(const char *, int);\n\n\t/*\n\t * Alternate filesystem access methods for glob; replacement\n\t * versions of closedir(3), readdir(3), opendir(3), stat(2)\n\t * and lstat(2).\n\t */\n\tvoid (*gl_closedir)(void *);\n\tstruct dirent *(*gl_readdir)(void *);\n\tvoid *(*gl_opendir)(const char *);\n\tint (*gl_lstat)(const char *, struct stat *);\n\tint (*gl_stat)(const char *, struct stat *);\n} glob_t;\n\n#if __POSIX_VISIBLE >= 199209\n/* Believed to have been introduced in 1003.2-1992 */\n#define\tGLOB_APPEND\t0x0001\t/* Append to output from previous call. */\n#define\tGLOB_DOOFFS\t0x0002\t/* Use gl_offs. */\n#define\tGLOB_ERR\t0x0004\t/* Return on error. */\n#define\tGLOB_MARK\t0x0008\t/* Append / to matching directories. */\n#define\tGLOB_NOCHECK\t0x0010\t/* Return pattern itself if nothing matches. */\n#define\tGLOB_NOSORT\t0x0020\t/* Don't sort. */\n#define\tGLOB_NOESCAPE\t0x2000\t/* Disable backslash escaping. */\n\n/* Error values returned by glob(3) */\n#define\tGLOB_NOSPACE\t(-1)\t/* Malloc call failed. */\n#define\tGLOB_ABORTED\t(-2)\t/* Unignored error. */\n#define\tGLOB_NOMATCH\t(-3)\t/* No match and GLOB_NOCHECK was not set. */\n#define\tGLOB_NOSYS\t(-4)\t/* Obsolete: source compatibility only. */\n#endif /* __POSIX_VISIBLE >= 199209 */\n\n#if __BSD_VISIBLE\n#define\tGLOB_ALTDIRFUNC\t0x0040\t/* Use alternately specified directory funcs. */\n#define\tGLOB_BRACE\t0x0080\t/* Expand braces ala csh. */\n#define\tGLOB_MAGCHAR\t0x0100\t/* Pattern had globbing characters. */\n#define\tGLOB_NOMAGIC\t0x0200\t/* GLOB_NOCHECK without magic chars (csh). */\n#define\tGLOB_QUOTE\t0x0400\t/* Quote special chars with \\. */\n#define\tGLOB_TILDE\t0x0800\t/* Expand tilde names from the passwd file. */\n#define\tGLOB_LIMIT\t0x1000\t/* limit number of returned paths */\n\n/* source compatibility, these are the old names */\n#define GLOB_MAXPATH\tGLOB_LIMIT\n#define\tGLOB_ABEND\tGLOB_ABORTED\n#endif /* __BSD_VISIBLE */\n\n__BEGIN_DECLS\nint\tglob(const char *, int, int (*)(const char *, int), glob_t *);\nvoid\tglobfree(glob_t *);\n__END_DECLS\n\n#endif /* !_GLOB_H_ */\n"
  },
  {
    "path": "pythonforandroid/recipes/libxslt/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom os.path import exists, join\nimport sh\n\n\nclass LibxsltRecipe(Recipe):\n    version = '1.1.34'\n    url = 'http://xmlsoft.org/sources/libxslt-{version}.tar.gz'\n    depends = ['libxml2']\n    patches = ['fix-dlopen.patch']\n    built_libraries = {\n        'libxslt.a': 'libxslt/.libs',\n        'libexslt.a': 'libexslt/.libs'\n    }\n\n    call_hostpython_via_targetpython = False\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        build_dir = self.get_build_dir(arch.arch)\n        with current_directory(build_dir):\n            # If the build is done with /bin/sh things blow up,\n            # try really hard to use bash\n            libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx)\n            libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch)\n            build_arch = shprint(sh.gcc, '-dumpmachine').stdout.decode(\n                'utf-8').split('\\n')[0]\n\n            if not exists('configure'):\n                shprint(sh.Command('./autogen.sh'), _env=env)\n            shprint(sh.Command('autoreconf'), '-vif', _env=env)\n            shprint(sh.Command('./configure'),\n                    '--build=' + build_arch,\n                    '--host=' + arch.command_prefix,\n                    '--target=' + arch.command_prefix,\n                    '--without-plugins',\n                    '--without-debug',\n                    '--without-python',\n                    '--without-crypto',\n                    '--with-libxml-src=' + libxml2_build_dir,\n                    '--disable-shared',\n                    _env=env)\n            shprint(sh.make, \"V=1\", _env=env)\n\n            shprint(sh.Command('chmod'), '+x', 'xslt-config')\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['CONFIG_SHELL'] = '/bin/bash'\n        env['SHELL'] = '/bin/bash'\n\n        libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx)\n        libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch)\n        libxml2_libs_dir = join(libxml2_build_dir, '.libs')\n\n        env['CFLAGS'] = ' '.join([\n            env['CFLAGS'],\n            '-I' + libxml2_build_dir,\n            '-I' + join(libxml2_build_dir, 'include', 'libxml'),\n            '-I' + self.get_build_dir(arch.arch),\n        ])\n        env['LDFLAGS'] += ' -L' + libxml2_libs_dir\n        env['LIBS'] = '-lxml2 -lz -lm'\n\n        return env\n\n\nrecipe = LibxsltRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libxslt/fix-dlopen.patch",
    "content": "--- libxslt-1.1.27.orig/python/libxsl.py\t2012-09-04 16:26:23.000000000 +0200\n+++ libxslt-1.1.27/python/libxsl.py\t2013-07-29 15:11:04.182227378 +0200\n@@ -4,7 +4,7 @@\n # loader to work in that mode if feasible\n #\n import sys\n-if not hasattr(sys,'getdlopenflags'):\n+if True:\n     import libxml2mod\n     import libxsltmod\n     import libxml2\n"
  },
  {
    "path": "pythonforandroid/recipes/libzbar/__init__.py",
    "content": "import os\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nfrom multiprocessing import cpu_count\nimport sh\n\n\nclass LibZBarRecipe(Recipe):\n\n    version = '0.10'\n\n    url = 'https://github.com/ZBar/ZBar/archive/{version}.zip'\n\n    depends = ['libiconv']\n\n    patches = [\"werror.patch\"]\n\n    built_libraries = {'libzbar.so': 'zbar/.libs'}\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        libiconv = self.get_recipe('libiconv', self.ctx)\n        libiconv_dir = libiconv.get_build_dir(arch.arch)\n        env['CFLAGS'] += ' -I' + os.path.join(libiconv_dir, 'include')\n        env['LIBS'] = env.get('LIBS', '') + ' -landroid -liconv'\n        return env\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            shprint(sh.Command('autoreconf'), '-vif', _env=env)\n            shprint(\n                sh.Command('./configure'),\n                '--host=' + arch.command_prefix,\n                '--target=' + arch.command_prefix,\n                '--prefix=' + self.ctx.get_python_install_dir(arch.arch),\n                # Python bindings are compiled in a separated recipe\n                '--with-python=no',\n                '--with-gtk=no',\n                '--with-qt=no',\n                '--with-x=no',\n                '--with-jpeg=no',\n                '--with-imagemagick=no',\n                '--enable-pthread=no',\n                '--enable-video=no',\n                '--enable-shared=yes',\n                '--enable-static=no',\n                _env=env)\n            shprint(sh.make, '-j' + str(cpu_count()), _env=env)\n\n\nrecipe = LibZBarRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/libzbar/werror.patch",
    "content": "diff --git a/configure.ac b/configure.ac\nindex 256aedb..727caba 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -3,7 +3,7 @@ AC_PREREQ([2.61])\n AC_INIT([zbar], [0.10], [spadix@users.sourceforge.net])\n AC_CONFIG_AUX_DIR(config)\n AC_CONFIG_MACRO_DIR(config)\n-AM_INIT_AUTOMAKE([1.10 -Wall -Werror foreign subdir-objects std-options dist-bzip2])\n+AM_INIT_AUTOMAKE([1.10 -Wall foreign subdir-objects std-options dist-bzip2])\n AC_CONFIG_HEADERS([include/config.h])\n AC_CONFIG_SRCDIR(zbar/scanner.c)\n LT_PREREQ([2.2])\n"
  },
  {
    "path": "pythonforandroid/recipes/libzmq/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nfrom os.path import join\nimport sh\n\n\nclass LibZMQRecipe(Recipe):\n    version = '4.3.4'\n    url = 'https://github.com/zeromq/libzmq/releases/download/v{version}/zeromq-{version}.zip'\n    depends = []\n    built_libraries = {'libzmq.so': 'src/.libs'}\n    need_stl_shared = True\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        #\n        # libsodium_recipe = Recipe.get_recipe('libsodium', self.ctx)\n        # libsodium_dir = libsodium_recipe.get_build_dir(arch.arch)\n        # env['sodium_CFLAGS'] = '-I{}'.format(join(\n        #     libsodium_dir, 'src'))\n        # env['sodium_LDLAGS'] = '-L{}'.format(join(\n        #     libsodium_dir, 'src', 'libsodium', '.libs'))\n\n        curdir = self.get_build_dir(arch.arch)\n        prefix = join(curdir, \"install\")\n\n        with current_directory(curdir):\n            bash = sh.Command('sh')\n            shprint(\n                bash, './configure',\n                '--host={}'.format(arch.command_prefix),\n                '--without-documentation',\n                '--prefix={}'.format(prefix),\n                '--with-libsodium=no',\n                '--disable-libunwind',\n                '--disable-Werror',\n                _env=env)\n            shprint(sh.make, _env=env)\n            shprint(sh.make, 'install', _env=env)\n\n\nrecipe = LibZMQRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/lxml/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe, CompiledComponentsPythonRecipe\nfrom os.path import exists, join\nfrom os import uname\n\n\nclass LXMLRecipe(CompiledComponentsPythonRecipe):\n    version = '4.8.0'\n    url = 'https://pypi.python.org/packages/source/l/lxml/lxml-{version}.tar.gz'  # noqa\n    depends = ['librt', 'libxml2', 'libxslt', 'setuptools']\n    name = 'lxml'\n\n    call_hostpython_via_targetpython = False  # Due to setuptools\n\n    def should_build(self, arch):\n        super().should_build(arch)\n\n        py_ver = self.ctx.python_recipe.major_minor_version_string\n        build_platform = \"{system}-{machine}\".format(\n            system=uname()[0], machine=uname()[-1]\n        ).lower()\n        build_dir = join(\n            self.get_build_dir(arch.arch),\n            \"build\",\n            \"lib.\" + build_platform + \"-\" + py_ver,\n            \"lxml\",\n        )\n        py_libs = [\"_elementpath.so\", \"builder.so\", \"etree.so\", \"objectify.so\"]\n\n        return not all([exists(join(build_dir, lib)) for lib in py_libs])\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n\n        # libxslt flags\n        libxslt_recipe = Recipe.get_recipe('libxslt', self.ctx)\n        libxslt_build_dir = libxslt_recipe.get_build_dir(arch.arch)\n\n        # libxml2 flags\n        libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx)\n        libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch)\n\n        env[\"STATIC\"] = \"true\"\n\n        env[\"LXML_STATIC_INCLUDE_DIRS\"] = \"{}:{}\".format(\n            join(libxml2_build_dir, \"include\"), join(libxslt_build_dir)\n        )\n        env[\"LXML_STATIC_LIBRARY_DIRS\"] = \"{}:{}:{}\".format(\n            join(libxml2_build_dir, \".libs\"),\n            join(libxslt_build_dir, \"libxslt\", \".libs\"),\n            join(libxslt_build_dir, \"libexslt\", \".libs\"),\n        )\n\n        env[\"WITH_XML2_CONFIG\"] = join(libxml2_build_dir, \"xml2-config\")\n        env[\"WITH_XSLT_CONFIG\"] = join(libxslt_build_dir, \"xslt-config\")\n\n        env[\"LXML_STATIC_BINARIES\"] = \"{}:{}:{}\".format(\n            join(libxml2_build_dir, \".libs\", \"libxml2.a\"),\n            join(libxslt_build_dir, \"libxslt\", \".libs\", \"libxslt.a\"),\n            join(libxslt_build_dir, \"libexslt\", \".libs\", \"libexslt.a\"),\n        )\n\n        return env\n\n\nrecipe = LXMLRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/m2crypto/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\nfrom pythonforandroid.toolchain import current_directory\nfrom pythonforandroid.logger import shprint, info\nimport glob\nimport sh\n\n\nclass M2CryptoRecipe(CompiledComponentsPythonRecipe):\n    version = '0.30.1'\n    url = 'https://pypi.python.org/packages/source/M/M2Crypto/M2Crypto-{version}.tar.gz'\n    depends = ['openssl', 'setuptools']\n    site_packages_name = 'M2Crypto'\n    call_hostpython_via_targetpython = False\n\n    def build_compiled_components(self, arch):\n        info('Building compiled components in {}'.format(self.name))\n\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            # Build M2Crypto\n            hostpython = sh.Command(self.hostpython_location)\n            if self.install_in_hostpython:\n                shprint(hostpython, 'setup.py', 'clean', '--all', _env=env)\n            shprint(hostpython, 'setup.py', self.build_cmd,\n                    '-p' + arch.arch,\n                    '-c' + 'unix',\n                    '-o' + env['OPENSSL_BUILD_PATH'],\n                    '-L' + env['OPENSSL_BUILD_PATH'],\n                    _env=env, *self.setup_extra_args)\n            build_dir = glob.glob('build/lib.*')[0]\n            shprint(sh.find, build_dir, '-name', '\"*.o\"', '-exec',\n                    env['STRIP'], '{}', ';', _env=env)\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['OPENSSL_BUILD_PATH'] = self.get_recipe('openssl', self.ctx).get_build_dir(arch.arch)\n        return env\n\n\nrecipe = M2CryptoRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/materialyoucolor/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass MaterialyoucolorRecipe(PyProjectRecipe):\n    stl_lib_name = \"c++_shared\"\n    version = \"2.0.10\"\n    url = \"https://github.com/T-Dynamos/materialyoucolor-python/releases/download/v{version}/materialyoucolor-{version}.tar.gz\"\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env['LDCXXSHARED'] = env['CXX'] + ' -shared'\n        return env\n\n\nrecipe = MaterialyoucolorRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/matplotlib/__init__.py",
    "content": "from pythonforandroid.recipe import MesonRecipe\nfrom pythonforandroid.logger import shprint\n\nfrom os.path import join\nimport sh\n\n\nclass MatplotlibRecipe(MesonRecipe):\n    version = '3.10.7'\n    url = 'https://github.com/matplotlib/matplotlib/archive/v{version}.zip'\n    depends = ['kiwisolver', 'numpy', 'pillow']\n    python_depends = ['cycler', 'fonttools', 'packaging', 'pyparsing', 'python-dateutil']\n    hostpython_prerequisites = [\"setuptools_scm>=7\"]\n    patches = [\"meson.patch\"]\n    need_stl_shared = True\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env['CXXFLAGS'] += ' -Wno-c++11-narrowing'\n        return env\n\n    def build_arch(self, arch):\n        python_path = join(self.ctx.python_recipe.get_build_dir(arch), \"android-build\", \"python3\")\n        self.extra_build_args += [f'-Csetup-args=-Dpython3_program={python_path}']\n        shprint(sh.cp, self.real_hostpython_location, python_path)\n        super().build_arch(arch)\n\n\nrecipe = MatplotlibRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/matplotlib/meson.patch",
    "content": "diff '--color=auto' -uNr matplotlib-3.10.7/meson.build matplotlib-3.10.7.mod/meson.build\n--- matplotlib-3.10.7/meson.build\t2025-10-09 04:16:31.000000000 +0530\n+++ matplotlib-3.10.7.mod/meson.build\t2025-10-12 10:19:29.664280049 +0530\n@@ -36,7 +36,7 @@\n \n # https://mesonbuild.com/Python-module.html\n py_mod = import('python')\n-py3 = py_mod.find_installation(pure: false)\n+py3 = py_mod.find_installation(get_option('python3_program'), pure: false)\n py3_dep = py3.dependency()\n \n pybind11_dep = dependency('pybind11', version: '>=2.13.2')\ndiff '--color=auto' -uNr matplotlib-3.10.7/meson.options matplotlib-3.10.7.mod/meson.options\n--- matplotlib-3.10.7/meson.options\t2025-10-09 04:16:31.000000000 +0530\n+++ matplotlib-3.10.7.mod/meson.options\t2025-10-12 10:19:23.762042521 +0530\n@@ -28,3 +28,5 @@\n # default is determined by fallback.\n option('rcParams-backend', type: 'string', value: 'auto',\n        description: 'Set default backend at runtime')\n+\n+option('python3_program', type : 'string', value : '', description : 'Path to Python 3 executable')\n"
  },
  {
    "path": "pythonforandroid/recipes/moderngl/__init__.py",
    "content": "from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe\n\n\nclass ModernGLRecipe(CppCompiledComponentsPythonRecipe):\n    version = '5.10.0'\n    url = 'https://github.com/moderngl/moderngl/archive/refs/tags/{version}.tar.gz'\n\n    site_packages_name = 'moderngl'\n    name = 'moderngl'\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['LDFLAGS'] += ' -lstdc++'\n        return env\n\n\nrecipe = ModernGLRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/msgpack-python/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\n\n\nclass MsgPackRecipe(CythonRecipe):\n    version = '0.4.7'\n    url = 'https://pypi.python.org/packages/source/m/msgpack-python/msgpack-python-{version}.tar.gz'\n    depends = [\"setuptools\"]\n    call_hostpython_via_targetpython = False\n\n\nrecipe = MsgPackRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/ndghttpsclient",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\nclass NdgHttpsClientRecipe(PythonRecipe):\n    version = '0.5.1'\n    url = 'https://pypi.python.org/packages/source/n/ndg-httpsclient/ndg_httpsclient-{version}.tar.gz'\n    depends = ['python3', 'pyopenssl', 'cryptography']\n    call_hostpython_via_targetpython = False\n\nrecipe = NdgHttpsClientRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/netifaces/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\n\n\nclass NetifacesRecipe(CompiledComponentsPythonRecipe):\n\n    version = '0.10.9'\n\n    url = 'https://files.pythonhosted.org/packages/source/n/netifaces/netifaces-{version}.tar.gz'\n\n    depends = ['setuptools']\n\n    patches = ['fix-build.patch']\n\n    site_packages_name = 'netifaces'\n\n    call_hostpython_via_targetpython = False\n\n\nrecipe = NetifacesRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/netifaces/fix-build.patch",
    "content": "--- netifaces/setup.py.orig\t2018-05-02 09:45:09.000000000 +0200\n+++ netifaces/setup.py\t2018-12-11 14:12:02.785808692 +0100\n@@ -55,7 +55,7 @@\n         self.check_requirements()\n         build_ext.build_extensions(self)\n \n-    def test_build(self, contents, link=True, execute=False, libraries=None,\n+    def test_build(self, contents, link=False, execute=False, libraries=None,\n                    include_dirs=None, library_dirs=None):\n         name = os.path.join(self.build_temp, 'conftest-%s.c' % self.conftestidx)\n         self.conftestidx += 1\n"
  },
  {
    "path": "pythonforandroid/recipes/numpy/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe, MesonRecipe\nfrom os.path import join\nimport shutil\n\nNUMPY_NDK_MESSAGE = \"In order to build numpy, you must set minimum ndk api (minapi) to `24`.\\n\"\n\n\nclass NumpyRecipe(MesonRecipe):\n    version = 'v2.3.0'\n    url = 'git+https://github.com/numpy/numpy'\n    hostpython_prerequisites = [\"Cython>=3.0.6\", \"numpy\"]  # meson does not detects venv's cython\n    extra_build_args = ['-Csetup-args=-Dblas=none', '-Csetup-args=-Dlapack=none']\n    need_stl_shared = True\n    min_ndk_api_support = 24\n\n    def get_recipe_meson_options(self, arch):\n        options = super().get_recipe_meson_options(arch)\n        # Custom python is required, so that meson\n        # gets libs and config files properly\n        options[\"binaries\"][\"python\"] = self.ctx.python_recipe.python_exe\n        options[\"binaries\"][\"python3\"] = self.ctx.python_recipe.python_exe\n        options[\"properties\"][\"longdouble_format\"] = \"IEEE_DOUBLE_LE\" if arch.arch in [\"armeabi-v7a\", \"x86\"] else \"IEEE_QUAD_LE\"\n        return options\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n\n        # _PYTHON_HOST_PLATFORM declares that we're cross-compiling\n        # and avoids issues when building on macOS for Android targets.\n        env[\"_PYTHON_HOST_PLATFORM\"] = arch.command_prefix\n\n        # NPY_DISABLE_SVML=1 allows numpy to build for non-AVX512 CPUs\n        # See: https://github.com/numpy/numpy/issues/21196\n        env[\"NPY_DISABLE_SVML\"] = \"1\"\n        env[\"TARGET_PYTHON_EXE\"] = join(Recipe.get_recipe(\n                \"python3\", self.ctx).get_build_dir(arch.arch), \"android-build\", \"python\")\n        return env\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        self.restore_hostpython_prerequisites([\"cython\"])\n\n    def get_hostrecipe_env(self, arch=None):\n        env = super().get_hostrecipe_env(arch=arch)\n        env['RANLIB'] = shutil.which('ranlib')\n        return env\n\n\nrecipe = NumpyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/omemo/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass OmemoRecipe(PythonRecipe):\n    name = 'omemo'\n    version = '0.11.0'\n    url = 'https://pypi.python.org/packages/source/O/OMEMO/OMEMO-{version}.tar.gz'\n    site_packages_name = 'omemo'\n    depends = [\n        'setuptools',\n        'x3dh',\n        'cryptography',\n    ]\n    call_hostpython_via_targetpython = False\n\n\nrecipe = OmemoRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/omemo-backend-signal/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass OmemoBackendSignalRecipe(PythonRecipe):\n    name = 'omemo-backend-signal'\n    version = '0.2.5'\n    url = 'https://pypi.python.org/packages/source/o/omemo-backend-signal/omemo-backend-signal-{version}.tar.gz'\n    site_packages_name = 'omemo-backend-signal'\n    depends = [\n        'setuptools',\n        'protobuf_cpp',\n        'x3dh',\n        'DoubleRatchet',\n        'hkdf==0.0.3',\n        'cryptography',\n        'omemo',\n    ]\n    call_hostpython_via_targetpython = False\n\n\nrecipe = OmemoBackendSignalRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/openal/__init__.py",
    "content": "from pythonforandroid.recipe import NDKRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nfrom os.path import join\nimport sh\n\n\nclass OpenALRecipe(NDKRecipe):\n    version = '1.21.1'\n    url = 'https://github.com/kcat/openal-soft/archive/refs/tags/{version}.tar.gz'\n\n    generated_libraries = ['libopenal.so']\n\n    def build_arch(self, arch):\n        with current_directory(self.get_build_dir(arch.arch)):\n            env = self.get_recipe_env(arch)\n            cmake_args = [\n                \"-DANDROID_STL=\" + self.stl_lib_name,\n                \"-DCMAKE_TOOLCHAIN_FILE={}\".format(\n                    join(self.ctx.ndk_dir, \"build\", \"cmake\", \"android.toolchain.cmake\")\n                ),\n            ]\n            shprint(\n                sh.cmake, '.',\n                *cmake_args,\n                _env=env\n            )\n            shprint(sh.make, _env=env)\n            self.install_libs(arch, 'libopenal.so')\n\n\nrecipe = OpenALRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/opencv/__init__.py",
    "content": "from multiprocessing import cpu_count\nfrom os.path import join\n\nimport sh\n\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import NDKRecipe\nfrom pythonforandroid.util import current_directory, ensure_dir\n\n\nclass OpenCVRecipe(NDKRecipe):\n    '''\n    .. versionchanged:: 0.7.1\n        rewrote recipe to support the python bindings (cv2.so) and enable the\n        build of most of the libraries of the opencv's package, so we can\n        process images, videos, objects, photos...\n    '''\n    version = '4.12.0'\n    url = 'https://github.com/opencv/opencv/archive/{version}.zip'\n    depends = ['numpy']\n    patches = ['patches/p4a_build.patch']\n    generated_libraries = [\n        'libopencv_features2d.so',\n        'libopencv_imgproc.so',\n        'libopencv_stitching.so',\n        'libopencv_calib3d.so',\n        'libopencv_flann.so',\n        'libopencv_ml.so',\n        'libopencv_videoio.so',\n        'libopencv_core.so',\n        'libopencv_highgui.so',\n        'libopencv_objdetect.so',\n        'libopencv_video.so',\n        'libopencv_dnn.so',\n        'libopencv_imgcodecs.so',\n        'libopencv_photo.so',\n    ]\n\n    def get_lib_dir(self, arch):\n        return join(self.get_build_dir(arch.arch), 'build', 'lib', arch.arch)\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['ANDROID_NDK'] = self.ctx.ndk_dir\n        env['ANDROID_SDK'] = self.ctx.sdk_dir\n        return env\n\n    def build_arch(self, arch):\n        build_dir = join(self.get_build_dir(arch.arch), 'build')\n        ensure_dir(build_dir)\n\n        opencv_extras = []\n        if 'opencv_extras' in self.ctx.recipe_build_order:\n            opencv_extras_dir = self.get_recipe(\n                'opencv_extras', self.ctx).get_build_dir(arch.arch)\n            opencv_extras = [\n                f'-DOPENCV_EXTRA_MODULES_PATH={opencv_extras_dir}/modules',\n                '-DBUILD_opencv_legacy=OFF',\n            ]\n\n        with current_directory(build_dir):\n            env = self.get_recipe_env(arch)\n\n            python_major = self.ctx.python_recipe.version[0]\n            python_include_root = self.ctx.python_recipe.include_root(arch.arch)\n            python_site_packages = self.ctx.get_site_packages_dir(arch)\n            python_link_root = self.ctx.python_recipe.link_root(arch.arch)\n            python_link_version = self.ctx.python_recipe.link_version\n            python_library = join(python_link_root,\n                                  'libpython{}.so'.format(python_link_version))\n            python_include_numpy = join(\n                self.ctx.get_python_install_dir(arch.arch), \"numpy/_core/include\",\n            )\n\n            shprint(sh.cmake,\n                    '-DP4A=ON',\n                    '-DANDROID_ABI={}'.format(arch.arch),\n                    '-DANDROID_STANDALONE_TOOLCHAIN={}'.format(self.ctx.ndk_dir),\n                    '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),\n                    '-DANDROID_EXECUTABLE={}/tools/android'.format(env['ANDROID_SDK']),\n                    '-DANDROID_SDK_TOOLS_VERSION=6514223',\n                    '-DANDROID_PROJECTS_SUPPORT_GRADLE=ON',\n\n                    '-DCMAKE_TOOLCHAIN_FILE={}'.format(\n                        join(self.ctx.ndk_dir, 'build', 'cmake',\n                             'android.toolchain.cmake')),\n                    # Make the linkage with our python library, otherwise we\n                    # will get dlopen error when trying to import cv2's module.\n                    '-DCMAKE_SHARED_LINKER_FLAGS=-L{path} -lpython{version}'.format(\n                        path=python_link_root,\n                        version=python_link_version),\n\n                    '-DBUILD_WITH_STANDALONE_TOOLCHAIN=ON',\n                    # Force to build as shared libraries the cv2's dependent\n                    # libs or we will not be able to link with our python\n                    '-DBUILD_SHARED_LIBS=ON',\n                    '-DBUILD_STATIC_LIBS=OFF',\n\n                    # Disable some opencv's features\n                    '-DBUILD_opencv_java=OFF',\n                    '-DBUILD_opencv_java_bindings_generator=OFF',\n                    # '-DBUILD_opencv_highgui=OFF',\n                    # '-DBUILD_opencv_imgproc=OFF',\n                    # '-DBUILD_opencv_flann=OFF',\n                    '-DBUILD_TESTS=OFF',\n                    '-DBUILD_PERF_TESTS=OFF',\n                    '-DENABLE_TESTING=OFF',\n                    '-DBUILD_EXAMPLES=OFF',\n                    '-DBUILD_ANDROID_EXAMPLES=OFF',\n\n                    # Force to only build our version of python\n                    '-DBUILD_OPENCV_PYTHON{major}=ON'.format(major=python_major),\n                    '-DBUILD_OPENCV_PYTHON{major}=OFF'.format(\n                        major='2' if python_major == '3' else '3'),\n\n                    # Force to install the `cv2.so` library directly into\n                    # python's site packages (otherwise the cv2's loader fails\n                    # on finding the cv2.so library)\n                    '-DOPENCV_SKIP_PYTHON_LOADER=ON',\n                    '-DOPENCV_PYTHON{major}_INSTALL_PATH={site_packages}'.format(\n                        major=python_major, site_packages=python_site_packages),\n\n                    # Define python's paths for: exe, lib, includes, numpy...\n                    '-DPYTHON_DEFAULT_EXECUTABLE={}'.format(self.ctx.hostpython),\n                    '-DPYTHON{major}_EXECUTABLE={host_python}'.format(\n                        major=python_major, host_python=self.ctx.hostpython),\n                    '-DPYTHON{major}_INCLUDE_PATH={include_path}'.format(\n                        major=python_major, include_path=python_include_root),\n                    '-DPYTHON{major}_LIBRARIES={python_lib}'.format(\n                        major=python_major, python_lib=python_library),\n                    '-DPYTHON{major}_NUMPY_INCLUDE_DIRS={numpy_include}'.format(\n                        major=python_major, numpy_include=python_include_numpy),\n                    '-DPYTHON{major}_PACKAGES_PATH={site_packages}'.format(\n                        major=python_major, site_packages=python_site_packages),\n\n                    *opencv_extras,\n\n                    self.get_build_dir(arch.arch),\n                    _env=env)\n\n            # patch link.txt for unsupported flag\n            link_txt = 'modules/python3/CMakeFiles/opencv_python3.dir/link.txt'\n            with open(link_txt, 'r+') as f:\n                content = f.read().replace('-version', ' ')\n                f.seek(0)\n                f.write(content)\n                f.truncate()\n\n            shprint(sh.make, '-j' + str(cpu_count()), 'opencv_python' + python_major)\n            # Install python bindings (cv2.so)\n            shprint(sh.cmake, '-DCOMPONENT=python', '-P', './cmake_install.cmake')\n            # Copy third party shared libs that we need in our final apk\n            sh.cp('-a', sh.glob('./lib/{}/lib*.so'.format(arch.arch)),\n                  self.ctx.get_libs_dir(arch.arch))\n\n\nrecipe = OpenCVRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/opencv/patches/p4a_build.patch",
    "content": "diff '--color=auto' -uNr opencv-4.12.0/cmake/OpenCVDetectPython.cmake opencv-4.12.0.mod/cmake/OpenCVDetectPython.cmake\n--- opencv-4.12.0/cmake/OpenCVDetectPython.cmake\t2025-07-02 13:24:13.000000000 +0530\n+++ opencv-4.12.0.mod/cmake/OpenCVDetectPython.cmake\t2025-09-20 22:22:14.961944470 +0530\n@@ -175,7 +175,7 @@\n       endif()\n     endif()\n \n-    if(NOT ANDROID AND NOT IOS AND NOT XROS)\n+    if(P4A OR NOT ANDROID AND NOT IOS AND NOT XROS)\n       if(CMAKE_HOST_UNIX)\n         execute_process(COMMAND ${_executable} -c \"from sysconfig import *; print(get_path('purelib'))\"\n                         RESULT_VARIABLE _cvpy_process\ndiff '--color=auto' -uNr opencv-4.12.0/modules/python/CMakeLists.txt opencv-4.12.0.mod/modules/python/CMakeLists.txt\n--- opencv-4.12.0/modules/python/CMakeLists.txt\t2025-07-02 13:24:13.000000000 +0530\n+++ opencv-4.12.0.mod/modules/python/CMakeLists.txt\t2025-09-20 22:23:15.124356524 +0530\n@@ -3,7 +3,7 @@\n # ----------------------------------------------------------------------------\n if(DEFINED OPENCV_INITIAL_PASS)  # OpenCV build\n \n-if(ANDROID OR APPLE_FRAMEWORK OR WINRT)\n+if(ANDROID AND NOT P4A OR APPLE_FRAMEWORK OR WINRT)\n   ocv_module_disable_(python2)\n   ocv_module_disable_(python3)\n   return()\n"
  },
  {
    "path": "pythonforandroid/recipes/opencv_extras/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\n\n\nclass OpenCVExtrasRecipe(Recipe):\n    \"\"\"\n    OpenCV extras recipe allows us to build extra modules from the\n    `opencv_contrib` repository. It depends on opencv recipe and all the build\n    of the modules will be performed inside opencv recipe build directory.\n\n    .. note:: the version of this recipe should be the same than opencv recipe.\n\n    .. warning:: Be aware that these modules are experimental, some of them\n        maybe included in opencv future releases and removed from extras.\n\n    .. seealso:: https://github.com/opencv/opencv_contrib\n\n    \"\"\"\n    version = '4.5.1'\n    url = 'https://github.com/opencv/opencv_contrib/archive/{version}.zip'\n    depends = ['opencv']\n\n\nrecipe = OpenCVExtrasRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/openssl/__init__.py",
    "content": "from os.path import join\nfrom multiprocessing import cpu_count\n\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.logger import shprint\nimport sh\n\n\nclass OpenSSLRecipe(Recipe):\n    '''\n    The OpenSSL libraries for python-for-android. This recipe will generate the\n    following libraries as shared libraries (*.so):\n\n        - crypto\n        - ssl\n\n    The generated openssl libraries are versioned, where the version is the\n    recipe attribute :attr:`version` e.g.: ``libcrypto1.1.so``,\n    ``libssl1.1.so``...so...to link your recipe with the openssl libs,\n    remember to add the version at the end, e.g.:\n    ``-lcrypto1.1 -lssl1.1``. Or better, you could do it dynamically\n    using the methods: :meth:`include_flags`, :meth:`link_dirs_flags` and\n    :meth:`link_libs_flags`.\n\n    .. warning:: This recipe is very sensitive because is used for our core\n        recipes, the python recipes. The used API should match with the one\n        used in our python build, otherwise we will be unable to build the\n        _ssl.so python module.\n\n    .. versionchanged:: 0.6.0\n\n        - The gcc compiler has been deprecated in favour of clang and libraries\n          updated to version 1.1.1 (LTS - supported until 11th September 2023)\n        - Added two new methods to make easier to link with openssl:\n          :meth:`include_flags` and :meth:`link_flags`\n        - subclassed versioned_url\n        - Adapted method :meth:`select_build_arch` to API 21+\n        - Add ability to build a legacy version of the openssl libs when using\n          python2legacy or python3crystax.\n\n    .. versionchanged:: 2019.06.06.1.dev0\n\n        - Removed legacy version of openssl libraries\n\n    '''\n\n    version = '3.3.1'\n    url = 'https://www.openssl.org/source/openssl-{version}.tar.gz'\n\n    built_libraries = {\n        'libcrypto.so': '.',\n        'libssl.so': '.',\n    }\n\n    def get_build_dir(self, arch):\n        return join(\n            self.get_build_container_dir(arch), self.name + self.version[0]\n        )\n\n    def include_flags(self, arch):\n        '''Returns a string with the include folders'''\n        openssl_includes = join(self.get_build_dir(arch.arch), 'include')\n        return (' -I' + openssl_includes +\n                ' -I' + join(openssl_includes, 'openssl'))\n\n    def link_dirs_flags(self, arch):\n        '''Returns a string with the appropriate `-L<lib directory>` to link\n        with the openssl libs. This string is usually added to the environment\n        variable `LDFLAGS`'''\n        return ' -L' + self.get_build_dir(arch.arch)\n\n    def link_libs_flags(self):\n        '''Returns a string with the appropriate `-l<lib>` flags to link with\n        the openssl libs. This string is usually added to the environment\n        variable `LIBS`'''\n        return ' -lcrypto -lssl'\n\n    def link_flags(self, arch):\n        '''Returns a string with the flags to link with the openssl libraries\n        in the format: `-L<lib directory> -l<lib>`'''\n        return self.link_dirs_flags(arch) + self.link_libs_flags()\n\n    def get_recipe_env(self, arch=None):\n        env = super().get_recipe_env(arch)\n        env['OPENSSL_VERSION'] = self.version[0]\n        env['CC'] = 'clang'\n        env['ANDROID_NDK_ROOT'] = self.ctx.ndk_dir\n        env[\"PATH\"] = f\"{self.ctx.ndk.llvm_bin_dir}:{env['PATH']}\"\n        env[\"CFLAGS\"] += \" -Wno-macro-redefined\"\n        env[\"MAKE\"] = \"make\"\n        return env\n\n    def select_build_arch(self, arch):\n        aname = arch.arch\n        if 'arm64' in aname:\n            return 'android-arm64'\n        if 'v7a' in aname:\n            return 'android-arm'\n        if 'arm' in aname:\n            return 'android'\n        if 'x86_64' in aname:\n            return 'android-x86_64'\n        if 'x86' in aname:\n            return 'android-x86'\n        return 'linux-armv4'\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            # sh fails with code 255 trying to execute ./Configure\n            # so instead we manually run perl passing in Configure\n            perl = sh.Command('perl')\n            buildarch = self.select_build_arch(arch)\n            config_args = [\n                'shared',\n                'no-dso',\n                'no-asm',\n                'no-tests',\n                buildarch,\n                '-D__ANDROID_API__={}'.format(self.ctx.ndk_api),\n            ]\n            shprint(perl, 'Configure', *config_args, _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n\n\nrecipe = OpenSSLRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pandas/__init__.py",
    "content": "from os.path import join\nfrom pythonforandroid.recipe import MesonRecipe\n\n\nclass PandasRecipe(MesonRecipe):\n    version = 'v2.3.0'\n    url = 'git+https://github.com/pandas-dev/pandas'\n    depends = ['numpy', 'libbz2', 'liblzma']\n    hostpython_prerequisites = [\"Cython<4.0.0a0\", \"versioneer\", \"numpy\"]  # meson does not detects venv's cython\n    patches = ['fix_numpy_includes.patch']\n    python_depends = ['python-dateutil', 'pytz']\n    need_stl_shared = True\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        # we need the includes from our installed numpy at site packages\n        # because we need some includes generated at numpy's compile time\n\n        env['NUMPY_INCLUDES'] = join(\n            self.ctx.get_python_install_dir(arch.arch), \"numpy/_core/include\",\n        )\n        env[\"PYTHON_INCLUDE_DIR\"] = self.ctx.python_recipe.include_root(arch)\n\n        # this flag below is to fix a runtime error:\n        #   ImportError: dlopen failed: cannot locate symbol\n        #   \"_ZTVSt12length_error\" referenced by\n        #   \"/data/data/org.test.matplotlib_testapp/files/app/_python_bundle\n        #   /site-packages/pandas/_libs/window/aggregations.so\"...\n        env['LDFLAGS'] += f' -landroid  -l{self.stl_lib_name}'\n        return env\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        self.restore_hostpython_prerequisites([\"cython\"])\n\n\nrecipe = PandasRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pandas/fix_numpy_includes.patch",
    "content": "diff '--color=auto' -uNr pandas/pandas/_libs/meson.build pandas.mod/pandas/_libs/meson.build\n--- pandas/pandas/_libs/meson.build\t2024-04-24 07:24:46.009296003 +0530\n+++ pandas.mod/pandas/_libs/meson.build\t2024-04-24 07:45:15.221534571 +0530\n@@ -115,7 +115,7 @@\n         ext_name,\n         ext_dict.get('sources'),\n         cython_args: cython_args,\n-        include_directories: [inc_np, inc_pd],\n+        include_directories: [inc_android, inc_np, inc_pd],\n         dependencies: ext_dict.get('deps', ''),\n         subdir: 'pandas/_libs',\n         install: true\ndiff '--color=auto' -uNr pandas/pandas/_libs/tslibs/meson.build pandas.mod/pandas/_libs/tslibs/meson.build\n--- pandas/pandas/_libs/tslibs/meson.build\t2024-04-24 07:24:46.019296090 +0530\n+++ pandas.mod/pandas/_libs/tslibs/meson.build\t2024-04-24 07:45:53.528798309 +0530\n@@ -33,7 +33,7 @@\n         ext_name,\n         ext_dict.get('sources'),\n         cython_args: cython_args,\n-        include_directories: [inc_np, inc_pd],\n+        include_directories: [inc_android, inc_np, inc_pd],\n         dependencies: ext_dict.get('deps', ''),\n         subdir: 'pandas/_libs/tslibs',\n         install: true\ndiff '--color=auto' -uNr pandas/pandas/_libs/window/meson.build pandas.mod/pandas/_libs/window/meson.build\n--- pandas/pandas/_libs/window/meson.build\t2024-04-24 07:24:46.029296177 +0530\n+++ pandas.mod/pandas/_libs/window/meson.build\t2024-04-28 10:47:16.915307381 +0530\n@@ -2,7 +2,7 @@\n     'aggregations',\n     ['aggregations.pyx'],\n     cython_args: ['-X always_allow_keywords=true'],\n-    include_directories: [inc_np, inc_pd],\n+    include_directories: [inc_android, inc_np, inc_pd],\n     subdir: 'pandas/_libs/window',\n     override_options : ['cython_language=cpp'],\n     install: true\n@@ -12,7 +12,7 @@\n     'indexers',\n     ['indexers.pyx'],\n     cython_args: ['-X always_allow_keywords=true'],\n-    include_directories: [inc_np, inc_pd],\n+    include_directories: [inc_android, inc_np, inc_pd],\n     subdir: 'pandas/_libs/window',\n     install: true\n )\ndiff '--color=auto' -uNr pandas/pandas/meson.build pandas.mod/pandas/meson.build\n--- pandas/pandas/meson.build\t2024-04-24 07:24:46.232297943 +0530\n+++ pandas.mod/pandas/meson.build\t2024-04-24 07:46:12.508929590 +0530\n@@ -3,20 +3,23 @@\n     '-c',\n     '''\n import os\n-import numpy as np\n-try:\n-    # Check if include directory is inside the pandas dir\n-    # e.g. a venv created inside the pandas dir\n-    # If so, convert it to a relative path\n-    incdir = os.path.relpath(np.get_include())\n-except Exception:\n-    incdir = np.get_include()\n-print(incdir)\n-     '''\n+print(os.environ[\"NUMPY_INCLUDES\"]) \n+    '''\n+  ],\n+  check: true\n+).stdout().strip()\n+incdir_android = run_command(py,\n+  [\n+    '-c',\n+    '''\n+import os\n+print(os.environ[\"PYTHON_INCLUDE_DIR\"]) \n+    '''\n   ],\n   check: true\n ).stdout().strip()\n \n+inc_android = include_directories(incdir_android)\n inc_np = include_directories(incdir_numpy)\n inc_pd = include_directories('_libs/include')\n \n"
  },
  {
    "path": "pythonforandroid/recipes/pil/__init__.py",
    "content": "from pythonforandroid.recipes.Pillow import PillowRecipe\nfrom pythonforandroid.logger import warning\n\n\nclass PilRecipe(PillowRecipe):\n    \"\"\"A transparent wrapper around the Pillow recipe, it should build\n    Pillow automatically as if \"pillow\" were specified in the\n    requirements.\n    \"\"\"\n\n    name = 'Pillow'  # ensures the Pillow recipe directory is used where necessary\n\n    conflicts = ['pillow']\n\n    def build_arch(self, arch):\n        warning('PIL is no longer supported, building Pillow instead. '\n                'This should be a drop-in replacement.')\n        warning('It is recommended to change \"pil\" to \"pillow\" in your requirements, '\n                'to ensure future compatibility')\n        super().build_arch(arch)\n\n\nrecipe = PilRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/png/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nfrom multiprocessing import cpu_count\nimport sh\n\n\nclass PngRecipe(Recipe):\n    name = 'png'\n    version = '1.6.37'\n    url = 'https://github.com/glennrp/libpng/archive/v{version}.zip'\n    built_libraries = {'libpng16.so': '.libs'}\n\n    def build_arch(self, arch):\n        build_dir = self.get_build_dir(arch.arch)\n        with current_directory(build_dir):\n            env = self.get_recipe_env(arch)\n            shprint(\n                sh.Command('./configure'),\n                '--host=' + arch.command_prefix,\n                '--target=' + arch.command_prefix,\n                '--disable-static',\n                '--enable-shared',\n                '--prefix={}/install'.format(self.get_build_dir(arch.arch)),\n                _env=env,\n            )\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n\n\nrecipe = PngRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/png/build_shared_library.patch",
    "content": "diff --git a/jni/Android.mk b/jni/Android.mk\nindex df2ff1a..2f70985 100644\n--- a/jni/Android.mk\n+++ b/jni/Android.mk\n@@ -26,8 +26,9 @@ LOCAL_SRC_FILES :=\\\n \tarm/filter_neon_intrinsics.c\n \n #LOCAL_SHARED_LIBRARIES := -lz\n-LOCAL_EXPORT_LDLIBS := -lz\n+# LOCAL_EXPORT_LDLIBS := -lz\n+LOCAL_LDLIBS := -lz\n LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.\n \n-#include $(BUILD_SHARED_LIBRARY)\n-include $(BUILD_STATIC_LIBRARY)\n+include $(BUILD_SHARED_LIBRARY)\n+# include $(BUILD_STATIC_LIBRARY)\n"
  },
  {
    "path": "pythonforandroid/recipes/preppy/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass PreppyRecipe(PythonRecipe):\n    version = '27b7085'\n    url = 'https://bitbucket.org/rptlab/preppy/get/{version}.tar.gz'\n    depends = []\n    patches = ['fix-setup.patch']\n    call_hostpython_via_targetpython = False\n\n\nrecipe = PreppyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/preppy/fix-setup.patch",
    "content": "--- a/setup.py\t2017-11-20 13:53:42.000000000 +0000\n+++ b/setup.py\t2017-11-20 14:00:44.862203526 +0000\n@@ -15,35 +15,6 @@\n \n     import preppy\n     version = preppy.VERSION\n-    scriptsPath=os.path.join(pkgDir,'build','scripts')\n-\n-    def makeScript(modName):\n-        try:\n-            bat=sys.platform in ('win32','amd64')\n-            scriptPath=os.path.join(scriptsPath,modName+(bat and '.bat' or ''))\n-            exePath=sys.executable\n-            f = open(scriptPath,'w')\n-            try:\n-                if bat:\n-                    text = '@echo off\\nrem startup script for %s-%s\\n\"%s\" -m \"%s\" %%*\\n' % (modName,version,exePath,modName)\n-                else:\n-                    text = '#!/bin/sh\\n#startup script for %s-%s\\nexec \"%s\" -m \"%s\" $*\\n' % (modName,version,exePath,modName)\n-                f.write(text)\n-            finally:\n-                f.close()\n-        except:\n-            print('script for %s not created or erroneous' % modName)\n-            import traceback\n-            traceback.print_exc(file=sys.stdout)\n-            return None\n-        print('Created \"%s\"' % scriptPath)\n-        return scriptPath\n-\n-    scripts = []\n-    if not os.path.isdir(scriptsPath): os.makedirs(scriptsPath)\n-    scripts.extend(filter(None,[\n-            makeScript('preppy'),\n-        ]))\n \n     setup(name='preppy',\n         version=version,\n@@ -52,5 +23,4 @@\n         author_email='andy@reportlab.com',\n         url='http://bitbucket.org/rptlab/preppy',\n         py_modules=['preppy'],\n-        scripts=scripts,\n         )\n"
  },
  {
    "path": "pythonforandroid/recipes/primp/__init__.py",
    "content": "from pythonforandroid.logger import info\nfrom pythonforandroid.recipe import RustCompiledComponentsRecipe\n\n\nclass PrimpRecipe(RustCompiledComponentsRecipe):\n    version = \"v0.14.0\"\n    url = \"https://github.com/deedy5/primp/archive/refs/tags/{version}.tar.gz\"\n\n    def get_recipe_env_post(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env[\"ANDROID_NDK_HOME\"] = self.ctx.ndk.llvm_prebuilt_dir\n        return env\n\n    def get_recipe_env_pre(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env[\"ANDROID_NDK_HOME\"] = self.ctx.ndk_dir\n        return env\n\n    def build_arch(self, arch):\n        # Why need of two env?\n        # Because there are two dependencies which accepts\n        # different ANDROID_NDK_HOME\n        self.get_recipe_env = self.get_recipe_env_pre\n        prebuild_ = super().build_arch\n        try:\n            prebuild_(arch)\n        except Exception:\n            info(\"pyreqwest_impersonate first build failed, as expected\")\n            self.get_recipe_env = self.get_recipe_env_post\n            prebuild_(arch)\n\n\nrecipe = PrimpRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/protobuf_cpp/__init__.py",
    "content": "from multiprocessing import cpu_count\nimport os\nfrom os.path import exists, join\nfrom pythonforandroid.toolchain import info\nimport sh\nimport sys\n\nfrom pythonforandroid.recipe import CppCompiledComponentsPythonRecipe\nfrom pythonforandroid.logger import shprint, info_notify\nfrom pythonforandroid.util import current_directory, touch\n\n\nclass ProtobufCppRecipe(CppCompiledComponentsPythonRecipe):\n    \"\"\"This is a two-in-one recipe:\n      - build labraru `libprotobuf.so`\n      - build and install python binding for protobuf_cpp\n    \"\"\"\n    name = 'protobuf_cpp'\n    version = '3.6.1'\n    url = 'https://github.com/google/protobuf/releases/download/v{version}/protobuf-python-{version}.tar.gz'\n    call_hostpython_via_targetpython = False\n    depends = ['cffi', 'setuptools']\n    site_packages_name = 'google/protobuf/pyext'\n    setup_extra_args = ['--cpp_implementation']\n    built_libraries = {'libprotobuf.so': 'src/.libs'}\n    protoc_dir = None\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n\n        patch_mark = join(self.get_build_dir(arch.arch), '.protobuf-patched')\n        if self.ctx.python_recipe.name == 'python3' and not exists(patch_mark):\n            self.apply_patch('fix-python3-compatibility.patch', arch.arch)\n            touch(patch_mark)\n\n        # During building, host needs to transpile .proto files to .py\n        # ideally with the same version as protobuf runtime, or with an older one.\n        # Because protoc is compiled for target (i.e. Android), we need an other binary\n        # which can be run by host.\n        # To make it easier, we download prebuild protoc binary adapted to the platform\n\n        info_notify(\"Downloading protoc compiler for your platform\")\n        url_prefix = \"https://github.com/protocolbuffers/protobuf/releases/download/v{version}\".format(version=self.version)\n        if sys.platform.startswith('linux'):\n            info_notify(\"GNU/Linux detected\")\n            filename = \"protoc-{version}-linux-x86_64.zip\".format(version=self.version)\n        elif sys.platform.startswith('darwin'):\n            info_notify(\"Mac OS X detected\")\n            filename = \"protoc-{version}-osx-x86_64.zip\".format(version=self.version)\n        else:\n            info_notify(\"Your platform is not supported, but recipe can still \"\n                        \"be built if you have a valid protoc (<={version}) in \"\n                        \"your path\".format(version=self.version))\n            return\n\n        protoc_url = join(url_prefix, filename)\n        self.protoc_dir = join(self.ctx.build_dir, \"tools\", \"protoc\")\n        if os.path.exists(join(self.protoc_dir, \"bin\", \"protoc\")):\n            info_notify(\"protoc found, no download needed\")\n            return\n        try:\n            os.makedirs(self.protoc_dir)\n        except OSError as e:\n            # if dir already exists (errno 17), we ignore the error\n            if e.errno != 17:\n                raise e\n        info_notify(\"Will download into {dest_dir}\".format(dest_dir=self.protoc_dir))\n        self.download_file(protoc_url, join(self.protoc_dir, filename))\n        with current_directory(self.protoc_dir):\n            shprint(sh.unzip, join(self.protoc_dir, filename))\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        # Build libproto.so\n        with current_directory(self.get_build_dir(arch.arch)):\n            build_arch = (\n                shprint(sh.gcc, '-dumpmachine')\n                .stdout.decode('utf-8')\n                .split('\\n')[0]\n            )\n\n            if not exists('configure'):\n                shprint(sh.Command('./autogen.sh'), _env=env)\n\n            shprint(sh.Command('./configure'),\n                    '--build={}'.format(build_arch),\n                    '--host={}'.format(arch.command_prefix),\n                    '--target={}'.format(arch.command_prefix),\n                    '--disable-static',\n                    '--enable-shared',\n                    _env=env)\n\n            with current_directory(join(self.get_build_dir(arch.arch), 'src')):\n                shprint(sh.make, 'libprotobuf.la', '-j'+str(cpu_count()), _env=env)\n\n        self.install_python_package(arch)\n\n    def build_compiled_components(self, arch):\n        # Build python bindings and _message.so\n        env = self.get_recipe_env(arch)\n        with current_directory(join(self.get_build_dir(arch.arch), 'python')):\n            hostpython = sh.Command(self.hostpython_location)\n            shprint(hostpython,\n                    'setup.py',\n                    'build_ext',\n                    _env=env, *self.setup_extra_args)\n\n    def install_python_package(self, arch):\n        env = self.get_recipe_env(arch)\n\n        info('Installing {} into site-packages'.format(self.name))\n\n        with current_directory(join(self.get_build_dir(arch.arch), 'python')):\n            hostpython = sh.Command(self.hostpython_location)\n\n            hpenv = env.copy()\n            shprint(hostpython, 'setup.py', 'install', '-O2',\n                    '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),\n                    '--install-lib=.',\n                    _env=hpenv, *self.setup_extra_args)\n\n        # Create __init__.py which is missing, see also:\n        #   - https://github.com/protocolbuffers/protobuf/issues/1296\n        #   - https://stackoverflow.com/questions/13862562/\n        #   google-protocol-buffers-not-found-when-trying-to-freeze-python-app\n        open(\n            join(self.ctx.get_site_packages_dir(arch), 'google', '__init__.py'),\n            'a',\n        ).close()\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        if self.protoc_dir is not None:\n            # we need protoc with binary for host platform\n            env['PROTOC'] = join(self.protoc_dir, 'bin', 'protoc')\n        env['TARGET_OS'] = 'OS_ANDROID_CROSSCOMPILE'\n        env['CXXFLAGS'] += ' -std=c++11'\n        env['LDFLAGS'] += ' -lm -landroid -llog'\n        return env\n\n\nrecipe = ProtobufCppRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch",
    "content": "From 539bc017a62f91bdf7c547b58948cb5a2f59d918 Mon Sep 17 00:00:00 2001\nFrom: Ben Webb <ben@salilab.org>\nDate: Thu, 12 Jul 2018 10:58:10 -0700\nSubject: [PATCH] Add Python 3.7 compatibility (#4862)\n\nCompilation of Python wrappers fails with Python 3.7 because\nthe Python folks changed their C API such that\nPyUnicode_AsUTF8AndSize() now returns a const char* rather\nthan a char*. Add a patch to work around. Relates #4086.\n---\n python/google/protobuf/pyext/descriptor.cc            | 2 +-\n python/google/protobuf/pyext/descriptor_containers.cc | 2 +-\n python/google/protobuf/pyext/descriptor_pool.cc       | 2 +-\n python/google/protobuf/pyext/extension_dict.cc        | 2 +-\n python/google/protobuf/pyext/message.cc               | 4 ++--\n 5 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/python/google/protobuf/pyext/descriptor.cc b/python/google/protobuf/pyext/descriptor.cc\nindex 8af0cb1289..19a1c38a62 100644\n--- a/python/google/protobuf/pyext/descriptor.cc\n+++ b/python/google/protobuf/pyext/descriptor.cc\n@@ -56,7 +56,7 @@\n   #endif\n   #define PyString_AsStringAndSize(ob, charpp, sizep) \\\n     (PyUnicode_Check(ob)? \\\n-       ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \\\n+       ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \\\n        PyBytes_AsStringAndSize(ob, (charpp), (sizep)))\n #endif\n\ndiff --git a/python/google/protobuf/pyext/descriptor_containers.cc b/python/google/protobuf/pyext/descriptor_containers.cc\nindex bc007f7efa..0153664f50 100644\n--- a/python/google/protobuf/pyext/descriptor_containers.cc\n+++ b/python/google/protobuf/pyext/descriptor_containers.cc\n@@ -66,7 +66,7 @@\n   #endif\n   #define PyString_AsStringAndSize(ob, charpp, sizep) \\\n     (PyUnicode_Check(ob)? \\\n-       ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \\\n+       ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \\\n        PyBytes_AsStringAndSize(ob, (charpp), (sizep)))\n #endif\n\ndiff --git a/python/google/protobuf/pyext/descriptor_pool.cc b/python/google/protobuf/pyext/descriptor_pool.cc\nindex 95882aeb35..962accc6e9 100644\n--- a/python/google/protobuf/pyext/descriptor_pool.cc\n+++ b/python/google/protobuf/pyext/descriptor_pool.cc\n@@ -48,7 +48,7 @@\n   #endif\n   #define PyString_AsStringAndSize(ob, charpp, sizep) \\\n     (PyUnicode_Check(ob)? \\\n-       ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \\\n+       ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \\\n        PyBytes_AsStringAndSize(ob, (charpp), (sizep)))\n #endif\n\ndiff --git a/python/google/protobuf/pyext/extension_dict.cc b/python/google/protobuf/pyext/extension_dict.cc\nindex 018b5c2c49..174c5470c2 100644\n--- a/python/google/protobuf/pyext/extension_dict.cc\n+++ b/python/google/protobuf/pyext/extension_dict.cc\n@@ -53,7 +53,7 @@\n   #endif\n   #define PyString_AsStringAndSize(ob, charpp, sizep) \\\n     (PyUnicode_Check(ob)? \\\n-       ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \\\n+       ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \\\n        PyBytes_AsStringAndSize(ob, (charpp), (sizep)))\n #endif\n\ndiff --git a/python/google/protobuf/pyext/message.cc b/python/google/protobuf/pyext/message.cc\nindex 5893533adf..31094b7e10 100644\n--- a/python/google/protobuf/pyext/message.cc\n+++ b/python/google/protobuf/pyext/message.cc\n@@ -79,7 +79,7 @@\n     (PyUnicode_Check(ob)? PyUnicode_AsUTF8(ob): PyBytes_AsString(ob))\n   #define PyString_AsStringAndSize(ob, charpp, sizep) \\\n     (PyUnicode_Check(ob)? \\\n-       ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \\\n+       ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \\\n        PyBytes_AsStringAndSize(ob, (charpp), (sizep)))\n   #endif\n #endif\n@@ -1529,7 +1529,7 @@ PyObject* HasField(CMessage* self, PyObject* arg) {\n     return NULL;\n   }\n #else\n-  field_name = PyUnicode_AsUTF8AndSize(arg, &size);\n+  field_name = const_cast<char*>(PyUnicode_AsUTF8AndSize(arg, &size));\n   if (!field_name) {\n     return NULL;\n   }\n"
  },
  {
    "path": "pythonforandroid/recipes/psycopg2/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nimport sh\n\n\nclass Psycopg2Recipe(PythonRecipe):\n    \"\"\"\n    Requires `libpq-dev` system dependency e.g. for `pg_config` binary.\n    If you get `nl_langinfo` symbol runtime error, make sure you're running on\n    `ANDROID_API` (`ndk-api`) >= 26, see:\n    https://github.com/kivy/python-for-android/issues/1711#issuecomment-465747557\n    \"\"\"\n    version = '2.8.5'\n    url = 'https://pypi.python.org/packages/source/p/psycopg2/psycopg2-{version}.tar.gz'\n    depends = ['libpq', 'setuptools']\n    site_packages_name = 'psycopg2'\n    call_hostpython_via_targetpython = False\n\n    def prebuild_arch(self, arch):\n        libdir = self.ctx.get_libs_dir(arch.arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            # pg_config_helper will return the system installed libpq, but we\n            # need the one we just cross-compiled\n            shprint(sh.sed, '-i',\n                    \"s|pg_config_helper.query(.libdir.)|'{}'|\".format(libdir),\n                    'setup.py')\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['LDFLAGS'] = \"{} -L{}\".format(env['LDFLAGS'], self.ctx.get_libs_dir(arch.arch))\n        env['EXTRA_CFLAGS'] = \"--host linux-armv\"\n        return env\n\n    def install_python_package(self, arch, name=None, env=None, is_dir=True):\n        '''Automate the installation of a Python package (or a cython\n        package where the cython components are pre-built).'''\n        if env is None:\n            env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            hostpython = sh.Command(self.ctx.hostpython)\n\n            shprint(hostpython, 'setup.py', 'build_ext', '--static-libpq',\n                    _env=env)\n            shprint(hostpython, 'setup.py', 'install', '-O2',\n                    '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),\n                    '--install-lib=.', _env=env)\n\n\nrecipe = Psycopg2Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/py3dns/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass Py3DNSRecipe(PythonRecipe):\n    site_packages_name = 'DNS'\n    version = '3.2.1'\n    url = 'https://launchpad.net/py3dns/trunk/{version}/+download/py3dns-{version}.tar.gz'\n    depends = ['setuptools']\n    patches = ['patches/android.patch']\n    call_hostpython_via_targetpython = False\n\n\nrecipe = Py3DNSRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/py3dns/patches/android.patch",
    "content": "diff --git a/DNS/Base.py b/DNS/Base.py\nindex 34a6da7..a558889 100644\n--- a/DNS/Base.py\n+++ b/DNS/Base.py\n@@ -15,6 +15,7 @@ import socket, string, types, time, select\n import errno\n from . import Type,Class,Opcode\n import asyncore\n+import os\n #\n # This random generator is used for transaction ids and port selection.  This\n # is important to prevent spurious results from lost packets, and malicious\n@@ -50,8 +51,12 @@ defaults= { 'protocol':'udp', 'port':53, 'opcode':Opcode.QUERY,\n \n def ParseResolvConf(resolv_path=\"/etc/resolv.conf\"):\n     \"parses the /etc/resolv.conf file and sets defaults for name servers\"\n-    with open(resolv_path, 'r') as stream:\n-        return ParseResolvConfFromIterable(stream)\n+    if os.path.exists(resolv_path):\n+        with open(resolv_path, 'r') as stream:\n+            return ParseResolvConfFromIterable(stream)\n+    else:\n+        defaults['server'].append('127.0.0.1')\n+        return\n \n def ParseResolvConfFromIterable(lines):\n     \"parses a resolv.conf formatted stream and sets defaults for name servers\"\n"
  },
  {
    "path": "pythonforandroid/recipes/pyaml/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass PyamlRecipe(PythonRecipe):\n    version = \"15.8.2\"\n    url = 'https://pypi.python.org/packages/source/p/pyaml/pyaml-{version}.tar.gz'\n    depends = [\"setuptools\"]\n    site_packages_name = 'yaml'\n    call_hostpython_via_targetpython = False\n\n\nrecipe = PyamlRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pybind11/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\r\nfrom os.path import join\r\n\r\n\r\nclass Pybind11Recipe(PythonRecipe):\r\n\r\n    version = '2.11.1'\r\n    url = 'https://github.com/pybind/pybind11/archive/refs/tags/v{version}.zip'\r\n    depends = ['setuptools']\r\n    call_hostpython_via_targetpython = False\r\n    install_in_hostpython = True\r\n\r\n    def get_include_dir(self, arch):\r\n        return join(self.get_build_dir(arch.arch), 'include')\r\n\r\n\r\nrecipe = Pybind11Recipe()\r\n"
  },
  {
    "path": "pythonforandroid/recipes/pycairo/__init__.py",
    "content": "from pythonforandroid.recipe import MesonRecipe\nfrom os.path import join\n\n\nclass PyCairoRecipe(MesonRecipe):\n    version = '1.28.0'\n    url = 'https://github.com/pygobject/pycairo/releases/download/v{version}/pycairo-{version}.tar.gz'\n    name = 'pycairo'\n    site_packages_name = 'cairo'\n    depends = ['libcairo']\n    patches = [\"meson.patch\"]\n\n    def build_arch(self, arch):\n\n        include_path = join(self.get_recipe('libcairo', self.ctx).get_build_dir(arch), \"install\", \"include\", \"cairo\")\n        lib_path = self.ctx.get_libs_dir(arch.arch)\n\n        self.extra_build_args += [\n            f'-Csetup-args=-Dcairo_include={include_path}',\n            f'-Csetup-args=-Dcairo_lib={lib_path}',\n        ]\n\n        super().build_arch(arch)\n\n\nrecipe = PyCairoRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pycairo/meson.patch",
    "content": "diff '--color=auto' -uNr pycairo-1.28.0/cairo/meson.build pycairo-1.28.0.mod/cairo/meson.build\n--- pycairo-1.28.0/cairo/meson.build\t2025-04-15 00:22:30.000000000 +0530\n+++ pycairo-1.28.0.mod/cairo/meson.build\t2025-07-14 21:56:34.782983845 +0530\n@@ -28,7 +28,10 @@\n   fs.copyfile(python_file, python_file)\n endforeach\n \n-cairo_dep = dependency('cairo', version: cair_version_req, required: cc.get_id() != 'msvc')\n+cairo_dep = declare_dependency(\n+  include_directories: include_directories(get_option('cairo_include')),\n+  link_args: ['-L' + get_option('cairo_lib'), '-lcairo']\n+)\n \n if cc.get_id() == 'msvc' and not cairo_dep.found()\n   if cc.has_header('cairo.h')\ndiff '--color=auto' -uNr pycairo-1.28.0/meson_options.txt pycairo-1.28.0.mod/meson_options.txt\n--- pycairo-1.28.0/meson_options.txt\t2025-04-15 00:22:30.000000000 +0530\n+++ pycairo-1.28.0.mod/meson_options.txt\t2025-07-14 21:56:52.824191314 +0530\n@@ -1,3 +1,5 @@\n option('python', type : 'string', value : 'python3')\n option('tests', type : 'boolean', value : true, description : 'build unit tests')\n option('wheel', type : 'boolean', value : false, description : 'build for a Python wheel')\n+option('cairo_include', type: 'string', value: '', description: 'Path to cairo headers')\n+option('cairo_lib', type: 'string', value: '', description: 'Path to cairo libraries')\n"
  },
  {
    "path": "pythonforandroid/recipes/pycparser/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass PycparserRecipe(PythonRecipe):\n    name = 'pycparser'\n    version = '2.14'\n    url = 'https://pypi.python.org/packages/source/p/pycparser/pycparser-{version}.tar.gz'\n\n    depends = ['setuptools']\n\n    call_hostpython_via_targetpython = False\n\n    install_in_hostpython = True\n\n\nrecipe = PycparserRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pycrypto/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe\nfrom pythonforandroid.toolchain import (\n    current_directory,\n    info,\n    shprint,\n)\nimport sh\n\n\nclass PyCryptoRecipe(CompiledComponentsPythonRecipe):\n    version = '2.7a1'\n    url = 'https://github.com/dlitz/pycrypto/archive/v{version}.zip'\n    depends = ['openssl', 'python3']\n    site_packages_name = 'Crypto'\n    call_hostpython_via_targetpython = False\n    patches = ['add_length.patch']\n\n    def get_recipe_env(self, arch=None):\n        env = super().get_recipe_env(arch)\n        openssl_recipe = Recipe.get_recipe('openssl', self.ctx)\n        env['CC'] = env['CC'] + openssl_recipe.include_flags(arch)\n\n        env['LDFLAGS'] += ' -L{}'.format(self.ctx.get_libs_dir(arch.arch))\n        env['LDFLAGS'] += ' -L{}'.format(self.ctx.libs_dir)\n        env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)\n        env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags()\n\n        env['EXTRA_CFLAGS'] = '--host linux-armv'\n        env['ac_cv_func_malloc_0_nonnull'] = 'yes'\n        return env\n\n    def build_compiled_components(self, arch):\n        info('Configuring compiled components in {}'.format(self.name))\n\n        env = self.get_recipe_env(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            configure = sh.Command('./configure')\n            shprint(configure, '--host=arm-eabi',\n                    '--prefix={}'.format(self.ctx.get_python_install_dir(arch.arch)),\n                    '--enable-shared', _env=env)\n        super().build_compiled_components(arch)\n\n\nrecipe = PyCryptoRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pycrypto/add_length.patch",
    "content": "--- pycrypto-2.6.1/src/hash_SHA2_template.c.orig\t2013-10-14 14:38:10.000000000 -0700\n+++ pycrypto-2.6.1/src/hash_SHA2_template.c\t2014-05-19 10:15:51.000000000 -0700\n@@ -87,7 +87,7 @@\n  * return 1 on success\n  * return 0 if the length overflows\n  */\n-int add_length(hash_state *hs, sha2_word_t inc) {\n+static int add_length(hash_state *hs, sha2_word_t inc) {\n     sha2_word_t overflow_detector;\n     overflow_detector = hs->length_lower;\n     hs->length_lower += inc;\n"
  },
  {
    "path": "pythonforandroid/recipes/pycryptodome/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass PycryptodomeRecipe(PyProjectRecipe):\n    version = '3.23.0'\n    url = 'https://github.com/Legrandin/pycryptodome/archive/refs/tags/v{version}.tar.gz'\n    depends = ['cffi']\n\n\nrecipe = PycryptodomeRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pydantic-core/__init__.py",
    "content": "from pythonforandroid.recipe import RustCompiledComponentsRecipe\n\n\nclass PydanticcoreRecipe(RustCompiledComponentsRecipe):\n    version = \"2.41.4\"\n    url = \"https://github.com/pydantic/pydantic-core/archive/refs/tags/v{version}.tar.gz\"\n    site_packages_name = \"pydantic_core\"\n\n\nrecipe = PydanticcoreRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pygame/__init__.py",
    "content": "from os.path import join\n\nfrom pythonforandroid.recipe import CompiledComponentsPythonRecipe\nfrom pythonforandroid.toolchain import current_directory\n\n\nclass Pygame2Recipe(CompiledComponentsPythonRecipe):\n    \"\"\"\n    Recipe to build apps based on SDL2-based pygame.\n\n    .. warning:: Some pygame functionality is still untested, and some\n        dependencies like freetype, postmidi and libjpeg are currently\n        not part of the build. It's usable, but not complete.\n    \"\"\"\n\n    version = '2.1.0'\n    url = 'https://github.com/pygame/pygame/archive/{version}.tar.gz'\n\n    site_packages_name = 'pygame'\n    name = 'pygame'\n\n    depends = ['sdl2', 'sdl2_image', 'sdl2_mixer', 'sdl2_ttf', 'setuptools', 'jpeg', 'png']\n    call_hostpython_via_targetpython = False  # Due to setuptools\n    install_in_hostpython = False\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            setup_template = open(join(\"buildconfig\", \"Setup.Android.SDL2.in\")).read()\n            env = self.get_recipe_env(arch)\n            env['ANDROID_ROOT'] = join(self.ctx.ndk.sysroot, 'usr')\n\n            png = self.get_recipe('png', self.ctx)\n            png_lib_dir = join(png.get_build_dir(arch.arch), '.libs')\n            png_inc_dir = png.get_build_dir(arch)\n\n            jpeg = self.get_recipe('jpeg', self.ctx)\n            jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch)\n\n            sdl_mixer_includes = \"\"\n            sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)\n            for include_dir in sdl2_mixer_recipe.get_include_dirs(arch):\n                sdl_mixer_includes += f\"-I{include_dir} \"\n\n            sdl2_image_includes = \"\"\n            sdl2_image_recipe = self.get_recipe('sdl2_image', self.ctx)\n            for include_dir in sdl2_image_recipe.get_include_dirs(arch):\n                sdl2_image_includes += f\"-I{include_dir} \"\n\n            setup_file = setup_template.format(\n                sdl_includes=(\n                    \" -I\" + join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include') +\n                    \" -L\" + join(self.ctx.bootstrap.build_dir, \"libs\", str(arch)) +\n                    \" -L\" + png_lib_dir + \" -L\" + jpeg_lib_dir + \" -L\" + arch.ndk_lib_dir_versioned),\n                sdl_ttf_includes=\"-I\"+join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'),\n                sdl_image_includes=sdl2_image_includes,\n                sdl_mixer_includes=sdl_mixer_includes,\n                jpeg_includes=\"-I\"+jpeg_inc_dir,\n                png_includes=\"-I\"+png_inc_dir,\n                freetype_includes=\"\"\n            )\n            open(\"Setup\", \"w\").write(setup_file)\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env['USE_SDL2'] = '1'\n        env[\"PYGAME_CROSS_COMPILE\"] = \"TRUE\"\n        env[\"PYGAME_ANDROID\"] = \"TRUE\"\n        return env\n\n\nrecipe = Pygame2Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyicu/__init__.py",
    "content": "from os.path import join\nfrom pythonforandroid.recipe import CppCompiledComponentsPythonRecipe\n\n\nclass PyICURecipe(CppCompiledComponentsPythonRecipe):\n    version = '1.9.2'\n    url = ('https://pypi.python.org/packages/source/P/PyICU/'\n           'PyICU-{version}.tar.gz')\n    depends = [\"icu\"]\n    patches = ['locale.patch']\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n\n        icu_include = join(\n            self.ctx.get_python_install_dir(arch.arch), \"include\", \"icu\")\n\n        icu_recipe = self.get_recipe('icu', self.ctx)\n        icu_link_libs = icu_recipe.built_libraries.keys()\n        env[\"PYICU_LIBRARIES\"] = \":\".join(lib[3:-3] for lib in icu_link_libs)\n        env[\"CPPFLAGS\"] += \" -I\" + icu_include\n        env[\"LDFLAGS\"] += \" -L\" + join(\n            icu_recipe.get_build_dir(arch.arch), \"icu_build\", \"lib\"\n        )\n\n        return env\n\n\nrecipe = PyICURecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyicu/locale.patch",
    "content": "diff -Naur locale.cpp locale1.cpp\n--- pyicu/locale.cpp\t2015-04-29 07:32:39.000000000 +0200\n+++ locale1.cpp\t2016-05-12 17:13:08.990059346 +0200\n@@ -27,7 +27,7 @@\n #if defined(_MSC_VER) || defined(__WIN32)\n #include <windows.h>\n #else\n-#include <sys/fcntl.h>\n+#include <fcntl.h>\n #include <sys/stat.h>\n #include <sys/mman.h>\n #endif\n"
  },
  {
    "path": "pythonforandroid/recipes/pyjnius/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\nfrom pythonforandroid.toolchain import shprint, current_directory, info\nfrom pythonforandroid.patching import will_build\nimport sh\nfrom os.path import join\n\n\nclass PyjniusRecipe(PyProjectRecipe):\n    version = '1.7.0'\n    url = 'https://github.com/kivy/pyjnius/archive/{version}.zip'\n    name = 'pyjnius'\n    depends = [('genericndkbuild', 'sdl2', 'sdl3'), 'six']\n    site_packages_name = 'jnius'\n    hostpython_prerequisites = [\"Cython<3.2\"]\n    patches = [\n        \"use_cython.patch\",\n        ('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild')),\n        ('sdl3_jnienv_getter.patch', will_build('sdl3')),\n    ]\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n\n        # Taken from CythonRecipe\n        env['LDFLAGS'] = env['LDFLAGS'] + ' -L{} '.format(\n            self.ctx.get_libs_dir(arch.arch) +\n            ' -L{} '.format(self.ctx.libs_dir) +\n            ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'obj', 'local',\n                                arch.arch)))\n        env['LDSHARED'] = env['CC'] + ' -shared'\n        env['LIBLINK'] = 'NOTNONE'\n\n        # NDKPLATFORM is our switch for detecting Android platform, so can't be None\n        env['NDKPLATFORM'] = \"NOTNONE\"\n        return env\n\n    def postbuild_arch(self, arch):\n        super().postbuild_arch(arch)\n        info('Copying pyjnius java class to classes build dir')\n        with current_directory(self.get_build_dir(arch.arch)):\n            shprint(sh.cp, '-a', join('jnius', 'src', 'org'), self.ctx.javaclass_dir)\n\n\nrecipe = PyjniusRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch",
    "content": "diff -Naur pyjnius.orig/jnius/env.py pyjnius/jnius/env.py\n--- pyjnius.orig/jnius/env.py\t2022-05-28 11:16:02.000000000 +0200\n+++ pyjnius/jnius/env.py\t2022-05-28 11:18:30.000000000 +0200\n@@ -268,7 +268,7 @@\n \n class AndroidJavaLocation(UnixJavaLocation):\n     def get_libraries(self):\n-        return ['SDL2', 'log']\n+        return ['main', 'log']\n \n     def get_include_dirs(self):\n         # When cross-compiling for Android, we should not use the include dirs\ndiff -Naur pyjnius.orig/jnius/jnius_jvm_android.pxi pyjnius/jnius/jnius_jvm_android.pxi\n--- pyjnius.orig/jnius/jnius_jvm_android.pxi\t2022-05-28 11:16:02.000000000 +0200\n+++ pyjnius/jnius/jnius_jvm_android.pxi\t2022-05-28 11:17:17.000000000 +0200\n@@ -1,6 +1,6 @@\n # on android, rely on SDL to get the JNI env\n-cdef extern JNIEnv *SDL_AndroidGetJNIEnv()\n+cdef extern JNIEnv *WebView_AndroidGetJNIEnv()\n \n \n cdef JNIEnv *get_platform_jnienv() except NULL:\n-    return <JNIEnv*>SDL_AndroidGetJNIEnv()\n+    return <JNIEnv*>WebView_AndroidGetJNIEnv()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyjnius/sdl3_jnienv_getter.patch",
    "content": "diff -Naur pyjnius.orig/jnius/env.py pyjnius/jnius/env.py\n--- pyjnius.orig/jnius/env.py\t2022-05-28 11:16:02.000000000 +0200\n+++ pyjnius/jnius/env.py\t2022-05-28 11:18:30.000000000 +0200\n@@ -268,7 +268,7 @@\n \n class AndroidJavaLocation(UnixJavaLocation):\n     def get_libraries(self):\n-        return ['SDL2', 'log']\n+        return ['SDL3', 'log']\n \n     def get_include_dirs(self):\n         # When cross-compiling for Android, we should not use the include dirs\ndiff -Naur pyjnius.orig/jnius/jnius_jvm_android.pxi pyjnius/jnius/jnius_jvm_android.pxi\n--- pyjnius.orig/jnius/jnius_jvm_android.pxi\t2022-05-28 11:16:02.000000000 +0200\n+++ pyjnius/jnius/jnius_jvm_android.pxi\t2022-05-28 11:17:17.000000000 +0200\n@@ -1,6 +1,6 @@\n # on android, rely on SDL to get the JNI env\n-cdef extern JNIEnv *SDL_AndroidGetJNIEnv()\n+cdef extern JNIEnv *SDL_GetAndroidJNIEnv()\n \n \n cdef JNIEnv *get_platform_jnienv() except NULL:\n-    return <JNIEnv*>SDL_AndroidGetJNIEnv()\n+    return <JNIEnv*>SDL_GetAndroidJNIEnv()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyjnius/use_cython.patch",
    "content": "--- pyjnius-1.6.1/setup.py\t2023-11-05 21:07:43.000000000 +0530\n+++ pyjnius-1.6.1.mod/setup.py\t2025-03-01 14:47:11.964847337 +0530\n@@ -59,10 +59,6 @@\n if NDKPLATFORM is not None and getenv('LIBLINK'):\n     PLATFORM = 'android'\n \n-# detect platform\n-if PLATFORM == 'android':\n-    PYX_FILES = [fn[:-3] + 'c' for fn in PYX_FILES]\n-\n JAVA=get_java_setup(PLATFORM)\n \n assert JAVA.is_jdk(), \"You need a JDK, we only found a JRE. Try setting JAVA_HOME\"\n"
  },
  {
    "path": "pythonforandroid/recipes/pyleveldb/__init__.py",
    "content": "from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe\n\n\nclass PyLevelDBRecipe(CppCompiledComponentsPythonRecipe):\n    version = '0.194'\n    url = ('https://pypi.python.org/packages/source/l/leveldb/'\n           'leveldb-{version}.tar.gz')\n    depends = ['snappy', 'leveldb', 'setuptools']\n    patches = ['bindings-only.patch']\n    site_packages_name = 'leveldb'\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n\n        snappy_recipe = self.get_recipe('snappy', self.ctx)\n        leveldb_recipe = self.get_recipe('leveldb', self.ctx)\n\n        env[\"LDFLAGS\"] += \" -L\" + snappy_recipe.get_build_dir(arch.arch)\n        env[\"LDFLAGS\"] += \" -L\" + leveldb_recipe.get_build_dir(arch.arch)\n\n        env[\"SNAPPY_BUILD_PATH\"] = snappy_recipe.get_build_dir(arch.arch)\n        env[\"LEVELDB_BUILD_PATH\"] = leveldb_recipe.get_build_dir(arch.arch)\n\n        return env\n\n\nrecipe = PyLevelDBRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyleveldb/bindings-only.patch",
    "content": "This patch force to only build the python bindings, and to do so, we modify\nthe setup.py file in oder that finds our compiled libraries (libleveldb.so and\nlibsnappy.so)\n--- leveldb-0.194/setup.py.orig\t2016-09-17 02:05:55.000000000 +0200\n+++ leveldb-0.194/setup.py\t2019-02-26 16:57:40.997435911 +0100\n@@ -11,44 +11,25 @@ import platform\n import sys\n\n from setuptools import setup, Extension\n+from os import environ\n\n system, node, release, version, machine, processor = platform.uname()\n-common_flags = [\n-    '-I./leveldb/include',\n-    '-I./leveldb',\n-    '-I./snappy',\n+extra_compile_args = [\n+    '-I{}/include'.format(environ.get('LEVELDB_BUILD_PATH')),\n+    '-I{}'.format(environ.get('LEVELDB_BUILD_PATH')),\n+    '-I{}'.format(environ.get('SNAPPY_BUILD_PATH')),\n+    '-I.',\n     '-I.',\n-    '-fno-builtin-memcmp',\n     '-O2',\n     '-fPIC',\n     '-DNDEBUG',\n     '-DSNAPPY',\n+    '-pthread',\n+    '-Wall',\n+    '-D_REENTRANT',\n+    '-DOS_ANDROID',\n ]\n\n-if system == 'Darwin':\n-    extra_compile_args = common_flags + [\n-        '-DOS_MACOSX',\n-        '-DLEVELDB_PLATFORM_POSIX',\n-        '-Wno-error=unused-command-line-argument-hard-error-in-future',\n-    ]\n-elif system == 'Linux':\n-    extra_compile_args = common_flags + [\n-        '-pthread',\n-        '-Wall',\n-        '-DOS_LINUX',\n-        '-DLEVELDB_PLATFORM_POSIX',\n-    ]\n-elif system == 'SunOS':\n-  extra_compile_args = common_flags + [\n-      '-pthread',\n-      '-Wall',\n-      '-DOS_SOLARIS',\n-      '-DLEVELDB_PLATFORM_POSIX',\n-      ]\n-else:\n-    sys.stderr.write(\"Don't know how to compile leveldb for %s!\\n\" % system)\n-    sys.exit(1)\n-\n setup(\n     name = 'leveldb',\n     version = '0.194',\n@@ -81,57 +62,11 @@ setup(\n     ext_modules = [\n         Extension('leveldb',\n             sources = [\n-                # snappy\n-                './snappy/snappy.cc',\n-                './snappy/snappy-stubs-internal.cc',\n-                './snappy/snappy-sinksource.cc',\n-                './snappy/snappy-c.cc',\n-\n-                #leveldb\n-                'leveldb/db/builder.cc',\n-                'leveldb/db/c.cc',\n-                'leveldb/db/db_impl.cc',\n-                'leveldb/db/db_iter.cc',\n-                'leveldb/db/dbformat.cc',\n-                'leveldb/db/filename.cc',\n-                'leveldb/db/log_reader.cc',\n-                'leveldb/db/log_writer.cc',\n-                'leveldb/db/memtable.cc',\n-                'leveldb/db/repair.cc',\n-                'leveldb/db/table_cache.cc',\n-                'leveldb/db/version_edit.cc',\n-                'leveldb/db/version_set.cc',\n-                'leveldb/db/write_batch.cc',\n-                'leveldb/table/block.cc',\n-                'leveldb/table/block_builder.cc',\n-                'leveldb/table/filter_block.cc',\n-                'leveldb/table/format.cc',\n-                'leveldb/table/iterator.cc',\n-                'leveldb/table/merger.cc',\n-                'leveldb/table/table.cc',\n-                'leveldb/table/table_builder.cc',\n-                'leveldb/table/two_level_iterator.cc',\n-                'leveldb/util/arena.cc',\n-                'leveldb/util/bloom.cc',\n-                'leveldb/util/cache.cc',\n-                'leveldb/util/coding.cc',\n-                'leveldb/util/comparator.cc',\n-                'leveldb/util/crc32c.cc',\n-                'leveldb/util/env.cc',\n-                'leveldb/util/env_posix.cc',\n-                'leveldb/util/filter_policy.cc',\n-                'leveldb/util/hash.cc',\n-                'leveldb/util/histogram.cc',\n-                'leveldb/util/logging.cc',\n-                'leveldb/util/options.cc',\n-                'leveldb/util/status.cc',\n-                'leveldb/port/port_posix.cc',\n-\n                 # python stuff\n                 'leveldb_ext.cc',\n                 'leveldb_object.cc',\n             ],\n-            libraries = ['stdc++'],\n+            libraries = ['snappy', 'leveldb', 'stdc++', 'c++_shared'],\n             extra_compile_args = extra_compile_args,\n         )\n     ]\n"
  },
  {
    "path": "pythonforandroid/recipes/pymunk/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\n\n\nclass PymunkRecipe(CompiledComponentsPythonRecipe):\n    name = \"pymunk\"\n    version = \"6.0.0\"\n    url = \"https://pypi.python.org/packages/source/p/pymunk/pymunk-{version}.zip\"\n    depends = [\"cffi\", \"setuptools\"]\n    call_hostpython_via_targetpython = False\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        env[\"LDFLAGS\"] += \" -llog\"  # Used by Chipmunk cpMessage\n        env[\"LDFLAGS\"] += \" -lm\"  # For older versions of Android\n        return env\n\n\nrecipe = PymunkRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pynacl/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\nimport os\n\n\nclass PyNaCLRecipe(PyProjectRecipe):\n    name = 'pynacl'\n    version = '1.3.0'\n    url = 'https://github.com/pyca/pynacl/archive/refs/tags/{version}.tar.gz'\n\n    depends = ['hostpython3', 'six', 'setuptools', 'cffi', 'libsodium']\n    call_hostpython_via_targetpython = False\n    hostpython_prerequisites = [\"cffi>=2.0.0\"]\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env['SODIUM_INSTALL'] = 'system'\n\n        libsodium_build_dir = self.get_recipe(\n            'libsodium', self.ctx\n        ).get_build_dir(arch.arch)\n\n        env['CFLAGS'] += ' -I{}'.format(\n            os.path.join(libsodium_build_dir, 'src/libsodium/include')\n        )\n\n        for ldflag in [\n            self.ctx.get_libs_dir(arch.arch),\n            self.ctx.libs_dir,\n            libsodium_build_dir\n        ]:\n            env['LDFLAGS'] += ' -L{}'.format(ldflag)\n\n        return env\n\n\nrecipe = PyNaCLRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyogg/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\nfrom os.path import join\n\n\nclass PyOggRecipe(PythonRecipe):\n    version = '0.6.4a1'\n    url = 'https://files.pythonhosted.org/packages/source/p/pyogg/PyOgg-{version}.tar.gz'\n    depends = ['libogg', 'libvorbis', 'setuptools']\n    patches = [join('patches', 'fix-find-lib.patch')]\n\n    call_hostpython_via_targetpython = False\n\n\nrecipe = PyOggRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyogg/patches/fix-find-lib.patch",
    "content": "diff --git a/pyogg/library_loader.py b/pyogg/library_loader.py\nindex c2ba36c..383331a 100644\n--- a/pyogg/library_loader.py\n+++ b/pyogg/library_loader.py\n@@ -54,7 +54,7 @@ def load_other(name, paths = None):\n                     except:\r\n                         pass\r\n     else:\r\n-        for path in [os.getcwd(), _here]:\r\n+        for path in [os.path.join(os.environ['ANDROID_PRIVATE'], '..', 'lib')]:\r\n             for style in other_styles:\r\n                 candidate = os.path.join(path, style.format(name))\r\n                 if os.path.exists(candidate):\r\n"
  },
  {
    "path": "pythonforandroid/recipes/pyopenal/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\nfrom os.path import join\n\n\nclass PyOpenALRecipe(PythonRecipe):\n    version = '0.7.3a1'\n    url = 'https://files.pythonhosted.org/packages/source/p/pyopenal/PyOpenAL-{version}.tar.gz'\n    depends = ['openal', 'numpy', 'setuptools']\n    patches = [join('patches', 'fix-find-lib.patch')]\n\n    call_hostpython_via_targetpython = False\n\n\nrecipe = PyOpenALRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyopenal/patches/fix-find-lib.patch",
    "content": "diff --git a/openal/library_loader.py b/openal/library_loader.py\nindex be2485c..e8c6cd2 100644\n--- a/openal/library_loader.py\n+++ b/openal/library_loader.py\n@@ -56,7 +56,7 @@ class ExternalLibrary:\n                         except:\r\n                             pass\r\n         else:\r\n-            for path in [os.getcwd(), _here]:\r\n+            for path in [os.path.join(os.environ['ANDROID_PRIVATE'], '..', 'lib')]:\r\n                 for style in ExternalLibrary.other_styles:\r\n                     candidate = os.path.join(path, style.format(name))\r\n                     if os.path.exists(candidate) and os.path.isfile(candidate):\r\n"
  },
  {
    "path": "pythonforandroid/recipes/pyopenssl/__init__.py",
    "content": "\nfrom pythonforandroid.recipe import PythonRecipe\n\n\nclass PyOpenSSLRecipe(PythonRecipe):\n    version = '24.1.0'\n    url = 'https://pypi.python.org/packages/source/p/pyOpenSSL/pyOpenSSL-{version}.tar.gz'\n    depends = ['cffi', 'openssl', 'setuptools']\n    site_packages_name = 'OpenSSL'\n\n    call_hostpython_via_targetpython = False\n\n\nrecipe = PyOpenSSLRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyopenssl/fix-dlfcn.patch",
    "content": "--- pyOpenSSL-0.13.orig/OpenSSL/__init__.py\t2011-09-02 17:46:13.000000000 +0200\n+++ pyOpenSSL-0.13/OpenSSL/__init__.py\t2013-07-29 17:20:15.750079894 +0200\n@@ -12,6 +12,11 @@\n except AttributeError:\n     from OpenSSL import crypto\n else:\n+    # XXX android fix\n+    # linux: RTLD_NOW (0x2) | RTLD_GLOBAL (0x100 / 256)\n+    # android: RTLD_NOW (0x0) | RTLD_GLOBAL (0x2)\n+    flags = 0x2\n+    '''\n     try:\n         import DLFCN\n     except ImportError:\n@@ -31,6 +36,7 @@\n     else:\n         flags = DLFCN.RTLD_NOW | DLFCN.RTLD_GLOBAL\n         del DLFCN\n+    '''\n \n     sys.setdlopenflags(flags)\n     from OpenSSL import crypto\n"
  },
  {
    "path": "pythonforandroid/recipes/pyproj/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\n\n\nclass PyProjRecipe(CythonRecipe):\n    version = '1.9.6'\n    url = 'https://github.com/pyproj4/pyproj/archive/v{version}rel.zip'\n    depends = ['setuptools']\n    call_hostpython_via_targetpython = False\n\n\nrecipe = PyProjRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyrxp/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\n\n\nclass PyRXPURecipe(CompiledComponentsPythonRecipe):\n    version = '2a02cecc87b9'\n    url = 'https://bitbucket.org/rptlab/pyrxp/get/{version}.tar.gz'\n    depends = []\n    patches = []\n\n\nrecipe = PyRXPURecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pysdl2/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass PySDL2Recipe(PythonRecipe):\n    version = '0.9.6'\n    url = 'https://files.pythonhosted.org/packages/source/P/PySDL2/PySDL2-{version}.tar.gz'\n\n    depends = ['sdl2']\n\n\nrecipe = PySDL2Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pysha3/__init__.py",
    "content": "import os\nfrom pythonforandroid.recipe import PythonRecipe\n\n\n# TODO: CompiledComponentsPythonRecipe\nclass Pysha3Recipe(PythonRecipe):\n    version = '1.0.2'\n    url = 'https://github.com/tiran/pysha3/archive/{version}.tar.gz'\n    depends = ['setuptools']\n    call_hostpython_via_targetpython = False\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS\n        env['CPPFLAGS'] = env['CFLAGS']\n        env['CFLAGS'] = ''\n        # LDFLAGS may only be used to specify linker flags, for libraries use LIBS\n        env['LDFLAGS'] = env['LDFLAGS'].replace('-lm', '')\n        env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))\n        env['LIBS'] = ' -lm'\n        env['LDSHARED'] += env['LIBS']\n        return env\n\n\nrecipe = Pysha3Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/__init__.py",
    "content": "import glob\nimport sh\nimport subprocess\n\nfrom os import environ, utime\nfrom os.path import dirname, exists, join, isfile\nimport shutil\n\nfrom packaging.version import Version\nfrom pythonforandroid.logger import info, shprint, warning\nfrom pythonforandroid.recipe import Recipe, TargetPythonRecipe\nfrom pythonforandroid.util import (\n    current_directory,\n    ensure_dir,\n    walk_valid_filens,\n    BuildInterruptingException,\n)\n\nNDK_API_LOWER_THAN_SUPPORTED_MESSAGE = (\n    'Target ndk-api is {ndk_api}, '\n    'but the python3 recipe supports only {min_ndk_api}+'\n)\n\n\nclass Python3Recipe(TargetPythonRecipe):\n    '''\n    The python3's recipe\n    ^^^^^^^^^^^^^^^^^^^^\n\n    The python 3 recipe can be built with some extra python modules, but to do\n    so, we need some libraries. By default, we ship the python3 recipe with\n    some common libraries, defined in ``depends``. We also support some optional\n    libraries, which are less common that the ones defined in ``depends``, so\n    we added them as optional dependencies (``opt_depends``).\n\n    Below you have a relationship between the python modules and the recipe\n    libraries::\n\n        - _ctypes: you must add the recipe for ``libffi``.\n        - _sqlite3: you must add the recipe for ``sqlite3``.\n        - _ssl: you must add the recipe for ``openssl``.\n        - _bz2: you must add the recipe for ``libbz2`` (optional).\n        - _lzma: you must add the recipe for ``liblzma`` (optional).\n\n    .. note:: This recipe can be built only against API 21+.\n\n    .. versionchanged:: 2019.10.06.post0\n        - Refactored from deleted class ``python.GuestPythonRecipe`` into here\n        - Added optional dependencies: :mod:`~pythonforandroid.recipes.libbz2`\n          and :mod:`~pythonforandroid.recipes.liblzma`\n\n    .. versionchanged:: 0.6.0\n        Refactored into class\n        :class:`~pythonforandroid.python.GuestPythonRecipe`\n    '''\n\n    version = '3.14.2'\n    url = 'https://github.com/python/cpython/archive/refs/tags/v{version}.tar.gz'\n    name = 'python3'\n\n    patches = [\n        'patches/pyconfig_detection.patch',\n        'patches/reproducible-buildinfo.diff',\n    ]\n\n    depends = ['hostpython3', 'sqlite3', 'openssl', 'libffi']\n    # those optional depends allow us to build python compression modules:\n    #   - _bz2.so\n    #   - _lzma.so\n    opt_depends = ['libbz2', 'liblzma']\n    '''The optional libraries which we would like to get our python linked'''\n\n    configure_args = [\n        '--host={android_host}',\n        '--build={android_build}',\n        '--enable-shared',\n        '--enable-ipv6',\n        '--enable-loadable-sqlite-extensions',\n        '--without-static-libpython',\n        '--without-readline',\n        '--without-ensurepip',\n\n        # Android prefix\n        '--prefix={prefix}',\n        '--exec-prefix={exec_prefix}',\n        '--enable-loadable-sqlite-extensions',\n\n        # Special cross compile args\n        'ac_cv_file__dev_ptmx=yes',\n        'ac_cv_file__dev_ptc=no',\n        'ac_cv_header_sys_eventfd_h=no',\n        'ac_cv_little_endian_double=yes',\n        'ac_cv_header_bzlib_h=no',\n    ]\n\n    '''The configure arguments needed to build the python recipe. Those are\n    used in method :meth:`build_arch` (if not overwritten like python3's\n    recipe does).\n    '''\n\n    MIN_NDK_API = 21\n    '''Sets the minimal ndk api number needed to use the recipe.\n\n    .. warning:: This recipe can be built only against API 21+, so it means\n        that any class which inherits from class:`GuestPythonRecipe` will have\n        this limitation.\n    '''\n\n    stdlib_dir_blacklist = {\n        '__pycache__',\n        'test',\n        'tests',\n        'lib2to3',\n        'ensurepip',\n        'idlelib',\n        'tkinter',\n    }\n    '''The directories that we want to omit for our python bundle'''\n\n    stdlib_filen_blacklist = [\n        '*.py',\n        '*.exe',\n        '*.whl',\n    ]\n    '''The file extensions that we want to blacklist for our python bundle'''\n\n    site_packages_dir_blacklist = {\n        '__pycache__',\n        'tests'\n    }\n    '''The directories from site packages dir that we don't want to be included\n    in our python bundle.'''\n\n    site_packages_excluded_dir_exceptions = [\n        # 'numpy' is excluded here because importing with `import numpy as np`\n        # can fail if the `tests` directory inside the numpy package is excluded.\n        'numpy',\n    ]\n    '''Directories from `site_packages_dir_blacklist` will not be excluded\n    if the full path contains any of these exceptions.'''\n\n    site_packages_filen_blacklist = [\n        '*.py'\n    ]\n    '''The file extensions from site packages dir that we don't want to be\n    included in our python bundle.'''\n\n    compiled_extension = '.pyc'\n    '''the default extension for compiled python files.\n\n    .. note:: the default extension for compiled python files has been .pyo for\n        python 2.x-3.4 but as of Python 3.5, the .pyo filename extension is no\n        longer used and has been removed in favour of extension .pyc\n    '''\n\n    disable_gil = False\n    '''python3.13 experimental free-threading build'''\n\n    built_libraries = {\"libpythonbin.so\": \"./android-build/\"}\n\n    def __init__(self, *args, **kwargs):\n        self._ctx = None\n        super().__init__(*args, **kwargs)\n\n    @property\n    def _libpython(self):\n        '''return the python's library name (with extension)'''\n        return 'libpython{link_version}.so'.format(\n            link_version=self.link_version\n        )\n\n    @property\n    def link_version(self):\n        '''return the python's library link version e.g. 3.7m, 3.8'''\n        major, minor = self.major_minor_version_string.split('.')\n        flags = ''\n        if major == '3' and int(minor) < 8:\n            flags += 'm'\n        return '{major}.{minor}{flags}'.format(\n            major=major,\n            minor=minor,\n            flags=flags\n        )\n\n    def apply_patches(self, arch, build_dir=None):\n\n        _p_version = Version(self.version)\n        if _p_version.major == 3 and _p_version.minor == 7:\n            self.patches += [\n                'patches/py3.7.1_fix-ctypes-util-find-library.patch',\n                'patches/py3.7.1_fix-zlib-version.patch',\n            ]\n\n        if 8 <= _p_version.minor <= 10:\n            self.patches.append('patches/py3.8.1.patch')\n\n        if _p_version.minor >= 11:\n            self.patches.append('patches/cpython-311-ctypes-find-library.patch')\n\n        if _p_version.minor >= 14:\n            self.patches.append('patches/3.14_armv7l_fix.patch')\n            self.patches.append('patches/3.14_fix_remote_debug.patch')\n\n        if shutil.which('lld') is not None:\n            if _p_version.minor == 7:\n                self.patches.append(\"patches/py3.7.1_fix_cortex_a8.patch\")\n            elif _p_version.minor >= 8:\n                self.patches.append(\"patches/py3.8.1_fix_cortex_a8.patch\")\n\n        self.patches = list(set(self.patches))\n        super().apply_patches(arch, build_dir)\n\n    def include_root(self, arch_name):\n        return join(self.get_build_dir(arch_name), 'Include')\n\n    def link_root(self, arch_name):\n        return join(self.get_build_dir(arch_name), 'android-build')\n\n    def should_build(self, arch):\n        return not isfile(join(self.link_root(arch.arch), self._libpython))\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        self.ctx.python_recipe = self\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch)\n        env['HOSTARCH'] = arch.command_prefix\n\n        env['CC'] = arch.get_clang_exe(with_target=True)\n\n        env['PATH'] = (\n            '{hostpython_dir}:{old_path}').format(\n                hostpython_dir=self.get_recipe(\n                    'host' + self.name, self.ctx).get_path_to_python(),\n                old_path=env['PATH'])\n\n        env['CFLAGS'] = ' '.join(\n            [\n                '-fPIC',\n                '-DANDROID'\n            ]\n        )\n\n        env['LDFLAGS'] = env.get('LDFLAGS', '')\n        if shutil.which('lld') is not None:\n            # Note: The -L. is to fix a bug in python 3.7.\n            # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234409\n            env['LDFLAGS'] += ' -L. -fuse-ld=lld'\n        else:\n            warning('lld not found, linking without it. '\n                    'Consider installing lld if linker errors occur.')\n\n        return env\n\n    def set_libs_flags(self, env, arch):\n        '''Takes care to properly link libraries with python depending on our\n        requirements and the attribute :attr:`opt_depends`.\n        '''\n        def add_flags(include_flags, link_dirs, link_libs):\n            env['CPPFLAGS'] = env.get('CPPFLAGS', '') + include_flags\n            env['LDFLAGS'] = env.get('LDFLAGS', '') + link_dirs\n            env['LIBS'] = env.get('LIBS', '') + link_libs\n\n        info('Activating flags for sqlite3')\n        recipe = Recipe.get_recipe('sqlite3', self.ctx)\n        add_flags(' -I' + recipe.get_build_dir(arch.arch),\n                  ' -L' + recipe.get_build_dir(arch.arch), ' -lsqlite3')\n\n        info('Activating flags for libffi')\n        recipe = Recipe.get_recipe('libffi', self.ctx)\n        # In order to force the correct linkage for our libffi library, we\n        # set the following variable to point where is our libffi.pc file,\n        # because the python build system uses pkg-config to configure it.\n        env['PKG_CONFIG_LIBDIR'] = recipe.get_build_dir(arch.arch)\n        add_flags(' -I' + ' -I'.join(recipe.get_include_dirs(arch)),\n                  ' -L' + join(recipe.get_build_dir(arch.arch), '.libs'),\n                  ' -lffi')\n\n        info('Activating flags for openssl')\n        recipe = Recipe.get_recipe('openssl', self.ctx)\n        self.configure_args.append('--with-openssl=' + recipe.get_build_dir(arch.arch))\n        add_flags(recipe.include_flags(arch),\n                  recipe.link_dirs_flags(arch), recipe.link_libs_flags())\n\n        for library_name in {'libbz2', 'liblzma'}:\n            if library_name in self.ctx.recipe_build_order:\n                info(f'Activating flags for {library_name}')\n                recipe = Recipe.get_recipe(library_name, self.ctx)\n                add_flags(recipe.get_library_includes(arch),\n                          recipe.get_library_ldflags(arch),\n                          recipe.get_library_libs_flag())\n\n        # python build system contains hardcoded zlib version which prevents\n        # the build of zlib module, here we search for android's zlib version\n        # and sets the right flags, so python can be build with android's zlib\n        info(\"Activating flags for android's zlib\")\n        zlib_lib_path = arch.ndk_lib_dir_versioned\n        zlib_includes = self.ctx.ndk.sysroot_include_dir\n        zlib_h = join(zlib_includes, 'zlib.h')\n        try:\n            with open(zlib_h) as fileh:\n                zlib_data = fileh.read()\n        except IOError:\n            raise BuildInterruptingException(\n                \"Could not determine android's zlib version, no zlib.h ({}) in\"\n                \" the NDK dir includes\".format(zlib_h)\n            )\n        for line in zlib_data.split('\\n'):\n            if line.startswith('#define ZLIB_VERSION '):\n                break\n        else:\n            raise BuildInterruptingException(\n                'Could not parse zlib.h...so we cannot find zlib version,'\n                'required by python build,'\n            )\n        env['ZLIB_VERSION'] = line.replace('#define ZLIB_VERSION ', '')\n        add_flags(' -I' + zlib_includes, ' -L' + zlib_lib_path, ' -lz')\n\n        _p_version = Version(self.version)\n        if _p_version.minor >= 11:\n            self.configure_args.append('--with-build-python={python_host_bin}')\n\n        if _p_version.minor >= 13 and self.disable_gil:\n            self.configure_args.append(\"--disable-gil\")\n\n        self.configure_args = list(set(self.configure_args))\n\n        return env\n\n    def build_arch(self, arch):\n        if self.ctx.ndk_api < self.MIN_NDK_API:\n            raise BuildInterruptingException(\n                NDK_API_LOWER_THAN_SUPPORTED_MESSAGE.format(\n                    ndk_api=self.ctx.ndk_api, min_ndk_api=self.MIN_NDK_API\n                ),\n            )\n\n        recipe_build_dir = self.get_build_dir(arch.arch)\n\n        # Create a subdirectory to actually perform the build\n        build_dir = join(recipe_build_dir, 'android-build')\n        ensure_dir(build_dir)\n\n        # TODO: Get these dynamically, like bpo-30386 does\n        sys_prefix = '/usr/local'\n        sys_exec_prefix = '/usr/local'\n\n        env = self.get_recipe_env(arch)\n        env = self.set_libs_flags(env, arch)\n\n        android_build = sh.Command(\n            join(recipe_build_dir,\n                 'config.guess'))().strip()\n\n        with current_directory(build_dir):\n            if not exists('config.status'):\n                shprint(\n                    sh.Command(join(recipe_build_dir, 'configure')),\n                    *(' '.join(self.configure_args).format(\n                                    android_host=env['HOSTARCH'],\n                                    android_build=android_build,\n                                    python_host_bin=join(self.get_recipe(\n                                        'host' + self.name, self.ctx\n                                    ).get_path_to_python(), \"python3\"),\n                                    prefix=sys_prefix,\n                                    exec_prefix=sys_exec_prefix)).split(' '),\n                    _env=env)\n\n            shprint(\n                sh.make,\n                'all',\n                'INSTSONAME={lib_name}'.format(lib_name=self._libpython),\n                _env=env\n            )\n            # rename executable\n            if isfile(\"python\"):\n                sh.cp('python', 'libpythonbin.so')\n            elif isfile(\"python.exe\"):  # for macos\n                sh.cp('python.exe', 'libpythonbin.so')\n\n            # TODO: Look into passing the path to pyconfig.h in a\n            # better way, although this is probably acceptable\n            sh.cp('pyconfig.h', join(recipe_build_dir, 'Include'))\n\n    def compile_python_files(self, dir):\n        '''\n        Compile the python files (recursively) for the python files inside\n        a given folder.\n\n        .. note:: python2 compiles the files into extension .pyo, but in\n            python3, and as of Python 3.5, the .pyo filename extension is no\n            longer used...uses .pyc (https://www.python.org/dev/peps/pep-0488)\n        '''\n        args = [self.ctx.hostpython]\n        args += ['-OO', '-m', 'compileall', '-b', '-f', dir]\n        subprocess.call(args)\n\n    def create_python_bundle(self, dirn, arch):\n        \"\"\"\n        Create a packaged python bundle in the target directory, by\n        copying all the modules and standard library to the right\n        place.\n        \"\"\"\n        modules_build_dir = glob.glob(join(\n            self.get_build_dir(arch.arch),\n            'android-build',\n            'build',\n            'lib.*'\n        ))[0]\n        # Compile to *.pyc the python modules\n        self.compile_python_files(modules_build_dir)\n        # Compile to *.pyc the standard python library\n        self.compile_python_files(join(self.get_build_dir(arch.arch), 'Lib'))\n        # Compile to *.pyc the other python packages (site-packages)\n        self.compile_python_files(self.ctx.get_python_install_dir(arch.arch))\n\n        # Bundle compiled python modules to a folder\n        modules_dir = join(dirn, 'modules')\n        c_ext = self.compiled_extension\n        ensure_dir(modules_dir)\n        module_filens = (glob.glob(join(modules_build_dir, '*.so')) +\n                         glob.glob(join(modules_build_dir, '*' + c_ext)))\n        info(\"Copy {} files into the bundle\".format(len(module_filens)))\n        for filen in module_filens:\n            info(\" - copy {}\".format(filen))\n            shutil.copy2(filen, modules_dir)\n\n        # zip up the standard library\n        stdlib_zip = join(dirn, 'stdlib.zip')\n        with current_directory(join(self.get_build_dir(arch.arch), 'Lib')):\n            stdlib_filens = list(walk_valid_filens(\n                '.', self.stdlib_dir_blacklist, self.stdlib_filen_blacklist))\n            if 'SOURCE_DATE_EPOCH' in environ:\n                # for reproducible builds\n                stdlib_filens.sort()\n                timestamp = int(environ['SOURCE_DATE_EPOCH'])\n                for filen in stdlib_filens:\n                    utime(filen, (timestamp, timestamp))\n            info(\"Zip {} files into the bundle\".format(len(stdlib_filens)))\n            shprint(sh.zip, '-X', stdlib_zip, *stdlib_filens)\n\n        # copy the site-packages into place\n        ensure_dir(join(dirn, 'site-packages'))\n        ensure_dir(self.ctx.get_python_install_dir(arch.arch))\n        # TODO: Improve the API around walking and copying the files\n        with current_directory(self.ctx.get_python_install_dir(arch.arch)):\n            filens = list(walk_valid_filens(\n                '.', self.site_packages_dir_blacklist,\n                self.site_packages_filen_blacklist,\n                excluded_dir_exceptions=self.site_packages_excluded_dir_exceptions))\n            info(\"Copy {} files into the site-packages\".format(len(filens)))\n            for filen in filens:\n                info(\" - copy {}\".format(filen))\n                ensure_dir(join(dirn, 'site-packages', dirname(filen)))\n                shutil.copy2(filen, join(dirn, 'site-packages', filen))\n\n        # copy the python .so files into place\n        python_build_dir = join(self.get_build_dir(arch.arch),\n                                'android-build')\n        python_lib_name = 'libpython' + self.link_version\n        shprint(\n            sh.cp,\n            join(python_build_dir, python_lib_name + '.so'),\n            join(self.ctx.bootstrap.dist_dir, 'libs', arch.arch)\n        )\n\n        info('Renaming .so files to reflect cross-compile')\n        self.reduce_object_file_names(join(dirn, 'site-packages'))\n\n        return join(dirn, 'site-packages')\n\n\nrecipe = Python3Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/3.14_armv7l_fix.patch",
    "content": "diff '--color=auto' -uNr cpython-3.14.0/Lib/sysconfig/__init__.py cpython-3.14.0.mod/Lib/sysconfig/__init__.py\n--- cpython-3.14.0/Lib/sysconfig/__init__.py\t2025-10-07 21:45:41.236149298 +0530\n+++ cpython-3.14.0.mod/Lib/sysconfig/__init__.py\t2025-10-07 21:45:54.650245131 +0530\n@@ -702,7 +702,7 @@\n             \"x86_64\": \"x86_64\",\n             \"i686\": \"x86\",\n             \"aarch64\": \"arm64_v8a\",\n-            \"armv7l\": \"armeabi_v7a\",\n+            \"arm\": \"armeabi_v7a\",\n         }[machine]\n     elif osname == \"linux\":\n         # At least on Linux/Intel, 'machine' is the processor --\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/3.14_fix_remote_debug.patch",
    "content": "diff '--color=auto' -uNr cpython-3.14.2/Modules/_remote_debugging_module.c cpython-3.14.2.mod/Modules/_remote_debugging_module.c\n--- cpython-3.14.2/Modules/_remote_debugging_module.c\t2025-12-05 22:19:16.000000000 +0530\n+++ cpython-3.14.2.mod/Modules/_remote_debugging_module.c\t2025-12-13 20:22:44.011497868 +0530\n@@ -812,7 +812,9 @@\n         PyErr_SetString(PyExc_RuntimeError, \"Failed to find the AsyncioDebug section in the process.\");\n         _PyErr_ChainExceptions1(exc);\n     }\n-#elif defined(__linux__)\n+\n+// https://github.com/python/cpython/commit/1963e701001839389cfb1b11d803b0743f4705d7\n+#elif defined(__linux__) && HAVE_PROCESS_VM_READV\n     // On Linux, search for asyncio debug in executable or DLL\n     address = search_linux_map_for_section(handle, \"AsyncioDebug\", \"_asyncio.cpython\");\n     if (address == 0) {\ndiff '--color=auto' -uNr cpython-3.14.2/Python/remote_debug.h cpython-3.14.2.mod/Python/remote_debug.h\n--- cpython-3.14.2/Python/remote_debug.h\t2025-12-05 22:19:16.000000000 +0530\n+++ cpython-3.14.2.mod/Python/remote_debug.h\t2025-12-13 20:23:27.917518543 +0530\n@@ -881,7 +881,9 @@\n             handle->pid);\n         _PyErr_ChainExceptions1(exc);\n     }\n-#elif defined(__linux__)\n+\n+// https://github.com/python/cpython/commit/1963e701001839389cfb1b11d803b0743f4705d7\n+#elif defined(__linux__) && HAVE_PROCESS_VM_READV\n     // On Linux, search for 'python' in executable or DLL\n     address = search_linux_map_for_section(handle, \"PyRuntime\", \"python\");\n     if (address == 0) {\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/cpython-311-ctypes-find-library.patch",
    "content": "--- Python-3.11.5/Lib/ctypes/util.py\t2023-08-24 17:39:18.000000000 +0530\n+++ Python-3.11.5.mod/Lib/ctypes/util.py\t2023-11-18 22:12:17.356160615 +0530\n@@ -4,7 +4,15 @@\n import sys\n \n # find_library(name) returns the pathname of a library, or None.\n-if os.name == \"nt\":\n+\n+# This patch overrides the find_library to look in the right places on\n+# Android\n+if True:\n+    from android._ctypes_library_finder import find_library as _find_lib\n+    def find_library(name):\n+        return _find_lib(name)\n+\n+elif os.name == \"nt\":\n \n     def _get_build_version():\n         \"\"\"Return the version of MSVC that was used to build Python.\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/py3.7.1_fix-ctypes-util-find-library.patch",
    "content": "diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py\n--- a/Lib/ctypes/util.py\n+++ b/Lib/ctypes/util.py\n@@ -67,4 +67,11 @@\n                 return fname\n         return None\n \n+# This patch overrides the find_library to look in the right places on\n+# Android\n+if True:\n+    from android._ctypes_library_finder import find_library as _find_lib\n+    def find_library(name):\n+        return _find_lib(name)\n+\n elif os.name == \"posix\" and sys.platform == \"darwin\":\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/py3.7.1_fix-zlib-version.patch",
    "content": "--- Python-3.7.1/setup.py.orig\t2018-10-20 08:04:19.000000000 +0200\n+++ Python-3.7.1/setup.py\t2019-02-17 00:24:30.715904412 +0100\n@@ -1410,7 +1410,8 @@ class PyBuildExt(build_ext):\n         if zlib_inc is not None:\n             zlib_h = zlib_inc[0] + '/zlib.h'\n             version = '\"0.0.0\"'\n-            version_req = '\"1.1.3\"'\n+            version_req = '\"{}\"'.format(\n+                os.environ.get('ZLIB_VERSION', '1.1.3'))\n             if host_platform == 'darwin' and is_macosx_sdk_path(zlib_h):\n                 zlib_h = os.path.join(macosx_sdk_root(), zlib_h[1:])\n             with open(zlib_h) as fp:\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/py3.7.1_fix_cortex_a8.patch",
    "content": "This patch removes --fix-cortex-a8 from the linker flags in order to support linking\nwith lld, as lld does not support this flag (https://github.com/android-ndk/ndk/issues/766).\ndiff --git a/configure b/configure\n--- a/configure\n+++ b/configure\n@@ -5671,7 +5671,7 @@ $as_echo_n \"checking for the Android arm ABI... \" >&6; }\n $as_echo \"$_arm_arch\" >&6; }\n   if test \"$_arm_arch\" = 7; then\n     BASECFLAGS=\"${BASECFLAGS} -mfloat-abi=softfp -mfpu=vfpv3-d16\"\n-    LDFLAGS=\"${LDFLAGS} -march=armv7-a -Wl,--fix-cortex-a8\"\n+    LDFLAGS=\"${LDFLAGS} -march=armv7-a\"\n   fi\n else\n   { $as_echo \"$as_me:${as_lineno-$LINENO}: result: not Android\" >&5"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/py3.8.1.patch",
    "content": "diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py\nindex 97973bc..053c231 100644\n--- a/Lib/ctypes/util.py\n+++ b/Lib/ctypes/util.py\n@@ -67,6 +67,13 @@ if os.name == \"nt\":\n                 return fname\n         return None\n \n+# This patch overrides the find_library to look in the right places on\n+# Android\n+if True:\n+    from android._ctypes_library_finder import find_library as _find_lib\n+    def find_library(name):\n+        return _find_lib(name)\n+\n elif os.name == \"posix\" and sys.platform == \"darwin\":\n     from ctypes.macholib.dyld import dyld_find as _dyld_find\n     def find_library(name):\ndiff --git a/configure b/configure\nindex 0914e24..dd00812 100755\n--- a/configure\n+++ b/configure\n@@ -18673,4 +18673,3 @@ if test \"$Py_OPT\" = 'false' -a \"$Py_DEBUG\" != 'true'; then\n     echo \"\" >&6\n     echo \"\" >&6\n fi\n-\ndiff --git a/setup.py b/setup.py\nindex 20d7f35..af15cc2 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -1501,7 +1501,9 @@ class PyBuildExt(build_ext):\n         if zlib_inc is not None:\n             zlib_h = zlib_inc[0] + '/zlib.h'\n             version = '\"0.0.0\"'\n-            version_req = '\"1.1.3\"'\n+            # version_req = '\"1.1.3\"'\n+            version_req = '\"{}\"'.format(\n+                os.environ.get('ZLIB_VERSION', '1.1.3'))\n             if MACOS and is_macosx_sdk_path(zlib_h):\n                 zlib_h = os.path.join(macosx_sdk_root(), zlib_h[1:])\n             with open(zlib_h) as fp:\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/py3.8.1_fix_cortex_a8.patch",
    "content": "This patch removes --fix-cortex-a8 from the linker flags in order to support linking\nwith lld, as lld does not support this flag (https://github.com/android-ndk/ndk/issues/766).\ndiff --git a/configure b/configure\nindex 0914e24..7517168 100755\n--- a/configure\n+++ b/configure\n@@ -5642,7 +5642,7 @@ $as_echo_n \"checking for the Android arm ABI... \" >&6; }\n $as_echo \"$_arm_arch\" >&6; }\n   if test \"$_arm_arch\" = 7; then\n     BASECFLAGS=\"${BASECFLAGS} -mfloat-abi=softfp -mfpu=vfpv3-d16\"\n-    LDFLAGS=\"${LDFLAGS} -march=armv7-a -Wl,--fix-cortex-a8\"\n+    LDFLAGS=\"${LDFLAGS} -march=armv7-a\"\n   fi\n else\n   { $as_echo \"$as_me:${as_lineno-$LINENO}: result: not Android\" >&5\n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/pyconfig_detection.patch",
    "content": "diff -Nru Python-3.8.2/Lib/site.py Python-3.8.2-new/Lib/site.py\n--- Python-3.8.2/Lib/site.py    2020-04-28 12:48:38.000000000 -0700\n+++ Python-3.8.2-new/Lib/site.py        2020-04-28 12:52:46.000000000 -0700\n@@ -487,7 +487,8 @@\n                     if key == 'include-system-site-packages':\n                         system_site = value.lower()\n                     elif key == 'home':\n-                        sys._home = value\n+                        # this is breaking pyconfig.h path detection with venv\n+                        print('Ignoring \"sys._home = value\" override')\n \n         sys.prefix = sys.exec_prefix = site_prefix\n \n"
  },
  {
    "path": "pythonforandroid/recipes/python3/patches/reproducible-buildinfo.diff",
    "content": "# DP: Build getbuildinfo.o with DATE/TIME values when defined\n\n--- a/Makefile.pre.in\n+++ b/Makefile.pre.in\n@@ -785,6 +785,8 @@ Modules/getbuildinfo.o: $(PARSER_OBJS) \\\n \t      -DGITVERSION=\"\\\"`LC_ALL=C $(GITVERSION)`\\\"\" \\\n \t      -DGITTAG=\"\\\"`LC_ALL=C $(GITTAG)`\\\"\" \\\n \t      -DGITBRANCH=\"\\\"`LC_ALL=C $(GITBRANCH)`\\\"\" \\\n+\t      $(if $(BUILD_DATE),-DDATE='\"$(BUILD_DATE)\"') \\\n+\t      $(if $(BUILD_TIME),-DTIME='\"$(BUILD_TIME)\"') \\\n \t      -o $@ $(srcdir)/Modules/getbuildinfo.c\n \n Modules/getpath.o: $(srcdir)/Modules/getpath.c Makefile\n"
  },
  {
    "path": "pythonforandroid/recipes/pyusb/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass PyusbRecipe(PythonRecipe):\n    name = 'pyusb'\n    version = '1.0.0b1'\n    url = 'https://pypi.python.org/packages/source/p/pyusb/pyusb-{version}.tar.gz'\n    depends = []\n    site_packages_name = 'usb'\n\n    patches = ['fix-android.patch']\n\n\nrecipe = PyusbRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyusb/fix-android.patch",
    "content": "--- pyusb-1.0.0b1.orig/usb/backend/libusb1.py\t2013-10-21 12:56:10.000000000 -0500\n+++ pyusb-1.0.0b1/usb/backend/libusb1.py\t2014-12-08 16:49:07.141514148 -0600\n@@ -265,13 +265,7 @@\n \n def _load_library():\n     if sys.platform != 'cygwin':\n-        candidates = ('usb-1.0', 'libusb-1.0', 'usb')\n-        for candidate in candidates:\n-            if sys.platform == 'win32':\n-                candidate = candidate + '.dll'\n-\n-            libname = ctypes.util.find_library(candidate)\n-            if libname is not None: break\n+        libname = '/system/lib/libusb1.0.so'\n     else:\n         # corner cases\n         # cygwin predefines library names with 'cyg' instead of 'lib'\n@@ -672,16 +666,21 @@\n \n # implementation of libusb 1.0 backend\n class _LibUSB(usb.backend.IBackend):\n+    \n+    ran_init = False\n+    \n     @methodtrace(_logger)\n     def __init__(self, lib):\n         usb.backend.IBackend.__init__(self)\n         self.lib = lib\n         self.ctx = c_void_p()\n         _check(self.lib.libusb_init(byref(self.ctx)))\n+        self.ran_init = True\n \n     @methodtrace(_logger)\n     def __del__(self):\n-        self.lib.libusb_exit(self.ctx)\n+\tif self.ran_init is True:\n+\t  self.lib.libusb_exit(self.ctx)\n \n \n     @methodtrace(_logger)\n"
  },
  {
    "path": "pythonforandroid/recipes/pyzbar/__init__.py",
    "content": "from os.path import join\nfrom pythonforandroid.recipe import PythonRecipe\n\n\nclass PyZBarRecipe(PythonRecipe):\n\n    version = '0.1.9'\n\n    url = 'https://github.com/NaturalHistoryMuseum/pyzbar/archive/v{version}.tar.gz'  # noqa\n\n    call_hostpython_via_targetpython = False\n\n    depends = ['setuptools', 'libzbar']\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        libzbar = self.get_recipe('libzbar', self.ctx)\n        libzbar_dir = libzbar.get_build_dir(arch.arch)\n        env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)\n        env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')\n        env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')\n        env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'\n        return env\n\n\nrecipe = PyZBarRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/pyzmq/__init__.py",
    "content": "# coding=utf-8\n\nfrom pythonforandroid.recipe import CythonRecipe, Recipe\nfrom os.path import join\nfrom pythonforandroid.util import current_directory\nimport sh\nfrom pythonforandroid.logger import shprint\nimport glob\n\n\nclass PyZMQRecipe(CythonRecipe):\n    name = 'pyzmq'\n    version = '20.0.0'\n    url = 'https://github.com/zeromq/pyzmq/archive/v{version}.zip'\n    site_packages_name = 'zmq'\n    depends = ['setuptools', 'libzmq']\n    cython_args = ['-Izmq/utils',\n                   '-Izmq/backend/cython',\n                   '-Izmq/devices']\n\n    def get_recipe_env(self, arch=None):\n        env = super().get_recipe_env(arch)\n        # TODO: fix hardcoded path\n        # This is required to prevent issue with _io.so import.\n        # hostpython = self.get_recipe('hostpython2', self.ctx)\n        # env['PYTHONPATH'] = (\n        #     join(hostpython.get_build_dir(arch.arch), 'build',\n        #          'lib.linux-x86_64-2.7') + ':' + env.get('PYTHONPATH', '')\n        # )\n        # env[\"LDSHARED\"] = env[\"CC\"] + ' -shared'\n        return env\n\n    def build_cython_components(self, arch):\n        libzmq_recipe = Recipe.get_recipe('libzmq', self.ctx)\n        libzmq_prefix = join(libzmq_recipe.get_build_dir(arch.arch), \"install\")\n        self.setup_extra_args = [\"--zmq={}\".format(libzmq_prefix)]\n        self.build_cmd = \"configure\"\n\n        env = self.get_recipe_env(arch)\n        setup_cfg = join(self.get_build_dir(arch.arch), \"setup.cfg\")\n        with open(setup_cfg, \"wb\") as fd:\n            fd.write(\"\"\"\n[global]\nzmq_prefix = {}\nskip_check_zmq = True\n\"\"\".format(libzmq_prefix).encode())\n\n        return super().build_cython_components(arch)\n\n        with current_directory(self.get_build_dir(arch.arch)):\n            hostpython = sh.Command(self.hostpython_location)\n            shprint(hostpython, 'setup.py', 'configure', '-v', _env=env)\n            shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env)\n            build_dir = glob.glob('build/lib.*')[0]\n            shprint(sh.find, build_dir, '-name', '\"*.o\"', '-exec',\n                    env['STRIP'], '{}', ';', _env=env)\n\n\nrecipe = PyZMQRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/regex/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\n\n\nclass RegexRecipe(CompiledComponentsPythonRecipe):\n    name = 'regex'\n    version = '2019.06.08'\n    url = 'https://pypi.python.org/packages/source/r/regex/regex-{version}.tar.gz'  # noqa\n\n    depends = ['setuptools']\n    call_hostpython_via_targetpython = False\n\n\nrecipe = RegexRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/reportlab/__init__.py",
    "content": "import os\nimport sh\n\nfrom pythonforandroid.logger import info\nfrom pythonforandroid.recipe import CompiledComponentsPythonRecipe\nfrom pythonforandroid.util import current_directory, ensure_dir, touch\n\n\nclass ReportLabRecipe(CompiledComponentsPythonRecipe):\n    version = 'fe660f227cac'\n    url = 'https://hg.reportlab.com/hg-public/reportlab/archive/{version}.tar.gz'\n    depends = ['freetype']\n    call_hostpython_via_targetpython = False\n\n    def prebuild_arch(self, arch):\n        if not self.is_patched(arch):\n            super().prebuild_arch(arch)\n            recipe_dir = self.get_build_dir(arch.arch)\n\n            # Some versions of reportlab ship with a GPL-licensed font.\n            # Remove it, since this is problematic in .apks unless the\n            # entire app is GPL:\n            font_dir = os.path.join(recipe_dir,\n                                    \"src\", \"reportlab\", \"fonts\")\n            if os.path.exists(font_dir):\n                for file in os.listdir(font_dir):\n                    if file.lower().startswith('darkgarden'):\n                        os.remove(os.path.join(font_dir, file))\n\n            # Apply patches:\n            self.apply_patch('patches/fix-setup.patch', arch.arch)\n            touch(os.path.join(recipe_dir, '.patched'))\n            ft = self.get_recipe('freetype', self.ctx)\n            ft_dir = ft.get_build_dir(arch.arch)\n            ft_lib_dir = os.environ.get('_FT_LIB_', os.path.join(ft_dir, 'objs', '.libs'))\n            ft_inc_dir = os.environ.get('_FT_INC_', os.path.join(ft_dir, 'include'))\n            tmp_dir = os.path.normpath(os.path.join(recipe_dir, \"..\", \"..\", \"tmp\"))\n            info('reportlab recipe: recipe_dir={}'.format(recipe_dir))\n            info('reportlab recipe: tmp_dir={}'.format(tmp_dir))\n            info('reportlab recipe: ft_dir={}'.format(ft_dir))\n            info('reportlab recipe: ft_lib_dir={}'.format(ft_lib_dir))\n            info('reportlab recipe: ft_inc_dir={}'.format(ft_inc_dir))\n            with current_directory(recipe_dir):\n                ensure_dir(tmp_dir)\n                pfbfile = os.path.join(tmp_dir, \"pfbfer-20070710.zip\")\n                if not os.path.isfile(pfbfile):\n                    sh.wget(\"http://www.reportlab.com/ftp/pfbfer-20070710.zip\", \"-O\", pfbfile)\n                sh.unzip(\"-u\", \"-d\", os.path.join(recipe_dir, \"src\", \"reportlab\", \"fonts\"), pfbfile)\n                if os.path.isfile(\"setup.py\"):\n                    with open('setup.py', 'r') as f:\n                        text = f.read().replace('_FT_LIB_', ft_lib_dir).replace('_FT_INC_', ft_inc_dir)\n                    with open('setup.py', 'w') as f:\n                        f.write(text)\n\n\nrecipe = ReportLabRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/reportlab/patches/fix-setup.patch",
    "content": "diff -r 9ecdf084933c setup.py\n--- a/setup.py\tWed May 13 14:09:03 2015 +0100\n+++ b/setup.py\tFri May 22 10:14:29 2015 +0100\n@@ -14,8 +14,8 @@\n #no-download-t1-files=yes\n #ignore-system-libart=yes\n # if used on command line the config values are not used\n-dlt1 = not specialOption('--no-download-t1-files')\n-isla = specialOption('--ignore-system-libart')\n+dlt1 = False\n+isla = True\n \n try:\n     import configparser\n@@ -121,39 +121,6 @@\n         else:\n             P.insert(x, d)\n \n-class inc_lib_dirs:\n-    L = None\n-    I = None\n-    def __call__(self):\n-        if self.L is None:\n-            L = []\n-            I = []\n-            if platform == \"cygwin\":\n-                aDir(L, os.path.join(\"/usr/lib\", \"python%s\" % sys.version[:3], \"config\"))\n-            elif platform == \"darwin\":\n-                # attempt to make sure we pick freetype2 over other versions\n-                aDir(I, \"/sw/include/freetype2\")\n-                aDir(I, \"/sw/lib/freetype2/include\")\n-                # fink installation directories\n-                aDir(L, \"/sw/lib\")\n-                aDir(I, \"/sw/include\")\n-                # darwin ports installation directories\n-                aDir(L, \"/opt/local/lib\")\n-                aDir(I, \"/opt/local/include\")\n-            aDir(I, \"/usr/local/include\")\n-            aDir(L, \"/usr/local/lib\")\n-            aDir(I, \"/usr/include\")\n-            aDir(L, \"/usr/lib\")\n-            aDir(I, \"/usr/include/freetype2\")\n-            prefix = sysconfig.get_config_var(\"prefix\")\n-            if prefix:\n-                aDir(L, pjoin(prefix, \"lib\"))\n-                aDir(I, pjoin(prefix, \"include\"))\n-            self.L=L\n-            self.I=I\n-        return self.I,self.L\n-inc_lib_dirs=inc_lib_dirs()\n-\n def getVersionFromCCode(fn):\n     import re\n     tag = re.search(r'^#define\\s+VERSION\\s+\"([^\"]*)\"',open(fn,'r').read(),re.M)\n@@ -244,11 +211,7 @@\n         ]\n \n def get_fonts(PACKAGE_DIR, reportlab_files):\n-    import sys, os, os.path, zipfile, io\n-    if isPy3:\n-        import urllib.request as ureq\n-    else:\n-        import urllib2 as ureq\n+    import os, os.path\n     rl_dir = PACKAGE_DIR['reportlab']\n     if not [x for x in reportlab_files if not os.path.isfile(pjoin(rl_dir,x))]:\n         infoline(\"Standard T1 font curves already downloaded\")\n@@ -257,6 +220,11 @@\n         infoline('not downloading T1 font curve files')\n         return\n     try:\n+        if isPy3:\n+            import urllib.request as ureq\n+        else:\n+            import urllib2 as ureq\n+        import zipfile, io\n         infoline(\"Downloading standard T1 font curves\")\n \n         remotehandle = ureq.urlopen(\"http://www.reportlab.com/ftp/pfbfer-20070710.zip\")\n@@ -448,7 +416,8 @@\n                 FT_LIB_DIR=[FT_LIB_DIR] if FT_LIB_DIR else []\n                 FT_INC_DIR=config('FREETYPE_PATHS','inc')\n                 FT_INC_DIR=[FT_INC_DIR] if FT_INC_DIR else []\n-            I,L=inc_lib_dirs()\n+            I=[\"_FT_INC_\"]\n+            L=[\"_FT_LIB_\"]\n             ftv = None\n             for d in I:\n                 if isfile(pjoin(d, \"ft2build.h\")):\n"
  },
  {
    "path": "pythonforandroid/recipes/ruamel.yaml/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass RuamelYamlRecipe(PythonRecipe):\n    version = '0.15.77'\n    url = 'https://pypi.python.org/packages/source/r/ruamel.yaml/ruamel.yaml-{version}.tar.gz'\n    depends = ['setuptools']\n    site_packages_name = 'ruamel'\n    call_hostpython_via_targetpython = False\n    patches = ['disable-pip-req.patch']\n\n\nrecipe = RuamelYamlRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/ruamel.yaml/disable-pip-req.patch",
    "content": "--- setup.py        2018-11-11 18:27:31.936424140 +0100\n+++ b/setup.py    2018-11-11 18:28:19.873507071 +0100\n@@ -396,7 +396,7 @@\n                 sys.exit(0)\n             if not os.environ.get('RUAMEL_NO_PIP_INSTALL_CHECK', False):\n                 print('error: you have to install with \"pip install .\"')\n-                sys.exit(1)\n+                # sys.exit(1)\n         # If you only support an extension module on Linux, Windows thinks it\n         # is pure. That way you would get pure python .whl files that take\n         # precedence for downloading on Linux over source with compilable C code\n"
  },
  {
    "path": "pythonforandroid/recipes/scipy/__init__.py",
    "content": "import os\nfrom os.path import join, dirname, basename\nfrom pythonforandroid.recipe import MesonRecipe, Recipe\nfrom pythonforandroid.logger import warning\nfrom pathlib import Path\n\n\nclass ScipyRecipe(MesonRecipe):\n\n    version = \"v1.16.2\"\n    url = \"git+https://github.com/scipy/scipy.git\"\n    depends = [\"numpy\", \"libopenblas\", \"fortran\"]\n    need_stl_shared = True\n    meson_version = \"1.5.0\"\n    hostpython_prerequisites = [\"numpy\", \"Cython>=3.0.8\"]\n    patches = [\"meson.patch\"]\n\n    def get_recipe_meson_options(self, arch):\n        options = super().get_recipe_meson_options(arch)\n        options[\"binaries\"][\"python\"] = self.ctx.python_recipe.python_exe\n        options[\"binaries\"][\"fortran\"] = self.place_wrapper(arch)\n        options[\"properties\"][\"numpy-include-dir\"] = join(\n            self.ctx.get_python_install_dir(arch.arch), \"numpy/_core/include\"\n        )\n        self.ensure_args(\n            \"-Csetup-args=-Dblas=openblas\",\n            \"-Csetup-args=-Dlapack=openblas\",\n            f\"-Csetup-args=-Dopenblas_libdir={self.ctx.get_libs_dir(arch.arch)}\",\n            f'-Csetup-args=-Dopenblas_incldir={join(Recipe.get_recipe(\"libopenblas\", self.ctx).get_build_dir(arch.arch), \"build\")}',\n            \"-Csetup-args=-Duse-pythran=false\",\n        )\n        return options\n\n    def place_wrapper(self, arch):\n        compiler = Recipe.get_recipe(\"fortran\", self.ctx).get_fortran_bin(arch.arch)\n        file = join(self.get_recipe_dir(), \"wrapper.py\")\n        with open(file, \"r\") as _file:\n            data = _file.read()\n            _file.close()\n        data = data.replace(\"@COMPILER@\", compiler)\n        # custom compiler\n        # taken from: https://github.com/termux/termux-packages/blob/master/packages/python-scipy/\n        m_compiler = Path(join(dirname(compiler), basename(compiler) + \"-scipy\"))\n        m_compiler.write_text(data)\n        m_compiler.chmod(0o755)\n        self.patch_shebang(str(m_compiler), self.real_hostpython_location)\n        return str(m_compiler)\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        arch_env = arch.get_env()\n        env[\"LDFLAGS\"] = arch_env[\"LDFLAGS\"]\n        env[\"LDFLAGS\"] += \" -L{} -lpython{}\".format(\n            self.ctx.python_recipe.link_root(arch.arch),\n            self.ctx.python_recipe.link_version,\n        )\n        return env\n\n    def build_arch(self, arch):\n        if arch.arch not in [\"arm64-v8a\", \"x86_64\"]:\n            warning(\n                \"SciPy supports only 64-bit Android architectures: arm64-v8a and x86_64; skipping build.\"\n            )\n            return\n\n        if os.name != \"posix\":\n            warning(\"Building SciPy is only supported on Linux; skipping.\")\n            return\n\n        super().build_arch(arch)\n\n\nrecipe = ScipyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/scipy/meson.patch",
    "content": "diff '--color=auto' -uNr scipy.git/meson.options scipy.git.patch/meson.options\n--- scipy.git/meson.options\t2025-03-27 02:55:14.586853766 +0530\n+++ scipy.git.patch/meson.options\t2025-03-27 02:07:29.736674085 +0530\n@@ -2,6 +2,8 @@\n         description: 'option for BLAS library switching')\n option('lapack', type: 'string', value: 'openblas',\n         description: 'option for LAPACK library switching')\n+option('openblas_incldir', type: 'string', value: '', description: 'OpenBLAS include directory')\n+option('openblas_libdir', type: 'string', value: '', description: 'OpenBLAS library directory')\n option('use-g77-abi', type: 'boolean', value: false,\n         description: 'If set to true, forces using g77 compatibility wrappers ' +\n                      'for LAPACK functions. The default is to use gfortran ' +\ndiff '--color=auto' -uNr scipy.git/scipy/meson.build scipy.git.patch/scipy/meson.build\n--- scipy.git/scipy/meson.build\t2025-03-27 02:55:14.632428649 +0530\n+++ scipy.git.patch/scipy/meson.build\t2025-03-27 11:25:33.756445056 +0530\n@@ -268,10 +268,18 @@\n   endif\n endif\n\n+openblas_inc = get_option('openblas_incldir')\n+openblas_lib = get_option('openblas_libdir')\n+\n+openblas_dep = declare_dependency(\n+  include_directories: include_directories(openblas_inc),\n+  link_args: ['-L' + openblas_lib, '-lopenblas']\n+)\n+\n # pkg-config uses a lower-case name while CMake uses a capitalized name, so try\n # that too to make the fallback detection with CMake work\n if blas_name == 'openblas'\n-  blas = dependency(['openblas', 'OpenBLAS'])\n+  blas = openblas_dep\n elif blas_name != 'scipy-openblas'  # if so, we found it already\n   blas = dependency(blas_name)\n endif\n@@ -295,7 +303,7 @@\n   # use that - no need to run the full detection twice.\n   lapack = blas\n elif lapack_name == 'openblas'\n-  lapack = dependency(['openblas', 'OpenBLAS'])\n+  lapack = openblas_dep\n else\n   lapack = dependency(lapack_name)\n endif\n"
  },
  {
    "path": "pythonforandroid/recipes/scipy/wrapper.py",
    "content": "#!/usr/bin/python3\n\n# Taken from https://github.com/termux/termux-packages/blob/master/packages/python-scipy/wrapper.py.in\n\nimport os\nimport subprocess\nimport sys\nimport typing\n\n\"\"\"\nThis wrapper is used to ignore or replace some unsupported flags for flang-new.\n\nIt will operate as follows:\n\n1. Ignore `-Minform=inform` and `-fdiagnostics-color`.\n  They are added by meson automatically, but are not supported by flang-new yet.\n2. Remove `-lflang` and `-lpgmath`.\n  It exists in classic-flang but doesn't exist in flang-new.\n3. Replace `-Oz` to `-O2`.\n  `-Oz` is not supported by flang-new.\n4. Replace `-module` to `-J`.\n  See https://github.com/llvm/llvm-project/issues/66969\n5. Ignore `-MD`, `-MQ file` and `-MF file`.\n  They generates files used by GNU make but we're using ninja.\n6. Ignore `-fvisibility=hidden`.\n  It is not supported by flang-new, and ignoring it will not break the functionality,\n  as scipy also uses version script for shared libraries.\n\"\"\"\n\nCOMPLIER_PATH = \"@COMPILER@\"\n\n\ndef main(argv: typing.List[str]):\n    cwd = os.getcwd()\n    argv_new = []\n    i = 0\n    while i < len(argv):\n        arg = argv[i]\n        if arg in [\n            \"-Minform=inform\",\n            \"-lflang\",\n            \"-lpgmath\",\n            \"-MD\",\n            \"-fvisibility=hidden\",\n        ] or arg.startswith(\"-fdiagnostics-color\"):\n            pass\n        elif arg == \"-Oz\":\n            argv_new.append(\"-O2\")\n        elif arg == \"-module\":\n            argv_new.append(\"-J\")\n        elif arg in [\"-MQ\", \"-MF\"]:\n            i += 1\n        else:\n            argv_new.append(arg)\n        i += 1\n\n    args = [COMPLIER_PATH] + argv_new\n    subprocess.check_call(args, env=os.environ, cwd=cwd, text=True)\n\n\nif __name__ == \"__main__\":\n    main(sys.argv[1:])\n"
  },
  {
    "path": "pythonforandroid/recipes/scrypt/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\n\n\nclass ScryptRecipe(CythonRecipe):\n\n    version = '0.8.20'\n    url = 'https://github.com/holgern/py-scrypt/archive/refs/tags/v{version}.zip'\n    depends = ['setuptools', 'openssl']\n    call_hostpython_via_targetpython = False\n    patches = [\"remove_librt.patch\"]\n\n    def get_recipe_env(self, arch, with_flags_in_cc=True):\n        \"\"\"\n        Adds openssl recipe to include and library path.\n        \"\"\"\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        openssl_recipe = self.get_recipe('openssl', self.ctx)\n        env['CFLAGS'] += openssl_recipe.include_flags(arch)\n        env['LDFLAGS'] += ' -L{}'.format(self.ctx.get_libs_dir(arch.arch))\n        env['LDFLAGS'] += ' -L{}'.format(self.ctx.libs_dir)\n        env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)\n        env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags()\n        return env\n\n\nrecipe = ScryptRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/scrypt/remove_librt.patch",
    "content": "--- a/setup.py\t2018-05-06 23:25:08.757522119 +0200\n+++ b/setup.py\t2018-05-06 23:25:30.269797365 +0200\n@@ -15,7 +15,6 @@\n \n if sys.platform.startswith('linux'):\n     define_macros = [('HAVE_CLOCK_GETTIME', '1'),\n-                     ('HAVE_LIBRT', '1'),\n                      ('HAVE_POSIX_MEMALIGN', '1'),\n                      ('HAVE_STRUCT_SYSINFO', '1'),\n                      ('HAVE_STRUCT_SYSINFO_MEM_UNIT', '1'),\n@@ -23,8 +22,7 @@\n                      ('HAVE_SYSINFO', '1'),\n                      ('HAVE_SYS_SYSINFO_H', '1'),\n                      ('_FILE_OFFSET_BITS', '64')]\n-    libraries = ['crypto', 'rt']\n-    includes = ['/usr/local/include', '/usr/include']\n+    libraries = ['crypto']\n     CFLAGS.append('-O2')\n elif sys.platform.startswith('win32'):\n     define_macros = [('inline', '__inline')]\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl2/__init__.py",
    "content": "from os.path import exists, join\n\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nimport sh\n\n\nclass LibSDL2Recipe(BootstrapNDKRecipe):\n    version = \"2.30.11\"\n    url = \"https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL2-{version}.tar.gz\"\n    md5sum = 'bea190b480f6df249db29eb3bacfe41e'\n\n    conflicts = ['sdl3']\n\n    dir_name = 'SDL'\n\n    depends = ['sdl2_image', 'sdl2_mixer', 'sdl2_ttf']\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=True):\n        env = super().get_recipe_env(\n            arch=arch, with_flags_in_cc=with_flags_in_cc, with_python=with_python)\n        env['APP_ALLOW_MISSING_DEPS'] = 'true'\n        return env\n\n    def should_build(self, arch):\n        libdir = join(self.get_build_dir(arch.arch), \"../..\", \"libs\", arch.arch)\n        libs = ['libmain.so', 'libSDL2.so', 'libSDL2_image.so', 'libSDL2_mixer.so', 'libSDL2_ttf.so']\n        return not all(exists(join(libdir, x)) for x in libs)\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_jni_dir()):\n            shprint(\n                sh.Command(join(self.ctx.ndk_dir, \"ndk-build\")),\n                \"V=1\",\n                \"NDK_DEBUG=\" + (\"1\" if self.ctx.build_as_debuggable else \"0\"),\n                _env=env\n            )\n\n\nrecipe = LibSDL2Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl2_image/__init__.py",
    "content": "import os\nimport sh\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\n\n\nclass LibSDL2Image(BootstrapNDKRecipe):\n    version = '2.8.2'\n    url = 'https://github.com/libsdl-org/SDL_image/releases/download/release-{version}/SDL2_image-{version}.tar.gz'\n    dir_name = 'SDL2_image'\n\n    patches = ['enable-webp.patch']\n\n    def get_include_dirs(self, arch):\n        return [\n            os.path.join(self.ctx.bootstrap.build_dir, \"jni\", \"SDL2_image\", \"include\")\n        ]\n\n    def prebuild_arch(self, arch):\n        # We do not have a folder for each arch on BootstrapNDKRecipe, so we\n        # need to skip the external deps download if we already have done it.\n\n        build_dir = self.get_build_dir(arch.arch)\n\n        with open(os.path.join(build_dir, \".gitmodules\"), \"r\") as file:\n            for section in file.read().split('[submodule \"')[1:]:\n                line_split = section.split(\" = \")\n                # Parse .gitmodule section\n                clone_path, url, branch = (\n                    os.path.join(build_dir, line_split[1].split(\"\\n\")[0].strip()),\n                    line_split[2].split(\"\\n\")[0].strip(),\n                    line_split[-1].strip()\n                )\n                # Clone if needed\n                if not os.path.exists(clone_path) or not os.listdir(clone_path):\n                    shprint(\n                        sh.git, \"clone\", url,\n                        \"--depth\", \"1\", \"-b\",\n                        branch, clone_path, \"--recursive\"\n                    )\n\n        super().prebuild_arch(arch)\n\n\nrecipe = LibSDL2Image()\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl2_image/enable-webp.patch",
    "content": "diff -Naur SDL2_image.orig/Android.mk SDL2_image/Android.mk\n--- SDL2_image.orig/Android.mk\t2022-10-03 20:51:52.000000000 +0200\n+++ SDL2_image/Android.mk\t2022-10-03 20:52:48.000000000 +0200\n@@ -32,7 +32,7 @@\n \n # Enable this if you want to support loading WebP images\n # The library path should be a relative path to this directory.\n-SUPPORT_WEBP ?= false\n+SUPPORT_WEBP := true\n WEBP_LIBRARY_PATH := external/libwebp\n \n \n"
  },
  {
    "path": "pythonforandroid/recipes/sdl2_mixer/__init__.py",
    "content": "import os\n\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\n\n\nclass LibSDL2Mixer(BootstrapNDKRecipe):\n    version = '2.6.3'\n    url = 'https://github.com/libsdl-org/SDL_mixer/releases/download/release-{version}/SDL2_mixer-{version}.tar.gz'\n    dir_name = 'SDL2_mixer'\n\n    def get_include_dirs(self, arch):\n        return [\n            os.path.join(self.ctx.bootstrap.build_dir, \"jni\", \"SDL2_mixer\", \"include\")\n        ]\n\n\nrecipe = LibSDL2Mixer()\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl2_ttf/__init__.py",
    "content": "from pythonforandroid.recipe import BootstrapNDKRecipe\n\n\nclass LibSDL2TTF(BootstrapNDKRecipe):\n    version = '2.22.0'\n    url = 'https://github.com/libsdl-org/SDL_ttf/releases/download/release-{version}/SDL2_ttf-{version}.tar.gz'\n    dir_name = 'SDL2_ttf'\n\n\nrecipe = LibSDL2TTF()\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl3/__init__.py",
    "content": "from os.path import exists, join\n\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nimport sh\n\n\nclass LibSDL3Recipe(BootstrapNDKRecipe):\n    version = \"3.4.2\"\n    url = \"https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL3-{version}.tar.gz\"\n    md5sum = \"b488ea1ede947c06855588314effe905\"\n\n    conflicts = [\"sdl2\"]\n\n    dir_name = \"SDL\"\n\n    depends = [\"sdl3_image\", \"sdl3_mixer\", \"sdl3_ttf\"]\n\n    def get_recipe_env(\n        self, arch=None, with_flags_in_cc=True, with_python=True\n    ):\n        env = super().get_recipe_env(\n            arch=arch,\n            with_flags_in_cc=with_flags_in_cc,\n            with_python=with_python,\n        )\n        env[\"APP_ALLOW_MISSING_DEPS\"] = \"true\"\n        return env\n\n    def get_include_dirs(self, arch):\n        return [\n            join(self.ctx.bootstrap.build_dir, \"jni\", \"SDL\", \"include\"),\n            join(self.ctx.bootstrap.build_dir, \"jni\", \"SDL\", \"include\", \"SDL3\"),\n        ]\n\n    def should_build(self, arch):\n        libdir = join(self.get_build_dir(arch.arch), \"../..\", \"libs\", arch.arch)\n        libs = [\n            \"libmain.so\",\n            \"libSDL3.so\",\n            \"libSDL3_image.so\",\n            \"libSDL3_mixer.so\",\n            \"libSDL3_ttf.so\",\n        ]\n        return not all(exists(join(libdir, x)) for x in libs)\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n\n        with current_directory(self.get_jni_dir()):\n            shprint(\n                sh.Command(join(self.ctx.ndk_dir, \"ndk-build\")),\n                \"V=1\",\n                \"NDK_DEBUG=\" + (\"1\" if self.ctx.build_as_debuggable else \"0\"),\n                _env=env,\n            )\n\n\nrecipe = LibSDL3Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl3_image/__init__.py",
    "content": "import os\nimport sh\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\nfrom pythonforandroid.util import current_directory\n\n\nclass LibSDL3Image(BootstrapNDKRecipe):\n    version = \"3.4.0\"\n    url = \"https://github.com/libsdl-org/SDL_image/releases/download/release-{version}/SDL3_image-{version}.tar.gz\"\n    dir_name = \"SDL3_image\"\n\n    patches = [\"enable-webp.patch\"]\n\n    def get_include_dirs(self, arch):\n        return [\n            os.path.join(\n                self.ctx.bootstrap.build_dir, \"jni\", \"SDL3_image\", \"include\"\n            ),\n            os.path.join(\n                self.ctx.bootstrap.build_dir,\n                \"jni\",\n                \"SDL3_image\",\n                \"include\",\n                \"SDL3_image\",\n            ),\n        ]\n\n    def prebuild_arch(self, arch):\n        # We do not have a folder for each arch on BootstrapNDKRecipe, so we\n        # need to skip the external deps download if we already have done it.\n        external_deps_dir = os.path.join(\n            self.get_build_dir(arch.arch), \"external\"\n        )\n        if not os.path.exists(os.path.join(external_deps_dir, \"libwebp\")):\n            with current_directory(external_deps_dir):\n                shprint(sh.Command(\"./download.sh\"))\n        super().prebuild_arch(arch)\n\n\nrecipe = LibSDL3Image()\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl3_image/enable-webp.patch",
    "content": "--- a/Android.mk\n+++ b/Android.mk\n@@ -33,8 +33,8 @@\n \n # Enable this if you want to support loading WebP images\n # The library path should be a relative path to this directory.\n-SUPPORT_WEBP ?= false\n-SUPPORT_SAVE_WEBP ?= true\n+SUPPORT_WEBP := true\n+SUPPORT_SAVE_WEBP := true\n WEBP_LIBRARY_PATH := external/libwebp\n \n \n@@ -160,7 +160,7 @@\n ifeq ($(SUPPORT_WEBP),true)\n     LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(WEBP_LIBRARY_PATH)/src\n     LOCAL_CFLAGS += -DLOAD_WEBP\n-    LOCAL_STATIC_LIBRARIES += webpdemux\n+    LOCAL_STATIC_LIBRARIES += webpmux webpdemux\n     LOCAL_STATIC_LIBRARIES += webp\n ifeq ($(SUPPORT_SAVE_WEBP),true)\n     LOCAL_CFLAGS += -DSAVE_WEBP=1"
  },
  {
    "path": "pythonforandroid/recipes/sdl3_mixer/__init__.py",
    "content": "import os\nimport sh\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\nfrom pythonforandroid.util import current_directory\n\n\nclass LibSDL3Mixer(BootstrapNDKRecipe):\n    version = \"72a73339731a12c1002f9caca64f1ab924938102\"\n    url = \"https://github.com/libsdl-org/SDL_mixer/archive/{version}.tar.gz\"\n    dir_name = \"SDL3_mixer\"\n\n    patches = [\"disable-libgme.patch\"]\n\n    def get_include_dirs(self, arch):\n        return [\n            os.path.join(\n                self.ctx.bootstrap.build_dir, \"jni\", \"SDL3_mixer\", \"include\"\n            ),\n            os.path.join(\n                self.ctx.bootstrap.build_dir,\n                \"jni\",\n                \"SDL3_mixer\",\n                \"include\",\n                \"SDL3_mixer\",\n            ),\n        ]\n\n    def prebuild_arch(self, arch):\n        # We do not have a folder for each arch on BootstrapNDKRecipe, so we\n        # need to skip the external deps download if we already have done it.\n        external_deps_dir = os.path.join(\n            self.get_build_dir(arch.arch), \"external\"\n        )\n\n        if not os.path.exists(\n            os.path.join(external_deps_dir, \"libgme\", \"Android.mk\")\n        ):\n            with current_directory(external_deps_dir):\n                shprint(sh.Command(\"./download.sh\"))\n        super().prebuild_arch(arch)\n\n\nrecipe = LibSDL3Mixer()\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl3_mixer/disable-libgme.patch",
    "content": "diff -Naur SDL3_mixer.orig/Android.mk SDL3_mixer/Android.mk\n--- SDL3_mixer.orig/Android.mk\t2025-03-16 21:05:19\n+++ SDL3_mixer/Android.mk\t2025-03-16 21:06:33\n@@ -31,7 +31,7 @@\n WAVPACK_LIBRARY_PATH := external/wavpack\n \n # Enable this if you want to support loading music via libgme\n-SUPPORT_GME ?= true\n+SUPPORT_GME ?= false\n GME_LIBRARY_PATH := external/libgme\n \n # Enable this if you want to support loading MOD music via XMP-lite\n"
  },
  {
    "path": "pythonforandroid/recipes/sdl3_ttf/__init__.py",
    "content": "import os\nimport sh\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.recipe import BootstrapNDKRecipe\nfrom pythonforandroid.util import current_directory\n\n\nclass LibSDL3TTF(BootstrapNDKRecipe):\n    version = \"3.2.2\"\n    url = \"https://github.com/libsdl-org/SDL_ttf/releases/download/release-{version}/SDL3_ttf-{version}.tar.gz\"\n    dir_name = \"SDL3_ttf\"\n\n    def get_include_dirs(self, arch):\n        return [\n            os.path.join(\n                self.ctx.bootstrap.build_dir, \"jni\", \"SDL3_ttf\", \"include\"\n            ),\n            os.path.join(\n                self.ctx.bootstrap.build_dir,\n                \"jni\",\n                \"SDL3_ttf\",\n                \"include\",\n                \"SDL3_ttf\",\n            ),\n        ]\n\n    def prebuild_arch(self, arch):\n        # We do not have a folder for each arch on BootstrapNDKRecipe, so we\n        # need to skip the external deps download if we already have done it.\n        external_deps_dir = os.path.join(\n            self.get_build_dir(arch.arch), \"external\"\n        )\n        if not os.path.exists(os.path.join(external_deps_dir, \"harfbuzz\")):\n            with current_directory(external_deps_dir):\n                shprint(sh.Command(\"./download.sh\"))\n        super().prebuild_arch(arch)\n\n\nrecipe = LibSDL3TTF()\n"
  },
  {
    "path": "pythonforandroid/recipes/secp256k1/__init__.py",
    "content": "import os\nfrom pythonforandroid.recipe import CppCompiledComponentsPythonRecipe\n\n\nclass Secp256k1Recipe(CppCompiledComponentsPythonRecipe):\n\n    version = '0.13.2.4'\n    url = 'https://github.com/ludbb/secp256k1-py/archive/{version}.tar.gz'\n\n    call_hostpython_via_targetpython = False\n\n    depends = [\n        'openssl',\n        'hostpython3',\n        'python3',\n        'setuptools',\n        'libffi',\n        'cffi',\n        'libsecp256k1'\n    ]\n\n    patches = [\n        \"cross_compile.patch\", \"drop_setup_requires.patch\",\n        \"pkg-config.patch\", \"find_lib.patch\", \"no-download.patch\"]\n\n    def get_recipe_env(self, arch=None):\n        env = super().get_recipe_env(arch)\n        libsecp256k1 = self.get_recipe('libsecp256k1', self.ctx)\n        libsecp256k1_dir = libsecp256k1.get_build_dir(arch.arch)\n        env['CFLAGS'] += ' -I' + os.path.join(libsecp256k1_dir, 'include')\n        env['LDFLAGS'] += ' -L{} -lsecp256k1'.format(libsecp256k1_dir)\n        return env\n\n\nrecipe = Secp256k1Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/secp256k1/cross_compile.patch",
    "content": "diff --git a/setup.py b/setup.py\nindex bba4bce..b86b369 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -191,6 +192,7 @@ class build_clib(_build_clib):\n             \"--disable-dependency-tracking\",\n             \"--with-pic\",\n             \"--enable-module-recovery\",\n+            \"--host=\" + arch.command_prefix,\n             \"--prefix\",\n             os.path.abspath(self.build_clib),\n         ]\n"
  },
  {
    "path": "pythonforandroid/recipes/secp256k1/drop_setup_requires.patch",
    "content": "diff --git a/setup.py b/setup.py\nindex bba4bce..bfffbbc 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -263,7 +263,6 @@ setup(\n     author_email='lud@tutanota.com',\n     license='MIT',\n \n-    setup_requires=['cffi>=1.3.0', 'pytest-runner==2.6.2'],\n     install_requires=['cffi>=1.3.0'],\n     tests_require=['pytest==2.8.7'],\n \n"
  },
  {
    "path": "pythonforandroid/recipes/secp256k1/find_lib.patch",
    "content": "diff --git a/setup_support.py b/setup_support.py\nindex 68a2a7f..b84f420 100644\n--- a/setup_support.py\n+++ b/setup_support.py\n@@ -68,6 +68,8 @@ def build_flags(library, type_, path):\n \n \n def _find_lib():\n+    # we're picking up the recipe one\n+    return True\n     from cffi import FFI\n     ffi = FFI()\n     try:\n"
  },
  {
    "path": "pythonforandroid/recipes/secp256k1/no-download.patch",
    "content": "diff --git a/setup.py b/setup.py\nindex bba4bce..5ea0228 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -55,6 +55,8 @@ except OSError:\n \n \n def download_library(command):\n+    # we will use the custom libsecp256k1 recipe\n+    return\n     if command.dry_run:\n         return\n     libdir = absolute(\"libsecp256k1\")\n"
  },
  {
    "path": "pythonforandroid/recipes/secp256k1/pkg-config.patch",
    "content": "diff --git a/setup.py b/setup.py\nindex bba4bce..609481c 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -48,10 +48,7 @@ if [int(i) for i in setuptools_version.split('.')] < [3, 3]:\n try:\n     subprocess.check_call(['pkg-config', '--version'])\n except OSError:\n-    raise SystemExit(\n-        \"'pkg-config' is required to install this package. \"\n-        \"Please see the README for details.\"\n-    )\n+    pass\n \n \n def download_library(command):\ndiff --git a/setup_support.py b/setup_support.py\nindex 68a2a7f..ccbafac 100644\n--- a/setup_support.py\n+++ b/setup_support.py\n@@ -40,6 +40,7 @@ def absolute(*paths):\n \n def build_flags(library, type_, path):\n     \"\"\"Return separated build flags from pkg-config output\"\"\"\n+    return []\n \n     pkg_config_path = [path]\n     if \"PKG_CONFIG_PATH\" in os.environ:\n"
  },
  {
    "path": "pythonforandroid/recipes/setuptools/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass SetuptoolsRecipe(PyProjectRecipe):\n    hostpython_prerequisites = ['setuptools']\n\n\nrecipe = SetuptoolsRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/shapely/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\nfrom os.path import join\n\n\nclass ShapelyRecipe(CythonRecipe):\n    version = '1.7a1'\n    url = 'https://github.com/Toblerity/Shapely/archive/{version}.tar.gz'\n    depends = ['setuptools', 'libgeos']\n\n    call_hostpython_via_targetpython = False\n\n    # Patch to avoid libgeos check (because it fails), insert environment\n    # variables for our libgeos build (includes, lib paths...) and force\n    # the cython's compilation to raise an error in case that it fails\n    patches = ['setup.patch']\n\n    # Don't Force Cython\n    # setup_extra_args = ['sdist']\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch)\n\n        libgeos_install = join(self.get_recipe(\n            'libgeos', self.ctx).get_build_dir(arch.arch), 'install_target')\n        # All this `GEOS_X` variables should be string types, separated\n        # by commas in case that we need to pass more than one value\n        env['GEOS_INCLUDE_DIRS'] = join(libgeos_install, 'include')\n        env['GEOS_LIBRARY_DIRS'] = join(libgeos_install, 'lib')\n        env['GEOS_LIBRARIES'] = 'geos_c,geos'\n\n        return env\n\n\nrecipe = ShapelyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/shapely/setup.patch",
    "content": "This patch does three things:\n    - disable the libgeos check, because, even setting the proper env variables,\n      it fails to load our libgeos library, so we skip that because it's not\n      mandatory for the cythonizing.\n    - sets some environment variables into the setup.py file, so we can pass\n      our libgeos information (includes, lib path and libraries)\n    - force to raise an error when cython file to compile (our current build\n      system relies on this failure to do the proper `cythonizing`, if we don't\n      raise the error, we will end up with the package installed without the\n      speed optimizations.\n--- Shapely-1.7a1/setup.py.orig\t2018-07-29 22:53:13.000000000 +0200\n+++ Shapely-1.7a1/setup.py\t2019-02-24 14:26:19.178610660 +0100\n@@ -82,8 +82,8 @@ if not (py_version == (2, 7) or py_versi\n\n # Get geos_version from GEOS dynamic library, which depends on\n # GEOS_LIBRARY_PATH and/or GEOS_CONFIG environment variables\n-from shapely._buildcfg import geos_version_string, geos_version, \\\n-        geos_config, get_geos_config\n+# from shapely._buildcfg import geos_version_string, geos_version, \\\n+#         geos_config, get_geos_config\n\n logging.basicConfig()\n log = logging.getLogger(__file__)\n@@ -248,9 +248,9 @@ if sys.platform == 'win32':\n     setup_args['package_data']['shapely'].append('shapely/DLLs/*.dll')\n\n # Prepare build opts and args for the speedups extension module.\n-include_dirs = []\n-library_dirs = []\n-libraries = []\n+include_dirs = os.environ.get('GEOS_INCLUDE_DIRS', '').split(',')\n+library_dirs = os.environ.get('GEOS_LIBRARY_DIRS', '').split(',')\n+libraries = os.environ.get('GEOS_LIBRARIES', '').split(',')\n extra_link_args = []\n\n # If NO_GEOS_CONFIG is set in the environment, geos-config will not\n@@ -375,6 +375,7 @@ try:\n         construct_build_ext(existing_build_ext)\n     setup(ext_modules=ext_modules, **setup_args)\n except BuildFailed as ex:\n+    raise  # Force python only build to fail\n     BUILD_EXT_WARNING = \"The C extension could not be compiled, \" \\\n                         \"speedups are not enabled.\"\n     log.warn(ex)\n"
  },
  {
    "path": "pythonforandroid/recipes/snappy/__init__.py",
    "content": "from pythonforandroid.recipe import Recipe\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nfrom os.path import join\nimport sh\n\n\nclass SnappyRecipe(Recipe):\n    version = '1.1.7'\n    url = 'https://github.com/google/snappy/archive/{version}.tar.gz'\n    built_libraries = {'libsnappy.so': '.'}\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        source_dir = self.get_build_dir(arch.arch)\n        with current_directory(source_dir):\n            shprint(sh.cmake, source_dir,\n                    '-DANDROID_ABI={}'.format(arch.arch),\n                    '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),\n                    '-DCMAKE_TOOLCHAIN_FILE={}'.format(\n                        join(self.ctx.ndk_dir, 'build', 'cmake',\n                             'android.toolchain.cmake')),\n                    '-DBUILD_SHARED_LIBS=1',\n                    _env=env)\n            shprint(sh.make, _env=env)\n\n\nrecipe = SnappyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/spine/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\n\n\nclass SpineCython(CythonRecipe):\n\n    version = '0.5.1'\n    url = 'https://github.com/tileworks/spine-cython/archive/{version}.zip'\n    name = 'spine'\n    depends = ['setuptools']\n    site_packages_name = 'spine'\n    call_hostpython_via_targetpython = False\n\n\nrecipe = SpineCython()\n"
  },
  {
    "path": "pythonforandroid/recipes/sqlalchemy/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass SQLAlchemyRecipe(PyProjectRecipe):\n    name = 'sqlalchemy'\n    version = '2.0.30'\n    url = 'https://github.com/sqlalchemy/sqlalchemy/archive/refs/tags/rel_{}.tar.gz'\n    depends = ['setuptools']\n\n    @property\n    def versioned_url(self):\n        return self.url.format(self.version.replace(\".\", \"_\"))\n\n\nrecipe = SQLAlchemyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/sqlite3/__init__.py",
    "content": "import sh\nfrom pythonforandroid.logger import shprint\nfrom pythonforandroid.util import current_directory\nfrom pythonforandroid.recipe import Recipe\nfrom multiprocessing import cpu_count\n\n\nclass Sqlite3Recipe(Recipe):\n    version = '3.50.4'\n    url = 'https://github.com/sqlite/sqlite/archive/refs/tags/version-{version}.tar.gz'\n    built_libraries = {'libsqlite3.so': '.'}\n\n    def build_arch(self, arch):\n        env = self.get_recipe_env(arch)\n        build_dir = self.get_build_dir(arch.arch)\n        config_args = {\n            '--host={}'.format(arch.command_prefix),\n            '--prefix={}'.format(build_dir),\n            '--disable-tcl',\n        }\n        with current_directory(build_dir):\n            configure = sh.Command('./configure')\n            shprint(configure, *config_args, _env=env)\n            shprint(sh.make, '-j', str(cpu_count()), _env=env)\n\n\nrecipe = Sqlite3Recipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/storm/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe, current_directory, shprint\nimport sh\n\n\nclass StormRecipe(PythonRecipe):\n    version = '0.20'\n    url = 'https://launchpad.net/storm/trunk/{version}/+download/storm-{version}.tar.bz2'\n    depends = []\n    site_packages_name = 'storm'\n    call_hostpython_via_targetpython = False\n\n    def prebuild_arch(self, arch):\n        with current_directory(self.get_build_dir(arch.arch)):\n            # Cross compiling for 32 bits in 64 bit ubuntu before precise is\n            # failing. See\n            # https://bugs.launchpad.net/ubuntu/+source/python2.7/+bug/873007\n            shprint(sh.sed, '-i',\n                    \"s|BUILD_CEXTENSIONS = True|BUILD_CEXTENSIONS = False|\",\n                    'setup.py')\n\n\nrecipe = StormRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/tflite-runtime/CMakeLists.patch",
    "content": "--- tflite-runtime/tensorflow/lite/CMakeLists.txt\t2022-01-27 17:29:49.460000000 -1000\n+++ CMakeLists.txt\t2022-02-21 15:03:09.568367300 -1000\n@@ -220,6 +220,9 @@\n if(NOT \"${CMAKE_SYSTEM_NAME}\" STREQUAL \"iOS\")\n   list(FILTER TFLITE_SRCS EXCLUDE REGEX \".*minimal_logging_ios\\\\.cc$\")\n endif()\n+if(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Android\")\n+  list(FILTER TFLITE_SRCS EXCLUDE REGEX \".*minimal_logging_default\\\\.cc$\")\n+endif()\n populate_tflite_source_vars(\"core\" TFLITE_CORE_SRCS)\n populate_tflite_source_vars(\"core/api\" TFLITE_CORE_API_SRCS)\n populate_tflite_source_vars(\"c\" TFLITE_C_SRCS)\n@@ -505,6 +508,7 @@\n     ruy\n     ${CMAKE_DL_LIBS}\n     ${TFLITE_TARGET_DEPENDENCIES}\n+    ${ANDROID_LOG_LIB}\n )\n \n if (NOT BUILD_SHARED_LIBS)\n@@ -550,6 +554,7 @@\n   tensorflow-lite\n   ${CMAKE_DL_LIBS}\n )\n+\n target_compile_options(_pywrap_tensorflow_interpreter_wrapper\n   PUBLIC ${TFLITE_TARGET_PUBLIC_OPTIONS}\n   PRIVATE ${TFLITE_TARGET_PRIVATE_OPTIONS}\n"
  },
  {
    "path": "pythonforandroid/recipes/tflite-runtime/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe, current_directory, \\\r\n    shprint, info_main, warning\r\nfrom pythonforandroid.logger import error\r\nfrom os.path import join\r\nimport sh\r\n\r\n\r\nclass TFLiteRuntimeRecipe(PythonRecipe):\r\n    ###############################################################\r\n    #\r\n    # tflite-runtime README:\r\n    # https://github.com/Android-for-Python/c4k_tflite_example/blob/main/README.md\r\n    #\r\n    # Recipe build references:\r\n    # https://developer.android.com/ndk/guides/cmake\r\n    # https://developer.android.com/ndk/guides/cpu-arm-neon#cmake\r\n    # https://www.tensorflow.org/lite/guide/build_cmake\r\n    # https://www.tensorflow.org/lite/guide/build_cmake_arm\r\n    #\r\n    # Tested using cmake 3.16.3 probably requires cmake >= 3.13\r\n    #\r\n    # THIS RECIPE DOES NOT BUILD x86_64, USE X86 FOR AN EMULATOR\r\n    #\r\n    ###############################################################\r\n\r\n    version = '2.8.0'\r\n    url = 'https://github.com/tensorflow/tensorflow/archive/refs/tags/v{version}.zip'\r\n    depends = ['pybind11', 'numpy']\r\n    patches = ['CMakeLists.patch', 'build_with_cmake.patch']\r\n    site_packages_name = 'tflite-runtime'\r\n    call_hostpython_via_targetpython = False\r\n\r\n    def should_build(self, arch):\r\n        name = self.folder_name.replace('-', '_')\r\n\r\n        if self.ctx.has_package(name, arch):\r\n            info_main('Python package already exists in site-packages')\r\n            return False\r\n        info_main('{} apparently isn\\'t already in site-packages'.format(name))\r\n        return True\r\n\r\n    def build_arch(self, arch):\r\n        if arch.arch == 'x86_64':\r\n            warning(\"******** tflite-runtime x86_64 will not be built *******\")\r\n            warning(\"Expect one of these app run time error messages:\")\r\n            warning(\"ModuleNotFoundError: No module named 'tensorflow'\")\r\n            warning(\"ModuleNotFoundError: No module named 'tflite_runtime'\")\r\n            warning(\"Use x86 not x86_64\")\r\n            return\r\n\r\n        env = self.get_recipe_env(arch)\r\n\r\n        # Directories\r\n        root_dir = self.get_build_dir(arch.arch)\r\n        script_dir = join(root_dir,\r\n                          'tensorflow', 'lite', 'tools', 'pip_package')\r\n        build_dir = join(script_dir, 'gen', 'tflite_pip', 'python3')\r\n\r\n        # Includes\r\n        python_include_dir = self.ctx.python_recipe.include_root(arch.arch)\r\n        pybind11_recipe = self.get_recipe('pybind11', self.ctx)\r\n        pybind11_include_dir = pybind11_recipe.get_include_dir(arch)\r\n        numpy_include_dir = join(self.ctx.get_site_packages_dir(arch),\r\n                                 'numpy', 'core', 'include')\r\n        includes = ' -I' + python_include_dir + \\\r\n                   ' -I' + numpy_include_dir + \\\r\n                   ' -I' + pybind11_include_dir\r\n\r\n        # Scripts\r\n        build_script = join(script_dir, 'build_pip_package_with_cmake.sh')\r\n        toolchain = join(self.ctx.ndk_dir,\r\n                         'build', 'cmake', 'android.toolchain.cmake')\r\n\r\n        # Build\r\n        ########\r\n        with current_directory(root_dir):\r\n            env.update({\r\n                'TENSORFLOW_TARGET': 'android',\r\n                'CMAKE_TOOLCHAIN_FILE': toolchain,\r\n                'ANDROID_PLATFORM': str(self.ctx.ndk_api),\r\n                'ANDROID_ABI': arch.arch,\r\n                'WRAPPER_INCLUDES': includes,\r\n                'CMAKE_SHARED_LINKER_FLAGS': env['LDFLAGS'],\r\n            })\r\n\r\n            try:\r\n                info_main('tflite-runtime is building...')\r\n                info_main('Expect this to take at least 5 minutes...')\r\n                cmd = sh.Command(build_script)\r\n                cmd(_env=env)\r\n            except sh.ErrorReturnCode as e:\r\n                error(str(e.stderr))\r\n                exit(1)\r\n\r\n        # Install\r\n        ##########\r\n        info_main('Installing tflite-runtime into site-packages')\r\n        with current_directory(build_dir):\r\n            hostpython = sh.Command(self.hostpython_location)\r\n            install_dir = self.ctx.get_python_install_dir(arch.arch)\r\n            env['PACKAGE_VERSION'] = self.version\r\n            shprint(hostpython, 'setup.py', 'install', '-O2',\r\n                    '--root={}'.format(install_dir),\r\n                    '--install-lib=.',\r\n                    _env=env)\r\n\r\n\r\nrecipe = TFLiteRuntimeRecipe()\r\n"
  },
  {
    "path": "pythonforandroid/recipes/tflite-runtime/build_with_cmake.patch",
    "content": "--- tflite-runtime/tensorflow/lite/tools/pip_package/build_pip_package_with_cmake.sh\t2022-01-22 08:57:16.000000000 -1000\n+++ build_pip_package_with_cmake.sh\t2022-03-02 18:19:05.185550500 -1000\n@@ -28,7 +28,7 @@\n   export TENSORFLOW_TARGET=\"armhf\"\n fi\n PYTHON_INCLUDE=$(${PYTHON} -c \"from sysconfig import get_paths as gp; print(gp()['include'])\")\n-PYBIND11_INCLUDE=$(${PYTHON} -c \"import pybind11; print (pybind11.get_include())\")\n+# PYBIND11_INCLUDE=$(${PYTHON} -c \"import pybind11; print (pybind11.get_include())\")\n export CROSSTOOL_PYTHON_INCLUDE_PATH=${PYTHON_INCLUDE}\n \n # Fix container image for cross build.\n@@ -58,7 +58,7 @@\n    \"${TENSORFLOW_LITE_DIR}/python/metrics/metrics_portable.py\" \\\n    \"${BUILD_DIR}/tflite_runtime\"\n echo \"__version__ = '${PACKAGE_VERSION}'\" >> \"${BUILD_DIR}/tflite_runtime/__init__.py\"\n-echo \"__git_version__ = '$(git -C \"${TENSORFLOW_DIR}\" describe)'\" >> \"${BUILD_DIR}/tflite_runtime/__init__.py\"\n+echo \"__git_version__ = '${PACKAGE_VERSION}'\" >> \"${BUILD_DIR}/tflite_runtime/__init__.py\"\n \n # Build python interpreter_wrapper.\n mkdir -p \"${BUILD_DIR}/cmake_build\"\n@@ -111,6 +111,18 @@\n       -DCMAKE_CXX_FLAGS=\"${BUILD_FLAGS}\" \\\n       \"${TENSORFLOW_LITE_DIR}\"\n     ;;\n+  android)\n+    BUILD_FLAGS=${BUILD_FLAGS:-\"${WRAPPER_INCLUDES}\"}\n+    cmake \\\n+      -DCMAKE_SYSTEM_NAME=Android \\\n+      -DANDROID_ARM_NEON=ON \\\n+      -DCMAKE_CXX_FLAGS=\"${BUILD_FLAGS}\" \\\n+      -DCMAKE_SHARED_LINKER_FLAGS=\"${CMAKE_SHARED_LINKER_FLAGS}\" \\\n+      -DCMAKE_TOOLCHAIN_FILE=\"${CMAKE_TOOLCHAIN_FILE}\" \\\n+      -DANDROID_PLATFORM=\"${ANDROID_PLATFORM}\" \\\n+      -DANDROID_ABI=\"${ANDROID_ABI}\" \\\n+      \"${TENSORFLOW_LITE_DIR}\" \n+    ;;\n   *)\n     BUILD_FLAGS=${BUILD_FLAGS:-\"-I${PYTHON_INCLUDE} -I${PYBIND11_INCLUDE}\"}\n     cmake \\\n@@ -162,7 +174,7 @@\n       ${PYTHON} setup.py bdist --plat-name=${WHEEL_PLATFORM_NAME} \\\n                          bdist_wheel --plat-name=${WHEEL_PLATFORM_NAME}\n     else\n-      ${PYTHON} setup.py bdist bdist_wheel\n+      ${PYTHON} setup.py bdist\n     fi\n     ;;\n esac\n"
  },
  {
    "path": "pythonforandroid/recipes/tiktoken/__init__.py",
    "content": "from pythonforandroid.recipe import RustCompiledComponentsRecipe\n\n\nclass TiktokenRecipe(RustCompiledComponentsRecipe):\n    name = 'tiktoken'\n    version = '0.7.0'\n    url = 'https://github.com/openai/tiktoken/archive/refs/tags/{version}.tar.gz'\n    sha512sum = \"bb2d8fd5acd660d60e690769e46cf29b06361343ea30e35613d27d55f44acf9834e51aef28f4ff316ef66f2130042079718cea04b2353301aef334cd7bd6d221\"\n    depends = ['regex', 'requests']\n\n\nrecipe = TiktokenRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/twisted/__init__.py",
    "content": "import os\n\nfrom pythonforandroid.recipe import CythonRecipe\nfrom pythonforandroid.util import rmdir\n\n\nclass TwistedRecipe(CythonRecipe):\n    version = '20.3.0'\n    url = 'https://github.com/twisted/twisted/archive/twisted-{version}.tar.gz'\n\n    depends = ['setuptools', 'zope_interface', 'incremental', 'constantly']\n    patches = ['incremental.patch', 'remove_tests.patch']\n\n    call_hostpython_via_targetpython = False\n    install_in_hostpython = False\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        # TODO Need to whitelist tty.pyo and termios.so here\n\n        # remove the unit test dirs\n        source_dir = os.path.join(self.get_build_dir(arch.arch), 'src/twisted')\n        for item in os.walk(source_dir):\n            if os.path.basename(item[0]) == 'test':\n                full_path = os.path.join(source_dir, item[0])\n                rmdir(full_path, ignore_errors=True)\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n        # We add BUILDLIB_PATH to PYTHONPATH so twisted can find _io.so\n        env['PYTHONPATH'] = ':'.join([\n            self.ctx.get_site_packages_dir(arch),\n            env['BUILDLIB_PATH'],\n        ])\n        return env\n\n\nrecipe = TwistedRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/twisted/incremental.patch",
    "content": "diff -Naur twisted-twisted-19.7.0/src/twisted/python/_setup.py twisted-twisted-19.7.0_patched/src/twisted/python/_setup.py\n--- twisted-twisted-19.7.0/src/twisted/python/_setup.py\t2019-07-28 11:17:29.000000000 +0200\n+++ twisted-twisted-19.7.0_patched/src/twisted/python/_setup.py\t2019-10-21 22:10:03.643068863 +0200\n@@ -282,7 +282,6 @@\n     requirements = [\n         \"zope.interface >= 4.4.2\",\n         \"constantly >= 15.1\",\n-        \"incremental >= 16.10.1\",\n         \"Automat >= 0.3.0\",\n         \"hyperlink >= 17.1.1\",\n         \"PyHamcrest >= 1.9.0\",\n@@ -291,8 +290,6 @@\n \n     arguments.update(dict(\n         packages=find_packages(\"src\"),\n-        use_incremental=True,\n-        setup_requires=[\"incremental >= 16.10.1\"],\n         install_requires=requirements,\n         entry_points={\n             'console_scripts': _CONSOLE_SCRIPTS\n"
  },
  {
    "path": "pythonforandroid/recipes/twisted/remove_tests.patch",
    "content": "diff --git a/src/twisted/python/_setup.py b/src/twisted/python/_setup.py\nindex 32cb096c7..a607fef07 100644\n--- a/src/twisted/python/_setup.py\n+++ b/src/twisted/python/_setup.py\n@@ -160,11 +160,6 @@ class ConditionalExtension(Extension, object):\n \n # The C extensions used for Twisted.\n _EXTENSIONS = [\n-    ConditionalExtension(\n-        \"twisted.test.raiser\",\n-        sources=[\"src/twisted/test/raiser.c\"],\n-        condition=lambda _: _isCPython),\n-\n     ConditionalExtension(\n         \"twisted.internet.iocpreactor.iocpsupport\",\n         sources=[\n"
  },
  {
    "path": "pythonforandroid/recipes/ujson/__init__.py",
    "content": "from pythonforandroid.recipe import CompiledComponentsPythonRecipe\n\n\nclass UJsonRecipe(CompiledComponentsPythonRecipe):\n    version = '1.35'\n    url = 'https://pypi.python.org/packages/source/u/ujson/ujson-{version}.tar.gz'\n    depends = []\n\n\nrecipe = UJsonRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/uvloop/__init__.py",
    "content": "from pythonforandroid.recipe import PyProjectRecipe\n\n\nclass UvloopRecipe(PyProjectRecipe):\n    version = 'v0.19.0'\n    url = 'git+https://github.com/MagicStack/uvloop'\n    depends = ['librt', 'libpthread']\n\n    def get_recipe_env(self, arch, **kwargs):\n        env = super().get_recipe_env(arch, **kwargs)\n        env[\"LIBUV_CONFIGURE_HOST\"] = arch.command_prefix\n        env[\"PLATFORM\"] = \"android\"\n        return env\n\n\nrecipe = UvloopRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/vispy/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass VispyRecipe(PythonRecipe):\n    version = '0.4.0'\n    url = 'https://github.com/vispy/vispy/archive/v{version}.tar.gz'\n    depends = ['numpy', 'pysdl2']\n    patches = ['disable_freetype.patch',\n               'disable_font_triage.patch',\n               'use_es2.patch',\n               'remove_ati_check.patch']\n\n\nrecipe = VispyRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/vispy/disable_font_triage.patch",
    "content": "diff --git a/vispy/util/fonts/_triage.py b/vispy/util/fonts/_triage.py\nindex ddbc93d..324c161 100644\n--- a/vispy/util/fonts/_triage.py\n+++ b/vispy/util/fonts/_triage.py\n@@ -9,14 +9,14 @@ import sys\n from ._vispy_fonts import _vispy_fonts\n if sys.platform.startswith('linux'):\n     from ._freetype import _load_glyph\n-    from ...ext.fontconfig import _list_fonts\n-elif sys.platform == 'darwin':\n-    from ._quartz import _load_glyph, _list_fonts\n-elif sys.platform.startswith('win'):\n-    from ._freetype import _load_glyph  # noqa, analysis:ignore\n-    from ._win32 import _list_fonts  # noqa, analysis:ignore\n-else:\n-    raise NotImplementedError('unknown system %s' % sys.platform)\n+    # from ...ext.fontconfig import _list_fonts\n+# elif sys.platform == 'darwin':\n+#     from ._quartz import _load_glyph, _list_fonts\n+# elif sys.platform.startswith('win'):\n+#     from ._freetype import _load_glyph  # noqa, analysis:ignore\n+#     from ._win32 import _list_fonts  # noqa, analysis:ignore\n+# else:\n+#     raise NotImplementedError('unknown system %s' % sys.platform)\n \n _fonts = {}\n \n"
  },
  {
    "path": "pythonforandroid/recipes/vispy/disable_freetype.patch",
    "content": "diff --git a/vispy/util/fonts/_freetype.py b/vispy/util/fonts/_freetype.py\nindex 3b33d0b..229d559 100644\n--- a/vispy/util/fonts/_freetype.py\n+++ b/vispy/util/fonts/_freetype.py\n@@ -12,12 +12,12 @@ import numpy as np\n \n # Convert face to filename\n from ._vispy_fonts import _vispy_fonts, _get_vispy_font_filename\n-if sys.platform.startswith('linux'):\n-    from ...ext.fontconfig import find_font\n-elif sys.platform.startswith('win'):\n-    from ._win32 import find_font  # noqa, analysis:ignore\n-else:\n-    raise NotImplementedError\n+# if sys.platform.startswith('linux'):\n+#     from ...ext.fontconfig import find_font\n+# elif sys.platform.startswith('win'):\n+#     from ._win32 import find_font  # noqa, analysis:ignore\n+# else:\n+#     raise NotImplementedError\n \n _font_dict = {}\n \n@@ -41,6 +41,7 @@ def _load_font(face, bold, italic):\n \n def _load_glyph(f, char, glyphs_dict):\n     \"\"\"Load glyph from font into dict\"\"\"\n+    return\n     from ...ext.freetype import (FT_LOAD_RENDER, FT_LOAD_NO_HINTING,\n                                  FT_LOAD_NO_AUTOHINT)\n     flags = FT_LOAD_RENDER | FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT\n"
  },
  {
    "path": "pythonforandroid/recipes/vispy/disable_freetype.patch_backup",
    "content": "diff --git a/vispy/util/fonts/_freetype.py b/vispy/util/fonts/_freetype.py\nindex 3b33d0b..229d559 100644\n--- a/vispy/util/fonts/_freetype.py\n+++ b/vispy/util/fonts/_freetype.py\n@@ -12,12 +12,12 @@ import numpy as np\n \n # Convert face to filename\n from ._vispy_fonts import _vispy_fonts, _get_vispy_font_filename\n-if sys.platform.startswith('linux'):\n-    from ...ext.fontconfig import find_font\n-elif sys.platform.startswith('win'):\n-    from ._win32 import find_font  # noqa, analysis:ignore\n-else:\n-    raise NotImplementedError\n+# if sys.platform.startswith('linux'):\n+#     from ...ext.fontconfig import find_font\n+# elif sys.platform.startswith('win'):\n+#     from ._win32 import find_font  # noqa, analysis:ignore\n+# else:\n+#     raise NotImplementedError\n \n _font_dict = {}\n \n@@ -41,6 +41,7 @@ def _load_font(face, bold, italic):\n \n def _load_glyph(f, char, glyphs_dict):\n     \"\"\"Load glyph from font into dict\"\"\"\n+    return\n     from ...ext.freetype import (FT_LOAD_RENDER, FT_LOAD_NO_HINTING,\n                                  FT_LOAD_NO_AUTOHINT)\n     flags = FT_LOAD_RENDER | FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT\n"
  },
  {
    "path": "pythonforandroid/recipes/vispy/remove_ati_check.patch",
    "content": "diff --git a/vispy/gloo/glir.py b/vispy/gloo/glir.py\nindex 67419b5..341c13d 100644\n--- a/vispy/gloo/glir.py\n+++ b/vispy/gloo/glir.py\n@@ -878,19 +878,19 @@ class GlirBuffer(GlirObject):\n         self.activate()\n         nbytes = data.nbytes\n         \n-        # Determine whether to check errors to try handling the ATI bug\n-        check_ati_bug = ((not self._bufferSubDataOk) and\n-                         (gl.current_backend is gl.gl2) and\n-                         sys.platform.startswith('win'))\n-\n-        # flush any pending errors\n-        if check_ati_bug:\n-            gl.check_error('periodic check')\n+        # # Determine whether to check errors to try handling the ATI bug\n+        # check_ati_bug = ((not self._bufferSubDataOk) and\n+        #                  (gl.current_backend is gl.gl2) and\n+        #                  sys.platform.startswith('win'))\n+\n+        # # flush any pending errors\n+        # if check_ati_bug:\n+        #     gl.check_error('periodic check')\n         \n         try:\n             gl.glBufferSubData(self._target, offset, data)\n-            if check_ati_bug:\n-                gl.check_error('glBufferSubData')\n+            # if check_ati_bug:\n+            #     gl.check_error('glBufferSubData')\n             self._bufferSubDataOk = True  # glBufferSubData seems to work\n         except Exception:\n             # This might be due to a driver error (seen on ATI), issue #64.\n"
  },
  {
    "path": "pythonforandroid/recipes/vispy/use_es2.patch",
    "content": "diff --git a/vispy/gloo/gl/__init__.py b/vispy/gloo/gl/__init__.py\nindex 93813fa..c41859c 100644\n--- a/vispy/gloo/gl/__init__.py\n+++ b/vispy/gloo/gl/__init__.py\n@@ -210,7 +210,7 @@ def check_error(when='periodic check'):\n \n \n # Load default gl backend\n-from . import gl2 as default_backend  # noqa\n+from . import es2 as default_backend  # noqa\n \n # Call use to start using our default backend\n-use_gl()\n+use_gl('es2')\n"
  },
  {
    "path": "pythonforandroid/recipes/vlc/__init__.py",
    "content": "from pythonforandroid.toolchain import Recipe, current_directory\nfrom pythonforandroid.logger import info, debug, shprint, warning\nfrom os.path import join, isdir, isfile\nfrom os import environ\nimport sh\n\n\nclass VlcRecipe(Recipe):\n    version = '3.0.18'\n    url = None\n    name = 'vlc'\n\n    depends = []\n\n    port_git = 'http://git.videolan.org/git/vlc-ports/android.git'\n#    vlc_git = 'http://git.videolan.org/git/vlc.git'\n    ENV_LIBVLC_AAR = 'LIBVLC_AAR'\n    aars = {}  # for future use of multiple arch\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        build_dir = self.get_build_dir(arch.arch)\n        port_dir = join(build_dir, 'vlc-port-android')\n        if self.ENV_LIBVLC_AAR in environ:\n            aar = environ.get(self.ENV_LIBVLC_AAR)\n            if isdir(aar):\n                aar = join(aar, 'libvlc-{}.aar'.format(self.version))\n            if not isfile(aar):\n                warning(\"Error: {} is not valid libvlc-<ver>.aar bundle\".format(aar))\n                info(\"check {} environment!\".format(self.ENV_LIBVLC_AAR))\n                exit(1)\n            self.aars[arch] = aar\n        else:\n            aar_path = join(port_dir, 'libvlc', 'build', 'outputs', 'aar')\n            self.aars[arch] = aar = join(aar_path, 'libvlc-{}.aar'.format(self.version))\n            warning(\"HINT: set path to precompiled libvlc-<ver>.aar bundle \"\n                    \"in {} environment!\".format(self.ENV_LIBVLC_AAR))\n            info(\"libvlc-<ver>.aar should build \"\n                 \"from sources at {}\".format(port_dir))\n            if not isfile(join(port_dir, 'compile.sh')):\n                info(\"clone vlc port for android sources from {}\".format(\n                            self.port_git))\n                shprint(sh.git, 'clone', self.port_git, port_dir,\n                        _tail=20, _critical=True)\n# now \"git clone ...\" is a part of compile.sh\n#            vlc_dir = join(port_dir, 'vlc')\n#            if not isfile(join(vlc_dir, 'Makefile.am')):\n#                info(\"clone vlc sources from {}\".format(self.vlc_git))\n#                shprint(sh.git, 'clone', self.vlc_git, vlc_dir,\n#                            _tail=20, _critical=True)\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        build_dir = self.get_build_dir(arch.arch)\n        port_dir = join(build_dir, 'vlc-port-android', 'buildsystem')\n        aar = self.aars[arch]\n        if not isfile(aar):\n            with current_directory(port_dir):\n                env = dict(environ)\n                env.update({\n                    'ANDROID_ABI': arch.arch,\n                    'ANDROID_NDK': self.ctx.ndk_dir,\n                    'ANDROID_SDK': self.ctx.sdk_dir,\n                })\n                info(\"compiling vlc from sources\")\n                debug(\"environment: {}\".format(env))\n                if not isfile(join('bin', 'VLC-debug.apk')):\n                    shprint(sh.Command('./compile.sh'), _env=env,\n                            _tail=50, _critical=True)\n                shprint(sh.Command('./compile-medialibrary.sh'), _env=env,\n                        _tail=50, _critical=True)\n        shprint(sh.cp, '-a', aar, self.ctx.aars_dir)\n\n\nrecipe = VlcRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/wsaccel/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\n\n\nclass WSAccellRecipe(CythonRecipe):\n    version = '0.6.2'\n    url = 'https://pypi.python.org/packages/source/w/wsaccel/wsaccel-{version}.tar.gz'\n    depends = []\n    call_hostpython_via_targetpython = False\n\n\nrecipe = WSAccellRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/x3dh/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass X3DHRecipe(PythonRecipe):\n    name = 'x3dh'\n    version = '0.5.3'\n    url = 'https://pypi.python.org/packages/source/X/X3DH/X3DH-{version}.tar.gz'\n    site_packages_name = 'x3dh'\n    depends = [\n        'setuptools',\n        'cryptography',\n        'xeddsa',\n    ]\n    patches = ['requires_fix.patch']\n    call_hostpython_via_targetpython = False\n\n\nrecipe = X3DHRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/x3dh/requires_fix.patch",
    "content": "diff -urN X3DH-0.5.3.ori/setup.py X3DH-0.5.3/setup.py\n--- X3DH-0.5.3.ori/setup.py\t2018-10-28 19:15:16.444766623 +0100\n+++ X3DH-0.5.3/setup.py\t2018-10-28 19:15:38.028060948 +0100\n@@ -24,7 +24,7 @@\n     author_email = \"tim@cifg.io\",\n     license = \"MIT\",\n     packages = find_packages(),\n-    install_requires = [ \"cryptography>=1.7.1\", \"XEdDSA>=0.4.2\" ],\n+    install_requires = [],\n     python_requires  = \">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4\",\n     zip_safe = True,\n     classifiers = [\n"
  },
  {
    "path": "pythonforandroid/recipes/xeddsa/__init__.py",
    "content": "from pythonforandroid.recipe import CythonRecipe\nfrom pythonforandroid.toolchain import current_directory, shprint\nfrom os.path import join\nimport sh\n\n\nclass XedDSARecipe(CythonRecipe):\n    name = 'xeddsa'\n    version = '0.4.4'\n    url = 'https://pypi.python.org/packages/source/X/XEdDSA/XEdDSA-{version}.tar.gz'\n    depends = [\n        'setuptools',\n        'cffi',\n        'pynacl',\n    ]\n    patches = ['remove_dependencies.patch']\n    call_hostpython_via_targetpython = False\n\n    def build_arch(self, arch):\n        with current_directory(join(self.get_build_dir(arch.arch))):\n            env = self.get_recipe_env(arch)\n            hostpython = sh.Command(self.ctx.hostpython)\n            shprint(\n                hostpython, 'ref10/build.py',\n                _env=env\n            )\n            # the library could be `_crypto_sign.cpython-37m-x86_64-linux-gnu.so`\n            # or simply `_crypto_sign.so` depending on the platform/distribution\n            sh.cp('-a', sh.glob('_crypto_sign*.so'), self.ctx.get_site_packages_dir(arch))\n            self.install_python_package(arch)\n\n\nrecipe = XedDSARecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/xeddsa/remove_dependencies.patch",
    "content": "diff -urN XEdDSA-0.4.4.ori/setup.py XEdDSA-0.4.4/setup.py\n--- XEdDSA-0.4.4.ori/setup.py\t2018-09-23 16:08:35.000000000 +0200\n+++ XEdDSA-0.4.4/setup.py\t2018-10-30 08:21:23.338790184 +0100\n@@ -22,9 +22,8 @@\n     author_email = \"tim@cifg.io\",\n     license = \"MIT\",\n     packages = find_packages(),\n-    install_requires = [ \"cffi>=1.9.1\", \"pynacl>=1.0.1\" ],\n-    setup_requires   = [ \"cffi>=1.9.1\" ],\n-    cffi_modules     = [ os.path.join(\"ref10\", \"build.py\") + \":ffibuilder\" ],\n+    install_requires = [\"pynacl>=1.0.1\" ],\n+    setup_requires   = [],\n     python_requires  = \">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4\",\n     include_package_data = True,\n     zip_safe = False,\n"
  },
  {
    "path": "pythonforandroid/recipes/zbar/__init__.py",
    "content": "from os.path import join\nfrom pythonforandroid.recipe import PythonRecipe\n\n\nclass ZBarRecipe(PythonRecipe):\n\n    version = '0.10'\n\n    # For some reason the version 0.10 on PyPI is not the same as the ones\n    # in sourceforge and GitHub. The one in PyPI has a setup.py.\n    # url = 'https://github.com/ZBar/ZBar/archive/{version}.zip'\n    url = 'https://pypi.python.org/packages/e0/5c/' + \\\n        'bd2a96a9f2adacffceb4482cdd56831735ab5a67ea6a60c0a8757c17b62e' + \\\n        '/zbar-{version}.tar.gz'\n\n    call_hostpython_via_targetpython = False\n\n    depends = ['setuptools', 'libzbar']\n\n    patches = [\"zbar-0.10-python-crash.patch\"]\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        libzbar = self.get_recipe('libzbar', self.ctx)\n        libzbar_dir = libzbar.get_build_dir(arch.arch)\n        env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)\n        env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')\n        env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')\n        env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'\n        return env\n\n\nrecipe = ZBarRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/zbar/zbar-0.10-python-crash.patch",
    "content": "https://sourceforge.net/p/zbar/patches/37/\n\nfix from Debian for crashes when importing the python module.\nhttp://bugs.debian.org/cgi-bin/bugreport.cgi?bug=702499\n\nthis doesn't happen on some arches as the data naturally ends up with zero\ndata after the structure, but on some (like arm), it isn't so we crash when\npython walks the list.\n\n--- a/imagescanner.c\n+++ b/imagescanner.c\n@@ -68,6 +68,7 @@ imagescanner_get_results (zbarImageScanner *self,\n \n static PyGetSetDef imagescanner_getset[] = {\n     { \"results\", (getter)imagescanner_get_results, },\n+    { NULL },\n };\n \n static PyObject*\n"
  },
  {
    "path": "pythonforandroid/recipes/zbarlight/__init__.py",
    "content": "from os.path import join\nfrom pythonforandroid.recipe import PythonRecipe\n\n\nclass ZBarLightRecipe(PythonRecipe):\n\n    version = '2.1'\n\n    url = 'https://github.com/Polyconseil/zbarlight/archive/{version}.tar.gz'  # noqa\n\n    call_hostpython_via_targetpython = False\n\n    depends = ['setuptools', 'libzbar']\n\n    def get_recipe_env(self, arch=None, with_flags_in_cc=True):\n        env = super().get_recipe_env(arch, with_flags_in_cc)\n        libzbar = self.get_recipe('libzbar', self.ctx)\n        libzbar_dir = libzbar.get_build_dir(arch.arch)\n        env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)\n        env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')\n        env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')\n        env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'\n        return env\n\n\nrecipe = ZBarLightRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/zeroconf/__init__.py",
    "content": "from pythonforandroid.recipe import PythonRecipe\n\n\nclass ZeroconfRecipe(PythonRecipe):\n    name = 'zeroconf'\n    version = '0.24.5'\n    url = 'https://pypi.python.org/packages/source/z/zeroconf/zeroconf-{version}.tar.gz'\n    depends = ['setuptools', 'ifaddr', 'typing;python_version<\"3.5\"']\n    call_hostpython_via_targetpython = False\n\n\nrecipe = ZeroconfRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/zeroconf/patches/setup.patch",
    "content": "--- zeroconf.orig/setup.py\t2015-07-11 21:55:09.000000000 +0200\n+++ zeroconf/setup.py\t2017-02-23 01:04:13.370018716 +0100\n@@ -55,12 +55,5 @@\n         'mDNS',\n     ],\n     install_requires=[\n-        'enum-compat',\n-        # netifaces 0.10.5 has a bug that results in all interfaces' netmasks\n-        # to be 255.255.255.255 on Windows which breaks things. See:\n-        # * https://github.com/jstasiak/python-zeroconf/issues/84\n-        # * https://bitbucket.org/al45tair/netifaces/issues/39/netmask-is-always-255255255255\n-        'netifaces<=0.10.4',\n-        'six',\n     ],\n )\n"
  },
  {
    "path": "pythonforandroid/recipes/zope/__init__.py",
    "content": "\nfrom pythonforandroid.recipe import PythonRecipe\nfrom os.path import join\n\n\nclass ZopeRecipe(PythonRecipe):\n    name = 'zope'\n    version = '4.1.3'\n    url = 'https://pypi.python.org/packages/source/z/zope.interface/zope.interface-{version}.tar.gz'\n\n    depends = []\n\n    def get_recipe_env(self, arch):\n        env = super().get_recipe_env(arch)\n\n        # These are in the old zope recipe but seem like they shouldn't actually be necessary\n        env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(\n            self.ctx.get_libs_dir(arch.arch))\n        env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')\n        return env\n\n    def postbuild_arch(self, arch):\n        super().postbuild_arch(arch)\n\n        # Should do some deleting here\n\n\nrecipe = ZopeRecipe()\n\n# FIXME: @mirko liblink & LD\n"
  },
  {
    "path": "pythonforandroid/recipes/zope_interface/__init__.py",
    "content": "from os.path import join\n\nfrom pythonforandroid.recipe import PythonRecipe\nfrom pythonforandroid.toolchain import current_directory\nfrom pythonforandroid.util import rmdir\n\n\nclass ZopeInterfaceRecipe(PythonRecipe):\n    call_hostpython_via_targetpython = False\n    name = 'zope_interface'\n    version = '4.1.3'\n    url = 'https://pypi.python.org/packages/source/z/zope.interface/zope.interface-{version}.tar.gz'\n    site_packages_name = 'zope.interface'\n    depends = ['setuptools']\n    patches = ['no_tests.patch']\n\n    def build_arch(self, arch):\n        super().build_arch(arch)\n        # The zope.interface module lacks of the __init__.py file in one of his\n        # folders (once is installed), that leads into an ImportError.\n        # Here we intentionally apply a patch to solve that, so, in case that\n        # this is solved in the future an error will be triggered\n        zope_install = join(self.ctx.get_site_packages_dir(arch), 'zope')\n        self.apply_patch('fix-init.patch', arch.arch, build_dir=zope_install)\n\n    def prebuild_arch(self, arch):\n        super().prebuild_arch(arch)\n        with current_directory(self.get_build_dir(arch.arch)):\n            rmdir('src/zope/interface/tests')\n            rmdir('src/zope/interface/common/tests')\n\n\nrecipe = ZopeInterfaceRecipe()\n"
  },
  {
    "path": "pythonforandroid/recipes/zope_interface/fix-init.patch",
    "content": "The zope.interface module lacks of the __init__.py file in `zope` folder\n(once is installed), this patch creates that missing file. This seems to be\ncaused during the installation process because that file exists in source\nfiles.\ndiff -Naurp zope.orig/__init__.py zope/__init__.py\n--- zope.orig/__init__.py\t1970-01-01 01:00:00.000000000 +0100\n+++ zope/__init__.py\t2019-02-05 11:29:22.666757227 +0100\n@@ -0,0 +1 @@\n+ \n"
  },
  {
    "path": "pythonforandroid/recipes/zope_interface/no_tests.patch",
    "content": "--- zope_interface/setup.py\t2015-10-05 09:35:14.000000000 +0200\n+++ b/setup.py\t2016-06-15 17:44:35.108263993 +0200\n@@ -139,9 +139,8 @@\n         \"Topic :: Software Development :: Libraries :: Python Modules\",\n       ],\n \n-      packages = ['zope', 'zope.interface', 'zope.interface.tests'],\n+      packages = ['zope', 'zope.interface'],\n       package_dir = {'': 'src'},\n       cmdclass = {'build_ext': optional_build_ext,\n                   },\n-      test_suite = 'zope.interface.tests',\n       **extra)\n"
  },
  {
    "path": "pythonforandroid/recommendations.py",
    "content": "\"\"\"Simple functions for checking dependency versions.\"\"\"\n\nimport sys\nfrom os.path import join\n\nimport packaging.version\n\nfrom pythonforandroid.logger import info, warning\nfrom pythonforandroid.util import BuildInterruptingException\n\n# We only check the NDK major version\nMIN_NDK_VERSION = 25\nMAX_NDK_VERSION = 25\n\n# DO NOT CHANGE LINE FORMAT: buildozer parses the existence of a RECOMMENDED_NDK_VERSION\nRECOMMENDED_NDK_VERSION = \"28c\"\n\nNDK_DOWNLOAD_URL = \"https://developer.android.com/ndk/downloads/\"\n\n# Important log messages\nNEW_NDK_MESSAGE = 'Newer NDKs may not be fully supported by p4a.'\nUNKNOWN_NDK_MESSAGE = (\n    'Could not determine NDK version, no source.properties in the NDK dir.'\n)\nPARSE_ERROR_NDK_MESSAGE = (\n    'Could not parse $NDK_DIR/source.properties, not checking NDK version.'\n)\nREAD_ERROR_NDK_MESSAGE = (\n    'Unable to read the NDK version from the given directory {ndk_dir}.'\n)\nENSURE_RIGHT_NDK_MESSAGE = (\n    'Make sure your NDK version is greater than {min_supported}. If you get '\n    'build errors, download the recommended NDK {rec_version} from {ndk_url}.'\n)\nNDK_LOWER_THAN_SUPPORTED_MESSAGE = (\n    'The minimum supported NDK version is {min_supported}. '\n    'You can download it from {ndk_url}.'\n)\nUNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE = (\n    'Asked to build for armeabi architecture with API '\n    '{req_ndk_api}, but API {max_ndk_api} or greater does not support armeabi.'\n)\nCURRENT_NDK_VERSION_MESSAGE = (\n    'Found NDK version {ndk_version}'\n)\nRECOMMENDED_NDK_VERSION_MESSAGE = (\n    'Maximum recommended NDK version is {recommended_ndk_version}, but newer versions may work.'\n)\n\n\ndef check_ndk_version(ndk_dir):\n    \"\"\"\n    Check the NDK version against what is currently recommended and raise an\n    exception of :class:`~pythonforandroid.util.BuildInterruptingException` in\n    case that the user tries to use an NDK lower than minimum supported,\n    specified via attribute `MIN_NDK_VERSION`.\n\n    .. versionchanged:: 2019.06.06.1.dev0\n        Added the ability to get android's NDK `letter version` and also\n        rewrote to raise an exception in case that an NDK version lower than\n        the minimum supported is detected.\n    \"\"\"\n    ndk_version = read_ndk_version(ndk_dir)\n\n    if ndk_version is None:\n        warning(READ_ERROR_NDK_MESSAGE.format(ndk_dir=ndk_dir))\n        warning(\n            ENSURE_RIGHT_NDK_MESSAGE.format(\n                min_supported=MIN_NDK_VERSION,\n                rec_version=RECOMMENDED_NDK_VERSION,\n                ndk_url=NDK_DOWNLOAD_URL,\n            )\n        )\n        return\n\n    # create a dictionary which will describe the relationship of the android's\n    # NDK minor version with the `human readable` letter version, egs:\n    # Pkg.Revision = 17.1.4828580 => ndk-17b\n    # Pkg.Revision = 17.2.4988734 => ndk-17c\n    # Pkg.Revision = 19.0.5232133 => ndk-19 (No letter)\n    minor_to_letter = {0: ''}\n    minor_to_letter.update(\n        {n + 1: chr(i) for n, i in enumerate(range(ord('b'), ord('b') + 25))}\n    )\n    string_version = f\"{ndk_version.major}{minor_to_letter[ndk_version.minor]}\"\n\n    info(CURRENT_NDK_VERSION_MESSAGE.format(ndk_version=string_version))\n\n    if ndk_version.major < MIN_NDK_VERSION:\n        raise BuildInterruptingException(\n            NDK_LOWER_THAN_SUPPORTED_MESSAGE.format(\n                min_supported=MIN_NDK_VERSION, ndk_url=NDK_DOWNLOAD_URL\n            ),\n            instructions=(\n                'Please, go to the android NDK page ({ndk_url}) and download a'\n                ' supported version.\\n*** The currently recommended NDK'\n                ' version is {rec_version} ***'.format(\n                    ndk_url=NDK_DOWNLOAD_URL,\n                    rec_version=RECOMMENDED_NDK_VERSION,\n                )\n            ),\n        )\n    elif ndk_version.major > MAX_NDK_VERSION:\n        warning(\n            RECOMMENDED_NDK_VERSION_MESSAGE.format(\n                recommended_ndk_version=RECOMMENDED_NDK_VERSION\n            )\n        )\n        warning(NEW_NDK_MESSAGE)\n\n\ndef read_ndk_version(ndk_dir):\n    \"\"\"Read the NDK version from the NDK dir, if possible\"\"\"\n    try:\n        with open(join(ndk_dir, 'source.properties')) as fileh:\n            ndk_data = fileh.read()\n    except IOError:\n        info(UNKNOWN_NDK_MESSAGE)\n        return\n\n    for line in ndk_data.split('\\n'):\n        if line.startswith('Pkg.Revision'):\n            break\n    else:\n        info(PARSE_ERROR_NDK_MESSAGE)\n        return\n\n    # Line should have the form \"Pkg.Revision = ...\"\n    unparsed_ndk_version = line.split('=')[-1].strip()\n\n    return packaging.version.parse(unparsed_ndk_version)\n\n\nMIN_TARGET_API = 30\n\n# highest version tested to work fine with SDL2\n# should be a good default for other bootstraps too\nRECOMMENDED_TARGET_API = 33\n\nARMEABI_MAX_TARGET_API = 21\nOLD_API_MESSAGE = (\n    'Target APIs lower than 30 are no longer supported on Google Play, '\n    'and are not recommended. Note that the Target API can be higher than '\n    'your device Android version, and should usually be as high as possible.')\n\n\ndef check_target_api(api, arch):\n    \"\"\"Warn if the user's target API is less than the current minimum\n    recommendation\n    \"\"\"\n\n    # FIXME: Should We remove support for armeabi (ARMv5)?\n    if api >= ARMEABI_MAX_TARGET_API and arch == 'armeabi':\n        raise BuildInterruptingException(\n            UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE.format(\n                req_ndk_api=api, max_ndk_api=ARMEABI_MAX_TARGET_API\n            ),\n            instructions='You probably want to build with --arch=armeabi-v7a instead')\n\n    if api < MIN_TARGET_API:\n        warning('Target API {} < {}'.format(api, MIN_TARGET_API))\n        warning(OLD_API_MESSAGE)\n\n\nMIN_NDK_API = 21\nRECOMMENDED_NDK_API = 24\nOLD_NDK_API_MESSAGE = ('NDK API less than {} is not supported'.format(MIN_NDK_API))\nTARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE = (\n    'Target NDK API is {ndk_api}, '\n    'higher than the target Android API {android_api}.'\n)\n\n\ndef check_ndk_api(ndk_api, android_api):\n    \"\"\"Warn if the user's NDK is too high or low.\"\"\"\n    if ndk_api > android_api:\n        raise BuildInterruptingException(\n            TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE.format(\n                ndk_api=ndk_api, android_api=android_api\n            ),\n            instructions=('The NDK API is a minimum supported API number and must be lower '\n                          'than the target Android API'))\n\n    if ndk_api < MIN_NDK_API:\n        warning(OLD_NDK_API_MESSAGE)\n\n\nMIN_PYTHON_MAJOR_VERSION = 3\nMIN_PYTHON_MINOR_VERSION = 6\nMIN_PYTHON_VERSION = packaging.version.Version(\n    f\"{MIN_PYTHON_MAJOR_VERSION}.{MIN_PYTHON_MINOR_VERSION}\"\n)\nPY2_ERROR_TEXT = (\n    'python-for-android no longer supports running under Python 2. Either upgrade to '\n    'Python {min_version} or higher (recommended), or revert to python-for-android 2019.07.08.'\n).format(min_version=MIN_PYTHON_VERSION)\n\nPY_VERSION_ERROR_TEXT = (\n    'Your Python version {user_major}.{user_minor} is not supported by python-for-android, '\n    'please upgrade to {min_version} or higher.'\n    ).format(\n        user_major=sys.version_info.major,\n        user_minor=sys.version_info.minor,\n        min_version=MIN_PYTHON_VERSION)\n\n\ndef check_python_version():\n    # Python 2 special cased because it's a major transition. In the\n    # future the major or minor versions can increment more quietly.\n    if sys.version_info.major == 2:\n        raise BuildInterruptingException(PY2_ERROR_TEXT)\n\n    if (\n        sys.version_info.major < MIN_PYTHON_MAJOR_VERSION or\n        sys.version_info.minor < MIN_PYTHON_MINOR_VERSION\n    ):\n\n        raise BuildInterruptingException(PY_VERSION_ERROR_TEXT)\n\n\ndef print_recommendations():\n    \"\"\"\n    Print the main recommended dependency versions as simple key-value pairs.\n    \"\"\"\n    print('Min supported NDK version: {}'.format(MIN_NDK_VERSION))\n    print('Recommended NDK version: {}'.format(RECOMMENDED_NDK_VERSION))\n    print('Min target API: {}'.format(MIN_TARGET_API))\n    print('Recommended target API: {}'.format(RECOMMENDED_TARGET_API))\n    print('Min NDK API: {}'.format(MIN_NDK_API))\n    print('Recommended NDK API: {}'.format(RECOMMENDED_NDK_API))\n"
  },
  {
    "path": "pythonforandroid/toolchain.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nTool for packaging Python apps for Android\n==========================================\n\nThis module defines the entry point for command line and programmatic use.\n\"\"\"\n\nfrom appdirs import user_data_dir\nimport argparse\nfrom functools import wraps\nimport glob\nimport logging\nimport os\nfrom os import environ\nfrom os.path import (join, dirname, realpath, exists, expanduser, basename)\nimport re\nimport shlex\nimport sys\nfrom sys import platform\n\n# This must be imported and run before other third-party or p4a\n# packages.\nfrom pythonforandroid.checkdependencies import check\ncheck()\n\nfrom packaging.version import Version\nimport sh\n\nfrom pythonforandroid import __version__\nfrom pythonforandroid.bootstrap import Bootstrap\nfrom pythonforandroid.build import Context, build_recipes, project_has_setup_py\nfrom pythonforandroid.distribution import Distribution, pretty_log_dists\nfrom pythonforandroid.entrypoints import main\nfrom pythonforandroid.graph import get_recipe_order_and_bootstrap\nfrom pythonforandroid.logger import (logger, info, warning, setup_color,\n                                     Out_Style, Out_Fore,\n                                     info_notify, info_main, shprint)\nfrom pythonforandroid.pythonpackage import get_dep_names_of_package\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.recommendations import (\n    RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API, print_recommendations)\nfrom pythonforandroid.util import (\n    current_directory,\n    BuildInterruptingException,\n    load_source,\n    rmdir,\n    max_build_tool_version,\n)\n\nuser_dir = dirname(realpath(os.path.curdir))\ntoolchain_dir = dirname(__file__)\nsys.path.insert(0, join(toolchain_dir, \"tools\", \"external\"))\n\n\ndef add_boolean_option(parser, names, no_names=None,\n                       default=True, dest=None, description=None):\n    group = parser.add_argument_group(description=description)\n    if not isinstance(names, (list, tuple)):\n        names = [names]\n    if dest is None:\n        dest = names[0].strip(\"-\").replace(\"-\", \"_\")\n\n    def add_dashes(x):\n        return x if x.startswith(\"-\") else \"--\"+x\n\n    opts = [add_dashes(x) for x in names]\n    group.add_argument(\n        *opts, help=(\"(this is the default)\" if default else None),\n        dest=dest, action='store_true')\n    if no_names is None:\n        def add_no(x):\n            x = x.lstrip(\"-\")\n            return (\"no_\"+x) if \"_\" in x else (\"no-\"+x)\n        no_names = [add_no(x) for x in names]\n    opts = [add_dashes(x) for x in no_names]\n    group.add_argument(\n        *opts, help=(None if default else \"(this is the default)\"),\n        dest=dest, action='store_false')\n    parser.set_defaults(**{dest: default})\n\n\ndef require_prebuilt_dist(func):\n    \"\"\"Decorator for ToolchainCL methods. If present, the method will\n    automatically make sure a dist has been built before continuing\n    or, if no dists are present or can be obtained, will raise an\n    error.\n    \"\"\"\n\n    @wraps(func)\n    def wrapper_func(self, args, **kw):\n        ctx = self.ctx\n        ctx.set_archs(self._archs)\n        ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,\n                                      user_ndk_dir=self.ndk_dir,\n                                      user_android_api=self.android_api,\n                                      user_ndk_api=self.ndk_api)\n        dist = self._dist\n        if dist.needs_build:\n            if dist.folder_exists():  # possible if the dist is being replaced\n                dist.delete()\n            info_notify('No dist exists that meets your requirements, '\n                        'so one will be built.')\n            build_dist_from_args(ctx, dist, args)\n        func(self, args, **kw)\n    return wrapper_func\n\n\ndef dist_from_args(ctx, args):\n    \"\"\"Parses out any distribution-related arguments, and uses them to\n    obtain a Distribution class instance for the build.\n    \"\"\"\n    return Distribution.get_distribution(\n        ctx,\n        name=args.dist_name,\n        recipes=split_argument_list(args.requirements),\n        archs=args.arch,\n        ndk_api=args.ndk_api,\n        force_build=args.force_build,\n        require_perfect_match=args.require_perfect_match,\n        allow_replace_dist=args.allow_replace_dist)\n\n\ndef build_dist_from_args(ctx, dist, args):\n    \"\"\"Parses out any bootstrap related arguments, and uses them to build\n    a dist.\"\"\"\n    bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)\n    blacklist = getattr(args, \"blacklist_requirements\", \"\").split(\",\")\n    if len(blacklist) == 1 and blacklist[0] == \"\":\n        blacklist = []\n    build_order, python_modules, bs = (\n        get_recipe_order_and_bootstrap(\n            ctx, dist.recipes, bs,\n            blacklist=blacklist\n        ))\n    assert set(build_order).intersection(set(python_modules)) == set()\n    ctx.recipe_build_order = build_order\n    ctx.python_modules = python_modules\n\n    info('The selected bootstrap is {}'.format(bs.name))\n    info_main('# Creating dist with {} bootstrap'.format(bs.name))\n    bs.distribution = dist\n    info_notify('Dist will have name {} and requirements ({})'.format(\n        dist.name, ', '.join(dist.recipes)))\n    info('Dist contains the following requirements as recipes: {}'.format(\n        ctx.recipe_build_order))\n    info('Dist will also contain modules ({}) installed from pip'.format(\n        ', '.join(ctx.python_modules)))\n    info(\n        'Dist will be build in mode {build_mode}{with_debug_symbols}'.format(\n            build_mode='debug' if ctx.build_as_debuggable else 'release',\n            with_debug_symbols=' (with debug symbols)'\n            if ctx.with_debug_symbols\n            else '',\n        )\n    )\n\n    ctx.distribution = dist\n    ctx.prepare_bootstrap(bs)\n    if dist.needs_build:\n        ctx.prepare_dist()\n\n    build_recipes(build_order, python_modules, ctx,\n                  getattr(args, \"private\", None),\n                  ignore_project_setup_py=getattr(\n                      args, \"ignore_setup_py\", False\n                  ),\n                 )\n\n    ctx.bootstrap.assemble_distribution()\n\n    info_main('# Your distribution was created successfully, exiting.')\n    info('Dist can be found at (for now) {}'\n         .format(join(ctx.dist_dir, ctx.distribution.dist_dir)))\n\n\ndef split_argument_list(arg_list):\n    if not len(arg_list):\n        return []\n    return re.split(r'[ ,]+', arg_list)\n\n\nclass NoAbbrevParser(argparse.ArgumentParser):\n    \"\"\"We want to disable argument abbreviation so as not to interfere\n    with passing through arguments to build.py, but in python2 argparse\n    doesn't have this option.\n\n    This subclass alternative is follows the suggestion at\n    https://bugs.python.org/issue14910.\n    \"\"\"\n    def _get_option_tuples(self, option_string):\n        return []\n\n\nclass ToolchainCL:\n\n    def __init__(self):\n\n        argv = sys.argv\n        self.warn_on_carriage_return_args(argv)\n        # Buildozer used to pass these arguments in a now-invalid order\n        # If that happens, apply this fix\n        # This fix will be removed once a fixed buildozer is released\n        if (len(argv) > 2\n                and argv[1].startswith('--color')\n                and argv[2].startswith('--storage-dir')):\n            argv.append(argv.pop(1))  # the --color arg\n            argv.append(argv.pop(1))  # the --storage-dir arg\n\n        parser = NoAbbrevParser(\n            description='A packaging tool for turning Python scripts and apps '\n                        'into Android APKs')\n\n        generic_parser = argparse.ArgumentParser(\n            add_help=False,\n            description='Generic arguments applied to all commands')\n        argparse.ArgumentParser(\n            add_help=False, description='Arguments for dist building')\n\n        generic_parser.add_argument(\n            '--debug', dest='debug', action='store_true', default=False,\n            help='Display debug output and all build info')\n        generic_parser.add_argument(\n            '--color', dest='color', choices=['always', 'never', 'auto'],\n            help='Enable or disable color output (default enabled on tty)')\n        generic_parser.add_argument(\n            '--sdk-dir', '--sdk_dir', dest='sdk_dir', default='',\n            help='The filepath where the Android SDK is installed')\n        generic_parser.add_argument(\n            '--ndk-dir', '--ndk_dir', dest='ndk_dir', default='',\n            help='The filepath where the Android NDK is installed')\n        generic_parser.add_argument(\n            '--android-api',\n            '--android_api',\n            dest='android_api',\n            default=0,\n            type=int,\n            help=('The Android API level to build against defaults to {} if '\n                  'not specified.').format(RECOMMENDED_TARGET_API))\n        generic_parser.add_argument(\n            '--ndk-version', '--ndk_version', dest='ndk_version', default=None,\n            help=('DEPRECATED: the NDK version is now found automatically or '\n                  'not at all.'))\n        generic_parser.add_argument(\n            '--ndk-api', type=int, default=None,\n            help=('The Android API level to compile against. This should be your '\n                  '*minimal supported* API, not normally the same as your --android-api. '\n                  'Defaults to min(ANDROID_API, {}) if not specified.').format(RECOMMENDED_NDK_API))\n        generic_parser.add_argument(\n            '--symlink-bootstrap-files', '--ssymlink_bootstrap_files',\n            action='store_true',\n            dest='symlink_bootstrap_files',\n            default=False,\n            help=('If True, symlinks the bootstrap files '\n                  'creation. This is useful for development only, it could also'\n                  ' cause weird problems.'))\n\n        default_storage_dir = user_data_dir('python-for-android')\n        if ' ' in default_storage_dir:\n            default_storage_dir = '~/.python-for-android'\n        generic_parser.add_argument(\n            '--storage-dir', dest='storage_dir', default=default_storage_dir,\n            help=('Primary storage directory for downloads and builds '\n                  '(default: {})'.format(default_storage_dir)))\n\n        generic_parser.add_argument(\n            '--arch', help='The archs to build for.',\n            action='append', default=[])\n\n        # Options for specifying the Distribution\n        generic_parser.add_argument(\n            '--dist-name', '--dist_name',\n            help='The name of the distribution to use or create', default='')\n\n        generic_parser.add_argument(\n            '--requirements',\n            help=('Dependencies of your app, should be recipe names or '\n                  'Python modules. NOT NECESSARY if you are using '\n                  'Python 3 with --use-setup-py'),\n            default='')\n\n        generic_parser.add_argument(\n            '--recipe-blacklist',\n            help=('Blacklist an internal recipe from use. Allows '\n                  'disabling Python 3 core modules to save size'),\n            dest=\"recipe_blacklist\",\n            default='')\n\n        generic_parser.add_argument(\n            '--blacklist-requirements',\n            help=('Blacklist an internal recipe from use. Allows '\n                  'disabling Python 3 core modules to save size'),\n            dest=\"blacklist_requirements\",\n            default='')\n\n        generic_parser.add_argument(\n            '--bootstrap',\n            help='The bootstrap to build with. Leave unset to choose '\n                 'automatically.',\n            default=None)\n\n        generic_parser.add_argument(\n            '--hook',\n            help='Filename to a module that contains python-for-android hooks',\n            default=None)\n\n        add_boolean_option(\n            generic_parser, [\"force-build\"],\n            default=False,\n            description='Whether to force compilation of a new distribution')\n\n        add_boolean_option(\n            generic_parser, [\"require-perfect-match\"],\n            default=False,\n            description=('Whether the dist recipes must perfectly match '\n                         'those requested'))\n\n        add_boolean_option(\n            generic_parser, [\"allow-replace-dist\"],\n            default=True,\n            description='Whether existing dist names can be automatically replaced'\n            )\n\n        generic_parser.add_argument(\n            '--local-recipes', '--local_recipes',\n            dest='local_recipes', default='./p4a-recipes',\n            help='Directory to look for local recipes')\n\n        generic_parser.add_argument(\n            '--activity-class-name',\n            dest='activity_class_name', default='org.kivy.android.PythonActivity',\n            help='The full java class name of the main activity')\n\n        generic_parser.add_argument(\n            '--service-class-name',\n            dest='service_class_name', default='org.kivy.android.PythonService',\n            help='Full java package name of the PythonService class')\n\n        generic_parser.add_argument(\n            '--java-build-tool',\n            dest='java_build_tool', default='auto',\n            choices=['auto', 'ant', 'gradle'],\n            help=('The java build tool to use when packaging the APK, defaults '\n                  'to automatically selecting an appropriate tool.'))\n\n        add_boolean_option(\n            generic_parser, ['copy-libs'],\n            default=False,\n            description='Copy libraries instead of using biglink (Android 4.3+)'\n        )\n\n        self._read_configuration()\n\n        subparsers = parser.add_subparsers(dest='subparser_name',\n                                           help='The command to run')\n\n        def add_parser(subparsers, *args, **kwargs):\n            \"\"\"\n            argparse in python2 doesn't support the aliases option,\n            so we just don't provide the aliases there.\n            \"\"\"\n            if 'aliases' in kwargs and sys.version_info.major < 3:\n                kwargs.pop('aliases')\n            return subparsers.add_parser(*args, **kwargs)\n\n        add_parser(\n            subparsers,\n            'recommendations',\n            parents=[generic_parser],\n            help='List recommended p4a dependencies')\n        parser_recipes = add_parser(\n            subparsers,\n            'recipes',\n            parents=[generic_parser],\n            help='List the available recipes')\n        parser_recipes.add_argument(\n            \"--compact\",\n            action=\"store_true\", default=False,\n            help=\"Produce a compact list suitable for scripting\")\n        add_parser(\n            subparsers, 'bootstraps',\n            help='List the available bootstraps',\n            parents=[generic_parser])\n        add_parser(\n            subparsers, 'clean_all',\n            aliases=['clean-all'],\n            help='Delete all builds, dists and caches',\n            parents=[generic_parser])\n        add_parser(\n            subparsers, 'clean_dists',\n            aliases=['clean-dists'],\n            help='Delete all dists',\n            parents=[generic_parser])\n        add_parser(\n            subparsers, 'clean_bootstrap_builds',\n            aliases=['clean-bootstrap-builds'],\n            help='Delete all bootstrap builds',\n            parents=[generic_parser])\n        add_parser(\n            subparsers, 'clean_builds',\n            aliases=['clean-builds'],\n            help='Delete all builds',\n            parents=[generic_parser])\n\n        parser_clean = add_parser(\n            subparsers, 'clean',\n            help='Delete build components.',\n            parents=[generic_parser])\n        parser_clean.add_argument(\n            'component', nargs='+',\n            help=('The build component(s) to delete. You can pass any '\n                  'number of arguments from \"all\", \"builds\", \"dists\", '\n                  '\"distributions\", \"bootstrap_builds\", \"downloads\".'))\n\n        parser_clean_recipe_build = add_parser(\n            subparsers,\n            'clean_recipe_build', aliases=['clean-recipe-build'],\n            help=('Delete the build components of the given recipe. '\n                  'By default this will also delete built dists'),\n            parents=[generic_parser])\n        parser_clean_recipe_build.add_argument(\n            'recipe', help='The recipe name')\n        parser_clean_recipe_build.add_argument(\n            '--no-clean-dists', default=False,\n            dest='no_clean_dists',\n            action='store_true',\n            help='If passed, do not delete existing dists')\n\n        parser_clean_download_cache = add_parser(\n            subparsers,\n            'clean_download_cache', aliases=['clean-download-cache'],\n            help='Delete cached downloads for requirement builds',\n            parents=[generic_parser])\n        parser_clean_download_cache.add_argument(\n            'recipes',\n            nargs='*',\n            help='The recipes to clean (space-separated). If no recipe name is'\n                  ' provided, the entire cache is cleared.')\n\n        parser_export_dist = add_parser(\n            subparsers,\n            'export_dist', aliases=['export-dist'],\n            help='Copy the named dist to the given path',\n            parents=[generic_parser])\n        parser_export_dist.add_argument('output_dir',\n                                        help='The output dir to copy to')\n        parser_export_dist.add_argument(\n            '--symlink',\n            action='store_true',\n            help='Symlink the dist instead of copying')\n\n        parser_packaging = argparse.ArgumentParser(\n            parents=[generic_parser],\n            add_help=False,\n            description='common options for packaging (apk, aar)')\n\n        # This is actually an internal argument of the build.py\n        # (see pythonforandroid/bootstraps/common/build/build.py).\n        # However, it is also needed before the distribution is finally\n        # assembled for locating the setup.py / other build systems, which\n        # is why we also add it here:\n        parser_packaging.add_argument(\n            '--add-asset', dest='assets',\n            action=\"append\", default=[],\n            help='Put this in the assets folder in the apk.')\n        parser_packaging.add_argument(\n            '--add-resource', dest='resources',\n            action=\"append\", default=[],\n            help='Put this in the res folder in the apk.')\n        parser_packaging.add_argument(\n            '--private', dest='private',\n            help='the directory with the app source code files' +\n                 ' (containing your main.py entrypoint)',\n            required=False, default=None)\n        parser_packaging.add_argument(\n            '--use-setup-py', dest=\"use_setup_py\",\n            action='store_true', default=False,\n            help=\"Process the setup.py of a project if present. \" +\n                 \"(Experimental!\")\n        parser_packaging.add_argument(\n            '--ignore-setup-py', dest=\"ignore_setup_py\",\n            action='store_true', default=False,\n            help=\"Don't run the setup.py of a project if present. \" +\n                 \"This may be required if the setup.py is not \" +\n                 \"designed to work inside p4a (e.g. by installing \" +\n                 \"dependencies that won't work or aren't desired \" +\n                 \"on Android\")\n        parser_packaging.add_argument(\n            '--release', dest='build_mode', action='store_const',\n            const='release', default='debug',\n            help='Build your app as a non-debug release build. '\n                 '(Disables gdb debugging among other things)')\n        parser_packaging.add_argument(\n            '--with-debug-symbols', dest='with_debug_symbols',\n            action='store_const', const=True, default=False,\n            help='Will keep debug symbols from `.so` files.')\n        parser_packaging.add_argument(\n            '--keystore', dest='keystore', action='store', default=None,\n            help=('Keystore for JAR signing key, will use jarsigner '\n                  'default if not specified (release build only)'))\n        parser_packaging.add_argument(\n            '--signkey', dest='signkey', action='store', default=None,\n            help='Key alias to sign PARSER_APK. with (release build only)')\n        parser_packaging.add_argument(\n            '--keystorepw', dest='keystorepw', action='store', default=None,\n            help='Password for keystore')\n        parser_packaging.add_argument(\n            '--signkeypw', dest='signkeypw', action='store', default=None,\n            help='Password for key alias')\n\n        add_parser(\n            subparsers,\n            'aar', help='Build an AAR',\n            parents=[parser_packaging])\n\n        add_parser(\n            subparsers,\n            'apk', help='Build an APK',\n            parents=[parser_packaging])\n\n        add_parser(\n            subparsers,\n            'aab', help='Build an AAB',\n            parents=[parser_packaging])\n\n        add_parser(\n            subparsers,\n            'create', help='Compile a set of requirements into a dist',\n            parents=[generic_parser])\n        add_parser(\n            subparsers,\n            'archs', help='List the available target architectures',\n            parents=[generic_parser])\n        add_parser(\n            subparsers,\n            'distributions', aliases=['dists'],\n            help='List the currently available (compiled) dists',\n            parents=[generic_parser])\n        add_parser(\n            subparsers,\n            'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',\n            parents=[generic_parser])\n\n        parser_sdk_tools = add_parser(\n            subparsers,\n            'sdk_tools', aliases=['sdk-tools'],\n            help='Run the given binary from the SDK tools dis',\n            parents=[generic_parser])\n        parser_sdk_tools.add_argument(\n            'tool', help='The binary tool name to run')\n\n        add_parser(\n            subparsers,\n            'adb', help='Run adb from the given SDK',\n            parents=[generic_parser])\n        add_parser(\n            subparsers,\n            'logcat', help='Run logcat from the given SDK',\n            parents=[generic_parser])\n        add_parser(\n            subparsers,\n            'build_status', aliases=['build-status'],\n            help='Print some debug information about current built components',\n            parents=[generic_parser])\n\n        parser.add_argument('-v', '--version', action='version',\n                            version=__version__)\n\n        args, unknown = parser.parse_known_args(sys.argv[1:])\n        args.unknown_args = unknown\n\n        if getattr(args, \"private\", None) is not None:\n            # Pass this value on to the internal bootstrap build.py:\n            args.unknown_args += [\"--private\", args.private]\n        if getattr(args, \"build_mode\", None) == \"release\":\n            args.unknown_args += [\"--release\"]\n        if getattr(args, \"with_debug_symbols\", False):\n            args.unknown_args += [\"--with-debug-symbols\"]\n        if getattr(args, \"ignore_setup_py\", False):\n            args.use_setup_py = False\n        if getattr(args, \"activity_class_name\", \"org.kivy.android.PythonActivity\") != 'org.kivy.android.PythonActivity':\n            args.unknown_args += [\"--activity-class-name\", args.activity_class_name]\n        if getattr(args, \"service_class_name\", \"org.kivy.android.PythonService\") != 'org.kivy.android.PythonService':\n            args.unknown_args += [\"--service-class-name\", args.service_class_name]\n\n        self.args = args\n\n        if args.subparser_name is None:\n            parser.print_help()\n            exit(1)\n\n        setup_color(args.color)\n\n        if args.debug:\n            logger.setLevel(logging.DEBUG)\n\n        self.ctx = Context()\n        self.ctx.use_setup_py = getattr(args, \"use_setup_py\", True)\n        self.ctx.build_as_debuggable = getattr(\n            args, \"build_mode\", \"debug\"\n        ) == \"debug\"\n        self.ctx.with_debug_symbols = getattr(\n            args, \"with_debug_symbols\", False\n        )\n\n        # Process requirements and put version in environ\n        if hasattr(args, 'requirements'):\n            requirements = []\n\n            # Add dependencies from setup.py, but only if they are recipes\n            # (because otherwise, setup.py itself will install them later)\n            if (project_has_setup_py(getattr(args, \"private\", None)) and\n                    getattr(args, \"use_setup_py\", False)):\n                try:\n                    info(\"Analyzing package dependencies. MAY TAKE A WHILE.\")\n                    # Get all the dependencies corresponding to a recipe:\n                    dependencies = [\n                        dep.lower() for dep in\n                        get_dep_names_of_package(\n                            args.private,\n                            keep_version_pins=True,\n                            recursive=True,\n                            verbose=True,\n                        )\n                    ]\n                    info(\"Dependencies obtained: \" + str(dependencies))\n                    all_recipes = [\n                        recipe.lower() for recipe in\n                        set(Recipe.list_recipes(self.ctx))\n                    ]\n                    dependencies = set(dependencies).intersection(\n                        set(all_recipes)\n                    )\n                    # Add dependencies to argument list:\n                    if len(dependencies) > 0:\n                        if len(args.requirements) > 0:\n                            args.requirements += u\",\"\n                        args.requirements += u\",\".join(dependencies)\n                except ValueError:\n                    # Not a python package, apparently.\n                    warning(\n                        \"Processing failed, is this project a valid \"\n                        \"package? Will continue WITHOUT setup.py deps.\"\n                    )\n\n            # Parse --requirements argument list:\n            for requirement in split_argument_list(args.requirements):\n                if \"==\" in requirement:\n                    requirement, version = requirement.split(u\"==\", 1)\n                    os.environ[\"VERSION_{}\".format(requirement)] = version\n                    info('Recipe {}: version \"{}\" requested'.format(\n                        requirement, version))\n                requirements.append(requirement)\n            args.requirements = u\",\".join(requirements)\n\n        self.warn_on_deprecated_args(args)\n\n        self.storage_dir = args.storage_dir\n        self.ctx.setup_dirs(self.storage_dir)\n        self.sdk_dir = args.sdk_dir\n        self.ndk_dir = args.ndk_dir\n        self.android_api = args.android_api\n        self.ndk_api = args.ndk_api\n        self.ctx.symlink_bootstrap_files = args.symlink_bootstrap_files\n        self.ctx.java_build_tool = args.java_build_tool\n\n        self._archs = args.arch\n\n        self.ctx.local_recipes = realpath(args.local_recipes)\n        self.ctx.copy_libs = args.copy_libs\n\n        self.ctx.activity_class_name = args.activity_class_name\n        self.ctx.service_class_name = args.service_class_name\n\n        # Each subparser corresponds to a method\n        command = args.subparser_name.replace('-', '_')\n        getattr(self, command)(args)\n\n    @staticmethod\n    def warn_on_carriage_return_args(args):\n        for check_arg in args:\n            if '\\r' in check_arg:\n                warning(\"Argument '{}' contains a carriage return (\\\\r).\".format(str(check_arg.replace('\\r', ''))))\n                warning(\"Invoking this program via scripts which use CRLF instead of LF line endings will have undefined behaviour.\")\n\n    def warn_on_deprecated_args(self, args):\n        \"\"\"\n        Print warning messages for any deprecated arguments that were passed.\n        \"\"\"\n\n        # Output warning if setup.py is present and neither --ignore-setup-py\n        # nor --use-setup-py was specified.\n        if project_has_setup_py(getattr(args, \"private\", None)):\n            if not getattr(args, \"use_setup_py\", False) and \\\n                    not getattr(args, \"ignore_setup_py\", False):\n                warning(\"  **** FUTURE BEHAVIOR CHANGE WARNING ****\")\n                warning(\"Your project appears to contain a setup.py file.\")\n                warning(\"Currently, these are ignored by default.\")\n                warning(\"This will CHANGE in an upcoming version!\")\n                warning(\"\")\n                warning(\"To ensure your setup.py is ignored, please specify:\")\n                warning(\"    --ignore-setup-py\")\n                warning(\"\")\n                warning(\"To enable what will some day be the default, specify:\")\n                warning(\"    --use-setup-py\")\n\n        # NDK version is now determined automatically\n        if args.ndk_version is not None:\n            warning('--ndk-version is deprecated and no longer necessary, '\n                    'the value you passed is ignored')\n        if 'ANDROIDNDKVER' in environ:\n            warning('$ANDROIDNDKVER is deprecated and no longer necessary, '\n                    'the value you set is ignored')\n\n    def hook(self, name):\n        if not self.args.hook:\n            return\n        if not hasattr(self, \"hook_module\"):\n            # first time, try to load the hook module\n            self.hook_module = load_source(\n                \"pythonforandroid.hook\", self.args.hook)\n        if hasattr(self.hook_module, name):\n            info(\"Hook: execute {}\".format(name))\n            getattr(self.hook_module, name)(self)\n        else:\n            info(\"Hook: ignore {}\".format(name))\n\n    @property\n    def default_storage_dir(self):\n        udd = user_data_dir('python-for-android')\n        if ' ' in udd:\n            udd = '~/.python-for-android'\n        return udd\n\n    @staticmethod\n    def _read_configuration():\n        # search for a .p4a configuration file in the current directory\n        if not exists(\".p4a\"):\n            return\n        info(\"Reading .p4a configuration\")\n        with open(\".p4a\") as fd:\n            lines = fd.readlines()\n        lines = [shlex.split(line)\n                 for line in lines if not line.startswith(\"#\")]\n        for line in lines:\n            for arg in line:\n                sys.argv.append(arg)\n\n    def recipes(self, args):\n        \"\"\"\n        Prints recipes basic info, e.g.\n        .. code-block:: bash\n\n            python3      3.7.1\n                depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi']\n                conflicts: []\n                optional depends: ['sqlite3', 'libffi', 'openssl']\n        \"\"\"\n        ctx = self.ctx\n        if args.compact:\n            print(\" \".join(set(Recipe.list_recipes(ctx))))\n        else:\n            for name in sorted(Recipe.list_recipes(ctx)):\n                try:\n                    recipe = Recipe.get_recipe(name, ctx)\n                except (IOError, ValueError):\n                    warning('Recipe \"{}\" could not be loaded'.format(name))\n                except SyntaxError:\n                    import traceback\n                    traceback.print_exc()\n                    warning(('Recipe \"{}\" could not be loaded due to a '\n                             'syntax error').format(name))\n                version = str(recipe.version)\n                print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} '\n                      '{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}'\n                      '{version:<8}{Style.RESET_ALL}'.format(\n                            recipe=recipe, Fore=Out_Fore, Style=Out_Style,\n                            version=version))\n                print('    {Fore.GREEN}depends: {recipe.depends}'\n                      '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore))\n                if recipe.conflicts:\n                    print('    {Fore.RED}conflicts: {recipe.conflicts}'\n                          '{Fore.RESET}'\n                          .format(recipe=recipe, Fore=Out_Fore))\n                if recipe.opt_depends:\n                    print('    {Fore.YELLOW}optional depends: '\n                          '{recipe.opt_depends}{Fore.RESET}'\n                          .format(recipe=recipe, Fore=Out_Fore))\n\n    def bootstraps(self, _args):\n        \"\"\"List all the bootstraps available to build with.\"\"\"\n        for bs in Bootstrap.all_bootstraps():\n            bs = Bootstrap.get_bootstrap(bs, self.ctx)\n            print('{Fore.BLUE}{Style.BRIGHT}{bs.name}{Style.RESET_ALL}'\n                  .format(bs=bs, Fore=Out_Fore, Style=Out_Style))\n            print('    {Fore.GREEN}depends: {bs.recipe_depends}{Fore.RESET}'\n                  .format(bs=bs, Fore=Out_Fore))\n\n    def clean(self, args):\n        components = args.component\n\n        component_clean_methods = {\n            'all': self.clean_all,\n            'dists': self.clean_dists,\n            'distributions': self.clean_dists,\n            'builds': self.clean_builds,\n            'bootstrap_builds': self.clean_bootstrap_builds,\n            'downloads': self.clean_download_cache}\n\n        for component in components:\n            if component not in component_clean_methods:\n                raise BuildInterruptingException((\n                    'Asked to clean \"{}\" but this argument is not '\n                    'recognised'.format(component)))\n            component_clean_methods[component](args)\n\n    def clean_all(self, args):\n        \"\"\"Delete all build components; the package cache, package builds,\n        bootstrap builds and distributions.\"\"\"\n        self.clean_dists(args)\n        self.clean_builds(args)\n        self.clean_download_cache(args)\n\n    def clean_dists(self, _args):\n        \"\"\"Delete all compiled distributions in the internal distribution\n        directory.\"\"\"\n        ctx = self.ctx\n        rmdir(ctx.dist_dir)\n\n    def clean_bootstrap_builds(self, _args):\n        \"\"\"Delete all the bootstrap builds.\"\"\"\n        rmdir(join(self.ctx.build_dir, 'bootstrap_builds'))\n        # for bs in Bootstrap.all_bootstraps():\n        #     bs = Bootstrap.get_bootstrap(bs, self.ctx)\n        #     if bs.build_dir and exists(bs.build_dir):\n        #         info('Cleaning build for {} bootstrap.'.format(bs.name))\n        #         rmdir(bs.build_dir)\n\n    def clean_builds(self, _args):\n        \"\"\"Delete all build caches for each recipe, python-install, java code\n        and compiled libs collection.\n\n        This does *not* delete the package download cache or the final\n        distributions.  You can also use clean_recipe_build to delete the build\n        of a specific recipe.\n        \"\"\"\n        ctx = self.ctx\n        rmdir(ctx.build_dir)\n        rmdir(ctx.python_installs_dir)\n        libs_dir = join(self.ctx.build_dir, 'libs_collections')\n        rmdir(libs_dir)\n\n    def clean_recipe_build(self, args):\n        \"\"\"Deletes the build files of the given recipe.\n\n        This is intended for debug purposes. You may experience\n        strange behaviour or problems with some recipes if their\n        build has made unexpected state changes. If this happens, run\n        clean_builds, or attempt to clean other recipes until things\n        work again.\n        \"\"\"\n        recipe = Recipe.get_recipe(args.recipe, self.ctx)\n        info('Cleaning build for {} recipe.'.format(recipe.name))\n        recipe.clean_build()\n        if not args.no_clean_dists:\n            self.clean_dists(args)\n\n    def clean_download_cache(self, args):\n        \"\"\" Deletes a download cache for recipes passed as arguments. If no\n        argument is passed, it'll delete *all* downloaded caches. ::\n\n            p4a clean_download_cache kivy,pyjnius\n\n        This does *not* delete the build caches or final distributions.\n        \"\"\"\n        ctx = self.ctx\n        if hasattr(args, 'recipes') and args.recipes:\n            for package in args.recipes:\n                remove_path = join(ctx.packages_path, package)\n                if exists(remove_path):\n                    rmdir(remove_path)\n                    info('Download cache removed for: \"{}\"'.format(package))\n                else:\n                    warning('No download cache found for \"{}\", skipping'.format(\n                        package))\n        else:\n            if exists(ctx.packages_path):\n                rmdir(ctx.packages_path)\n                info('Download cache removed.')\n            else:\n                print('No cache found at \"{}\"'.format(ctx.packages_path))\n\n    @require_prebuilt_dist\n    def export_dist(self, args):\n        \"\"\"Copies a created dist to an output dir.\n\n        This makes it easy to navigate to the dist to investigate it\n        or call build.py, though you do not in general need to do this\n        and can use the apk command instead.\n        \"\"\"\n        ctx = self.ctx\n        dist = dist_from_args(ctx, args)\n        if dist.needs_build:\n            raise BuildInterruptingException(\n                'You asked to export a dist, but there is no dist '\n                'with suitable recipes available. For now, you must '\n                ' create one first with the create argument.')\n        if args.symlink:\n            shprint(sh.ln, '-s', dist.dist_dir, args.output_dir)\n        else:\n            shprint(sh.cp, '-r', dist.dist_dir, args.output_dir)\n\n    @property\n    def _dist(self):\n        ctx = self.ctx\n        dist = dist_from_args(ctx, self.args)\n        ctx.distribution = dist\n        return dist\n\n    @staticmethod\n    def _fix_args(args):\n        \"\"\"\n        Manually fixing these arguments at the string stage is\n        unsatisfactory and should probably be changed somehow, but\n        we can't leave it until later as the build.py scripts assume\n        they are in the current directory.\n        works in-place\n        :param args: parser args\n        \"\"\"\n\n        fix_args = ('--dir', '--private', '--add-jar', '--add-source',\n                    '--whitelist', '--blacklist', '--presplash', '--icon',\n                    '--icon-bg', '--icon-fg')\n        unknown_args = args.unknown_args\n\n        for asset in args.assets:\n            if \":\" in asset:\n                asset_src, asset_dest = asset.split(\":\")\n            else:\n                asset_src = asset_dest = asset\n            # take abspath now, because build.py will be run in bootstrap dir\n            unknown_args += [\"--asset\", os.path.abspath(asset_src)+\":\"+asset_dest]\n        for resource in args.resources:\n            if \":\" in resource:\n                resource_src, resource_dest = resource.split(\":\")\n            else:\n                resource_src = resource\n                resource_dest = \"\"\n            # take abspath now, because build.py will be run in bootstrap dir\n            unknown_args += [\"--resource\", os.path.abspath(resource_src)+\":\"+resource_dest]\n        for i, arg in enumerate(unknown_args):\n            argx = arg.split('=')\n            if argx[0] in fix_args:\n                if len(argx) > 1:\n                    unknown_args[i] = '='.join(\n                        (argx[0], realpath(expanduser(argx[1]))))\n                elif i + 1 < len(unknown_args):\n                    unknown_args[i+1] = realpath(expanduser(unknown_args[i+1]))\n\n    @staticmethod\n    def _prepare_release_env(args):\n        \"\"\"\n        prepares envitonment dict with the necessary flags for signing an apk\n        :param args: parser args\n        \"\"\"\n        env = os.environ.copy()\n        if args.build_mode == 'release':\n            if args.keystore:\n                env['P4A_RELEASE_KEYSTORE'] = realpath(expanduser(args.keystore))\n            if args.signkey:\n                env['P4A_RELEASE_KEYALIAS'] = args.signkey\n            if args.keystorepw:\n                env['P4A_RELEASE_KEYSTORE_PASSWD'] = args.keystorepw\n            if args.signkeypw:\n                env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.signkeypw\n            elif args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env:\n                env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.keystorepw\n\n        return env\n\n    def _build_package(self, args, package_type):\n        \"\"\"\n        Creates an android package using gradle\n        :param args: parser args\n        :param package_type: one of 'apk', 'aar', 'aab'\n        :return (gradle output, build_args)\n        \"\"\"\n        ctx = self.ctx\n        dist = self._dist\n        bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)\n        ctx.prepare_bootstrap(bs)\n        self._fix_args(args)\n        env = self._prepare_release_env(args)\n\n        with current_directory(dist.dist_dir):\n            self.hook(\"before_apk_build\")\n            os.environ[\"ANDROID_API\"] = str(self.ctx.android_api)\n            build = load_source('build', join(dist.dist_dir, 'build.py'))\n            build_args = build.parse_args_and_make_package(\n                args.unknown_args\n            )\n\n            self.hook(\"after_apk_build\")\n            self.hook(\"before_apk_assemble\")\n            build_tools_versions = os.listdir(join(ctx.sdk_dir,\n                                                   'build-tools'))\n            build_tools_version = max_build_tool_version(build_tools_versions)\n            info(('Detected highest available build tools '\n                  'version to be {}').format(build_tools_version))\n\n            if Version(build_tools_version.replace(\" \", \"\")) < Version('25.0'):\n                raise BuildInterruptingException(\n                    'build_tools >= 25 is required, but %s is installed' % build_tools_version)\n            if not exists(\"gradlew\"):\n                raise BuildInterruptingException(\"gradlew file is missing\")\n\n            env[\"ANDROID_NDK_HOME\"] = self.ctx.ndk_dir\n            env[\"ANDROID_HOME\"] = self.ctx.sdk_dir\n\n            gradlew = sh.Command('./gradlew')\n\n            if exists('/usr/bin/dos2unix'):\n                # .../dists/bdisttest_python3/gradlew\n                # .../build/bootstrap_builds/sdl2-python3/gradlew\n                # if docker on windows, gradle contains CRLF\n                output = shprint(\n                    sh.Command('dos2unix'), gradlew._path,\n                    _tail=20, _critical=True, _env=env\n                )\n            if args.build_mode == \"debug\":\n                if package_type == \"aab\":\n                    raise BuildInterruptingException(\n                        \"aab is meant only for distribution and is not available in debug mode. \"\n                        \"Instead, you can use apk while building for debugging purposes.\"\n                    )\n                gradle_task = \"assembleDebug\"\n            elif args.build_mode == \"release\":\n                if package_type in [\"apk\", \"aar\"]:\n                    gradle_task = \"assembleRelease\"\n                elif package_type == \"aab\":\n                    gradle_task = \"bundleRelease\"\n            else:\n                raise BuildInterruptingException(\n                    \"Unknown build mode {} for apk()\".format(args.build_mode))\n\n            # WARNING: We should make sure to clean the build directory before building.\n            # See PR: kivy/python-for-android#2705\n            output = shprint(gradlew, \"clean\", gradle_task, _tail=20,\n                             _critical=True, _env=env)\n        return output, build_args\n\n    def _finish_package(self, args, output, build_args, package_type, output_dir):\n        \"\"\"\n        Finishes the package after the gradle script run\n        :param args: the parser args\n        :param output: RunningCommand output\n        :param build_args: build args as returned by build.parse_args\n        :param package_type: one of 'apk', 'aar', 'aab'\n        :param output_dir: where to put the package file\n        \"\"\"\n\n        package_glob = \"*-{}.%s\" % package_type\n        package_add_version = True\n\n        self.hook(\"after_apk_assemble\")\n\n        info_main('# Copying android package to current directory')\n\n        package_re = re.compile(r'.*Package: (.*\\.apk)$')\n        package_file = None\n        for line in reversed(output.splitlines()):\n            m = package_re.match(line)\n            if m:\n                package_file = m.groups()[0]\n                break\n        if not package_file:\n            info_main('# Android package filename not found in build output. Guessing...')\n            if args.build_mode == \"release\":\n                suffixes = (\"release\", \"release-unsigned\")\n            else:\n                suffixes = (\"debug\", )\n            for suffix in suffixes:\n\n                package_files = glob.glob(join(output_dir, package_glob.format(suffix)))\n                if package_files:\n                    if len(package_files) > 1:\n                        info('More than one built APK found... guessing you '\n                             'just built {}'.format(package_files[-1]))\n                    package_file = package_files[-1]\n                    break\n            else:\n                raise BuildInterruptingException('Couldn\\'t find the built APK')\n\n        info_main('# Found android package file: {}'.format(package_file))\n        package_extension = f\".{package_type}\"\n        if package_add_version:\n            info('# Add version number to android package')\n            package_name = basename(package_file)[:-len(package_extension)]\n            package_file_dest = \"{}-{}{}\".format(\n                package_name, build_args.version, package_extension)\n            info('# Android package renamed to {}'.format(package_file_dest))\n            shprint(sh.cp, package_file, package_file_dest)\n        else:\n            shprint(sh.cp, package_file, './')\n\n    @require_prebuilt_dist\n    def apk(self, args):\n        output, build_args = self._build_package(args, package_type='apk')\n        output_dir = join(self._dist.dist_dir, \"build\", \"outputs\", 'apk', args.build_mode)\n        self._finish_package(args, output, build_args, 'apk', output_dir)\n\n    @require_prebuilt_dist\n    def aar(self, args):\n        output, build_args = self._build_package(args, package_type='aar')\n        output_dir = join(self._dist.dist_dir, \"build\", \"outputs\", 'aar')\n        self._finish_package(args, output, build_args, 'aar', output_dir)\n\n    @require_prebuilt_dist\n    def aab(self, args):\n        output, build_args = self._build_package(args, package_type='aab')\n        output_dir = join(self._dist.dist_dir, \"build\", \"outputs\", 'bundle', args.build_mode)\n        self._finish_package(args, output, build_args, 'aab', output_dir)\n\n    @require_prebuilt_dist\n    def create(self, args):\n        \"\"\"Create a distribution directory if it doesn't already exist, run\n        any recipes if necessary, and build the apk.\n        \"\"\"\n        pass  # The decorator does everything\n\n    def archs(self, _args):\n        \"\"\"List the target architectures available to be built for.\"\"\"\n        print('{Style.BRIGHT}Available target architectures are:'\n              '{Style.RESET_ALL}'.format(Style=Out_Style))\n        for arch in self.ctx.archs:\n            print('    {}'.format(arch.arch))\n\n    def dists(self, args):\n        \"\"\"The same as :meth:`distributions`.\"\"\"\n        self.distributions(args)\n\n    def distributions(self, _args):\n        \"\"\"Lists all distributions currently available (i.e. that have already\n        been built).\"\"\"\n        ctx = self.ctx\n        dists = Distribution.get_distributions(ctx)\n\n        if dists:\n            print('{Style.BRIGHT}Distributions currently installed are:'\n                  '{Style.RESET_ALL}'.format(Style=Out_Style))\n            pretty_log_dists(dists, print)\n        else:\n            print('{Style.BRIGHT}There are no dists currently built.'\n                  '{Style.RESET_ALL}'.format(Style=Out_Style))\n\n    def delete_dist(self, _args):\n        dist = self._dist\n        if not dist.folder_exists():\n            info('No dist exists that matches your specifications, '\n                 'exiting without deleting.')\n            return\n        dist.delete()\n\n    def sdk_tools(self, args):\n        \"\"\"Runs the android binary from the detected SDK directory, passing\n        all arguments straight to it. This binary is used to install\n        e.g. platform-tools for different API level targets. This is\n        intended as a convenience function if android is not in your\n        $PATH.\n        \"\"\"\n        ctx = self.ctx\n        ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,\n                                      user_ndk_dir=self.ndk_dir,\n                                      user_android_api=self.android_api,\n                                      user_ndk_api=self.ndk_api)\n        android = sh.Command(join(ctx.sdk_dir, 'tools', args.tool))\n        output = android(\n            *args.unknown_args, _iter=True, _out_bufsize=1, _err_to_out=True)\n        for line in output:\n            sys.stdout.write(line)\n            sys.stdout.flush()\n\n    def adb(self, args):\n        \"\"\"Runs the adb binary from the detected SDK directory, passing all\n        arguments straight to it. This is intended as a convenience\n        function if adb is not in your $PATH.\n        \"\"\"\n        self._adb(args.unknown_args)\n\n    def logcat(self, args):\n        \"\"\"Runs ``adb logcat`` using the adb binary from the detected SDK\n        directory. All extra args are passed as arguments to logcat.\"\"\"\n        self._adb(['logcat'] + args.unknown_args)\n\n    def _adb(self, commands):\n        \"\"\"Call the adb executable from the SDK, passing the given commands as\n        arguments.\"\"\"\n        ctx = self.ctx\n        ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,\n                                      user_ndk_dir=self.ndk_dir,\n                                      user_android_api=self.android_api,\n                                      user_ndk_api=self.ndk_api)\n        if platform in ('win32', 'cygwin'):\n            adb = sh.Command(join(ctx.sdk_dir, 'platform-tools', 'adb.exe'))\n        else:\n            adb = sh.Command(join(ctx.sdk_dir, 'platform-tools', 'adb'))\n        info_notify('Starting adb...')\n        output = adb(*commands, _iter=True, _out_bufsize=1, _err_to_out=True)\n        for line in output:\n            sys.stdout.write(line)\n            sys.stdout.flush()\n\n    def recommendations(self, args):\n        print_recommendations()\n\n    def build_status(self, _args):\n        \"\"\"Print the status of the specified build. \"\"\"\n        print('{Style.BRIGHT}Bootstraps whose core components are probably '\n              'already built:{Style.RESET_ALL}'.format(Style=Out_Style))\n\n        bootstrap_dir = join(self.ctx.build_dir, 'bootstrap_builds')\n        if exists(bootstrap_dir):\n            for filen in os.listdir(bootstrap_dir):\n                print('    {Fore.GREEN}{Style.BRIGHT}{filen}{Style.RESET_ALL}'\n                      .format(filen=filen, Fore=Out_Fore, Style=Out_Style))\n\n        print('{Style.BRIGHT}Recipes that are probably already built:'\n              '{Style.RESET_ALL}'.format(Style=Out_Style))\n        other_builds_dir = join(self.ctx.build_dir, 'other_builds')\n        if exists(other_builds_dir):\n            for filen in sorted(os.listdir(other_builds_dir)):\n                name = filen.split('-')[0]\n                dependencies = filen.split('-')[1:]\n                recipe_str = ('    {Style.BRIGHT}{Fore.GREEN}{name}'\n                              '{Style.RESET_ALL}'.format(\n                                  Style=Out_Style, name=name, Fore=Out_Fore))\n                if dependencies:\n                    recipe_str += (\n                        ' ({Fore.BLUE}with ' + ', '.join(dependencies) +\n                        '{Fore.RESET})').format(Fore=Out_Fore)\n                recipe_str += '{Style.RESET_ALL}'.format(Style=Out_Style)\n                print(recipe_str)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "pythonforandroid/tools/biglink",
    "content": "#!/usr/bin/env python\n\nimport os\nimport sys\nimport subprocess\n\nsofiles = []\n\nfor directory in sys.argv[2:]:\n\n    for fn in os.listdir(directory):\n        fn = os.path.join(directory, fn)\n\n        if not fn.endswith(\".so.o\"):\n            continue\n        if not os.path.exists(fn[:-2] + \".libs\"):\n            continue\n\n        sofiles.append(fn[:-2])\n\n# The raw argument list.\nargs = []\n\nfor fn in sofiles:\n    afn = fn + \".o\"\n    libsfn = fn + \".libs\"\n\n    args.append(afn)\n    with open(libsfn) as fd:\n        data = fd.read()\n        args.extend(data.split(\" \"))\n\nunique_args = []\nwhile args:\n    a = args.pop()\n    if a in ('-L', ):\n        continue\n    if a not in unique_args:\n        unique_args.insert(0, a)\nunique_args = [x for x in unique_args if x]\n\nprint('Biglink create %s library' % sys.argv[1])\nprint('Biglink arguments:')\nfor arg in unique_args:\n    print(' %s' % arg)\n\nargs = os.environ['CC'].split() + \\\n    ['-shared', '-O3', '-o', sys.argv[1]] + \\\n    unique_args\n\nsys.exit(subprocess.call(args))\n"
  },
  {
    "path": "pythonforandroid/tools/liblink",
    "content": "#!/usr/bin/env python\n\nimport sys\nimport subprocess\nfrom os import environ\nfrom os.path import basename, join\n\nlibs = [ ]\nobjects = [ ]\noutput = None\n\ncopylibs = environ.get('COPYLIBS', '0') == '1'\n\ni = 1\nwhile i < len(sys.argv):\n    opt = sys.argv[i]\n    i += 1\n\n    if opt == \"-o\":\n        output = sys.argv[i]\n        i += 1\n        continue\n\n    if opt.startswith((\"-l\", \"-L\")):\n        libs.append(opt)\n        continue\n\n    if opt in (\"-r\", \"-pipe\", \"-no-cpp-precomp\"):\n        continue\n\n    if opt in (\"--sysroot\", \"-isysroot\", \"-framework\", \"-undefined\",\n            \"-macosx_version_min\"):\n        i += 1\n        continue\n\n    if opt.startswith(\n            (\"-I\", \"-isystem\", \"-m\", \"-f\", \"-O\", \"-g\", \"-D\", \"-R\")):\n        continue\n\n    if opt.startswith(\"-\"):\n        print(sys.argv)\n        print(\"Unknown option: %s\" % opt)\n        sys.exit(1)\n\n    if not opt.endswith('.o'):\n        continue\n\n    objects.append(opt)\n\n\nprint('liblink path is', str(environ.get('LIBLINK_PATH')))\nabs_output = join(environ.get('LIBLINK_PATH'), basename(output))\n\nif not copylibs:\n    f = open(output, \"w\")\n    f.close()\n\n    output = abs_output\n\n    f = open(output + \".libs\", \"w\")\n    f.write(\" \".join(libs))\n    f.close()\n\n    sys.exit(subprocess.call([\n        environ.get('LD'), '-r',\n        '-o', output + '.o'\n        #, '-arch', environ.get('ARCH')\n        ] + objects))\nelse:\n    with open(abs_output + '.libs', 'w') as f_libs:\n        with open(abs_output + '.libdirs', 'w') as f_libdirs:\n            for l in libs:\n                if l[1] == 'l':\n                    f_libs.write(l[2:])\n                    f_libs.write(' ')\n                else:\n                    f_libdirs.write(l[2:])\n                    f_libdirs.write(' ')\n\n    libargs = ' '.join([\"'%s'\" % arg for arg in sys.argv[1:]])\n    cmd = '%s -shared %s %s' % (environ['CC'], environ['LDFLAGS'], libargs)\n    sys.exit(subprocess.call(cmd, shell=True))\n"
  },
  {
    "path": "pythonforandroid/tools/liblink.sh",
    "content": "#!/bin/sh\n\nPYTHONPATH= python `dirname $0`/liblink \"$@\"\n"
  },
  {
    "path": "pythonforandroid/util.py",
    "content": "import contextlib\nfrom unittest import mock\nfrom fnmatch import fnmatch\nimport logging\nfrom os.path import exists, join\nfrom os import getcwd, chdir, makedirs, walk\nfrom pathlib import Path\nfrom platform import uname\nimport shutil\nfrom tempfile import mkdtemp\n\nimport packaging.version\n\nfrom pythonforandroid.logger import (logger, Err_Fore, error, info)\n\nLOGGER = logging.getLogger(\"p4a.util\")\n\nbuild_platform = \"{system}-{machine}\".format(\n    system=uname().system, machine=uname().machine\n).lower()\n\"\"\"the build platform in the format `system-machine`. We use\nthis string to define the right build system when compiling some recipes or\nto get the right path for clang compiler\"\"\"\n\n\n@contextlib.contextmanager\ndef current_directory(new_dir):\n    cur_dir = getcwd()\n    logger.info(''.join((Err_Fore.CYAN, '-> directory context ', new_dir,\n                         Err_Fore.RESET)))\n    chdir(new_dir)\n    yield\n    logger.info(''.join((Err_Fore.CYAN, '<- directory context ', cur_dir,\n                         Err_Fore.RESET)))\n    chdir(cur_dir)\n\n\n@contextlib.contextmanager\ndef temp_directory():\n    temp_dir = mkdtemp()\n    try:\n        logger.debug(''.join((Err_Fore.CYAN, ' + temp directory used ',\n                              temp_dir, Err_Fore.RESET)))\n        yield temp_dir\n    finally:\n        shutil.rmtree(temp_dir)\n        logger.debug(''.join((Err_Fore.CYAN, ' - temp directory deleted ',\n                              temp_dir, Err_Fore.RESET)))\n\n\ndef walk_valid_filens(base_dir, invalid_dir_names, invalid_file_patterns, excluded_dir_exceptions=None):\n    \"\"\"Recursively walks all the files and directories in ``dirn``,\n    ignoring directories that match any pattern in ``invalid_dirns``\n    and files that patch any pattern in ``invalid_filens``.\n\n    ``invalid_dirns`` and ``invalid_filens`` should both be lists of\n    strings to match. ``invalid_dir_patterns`` expects a list of\n    invalid directory names, while ``invalid_file_patterns`` expects a\n    list of glob patterns compared against the full filepath.\n\n    File and directory paths are evaluated as full paths relative to ``dirn``.\n\n    If ``excluded_dir_exceptions`` is given, any directory path that contains\n    any of those strings will *not* exclude subdirectories matching\n    ``invalid_dir_names``.\n    \"\"\"\n\n    excluded_dir_exceptions = [] if excluded_dir_exceptions is None else excluded_dir_exceptions\n\n    for dirn, subdirs, filens in walk(base_dir):\n        allow_invalid_dirs = any(ex in dirn for ex in excluded_dir_exceptions)\n\n        # Remove invalid subdirs so that they will not be walked\n        if not allow_invalid_dirs:\n            for i in reversed(range(len(subdirs))):\n                subdir = subdirs[i]\n                if subdir in invalid_dir_names:\n                    subdirs.pop(i)\n\n        for filen in filens:\n            for pattern in invalid_file_patterns:\n                if fnmatch(filen, pattern):\n                    break\n            else:\n                yield join(dirn, filen)\n\n\ndef load_source(module, filename):\n    # Python 3.5+\n    import importlib.util\n    if hasattr(importlib.util, 'module_from_spec'):\n        spec = importlib.util.spec_from_file_location(module, filename)\n        mod = importlib.util.module_from_spec(spec)\n        spec.loader.exec_module(mod)\n        return mod\n    else:\n        # Python 3.3 and 3.4:\n        from importlib.machinery import SourceFileLoader\n        return SourceFileLoader(module, filename).load_module()\n\n\nclass BuildInterruptingException(Exception):\n    def __init__(self, message, instructions=None):\n        super().__init__(message, instructions)\n        self.message = message\n        self.instructions = instructions\n\n\ndef handle_build_exception(exception):\n    \"\"\"\n    Handles a raised BuildInterruptingException by printing its error\n    message and associated instructions, if any, then exiting.\n    \"\"\"\n    error('Build failed: {}'.format(exception.message))\n    if exception.instructions is not None:\n        info('Instructions: {}'.format(exception.instructions))\n    exit(1)\n\n\ndef rmdir(dn, ignore_errors=False):\n    if not exists(dn):\n        return\n    LOGGER.debug(\"Remove directory and subdirectory {}\".format(dn))\n    shutil.rmtree(dn, ignore_errors)\n\n\ndef ensure_dir(dn):\n    if exists(dn):\n        return\n    LOGGER.debug(\"Create directory {0}\".format(dn))\n    makedirs(dn)\n\n\ndef move(source, destination):\n    LOGGER.debug(\"Moving {} to {}\".format(source, destination))\n    shutil.move(source, destination)\n\n\ndef touch(filename):\n    Path(filename).touch()\n\n\ndef build_tools_version_sort_key(\n    version_string: str,\n) -> packaging.version.Version:\n    \"\"\"\n    Returns a packaging.version.Version object for comparison purposes.\n    It includes canonicalization of the version string to allow for\n    comparison of versions with spaces in them (historically, RC candidates)\n\n    If the version string is invalid, it returns a version object with\n    version 0, which will be sorted at worst position.\n    \"\"\"\n\n    try:\n        # Historically, Android build release candidates have had\n        # spaces in the version number.\n        return packaging.version.Version(version_string.replace(\" \", \"\"))\n    except packaging.version.InvalidVersion:\n        # Put badly named versions at worst position.\n        return packaging.version.Version(\"0\")\n\n\ndef max_build_tool_version(\n    build_tools_versions: list,\n) -> str:\n    \"\"\"\n    Returns the maximum build tools version from a list of build tools\n    versions. It uses the :meth:`build_tools_version_sort_key` function to\n    canonicalize the version strings and then returns the maximum version.\n    \"\"\"\n\n    return max(build_tools_versions, key=build_tools_version_sort_key)\n\n\ndef patch_wheel_setuptools_logging():\n    \"\"\"\n    When setuptools is not present and the root logger has no handlers,\n    Wheels would configure the root logger with DEBUG level, refs:\n    - https://github.com/pypa/wheel/blob/0.44.0/src/wheel/util.py\n    - https://github.com/pypa/wheel/blob/0.44.0/src/wheel/_setuptools_logging.py\n\n    Both of these conditions are met in our CI, leading to very verbose\n    and unreadable `sh` logs. Patching it prevents that.\n    \"\"\"\n    return mock.patch(\"wheel._setuptools_logging.configure\")\n"
  },
  {
    "path": "setup.py",
    "content": "\nimport glob\nfrom io import open  # for open(..,encoding=...) parameter in python 2\nfrom os import walk\nfrom os.path import join, dirname, sep\nimport re\nfrom setuptools import setup, find_packages\n\n# NOTE: All package data should also be set in MANIFEST.in\n\npackages = find_packages()\n\npackage_data = {'': ['*.tmpl',\n                     '*.patch',\n                     '*.diff', ], }\n\ndata_files = []\n\n\n# must be a single statement since buildozer is currently parsing it, refs:\n# https://github.com/kivy/buildozer/issues/722\ninstall_reqs = [\n    'appdirs', 'colorama>=0.3.3', 'jinja2',\n    'sh>=2, <3.0; sys_platform!=\"win32\"', 'meson', 'ninja',\n    'build', 'toml', 'packaging', 'setuptools', 'wheel~=0.43.0'\n]\n# (build and toml are used by pythonpackage.py)\n\n\n# By specifying every file manually, package_data will be able to\n# include them in binary distributions. Note that we have to add\n# everything as a 'pythonforandroid' rule, using '' apparently doesn't\n# work.\ndef recursively_include(results, directory, patterns):\n    for root, subfolders, files in walk(directory):\n        for fn in files:\n            if not any(\n                    glob.fnmatch.fnmatch(fn, pattern) for pattern in patterns):\n                continue\n            filename = join(root, fn)\n            directory = 'pythonforandroid'\n            if directory not in results:\n                results[directory] = []\n            results[directory].append(join(*filename.split(sep)[1:]))\n\n\nrecursively_include(package_data, 'pythonforandroid/recipes',\n                    ['*.patch', 'Setup*', '*.pyx', '*.py', '*.c', '*.h',\n                     '*.mk', '*.jam', '*.diff', ])\nrecursively_include(package_data, 'pythonforandroid/bootstraps',\n                    [\n                        '*.properties', '*.xml', '*.java', '*.tmpl', '*.txt',\n                        '*.png', '*.mk', '*.c', '*.h', '*.py', '*.sh', '*.jpg',\n                        '*.aidl', '*.gradle', '.gitkeep', 'gradlew*', '*.jar',\n                        '*.patch',\n                    ])\nrecursively_include(package_data, 'pythonforandroid/bootstraps',\n                    ['sdl-config', ])\nrecursively_include(package_data, 'pythonforandroid/bootstraps/webview',\n                    ['*.html', ])\nrecursively_include(package_data, 'pythonforandroid',\n                    ['liblink', 'biglink', 'liblink.sh'])\n\nwith open(join(dirname(__file__), 'README.md'),\n          encoding=\"utf-8\",\n          errors=\"replace\", ) as fileh:\n    long_description = fileh.read()\n\ninit_filen = join(dirname(__file__), 'pythonforandroid', '__init__.py')\nversion = None\ntry:\n    with open(init_filen,\n              encoding=\"utf-8\",\n              errors=\"replace\") as fileh:\n        lines = fileh.readlines()\nexcept IOError:\n    pass\nelse:\n    for line in lines:\n        line = line.strip()\n        if line.startswith('__version__ = '):\n            matches = re.findall(r'[\"\\'].+[\"\\']', line)\n            if matches:\n                version = matches[0].strip(\"'\").strip('\"')\n                break\nif version is None:\n    raise Exception(\n        'Error: version could not be loaded from {}'.format(init_filen))\n\nsetup(name='python-for-android',\n      version=version,\n      description=(\n          'A development tool that packages Python apps into '\n          'binaries that can run on Android devices.'\n      ),\n      long_description=long_description,\n      long_description_content_type='text/markdown',\n      python_requires=\">=3.7.0\",\n      author='Kivy Team and other contributors',\n      author_email='kivy-dev@googlegroups.com',\n      url='https://github.com/kivy/python-for-android',\n      license='MIT',\n      install_requires=install_reqs,\n      entry_points={\n          'console_scripts': [\n              'python-for-android = pythonforandroid.entrypoints:main',\n              'p4a = pythonforandroid.entrypoints:main',\n              ],\n          'distutils.commands': [\n              'apk = pythonforandroid.bdistapk:BdistAPK',\n              'aar = pythonforandroid.bdistapk:BdistAAR',\n              'aab = pythonforandroid.bdistapk:BdistAAB',\n              ],\n          },\n      classifiers=[\n          'Development Status :: 5 - Production/Stable',\n          'Intended Audience :: Developers',\n          'License :: OSI Approved :: MIT License',\n          'Operating System :: Microsoft :: Windows',\n          'Operating System :: OS Independent',\n          'Operating System :: POSIX :: Linux',\n          'Operating System :: MacOS :: MacOS X',\n          'Operating System :: Android',\n          'Programming Language :: C',\n          'Programming Language :: Python :: 3',\n          'Programming Language :: Python :: 3.7',\n          'Programming Language :: Python :: 3.8',\n          'Programming Language :: Python :: 3.9',\n          'Programming Language :: Python :: 3.10',\n          'Programming Language :: Python :: 3.11',\n          'Topic :: Software Development',\n          'Topic :: Utilities',\n          ],\n      packages=packages,\n      package_data=package_data,\n      project_urls={\n          'Documentation': \"https://python-for-android.readthedocs.io\",\n          'Source': \"https://github.com/kivy/python-for-android\",\n          'Bug Reports': \"https://github.com/kivy/python-for-android/issues\",\n      },\n\n      )\n"
  },
  {
    "path": "testapps/on_device_unit_tests/README.rst",
    "content": "On device unit tests\n====================\n\nThis test app runs a set of unit tests, to help confirm that the\npython-for-android build is actually working properly.\n\nAlso it's dynamic, because it will run one app or another depending on the\nsupplied recipes at build time.\n\nIt currently supports three app `modes`:\n  - `kivy app` (with sdl2 bootstrap): if kivy in recipes\n  - `flask app` (with webview bootstrap): if flask in recipes\n  - `no gui`: if neither of above cases is taken\n\nThe main tests are for the recipes built in the apk. Each module (or\nother tool) is at least imported and subject to some basic check.\n\nThis test app can be build via `setup.py` or via buildozer. In both\ncases it will build a basic kivy app with a set of tests defined via the\n`requirements` keyword (specified at build time).\n\nIn case that you build the `test app with no-gui`, the unittests results must\nbe checked via command `adb logcat` or some logging apk (you may need root\npermissions in your device to use such app).\n\nBuilding the app with python-for-android\n========================================\n\nYou can use the provided file `setup.py`. Check our `Makefile\n<https://github.com/kivy/python-for-android/blob/develop/Makefile>`__ to guess\nhow to build the test app, or also you can look at `testing pull requests documentation\n<https://github.com/kivy/python-for-android/blob/develop/doc/source/testing_pull_requests.rst>`__,\nwhich describes some of the methods that you can use to build the test app.\n\nBuilding the app with buildozer\n===============================\n\nThis app can be built using buildozer, which it also serves as a\ntest for::\n\n  $ buildozer android debug\n\nInstall on an Android device::\n\n  $ adb install -r adb install -r bin/p4aunittests-0.1-debug.apk\n    # or\n  $ buildozer android deploy\n\nRun the app and check in logcat that all the tests pass::\n\n  $ adb logcat | grep python  # or look up the adb syntax for this\n"
  },
  {
    "path": "testapps/on_device_unit_tests/buildozer.spec",
    "content": "[app]\n\n# (str) Title of your application\ntitle = p4a unit tests\n\n# (str) Package name\npackage.name = p4aunittests\n\n# (str) Package domain (needed for android/ios packaging)\npackage.domain = org.kivy\n\n# (str) Source code where the main.py live\nsource.dir = test_app\n\n# (list) Source files to include (let empty to include all the files)\nsource.include_exts = py,png,jpg,kv,atlas,html,css,otf,txt\n\n# (list) List of inclusions using pattern matching\n#source.include_patterns = assets/*,images/*.png\n\n# (list) Source files to exclude (let empty to not exclude anything)\n#source.exclude_exts = spec\n\n# (list) List of directory to exclude (let empty to not exclude anything)\n#source.exclude_dirs = tests, bin\n\n# (list) List of exclusions using pattern matching\n#source.exclude_patterns = license,images/*/*.jpg\n\n# (str) Application versioning (method 1)\nversion = 0.1\n\n# (str) Application versioning (method 2)\n# version.regex = __version__ = ['\"](.*)['\"]\n# version.filename = %(source.dir)s/main.py\n\n# (list) Application requirements\n# comma separated e.g. requirements = sqlite3,kivy\nrequirements = python3,kivy,libffi,openssl,numpy,sqlite3\n\n# (str) Custom source folders for requirements\n# Sets custom source for any requirements with recipes\n# requirements.source.kivy = ../../kivy\n\n# (list) Garden requirements\n#garden_requirements =\n\n# (str) Presplash of the application\n#presplash.filename = %(source.dir)s/data/presplash.png\n\n# (str) Icon of the application\n#icon.filename = %(source.dir)s/data/icon.png\n\n# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)\norientation = all\n\n# (list) List of service to declare\n#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY\n\n#\n# OSX Specific\n#\n\n#\n# author = © Copyright Info\n\n# change the major version of python used by the app\nosx.python_version = 3\n\n# Kivy version to use\nosx.kivy_version = 1.9.1\n\n#\n# Android specific\n#\n\n# (bool) Indicate if the application should be fullscreen or not\nfullscreen = 0\n\n# (string) Presplash background color (for new android toolchain)\n# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:\n# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,\n# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,\n# olive, purple, silver, teal.\n#android.presplash_color = #FFFFFF\n\n# (list) Permissions\n#android.permissions = INTERNET\n\n# (int) Target Android API, should be as high as possible.\nandroid.api = 35\n\n# (int) Minimum API your APK will support.\n#android.minapi = 21\n\n# (int) Android SDK version to use\n#android.sdk = 20\n\n# (str) Android NDK version to use\n#android.ndk = 17c\n\n# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.\n#android.ndk_api = 21\n\n# (bool) Use --private data storage (True) or --dir public storage (False)\n#android.private_storage = True\n\n# (str) Android NDK directory (if empty, it will be automatically downloaded.)\n#android.ndk_path =\n\n# (str) Android SDK directory (if empty, it will be automatically downloaded.)\n#android.sdk_path =\n\n# (str) ANT directory (if empty, it will be automatically downloaded.)\n#android.ant_path =\n\n# (bool) If True, then skip trying to update the Android sdk\n# This can be useful to avoid excess Internet downloads or save time\n# when an update is due and you just want to test/build your package\n# android.skip_update = False\n\n# (str) Android entry point, default is ok for Kivy-based app\n#android.entrypoint = org.renpy.android.PythonActivity\n\n# (list) Pattern to whitelist for the whole project\nandroid.whitelist = unittest/*\n\n# (str) Path to a custom whitelist file\n#android.whitelist_src =\n\n# (str) Path to a custom blacklist file\n#android.blacklist_src =\n\n# (list) List of Java .jar files to add to the libs so that pyjnius can access\n# their classes. Don't add jars that you do not need, since extra jars can slow\n# down the build process. Allows wildcards matching, for example:\n# OUYA-ODK/libs/*.jar\n#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar\n\n# (list) List of Java files to add to the android project (can be java or a\n# directory containing the files)\n#android.add_src =\n\n# (list) Android AAR archives to add (currently works only with sdl2_gradle\n# bootstrap)\n#android.add_aars =\n\n# (list) Gradle dependencies to add (currently works only with sdl2_gradle\n# bootstrap)\n#android.gradle_dependencies =\n\n# (list) Java classes to add as activities to the manifest.\n#android.add_activites = com.example.ExampleActivity\n\n# (str) python-for-android branch to use, defaults to master\np4a.branch = develop\n\n# (str) OUYA Console category. Should be one of GAME or APP\n# If you leave this blank, OUYA support will not be enabled\n#android.ouya.category = GAME\n\n# (str) Filename of OUYA Console icon. It must be a 732x412 png image.\n#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png\n\n# (str) XML file to include as an intent filters in <activity> tag\n#android.manifest.intent_filters =\n\n# (str) launchMode to set for the main activity\n#android.manifest.launch_mode = standard\n\n# (list) Android additional libraries to copy into libs/armeabi\n#android.add_libs_armeabi = libs/android/*.so\n#android.add_libs_armeabi_v7a = libs/android-v7/*.so\n#android.add_libs_x86 = libs/android-x86/*.so\n#android.add_libs_mips = libs/android-mips/*.so\n\n# (bool) Indicate whether the screen should stay on\n# Don't forget to add the WAKE_LOCK permission if you set this to True\n#android.wakelock = False\n\n# (list) Android application meta-data to set (key=value format)\n#android.meta_data =\n\n# (list) Android library project to add (will be added in the\n# project.properties automatically.)\n#android.library_references =\n\n# (str) Android logcat filters to use\n#android.logcat_filters = *:S python:D\n\n# (bool) Copy library instead of making a libpymodules.so\n#android.copy_libs = 1\n\n# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86\nandroid.arch = armeabi-v7a\n\n#\n# Python for android (p4a) specific\n#\n\n# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)\n#p4a.source_dir =\n\n# (str) The directory in which python-for-android should look for your own build recipes (if any)\n#p4a.local_recipes =\n\n# (str) Filename to the hook for p4a\n#p4a.hook =\n\n# (str) Bootstrap to use for android builds\n# p4a.bootstrap = sdl2\n\n# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)\n#p4a.port =\n\n\n#\n# iOS specific\n#\n\n# (str) Path to a custom kivy-ios folder\n#ios.kivy_ios_dir = ../kivy-ios\n\n# (str) Name of the certificate to use for signing the debug version\n# Get a list of available identities: buildozer ios list_identities\n#ios.codesign.debug = \"iPhone Developer: <lastname> <firstname> (<hexstring>)\"\n\n# (str) Name of the certificate to use for signing the release version\n#ios.codesign.release = %(ios.codesign.debug)s\n\n\n[buildozer]\n\n# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))\nlog_level = 2\n\n# (int) Display warning if buildozer is run as root (0 = False, 1 = True)\nwarn_on_root = 1\n\n# (str) Path to build artifact storage, absolute or relative to spec file\n# build_dir = ./.buildozer\n\n# (str) Path to build output (i.e. .apk, .ipa) storage\n# bin_dir = ./bin\n\n#    -----------------------------------------------------------------------------\n#    List as sections\n#\n#    You can define all the \"list\" as [section:key].\n#    Each line will be considered as a option to the list.\n#    Let's take [app] / source.exclude_patterns.\n#    Instead of doing:\n#\n#[app]\n#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*\n#\n#    This can be translated into:\n#\n#[app:source.exclude_patterns]\n#license\n#data/audio/*.wav\n#data/images/original/*\n#\n\n\n#    -----------------------------------------------------------------------------\n#    Profiles\n#\n#    You can extend section / key with a profile\n#    For example, you want to deploy a demo version of your application without\n#    HD content. You could first change the title to add \"(demo)\" in the name\n#    and extend the excluded directories to remove the HD content.\n#\n#[app@demo]\n#title = My Application (demo)\n#\n#[app:source.exclude_patterns@demo]\n#images/hd/*\n#\n#    Then, invoke the command line with the \"demo\" profile:\n#\n#buildozer --profile demo android debug\n"
  },
  {
    "path": "testapps/on_device_unit_tests/setup.py",
    "content": "\"\"\"\nThis is the `setup.py` file for the `on device unit test app`.\n\nIn this module we can control how will be built our test app. Depending on\nour requirements we can build an kivy, flask or a non-gui app. We default to an\nkivy app, since the python-for-android project its a sister project of kivy.\n\nThe parameter `requirements` is crucial to determine the unit tests we will\nperform with our app, so we must explicitly name the recipe we want to test\nand, of course, we should have the proper test for the given recipe at\n`tests.test_requirements.py` or nothing will be tested. We control our default\napp requirements via the dictionary `options`. Here you have some examples\nto build the supported app modes::\n\n  - kivy *basic*: `sqlite3,libffi,openssl,pyjnius,kivy,python3,requests,\n    urllib3,chardet,idna`\n  - kivy *images/graphs*: `kivy,python3,numpy,matplotlib,Pillow`\n  - kivy *encryption*: `kivy,python3,cryptography,pycryptodome,scrypt,\n    m2crypto,pysha3`\n  - flask (with webview bootstrap): `sqlite3,libffi,openssl,pyjnius,flask,\n    python3,genericndkbuild`\n\n\n.. note:: just noting that, for the `kivy basic` app, we add the requirements:\n          `sqlite3,libffi,openssl` so this way we will trigger the unit tests\n          that we have for such recipes.\n\n.. tip:: to force `python-for-android` generate an `flask` app without using\n         the kwarg `bootstrap`, we add the recipe `genericndkbuild`, which will\n         trigger the `webview bootstrap` at build time.\n\"\"\"\n\nimport os\nimport sys\n\nfrom setuptools import setup, find_packages\n\n# define a basic test app, which can be override passing the proper args to cli\noptions = {\n    'apk':\n        {\n            'requirements':\n                'sqlite3,libffi,openssl,pyjnius,kivy,python3,requests,urllib3,'\n                'chardet,idna',\n            'android-api': 36,\n            'ndk-api': 24,\n            'dist-name': 'bdist_unit_tests_app',\n            'arch': 'armeabi-v7a',\n            'bootstrap' : 'sdl2',\n            'permissions': ['INTERNET', 'VIBRATE'],\n            'orientation': ['portrait', 'landscape'],\n            'service': 'P4a_test_service:app_service.py',\n        },\n    'aab':\n        {\n            'requirements':\n                'sqlite3,libffi,openssl,pyjnius,kivy,python3,requests,urllib3,'\n                'chardet,idna',\n            'android-api': 36,\n            'ndk-api': 24,\n            'dist-name': 'bdist_unit_tests_app',\n            'arch': 'armeabi-v7a',\n            'bootstrap' : 'sdl2',\n            'permissions': ['INTERNET', 'VIBRATE'],\n            'orientation': ['portrait', 'landscape'],\n            'service': 'P4a_test_service:app_service.py',\n        },\n    'aar':\n        {\n            'requirements' : 'python3',\n            'android-api': 36,\n            'ndk-api': 24,\n            'dist-name': 'bdist_unit_tests_app',\n            'arch': 'arm64-v8a',\n            'bootstrap' : 'service_library',\n            'permissions': ['INTERNET', 'VIBRATE'],\n            'service': 'P4a_test_service:app_service.py',\n        }\n}\n\n# check if we overwrote the default test_app requirements via `cli`\nrequirements = options['apk']['requirements'].rsplit(',')\nfor n, arg in enumerate(sys.argv):\n    if arg == '--requirements':\n        print('found requirements')\n        requirements = sys.argv[n + 1].rsplit(',')\n        break\n\n# remove `orientation` in case that we don't detect a kivy or flask app,\n# since the `service_only` bootstrap does not support such argument\nif not ({'kivy', 'flask'} & set(requirements)):\n    options['apk'].pop('orientation')\n\n# write a file to let the test_app know which requirements we want to test\n# Note: later, when running the app, we will guess if we have the right test.\napp_requirements_txt = os.path.join(\n    os.path.split(__file__)[0],\n    'test_app',\n    'app_requirements.txt',\n)\nwith open(app_requirements_txt, 'w') as requirements_file:\n    for req in requirements:\n        requirements_file.write(f'{req.split(\"==\")[0]}\\n')\n\n# run the install\nsetup(\n    name='unit_tests_app',\n    version='1.1',\n    description='p4a on device unit test app',\n    author='Alexander Taylor, Pol Canelles',\n    author_email='alexanderjohntaylor@gmail.com, canellestudi@gmail.com',\n    packages=find_packages(),\n    options=options,\n    package_data={\n        'test_app': ['*.py', '*.kv', '*.txt'],\n        'test_app/static': ['*.png', '*.css', '*.otf'],\n        'test_app/templates': ['*.html'],\n        'test_app/tests': ['*.py'],\n    }\n)\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/app_flask.py",
    "content": "print('main.py was successfully called')\nprint('this is the new main.py')\n\nimport sys\nprint('python version is: ' + sys.version)\nprint('python path is', sys.path)\n\nimport os\nprint('imported os')\nprint('contents of this dir', os.listdir('./'))\n\nfrom flask import (\n    Flask,\n    render_template,\n    request,\n    Markup\n)\n\nprint('imported flask etc')\n\nfrom constants import RUNNING_ON_ANDROID\nfrom tools import (\n    run_test_suites_into_buffer,\n    get_failed_unittests_from,\n    vibrate_with_pyjnius,\n    get_android_python_activity,\n    set_device_orientation,\n    setup_lifecycle_callbacks,\n)\n\n\napp = Flask(__name__)\nsetup_lifecycle_callbacks()\nservice_running = False\nTESTS_TO_PERFORM = dict()\nNON_ANDROID_DEVICE_MSG = 'Not running from Android device'\n\n\ndef get_html_for_tested_modules(tested_modules, failed_tests):\n    modules_text = ''\n    for n, module in enumerate(sorted(tested_modules)):\n        print(module)\n        base_text = '<label class=\"{color}\">{module}</label>'\n        if TESTS_TO_PERFORM[module] in failed_tests:\n            color = 'text-red'\n        else:\n            color = 'text-green'\n        if n != len(tested_modules) - 1:\n            base_text += ', '\n\n        modules_text += base_text.format(color=color, module=module)\n\n    return Markup(modules_text)\n\n\ndef get_test_service():\n    from jnius import autoclass\n\n    return autoclass('org.test.unit_tests_app.ServiceP4a_test_service')\n\n\ndef start_service():\n    global service_running\n    activity = get_android_python_activity()\n    test_service = get_test_service()\n    test_service.start(activity, 'Some argument')\n    service_running = True\n\n\ndef stop_service():\n    global service_running\n    activity = get_android_python_activity()\n    test_service = get_test_service()\n    test_service.stop(activity)\n    service_running = False\n\n\n@app.route('/')\ndef index():\n    return render_template(\n        'index.html',\n        platform='Android' if RUNNING_ON_ANDROID else 'Desktop',\n        service_running=service_running,\n    )\n\n\n@app.route('/unittests')\ndef unittests():\n    import unittest\n    print('Imported unittest')\n\n    print(\"loading tests...\")\n    suites = unittest.TestLoader().loadTestsFromNames(\n        list(TESTS_TO_PERFORM.values()),\n    )\n\n    print(\"running unittest...\")\n    terminal_output = run_test_suites_into_buffer(suites)\n\n    print(\"unittest result is:\")\n    unittest_error_text = terminal_output.split('\\n')\n    print(terminal_output)\n\n    # get a nice colored `html` output for our tested recipes\n    failed_tests = get_failed_unittests_from(\n        terminal_output, TESTS_TO_PERFORM.values(),\n    )\n    colored_tested_recipes = get_html_for_tested_modules(\n        TESTS_TO_PERFORM.keys(), failed_tests,\n    )\n\n    return render_template(\n        'unittests.html',\n        tested_recipes=colored_tested_recipes,\n        unittests_output=unittest_error_text,\n        platform='Android' if RUNNING_ON_ANDROID else 'Desktop',\n    )\n\n\n@app.route('/page2')\ndef page2():\n    return render_template(\n        'page2.html',\n        platform='Android' if RUNNING_ON_ANDROID else 'Desktop',\n    )\n\n\n@app.route('/loadUrl')\ndef loadUrl():\n    if not RUNNING_ON_ANDROID:\n        print(NON_ANDROID_DEVICE_MSG, '...cancelled loadUrl.')\n        return NON_ANDROID_DEVICE_MSG\n    args = request.args\n    if 'url' not in args:\n        print('ERROR: asked to open an url but without url argument')\n    print('asked to open url', args['url'])\n    activity = get_android_python_activity()\n    activity.loadUrl(args['url'])\n    return ('', 204)\n\n\n@app.route('/vibrate')\ndef vibrate():\n    if not RUNNING_ON_ANDROID:\n        print(NON_ANDROID_DEVICE_MSG, '...cancelled vibrate.')\n        return NON_ANDROID_DEVICE_MSG\n\n    args = request.args\n    if 'time' not in args:\n        print('ERROR: asked to vibrate but without time argument')\n    print('asked to vibrate', args['time'])\n    vibrate_with_pyjnius(int(float(args['time']) * 1000))\n    return ('', 204)\n\n\n@app.route('/orientation')\ndef orientation():\n    if not RUNNING_ON_ANDROID:\n        print(NON_ANDROID_DEVICE_MSG, '...cancelled orientation.')\n        return NON_ANDROID_DEVICE_MSG\n    args = request.args\n    if 'dir' not in args:\n        print('ERROR: asked to orient but no dir specified')\n        return 'No direction specified '\n    direction = args['dir']\n    set_device_orientation(direction)\n    return ('', 204)\n\n\n@app.route('/service')\ndef service():\n    if not RUNNING_ON_ANDROID:\n        print(NON_ANDROID_DEVICE_MSG, '...cancelled service.')\n        return (NON_ANDROID_DEVICE_MSG, 400)\n    args = request.args\n    if 'action' not in args:\n        print('ERROR: asked to manage service but no action specified')\n        return ('No action specified', 400)\n\n    action = args['action']\n    if action == 'start':\n        start_service()\n    else:\n        stop_service()\n    return ('', 204)\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/app_kivy.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport subprocess\n\nfrom os.path import split\n\nfrom kivy.app import App\nfrom kivy.clock import Clock\nfrom kivy.properties import (\n    BooleanProperty,\n    DictProperty,\n    ListProperty,\n    StringProperty,\n)\nfrom kivy.lang import Builder\n\nfrom constants import RUNNING_ON_ANDROID\nfrom tools import (\n    get_android_python_activity,\n    get_failed_unittests_from,\n    get_images_with_extension,\n    load_kv_from,\n    raise_error,\n    run_test_suites_into_buffer,\n    setup_lifecycle_callbacks,\n    vibrate_with_pyjnius,\n)\nfrom widgets import TestImage\n\n# define our app's screen manager and load the screen templates\nscreen_manager_app = '''\nScreenManager:\n    ScreenUnittests:\n    ScreenKeyboard:\n    ScreenOrientation:\n    ScreenService:\n'''\nload_kv_from('screen_unittests.kv')\nload_kv_from('screen_keyboard.kv')\nload_kv_from('screen_orientation.kv')\nload_kv_from('screen_service.kv')\n\n\nclass TestKivyApp(App):\n\n    tests_to_perform = DictProperty()\n    unittest_error_text = StringProperty('Running unittests...')\n    test_packages = StringProperty('Unittest recipes:')\n    generated_images = ListProperty()\n    service_running = BooleanProperty(False)\n\n    def build(self):\n        self.reset_unittests_results()\n        self.sm = Builder.load_string(screen_manager_app)\n        return self.sm\n\n    def on_start(self):\n        setup_lifecycle_callbacks()\n\n    def reset_unittests_results(self, refresh_ui=False):\n        for img in get_images_with_extension():\n            subprocess.call([\"rm\", \"-r\", img])\n            print('removed image: ', img)\n        if refresh_ui:\n            self.set_color_for_tested_modules(restart=True)\n            self.unittest_error_text = ''\n            screen_unittests = self.sm.get_screen('unittests')\n            images_box = screen_unittests.ids.test_images_box\n            images_box.clear_widgets()\n            self.generated_images = []\n\n    def on_tests_to_perform(self, *args):\n        \"\"\"\n        Check `test_to_perform` so we can build some special tests in our ui.\n        Also will schedule the run of our tests.\n        \"\"\"\n        print('on_tests_to_perform: ', self.tests_to_perform.keys())\n        self.set_color_for_tested_modules(restart=True)\n        Clock.schedule_once(self.run_unittests, 3)\n\n    def run_unittests(self, *args):\n        import unittest\n        print('Imported unittest')\n\n        print(\"loading tests...\")\n        suites = unittest.TestLoader().loadTestsFromNames(\n            list(self.tests_to_perform.values()),\n        )\n        self.test_packages = ', '.join(self.tests_to_perform.keys())\n\n        print(\"running unittest...\")\n        self.unittest_error_text = run_test_suites_into_buffer(suites)\n\n        print(\"unittest result is:\")\n        print(self.unittest_error_text)\n        print('Ran tests')\n\n        self.set_color_for_tested_modules()\n\n        # check generated images by unittests\n        self.generated_images = get_images_with_extension()\n\n    def set_color_for_tested_modules(self, restart=False):\n        tests_made = sorted(list(self.tests_to_perform.keys()))\n        failed_tests = get_failed_unittests_from(\n            self.unittest_error_text,\n            self.tests_to_perform.values(),\n        )\n\n        modules_text = 'Unittest recipes: '\n        for n, module in enumerate(tests_made):\n            base_text = '[color={color}]{module}[/color]'\n            if restart:\n                color = '#838383'  # grey\n            elif self.tests_to_perform[module] in failed_tests:\n                color = '#ff0000'  # red\n            else:\n                color = '#5d8000'  # green\n            if n != len(tests_made) - 1:\n                base_text += ', '\n\n            modules_text += base_text.format(color=color, module=module)\n\n        self.test_packages = modules_text\n\n    def on_generated_images(self, *args):\n        screen_unittests = self.sm.get_screen('unittests')\n        images_box = screen_unittests.ids.test_images_box\n        for i in self.generated_images:\n            img = TestImage(\n                text='Generated image by unittests: {}'.format(split(i)[1]),\n                source=i,\n            )\n            images_box.add_widget(img)\n\n    def test_vibration_with_pyjnius(self, *args):\n        vibrate_with_pyjnius()\n\n    @property\n    def service_time(self):\n        from jnius import autoclass\n\n        return autoclass('org.test.unit_tests_app.ServiceP4a_test_service')\n\n    def on_service_running(self, *args):\n        if RUNNING_ON_ANDROID:\n            if self.service_running:\n                print('Starting service')\n                self.start_service()\n            else:\n                print('Stopping service')\n                self.stop_service()\n        else:\n            raise_error('Service test not supported on desktop')\n\n    def start_service(self):\n        activity = get_android_python_activity()\n        service = self.service_time\n        try:\n            service.start(activity, 'Some argument')\n        except Exception as err:\n            raise_error(str(err))\n\n    def stop_service(self):\n        service = self.service_time\n        activity = get_android_python_activity()\n        service.stop(activity)\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/app_service.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport datetime\nimport threading\nimport time\n\nfrom os import environ\n\nargument = environ.get('PYTHON_SERVICE_ARGUMENT', '')\nprint(\n    'app_service.py was successfully called with argument: \"{}\"'.format(\n        argument,\n    ),\n)\n\nnext_call = time.time()\nnext_call_in = 5  # seconds\n\n\ndef service_timer():\n    global next_call\n    print('P4a test service: {}'.format(datetime.datetime.now()))\n\n    next_call += next_call_in\n    threading.Timer(next_call - time.time(), service_timer).start()\n\n\nprint('Starting the test service timer...')\nservice_timer()\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/constants.py",
    "content": "# -*- coding: utf-8 -*-\n\nfrom os import environ\n\nRUNNING_ON_ANDROID = \"ANDROID_APP_PATH\" in environ\n\nFONT_SIZE_TITLE = 32 if RUNNING_ON_ANDROID else 60\nFONT_SIZE_SUBTITLE = 16 if RUNNING_ON_ANDROID else 32\nFONT_SIZE_TEXT = 8 if RUNNING_ON_ANDROID else 16\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/main.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nOn device unit test app\n=======================\n\nThis is a dynamic test app, which means that depending on the requirements\nsupplied at build time, will perform some tests or others. Also, this app will\nhave an ui, or not, depending on requirements as well.\n\nFor now, we contemplate three possibilities:\n  -  A kivy unittest app (sdl2 bootstrap)\n  -  A unittest app (webview bootstrap)\n  -  A non-gui unittests app\n  -  A non-gui Qt app (qt bootstrap)\n\nIf you install/build this app via the `setup.py` file, a file named\n`app_requirements.txt` will be generated which will contain the requirements\nthat we passed to the `setup.py` via arguments, which will determine\nthe unittests that this app will run.\n\n.. note:: This app is made to be working on desktop and on an android device.\n          Be aware that some of the functionality of this app will only work on\n          an android device.\n\n.. tip:: you can write more unit tests at `tests/test_requirements.py` and test\n         these on desktop just by editing the file `app_requirements.txt`,\n         which should be located at the same location than this file. This\n         `app_requirements.txt` file, it's autogenerated when the\n         `setup.py` is ran, so in certain circumstances, you may need\n         to create it. Also be aware that each `python-for-android` recipe\n         that you want to test should be in a new line, taking into account the\n         case of the recipe.\n\n.. warning:: If you use buildozer you only will get the basic `kivy unittest\n             app`, with a basic set of tests: sqlite3, libffi, openssl and\n             pyjnius.\n\"\"\"\n\nimport sys\nimport unittest\n\nfrom os import curdir\nfrom os.path import isfile, realpath\n\nprint('Imported unittest')\n\nsys.path.append('./')\n\n# read `app_requirements.txt` and find out which tests to perform\ntests_to_perform = {}\nrequirements = None\nif isfile('app_requirements.txt'):\n    with open('app_requirements.txt', 'r') as requirements_file:\n        requirements = set(requirements_file.read().splitlines())\nif not requirements:\n    # we will test a basic set of recipes\n    requirements = {'sqlite3', 'libffi', 'openssl', 'pyjnius'}\nprint('App requirements are: ', requirements)\n\nfor recipe in requirements:\n    test_name = 'tests.test_requirements.{recipe}TestCase'.format(\n        recipe=recipe.capitalize()\n    )\n    try:\n        exist_test = unittest.TestLoader().loadTestsFromName(test_name)\n    except AttributeError:\n        # python2 case\n        pass\n    else:\n        if '_exception' not in exist_test._tests[0].__dict__:\n            print('Adding Testcase: ', test_name)\n            tests_to_perform[recipe] = test_name\nprint('Tests to perform are: ', tests_to_perform)\n\n# Find out which app we want to run\nif 'kivy' in requirements:\n    from app_kivy import TestKivyApp\n\n    test_app = TestKivyApp()\n    test_app.tests_to_perform = tests_to_perform\n    test_app.run()\nelif 'flask' in requirements:\n    import app_flask\n    app_flask.TESTS_TO_PERFORM = tests_to_perform\n\n    print('Current directory is ', realpath(curdir))\n    flask_debug = not realpath(curdir).startswith('/data')\n\n    # Flask is run non-threaded since it tries to resolve app classes\n    # through pyjnius from request handlers. That doesn't work since the\n    # JNI ends up using the Java system class loader in new native\n    # threads.\n    #\n    # https://github.com/kivy/python-for-android/issues/2533\n    app_flask.app.run(threaded=False, debug=flask_debug)\nelse:\n    # we don't have kivy or flask in our\n    # requirements, so we run unittests in terminal\n    suite = unittest.TestLoader().loadTestsFromNames(list(tests_to_perform.values()))\n    unittest.TextTestRunner().run(suite)\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/screen_keyboard.kv",
    "content": "#:import Window kivy.core.window.Window\n\n#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE\n#:import FONT_SIZE_TEXT constants.FONT_SIZE_TEXT\n#:import FONT_SIZE_TITLE constants.FONT_SIZE_TITLE\n#:import Spacer20 widgets.Spacer20\n\n<ScreenKeyboard@Screen>:\n    name: 'keyboard'\n    BoxLayout:\n        orientation: 'vertical'\n        Button:\n            text: 'Back to unittests'\n            font_size: sp(FONT_SIZE_SUBTITLE)\n            size_hint_y: None\n            height: dp(60)\n            on_press: root.parent.current = 'unittests'\n        Image:\n            keep_ratio: False\n            allow_stretch: True\n            source: 'static/coloursinv.png'\n            size_hint_y: None\n            height: dp(100)\n        Label:\n            text:\n                '[color=#999999]Test[/color] kivy ' \\\n                '[color=#999999]keyboard[/color] modes'\n            height: self.texture_size[1]\n            size_hint_y: None\n            padding: 0, 20\n            font_size: sp(FONT_SIZE_TITLE)\n            font_name: 'static/Blanka-Regular.otf'\n            text_size: root.width, None\n            markup: True\n            halign: 'center'\n        Label:\n            text:\n                'Specifies the behavior of window contents on display ' \\\n                'of the soft keyboard on Android.\\n\\n\\n\\n' \\\n                '[color=#ff5900]WARNING:[/color] ' \\\n                'these tests only works on an Android device'\n            markup: True\n            padding: 0, 20\n            size_hint_y: None\n            text_size: root.width, None\n            font_size: sp(FONT_SIZE_TEXT)\n            height: self.texture_size[1]\n            halign: 'center'\n        Spacer20:\n        Spacer20:\n        RelativeLayout:\n            size_hint_y: None\n            height: dp(50)\n            BoxLayout:\n                size_hint_x: None\n                width: min(dp(500), root.width)\n                orientation: 'horizontal'\n                pos_hint: {'center_x': .5}\n                ToggleButton:\n                    text: 'None'\n                    group: 'keyboard_modes'\n                    state: 'down'\n                    on_press: Window.softinput_mode = ''\n                ToggleButton:\n                    text: 'pan'\n                    group: 'keyboard_modes'\n                    on_press: Window.softinput_mode = 'pan'\n                ToggleButton:\n                    text: 'below_target'\n                    group: 'keyboard_modes'\n                    on_press: Window.softinput_mode = 'below_target'\n                ToggleButton:\n                    text: 'resize'\n                    group: 'keyboard_modes'\n                    on_press: Window.softinput_mode = 'resize'\n        Widget:\n            Scatter:\n                id: scatter\n                size_hint: None, None\n                size: dp(300), dp(80)\n                on_parent: self.pos = (300, 100)\n                BoxLayout:\n                    size: scatter.size\n                    orientation: 'horizontal'\n                    canvas:\n                        Color:\n                            rgba: 1, 0, 1, .25\n                        Rectangle:\n                            pos: 0, 0\n                            size: self.size\n                    Label:\n                        size_hint_x: None\n                        width: dp(30)\n                        text: 'drag me'\n                        canvas.before:\n                            Color:\n                                rgb: 1, 1, 1\n                            PushMatrix\n                            Translate:\n                                xy: self.center_x, self.center_y\n                            Rotate:\n                                angle: 90\n                                axis: 0, 0, 1\n                            Translate:\n                                xy: -self.center_x, -self.center_y\n                        canvas.after:\n                            PopMatrix\n                    TextInput:\n                        text: 'type in me'\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/screen_orientation.kv",
    "content": "#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE\n#:import FONT_SIZE_TEXT constants.FONT_SIZE_TEXT\n#:import FONT_SIZE_TITLE constants.FONT_SIZE_TITLE\n\n#:import set_device_orientation tools.set_device_orientation\n#:import Spacer20 widgets.Spacer20\n\n<ScreenOrientation@Screen>:\n    name: 'orientation'\n    ScrollView:\n        BoxLayout:\n            orientation: 'vertical'\n            size_hint_y: None\n            height: self.minimum_height\n            Button:\n                text: 'Back to unittests'\n                font_size: sp(FONT_SIZE_SUBTITLE)\n                size_hint_y: None\n                height: dp(60)\n                on_press: root.parent.current = 'unittests'\n            Image:\n                keep_ratio: False\n                allow_stretch: True\n                source: 'static/coloursinv.png'\n                size_hint_y: None\n                height: dp(100)\n            Label:\n                text:\n                    '[color=#999999]Test[/color] device ' \\\n                    '[color=#999999]orientation[/color]'\n                height: self.texture_size[1]\n                size_hint_y: None\n                padding: 0, 20\n                font_size: sp(FONT_SIZE_TITLE)\n                font_name: 'static/Blanka-Regular.otf'\n                text_size: root.width, None\n                markup: True\n                halign: 'center'\n            Spacer20:\n            Spacer20:\n            RelativeLayout:\n                size_hint_y: None\n                height: dp(50)\n                BoxLayout:\n                    size_hint_x: None\n                    width: min(dp(500), root.width)\n                    pos_hint: {'center_x': .5}\n                    orientation: 'horizontal'\n                    ToggleButton:\n                        text: 'Sensor'\n                        group: 'device_orientations'\n                        state: 'down'\n                        on_press: set_device_orientation('sensor')\n                    ToggleButton:\n                        text: 'Horizontal'\n                        group: 'device_orientations'\n                        on_press: set_device_orientation('horizontal')\n                    ToggleButton:\n                        text: 'Vertical'\n                        group: 'device_orientations'\n                        on_press: set_device_orientation('vertical')\n            Spacer20:\n            Spacer20:\n            Label:\n                text:\n                    '[color=#ff5900]WARNING:[/color] ' \\\n                    'these tests only works on an Android device'\n                markup: True\n                padding: 20, 20\n                size_hint_y: None\n                text_size: root.width, None\n                font_size: sp(FONT_SIZE_TEXT)\n                height: self.texture_size[1]\n                halign: 'center'\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/screen_service.kv",
    "content": "#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE\n#:import FONT_SIZE_TEXT constants.FONT_SIZE_TEXT\n#:import FONT_SIZE_TITLE constants.FONT_SIZE_TITLE\n\n#:import set_device_orientation tools.set_device_orientation\n#:import Spacer20 widgets.Spacer20\n#:import CircularButton widgets.CircularButton\n\n#:set green_color (0.3, 0.5, 0, 1)\n#:set red_color (1.0, 0, 0, 1)\n\n<ScreenService@Screen>:\n    name: 'service'\n    ScrollView:\n        BoxLayout:\n            orientation: 'vertical'\n            size_hint_y: None\n            height: self.minimum_height\n            Button:\n                text: 'Back to unittests'\n                font_size: sp(FONT_SIZE_SUBTITLE)\n                size_hint_y: None\n                height: dp(60)\n                on_press: root.parent.current = 'unittests'\n            Image:\n                keep_ratio: False\n                allow_stretch: True\n                source: 'static/coloursinv.png'\n                size_hint_y: None\n                height: dp(100)\n            Label:\n                text:\n                    '[color=#999999]Test[/color] P4A ' \\\n                    '[color=#999999]service[/color]'\n                size_hint_y: None\n                padding: 0, 20\n                height: self.texture_size[1]\n                halign: 'center'\n                font_size: sp(FONT_SIZE_TITLE)\n                font_name: 'static/Blanka-Regular.otf'\n                text_size: root.width, None\n                markup: True\n            Spacer20:\n            Spacer20:\n            RelativeLayout:\n                size_hint_y: None\n                height: dp(100)\n                CircularButton:\n                    text: 'Start service' if not app.service_running else 'Stop Service'\n                    pos_hint: {'center_x': .5}\n                    background_color: red_color\n                    on_press:\n                        app.service_running = not app.service_running;\n                        self.background_color = green_color \\\n                        if app.service_running else red_color\n            Spacer20:\n            Spacer20:\n            Label:\n                text:\n                    '[color=#ff5900]WARNING:[/color] ' \\\n                    'this test only works on an Android device'\n                markup: True\n                size_hint_y: None\n                height: self.texture_size[1]\n                halign: 'center'\n                text_size: root.width, None\n                font_size: sp(FONT_SIZE_TEXT)\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/screen_unittests.kv",
    "content": "#:import sys sys\n\n#:import Clock kivy.clock.Clock\n#:import Metrics kivy.metrics.Metrics\n\n#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE\n#:import FONT_SIZE_TEXT constants.FONT_SIZE_TEXT\n#:import FONT_SIZE_TITLE constants.FONT_SIZE_TITLE\n#:import Spacer20 widgets.Spacer20\n\n<ScreenUnittests@Screen>:\n    name: 'unittests'\n    ScrollView:\n        id: scroll_view\n        GridLayout:\n            id: grid\n            cols: 1\n            size_hint_y: None\n            height: self.minimum_height\n            BoxLayout:\n                id: header_box\n                orientation: 'vertical'\n                size_hint_y: None\n                height: self.minimum_height\n                GridLayout:\n                    rows: 1 if root.width > root.height else 2\n                    size_hint_y: None\n                    height: dp(60) * self.rows\n                    Button:\n                        text: 'Test vibration'\n                        font_size: sp(FONT_SIZE_SUBTITLE)\n                        on_press: app.test_vibration_with_pyjnius()\n                    Button:\n                        text: 'Test Keyboard'\n                        font_size: sp(FONT_SIZE_SUBTITLE)\n                        on_press: root.parent.current = 'keyboard'\n                    Button:\n                        text: 'Test Orientation'\n                        font_size: sp(FONT_SIZE_SUBTITLE)\n                        on_press: root.parent.current = 'orientation'\n                    Button:\n                        text: 'Test Service'\n                        font_size: sp(FONT_SIZE_SUBTITLE)\n                        on_press: root.parent.current = 'service'\n                Image:\n                    keep_ratio: False\n                    allow_stretch: True\n                    source: 'static/colours.png'\n                    size_hint_y: None\n                    height: dp(100)\n                Label:\n                    height: self.texture_size[1]\n                    size_hint_y: None\n                    padding: 0, 20\n                    font_name: 'static/Blanka-Regular.otf'\n                    font_size: sp(FONT_SIZE_TITLE)\n                    text_size: self.size[0], None\n                    markup: True\n                    text:\n                        '[color=#999999]Kivy[/color] on ' \\\n                        '[color=#999999]SDL2[/color] on ' \\\n                        '[color=#999999]Android[/color] !'\n                    halign: 'center'\n                Label:\n                    height: self.texture_size[1]\n                    size_hint_y: None\n                    text_size: self.size[0], None\n                    font_size: sp(FONT_SIZE_TEXT)\n                    markup: True\n                    text: sys.version\n                    halign: 'center'\n                    padding_y: dp(10)\n                Spacer20:\n                Label:\n                    height: self.texture_size[1]\n                    size_hint_y: None\n                    font_size: sp(FONT_SIZE_SUBTITLE)\n                    text_size: self.size[0], None\n                    markup: True\n                    text:\n                        'Dpi: {}\\nDensity: {}\\nFontscale: {}'.format(\n                        Metrics.dpi, Metrics.density, Metrics.fontscale)\n                    halign: 'center'\n                Spacer20:\n            BoxLayout:\n                id: output_box\n                orientation: 'vertical'\n                size_hint_y: None\n                height: self.minimum_height\n                canvas.before:\n                    Color:\n                        rgba: 1, 0, 1, .25\n                    Rectangle:\n                        pos: self.pos\n                        size: self.size\n                Spacer20:\n                Label:\n                    id: test_packages_text\n                    size_hint_y: None\n                    text_size: self.width, None\n                    height: self.texture_size[1]\n                    font_size: sp(FONT_SIZE_SUBTITLE)\n                    padding: 40, 20\n                    markup: True\n                    text: app.test_packages\n                    canvas.before:\n                        Color:\n                            rgba: 0, 0, 0, .65\n                        Rectangle:\n                            pos: self.x + 20, self.y\n                            size: self.width - 40, self.height\n                Label:\n                    id: output_text\n                    height: self.texture_size[1]\n                    size_hint: None, None\n                    pos_hint: {'center_x': .5 }\n                    width: output_box.width - 40\n                    padding: 20, 20\n                    font_size: sp(FONT_SIZE_TEXT)\n                    text_size: self.size[0], None\n                    markup: True\n                    text: app.unittest_error_text\n                    halign: 'justify'\n                    canvas.before:\n                        Color:\n                            rgba: 0, 0, 0, .35\n                        Rectangle:\n                            pos: self.pos\n                            size: self.size\n            Widget:\n                id: fill_space\n                size_hint_y: None\n                height:\n                    max(20, root.height - header_box.height - output_box.height + 20)\n                canvas.before:\n                    Color:\n                        rgba: 1, 0, 1, .25\n                    Rectangle:\n                        pos: self.pos\n                        size: self.size\n            BoxLayout:\n                id: test_images_box\n                orientation: 'vertical'\n                size_hint_y: None\n                height: self.minimum_height if self.children else 0\n                padding: 0, 20\n            Button:\n                size_hint_y: None\n                height: dp(60)\n                text: 'Restart unittests'\n                font_size: sp(FONT_SIZE_SUBTITLE)\n                on_press:\n                    app.reset_unittests_results(refresh_ui=True);\n                    root.ids.scroll_view.scroll_y = 1;\n                    Clock.schedule_once(app.run_unittests, 2)\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/static/flask.css",
    "content": "\n@font-face{\n  font-family: Blanka;\n  src: url('Blanka-Regular.otf');\n  font-weight: normal;\n}\n\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: \"Roboto Slab\", Roboto, sans-serif;\n  color: #444;\n}\n\n/*\n * Formatting the header area\n */\nheader {\n  background-color: #6600b8;\n  height: 90px;\n  width: 100%;\n  opacity: .9;\n  margin-bottom: 10px;\n}\nheader h1.logo {\n  margin-top: 4px;\n  font-size: 1.4em;\n  font-family: Blanka, sans-serif;\n  color: #fff;\n  text-transform: uppercase;\n  float: left;\n  text-align: center;\n  width: 100%;\n}\nheader h1.logo:hover {\n  color: #000000;\n  text-decoration: none;\n}\n\n/*\n * Navbar\n */\n.menu {\n  float: right;\n  margin-top: 0px;\n  width: 100%;\n  text-align: center;\n  font-family: Blanka, sans-serif;\n}\n.menu li {\n  display: inline-block;\n}\n.menu li + li {\n  margin-left: 35px;\n}\n.menu li a {\n  color: #999999;\n  text-decoration: none;\n  text-transform: uppercase;\n}\n\n/*\n * Centering the body content\n */\n.container {\n  width: auto;\n  margin: 0 8px;\n  text-align: center;\n}\n\nh2.page-title {\n  text-align: center;\n  text-transform: uppercase;\n  font-family: Blanka, sans-serif;\n}\n\n.text-underline {\n  border-bottom: 0px solid #333;\n  font-family: Blanka, sans-serif;\n  width: 100%;\n  display: block;\n}\n\n.center {\n  margin: auto;\n  width: 50%;\n}\n\n/*\n * Unittests page\n */\n.text-green {\n  color: #5d8000;\n}\n\n.text-red {\n  color: #ff0000;\n}\n\n.terminal-box {\n  background-color: #333;\n}\n\n.terminal-content {\n  color: #fff;\n  margin: 8px;\n  padding-top: 8px;\n}\n\n/*\n * Adapt header to bigger screens\n */\n@media only screen and (min-width: 530px) {\n  header {\n    height: 45px;\n  }\n\n  header h1.logo {\n    width: auto;\n    text-align: left;\n  }\n\n  .menu {\n    width: auto;\n    margin-top: 12px;\n  }\n  .container {\n    text-align: left;\n  }\n  .text-underline {\n    border-bottom: 1px solid #333;\n  }\n}"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/templates/base.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <link rel=\"stylesheet\" href=\"{{ url_for('static', filename='flask.css') }}\">\n        <title>\n        {% block title %}\n            Flask on {{ platform }}\n        {% endblock %}\n        </title>\n        \n    </head>\n    <body>\n        <header>\n            <div class=\"container\">\n                <h1 class=\"logo\">Flask on {{ platform }}!</h1>\n                <strong>\n                    <nav>\n                        <ul class=\"menu\">\n                            <li><a href=\"{{ url_for('index') }}\">Home</a></li>\n                            <li><a href=\"{{ url_for('page2') }}\">Another Page</a></li>\n                        </ul>\n                    </nav>\n                </strong>\n            </div>\n        </header>\n\n        <div class=\"container\">\n            {% block body %}\n            {% endblock %}\n        </div>\n\n    </body>\n</html> \n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/templates/index.html",
    "content": "{% extends \"base.html\" %}\n\n\n{% block body %}\n\n<h2 class=\"page-title\">Main Page</h2>\n\n<img src=\"static/colours.png\" style=\"width:50%;margin-left:auto;margin-right:auto;display:block\">\n\n<div>\n    <h3 class=\"text-underline\">Perform unittests</h3>\n    <form>\n    <button formaction=\"/unittests\">\n        Run unittests\n    </button>\n\n    </form>\n</div>\n\n<div>\n    <h3 class=\"text-underline\">Test navigation</h3>\n    <form>\n        <button formaction=\"/page2\" >Second page</button>\n    </form>\n</div>\n\n<div>\n    <h3 class=\"text-underline\">Android tests</h3>\n\n    <div>\n        <button onClick=\"vibrate()\">\n            vibrate for 1s with pyjnius\n        </button>\n\n        <script>\n         function vibrate() {\n             var request = new XMLHttpRequest();\n             request.open('GET', 'vibrate?time=1', true);\n             request.send();\n             }\n        </script>\n    </div>\n\n    <div>\n        <button onClick=\"loadUrl()\">\n            open url\n        </button>\n        <input type=\"text\" value=\"http://www.google.com\" id=\"url_field\"/>\n\n        <script>\n         function loadUrl() {\n             var request = new XMLHttpRequest();\n             var url = document.getElementById(\"url_field\").value\n             request.open('GET', 'loadUrl?url=' + url, true);\n             request.send();\n             }\n        </script>\n    </div>\n\n    <div>\n        <button onClick=\"sensor()\">\n            sensor orientation\n        </button>\n\n        <button onClick=\"vertical()\">\n            vertical orientation\n        </button>\n\n        <button onClick=\"horizontal()\">\n            horizontal orientation\n        </button>\n\n        <script>\n         function sensor() {\n             var request = new XMLHttpRequest();\n             request.open('GET', 'orientation?dir=sensor', true);\n             request.send();\n         }\n\n         function horizontal() {\n             var request = new XMLHttpRequest();\n             request.open('GET', 'orientation?dir=horizontal', true);\n             request.send();\n         }\n\n         function vertical() {\n             var request = new XMLHttpRequest();\n             request.open('GET', 'orientation?dir=vertical', true);\n             request.send();\n         }\n        </script>\n    </div>\n\n    <div>\n        <button onClick=\"start_service()\">\n            start service\n        </button>\n\n        <button onClick=\"stop_service()\">\n            stop service\n        </button>\n\n        <div id=\"service-status\">\n            {{ 'Service started' if service_running else 'Service stopped' }}\n        </div>\n\n        <script>\n         function start_service() {\n             var request = new XMLHttpRequest();\n             request.onload = function() {\n                 var elem = document.getElementById('service-status');\n                 if (this.status >= 400)\n                     elem.textContent = `Service start failed: ${this.statusText}`;\n                 else\n                     elem.textContent = 'Service started';\n             };\n             request.open('GET', 'service?action=start', true);\n             request.send();\n         }\n\n         function stop_service() {\n             var request = new XMLHttpRequest();\n             request.onload = function() {\n                 var elem = document.getElementById('service-status');\n                 if (this.status >= 400)\n                     elem.textContent = `Service stop failed: ${this.statusText}`;\n                 else\n                     elem.textContent = 'Service stopped';\n             };\n             request.open('GET', 'service?action=stop', true);\n             request.send();\n             alert('Service stopped')\n         }\n        </script>\n    </div>\n\n    <br>\n    <br>\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/templates/page2.html",
    "content": "\n{% extends \"base.html\" %}\n\n\n{% block body %}\n\n<h2 class=\"page-title\">Page two</h2>\n\n<img src=\"static/coloursinv.png\" style=\"width:50%;margin-left:auto;margin-right:auto;display:block\">\n\n<div class=\"center\">\n    <br>\n    Yeah, it seems to work, I would suggest to go to:\n    <br>\n    <form>\n        <button formaction=\"/\">First page</button>\n    </form>\n    <br>\n    ...far more interesting ;)\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/templates/unittests.html",
    "content": "{% extends \"base.html\" %}\n\n\n{% block body %}\n\n<h2 class=\"page-title\">Unittests Page</h2>\n\n<br>\nUnittest recipes: {{ tested_recipes }}\n<br>\n\n<div class=\"terminal-box\">\n    <div class=\"terminal-content\">\n        {% for line in unittests_output %}\n            {{ line }}<br>\n        {% endfor %}\n    </div>\n</div>\n\n{% endblock %}"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/tests/__init__.py",
    "content": ""
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/tests/mixin.py",
    "content": "import importlib\n\n\nclass PythonTestMixIn(object):\n\n    module_import = None\n\n    def test_import_module(self):\n        \"\"\"Test importing the specified Python module name. This import test\n        is common to all Python modules, it does not test any further\n        functionality.\n        \"\"\"\n        self.assertIsNotNone(\n            self.module_import,\n            'module_import is not set (was default None)')\n\n        importlib.import_module(self.module_import)\n\n    def test_run_module(self):\n        \"\"\"Import the specified module and do something with it as a minimal\n        check that it actually works.\n\n        This test fails by default, it must be overridden by every\n        child test class.\n        \"\"\"\n\n        self.fail('This test must be overridden by {}'.format(self))\n\n\nprint('Defined test case')\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/tests/test_requirements.py",
    "content": "\nfrom unittest import TestCase\nfrom .mixin import PythonTestMixIn\n\n\nclass NumpyTestCase(PythonTestMixIn, TestCase):\n    module_import = 'numpy'\n\n    def test_run_module(self):\n        import numpy as np\n\n        arr = np.random.random((3, 3))\n        det = np.linalg.det(arr)\n\nclass ScipyTestCase(PythonTestMixIn, TestCase):\n    module_import = 'scipy'\n\n    def test_run_module(self):\n        import numpy as np\n        from scipy.cluster.vq import vq, kmeans, whiten\n        features  = np.array([[ 1.9,2.3],\n                        [ 1.5,2.5],\n                        [ 0.8,0.6],\n                        [ 0.4,1.8],\n                        [ 0.1,0.1],\n                        [ 0.2,1.8],\n                        [ 2.0,0.5],\n                        [ 0.3,1.5],\n                        [ 1.0,1.0]])\n        whitened = whiten(features)\n        book = np.array((whitened[0],whitened[2]))\n        print('kmeans', kmeans(whitened,book))\n\n\nclass OpensslTestCase(PythonTestMixIn, TestCase):\n    module_import = '_ssl'\n\n    def test_run_module(self):\n        import ssl\n\n        ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n        ctx.options &= ~ssl.OP_NO_SSLv3\n\n\nclass Sqlite3TestCase(PythonTestMixIn, TestCase):\n    module_import = 'sqlite3'\n\n    def test_run_module(self):\n        import sqlite3\n\n        conn = sqlite3.connect('example.db')\n        conn.cursor()\n\n\nclass KivyTestCase(PythonTestMixIn, TestCase):\n    module_import = 'kivy'\n\n    def test_run_module(self):\n        # This import has side effects, if it works then it's an\n        # indication that Kivy is okay\n        from kivy.core.window import Window\n\n\nclass PyjniusTestCase(PythonTestMixIn, TestCase):\n    module_import = 'jnius'\n\n    def test_run_module(self):\n        from jnius import autoclass\n\n        autoclass('org.kivy.android.PythonActivity')\n\n\nclass LibffiTestCase(PythonTestMixIn, TestCase):\n    module_import = 'ctypes'\n\n    def test_run_module(self):\n        from os import environ\n        from ctypes import cdll\n\n        if \"ANDROID_APP_PATH\" in environ:\n            libc = cdll.LoadLibrary(\"libc.so\")\n        else:\n            from ctypes.util import find_library\n            path_libc = find_library(\"c\")\n            libc = cdll.LoadLibrary(path_libc)\n        libc.printf(b\"%s\\n\", b\"Using the C printf function from Python ... \")\n\n\nclass RequestsTestCase(PythonTestMixIn, TestCase):\n    module_import = 'requests'\n\n    def test_run_module(self):\n        import requests\n\n        requests.get('https://kivy.org/')\n\n\nclass PillowTestCase(PythonTestMixIn, TestCase):\n    module_import = 'PIL'\n\n    def test_run_module(self):\n        import os\n        from PIL import (\n            Image as PilImage,\n            ImageOps,\n            ImageFont,\n            ImageDraw,\n            ImageFilter,\n            ImageChops,\n        )\n\n        text_to_draw = \"Kivy\"\n        img_target = \"pillow_text_draw.png\"\n        image_width = 200\n        image_height = 100\n\n        img = PilImage.open(\"static/colours.png\")\n        img = img.resize((image_width, image_height), PilImage.ANTIALIAS)\n        font = ImageFont.truetype(\"static/Blanka-Regular.otf\", 55)\n\n        draw = ImageDraw.Draw(img)\n        for n in range(2, image_width, 2):\n            draw.rectangle(\n                (n, n, image_width - n, image_height - n), outline=\"black\"\n            )\n        img.filter(ImageFilter.GaussianBlur(radius=1.5))\n\n        text_pos = (image_width / 2.0 - 55, 5)\n        halo = PilImage.new(\"RGBA\", img.size, (0, 0, 0, 0))\n        ImageDraw.Draw(halo).text(\n            text_pos, text_to_draw, font=font, fill=\"black\"\n        )\n        blurred_halo = halo.filter(ImageFilter.BLUR)\n        ImageDraw.Draw(blurred_halo).text(\n            text_pos, text_to_draw, font=font, fill=\"white\"\n        )\n        img = PilImage.composite(\n            img, blurred_halo, ImageChops.invert(blurred_halo)\n        )\n\n        img.save(img_target, \"PNG\")\n        self.assertTrue(os.path.isfile(img_target))\n\n\nclass MatplotlibTestCase(PythonTestMixIn, TestCase):\n    module_import = 'matplotlib'\n\n    def test_run_module(self):\n        import os\n        import numpy as np\n        from matplotlib import pyplot as plt\n\n        fig, ax = plt.subplots()\n        ax.set_xlabel('test xlabel')\n        ax.set_ylabel('test ylabel')\n        ax.plot(np.random.random(50))\n        ax.plot(np.sin(np.linspace(0, 3 * np.pi, 30)))\n\n        ax.legend(['random numbers', 'sin'])\n\n        fig.set_size_inches((5, 4))\n        fig.tight_layout()\n\n        fig.savefig('matplotlib_test.png', dpi=150)\n        self.assertTrue(os.path.isfile(\"matplotlib_test.png\"))\n\n\nclass CryptographyTestCase(PythonTestMixIn, TestCase):\n    module_import = 'cryptography'\n\n    def test_run_module(self):\n        from cryptography.fernet import Fernet\n\n        key = Fernet.generate_key()\n        f = Fernet(key)\n        cryptography_encrypted = f.encrypt(\n            b'A really secret message. Not for prying eyes.')\n        cryptography_decrypted = f.decrypt(cryptography_encrypted)\n\n\nclass PycryptoTestCase(PythonTestMixIn, TestCase):\n    module_import = 'Crypto'\n\n    def test_run_module(self):\n        from Crypto.Hash import SHA256\n\n        crypto_hash_message = 'A secret message'\n        hash = SHA256.new()\n        hash.update(crypto_hash_message)\n        crypto_hash_hexdigest = hash.hexdigest()\n\n\nclass PycryptodomeTestCase(PythonTestMixIn, TestCase):\n    module_import = 'Crypto'\n\n    def test_run_module(self):\n        import os\n        from Crypto.PublicKey import RSA\n\n        print('Ok imported pycryptodome, testing some basic operations...')\n        secret_code = \"Unguessable\"\n        key = RSA.generate(2048)\n        encrypted_key = key.export_key(passphrase=secret_code, pkcs=8,\n                                       protection=\"scryptAndAES128-CBC\")\n        print('\\t -> Testing key for secret code \"Unguessable\": {}'.format(\n            encrypted_key))\n\n        file_out = open(\"rsa_key.bin\", \"wb\")\n        file_out.write(encrypted_key)\n        print('\\t -> Testing key write: {}'.format(\n            'ok' if os.path.exists(\"rsa_key.bin\") else 'fail'))\n        self.assertTrue(os.path.exists(\"rsa_key.bin\"))\n\n        print('\\t -> Testing Public key:'.format(key.publickey().export_key()))\n\n\nclass ScryptTestCase(PythonTestMixIn, TestCase):\n    module_import = 'scrypt'\n\n    def test_run_module(self):\n        import scrypt\n        h1 = scrypt.hash('password', 'random salt')\n        # The hash should be 64 bytes (default value)\n        self.assertEqual(64, len(h1))\n\n\nclass M2CryptoTestCase(PythonTestMixIn, TestCase):\n    module_import = 'M2Crypto'\n\n    def test_run_module(self):\n        from M2Crypto import SSL\n        ctx = SSL.Context('sslv23')\n\n\nclass Pysha3TestCase(PythonTestMixIn, TestCase):\n    module_import = 'sha3'\n\n    def test_run_module(self):\n        import sha3\n\n        print('Ok imported pysha3, testing some basic operations...')\n        k = sha3.keccak_512()\n        k.update(b\"data\")\n        print('Test pysha3 operation (keccak_512): {}'.format(k.hexdigest()))\n\n\nclass LibtorrentTestCase(PythonTestMixIn, TestCase):\n    module_import = 'libtorrent'\n\n    def test_run_module(self):\n        import libtorrent as lt\n\n        print('Imported libtorrent version {}'.format(lt.version))\n\n\nclass Pyside6TestCase(PythonTestMixIn, TestCase):\n    module_import = 'PySide6'\n\n    def test_run_module(self):\n        import PySide6\n        from PySide6.QtCore import QDateTime\n        from PySide6 import QtWidgets\n\n        print(f\"Imported PySide6 version {PySide6.__version__}\")\n        print(f\"Current date and time obtained from PySide6 : {QDateTime.currentDateTime().toString()}\")\n\n\nclass Shiboken6TestCase(PythonTestMixIn, TestCase):\n    module_import = 'shiboken6'\n\n    def test_run_module(self):\n        import shiboken6\n        from shiboken6 import Shiboken\n\n        print('Imported shiboken6 version {}'.format(shiboken6.__version__))\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/tools.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport glob\nimport unittest\n\ntry:\n    # python2 case\n    from StringIO import StringIO\nexcept ImportError:\n    # python3 case\n    from io import StringIO\nfrom os.path import abspath, split, join\n\nfrom constants import RUNNING_ON_ANDROID\n\nAPP_PATH = split(abspath(__file__))[0]\n\n\ndef run_test_suites_into_buffer(suites):\n    \"\"\"Run a suite of unittests but into a buffer so we can read the result.\"\"\"\n    terminal_output = StringIO()\n    unittest.TextTestRunner(stream=terminal_output).run(suites)\n    return terminal_output.getvalue()\n\n\ndef get_images_with_extension(path=APP_PATH, extension='*.png'):\n    \"\"\"\n    Return a list of image files given a path and an file extension.\n\n    .. note:: those image files are supposed to be created by our unittests\n              inside the app's root folder.\n    \"\"\"\n    return glob.glob(join(path, extension))\n\n\ndef load_kv_from(kv_name):\n    \"\"\"\n    Load a kivy's kv file givel a kv filename.\n\n    .. note:: requires `.kv` extension.\n    \"\"\"\n    from kivy.lang import Builder\n\n    kv_file = join(APP_PATH, kv_name)\n    return Builder.load_file(kv_file)\n\n\ndef raise_error(error):\n    \"\"\"\n    A function to notify an error without raising an exception.\n\n    .. warning:: we will try to notify via an kivy's Popup, but if kivy is not\n              installed, it will only print an error message.\n    \"\"\"\n    try:\n        from widgets import ErrorPopup\n    except ImportError:\n        print('raise_error:',  error)\n        return\n    ErrorPopup(error_text=error).open()\n\n\ndef get_failed_unittests_from(unittests_output, set_of_tests):\n    \"\"\"Parse unittests output trying to find the failed tests\"\"\"\n    failed_tests = set()\n    for test in set_of_tests:\n        if test in unittests_output:\n            failed_tests.add(test)\n    return failed_tests\n\n\ndef skip_if_not_running_from_android_device(func):\n    \"\"\"\n    Skip run of the function in case that we are running the app form android.\n\n    .. note:: this is useful for some kind of tests that are supposed to be run\n              from an android device and relies on `pyjnius`.\n    \"\"\"\n\n    def wrapper(*arg, **kwarg):\n        if RUNNING_ON_ANDROID:\n            return func(*arg, **kwarg)\n        raise_error(\n            'Function `{func_name}` only available for android devices'.format(\n                func_name=func.__name__,\n            ),\n        )\n        return None\n\n    return wrapper\n\n\n@skip_if_not_running_from_android_device\ndef get_android_python_activity():\n    \"\"\"\n    Return the `PythonActivity.mActivity` using `pyjnius`.\n\n    .. warning:: This function will only be ran if executed from android\"\"\"\n    from jnius import autoclass\n\n    PythonActivity = autoclass('org.kivy.android.PythonActivity')\n    return PythonActivity.mActivity\n\n\n@skip_if_not_running_from_android_device\ndef vibrate_with_pyjnius(time=1000):\n    \"\"\"\n    Vibrate an android device using `pyjnius`.\n\n    .. warning:: This function will only be ran if executed from android.\"\"\"\n    from jnius import autoclass, cast\n\n    print('Attempting to vibrate with pyjnius')\n    ANDROID_VERSION = autoclass('android.os.Build$VERSION')\n    SDK_INT = ANDROID_VERSION.SDK_INT\n\n    Context = autoclass(\"android.content.Context\")\n    activity = get_android_python_activity()\n\n    vibrator_service = activity.getSystemService(Context.VIBRATOR_SERVICE)\n    vibrator = cast(\"android.os.Vibrator\", vibrator_service)\n\n    if vibrator and SDK_INT >= 26:\n        print(\"Using android's `VibrationEffect` (SDK >= 26)\")\n        VibrationEffect = autoclass(\"android.os.VibrationEffect\")\n        vibrator.vibrate(\n            VibrationEffect.createOneShot(\n                time, VibrationEffect.DEFAULT_AMPLITUDE,\n            ),\n        )\n    elif vibrator:\n        print(\"Using deprecated android's vibrate (SDK < 26)\")\n        vibrator.vibrate(time)\n    else:\n        print('Something happened...vibrator service disabled?')\n\n\n@skip_if_not_running_from_android_device\ndef set_device_orientation(direction):\n    \"\"\"\n    Modifies the app orientation for an android device.\n\n    .. warning:: This function will only be ran if executed from android.\"\"\"\n    if direction not in ('sensor', 'horizontal', 'vertical'):\n        print(\n            'ERROR: asked to orient to `{direction}`, but we only support: '\n            'sensor, horizontal or vertical'.format(direction=direction)\n        )\n    from jnius import autoclass\n\n    activity = get_android_python_activity()\n    ActivityInfo = autoclass('android.content.pm.ActivityInfo')\n\n    if direction == 'sensor':\n        activity.setRequestedOrientation(\n            ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)\n    elif direction == 'horizontal':\n        activity.setRequestedOrientation(\n            ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)\n    else:\n        activity.setRequestedOrientation(\n            ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)\n\n\n@skip_if_not_running_from_android_device\ndef setup_lifecycle_callbacks():\n    \"\"\"\n    Register example ActivityLifecycleCallbacks\n    \"\"\"\n    from android.activity import register_activity_lifecycle_callbacks\n\n    register_activity_lifecycle_callbacks(\n        onActivityStarted=lambda activity: print('onActivityStarted'),\n        onActivityPaused=lambda activity: print('onActivityPaused'),\n        onActivityResumed=lambda activity: print('onActivityResumed'),\n        onActivityStopped=lambda activity: print('onActivityStopped'),\n        onActivityDestroyed=lambda activity: print('onActivityDestroyed'),\n    )\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/widgets.kv",
    "content": "#:import get_color_from_hex kivy.utils.get_color_from_hex\n#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE\n\n<Spacer20@Widget>:\n    size_hint_y: None\n    height: dp(20)\n\n\n<TestImage@BoxLayout>:\n    text: ''\n    source: ''\n    size_hint_y: None\n    height: self.minimum_height\n    orientation: 'vertical'\n    canvas.before:\n        Color:\n            rgba: 1, 0, 1, .25\n        Rectangle:\n            pos: self.pos\n            size: self.size\n    Spacer20:\n    Label:\n        text: root.text\n        size_hint_y: None\n        height: dp(60)\n        font_size: sp(FONT_SIZE_SUBTITLE)\n        canvas.before:\n            Color:\n                rgba: 0, 0, 0, .65\n            Rectangle:\n                pos: self.x + 20, self.y\n                size: self.width - 40, self.height\n    BoxLayout:\n        size_hint_y: None\n        height: self.minimum_height\n        orientation: 'vertical'\n        canvas.before:\n            Color:\n                rgba: 0, 0, 0, .35\n            Rectangle:\n                pos: self.x + 20, self.y\n                size: self.width - 40, self.height\n        Spacer20:\n        Image:\n            source: root.source\n            allow_stretch: True\n            size_hint_y: None\n            height: dp(120)\n        Spacer20:\n    Spacer20:\n\n\n<CircularButton>:\n    size_hint: None, None\n    size: dp(120), dp(120)\n    text: ''\n    background_color: None\n    canvas.before:\n        Color:\n            rgba: .34, .34, .34, 1\n        Ellipse:\n            pos: self.pos\n            size: self.size\n    canvas:\n        Color:\n            rgba:\n                root.background_color \\\n                if root.background_color \\\n                else (1., 0., 1., .65)  # purple\n        Ellipse:\n            pos: self.x + dp(2), self.y + dp(2)\n            size: self.width - dp(4), self.height - dp(4)\n    Label:\n        text: root.text\n        pos: root.pos\n        size_hint: None, None\n        size: root.size\n\n\n<ErrorPopup@Popup>:\n    title: 'Error'\n    size_hint: 0.75, 0.75\n    Label:\n        text: root.error_text"
  },
  {
    "path": "testapps/on_device_unit_tests/test_app/widgets.py",
    "content": "# -*- coding: utf-8 -*-\n\nfrom kivy.properties import StringProperty\nfrom kivy.uix.boxlayout import BoxLayout\nfrom kivy.uix.popup import Popup\nfrom kivy.uix.behaviors.button import ButtonBehavior\nfrom kivy.uix.widget import Widget\nfrom kivy.vector import Vector\nfrom tools import load_kv_from\n\nload_kv_from('widgets.kv')\n\n\nclass Spacer20(Widget):\n    pass\n\n\nclass TestImage(BoxLayout):\n    text = StringProperty()\n    source = StringProperty()\n\n\nclass CircularButton(ButtonBehavior, Widget):\n    def collide_point(self, x, y):\n        return Vector(x, y).distance(self.center) <= self.width / 2\n\n\nclass ErrorPopup(Popup):\n    error_text = StringProperty('')\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_qt/recipes/PySide6/__init__.py",
    "content": "# Copyright (C) 2023 The Qt Company Ltd.\n# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only\n\nimport shutil\nimport zipfile\nfrom os.path import join\nfrom pathlib import Path\n\nfrom pythonforandroid.logger import info\nfrom pythonforandroid.recipe import PythonRecipe\n\n\nclass PySideRecipe(PythonRecipe):\n    version = '6.6.0a1'\n    # This will download the aarch64 wheel from the Qt servers.\n    # This wheel is only for testing purposes. This test will be update when PySide releases\n    # official PySide6 Android wheels.\n    url = (\"https://download.qt.io/snapshots/ci/pyside/test/Android/aarch64/\"\n           \"PySide6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl\")\n    wheel_name = 'PySide6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl'\n    depends = [\"shiboken6\"]\n    call_hostpython_via_targetpython = False\n    install_in_hostpython = False\n\n    def build_arch(self, arch):\n        \"\"\"Unzip the wheel and copy into site-packages of target\"\"\"\n\n        self.wheel_path = join(self.ctx.packages_path, self.name, self.wheel_name)\n        info(\"Copying libc++_shared.so from SDK to be loaded on startup\")\n        libcpp_path = f\"{self.ctx.ndk.sysroot_lib_dir}/{arch.command_prefix}/libc++_shared.so\"\n        shutil.copyfile(libcpp_path, Path(self.ctx.get_libs_dir(arch.arch)) / \"libc++_shared.so\")\n\n        info(f\"Installing {self.name} into site-packages\")\n        with zipfile.ZipFile(self.wheel_path, \"r\") as zip_ref:\n            info(\"Unzip wheels and copy into {}\".format(self.ctx.get_python_install_dir(arch.arch)))\n            zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))\n\n        lib_dir = Path(f\"{self.ctx.get_python_install_dir(arch.arch)}/PySide6/Qt/lib\")\n\n        info(\"Copying Qt libraries to be loaded on startup\")\n        shutil.copytree(lib_dir, self.ctx.get_libs_dir(arch.arch), dirs_exist_ok=True)\n        shutil.copyfile(lib_dir.parent.parent / \"libpyside6.abi3.so\",\n                        Path(self.ctx.get_libs_dir(arch.arch)) / \"libpyside6.abi3.so\")\n\n        shutil.copyfile(lib_dir.parent.parent / \"QtCore.abi3.so\",\n                        Path(self.ctx.get_libs_dir(arch.arch)) / \"QtCore.abi3.so\")\n\n        shutil.copyfile(lib_dir.parent.parent / \"QtWidgets.abi3.so\",\n                        Path(self.ctx.get_libs_dir(arch.arch)) / \"QtWidgets.abi3.so\")\n\n        shutil.copyfile(lib_dir.parent.parent / \"QtGui.abi3.so\",\n                        Path(self.ctx.get_libs_dir(arch.arch)) / \"QtGui.abi3.so\")\n\n        plugin_path = (lib_dir.parent / \"plugins\" / \"platforms\" /\n                       f\"libplugins_platforms_qtforandroid_{arch.arch}.so\")\n\n        if plugin_path.exists():\n            shutil.copyfile(plugin_path,\n                            (Path(self.ctx.get_libs_dir(arch.arch)) /\n                             f\"libplugins_platforms_qtforandroid_{arch.arch}.so\"))\n\n\nrecipe = PySideRecipe()\n"
  },
  {
    "path": "testapps/on_device_unit_tests/test_qt/recipes/shiboken6/__init__.py",
    "content": "# Copyright (C) 2023 The Qt Company Ltd.\n# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only\n\nimport shutil\nimport zipfile\nfrom os.path import join\nfrom pathlib import Path\n\nfrom pythonforandroid.logger import info\nfrom pythonforandroid.recipe import PythonRecipe\n\n\nclass ShibokenRecipe(PythonRecipe):\n    version = '6.6.0a1'\n    # This will download the aarch64 wheel from the Qt servers.\n    # This wheel is only for testing purposes. This test will be update when PySide releases\n    # official shiboken6 Android wheels.\n    url = (\"https://download.qt.io/snapshots/ci/pyside/test/Android/aarch64/\"\n           \"shiboken6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl\")\n    wheel_name = 'shiboken6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl'\n\n    call_hostpython_via_targetpython = False\n    install_in_hostpython = False\n\n    def build_arch(self, arch):\n        ''' Unzip the wheel and copy into site-packages of target'''\n\n        self.wheel_path = join(self.ctx.packages_path, self.name, self.wheel_name)\n        info('Installing {} into site-packages'.format(self.name))\n        with zipfile.ZipFile(self.wheel_path, 'r') as zip_ref:\n            info('Unzip wheels and copy into {}'.format(self.ctx.get_python_install_dir(arch.arch)))\n            zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))\n\n        lib_dir = Path(f\"{self.ctx.get_python_install_dir(arch.arch)}/shiboken6\")\n        shutil.copyfile(lib_dir / \"libshiboken6.abi3.so\",\n                        Path(self.ctx.get_libs_dir(arch.arch)) / \"libshiboken6.abi3.so\")\n\n\nrecipe = ShibokenRecipe()\n"
  },
  {
    "path": "testapps/setup_testapp_python3_sqlite_openssl.py",
    "content": "from setuptools import setup, find_packages\n\noptions = {'apk': {'requirements': 'requests,peewee,sdl2,pyjnius,kivy,python3',\n                   'android-api': 36,\n                   'ndk-api': 21,\n                   'bootstrap': 'sdl2',\n                   'dist-name': 'bdisttest_python3_sqlite_openssl_googlendk',\n                   'ndk-version': '10.3.2',\n                   'arch': 'armeabi-v7a',\n                   'permissions': ['INTERNET', 'VIBRATE'],\n                   }}\n\nsetup(\n    name='testapp_python3_sqlite_openssl_googlendk',\n    version='1.1',\n    description='p4a setup.py test',\n    author='Alexander Taylor',\n    author_email='alexanderjohntaylor@gmail.com',\n    packages=find_packages(),\n    options=options,\n    package_data={'testapp_sqlite_openssl': ['*.py', '*.png']}\n)\n"
  },
  {
    "path": "testapps/setup_vispy.py",
    "content": "from setuptools import setup, find_packages\n\noptions = {'apk': {'debug': None,\n                   'requirements': 'python3,vispy',\n                   'blacklist-requirements': 'openssl,sqlite3',\n                   'android-api': 33,\n                   'ndk-api': 21,\n                   'bootstrap': 'empty',\n                   'ndk-dir': '/home/asandy/android/android-ndk-r17c',\n                   'dist-name': 'bdisttest',\n                   'ndk-version': '10.3.2',\n                   'permission': 'VIBRATE',\n                   }}\n\npackage_data = {'': ['*.py',\n                     '*.png']\n                }\n\npackages = find_packages()\nprint('packages are', packages)\n\nsetup(\n    name='testapp_vispy',\n    version='1.1',\n    description='p4a setup.py test',\n    author='Alexander Taylor',\n    author_email='alexanderjohntaylor@gmail.com',\n    packages=find_packages(),\n    options=options,\n    package_data={'testapp_vispy': ['*.py', '*.png']}\n)\n"
  },
  {
    "path": "testapps/testapp_sqlite_openssl/main.py",
    "content": "print('main.py was successfully called')\n\nimport os\nprint('imported os')\n\n\nprint('this dir is', os.path.abspath(os.curdir))\n\nprint('contents of this dir', os.listdir('./'))\n\nimport sys\nprint('pythonpath is', sys.path)\n\nimport kivy\nprint('imported kivy')\nprint('file is', kivy.__file__)\n\nfrom kivy.app import App\n\nfrom kivy.lang import Builder\nfrom kivy.properties import StringProperty\n\nfrom kivy.uix.popup import Popup\nfrom kivy.clock import Clock\n\nprint('Imported kivy')\nfrom kivy.utils import platform\nprint('platform is', platform)\n\nimport peewee\nimport requests\nimport sqlite3\n\n\ntry:\n    inclemnet = requests.get('http://inclem.net/')\n    print('got inclem.net request')\nexcept:\n    inclemnet = 'failed inclemnet'\n\ntry:\n    kivy = requests.get('https://kivy.org/')\n    print('got kivy request (https)')\nexcept:\n    kivy = 'failed kivy'\n\nfrom peewee import *\ndb = SqliteDatabase('test.db')\n\nclass Person(Model):\n    name = CharField()\n    birthday = DateField()\n    is_relative = BooleanField()\n\n    class Meta:\n        database = db\n\n    def __repr__(self):\n        return '<Person: {}, {}>'.format(self.name, self.birthday)\n\n    def __str__(self):\n        return repr(self)\n\ndb.connect()\ntry:\n    db.create_tables([Person])\nexcept:\n    import traceback\n    traceback.print_exc()\n\nimport random\nfrom datetime import date\ntest_person = Person(name='person{}'.format(random.randint(0, 1000)),\n                     birthday=date(random.randint(1900, 2000), random.randint(1, 9), random.randint(1, 20)),\n                     is_relative=False)\ntest_person.save()\n\n\nkv = '''\n#:import Metrics kivy.metrics.Metrics\n#:import sys sys\n\n<FixedSizeButton@Button>:\n    size_hint_y: None\n    height: dp(60)\n\n\nScrollView:\n    GridLayout:\n        cols: 1\n        size_hint_y: None\n        height: self.minimum_height\n        FixedSizeButton:\n            text: 'test pyjnius'\n            on_press: app.test_pyjnius()\n        Label:\n            height: self.texture_size[1]\n            size_hint_y: None\n            text_size: self.size[0], None\n            markup: True\n            text: 'kivy request: {}\\\\ninclemnet request: {}'.format(app.kivy_request, app.inclemnet_request)\n            halign: 'center'\n        Label:\n            height: self.texture_size[1]\n            size_hint_y: None\n            text_size: self.size[0], None\n            markup: True\n            text: 'people: {}'.format(app.people)\n            halign: 'center'\n        Image:\n            keep_ratio: False\n            allow_stretch: True\n            source: 'colours.png'\n            size_hint_y: None\n            height: dp(100)\n        Label:\n            height: self.texture_size[1]\n            size_hint_y: None\n            font_size: 100\n            text_size: self.size[0], None\n            markup: True\n            text: '[b]Kivy[/b] on [b]SDL2[/b] on [b]Android[/b]!'\n            halign: 'center'\n        Label:\n            height: self.texture_size[1]\n            size_hint_y: None\n            text_size: self.size[0], None\n            markup: True\n            text: sys.version\n            halign: 'center'\n            padding_y: dp(10)\n        Widget:\n            size_hint_y: None\n            height: 20\n        Label:\n            height: self.texture_size[1]\n            size_hint_y: None\n            font_size: 50\n            text_size: self.size[0], None\n            markup: True\n            text: 'dpi: {}\\\\ndensity: {}\\\\nfontscale: {}'.format(Metrics.dpi, Metrics.density, Metrics.fontscale)\n            halign: 'center'\n        FixedSizeButton:\n            text: 'test ctypes'\n            on_press: app.test_ctypes()\n        FixedSizeButton:\n            text: 'test numpy'\n            on_press: app.test_numpy()\n        Widget:\n            size_hint_y: None\n            height: 1000\n            on_touch_down: print('touched at', args[-1].pos)\n\n<ErrorPopup>:\n    title: 'Error' \n    size_hint: 0.75, 0.75\n    Label:\n        text: root.error_text\n'''\n\n\nclass ErrorPopup(Popup):\n    error_text = StringProperty('')\n\ndef raise_error(error):\n    print('ERROR:',  error)\n    ErrorPopup(error_text=error).open()\n\nclass TestApp(App):\n\n    kivy_request = kivy\n    inclemnet_request = inclemnet\n\n    people = ', '.join(map(str, list(Person.select())))\n\n    def build(self):\n        root = Builder.load_string(kv)\n        Clock.schedule_interval(self.print_something, 2)\n        # Clock.schedule_interval(self.test_pyjnius, 5)\n        print('testing metrics')\n        from kivy.metrics import Metrics\n        print('dpi is', Metrics.dpi)\n        print('density is', Metrics.density)\n        print('fontscale is', Metrics.fontscale)\n        return root\n\n    def print_something(self, *args):\n        print('App print tick', Clock.get_boottime())\n\n    def on_pause(self):\n        return True\n\n    def test_pyjnius(self, *args):\n        try:\n            from jnius import autoclass, cast\n        except ImportError:\n            raise_error('Could not import pyjnius')\n            return\n        print('Attempting to vibrate with pyjnius')\n        ANDROID_VERSION = autoclass('android.os.Build$VERSION')\n        SDK_INT = ANDROID_VERSION.SDK_INT\n\n        Context = autoclass(\"android.content.Context\")\n        PythonActivity = autoclass('org.kivy.android.PythonActivity')\n        activity = PythonActivity.mActivity\n\n        vibrator_service = activity.getSystemService(Context.VIBRATOR_SERVICE)\n        vibrator = cast(\"android.os.Vibrator\", vibrator_service)\n\n        if vibrator and SDK_INT >= 26:\n            print(\"Using android's `VibrationEffect` (SDK >= 26)\")\n            VibrationEffect = autoclass(\"android.os.VibrationEffect\")\n            vibrator.vibrate(\n                VibrationEffect.createOneShot(\n                    1000, VibrationEffect.DEFAULT_AMPLITUDE,\n                ),\n            )\n        elif vibrator:\n            print(\"Using deprecated android's vibrate (SDK < 26)\")\n            vibrator.vibrate(1000)\n        else:\n            print('Something happened...vibrator service disabled?')\n\n    def test_ctypes(self, *args):\n        pass\n            \n    def test_numpy(self, *args):\n        import numpy\n\n        print(numpy.zeros(5))\n        print(numpy.arange(5))\n        print(numpy.random.random((3, 3)))\n                    \n\nTestApp().run()\n"
  },
  {
    "path": "testapps/testapp_vispy/main.py",
    "content": "\"\"\"\nDemonstration of Tube\n\"\"\"\n\nprint('testing!')\nimport ctypes\nprint('imported ctypes')\nprint(ctypes.__dict__)\nprint('dict done')\nimport ctypes.util\nprint('imported util')\nprint(ctypes.util.find_library)\n\nimport vispy\n# vispy.set_log_level('debug')\n\nimport sys\nfrom vispy import scene\nfrom vispy.geometry.torusknot import TorusKnot\n\nfrom colorsys import hsv_to_rgb\nimport numpy as np\n\ncanvas = scene.SceneCanvas(keys='interactive', bgcolor='white')\ncanvas.unfreeze()\ncanvas.view = canvas.central_widget.add_view()\n\npoints1 = TorusKnot(5, 3).first_component[:-1]\npoints1[:, 0] -= 20.\npoints1[:, 2] -= 15.\n\npoints2 = points1.copy()\npoints2[:, 2] += 30.\n\npoints3 = points1.copy()\npoints3[:, 0] += 41.\npoints3[:, 2] += 30\n\npoints4 = points1.copy()\npoints4[:, 0] += 41.\n\ncolors = np.linspace(0, 1, len(points1))\ncolors = np.array([hsv_to_rgb(c, 1, 1) for c in colors])\n\nvertex_colors = np.random.random(8 * len(points1))\nvertex_colors = np.array([hsv_to_rgb(c, 1, 1) for c in vertex_colors])\n\nl1 = scene.visuals.Tube(points1,\n                        shading='flat',\n                        color=colors,  # this is overridden by\n                                       # the vertex_colors argument\n                        vertex_colors=vertex_colors,\n                        tube_points=8)\n\nl2 = scene.visuals.Tube(points2,\n                        color=['red', 'green', 'blue'],\n                        shading='smooth',\n                        tube_points=8)\n\nl3 = scene.visuals.Tube(points3,\n                        color=colors,\n                        shading='flat',\n                        tube_points=8,\n                        closed=True)\n\nl4 = scene.visuals.Tube(points4,\n                        color=colors,\n                        shading='flat',\n                        tube_points=8,\n                        mode='lines')\n\ncanvas.view.add(l1)\ncanvas.view.add(l2)\ncanvas.view.add(l3)\ncanvas.view.add(l4)\ncanvas.view.camera = scene.TurntableCamera()\n# tube does not expose its limits yet\ncanvas.view.camera.set_range((-20, 20), (-20, 20), (-20, 20))\ncanvas.show()\n\nif __name__ == '__main__':\n    if sys.flags.interactive != 1:\n        canvas.app.run()\n"
  },
  {
    "path": "testapps/testlauncher_setup/sdl2.py",
    "content": "from setuptools import setup\n\noptions = {'apk': {'debug': None,\n                   'bootstrap': 'sdl2',\n                   'launcher': None,\n                   'requirements': (\n                        'python3,sdl2,android,'\n                        'sqlite3,docutils,pygments,kivy,pyjnius,plyer,'\n                        'cymunk,lxml,pil,openssl,pyopenssl,'\n                        'twisted'),  # audiostream, ffmpeg, numpy\n                   'android-api': 14,\n                   'dist-name': 'launchertest_sdl2',\n                   'name': 'TestLauncher-sdl2',\n                   'package': 'org.kivy.testlauncher_sdl2',\n                   'permissions': [\n                        'ACCESS_COARSE_LOCATION', 'ACCESS_FINE_LOCATION',\n                        'BLUETOOTH', 'BODY_SENSORS', 'CAMERA', 'INTERNET',\n                        'NFC', 'READ_EXTERNAL_STORAGE', 'RECORD_AUDIO',\n                        'USE_FINGERPRINT', 'VIBRATE', 'WAKE_LOCK',\n                        'WRITE_EXTERNAL_STORAGE']\n                   }}\n\nsetup(\n    name='testlauncher_sdl2',\n    version='1.0',\n    description='p4a sdl2.py apk',\n    author='Peter Badida',\n    options=options\n)\n"
  },
  {
    "path": "testapps/testlauncherreboot_setup/sdl2.py",
    "content": "'''\nClone Python implementation of Kivy Launcher from kivy/kivy-launcher repo,\ninstall deps specified in the OPTIONS['apk']['requirements'] and put it\nto a dist named OPTIONS['apk']['dist-name'].\n\nTested with P4A Dockerfile at 5fc5241e01fbbc2b23b3749f53ab48f22239f4fc,\nkivy-launcher at ad5c5c6e886a310bf6dd187e992df972864d1148 on Windows 8.1\nwith Docker for Windows and running on Samsung Galaxy Note 9, Android 8.1.\n\ndocker run \\\n    --interactive \\\n    --tty \\\n    -v \"/c/Users/.../python-for-android/testapps\":/home/user/testapps \\\n    -v \".../python-for-android/pythonforandroid\":/home/user/pythonforandroid \\\n    p4a sh -c '\\\n        . venv/bin/activate \\\n        && cd testapps/testlauncherreboot_setup \\\n        && python sdl2.py apk \\\n            --sdk-dir $ANDROID_SDK_HOME \\\n            --ndk-dir $ANDROID_NDK_HOME'\n'''\n\n# pylint: disable=import-error,no-name-in-module\nfrom subprocess import Popen\nfrom os import listdir\nfrom os.path import join, dirname, abspath, exists\nfrom pprint import pprint\nfrom setuptools import setup, find_packages\n\nROOT = dirname(abspath(__file__))\nLAUNCHER = join(ROOT, 'launcherapp')\n\nif not exists(LAUNCHER):\n    PROC = Popen([\n        'git', 'clone',\n        'https://github.com/kivy/kivy-launcher',\n        LAUNCHER\n    ])\n    PROC.communicate()\n    assert PROC.returncode == 0, PROC.returncode\n\n    pprint(listdir(LAUNCHER))\n    pprint(listdir(ROOT))\n\n\nOPTIONS = {\n    'apk': {\n        'debug': None,\n        'bootstrap': 'sdl2',\n        'requirements': (\n            'python3,sdl2,kivy,android,pyjnius,plyer'\n        ),\n        # 'sqlite3,docutils,pygments,'\n        # 'cymunk,lxml,pil,openssl,pyopenssl,'\n        # 'twisted,audiostream,ffmpeg,numpy'\n\n        'android-api': 36,\n        'ndk-api': 21,\n        'dist-name': 'bdisttest_python3launcher_sdl2_googlendk',\n        'name': 'TestLauncherPy3-sdl2',\n        'package': 'org.kivy.testlauncherpy3_sdl2_googlendk',\n        'ndk-version': '10.3.2',\n        'arch': 'armeabi-v7a',\n        'permissions': [\n            'ACCESS_COARSE_LOCATION', 'ACCESS_FINE_LOCATION',\n            'BLUETOOTH', 'BODY_SENSORS', 'CAMERA', 'INTERNET',\n            'NFC', 'READ_EXTERNAL_STORAGE', 'RECORD_AUDIO',\n            'USE_FINGERPRINT', 'VIBRATE', 'WAKE_LOCK',\n            'WRITE_EXTERNAL_STORAGE'\n        ]\n    }\n}\n\nPACKAGE_DATA = {\n    'launcherapp': [\n        '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',\n    ],\n    'launcherapp/art': [\n        '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',\n    ],\n    'launcherapp/art/fontello': [\n        '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',\n    ],\n    'launcherapp/data': [\n        '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',\n    ],\n    'launcherapp/launcher': [\n        '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',\n    ]\n}\n\nPACKAGES = find_packages()\nprint('packages are', PACKAGES)\n\nsetup(\n    name='testlauncherpy3_sdl2_googlendk',\n    version='1.0',\n    description='p4a sdl2.py apk',\n    author='Peter Badida',\n    author_email='keyweeusr@gmail.com',\n    packages=find_packages(),\n    options=OPTIONS,\n    package_data=PACKAGE_DATA\n)\n"
  },
  {
    "path": "tests/recipes/recipe_ctx.py",
    "content": "import os\n\nfrom pythonforandroid.bootstrap import Bootstrap\nfrom pythonforandroid.distribution import Distribution\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.build import Context\nfrom pythonforandroid.archs import ArchAarch_64\nfrom pythonforandroid.androidndk import AndroidNDK\n\n\nclass RecipeCtx:\n    \"\"\"\n    An base class for unit testing a recipe. This will create a context so we\n    can test any recipe using this context. Implement `setUp` and `tearDown`\n    methods used by unit testing.\n    \"\"\"\n\n    ctx = None\n    arch = None\n    recipe = None\n\n    recipe_name = \"\"\n    \"The name of the recipe to test.\"\n\n    recipes = []\n    \"\"\"A List of recipes to pass to `Distribution.get_distribution`. Should\n    contain the target recipe to test as well as a python recipe.\"\"\"\n    recipe_build_order = []\n    \"\"\"A recipe_build_order which should take into account the recipe we want\n    to test as well as the possible dependent recipes\"\"\"\n\n    TEST_ARCH = 'arm64-v8a'\n\n    def setUp(self):\n        self.ctx = Context()\n        self.ctx.ndk_api = 21\n        self.ctx.android_api = 27\n        self.ctx._sdk_dir = \"/opt/android/android-sdk\"\n        self.ctx._ndk_dir = \"/opt/android/android-ndk\"\n        self.ctx.ndk = AndroidNDK(self.ctx._ndk_dir)\n        self.ctx.setup_dirs(os.getcwd())\n        self.ctx.bootstrap = Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        self.ctx.bootstrap.distribution = Distribution.get_distribution(\n            self.ctx, name=\"sdl2\", recipes=self.recipes, archs=[self.TEST_ARCH],\n        )\n        self.ctx.recipe_build_order = self.recipe_build_order\n        self.ctx.python_recipe = Recipe.get_recipe(\"python3\", self.ctx)\n        self.arch = ArchAarch_64(self.ctx)\n        self.ctx.ndk_sysroot = f'{self.ctx._ndk_dir}/sysroot'\n        self.ctx.ndk_include_dir = f'{self.ctx.ndk_sysroot}/usr/include'\n        self.recipe = Recipe.get_recipe(self.recipe_name, self.ctx)\n\n    def tearDown(self):\n        self.ctx = None\n"
  },
  {
    "path": "tests/recipes/recipe_lib_test.py",
    "content": "from unittest import mock\nfrom platform import system\nfrom tests.recipes.recipe_ctx import RecipeCtx\n\n\nclass BaseTestForMakeRecipe(RecipeCtx):\n    \"\"\"\n    An unittest for testing any recipe using the standard build commands\n    (`configure/make`).\n\n    .. note:: Note that Some cmake recipe may need some more specific testing\n        ...but this should cover the basics.\n    \"\"\"\n\n    recipe_name = None\n    expected_compiler = (\n        \"{android_ndk}/toolchains/llvm/prebuilt/{system}-x86_64/bin/clang\"\n    )\n\n    sh_command_calls = [\"./configure\"]\n    \"\"\"The expected commands that the recipe runs via `sh.command`.\"\"\"\n\n    extra_env_flags = {}\n    \"\"\"\n    This must be a dictionary containing pairs of key (env var) and value.\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.recipes = [\"python3\", \"kivy\", self.recipe_name]\n        self.recipe_build_order = [\n            \"hostpython3\", self.recipe_name, \"python3\", \"sdl2\", \"kivy\"\n        ]\n        print(f\"We are testing recipe: {self.recipe_name}\")\n\n    @mock.patch(\"pythonforandroid.recipe.Recipe.check_recipe_choices\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_get_recipe_env(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_check_recipe_choices,\n    ):\n        \"\"\"\n        Test that get_recipe_env contains some expected arch flags and that\n        some internal methods has been called.\n        \"\"\"\n        mock_shutil_which.return_value = self.expected_compiler.format(\n                android_ndk=self.ctx._ndk_dir, system=system().lower()\n        )\n        mock_check_recipe_choices.return_value = sorted(\n            self.ctx.recipe_build_order\n        )\n\n        # make sure the arch flags are in env\n        env = self.recipe.get_recipe_env(self.arch)\n        for flag in self.arch.arch_cflags:\n            self.assertIn(flag, env[\"CFLAGS\"])\n        self.assertIn(\n            f\"-target {self.arch.target}\",\n            env[\"CFLAGS\"],\n        )\n\n        for flag, value in self.extra_env_flags.items():\n            self.assertIn(value, env[flag])\n\n        # make sure that the mocked methods are actually called\n        mock_ensure_dir.assert_called()\n        mock_shutil_which.assert_called()\n        mock_check_recipe_choices.assert_called()\n\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_current_directory,\n    ):\n        mock_shutil_which.return_value = self.expected_compiler.format(\n                android_ndk=self.ctx._ndk_dir, system=system().lower()\n        )\n\n        # Since the following mocks are dynamic,\n        # we mock it inside a Context Manager\n        with mock.patch(\n            f\"pythonforandroid.recipes.{self.recipe_name}.sh.Command\"\n        ) as mock_sh_command, mock.patch(\n            f\"pythonforandroid.recipes.{self.recipe_name}.sh.make\", create=True\n        ) as mock_make:\n            self.recipe.build_arch(self.arch)\n\n        # make sure that the mocked methods are actually called\n        for command in self.sh_command_calls:\n            self.assertIn(\n                mock.call(command),\n                mock_sh_command.mock_calls,\n            )\n        mock_make.assert_called()\n        mock_ensure_dir.assert_called()\n        mock_current_directory.assert_called()\n        mock_shutil_which.assert_called()\n\n\nclass BaseTestForCmakeRecipe(BaseTestForMakeRecipe):\n    \"\"\"\n    An unittest for testing any recipe using `cmake`. It inherits from\n    `BaseTestForMakeRecipe` but we override the build method to match the cmake\n    build method.\n\n    .. note:: Note that Some cmake recipe may need some more specific testing\n        ...but this should cover the basics.\n    \"\"\"\n\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_current_directory,\n    ):\n        mock_shutil_which.return_value = self.expected_compiler.format(\n                android_ndk=self.ctx._ndk_dir, system=system().lower()\n        )\n\n        # Since the following mocks are dynamic,\n        # we mock it inside a Context Manager\n        with mock.patch(\n            f\"pythonforandroid.recipes.{self.recipe_name}.sh.make\", create=True\n        ) as mock_make, mock.patch(\n            f\"pythonforandroid.recipes.{self.recipe_name}.sh.cmake\", create=True\n        ) as mock_cmake:\n            self.recipe.build_arch(self.arch)\n\n        # make sure that the mocked methods are actually called\n        mock_cmake.assert_called()\n        mock_make.assert_called()\n        mock_ensure_dir.assert_called()\n        mock_current_directory.assert_called()\n        mock_shutil_which.assert_called()\n"
  },
  {
    "path": "tests/recipes/test_freetype.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestFreetypeRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.freetype`\n    \"\"\"\n    recipe_name = \"freetype\"\n    sh_command_calls = [\"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_gevent.py",
    "content": "import unittest\nfrom unittest.mock import patch\nfrom tests.recipes.recipe_ctx import RecipeCtx\n\n\nclass TestGeventRecipe(RecipeCtx, unittest.TestCase):\n\n    recipe_name = \"gevent\"\n\n    def test_get_recipe_env(self):\n        \"\"\"\n        Makes sure `get_recipe_env()` sets compilation flags properly.\n        \"\"\"\n        mocked_cflags = (\n            '-DANDROID -fomit-frame-pointer -D__ANDROID_API__=27 -mandroid '\n            '-isystem /path/to/isystem '\n            '-I/path/to/include1 '\n            '-isysroot /path/to/sysroot '\n            '-I/path/to/include2 '\n            '-march=armv7-a -mfloat-abi=softfp -mfpu=vfp -mthumb '\n            '-I/path/to/python3-libffi-openssl/include'\n        )\n        mocked_ldflags = (\n            ' --sysroot /path/to/sysroot '\n            '-lm '\n            '-L/path/to/library1 '\n            '-L/path/to/library2 '\n            '-lpython3.7m '\n            # checks the regex doesn't parse `python3-libffi-openssl` as a `-libffi`\n            '-L/path/to/python3-libffi-openssl/library3 '\n        )\n        mocked_ldlibs = ' -lm'\n        mocked_env = {\n            'CFLAGS': mocked_cflags,\n            'LDFLAGS': mocked_ldflags,\n            'LDLIBS': mocked_ldlibs,\n        }\n        with patch('pythonforandroid.recipe.PyProjectRecipe.get_recipe_env') as m_get_recipe_env:\n            m_get_recipe_env.return_value = mocked_env\n            env = self.recipe.get_recipe_env(self.arch)\n        expected_cflags = (\n            ' -fomit-frame-pointer -mandroid -isystem /path/to/isystem'\n            ' -isysroot /path/to/sysroot'\n            ' -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -mthumb'\n        )\n        expected_cppflags = (\n            '-DANDROID -D__ANDROID_API__=27 '\n            '-I/path/to/include1 '\n            '-I/path/to/include2 '\n            '-I/path/to/python3-libffi-openssl/include'\n        )\n        expected_ldflags = (\n            ' --sysroot /path/to/sysroot'\n            ' -L/path/to/library1'\n            ' -L/path/to/library2'\n            ' -L/path/to/python3-libffi-openssl/library3 '\n        )\n        expected_ldlibs = mocked_ldlibs\n        expected_libs = '-lm -lpython3.7m -lm'\n        expected_command_prefix = 'aarch64-linux-android'\n        expected_env = {\n            'CFLAGS': expected_cflags,\n            'CPPFLAGS': expected_cppflags,\n            'LDFLAGS': expected_ldflags,\n            'LDLIBS': expected_ldlibs,\n            'LIBS': expected_libs,\n            'COMMAND_PREFIX': expected_command_prefix,\n        }\n        self.assertEqual(expected_env, env)\n"
  },
  {
    "path": "tests/recipes/test_harfbuzz.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestHarfbuzzRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.harfbuzz`\n    \"\"\"\n    recipe_name = \"harfbuzz\"\n    sh_command_calls = [\"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_hostpython3.py",
    "content": "import unittest\n\nfrom os.path import join\nfrom unittest import mock\n\nfrom pythonforandroid.recipes.hostpython3 import (\n    HOSTPYTHON_VERSION_UNSET_MESSAGE, SETUP_DIST_NOT_FIND_MESSAGE,\n)\nfrom pythonforandroid.util import BuildInterruptingException\nfrom tests.recipes.recipe_lib_test import RecipeCtx\n\n\nclass TestHostPython3Recipe(RecipeCtx, unittest.TestCase):\n    \"\"\"\n    TestCase for recipe :mod:`~pythonforandroid.recipes.hostpython3`\n    \"\"\"\n    recipe_name = \"hostpython3\"\n\n    def test_property__exe_name_no_version(self):\n        hostpython_version = self.recipe.version\n        self.recipe._version = None\n        with self.assertRaises(BuildInterruptingException) as e:\n            py_exe = self.recipe._exe_name  # noqa: F841\n        self.assertEqual(e.exception.args[0], HOSTPYTHON_VERSION_UNSET_MESSAGE)\n        # restore recipe's version or we will get failures with other test,\n        # since we share `self.recipe with all the tests of the class\n        self.recipe._version = hostpython_version\n\n    def test_property__exe_name(self):\n        self.assertEqual(self.recipe._exe_name, 'python3')\n\n    def test_property_python_exe(self):\n        self.assertEqual(\n            self.recipe.python_exe,\n            join(self.recipe.get_path_to_python(), 'python3')\n        )\n\n    @mock.patch(\"pythonforandroid.recipes.hostpython3.Path.exists\")\n    def test_should_build(self, mock_exists):\n        # test case for existing python exe which shouldn't trigger the build\n        mock_exists.return_value = True\n        self.assertFalse(self.recipe.should_build(self.arch))\n        # test case for existing python exe which should trigger the build\n        mock_exists.return_value = False\n        self.assertTrue(self.recipe.should_build(self.arch))\n\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.util.makedirs\")\n    def test_build_arch(self, mock_makedirs, mock_chdir):\n        \"\"\"\n        Test case for\n        :meth:`~pythonforandroid.recipes.python3.HostPython3Recipe.build_arch`,\n        where we simulate the build for Python 3.8+.\n        \"\"\"\n\n        with mock.patch(\n            \"pythonforandroid.recipes.hostpython3.Path.exists\"\n        ) as mock_path_exists, mock.patch(\n            \"pythonforandroid.recipes.hostpython3.sh.Command\"\n        ) as mock_sh_command, mock.patch(\n            \"pythonforandroid.recipes.hostpython3.sh.make\"\n        ) as mock_make, mock.patch(\n            \"pythonforandroid.recipes.hostpython3.Path.is_file\"\n        ) as mock_path_isfile, mock.patch(\n            \"pythonforandroid.recipes.hostpython3.sh.cp\"\n        ) as mock_sh_cp:\n            # here we simulate the expected behaviour for Python 3.8+\n            mock_path_exists.side_effect = [\n                False,  # \"config.status\" not exists, so we trigger.configure\n                False,  # \"Modules/Setup.dist\" shouldn't exist (3.8+ case)\n                True,  # \"Modules/Setup\" exists, so we skip raise exception\n            ]\n            self.recipe.build_arch(self.arch)\n\n        # make sure that the mocked methods are actually called\n        mock_path_exists.assert_called()\n        recipe_src = self.recipe.get_build_dir(self.arch.arch)\n        self.assertIn(\n            mock.call(f\"{recipe_src}/configure\"),\n            mock_sh_command.mock_calls,\n        )\n        mock_make.assert_called()\n\n        exe = join(self.recipe.get_path_to_python(), 'python.exe')\n        mock_path_isfile.assert_called()\n\n        self.assertEqual(mock_sh_cp.call_count, 1)\n        mock_call_args, mock_call_kwargs = mock_sh_cp.call_args_list[0]\n        self.assertEqual(mock_call_args[0], exe)\n        self.assertEqual(mock_call_args[1], self.recipe.python_exe)\n        mock_makedirs.assert_called()\n        mock_chdir.assert_called()\n\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.util.makedirs\")\n    def test_build_arch_python_lower_than_3_8(self, mock_makedirs, mock_chdir):\n        \"\"\"\n        Test case for\n        :meth:`~pythonforandroid.recipes.python3.HostPython3Recipe.build_arch`,\n        where we simulate a Python 3.7 build. Here we copy an extra file:\n          - Modules/Setup.dist  -> Modules/Setup.\n\n        .. note:: We omit some checks because we already dit that at\n                  `test_build_arch`. Also we skip configure command for the\n                  same reason.\n        \"\"\"\n        with mock.patch(\n            \"pythonforandroid.recipes.hostpython3.Path.exists\"\n        ) as mock_path_exists, mock.patch(\n            \"pythonforandroid.recipes.hostpython3.sh.make\"\n        ) as mock_make, mock.patch(\n            \"pythonforandroid.recipes.hostpython3.Path.is_file\"\n        ) as mock_path_isfile, mock.patch(\n            \"pythonforandroid.recipes.hostpython3.sh.cp\"\n        ) as mock_sh_cp:\n            mock_path_exists.side_effect = [\n                True,  # simulate that \"config.status\" exists to skip configure\n                True,  # \"Modules/Setup.dist\" exist for Python 3.7\n                True,  # \"Modules/Setup\" exists...we skip raise exception\n            ]\n            self.recipe.build_arch(self.arch)\n\n        build_dir = join(\n            self.recipe.get_build_dir(self.arch.arch), self.recipe.build_subdir\n        )\n        # we expect two calls to copy command, The one described below and the\n        # copy of the python binary which already chechet at `test_build_arch`.\n        self.assertEqual(mock_sh_cp.call_count, 2)\n        mock_call_args, mock_call_kwargs = mock_sh_cp.call_args_list[0]\n        self.assertEqual(mock_call_args[0], \"Modules/Setup.dist\")\n        self.assertEqual(mock_call_args[1], join(build_dir, \"Modules/Setup\"))\n\n        mock_path_exists.assert_called()\n        mock_make.assert_called()\n        mock_path_isfile.assert_called()\n        mock_makedirs.assert_called()\n        mock_chdir.assert_called()\n\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.util.makedirs\")\n    def test_build_arch_setup_dist_exception(self, mock_makedirs, mock_chdir):\n        \"\"\"\n        Test case for\n        :meth:`~pythonforandroid.recipes.python3.HostPython3Recipe.build_arch`,\n        where we simulate that the sources hasn't Setup.dist file, which should\n        raise an exception.\n\n        .. note:: We skip configure command because already tested at\n                  `test_build_arch`.\n        \"\"\"\n        with mock.patch(\n            \"pythonforandroid.recipes.hostpython3.Path.exists\"\n        ) as mock_path_exists:\n            mock_path_exists.side_effect = [\n                True,  # simulate that \"config.status\" exists to skip configure\n                False,  # \"Modules/Setup.dist\" shouldn't exist (3.8+ case)\n                False,  # \"Modules/Setup\" doesn't exists...raise exception\n            ]\n\n            with self.assertRaises(BuildInterruptingException) as e:\n                self.recipe.build_arch(self.arch)\n        self.assertEqual(e.exception.args[0], SETUP_DIST_NOT_FIND_MESSAGE)\n        mock_makedirs.assert_called()\n        mock_chdir.assert_called()\n"
  },
  {
    "path": "tests/recipes/test_icu.py",
    "content": "import os\nimport unittest\nfrom unittest import mock\n\nfrom tests.recipes.recipe_ctx import RecipeCtx\nfrom pythonforandroid.recipes.icu import ICURecipe\n\n\nclass TestIcuRecipe(RecipeCtx, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.icu`\n    \"\"\"\n\n    recipe_name = \"icu\"\n\n    def test_url(self):\n        self.assertTrue(self.recipe.versioned_url.startswith(\"http\"))\n        self.assertIn(self.recipe.version.replace('.', '-'), self.recipe.versioned_url)\n\n    @mock.patch(\n        \"pythonforandroid.recipe.Recipe.url\", new_callable=mock.PropertyMock\n    )\n    def test_url_none(self, mock_url):\n        mock_url.return_value = None\n        self.assertIsNone(self.recipe.versioned_url)\n\n    def test_get_recipe_dir(self):\n        expected_dir = os.path.join(self.ctx.root_dir, \"recipes\", \"icu\")\n        self.assertEqual(self.recipe.get_recipe_dir(), expected_dir)\n\n    @mock.patch(\"pythonforandroid.util.makedirs\")\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.bootstrap.sh.Command\")\n    @mock.patch(\"pythonforandroid.recipes.icu.sh.make\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_sh_make,\n        mock_sh_command,\n        mock_chdir,\n        mock_makedirs,\n    ):\n        mock_shutil_which.return_value = os.path.join(\n            self.ctx._ndk_dir,\n            f\"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang\",\n        )\n        self.ctx.toolchain_version = \"4.9\"\n        self.recipe.build_arch(self.arch)\n\n        # We expect some calls to `sh.Command`\n        build_root = self.recipe.get_build_dir(self.arch.arch)\n        mock_sh_command.assert_has_calls(\n            [\n                mock.call(\n                    os.path.join(build_root, \"source\", \"runConfigureICU\")\n                ),\n                mock.call(os.path.join(build_root, \"source\", \"configure\")),\n            ],\n            any_order=True\n        )\n        mock_ensure_dir.assert_called()\n        mock_chdir.assert_called()\n        # we expect multiple calls to sh.make command\n        expected_host_cppflags = (\n            \"-O3 -fno-short-wchar -DU_USING_ICU_NAMESPACE=1 -fno-short-enums \"\n            \"-DU_HAVE_NL_LANGINFO_CODESET=0 -D__STDC_INT64__ -DU_TIMEZONE=0 \"\n            \"-DUCONFIG_NO_LEGACY_CONVERSION=1 \"\n            \"-DUCONFIG_NO_TRANSLITERATION=0 \"\n        )\n        for call_number, call in enumerate(mock_sh_make.call_args_list):\n            # here we expect to find the compile command  `make -j`in first and\n            # third calls, the others should be the  `make install` commands\n            is_host_build = call_number in [0, 1]\n            is_compile = call_number in [0, 2]\n            call_args, call_kwargs = call\n            self.assertTrue(\n                call_args[0].startswith(\"-j\" if is_compile else \"install\")\n            )\n            self.assertIn(\"_env\", call_kwargs)\n            if is_host_build:\n                self.assertIn(\n                    expected_host_cppflags, call_kwargs[\"_env\"][\"CPPFLAGS\"]\n                )\n            else:\n                self.assertNotIn(\n                    expected_host_cppflags, call_kwargs[\"_env\"][\"CPPFLAGS\"]\n                )\n        mock_makedirs.assert_called()\n\n        mock_shutil_which.assert_called_once()\n        self.assertEqual(\n            mock_shutil_which.call_args[0][0],\n            mock_shutil_which.return_value,\n        )\n\n    @mock.patch(\"pythonforandroid.recipes.icu.sh.cp\")\n    @mock.patch(\"pythonforandroid.util.makedirs\")\n    def test_install_libraries(self, mock_makedirs, mock_sh_cp):\n        self.recipe.install_libraries(self.arch)\n        mock_makedirs.assert_called()\n        mock_sh_cp.assert_called()\n\n    @mock.patch(\"pythonforandroid.recipes.icu.exists\")\n    def test_get_recipe_dir_with_local_recipes(self, mock_exists):\n        self.ctx.local_recipes = \"/home/user/p4a_local_recipes\"\n\n        # we don't use `self.recipe` because, somehow, the modified variable\n        # above is not updated in the `ctx` and makes the test fail...\n        recipe = ICURecipe()\n        recipe.ctx = self.ctx\n        recipe_dir = recipe.get_recipe_dir()\n\n        expected_dir = os.path.join(self.ctx.local_recipes, \"icu\")\n        self.assertEqual(recipe_dir, expected_dir)\n        mock_exists.assert_called_once_with(expected_dir)\n"
  },
  {
    "path": "tests/recipes/test_jpeg.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe\n\n\nclass TestJpegRecipe(BaseTestForCmakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.jpeg`\n    \"\"\"\n    recipe_name = \"jpeg\"\n"
  },
  {
    "path": "tests/recipes/test_leveldb.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe\n\n\nclass TestLeveldbRecipe(BaseTestForCmakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.leveldb`\n    \"\"\"\n    recipe_name = \"leveldb\"\n"
  },
  {
    "path": "tests/recipes/test_libbz2.py",
    "content": "import unittest\n\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibBz2Recipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"TestCase for recipe :mod:`~pythonforandroid.recipes.libbz2`.\"\"\"\n    recipe_name = \"libbz2\"\n    sh_command_calls = []\n\n    def test_get_library_includes(self):\n        \"\"\"\n        Test :meth:`~pythonforandroid.recipes.libbz2.get_library_includes`.\n        \"\"\"\n        self.assertEqual(\n            self.recipe.get_library_includes(self.arch),\n            f\" -I{self.recipe.get_build_dir(self.arch.arch)}\",\n        )\n\n    def test_get_library_ldflags(self):\n        \"\"\"\n        Test :meth:`~pythonforandroid.recipes.libbz2.get_library_ldflags`.\n        \"\"\"\n        self.assertEqual(\n            self.recipe.get_library_ldflags(self.arch),\n            f\" -L{self.recipe.get_build_dir(self.arch.arch)}\",\n        )\n\n    def test_link_libs_flags(self):\n        \"\"\"\n        Test :meth:`~pythonforandroid.recipes.libbz2.get_library_ldflags`.\n        \"\"\"\n        self.assertEqual(self.recipe.get_library_libs_flag(), \" -lbz2\")\n"
  },
  {
    "path": "tests/recipes/test_libcurl.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibcurlRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libcurl`\n    \"\"\"\n    recipe_name = \"libcurl\"\n    sh_command_calls = [\"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_libexpat.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibexpatRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libexpat`\n    \"\"\"\n    recipe_name = \"libexpat\"\n    sh_command_calls = [\"./buildconf.sh\", \"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_libffi.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibffiRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libffi`\n    \"\"\"\n    recipe_name = \"libffi\"\n    sh_command_calls = [\"./autogen.sh\", \"autoreconf\", \"./configure\"]\n\n    def test_get_include_dirs(self):\n        list_of_includes = self.recipe.get_include_dirs(self.arch)\n        self.assertIsInstance(list_of_includes, list)\n        self.assertTrue(list_of_includes[0].endswith(\"include\"))\n"
  },
  {
    "path": "tests/recipes/test_libgeos.py",
    "content": "import unittest\nfrom unittest import mock\nfrom tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe\n\n\nclass TestLibgeosRecipe(BaseTestForCmakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libgeos`\n    \"\"\"\n    recipe_name = \"libgeos\"\n\n    @mock.patch(\"pythonforandroid.util.makedirs\")\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_current_directory,\n        mock_makedirs,\n    ):\n        # We overwrite the base test method because we\n        # want to avoid any file/directory creation\n        super().test_build_arch()\n        # make sure that the mocked methods are actually called\n        mock_makedirs.assert_called()\n"
  },
  {
    "path": "tests/recipes/test_libiconv.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibiconvRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libiconv`\n    \"\"\"\n    recipe_name = \"libiconv\"\n    sh_command_calls = [\"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_liblzma.py",
    "content": "import unittest\nfrom os.path import join\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibLzmaRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"TestCase for recipe :mod:`~pythonforandroid.recipes.liblzma`.\"\"\"\n    recipe_name = \"liblzma\"\n    sh_command_calls = [\"./autogen.sh\", \"autoreconf\", \"./configure\"]\n\n    def test_get_library_includes(self):\n        \"\"\"\n        Test :meth:`~pythonforandroid.recipes.liblzma.get_library_includes`.\n        \"\"\"\n        recipe_build_dir = self.recipe.get_build_dir(self.arch.arch)\n        self.assertEqual(\n            self.recipe.get_library_includes(self.arch),\n            f\" -I{join(recipe_build_dir, 'p4a_install/include')}\",\n        )\n\n    def test_get_library_ldflags(self):\n        \"\"\"\n        Test :meth:`~pythonforandroid.recipes.liblzma.get_library_ldflags`.\n        \"\"\"\n        recipe_build_dir = self.recipe.get_build_dir(self.arch.arch)\n        self.assertEqual(\n            self.recipe.get_library_ldflags(self.arch),\n            f\" -L{join(recipe_build_dir, 'p4a_install/lib')}\",\n        )\n\n    def test_link_libs_flags(self):\n        \"\"\"\n        Test :meth:`~pythonforandroid.recipes.liblzma.get_library_libs_flag`.\n        \"\"\"\n        self.assertEqual(self.recipe.get_library_libs_flag(), \" -llzma\")\n\n    def test_install_dir_not_named_install(self):\n        \"\"\"\n        Tests that the install directory is not named ``install``.\n\n        liblzma already have a file named ``INSTALL`` in its source directory.\n        On case-insensitive filesystems, using a folder named ``install`` will\n        cause a conflict. (See issue: #2343).\n\n        WARNING: This test is quite flaky, but should be enough to\n        ensure that someone in the future will not accidentally rename\n        the install directory without seeing this test to fail.\n        \"\"\"\n        liblzma_install_dir = self.recipe.built_libraries[\"liblzma.so\"]\n\n        self.assertNotIn(\"install\", liblzma_install_dir.split(\"/\"))\n"
  },
  {
    "path": "tests/recipes/test_libogg.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLiboggRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libogg`\n    \"\"\"\n    recipe_name = \"libogg\"\n    sh_command_calls = [\"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_libpq.py",
    "content": "import unittest\nfrom unittest import mock\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibpqRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libpq`\n    \"\"\"\n    recipe_name = \"libpq\"\n    sh_command_calls = [\"./configure\"]\n\n    @mock.patch(\"pythonforandroid.recipes.libpq.sh.cp\")\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_current_directory,\n        mock_sh_cp,\n    ):\n        # We overwrite the base test method because we need to mock a little\n        # more with this recipe (`sh.cp`)\n        super().test_build_arch()\n        # make sure that the mocked methods are actually called\n        mock_sh_cp.assert_called()\n"
  },
  {
    "path": "tests/recipes/test_libsecp256k1.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibsecp256k1Recipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libsecp256k1`\n    \"\"\"\n    recipe_name = \"libsecp256k1\"\n    sh_command_calls = [\"./autogen.sh\", \"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_libshine.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibshineRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libshine`\n    \"\"\"\n    recipe_name = \"libshine\"\n    sh_command_calls = [\"./bootstrap\", \"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_libvorbis.py",
    "content": "import unittest\nfrom unittest import mock\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibvorbisRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libvorbis`\n    \"\"\"\n    recipe_name = \"libvorbis\"\n    sh_command_calls = [\"./configure\"]\n    extra_env_flags = {'CFLAGS': 'libogg/include'}\n\n    @mock.patch(\"pythonforandroid.recipes.libvorbis.sh.cp\")\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_current_directory,\n        mock_sh_cp,\n    ):\n        # We overwrite the base test method because we need to mock a little\n        # more with this recipe (`sh.cp`)\n        super().test_build_arch()\n        # make sure that the mocked methods are actually called\n        mock_sh_cp.assert_called()\n"
  },
  {
    "path": "tests/recipes/test_libvpx.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibVPXRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libvpx`\n    \"\"\"\n    recipe_name = \"libvpx\"\n    sh_command_calls = [\"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_libx264.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibx264Recipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libx264`\n    \"\"\"\n    recipe_name = \"libx264\"\n    sh_command_calls = [\"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_libxml2.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibxml2Recipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libxml2`\n    \"\"\"\n    recipe_name = \"libxml2\"\n    sh_command_calls = [\"./autogen.sh\", \"autoreconf\", \"./configure\"]\n    extra_env_flags = {\n        \"CONFIG_SHELL\": \"/bin/bash\",\n        \"SHELL\": \"/bin/bash\",\n    }\n"
  },
  {
    "path": "tests/recipes/test_libxslt.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestLibxsltRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.libxslt`\n    \"\"\"\n    recipe_name = \"libxslt\"\n    sh_command_calls = [\"./autogen.sh\", \"autoreconf\", \"./configure\"]\n    extra_env_flags = {\n        \"CONFIG_SHELL\": \"/bin/bash\",\n        \"SHELL\": \"/bin/bash\",\n        \"LIBS\": \"-lxml2 -lz -lm\",\n    }\n"
  },
  {
    "path": "tests/recipes/test_openal.py",
    "content": "import unittest\nfrom unittest import mock\nfrom tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe\n\n\nclass TestOpenalRecipe(BaseTestForCmakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.openal`\n    \"\"\"\n    recipe_name = \"openal\"\n\n    @mock.patch(\"pythonforandroid.recipes.openal.sh.cmake\", create=True)\n    @mock.patch(\"pythonforandroid.recipes.openal.sh.make\", create=True)\n    @mock.patch(\"pythonforandroid.recipes.openal.sh.cp\")\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_prebuild_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_current_directory,\n        mock_sh_cp,\n        mock_sh_make,\n        mock_sh_cmake,\n    ):\n        mock_shutil_which.return_value = (\n            \"/opt/android/android-ndk/toolchains/\"\n            \"llvm/prebuilt/linux-x86_64/bin/clang\"\n        )\n        self.recipe.build_arch(self.arch)\n\n        # make sure that the mocked methods are actually called\n        mock_ensure_dir.assert_called()\n        mock_current_directory.assert_called()\n        mock_shutil_which.assert_called()\n        mock_sh_cp.assert_called()\n        mock_sh_make.assert_called()\n        mock_sh_cmake.assert_called()\n\n    @mock.patch(\"pythonforandroid.recipes.openal.sh.cp\")\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_current_directory,\n        mock_sh_cp,\n    ):\n        # We overwrite the base test method because we need to mock a little\n        # more with this recipe.\n        super().test_build_arch()\n        # make sure that the mocked methods are actually called\n        mock_sh_cp.assert_called()\n"
  },
  {
    "path": "tests/recipes/test_openssl.py",
    "content": "import unittest\nfrom unittest import mock\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestOpensslRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.openssl`\n    \"\"\"\n\n    recipe_name = \"openssl\"\n    sh_command_calls = [\"perl\"]\n\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_current_directory,\n    ):\n        # We overwrite the base test method because we need to mock a little\n        # more with this recipe.\n        super().test_build_arch()\n\n    def test_include_flags(self):\n        inc = self.recipe.include_flags(self.arch)\n        build_dir = self.recipe.get_build_dir(self.arch)\n        for i in {\"include\", \"include/openssl\"}:\n            self.assertIn(f\"-I{build_dir}/{i}\", inc)\n\n    def test_link_flags(self):\n        build_dir = self.recipe.get_build_dir(self.arch)\n        self.assertEqual(\n            f\" -L{build_dir} -lcrypto -lssl\",\n            self.recipe.link_flags(self.arch),\n        )\n\n    def test_select_build_arch(self):\n        expected_build_archs = {\n            \"armeabi\": \"android\",\n            \"armeabi-v7a\": \"android-arm\",\n            \"arm64-v8a\": \"android-arm64\",\n            \"x86\": \"android-x86\",\n            \"x86_64\": \"android-x86_64\",\n        }\n        for arch in self.ctx.archs:\n            self.assertEqual(\n                expected_build_archs[arch.arch],\n                self.recipe.select_build_arch(arch),\n            )\n"
  },
  {
    "path": "tests/recipes/test_pandas.py",
    "content": "import unittest\n\nfrom os.path import join\nfrom unittest import mock\n\nfrom tests.recipes.recipe_lib_test import RecipeCtx\n\n\nclass TestPandasRecipe(RecipeCtx, unittest.TestCase):\n    \"\"\"\n    TestCase for recipe :mod:`~pythonforandroid.recipes.pandas`\n    \"\"\"\n    recipe_name = \"pandas\"\n\n    @mock.patch(\"pythonforandroid.recipe.Recipe.check_recipe_choices\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_get_recipe_env(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_check_recipe_choices,\n    ):\n        \"\"\"\n        Test that method\n        :meth:`~pythonforandroid.recipes.pandas.PandasRecipe.get_recipe_env`\n        returns the expected flags\n        \"\"\"\n\n        mock_shutil_which.return_value = (\n            \"/opt/android/android-ndk/toolchains/\"\n            \"llvm/prebuilt/linux-x86_64/bin/clang\"\n        )\n        mock_check_recipe_choices.return_value = sorted(\n            self.ctx.recipe_build_order\n        )\n        numpy_includes = join(\n            self.ctx.get_python_install_dir(self.arch.arch), \"numpy/_core/include\",\n        )\n        env = self.recipe.get_recipe_env(self.arch)\n        self.assertIn(numpy_includes, env[\"NUMPY_INCLUDES\"])\n        self.assertIn(\" -landroid\", env[\"LDFLAGS\"])\n\n        # make sure that the mocked methods are actually called\n        mock_ensure_dir.assert_called()\n        mock_shutil_which.assert_called()\n        mock_check_recipe_choices.assert_called()\n"
  },
  {
    "path": "tests/recipes/test_png.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForMakeRecipe\n\n\nclass TestPngRecipe(BaseTestForMakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.png`\n    \"\"\"\n    recipe_name = \"png\"\n    sh_command_calls = [\"./configure\"]\n"
  },
  {
    "path": "tests/recipes/test_pyicu.py",
    "content": "import unittest\nfrom unittest import mock\nfrom tests.recipes.recipe_ctx import RecipeCtx\nfrom pythonforandroid.recipe import Recipe\n\n\nclass TestPyIcuRecipe(RecipeCtx, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.pyicu`\n    \"\"\"\n    recipe_name = \"pyicu\"\n\n    @mock.patch(\"pythonforandroid.recipe.Recipe.check_recipe_choices\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_get_recipe_env(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_check_recipe_choices,\n    ):\n        \"\"\"\n        Test that method\n        :meth:`~pythonforandroid.recipes.pyicu.PyICURecipe.get_recipe_env`\n        returns the expected flags\n        \"\"\"\n        icu_recipe = Recipe.get_recipe(\"icu\", self.ctx)\n\n        mock_shutil_which.return_value = (\n            \"/opt/android/android-ndk/toolchains/\"\n            \"llvm/prebuilt/linux-x86_64/bin/clang\"\n        )\n        mock_check_recipe_choices.return_value = sorted(\n            self.ctx.recipe_build_order\n        )\n\n        expected_pyicu_libs = [\n            lib[3:-3] for lib in icu_recipe.built_libraries.keys()\n        ]\n        env = self.recipe.get_recipe_env(self.arch)\n        self.assertEqual(\":\".join(expected_pyicu_libs), env[\"PYICU_LIBRARIES\"])\n        self.assertIn(\"include/icu\", env[\"CPPFLAGS\"])\n        self.assertIn(\"icu4c/icu_build/lib\", env[\"LDFLAGS\"])\n\n        # make sure that the mocked methods are actually called\n        mock_ensure_dir.assert_called()\n        mock_shutil_which.assert_called()\n        mock_check_recipe_choices.assert_called()\n"
  },
  {
    "path": "tests/recipes/test_python3.py",
    "content": "import unittest\n\nfrom os.path import join\nfrom unittest import mock\n\nfrom pythonforandroid.recipes.python3 import (\n    NDK_API_LOWER_THAN_SUPPORTED_MESSAGE,\n)\nfrom pythonforandroid.util import BuildInterruptingException, build_platform\nfrom tests.recipes.recipe_lib_test import RecipeCtx\n\n\nclass TestPython3Recipe(RecipeCtx, unittest.TestCase):\n    \"\"\"\n    TestCase for recipe :mod:`~pythonforandroid.recipes.python3`\n    \"\"\"\n    recipe_name = \"python3\"\n    expected_compiler = (\n        f\"/opt/android/android-ndk/toolchains/\"\n        f\"llvm/prebuilt/{build_platform}/bin/clang\"\n    )\n\n    def test_property__libpython(self):\n        self.assertEqual(\n            self.recipe._libpython,\n            f'libpython{self.recipe.link_version}.so'\n        )\n\n    def test_include_root(self):\n        expected_include_dir = join(\n            self.recipe.get_build_dir(self.arch.arch), 'Include',\n        )\n        self.assertEqual(\n            expected_include_dir, self.recipe.include_root(self.arch.arch)\n        )\n\n    def test_link_root(self):\n        expected_link_root = join(\n            self.recipe.get_build_dir(self.arch.arch), 'android-build',\n        )\n        self.assertEqual(\n            expected_link_root, self.recipe.link_root(self.arch.arch)\n        )\n\n    @mock.patch(\"pythonforandroid.recipes.python3.subprocess.call\")\n    def test_compile_python_files(self, mock_subprocess):\n        fake_compile_dir = '/fake/compile/dir'\n        hostpy = self.recipe.ctx.hostpython = '/fake/hostpython3'\n        self.recipe.compile_python_files(fake_compile_dir)\n        mock_subprocess.assert_called_once_with(\n            [hostpy, '-OO', '-m', 'compileall', '-b', '-f', fake_compile_dir],\n        )\n\n    @mock.patch(\"pythonforandroid.recipe.Recipe.check_recipe_choices\")\n    @mock.patch(\"shutil.which\")\n    def test_get_recipe_env(\n        self,\n        mock_shutil_which,\n        mock_check_recipe_choices,\n    ):\n        \"\"\"\n        Test that method\n        :meth:`~pythonforandroid.recipes.python3.Python3Recipe.get_recipe_env`\n        returns the expected flags\n        \"\"\"\n        mock_shutil_which.return_value = self.expected_compiler\n        mock_check_recipe_choices.return_value = sorted(\n            self.ctx.recipe_build_order\n        )\n        env = self.recipe.get_recipe_env(self.arch)\n\n        self.assertIn('-fPIC -DANDROID', env[\"CFLAGS\"])\n        self.assertEqual(env[\"CC\"], self.arch.get_clang_exe(with_target=True))\n\n        # make sure that the mocked methods are actually called\n        mock_check_recipe_choices.assert_called()\n\n    def test_set_libs_flags(self):\n        # todo: properly check `Python3Recipe.set_lib_flags`\n        pass\n\n    # These decorators are to mock calls to `get_recipe_env`\n    # and `set_libs_flags`, since these calls are tested separately\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.util.makedirs\")\n    @mock.patch(\"shutil.which\")\n    def test_build_arch(\n            self,\n            mock_shutil_which,\n            mock_makedirs,\n            mock_chdir):\n        mock_shutil_which.return_value = self.expected_compiler\n\n        # specific `build_arch` mocks\n        with mock.patch(\n                \"builtins.open\",\n                mock.mock_open(read_data=\"#define ZLIB_VERSION 1.1\\nfoo\")\n        ) as mock_open_zlib, mock.patch(\n            \"pythonforandroid.recipes.python3.sh.Command\"\n        ) as mock_sh_command, mock.patch(\n            \"pythonforandroid.recipes.python3.sh.make\"\n        ) as mock_make, mock.patch(\n            \"pythonforandroid.recipes.python3.sh.cp\"\n        ) as mock_cp:\n            self.recipe.build_arch(self.arch)\n\n        # make sure that the mocked methods are actually called\n        recipe_build_dir = self.recipe.get_build_dir(self.arch.arch)\n        sh_command_calls = {\n            f\"{recipe_build_dir}/config.guess\",\n            f\"{recipe_build_dir}/configure\",\n        }\n        for command in sh_command_calls:\n            self.assertIn(\n                mock.call(command),\n                mock_sh_command.mock_calls,\n            )\n        mock_open_zlib.assert_called()\n        self.assertEqual(mock_make.call_count, 1)\n        for make_call, kw in mock_make.call_args_list:\n            self.assertIn(\n                f'INSTSONAME={self.recipe._libpython}', make_call\n            )\n        mock_cp.assert_called_with(\n            \"pyconfig.h\", join(recipe_build_dir, 'Include'),\n        )\n        mock_makedirs.assert_called()\n        mock_chdir.assert_called()\n\n    def test_build_arch_wrong_ndk_api(self):\n        # we check ndk_api using recipe's ctx\n        self.recipe.ctx.ndk_api = 20\n        with self.assertRaises(BuildInterruptingException) as e:\n            self.recipe.build_arch(self.arch)\n        self.assertEqual(\n            e.exception.args[0],\n            NDK_API_LOWER_THAN_SUPPORTED_MESSAGE.format(\n                ndk_api=self.recipe.ctx.ndk_api,\n                min_ndk_api=self.recipe.MIN_NDK_API,\n            ),\n        )\n        # restore recipe's ctx or we could get failures with other test,\n        # since we share `self.recipe with all the tests of the class\n        self.recipe.ctx.ndk_api = self.ctx.ndk_api\n"
  },
  {
    "path": "tests/recipes/test_reportlab.py",
    "content": "import os\nimport unittest\nfrom unittest.mock import patch\nfrom tests.recipes.recipe_ctx import RecipeCtx\nfrom pythonforandroid.util import ensure_dir\n\n\nclass TestReportLabRecipe(RecipeCtx, unittest.TestCase):\n    recipe_name = \"reportlab\"\n\n    def setUp(self):\n        \"\"\"\n        Setups recipe and context.\n        \"\"\"\n        super().setUp()\n        self.recipe_dir = self.recipe.get_build_dir(self.arch.arch)\n        ensure_dir(self.recipe_dir)\n\n    def test_prebuild_arch(self):\n        \"\"\"\n        Makes sure `prebuild_arch()` runs without error and patches `setup.py`\n        as expected.\n        \"\"\"\n        # `prebuild_arch()` dynamically replaces strings in the `setup.py` file\n        setup_path = os.path.join(self.recipe_dir, 'setup.py')\n        with open(setup_path, 'w') as setup_file:\n            setup_file.write('_FT_LIB_\\n')\n            setup_file.write('_FT_INC_\\n')\n\n        # these sh commands are not relevant for the test and need to be mocked\n        with \\\n                patch('sh.patch'), \\\n                patch('pythonforandroid.recipe.touch'), \\\n                patch('sh.unzip'), \\\n                patch('pythonforandroid.recipe.Recipe.is_patched', lambda *a: False), \\\n                patch('os.path.isfile'):\n            self.recipe.prebuild_arch(self.arch)\n        # makes sure placeholder got replaced with library and include paths\n        with open(setup_path, 'r') as setup_file:\n            lines = setup_file.readlines()\n        self.assertTrue(lines[0].endswith('freetype/objs/.libs\\n'))\n        self.assertTrue(lines[1].endswith('freetype/include\\n'))\n"
  },
  {
    "path": "tests/recipes/test_sdl2_mixer.py",
    "content": "import unittest\nfrom tests.recipes.recipe_ctx import RecipeCtx\n\n\nclass TestSDL2MixerRecipe(RecipeCtx, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.sdl2_mixer`\n    \"\"\"\n    recipe_name = \"sdl2_mixer\"\n\n    def setUp(self):\n        \"\"\"Setups bootstrap build_dir.\"\"\"\n        super().setUp()\n        bootstrap = self.ctx.bootstrap\n        bootstrap.build_dir = bootstrap.get_build_dir()\n\n    def test_get_include_dirs(self):\n        list_of_includes = self.recipe.get_include_dirs(self.arch)\n        self.assertIsInstance(list_of_includes, list)\n        self.assertTrue(list_of_includes[0].endswith(\"include\"))\n"
  },
  {
    "path": "tests/recipes/test_snappy.py",
    "content": "import unittest\nfrom tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe\n\n\nclass TestSnappyRecipe(BaseTestForCmakeRecipe, unittest.TestCase):\n    \"\"\"\n    An unittest for recipe :mod:`~pythonforandroid.recipes.snappy`\n    \"\"\"\n    recipe_name = \"snappy\"\n"
  },
  {
    "path": "tests/test_androidmodule_ctypes_finder.py",
    "content": "\n# This test is still expected to support Python 2, as it tests\n# on-Android functionality that we still maintain\ntry:  # Python 3+\n    from unittest import mock\n    from unittest.mock import MagicMock\nexcept ImportError:  # Python 2\n    import mock\n    from mock import MagicMock\nimport os\nimport shutil\nimport sys\nimport tempfile\n\n\n# Import the tested android._ctypes_library_finder module,\n# making sure android._android won't crash us!\n# (since android._android is android-only / not compilable on desktop)\nandroid_module_folder = os.path.abspath(os.path.join(\n    os.path.dirname(__file__),\n    \"..\", \"pythonforandroid\", \"recipes\", \"android\", \"src\"\n))\nsys.path.insert(0, android_module_folder)\nsys.modules['android._android'] = MagicMock()\nimport android._ctypes_library_finder\nsys.path.remove(android_module_folder)\n\n\n@mock.patch.dict('sys.modules', jnius=MagicMock())\ndef test_get_activity_lib_dir():\n    import jnius  # should get us our fake module\n\n    # Short test that it works when activity doesn't exist:\n    jnius.autoclass = MagicMock()\n    jnius.autoclass.return_value = None\n    assert android._ctypes_library_finder.get_activity_lib_dir(\n        \"JavaClass\"\n    ) is None\n    assert mock.call(\"JavaClass\") in jnius.autoclass.call_args_list\n\n    # Comprehensive test that verifies getApplicationInfo() call:\n    activity = MagicMock()\n    app_context = activity.getApplicationContext()\n    app_context.getPackageName.return_value = \"test.package\"\n    app_info = app_context.getPackageManager().getApplicationInfo()\n    app_info.nativeLibraryDir = '/testpath'\n\n    def pick_class(name):\n        cls = MagicMock()\n        if name == \"JavaClass\":\n            cls.mActivity = activity\n        elif name == \"android.content.pm.PackageManager\":\n            # Manager class:\n            cls.GET_SHARED_LIBRARY_FILES = 1024\n        return cls\n\n    jnius.autoclass = MagicMock(side_effect=pick_class)\n    assert android._ctypes_library_finder.get_activity_lib_dir(\n        \"JavaClass\"\n    ) == \"/testpath\"\n    assert mock.call(\"JavaClass\") in jnius.autoclass.call_args_list\n    assert mock.call(\"test.package\", 1024) in (\n        app_context.getPackageManager().getApplicationInfo.call_args_list\n    )\n\n\n@mock.patch.dict('sys.modules', jnius=MagicMock())\ndef test_find_library():\n    test_d = tempfile.mkdtemp(prefix=\"p4a-android-ctypes-test-libdir-\")\n    try:\n        with open(os.path.join(test_d, \"mymadeuplib.so.5\"), \"w\"):\n            pass\n        import jnius  # should get us our fake module\n\n        # Test with mActivity returned:\n        jnius.autoclass = MagicMock()\n        jnius.autoclass().mService = None\n        app_context = jnius.autoclass().mActivity.getApplicationContext()\n        app_info = app_context.getPackageManager().getApplicationInfo()\n        app_info.nativeLibraryDir = '/doesnt-exist-testpath'\n        assert android._ctypes_library_finder.find_library(\n            \"mymadeuplib\"\n        ) is None\n        assert mock.call(\"org.kivy.android.PythonActivity\") in (\n            jnius.autoclass.call_args_list\n        )\n        app_info.nativeLibraryDir = test_d\n        assert os.path.normpath(android._ctypes_library_finder.find_library(\n            \"mymadeuplib\"\n        )) == os.path.normpath(os.path.join(test_d, \"mymadeuplib.so.5\"))\n\n        # Test with mService returned:\n        jnius.autoclass = MagicMock()\n        jnius.autoclass().mActivity = None\n        app_context = jnius.autoclass().mService.getApplicationContext()\n        app_info = app_context.getPackageManager().getApplicationInfo()\n        app_info.nativeLibraryDir = '/doesnt-exist-testpath'\n        assert android._ctypes_library_finder.find_library(\n            \"mymadeuplib\"\n        ) is None\n        app_info.nativeLibraryDir = test_d\n        assert os.path.normpath(android._ctypes_library_finder.find_library(\n            \"mymadeuplib\"\n        )) == os.path.normpath(os.path.join(test_d, \"mymadeuplib.so.5\"))\n    finally:\n        shutil.rmtree(test_d)\n\n\ndef test_does_libname_match_filename():\n    assert android._ctypes_library_finder.does_libname_match_filename(\n        \"mylib\", \"mylib.so\"\n    )\n    assert not android._ctypes_library_finder.does_libname_match_filename(\n        \"mylib\", \"amylib.so\"\n    )\n    assert not android._ctypes_library_finder.does_libname_match_filename(\n        \"mylib\", \"mylib.txt\"\n    )\n    assert not android._ctypes_library_finder.does_libname_match_filename(\n        \"mylib\", \"mylib\"\n    )\n    assert android._ctypes_library_finder.does_libname_match_filename(\n        \"mylib\", \"libmylib.test.so.1.2.3\"\n    )\n    assert not android._ctypes_library_finder.does_libname_match_filename(\n        \"mylib\", \"libtest.mylib.so\"\n    )\n    assert android._ctypes_library_finder.does_libname_match_filename(\n        \"mylib\", \"mylib.so.5\"\n    )\n"
  },
  {
    "path": "tests/test_androidndk.py",
    "content": "import unittest\nfrom unittest import mock\n\nfrom pythonforandroid.androidndk import AndroidNDK\n\n\nclass TestAndroidNDK(unittest.TestCase):\n    \"\"\"\n    An inherited class of `unittest.TestCase`to test the module\n    :mod:`~pythonforandroid.androidndk`.\n    \"\"\"\n\n    def setUp(self):\n        \"\"\"Configure a :class:`~pythonforandroid.androidndk.AndroidNDK` so we can\n        perform our unittests\"\"\"\n        self.ndk = AndroidNDK(\"/opt/android/android-ndk\")\n\n    @mock.patch(\"sys.platform\", \"linux\")\n    def test_host_tag_linux(self):\n        \"\"\"Test the `host_tag` property of the :class:`~pythonforandroid.androidndk.AndroidNDK`\n        class when the host is Linux.\"\"\"\n        self.assertEqual(self.ndk.host_tag, \"linux-x86_64\")\n\n    @mock.patch(\"sys.platform\", \"darwin\")\n    def test_host_tag_darwin(self):\n        \"\"\"Test the `host_tag` property of the :class:`~pythonforandroid.androidndk.AndroidNDK`\n        class when the host is Darwin.\"\"\"\n        self.assertEqual(self.ndk.host_tag, \"darwin-x86_64\")\n\n    def test_llvm_prebuilt_dir(self):\n        \"\"\"Test the `llvm_prebuilt_dir` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.llvm_prebuilt_dir,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}\",\n        )\n\n    def test_llvm_bin_dir(self):\n        \"\"\"Test the `llvm_bin_dir` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.llvm_bin_dir,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin\",\n        )\n\n    def test_clang(self):\n        \"\"\"Test the `clang` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.clang,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/clang\",\n        )\n\n    def test_clang_cxx(self):\n        \"\"\"Test the `clang_cxx` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.clang_cxx,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/clang++\",\n        )\n\n    def test_llvm_ar(self):\n        \"\"\"Test the `llvm_ar` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.llvm_ar,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-ar\",\n        )\n\n    def test_llvm_ranlib(self):\n        \"\"\"Test the `llvm_ranlib` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.llvm_ranlib,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-ranlib\",\n        )\n\n    def test_llvm_objcopy(self):\n        \"\"\"Test the `llvm_objcopy` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.llvm_objcopy,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-objcopy\",\n        )\n\n    def test_llvm_objdump(self):\n        \"\"\"Test the `llvm_objdump` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.llvm_objdump,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-objdump\",\n        )\n\n    def test_llvm_readelf(self):\n        \"\"\"Test the `llvm_readelf` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.llvm_readelf,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-readelf\",\n        )\n\n    def test_llvm_strip(self):\n        \"\"\"Test the `llvm_strip` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.llvm_strip,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-strip\",\n        )\n\n    def test_sysroot(self):\n        \"\"\"Test the `sysroot` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.sysroot,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/sysroot\",\n        )\n\n    def test_sysroot_include_dir(self):\n        \"\"\"Test the `sysroot_include_dir` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.sysroot_include_dir,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/sysroot/usr/include\",\n        )\n\n    def test_sysroot_lib_dir(self):\n        \"\"\"Test the `sysroot_lib_dir` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.sysroot_lib_dir,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/sysroot/usr/lib\",\n        )\n\n    def test_libcxx_include_dir(self):\n        \"\"\"Test the `libcxx_include_dir` property of the\n        :class:`~pythonforandroid.androidndk.AndroidNDK` class.\"\"\"\n        self.assertEqual(\n            self.ndk.libcxx_include_dir,\n            f\"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/sysroot/usr/include/c++/v1\",\n        )\n"
  },
  {
    "path": "tests/test_archs.py",
    "content": "import os\nimport unittest\nfrom os import environ\nfrom unittest import mock\n\nfrom pythonforandroid.bootstrap import Bootstrap\nfrom pythonforandroid.distribution import Distribution\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.build import Context\nfrom pythonforandroid.util import BuildInterruptingException\nfrom pythonforandroid.archs import (\n    Arch,\n    ArchARM,\n    ArchARMv7_a,\n    ArchAarch_64,\n    Archx86,\n    Archx86_64,\n)\nfrom pythonforandroid.androidndk import AndroidNDK\n\nexpected_env_gcc_keys = {\n    \"CFLAGS\",\n    \"LDFLAGS\",\n    \"CXXFLAGS\",\n    \"CC\",\n    \"CXX\",\n    \"LDSHARED\",\n    \"STRIP\",\n    \"MAKE\",\n    \"READELF\",\n    \"BUILDLIB_PATH\",\n    \"PATH\",\n    \"ARCH\",\n    \"NDK_API\",\n}\n\n\nclass ArchSetUpBaseClass(object):\n    \"\"\"\n    An class object which is intended to be used as a base class to configure\n    an inherited class of `unittest.TestCase`. This class will override the\n    `setUp` method.\n    \"\"\"\n\n    ctx = None\n    expected_compiler = \"\"\n\n    TEST_ARCH = 'armeabi-v7a'\n\n    def setUp(self):\n        self.ctx = Context()\n        self.ctx.ndk_api = 21\n        self.ctx.android_api = 27\n        self.ctx._sdk_dir = \"/opt/android/android-sdk\"\n        self.ctx._ndk_dir = \"/opt/android/android-ndk\"\n        self.ctx.ndk = AndroidNDK(self.ctx._ndk_dir)\n        self.ctx.setup_dirs(os.getcwd())\n        self.ctx.bootstrap = Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        self.ctx.bootstrap.distribution = Distribution.get_distribution(\n            self.ctx,\n            name=\"sdl2\",\n            recipes=[\"python3\", \"kivy\"],\n            archs=[self.TEST_ARCH],\n        )\n        self.ctx.python_recipe = Recipe.get_recipe(\"python3\", self.ctx)\n        # Here we define the expected compiler, which, as per ndk >= r19,\n        # should be the same for all the tests (no more gcc compiler)\n        self.expected_compiler = (\n            f\"/opt/android/android-ndk/toolchains/\"\n            f\"llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang\"\n        )\n\n\nclass TestArch(ArchSetUpBaseClass, unittest.TestCase):\n    \"\"\"\n    An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which\n    will be used to perform tests for the base class\n    :class:`~pythonforandroid.archs.Arch`.\n    \"\"\"\n\n    def test_arch(self):\n        arch = Arch(self.ctx)\n        self.assertEqual(arch.__str__(), arch.arch)\n        self.assertEqual(arch.target, \"None21\")\n        self.assertIsNone(arch.command_prefix)\n        self.assertIsInstance(arch.include_dirs, list)\n\n\nclass TestArchARM(ArchSetUpBaseClass, unittest.TestCase):\n    \"\"\"\n    An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which\n    will be used to perform tests for :class:`~pythonforandroid.archs.ArchARM`.\n    \"\"\"\n\n    @mock.patch(\"shutil.which\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    def test_arch_arm(self, mock_ensure_dir, mock_shutil_which):\n        \"\"\"\n        Test that class :class:`~pythonforandroid.archs.ArchARM` returns some\n        expected attributes and environment variables.\n\n        .. note::\n            Here we mock two methods:\n\n                - `ensure_dir` because we don't want to create any directory\n                - `shutil.which` because otherwise we will\n                  get an error when trying to find the compiler (we are setting\n                  some fake paths for our android sdk and ndk so probably will\n                  not exist)\n\n        \"\"\"\n        mock_shutil_which.return_value = self.expected_compiler\n        mock_ensure_dir.return_value = True\n\n        arch = ArchARM(self.ctx)\n        self.assertEqual(arch.arch, \"armeabi\")\n        self.assertEqual(arch.__str__(), \"armeabi\")\n        self.assertEqual(arch.command_prefix, \"arm-linux-androideabi\")\n        self.assertEqual(arch.target, \"armv7a-linux-androideabi21\")\n        arch = ArchARM(self.ctx)\n\n        # Check environment flags\n        env = arch.get_env()\n        self.assertIsInstance(env, dict)\n        self.assertEqual(\n            expected_env_gcc_keys, set(env.keys()) & expected_env_gcc_keys\n        )\n\n        # check shutil.which calls\n        mock_shutil_which.assert_called_once_with(\n            self.expected_compiler, path=environ[\"PATH\"]\n        )\n\n        # check gcc compilers\n        self.assertEqual(env[\"CC\"].split()[0], self.expected_compiler)\n        self.assertEqual(env[\"CXX\"].split()[0], self.expected_compiler + \"++\")\n        # check android binaries\n        self.assertEqual(\n            env[\"STRIP\"].split()[0],\n            os.path.join(\n                self.ctx._ndk_dir,\n                f\"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin\",\n                \"llvm-strip\",\n            )\n        )\n        self.assertEqual(\n            env[\"READELF\"].split()[0],\n            os.path.join(\n                self.ctx._ndk_dir,\n                f\"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin\",\n                \"llvm-readelf\",\n            )\n        )\n\n        # check that cflags are in gcc\n        self.assertIn(env[\"CFLAGS\"], env[\"CC\"])\n\n        # check that flags aren't in gcc and also check ccache\n        self.ctx.ccache = \"/usr/bin/ccache\"\n        env = arch.get_env(with_flags_in_cc=False)\n        self.assertNotIn(env[\"CFLAGS\"], env[\"CC\"])\n        self.assertEqual(env[\"USE_CCACHE\"], \"1\")\n        self.assertEqual(env[\"NDK_CCACHE\"], \"/usr/bin/ccache\")\n\n        # Check exception in case that CC is not found\n        mock_shutil_which.return_value = None\n        with self.assertRaises(BuildInterruptingException) as e:\n            arch.get_env()\n        self.assertEqual(\n            e.exception.args[0],\n            \"Couldn't find executable for CC. This indicates a problem \"\n            \"locating the {expected_compiler} executable in the Android \"\n            \"NDK, not that you don't have a normal compiler installed. \"\n            \"Exiting.\".format(expected_compiler=self.expected_compiler),\n        )\n\n\nclass TestArchARMv7a(ArchSetUpBaseClass, unittest.TestCase):\n    \"\"\"\n    An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.archs.ArchARMv7_a`.\n    \"\"\"\n\n    @mock.patch(\"shutil.which\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    def test_arch_armv7a(\n        self, mock_ensure_dir, mock_shutil_which\n    ):\n        \"\"\"\n        Test that class :class:`~pythonforandroid.archs.ArchARMv7_a` returns\n        some expected attributes and environment variables.\n\n        .. note::\n            Here we mock the same functions than\n            :meth:`TestArchARM.test_arch_arm`.\n            This has to be done because here we tests the `get_env` with clang\n\n        \"\"\"\n        mock_shutil_which.return_value = self.expected_compiler\n        mock_ensure_dir.return_value = True\n\n        arch = ArchARMv7_a(self.ctx)\n        self.assertEqual(arch.arch, \"armeabi-v7a\")\n        self.assertEqual(arch.__str__(), \"armeabi-v7a\")\n        self.assertEqual(arch.command_prefix, \"arm-linux-androideabi\")\n        self.assertEqual(arch.target, \"armv7a-linux-androideabi21\")\n\n        env = arch.get_env()\n        # check shutil.which calls\n        mock_shutil_which.assert_called_once_with(\n            self.expected_compiler, path=environ[\"PATH\"]\n        )\n\n        # check clang\n        self.assertEqual(\n            env[\"CC\"].split()[0],\n            \"{ndk_dir}/toolchains/llvm/prebuilt/\"\n            \"{host_tag}/bin/clang\".format(\n                ndk_dir=self.ctx._ndk_dir, host_tag=self.ctx.ndk.host_tag\n            ),\n        )\n        self.assertEqual(\n            env[\"CXX\"].split()[0],\n            \"{ndk_dir}/toolchains/llvm/prebuilt/\"\n            \"{host_tag}/bin/clang++\".format(\n                ndk_dir=self.ctx._ndk_dir, host_tag=self.ctx.ndk.host_tag\n            ),\n        )\n\n        # For armeabi-v7a we expect some extra cflags\n        self.assertIn(\n            \" -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -mthumb\",\n            env[\"CFLAGS\"],\n        )\n\n\nclass TestArchX86(ArchSetUpBaseClass, unittest.TestCase):\n    \"\"\"\n    An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which\n    will be used to perform tests for :class:`~pythonforandroid.archs.Archx86`.\n    \"\"\"\n\n    @mock.patch(\"shutil.which\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    def test_arch_x86(self, mock_ensure_dir, mock_shutil_which):\n        \"\"\"\n        Test that class :class:`~pythonforandroid.archs.Archx86` returns\n        some expected attributes and environment variables.\n\n        .. note::\n            Here we mock the same functions than\n            :meth:`TestArchARM.test_arch_arm` plus `glob`, so we make sure that\n            the glob result is the expected even if the folder doesn't exist,\n            which is probably the case. This has to be done because here we\n            tests the `get_env` with clang\n        \"\"\"\n        mock_shutil_which.return_value = self.expected_compiler\n        mock_ensure_dir.return_value = True\n\n        arch = Archx86(self.ctx)\n        self.assertEqual(arch.arch, \"x86\")\n        self.assertEqual(arch.__str__(), \"x86\")\n        self.assertEqual(arch.command_prefix, \"i686-linux-android\")\n        self.assertEqual(arch.target, \"i686-linux-android21\")\n\n        env = arch.get_env()\n        # check shutil.which calls\n        mock_shutil_which.assert_called_once_with(\n            self.expected_compiler, path=environ[\"PATH\"]\n        )\n\n        # For x86 we expect some extra cflags in our `environment`\n        self.assertIn(\n            \" -march=i686 -mssse3 -mfpmath=sse -m32\",\n            env[\"CFLAGS\"],\n        )\n\n\nclass TestArchX86_64(ArchSetUpBaseClass, unittest.TestCase):\n    \"\"\"\n    An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.archs.Archx86_64`.\n    \"\"\"\n\n    @mock.patch(\"shutil.which\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    def test_arch_x86_64(\n        self, mock_ensure_dir, mock_shutil_which\n    ):\n        \"\"\"\n        Test that class :class:`~pythonforandroid.archs.Archx86_64` returns\n        some expected attributes and environment variables.\n\n        .. note::\n            Here we mock the same functions than\n            :meth:`TestArchARM.test_arch_arm` plus `glob`, so we make sure that\n            the glob result is the expected even if the folder doesn't exist,\n            which is probably the case. This has to be done because here we\n            tests the `get_env` with clang\n        \"\"\"\n        mock_shutil_which.return_value = self.expected_compiler\n        mock_ensure_dir.return_value = True\n\n        arch = Archx86_64(self.ctx)\n        self.assertEqual(arch.arch, \"x86_64\")\n        self.assertEqual(arch.__str__(), \"x86_64\")\n        self.assertEqual(arch.command_prefix, \"x86_64-linux-android\")\n        self.assertEqual(arch.target, \"x86_64-linux-android21\")\n\n        env = arch.get_env()\n        # check shutil.which calls\n        mock_shutil_which.assert_called_once_with(\n            self.expected_compiler, path=environ[\"PATH\"]\n        )\n\n        # For x86_64 we expect some extra cflags in our `environment`\n        mock_shutil_which.assert_called_once()\n        self.assertIn(\n            \" -march=x86-64 -msse4.2 -mpopcnt -m64\", env[\"CFLAGS\"]\n        )\n\n\nclass TestArchAArch64(ArchSetUpBaseClass, unittest.TestCase):\n    \"\"\"\n    An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.archs.ArchAarch_64`.\n    \"\"\"\n\n    @mock.patch(\"shutil.which\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    def test_arch_aarch_64(\n        self, mock_ensure_dir, mock_shutil_which\n    ):\n        \"\"\"\n        Test that class :class:`~pythonforandroid.archs.ArchAarch_64` returns\n        some expected attributes and environment variables.\n\n        .. note::\n            Here we mock the same functions than\n            :meth:`TestArchARM.test_arch_arm` plus `glob`, so we make sure that\n            the glob result is the expected even if the folder doesn't exist,\n            which is probably the case. This has to be done because here we\n            tests the `get_env` with clang\n        \"\"\"\n        mock_shutil_which.return_value = self.expected_compiler\n        mock_ensure_dir.return_value = True\n\n        arch = ArchAarch_64(self.ctx)\n        self.assertEqual(arch.arch, \"arm64-v8a\")\n        self.assertEqual(arch.__str__(), \"arm64-v8a\")\n        self.assertEqual(arch.command_prefix, \"aarch64-linux-android\")\n        self.assertEqual(arch.target, \"aarch64-linux-android21\")\n\n        env = arch.get_env()\n        # check shutil.which calls\n        mock_shutil_which.assert_called_once_with(\n            self.expected_compiler, path=environ[\"PATH\"]\n        )\n\n        # For x86_64 we expect to find an extra key in`environment`\n        for flag in {\"CFLAGS\", \"CXXFLAGS\", \"CC\", \"CXX\"}:\n            self.assertIn(\"-march=armv8-a\", env[flag])\n"
  },
  {
    "path": "tests/test_bdistapk.py",
    "content": "import sys\nfrom unittest import mock\nfrom setuptools.dist import Distribution\n\nfrom pythonforandroid.bdistapk import (\n    argv_contains,\n    BdistAPK,\n    BdistAAR,\n    BdistAAB,\n)\n\n\nclass TestArgvContains:\n    \"\"\"Test argv_contains helper function.\"\"\"\n\n    def test_argv_contains_present(self):\n        \"\"\"Test argv_contains returns True when argument is present.\"\"\"\n        with mock.patch.object(sys, 'argv', ['prog', '--name=test', '--version=1.0']):\n            assert argv_contains('--name')\n            assert argv_contains('--version')\n\n    def test_argv_contains_partial_match(self):\n        \"\"\"Test argv_contains returns True for partial matches.\"\"\"\n        with mock.patch.object(sys, 'argv', ['prog', '--name=test']):\n            assert argv_contains('--name')\n            assert argv_contains('--nam')\n\n    def test_argv_contains_not_present(self):\n        \"\"\"Test argv_contains returns False when argument is not present.\"\"\"\n        with mock.patch.object(sys, 'argv', ['prog', '--name=test']):\n            assert not argv_contains('--package')\n            assert not argv_contains('--arch')\n\n\nclass TestBdist:\n    \"\"\"Test Bdist base class.\"\"\"\n\n    def setup_method(self):\n        \"\"\"Set up test fixtures.\"\"\"\n        self.distribution = Distribution({\n            'name': 'TestApp',\n            'version': '1.0.0',\n        })\n        self.distribution.package_data = {'testapp': ['*.py', '*.kv']}\n\n    @mock.patch('pythonforandroid.bdistapk.ensure_dir')\n    @mock.patch('pythonforandroid.bdistapk.rmdir')\n    def test_initialize_options(self, mock_rmdir, mock_ensure_dir):\n        \"\"\"Test initialize_options sets attributes from user_options.\"\"\"\n        bdist = BdistAPK(self.distribution)\n        bdist.user_options = [('name=', None, None), ('version=', None, None)]\n\n        bdist.initialize_options()\n\n        assert hasattr(bdist, 'name')\n        assert hasattr(bdist, 'version')\n\n    @mock.patch('pythonforandroid.bdistapk.argv_contains')\n    @mock.patch('pythonforandroid.bdistapk.ensure_dir')\n    @mock.patch('pythonforandroid.bdistapk.rmdir')\n    def test_finalize_options_injects_defaults(\n        self, mock_rmdir, mock_ensure_dir, mock_argv_contains\n    ):\n        \"\"\"Test finalize_options injects default name, package, version, arch.\"\"\"\n        mock_argv_contains.return_value = False\n\n        with mock.patch.object(sys, 'argv', ['setup.py', 'apk']):\n            bdist = BdistAPK(self.distribution)\n            bdist.finalize_options()\n\n            # Check that defaults were added to sys.argv\n            argv_str = ' '.join(sys.argv)\n            assert '--name=' in argv_str or any('--name' in arg for arg in sys.argv)\n\n    @mock.patch('pythonforandroid.bdistapk.argv_contains')\n    @mock.patch('pythonforandroid.bdistapk.ensure_dir')\n    @mock.patch('pythonforandroid.bdistapk.rmdir')\n    def test_finalize_options_permissions_handling(\n        self, mock_rmdir, mock_ensure_dir, mock_argv_contains\n    ):\n        \"\"\"Test finalize_options handles permissions list correctly.\"\"\"\n        mock_argv_contains.side_effect = lambda x: x != '--permissions'\n\n        # Set up permissions in the distribution command options\n        self.distribution.command_options['apk'] = {\n            'permissions': ('setup.py', ['INTERNET', 'CAMERA'])\n        }\n\n        with mock.patch.object(sys, 'argv', ['setup.py', 'apk']):\n            bdist = BdistAPK(self.distribution)\n            bdist.package_type = 'apk'\n            bdist.finalize_options()\n\n            # Check permissions were added\n            assert any('--permission=INTERNET' in arg for arg in sys.argv)\n            assert any('--permission=CAMERA' in arg for arg in sys.argv)\n\n    @mock.patch('pythonforandroid.entrypoints.main')\n    @mock.patch('pythonforandroid.bdistapk.argv_contains')\n    @mock.patch('pythonforandroid.bdistapk.ensure_dir')\n    @mock.patch('pythonforandroid.bdistapk.rmdir')\n    @mock.patch('pythonforandroid.bdistapk.copyfile')\n    @mock.patch('pythonforandroid.bdistapk.glob')\n    def test_run_calls_main(\n        self, mock_glob, mock_copyfile, mock_rmdir, mock_ensure_dir,\n        mock_argv_contains, mock_main\n    ):\n        \"\"\"Test run() calls prepare_build_dir and then main().\"\"\"\n        mock_glob.return_value = ['testapp/main.py']\n        mock_argv_contains.return_value = False  # Not using --launcher or --private\n\n        with mock.patch.object(sys, 'argv', ['setup.py', 'apk']):\n            bdist = BdistAPK(self.distribution)\n            bdist.arch = 'armeabi-v7a'\n            bdist.run()\n\n            mock_rmdir.assert_called()\n            mock_ensure_dir.assert_called()\n            mock_main.assert_called_once()\n            assert sys.argv[1] == 'apk'\n\n    @mock.patch('pythonforandroid.bdistapk.argv_contains')\n    @mock.patch('pythonforandroid.bdistapk.ensure_dir')\n    @mock.patch('pythonforandroid.bdistapk.rmdir')\n    @mock.patch('pythonforandroid.bdistapk.copyfile')\n    @mock.patch('pythonforandroid.bdistapk.glob')\n    @mock.patch('builtins.exit', side_effect=SystemExit(1))\n    def test_prepare_build_dir_no_main_py(\n        self, mock_exit, mock_glob, mock_copyfile,\n        mock_rmdir, mock_ensure_dir, mock_argv_contains\n    ):\n        \"\"\"Test prepare_build_dir exits if no main.py found and not using launcher.\"\"\"\n        mock_glob.return_value = ['testapp/helper.py']\n        mock_argv_contains.return_value = False  # Not using --launcher\n\n        bdist = BdistAPK(self.distribution)\n        bdist.arch = 'armeabi-v7a'\n\n        # Expect SystemExit to be raised\n        try:\n            bdist.prepare_build_dir()\n            assert False, \"Expected SystemExit to be raised\"\n        except SystemExit:\n            pass\n\n        mock_exit.assert_called_once_with(1)\n\n    @mock.patch('pythonforandroid.bdistapk.argv_contains')\n    @mock.patch('pythonforandroid.bdistapk.ensure_dir')\n    @mock.patch('pythonforandroid.bdistapk.rmdir')\n    @mock.patch('pythonforandroid.bdistapk.copyfile')\n    @mock.patch('pythonforandroid.bdistapk.glob')\n    def test_prepare_build_dir_with_main_py(\n        self, mock_glob, mock_copyfile, mock_rmdir,\n        mock_ensure_dir, mock_argv_contains\n    ):\n        \"\"\"Test prepare_build_dir succeeds when main.py is found.\"\"\"\n        mock_glob.return_value = ['testapp/main.py', 'testapp/helper.py']\n        # Return False for all argv_contains checks (no --launcher, no --private)\n        mock_argv_contains.return_value = False\n\n        with mock.patch.object(sys, 'argv', ['setup.py', 'apk']):\n            bdist = BdistAPK(self.distribution)\n            bdist.arch = 'armeabi-v7a'\n            bdist.prepare_build_dir()\n\n            # Should have copied files (glob might return duplicates)\n            assert mock_copyfile.call_count >= 2\n            # Should have added --private argument\n            assert any('--private=' in arg for arg in sys.argv)\n\n\nclass TestBdistSubclasses:\n    \"\"\"Test BdistAPK, BdistAAR, BdistAAB subclasses.\"\"\"\n\n    def setup_method(self):\n        \"\"\"Set up test fixtures.\"\"\"\n        self.distribution = Distribution({\n            'name': 'TestApp',\n            'version': '1.0.0',\n        })\n        self.distribution.package_data = {}\n\n    def test_bdist_apk_package_type(self):\n        \"\"\"Test BdistAPK has correct package_type.\"\"\"\n        bdist = BdistAPK(self.distribution)\n        assert bdist.package_type == 'apk'\n        assert bdist.description == 'Create an APK with python-for-android'\n\n    def test_bdist_aar_package_type(self):\n        \"\"\"Test BdistAAR has correct package_type.\"\"\"\n        bdist = BdistAAR(self.distribution)\n        assert bdist.package_type == 'aar'\n        assert bdist.description == 'Create an AAR with python-for-android'\n\n    def test_bdist_aab_package_type(self):\n        \"\"\"Test BdistAAB has correct package_type.\"\"\"\n        bdist = BdistAAB(self.distribution)\n        assert bdist.package_type == 'aab'\n        assert bdist.description == 'Create an AAB with python-for-android'\n"
  },
  {
    "path": "tests/test_bootstrap.py",
    "content": "\nimport os\nimport sh\nimport unittest\n\nfrom unittest import mock\n\nfrom pythonforandroid.bootstrap import (\n    _cmp_bootstraps_by_priority, Bootstrap, expand_dependencies,\n)\nfrom pythonforandroid.distribution import Distribution\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.archs import ArchARMv7_a\nfrom pythonforandroid.build import Context\nfrom pythonforandroid.util import BuildInterruptingException\nfrom pythonforandroid.androidndk import AndroidNDK\n\nfrom tests.test_graph import get_fake_recipe\n\n\nclass BaseClassSetupBootstrap:\n    \"\"\"\n    An class which is intended to be used as a base class to configure\n    an inherited class of `unittest.TestCase`. This class will override the\n    `setUp` and `tearDown` methods.\n    \"\"\"\n\n    TEST_ARCH = 'armeabi-v7a'\n\n    def setUp(self):\n        Recipe.recipes = {}  # clear Recipe class cache\n        self.ctx = Context()\n        self.ctx.ndk_api = 21\n        self.ctx.android_api = 27\n        self.ctx._sdk_dir = \"/opt/android/android-sdk\"\n        self.ctx._ndk_dir = \"/opt/android/android-ndk\"\n        self.ctx.ndk = AndroidNDK(self.ctx._ndk_dir)\n        self.ctx.setup_dirs(os.getcwd())\n        self.ctx.recipe_build_order = [\n            \"hostpython3\",\n            \"python3\",\n            \"sdl2\",\n            \"kivy\",\n        ]\n\n    def setUp_distribution_with_bootstrap(self, bs):\n        \"\"\"\n        Extend the setUp by configuring a distribution, because some test\n        needs a distribution to be set to be properly tested\n        \"\"\"\n        self.ctx.bootstrap = bs\n        self.ctx.bootstrap.distribution = Distribution.get_distribution(\n            self.ctx, name=\"test_prj\",\n            recipes=[\"python3\", \"kivy\"],\n            archs=[self.TEST_ARCH],\n        )\n\n    def tearDown(self):\n        \"\"\"\n        Extend the `tearDown` by configuring a distribution, because some test\n        needs a distribution to be set to be properly tested\n        \"\"\"\n        self.ctx.bootstrap = None\n\n\nclass TestBootstrapBasic(BaseClassSetupBootstrap, unittest.TestCase):\n    \"\"\"\n    An inherited class of `BaseClassSetupBootstrap` and `unittest.TestCase`\n    which will be used to perform tests for the methods/attributes shared\n    between all bootstraps which inherits from class\n    :class:`~pythonforandroid.bootstrap.Bootstrap`.\n    \"\"\"\n\n    def test_attributes(self):\n        \"\"\"A test which will initialize a bootstrap and will check if the\n        values are the expected.\n        \"\"\"\n        bs = Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        self.assertEqual(bs.name, \"sdl2\")\n        self.assertEqual(bs.jni_dir, \"sdl2/jni\")\n        self.assertEqual(bs.get_build_dir_name(), \"sdl2\")\n\n        # bs.dist_dir should raise an error if there is no distribution to query\n        bs.distribution = None\n        with self.assertRaises(BuildInterruptingException):\n            bs.dist_dir\n\n        # test dist_dir success\n        self.setUp_distribution_with_bootstrap(bs)\n        expected_folder_name = 'test_prj'\n        self.assertTrue(\n            bs.dist_dir.endswith(f\"dists/{expected_folder_name}\"))\n\n    def test_build_dist_dirs(self):\n        \"\"\"A test which will initialize a bootstrap and will check if the\n        directories we set has the values that we expect. Here we test methods:\n\n            - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_build_dir`\n            - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_dist_dir`\n            - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_common_dir`\n        \"\"\"\n        bs = Bootstrap.get_bootstrap(\"sdl2\", self.ctx)\n\n        self.assertTrue(\n            bs.get_build_dir().endswith(\"build/bootstrap_builds/sdl2\")\n        )\n        self.assertTrue(bs.get_dist_dir(\"test_prj\").endswith(\"dists/test_prj\"))\n\n    def test__cmp_bootstraps_by_priority(self):\n        # Test service_only has higher priority than sdl2:\n        # (higher priority = smaller number/comes first)\n        self.assertTrue(_cmp_bootstraps_by_priority(\n            Bootstrap.get_bootstrap(\"service_only\", self.ctx),\n            Bootstrap.get_bootstrap(\"sdl2\", self.ctx)\n        ) < 0)\n\n        # Test a random bootstrap is always lower priority than sdl2:\n        class _FakeBootstrap:\n            def __init__(self, name):\n                self.name = name\n        bs1 = _FakeBootstrap(\"alpha\")\n        bs2 = _FakeBootstrap(\"zeta\")\n        self.assertTrue(_cmp_bootstraps_by_priority(\n            bs1,\n            Bootstrap.get_bootstrap(\"sdl2\", self.ctx)\n        ) > 0)\n        self.assertTrue(_cmp_bootstraps_by_priority(\n            bs2,\n            Bootstrap.get_bootstrap(\"sdl2\", self.ctx)\n        ) > 0)\n\n        # Test bootstraps that aren't otherwise recognized are ranked\n        # alphabetically:\n        self.assertTrue(_cmp_bootstraps_by_priority(\n            bs2,\n            bs1,\n        ) > 0)\n        self.assertTrue(_cmp_bootstraps_by_priority(\n            bs1,\n            bs2,\n        ) < 0)\n\n    def test_all_bootstraps(self):\n        \"\"\"A test which will initialize a bootstrap and will check if the\n        method :meth:`~pythonforandroid.bootstrap.Bootstrap.all_bootstraps `\n        returns the expected values, which should be: `empty\", `service_only`,\n        `webview`, `sdl2`, `sdl3` and `qt`\n        \"\"\"\n        expected_bootstraps = {\n            \"empty\",\n            \"service_only\",\n            \"service_library\",\n            \"webview\",\n            \"sdl2\",\n            \"sdl3\",\n            \"qt\",\n        }\n        set_of_bootstraps = Bootstrap.all_bootstraps()\n        self.assertEqual(\n            expected_bootstraps, expected_bootstraps & set_of_bootstraps\n        )\n        self.assertEqual(len(expected_bootstraps), len(set_of_bootstraps))\n\n    def test_expand_dependencies(self):\n        # Test dependency expansion of a recipe with no alternatives:\n        expanded_result_1 = expand_dependencies([\"pysdl2\"], self.ctx)\n        self.assertTrue(\n            {\"sdl2\", \"pysdl2\", \"python3\"} in\n            [set(s) for s in expanded_result_1]\n        )\n\n        # Test expansion of a single element but as tuple:\n        expanded_result_1 = expand_dependencies([(\"pysdl2\",)], self.ctx)\n        self.assertTrue(\n            {\"sdl2\", \"pysdl2\", \"python3\"} in\n            [set(s) for s in expanded_result_1]\n        )\n\n        # Test all alternatives are listed (they won't have dependencies\n        # expanded since expand_dependencies() is too simplistic):\n        expanded_result_2 = expand_dependencies([(\"pysdl2\", \"kivy\")], self.ctx)\n        self.assertEqual([[\"pysdl2\"], [\"kivy\"]], expanded_result_2)\n\n    def test_expand_dependencies_with_pure_python_package(self):\n        \"\"\"Check that `expanded_dependencies`, with a pure python package as\n        one of the dependencies, returns a list of dependencies\n        \"\"\"\n        expanded_result = expand_dependencies(\n            [\"python3\", \"kivy\", \"peewee\"], self.ctx\n        )\n        # we expect to 2 results for python3\n        # (python3, sdl2/sdl3 [one is blacklisted])\n        self.assertEqual(len(expanded_result), 2)\n        self.assertIsInstance(expanded_result, list)\n        for i in expanded_result:\n            self.assertIsInstance(i, list)\n\n    def test_get_bootstraps_from_recipes(self):\n        \"\"\"A test which will initialize a bootstrap and will check if the\n        method :meth:`~pythonforandroid.bootstrap.Bootstrap.\n        get_bootstraps_from_recipes` returns the expected values\n        \"\"\"\n\n        import pythonforandroid.recipe\n        original_get_recipe = pythonforandroid.recipe.Recipe.get_recipe\n\n        # Test that SDL2 works with kivy:\n        recipes_sdl2 = {\"sdl2\", \"python3\", \"kivy\"}\n        bs = Bootstrap.get_bootstrap_from_recipes(recipes_sdl2, self.ctx)\n        self.assertEqual(bs.name, \"sdl2\")\n\n        # Test that pysdl2 or kivy alone will also yield SDL2 (dependency):\n        recipes_pysdl2_only = {\"pysdl2\"}\n        bs = Bootstrap.get_bootstrap_from_recipes(\n            recipes_pysdl2_only, self.ctx\n        )\n        self.assertEqual(bs.name, \"sdl2\")\n        recipes_kivy_only = {\"kivy\"}\n        bs = Bootstrap.get_bootstrap_from_recipes(\n            recipes_kivy_only, self.ctx\n        )\n        self.assertEqual(bs.name, \"sdl2\")\n\n        with mock.patch(\"pythonforandroid.recipe.Recipe.get_recipe\") as \\\n                mock_get_recipe:\n            # Test that something conflicting with sdl2 won't give sdl2:\n            def _add_sdl2_conflicting_recipe(name, ctx):\n                if name == \"conflictswithsdl2\":\n                    if name not in pythonforandroid.recipe.Recipe.recipes:\n                        pythonforandroid.recipe.Recipe.recipes[name] = (\n                            get_fake_recipe(\"sdl2\", conflicts=[\"sdl2\"])\n                        )\n                return original_get_recipe(name, ctx)\n            mock_get_recipe.side_effect = _add_sdl2_conflicting_recipe\n            recipes_with_sdl2_conflict = {\"python3\", \"conflictswithsdl2\"}\n            bs = Bootstrap.get_bootstrap_from_recipes(\n                recipes_with_sdl2_conflict, self.ctx\n            )\n            self.assertNotEqual(bs.name, \"sdl2\")\n\n        # Test using flask will default to webview:\n        recipes_with_flask = {\"python3\", \"flask\"}\n        bs = Bootstrap.get_bootstrap_from_recipes(\n            recipes_with_flask, self.ctx\n        )\n        self.assertEqual(bs.name, \"webview\")\n\n        # Test using random packages will default to service_only:\n        recipes_with_no_sdl2_or_web = {\"python3\", \"numpy\"}\n        bs = Bootstrap.get_bootstrap_from_recipes(\n            recipes_with_no_sdl2_or_web, self.ctx\n        )\n        self.assertEqual(bs.name, \"service_only\")\n\n    @mock.patch(\"pythonforandroid.bootstrap.ensure_dir\")\n    def test_prepare_dist_dir(self, mock_ensure_dir):\n        \"\"\"A test which will initialize a bootstrap and will check if the\n        method :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_dist_dir`\n        successfully calls once the method `endure_dir`\n        \"\"\"\n        bs = Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n\n        bs.prepare_dist_dir()\n        mock_ensure_dir.assert_called_once()\n\n    @mock.patch(\"pythonforandroid.bootstrap.open\", create=True)\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.bootstrap.shutil.copy\")\n    @mock.patch(\"pythonforandroid.bootstrap.os.makedirs\")\n    def test_bootstrap_prepare_build_dir(\n        self, mock_os_makedirs, mock_shutil_copy, mock_chdir, mock_open\n    ):\n        \"\"\"A test which will initialize a bootstrap and will check if the\n        method :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_build_dir`\n        successfully calls the methods that we need to prepare a build dir.\n        \"\"\"\n\n        # prepare bootstrap\n        bs = Bootstrap().get_bootstrap(\"service_only\", self.ctx)\n        self.ctx.bootstrap = bs\n\n        # test that prepare_build_dir runs (notice that we mock\n        # any file/dir creation so we can speed up the tests)\n        bs.prepare_build_dir()\n\n        # make sure that the open command has been called only once\n        mock_open.assert_called_once_with(\"project.properties\", \"w\")\n\n        # check that the other mocks we made are actually called\n        mock_os_makedirs.assert_called()\n        mock_shutil_copy.assert_called()\n        mock_chdir.assert_called()\n\n    @mock.patch(\"pythonforandroid.bootstrap.os.path.isfile\")\n    @mock.patch(\"pythonforandroid.bootstrap.os.path.exists\")\n    @mock.patch(\"pythonforandroid.bootstrap.os.unlink\")\n    @mock.patch(\"pythonforandroid.bootstrap.open\", create=True)\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.bootstrap.listdir\")\n    def test_bootstrap_prepare_build_dir_with_java_src(\n        self,\n        mock_listdir,\n        mock_chdir,\n        mock_open,\n        mock_os_unlink,\n        mock_os_path_exists,\n        mock_os_path_isfile,\n    ):\n        \"\"\"A test which will initialize a bootstrap and will check perform\n        another test for method\n        :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_build_dir`. In\n        here we will simulate that we have `with_java_src` set to some value.\n        \"\"\"\n        self.ctx.symlink_bootstrap_files = True\n        mock_listdir.return_value = [\n            \"jnius\",\n            \"kivy\",\n            \"Kivy-1.11.0.dev0-py3.7.egg-info\",\n            \"pyjnius-1.2.1.dev0-py3.7.egg\",\n        ]\n\n        # prepare bootstrap\n        bs = Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        self.ctx.bootstrap = bs\n\n        # test that prepare_build_dir runs (notice that we mock\n        # any file/dir creation so we can speed up the tests)\n        bs.prepare_build_dir()\n        # make sure that the open command has been called only once\n        mock_open.assert_called_with(\"project.properties\", \"w\")\n\n        # check that the other mocks we made are actually called\n        mock_chdir.assert_called()\n        mock_os_unlink.assert_called()\n        mock_os_path_exists.assert_called()\n        mock_os_path_isfile.assert_called()\n\n\nclass GenericBootstrapTest(BaseClassSetupBootstrap):\n    \"\"\"\n    An inherited class of `BaseClassSetupBootstrap` which will extends his\n    functionality by adding some generic bootstrap tests, so this way we can\n    test all our sub modules of :mod:`~pythonforandroid.bootstraps` from within\n    this module.\n\n    .. warning:: This is supposed to be used as a base class, so please, don't\n                 use this directly.\n    \"\"\"\n\n    @property\n    def bootstrap_name(self):\n        \"\"\"Subclasses must have property 'bootstrap_name'. It should be the\n        name of the bootstrap to test\"\"\"\n        raise NotImplementedError(\"Not implemented in GenericBootstrapTest\")\n\n    @mock.patch(\"pythonforandroid.bootstraps.qt.shprint\")\n    @mock.patch(\"pythonforandroid.bootstraps.qt.rmdir\")\n    @mock.patch(\"pythonforandroid.bootstraps.qt.open\", create=True)\n    @mock.patch(\"pythonforandroid.bootstrap.open\", create=True)\n    @mock.patch(\"pythonforandroid.distribution.open\", create=True)\n    @mock.patch(\"pythonforandroid.bootstrap.Bootstrap.strip_libraries\")\n    @mock.patch(\"pythonforandroid.util.exists\")\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.bootstrap.listdir\")\n    @mock.patch(\"pythonforandroid.bootstrap.rmdir\")\n    @mock.patch(\"pythonforandroid.bootstrap.shprint\")\n    def test_assemble_distribution(\n        self,\n        mock_shprint,\n        mock_rmdir,\n        mock_listdir,\n        mock_chdir,\n        mock_ensure_dir,\n        mock_strip_libraries,\n        mock_open_dist_files,\n        mock_open_bootstrap_files,\n        mock_open_qt_files,\n        mock_qt_rmdir,\n        mock_qt_shprint\n    ):\n        \"\"\"\n        A test for any overwritten method of\n        `~pythonforandroid.bootstrap.Bootstrap.assemble_distribution`. Here we mock\n        any file/dir operation that it could slow down our tests, and there is\n        a lot to mock, because the `assemble_distribution` method it should take care\n        of prepare all compiled files to generate the final `apk`. The targets\n        of this test will be:\n\n            - :meth:`~pythonforandroid.bootstraps.sdl2.BootstrapSdl2\n              .assemble_distribution`\n            - :meth:`~pythonforandroid.bootstraps.service_only\n              .ServiceOnlyBootstrap.assemble_distribution`\n            - :meth:`~pythonforandroid.bootstraps.webview.WebViewBootstrap\n               .assemble_distribution`\n            - :meth:`~pythonforandroid.bootstraps.empty.EmptyBootstrap.\n              assemble_distribution`\n\n        Here we will tests all those methods that are specific for each class.\n        \"\"\"\n        # prepare bootstrap and distribution\n        bs = Bootstrap.get_bootstrap(self.bootstrap_name, self.ctx)\n        self.assertNotEqual(bs.ctx, None)\n        bs.build_dir = bs.get_build_dir()\n        self.setUp_distribution_with_bootstrap(bs)\n\n        self.ctx.hostpython = \"/some/fake/hostpython3\"\n        self.ctx.python_recipe = Recipe.get_recipe(\"python3\", self.ctx)\n        self.ctx.python_recipe.create_python_bundle = mock.MagicMock()\n        self.ctx.python_modules = [\"requests\"]\n        self.ctx.archs = [ArchARMv7_a(self.ctx)]\n        self.ctx.bootstrap = bs\n\n        bs.assemble_distribution()\n\n        mock_open_dist_files.assert_called_once_with(\"dist_info.json\", \"w\")\n        # Qt bootstrap has its own assemble_distribution, others use base class\n        if self.bootstrap_name == \"qt\":\n            mock_open_bs = mock_open_qt_files\n        else:\n            mock_open_bs = mock_open_bootstrap_files\n        expected_open_calls = {\n            \"sdl2\": [\n                mock.call(\"local.properties\", \"w\"),\n                mock.call(\"blacklist.txt\", \"a\"),\n            ],\n            \"sdl3\": [\n                mock.call(\"local.properties\", \"w\"),\n                mock.call(\"blacklist.txt\", \"a\"),\n            ],\n            \"webview\": [\n                mock.call(\"local.properties\", \"w\"),\n                mock.call(\"blacklist.txt\", \"a\"),\n            ],\n            \"service_only\": [\n                mock.call(\"local.properties\", \"w\"),\n                mock.call(\"blacklist.txt\", \"a\"),\n            ],\n            \"qt\": [mock.call(\"local.properties\", \"w\")]\n        }\n        # test that the expected calls has been called\n        for expected_call in expected_open_calls[self.bootstrap_name]:\n            self.assertIn(expected_call, mock_open_bs.call_args_list)\n        # test that the write function has been called with the expected args\n        self.assertIn(\n            mock.call().__enter__().write(\"sdk.dir=/opt/android/android-sdk\"),\n            mock_open_bs.mock_calls,\n        )\n        if self.bootstrap_name in [\"sdl2\", \"sdl3\", \"webview\", \"service_only\"]:\n            self.assertIn(\n                mock.call()\n                .__enter__()\n                .write(\"\\nsqlite3/*\\nlib-dynload/_sqlite3.so\\n\"),\n                mock_open_bs.mock_calls,\n            )\n\n        # check that the other mocks we made are actually called\n        mock_shprint.assert_called()\n        mock_chdir.assert_called()\n        mock_listdir.assert_called()\n        mock_strip_libraries.assert_called()\n        expected__python_bundle = os.path.join(\n            self.ctx.dist_dir,\n            self.ctx.bootstrap.distribution.name,\n            f\"_python_bundle__{self.TEST_ARCH}\",\n            \"_python_bundle\",\n        )\n        self.assertIn(\n            mock.call(expected__python_bundle, self.ctx.archs[0]),\n            self.ctx.python_recipe.create_python_bundle.call_args_list,\n        )\n\n    @mock.patch(\"pythonforandroid.bootstrap.shprint\")\n    @mock.patch(\"pythonforandroid.bootstrap.glob.glob\")\n    @mock.patch(\"pythonforandroid.bootstrap.ensure_dir\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    def test_distribute_methods(\n        self, mock_build_dir, mock_bs_dir, mock_glob, mock_shprint\n    ):\n        # prepare arch, bootstrap and distribution\n        arch = ArchARMv7_a(self.ctx)\n        bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx)\n        self.setUp_distribution_with_bootstrap(bs)\n\n        # a convenient method to reset mocks in one shot\n        def reset_mocks():\n            mock_glob.reset_mock()\n            mock_shprint.reset_mock()\n            mock_build_dir.reset_mock()\n            mock_bs_dir.reset_mock()\n\n        # test distribute_libs\n        mock_glob.return_value = [\n            \"/fake_dir/libsqlite3.so\",\n            \"/fake_dir/libpng16.so\",\n        ]\n        bs.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])\n        libs_dir = os.path.join(\"libs\", arch.arch)\n        # we expect two calls to glob/copy command via shprint\n        self.assertEqual(len(mock_glob.call_args_list), 2)\n        self.assertEqual(len(mock_shprint.call_args_list), 1)\n        self.assertEqual(\n            mock_shprint.call_args_list,\n            [mock.call(sh.cp, \"-a\", *mock_glob.return_value, libs_dir)]\n        )\n        mock_build_dir.assert_called()\n        mock_bs_dir.assert_called_once_with(libs_dir)\n        reset_mocks()\n\n        # test distribute_javaclasses\n        mock_glob.return_value = [\"/fakedir/java_file.java\"]\n        bs.distribute_javaclasses(self.ctx.javaclass_dir)\n        mock_glob.assert_called_once_with(self.ctx.javaclass_dir)\n        mock_build_dir.assert_called_with(self.ctx.javaclass_dir)\n        mock_bs_dir.assert_called_once_with(\"src\")\n        self.assertEqual(\n            mock_shprint.call_args,\n            mock.call(sh.cp, \"-a\", \"/fakedir/java_file.java\", \"src\"),\n        )\n        reset_mocks()\n\n        # test distribute_aars\n        mock_glob.return_value = [\"/fakedir/file.aar\"]\n        bs.distribute_aars(arch)\n        mock_build_dir.assert_called_with(self.ctx.aars_dir)\n        # We expect three calls to shprint: unzip, cp, cp\n        zip_call, kw = mock_shprint.call_args_list[0]\n        self.assertEqual(zip_call[0], sh.unzip)\n        self.assertEqual(zip_call[2], \"/fakedir/file.aar\")\n        cp_java_call, kw = mock_shprint.call_args_list[1]\n        self.assertEqual(cp_java_call[0], sh.cp)\n        self.assertTrue(cp_java_call[2].endswith(\"classes.jar\"))\n        self.assertEqual(cp_java_call[3], \"libs/file.jar\")\n        cp_libs_call, kw = mock_shprint.call_args_list[2]\n        self.assertEqual(cp_libs_call[0], sh.cp)\n        self.assertEqual(cp_libs_call[2], \"/fakedir/file.aar\")\n        self.assertEqual(cp_libs_call[3], libs_dir)\n        mock_bs_dir.assert_has_calls([mock.call(\"libs\"), mock.call(libs_dir)])\n        mock_glob.assert_called()\n\n    @mock.patch(\"pythonforandroid.bootstrap.shprint\")\n    @mock.patch(\"pythonforandroid.bootstrap.sh.Command\")\n    @mock.patch(\"pythonforandroid.build.ensure_dir\")\n    @mock.patch(\"shutil.which\")\n    def test_bootstrap_strip(\n        self,\n        mock_shutil_which,\n        mock_ensure_dir,\n        mock_sh_command,\n        mock_sh_print,\n    ):\n        mock_shutil_which.return_value = os.path.join(\n            self.ctx._ndk_dir,\n            f\"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang\",\n        )\n        # prepare arch, bootstrap, distribution and PythonRecipe\n        arch = ArchARMv7_a(self.ctx)\n        bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx)\n        self.setUp_distribution_with_bootstrap(bs)\n        self.ctx.python_recipe = Recipe.get_recipe(\"python3\", self.ctx)\n\n        # test that strip_libraries runs with a fake distribution\n        bs.strip_libraries(arch)\n\n        self.assertEqual(\n            mock_shutil_which.call_args[0][0],\n            mock_shutil_which.return_value,\n        )\n        mock_sh_command.assert_called_once_with(\n            os.path.join(\n                self.ctx._ndk_dir,\n                f\"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin\",\n                \"llvm-strip\",\n            )\n        )\n        # check that the other mocks we made are actually called\n        mock_ensure_dir.assert_called()\n        mock_sh_print.assert_called()\n\n    @mock.patch(\"pythonforandroid.bootstrap.listdir\")\n    @mock.patch(\"pythonforandroid.bootstrap.rmdir\")\n    @mock.patch(\"pythonforandroid.bootstrap.move\")\n    @mock.patch(\"pythonforandroid.bootstrap.isdir\")\n    def test_bootstrap_fry_eggs(\n        self, mock_isdir, mock_move, mock_rmdir, mock_listdir\n    ):\n        mock_listdir.return_value = [\n            \"jnius\",\n            \"kivy\",\n            \"Kivy-1.11.0.dev0-py3.7.egg-info\",\n            \"pyjnius-1.2.1.dev0-py3.7.egg\",\n        ]\n\n        # prepare bootstrap, context and distribution\n        bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx)\n        self.setUp_distribution_with_bootstrap(bs)\n\n        # test that fry_eggs runs with a fake distribution\n        site_packages = os.path.join(\n            bs.dist_dir, \"_python_bundle\", \"_python_bundle\"\n        )\n        bs.fry_eggs(site_packages)\n\n        mock_listdir.assert_has_calls(\n            [\n                mock.call(site_packages),\n                mock.call(\n                    os.path.join(site_packages, \"pyjnius-1.2.1.dev0-py3.7.egg\")\n                ),\n            ]\n        )\n        self.assertEqual(\n            mock_rmdir.call_args[0][0], \"pyjnius-1.2.1.dev0-py3.7.egg\"\n        )\n        # check that the other mocks we made are actually called\n        mock_isdir.assert_called()\n        mock_move.assert_called()\n\n\nclass TestBootstrapSdl2(GenericBootstrapTest, unittest.TestCase):\n    \"\"\"\n    An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.bootstraps.sdl2.BootstrapSdl2`.\n    \"\"\"\n\n    @property\n    def bootstrap_name(self):\n        return \"sdl2\"\n\n\nclass TestBootstrapSdl3(GenericBootstrapTest, unittest.TestCase):\n    \"\"\"\n    An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.bootstraps.sdl3.BootstrapSdl3`.\n    \"\"\"\n\n    @property\n    def bootstrap_name(self):\n        return \"sdl3\"\n\n\nclass TestBootstrapServiceOnly(GenericBootstrapTest, unittest.TestCase):\n    \"\"\"\n    An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.bootstraps.service_only.ServiceOnlyBootstrap`.\n    \"\"\"\n\n    @property\n    def bootstrap_name(self):\n        return \"service_only\"\n\n\nclass TestBootstrapWebview(GenericBootstrapTest, unittest.TestCase):\n    \"\"\"\n    An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.bootstraps.webview.WebViewBootstrap`.\n    \"\"\"\n\n    @property\n    def bootstrap_name(self):\n        return \"webview\"\n\n\nclass TestBootstrapEmpty(GenericBootstrapTest, unittest.TestCase):\n    \"\"\"\n    An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.bootstraps.empty.EmptyBootstrap`.\n\n    .. note:: here will test most of the base class methods, because we only\n              overwrite :meth:`~pythonforandroid.bootstraps.empty.\n              EmptyBootstrap.assemble_distribution`\n    \"\"\"\n\n    @property\n    def bootstrap_name(self):\n        return \"empty\"\n\n    def test_assemble_distribution(self, *args):\n        # prepare bootstrap\n        bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx)\n        self.ctx.bootstrap = bs\n\n        # test dist_dir error\n        with self.assertRaises(SystemExit) as e:\n            bs.assemble_distribution()\n        self.assertEqual(e.exception.args[0], 1)\n\n\nclass TestBootstrapQt(GenericBootstrapTest, unittest.TestCase):\n    \"\"\"\n    An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which\n    will be used to perform tests for\n    :class:`~pythonforandroid.bootstraps.qt.BootstrapQt`.\n    \"\"\"\n\n    @property\n    def bootstrap_name(self):\n        return \"qt\"\n"
  },
  {
    "path": "tests/test_bootstrap_build.py",
    "content": "import unittest\nfrom unittest import mock\nimport pytest\nimport os\n\nfrom pythonforandroid.util import load_source\n\n\nclass TestBootstrapBuild(unittest.TestCase):\n    def setUp(self):\n        os.environ[\"P4A_BUILD_IS_RUNNING_UNITTESTS\"] = \"1\"\n\n        build_src = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)),\n            \"../pythonforandroid/bootstraps/common/build/build.py\",\n        )\n\n        self.buildpy = load_source(\"buildpy\", build_src)\n        self.buildpy.get_bootstrap_name = mock.Mock(return_value=\"sdl2\")\n\n        self.ap = self.buildpy.create_argument_parser()\n\n        self.common_args = [\n            \"--package\",\n            \"org.test.app\",\n            \"--name\",\n            \"TestApp\",\n            \"--version\",\n            \"0.1\",\n        ]\n\n\nclass TestParsePermissions(TestBootstrapBuild):\n    def test_parse_permissions_with_migrations(self):\n        # Test that permissions declared in the old format are migrated to the\n        # new format.\n        # (Users can new declare permissions in both formats, even a mix)\n\n        self.ap = self.buildpy.create_argument_parser()\n\n        args = [\n            *self.common_args,\n            \"--permission\",\n            \"INTERNET\",\n            \"--permission\",\n            \"com.android.voicemail.permission.ADD_VOICEMAIL\",\n            \"--permission\",\n            \"(name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)\",\n            \"--permission\",\n            \"(name=android.permission.BLUETOOTH_SCAN;usesPermissionFlags=neverForLocation)\",\n        ]\n\n        args = self.ap.parse_args(args)\n\n        parsed_permissions = self.buildpy.parse_permissions(args.permissions)\n\n        assert parsed_permissions == [\n            dict(name=\"android.permission.INTERNET\"),\n            dict(name=\"com.android.voicemail.permission.ADD_VOICEMAIL\"),\n            dict(name=\"android.permission.WRITE_EXTERNAL_STORAGE\", maxSdkVersion=\"18\"),\n            dict(\n                name=\"android.permission.BLUETOOTH_SCAN\",\n                usesPermissionFlags=\"neverForLocation\",\n            ),\n        ]\n\n    def test_parse_permissions_invalid_property(self):\n\n        self.ap = self.buildpy.create_argument_parser()\n\n        args = [\n            *self.common_args,\n            \"--permission\",\n            \"(name=android.permission.BLUETOOTH_SCAN;propertyThatFails=neverForLocation)\",\n        ]\n\n        args = self.ap.parse_args(args)\n\n        with pytest.raises(\n            ValueError, match=\"Property 'propertyThatFails' is not supported.\"\n        ):\n            self.buildpy.parse_permissions(args.permissions)\n\n\nclass TestOrientationArg(TestBootstrapBuild):\n    def test_no_orientation_args(self):\n\n        args = self.common_args\n\n        args = self.ap.parse_args(args)\n\n        assert (\n            self.buildpy.get_manifest_orientation(\n                args.orientation, args.manifest_orientation\n            )\n            == \"unspecified\"\n        )\n        assert self.buildpy.get_sdl_orientation_hint(args.orientation) == \"\"\n\n    def test_manifest_orientation_present(self):\n\n        args = [\n            *self.common_args,\n            \"--orientation\",\n            \"landscape\",\n            \"--orientation\",\n            \"portrait\",\n            \"--manifest-orientation\",\n            \"fullSensor\",\n        ]\n\n        args = self.ap.parse_args(args)\n\n        assert (\n            self.buildpy.get_manifest_orientation(\n                args.orientation, manifest_orientation=args.manifest_orientation\n            )\n            == \"fullSensor\"\n        )\n\n    def test_manifest_orientation_supported(self):\n\n        args = [*self.common_args, \"--orientation\", \"landscape\"]\n\n        args = self.ap.parse_args(args)\n\n        assert (\n            self.buildpy.get_manifest_orientation(\n                args.orientation, manifest_orientation=args.manifest_orientation\n            )\n            == \"landscape\"\n        )\n\n    def test_android_manifest_multiple_orientation_supported(self):\n\n        args = [\n            *self.common_args,\n            \"--orientation\",\n            \"landscape\",\n            \"--orientation\",\n            \"portrait\",\n        ]\n\n        args = self.ap.parse_args(args)\n\n        assert (\n            self.buildpy.get_manifest_orientation(\n                args.orientation, manifest_orientation=args.manifest_orientation\n            )\n            == \"unspecified\"\n        )\n\n    def test_sdl_orientation_hint_single(self):\n\n        args = [*self.common_args, \"--orientation\", \"landscape\"]\n\n        args = self.ap.parse_args(args)\n\n        assert (\n            self.buildpy.get_sdl_orientation_hint(args.orientation) == \"LandscapeLeft\"\n        )\n\n    def test_sdl_orientation_hint_multiple(self):\n\n        args = [\n            *self.common_args,\n            \"--orientation\",\n            \"landscape\",\n            \"--orientation\",\n            \"portrait\",\n        ]\n\n        args = self.ap.parse_args(args)\n\n        sdl_orientation_hint = self.buildpy.get_sdl_orientation_hint(\n            args.orientation\n        ).split(\" \")\n\n        assert \"LandscapeLeft\" in sdl_orientation_hint\n        assert \"Portrait\" in sdl_orientation_hint\n"
  },
  {
    "path": "tests/test_build.py",
    "content": "import os\nimport unittest\nfrom unittest import mock\n\nimport jinja2\n\nfrom pythonforandroid.build import (\n    Context, RECOMMENDED_TARGET_API, run_pymodules_install, process_python_modules\n)\nfrom pythonforandroid.archs import ArchARMv7_a, ArchAarch_64\n\n\nclass TestBuildBasic(unittest.TestCase):\n\n    def test_run_pymodules_install_optional_project_dir(self):\n        \"\"\"\n        Makes sure the `run_pymodules_install()` doesn't crash when the\n        `project_dir` optional parameter is None, refs #1898\n        \"\"\"\n        ctx = mock.Mock(recipe_build_order=[])\n        ctx.archs = [ArchARMv7_a(ctx), ArchAarch_64(ctx)]\n        modules = []\n        project_dir = None\n        with mock.patch('pythonforandroid.build.info') as m_info:\n            assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None\n        assert m_info.call_args_list[-1] == mock.call(\n            'No Python modules and no setup.py to process, skipping')\n\n    def test_python_module_parser(self):\n        ctx = mock.Mock(recipe_build_order=[])\n        ctx.archs = [ArchARMv7_a(ctx), ArchAarch_64(ctx)]\n        # should not alter original module name (like with adding version number)\n        assert \"kivy_garden.frostedglass\" in process_python_modules(ctx, [\"kivy_garden.frostedglass\"])\n\n        # should skip urls and other unsupported format\n        modules = [\"https://example.com/some.zip\", \"git+https://github.com/kivy/python-for-android@develop\"]\n        result = process_python_modules(ctx, modules)\n        assert modules == result\n\n    def test_strip_if_with_debug_symbols(self):\n        ctx = mock.Mock(recipe_build_order=[])\n        ctx.python_recipe.major_minor_version_string = \"3.6\"\n        ctx.get_site_packages_dir.return_value = \"test-doesntexist\"\n        ctx.build_dir = \"nonexistant_directory\"\n        ctx.archs = [\"arm64\"]\n\n        modules = [\"mymodule\"]\n        project_dir = None\n        with mock.patch('pythonforandroid.build.info'), \\\n                mock.patch('sh.Command'), \\\n                mock.patch('pythonforandroid.build.open'), \\\n                mock.patch('pythonforandroid.build.shprint'), \\\n                mock.patch('pythonforandroid.build.current_directory'), \\\n                mock.patch('pythonforandroid.build.CythonRecipe') as m_CythonRecipe, \\\n                mock.patch('pythonforandroid.build.project_has_setup_py') as m_project_has_setup_py, \\\n                mock.patch('pythonforandroid.build.run_setuppy_install'):\n            m_project_has_setup_py.return_value = False\n\n            # Make sure it is NOT called when `with_debug_symbols` is true:\n            ctx.with_debug_symbols = True\n            assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None\n            assert m_CythonRecipe().strip_object_files.called is False\n\n            # Make sure strip object files IS called when\n            # `with_debug_symbols` is false:\n            ctx.with_debug_symbols = False\n            assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None\n            assert m_CythonRecipe().strip_object_files.called is True\n\n\nclass TestTemplates(unittest.TestCase):\n\n    def test_android_manifest_xml(self):\n        args = mock.Mock()\n        args.min_sdk_version = 12\n        args.build_mode = 'debug'\n        args.native_services = ['abcd', ]\n        args.permissions = [\n            dict(name=\"android.permission.INTERNET\"),\n            dict(name=\"android.permission.WRITE_EXTERNAL_STORAGE\", maxSdkVersion=18),\n            dict(name=\"android.permission.BLUETOOTH_SCAN\", usesPermissionFlags=\"neverForLocation\")]\n        args.add_activity = []\n        args.android_used_libs = []\n        args.meta_data = []\n        args.extra_manifest_xml = '<tag-a><tag-b></tag-b></tag-a>'\n        args.extra_manifest_application_arguments = 'android:someParameter=\"true\" android:anotherParameter=\"false\"'\n        render_args = {\n            \"args\": args,\n            \"service\": False,\n            \"service_names\": [],\n            \"android_api\": 1234,\n            \"debug\": \"debug\" in args.build_mode,\n            \"native_services\": args.native_services\n        }\n        environment = jinja2.Environment(\n            loader=jinja2.FileSystemLoader('pythonforandroid/bootstraps/_sdl_common/build/templates/')\n        )\n        template = environment.get_template('AndroidManifest.tmpl.xml')\n        xml = template.render(**render_args)\n        assert xml.count('android:minSdkVersion=\"12\"') == 1\n        assert xml.count('android:anotherParameter=\"false\"') == 1\n        assert xml.count('android:someParameter=\"true\"') == 1\n        assert xml.count('<tag-a><tag-b></tag-b></tag-a>') == 1\n        assert xml.count('android:process=\":service_') == 0\n        assert xml.count('targetSdkVersion=\"1234\"') == 1\n        assert xml.count('android:debuggable=\"true\"') == 1\n        assert xml.count('<service android:name=\"abcd\" />') == 1\n        assert xml.count('<uses-permission android:name=\"android.permission.INTERNET\" />') == 1\n        assert xml.count('<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" android:maxSdkVersion=\"18\" />') == 1\n        assert xml.count('<uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" android:usesPermissionFlags=\"neverForLocation\" />') == 1\n        # TODO: potentially some other checks to be added here to cover other \"logic\" (flags and loops) in the template\n\n\nclass TestContext(unittest.TestCase):\n\n    @mock.patch.dict('pythonforandroid.build.Context.env')\n    @mock.patch('pythonforandroid.build.get_available_apis')\n    @mock.patch('pythonforandroid.build.ensure_dir')\n    def test_sdk_ndk_paths(\n            self,\n            mock_ensure_dir,\n            mock_get_available_apis,\n    ):\n        mock_get_available_apis.return_value = [RECOMMENDED_TARGET_API]\n        context = Context()\n        context.setup_dirs(os.getcwd())\n        context.prepare_build_environment(\n            user_sdk_dir='sdk',\n            user_ndk_dir='ndk',\n            user_android_api=None,\n            user_ndk_api=None,\n        )\n\n        # The context was supplied with relative SDK and NDK dirs. Check\n        # that it resolved them to absolute paths.\n        real_sdk_dir = os.path.join(os.getcwd(), 'sdk')\n        real_ndk_dir = os.path.join(os.getcwd(), 'ndk')\n        assert context.sdk_dir == real_sdk_dir\n        assert context.ndk_dir == real_ndk_dir\n\n        context_paths = context.env['PATH'].split(':')\n        assert context_paths[0:3] == [\n            f'{real_ndk_dir}/toolchains/llvm/prebuilt/{context.ndk.host_tag}/bin',\n            real_ndk_dir,\n            f'{real_sdk_dir}/tools'\n        ]\n"
  },
  {
    "path": "tests/test_checkdependencies.py",
    "content": "import sys\nfrom unittest import mock\n\nfrom pythonforandroid import checkdependencies\n\n\nclass TestCheckPythonDependencies:\n    \"\"\"Test check_python_dependencies function.\"\"\"\n\n    @mock.patch('pythonforandroid.checkdependencies.import_module')\n    def test_all_modules_present(self, mock_import):\n        \"\"\"Test that check_python_dependencies completes when all modules are present.\"\"\"\n        # Mock all required modules\n        mock_colorama = mock.Mock()\n        mock_colorama.__version__ = '0.4.0'\n        mock_sh = mock.Mock()\n        mock_sh.__version__ = '1.12'\n        mock_appdirs = mock.Mock()\n        mock_jinja2 = mock.Mock()\n\n        def import_side_effect(name):\n            if name == 'colorama':\n                return mock_colorama\n            elif name == 'sh':\n                return mock_sh\n            elif name == 'appdirs':\n                return mock_appdirs\n            elif name == 'jinja2':\n                return mock_jinja2\n            raise ImportError(f\"No module named '{name}'\")\n\n        mock_import.side_effect = import_side_effect\n\n        with mock.patch.object(sys, 'modules', {\n            'colorama': mock_colorama,\n            'sh': mock_sh,\n            'appdirs': mock_appdirs,\n            'jinja2': mock_jinja2\n        }):\n            checkdependencies.check_python_dependencies()\n\n    @mock.patch('builtins.exit')\n    @mock.patch('builtins.print')\n    @mock.patch('pythonforandroid.checkdependencies.import_module')\n    def test_missing_module_without_version(self, mock_import, mock_print, mock_exit):\n        \"\"\"Test error message when module without version requirement is missing.\"\"\"\n        modules_dict = {}\n\n        def import_side_effect(name):\n            if name == 'appdirs':\n                raise ImportError(f\"No module named '{name}'\")\n            mock_mod = mock.Mock()\n            mock_mod.__version__ = '1.0'\n            modules_dict[name] = mock_mod\n            return mock_mod\n\n        mock_import.side_effect = import_side_effect\n\n        with mock.patch.object(sys, 'modules', modules_dict):\n            checkdependencies.check_python_dependencies()\n\n        # Verify error message was printed\n        error_calls = [str(call) for call in mock_print.call_args_list]\n        assert any('appdirs' in call and 'ERROR' in call for call in error_calls)\n        mock_exit.assert_called_once_with(1)\n\n    @mock.patch('builtins.exit')\n    @mock.patch('builtins.print')\n    @mock.patch('pythonforandroid.checkdependencies.import_module')\n    def test_missing_module_with_version(self, mock_import, mock_print, mock_exit):\n        \"\"\"Test error message when module with version requirement is missing.\"\"\"\n        modules_dict = {}\n\n        def import_side_effect(name):\n            if name == 'colorama':\n                raise ImportError(f\"No module named '{name}'\")\n            mock_mod = mock.Mock()\n            mock_mod.__version__ = '1.0'\n            modules_dict[name] = mock_mod\n            return mock_mod\n\n        mock_import.side_effect = import_side_effect\n\n        with mock.patch.object(sys, 'modules', modules_dict):\n            checkdependencies.check_python_dependencies()\n\n        # Verify error message includes version requirement\n        error_calls = [str(call) for call in mock_print.call_args_list]\n        assert any('colorama' in call and '0.3.3' in call for call in error_calls)\n        mock_exit.assert_called_once_with(1)\n\n    @mock.patch('builtins.exit')\n    @mock.patch('builtins.print')\n    @mock.patch('pythonforandroid.checkdependencies.import_module')\n    def test_module_version_too_old(self, mock_import, mock_print, mock_exit):\n        \"\"\"Test error when module version is too old.\"\"\"\n        mock_colorama = mock.Mock()\n        mock_colorama.__version__ = '0.2.0'  # Too old, needs 0.3.3\n        modules_dict = {'colorama': mock_colorama}\n\n        def import_side_effect(name):\n            if name == 'colorama':\n                return mock_colorama\n            mock_mod = mock.Mock()\n            mock_mod.__version__ = '1.0'\n            modules_dict[name] = mock_mod\n            return mock_mod\n\n        mock_import.side_effect = import_side_effect\n\n        with mock.patch.object(sys, 'modules', modules_dict):\n            checkdependencies.check_python_dependencies()\n\n        # Verify error message about version\n        error_calls = [str(call) for call in mock_print.call_args_list]\n        assert any('version' in call.lower() and 'colorama' in call for call in error_calls)\n        mock_exit.assert_called_once_with(1)\n\n    @mock.patch('pythonforandroid.checkdependencies.import_module')\n    def test_module_version_acceptable(self, mock_import):\n        \"\"\"Test that acceptable versions pass.\"\"\"\n        mock_colorama = mock.Mock()\n        mock_colorama.__version__ = '0.4.0'  # Newer than 0.3.3\n        mock_sh = mock.Mock()\n        mock_sh.__version__ = '1.12'  # Newer than 1.10\n\n        def import_side_effect(name):\n            if name == 'colorama':\n                return mock_colorama\n            elif name == 'sh':\n                return mock_sh\n            mock_mod = mock.Mock()\n            return mock_mod\n\n        mock_import.side_effect = import_side_effect\n\n        with mock.patch.object(sys, 'modules', {\n            'colorama': mock_colorama,\n            'sh': mock_sh\n        }):\n            # Should complete without error\n            checkdependencies.check_python_dependencies()\n\n    @mock.patch('pythonforandroid.checkdependencies.import_module')\n    def test_module_without_version_attribute(self, mock_import):\n        \"\"\"Test handling of modules that don't have __version__.\"\"\"\n        mock_colorama = mock.Mock(spec=[])  # No __version__ attribute\n        modules_dict = {'colorama': mock_colorama}\n\n        def import_side_effect(name):\n            if name == 'colorama':\n                return mock_colorama\n            mock_mod = mock.Mock()\n            modules_dict[name] = mock_mod\n            return mock_mod\n\n        mock_import.side_effect = import_side_effect\n\n        with mock.patch.object(sys, 'modules', modules_dict):\n            # Should complete without error (version check is skipped)\n            checkdependencies.check_python_dependencies()\n\n\nclass TestCheck:\n    \"\"\"Test the main check() function.\"\"\"\n\n    @mock.patch('pythonforandroid.checkdependencies.check_python_dependencies')\n    @mock.patch('pythonforandroid.checkdependencies.check_and_install_default_prerequisites')\n    def test_check_with_skip_prerequisites(self, mock_prereqs, mock_python_deps):\n        \"\"\"Test check() skips prerequisites when SKIP_PREREQUISITES_CHECK=1.\"\"\"\n        with mock.patch.dict('os.environ', {'SKIP_PREREQUISITES_CHECK': '1'}):\n            checkdependencies.check()\n\n        mock_prereqs.assert_not_called()\n        mock_python_deps.assert_called_once()\n\n    @mock.patch('pythonforandroid.checkdependencies.check_python_dependencies')\n    @mock.patch('pythonforandroid.checkdependencies.check_and_install_default_prerequisites')\n    def test_check_without_skip(self, mock_prereqs, mock_python_deps):\n        \"\"\"Test check() runs prerequisites when SKIP_PREREQUISITES_CHECK is not set.\"\"\"\n        with mock.patch.dict('os.environ', {}, clear=True):\n            checkdependencies.check()\n\n        mock_prereqs.assert_called_once()\n        mock_python_deps.assert_called_once()\n\n    @mock.patch('pythonforandroid.checkdependencies.check_python_dependencies')\n    @mock.patch('pythonforandroid.checkdependencies.check_and_install_default_prerequisites')\n    def test_check_with_skip_set_to_zero(self, mock_prereqs, mock_python_deps):\n        \"\"\"Test check() runs prerequisites when SKIP_PREREQUISITES_CHECK=0.\"\"\"\n        with mock.patch.dict('os.environ', {'SKIP_PREREQUISITES_CHECK': '0'}):\n            checkdependencies.check()\n\n        mock_prereqs.assert_called_once()\n        mock_python_deps.assert_called_once()\n"
  },
  {
    "path": "tests/test_distribution.py",
    "content": "import os\nimport json\nimport unittest\nfrom unittest import mock\n\nfrom pythonforandroid.bootstrap import Bootstrap\nfrom pythonforandroid.distribution import Distribution\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import BuildInterruptingException\nfrom pythonforandroid.build import Context\n\ndist_info_data = {\n    \"dist_name\": \"sdl2_dist\",\n    \"bootstrap\": \"sdl2\",\n    \"archs\": [\"armeabi\", \"armeabi-v7a\", \"x86\", \"x86_64\", \"arm64-v8a\"],\n    \"ndk_api\": 21,\n    \"use_setup_py\": False,\n    \"recipes\": [\"hostpython3\", \"python3\", \"sdl2\", \"kivy\", \"requests\"],\n    \"hostpython\": \"/some/fake/hostpython3\",\n    \"python_version\": \"3.7\",\n}\n\n\nclass TestDistribution(unittest.TestCase):\n    \"\"\"\n    An inherited class of `unittest.TestCase`to test the module\n    :mod:`~pythonforandroid.distribution`.\n    \"\"\"\n\n    TEST_ARCH = 'armeabi-v7a'\n\n    def setUp(self):\n        \"\"\"Configure a :class:`~pythonforandroid.build.Context` so we can\n        perform our unittests\"\"\"\n        self.ctx = Context()\n        self.ctx.ndk_api = 21\n        self.ctx.android_api = 27\n        self.ctx._sdk_dir = \"/opt/android/android-sdk\"\n        self.ctx._ndk_dir = \"/opt/android/android-ndk\"\n        self.ctx.setup_dirs(os.getcwd())\n        self.ctx.recipe_build_order = [\n            \"hostpython3\",\n            \"python3\",\n            \"sdl2\",\n            \"kivy\",\n        ]\n\n    def setUp_distribution_with_bootstrap(self, bs, **kwargs):\n        \"\"\"Extend the setUp by configuring a distribution, because some test\n        needs a distribution to be set to be properly tested\"\"\"\n        self.ctx.bootstrap = bs\n        self.ctx.bootstrap.distribution = Distribution.get_distribution(\n            self.ctx,\n            name=kwargs.pop(\"name\", \"test_prj\"),\n            recipes=kwargs.pop(\"recipes\", [\"python3\", \"kivy\"]),\n            archs=[self.TEST_ARCH],\n            **kwargs\n        )\n\n    def tearDown(self):\n        \"\"\"Here we make sure that we reset a possible bootstrap created in\n        `setUp_distribution_with_bootstrap`\"\"\"\n        self.ctx.bootstrap = None\n\n    def test_properties(self):\n        \"\"\"Test that some attributes has the expected result (for now, we check\n        that `__repr__` and `__str__` return the proper values\"\"\"\n        self.setUp_distribution_with_bootstrap(\n            Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        )\n        distribution = self.ctx.bootstrap.distribution\n        self.assertEqual(self.ctx, distribution.ctx)\n        expected_repr = (\n            \"<Distribution: name test_prj with recipes (python3, kivy)>\"\n        )\n        self.assertEqual(distribution.__str__(), expected_repr)\n        self.assertEqual(distribution.__repr__(), expected_repr)\n\n    @mock.patch(\"pythonforandroid.distribution.exists\")\n    def test_folder_exist(self, mock_exists):\n        \"\"\"Test that method\n        :meth:`~pythonforandroid.distribution.Distribution.folder_exist` is\n        called once with the proper arguments.\"\"\"\n\n        mock_exists.return_value = False\n        self.setUp_distribution_with_bootstrap(\n            Bootstrap.get_bootstrap(\"sdl2\", self.ctx)\n        )\n        self.ctx.bootstrap.distribution.folder_exists()\n        mock_exists.assert_called_with(\n            self.ctx.bootstrap.distribution.dist_dir\n        )\n\n    @mock.patch(\"pythonforandroid.distribution.rmdir\")\n    def test_delete(self, mock_rmdir):\n        \"\"\"Test that method\n        :meth:`~pythonforandroid.distribution.Distribution.delete` is\n        called once with the proper arguments.\"\"\"\n        self.setUp_distribution_with_bootstrap(\n            Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        )\n        self.ctx.bootstrap.distribution.delete()\n        mock_rmdir.assert_called_once_with(\n            self.ctx.bootstrap.distribution.dist_dir\n        )\n\n    @mock.patch(\"pythonforandroid.distribution.exists\")\n    def test_get_distribution_no_name(self, mock_exists):\n        \"\"\"Test that method\n        :meth:`~pythonforandroid.distribution.Distribution.get_distribution`\n        returns the proper result which should `unnamed_dist_1`.\"\"\"\n        mock_exists.return_value = False\n        self.ctx.bootstrap = Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        dist = Distribution.get_distribution(self.ctx, archs=[self.TEST_ARCH])\n        self.assertEqual(dist.name, \"unnamed_dist_1\")\n\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    @mock.patch(\"pythonforandroid.distribution.open\", create=True)\n    def test_save_info(self, mock_open_dist_info, mock_chdir):\n        \"\"\"Test that method\n        :meth:`~pythonforandroid.distribution.Distribution.save_info`\n        is called once with the proper arguments.\"\"\"\n        self.setUp_distribution_with_bootstrap(\n            Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        )\n        self.ctx.hostpython = \"/some/fake/hostpython3\"\n        self.ctx.python_recipe = Recipe.get_recipe(\"python3\", self.ctx)\n        self.ctx.python_modules = [\"requests\"]\n        mock_open_dist_info.side_effect = [\n            mock.mock_open(read_data=json.dumps(dist_info_data)).return_value\n        ]\n        self.ctx.bootstrap.distribution.save_info(\"/fake_dir\")\n        mock_open_dist_info.assert_called_once_with(\"dist_info.json\", \"w\")\n        mock_open_dist_info.reset_mock()\n\n    @mock.patch(\"pythonforandroid.distribution.open\", create=True)\n    @mock.patch(\"pythonforandroid.distribution.exists\")\n    @mock.patch(\"pythonforandroid.distribution.glob.glob\")\n    def test_get_distributions(\n        self, mock_glob, mock_exists, mock_open_dist_info\n    ):\n        \"\"\"Test that method\n        :meth:`~pythonforandroid.distribution.Distribution.get_distributions`\n        returns some expected values:\n\n            - A list of instances of class\n              `~pythonforandroid.distribution.Distribution\n            - That one of the distributions returned in the result has the\n              proper values (`name`, `ndk_api` and `recipes`)\n        \"\"\"\n        self.setUp_distribution_with_bootstrap(\n            Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        )\n        mock_glob.return_value = [\"sdl2-python3\"]\n        mock_open_dist_info.side_effect = [\n            mock.mock_open(read_data=json.dumps(dist_info_data)).return_value\n        ]\n\n        dists = self.ctx.bootstrap.distribution.get_distributions(self.ctx)\n        self.assertIsInstance(dists, list)\n        self.assertEqual(len(dists), 1)\n        self.assertIsInstance(dists[0], Distribution)\n        self.assertEqual(dists[0].name, \"sdl2_dist\")\n        self.assertEqual(dists[0].dist_dir, \"sdl2-python3\")\n        self.assertEqual(dists[0].ndk_api, 21)\n        self.assertEqual(\n            dists[0].recipes,\n            [\"hostpython3\", \"python3\", \"sdl2\", \"kivy\", \"requests\"],\n        )\n        mock_open_dist_info.assert_called_with(\"sdl2-python3/dist_info.json\")\n        mock_open_dist_info.reset_mock()\n\n    @mock.patch(\"pythonforandroid.distribution.open\", create=True)\n    @mock.patch(\"pythonforandroid.distribution.exists\")\n    @mock.patch(\"pythonforandroid.distribution.glob.glob\")\n    def test_get_distributions_error_ndk_api(\n        self, mock_glob, mock_exists, mock_open_dist_info\n    ):\n        \"\"\"Test method\n        :meth:`~pythonforandroid.distribution.Distribution.get_distributions`\n        in case that `ndk_api` is not set..which should return a `None`.\n        \"\"\"\n        dist_info_data_no_ndk_api = dist_info_data.copy()\n        dist_info_data_no_ndk_api.pop(\"ndk_api\")\n        self.setUp_distribution_with_bootstrap(\n            Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        )\n        mock_glob.return_value = [\"sdl2-python3\"]\n        mock_open_dist_info.side_effect = [\n            mock.mock_open(\n                read_data=json.dumps(dist_info_data_no_ndk_api)\n            ).return_value\n        ]\n\n        dists = self.ctx.bootstrap.distribution.get_distributions(self.ctx)\n        self.assertEqual(dists[0].ndk_api, None)\n        mock_open_dist_info.assert_called_with(\"sdl2-python3/dist_info.json\")\n        mock_open_dist_info.reset_mock()\n\n    @mock.patch(\"pythonforandroid.distribution.Distribution.get_distributions\")\n    @mock.patch(\"pythonforandroid.distribution.exists\")\n    @mock.patch(\"pythonforandroid.distribution.glob.glob\")\n    def test_get_distributions_error_ndk_api_mismatch(\n        self, mock_glob, mock_exists, mock_get_dists\n    ):\n        \"\"\"Test that method\n        :meth:`~pythonforandroid.distribution.Distribution.get_distribution`\n        raises an error in case that we have some distribution already build,\n        with a given `name` and `ndk_api`, and we try to get another\n        distribution with the same `name` but different `ndk_api`.\n        \"\"\"\n        expected_dist = Distribution.get_distribution(\n            self.ctx,\n            name=\"test_prj\",\n            recipes=[\"python3\", \"kivy\"],\n            archs=[self.TEST_ARCH],\n        )\n        mock_get_dists.return_value = [expected_dist]\n        mock_glob.return_value = [\"sdl2-python3\"]\n\n        with self.assertRaises(BuildInterruptingException) as e:\n            self.setUp_distribution_with_bootstrap(\n                Bootstrap().get_bootstrap(\"sdl2\", self.ctx),\n                allow_replace_dist=False,\n                ndk_api=22,\n            )\n        self.assertEqual(\n            e.exception.args[0],\n            \"Asked for dist with name test_prj with recipes (python3, kivy)\"\n            \" and NDK API 22, but a dist with this name already exists and has\"\n            \" either incompatible recipes (python3, kivy) or NDK API 21\",\n        )\n\n    def test_get_distributions_error_extra_dist_dirs(self):\n        \"\"\"Test that method\n        :meth:`~pythonforandroid.distribution.Distribution.get_distributions`\n        raises an exception of\n        :class:`~pythonforandroid.util.BuildInterruptingException` in case that\n        we supply the kwargs `extra_dist_dirs`.\n        \"\"\"\n        self.setUp_distribution_with_bootstrap(\n            Bootstrap().get_bootstrap(\"sdl2\", self.ctx)\n        )\n        with self.assertRaises(BuildInterruptingException) as e:\n            self.ctx.bootstrap.distribution.get_distributions(\n                self.ctx, extra_dist_dirs=[\"/fake/extra/dist_dirs\"]\n            )\n        self.assertEqual(\n            e.exception.args[0],\n            \"extra_dist_dirs argument to get\"\n            \"_distributions is not yet implemented\",\n        )\n\n    @mock.patch(\"pythonforandroid.distribution.Distribution.get_distributions\")\n    def test_get_distributions_possible_dists(self, mock_get_dists):\n        \"\"\"Test that method\n        :meth:`~pythonforandroid.distribution.Distribution.get_distributions`\n        returns the proper\n        `:class:`~pythonforandroid.distribution.Distribution` in case that we\n        already have it build and we request the same\n        `:class:`~pythonforandroid.distribution.Distribution`.\n        \"\"\"\n        expected_dist = Distribution.get_distribution(\n            self.ctx,\n            name=\"test_prj\",\n            recipes=[\"python3\", \"kivy\"],\n            archs=[self.TEST_ARCH],\n        )\n        mock_get_dists.return_value = [expected_dist]\n        self.setUp_distribution_with_bootstrap(\n            Bootstrap().get_bootstrap(\"sdl2\", self.ctx), name=\"test_prj\"\n        )\n        dists = self.ctx.bootstrap.distribution.get_distributions(self.ctx)\n        self.assertEqual(dists[0], expected_dist)\n"
  },
  {
    "path": "tests/test_entrypoints.py",
    "content": "from unittest import mock\n\nfrom pythonforandroid.entrypoints import main\nfrom pythonforandroid.util import BuildInterruptingException\n\n\nclass TestMain:\n    \"\"\"Test the main entry point function.\"\"\"\n\n    @mock.patch('pythonforandroid.toolchain.ToolchainCL')\n    @mock.patch('pythonforandroid.entrypoints.check_python_version')\n    def test_main_success(self, mock_check_version, mock_toolchain):\n        \"\"\"Test main() executes successfully with valid Python version.\"\"\"\n        main()\n\n        mock_check_version.assert_called_once()\n        mock_toolchain.assert_called_once()\n\n    @mock.patch('pythonforandroid.entrypoints.handle_build_exception')\n    @mock.patch('pythonforandroid.toolchain.ToolchainCL')\n    @mock.patch('pythonforandroid.entrypoints.check_python_version')\n    def test_main_build_interrupting_exception(\n        self, mock_check_version, mock_toolchain, mock_handler\n    ):\n        \"\"\"Test main() catches BuildInterruptingException and handles it.\"\"\"\n        exc = BuildInterruptingException(\"Build failed\", \"Try reinstalling\")\n        mock_toolchain.side_effect = exc\n\n        main()\n\n        mock_check_version.assert_called_once()\n        mock_toolchain.assert_called_once()\n        mock_handler.assert_called_once_with(exc)\n\n    @mock.patch('pythonforandroid.toolchain.ToolchainCL')\n    @mock.patch('pythonforandroid.entrypoints.check_python_version')\n    def test_main_other_exception_propagates(\n        self, mock_check_version, mock_toolchain\n    ):\n        \"\"\"Test main() allows non-BuildInterruptingException to propagate.\"\"\"\n        mock_toolchain.side_effect = RuntimeError(\"Unexpected error\")\n\n        try:\n            main()\n            assert False, \"Expected RuntimeError to be raised\"\n        except RuntimeError as e:\n            assert str(e) == \"Unexpected error\"\n\n        mock_check_version.assert_called_once()\n        mock_toolchain.assert_called_once()\n\n    @mock.patch('pythonforandroid.entrypoints.check_python_version')\n    def test_main_python_version_check_fails(self, mock_check_version):\n        \"\"\"Test main() allows Python version check failure to propagate.\"\"\"\n        mock_check_version.side_effect = SystemExit(1)\n\n        try:\n            main()\n            assert False, \"Expected SystemExit to be raised\"\n        except SystemExit as e:\n            assert e.code == 1\n\n        mock_check_version.assert_called_once()\n"
  },
  {
    "path": "tests/test_graph.py",
    "content": "from pythonforandroid.build import Context\nfrom pythonforandroid.graph import (\n    fix_deplist, get_dependency_tuple_list_for_recipe,\n    get_recipe_order_and_bootstrap, obvious_conflict_checker,\n)\nfrom pythonforandroid.bootstrap import Bootstrap\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.util import BuildInterruptingException\nfrom itertools import product\n\nfrom unittest import mock\nimport pytest\n\nctx = Context()\n\nname_sets = [['python3'],\n             ['kivy']]\nbootstraps = [None,\n              Bootstrap.get_bootstrap('sdl2', ctx)]\nvalid_combinations = list(product(name_sets, bootstraps))\nvalid_combinations.extend(\n    [(['python3'], Bootstrap.get_bootstrap('sdl2', ctx)),\n     (['kivy', 'python3'], Bootstrap.get_bootstrap('sdl2', ctx)),\n     (['flask'], Bootstrap.get_bootstrap('webview', ctx)),\n     (['pysdl2'], None),  # auto-detect bootstrap! important corner case\n    ]\n)\ninvalid_combinations = [\n    [['pil', 'pillow'], None],\n    [['pysdl2', 'genericndkbuild'], None],\n]\ninvalid_combinations_simple = list(invalid_combinations)\n# NOTE !! keep in mind when setting invalid_combinations_simple:\n#\n# This is used to test obvious_conflict_checker(), which only\n# catches CERTAIN conflicts:\n#\n# This must be a list of conflicts where the conflict is ONLY in\n# non-tuple/non-ambiguous dependencies, e.g.:\n#\n#     dependencies_1st = [\"python2\", \"pillow\"]\n#     dependencies_2nd = [\"python3\", \"pillow\"]\n#\n# This however won't work:\n#\n#     dependencies_1st = [(\"python2\", \"python3\"), \"pillow\"]\n#\n# (This is simply because the conflict checker doesn't resolve this to\n# keep the code ismple enough)\n\n\ndef get_fake_recipe(name, depends=None, conflicts=None):\n    recipe = mock.Mock()\n    recipe.name = name\n    recipe.get_opt_depends_in_list = lambda: []\n    recipe.get_dir_name = lambda: name\n    recipe.depends = list(depends or [])\n    recipe.conflicts = list(conflicts or [])\n    return recipe\n\n\ndef register_fake_recipes_for_test(monkeypatch, recipe_list):\n    _orig_get_recipe = Recipe.get_recipe\n\n    def mock_get_recipe(name, ctx):\n        for recipe in recipe_list:\n            if recipe.name == name:\n                return recipe\n        return _orig_get_recipe(name, ctx)\n    # Note: staticmethod() needed for python ONLY, don't ask me why:\n    monkeypatch.setattr(Recipe, 'get_recipe', staticmethod(mock_get_recipe))\n\n\n@pytest.mark.parametrize('names,bootstrap', valid_combinations)\ndef test_valid_recipe_order_and_bootstrap(names, bootstrap):\n    get_recipe_order_and_bootstrap(ctx, names, bootstrap)\n\n\n@pytest.mark.parametrize('names,bootstrap', invalid_combinations)\ndef test_invalid_recipe_order_and_bootstrap(names, bootstrap):\n    with pytest.raises(BuildInterruptingException) as e_info:\n        get_recipe_order_and_bootstrap(ctx, names, bootstrap)\n    assert \"conflict\" in e_info.value.message.lower()\n\n\ndef test_blacklist():\n    # First, get order without blacklist:\n    build_order, python_modules, bs = get_recipe_order_and_bootstrap(\n        ctx, [\"python3\", \"kivy\"], None\n    )\n    # Now, obtain again with blacklist:\n    build_order_2, python_modules_2, bs_2 = get_recipe_order_and_bootstrap(\n        ctx, [\"python3\", \"kivy\"], None, blacklist=[\"libffi\"]\n    )\n    assert \"libffi\" not in build_order_2\n    assert set(build_order_2).union({\"libffi\"}) == set(build_order)\n\n    # Check that we get a conflict when using webview and kivy combined:\n    wbootstrap = Bootstrap.get_bootstrap('webview', ctx)\n    with pytest.raises(BuildInterruptingException) as e_info:\n        get_recipe_order_and_bootstrap(ctx, [\"flask\", \"kivy\"], wbootstrap)\n    assert \"conflict\" in e_info.value.message.lower()\n\n    # We should no longer get a conflict blacklisting sdl2 and sdl3\n    get_recipe_order_and_bootstrap(\n        ctx, [\"flask\", \"kivy\"], wbootstrap, blacklist=[\"sdl2\", \"sdl3\"]\n    )\n\n\ndef test_get_dependency_tuple_list_for_recipe(monkeypatch):\n    r = get_fake_recipe(\"recipe1\", depends=[\n        \"libffi\",\n        (\"libffi\", \"Pillow\")\n    ])\n    dep_list = get_dependency_tuple_list_for_recipe(\n        r, blacklist={\"libffi\"}\n    )\n    assert dep_list == [(\"pillow\",)]\n\n\n@pytest.mark.parametrize('names,bootstrap', valid_combinations)\ndef test_valid_obvious_conflict_checker(names, bootstrap):\n    # Note: obvious_conflict_checker is stricter on input\n    # (needs fix_deplist) than get_recipe_order_and_bootstrap!\n    obvious_conflict_checker(ctx, fix_deplist(names))\n\n\n@pytest.mark.parametrize('names,bootstrap',\n                         invalid_combinations_simple  # see above for why this\n                        )                             # is a separate list\ndef test_invalid_obvious_conflict_checker(names, bootstrap):\n    # Note: obvious_conflict_checker is stricter on input\n    # (needs fix_deplist) than get_recipe_order_and_bootstrap!\n    with pytest.raises(BuildInterruptingException) as e_info:\n        obvious_conflict_checker(ctx, fix_deplist(names))\n    assert \"conflict\" in e_info.value.message.lower()\n\n\ndef test_misc_obvious_conflict_checker(monkeypatch):\n    # Check that the assert about wrong input data is hit:\n    with pytest.raises(AssertionError) as e_info:\n        obvious_conflict_checker(\n            ctx,\n            [\"this_is_invalid\"]\n            # (invalid because it isn't properly nested as tuple)\n        )\n\n    # Test that non-recipe dependencies work in overall:\n    obvious_conflict_checker(\n        ctx, fix_deplist([\"python3\", \"notarecipelibrary\"])\n    )\n\n    # Test that a conflict with a non-recipe dependency works:\n    # This is currently not used, so we need a custom test recipe:\n    # To get that, we simply modify one!\n    with monkeypatch.context() as m:\n        register_fake_recipes_for_test(m, [\n            get_fake_recipe(\"recipe1\", conflicts=[(\"fakelib\")]),\n        ])\n        with pytest.raises(BuildInterruptingException) as e_info:\n            obvious_conflict_checker(ctx, fix_deplist([\"recipe1\", \"fakelib\"]))\n        assert \"conflict\" in e_info.value.message.lower()\n\n    # Test a case where a recipe pulls in a conditional tuple\n    # of additional dependencies. This is e.g. done for ('python3',\n    # 'python2', ...) but most recipes don't depend on this anymore,\n    # so we need to add a manual test for this case:\n    with monkeypatch.context() as m:\n        register_fake_recipes_for_test(m, [\n            get_fake_recipe(\"recipe1\", depends=[(\"libffi\", \"Pillow\")]),\n        ])\n        obvious_conflict_checker(ctx, fix_deplist([\"recipe1\"]))\n\n\ndef test_indirectconflict_obvious_conflict_checker(monkeypatch):\n    # Test a case where there's an indirect conflict, which also\n    # makes sure the error message correctly blames the OUTER recipes\n    # as original conflict source:\n    with monkeypatch.context() as m:\n        register_fake_recipes_for_test(m, [\n            get_fake_recipe(\"outerrecipe1\", depends=[\"innerrecipe1\"]),\n            get_fake_recipe(\"outerrecipe2\", depends=[\"innerrecipe2\"]),\n            get_fake_recipe(\"innerrecipe1\"),\n            get_fake_recipe(\"innerrecipe2\", conflicts=[\"innerrecipe1\"]),\n        ])\n        with pytest.raises(BuildInterruptingException) as e_info:\n            obvious_conflict_checker(\n                ctx,\n                fix_deplist([\"outerrecipe1\", \"outerrecipe2\"])\n            )\n        assert (\"conflict\" in e_info.value.message.lower() and\n                \"outerrecipe1\" in e_info.value.message.lower() and\n                \"outerrecipe2\" in e_info.value.message.lower())\n\n\ndef test_multichoice_obvious_conflict_checker(monkeypatch):\n    # Test a case where there's a conflict with a multi-choice tuple:\n    with monkeypatch.context() as m:\n        register_fake_recipes_for_test(m, [\n            get_fake_recipe(\"recipe1\", conflicts=[\"lib1\", \"lib2\"]),\n            get_fake_recipe(\"recipe2\", depends=[(\"lib1\", \"lib2\")]),\n        ])\n        with pytest.raises(BuildInterruptingException) as e_info:\n            obvious_conflict_checker(\n                ctx,\n                fix_deplist([(\"lib1\", \"lib2\"), \"recipe1\"])\n            )\n        assert \"conflict\" in e_info.value.message.lower()\n\n\ndef test_bootstrap_dependency_addition():\n    build_order, python_modules, bs = get_recipe_order_and_bootstrap(\n        ctx, ['kivy'], None)\n    assert ('hostpython3' in build_order)\n\n\ndef test_graph_deplist_transformation():\n    test_pairs = [\n        ([\"Pillow\", ('python2', 'python3')],\n         [('pillow',), ('python2', 'python3')]),\n        ([\"Pillow\", ('python2',)],\n         [('pillow',), ('python2',)]),\n    ]\n    for (before_list, after_list) in test_pairs:\n        assert fix_deplist(before_list) == after_list\n\n\ndef test_bootstrap_dependency_addition2():\n    build_order, python_modules, bs = get_recipe_order_and_bootstrap(\n        ctx, ['kivy', 'python3'], None)\n    assert 'hostpython3' in build_order\n\n\nif __name__ == \"__main__\":\n    get_recipe_order_and_bootstrap(ctx, ['python3'],\n                                   Bootstrap.get_bootstrap('sdl2', ctx))\n"
  },
  {
    "path": "tests/test_logger.py",
    "content": "import logging\nimport sh\nimport pytest\nimport unittest\nfrom unittest.mock import MagicMock, Mock, patch\nfrom pythonforandroid import logger\n\n\nclass TestColorSetup:\n    \"\"\"Test color setup and configuration.\"\"\"\n\n    def teardown_method(self):\n        \"\"\"Reset color state after each test to avoid affecting other tests.\"\"\"\n        logger.setup_color('never')\n\n    def test_setup_color_never(self):\n        \"\"\"Test color disabled when set to 'never'.\"\"\"\n        logger.setup_color('never')\n        assert not logger.Out_Style._enabled\n        assert not logger.Out_Fore._enabled\n        assert not logger.Err_Style._enabled\n        assert not logger.Err_Fore._enabled\n\n    def test_setup_color_always(self):\n        \"\"\"Test color enabled when set to 'always'.\"\"\"\n        logger.setup_color('always')\n        assert logger.Out_Style._enabled\n        assert logger.Out_Fore._enabled\n        assert logger.Err_Style._enabled\n        assert logger.Err_Fore._enabled\n\n    @patch('pythonforandroid.logger.stdout')\n    @patch('pythonforandroid.logger.stderr')\n    def test_setup_color_auto_with_tty(self, mock_stderr, mock_stdout):\n        \"\"\"Test color enabled when auto and isatty() returns True.\"\"\"\n        mock_stdout.isatty.return_value = True\n        mock_stderr.isatty.return_value = True\n        logger.setup_color('auto')\n        assert logger.Out_Style._enabled\n        assert logger.Err_Style._enabled\n\n\nclass TestUtilityFunctions:\n    \"\"\"Test logger utility functions.\"\"\"\n\n    def test_shorten_string_short(self):\n        \"\"\"Test shorten_string returns string unchanged when under limit.\"\"\"\n        result = logger.shorten_string(\"short\", 50)\n        assert result == \"short\"\n\n    def test_shorten_string_long(self):\n        \"\"\"Test shorten_string truncates long strings correctly.\"\"\"\n        long_string = \"a\" * 100\n        result = logger.shorten_string(long_string, 50)\n        assert \"...(and\" in result\n        assert \"more)\" in result\n        assert len(result) <= 50\n\n    def test_shorten_string_bytes(self):\n        \"\"\"Test shorten_string handles bytes input.\"\"\"\n        byte_string = b\"test\" * 50\n        result = logger.shorten_string(byte_string, 50)\n        assert \"...(and\" in result\n\n    @patch.dict('os.environ', {'COLUMNS': '120'})\n    def test_get_console_width_from_env(self):\n        \"\"\"Test get_console_width reads from COLUMNS env var.\"\"\"\n        width = logger.get_console_width()\n        assert width == 120\n\n    @patch.dict('os.environ', {}, clear=True)\n    @patch('os.popen')\n    def test_get_console_width_from_stty(self, mock_popen):\n        \"\"\"Test get_console_width falls back to stty command.\"\"\"\n        mock_popen.return_value.read.return_value = \"40 80\"\n        width = logger.get_console_width()\n        assert width == 80\n        mock_popen.assert_called_once_with('stty size', 'r')\n\n    @patch.dict('os.environ', {}, clear=True)\n    @patch('os.popen')\n    def test_get_console_width_default(self, mock_popen):\n        \"\"\"Test get_console_width returns default when stty fails.\"\"\"\n        mock_popen.return_value.read.side_effect = Exception(\"stty failed\")\n        width = logger.get_console_width()\n        assert width == 100\n\n\nclass TestLevelDifferentiatingFormatter:\n    \"\"\"Test custom log message formatter.\"\"\"\n\n    def test_format_error_level(self):\n        \"\"\"Test formatter adds [ERROR] prefix for ERROR level.\"\"\"\n        formatter = logger.LevelDifferentiatingFormatter('%(message)s')\n        record = logging.LogRecord(\n            name='test', level=40, pathname='', lineno=0,\n            msg='test error', args=(), exc_info=None\n        )\n        formatted = formatter.format(record)\n        assert '[ERROR]' in formatted\n\n    def test_format_warning_level(self):\n        \"\"\"Test formatter adds [WARNING] prefix for WARNING level.\"\"\"\n        formatter = logger.LevelDifferentiatingFormatter('%(message)s')\n        record = logging.LogRecord(\n            name='test', level=30, pathname='', lineno=0,\n            msg='test warning', args=(), exc_info=None\n        )\n        formatted = formatter.format(record)\n        assert '[WARNING]' in formatted\n\n    def test_format_info_level(self):\n        \"\"\"Test formatter adds [INFO] prefix for INFO level.\"\"\"\n        formatter = logger.LevelDifferentiatingFormatter('%(message)s')\n        record = logging.LogRecord(\n            name='test', level=20, pathname='', lineno=0,\n            msg='test info', args=(), exc_info=None\n        )\n        formatted = formatter.format(record)\n        assert '[INFO]' in formatted\n\n    def test_format_debug_level(self):\n        \"\"\"Test formatter adds [DEBUG] prefix for DEBUG level.\"\"\"\n        formatter = logger.LevelDifferentiatingFormatter('%(message)s')\n        record = logging.LogRecord(\n            name='test', level=10, pathname='', lineno=0,\n            msg='test debug', args=(), exc_info=None\n        )\n        formatted = formatter.format(record)\n        assert '[DEBUG]' in formatted\n\n\nclass TestShprintErrorHandling:\n    \"\"\"Test shprint error handling and edge cases.\"\"\"\n\n    @patch('pythonforandroid.logger.get_console_width')\n    def test_shprint_with_filter(self, mock_width):\n        \"\"\"Test shprint filters output with _filter parameter.\"\"\"\n        mock_width.return_value = 100\n\n        command = MagicMock()\n        # Create a mock error with required attributes\n        error = Mock(spec=sh.ErrorReturnCode)\n        error.stdout = b'line1\\nfiltered_line\\nline3'\n        error.stderr = b''\n        command.side_effect = error\n\n        with pytest.raises(TypeError):\n            logger.shprint(command, _filter='filtered', _tail=10)\n\n    @patch('pythonforandroid.logger.get_console_width')\n    def test_shprint_with_filterout(self, mock_width):\n        \"\"\"Test shprint excludes output with _filterout parameter.\"\"\"\n        mock_width.return_value = 100\n\n        command = MagicMock()\n        error = Mock(spec=sh.ErrorReturnCode)\n        error.stdout = b'keep1\\nexclude_line\\nkeep2'\n        error.stderr = b''\n        command.side_effect = error\n\n        with pytest.raises(TypeError):\n            logger.shprint(command, _filterout='exclude', _tail=10)\n\n    @patch('pythonforandroid.logger.get_console_width')\n    @patch('pythonforandroid.logger.stdout')\n    @patch.dict('os.environ', {'P4A_FULL_DEBUG': '1'})\n    def test_shprint_full_debug_mode(self, mock_stdout, mock_width):\n        \"\"\"Test shprint in P4A_FULL_DEBUG mode shows all output.\"\"\"\n        mock_width.return_value = 100\n\n        command = MagicMock()\n        command.return_value = iter(['debug line 1\\n', 'debug line 2\\n'])\n\n        logger.shprint(command)\n        # In full debug mode, output is written directly to stdout\n        assert mock_stdout.write.called\n\n    @patch('pythonforandroid.logger.get_console_width')\n    @patch.dict('os.environ', {}, clear=True)\n    def test_shprint_critical_failure_exits(self, mock_width):\n        \"\"\"Test shprint exits on critical command failure.\"\"\"\n        mock_width.return_value = 100\n\n        command = MagicMock()\n\n        # Create a proper exception class that mimics sh.ErrorReturnCode\n        class MockErrorReturnCode(sh.ErrorReturnCode):\n            def __init__(self):\n                self.full_cmd = 'test'\n                self.stdout = b'output'\n                self.stderr = b'error'\n                self.exit_code = 1\n\n        error = MockErrorReturnCode()\n        command.side_effect = error\n\n        with patch('pythonforandroid.logger.exit', side_effect=SystemExit) as mock_exit:\n            with pytest.raises(SystemExit):\n                logger.shprint(command, _critical=True, _tail=5)\n            mock_exit.assert_called_once_with(1)\n\n\nclass TestLoggingHelpers:\n    \"\"\"Test logging helper functions.\"\"\"\n\n    @patch('pythonforandroid.logger.logger')\n    def test_info_main(self, mock_logger):\n        \"\"\"Test info_main logs with bright green formatting.\"\"\"\n        logger.info_main('test', 'message')\n        mock_logger.info.assert_called_once()\n        # Verify the call contains color codes and text\n        call_args = mock_logger.info.call_args[0][0]\n        assert 'test' in call_args\n        assert 'message' in call_args\n\n    @patch('pythonforandroid.logger.info')\n    def test_info_notify(self, mock_info):\n        \"\"\"Test info_notify logs with blue formatting.\"\"\"\n        logger.info_notify('notification')\n        mock_info.assert_called_once()\n        call_args = mock_info.call_args[0][0]\n        assert 'notification' in call_args\n\n\nclass TestShprint(unittest.TestCase):\n\n    def test_unicode_encode(self):\n        \"\"\"\n        Makes sure `shprint()` can handle unicode command output.\n        Running the test with PYTHONIOENCODING=ASCII env would fail, refs:\n        https://github.com/kivy/python-for-android/issues/1654\n        \"\"\"\n        expected_command_output = [\"foo\\xa0bar\"]\n        command = MagicMock()\n        command.return_value = expected_command_output\n        output = logger.shprint(command, 'a1', k1='k1')\n        self.assertEqual(output, expected_command_output)\n"
  },
  {
    "path": "tests/test_patching.py",
    "content": "from unittest import mock\n\nfrom pythonforandroid.patching import (\n    is_platform,\n    is_linux,\n    is_darwin,\n    is_windows,\n    is_arch,\n    is_api,\n    is_api_gt,\n    is_api_gte,\n    is_api_lt,\n    is_api_lte,\n    is_ndk,\n    is_version_gt,\n    is_version_lt,\n    version_starts_with,\n    will_build,\n    check_all,\n    check_any,\n)\n\n\nclass TestPlatformChecks:\n    \"\"\"Test platform detection functions.\"\"\"\n\n    @mock.patch('pythonforandroid.patching.uname')\n    def test_is_platform_linux(self, mock_uname):\n        \"\"\"Test is_platform returns check function for Linux.\"\"\"\n        mock_uname.return_value = mock.Mock(system='Linux')\n        check_fn = is_platform('Linux')\n        assert check_fn(None, None)\n\n    @mock.patch('pythonforandroid.patching.uname')\n    def test_is_platform_darwin(self, mock_uname):\n        \"\"\"Test is_platform returns check function for Darwin.\"\"\"\n        mock_uname.return_value = mock.Mock(system='Darwin')\n        check_fn = is_platform('Darwin')\n        assert check_fn(None, None)\n\n    @mock.patch('pythonforandroid.patching.uname')\n    def test_is_platform_case_insensitive(self, mock_uname):\n        \"\"\"Test is_platform is case insensitive.\"\"\"\n        mock_uname.return_value = mock.Mock(system='LINUX')\n        check_fn = is_platform('linux')\n        assert check_fn(None, None)\n\n    @mock.patch('pythonforandroid.patching.uname')\n    def test_is_platform_mismatch(self, mock_uname):\n        \"\"\"Test is_platform returns False for mismatched platform.\"\"\"\n        mock_uname.return_value = mock.Mock(system='Linux')\n        check_fn = is_platform('Windows')\n        assert not check_fn(None, None)\n\n    def test_is_linux(self):\n        \"\"\"Test is_linux constant function is defined.\"\"\"\n        # is_linux is defined at module import time based on real platform\n        # We can only verify it's callable\n        assert callable(is_linux)\n\n    def test_is_darwin(self):\n        \"\"\"Test is_darwin constant function is defined.\"\"\"\n        # is_darwin is defined at module import time based on real platform\n        # We can only verify it's callable\n        assert callable(is_darwin)\n\n    def test_is_windows(self):\n        \"\"\"Test is_windows constant function is defined.\"\"\"\n        # is_windows is defined at module import time based on real platform\n        # We can only verify it's callable\n        assert callable(is_windows)\n\n\nclass TestArchChecks:\n    \"\"\"Test architecture check functions.\"\"\"\n\n    def test_is_arch_match(self):\n        \"\"\"Test is_arch returns True for matching architecture.\"\"\"\n        mock_arch = mock.Mock(arch='armeabi-v7a')\n        check_fn = is_arch('armeabi-v7a')\n        assert check_fn(mock_arch)\n\n    def test_is_arch_mismatch(self):\n        \"\"\"Test is_arch returns False for mismatched architecture.\"\"\"\n        mock_arch = mock.Mock(arch='armeabi-v7a')\n        check_fn = is_arch('arm64-v8a')\n        assert not check_fn(mock_arch)\n\n\nclass TestAndroidAPIChecks:\n    \"\"\"Test Android API level comparison functions.\"\"\"\n\n    def test_is_api_equal(self):\n        \"\"\"Test is_api for equal API level.\"\"\"\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.android_api = 21\n        check_fn = is_api(21)\n        assert check_fn(None, mock_recipe)\n\n    def test_is_api_not_equal(self):\n        \"\"\"Test is_api for unequal API level.\"\"\"\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.android_api = 21\n        check_fn = is_api(27)\n        assert not check_fn(None, mock_recipe)\n\n    def test_is_api_gt(self):\n        \"\"\"Test is_api_gt for greater than comparison.\"\"\"\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.android_api = 27\n        check_fn = is_api_gt(21)\n        assert check_fn(None, mock_recipe)\n\n        mock_recipe.ctx.android_api = 21\n        assert not check_fn(None, mock_recipe)\n\n    def test_is_api_gte(self):\n        \"\"\"Test is_api_gte for greater than or equal comparison.\"\"\"\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.android_api = 27\n        check_fn = is_api_gte(21)\n        assert check_fn(None, mock_recipe)\n\n        mock_recipe.ctx.android_api = 21\n        check_fn = is_api_gte(21)\n        assert check_fn(None, mock_recipe)\n\n        mock_recipe.ctx.android_api = 19\n        assert not check_fn(None, mock_recipe)\n\n    def test_is_api_lt(self):\n        \"\"\"Test is_api_lt for less than comparison.\"\"\"\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.android_api = 19\n        check_fn = is_api_lt(21)\n        assert check_fn(None, mock_recipe)\n\n        mock_recipe.ctx.android_api = 21\n        assert not check_fn(None, mock_recipe)\n\n    def test_is_api_lte(self):\n        \"\"\"Test is_api_lte for less than or equal comparison.\"\"\"\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.android_api = 19\n        check_fn = is_api_lte(21)\n        assert check_fn(None, mock_recipe)\n\n        mock_recipe.ctx.android_api = 21\n        check_fn = is_api_lte(21)\n        assert check_fn(None, mock_recipe)\n\n        mock_recipe.ctx.android_api = 27\n        assert not check_fn(None, mock_recipe)\n\n\nclass TestNDKChecks:\n    \"\"\"Test NDK version check functions.\"\"\"\n\n    def test_is_ndk_equal(self):\n        \"\"\"Test is_ndk for equal NDK version.\"\"\"\n        mock_ndk = mock.Mock(name='ndk_r21e')\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.ndk = mock_ndk\n        check_fn = is_ndk(mock_ndk)\n        assert check_fn(None, mock_recipe)\n\n    def test_is_ndk_not_equal(self):\n        \"\"\"Test is_ndk for unequal NDK version.\"\"\"\n        mock_ndk1 = mock.Mock(name='ndk_r21e')\n        mock_ndk2 = mock.Mock(name='ndk_r25c')\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.ndk = mock_ndk1\n        check_fn = is_ndk(mock_ndk2)\n        assert not check_fn(None, mock_recipe)\n\n\nclass TestVersionChecks:\n    \"\"\"Test recipe version comparison functions.\"\"\"\n\n    def test_is_version_gt(self):\n        \"\"\"Test is_version_gt for version comparison.\"\"\"\n        mock_recipe = mock.Mock(version='2.0.0')\n        check_fn = is_version_gt('1.0.0')\n        assert check_fn(None, mock_recipe)\n\n        mock_recipe.version = '1.0.0'\n        assert not check_fn(None, mock_recipe)\n\n    def test_is_version_lt(self):\n        \"\"\"Test is_version_lt for version comparison.\"\"\"\n        mock_recipe = mock.Mock(version='1.0.0')\n        check_fn = is_version_lt('2.0.0')\n        assert check_fn(None, mock_recipe)\n\n        mock_recipe.version = '2.0.0'\n        assert not check_fn(None, mock_recipe)\n\n    def test_version_starts_with(self):\n        \"\"\"Test version_starts_with for version prefix matching.\"\"\"\n        mock_recipe = mock.Mock(version='1.15.2')\n        check_fn = version_starts_with('1.15')\n        assert check_fn(None, mock_recipe)\n\n        check_fn = version_starts_with('1.14')\n        assert not check_fn(None, mock_recipe)\n\n        check_fn = version_starts_with('2')\n        assert not check_fn(None, mock_recipe)\n\n\nclass TestWillBuild:\n    \"\"\"Test will_build function.\"\"\"\n\n    def test_will_build_present(self):\n        \"\"\"Test will_build returns True when recipe is in build order.\"\"\"\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.recipe_build_order = ['python3', 'numpy', 'kivy']\n        check_fn = will_build('numpy')\n        assert check_fn(None, mock_recipe)\n\n    def test_will_build_absent(self):\n        \"\"\"Test will_build returns False when recipe is not in build order.\"\"\"\n        mock_recipe = mock.Mock()\n        mock_recipe.ctx.recipe_build_order = ['python3', 'numpy', 'kivy']\n        check_fn = will_build('scipy')\n        assert not check_fn(None, mock_recipe)\n\n\nclass TestConjunctions:\n    \"\"\"Test logical conjunction functions.\"\"\"\n\n    def test_check_all_all_true(self):\n        \"\"\"Test check_all returns True when all checks pass.\"\"\"\n        def check1(_arch, _recipe):\n            return True\n\n        def check2(_arch, _recipe):\n            return True\n\n        def check3(_arch, _recipe):\n            return True\n\n        check_fn = check_all(check1, check2, check3)\n        assert check_fn(None, None)\n\n    def test_check_all_one_false(self):\n        \"\"\"Test check_all returns False when one check fails.\"\"\"\n        def check1(_arch, _recipe):\n            return True\n\n        def check2(_arch, _recipe):\n            return False\n\n        def check3(_arch, _recipe):\n            return True\n\n        check_fn = check_all(check1, check2, check3)\n        assert not check_fn(None, None)\n\n    def test_check_all_all_false(self):\n        \"\"\"Test check_all returns False when all checks fail.\"\"\"\n        def check1(_arch, _recipe):\n            return False\n\n        def check2(_arch, _recipe):\n            return False\n\n        check_fn = check_all(check1, check2)\n        assert not check_fn(None, None)\n\n    def test_check_any_one_true(self):\n        \"\"\"Test check_any returns True when one check passes.\"\"\"\n        def check1(_arch, _recipe):\n            return False\n\n        def check2(_arch, _recipe):\n            return True\n\n        def check3(_arch, _recipe):\n            return False\n\n        check_fn = check_any(check1, check2, check3)\n        assert check_fn(None, None)\n\n    def test_check_any_all_false(self):\n        \"\"\"Test check_any returns False when all checks fail.\"\"\"\n        def check1(_arch, _recipe):\n            return False\n\n        def check2(_arch, _recipe):\n            return False\n\n        check_fn = check_any(check1, check2)\n        assert not check_fn(None, None)\n\n    def test_check_any_all_true(self):\n        \"\"\"Test check_any returns True when all checks pass.\"\"\"\n        def check1(_arch, _recipe):\n            return True\n\n        def check2(_arch, _recipe):\n            return True\n\n        check_fn = check_any(check1, check2)\n        assert check_fn(None, None)\n\n    @mock.patch('pythonforandroid.patching.uname')\n    def test_combined_checks(self, mock_uname):\n        \"\"\"Test combining multiple check functions with check_all and check_any.\"\"\"\n        # Test check_all with is_platform and is_version_gt\n        mock_uname.return_value = mock.Mock(system='Linux')\n        mock_recipe = mock.Mock(version='2.0.0')\n\n        check_fn = check_all(\n            is_platform('Linux'),\n            is_version_gt('1.0.0')\n        )\n        assert check_fn(None, mock_recipe)\n\n        # Test check_any with is_platform and is_version_gt\n        mock_uname.return_value = mock.Mock(system='Windows')\n        check_fn = check_any(\n            is_platform('Linux'),\n            is_version_gt('1.0.0')\n        )\n        assert check_fn(None, mock_recipe)\n"
  },
  {
    "path": "tests/test_prerequisites.py",
    "content": "import unittest\nfrom unittest import mock, skipIf\n\nimport sys\nimport pytest\n\nfrom pythonforandroid.prerequisites import (\n    Prerequisite,\n    JDKPrerequisite,\n    HomebrewPrerequisite,\n    OpenSSLPrerequisite,\n    AutoconfPrerequisite,\n    AutomakePrerequisite,\n    LibtoolPrerequisite,\n    PkgConfigPrerequisite,\n    CmakePrerequisite,\n    get_required_prerequisites,\n    check_and_install_default_prerequisites,\n)\n\n\nclass PrerequisiteSetUpBaseClass:\n    def setUp(self):\n        self.mandatory = dict(linux=False, darwin=False)\n        self.installer_is_supported = dict(linux=False, darwin=False)\n        self.expected_homebrew_formula_name = \"\"\n\n    def test_is_mandatory_on_darwin(self):\n        assert self.prerequisite.mandatory[\"darwin\"] == self.mandatory[\"darwin\"]\n\n    def test_is_mandatory_on_linux(self):\n        assert self.prerequisite.mandatory[\"linux\"] == self.mandatory[\"linux\"]\n\n    def test_installer_is_supported_on_darwin(self):\n        assert (\n            self.prerequisite.installer_is_supported[\"darwin\"]\n            == self.installer_is_supported[\"darwin\"]\n        )\n\n    def test_installer_is_supported_on_linux(self):\n        assert (\n            self.prerequisite.installer_is_supported[\"linux\"]\n            == self.installer_is_supported[\"linux\"]\n        )\n\n    def test_darwin_pkg_config_location(self):\n        self.assertEqual(self.prerequisite.darwin_pkg_config_location(), \"\")\n\n    def test_linux_pkg_config_location(self):\n        self.assertEqual(self.prerequisite.linux_pkg_config_location(), \"\")\n\n    @skipIf(sys.platform != \"darwin\", \"Only run on macOS\")\n    def test_pkg_config_location_property__darwin(self):\n        self.assertEqual(\n            self.prerequisite.pkg_config_location,\n            self.prerequisite.darwin_pkg_config_location(),\n        )\n\n    @skipIf(sys.platform != \"linux\", \"Only run on Linux\")\n    def test_pkg_config_location_property__linux(self):\n        self.assertEqual(\n            self.prerequisite.pkg_config_location,\n            self.prerequisite.linux_pkg_config_location(),\n        )\n\n\nclass TestJDKPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.mandatory = dict(linux=False, darwin=True)\n        self.installer_is_supported = dict(linux=False, darwin=True)\n        self.prerequisite = JDKPrerequisite()\n\n\nclass TestBrewPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.mandatory = dict(linux=False, darwin=True)\n        self.installer_is_supported = dict(linux=False, darwin=False)\n        self.prerequisite = HomebrewPrerequisite()\n\n    @mock.patch(\"shutil.which\")\n    def test_darwin_checker(self, shutil_which):\n        shutil_which.return_value = None\n        self.assertFalse(self.prerequisite.darwin_checker())\n        shutil_which.return_value = \"/opt/homebrew/bin/brew\"\n        self.assertTrue(self.prerequisite.darwin_checker())\n\n    @mock.patch(\"pythonforandroid.prerequisites.info\")\n    def test_darwin_helper(self, info):\n        self.prerequisite.darwin_helper()\n        info.assert_called_once_with(\n            \"Installer for homebrew is not yet supported on macOS,\"\n            \"the nice news is that the installation process is easy!\"\n            \"See: https://brew.sh for further instructions.\"\n        )\n\n\nclass TestOpenSSLPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.mandatory = dict(linux=False, darwin=True)\n        self.installer_is_supported = dict(linux=False, darwin=True)\n        self.prerequisite = OpenSSLPrerequisite()\n        self.expected_homebrew_formula_name = \"openssl@3\"\n        self.expected_homebrew_location_prefix = \"/opt/homebrew/opt/openssl@3\"\n\n    @mock.patch(\n        \"pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix\"\n    )\n    def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):\n        _darwin_get_brew_formula_location_prefix.return_value = None\n        self.assertFalse(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.return_value = (\n            self.expected_homebrew_location_prefix\n        )\n        self.assertTrue(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.assert_called_with(\n            self.expected_homebrew_formula_name, installed=True\n        )\n\n    @mock.patch(\"pythonforandroid.prerequisites.subprocess.check_output\")\n    def test_darwin_installer(self, check_output):\n        self.prerequisite.darwin_installer()\n        check_output.assert_called_once_with(\n            [\"brew\", \"install\", self.expected_homebrew_formula_name]\n        )\n\n    @mock.patch(\n        \"pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix\"\n    )\n    def test_darwin_pkg_config_location(self, _darwin_get_brew_formula_location_prefix):\n        _darwin_get_brew_formula_location_prefix.return_value = (\n            self.expected_homebrew_location_prefix\n        )\n        self.assertEqual(\n            self.prerequisite.darwin_pkg_config_location(),\n            f\"{self.expected_homebrew_location_prefix}/lib/pkgconfig\",\n        )\n\n\nclass TestAutoconfPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.mandatory = dict(linux=False, darwin=True)\n        self.installer_is_supported = dict(linux=False, darwin=True)\n        self.prerequisite = AutoconfPrerequisite()\n\n    @mock.patch(\n        \"pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix\"\n    )\n    def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):\n        _darwin_get_brew_formula_location_prefix.return_value = None\n        self.assertFalse(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.return_value = (\n            \"/opt/homebrew/opt/autoconf\"\n        )\n        self.assertTrue(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.assert_called_with(\n            \"autoconf\", installed=True\n        )\n\n    @mock.patch(\"pythonforandroid.prerequisites.subprocess.check_output\")\n    def test_darwin_installer(self, check_output):\n        self.prerequisite.darwin_installer()\n        check_output.assert_called_once_with([\"brew\", \"install\", \"autoconf\"])\n\n\nclass TestAutomakePrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.mandatory = dict(linux=False, darwin=True)\n        self.installer_is_supported = dict(linux=False, darwin=True)\n        self.prerequisite = AutomakePrerequisite()\n\n    @mock.patch(\n        \"pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix\"\n    )\n    def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):\n        _darwin_get_brew_formula_location_prefix.return_value = None\n        self.assertFalse(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.return_value = (\n            \"/opt/homebrew/opt/automake\"\n        )\n        self.assertTrue(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.assert_called_with(\n            \"automake\", installed=True\n        )\n\n    @mock.patch(\"pythonforandroid.prerequisites.subprocess.check_output\")\n    def test_darwin_installer(self, check_output):\n        self.prerequisite.darwin_installer()\n        check_output.assert_called_once_with([\"brew\", \"install\", \"automake\"])\n\n\nclass TestLibtoolPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.mandatory = dict(linux=False, darwin=True)\n        self.installer_is_supported = dict(linux=False, darwin=True)\n        self.prerequisite = LibtoolPrerequisite()\n\n    @mock.patch(\n        \"pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix\"\n    )\n    def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):\n        _darwin_get_brew_formula_location_prefix.return_value = None\n        self.assertFalse(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.return_value = (\n            \"/opt/homebrew/opt/libtool\"\n        )\n        self.assertTrue(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.assert_called_with(\n            \"libtool\", installed=True\n        )\n\n    @mock.patch(\"pythonforandroid.prerequisites.subprocess.check_output\")\n    def test_darwin_installer(self, check_output):\n        self.prerequisite.darwin_installer()\n        check_output.assert_called_once_with([\"brew\", \"install\", \"libtool\"])\n\n\nclass TestPkgConfigPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.mandatory = dict(linux=False, darwin=True)\n        self.installer_is_supported = dict(linux=False, darwin=True)\n        self.prerequisite = PkgConfigPrerequisite()\n\n    @mock.patch(\n        \"pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix\"\n    )\n    def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):\n        _darwin_get_brew_formula_location_prefix.return_value = None\n        self.assertFalse(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.return_value = (\n            \"/opt/homebrew/opt/pkg-config\"\n        )\n        self.assertTrue(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.assert_called_with(\n            \"pkg-config\", installed=True\n        )\n\n    @mock.patch(\"pythonforandroid.prerequisites.subprocess.check_output\")\n    def test_darwin_installer(self, check_output):\n        self.prerequisite.darwin_installer()\n        check_output.assert_called_once_with([\"brew\", \"install\", \"pkg-config\"])\n\n\nclass TestCmakePrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):\n    def setUp(self):\n        super().setUp()\n        self.mandatory = dict(linux=False, darwin=True)\n        self.installer_is_supported = dict(linux=False, darwin=True)\n        self.prerequisite = CmakePrerequisite()\n\n    @mock.patch(\n        \"pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix\"\n    )\n    def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):\n        _darwin_get_brew_formula_location_prefix.return_value = None\n        self.assertFalse(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.return_value = (\n            \"/opt/homebrew/opt/cmake\"\n        )\n        self.assertTrue(self.prerequisite.darwin_checker())\n        _darwin_get_brew_formula_location_prefix.assert_called_with(\n            \"cmake\", installed=True\n        )\n\n    @mock.patch(\"pythonforandroid.prerequisites.subprocess.check_output\")\n    def test_darwin_installer(self, check_output):\n        self.prerequisite.darwin_installer()\n        check_output.assert_called_once_with([\"brew\", \"install\", \"cmake\"])\n\n\nclass TestDefaultPrerequisitesCheckandInstall(unittest.TestCase):\n\n    def test_default_darwin_prerequisites_set(self):\n        self.assertListEqual(\n            [\n                p.__class__.__name__\n                for p in get_required_prerequisites(platform=\"darwin\")\n            ],\n            [\n                \"HomebrewPrerequisite\",\n                \"AutoconfPrerequisite\",\n                \"AutomakePrerequisite\",\n                \"LibtoolPrerequisite\",\n                \"PkgConfigPrerequisite\",\n                \"CmakePrerequisite\",\n                \"OpenSSLPrerequisite\",\n                \"JDKPrerequisite\",\n            ],\n        )\n\n    def test_default_linux_prerequisites_set(self):\n        self.assertListEqual(\n            [\n                p.__class__.__name__\n                for p in get_required_prerequisites(platform=\"linux\")\n            ],\n            [\n            ],\n        )\n\n\nclass TestPrerequisiteBaseClass:\n    \"\"\"Test base Prerequisite class methods.\"\"\"\n\n    @mock.patch('pythonforandroid.prerequisites.info')\n    @mock.patch.object(Prerequisite, 'checker')\n    def test_is_valid_when_met(self, mock_checker, mock_info):\n        \"\"\"Test is_valid returns True when prerequisite is met.\"\"\"\n        mock_checker.return_value = True\n        prerequisite = Prerequisite()\n        result = prerequisite.is_valid()\n        assert result == (True, \"\")\n        mock_info.assert_called()\n        assert \"is met\" in mock_info.call_args[0][0]\n\n    @mock.patch('pythonforandroid.prerequisites.warning')\n    @mock.patch.object(Prerequisite, 'checker')\n    def test_is_valid_when_not_met_non_mandatory(self, mock_checker, mock_warning):\n        \"\"\"Test is_valid warns when non-mandatory prerequisite not met.\"\"\"\n        mock_checker.return_value = False\n        prerequisite = Prerequisite()\n        prerequisite.mandatory = dict(linux=False, darwin=False)\n\n        result = prerequisite.is_valid()\n        assert result is None\n        mock_warning.assert_called()\n        assert \"not met\" in mock_warning.call_args[0][0]\n\n    @mock.patch('pythonforandroid.prerequisites.error')\n    @mock.patch.object(Prerequisite, 'checker')\n    @mock.patch('sys.platform', 'linux')\n    def test_is_valid_when_not_met_mandatory(self, mock_checker, mock_error):\n        \"\"\"Test is_valid errors when mandatory prerequisite not met.\"\"\"\n        mock_checker.return_value = False\n        prerequisite = Prerequisite()\n        prerequisite.mandatory = dict(linux=True, darwin=False)\n\n        result = prerequisite.is_valid()\n        assert result is None\n        mock_error.assert_called()\n        assert \"not met\" in mock_error.call_args[0][0]\n\n    @mock.patch('sys.platform', 'linux')\n    @mock.patch.object(Prerequisite, 'linux_checker')\n    def test_checker_calls_linux_checker(self, mock_linux_checker):\n        \"\"\"Test checker dispatches to linux_checker on Linux.\"\"\"\n        mock_linux_checker.return_value = True\n        prerequisite = Prerequisite()\n        result = prerequisite.checker()\n        assert result is True\n        mock_linux_checker.assert_called_once()\n\n    @mock.patch('sys.platform', 'darwin')\n    @mock.patch.object(Prerequisite, 'darwin_checker')\n    def test_checker_calls_darwin_checker(self, mock_darwin_checker):\n        \"\"\"Test checker dispatches to darwin_checker on macOS.\"\"\"\n        mock_darwin_checker.return_value = True\n        prerequisite = Prerequisite()\n        result = prerequisite.checker()\n        assert result is True\n        mock_darwin_checker.assert_called_once()\n\n    @mock.patch('sys.platform', 'win32')\n    def test_checker_raises_on_unsupported_platform(self):\n        \"\"\"Test checker raises exception on unsupported platform.\"\"\"\n        prerequisite = Prerequisite()\n        with pytest.raises(Exception, match=\"Unsupported platform\"):\n            prerequisite.checker()\n\n\nclass TestPrerequisiteInstallation:\n    \"\"\"Test prerequisite installation workflow.\"\"\"\n\n    @mock.patch.dict('os.environ', {'PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE': '1'})\n    @mock.patch('builtins.input')\n    def test_ask_to_install_user_accepts(self, mock_input):\n        \"\"\"Test ask_to_install returns True when user enters 'y'.\"\"\"\n        prerequisite = Prerequisite()\n        prerequisite.name = \"TestPrerequisite\"\n        mock_input.return_value = 'y'\n        result = prerequisite.ask_to_install()\n        assert result is True\n\n    @mock.patch.dict('os.environ', {'PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE': '1'})\n    @mock.patch('builtins.input')\n    def test_ask_to_install_user_declines(self, mock_input):\n        \"\"\"Test ask_to_install returns False when user enters 'n'.\"\"\"\n        prerequisite = Prerequisite()\n        prerequisite.name = \"TestPrerequisite\"\n        mock_input.return_value = 'n'\n        result = prerequisite.ask_to_install()\n        assert result is False\n\n    @mock.patch.dict('os.environ', {'PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE': '0'})\n    @mock.patch('pythonforandroid.prerequisites.info')\n    def test_ask_to_install_non_interactive(self, mock_info):\n        \"\"\"Test ask_to_install returns True in non-interactive mode (CI).\"\"\"\n        prerequisite = Prerequisite()\n        prerequisite.name = \"TestPrerequisite\"\n        result = prerequisite.ask_to_install()\n        assert result is True\n        mock_info.assert_called()\n        assert \"not interactive\" in mock_info.call_args[0][0]\n\n    @mock.patch('sys.platform', 'linux')\n    @mock.patch.object(Prerequisite, 'ask_to_install')\n    @mock.patch.object(Prerequisite, 'linux_installer')\n    @mock.patch('pythonforandroid.prerequisites.info')\n    def test_install_when_user_accepts_linux(self, mock_info, mock_installer, mock_ask):\n        \"\"\"Test install calls linux_installer when user accepts on Linux.\"\"\"\n        prerequisite = Prerequisite()\n        prerequisite.installer_is_supported = dict(linux=True, darwin=True)\n        mock_ask.return_value = True\n        prerequisite.install()\n        mock_installer.assert_called_once()\n\n    @mock.patch('sys.platform', 'darwin')\n    @mock.patch.object(Prerequisite, 'ask_to_install')\n    @mock.patch.object(Prerequisite, 'darwin_installer')\n    def test_install_when_user_accepts_darwin(self, mock_installer, mock_ask):\n        \"\"\"Test install calls darwin_installer when user accepts on macOS.\"\"\"\n        prerequisite = Prerequisite()\n        prerequisite.installer_is_supported = dict(linux=True, darwin=True)\n        mock_ask.return_value = True\n        prerequisite.install()\n        mock_installer.assert_called_once()\n\n    @mock.patch.object(Prerequisite, 'ask_to_install')\n    @mock.patch('pythonforandroid.prerequisites.info')\n    def test_install_when_user_declines(self, mock_info, mock_ask):\n        \"\"\"Test install skips installation when user declines.\"\"\"\n        prerequisite = Prerequisite()\n        prerequisite.name = \"TestPrerequisite\"\n        mock_ask.return_value = False\n        prerequisite.install()\n        mock_info.assert_called()\n        assert \"Skipping\" in mock_info.call_args[0][0]\n\n    def test_install_is_supported(self):\n        \"\"\"Test install_is_supported returns correct platform support.\"\"\"\n        prerequisite = Prerequisite()\n        prerequisite.installer_is_supported = dict(linux=True, darwin=False)\n        with mock.patch('sys.platform', 'linux'):\n            assert prerequisite.install_is_supported() is True\n\n\nclass TestJDKPrerequisiteVersionChecking:\n    \"\"\"Test JDK version checking logic.\"\"\"\n\n    @mock.patch('pythonforandroid.prerequisites.subprocess.Popen')\n    @mock.patch('os.path.exists')\n    def test_darwin_jdk_is_supported_valid_version(self, mock_exists, mock_popen):\n        \"\"\"Test _darwin_jdk_is_supported returns True for valid JDK 17.\"\"\"\n        prerequisite = JDKPrerequisite()\n        mock_exists.return_value = True\n\n        # Mock javac version output\n        mock_process = mock.Mock()\n        mock_process.returncode = 0\n        mock_process.communicate.return_value = (b'javac 17.0.2\\n', b'')\n        mock_popen.return_value = mock_process\n\n        result = prerequisite._darwin_jdk_is_supported('/path/to/jdk')\n        assert result is True\n\n    @mock.patch('pythonforandroid.prerequisites.subprocess.Popen')\n    @mock.patch('os.path.exists')\n    def test_darwin_jdk_is_supported_invalid_version(self, mock_exists, mock_popen):\n        \"\"\"Test _darwin_jdk_is_supported returns False for wrong JDK version.\"\"\"\n        prerequisite = JDKPrerequisite()\n        mock_exists.return_value = True\n\n        mock_process = mock.Mock()\n        mock_process.returncode = 0\n        mock_process.communicate.return_value = (b'javac 11.0.1\\n', b'')\n        mock_popen.return_value = mock_process\n\n        result = prerequisite._darwin_jdk_is_supported('/path/to/jdk')\n        assert result is False\n\n    @mock.patch('os.path.exists')\n    def test_darwin_jdk_is_supported_no_javac(self, mock_exists):\n        \"\"\"Test _darwin_jdk_is_supported returns False when javac doesn't exist.\"\"\"\n        prerequisite = JDKPrerequisite()\n        mock_exists.return_value = False\n        result = prerequisite._darwin_jdk_is_supported('/path/to/jdk')\n        assert result is False\n\n    @mock.patch('pythonforandroid.prerequisites.subprocess.run')\n    def test_darwin_get_libexec_jdk_path(self, mock_run):\n        \"\"\"Test _darwin_get_libexec_jdk_path calls java_home correctly.\"\"\"\n        prerequisite = JDKPrerequisite()\n        mock_run.return_value = mock.Mock(stdout=b'/Library/Java/JDK/17\\n')\n\n        result = prerequisite._darwin_get_libexec_jdk_path(version='17')\n        assert result == '/Library/Java/JDK/17'\n        mock_run.assert_called_once()\n        assert '-v' in mock_run.call_args[0][0]\n        assert '17' in mock_run.call_args[0][0]\n\n    @mock.patch.dict('os.environ', {'JAVA_HOME': '/custom/jdk'})\n    @mock.patch.object(JDKPrerequisite, '_darwin_jdk_is_supported')\n    def test_darwin_checker_uses_java_home_env(self, mock_is_supported):\n        \"\"\"Test darwin_checker uses JAVA_HOME env var if set.\"\"\"\n        prerequisite = JDKPrerequisite()\n        mock_is_supported.return_value = True\n\n        result = prerequisite.darwin_checker()\n        assert result is True\n        mock_is_supported.assert_called_with('/custom/jdk')\n\n\nclass TestHomebrewHelpers:\n    \"\"\"Test Homebrew helper methods.\"\"\"\n\n    @mock.patch('pythonforandroid.prerequisites.subprocess.Popen')\n    def test_darwin_get_brew_formula_location_prefix_success(self, mock_popen):\n        \"\"\"Test _darwin_get_brew_formula_location_prefix returns path on success.\"\"\"\n        prerequisite = Prerequisite()\n        mock_process = mock.Mock()\n        mock_process.returncode = 0\n        mock_process.communicate.return_value = (b'/opt/homebrew/opt/openssl@3\\n', b'')\n        mock_popen.return_value = mock_process\n\n        result = prerequisite._darwin_get_brew_formula_location_prefix('openssl@3')\n        assert result == '/opt/homebrew/opt/openssl@3'\n        mock_popen.assert_called_once()\n        assert 'brew' in mock_popen.call_args[0][0]\n        assert '--prefix' in mock_popen.call_args[0][0]\n\n    @mock.patch('pythonforandroid.prerequisites.subprocess.Popen')\n    @mock.patch('pythonforandroid.prerequisites.error')\n    def test_darwin_get_brew_formula_location_prefix_failure(self, mock_error, mock_popen):\n        \"\"\"Test _darwin_get_brew_formula_location_prefix returns None on failure.\"\"\"\n        prerequisite = Prerequisite()\n        mock_process = mock.Mock()\n        mock_process.returncode = 1\n        mock_process.communicate.return_value = (b'', b'Formula not found\\n')\n        mock_popen.return_value = mock_process\n\n        result = prerequisite._darwin_get_brew_formula_location_prefix('nonexistent')\n        assert result is None\n        mock_error.assert_called()\n\n    @mock.patch('pythonforandroid.prerequisites.subprocess.Popen')\n    def test_darwin_get_brew_formula_location_prefix_with_installed_flag(self, mock_popen):\n        \"\"\"Test _darwin_get_brew_formula_location_prefix uses --installed flag.\"\"\"\n        prerequisite = Prerequisite()\n        mock_process = mock.Mock()\n        mock_process.returncode = 0\n        mock_process.communicate.return_value = (b'/opt/homebrew/opt/cmake\\n', b'')\n        mock_popen.return_value = mock_process\n\n        prerequisite._darwin_get_brew_formula_location_prefix('cmake', installed=True)\n        assert '--installed' in mock_popen.call_args[0][0]\n\n\nclass TestCheckAndInstallPrerequisites:\n    \"\"\"Test main prerequisite checking workflow.\"\"\"\n\n    @mock.patch('pythonforandroid.prerequisites.get_required_prerequisites')\n    def test_check_and_install_all_met(self, mock_get_prereqs):\n        \"\"\"Test check_and_install when all prerequisites are met.\"\"\"\n        # Create mock prerequisites that are all valid\n        mock_prereq1 = mock.Mock()\n        mock_prereq1.is_valid.return_value = True\n        mock_prereq2 = mock.Mock()\n        mock_prereq2.is_valid.return_value = True\n\n        mock_get_prereqs.return_value = [mock_prereq1, mock_prereq2]\n\n        check_and_install_default_prerequisites()\n\n        # Verify prerequisites were checked\n        mock_prereq1.is_valid.assert_called_once()\n        mock_prereq2.is_valid.assert_called_once()\n\n        # Verify no installation attempted\n        mock_prereq1.install.assert_not_called()\n        mock_prereq2.install.assert_not_called()\n\n    @mock.patch('pythonforandroid.prerequisites.get_required_prerequisites')\n    def test_check_and_install_some_not_met(self, mock_get_prereqs):\n        \"\"\"Test check_and_install when some prerequisites are not met.\"\"\"\n        # First prerequisite valid, second not valid but has installer\n        mock_prereq1 = mock.Mock()\n        mock_prereq1.is_valid.return_value = True\n\n        mock_prereq2 = mock.Mock()\n        mock_prereq2.is_valid.return_value = False\n        mock_prereq2.install_is_supported.return_value = True\n\n        mock_get_prereqs.return_value = [mock_prereq1, mock_prereq2]\n\n        check_and_install_default_prerequisites()\n\n        # Verify second prerequisite triggers installation workflow\n        mock_prereq2.show_helper.assert_called_once()\n        mock_prereq2.install.assert_called_once()\n"
  },
  {
    "path": "tests/test_pythonpackage.py",
    "content": "\"\"\"\nTHESE TESTS DON'T RUN IN GITHUB-ACTIONS (takes too long!!)\nONLY THE BASIC ONES IN test_pythonpackage_basic.py DO.\n\n(This file basically covers all tests for any of the\nfunctions that aren't already part of the basic\ntest set)\n\"\"\"\n\nimport os\nimport shutil\nimport tempfile\n\nfrom pythonforandroid.pythonpackage import (\n    _extract_info_from_package,\n    extract_metainfo_files_from_package,\n    get_package_as_folder,\n    get_package_dependencies,\n)\n\n\ndef local_repo_folder():\n    return os.path.abspath(os.path.join(\n        os.path.dirname(__file__), \"..\"\n    ))\n\n\ndef test_get_package_dependencies():\n    # TEST 1 from source code folder:\n    deps_nonrecursive = get_package_dependencies(\n        local_repo_folder(), recursive=False\n    )\n    deps_recursive = get_package_dependencies(\n        local_repo_folder(), recursive=True\n    )\n    # Check that jinja2 is returned as direct dep:\n    assert len([dep for dep in deps_nonrecursive\n                if \"jinja2\" in dep]) > 0\n    # Check that MarkupSafe is returned as indirect dep of jinja2:\n    assert [\n        dep for dep in deps_recursive\n        if \"MarkupSafe\" in dep\n    ]\n    # Check setuptools not being in non-recursive deps:\n    # (It will be in recursive ones due to p4a's build dependency)\n    assert \"setuptools\" not in deps_nonrecursive\n    # Check setuptools is present in non-recursive deps,\n    # if we also add build requirements:\n    assert \"setuptools\" in get_package_dependencies(\n        local_repo_folder(), recursive=False,\n        include_build_requirements=True,\n    )\n\n    # TEST 2 from external ref:\n    # Check that jinja2 is returned as direct dep:\n    assert len([dep for dep in get_package_dependencies(\"python-for-android\")\n                if \"jinja2\" in dep]) > 0\n    # Check that MarkupSafe is returned as indirect dep of jinja2:\n    assert [\n        dep for dep in get_package_dependencies(\n            \"python-for-android\", recursive=True\n        )\n        if \"MarkupSafe\" in dep\n    ]\n\n\ndef test_extract_metainfo_files_from_package():\n    # TEST 1 from external ref:\n    files_dir = tempfile.mkdtemp()\n    try:\n        extract_metainfo_files_from_package(\"python-for-android\",\n                                            files_dir, debug=True)\n        assert os.path.exists(os.path.join(files_dir, \"METADATA\"))\n    finally:\n        shutil.rmtree(files_dir)\n\n    # TEST 2 from local folder:\n    files_dir = tempfile.mkdtemp()\n    try:\n        extract_metainfo_files_from_package(local_repo_folder(),\n                                            files_dir, debug=True)\n        assert os.path.exists(os.path.join(files_dir, \"METADATA\"))\n    finally:\n        shutil.rmtree(files_dir)\n\n\ndef test_get_package_as_folder():\n    # WARNING !!! This function behaves DIFFERENTLY if the requested package\n    # has a wheel available vs a source package. What we're getting is\n    # essentially what pip also would fetch, but this can obviously CHANGE\n    # depending on what is happening/available on PyPI.\n    #\n    # Therefore, this test doesn't really go in-depth.\n    (obtained_type, obtained_path) = \\\n        get_package_as_folder(\"python-for-android\")\n    try:\n        assert obtained_type in {\"source\", \"wheel\"}\n        assert os.path.isdir(obtained_path)\n    finally:\n        # Try to ensure cleanup:\n        shutil.rmtree(obtained_path)\n\n\ndef test__extract_info_from_package():\n    # This is indirectly already tested a lot through get_package_name()\n    # and get_package_dependencies(), so we'll just do one basic test:\n\n    assert _extract_info_from_package(\n        local_repo_folder(),\n        extract_type=\"name\"\n    ) == \"python-for-android\"\n"
  },
  {
    "path": "tests/test_pythonpackage_basic.py",
    "content": "\"\"\"\nONLY BASIC TEST SET. The additional ones are in test_pythonpackage.py.\n\nThese are in a separate file because these were picked to run in github-actions,\nwhile the other additional ones aren't (for build time reasons).\n\"\"\"\n\nimport os\nimport shutil\nimport sys\nimport subprocess\nimport tempfile\nimport textwrap\nfrom unittest import mock\nimport pytest\nfrom build import BuildBackendException\n\nfrom pythonforandroid.pythonpackage import (\n    _extract_info_from_package,\n    get_dep_names_of_package,\n    get_package_name,\n    _get_system_python_executable,\n    is_filesystem_path,\n    parse_as_folder_reference,\n    transform_dep_for_pip,\n)\n\n\ndef local_repo_folder():\n    return os.path.abspath(os.path.join(\n        os.path.dirname(__file__), \"..\"\n    ))\n\n\ndef fake_metadata_extract(dep_name, output_folder, debug=False):\n    # Helper function to write out fake metadata.\n    with open(os.path.join(output_folder, \"METADATA\"), \"w\") as f:\n        f.write(textwrap.dedent(\"\"\"\\\n            Metadata-Version: 2.1\n            Name: testpackage\n            Version: 0.1\n            Requires-Dist: testpkg\n            Requires-Dist: testpkg2\n\n            Lorem Ipsum\"\"\"\n        ))\n    with open(os.path.join(output_folder, \"metadata_source\"), \"w\") as f:\n        f.write(u\"wheel\")  # since we have no pyproject.toml\n\n\ndef test__extract_info_from_package():\n    import pythonforandroid.pythonpackage  # noqa\n    with mock.patch(\"pythonforandroid.pythonpackage.\"\n                    \"extract_metainfo_files_from_package\",\n                    fake_metadata_extract):\n        assert _extract_info_from_package(\n            \"whatever\", extract_type=\"name\"\n        ) == \"testpackage\"\n        assert set(_extract_info_from_package(\n            \"whatever\", extract_type=\"dependencies\"\n        )) == {\"testpkg\", \"testpkg2\"}\n\n\ndef test_get_package_name():\n    # TEST 1 from external ref\n    with mock.patch(\"pythonforandroid.pythonpackage.\"\n                    \"extract_metainfo_files_from_package\",\n                    fake_metadata_extract):\n        assert get_package_name(\"TeStPackaGe\") == \"testpackage\"\n\n    # TEST 2 from a local folder, for which we'll create a fake package:\n    temp_d = tempfile.mkdtemp(prefix=\"p4a-pythonpackage-test-tmp-\")\n    try:\n        with open(os.path.join(temp_d, \"setup.py\"), \"w\") as f:\n            f.write(textwrap.dedent(\"\"\"\\\n                from setuptools import setup\n                setup(name=\"testpackage\")\n                \"\"\"\n            ))\n        pkg_name = get_package_name(temp_d)\n        assert pkg_name == \"testpackage\"\n    finally:\n        shutil.rmtree(temp_d)\n\n\ndef test_get_dep_names_of_package():\n    # TEST 1 from external ref:\n    # Check that colorama is returned without the install condition when\n    # just getting the names (it has a \"; ...\" conditional originally):\n    dep_names = get_dep_names_of_package(\"python-for-android==2023.9.16\")\n    assert \"colorama\" in dep_names\n    assert \"setuptools\" not in dep_names\n    try:\n        dep_names = get_dep_names_of_package(\n            \"python-for-android\", include_build_requirements=True,\n            verbose=True,\n        )\n    except NotImplementedError as e:\n        # If python-for-android was fetched as wheel then build requirements\n        # cannot be obtained (since that is not implemented for wheels).\n        # Check for the correct error message:\n        assert \"wheel\" in str(e)\n        # (And yes it would be better to do a local test with something\n        #  that is guaranteed to be a wheel and not remote on pypi,\n        #  but that might require setting up a full local pypiserver.\n        #  Not worth the test complexity for now, but if anyone has an\n        #  idea in the future feel free to replace this subtest.)\n    else:\n        # We managed to obtain build requirements!\n        # Check setuptools is in here:\n        assert \"setuptools\" in dep_names\n\n    # TEST 2 from local folder:\n    assert \"colorama\" in get_dep_names_of_package(local_repo_folder())\n\n    # Now test that exact version pins are kept, but others aren't:\n    test_fake_package = tempfile.mkdtemp()\n    try:\n        with open(os.path.join(test_fake_package, \"setup.py\"), \"w\") as f:\n            f.write(textwrap.dedent(\"\"\"\\\n            from setuptools import setup\n\n            setup(name='fakeproject',\n                  description='fake for testing',\n                  install_requires=['buildozer==0.39',\n                                    'python-for-android>=0.5.1'],\n            )\n            \"\"\"))\n        # See that we get the deps with the exact version pin kept but\n        # the other version restriction gone:\n        assert set(get_dep_names_of_package(\n            test_fake_package, recursive=False,\n            keep_version_pins=True, verbose=True\n        )) == {\"buildozer==0.39\", \"python-for-android\"}\n\n        # Make sure we also can get the fully cleaned up variant:\n        assert set(get_dep_names_of_package(\n            test_fake_package, recursive=False,\n            keep_version_pins=False, verbose=True\n        )) == {\"buildozer\", \"python-for-android\"}\n\n        # Test with build requirements included:\n        dep_names = get_dep_names_of_package(\n            test_fake_package, recursive=False,\n            keep_version_pins=False, verbose=True,\n            include_build_requirements=True\n            )\n        assert len(\n            {\"buildozer\", \"python-for-android\", \"setuptools\"}.intersection(\n                dep_names\n            )\n        ) == 3  # all three must be included\n    finally:\n        shutil.rmtree(test_fake_package)\n\n\ndef test_transform_dep_for_pip():\n    # A reminder, this entire function we test here is just a workaround\n    # for https://github.com/pypa/pip/issues/6097 (and not a nice one.)\n    # As soon as upstream fixes it, we should throw it & this test out\n    transformed = (\n        transform_dep_for_pip(\n            \"python-for-android @ https://github.com/kivy/\" +\n            \"python-for-android/archive/master.zip\"\n        ),\n        transform_dep_for_pip(\n            \"python-for-android @ https://github.com/kivy/\" +\n            \"python-for-android/archive/master.zip\" +\n            \"#egg=python-for-android-master\"\n        ),\n        transform_dep_for_pip(\n            \"python-for-android @ https://github.com/kivy/\" +\n            \"python-for-android/archive/master.zip\" +\n            \"#\"  # common hack variant used by others to make pip parse it\n        ),\n    )\n    expected = (\n        \"https://github.com/kivy/python-for-android/archive/master.zip\" +\n        \"#egg=python-for-android\"\n    )\n    assert transformed == (expected, expected, expected)\n    assert transform_dep_for_pip(\"https://a@b/\") == \"https://a@b/\"\n\n\ndef test_is_filesystem_path():\n    assert is_filesystem_path(\"/some/test\")\n    assert not is_filesystem_path(\"https://blubb\")\n    assert not is_filesystem_path(\"test @ bla\")\n    assert is_filesystem_path(\"/abc/c@d\")\n    assert not is_filesystem_path(\"https://user:pw@host/\")\n    assert is_filesystem_path(\".\")\n    assert is_filesystem_path(\"\")\n\n\ndef test_parse_as_folder_reference():\n    assert parse_as_folder_reference(\"file:///a%20test\") == \"/a test\"\n    assert parse_as_folder_reference(\"https://github.com\") is None\n    assert parse_as_folder_reference(\"/a/folder\") == \"/a/folder\"\n    assert parse_as_folder_reference(\"test @ /abc\") == \"/abc\"\n    assert parse_as_folder_reference(\"test @ https://bla\") is None\n\n\n@pytest.mark.parametrize(\"input_ref,expected\", [\n    # URL-encoded special characters\n    (\"file:///path/with%40special\", \"/path/with@special\"),\n    (\"file:///path/with%23hash\", \"/path/with#hash\"),\n    # Mixed @ syntax\n    (\"pkg @ file:///path/to/pkg\", \"/path/to/pkg\"),\n    # Empty and relative paths\n    (\"\", \"\"),\n    (\"./relative\", \"./relative\"),\n])\ndef test_parse_as_folder_reference_edge_cases(input_ref, expected):\n    \"\"\"Test edge cases in folder reference parsing.\"\"\"\n    assert parse_as_folder_reference(input_ref) == expected\n\n\n@pytest.mark.parametrize(\"path,expected\", [\n    # Relative paths (should be filesystem paths)\n    (\"../parent\", True),\n    (\"~/home/path\", True),\n    (\"./current\", True),\n    # Git URLs (should not be filesystem paths)\n    (\"git+https://github.com/user/repo.git\", False),\n    (\"git+ssh://git@github.com/user/repo.git\", False),\n    # Version specifiers (should not be filesystem paths)\n    (\"package>=1.0,<2.0\", False),\n    (\"package[extra]>=1.0\", False),\n])\ndef test_is_filesystem_path_edge_cases(path, expected):\n    \"\"\"Test additional edge cases for filesystem path detection.\"\"\"\n    assert is_filesystem_path(path) == expected\n\n\n@pytest.mark.parametrize(\"input_dep,expected\", [\n    # Query parameters\n    (\"pkg @ https://example.com/pkg.zip?token=abc123\", \"https://example.com/pkg.zip?token=abc123#egg=pkg\"),\n    # Fragments\n    (\"pkg @ https://example.com/pkg.zip#sha256=abc\", \"https://example.com/pkg.zip#sha256=abc#egg=pkg\"),\n])\ndef test_transform_dep_for_pip_with_special_urls(input_dep, expected):\n    \"\"\"Test dependency transformation with query parameters and fragments.\"\"\"\n    assert transform_dep_for_pip(input_dep) == expected\n\n\ndef test_transform_dep_for_pip_passthrough():\n    \"\"\"Test passthrough for already-transformed URLs.\"\"\"\n    url = \"https://example.com/package.zip#egg=package\"\n    assert transform_dep_for_pip(url) == url\n\n\ndef test_get_package_name_with_error():\n    \"\"\"Test get_package_name handles errors gracefully.\"\"\"\n    # Test with invalid package that doesn't exist\n    with mock.patch(\"pythonforandroid.pythonpackage.\"\n                    \"extract_metainfo_files_from_package\") as mock_extract:\n        exception_message = \"Package not found\"\n        mock_extract.side_effect = Exception(exception_message)\n\n        with pytest.raises(Exception, match=exception_message):\n            get_package_name(\"nonexistent-package-xyz-123\")\n\n\ndef test_get_dep_names_error_handling():\n    \"\"\"Test error handling in dependency extraction.\"\"\"\n    # Use context manager to ensure cleanup even if test fails\n    with tempfile.TemporaryDirectory(prefix=\"p4a-error-test-\") as temp_d:\n        # Create a setup.py that will fail\n        with open(os.path.join(temp_d, \"setup.py\"), \"w\") as f:\n            f.write(\"raise RuntimeError('Invalid setup.py')\")\n\n        with pytest.raises(BuildBackendException, match=\"Backend subprocess exited when trying to invoke get_requires_for_build_wheel\"):\n            get_dep_names_of_package(temp_d, recursive=False, verbose=True)\n\n\ndef test_extract_info_from_package_missing_metadata():\n    \"\"\"Test _extract_info_from_package raises error when metadata is missing.\"\"\"\n    def fake_empty_metadata(dep_name, output_folder, debug=False):\n        # Don't create any metadata files\n        pass\n\n    with mock.patch(\"pythonforandroid.pythonpackage.\"\n                    \"extract_metainfo_files_from_package\",\n                    fake_empty_metadata):\n        # Should raise an exception when metadata is missing\n        with pytest.raises(FileNotFoundError):\n            _extract_info_from_package(\"test\", extract_type=\"name\")\n\n\nclass TestGetSystemPythonExecutable():\n    \"\"\" This contains all tests for _get_system_python_executable().\n\n    ULTRA IMPORTANT THING TO UNDERSTAND: (if you touch this)\n\n    This code runs things with other python interpreters NOT in the tox\n    environment/virtualenv.\n    E.g. _get_system_python_executable() is outside in the regular\n    host environment! That also means all dependencies can be possibly\n    not present!\n\n    This is kind of absurd that we need this to run the test at all,\n    but we can't test this inside tox's virtualenv:\n    \"\"\"\n\n    def test_basic(self):\n        # Tests function inside tox env with no further special setup.\n\n        # Get system-wide python bin:\n        pybin = _get_system_python_executable()\n\n        # The python binary needs to match our major version to be correct:\n        pyversion = subprocess.check_output([\n            pybin, \"-c\", \"import sys; print(sys.version)\"\n        ], stderr=subprocess.STDOUT).decode(\"utf-8\", \"replace\")\n        assert pyversion.strip() == sys.version.strip()\n\n    def run__get_system_python_executable(self, pybin):\n        \"\"\" Helper function to run our function.\n\n            We want to see what _get_system_python_executable() does given\n            a specific python, so we need to make it import it and run it,\n            with that TARGET python, which this function does.\n        \"\"\"\n        cmd = [\n            pybin,\n            \"-c\",\n            \"import importlib\\n\"\n            \"import build.util\\n\"\n            \"import os\\n\"\n            \"import sys\\n\"\n            \"sys.path = [os.path.dirname(sys.argv[1])] + sys.path\\n\"\n            \"m = importlib.import_module(\\n\"\n            \"    os.path.basename(sys.argv[1]).partition('.')[0]\\n\"\n            \")\\n\"\n            \"print(m._get_system_python_executable())\",\n            os.path.join(os.path.dirname(__file__), \"..\",\n                         \"pythonforandroid\", \"pythonpackage.py\"),\n        ]\n        # Actual call to python:\n        try:\n            return subprocess.check_output(\n                cmd, stderr=subprocess.STDOUT\n            ).decode(\"utf-8\", \"replace\").strip()\n        except subprocess.CalledProcessError as e:\n            raise RuntimeError(\"call failed, with output: \" + str(e.output))\n\n    def test_systemwide_python(self):\n        # Get system-wide python bin seen from here first:\n        pybin = _get_system_python_executable()\n        # (this call was not a test, we really just need the path here)\n\n        # Check that in system-wide python, the system-wide python is returned:\n        # IMPORTANT: understand that this runs OUTSIDE of any virtualenv.\n        try:\n            p1 = os.path.normpath(\n                self.run__get_system_python_executable(pybin)\n            )\n            p2 = os.path.normpath(pybin)\n            assert p1 == p2\n        except RuntimeError as e:\n            # (remember this is not in a virtualenv)\n            # Some deps may not be installed, so we just avoid to raise\n            # an exception here, as a missing dep should not make the test\n            # fail.\n            if \"build\" in str(e.args):\n                # System python probably doesn't have build available!\n                pass\n            elif \"toml\" in str(e.args):\n                # System python probably doesn't have toml available!\n                pass\n            else:\n                raise\n\n    def test_venv(self):\n        \"\"\" Verifies that _get_system_python_executable() works correctly\n            in a 'venv' (Python 3 only feature).\n        \"\"\"\n\n        # Get system-wide python bin seen from here first:\n        pybin = _get_system_python_executable()\n        # (this call was not a test, we really just need the path here)\n\n        test_dir = tempfile.mkdtemp()\n        try:\n            # Check that in a venv/pyvenv, the system-wide python is returned:\n            subprocess.check_output([\n                pybin, \"-m\", \"venv\", \"--\",\n                os.path.join(test_dir, \"venv\")\n            ])\n            subprocess.check_output([\n                os.path.join(test_dir, \"venv\", \"bin\", \"pip\"),\n                \"install\", \"-U\", \"pip\"\n            ])\n            subprocess.check_output([\n                os.path.join(test_dir, \"venv\", \"bin\", \"pip\"),\n                \"install\", \"-U\", \"build\", \"toml\", \"sh<2.0\", \"colorama\",\n                \"appdirs\", \"jinja2\", \"packaging\"\n            ])\n            sys_python_path = self.run__get_system_python_executable(\n                os.path.join(test_dir, \"venv\", \"bin\", \"python\")\n            )\n            assert os.path.normpath(sys_python_path).startswith(\n                os.path.normpath(pybin)\n            )\n        finally:\n            shutil.rmtree(test_dir)\n"
  },
  {
    "path": "tests/test_recipe.py",
    "content": "import os\nimport pytest\nimport tempfile\nimport types\nimport unittest\nimport warnings\nfrom unittest import mock\n\nfrom pythonforandroid.build import Context\nfrom pythonforandroid.recipe import Recipe, TargetPythonRecipe, import_recipe\nfrom pythonforandroid.archs import ArchAarch_64\nfrom pythonforandroid.bootstrap import Bootstrap\nfrom tests.test_bootstrap import BaseClassSetupBootstrap\n\n\ndef patch_logger(level):\n    return mock.patch('pythonforandroid.recipe.{}'.format(level))\n\n\ndef patch_logger_info():\n    return patch_logger('info')\n\n\ndef patch_logger_debug():\n    return patch_logger('debug')\n\n\ndef patch_urlretrieve():\n    return mock.patch('pythonforandroid.recipe.urlretrieve')\n\n\nclass DummyRecipe(Recipe):\n    pass\n\n\nclass TestRecipe(unittest.TestCase):\n\n    def test_recipe_dirs(self):\n        \"\"\"\n        Trivial `recipe_dirs()` test.\n        Makes sure the list is not empty and has the root directory.\n        \"\"\"\n        ctx = Context()\n        recipes_dir = Recipe.recipe_dirs(ctx)\n        # by default only the root dir `recipes` directory\n        self.assertEqual(len(recipes_dir), 1)\n        self.assertTrue(recipes_dir[0].startswith(ctx.root_dir))\n\n    def test_list_recipes(self):\n        \"\"\"\n        Trivial test verifying list_recipes returns a generator with some recipes.\n        \"\"\"\n        ctx = Context()\n        recipes = Recipe.list_recipes(ctx)\n        self.assertTrue(isinstance(recipes, types.GeneratorType))\n        recipes = list(recipes)\n        self.assertIn('python3', recipes)\n\n    def test_get_recipe(self):\n        \"\"\"\n        Makes sure `get_recipe()` returns a `Recipe` object when possible.\n        \"\"\"\n        ctx = Context()\n        recipe_name = 'python3'\n        recipe = Recipe.get_recipe(recipe_name, ctx)\n        self.assertTrue(isinstance(recipe, Recipe))\n        self.assertEqual(recipe.name, recipe_name)\n        recipe_name = 'does_not_exist'\n        with self.assertRaises(ValueError) as e:\n            Recipe.get_recipe(recipe_name, ctx)\n        self.assertEqual(\n            e.exception.args[0], 'Recipe does not exist: {}'.format(recipe_name))\n\n    def test_import_recipe(self):\n        \"\"\"\n        Verifies we can dynamically import a recipe without warnings.\n        \"\"\"\n        p4a_root_dir = os.path.dirname(os.path.dirname(__file__))\n        name = 'pythonforandroid.recipes.python3'\n        pathname = os.path.join(\n            *([p4a_root_dir] + name.split('.') + ['__init__.py'])\n        )\n        with warnings.catch_warnings(record=True) as recorded_warnings:\n            warnings.simplefilter(\"always\")\n            module = import_recipe(name, pathname)\n        assert module is not None\n        assert recorded_warnings == []\n\n    def test_download_if_necessary(self):\n        \"\"\"\n        Download should happen via `Recipe.download()` only if the recipe\n        specific environment variable is not set.\n        \"\"\"\n        # download should happen as the environment variable is not set\n        recipe = DummyRecipe()\n        recipe.ctx = Context()\n        recipe.ctx._ndk_api = 36\n        with mock.patch.object(Recipe, 'download') as m_download:\n            recipe.download_if_necessary()\n        assert m_download.call_args_list == [mock.call()]\n        # after setting it the download should be skipped\n        env_var = 'P4A_test_recipe_DIR'\n        env_dict = {env_var: '1'}\n        with mock.patch.object(Recipe, 'download') as m_download, mock.patch.dict(os.environ, env_dict):\n            recipe.download_if_necessary()\n        assert m_download.call_args_list == []\n\n    def test_download_url_not_set(self):\n        \"\"\"\n        Verifies that no download happens when URL is not set.\n        \"\"\"\n        recipe = DummyRecipe()\n        with patch_logger_info() as m_info:\n            recipe.download()\n        assert m_info.call_args_list == [\n            mock.call('Skipping test_recipe download as no URL is set')]\n\n    @staticmethod\n    def get_dummy_python_recipe_for_download_tests():\n        \"\"\"\n        Helper method for creating a test recipe used in download tests.\n        \"\"\"\n        recipe = DummyRecipe()\n        filename = 'Python-3.7.4.tgz'\n        url = 'https://www.python.org/ftp/python/3.7.4/{}'.format(filename)\n        recipe._url = url\n        recipe.ctx = Context()\n        return recipe, filename\n\n    def test_download_url_is_set(self):\n        \"\"\"\n        Verifies the actual download gets triggered when the URL is set.\n        \"\"\"\n        recipe, filename = self.get_dummy_python_recipe_for_download_tests()\n        url = recipe.url\n        with (\n                patch_logger_debug()) as m_debug, (\n                mock.patch.object(Recipe, 'download_file')) as m_download_file, (\n                mock.patch('pythonforandroid.recipe.touch')) as m_touch, (\n                tempfile.TemporaryDirectory()) as temp_dir:\n            recipe.ctx.setup_dirs(temp_dir)\n            recipe.download()\n        assert m_download_file.call_args_list == [mock.call(url, filename)]\n        assert m_debug.call_args_list == [\n            mock.call(\n                'Downloading test_recipe from '\n                'https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz')]\n        assert m_touch.call_count == 1\n\n    def test_download_file_scheme_https(self):\n        \"\"\"\n        Verifies `urlretrieve()` is being called on https downloads.\n        \"\"\"\n        recipe, filename = self.get_dummy_python_recipe_for_download_tests()\n        url = recipe.url\n        with (\n                patch_urlretrieve()) as m_urlretrieve, (\n                tempfile.TemporaryDirectory()) as temp_dir:\n            recipe.ctx.setup_dirs(temp_dir)\n            assert recipe.download_file(url, filename) == filename\n        assert m_urlretrieve.call_args_list == [\n            mock.call(url, filename, mock.ANY)\n        ]\n\n    def test_download_file_scheme_https_oserror(self):\n        \"\"\"\n        Checks `urlretrieve()` is being retried on `OSError`.\n        After a number of retries the exception is re-reaised.\n        \"\"\"\n        recipe, filename = self.get_dummy_python_recipe_for_download_tests()\n        url = recipe.url\n        with (\n                patch_urlretrieve()) as m_urlretrieve, (\n                mock.patch('pythonforandroid.recipe.time.sleep')) as m_sleep, (\n                pytest.raises(OSError)), (\n                tempfile.TemporaryDirectory()) as temp_dir:\n            recipe.ctx.setup_dirs(temp_dir)\n            m_urlretrieve.side_effect = OSError\n            assert recipe.download_file(url, filename) == filename\n        retry = 5\n        expected_call_args_list = [mock.call(url, filename, mock.ANY)] * retry\n        assert m_urlretrieve.call_args_list == expected_call_args_list\n        expected_call_args_list = [mock.call(2**i) for i in range(retry - 1)]\n        assert m_sleep.call_args_list == expected_call_args_list\n\n\nclass TestTargetPythonRecipe(unittest.TestCase):\n\n    def test_major_minor_version_string(self):\n        \"\"\"\n        Test that the major_minor_version_string property returns the correct\n        string.\n        \"\"\"\n        class DummyTargetPythonRecipe(TargetPythonRecipe):\n            version = '1.2.3'\n\n        recipe = DummyTargetPythonRecipe()\n        assert recipe.major_minor_version_string == '1.2'\n\n\nclass TestLibraryRecipe(BaseClassSetupBootstrap, unittest.TestCase):\n    def setUp(self):\n        \"\"\"\n        Initialize a Context with a Bootstrap and a Distribution to properly\n        test an library recipe, to do so we reuse `BaseClassSetupBootstrap`\n        \"\"\"\n        super().setUp()\n        self.ctx.bootstrap = Bootstrap().get_bootstrap('sdl2', self.ctx)\n        self.setUp_distribution_with_bootstrap(self.ctx.bootstrap)\n\n    def test_built_libraries(self):\n        \"\"\"The openssl recipe is a library recipe, so it should have set the\n        attribute `built_libraries`, but not the case of `pyopenssl` recipe.\n        \"\"\"\n        recipe = Recipe.get_recipe('openssl', self.ctx)\n        self.assertTrue(recipe.built_libraries)\n\n        recipe = Recipe.get_recipe('pyopenssl', self.ctx)\n        self.assertFalse(recipe.built_libraries)\n\n    @mock.patch('pythonforandroid.recipe.exists')\n    def test_should_build(self, mock_exists):\n        # avoid trying to find the recipe in a non-existing storage directory\n        self.ctx.storage_dir = None\n\n        arch = ArchAarch_64(self.ctx)\n        recipe = Recipe.get_recipe('openssl', self.ctx)\n        recipe.ctx = self.ctx\n        self.assertFalse(recipe.should_build(arch))\n\n        mock_exists.return_value = False\n        self.assertTrue(recipe.should_build(arch))\n\n    @mock.patch('pythonforandroid.recipe.Recipe.get_libraries')\n    @mock.patch('pythonforandroid.recipe.Recipe.install_libs')\n    def test_install_libraries(self, mock_install_libs, mock_get_libraries):\n        mock_get_libraries.return_value = {\n            '/build_lib/libsample1.so',\n            '/build_lib/libsample2.so',\n        }\n        self.ctx.recipe_build_order = [\n            \"hostpython3\",\n            \"openssl\",\n            \"python3\",\n            \"sdl2\",\n            \"kivy\",\n        ]\n        arch = ArchAarch_64(self.ctx)\n        recipe = Recipe.get_recipe('openssl', self.ctx)\n        recipe.install_libraries(arch)\n        mock_install_libs.assert_called_once_with(\n            arch, *mock_get_libraries.return_value\n        )\n\n\nclass TesSTLRecipe(BaseClassSetupBootstrap, unittest.TestCase):\n    def setUp(self):\n        \"\"\"\n        Initialize a Context with a Bootstrap and a Distribution to properly\n        test a recipe which depends on android's STL library, to do so we reuse\n        `BaseClassSetupBootstrap`\n        \"\"\"\n        super().setUp()\n        self.ctx.bootstrap = Bootstrap().get_bootstrap('sdl2', self.ctx)\n        self.setUp_distribution_with_bootstrap(self.ctx.bootstrap)\n        self.ctx.python_recipe = Recipe.get_recipe('python3', self.ctx)\n\n    @mock.patch('shutil.which')\n    @mock.patch('pythonforandroid.build.ensure_dir')\n    def test_get_recipe_env_with(\n        self, mock_ensure_dir, mock_shutil_which\n    ):\n        \"\"\"\n        Test that :meth:`~pythonforandroid.recipe.STLRecipe.get_recipe_env`\n        returns some expected keys and values.\n\n        .. note:: We don't check all the env variables, only those one specific\n                  of :class:`~pythonforandroid.recipe.STLRecipe`, the others\n                  should be tested in the proper test.\n        \"\"\"\n        expected_compiler = (\n            f\"/opt/android/android-ndk/toolchains/\"\n            f\"llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang\"\n        )\n        mock_shutil_which.return_value = expected_compiler\n\n        arch = ArchAarch_64(self.ctx)\n        recipe = Recipe.get_recipe('libgeos', self.ctx)\n        assert recipe.need_stl_shared, True\n        env = recipe.get_recipe_env(arch)\n        #  check that the mocks have been called\n        mock_ensure_dir.assert_called()\n        mock_shutil_which.assert_called_once_with(\n            expected_compiler, path=self.ctx.env['PATH']\n        )\n        self.assertIsInstance(env, dict)\n\n    @mock.patch('pythonforandroid.recipe.Recipe.install_libs')\n    @mock.patch('pythonforandroid.recipe.isfile')\n    @mock.patch('pythonforandroid.build.ensure_dir')\n    def test_install_stl_lib(\n        self, mock_ensure_dir, mock_isfile, mock_install_lib\n    ):\n        \"\"\"\n        Test that :meth:`~pythonforandroid.recipe.STLRecipe.install_stl_lib`,\n        calls the method :meth:`~pythonforandroid.recipe.Recipe.install_libs`\n        with the proper arguments: a subclass of\n        :class:`~pythonforandroid.archs.Arch` and our stl lib\n        (:attr:`~pythonforandroid.recipe.STLRecipe.stl_lib_name`)\n        \"\"\"\n        mock_isfile.return_value = False\n\n        arch = ArchAarch_64(self.ctx)\n        recipe = Recipe.get_recipe('libgeos', self.ctx)\n        recipe.ctx = self.ctx\n        assert recipe.need_stl_shared, True\n        recipe.install_stl_lib(arch)\n        mock_install_lib.assert_called_once_with(\n            arch,\n            os.path.join(arch.ndk_lib_dir, f\"lib{recipe.stl_lib_name}.so\"),\n        )\n        mock_ensure_dir.assert_called()\n\n    @mock.patch('pythonforandroid.recipe.Recipe.install_stl_lib')\n    def test_postarch_build(self, mock_install_stl_lib):\n        arch = ArchAarch_64(self.ctx)\n        recipe = Recipe.get_recipe('libgeos', self.ctx)\n        assert recipe.need_stl_shared, True\n        recipe.postbuild_arch(arch)\n        mock_install_stl_lib.assert_called_once_with(arch)\n\n    def test_recipe_download_headers(self):\n        \"\"\"Download header can be created on the fly using environment variables.\"\"\"\n        recipe = DummyRecipe()\n        with mock.patch.dict(os.environ, {f'DOWNLOAD_HEADERS_{recipe.name}': '[[\"header1\",\"foo\"],[\"header2\", \"bar\"]]'}):\n            download_headers = recipe.download_headers\n        assert download_headers == [(\"header1\", \"foo\"), (\"header2\", \"bar\")]\n"
  },
  {
    "path": "tests/test_recommendations.py",
    "content": "import unittest\nfrom os.path import join\nfrom sys import version as py_version\n\nimport packaging.version\nfrom unittest import mock\nfrom pythonforandroid.recommendations import (\n    check_ndk_api,\n    check_ndk_version,\n    check_target_api,\n    read_ndk_version,\n    check_python_version,\n    print_recommendations,\n    MAX_NDK_VERSION,\n    RECOMMENDED_NDK_VERSION,\n    RECOMMENDED_TARGET_API,\n    MIN_NDK_API,\n    MIN_NDK_VERSION,\n    NDK_DOWNLOAD_URL,\n    ARMEABI_MAX_TARGET_API,\n    MIN_TARGET_API,\n    UNKNOWN_NDK_MESSAGE,\n    PARSE_ERROR_NDK_MESSAGE,\n    READ_ERROR_NDK_MESSAGE,\n    ENSURE_RIGHT_NDK_MESSAGE,\n    NDK_LOWER_THAN_SUPPORTED_MESSAGE,\n    UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE,\n    CURRENT_NDK_VERSION_MESSAGE,\n    RECOMMENDED_NDK_VERSION_MESSAGE,\n    TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE,\n    OLD_NDK_API_MESSAGE,\n    NEW_NDK_MESSAGE,\n    OLD_API_MESSAGE,\n    MIN_PYTHON_MAJOR_VERSION,\n    MIN_PYTHON_MINOR_VERSION,\n    PY2_ERROR_TEXT,\n    PY_VERSION_ERROR_TEXT,\n)\n\nfrom pythonforandroid.util import BuildInterruptingException\n\nrunning_in_py2 = int(py_version[0]) < 3\n\n\nclass TestRecommendations(unittest.TestCase):\n    \"\"\"\n    An inherited class of `unittest.TestCase`to test the module\n    :mod:`~pythonforandroid.recommendations`.\n    \"\"\"\n\n    def setUp(self):\n        self.ndk_dir = \"/opt/android/android-ndk\"\n\n    @unittest.skipIf(running_in_py2, \"`assertLogs` requires Python 3.4+\")\n    @mock.patch(\"pythonforandroid.recommendations.read_ndk_version\")\n    def test_check_ndk_version_greater_than_recommended(self, mock_read_ndk):\n        _version_string = f\"{MIN_NDK_VERSION + 1}.0.5232133\"\n        mock_read_ndk.return_value = packaging.version.Version(_version_string)\n        with self.assertLogs(level=\"INFO\") as cm:\n            check_ndk_version(self.ndk_dir)\n        mock_read_ndk.assert_called_once_with(self.ndk_dir)\n        self.assertEqual(\n            cm.output,\n            [\n                \"INFO:p4a:[INFO]:    {}\".format(\n                    CURRENT_NDK_VERSION_MESSAGE.format(\n                        ndk_version=MAX_NDK_VERSION + 1\n                    )\n                ),\n                \"WARNING:p4a:[WARNING]: {}\".format(\n                    RECOMMENDED_NDK_VERSION_MESSAGE.format(\n                        recommended_ndk_version=RECOMMENDED_NDK_VERSION\n                    )\n                ),\n                \"WARNING:p4a:[WARNING]: {}\".format(NEW_NDK_MESSAGE),\n            ],\n        )\n\n    @mock.patch(\"pythonforandroid.recommendations.read_ndk_version\")\n    def test_check_ndk_version_lower_than_recommended(self, mock_read_ndk):\n        _version_string = f\"{MIN_NDK_VERSION - 1}.0.5232133\"\n        mock_read_ndk.return_value = packaging.version.Version(_version_string)\n        with self.assertRaises(BuildInterruptingException) as e:\n            check_ndk_version(self.ndk_dir)\n        self.assertEqual(\n            e.exception.args[0],\n            NDK_LOWER_THAN_SUPPORTED_MESSAGE.format(\n                min_supported=MIN_NDK_VERSION, ndk_url=NDK_DOWNLOAD_URL\n            ),\n        )\n        mock_read_ndk.assert_called_once_with(self.ndk_dir)\n\n    @unittest.skipIf(running_in_py2, \"`assertLogs` requires Python 3.4+\")\n    def test_check_ndk_version_error(self):\n        \"\"\"\n        Test that a fake ndk dir give us two messages:\n            - first should be an `INFO` log\n            - second should be an `WARNING` log\n        \"\"\"\n        with self.assertLogs(level=\"INFO\") as cm:\n            check_ndk_version(self.ndk_dir)\n        self.assertEqual(\n            cm.output,\n            [\n                \"INFO:p4a:[INFO]:    {}\".format(UNKNOWN_NDK_MESSAGE),\n                \"WARNING:p4a:[WARNING]: {}\".format(\n                    READ_ERROR_NDK_MESSAGE.format(ndk_dir=self.ndk_dir)\n                ),\n                \"WARNING:p4a:[WARNING]: {}\".format(\n                    ENSURE_RIGHT_NDK_MESSAGE.format(\n                        min_supported=MIN_NDK_VERSION,\n                        rec_version=RECOMMENDED_NDK_VERSION,\n                        ndk_url=NDK_DOWNLOAD_URL,\n                    )\n                ),\n            ],\n        )\n\n    @mock.patch(\"pythonforandroid.recommendations.open\")\n    def test_read_ndk_version(self, mock_open_src_prop):\n        mock_open_src_prop.side_effect = [\n            mock.mock_open(\n                read_data=\"Pkg.Revision = 17.2.4988734\"\n            ).return_value\n        ]\n        version = read_ndk_version(self.ndk_dir)\n        mock_open_src_prop.assert_called_once_with(\n            join(self.ndk_dir, \"source.properties\")\n        )\n        assert version.major == 17\n        assert version.minor == 2\n        assert version.micro == 4988734\n\n    @unittest.skipIf(running_in_py2, \"`assertLogs` requires Python 3.4+\")\n    @mock.patch(\"pythonforandroid.recommendations.open\")\n    def test_read_ndk_version_error(self, mock_open_src_prop):\n        mock_open_src_prop.side_effect = [\n            mock.mock_open(read_data=\"\").return_value\n        ]\n        with self.assertLogs(level=\"INFO\") as cm:\n            version = read_ndk_version(self.ndk_dir)\n        self.assertEqual(\n            cm.output,\n            [\"INFO:p4a:[INFO]:    {}\".format(PARSE_ERROR_NDK_MESSAGE)],\n        )\n        mock_open_src_prop.assert_called_once_with(\n            join(self.ndk_dir, \"source.properties\")\n        )\n        assert version is None\n\n    def test_check_target_api_error_arch_armeabi(self):\n\n        with self.assertRaises(BuildInterruptingException) as e:\n            check_target_api(RECOMMENDED_TARGET_API, \"armeabi\")\n        self.assertEqual(\n            e.exception.args[0],\n            UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE.format(\n                req_ndk_api=RECOMMENDED_TARGET_API,\n                max_ndk_api=ARMEABI_MAX_TARGET_API,\n            ),\n        )\n\n    @unittest.skipIf(running_in_py2, \"`assertLogs` requires Python 3.4+\")\n    def test_check_target_api_warning_target_api(self):\n\n        with self.assertLogs(level=\"INFO\") as cm:\n            check_target_api(MIN_TARGET_API - 1, MIN_TARGET_API)\n        self.assertEqual(\n            cm.output,\n            [\n                \"WARNING:p4a:[WARNING]: Target API 29 < 30\",\n                \"WARNING:p4a:[WARNING]: {old_api_msg}\".format(\n                    old_api_msg=OLD_API_MESSAGE\n                ),\n            ],\n        )\n\n    def test_check_ndk_api_error_android_api(self):\n        \"\"\"\n        Given an `android api` greater than an `ndk_api`, we should get an\n        `BuildInterruptingException`.\n        \"\"\"\n        ndk_api = MIN_NDK_API + 1\n        android_api = MIN_NDK_API\n        with self.assertRaises(BuildInterruptingException) as e:\n            check_ndk_api(ndk_api, android_api)\n        self.assertEqual(\n            e.exception.args[0],\n            TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE.format(\n                ndk_api=ndk_api, android_api=android_api\n            ),\n        )\n\n    @unittest.skipIf(running_in_py2, \"`assertLogs` requires Python 3.4+\")\n    def test_check_ndk_api_warning_old_ndk(self):\n        \"\"\"\n        Given an `android api` lower than the supported by p4a, we should\n        get an `BuildInterruptingException`.\n        \"\"\"\n        ndk_api = MIN_NDK_API - 1\n        android_api = RECOMMENDED_TARGET_API\n        with self.assertLogs(level=\"INFO\") as cm:\n            check_ndk_api(ndk_api, android_api)\n        self.assertEqual(\n            cm.output,\n            [\n                \"WARNING:p4a:[WARNING]: {}\".format(\n                    OLD_NDK_API_MESSAGE.format(MIN_NDK_API)\n                )\n            ],\n        )\n\n    def test_check_python_version(self):\n        \"\"\"With any version info lower than the minimum, we should get a\n        BuildInterruptingException with an appropriate message.\n        \"\"\"\n        with mock.patch('sys.version_info') as fake_version_info:\n\n            # Major version is Python 2 => exception\n            fake_version_info.major = MIN_PYTHON_MAJOR_VERSION - 1\n            fake_version_info.minor = MIN_PYTHON_MINOR_VERSION\n            with self.assertRaises(BuildInterruptingException) as context:\n                check_python_version()\n            assert context.exception.message == PY2_ERROR_TEXT\n\n            # Major version too low => exception\n            # Using a float valued major version just to test the logic and avoid\n            # clashing with the Python 2 check\n            fake_version_info.major = MIN_PYTHON_MAJOR_VERSION - 0.1\n            fake_version_info.minor = MIN_PYTHON_MINOR_VERSION\n            with self.assertRaises(BuildInterruptingException) as context:\n                check_python_version()\n            assert context.exception.message == PY_VERSION_ERROR_TEXT\n\n            # Minor version too low => exception\n            fake_version_info.major = MIN_PYTHON_MAJOR_VERSION\n            fake_version_info.minor = MIN_PYTHON_MINOR_VERSION - 1\n            with self.assertRaises(BuildInterruptingException) as context:\n                check_python_version()\n            assert context.exception.message == PY_VERSION_ERROR_TEXT\n\n            # Version high enough => nothing interesting happens\n            fake_version_info.major = MIN_PYTHON_MAJOR_VERSION\n            fake_version_info.minor = MIN_PYTHON_MINOR_VERSION\n            check_python_version()\n\n    def test_print_recommendations(self):\n        \"\"\"\n        Simple test that the function actually runs.\n        \"\"\"\n        # The main failure mode is if the function tries to print a variable\n        # that doesn't actually exist, so simply running to check all the\n        # prints work is the most important test.\n        print_recommendations()\n"
  },
  {
    "path": "tests/test_toolchain.py",
    "content": "import io\nimport os\nimport sys\nimport pytest\nfrom unittest import mock\nfrom pythonforandroid.recipe import Recipe\nfrom pythonforandroid.toolchain import ToolchainCL\nfrom pythonforandroid.util import BuildInterruptingException\n\n\ndef patch_sys_argv(argv):\n    return mock.patch('sys.argv', argv)\n\n\ndef patch_argparse_print_help():\n    return mock.patch('argparse.ArgumentParser.print_help')\n\n\ndef patch_sys_stdout():\n    return mock.patch('sys.stdout', new_callable=io.StringIO)\n\n\ndef raises_system_exit():\n    return pytest.raises(SystemExit)\n\n\nclass TestToolchainCL:\n\n    def test_help(self):\n        \"\"\"\n        Calling with `--help` should print help and exit 0.\n        \"\"\"\n        argv = ['toolchain.py', '--help', '--storage-dir=/tmp']\n        with patch_sys_argv(argv), raises_system_exit(\n                ) as ex_info, patch_argparse_print_help() as m_print_help:\n            ToolchainCL()\n        assert ex_info.value.code == 0\n        assert m_print_help.call_args_list == [mock.call()]\n\n    @pytest.mark.skipif(sys.version_info < (3, 0), reason=\"requires python3\")\n    def test_unknown(self):\n        \"\"\"\n        Calling with unknown args should print help and exit 1.\n        \"\"\"\n        argv = ['toolchain.py', '--unknown']\n        with patch_sys_argv(argv), raises_system_exit(\n        ) as ex_info, patch_argparse_print_help() as m_print_help:\n            ToolchainCL()\n        assert ex_info.value.code == 1\n        assert m_print_help.call_args_list == [mock.call()]\n\n    def test_create(self):\n        \"\"\"\n        Basic `create` distribution test.\n        \"\"\"\n        argv = [\n            'toolchain.py',\n            'create',\n            '--sdk-dir=/tmp/android-sdk',\n            '--ndk-dir=/tmp/android-ndk',\n            '--bootstrap=service_only',\n            '--requirements=python3',\n            '--dist-name=test_toolchain',\n            '--activity-class-name=abc.myapp.android.CustomPythonActivity',\n            '--service-class-name=xyz.myapp.android.CustomPythonService',\n            '--arch=arm64-v8a',\n            '--arch=armeabi-v7a'\n        ]\n        with patch_sys_argv(argv), mock.patch(\n            'pythonforandroid.build.get_available_apis'\n        ) as m_get_available_apis, mock.patch(\n            'pythonforandroid.toolchain.build_recipes'\n        ) as m_build_recipes, mock.patch(\n            'pythonforandroid.bootstraps.service_only.'\n            'ServiceOnlyBootstrap.assemble_distribution'\n        ) as m_run_distribute:\n            m_get_available_apis.return_value = [33]\n            tchain = ToolchainCL()\n            assert tchain.ctx.activity_class_name == 'abc.myapp.android.CustomPythonActivity'\n            assert tchain.ctx.service_class_name == 'xyz.myapp.android.CustomPythonService'\n        assert m_get_available_apis.call_args_list in [\n            [mock.call('/tmp/android-sdk')],  # linux case\n            [mock.call('/private/tmp/android-sdk')]  # macos case\n        ]\n        build_order = [\n            'hostpython3', 'libffi', 'openssl', 'sqlite3', 'python3',\n            'genericndkbuild', 'pyjnius', 'android',\n        ]\n        python_modules = ['six']\n        context = mock.ANY\n        project_dir = None\n        assert m_build_recipes.call_args_list == [\n            mock.call(\n                build_order,\n                python_modules,\n                context,\n                project_dir,\n                ignore_project_setup_py=False\n            )\n        ]\n        assert m_run_distribute.call_args_list == [mock.call()]\n\n    @mock.patch(\n        'pythonforandroid.build.environ',\n        # Make sure that no environ variable modifies `sdk_dir`\n        {'ANDROIDSDK': None, 'ANDROID_HOME': None})\n    def test_create_no_sdk_dir(self):\n        \"\"\"\n        The `--sdk-dir` is mandatory to `create` a distribution.\n        \"\"\"\n        argv = ['toolchain.py', 'create', '--arch=arm64-v8a', '--arch=armeabi-v7a']\n        with patch_sys_argv(argv), pytest.raises(\n            BuildInterruptingException\n        ) as ex_info:\n            ToolchainCL()\n        assert ex_info.value.message == (\n            'Android SDK dir was not specified, exiting.')\n\n    @pytest.mark.skipif(sys.version_info < (3, 0), reason=\"requires python3\")\n    def test_recipes(self):\n        \"\"\"\n        Checks the `recipes` command prints out recipes information without crashing.\n        \"\"\"\n        argv = ['toolchain.py', 'recipes']\n        with patch_sys_argv(argv), patch_sys_stdout() as m_stdout:\n            ToolchainCL()\n        # check if we have common patterns in the output\n        expected_strings = (\n            'conflicts:',\n            'depends:',\n            'kivy',\n            'optional depends:',\n            'python3',\n            'sdl2',\n        )\n        for expected_string in expected_strings:\n            assert expected_string in m_stdout.getvalue()\n        # deletes static attribute to not mess with other tests\n        del Recipe.recipes\n\n    def test_local_recipes_dir(self):\n        \"\"\"\n        Checks the `local_recipes` attribute in the Context is absolute.\n        \"\"\"\n        cwd = os.path.realpath(os.getcwd())\n        common_args = [\n            'toolchain.py',\n            'recommendations',\n        ]\n\n        # Check the default ./p4a-recipes becomes absolute.\n        argv = common_args\n        with patch_sys_argv(argv):\n            toolchain = ToolchainCL()\n        expected_local_recipes = os.path.join(cwd, 'p4a-recipes')\n        assert toolchain.ctx.local_recipes == expected_local_recipes\n\n        # Check a supplied relative directory becomes absolute.\n        argv = common_args + ['--local-recipes=foo']\n        with patch_sys_argv(argv):\n            toolchain = ToolchainCL()\n        expected_local_recipes = os.path.join(cwd, 'foo')\n        assert toolchain.ctx.local_recipes == expected_local_recipes\n\n        # An absolute directory should remain unchanged.\n        local_recipes = os.path.join(cwd, 'foo')\n        argv = common_args + ['--local-recipes={}'.format(local_recipes)]\n        with patch_sys_argv(argv):\n            toolchain = ToolchainCL()\n        assert toolchain.ctx.local_recipes == local_recipes\n"
  },
  {
    "path": "tests/test_util.py",
    "content": "import os\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nimport types\nimport unittest\nfrom unittest import mock\n\nfrom pythonforandroid import util\n\n\nclass TestUtil(unittest.TestCase):\n    \"\"\"\n    An inherited class of `unittest.TestCase`to test the module\n    :mod:`~pythonforandroid.util`.\n    \"\"\"\n\n    @mock.patch(\"pythonforandroid.util.makedirs\")\n    def test_ensure_dir(self, mock_makedirs):\n        \"\"\"\n        Basic test for method :meth:`~pythonforandroid.util.ensure_dir`. Here\n        we make sure that the mentioned method is called only once.\n        \"\"\"\n        util.ensure_dir(\"fake_directory\")\n        mock_makedirs.assert_called_once_with(\"fake_directory\")\n\n    @mock.patch(\"shutil.rmtree\")\n    @mock.patch(\"pythonforandroid.util.mkdtemp\")\n    def test_temp_directory(self, mock_mkdtemp, mock_shutil_rmtree):\n\n        \"\"\"\n        Basic test for method :meth:`~pythonforandroid.util.temp_directory`. We\n        perform this test by `mocking` the command `mkdtemp` and\n        `shutil.rmtree` and we make sure that those functions are called in the\n        proper place.\n        \"\"\"\n        mock_mkdtemp.return_value = \"/temp/any_directory\"\n        with util.temp_directory():\n            mock_mkdtemp.assert_called_once()\n            mock_shutil_rmtree.assert_not_called()\n        mock_shutil_rmtree.assert_called_once_with(\"/temp/any_directory\")\n\n    @mock.patch(\"pythonforandroid.util.chdir\")\n    def test_current_directory(self, moch_chdir):\n        \"\"\"\n        Basic test for method :meth:`~pythonforandroid.util.current_directory`.\n        We `mock` chdir and we check that the command is executed once we are\n        inside a python's `with` statement. Then we check that `chdir has been\n        called with the proper arguments inside this `with` statement and also\n        that, once we leave the `with` statement, is called again with the\n        current working path.\n        \"\"\"\n        chdir_dir = \"/temp/any_directory\"\n        # test chdir to existing directory\n        with util.current_directory(chdir_dir):\n            moch_chdir.assert_called_once_with(\"/temp/any_directory\")\n        moch_chdir.assert_has_calls(\n            [mock.call(\"/temp/any_directory\"), mock.call(os.getcwd())]\n        )\n\n    def test_current_directory_exception(self):\n        \"\"\"\n        Another test for method\n        :meth:`~pythonforandroid.util.current_directory`, but here we check\n        that using the method with a non-existing-directory raises an `OSError`\n        exception.\n\n        .. note:: test chdir to non-existing directory, should raise error,\n            for py3 the exception is FileNotFoundError and IOError for py2, to\n            avoid introduce conditions, we test with a more generic exception\n        \"\"\"\n        with self.assertRaises(OSError), util.current_directory(\n            \"/fake/directory\"\n        ):\n            pass\n\n    @mock.patch(\"pythonforandroid.util.walk\")\n    def test_walk_valid_filens(self, mock_walk):\n        \"\"\"\n        Test method :meth:`~pythonforandroid.util.walk_valid_filens`\n        In here we simulate the following directory structure:\n\n        /fake_dir\n         |-- README\n         |-- setup.py\n         |-- __pycache__\n         |--     |__\n         |__Lib\n             |-- abc.pyc\n             |-- abc.py\n             |__ ctypes\n                  |-- util.pyc\n                  |-- util.py\n\n        Then we execute the method in order to check that we got the expected\n        result, which should be:\n\n        .. code-block:: python\n           :emphasize-lines: 2-4\n\n        expected_result = {\n            \"/fake_dir/README\",\n            \"/fake_dir/Lib/abc.pyc\",\n            \"/fake_dir/Lib/ctypes/util.pyc\",\n        }\n        \"\"\"\n        simulated_walk_result = [\n            [\"/fake_dir\", [\"__pycache__\", \"Lib\"], [\"README\", \"setup.py\"]],\n            [\"/fake_dir/Lib\", [\"ctypes\"], [\"abc.pyc\", \"abc.py\"]],\n            [\"/fake_dir/Lib/ctypes\", [], [\"util.pyc\", \"util.py\"]],\n        ]\n        mock_walk.return_value = simulated_walk_result\n        file_ens = util.walk_valid_filens(\n            \"/fake_dir\", [\"__pycache__\"], [\"*.py\"]\n        )\n        self.assertIsInstance(file_ens, types.GeneratorType)\n        expected_result = {\n            \"/fake_dir/README\",\n            \"/fake_dir/Lib/abc.pyc\",\n            \"/fake_dir/Lib/ctypes/util.pyc\",\n        }\n        result = set(file_ens)\n\n        self.assertEqual(result, expected_result)\n\n    def test_util_exceptions(self):\n        \"\"\"\n        Test exceptions for a couple of methods:\n\n           - method :meth:`~pythonforandroid.util.BuildInterruptingException`\n           - method :meth:`~pythonforandroid.util.handle_build_exception`\n\n        Here we create an exception with method\n        :meth:`~pythonforandroid.util.BuildInterruptingException` and we run it\n        inside method :meth:`~pythonforandroid.util.handle_build_exception` to\n        make sure that it raises an `SystemExit`.\n        \"\"\"\n        exc = util.BuildInterruptingException(\n            \"missing dependency xxx\", instructions=\"pip install --user xxx\"\n        )\n        with self.assertRaises(SystemExit):\n            util.handle_build_exception(exc)\n\n    def test_move(self):\n        with mock.patch(\n                \"pythonforandroid.util.LOGGER\"\n        ) as m_logger, TemporaryDirectory() as base_dir:\n            new_path = Path(base_dir) / \"new\"\n\n            # Set up source\n            old_path = Path(base_dir) / \"old\"\n            with open(old_path, \"w\") as outfile:\n                outfile.write(\"Temporary content\")\n\n            # Non existent source\n            with self.assertRaises(FileNotFoundError):\n                util.move(new_path, new_path)\n            m_logger.debug.assert_called()\n            m_logger.error.assert_not_called()\n            m_logger.reset_mock()\n            assert old_path.exists()\n            assert not new_path.exists()\n\n            # Successful move\n            util.move(old_path, new_path)\n            assert not old_path.exists()\n            assert new_path.exists()\n            m_logger.debug.assert_called()\n            m_logger.error.assert_not_called()\n            m_logger.reset_mock()\n\n            # Move over existing:\n            existing_path = Path(base_dir) / \"existing\"\n            existing_path.touch()\n\n            util.move(new_path, existing_path)\n            with open(existing_path, \"r\") as infile:\n                assert infile.read() == \"Temporary content\"\n            m_logger.debug.assert_called()\n            m_logger.error.assert_not_called()\n            m_logger.reset_mock()\n\n    def test_touch(self):\n        # Just checking the new file case.\n        # Assume the existing file timestamp case will work if this does.\n        with TemporaryDirectory() as base_dir:\n            new_file_path = Path(base_dir) / \"new_file\"\n            assert not new_file_path.exists()\n            util.touch(new_file_path)\n            assert new_file_path.exists()\n\n    def test_build_tools_version_sort_key(self):\n\n        build_tools_versions = [\n            \"26.0.1\",\n            \"26.0.0\",\n            \"26.0.2\",\n            \"32.0.0 rc1\",\n            \"31.0.0\",\n            \"999something\",\n        ]\n\n        expected_result = [\n            \"999something\",  # invalid version\n            \"26.0.0\",\n            \"26.0.1\",\n            \"26.0.2\",\n            \"31.0.0\",\n            \"32.0.0 rc1\",\n        ]\n\n        result = sorted(\n            build_tools_versions, key=util.build_tools_version_sort_key\n        )\n\n        self.assertEqual(result, expected_result)\n\n    def test_max_build_tool_version(self):\n\n        build_tools_versions = [\n            \"26.0.1\",\n            \"26.0.0\",\n            \"26.0.2\",\n            \"32.0.0 rc1\",\n            \"31.0.0\",\n            \"999something\",\n        ]\n\n        expected_result = \"32.0.0 rc1\"\n\n        result = util.max_build_tool_version(build_tools_versions)\n\n        self.assertEqual(result, expected_result)\n\n    def test_load_source(self):\n        \"\"\"\n        Test method :meth:`~pythonforandroid.util.load_source`.\n        We test loading a Python module from a file path using importlib.\n        \"\"\"\n        with TemporaryDirectory() as temp_dir:\n            # Create a test module file\n            test_module_path = Path(temp_dir) / \"test_module.py\"\n            with open(test_module_path, \"w\") as f:\n                f.write(\"TEST_VALUE = 42\\n\")\n                f.write(\"def test_function():\\n\")\n                f.write(\"    return 'hello'\\n\")\n\n            # Load the module\n            loaded_module = util.load_source(\"test_module\", str(test_module_path))\n\n            # Verify the module was loaded correctly\n            self.assertEqual(loaded_module.TEST_VALUE, 42)\n            self.assertEqual(loaded_module.test_function(), 'hello')\n\n    @mock.patch(\"pythonforandroid.util.exists\")\n    @mock.patch(\"shutil.rmtree\")\n    def test_rmdir_exists(self, mock_rmtree, mock_exists):\n        \"\"\"\n        Test method :meth:`~pythonforandroid.util.rmdir` when directory exists.\n        We mock exists to return True and verify rmtree is called.\n        \"\"\"\n        mock_exists.return_value = True\n        util.rmdir(\"/fake/directory\")\n        mock_rmtree.assert_called_once_with(\"/fake/directory\", False)\n\n    @mock.patch(\"pythonforandroid.util.exists\")\n    @mock.patch(\"shutil.rmtree\")\n    def test_rmdir_not_exists(self, mock_rmtree, mock_exists):\n        \"\"\"\n        Test method :meth:`~pythonforandroid.util.rmdir` when directory doesn't exist.\n        We mock exists to return False and verify rmtree is not called.\n        \"\"\"\n        mock_exists.return_value = False\n        util.rmdir(\"/fake/directory\")\n        mock_rmtree.assert_not_called()\n\n    @mock.patch(\"pythonforandroid.util.exists\")\n    @mock.patch(\"shutil.rmtree\")\n    def test_rmdir_ignore_errors(self, mock_rmtree, mock_exists):\n        \"\"\"\n        Test method :meth:`~pythonforandroid.util.rmdir` with ignore_errors flag.\n        We verify that the ignore_errors parameter is passed to rmtree.\n        \"\"\"\n        mock_exists.return_value = True\n        util.rmdir(\"/fake/directory\", ignore_errors=True)\n        mock_rmtree.assert_called_once_with(\"/fake/directory\", True)\n\n    @mock.patch(\"pythonforandroid.util.mock\")\n    def test_patch_wheel_setuptools_logging(self, mock_mock):\n        \"\"\"\n        Test method :meth:`~pythonforandroid.util.patch_wheel_setuptools_logging`.\n        We verify it returns a mock.patch object for the wheel logging module.\n        \"\"\"\n        mock_patch_obj = mock.Mock()\n        mock_mock.patch.return_value = mock_patch_obj\n\n        result = util.patch_wheel_setuptools_logging()\n\n        mock_mock.patch.assert_called_once_with(\"wheel._setuptools_logging.configure\")\n        self.assertEqual(result, mock_patch_obj)\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = pep8,py3\nbasepython = python3\n\n[testenv]\ndeps =\n    pytest\n    py3: coveralls\n# posargs will be replaced by the tox args, so you can override pytest\n# args e.g. `tox -- tests/test_graph.py`\ncommands = pytest {posargs:tests/}\npassenv = GITHUB_*\nsetenv =\n    PYTHONPATH={toxinidir}\n    SKIP_PREREQUISITES_CHECK=1\n\n[testenv:py3]\n# for py3 env we will get code coverage\ncommands =\n    coverage run --branch --source=pythonforandroid -m pytest {posargs:tests/}\n    coverage report -m\n\n[testenv:pep8]\ndeps = flake8\ncommands = flake8 pythonforandroid/ tests/ ci/ setup.py\n\n[flake8]\nignore =\n    # Closing bracket does not match indentation of opening bracket's line\n    E123,\n    # Closing bracket does not match visual indentation\n    E124,\n    # Continuation line over-indented for hanging indent\n    E126,\n    # Missing whitespace around arithmetic operator\n    E226,\n    # Module level import not at top of file\n    E402,\n    # Line too long (82 > 79 characters)\n    E501,\n    # Line break occurred before a binary operator\n    W503,\n    # Line break occurred after a binary operator\n    W504\n"
  }
]