diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 000000000..448f17445
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,11 @@
+[run]
+relative_files = True
+omit =
+ *test*
+
+[report]
+exclude_lines =
+ pragma: no cover
+ def __repr__
+ raise NotImplementedError
+ if __name__ == .__main__.:
\ No newline at end of file
diff --git a/.deepsource.toml b/.deepsource.toml
new file mode 100644
index 000000000..e30c701da
--- /dev/null
+++ b/.deepsource.toml
@@ -0,0 +1,12 @@
+version = 1
+
+test_patterns = ["tests/**"]
+
+exclude_patterns = ["testapps/**"]
+
+[[analyzers]]
+name = "python"
+enabled = true
+
+ [analyzers.meta]
+ runtime_version = "3.x.x"
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..23c0508ea
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+venv/
+.buildozer/
+**/.pytest_cache/
+.tox/
+bin/
+*.pyc
+**/__pycache__
+*.egg-info/
diff --git a/.env b/.env
new file mode 100644
index 000000000..3e65e73c3
--- /dev/null
+++ b/.env
@@ -0,0 +1,10 @@
+# used by coveralls.io, refs:
+# https://coveralls-python.readthedocs.io/en/latest/usage/tox.html#github-actions
+CI
+GITHUB_ACTIONS
+GITHUB_REF
+GITHUB_SHA
+GITHUB_HEAD_REF
+GITHUB_REPOSITORY
+GITHUB_RUN_ID
+GITHUB_TOKEN
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..fd61593e8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,45 @@
+<!--
+The issue tracker is a tool to address bugs NOT a support platform.
+Please use the Discord community or Stack Overflow for support questions,
+more information at https://github.com/kivy/python-for-android#support
+-->
+
+### Checklist
+
+- [ ] the issue is indeed a bug and not a support request
+- [ ] issue doesn't already exist: https://github.com/kivy/python-for-android/issues
+- [ ] I have a short, runnable example that reproduces the issue
+- [ ] I reproduced the problem with the latest development version (`p4a.branch = develop`)
+- [ ] I used the grave accent (aka backticks) to format code or logs when appropriated
+
+### Versions
+
+- Python:
+- OS:
+- Kivy:
+- Cython:
+- OpenJDK:
+
+### Description
+
+// REPLACE ME: What are you trying to get done, what has happened, what went wrong, and what did you expect?
+
+### buildozer.spec
+
+Command:
+```sh
+// REPLACE ME: buildozer command ran? e.g. buildozer android debug
+// Keep the triple grave accent (aka backquote/backtick) to have the code formatted
+```
+
+Spec file:
+```
+// REPLACE ME: Paste your buildozer.spec file here
+```
+
+### Logs
+
+```
+// REPLACE ME: Paste the build output containing the error
+// Keep the triple grave accent (a.k.a. backquote/backtick) to have the code formatted
+```
diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml
new file mode 100644
index 000000000..343d34456
--- /dev/null
+++ b/.github/workflows/no-response.yml
@@ -0,0 +1,32 @@
+name: No Response
+
+# Both `issue_comment` and `scheduled` event types are required for this Action
+# to work properly.
+on:
+ issue_comment:
+ types: [created]
+ schedule:
+ # Schedule for an arbitrary time (5am) once every day
+ - cron: '* 5 * * *'
+
+jobs:
+ noResponse:
+ # Don't run if in a fork
+ if: github.repository_owner == 'kivy'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: lee-dohm/no-response@9bb0a4b5e6a45046f00353d5de7d90fb8bd773bb
+ # This commit hash targets release v0.5.0 of lee-dohm/no-response.
+ # Targeting a commit hash instead of a tag has been done for security reasons.
+ # Please be aware that the commit hash specifically targets the "Automatic compilation"
+ # done by `github-actions[bot]` as the `no-response` Action needs to be compiled.
+ with:
+ token: ${{ github.token }}
+ daysUntilClose: 42
+ responseRequiredLabel: 'awaiting-reply'
+ closeComment: >
+ This issue has been automatically closed because there has been no response
+ to our request for more information from the original author. With only the
+ information that is currently in the issue, we don't have the means
+ to take action. Please reach out if you have or find the answers we need so
+ that we can investigate further.
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
new file mode 100644
index 000000000..4eff5d0f6
--- /dev/null
+++ b/.github/workflows/push.yml
@@ -0,0 +1,256 @@
+name: Unit tests & build apps
+
+on: ['push', 'pull_request']
+
+env:
+ APK_ARTIFACT_FILENAME: bdist_unit_tests_app-debug-1.1.apk
+ AAB_ARTIFACT_FILENAME: bdist_unit_tests_app-release-1.1.aab
+ AAR_ARTIFACT_FILENAME: bdist_unit_tests_app-release-1.1.aar
+ PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE: 0
+
+jobs:
+
+ flake8:
+ name: Flake8 tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout python-for-android
+ uses: actions/checkout@v4
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.x
+ - name: Run flake8
+ run: |
+ python -m pip install --upgrade pip
+ pip install tox>=2.0
+ tox -e pep8
+
+ test:
+ name: Pytest [Python ${{ matrix.python-version }} | ${{ matrix.os }}]
+ needs: flake8
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ python-version: ['3.8', '3.9', '3.10', '3.11']
+ os: [ubuntu-latest, macOs-latest]
+ steps:
+ - name: Checkout python-for-android
+ uses: actions/checkout@v4
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Tox tests
+ run: |
+ python -m pip install --upgrade pip
+ pip install tox>=2.0
+ make test
+ - name: Coveralls
+ uses: AndreMiras/coveralls-python-action@develop
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ with:
+ parallel: true
+ flag-name: run-${{ matrix.os }}-${{ matrix.python-version }}
+
+ ubuntu_build:
+ name: Build test APP [ ${{ matrix.runs_on }} | ${{ matrix.bootstrap.name }} ]
+ needs: [flake8]
+ runs-on: ${{ matrix.runs_on }}
+ continue-on-error: true
+ strategy:
+ matrix:
+ runs_on: [ubuntu-latest]
+ bootstrap:
+ - name: sdl2
+ target: testapps-with-numpy
+ - name: sdl2_scipy
+ target: testapps-with-scipy
+ - name: webview
+ target: testapps-webview
+ - name: service_library
+ target: testapps-service_library-aar
+ - name: qt
+ target: testapps-qt
+ steps:
+ - name: Checkout python-for-android
+ uses: actions/checkout@v4
+ - name: Build python-for-android docker image
+ run: |
+ docker build --tag=kivy/python-for-android .
+ - name: Build multi-arch ${{ matrix.bootstrap.target }} artifact with docker
+ run: |
+ docker run --name p4a-latest kivy/python-for-android make ${{ matrix.bootstrap.target }}
+ - name: Copy produced artifacts from docker container (*.apk, *.aab)
+ if: matrix.bootstrap.name != 'service_library'
+ run: |
+ mkdir -p dist
+ docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.APK_ARTIFACT_FILENAME }} dist/
+ docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.AAB_ARTIFACT_FILENAME }} dist/
+ - name: Copy produced artifacts from docker container (*.aar)
+ if: matrix.bootstrap.name == 'service_library'
+ run: |
+ mkdir -p dist
+ docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/${{ env.AAR_ARTIFACT_FILENAME }} dist/
+ - name: Rename artifacts to include the build platform name (*.apk, *.aab, *.aar)
+ run: |
+ 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
+ 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
+ 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
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-artifacts
+ path: dist
+
+ macos_build:
+ name: Build test APP [ ${{ matrix.runs_on }} | ${{ matrix.bootstrap.name }} ]
+ needs: [flake8]
+ runs-on: ${{ matrix.runs_on }}
+ continue-on-error: true
+ strategy:
+ matrix:
+ runs_on: [macos-latest, apple-silicon-m1]
+ bootstrap:
+ - name: sdl2
+ target: testapps-with-numpy
+ - name: webview
+ target: testapps-webview
+ env:
+ ANDROID_HOME: ${HOME}/.android
+ ANDROID_SDK_ROOT: ${HOME}/.android/android-sdk
+ ANDROID_SDK_HOME: ${HOME}/.android/android-sdk
+ ANDROID_NDK_HOME: ${HOME}/.android/android-ndk
+ steps:
+ - name: Checkout python-for-android
+ uses: actions/checkout@v4
+ - name: Install python-for-android
+ run: |
+ source ci/osx_ci.sh
+ arm64_set_path_and_python_version 3.9.7
+ python3 -m pip install -e .
+ - name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
+ run: |
+ source ci/osx_ci.sh
+ arm64_set_path_and_python_version 3.9.7
+ python3 pythonforandroid/prerequisites.py
+ - name: Install dependencies (Legacy)
+ run: |
+ source ci/osx_ci.sh
+ arm64_set_path_and_python_version 3.9.7
+ make --file ci/makefiles/osx.mk
+ - name: Build multi-arch apk Python 3 (armeabi-v7a, arm64-v8a, x86_64, x86)
+ run: |
+ source ci/osx_ci.sh
+ arm64_set_path_and_python_version 3.9.7
+ make ${{ matrix.bootstrap.target }}
+ - name: Copy produced artifacts into dist/ (*.apk, *.aab)
+ run: |
+ mkdir -p dist
+ cp testapps/on_device_unit_tests/*.apk dist/
+ cp testapps/on_device_unit_tests/*.aab dist/
+ ls -l dist/
+ - name: Rename artifacts to include the build platform name (*.apk, *.aab)
+ run: |
+ 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
+ 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
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-artifacts
+ path: dist
+
+ ubuntu_rebuild_updated_recipes:
+ name: Test updated recipes for arch ${{ matrix.android_arch }} [ ubuntu-latest ]
+ needs: [flake8]
+ runs-on: ubuntu-latest
+ continue-on-error: true
+ strategy:
+ matrix:
+ android_arch: ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]
+ env:
+ REBUILD_UPDATED_RECIPES_EXTRA_ARGS: --arch=${{ matrix.android_arch }}
+ steps:
+ - name: Checkout python-for-android (all-history)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ # helps with GitHub runner getting out of space
+ - name: Free disk space
+ run: |
+ df -h
+ sudo swapoff -a
+ sudo rm -f /swapfile
+ sudo apt -y clean
+ docker rmi $(docker image ls -aq)
+ df -h
+ - name: Pull docker image
+ run: |
+ make docker/pull
+ - name: Rebuild updated recipes
+ run: |
+ make docker/run/make/rebuild_updated_recipes
+
+ macos_rebuild_updated_recipes:
+ name: Test updated recipes for arch ${{ matrix.android_arch }} [ ${{ matrix.runs_on }} ]
+ needs: [flake8]
+ runs-on: ${{ matrix.runs_on }}
+ continue-on-error: true
+ strategy:
+ matrix:
+ android_arch: ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]
+ runs_on: [macos-latest, apple-silicon-m1]
+ env:
+ ANDROID_HOME: ${HOME}/.android
+ ANDROID_SDK_ROOT: ${HOME}/.android/android-sdk
+ ANDROID_SDK_HOME: ${HOME}/.android/android-sdk
+ ANDROID_NDK_HOME: ${HOME}/.android/android-ndk
+ REBUILD_UPDATED_RECIPES_EXTRA_ARGS: --arch=${{ matrix.android_arch }}
+ steps:
+ - name: Checkout python-for-android (all-history)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Install python-for-android
+ run: |
+ source ci/osx_ci.sh
+ arm64_set_path_and_python_version 3.9.7
+ python3 -m pip install -e .
+ - name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
+ run: |
+ source ci/osx_ci.sh
+ arm64_set_path_and_python_version 3.9.7
+ python3 pythonforandroid/prerequisites.py
+ - name: Install dependencies (Legacy)
+ run: |
+ source ci/osx_ci.sh
+ arm64_set_path_and_python_version 3.9.7
+ make --file ci/makefiles/osx.mk
+ - name: Rebuild updated recipes
+ run: |
+ source ci/osx_ci.sh
+ arm64_set_path_and_python_version 3.9.7
+ make rebuild_updated_recipes
+
+ coveralls_finish:
+ needs: test
+ runs-on: ubuntu-latest
+ steps:
+ - name: Coveralls Finished
+ uses: AndreMiras/coveralls-python-action@develop
+ with:
+ parallel-finished: true
+
+ documentation:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Requirements
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r doc/requirements.txt
+ - name: Check links
+ run: sphinx-build -b linkcheck doc/source doc/build
+ - name: Generate documentation
+ run: sphinx-build doc/source doc/build
+
diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml
new file mode 100644
index 000000000..a66a30567
--- /dev/null
+++ b/.github/workflows/pypi-release.yml
@@ -0,0 +1,25 @@
+name: PyPI release
+on: [push]
+
+jobs:
+ pypi_release:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.x'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade setuptools wheel twine
+ - name: Build
+ run: |
+ python setup.py sdist bdist_wheel
+ twine check dist/*
+ - name: Publish package
+ if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+ uses: pypa/gh-action-pypi-publish@v1.4.2
+ with:
+ user: __token__
+ password: ${{ secrets.pypi_password }}
\ No newline at end of file
diff --git a/.github/workflows/support.yml b/.github/workflows/support.yml
new file mode 100644
index 000000000..a0693e763
--- /dev/null
+++ b/.github/workflows/support.yml
@@ -0,0 +1,45 @@
+# When a user creates an issue that is actually a support request, it should
+# be closed with a friendly comment.
+#
+# This triggers on an issue being labelled with the `support` tag.
+
+name: 'Support Requests'
+
+on:
+ issues:
+ types: [labeled, unlabeled, reopened]
+
+permissions:
+ issues: write
+
+jobs:
+ action:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/support-requests@v4
+ with:
+ github-token: ${{ github.token }}
+ support-label: 'support'
+ issue-comment: >
+ 👋 @{issue-author},
+
+ 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.
+
+ 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.
+
+ However, this issue appears to be a support request. Please use our
+ [support channels](https://github.com/kivy/python-for-android/blob/master/CONTACT.md)
+ to get help with the project.
+
+ If you're having trouble installing python-for-android,
+ please see our [quickstart](https://python-for-android.readthedocs.io/en/latest/quickstart) guide.
+
+ If you're having trouble using python-for-android,
+ please see our [troubleshooting guide](https://python-for-android.readthedocs.io/en/latest/troubleshooting)
+ and [FAQ](https://github.com/kivy/python-for-android/blob/master/FAQ.md).
+
+ Let us know if this comment was made in error, and we'll be happy
+ to reopen the issue.
+
+ close-issue: true
+ lock-issue: false
diff --git a/.gitignore b/.gitignore
index 99acffaeb..f36a2f357 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,23 @@ python_for_android.egg-info
/build/
doc/build
__pycache__/
+venv/
#idea/pycharm
.idea/
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+coverage.xml
+*.cover
+.pytest_cache/
+
+# testapp's build folder
+testapps/build/
+
+# Dolphin (the KDE file manager autogenerates the file `.directory`)
+.directory
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 000000000..e3f99e76b
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,16 @@
+# Read the Docs configuration file for Sphinx projects
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+version: 2
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3"
+
+python:
+ install:
+ - requirements: doc/requirements.txt
+
+sphinx:
+ configuration: doc/source/conf.py
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..5686bcbb8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,2211 @@
+# Changelog
+
+## [v2024.01.21](https://github.com/kivy/python-for-android/tree/v2024.01.21)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2023.09.16...v2024.01.21)
+
+**Fixed bugs:**
+
+- Update documentation copyright [\#2921](https://github.com/kivy/python-for-android/issues/2921)
+- Support mail address is broken [\#2899](https://github.com/kivy/python-for-android/issues/2899)
+- doc/macos/jdk: invalid brew install command provided. [\#2896](https://github.com/kivy/python-for-android/issues/2896)
+- pyzmq recipe build fail [\#2818](https://github.com/kivy/python-for-android/issues/2818)
+- Existing distribution not detected due to pip package casing mismatch [\#2494](https://github.com/kivy/python-for-android/issues/2494)
+- unknown argument "fp-model" and strict is not a directory or a file [\#2359](https://github.com/kivy/python-for-android/issues/2359)
+- Copy past is not working on kivy mobile app [\#2270](https://github.com/kivy/python-for-android/issues/2270)
+- Flaky test failure in blacklist\(?\) - investigation needed [\#1781](https://github.com/kivy/python-for-android/issues/1781)
+- Problem with loding gevent: BadZipfile: File is not a zip file [\#1739](https://github.com/kivy/python-for-android/issues/1739)
+- ImportError when importing files containing \N{name} escape sequence [\#1060](https://github.com/kivy/python-for-android/issues/1060)
+- Error with permission specification via setup.cfg [\#985](https://github.com/kivy/python-for-android/issues/985)
+
+**Closed issues:**
+
+- Build failed: Could not find `android` or `sdkmanager` binaries in Android SDK [\#2956](https://github.com/kivy/python-for-android/issues/2956)
+- Libffi - configure: error: C compiler cannot create executables \(WSL 2\) [\#2953](https://github.com/kivy/python-for-android/issues/2953)
+- G [\#2951](https://github.com/kivy/python-for-android/issues/2951)
+- Hh [\#2949](https://github.com/kivy/python-for-android/issues/2949)
+- Can't build for Android on macOS on M2 [\#2947](https://github.com/kivy/python-for-android/issues/2947)
+- BroadcastReceiver does not invoke the callback [\#2946](https://github.com/kivy/python-for-android/issues/2946)
+- Add pdf2docx library recipe [\#2941](https://github.com/kivy/python-for-android/issues/2941)
+- use build aar in kotlin app ,can't load /lib/arm64/libpybundle.so file [\#2940](https://github.com/kivy/python-for-android/issues/2940)
+- Feature Request: Pymssql [\#2936](https://github.com/kivy/python-for-android/issues/2936)
+- LXML v4.8.0 fails to build. [\#2928](https://github.com/kivy/python-for-android/issues/2928)
+- Tryin to apply a plugin fails [\#2926](https://github.com/kivy/python-for-android/issues/2926)
+- ModuleNotFoundError: No module named '\_sysconfigdata\_\_darwin\_darwin' [\#2925](https://github.com/kivy/python-for-android/issues/2925)
+- ReadTheDocs version is unclear. [\#2920](https://github.com/kivy/python-for-android/issues/2920)
+- How to get real file path from uri [\#2911](https://github.com/kivy/python-for-android/issues/2911)
+- And [\#2910](https://github.com/kivy/python-for-android/issues/2910)
+- ModuleNotFoundError: No module named 'backports'
+ [\#2909](https://github.com/kivy/python-for-android/issues/2909)
+- not able to acess files unless connected to adb once [\#2907](https://github.com/kivy/python-for-android/issues/2907)
+- opening files in other apps [\#2906](https://github.com/kivy/python-for-android/issues/2906)
+- ImportError: dlopen failed: cannot locate symbol "\_ZTVSt9bad\_alloc" [\#2903](https://github.com/kivy/python-for-android/issues/2903)
+- Fails to build pyjnius [\#2902](https://github.com/kivy/python-for-android/issues/2902)
+- Kivy app crashes on startup [\#2895](https://github.com/kivy/python-for-android/issues/2895)
+- aar file does not import properly in version v2023.09.16 [\#2894](https://github.com/kivy/python-for-android/issues/2894)
+- App is crashing with Pyrebase4 [\#2893](https://github.com/kivy/python-for-android/issues/2893)
+- shared libs builds with 32 bit arch instaead of 64 bit [\#2888](https://github.com/kivy/python-for-android/issues/2888)
+- liblzma download error [\#2885](https://github.com/kivy/python-for-android/issues/2885)
+- Misconfiguration causing failure in compilation. [\#2879](https://github.com/kivy/python-for-android/issues/2879)
+- cygrpc.so is for EM\_X86\_64 \(62\) instead of EM\_AARCH64 \(183\) [\#2853](https://github.com/kivy/python-for-android/issues/2853)
+- Are you able to build cffi==1.15.1? [\#2847](https://github.com/kivy/python-for-android/issues/2847)
+- java.lang.IllegalStateException [\#2844](https://github.com/kivy/python-for-android/issues/2844)
+- \[BUG\]: ctypes: AttributeError: undefined symbol: PyCapsule\_New [\#2840](https://github.com/kivy/python-for-android/issues/2840)
+- kivy cant load image in requesturl android [\#2832](https://github.com/kivy/python-for-android/issues/2832)
+- Feature Request: Add Python `3.11` support [\#2798](https://github.com/kivy/python-for-android/issues/2798)
+- Error Build APK FIle using Flask [\#2783](https://github.com/kivy/python-for-android/issues/2783)
+- 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)
+- Kivy python Error loading video on some android device [\#2780](https://github.com/kivy/python-for-android/issues/2780)
+- buildozer/p4a.prerequisites: enable automation build with no questions asked. [\#2778](https://github.com/kivy/python-for-android/issues/2778)
+- \_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)
+- Background service implemented using Pyjnius does not auto-restart on Kivy APK close [\#2772](https://github.com/kivy/python-for-android/issues/2772)
+- \[JVM\]: FLAG\_IMMUTABLE or FLAG\_MUTABLE is required when a PendingIntent is created [\#2759](https://github.com/kivy/python-for-android/issues/2759)
+- there is an issue with playing video from URL on the latest p4a releases [\#2744](https://github.com/kivy/python-for-android/issues/2744)
+- App crahes at launch on specific devices \(\[libpython3.9.so\] \_PyEval\_EvalFrameDefault\) \(Adreno 730?\) [\#2723](https://github.com/kivy/python-for-android/issues/2723)
+- Pandas giving error in Buildozer [\#2719](https://github.com/kivy/python-for-android/issues/2719)
+- buildozer -v android debug [\#2711](https://github.com/kivy/python-for-android/issues/2711)
+- \[proposed feature-request\] Lacking psutil recipe [\#2707](https://github.com/kivy/python-for-android/issues/2707)
+- \[ERROR\]: Build failed: Asked to compile for no Archs, so failing. [\#2685](https://github.com/kivy/python-for-android/issues/2685)
+- 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)
+- `shutil.copy()` fails on external removable storage devices [\#2589](https://github.com/kivy/python-for-android/issues/2589)
+- jnius can't find class "org.kivy.android.PythonActivity" with webview [\#2533](https://github.com/kivy/python-for-android/issues/2533)
+- \[MACOS\] Android app crashes on start when using macos to build [\#2519](https://github.com/kivy/python-for-android/issues/2519)
+- Pillow-SIMD recipe? [\#2420](https://github.com/kivy/python-for-android/issues/2420)
+- --asset & directories [\#2413](https://github.com/kivy/python-for-android/issues/2413)
+- dlopen failed: cannot locate symbol "\_\_register\_atfork" on Android 5.0 [\#2410](https://github.com/kivy/python-for-android/issues/2410)
+- dlib module not found error [\#2395](https://github.com/kivy/python-for-android/issues/2395)
+- lxml build failed for x86 arch [\#2369](https://github.com/kivy/python-for-android/issues/2369)
+- Android 10 storage permission denied [\#2364](https://github.com/kivy/python-for-android/issues/2364)
+- for pytorch [\#2353](https://github.com/kivy/python-for-android/issues/2353)
+- Problem with ffmpeg on Android [\#2345](https://github.com/kivy/python-for-android/issues/2345)
+- NLTK recipe for python for android [\#2320](https://github.com/kivy/python-for-android/issues/2320)
+- 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)
+- verify downloads using sha256? [\#2294](https://github.com/kivy/python-for-android/issues/2294)
+- outdated recipes [\#2277](https://github.com/kivy/python-for-android/issues/2277)
+- Custom recipe for scipy fails with permission issue [\#2267](https://github.com/kivy/python-for-android/issues/2267)
+- Kivy application generated crashes instantly with dlopen failed [\#2266](https://github.com/kivy/python-for-android/issues/2266)
+- EGLlib: validate\_display: 92 error 3008 \(EGL\_BAD\_DISPLAY\) : App crashes immediately \(kivymd\) \(Buildozer\) [\#2258](https://github.com/kivy/python-for-android/issues/2258)
+- libEGL : EGLNativeWindowType disconnect failed [\#2253](https://github.com/kivy/python-for-android/issues/2253)
+- Hao to support multiprocess Queue in Android [\#2249](https://github.com/kivy/python-for-android/issues/2249)
+- autoclass: Class only found when called in specific places? [\#2242](https://github.com/kivy/python-for-android/issues/2242)
+- the app crach in time of import psycopg2 [\#2240](https://github.com/kivy/python-for-android/issues/2240)
+- env must be a dict [\#2170](https://github.com/kivy/python-for-android/issues/2170)
+- Pandas doesn't work [\#2157](https://github.com/kivy/python-for-android/issues/2157)
+- Webview bootstrap can't find 'org.jnius.NativeInvocationHandler'. [\#2140](https://github.com/kivy/python-for-android/issues/2140)
+- clang++: error: linker command failed with exit code 1 [\#2082](https://github.com/kivy/python-for-android/issues/2082)
+- ModuleNotFoundError: No module named 'setuptools' [\#2078](https://github.com/kivy/python-for-android/issues/2078)
+- Scraping web pages with javascript [\#2052](https://github.com/kivy/python-for-android/issues/2052)
+- open webbrowser regsiter\(\) error [\#2047](https://github.com/kivy/python-for-android/issues/2047)
+- Missing javaclass when using able with previously working recipe [\#2041](https://github.com/kivy/python-for-android/issues/2041)
+- :Class not found b'org/kivy/android/PythonActivity$ActivityResultListener' [\#2039](https://github.com/kivy/python-for-android/issues/2039)
+- App\(using socket and opencv\) crash on opening [\#2038](https://github.com/kivy/python-for-android/issues/2038)
+- android apk is crashing after displaying splash screen on phone [\#2030](https://github.com/kivy/python-for-android/issues/2030)
+- Leverage Docker image caching [\#2009](https://github.com/kivy/python-for-android/issues/2009)
+- entrypoint confusion with python3 [\#1999](https://github.com/kivy/python-for-android/issues/1999)
+- Android app crash on opening - Python Initialize [\#1987](https://github.com/kivy/python-for-android/issues/1987)
+- Error building APK: "Missing 'name' key attribute on element activity at AndroidManifest.xml" [\#1979](https://github.com/kivy/python-for-android/issues/1979)
+- Ugent issues on Webview \(Android Back Button to main App\) [\#1961](https://github.com/kivy/python-for-android/issues/1961)
+- JavaException: JVM exception occurred: Fail to connect to camera service [\#1943](https://github.com/kivy/python-for-android/issues/1943)
+- Python version number must have subversion? cannot find Python-3.7.tgz [\#1941](https://github.com/kivy/python-for-android/issues/1941)
+- dlopen failed: jnius.so is for EM\_ARM \(40\) instead of EM\_386 \(3\) [\#1927](https://github.com/kivy/python-for-android/issues/1927)
+- Matplotlib recipe depends on local environment [\#1900](https://github.com/kivy/python-for-android/issues/1900)
+- main window jumps up and down [\#1876](https://github.com/kivy/python-for-android/issues/1876)
+- ctypes.pythonapi issues; getting AttributeError: undefined symbol [\#1866](https://github.com/kivy/python-for-android/issues/1866)
+- \[enhancement\] do not rebuild already built packages [\#1860](https://github.com/kivy/python-for-android/issues/1860)
+- Matplotlib recipe fails sometimes [\#1859](https://github.com/kivy/python-for-android/issues/1859)
+- p4a build with NDK r18b: clang: error: unknown argument: '-mandroid' [\#1853](https://github.com/kivy/python-for-android/issues/1853)
+- Activity lifecycle issues. after onDestroy, application will become unusable [\#1844](https://github.com/kivy/python-for-android/issues/1844)
+- Service AutoRestart did not work [\#1823](https://github.com/kivy/python-for-android/issues/1823)
+- Android debug results in error involving clang++ and linker. [\#1796](https://github.com/kivy/python-for-android/issues/1796)
+- seek\(\) method on a file object doesn't use right arguments [\#1768](https://github.com/kivy/python-for-android/issues/1768)
+- Same issue w/ -lpython2.7 not found, workaround [\#1753](https://github.com/kivy/python-for-android/issues/1753)
+- Several issues when installing packages via pip [\#1745](https://github.com/kivy/python-for-android/issues/1745)
+- Publish a new Kivy Launcher for Python 3 [\#1638](https://github.com/kivy/python-for-android/issues/1638)
+- Travis conditional boostrap build support [\#1588](https://github.com/kivy/python-for-android/issues/1588)
+- Error when execute APK only on device: ImportError: cannot import name \_htmlparser [\#1523](https://github.com/kivy/python-for-android/issues/1523)
+- onSensorChanged continuosly called during app execution [\#1498](https://github.com/kivy/python-for-android/issues/1498)
+- GC deadlock on subprocess [\#1461](https://github.com/kivy/python-for-android/issues/1461)
+- Code runs on old pygame backend but not on SDL2 [\#1411](https://github.com/kivy/python-for-android/issues/1411)
+- build-tools below 25 will not add jars [\#1345](https://github.com/kivy/python-for-android/issues/1345)
+- Flaky continuous integration [\#1306](https://github.com/kivy/python-for-android/issues/1306)
+- Icon/Logo Proposal [\#1264](https://github.com/kivy/python-for-android/issues/1264)
+- Unable to write the config [\#1151](https://github.com/kivy/python-for-android/issues/1151)
+- p4a does not yet work with clang [\#1097](https://github.com/kivy/python-for-android/issues/1097)
+- android module seems to eat up a character from java properties [\#945](https://github.com/kivy/python-for-android/issues/945)
+- TypeError: a bytes-like object is required, not 'str' [\#856](https://github.com/kivy/python-for-android/issues/856)
+- Feature request: access to all permissions [\#843](https://github.com/kivy/python-for-android/issues/843)
+- Extending the launcher [\#565](https://github.com/kivy/python-for-android/issues/565)
+
+**Merged pull requests:**
+
+- Update OpenSSL version to `1.1.1w` [\#2958](https://github.com/kivy/python-for-android/pull/2958) ([prolenodev](https://github.com/prolenodev))
+- Bump Kivy version to `2.3.0` [\#2952](https://github.com/kivy/python-for-android/pull/2952) ([misl6](https://github.com/misl6))
+- `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))
+- Remove redundant append into WHITELIST\_PATTERNS [\#2935](https://github.com/kivy/python-for-android/pull/2935) ([shyamnathp](https://github.com/shyamnathp))
+- 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))
+- Update `python-for-android` prerequisites \(`Dockerfile`, `prerequisites.py`, docs\) [\#2923](https://github.com/kivy/python-for-android/pull/2923) ([misl6](https://github.com/misl6))
+- Update Contributing Guidelines and Readme [\#2922](https://github.com/kivy/python-for-android/pull/2922) ([Julian-O](https://github.com/Julian-O))
+- Initial support for PySide6 and Qt [\#2918](https://github.com/kivy/python-for-android/pull/2918) ([shyamnathp](https://github.com/shyamnathp))
+- Introduce FAQ [\#2917](https://github.com/kivy/python-for-android/pull/2917) ([Julian-O](https://github.com/Julian-O))
+- 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))
+- enable json1 extenstion in sqlite3 [\#2915](https://github.com/kivy/python-for-android/pull/2915) ([HyTurtle](https://github.com/HyTurtle))
+- Bump `pyjnius` version to `1.6.1` [\#2914](https://github.com/kivy/python-for-android/pull/2914) ([misl6](https://github.com/misl6))
+- 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))
+- Update Lottie player version [\#2900](https://github.com/kivy/python-for-android/pull/2900) ([HugoDaniel](https://github.com/HugoDaniel))
+- Merge master into develop [\#2892](https://github.com/kivy/python-for-android/pull/2892) ([misl6](https://github.com/misl6))
+- Add doc tests, make them pass. [\#2890](https://github.com/kivy/python-for-android/pull/2890) ([Julian-O](https://github.com/Julian-O))
+- 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))
+- 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))
+
+
+## [v2023.09.16](https://github.com/kivy/python-for-android/tree/v2023.09.16)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2023.05.21...v2023.09.16)
+
+**Closed issues:**
+
+- Microphone And Audio permissions doesn't work [\#2889](https://github.com/kivy/python-for-android/issues/2889)
+- Error with Scipy [\#2883](https://github.com/kivy/python-for-android/issues/2883)
+- Download failed \( Downloading sqlite3 from https://www.sqlite.org/2021/sqlite-amalgamation-3350500.zip \) during buildozer -v andriod debug [\#2881](https://github.com/kivy/python-for-android/issues/2881)
+- ONNXruntime lib open failed due to 64-bit [\#2880](https://github.com/kivy/python-for-android/issues/2880)
+- 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)
+- Impossible to install python for android [\#2867](https://github.com/kivy/python-for-android/issues/2867)
+- scipy with kivy [\#2861](https://github.com/kivy/python-for-android/issues/2861)
+- False positve parser warning. [\#2856](https://github.com/kivy/python-for-android/issues/2856)
+- After successfully building app, its crashes with this error, using firebase-admin [\#2854](https://github.com/kivy/python-for-android/issues/2854)
+- Kivy [\#2837](https://github.com/kivy/python-for-android/issues/2837)
+- not installing on windows 10 [\#2836](https://github.com/kivy/python-for-android/issues/2836)
+- Could not find com.android.tools.build:gradle:7.2.0. in android studio [\#2834](https://github.com/kivy/python-for-android/issues/2834)
+- vlc recipe build fail [\#2822](https://github.com/kivy/python-for-android/issues/2822)
+- mysqldb recipe build fail [\#2813](https://github.com/kivy/python-for-android/issues/2813)
+- babel recipe build fail [\#2803](https://github.com/kivy/python-for-android/issues/2803)
+- Python 3.10 cffi build fails [\#2799](https://github.com/kivy/python-for-android/issues/2799)
+- \[Recipe request\] OpenColorIO & rawpy \(libraw\) [\#2789](https://github.com/kivy/python-for-android/issues/2789)
+- Longer app load time, bigger apk size and unnecessary logs [\#2704](https://github.com/kivy/python-for-android/issues/2704)
+- -fp-model argument not found, directory strict not found [\#2329](https://github.com/kivy/python-for-android/issues/2329)
+- Kivy crashes on Android: ImportError: dlopen failed: library "libpython3.7m.so" not found [\#2237](https://github.com/kivy/python-for-android/issues/2237)
+- pyconfig.h issue [\#2074](https://github.com/kivy/python-for-android/issues/2074)
+
+**Merged pull requests:**
+
+- 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))
+- Standardise on move [\#2884](https://github.com/kivy/python-for-android/pull/2884) ([Julian-O](https://github.com/Julian-O))
+- Remove deprecated FlatDir in Gradle template [\#2876](https://github.com/kivy/python-for-android/pull/2876) ([Julian-O](https://github.com/Julian-O))
+- :rotating\_light: linter fixes [\#2874](https://github.com/kivy/python-for-android/pull/2874) ([AndreMiras](https://github.com/AndreMiras))
+- Standardise ensure\_dir and rmdir [\#2871](https://github.com/kivy/python-for-android/pull/2871) ([Julian-O](https://github.com/Julian-O))
+- Correct check for --sdk option [\#2870](https://github.com/kivy/python-for-android/pull/2870) ([Julian-O](https://github.com/Julian-O))
+- Python versions: Update documentation & CI testing [\#2869](https://github.com/kivy/python-for-android/pull/2869) ([Julian-O](https://github.com/Julian-O))
+- Patching cleanup [\#2868](https://github.com/kivy/python-for-android/pull/2868) ([Julian-O](https://github.com/Julian-O))
+- 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))
+- `build_platform` should be all-lowercase [\#2864](https://github.com/kivy/python-for-android/pull/2864) ([misl6](https://github.com/misl6))
+- Fix simple typos in comments [\#2863](https://github.com/kivy/python-for-android/pull/2863) ([Julian-O](https://github.com/Julian-O))
+- 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))
+- 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))
+- Docs: Fix code string - quickstart.rst [\#2859](https://github.com/kivy/python-for-android/pull/2859) ([kulothunganug](https://github.com/kulothunganug))
+- Automatically generate required pre-requisites [\#2858](https://github.com/kivy/python-for-android/pull/2858) ([Julian-O](https://github.com/Julian-O))
+- Use platform.uname instead of os.uname [\#2857](https://github.com/kivy/python-for-android/pull/2857) ([Julian-O](https://github.com/Julian-O))
+- Bump `kivy` version to `2.2.1` [\#2855](https://github.com/kivy/python-for-android/pull/2855) ([misl6](https://github.com/misl6))
+- Correct sys\_platform [\#2852](https://github.com/kivy/python-for-android/pull/2852) ([Julian-O](https://github.com/Julian-O))
+- Changed the url to use https as http fails [\#2846](https://github.com/kivy/python-for-android/pull/2846) ([kuzeyron](https://github.com/kuzeyron))
+- vlc: fix build [\#2841](https://github.com/kivy/python-for-android/pull/2841) ([T-Dynamos](https://github.com/T-Dynamos))
+- Optimize CI runs, by avoiding unnecessary rebuilds [\#2833](https://github.com/kivy/python-for-android/pull/2833) ([misl6](https://github.com/misl6))
+- Remove `pytz` recipe, as it's not needed anymore [\#2830](https://github.com/kivy/python-for-android/pull/2830) ([misl6](https://github.com/misl6))
+- Remove `dateutil` recipe, as it's not needed anymore [\#2829](https://github.com/kivy/python-for-android/pull/2829) ([misl6](https://github.com/misl6))
+- Removes `mysqldb` recipe as does not support Python 3 [\#2828](https://github.com/kivy/python-for-android/pull/2828) ([misl6](https://github.com/misl6))
+- 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))
+- Removes `Babel` recipe as it's not needed anymore. [\#2826](https://github.com/kivy/python-for-android/pull/2826) ([misl6](https://github.com/misl6))
+- Cffi update [\#2800](https://github.com/kivy/python-for-android/pull/2800) ([HyTurtle](https://github.com/HyTurtle))
+- Merge master into develop [\#2797](https://github.com/kivy/python-for-android/pull/2797) ([misl6](https://github.com/misl6))
+- 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))
+
+## [v2023.05.21](https://github.com/kivy/python-for-android/tree/v2023.05.21)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2023.02.10...v2023.05.21)
+
+**Closed issues:**
+
+- python [\#2795](https://github.com/kivy/python-for-android/issues/2795)
+- Create APK from PyQt app [\#2794](https://github.com/kivy/python-for-android/issues/2794)
+- psutil/\_psutil\_linux.so" is 64-bit instead of 32-bit [\#2785](https://github.com/kivy/python-for-android/issues/2785)
+- pythonforandroid.toolchain.py: error: unrecognized arguments: --dir [\#2775](https://github.com/kivy/python-for-android/issues/2775)
+- App [\#2774](https://github.com/kivy/python-for-android/issues/2774)
+- org.kivy.android.PythonActivity$NewIntentListener is not visible from class loader java.lang.IllegalArgumentException [\#2770](https://github.com/kivy/python-for-android/issues/2770)
+- Service don t start anymore, as smallIconName extra is now mandatory [\#2768](https://github.com/kivy/python-for-android/issues/2768)
+- Start a background sticky service that auto-restart. [\#2767](https://github.com/kivy/python-for-android/issues/2767)
+- Fail installation [\#2764](https://github.com/kivy/python-for-android/issues/2764)
+- Python exception when using colorlog due to incomplete IO implementation in sys.stderr [\#2762](https://github.com/kivy/python-for-android/issues/2762)
+- AttributeError: 'org.kivy.android.PythonService' object has no attribute 'getComponentName' [\#2760](https://github.com/kivy/python-for-android/issues/2760)
+- https://code.videolan.org not available [\#2758](https://github.com/kivy/python-for-android/issues/2758)
+- Cannot install Python-for-Android [\#2754](https://github.com/kivy/python-for-android/issues/2754)
+- c/\_cffi\_backend.c:407:23: error: expression is not assignable [\#2753](https://github.com/kivy/python-for-android/issues/2753)
+- not install [\#2749](https://github.com/kivy/python-for-android/issues/2749)
+- APK crashes upon launch. logcat error: null pointer dereference \(occurs with imported modules\) [\#2358](https://github.com/kivy/python-for-android/issues/2358)
+- Error occured while building the aplication using buildozer [\#2104](https://github.com/kivy/python-for-android/issues/2104)
+- "Could Not Extract Public Data" Needs very explicit instructions or feedback to the user [\#260](https://github.com/kivy/python-for-android/issues/260)
+
+**Merged pull requests:**
+
+- Update Kivy recipe for 2.2.0 [\#2793](https://github.com/kivy/python-for-android/pull/2793) ([misl6](https://github.com/misl6))
+- Update `pyjnius` version to `1.5.0` [\#2791](https://github.com/kivy/python-for-android/pull/2791) ([misl6](https://github.com/misl6))
+- fix tools/liblink: syntax error [\#2771](https://github.com/kivy/python-for-android/pull/2771) ([SomberNight](https://github.com/SomberNight))
+- 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))
+- android\_api to integer [\#2765](https://github.com/kivy/python-for-android/pull/2765) ([kuzeyron](https://github.com/kuzeyron))
+- Use io.IOBase for LogFile [\#2763](https://github.com/kivy/python-for-android/pull/2763) ([dylanmccall](https://github.com/dylanmccall))
+- Home app functionality [\#2761](https://github.com/kivy/python-for-android/pull/2761) ([kuzeyron](https://github.com/kuzeyron))
+- Add debug loggings for identifying a matching dist [\#2751](https://github.com/kivy/python-for-android/pull/2751) ([BitcoinWukong](https://github.com/BitcoinWukong))
+- Add PyAV recipe [\#2750](https://github.com/kivy/python-for-android/pull/2750) ([DexerBR](https://github.com/DexerBR))
+- Merge master into develop [\#2748](https://github.com/kivy/python-for-android/pull/2748) ([misl6](https://github.com/misl6))
+- 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))
+
+
+## [v2023.02.10](https://github.com/kivy/python-for-android/tree/v2023.02.10) (2023-02-10)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2023.01.28...v2023.02.10)
+
+**Closed issues:**
+
+- AttributeError: 'str' object has no attribute 'stdout' [\#2745](https://github.com/kivy/python-for-android/issues/2745)
+- 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)
+
+**Merged pull requests:**
+
+- restrict sh version [\#2746](https://github.com/kivy/python-for-android/pull/2746) ([HyTurtle](https://github.com/HyTurtle))
+- 🐛 fix: Update `pydantic` recipe [\#2742](https://github.com/kivy/python-for-android/pull/2742) ([FilipeMarch](https://github.com/FilipeMarch))
+- Merge master into develop [\#2741](https://github.com/kivy/python-for-android/pull/2741) ([misl6](https://github.com/misl6))
+
+## [v2023.01.28](https://github.com/kivy/python-for-android/tree/v2023.01.28) (2023-01-28)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.12.20...v2023.01.28)
+
+**Closed issues:**
+
+- Python
+ [\#2737](https://github.com/kivy/python-for-android/issues/2737)
+- AndroidX Issue [\#2736](https://github.com/kivy/python-for-android/issues/2736)
+- Kivy build failed [\#2735](https://github.com/kivy/python-for-android/issues/2735)
+- Can't build apk using READ\_EXTERNAL\_STORAGE, WRITE\_EXTERNAL\_STORAGE in buildozer.spec [\#2732](https://github.com/kivy/python-for-android/issues/2732)
+- BUILD FAILURE: No main.py\(o\) found in your app directory. [\#2731](https://github.com/kivy/python-for-android/issues/2731)
+- 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)
+- 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)
+- `sh.CommandNotFound: ./download.sh` [\#2726](https://github.com/kivy/python-for-android/issues/2726)
+- Setting `android:screenOrientation` via `--orientation` has no effect when targeting API 31 [\#2724](https://github.com/kivy/python-for-android/issues/2724)
+- \[Question\]: How to set 'compileSdkVersion' to 31 or higher. [\#2722](https://github.com/kivy/python-for-android/issues/2722)
+- Bug in the keyboard event listener [\#2423](https://github.com/kivy/python-for-android/issues/2423)
+
+**Merged pull requests:**
+
+- 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))
+- Update \_\_init\_\_.py from `scrypt` recipe [\#2738](https://github.com/kivy/python-for-android/pull/2738) ([FilipeMarch](https://github.com/FilipeMarch))
+- 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))
+- 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))
+- Merge master in develop [\#2721](https://github.com/kivy/python-for-android/pull/2721) ([misl6](https://github.com/misl6))
+
+## [v2022.12.20](https://github.com/kivy/python-for-android/tree/v2022.12.20) (2022-12-20)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.09.04...v2022.12.20)
+
+**Fixed bugs:**
+
+- `liblzma` fails to build on macOS \(sh.ErrorReturnCode\_2. when running buildozer android debug\) [\#2343](https://github.com/kivy/python-for-android/issues/2343)
+
+**Closed issues:**
+
+- SDL\_ttf 2.0.15 download missing \(deprecated\) related to \#2698 [\#2710](https://github.com/kivy/python-for-android/issues/2710)
+- Update kivy app that's already published on google play store. [\#2709](https://github.com/kivy/python-for-android/issues/2709)
+- Installing deepspeech [\#2702](https://github.com/kivy/python-for-android/issues/2702)
+- ImportError: dlopen failed: library "libc++\_shared.so" not found [\#2699](https://github.com/kivy/python-for-android/issues/2699)
+- ffpyplayer recipe broken after SDL2 upgrade [\#2698](https://github.com/kivy/python-for-android/issues/2698)
+- ModuleNotFoundError: No module named 'android' [\#2697](https://github.com/kivy/python-for-android/issues/2697)
+- Error When I set android.api = 31 [\#2696](https://github.com/kivy/python-for-android/issues/2696)
+- Is threre any way to protect .py code [\#2695](https://github.com/kivy/python-for-android/issues/2695)
+- args.service\_class\_name results in link error [\#2679](https://github.com/kivy/python-for-android/issues/2679)
+- Remove `x86_64` suffix from ndk download link [\#2676](https://github.com/kivy/python-for-android/issues/2676)
+- Pillow 9.2.0 recipe? [\#2671](https://github.com/kivy/python-for-android/issues/2671)
+- `ffmpeg`: unable to find library -lvpx [\#2665](https://github.com/kivy/python-for-android/issues/2665)
+- Buildozer fails while build numpy recipie 'UnixCCompiler' object has no attribute 'cxx\_compiler' [\#2664](https://github.com/kivy/python-for-android/issues/2664)
+- \[PEP 517\] Relax installation-time "pep517\<0.7.0" requirement [\#2573](https://github.com/kivy/python-for-android/issues/2573)
+- Add support for custom resources [\#2298](https://github.com/kivy/python-for-android/issues/2298)
+- Auto-correct / word suggestion does not work with Swiftkey/Samsung keyboard [\#2010](https://github.com/kivy/python-for-android/issues/2010)
+
+**Merged pull requests:**
+
+- `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))
+- secp256k1 Update "--host=" [\#2714](https://github.com/kivy/python-for-android/pull/2714) ([RobertFlatt](https://github.com/RobertFlatt))
+- Delete pythonforandroid/recipes/cdecimal directory [\#2713](https://github.com/kivy/python-for-android/pull/2713) ([RobertFlatt](https://github.com/RobertFlatt))
+- Bump `sdl2` version to `2.26.1` [\#2712](https://github.com/kivy/python-for-android/pull/2712) ([misl6](https://github.com/misl6))
+- 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))
+- 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))
+- Custom Service notification [\#2703](https://github.com/kivy/python-for-android/pull/2703) ([RobertFlatt](https://github.com/RobertFlatt))
+- 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))
+- WRITE\_EXTERNAL\_STORAGE maxSdk [\#2694](https://github.com/kivy/python-for-android/pull/2694) ([RobertFlatt](https://github.com/RobertFlatt))
+- 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))
+- 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))
+- Make CI compile aiohttp again. [\#2690](https://github.com/kivy/python-for-android/pull/2690) ([xavierfiechter](https://github.com/xavierfiechter))
+- Add resources [\#2684](https://github.com/kivy/python-for-android/pull/2684) ([RobertFlatt](https://github.com/RobertFlatt))
+- 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))
+- recipe.download\_file: implement shallow git cloning [\#2682](https://github.com/kivy/python-for-android/pull/2682) ([SomberNight](https://github.com/SomberNight))
+- requirements: relax version bound on "pep517" [\#2680](https://github.com/kivy/python-for-android/pull/2680) ([SomberNight](https://github.com/SomberNight))
+- Add new Android permissions [\#2677](https://github.com/kivy/python-for-android/pull/2677) ([RobertFlatt](https://github.com/RobertFlatt))
+- Resize webview when keyboard is shown [\#2674](https://github.com/kivy/python-for-android/pull/2674) ([dbnicholson](https://github.com/dbnicholson))
+- 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))
+- Fixes libvpx build [\#2672](https://github.com/kivy/python-for-android/pull/2672) ([misl6](https://github.com/misl6))
+- `toml` may not be available on systemwide python [\#2670](https://github.com/kivy/python-for-android/pull/2670) ([misl6](https://github.com/misl6))
+- android/activity: Add Application.ActivityLifecycleCallbacks helpers [\#2669](https://github.com/kivy/python-for-android/pull/2669) ([dbnicholson](https://github.com/dbnicholson))
+- Bump minimal and recommended Android NDK version to 25b [\#2668](https://github.com/kivy/python-for-android/pull/2668) ([misl6](https://github.com/misl6))
+- Include HOME in build environment [\#2582](https://github.com/kivy/python-for-android/pull/2582) ([dbnicholson](https://github.com/dbnicholson))
+
+## [v2022.09.04](https://github.com/kivy/python-for-android/tree/v2022.09.04) (2022-09-04)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.07.20...v2022.09.04)
+
+**Fixed bugs:**
+
+- Matplotlib failed to import properly on an APK from Buildozer and Kivy [\#2643](https://github.com/kivy/python-for-android/issues/2643)
+
+**Closed issues:**
+
+- KeyError: Matplotlib with kivy android [\#2658](https://github.com/kivy/python-for-android/issues/2658)
+- KeyError: Matplotlib [\#2659](https://github.com/kivy/python-for-android/issues/2659)
+- Upgrade from NDK 19b to 23b causes problems with Pandas library [\#2654](https://github.com/kivy/python-for-android/issues/2654)
+- Update Dockerfile for ARM [\#2653](https://github.com/kivy/python-for-android/issues/2653)
+- Apple M2 chip doesn't generate apk: compiling error on liblzma [\#2652](https://github.com/kivy/python-for-android/issues/2652)
+- aiohttp/\_http\_parser.pyx:46:0: '\_headers.pxi' not found [\#2651](https://github.com/kivy/python-for-android/issues/2651)
+- \[Question\] Pip SSL ? [\#2649](https://github.com/kivy/python-for-android/issues/2649)
+- 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)
+- \[Question\] Java Files [\#2646](https://github.com/kivy/python-for-android/issues/2646)
+- Using foreground services will cause wired behaviour on Android 8 [\#2641](https://github.com/kivy/python-for-android/issues/2641)
+- Can't apply patches with relative paths for local recipe [\#2623](https://github.com/kivy/python-for-android/issues/2623)
+- Compile for x86 on MacOS [\#2215](https://github.com/kivy/python-for-android/issues/2215)
+- splash always loading [\#1907](https://github.com/kivy/python-for-android/issues/1907)
+- python-for-android.readthedocs.io has problems updating, apparently [\#1709](https://github.com/kivy/python-for-android/issues/1709)
+- Webview apps not working on Android [\#1644](https://github.com/kivy/python-for-android/issues/1644)
+
+**Merged pull requests:**
+
+- `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))
+- Force `--platform=linux/amd64` in Dockerfile [\#2660](https://github.com/kivy/python-for-android/pull/2660) ([misl6](https://github.com/misl6))
+- Remove six and enum34 dependency [\#2657](https://github.com/kivy/python-for-android/pull/2657) ([misl6](https://github.com/misl6))
+- Update supported Python versions [\#2656](https://github.com/kivy/python-for-android/pull/2656) ([misl6](https://github.com/misl6))
+- Fixes some E275 - assert is a keyword. [\#2647](https://github.com/kivy/python-for-android/pull/2647) ([misl6](https://github.com/misl6))
+- Updates matplotlib, fixes an issue related to shared libc++ [\#2645](https://github.com/kivy/python-for-android/pull/2645) ([misl6](https://github.com/misl6))
+- RTSP support for ffmpeg [\#2644](https://github.com/kivy/python-for-android/pull/2644) ([alicakici1234](https://github.com/alicakici1234))
+- 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))
+- Resolve absolute path to local recipes [\#2640](https://github.com/kivy/python-for-android/pull/2640) ([dbnicholson](https://github.com/dbnicholson))
+- Merges master into develop after release 2022.07.20 [\#2639](https://github.com/kivy/python-for-android/pull/2639) ([misl6](https://github.com/misl6))
+- Fix webview Back button behaviour [\#2636](https://github.com/kivy/python-for-android/pull/2636) ([interlark](https://github.com/interlark))
+- Add icon-bg and icon-fg to fix\_args [\#2633](https://github.com/kivy/python-for-android/pull/2633) ([danigm](https://github.com/danigm))
+- Remove stray - in output file name [\#2581](https://github.com/kivy/python-for-android/pull/2581) ([dbnicholson](https://github.com/dbnicholson))
+- 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))
+
+## [v2022.07.20](https://github.com/kivy/python-for-android/tree/v2022.07.20) (2022-07-20)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2022.03.13...v2022.07.20)
+
+**Fixed bugs:**
+
+- 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)
+- Build failed for Pillow recipe when targeting x86\_64 arch [\#2259](https://github.com/kivy/python-for-android/issues/2259)
+- UnboundLocalError: local variable 'toolchain\_version' referenced before assignment [\#2190](https://github.com/kivy/python-for-android/issues/2190)
+- Numpy on MacOsX fails in our `CI` tests [\#2087](https://github.com/kivy/python-for-android/issues/2087)
+
+**Closed issues:**
+
+- pyzbar android building error [\#2635](https://github.com/kivy/python-for-android/issues/2635)
+- `tflite-runtime` build every time [\#2630](https://github.com/kivy/python-for-android/issues/2630)
+- Failed to build `matplotlib` because `kiwisolver` [\#2629](https://github.com/kivy/python-for-android/issues/2629)
+- Trying to build pandas with buildozer results in missing headers errors [\#2626](https://github.com/kivy/python-for-android/issues/2626)
+- https://github.com/kivy/python-for-android.git [\#2625](https://github.com/kivy/python-for-android/issues/2625)
+- \[SSL : CERTIFICATE\_VERIFY\_FAILED \] in Android [\#2620](https://github.com/kivy/python-for-android/issues/2620)
+- How to run Python script in background in android? [\#2618](https://github.com/kivy/python-for-android/issues/2618)
+- USB permission [\#2611](https://github.com/kivy/python-for-android/issues/2611)
+- ffmpeg recipe for 23b build fails [\#2608](https://github.com/kivy/python-for-android/issues/2608)
+- Broken jpeg recipe for NDK 23b? [\#2603](https://github.com/kivy/python-for-android/issues/2603)
+- Need a help [\#2595](https://github.com/kivy/python-for-android/issues/2595)
+- Termux build fails [\#2585](https://github.com/kivy/python-for-android/issues/2585)
+- lapack build error [\#2584](https://github.com/kivy/python-for-android/issues/2584)
+- still this issue is happening [\#2572](https://github.com/kivy/python-for-android/issues/2572)
+- "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)
+- unpackPyBundle\(\) on startup crashes already running service [\#2564](https://github.com/kivy/python-for-android/issues/2564)
+- Webview app fail to startup. [\#2559](https://github.com/kivy/python-for-android/issues/2559)
+- genericndkbuild receipe Not compiling with android api \> 28 [\#2555](https://github.com/kivy/python-for-android/issues/2555)
+- Is there a way to build smaller apks? [\#2553](https://github.com/kivy/python-for-android/issues/2553)
+- Webview, icon [\#2552](https://github.com/kivy/python-for-android/issues/2552)
+- SONAME header not present in libpython3.8.so [\#2548](https://github.com/kivy/python-for-android/issues/2548)
+- How to mention Python modules in Kivy buildozer.spec file? [\#2547](https://github.com/kivy/python-for-android/issues/2547)
+- Issue with pyaudio and portaudio [\#2535](https://github.com/kivy/python-for-android/issues/2535)
+- \[Temporary Resolved\] Python 4 android in mac os with Apple Silicon via Roseta [\#2528](https://github.com/kivy/python-for-android/issues/2528)
+- 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)
+- Lapack depends on arm-linux-androideabi-gfortran [\#2508](https://github.com/kivy/python-for-android/issues/2508)
+- Apk file built by buildozer is large in comparision to other apks [\#2473](https://github.com/kivy/python-for-android/issues/2473)
+- p4a is not compatible with ndk \>= 22 [\#2391](https://github.com/kivy/python-for-android/issues/2391)
+- Sympy module. Error in buildozer: no module named sympy.testing [\#2381](https://github.com/kivy/python-for-android/issues/2381)
+- build.gradle 'compile' depreciated [\#2362](https://github.com/kivy/python-for-android/issues/2362)
+- API 29 support [\#2360](https://github.com/kivy/python-for-android/issues/2360)
+- python for android [\#2307](https://github.com/kivy/python-for-android/issues/2307)
+- application is not working in android made with buildozer kivy [\#2260](https://github.com/kivy/python-for-android/issues/2260)
+- hostpython3 unpack error [\#2247](https://github.com/kivy/python-for-android/issues/2247)
+- no recipe for pyaudio \_portaudio. [\#2223](https://github.com/kivy/python-for-android/issues/2223)
+- How to add a native Python package for kivy? [\#2089](https://github.com/kivy/python-for-android/issues/2089)
+- scipy module fails loading for 32 bit and 64 bit APK builds. [\#2061](https://github.com/kivy/python-for-android/issues/2061)
+- Support for androidx [\#2020](https://github.com/kivy/python-for-android/issues/2020)
+- Cannot build apk using buidozer [\#2005](https://github.com/kivy/python-for-android/issues/2005)
+- Android NDK - "$NDK/platforms/android-25" missing? [\#1992](https://github.com/kivy/python-for-android/issues/1992)
+- Tidy up NDK 19+ support [\#1962](https://github.com/kivy/python-for-android/issues/1962)
+- Support for NDK 19 [\#1613](https://github.com/kivy/python-for-android/issues/1613)
+- Android NDK 18b issues [\#1525](https://github.com/kivy/python-for-android/issues/1525)
+- Google requiring 64 bits binary in August 2019 [\#1519](https://github.com/kivy/python-for-android/issues/1519)
+- Investigate Azure Pipelines [\#1400](https://github.com/kivy/python-for-android/issues/1400)
+
+**Merged pull requests:**
+
+- Use `shutil.which` instead of `sh.which` [\#2637](https://github.com/kivy/python-for-android/pull/2637) ([misl6](https://github.com/misl6))
+- add service\_lib and aar to the docs [\#2634](https://github.com/kivy/python-for-android/pull/2634) ([mzakharo](https://github.com/mzakharo))
+- Fix issue \#2630 [\#2631](https://github.com/kivy/python-for-android/pull/2631) ([Neizvestnyj](https://github.com/Neizvestnyj))
+- lapack/scipy: support NDK r21e, x86/64 archs [\#2619](https://github.com/kivy/python-for-android/pull/2619) ([mzakharo](https://github.com/mzakharo))
+- add scipy/lapack CI tests [\#2617](https://github.com/kivy/python-for-android/pull/2617) ([mzakharo](https://github.com/mzakharo))
+- 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))
+- Fixing service\_library bootstrap + .aar build. [\#2612](https://github.com/kivy/python-for-android/pull/2612) ([mzakharo](https://github.com/mzakharo))
+- Bump groestlcoin\_hash to 1.0.3 [\#2607](https://github.com/kivy/python-for-android/pull/2607) ([gruve-p](https://github.com/gruve-p))
+- 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))
+- 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))
+- 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))
+- 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))
+- Add service to webview test app [\#2598](https://github.com/kivy/python-for-android/pull/2598) ([dbnicholson](https://github.com/dbnicholson))
+- Fix webview testapp jnius usage [\#2597](https://github.com/kivy/python-for-android/pull/2597) ([dbnicholson](https://github.com/dbnicholson))
+- Support multiarch in webview bootstrap [\#2596](https://github.com/kivy/python-for-android/pull/2596) ([dbnicholson](https://github.com/dbnicholson))
+- 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))
+- Prefer avdmanager from cmdline-tools [\#2593](https://github.com/kivy/python-for-android/pull/2593) ([dbnicholson](https://github.com/dbnicholson))
+- \*\_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))
+- 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))
+- Added py3dns recipe [\#2590](https://github.com/kivy/python-for-android/pull/2590) ([Neizvestnyj](https://github.com/Neizvestnyj))
+- 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))
+- Fixes a typo in macos\_rebuild\_updated\_recipes [\#2587](https://github.com/kivy/python-for-android/pull/2587) ([misl6](https://github.com/misl6))
+- 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))
+- Set PATH using real SDK and NDK directories [\#2583](https://github.com/kivy/python-for-android/pull/2583) ([dbnicholson](https://github.com/dbnicholson))
+- 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))
+- 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))
+- 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))
+- macOS CI: ADD APK, AAB & Updated Recipes build [\#2574](https://github.com/kivy/python-for-android/pull/2574) ([misl6](https://github.com/misl6))
+- add version check to unpackPyBundle [\#2565](https://github.com/kivy/python-for-android/pull/2565) ([mzakharo](https://github.com/mzakharo))
+- Merges master into develop after release 2022.03.13 [\#2562](https://github.com/kivy/python-for-android/pull/2562) ([misl6](https://github.com/misl6))
+- Fixes App Icon and Presplash\_Screen For Webview bootstrap [\#2556](https://github.com/kivy/python-for-android/pull/2556) ([kengoon](https://github.com/kengoon))
+- NDK 23 + Gradle 7 support [\#2550](https://github.com/kivy/python-for-android/pull/2550) ([misl6](https://github.com/misl6))
+
+## [v2022.03.13](https://github.com/kivy/python-for-android/tree/v2022.03.13) (2022-03-13)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2021.09.05...v2022.03.13)
+
+**Closed issues:**
+
+- ModuleNotFoundError: No module named 'msvcrt' [\#2536](https://github.com/kivy/python-for-android/issues/2536)
+- Pyarrow module do not working [\#2531](https://github.com/kivy/python-for-android/issues/2531)
+- Error when building Android Application with Google modules [\#2530](https://github.com/kivy/python-for-android/issues/2530)
+- arm64-v8a \(apk and aab lib\) crashes [\#2524](https://github.com/kivy/python-for-android/issues/2524)
+- Python for android [\#2521](https://github.com/kivy/python-for-android/issues/2521)
+- ValueError: name is too long [\#2517](https://github.com/kivy/python-for-android/issues/2517)
+- 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)
+- How to get libnumpy.so & numpy py libs [\#2510](https://github.com/kivy/python-for-android/issues/2510)
+- pydantic compiling in Buildozer with 'crypt.h' file not found [\#2507](https://github.com/kivy/python-for-android/issues/2507)
+- 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)
+- matplotlib's recipe problem [\#2502](https://github.com/kivy/python-for-android/issues/2502)
+- cffi's recipe problem in aab generation [\#2501](https://github.com/kivy/python-for-android/issues/2501)
+- Pillow's recipe problem in aab generation [\#2497](https://github.com/kivy/python-for-android/issues/2497)
+- Python compilation error: LXMLRecipe' object has no attribute 'ndk\_include\_dir' [\#2493](https://github.com/kivy/python-for-android/issues/2493)
+- Android App crashing at launch - SDL seems crashing [\#2491](https://github.com/kivy/python-for-android/issues/2491)
+- build an android app with ffpyplayer [\#2453](https://github.com/kivy/python-for-android/issues/2453)
+- How to change "PythonActivity.java" to load mp4 file on presplash [\#2439](https://github.com/kivy/python-for-android/issues/2439)
+- Kivy app krashes on android 10 [\#2434](https://github.com/kivy/python-for-android/issues/2434)
+- Kivy app crashing on android after installation, what is the issue here? [\#2433](https://github.com/kivy/python-for-android/issues/2433)
+- Build failed [\#2366](https://github.com/kivy/python-for-android/issues/2366)
+- Reportlab bitbucket link is not working anymore [\#2310](https://github.com/kivy/python-for-android/issues/2310)
+- 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)
+- 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)
+- Unable to compile. [\#1710](https://github.com/kivy/python-for-android/issues/1710)
+
+**Merged pull requests:**
+
+- 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))
+- 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))
+- Update to Kivy 2.1.0 [\#2557](https://github.com/kivy/python-for-android/pull/2557) ([RobertFlatt](https://github.com/RobertFlatt))
+- tflite-runtime recipe [\#2554](https://github.com/kivy/python-for-android/pull/2554) ([RobertFlatt](https://github.com/RobertFlatt))
+- Update AndroidManifest.tmpl.xml [\#2551](https://github.com/kivy/python-for-android/pull/2551) ([drahba](https://github.com/drahba))
+- Update recipe.py \(\#2544\) [\#2546](https://github.com/kivy/python-for-android/pull/2546) ([misl6](https://github.com/misl6))
+- Update python versions matrix on CI [\#2534](https://github.com/kivy/python-for-android/pull/2534) ([misl6](https://github.com/misl6))
+- Add ifaddr recipe [\#2527](https://github.com/kivy/python-for-android/pull/2527) ([syrykh](https://github.com/syrykh))
+- Remove websocket-client recipe [\#2526](https://github.com/kivy/python-for-android/pull/2526) ([syrykh](https://github.com/syrykh))
+- Fix build [\#2525](https://github.com/kivy/python-for-android/pull/2525) ([correa](https://github.com/correa))
+- Add aaptOptions noCompress [\#2523](https://github.com/kivy/python-for-android/pull/2523) ([RobertFlatt](https://github.com/RobertFlatt))
+- 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))
+- Bump Pillow version to 8.4.0 [\#2516](https://github.com/kivy/python-for-android/pull/2516) ([misl6](https://github.com/misl6))
+- 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))
+- Add support-requests configuration. [\#2514](https://github.com/kivy/python-for-android/pull/2514) ([misl6](https://github.com/misl6))
+- proper --host for libsecp256k1, libogg, libvorbis, libcurl [\#2512](https://github.com/kivy/python-for-android/pull/2512) ([accumulator](https://github.com/accumulator))
+- Fix broken Contribute link [\#2505](https://github.com/kivy/python-for-android/pull/2505) ([daMatz](https://github.com/daMatz))
+- Makes pep8 happy on sdl2 recipe [\#2504](https://github.com/kivy/python-for-android/pull/2504) ([misl6](https://github.com/misl6))
+- 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))
+- added android permission ACCESS\_BACKGROUND\_LOCATION [\#2500](https://github.com/kivy/python-for-android/pull/2500) ([xloem](https://github.com/xloem))
+- GitHub Actions: Fixes wrong actions/checkout@v2 usage [\#2496](https://github.com/kivy/python-for-android/pull/2496) ([misl6](https://github.com/misl6))
+- Fixes ndk\_include\_dir on lxml recipe. [\#2495](https://github.com/kivy/python-for-android/pull/2495) ([misl6](https://github.com/misl6))
+- Move coveralls to github actions [\#2490](https://github.com/kivy/python-for-android/pull/2490) ([misl6](https://github.com/misl6))
+- Master [\#2489](https://github.com/kivy/python-for-android/pull/2489) ([misl6](https://github.com/misl6))
+- Add should\_build method to sdl2 recipe [\#2482](https://github.com/kivy/python-for-android/pull/2482) ([AndyRusso](https://github.com/AndyRusso))
+- AAB support related changes [\#2467](https://github.com/kivy/python-for-android/pull/2467) ([misl6](https://github.com/misl6))
+- services: fix START\_STICKY [\#2401](https://github.com/kivy/python-for-android/pull/2401) ([mzakharo](https://github.com/mzakharo))
+
+## [v2021.09.05](https://github.com/kivy/python-for-android/tree/v2021.09.05) (2021-09-05)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2020.06.02...v2021.09.05)
+
+**Fixed bugs:**
+
+- Execution failed for task ':compileDebugJavaWithJavac'. "DialogInterface", "AlertDialog" and "KeyEvent" not found. [\#2228](https://github.com/kivy/python-for-android/issues/2228)
+
+**Closed issues:**
+
+- Global varible/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)
+- 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)
+- Applying patches for hostpython3 fails [\#2476](https://github.com/kivy/python-for-android/issues/2476)
+- p4a failing to build webapp [\#2475](https://github.com/kivy/python-for-android/issues/2475)
+- Failed to build with recipe MySQLdb \(setuptools,libmysqlclient\) [\#2474](https://github.com/kivy/python-for-android/issues/2474)
+- ctypes.util.find\_library 64-bit error [\#2468](https://github.com/kivy/python-for-android/issues/2468)
+- Android x86\_64 instant crash with hello world app, missing zlib [\#2460](https://github.com/kivy/python-for-android/issues/2460)
+- pycrypto recipe is arm7 only [\#2457](https://github.com/kivy/python-for-android/issues/2457)
+- lazy loading of recycleview and wa [\#2454](https://github.com/kivy/python-for-android/issues/2454)
+- a kivy app can not send request to any website in phone [\#2450](https://github.com/kivy/python-for-android/issues/2450)
+- Android permissions not working on Android 10 [\#2444](https://github.com/kivy/python-for-android/issues/2444)
+- Feature request for upcoming fushia OS [\#2442](https://github.com/kivy/python-for-android/issues/2442)
+- where is 'main.py' ? [\#2441](https://github.com/kivy/python-for-android/issues/2441)
+- "diff" files are ignored during "pip install ." [\#2435](https://github.com/kivy/python-for-android/issues/2435)
+- Travis CI misconfigured? [\#2428](https://github.com/kivy/python-for-android/issues/2428)
+- NumPy | ImportError: dlopen failed: cannot locate symbol "log10f" referenced by "\_multiarray\_tests.so" [\#2426](https://github.com/kivy/python-for-android/issues/2426)
+- Pandas recipe doesn't work [\#2425](https://github.com/kivy/python-for-android/issues/2425)
+- 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)
+- buildozer failed -\_- [\#2417](https://github.com/kivy/python-for-android/issues/2417)
+- "Python for android ended." When printing \x00 [\#2415](https://github.com/kivy/python-for-android/issues/2415)
+- Does Kivy supports SDK 30? [\#2411](https://github.com/kivy/python-for-android/issues/2411)
+- Pymunk,kivy apk crashing on Android 5.1 [\#2388](https://github.com/kivy/python-for-android/issues/2388)
+- Select specific version of python [\#2373](https://github.com/kivy/python-for-android/issues/2373)
+- MDRaisedButton doesn't work on android [\#2371](https://github.com/kivy/python-for-android/issues/2371)
+- Add Python 3.9 support as it builds fine [\#2365](https://github.com/kivy/python-for-android/issues/2365)
+- Gradle Problem [\#2352](https://github.com/kivy/python-for-android/issues/2352)
+- service\_library lacks mActivity support [\#2350](https://github.com/kivy/python-for-android/issues/2350)
+- Possible bug in the "opencv\_extras" recipe [\#2349](https://github.com/kivy/python-for-android/issues/2349)
+- Pygame recipe \(sdl confusion\) [\#2342](https://github.com/kivy/python-for-android/issues/2342)
+- Youtube-dl [\#2336](https://github.com/kivy/python-for-android/issues/2336)
+- building apk using pygame [\#2333](https://github.com/kivy/python-for-android/issues/2333)
+- Buildozer ValueError: specify a path with --storage-dir [\#2331](https://github.com/kivy/python-for-android/issues/2331)
+- Webview app crashes when trying to request permissions [\#2325](https://github.com/kivy/python-for-android/issues/2325)
+- ffmpeg requirement causes apk crash on android [\#2324](https://github.com/kivy/python-for-android/issues/2324)
+- ModuleNotFoundError: No module named 'fcntl' [\#2321](https://github.com/kivy/python-for-android/issues/2321)
+- error python-for-android: git clone [\#2319](https://github.com/kivy/python-for-android/issues/2319)
+- 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)
+- pyconfig\_detection.patch breaks \(at least\) --use-setup-py [\#2313](https://github.com/kivy/python-for-android/issues/2313)
+- Cython is called in incompatible way, not all options are supported \(especially -I\) [\#2311](https://github.com/kivy/python-for-android/issues/2311)
+- PR 2285 breaks requestPermissions [\#2304](https://github.com/kivy/python-for-android/issues/2304)
+- Python services [\#2297](https://github.com/kivy/python-for-android/issues/2297)
+- Integrating ads in P4A [\#2295](https://github.com/kivy/python-for-android/issues/2295)
+- How to hiding Android keyboard in kivy? [\#2268](https://github.com/kivy/python-for-android/issues/2268)
+- KivyMD \#257 Bug vs MatPlotLib, one or the other??? [\#2262](https://github.com/kivy/python-for-android/issues/2262)
+- MatPlotLib not building with Buildozer \(but it did 2 months ago\) [\#2261](https://github.com/kivy/python-for-android/issues/2261)
+- How to add webp support to pillow? [\#2254](https://github.com/kivy/python-for-android/issues/2254)
+- Unable to run executable on Android [\#2251](https://github.com/kivy/python-for-android/issues/2251)
+- Provide a native service option [\#2243](https://github.com/kivy/python-for-android/issues/2243)
+- the kivy app crach in time of import psycopg2 [\#2241](https://github.com/kivy/python-for-android/issues/2241)
+- How can we make a camera App with zoom by Kivy [\#2238](https://github.com/kivy/python-for-android/issues/2238)
+- Building pycrypto for arm64-v8a fails [\#2230](https://github.com/kivy/python-for-android/issues/2230)
+- buildozer error Building pycrypto for arm64-v8a and x86\_64 [\#2216](https://github.com/kivy/python-for-android/issues/2216)
+- Does the opencv recipe for buildozer not include the extra face class? [\#2166](https://github.com/kivy/python-for-android/issues/2166)
+- Crash at relaunch in pythonlib in SDL2Thread [\#1787](https://github.com/kivy/python-for-android/issues/1787)
+- Background Service is killed when app is swiped \(close by user\) [\#1783](https://github.com/kivy/python-for-android/issues/1783)
+- tarfile failure with long user ID [\#1013](https://github.com/kivy/python-for-android/issues/1013)
+- Apps are too big \(in size\) [\#202](https://github.com/kivy/python-for-android/issues/202)
+
+**Merged pull requests:**
+
+- 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))
+- Upgrade grunt version re: CVE-2020-7729 [\#2484](https://github.com/kivy/python-for-android/pull/2484) ([Zen-CODE](https://github.com/Zen-CODE))
+- Recipe for pydantic [\#2480](https://github.com/kivy/python-for-android/pull/2480) ([FilipeMarch](https://github.com/FilipeMarch))
+- fix pandas recipe [\#2478](https://github.com/kivy/python-for-android/pull/2478) ([mzakharo](https://github.com/mzakharo))
+- Add \*.diff to manifest and package\_data [\#2471](https://github.com/kivy/python-for-android/pull/2471) ([viblo](https://github.com/viblo))
+- 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))
+- 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))
+- Adds libm to Pillow recipe [\#2465](https://github.com/kivy/python-for-android/pull/2465) ([Sahil-pixel](https://github.com/Sahil-pixel))
+- Add missing mipmap directories to bootstraps. [\#2463](https://github.com/kivy/python-for-android/pull/2463) ([rnixx](https://github.com/rnixx))
+- Move PythonActivityUtil.unpackData to PythonUtil.unpackData [\#2462](https://github.com/kivy/python-for-android/pull/2462) ([rnixx](https://github.com/rnixx))
+- 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))
+- fixed redirection for download liblzma [\#2452](https://github.com/kivy/python-for-android/pull/2452) ([td1803](https://github.com/td1803))
+- update \(host\)python3 to 3.8.9 [\#2451](https://github.com/kivy/python-for-android/pull/2451) ([obfusk](https://github.com/obfusk))
+- update sqlite3 [\#2449](https://github.com/kivy/python-for-android/pull/2449) ([obfusk](https://github.com/obfusk))
+- build.py: also clean\(\) tarfile directory entries [\#2447](https://github.com/kivy/python-for-android/pull/2447) ([obfusk](https://github.com/obfusk))
+- android: add support for adaptive icon/launcher [\#2446](https://github.com/kivy/python-for-android/pull/2446) ([SomberNight](https://github.com/SomberNight))
+- Fix ImportError bug: cannot locate symbol "modf" [\#2445](https://github.com/kivy/python-for-android/pull/2445) ([Neizvestnyj](https://github.com/Neizvestnyj))
+- update openssl [\#2443](https://github.com/kivy/python-for-android/pull/2443) ([obfusk](https://github.com/obfusk))
+- 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))
+- Add setuptools dependency for Groestlcoin\_hash recipe [\#2438](https://github.com/kivy/python-for-android/pull/2438) ([gruve-p](https://github.com/gruve-p))
+- set urllib user-agent [\#2437](https://github.com/kivy/python-for-android/pull/2437) ([obfusk](https://github.com/obfusk))
+- setup.py: add \*.diff to package\_data [\#2436](https://github.com/kivy/python-for-android/pull/2436) ([obfusk](https://github.com/obfusk))
+- Reintroduce documentation of android module [\#2432](https://github.com/kivy/python-for-android/pull/2432) ([tito](https://github.com/tito))
+- Update \_\_init\_\_.py [\#2429](https://github.com/kivy/python-for-android/pull/2429) ([Neizvestnyj](https://github.com/Neizvestnyj))
+- webview: flush cookies & improve shouldOverrideUrlLoading [\#2424](https://github.com/kivy/python-for-android/pull/2424) ([obfusk](https://github.com/obfusk))
+- travis: update pip \(fixes CI\) [\#2422](https://github.com/kivy/python-for-android/pull/2422) ([obfusk](https://github.com/obfusk))
+- update openssl [\#2421](https://github.com/kivy/python-for-android/pull/2421) ([obfusk](https://github.com/obfusk))
+- 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))
+- strip null bytes in call to androidembed.log\(\) [\#2416](https://github.com/kivy/python-for-android/pull/2416) ([obfusk](https://github.com/obfusk))
+- webview: put webview\_includes in assets dir [\#2412](https://github.com/kivy/python-for-android/pull/2412) ([obfusk](https://github.com/obfusk))
+- update setuptools [\#2409](https://github.com/kivy/python-for-android/pull/2409) ([obfusk](https://github.com/obfusk))
+- update sqlite3 [\#2408](https://github.com/kivy/python-for-android/pull/2408) ([obfusk](https://github.com/obfusk))
+- Update quickstart.rst macOS brew cask command [\#2407](https://github.com/kivy/python-for-android/pull/2407) ([yestoday-tv](https://github.com/yestoday-tv))
+- Added ability to set input type on android [\#2405](https://github.com/kivy/python-for-android/pull/2405) ([dwmoffatt](https://github.com/dwmoffatt))
+- PyZQM recipe needs setuptools, list it explicitly in deps [\#2404](https://github.com/kivy/python-for-android/pull/2404) ([rambo](https://github.com/rambo))
+- recipes: print recipe ENV on failure [\#2403](https://github.com/kivy/python-for-android/pull/2403) ([mzakharo](https://github.com/mzakharo))
+- recipes: scipy - fix build for armeabi-v7a [\#2402](https://github.com/kivy/python-for-android/pull/2402) ([mzakharo](https://github.com/mzakharo))
+- 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))
+- Fix Pymunk crash on older versions of Android [\#2399](https://github.com/kivy/python-for-android/pull/2399) ([viblo](https://github.com/viblo))
+- Recipe for argon2-cffi [\#2398](https://github.com/kivy/python-for-android/pull/2398) ([Arjun-Somvanshi](https://github.com/Arjun-Somvanshi))
+- update sqlite3 to 3.34.0 [\#2397](https://github.com/kivy/python-for-android/pull/2397) ([obfusk](https://github.com/obfusk))
+- update openssl to 1.1.1i [\#2396](https://github.com/kivy/python-for-android/pull/2396) ([obfusk](https://github.com/obfusk))
+- support Python 3.9 [\#2394](https://github.com/kivy/python-for-android/pull/2394) ([obfusk](https://github.com/obfusk))
+- reproducible builds [\#2390](https://github.com/kivy/python-for-android/pull/2390) ([obfusk](https://github.com/obfusk))
+- Update Pymunk recipe to 6.0.0 [\#2389](https://github.com/kivy/python-for-android/pull/2389) ([viblo](https://github.com/viblo))
+- webview: add mOpenExternalLinksInBrowser field [\#2387](https://github.com/kivy/python-for-android/pull/2387) ([obfusk](https://github.com/obfusk))
+- webview: add enableZoom\(\) method [\#2386](https://github.com/kivy/python-for-android/pull/2386) ([obfusk](https://github.com/obfusk))
+- Enable AndroidX [\#2385](https://github.com/kivy/python-for-android/pull/2385) ([RobertFlatt](https://github.com/RobertFlatt))
+- :arrow\_up: Updates to Kivy2 [\#2384](https://github.com/kivy/python-for-android/pull/2384) ([kuzeyron](https://github.com/kuzeyron))
+- fix travis [\#2383](https://github.com/kivy/python-for-android/pull/2383) ([obfusk](https://github.com/obfusk))
+- :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))
+- docs: fix simple typo, pacakged -\> packaged [\#2377](https://github.com/kivy/python-for-android/pull/2377) ([timgates42](https://github.com/timgates42))
+- Fix master only merges [\#2376](https://github.com/kivy/python-for-android/pull/2376) ([inclement](https://github.com/inclement))
+- Audiostream Fix [\#2375](https://github.com/kivy/python-for-android/pull/2375) ([xloem](https://github.com/xloem))
+- Add service information for buildozer.spec [\#2372](https://github.com/kivy/python-for-android/pull/2372) ([xloem](https://github.com/xloem))
+- recipes: add scipy support [\#2370](https://github.com/kivy/python-for-android/pull/2370) ([mzakharo](https://github.com/mzakharo))
+- fix CI [\#2368](https://github.com/kivy/python-for-android/pull/2368) ([obfusk](https://github.com/obfusk))
+- support activity\_launch\_mode in webview bootstrap [\#2367](https://github.com/kivy/python-for-android/pull/2367) ([obfusk](https://github.com/obfusk))
+- Support running tests on any arch [\#2355](https://github.com/kivy/python-for-android/pull/2355) ([jayvdb](https://github.com/jayvdb))
+- setup.py: Fix dependency syntax [\#2354](https://github.com/kivy/python-for-android/pull/2354) ([jayvdb](https://github.com/jayvdb))
+- added missing digest support to recipes [\#2351](https://github.com/kivy/python-for-android/pull/2351) ([fuzzyTew](https://github.com/fuzzyTew))
+- android: call non-static methods on .mActivity [\#2341](https://github.com/kivy/python-for-android/pull/2341) ([obfusk](https://github.com/obfusk))
+- fix webview jni [\#2340](https://github.com/kivy/python-for-android/pull/2340) ([obfusk](https://github.com/obfusk))
+- missing mActivity [\#2339](https://github.com/kivy/python-for-android/pull/2339) ([zworkb](https://github.com/zworkb))
+- 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))
+- ffmpeg and ffpyplayer improvements [\#2335](https://github.com/kivy/python-for-android/pull/2335) ([rnixx](https://github.com/rnixx))
+- Add recipe for https://docs.aiohttp.org/en/stable/ [\#2332](https://github.com/kivy/python-for-android/pull/2332) ([rambo](https://github.com/rambo))
+- Update \_\_init\_\_.py for Report Lab recipe [\#2323](https://github.com/kivy/python-for-android/pull/2323) ([marcets](https://github.com/marcets))
+- Print patched message to stderr [\#2314](https://github.com/kivy/python-for-android/pull/2314) ([rambo](https://github.com/rambo))
+- Call cython via the setuptools entrypoint, fixes \#2311 [\#2312](https://github.com/kivy/python-for-android/pull/2312) ([rambo](https://github.com/rambo))
+- Allow using background color with lottie splashscreen [\#2305](https://github.com/kivy/python-for-android/pull/2305) ([tshirtman](https://github.com/tshirtman))
+- Add manifestPlaceholders [\#2301](https://github.com/kivy/python-for-android/pull/2301) ([misl6](https://github.com/misl6))
+- Allow using lottie files for splashscreen \(SDL2 bootstrap\) [\#2296](https://github.com/kivy/python-for-android/pull/2296) ([tshirtman](https://github.com/tshirtman))
+- use https download for boost & zope [\#2293](https://github.com/kivy/python-for-android/pull/2293) ([obfusk](https://github.com/obfusk))
+- \(host\)python3: rm version check for pyconfig patch [\#2292](https://github.com/kivy/python-for-android/pull/2292) ([obfusk](https://github.com/obfusk))
+- download\_file: show error + exponential sleep [\#2291](https://github.com/kivy/python-for-android/pull/2291) ([obfusk](https://github.com/obfusk))
+- remove cruft from webview\_includes/\_load.html [\#2289](https://github.com/kivy/python-for-android/pull/2289) ([obfusk](https://github.com/obfusk))
+- tiny whitespace fix in buildoptions.rst [\#2288](https://github.com/kivy/python-for-android/pull/2288) ([obfusk](https://github.com/obfusk))
+- update libffi to 3.3 [\#2287](https://github.com/kivy/python-for-android/pull/2287) ([obfusk](https://github.com/obfusk))
+- update openssl to 1.1.1g [\#2286](https://github.com/kivy/python-for-android/pull/2286) ([obfusk](https://github.com/obfusk))
+- update pyjnius to 1.3.0 [\#2285](https://github.com/kivy/python-for-android/pull/2285) ([obfusk](https://github.com/obfusk))
+- update setuptools to 49.2.1 [\#2284](https://github.com/kivy/python-for-android/pull/2284) ([obfusk](https://github.com/obfusk))
+- update six to 1.15.0 [\#2283](https://github.com/kivy/python-for-android/pull/2283) ([obfusk](https://github.com/obfusk))
+- update flask to 1.1.2 [\#2282](https://github.com/kivy/python-for-android/pull/2282) ([obfusk](https://github.com/obfusk))
+- update python3 & hostpython3 to 3.8.5 [\#2281](https://github.com/kivy/python-for-android/pull/2281) ([obfusk](https://github.com/obfusk))
+- update sqlite3 to 3.32.3 [\#2280](https://github.com/kivy/python-for-android/pull/2280) ([obfusk](https://github.com/obfusk))
+- fix travis [\#2279](https://github.com/kivy/python-for-android/pull/2279) ([obfusk](https://github.com/obfusk))
+- `libpython3.8m.so` should not have `m` suffix [\#2278](https://github.com/kivy/python-for-android/pull/2278) ([davidhewitt](https://github.com/davidhewitt))
+- add recipe for libpcre [\#2276](https://github.com/kivy/python-for-android/pull/2276) ([obfusk](https://github.com/obfusk))
+- build python3 with loadable-sqlite-extensions [\#2275](https://github.com/kivy/python-for-android/pull/2275) ([obfusk](https://github.com/obfusk))
+- fix indentation [\#2273](https://github.com/kivy/python-for-android/pull/2273) ([obfusk](https://github.com/obfusk))
+- LogFile: rename .buffer \(to fix newer flask/click\) [\#2264](https://github.com/kivy/python-for-android/pull/2264) ([obfusk](https://github.com/obfusk))
+- Add Recipe for Kivy3 module [\#2263](https://github.com/kivy/python-for-android/pull/2263) ([excepterror](https://github.com/excepterror))
+- :sparkles: Rework of Pillow recipe adding WebP support [\#2256](https://github.com/kivy/python-for-android/pull/2256) ([opacam](https://github.com/opacam))
+- :sparkles: Add libwebp recipe [\#2255](https://github.com/kivy/python-for-android/pull/2255) ([opacam](https://github.com/opacam))
+- added --activity-class-name and --activity-package parameters [\#2248](https://github.com/kivy/python-for-android/pull/2248) ([vesellov](https://github.com/vesellov))
+- Fix runtime psycopg2 error [\#2246](https://github.com/kivy/python-for-android/pull/2246) ([Progdrasil](https://github.com/Progdrasil))
+- Bump libpq version [\#2245](https://github.com/kivy/python-for-android/pull/2245) ([Progdrasil](https://github.com/Progdrasil))
+- Support for native services [\#2244](https://github.com/kivy/python-for-android/pull/2244) ([lerela](https://github.com/lerela))
+- Added missing semicolon on service-only bootstrap [\#2236](https://github.com/kivy/python-for-android/pull/2236) ([Swpolo](https://github.com/Swpolo))
+- :green\_heart: Fixes Travis build post OpenJDK bump [\#2235](https://github.com/kivy/python-for-android/pull/2235) ([AndreMiras](https://github.com/AndreMiras))
+- Updated gradle plugin version [\#2234](https://github.com/kivy/python-for-android/pull/2234) ([shashi278](https://github.com/shashi278))
+- :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))
+- :bento: Add CHANGELOG.md [\#2232](https://github.com/kivy/python-for-android/pull/2232) ([opacam](https://github.com/opacam))
+- :arrow\_up: Bumps to OpenJDK 13 [\#2231](https://github.com/kivy/python-for-android/pull/2231) ([AndreMiras](https://github.com/AndreMiras))
+- Fixed KeyError not found [\#2229](https://github.com/kivy/python-for-android/pull/2229) ([sak96](https://github.com/sak96))
+- :rotating\_light: Depreciation warning fixes [\#2227](https://github.com/kivy/python-for-android/pull/2227) ([AndreMiras](https://github.com/AndreMiras))
+- Master [\#2226](https://github.com/kivy/python-for-android/pull/2226) ([AndreMiras](https://github.com/AndreMiras))
+- Add possibility to add a backup rules xml file [\#2208](https://github.com/kivy/python-for-android/pull/2208) ([tcdude](https://github.com/tcdude))
+
+## [v2020.06.02](https://github.com/kivy/python-for-android/tree/v2020.06.02) (2020-06-02)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2020.04.29...v2020.06.02)
+
+**Fixed bugs:**
+
+- Issues introduced by PR \#2113 \(SDL2\) [\#2169](https://github.com/kivy/python-for-android/issues/2169)
+- App exists immediately when importing kivy.core.window.Window [\#2167](https://github.com/kivy/python-for-android/issues/2167)
+
+**Closed issues:**
+
+- Python [\#2214](https://github.com/kivy/python-for-android/issues/2214)
+- build failed. [\#2212](https://github.com/kivy/python-for-android/issues/2212)
+- apk size growing [\#2207](https://github.com/kivy/python-for-android/issues/2207)
+- My despair at trying to simply import the opencv face class \(python-for-android\) [\#2206](https://github.com/kivy/python-for-android/issues/2206)
+- running python 3.6 [\#2204](https://github.com/kivy/python-for-android/issues/2204)
+- specify python version [\#2203](https://github.com/kivy/python-for-android/issues/2203)
+- p4a gets stuck at downloading setuptools [\#2199](https://github.com/kivy/python-for-android/issues/2199)
+- Kivy app crashes after asking for permission [\#2054](https://github.com/kivy/python-for-android/issues/2054)
+
+**Merged pull requests:**
+
+- Release 2020.06.02 [\#2225](https://github.com/kivy/python-for-android/pull/2225) ([AndreMiras](https://github.com/AndreMiras))
+- :arrow_up: Bumps to Gradle 6.4.1 [\#2222](https://github.com/kivy/python-for-android/pull/2222) ([AndreMiras](https://github.com/AndreMiras))
+- :bug: Adds missing requests sub dependencies [\#2221](https://github.com/kivy/python-for-android/pull/2221) ([AndreMiras](https://github.com/AndreMiras))
+- :arrow_up: Bumps to Cython==0.29.19 [\#2220](https://github.com/kivy/python-for-android/pull/2220) ([AndreMiras](https://github.com/AndreMiras))
+- :pencil: Updates install and troubleshooting docs [\#2219](https://github.com/kivy/python-for-android/pull/2219) ([AndreMiras](https://github.com/AndreMiras))
+- :arrow_up: Bumps to Ubuntu 20.04 [\#2218](https://github.com/kivy/python-for-android/pull/2218) ([AndreMiras](https://github.com/AndreMiras))
+- :pencil: Attempt to improve the issue template [\#2217](https://github.com/kivy/python-for-android/pull/2217) ([AndreMiras](https://github.com/AndreMiras))
+- :package: Split logic for build modes & debug symbols [\#2213](https://github.com/kivy/python-for-android/pull/2213) ([opacam](https://github.com/opacam))
+- :sparkles: Add `opencv_extras` recipe [\#2209](https://github.com/kivy/python-for-android/pull/2209) ([opacam](https://github.com/opacam))
+- :books: Troubleshoot SSL error [\#2205](https://github.com/kivy/python-for-android/pull/2205) ([AndreMiras](https://github.com/AndreMiras))
+- Remove superfluous recipes fixes \#1387 [\#2202](https://github.com/kivy/python-for-android/pull/2202) ([AndreMiras](https://github.com/AndreMiras))
+- :white_check_mark: Add tests for hostpython3 recipe [\#2196](https://github.com/kivy/python-for-android/pull/2196) ([opacam](https://github.com/opacam))
+- 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))
+- :pencil2: Rename `Hostpython3Recipe` class to camel case [\#2194](https://github.com/kivy/python-for-android/pull/2194) ([opacam](https://github.com/opacam))
+- :ambulance: Fix `test_should_build` [\#2193](https://github.com/kivy/python-for-android/pull/2193) ([opacam](https://github.com/opacam))
+- :white_check_mark: Add initial tests for python3 recipe [\#2192](https://github.com/kivy/python-for-android/pull/2192) ([opacam](https://github.com/opacam))
+- :bug: Fixes flake8 errors post update [\#2191](https://github.com/kivy/python-for-android/pull/2191) ([AndreMiras](https://github.com/AndreMiras))
+- PythonActivityUtil helper for unpacking data [\#2189](https://github.com/kivy/python-for-android/pull/2189) ([AndreMiras](https://github.com/AndreMiras))
+- Share PythonUtil.java between bootstraps [\#2188](https://github.com/kivy/python-for-android/pull/2188) ([AndreMiras](https://github.com/AndreMiras))
+- Java code linting using PMD 6.23.0 [\#2187](https://github.com/kivy/python-for-android/pull/2187) ([AndreMiras](https://github.com/AndreMiras))
+- Deletes deprecated renpy Python{Activity,Service}.java [\#2186](https://github.com/kivy/python-for-android/pull/2186) ([AndreMiras](https://github.com/AndreMiras))
+- Removes java concurrency/ folder [\#2185](https://github.com/kivy/python-for-android/pull/2185) ([AndreMiras](https://github.com/AndreMiras))
+- Moves kamranzafar/ java directory to common/ [\#2184](https://github.com/kivy/python-for-android/pull/2184) ([AndreMiras](https://github.com/AndreMiras))
+- Use common Hardware.java [\#2183](https://github.com/kivy/python-for-android/pull/2183) ([AndreMiras](https://github.com/AndreMiras))
+- Reuse common AssetExtract.java [\#2182](https://github.com/kivy/python-for-android/pull/2182) ([AndreMiras](https://github.com/AndreMiras))
+- Fixes service only unittest loading [\#2181](https://github.com/kivy/python-for-android/pull/2181) ([AndreMiras](https://github.com/AndreMiras))
+- Downgrades to SDL2 2.0.9 [\#2180](https://github.com/kivy/python-for-android/pull/2180) ([AndreMiras](https://github.com/AndreMiras))
+- Narrows some context manager scopes [\#2179](https://github.com/kivy/python-for-android/pull/2179) ([AndreMiras](https://github.com/AndreMiras))
+- Updates release documentation [\#2177](https://github.com/kivy/python-for-android/pull/2177) ([AndreMiras](https://github.com/AndreMiras))
+- Post release version bump 2020.04.29.dev0 [\#2176](https://github.com/kivy/python-for-android/pull/2176) ([AndreMiras](https://github.com/AndreMiras))
+- Updates version number to 2020.04.29 [\#2175](https://github.com/kivy/python-for-android/pull/2175) ([AndreMiras](https://github.com/AndreMiras))
+- Adds macOS install instructions [\#2165](https://github.com/kivy/python-for-android/pull/2165) ([AndreMiras](https://github.com/AndreMiras))
+- Adds pygame recipe [\#2164](https://github.com/kivy/python-for-android/pull/2164) ([AndreMiras](https://github.com/AndreMiras))
+- Removed python2 support mention from README [\#2162](https://github.com/kivy/python-for-android/pull/2162) ([inclement](https://github.com/inclement))
+- Fixes hostpython build with macOS venv [\#2159](https://github.com/kivy/python-for-android/pull/2159) ([AndreMiras](https://github.com/AndreMiras))
+- Get --add-source working for dirs in Gradle builds [\#2156](https://github.com/kivy/python-for-android/pull/2156) ([kollivier](https://github.com/kollivier))
+- Adding more assets [\#2132](https://github.com/kivy/python-for-android/pull/2132) ([robertpfeiffer](https://github.com/robertpfeiffer))
+- 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))
+
+## [v2020.04.29](https://github.com/kivy/python-for-android/tree/v2020.04.29) (2020-05-07)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2020.03.30...v2020.04.29)
+
+**Closed issues:**
+
+- BUILD FAILURE: No main.py\(o\) found in your app directory. [\#2171](https://github.com/kivy/python-for-android/issues/2171)
+- \[ERROR\] Building cffi for armeabi-v7a [\#2161](https://github.com/kivy/python-for-android/issues/2161)
+- 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)
+- Full screen apps have significantly degraded performance [\#2148](https://github.com/kivy/python-for-android/issues/2148)
+- Build failed: Couldn't find executable for CC [\#2146](https://github.com/kivy/python-for-android/issues/2146)
+- Sign in apk is not working [\#2139](https://github.com/kivy/python-for-android/issues/2139)
+- openssl 1.1.1 has moved, recipe fails [\#2119](https://github.com/kivy/python-for-android/issues/2119)
+- App not asking for permission [\#2086](https://github.com/kivy/python-for-android/issues/2086)
+- 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)
+
+**Merged pull requests:**
+
+- Release 2020.04.29 [\#2174](https://github.com/kivy/python-for-android/pull/2174) ([AndreMiras](https://github.com/AndreMiras))
+- Fixes sh `_env` should be a dictionary [\#2160](https://github.com/kivy/python-for-android/pull/2160) ([AndreMiras](https://github.com/AndreMiras))
+- :rotating light: Fix linting for setup.py [\#2158](https://github.com/kivy/python-for-android/pull/2158) ([opacam](https://github.com/opacam))
+- 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))
+- Show loading screen while unpacking for webview bootstrap [\#2153](https://github.com/kivy/python-for-android/pull/2153) ([kollivier](https://github.com/kollivier))
+- Use python3's venv instead of virtualenv [\#2152](https://github.com/kivy/python-for-android/pull/2152) ([opacam](https://github.com/opacam))
+- Update setup.py: add classifiers & python\_requires [\#2151](https://github.com/kivy/python-for-android/pull/2151) ([opacam](https://github.com/opacam))
+- Twisted recipe [\#2147](https://github.com/kivy/python-for-android/pull/2147) ([pavelsof](https://github.com/pavelsof))
+- Update AndroidManifest.tmpl.xml to support HTTP [\#2143](https://github.com/kivy/python-for-android/pull/2143) ([yingshaoxo](https://github.com/yingshaoxo))
+- Simplifies Dockerfile and fix GitHub runner space [\#2142](https://github.com/kivy/python-for-android/pull/2142) ([AndreMiras](https://github.com/AndreMiras))
+- Fix some code quality and bug-risk issues [\#2141](https://github.com/kivy/python-for-android/pull/2141) ([pnijhara](https://github.com/pnijhara))
+- Update bootstrap.py [\#2137](https://github.com/kivy/python-for-android/pull/2137) ([yingshaoxo](https://github.com/yingshaoxo))
+- :lock: Bump twisted version to `20.3.0` [\#2135](https://github.com/kivy/python-for-android/pull/2135) ([opacam](https://github.com/opacam))
+- release-2020.03.30 to develop [\#2134](https://github.com/kivy/python-for-android/pull/2134) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- recipe: update 'cryptography' and rm unnecessary dependencies [\#2130](https://github.com/kivy/python-for-android/pull/2130) ([SomberNight](https://github.com/SomberNight))
+- Fix coveralls error on GitHub Actions [\#2129](https://github.com/kivy/python-for-android/pull/2129) ([AndreMiras](https://github.com/AndreMiras))
+- bump zeroconf version to 0.24.5, fix depends [\#2128](https://github.com/kivy/python-for-android/pull/2128) ([mikevlz](https://github.com/mikevlz))
+- Install already compiled libraries [\#2127](https://github.com/kivy/python-for-android/pull/2127) ([robertpfeiffer](https://github.com/robertpfeiffer))
+- Fixes code block directives [\#2126](https://github.com/kivy/python-for-android/pull/2126) ([AndreMiras](https://github.com/AndreMiras))
+- Merge master into develop [\#2125](https://github.com/kivy/python-for-android/pull/2125) ([inclement](https://github.com/inclement))
+- Merge master into develop [\#2123](https://github.com/kivy/python-for-android/pull/2123) ([inclement](https://github.com/inclement))
+- Auto deploys to PyPI using Travis on tags [\#2122](https://github.com/kivy/python-for-android/pull/2122) ([AndreMiras](https://github.com/AndreMiras))
+- Update recommended NDK version to 19c [\#2116](https://github.com/kivy/python-for-android/pull/2116) ([inclement](https://github.com/inclement))
+- Minor fixes and cleanup [\#2115](https://github.com/kivy/python-for-android/pull/2115) ([AndreMiras](https://github.com/AndreMiras))
+- Fixes linting errors in runnable.py [\#2114](https://github.com/kivy/python-for-android/pull/2114) ([AndreMiras](https://github.com/AndreMiras))
+- :package: Refactor python module into hostpython3/python3 recipes [\#2108](https://github.com/kivy/python-for-android/pull/2108) ([opacam](https://github.com/opacam))
+- :fire: Move to python3 `super` calls [\#2106](https://github.com/kivy/python-for-android/pull/2106) ([opacam](https://github.com/opacam))
+- :fire: Drop Python 2 support [\#2105](https://github.com/kivy/python-for-android/pull/2105) ([opacam](https://github.com/opacam))
+- Android library [\#2092](https://github.com/kivy/python-for-android/pull/2092) ([zworkb](https://github.com/zworkb))
+- \[recipes\] Update harfbuzz to v2.6.4 [\#2069](https://github.com/kivy/python-for-android/pull/2069) ([opacam](https://github.com/opacam))
+- \[recipes\] Update freetype & add zlib support [\#2068](https://github.com/kivy/python-for-android/pull/2068) ([opacam](https://github.com/opacam))
+- 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))
+- Fix debug build not resulting in gdb-debuggable build [\#1867](https://github.com/kivy/python-for-android/pull/1867) ([etc0de](https://github.com/etc0de))
+- 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))
+
+## [v2020.03.30](https://github.com/kivy/python-for-android/tree/v2020.03.30) (2020-04-04)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2019.10.06...v2020.03.30)
+
+**Fixed bugs:**
+
+- Remove `--sysroot` from LDFLAGS for cffi and pymunk [\#1965](https://github.com/kivy/python-for-android/pull/1965) ([opacam](https://github.com/opacam))
+- 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))
+- 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))
+
+**Closed issues:**
+
+- Version bump for zeroconf to 0.25.4 [\#2107](https://github.com/kivy/python-for-android/issues/2107)
+- ValueError: read of closed file after download of psycopg2 [\#2098](https://github.com/kivy/python-for-android/issues/2098)
+- Why advise us to use Python2??? [\#2090](https://github.com/kivy/python-for-android/issues/2090)
+- KiwiSolver error led build fail when require matplotlib [\#2080](https://github.com/kivy/python-for-android/issues/2080)
+- Is it possible to run matplotlib script in android? [\#2079](https://github.com/kivy/python-for-android/issues/2079)
+- How to create my app name automatically on usb connect [\#2071](https://github.com/kivy/python-for-android/issues/2071)
+- Default buildozer.spec fails to build - fails on openssl [\#2060](https://github.com/kivy/python-for-android/issues/2060)
+- ImportError: dlopen failed: cannot locate symbol - Matplotlib module [\#2059](https://github.com/kivy/python-for-android/issues/2059)
+- ft2font build error with Matplotlib [\#2058](https://github.com/kivy/python-for-android/issues/2058)
+- SDL Error: Error Could not load any libpythonXXX.so [\#2056](https://github.com/kivy/python-for-android/issues/2056)
+- Crashing on phone. SDL Error Could not load any libpythonXXX.so [\#2051](https://github.com/kivy/python-for-android/issues/2051)
+- Hadi [\#2048](https://github.com/kivy/python-for-android/issues/2048)
+- p4a \(2019.10.6\) project build file management [\#2045](https://github.com/kivy/python-for-android/issues/2045)
+- listdir of primary\_external\_storage\_path\(\) fails [\#2032](https://github.com/kivy/python-for-android/issues/2032)
+- Can't use AsyncImage with HTTPS URL \(or any HTTPS url wit any request\): fix is to manually load certifi [\#1827](https://github.com/kivy/python-for-android/issues/1827)
+
+**Merged pull requests:**
+
+- Bumps openssl to 1.1.1f [\#2118](https://github.com/kivy/python-for-android/pull/2118) ([AndreMiras](https://github.com/AndreMiras))
+- Release 2020.03.30 [\#2111](https://github.com/kivy/python-for-android/pull/2111) ([inclement](https://github.com/inclement))
+- Updates quickstart.rst "Installing Dependencies" [\#2109](https://github.com/kivy/python-for-android/pull/2109) ([AndreMiras](https://github.com/AndreMiras))
+- :alien: Remove deprecated key `sudo` from `travis.yml` [\#2102](https://github.com/kivy/python-for-android/pull/2102) ([opacam](https://github.com/opacam))
+- :arrow_up: Update `pytz` to version `2019.3` [\#2101](https://github.com/kivy/python-for-android/pull/2101) ([opacam](https://github.com/opacam))
+- :sparkles: Add `pandas` recipe [\#2100](https://github.com/kivy/python-for-android/pull/2100) ([opacam](https://github.com/opacam))
+- Fixes psycopg2 URL, closes \#2098 [\#2099](https://github.com/kivy/python-for-android/pull/2099) ([AndreMiras](https://github.com/AndreMiras))
+- :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))
+- :sparkles: Compression libraries - episode II: liblzma [\#2096](https://github.com/kivy/python-for-android/pull/2096) ([opacam](https://github.com/opacam))
+- :sparkles: Compression libraries - episode I: libbz2 [\#2095](https://github.com/kivy/python-for-android/pull/2095) ([opacam](https://github.com/opacam))
+- Update quickstart.rst [\#2094](https://github.com/kivy/python-for-android/pull/2094) ([BornForFever](https://github.com/BornForFever))
+- Fixes gevent recipe on arm64-v8a arch [\#2093](https://github.com/kivy/python-for-android/pull/2093) ([AndreMiras](https://github.com/AndreMiras))
+- :bug: Add `-fPIC` to `CFLAGS` for Arch `x86_64` [\#2085](https://github.com/kivy/python-for-android/pull/2085) ([opacam](https://github.com/opacam))
+- Fix Python 3.8.1 patch naming and content [\#2083](https://github.com/kivy/python-for-android/pull/2083) ([opacam](https://github.com/opacam))
+- Update PythonService.java [\#2081](https://github.com/kivy/python-for-android/pull/2081) ([erikhu](https://github.com/erikhu))
+- Update `numpy` to v1.18.1 \(add `cython` recipe\) [\#2077](https://github.com/kivy/python-for-android/pull/2077) ([opacam](https://github.com/opacam))
+- Fix `matplotlib` and update to `v3.1.3` [\#2076](https://github.com/kivy/python-for-android/pull/2076) ([opacam](https://github.com/opacam))
+- Fix recipe `kiwisolver` \(add `cppy` recipe\) [\#2075](https://github.com/kivy/python-for-android/pull/2075) ([opacam](https://github.com/opacam))
+- \[gh-actions\] Move to actions/checkout@v2 [\#2070](https://github.com/kivy/python-for-android/pull/2070) ([opacam](https://github.com/opacam))
+- \[recipes\] Update Pillow to v7.0.0 [\#2067](https://github.com/kivy/python-for-android/pull/2067) ([opacam](https://github.com/opacam))
+- 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))
+- fixed patch's name to apply correctly [\#2064](https://github.com/kivy/python-for-android/pull/2064) ([HirotsuguMINOWA](https://github.com/HirotsuguMINOWA))
+- virtualenv 20 breaks the osx build [\#2063](https://github.com/kivy/python-for-android/pull/2063) ([AndreMiras](https://github.com/AndreMiras))
+- automatically load/enable certifi in kivy [\#2055](https://github.com/kivy/python-for-android/pull/2055) ([tshirtman](https://github.com/tshirtman))
+- \[protobuf\_cpp\] fixed python binding installation [\#2050](https://github.com/kivy/python-for-android/pull/2050) ([goffi-contrib](https://github.com/goffi-contrib))
+- \[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))
+- Python 3.8 support on Android [\#2044](https://github.com/kivy/python-for-android/pull/2044) ([inclement](https://github.com/inclement))
+- Updated version after 2019.10.06 release [\#2043](https://github.com/kivy/python-for-android/pull/2043) ([inclement](https://github.com/inclement))
+- Updated version to 2019.10.06 [\#2042](https://github.com/kivy/python-for-android/pull/2042) ([inclement](https://github.com/inclement))
+- 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))
+- update pyjnius recipe [\#2036](https://github.com/kivy/python-for-android/pull/2036) ([tshirtman](https://github.com/tshirtman))
+- added a recipe for bcrypt library [\#2035](https://github.com/kivy/python-for-android/pull/2035) ([tomgold182](https://github.com/tomgold182))
+- Fix vibration for testapps [\#2034](https://github.com/kivy/python-for-android/pull/2034) ([opacam](https://github.com/opacam))
+- \[gh-actions\] Add new testapp and upload artifacts... [\#2033](https://github.com/kivy/python-for-android/pull/2033) ([opacam](https://github.com/opacam))
+- from kivy.base import runTouchApp for line 75 [\#2028](https://github.com/kivy/python-for-android/pull/2028) ([cclauss](https://github.com/cclauss))
+- 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))
+- Introduce github-actions \(push & pull\_requests\) [\#2025](https://github.com/kivy/python-for-android/pull/2025) ([opacam](https://github.com/opacam))
+- Fix rebuild updated recipes, refs \#2011 [\#2024](https://github.com/kivy/python-for-android/pull/2024) ([opacam](https://github.com/opacam))
+- Exposes ANDROID\_SDK\_HOME to rebuild\_updated\_recipes [\#2018](https://github.com/kivy/python-for-android/pull/2018) ([AndreMiras](https://github.com/AndreMiras))
+- update pyproj recipe [\#2017](https://github.com/kivy/python-for-android/pull/2017) ([joergbrech](https://github.com/joergbrech))
+- Documentation: android hide loading screen [\#2014](https://github.com/kivy/python-for-android/pull/2014) ([adityabhawsingka](https://github.com/adityabhawsingka))
+- Travis CI revamp part 1, refs \#2008 [\#2011](https://github.com/kivy/python-for-android/pull/2011) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- Fix error on py2 when `import numpy` [\#2003](https://github.com/kivy/python-for-android/pull/2003) ([opacam](https://github.com/opacam))
+- Fix libcurl with openssl [\#2000](https://github.com/kivy/python-for-android/pull/2000) ([tito](https://github.com/tito))
+- Fixes ffmpeg build on ndk 19 [\#1997](https://github.com/kivy/python-for-android/pull/1997) ([misl6](https://github.com/misl6))
+- Fixes test\_virtualenv and test\_venv failing, closes \#1994 [\#1995](https://github.com/kivy/python-for-android/pull/1995) ([AndreMiras](https://github.com/AndreMiras))
+- Fixes libiconv & libzbar configure host [\#1993](https://github.com/kivy/python-for-android/pull/1993) ([AndreMiras](https://github.com/AndreMiras))
+- Updates Java version troubleshooting [\#1991](https://github.com/kivy/python-for-android/pull/1991) ([AndreMiras](https://github.com/AndreMiras))
+- Made p4a use per-arch dist build dirs [\#1986](https://github.com/kivy/python-for-android/pull/1986) ([inclement](https://github.com/inclement))
+- 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))
+- Recipes tests enhancements [\#1984](https://github.com/kivy/python-for-android/pull/1984) ([opacam](https://github.com/opacam))
+- 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))
+- Updated README.md to clarify NDK versions [\#1981](https://github.com/kivy/python-for-android/pull/1981) ([Zen-CODE](https://github.com/Zen-CODE))
+- Fix CI's test for `arm64-v8a` [\#1977](https://github.com/kivy/python-for-android/pull/1977) ([opacam](https://github.com/opacam))
+- Added missing recommendations command and cleaned up code [\#1975](https://github.com/kivy/python-for-android/pull/1975) ([inclement](https://github.com/inclement))
+- Added libffi headers troubleshooting note to doc [\#1972](https://github.com/kivy/python-for-android/pull/1972) ([inclement](https://github.com/inclement))
+- \[WIP\]\[LIBS - PART VII\] Rework of libtorrent and boost [\#1971](https://github.com/kivy/python-for-android/pull/1971) ([opacam](https://github.com/opacam))
+- \[WIP\]\[LIBS - PART VI\] Rework of protobuf\_cpp [\#1969](https://github.com/kivy/python-for-android/pull/1969) ([opacam](https://github.com/opacam))
+- \[WIP\]\[LIBS - PART V\] Rework of libzmq [\#1968](https://github.com/kivy/python-for-android/pull/1968) ([opacam](https://github.com/opacam))
+- \[WIP\]\[LIBS - PART IV\] Rework of shapely and libgeos [\#1967](https://github.com/kivy/python-for-android/pull/1967) ([opacam](https://github.com/opacam))
+- \[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))
+- 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))
+- Merge release-2019.08.09 branch into develop [\#1959](https://github.com/kivy/python-for-android/pull/1959) ([inclement](https://github.com/inclement))
+- Fix and update regex's recipe [\#1958](https://github.com/kivy/python-for-android/pull/1958) ([opacam](https://github.com/opacam))
+- Fix/doc quotes [\#1956](https://github.com/kivy/python-for-android/pull/1956) ([tshirtman](https://github.com/tshirtman))
+- \[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))
+- \[LIBS - PART I\] Initial refactor of library recipes [\#1944](https://github.com/kivy/python-for-android/pull/1944) ([opacam](https://github.com/opacam))
+- customizability [\#1869](https://github.com/kivy/python-for-android/pull/1869) ([zworkb](https://github.com/zworkb))
+- Initial migration to NDK r19 \(Part I - The core\) [\#1722](https://github.com/kivy/python-for-android/pull/1722) ([opacam](https://github.com/opacam))
+
+## [v2019.10.06](https://github.com/kivy/python-for-android/tree/v2019.10.06) (2019-12-22)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2019.08.09...v2019.10.06)
+
+**Fixed bugs:**
+
+- TestGetSystemPythonExecutable.test\_virtualenv test fail [\#1994](https://github.com/kivy/python-for-android/issues/1994)
+
+**Closed issues:**
+
+- Presplash is removed prematurely. [\#2029](https://github.com/kivy/python-for-android/issues/2029)
+- Sorry posted in the wrong repository. Closing this issue [\#2022](https://github.com/kivy/python-for-android/issues/2022)
+- p4a building apk error on Mac OS [\#2016](https://github.com/kivy/python-for-android/issues/2016)
+- Revamp .travis.yml file [\#2008](https://github.com/kivy/python-for-android/issues/2008)
+- Possible SDL2 issues introduced with P4A 2019.06.06 [\#2002](https://github.com/kivy/python-for-android/issues/2002)
+- Error message about python2 [\#2001](https://github.com/kivy/python-for-android/issues/2001)
+- ffmpeg recipe is broken on ndk19 [\#1996](https://github.com/kivy/python-for-android/issues/1996)
+- Error while running ".buildozer.../native-build/python -OO -m compileall -b -f /.../app [\#1990](https://github.com/kivy/python-for-android/issues/1990)
+- The mpl\_android\_fixes.patch didn't work [\#1989](https://github.com/kivy/python-for-android/issues/1989)
+- Importing numpy yields: TypeError: add\_docstring\(\) argument 2 must be str, not None [\#1988](https://github.com/kivy/python-for-android/issues/1988)
+- p4a apk :compileDebugJavaWithJavac error [\#1980](https://github.com/kivy/python-for-android/issues/1980)
+- \[question\]Python for android no longer supports Error ! [\#1978](https://github.com/kivy/python-for-android/issues/1978)
+- Can not Find on Google Play or Buy Premium [\#1974](https://github.com/kivy/python-for-android/issues/1974)
+- Build failed [\#1970](https://github.com/kivy/python-for-android/issues/1970)
+- Can't build a package with bcrypt as requirement [\#1910](https://github.com/kivy/python-for-android/issues/1910)
+- import wxpy module fail [\#1897](https://github.com/kivy/python-for-android/issues/1897)
+- Cannot build APK with buildozer [\#1817](https://github.com/kivy/python-for-android/issues/1817)
+- Kivy crashes on Android: ImportError: dlopen failed. [\#1810](https://github.com/kivy/python-for-android/issues/1810)
+- App build failing on MacOS [\#1647](https://github.com/kivy/python-for-android/issues/1647)
+- Remove superfluous recipes [\#1387](https://github.com/kivy/python-for-android/issues/1387)
+
+**Merged pull requests:**
+
+- Release 2019.10.06 [\#1998](https://github.com/kivy/python-for-android/pull/1998) ([inclement](https://github.com/inclement))
+
+## [v2019.08.09](https://github.com/kivy/python-for-android/tree/v2019.08.09) (2019-08-19)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2019.07.08...v2019.08.09)
+
+**Fixed bugs:**
+
+- 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))
+
+**Closed issues:**
+
+- Building an Android Library [\#1957](https://github.com/kivy/python-for-android/issues/1957)
+- dlopen failed: library "../../../../src/main/jniLibs/armeabi-v7a/libpython3.7m.so" not found [\#1954](https://github.com/kivy/python-for-android/issues/1954)
+- 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)
+- 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)
+- 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)
+- It seems kivy has no support for tkinter, os, sys, random modules [\#1934](https://github.com/kivy/python-for-android/issues/1934)
+- Mxnet recipe for running kivy app on android [\#1929](https://github.com/kivy/python-for-android/issues/1929)
+- java.lang.UnsatisfiedLinkError: dlopen failed: library "libffi.so.7" not found [\#1924](https://github.com/kivy/python-for-android/issues/1924)
+- Ndk19c compiled numpy problems [\#1923](https://github.com/kivy/python-for-android/issues/1923)
+- run\_on\_ui\_thread crash [\#1908](https://github.com/kivy/python-for-android/issues/1908)
+- please provide recipes for libraries dlib, easygui, Colormath , keras ,imutils [\#1906](https://github.com/kivy/python-for-android/issues/1906)
+- About TextInput, I can't type korean character. I only can type english. [\#1904](https://github.com/kivy/python-for-android/issues/1904)
+- app crash when bootstrap=webview [\#1894](https://github.com/kivy/python-for-android/issues/1894)
+- apk file crash on app launch generated using kivy, buildozer [\#1891](https://github.com/kivy/python-for-android/issues/1891)
+- StartService in Android Oreo and More should use startForegroundService [\#1785](https://github.com/kivy/python-for-android/issues/1785)
+- Remove WRITE\_EXTERNAL\_STORAGE default permission [\#1081](https://github.com/kivy/python-for-android/issues/1081)
+
+**Merged pull requests:**
+
+- Release 2019.08.09 [\#1955](https://github.com/kivy/python-for-android/pull/1955) ([inclement](https://github.com/inclement))
+- Unit tests Recipe.download\_file\(\) partly [\#1952](https://github.com/kivy/python-for-android/pull/1952) ([AndreMiras](https://github.com/AndreMiras))
+- Unit tests Recipe download feature [\#1946](https://github.com/kivy/python-for-android/pull/1946) ([AndreMiras](https://github.com/AndreMiras))
+- Added setuptools to Kivy recipe requirements [\#1938](https://github.com/kivy/python-for-android/pull/1938) ([inclement](https://github.com/inclement))
+- Bumps to Kivy==1.11.1 [\#1935](https://github.com/kivy/python-for-android/pull/1935) ([AndreMiras](https://github.com/AndreMiras))
+- Increases toolchain.py test coverage [\#1933](https://github.com/kivy/python-for-android/pull/1933) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- Fix pyzmq \(libunwind and arm64-v8a\) [\#1930](https://github.com/kivy/python-for-android/pull/1930) ([tito](https://github.com/tito))
+- Basic toolchain.py unit tests [\#1928](https://github.com/kivy/python-for-android/pull/1928) ([AndreMiras](https://github.com/AndreMiras))
+- Merge 2019.07.08 release branch to develop [\#1921](https://github.com/kivy/python-for-android/pull/1921) ([inclement](https://github.com/inclement))
+- Drop Python 2 support [\#1918](https://github.com/kivy/python-for-android/pull/1918) ([inclement](https://github.com/inclement))
+- Remove `nosetests.xml` from gitignore [\#1915](https://github.com/kivy/python-for-android/pull/1915) ([opacam](https://github.com/opacam))
+- Drop CrystaX support and code base [\#1913](https://github.com/kivy/python-for-android/pull/1913) ([opacam](https://github.com/opacam))
+- Release 2019.07.08 [\#1909](https://github.com/kivy/python-for-android/pull/1909) ([inclement](https://github.com/inclement))
+- Add documentation: testing a pull request [\#1901](https://github.com/kivy/python-for-android/pull/1901) ([opacam](https://github.com/opacam))
+- Fix foreground notification being mandatory and more \(attempt 2\) [\#1888](https://github.com/kivy/python-for-android/pull/1888) ([etc0de](https://github.com/etc0de))
+- 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))
+- 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))
+- feat: Allows registering the onRequestPermissionsResult callback. [\#1818](https://github.com/kivy/python-for-android/pull/1818) ([gbm001](https://github.com/gbm001))
+- Add functions for obtaining the default storage paths [\#1598](https://github.com/kivy/python-for-android/pull/1598) ([etc0de](https://github.com/etc0de))
+
+## [v2019.07.08](https://github.com/kivy/python-for-android/tree/v2019.07.08) (2019-07-11)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/v2019.06.06...v2019.07.08)
+
+**Fixed bugs:**
+
+- Fix crash when guessing Bootstrap \(expand\_dependencies\) [\#1914](https://github.com/kivy/python-for-android/pull/1914) ([opacam](https://github.com/opacam))
+- 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))
+- 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))
+- Typo [\#1880](https://github.com/kivy/python-for-android/pull/1880) ([JensGe](https://github.com/JensGe))
+- 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))
+- Fix various setup.py processing bugs [\#1862](https://github.com/kivy/python-for-android/pull/1862) ([etc0de](https://github.com/etc0de))
+- Add `--without-bzip2` to freetype's configure args [\#1857](https://github.com/kivy/python-for-android/pull/1857) ([opacam](https://github.com/opacam))
+- 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))
+
+**Closed issues:**
+
+- Proposal: drop CrystaX support and code base [\#1905](https://github.com/kivy/python-for-android/issues/1905)
+- android hardware back button does not work in kivy [\#1903](https://github.com/kivy/python-for-android/issues/1903)
+- import wxpy module fail:python for android ended [\#1896](https://github.com/kivy/python-for-android/issues/1896)
+- error compile with cristax-ndk [\#1895](https://github.com/kivy/python-for-android/issues/1895)
+- is recipe for the library "kivymd" available in p4a? [\#1893](https://github.com/kivy/python-for-android/issues/1893)
+- deleted [\#1889](https://github.com/kivy/python-for-android/issues/1889)
+- p4a isn't finding directories in apk on launch \(ModuleNotFoundError\) [\#1881](https://github.com/kivy/python-for-android/issues/1881)
+- Little but essential Typo [\#1879](https://github.com/kivy/python-for-android/issues/1879)
+- 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)
+- get\_bootstrap\_from\_recipes\(\) result consistency [\#1875](https://github.com/kivy/python-for-android/issues/1875)
+- install recursive dependencies of pure python packages [\#1874](https://github.com/kivy/python-for-android/issues/1874)
+- numpy recipe bug arm64-v8a [\#1873](https://github.com/kivy/python-for-android/issues/1873)
+- how do i support numba\(a python module\) [\#1865](https://github.com/kivy/python-for-android/issues/1865)
+- apk build error: bzlib.h: No such file or directory [\#1854](https://github.com/kivy/python-for-android/issues/1854)
+- Compilation error, previously worked [\#1846](https://github.com/kivy/python-for-android/issues/1846)
+- Keep track of coverage testing [\#1788](https://github.com/kivy/python-for-android/issues/1788)
+- 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)
+
+**Merged pull requests:**
+
+- Fixes ffmpeg and libx264 recipes for arm64-v8 [\#1916](https://github.com/kivy/python-for-android/pull/1916) ([misl6](https://github.com/misl6))
+- 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))
+- Feature gitignore additions [\#1911](https://github.com/kivy/python-for-android/pull/1911) ([opacam](https://github.com/opacam))
+- Simple run\_pymodules\_install test, refs \#1898 [\#1899](https://github.com/kivy/python-for-android/pull/1899) ([AndreMiras](https://github.com/AndreMiras))
+- Updated numpy recipe to version 1.16.4 [\#1892](https://github.com/kivy/python-for-android/pull/1892) ([inclement](https://github.com/inclement))
+- fix ctypes-util-find-library issue for python2 [\#1877](https://github.com/kivy/python-for-android/pull/1877) ([surbhicis](https://github.com/surbhicis))
+- Add unittest for module `pythonforandroid.bootstrap` [\#1872](https://github.com/kivy/python-for-android/pull/1872) ([opacam](https://github.com/opacam))
+- Remove legacy version of openssl [\#1870](https://github.com/kivy/python-for-android/pull/1870) ([opacam](https://github.com/opacam))
+- Make the tox jobs run in parallel &... [\#1864](https://github.com/kivy/python-for-android/pull/1864) ([opacam](https://github.com/opacam))
+- 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))
+- 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))
+- Add unittest for `pythonforandroid.util` and... [\#1855](https://github.com/kivy/python-for-android/pull/1855) ([opacam](https://github.com/opacam))
+- 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))
+- Improved release model documentation [\#1849](https://github.com/kivy/python-for-android/pull/1849) ([inclement](https://github.com/inclement))
+- Merge release-2019.06.06 to develop [\#1848](https://github.com/kivy/python-for-android/pull/1848) ([inclement](https://github.com/inclement))
+- Add unittest for module `pythonforandroid.distribution` [\#1847](https://github.com/kivy/python-for-android/pull/1847) ([opacam](https://github.com/opacam))
+- 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))
+- Add unittest for module `pythonforandroid.archs` [\#1842](https://github.com/kivy/python-for-android/pull/1842) ([opacam](https://github.com/opacam))
+- 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))
+
+## [v2019.06.06](https://github.com/kivy/python-for-android/tree/v2019.06.06) (2019-06-08)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/0.7.0...v2019.06.06)
+
+**Fixed bugs:**
+
+- AttributeError: 'Namespace' object has no attribute 'ignore\_setup\_py' [\#1808](https://github.com/kivy/python-for-android/issues/1808)
+- libzmq recipe compiling error [\#1802](https://github.com/kivy/python-for-android/issues/1802)
+- Cannot build APK - IndexError: List index out of range [\#1774](https://github.com/kivy/python-for-android/issues/1774)
+- ctypes.util.find\_library\(\) doesn't work on arch arm64-v8a [\#1770](https://github.com/kivy/python-for-android/issues/1770)
+- error building compiled components in netifaces [\#1539](https://github.com/kivy/python-for-android/issues/1539)
+- \[WIP\] Fix crashes when using other commands than 'apk' [\#1809](https://github.com/kivy/python-for-android/pull/1809) ([etc0de](https://github.com/etc0de))
+
+**Closed issues:**
+
+- Create a release checklist [\#1836](https://github.com/kivy/python-for-android/issues/1836)
+- Sorting out python-for-android releases [\#1833](https://github.com/kivy/python-for-android/issues/1833)
+- Error - Runtime permissions - object 'Permission' has no attribute 'ACCESS\_FINE\_LOCATION' [\#1824](https://github.com/kivy/python-for-android/issues/1824)
+- buildozer android debug deploy run -\> sh.CommandNotFound: ./gradlew [\#1804](https://github.com/kivy/python-for-android/issues/1804)
+- buildozer failed clang++: error: linker command failed with exit code 1 [\#1800](https://github.com/kivy/python-for-android/issues/1800)
+- Crash on "--orientation sensor" when rotate [\#1797](https://github.com/kivy/python-for-android/issues/1797)
+- error when compiling with flask\_sqlalmechy and sqlamechy [\#1793](https://github.com/kivy/python-for-android/issues/1793)
+- Some links are broken in the docs [\#1780](https://github.com/kivy/python-for-android/issues/1780)
+- packaged python is built with IPv6 disabled [\#1771](https://github.com/kivy/python-for-android/issues/1771)
+- `p4a recipes` terminates with error [\#1769](https://github.com/kivy/python-for-android/issues/1769)
+- the application does not work with scipy package [\#1767](https://github.com/kivy/python-for-android/issues/1767)
+- openssl not in the build order when compiling cryptography [\#1764](https://github.com/kivy/python-for-android/issues/1764)
+- "--orientation fullUser" is not working [\#1763](https://github.com/kivy/python-for-android/issues/1763)
+- pydub problem [\#1759](https://github.com/kivy/python-for-android/issues/1759)
+- App crashes using python3 and android's run\_on\_ui\_thread [\#1755](https://github.com/kivy/python-for-android/issues/1755)
+- str.decode\(\) issue again for Python 3 [\#1749](https://github.com/kivy/python-for-android/issues/1749)
+- Error while in gradlew [\#1740](https://github.com/kivy/python-for-android/issues/1740)
+- Buildozer [\#1736](https://github.com/kivy/python-for-android/issues/1736)
+- C compiler cannot create executables [\#1735](https://github.com/kivy/python-for-android/issues/1735)
+- Gevent reciepe problem [\#1732](https://github.com/kivy/python-for-android/issues/1732)
+- Permission RECORD\_AUDIO not working [\#1730](https://github.com/kivy/python-for-android/issues/1730)
+- Unable to build with flask, conflicting with genericndkbuild [\#1728](https://github.com/kivy/python-for-android/issues/1728)
+- Android setup.py not working for windows [\#1726](https://github.com/kivy/python-for-android/issues/1726)
+- Fullscreen mode does not work [\#1724](https://github.com/kivy/python-for-android/issues/1724)
+- 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)
+- App crashing following successful build [\#1719](https://github.com/kivy/python-for-android/issues/1719)
+- Null pointer when finding libraries [\#1717](https://github.com/kivy/python-for-android/issues/1717)
+- Psycopg2 error after the apk installation. [\#1711](https://github.com/kivy/python-for-android/issues/1711)
+- Match official requestPermissions interface [\#1704](https://github.com/kivy/python-for-android/issues/1704)
+- Webview build can't find `SDL_setenv`. [\#1702](https://github.com/kivy/python-for-android/issues/1702)
+- lxml and requests recipe: build interrupted, "\_ctype" error [\#1700](https://github.com/kivy/python-for-android/issues/1700)
+- weird orientation behavior [\#1698](https://github.com/kivy/python-for-android/issues/1698)
+- SDLActivity.java\:1948\: error: cannot find symbol case MotionEvent.ACTION\_BUTTON\_PRESS: [\#1697](https://github.com/kivy/python-for-android/issues/1697)
+- ImportError [\#1694](https://github.com/kivy/python-for-android/issues/1694)
+- unicode error during startup \(python3, numpy, opencv\) - patch included [\#1691](https://github.com/kivy/python-for-android/issues/1691)
+- "--orientation sensor" no longer works [\#1688](https://github.com/kivy/python-for-android/issues/1688)
+- kivy==master Window undefined [\#1687](https://github.com/kivy/python-for-android/issues/1687)
+- Pillow Python 3 compile error [\#1679](https://github.com/kivy/python-for-android/issues/1679)
+- numpy/opencv fails with latest p4a at runtime \(import\) [\#1678](https://github.com/kivy/python-for-android/issues/1678)
+- Remove pygame bootstrap [\#1668](https://github.com/kivy/python-for-android/issues/1668)
+- TODO: bring the kivy.org p4a documentation up to date [\#1657](https://github.com/kivy/python-for-android/issues/1657)
+- 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)
+- 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)
+- 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)
+- Broken libglob recipe [\#1399](https://github.com/kivy/python-for-android/issues/1399)
+- Crash when my Python program starts to load [\#1299](https://github.com/kivy/python-for-android/issues/1299)
+- Calendar.getTimeInMillis\(\) returns a negative value [\#942](https://github.com/kivy/python-for-android/issues/942)
+- plyer requirement does not work with ==master version [\#879](https://github.com/kivy/python-for-android/issues/879)
+- ImportError: No module named pysqlite2 [\#860](https://github.com/kivy/python-for-android/issues/860)
+- Local recipe dir is not returned by get\_recipe\_dir\(\) [\#613](https://github.com/kivy/python-for-android/issues/613)
+- SQLite gets compiled without Full Text Search \(FTS3\) support [\#431](https://github.com/kivy/python-for-android/issues/431)
+- BroadcastReceiver broken in service [\#278](https://github.com/kivy/python-for-android/issues/278)
+
+**Merged pull requests:**
+
+- Release 2019.06.06 [\#1839](https://github.com/kivy/python-for-android/pull/1839) ([inclement](https://github.com/inclement))
+- Documented the development and release models [\#1838](https://github.com/kivy/python-for-android/pull/1838) ([inclement](https://github.com/inclement))
+- Added readme for on device unit tests [\#1837](https://github.com/kivy/python-for-android/pull/1837) ([inclement](https://github.com/inclement))
+- 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))
+- Update Kivy version to 1.11.0 [\#1832](https://github.com/kivy/python-for-android/pull/1832) ([inclement](https://github.com/inclement))
+- \[R.I.P.\] Remove `python2legacy` and `hospython2legacy` [\#1831](https://github.com/kivy/python-for-android/pull/1831) ([opacam](https://github.com/opacam))
+- Update permissions.py [\#1828](https://github.com/kivy/python-for-android/pull/1828) ([tamano123](https://github.com/tamano123))
+- Add a Pillow test app [\#1826](https://github.com/kivy/python-for-android/pull/1826) ([opacam](https://github.com/opacam))
+- Rework of freetype/harfbuzz recipes [\#1825](https://github.com/kivy/python-for-android/pull/1825) ([opacam](https://github.com/opacam))
+- Added a matplotlib recipe [\#1822](https://github.com/kivy/python-for-android/pull/1822) ([inclement](https://github.com/inclement))
+- 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))
+- Fix `CI` errors with latest tox [\#1820](https://github.com/kivy/python-for-android/pull/1820) ([opacam](https://github.com/opacam))
+- Fixed build file for service\_only bootstrap [\#1819](https://github.com/kivy/python-for-android/pull/1819) ([devos50](https://github.com/devos50))
+- enable IPv6 for packaged python3 [\#1815](https://github.com/kivy/python-for-android/pull/1815) ([SomberNight](https://github.com/SomberNight))
+- Update pymunk to v5.5.0 [\#1814](https://github.com/kivy/python-for-android/pull/1814) ([viblo](https://github.com/viblo))
+- Fixes ffpyplayer build [\#1813](https://github.com/kivy/python-for-android/pull/1813) ([misl6](https://github.com/misl6))
+- 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))
+- Fixed doc links to plyer and pyjnius [\#1806](https://github.com/kivy/python-for-android/pull/1806) ([inclement](https://github.com/inclement))
+- Fixed libzqm recipe linking issues [\#1803](https://github.com/kivy/python-for-android/pull/1803) ([hpsaturn](https://github.com/hpsaturn))
+- Update recipes.rst [\#1799](https://github.com/kivy/python-for-android/pull/1799) ([FunmiKesa](https://github.com/FunmiKesa))
+- Update recipes.rst [\#1798](https://github.com/kivy/python-for-android/pull/1798) ([FunmiKesa](https://github.com/FunmiKesa))
+- Bumps to setuptools==40.9.0 [\#1795](https://github.com/kivy/python-for-android/pull/1795) ([AndreMiras](https://github.com/AndreMiras))
+- Fix sqlalchemy recipe [\#1794](https://github.com/kivy/python-for-android/pull/1794) ([sduenasg](https://github.com/sduenasg))
+- Move ffmpeg download url to github repo [\#1791](https://github.com/kivy/python-for-android/pull/1791) ([misl6](https://github.com/misl6))
+- Ignores Python 2 import\_recipe\(\) warnings [\#1789](https://github.com/kivy/python-for-android/pull/1789) ([AndreMiras](https://github.com/AndreMiras))
+- \[kivy\] updated to 44a8a6f [\#1777](https://github.com/kivy/python-for-android/pull/1777) ([goffi-contrib](https://github.com/goffi-contrib))
+- Fix outdated PySDL2 version and non-PyPI install source [\#1775](https://github.com/kivy/python-for-android/pull/1775) ([etc0de](https://github.com/etc0de))
+- Properly search native lib dir in ctypes, fixes \#1770 [\#1772](https://github.com/kivy/python-for-android/pull/1772) ([etc0de](https://github.com/etc0de))
+- \[kivy\] updated recipe to c4d6894 revision [\#1766](https://github.com/kivy/python-for-android/pull/1766) ([goffi-contrib](https://github.com/goffi-contrib))
+- Fixes libglob recipe, closes \#1399 [\#1765](https://github.com/kivy/python-for-android/pull/1765) ([AndreMiras](https://github.com/AndreMiras))
+- \[doubleratchet\] removed this recipe [\#1762](https://github.com/kivy/python-for-android/pull/1762) ([goffi-contrib](https://github.com/goffi-contrib))
+- \[cryptography\] updated to 2.6.1 [\#1761](https://github.com/kivy/python-for-android/pull/1761) ([goffi-contrib](https://github.com/goffi-contrib))
+- \[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))
+- \[omemo\] updated to v0.10.4 [\#1758](https://github.com/kivy/python-for-android/pull/1758) ([goffi-contrib](https://github.com/goffi-contrib))
+- \[protobuf\_cpp\] fixed runtime issues [\#1757](https://github.com/kivy/python-for-android/pull/1757) ([goffi-contrib](https://github.com/goffi-contrib))
+- \[xeddsa\] fixed shared library copying [\#1756](https://github.com/kivy/python-for-android/pull/1756) ([goffi-contrib](https://github.com/goffi-contrib))
+- remove call to decode on str in \_android.pyx [\#1752](https://github.com/kivy/python-for-android/pull/1752) ([tshirtman](https://github.com/tshirtman))
+- \[libxml2\] fixed crash on missing lzma.h [\#1751](https://github.com/kivy/python-for-android/pull/1751) ([goffi-contrib](https://github.com/goffi-contrib))
+- Remove string decoding in \_android.pyx for Python3 compatibility [\#1748](https://github.com/kivy/python-for-android/pull/1748) ([darosior](https://github.com/darosior))
+- decode JAVA\_NAMESPACE in \_android.pyx [\#1747](https://github.com/kivy/python-for-android/pull/1747) ([tshirtman](https://github.com/tshirtman))
+- 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))
+- 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))
+- Updated sdl2\_mixer version to 2.0.4 [\#1742](https://github.com/kivy/python-for-android/pull/1742) ([misl6](https://github.com/misl6))
+- Requests runtime permissions list, fixes \#1704 [\#1741](https://github.com/kivy/python-for-android/pull/1741) ([AndreMiras](https://github.com/AndreMiras))
+- fix groestlcoin\_hash recipe [\#1738](https://github.com/kivy/python-for-android/pull/1738) ([tshirtman](https://github.com/tshirtman))
+- Fixes object is not subscriptable error, refs \#1733 [\#1734](https://github.com/kivy/python-for-android/pull/1734) ([AndreMiras](https://github.com/AndreMiras))
+- Add missing arch to version code generator. [\#1733](https://github.com/kivy/python-for-android/pull/1733) ([OptimusGREEN](https://github.com/OptimusGREEN))
+- Fix import in 'Using a PythonRecipe' doc [\#1731](https://github.com/kivy/python-for-android/pull/1731) ([b3b](https://github.com/b3b))
+- Removed unnecessary genericndkbuild dependency from the flask recipe [\#1729](https://github.com/kivy/python-for-android/pull/1729) ([inclement](https://github.com/inclement))
+- Add ci\_mode to disable download progress [\#1727](https://github.com/kivy/python-for-android/pull/1727) ([mkg20001](https://github.com/mkg20001))
+- Zworkb services [\#1725](https://github.com/kivy/python-for-android/pull/1725) ([zworkb](https://github.com/zworkb))
+- Fixes psycopg2 lib install dir, closes \#1711 [\#1723](https://github.com/kivy/python-for-android/pull/1723) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- 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))
+- 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))
+- Fix setenv\(\) of non-SDL2 bootstrap [\#1714](https://github.com/kivy/python-for-android/pull/1714) ([j-devel](https://github.com/j-devel))
+- Update enum34, pyasn1and pyopenssl versions. [\#1713](https://github.com/kivy/python-for-android/pull/1713) ([rnixx](https://github.com/rnixx))
+- Added warning for arguments containing carriage returns. This happens… [\#1712](https://github.com/kivy/python-for-android/pull/1712) ([Aralox](https://github.com/Aralox))
+- Fix loadLibraries\(\) failing for 64bit arch [\#1701](https://github.com/kivy/python-for-android/pull/1701) ([j-devel](https://github.com/j-devel))
+- Fixes pyleveldb recipe [\#1699](https://github.com/kivy/python-for-android/pull/1699) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- change dependency from jdk7 to jdk8 [\#1695](https://github.com/kivy/python-for-android/pull/1695) ([Meteorix](https://github.com/Meteorix))
+- 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))
+- 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))
+- Use nativeSetenv\(\) provided by SDL2 and cleanups [\#1690](https://github.com/kivy/python-for-android/pull/1690) ([j-devel](https://github.com/j-devel))
+- Rename misguided shadowing --blacklist to --blacklist-requirements [\#1689](https://github.com/kivy/python-for-android/pull/1689) ([etc0de](https://github.com/etc0de))
+- Deleted kivent recipes from constants.py [\#1686](https://github.com/kivy/python-for-android/pull/1686) ([inclement](https://github.com/inclement))
+- Downgrade to pycryptodome==3.6.3 [\#1685](https://github.com/kivy/python-for-android/pull/1685) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- 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))
+- Origin [\#1681](https://github.com/kivy/python-for-android/pull/1681) ([strubbi77](https://github.com/strubbi77))
+- Delete kivent recipes [\#1680](https://github.com/kivy/python-for-android/pull/1680) ([inclement](https://github.com/inclement))
+- Fix zope\_interface and add python3 compatibility [\#1677](https://github.com/kivy/python-for-android/pull/1677) ([opacam](https://github.com/opacam))
+- Recipe class unit tests [\#1676](https://github.com/kivy/python-for-android/pull/1676) ([AndreMiras](https://github.com/AndreMiras))
+- Bumps netiffaces version & removes from broken list, refs \#1539 [\#1675](https://github.com/kivy/python-for-android/pull/1675) ([AndreMiras](https://github.com/AndreMiras))
+- tox update and linter fixes [\#1674](https://github.com/kivy/python-for-android/pull/1674) ([AndreMiras](https://github.com/AndreMiras))
+- tox update and linter fixes [\#1673](https://github.com/kivy/python-for-android/pull/1673) ([AndreMiras](https://github.com/AndreMiras))
+- Delete the pygame bootstrap [\#1670](https://github.com/kivy/python-for-android/pull/1670) ([inclement](https://github.com/inclement))
+- Fix various issues with graphs and recipes [\#1669](https://github.com/kivy/python-for-android/pull/1669) ([etc0de](https://github.com/etc0de))
+- 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))
+- s/README.rst/README.md/ refs \#1664 [\#1666](https://github.com/kivy/python-for-android/pull/1666) ([AndreMiras](https://github.com/AndreMiras))
+- Update and rename README.rst to README.md [\#1664](https://github.com/kivy/python-for-android/pull/1664) ([tito](https://github.com/tito))
+- 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))
+- 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))
+- Updated version to 0.7.1 [\#1660](https://github.com/kivy/python-for-android/pull/1660) ([inclement](https://github.com/inclement))
+- \[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))
+
+## [0.7.0](https://github.com/kivy/python-for-android/tree/0.7.0) (2019-02-01)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/0.6.0...0.7.0)
+
+**Fixed bugs:**
+
+- python3 + openssl compilation fail [\#1590](https://github.com/kivy/python-for-android/issues/1590)
+- Update conditional build to python3 [\#1485](https://github.com/kivy/python-for-android/issues/1485)
+- building apk failes \( python 3 \) [\#746](https://github.com/kivy/python-for-android/issues/746)
+
+**Closed issues:**
+
+- 'Orientation' and 'Fullscreen' settings in spec file: Possible issue. [\#1655](https://github.com/kivy/python-for-android/issues/1655)
+- cffi UnicodeEncodeError: 'ascii' codec can't encode character '\u2018' [\#1654](https://github.com/kivy/python-for-android/issues/1654)
+- Create an app for testing p4a builds on the device [\#1630](https://github.com/kivy/python-for-android/issues/1630)
+- Build crashes if NDK is installed system-wide without write permissions [\#1621](https://github.com/kivy/python-for-android/issues/1621)
+- libFFI recipe doesn't work with clang [\#1612](https://github.com/kivy/python-for-android/issues/1612)
+- How do I use this software? [\#1606](https://github.com/kivy/python-for-android/issues/1606)
+- no work [\#1599](https://github.com/kivy/python-for-android/issues/1599)
+- python2legacy - various warnings then no valid dependency graphs [\#1582](https://github.com/kivy/python-for-android/issues/1582)
+- pyjnius import crash / TypeError [\#1578](https://github.com/kivy/python-for-android/issues/1578)
+- reportlab broken, possibly using wrong python header or compiler flags with python 3? [\#1575](https://github.com/kivy/python-for-android/issues/1575)
+- p4a apk crash "IndexError: list index out of range" [\#1570](https://github.com/kivy/python-for-android/issues/1570)
+- Libffi build fail on Mac [\#1569](https://github.com/kivy/python-for-android/issues/1569)
+- All the python functions can run on kivy [\#1567](https://github.com/kivy/python-for-android/issues/1567)
+- --force-build is --force\_build in the docs [\#1565](https://github.com/kivy/python-for-android/issues/1565)
+- apk with sqlite3 python3 kivy No module named '\_sqlite3' [\#1564](https://github.com/kivy/python-for-android/issues/1564)
+- 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)
+- Minor exclude extensions code simplification [\#1560](https://github.com/kivy/python-for-android/issues/1560)
+- SSLError python 3.7.1 [\#1559](https://github.com/kivy/python-for-android/issues/1559)
+- 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)
+- 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)
+- raise exc [\#1540](https://github.com/kivy/python-for-android/issues/1540)
+- raise exc sh.ErrorReturnCode\_2: [\#1538](https://github.com/kivy/python-for-android/issues/1538)
+- How to build other ABI versions of "libcrypto.so" and "libssl.so"? [\#1536](https://github.com/kivy/python-for-android/issues/1536)
+- How to build other versions of ABI? [\#1535](https://github.com/kivy/python-for-android/issues/1535)
+- No module named sh [\#1531](https://github.com/kivy/python-for-android/issues/1531)
+- p4a crash "Couldn't find the built APK" [\#1530](https://github.com/kivy/python-for-android/issues/1530)
+- UnicodeEncodeError in logger.py [\#1529](https://github.com/kivy/python-for-android/issues/1529)
+- 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)
+- ValueError: storage dir path cannot contain spaces, please specify a path with --storage-dir [\#1526](https://github.com/kivy/python-for-android/issues/1526)
+- Build fails: Could not find com.android.tools.lint:lint-gradle:26.1.4 [\#1520](https://github.com/kivy/python-for-android/issues/1520)
+- App doesn't support pause mode' when using on\_pause method [\#1518](https://github.com/kivy/python-for-android/issues/1518)
+- Comprehensive list of broken python3 recipes [\#1514](https://github.com/kivy/python-for-android/issues/1514)
+- BUILD FAILURE: No main.py\(o\) found in your app directory. [\#1510](https://github.com/kivy/python-for-android/issues/1510)
+- 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)
+- Bootstrap detection for "service\_only" and "webview" is broken - always picks sdl2 [\#1508](https://github.com/kivy/python-for-android/issues/1508)
+- 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)
+- --requirements=android gives crash [\#1504](https://github.com/kivy/python-for-android/issues/1504)
+- pyjnius build failure with NDK r17c, NDK 21, SDK 28 [\#1502](https://github.com/kivy/python-for-android/issues/1502)
+- Need libpython3.7m.so.1.0 in android phone [\#1501](https://github.com/kivy/python-for-android/issues/1501)
+- Tech debt: webview, pygame and service\_only bootstraps are hardwired to Python 2.7 [\#1497](https://github.com/kivy/python-for-android/issues/1497)
+- ImportError: No module named android [\#1492](https://github.com/kivy/python-for-android/issues/1492)
+- p4a --requirements ignores absolute folder paths [\#1487](https://github.com/kivy/python-for-android/issues/1487)
+- Set the api level from 19 to 28 failed [\#1482](https://github.com/kivy/python-for-android/issues/1482)
+- libxml2 build broken on latest p4a master with python 3 [\#1479](https://github.com/kivy/python-for-android/issues/1479)
+- working: make: \*\*\* \[Makefile\:426\: sharedmods\] Error 139 [\#1474](https://github.com/kivy/python-for-android/issues/1474)
+- .pxd files of dependency targeted recipe'd library not found [\#1473](https://github.com/kivy/python-for-android/issues/1473)
+- Python 3 recipe follow up issues [\#1455](https://github.com/kivy/python-for-android/issues/1455)
+- Python-4-Android NumPy error: 'struct lconv' has no member named 'decimal\_point' [\#1450](https://github.com/kivy/python-for-android/issues/1450)
+- Update to latest SDL required for proper key handling [\#1449](https://github.com/kivy/python-for-android/issues/1449)
+- socket.getaddrinfo appears to be completely broken, all name resolutions fail [\#1447](https://github.com/kivy/python-for-android/issues/1447)
+- lxml recipe doesn't work with Python 3 [\#1445](https://github.com/kivy/python-for-android/issues/1445)
+- C Compiler can not create executables [\#1436](https://github.com/kivy/python-for-android/issues/1436)
+- Unable to create APK [\#1434](https://github.com/kivy/python-for-android/issues/1434)
+- 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)
+- AndroidManifest.xml.tmpl should set screenLayout & smallScreenSize [\#1422](https://github.com/kivy/python-for-android/issues/1422)
+- pyjnius really should be version pinned. [\#1415](https://github.com/kivy/python-for-android/issues/1415)
+- p4a git master pyjnius build breaks [\#1414](https://github.com/kivy/python-for-android/issues/1414)
+- enaml recipe compilation fails [\#1409](https://github.com/kivy/python-for-android/issues/1409)
+- Cython projects that don't need any special option should work without a CythonRecipe [\#1406](https://github.com/kivy/python-for-android/issues/1406)
+- libpq recipe compilation fails [\#1405](https://github.com/kivy/python-for-android/issues/1405)
+- cryptography + python3crystax fails [\#1404](https://github.com/kivy/python-for-android/issues/1404)
+- Comprehensive list of broken recipes [\#1402](https://github.com/kivy/python-for-android/issues/1402)
+- ifaddrs compilation error [\#1398](https://github.com/kivy/python-for-android/issues/1398)
+- 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)
+- error: kivy/graphics/texture.c: No such file or directory [\#1384](https://github.com/kivy/python-for-android/issues/1384)
+- Investigate conditional builds [\#1382](https://github.com/kivy/python-for-android/issues/1382)
+- Unit test recipes \(reportlab to begin with\) [\#1380](https://github.com/kivy/python-for-android/issues/1380)
+- There is insufficient memory for the Java Runtime Environment to continue [\#1373](https://github.com/kivy/python-for-android/issues/1373)
+- SSL/TLS is broken with Python 3: ImportError: missing module \_ssl [\#1372](https://github.com/kivy/python-for-android/issues/1372)
+- Buildozer fail to build numpy recipe [\#1369](https://github.com/kivy/python-for-android/issues/1369)
+- Buildozer weird error\(Still trying to use Kivy only\) [\#1368](https://github.com/kivy/python-for-android/issues/1368)
+- Error when trying to use numpy \(Broken Toolchain\) [\#1367](https://github.com/kivy/python-for-android/issues/1367)
+- 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)
+- Migrate away from ndk\_build? [\#1362](https://github.com/kivy/python-for-android/issues/1362)
+- Fix/clean-up LDSHARED [\#1360](https://github.com/kivy/python-for-android/issues/1360)
+- buildozer error: no module named kivy [\#1354](https://github.com/kivy/python-for-android/issues/1354)
+- Spurious nullpointer crash on app resume [\#1353](https://github.com/kivy/python-for-android/issues/1353)
+- Docs say ANDROIDAPI=19 sets minimum API level, but it sets target API level [\#1352](https://github.com/kivy/python-for-android/issues/1352)
+- Bug or support request? [\#1346](https://github.com/kivy/python-for-android/issues/1346)
+- Issue with not finding JNI and nativeSetEnv in SDLActivity [\#1344](https://github.com/kivy/python-for-android/issues/1344)
+- 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)
+- python2: jpeg recipe broken due to missing libcutils [\#1341](https://github.com/kivy/python-for-android/issues/1341)
+- Orientation change causes bogus SDL\_QUIT and SDL\_APP\_TERMINATING events [\#1338](https://github.com/kivy/python-for-android/issues/1338)
+- ctypes.util.find\_library doesn't work with python 3 [\#1337](https://github.com/kivy/python-for-android/issues/1337)
+- 'import lzma' fails with Python 3 [\#1336](https://github.com/kivy/python-for-android/issues/1336)
+- 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)
+- Build failed [\#1333](https://github.com/kivy/python-for-android/issues/1333)
+- Auto-close awaiting-reply labeled issues [\#1331](https://github.com/kivy/python-for-android/issues/1331)
+- App crashes when sending POST request http [\#1329](https://github.com/kivy/python-for-android/issues/1329)
+- kivy build error with python3crystax [\#1328](https://github.com/kivy/python-for-android/issues/1328)
+- No "pil" or "pillow" avaliable for Python 3 [\#1326](https://github.com/kivy/python-for-android/issues/1326)
+- pillow fails on import [\#1325](https://github.com/kivy/python-for-android/issues/1325)
+- Unavoidable PySDL2 crash on app resume [\#1323](https://github.com/kivy/python-for-android/issues/1323)
+- 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)
+- 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)
+- Crystax NDK size is larger than Android Studio + SDK + \(regular\) NDK + ... combined [\#1319](https://github.com/kivy/python-for-android/issues/1319)
+- Generated md5sum does not match expected md5sum for sdl2 recipe [\#1318](https://github.com/kivy/python-for-android/issues/1318)
+- 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)
+- CMake error with OpenCV [\#1315](https://github.com/kivy/python-for-android/issues/1315)
+- 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)
+- 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)
+- 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)
+- Build error to import shapely \(libgeos\) [\#1311](https://github.com/kivy/python-for-android/issues/1311)
+- "/data/user/0/org.gmail.gmail/files/app/libpymodules.so" not found [\#1309](https://github.com/kivy/python-for-android/issues/1309)
+- numpy Python3/Crystax broken toolchain can't link a simple C program [\#1303](https://github.com/kivy/python-for-android/issues/1303)
+- Screen Rotation and Re-layout [\#1302](https://github.com/kivy/python-for-android/issues/1302)
+- IOError: \[Errno socket error\] \[Errno 104\] Connection reset by peer [\#1301](https://github.com/kivy/python-for-android/issues/1301)
+- Python2 Build fails with make: \*\*\* \[Makefile\:426\: sharedmods\] Error 139 [\#1297](https://github.com/kivy/python-for-android/issues/1297)
+- ffmpeg breaks buildozer android debug compilation process. [\#1294](https://github.com/kivy/python-for-android/issues/1294)
+- Android: python startup complaining about missing hashlib functions [\#1293](https://github.com/kivy/python-for-android/issues/1293)
+- sh.CommandNotFound: ndk\_build [\#1292](https://github.com/kivy/python-for-android/issues/1292)
+- SDL Error: ... could not load library "libpython2.7.so" ... on Android 4.2.2 [\#1290](https://github.com/kivy/python-for-android/issues/1290)
+- Facing issues in making webbrowser.open\(url\) work [\#1287](https://github.com/kivy/python-for-android/issues/1287)
+- Travis download caching [\#1280](https://github.com/kivy/python-for-android/issues/1280)
+- 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)
+- Little typo [\#1275](https://github.com/kivy/python-for-android/issues/1275)
+- Fix numpy x86 build using \#1252 [\#1274](https://github.com/kivy/python-for-android/issues/1274)
+- Some phones don't allow access to /sdcard [\#1272](https://github.com/kivy/python-for-android/issues/1272)
+- 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)
+- Sometimes sdl2 - UnicodeDecodeError: 'utf-8' codec can't decode byte 0x98 [\#1270](https://github.com/kivy/python-for-android/issues/1270)
+- App crash when connecting to mysql [\#1269](https://github.com/kivy/python-for-android/issues/1269)
+- \[wishlist\] Android launcher: Please build with Python 3 [\#1268](https://github.com/kivy/python-for-android/issues/1268)
+- 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)
+- Why an error occurs 'python-for-android cannot continue; aborting'? [\#1266](https://github.com/kivy/python-for-android/issues/1266)
+- Internet connection impossible with kivy app on android [\#1265](https://github.com/kivy/python-for-android/issues/1265)
+- python-for-android recipe tests [\#1263](https://github.com/kivy/python-for-android/issues/1263)
+- 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)
+- fail to build application ERROR 'WindowInfoX11' is not a type identifier [\#1259](https://github.com/kivy/python-for-android/issues/1259)
+- Buildozer command failed [\#1258](https://github.com/kivy/python-for-android/issues/1258)
+- IOError: \[Errno 2\] No such file or directory: 'src/main/assets/private.mp3' [\#1257](https://github.com/kivy/python-for-android/issues/1257)
+- Prebuilt python does not contain binaries for any architecture. [\#1254](https://github.com/kivy/python-for-android/issues/1254)
+- Didn't find any valid dependency graphs. - Flask and websocket-client [\#1253](https://github.com/kivy/python-for-android/issues/1253)
+- assertion PyBytes\_Check failed [\#1247](https://github.com/kivy/python-for-android/issues/1247)
+- Issue of AttributeError [\#1246](https://github.com/kivy/python-for-android/issues/1246)
+- Python3 + greenlet install issue [\#1245](https://github.com/kivy/python-for-android/issues/1245)
+- Confusing / Outdated Bootstraps [\#1244](https://github.com/kivy/python-for-android/issues/1244)
+- 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)
+- APK Immediately Closes After Opening in Debug, Release, and Zipaligned & Signed Versions [\#1242](https://github.com/kivy/python-for-android/issues/1242)
+- Custom Recipes For Pandas, Matplotlib and Statsmodels [\#1241](https://github.com/kivy/python-for-android/issues/1241)
+- WebView.setWebContentsDebuggingEnabled [\#1240](https://github.com/kivy/python-for-android/issues/1240)
+- amreabi-v7a build cannot find SDL\_GetTicks\(\) [\#1239](https://github.com/kivy/python-for-android/issues/1239)
+- x86 inline assembly fails to build [\#1238](https://github.com/kivy/python-for-android/issues/1238)
+- Can't compile dependency in 32bit on 64bit system [\#1237](https://github.com/kivy/python-for-android/issues/1237)
+- Cannot import name 'uname' on Windows [\#1234](https://github.com/kivy/python-for-android/issues/1234)
+- Uses Arm builds for x86, if Arm builds already exist [\#1233](https://github.com/kivy/python-for-android/issues/1233)
+- The sh Python module could not be found [\#1232](https://github.com/kivy/python-for-android/issues/1232)
+- Issue with Android API 23+ [\#1231](https://github.com/kivy/python-for-android/issues/1231)
+- Failed to build application: 'WindowInfoX11' is not a type identifier [\#1230](https://github.com/kivy/python-for-android/issues/1230)
+- Missing arm-linux-androideabi-gcc [\#1229](https://github.com/kivy/python-for-android/issues/1229)
+- Android build issues: raise CommandNotFound\(path\) [\#1228](https://github.com/kivy/python-for-android/issues/1228)
+- TextInput display text only when suggestion validate on Asus ZenPhone3 [\#1227](https://github.com/kivy/python-for-android/issues/1227)
+- on\_stop not called on Android [\#1226](https://github.com/kivy/python-for-android/issues/1226)
+- The python3crystax recipe can only be built when using the CrystaX NDK. [\#1225](https://github.com/kivy/python-for-android/issues/1225)
+- Didn't find any valid dependency graphs. [\#1222](https://github.com/kivy/python-for-android/issues/1222)
+- Google requiring API Target 26 in Aug/Nov 2018 [\#1219](https://github.com/kivy/python-for-android/issues/1219)
+- p4a cant find android sdk [\#1218](https://github.com/kivy/python-for-android/issues/1218)
+- 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)
+- Buildozer android application crashs on android [\#1215](https://github.com/kivy/python-for-android/issues/1215)
+- Multiple issues with latest Android SDK [\#1212](https://github.com/kivy/python-for-android/issues/1212)
+- sdkmanager doesnt exist [\#1210](https://github.com/kivy/python-for-android/issues/1210)
+- add-jar doesn't work [\#1208](https://github.com/kivy/python-for-android/issues/1208)
+- Kivy not working on Sony devices [\#1206](https://github.com/kivy/python-for-android/issues/1206)
+- sqlite pre-populated database not being found [\#1203](https://github.com/kivy/python-for-android/issues/1203)
+- Try python3.7 with Google NDK [\#1202](https://github.com/kivy/python-for-android/issues/1202)
+- commit 3534a761 [\#1200](https://github.com/kivy/python-for-android/issues/1200)
+- Kivy basic button program doesnt work [\#1199](https://github.com/kivy/python-for-android/issues/1199)
+- Error Pythonforandroid.toolchain -m [\#1196](https://github.com/kivy/python-for-android/issues/1196)
+- assert keyword do not work [\#1193](https://github.com/kivy/python-for-android/issues/1193)
+- macOS Hight Siera installation issue [\#1192](https://github.com/kivy/python-for-android/issues/1192)
+- failed to setup p4a on ubuntu \(multiple issues\) [\#1191](https://github.com/kivy/python-for-android/issues/1191)
+- Cryptography woes: Can we freshen up our Python version... [\#1190](https://github.com/kivy/python-for-android/issues/1190)
+- Kivy crashes immediately on start, on Sony devices [\#1188](https://github.com/kivy/python-for-android/issues/1188)
+- UnicodeDecodeError [\#1187](https://github.com/kivy/python-for-android/issues/1187)
+- \[INFO\]: Building with ant, as no gradle executable detected [\#1186](https://github.com/kivy/python-for-android/issues/1186)
+- Local recipes can not be patched any longer [\#1185](https://github.com/kivy/python-for-android/issues/1185)
+- Cymunk build fail on python3crystax [\#1184](https://github.com/kivy/python-for-android/issues/1184)
+- p4a doesn't handle runtime permissions [\#1183](https://github.com/kivy/python-for-android/issues/1183)
+- Android app freeze on screen rotation \(again?\) [\#1179](https://github.com/kivy/python-for-android/issues/1179)
+- custom java class [\#1177](https://github.com/kivy/python-for-android/issues/1177)
+- Dockerfile [\#1175](https://github.com/kivy/python-for-android/issues/1175)
+- 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)
+- The webview bootstrap does not support gradle [\#1172](https://github.com/kivy/python-for-android/issues/1172)
+- 0.6 release checklist [\#1170](https://github.com/kivy/python-for-android/issues/1170)
+- python 2.7 compile with NDK 15c [\#1169](https://github.com/kivy/python-for-android/issues/1169)
+- Reopen running instance instead of starting a new one upon tapping app icon [\#1161](https://github.com/kivy/python-for-android/issues/1161)
+- python3 incompatibility [\#1154](https://github.com/kivy/python-for-android/issues/1154)
+- ffi.h: No such file or directory \(solutions included\) [\#1148](https://github.com/kivy/python-for-android/issues/1148)
+- After building FFMpeg recipe, I still am not able to do ffmpeg -v [\#1146](https://github.com/kivy/python-for-android/issues/1146)
+- Kivy/Buildozer/Psycopg2 [\#1144](https://github.com/kivy/python-for-android/issues/1144)
+- SDL Error: Error Could not load any libpythonXXX.so [\#1142](https://github.com/kivy/python-for-android/issues/1142)
+- Can't build numpy with current master, python 2, NDK 15 [\#1141](https://github.com/kivy/python-for-android/issues/1141)
+- pyopenssl cryptography dependence [\#1127](https://github.com/kivy/python-for-android/issues/1127)
+- Check if SDL2 libraries are up to date [\#1126](https://github.com/kivy/python-for-android/issues/1126)
+- bind recipes to well defined versions [\#1115](https://github.com/kivy/python-for-android/issues/1115)
+- pil and pillow modules for python3 [\#1114](https://github.com/kivy/python-for-android/issues/1114)
+- Kivy python android build issue? [\#1110](https://github.com/kivy/python-for-android/issues/1110)
+- simple flask app on android fails to start [\#1108](https://github.com/kivy/python-for-android/issues/1108)
+- "crystax\_python does not exist" with python3crystax [\#1105](https://github.com/kivy/python-for-android/issues/1105)
+- Running on Android 4.0 doesn't work when building for target api 19 [\#1104](https://github.com/kivy/python-for-android/issues/1104)
+- Can't type anything into textinput using new toolchain [\#1102](https://github.com/kivy/python-for-android/issues/1102)
+- "android" recipe isn't compatible with Python 3 [\#1093](https://github.com/kivy/python-for-android/issues/1093)
+- Recipe does not exist: matplotlib [\#1090](https://github.com/kivy/python-for-android/issues/1090)
+- Django App is not running. Web View does not load it [\#1083](https://github.com/kivy/python-for-android/issues/1083)
+- 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)
+- Numpy recipe broken \(atlas, blas, lapack, -lcrystax\) [\#1074](https://github.com/kivy/python-for-android/issues/1074)
+- requests module not compiling in buildozer when used with Python3crystax [\#1072](https://github.com/kivy/python-for-android/issues/1072)
+- ImportError: No module named audioop [\#1067](https://github.com/kivy/python-for-android/issues/1067)
+- sqlite3 not working with android\_new [\#1053](https://github.com/kivy/python-for-android/issues/1053)
+- dlopen failed: python2.7/site-packages/grpc/\_cython/cygrpc.so not 32-bit: 2 [\#1052](https://github.com/kivy/python-for-android/issues/1052)
+- Cant start service app [\#1049](https://github.com/kivy/python-for-android/issues/1049)
+- Cannot build APK with python3crystax and flask - conflicting dependencies [\#1041](https://github.com/kivy/python-for-android/issues/1041)
+- Slow build process since sh 1.12.5 [\#1038](https://github.com/kivy/python-for-android/issues/1038)
+- Python3 + PyYaml conflict [\#1031](https://github.com/kivy/python-for-android/issues/1031)
+- Can't write ti SD-card on Android 6.0.1 [\#1024](https://github.com/kivy/python-for-android/issues/1024)
+- pygame\_sdl2 compile failure \# include \<iconv.h\> [\#1023](https://github.com/kivy/python-for-android/issues/1023)
+- Build error on Mac: no archive symbol table \(run ranlib\) [\#1012](https://github.com/kivy/python-for-android/issues/1012)
+- Shouldn't P4A Raise Exception On User File Having Syntax Error [\#1009](https://github.com/kivy/python-for-android/issues/1009)
+- jnius is not working with webview bootstrap [\#1003](https://github.com/kivy/python-for-android/issues/1003)
+- Built APK fails with ImportError: dlopen failed on \_clock.so [\#998](https://github.com/kivy/python-for-android/issues/998)
+- apk not build using crystax NDK [\#992](https://github.com/kivy/python-for-android/issues/992)
+- 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)
+- Unpacking and copying app contents causes app to appear hung [\#983](https://github.com/kivy/python-for-android/issues/983)
+- kivy app crashing on launch [\#982](https://github.com/kivy/python-for-android/issues/982)
+- Android Emulator support [\#979](https://github.com/kivy/python-for-android/issues/979)
+- Kivy with SDL2 bootstrap crashes on pausing if app doesn't support pause mode [\#978](https://github.com/kivy/python-for-android/issues/978)
+- sqlite3 recipe not working with new toolchain [\#977](https://github.com/kivy/python-for-android/issues/977)
+- lxml is needed in new toolchain [\#976](https://github.com/kivy/python-for-android/issues/976)
+- P4A wants to start "ant" without using full SDK path [\#974](https://github.com/kivy/python-for-android/issues/974)
+- API automatic lookup doesn't use available SDK API [\#973](https://github.com/kivy/python-for-android/issues/973)
+- JNI ERROR \(app bug\): local reference table overflow \(max=512\) [\#971](https://github.com/kivy/python-for-android/issues/971)
+- Kivy with SDL2 bootstrap crushes on resuming in some cases [\#967](https://github.com/kivy/python-for-android/issues/967)
+- Could not ping localhost:5000 [\#961](https://github.com/kivy/python-for-android/issues/961)
+- Not a valid ELF executable [\#957](https://github.com/kivy/python-for-android/issues/957)
+- How to completely remove installed app? [\#953](https://github.com/kivy/python-for-android/issues/953)
+- ImportError android [\#943](https://github.com/kivy/python-for-android/issues/943)
+- Older android version can't load libraries properly [\#925](https://github.com/kivy/python-for-android/issues/925)
+- sed: 1: "Modules/Setup.local": invalid command code M [\#924](https://github.com/kivy/python-for-android/issues/924)
+- Python3: armeabi used to copy, but armeabi-v7a choosen [\#913](https://github.com/kivy/python-for-android/issues/913)
+- ImportError for sqlite3 [\#910](https://github.com/kivy/python-for-android/issues/910)
+- PyGame backend: error while using android.copy\_libs = 1 [\#888](https://github.com/kivy/python-for-android/issues/888)
+- pytz installation works, but requires user to make build folder manually [\#884](https://github.com/kivy/python-for-android/issues/884)
+- Numpy support w/ python3crystax [\#882](https://github.com/kivy/python-for-android/issues/882)
+- Scipy recipe [\#874](https://github.com/kivy/python-for-android/issues/874)
+- opencv recipe build error [\#871](https://github.com/kivy/python-for-android/issues/871)
+- Flask with Python3 does not seem to work. [\#870](https://github.com/kivy/python-for-android/issues/870)
+- p4a generates deprecated code under Android API 23 [\#864](https://github.com/kivy/python-for-android/issues/864)
+- Kivy builds failing [\#861](https://github.com/kivy/python-for-android/issues/861)
+- error when running an apk compiled with python3crystax [\#859](https://github.com/kivy/python-for-android/issues/859)
+- 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)
+- apk, built with openssl launch error: "libssl1.0.2h.so" not found [\#850](https://github.com/kivy/python-for-android/issues/850)
+- Can't install on Windows using pip [\#819](https://github.com/kivy/python-for-android/issues/819)
+- FFmpeg recipe broken [\#810](https://github.com/kivy/python-for-android/issues/810)
+- Todo: add rebuild-dist option [\#807](https://github.com/kivy/python-for-android/issues/807)
+- p4a create fails if cython is installed in ~/.local [\#771](https://github.com/kivy/python-for-android/issues/771)
+- Completely clean install of minimal application fails to launch on Android 6 [\#752](https://github.com/kivy/python-for-android/issues/752)
+- "NoBackendError: No backend available": Pyusb recipe for android [\#740](https://github.com/kivy/python-for-android/issues/740)
+- app crash on close [\#734](https://github.com/kivy/python-for-android/issues/734)
+- App crash when changing orientation [\#730](https://github.com/kivy/python-for-android/issues/730)
+- Default extraction of NDK version not compatible with most recent stable NDK release... [\#723](https://github.com/kivy/python-for-android/issues/723)
+- Enabling SSL for python3.5 using crystax [\#705](https://github.com/kivy/python-for-android/issues/705)
+- Need to set locale env variable for python3 package recipes [\#703](https://github.com/kivy/python-for-android/issues/703)
+- static jfieldID xxx not valid for class java.lang.Class\<org.renpy.android.PythonActivity [\#696](https://github.com/kivy/python-for-android/issues/696)
+- Python2 recipe for target 'libinstall' failed [\#690](https://github.com/kivy/python-for-android/issues/690)
+- Python2 recipe for target 'Parser/pgen.stamp' failed [\#689](https://github.com/kivy/python-for-android/issues/689)
+- Python2 recipe for target 'Lib/plat-linux4' failed [\#688](https://github.com/kivy/python-for-android/issues/688)
+- Pygame missing include & link path [\#687](https://github.com/kivy/python-for-android/issues/687)
+- Include NDK /sources/cxx-stl/gnu-libstdc++/ [\#670](https://github.com/kivy/python-for-android/issues/670)
+- LDFlags missing ' -lpython2.7' [\#668](https://github.com/kivy/python-for-android/issues/668)
+- Invalid option ccache: t [\#667](https://github.com/kivy/python-for-android/issues/667)
+- ImportError when the apk launches with SDL2 bootstrap, kivy and python3crystax [\#658](https://github.com/kivy/python-for-android/issues/658)
+- App crashes immediately after launching on Android [\#653](https://github.com/kivy/python-for-android/issues/653)
+- Use travis to automatically test builds on different platforms [\#625](https://github.com/kivy/python-for-android/issues/625)
+- AttributeError: module 'site' has no attribute 'getsitepackages' when running p4a create [\#610](https://github.com/kivy/python-for-android/issues/610)
+- The SDL2 bootstrap can only extract to app private dir [\#606](https://github.com/kivy/python-for-android/issues/606)
+- Can't load library "libPVROCL.so" [\#594](https://github.com/kivy/python-for-android/issues/594)
+- VERSION\_recipename env var functionality is not documented [\#589](https://github.com/kivy/python-for-android/issues/589)
+- ccache compilation issues? [\#550](https://github.com/kivy/python-for-android/issues/550)
+- Numpy recipe hardcodes arm [\#528](https://github.com/kivy/python-for-android/issues/528)
+- Recipes depending on external modules don't work [\#520](https://github.com/kivy/python-for-android/issues/520)
+- Touchscreen input with SDL2 bootstrap [\#516](https://github.com/kivy/python-for-android/issues/516)
+- PR \#408 needs applying to the new toolchain \(master\) [\#486](https://github.com/kivy/python-for-android/issues/486)
+- trouble compiling some modules with revamp [\#473](https://github.com/kivy/python-for-android/issues/473)
+- Foreground Kivy application stopped if phone locked via power button [\#462](https://github.com/kivy/python-for-android/issues/462)
+- Apk fails with rotation when using min api \<= 9 [\#436](https://github.com/kivy/python-for-android/issues/436)
+- Android app crashes on screen rotation if android.minapi \< 13 [\#430](https://github.com/kivy/python-for-android/issues/430)
+- PIL does not compile with freetype2 support [\#413](https://github.com/kivy/python-for-android/issues/413)
+- Android app crashing when ended and on\_stop is not executed [\#384](https://github.com/kivy/python-for-android/issues/384)
+- building harfbuzz with freetype support symbol errors [\#381](https://github.com/kivy/python-for-android/issues/381)
+- HOSTPYTHON Fails to compile module [\#377](https://github.com/kivy/python-for-android/issues/377)
+- p4a crashes under ARC [\#367](https://github.com/kivy/python-for-android/issues/367)
+- apk packages can't find standardlibrary libs if using external storage [\#363](https://github.com/kivy/python-for-android/issues/363)
+- TextInput error [\#357](https://github.com/kivy/python-for-android/issues/357)
+- Error In building kivy android on Mac OSX [\#341](https://github.com/kivy/python-for-android/issues/341)
+- Python 2.7.2 don't build cleanly with GCC ≥ 4.8 [\#321](https://github.com/kivy/python-for-android/issues/321)
+- import gevent -\> ImportError: cannot import name core [\#288](https://github.com/kivy/python-for-android/issues/288)
+- 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)
+- Use Debian's Python packages for ARM instead of cross-compiling? [\#242](https://github.com/kivy/python-for-android/issues/242)
+- Feature request: Possibility to choose the sensors' delay [\#207](https://github.com/kivy/python-for-android/issues/207)
+- Problems with posixpath [\#188](https://github.com/kivy/python-for-android/issues/188)
+- 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)
+- socket.AF\_UNIX is not supported [\#163](https://github.com/kivy/python-for-android/issues/163)
+- Recipe for pyzmq \($25 bounty\) \[$25\] [\#122](https://github.com/kivy/python-for-android/issues/122)
+
+**Merged pull requests:**
+
+- Updated version to 0.7.0 [\#1659](https://github.com/kivy/python-for-android/pull/1659) ([inclement](https://github.com/inclement))
+- Updates broken recipes list, refs \#1514 [\#1658](https://github.com/kivy/python-for-android/pull/1658) ([AndreMiras](https://github.com/AndreMiras))
+- Feature/ticket1654 cffi unicode encode error [\#1656](https://github.com/kivy/python-for-android/pull/1656) ([AndreMiras](https://github.com/AndreMiras))
+- Speed up Docker chown via COPY parameter [\#1652](https://github.com/kivy/python-for-android/pull/1652) ([AndreMiras](https://github.com/AndreMiras))
+- Speed up Python and NumPy compilation process [\#1651](https://github.com/kivy/python-for-android/pull/1651) ([AndreMiras](https://github.com/AndreMiras))
+- Fixes opencv compilation, fixes \#1313 [\#1650](https://github.com/kivy/python-for-android/pull/1650) ([AndreMiras](https://github.com/AndreMiras))
+- Remove unused variable in archs.py [\#1649](https://github.com/kivy/python-for-android/pull/1649) ([opacam](https://github.com/opacam))
+- Fix linux hardcoded entry in archs.py [\#1648](https://github.com/kivy/python-for-android/pull/1648) ([opacam](https://github.com/opacam))
+- Made the activity launch mode default to singleTask [\#1646](https://github.com/kivy/python-for-android/pull/1646) ([inclement](https://github.com/inclement))
+- Made build.py stop running if compileall failed [\#1645](https://github.com/kivy/python-for-android/pull/1645) ([inclement](https://github.com/inclement))
+- Retry on download hiccups, refs \#1306 [\#1643](https://github.com/kivy/python-for-android/pull/1643) ([AndreMiras](https://github.com/AndreMiras))
+- Set $LANG in PythonRecipe [\#1642](https://github.com/kivy/python-for-android/pull/1642) ([inclement](https://github.com/inclement))
+- 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))
+- 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))
+- Added app for on-device unit tests [\#1636](https://github.com/kivy/python-for-android/pull/1636) ([inclement](https://github.com/inclement))
+- 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))
+- Default Travis builds to Python3 [\#1634](https://github.com/kivy/python-for-android/pull/1634) ([AndreMiras](https://github.com/AndreMiras))
+- Fixes ifaddrs recipe, closes \#1398 [\#1633](https://github.com/kivy/python-for-android/pull/1633) ([AndreMiras](https://github.com/AndreMiras))
+- Do not verbose the "tar tf" command [\#1631](https://github.com/kivy/python-for-android/pull/1631) ([AndreMiras](https://github.com/AndreMiras))
+- psycopg2 recipe fixes and doc, fixes \#1405 [\#1629](https://github.com/kivy/python-for-android/pull/1629) ([AndreMiras](https://github.com/AndreMiras))
+- Use enaml {version} rather than master, fixes \#1409 [\#1628](https://github.com/kivy/python-for-android/pull/1628) ([AndreMiras](https://github.com/AndreMiras))
+- Clean-up LDSHARED, fixes \#1360 [\#1627](https://github.com/kivy/python-for-android/pull/1627) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- Fix librt recipe requires that NDK folder is writable [\#1623](https://github.com/kivy/python-for-android/pull/1623) ([etc0de](https://github.com/etc0de))
+- Update of Recipes for python3 test [\#1622](https://github.com/kivy/python-for-android/pull/1622) ([strubbi77](https://github.com/strubbi77))
+- - let cymunk also be built with python3 recipe [\#1620](https://github.com/kivy/python-for-android/pull/1620) ([maho](https://github.com/maho))
+- 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))
+- 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))
+- Made recipe graph resolution respect opt\_depends [\#1617](https://github.com/kivy/python-for-android/pull/1617) ([inclement](https://github.com/inclement))
+- 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))
+- 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))
+- 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))
+- Fix libffi/ctypes - wrong libffi headers when building python [\#1609](https://github.com/kivy/python-for-android/pull/1609) ([opacam](https://github.com/opacam))
+- 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))
+- Fix strip in bootstrap [\#1607](https://github.com/kivy/python-for-android/pull/1607) ([j-devel](https://github.com/j-devel))
+- Conditional build script fixes [\#1604](https://github.com/kivy/python-for-android/pull/1604) ([AndreMiras](https://github.com/AndreMiras))
+- Migrates greenlet to new python3 recipe, fixes \#1245 [\#1603](https://github.com/kivy/python-for-android/pull/1603) ([AndreMiras](https://github.com/AndreMiras))
+- Fix sdk license error for travis tests \(CI\) [\#1602](https://github.com/kivy/python-for-android/pull/1602) ([opacam](https://github.com/opacam))
+- \[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))
+- Migrates gevent to new python3 recipe [\#1600](https://github.com/kivy/python-for-android/pull/1600) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- Fix zeroconf compilation and grants python3 compatibility [\#1596](https://github.com/kivy/python-for-android/pull/1596) ([opacam](https://github.com/opacam))
+- Fix reportlab's recipe `crypt.h` error [\#1595](https://github.com/kivy/python-for-android/pull/1595) ([opacam](https://github.com/opacam))
+- 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))
+- Allow patching from any folder + fix pygame components issues [\#1592](https://github.com/kivy/python-for-android/pull/1592) ([opacam](https://github.com/opacam))
+- 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))
+- Minimal fixes to make pygame bootstrap work with python2legacy [\#1587](https://github.com/kivy/python-for-android/pull/1587) ([opacam](https://github.com/opacam))
+- 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))
+- \[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))
+- Move libffi to mainline repo [\#1584](https://github.com/kivy/python-for-android/pull/1584) ([opacam](https://github.com/opacam))
+- \[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))
+- fix missing gethostbyname\_r on Android 5.1 [\#1581](https://github.com/kivy/python-for-android/pull/1581) ([opacam](https://github.com/opacam))
+- \[WIP\] Rework libxml2, libxslt and lxml \(update versions\) [\#1580](https://github.com/kivy/python-for-android/pull/1580) ([opacam](https://github.com/opacam))
+- Fixes ffmpeg compilation w/ openssl 1.1.1 [\#1579](https://github.com/kivy/python-for-android/pull/1579) ([misl6](https://github.com/misl6))
+- 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))
+- 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))
+- Rework for Pillow/pil recipes & update jpeg and png [\#1573](https://github.com/kivy/python-for-android/pull/1573) ([opacam](https://github.com/opacam))
+- Fix APP\_PLATFORM not properly passed in NDKRecipe [\#1572](https://github.com/kivy/python-for-android/pull/1572) ([etc0de](https://github.com/etc0de))
+- 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))
+- 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))
+- \[OMEMO\] updated omemo recipe [\#1566](https://github.com/kivy/python-for-android/pull/1566) ([goffi-contrib](https://github.com/goffi-contrib))
+- Render format string argument on BuildInterruptingException [\#1561](https://github.com/kivy/python-for-android/pull/1561) ([AndreMiras](https://github.com/AndreMiras))
+- \[WIP\]\[CORE UPDATE - PART XV\] Add encryption test app [\#1556](https://github.com/kivy/python-for-android/pull/1556) ([opacam](https://github.com/opacam))
+- \[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))
+- \[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))
+- \[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))
+- \[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))
+- \[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))
+- \[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))
+- \[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))
+- \[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))
+- \[CORE UPDATE - PART VI\] Fix scrypt [\#1547](https://github.com/kivy/python-for-android/pull/1547) ([opacam](https://github.com/opacam))
+- \[CORE UPDATE - PART V\] Fix pycrypto [\#1546](https://github.com/kivy/python-for-android/pull/1546) ([opacam](https://github.com/opacam))
+- \[CORE UPDATE - PART IV\] Fix cryptography+cffi [\#1545](https://github.com/kivy/python-for-android/pull/1545) ([opacam](https://github.com/opacam))
+- fix wrong conditional for build custom\_rules.tmpl.xml [\#1544](https://github.com/kivy/python-for-android/pull/1544) ([bit4bit](https://github.com/bit4bit))
+- \[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))
+- \[CORE UPDATE - PART I\] Refactor python recipes + openssl + sqlite3 [\#1537](https://github.com/kivy/python-for-android/pull/1537) ([opacam](https://github.com/opacam))
+- 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))
+- Use API 27 as new default for travis & docs [\#1532](https://github.com/kivy/python-for-android/pull/1532) ([etc0de](https://github.com/etc0de))
+- 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))
+- Unify build.py contents [\#1524](https://github.com/kivy/python-for-android/pull/1524) ([etc0de](https://github.com/etc0de))
+- Unify configChanges manifest entry and add missing values [\#1522](https://github.com/kivy/python-for-android/pull/1522) ([etc0de](https://github.com/etc0de))
+- Add google repository at allprojects [\#1521](https://github.com/kivy/python-for-android/pull/1521) ([wo01](https://github.com/wo01))
+- Fix bytes/unicode issues in android recipe [\#1516](https://github.com/kivy/python-for-android/pull/1516) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- Uses target python3 on conditional buids, fixes \#1485 [\#1515](https://github.com/kivy/python-for-android/pull/1515) ([AndreMiras](https://github.com/AndreMiras))
+- Updates websocket-client recipe, fixes \#1253 [\#1513](https://github.com/kivy/python-for-android/pull/1513) ([AndreMiras](https://github.com/AndreMiras))
+- 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))
+- Update gradle version [\#1507](https://github.com/kivy/python-for-android/pull/1507) ([opacam](https://github.com/opacam))
+- Fix libnacl recipe missing libsodium [\#1505](https://github.com/kivy/python-for-android/pull/1505) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- 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))
+- Unify start.c of all bootstraps to one file [\#1500](https://github.com/kivy/python-for-android/pull/1500) ([etc0de](https://github.com/etc0de))
+- Minor fixes to basic common bootstrap handling code [\#1499](https://github.com/kivy/python-for-android/pull/1499) ([etc0de](https://github.com/etc0de))
+- Rework common bootstrap area based on kollivier's work [\#1496](https://github.com/kivy/python-for-android/pull/1496) ([etc0de](https://github.com/etc0de))
+- Fixes audiostream recipe on Python3 [\#1495](https://github.com/kivy/python-for-android/pull/1495) ([misl6](https://github.com/misl6))
+- 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))
+- Make Cython work without recipe [\#1483](https://github.com/kivy/python-for-android/pull/1483) ([etc0de](https://github.com/etc0de))
+- 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))
+- Remove crystax docker and optimize Dockerfile [\#1471](https://github.com/kivy/python-for-android/pull/1471) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- Replaced many `exit(1)`s with exception raising [\#1468](https://github.com/kivy/python-for-android/pull/1468) ([inclement](https://github.com/inclement))
+- Add ctypes support for python3's recipe [\#1465](https://github.com/kivy/python-for-android/pull/1465) ([opacam](https://github.com/opacam))
+- Fix jpeg build for newer NDKs [\#1363](https://github.com/kivy/python-for-android/pull/1363) ([mkg20001](https://github.com/mkg20001))
+- Added sympy recipe [\#1236](https://github.com/kivy/python-for-android/pull/1236) ([inclement](https://github.com/inclement))
+- 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))
+- android\_new: fix force\_build option [\#1006](https://github.com/kivy/python-for-android/pull/1006) ([ZingBallyhoo](https://github.com/ZingBallyhoo))
+
+## [0.6.0](https://github.com/kivy/python-for-android/tree/0.6.0) (2017-11-25)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/0.5.3...0.6.0)
+
+**Closed issues:**
+
+- buildozer cannot download sdl2 ,,, help [\#1176](https://github.com/kivy/python-for-android/issues/1176)
+- \_multiprocessing [\#1168](https://github.com/kivy/python-for-android/issues/1168)
+- p4a: command not found [\#1167](https://github.com/kivy/python-for-android/issues/1167)
+- no module named tty [\#1165](https://github.com/kivy/python-for-android/issues/1165)
+- Openssl recipe crashes on x86 arch [\#1162](https://github.com/kivy/python-for-android/issues/1162)
+- Please help building the cffi recipe [\#1159](https://github.com/kivy/python-for-android/issues/1159)
+- Build failed for Numpy [\#1158](https://github.com/kivy/python-for-android/issues/1158)
+- Base: Failed to import "android" module. Could not remove android presplash. [\#1153](https://github.com/kivy/python-for-android/issues/1153)
+- --ndk\_ver cli option not working, but ANDROIDNDKVER does [\#1149](https://github.com/kivy/python-for-android/issues/1149)
+- lxml uses etree.so that throws an unexpected e\_machine error [\#1147](https://github.com/kivy/python-for-android/issues/1147)
+- Incompatible pyopenssl and cryptography versions [\#1138](https://github.com/kivy/python-for-android/issues/1138)
+- "undefined reference to 'OBJ\_obj2txt'" error on building openssl with NDK 15b [\#1135](https://github.com/kivy/python-for-android/issues/1135)
+- buildozer can't download hostpython2 [\#1132](https://github.com/kivy/python-for-android/issues/1132)
+- App crashing on startup- ImportError: dlopen failed: \_imaging.so is 64-bit [\#1131](https://github.com/kivy/python-for-android/issues/1131)
+- Error on building FFPYPLAYER for VideoPlayer Widget [\#1130](https://github.com/kivy/python-for-android/issues/1130)
+- Kivy App Crashes Immediately on Android [\#1128](https://github.com/kivy/python-for-android/issues/1128)
+- Remove the python3 and hostpython3 recipes [\#1125](https://github.com/kivy/python-for-android/issues/1125)
+- building with opencv show error [\#1124](https://github.com/kivy/python-for-android/issues/1124)
+- Webview loading animation doesn't work [\#1123](https://github.com/kivy/python-for-android/issues/1123)
+- Old toolchain is now deprecated [\#1122](https://github.com/kivy/python-for-android/issues/1122)
+- `pip install kivy` fails with `'../include/config.pxi' not found` [\#1120](https://github.com/kivy/python-for-android/issues/1120)
+- Suggestion: Allow a recipe to checkout a module from a local git repository [\#1119](https://github.com/kivy/python-for-android/issues/1119)
+- Please add a 'version' command to p4a [\#1116](https://github.com/kivy/python-for-android/issues/1116)
+- Websocket error: SSL not available [\#1107](https://github.com/kivy/python-for-android/issues/1107)
+- Pure python module as requirements aren't installed via pip [\#1098](https://github.com/kivy/python-for-android/issues/1098)
+- Current android sdk has removed the ant/build.xml [\#1069](https://github.com/kivy/python-for-android/issues/1069)
+- python-for-android 0.5 release checklist [\#1043](https://github.com/kivy/python-for-android/issues/1043)
+- Numpy recipes build fail [\#1040](https://github.com/kivy/python-for-android/issues/1040)
+- SDL2 launcher does not work with python3 [\#980](https://github.com/kivy/python-for-android/issues/980)
+- ffpyplayer can't be built with new toolchain [\#951](https://github.com/kivy/python-for-android/issues/951)
+- "Couldn't load python3.5m: findLibrary returned null" on older versions of Android [\#866](https://github.com/kivy/python-for-android/issues/866)
+- Problems in creation of recipe for zbar [\#854](https://github.com/kivy/python-for-android/issues/854)
+- freshly built old\_toolchain crashes with 'cannot locate symbol "\_Py\_asinh"' [\#487](https://github.com/kivy/python-for-android/issues/487)
+- The SDL2 bootstrap can't make a Kivy Launcher [\#468](https://github.com/kivy/python-for-android/issues/468)
+- Error: JAVA\_HOME is not defined correctly. [\#427](https://github.com/kivy/python-for-android/issues/427)
+- Compilation Error at ARM Environment [\#352](https://github.com/kivy/python-for-android/issues/352)
+- Build errors on OSX 10.10 [\#311](https://github.com/kivy/python-for-android/issues/311)
+- Easily reproducible crash accessing Context constants [\#235](https://github.com/kivy/python-for-android/issues/235)
+- AttributeError: 'java.io.File' object has no attribute 'endswith' [\#170](https://github.com/kivy/python-for-android/issues/170)
+- KeyEvent.getCharacters\(\) returns `null` instead of `KEYCODE_UNKNOWN` [\#142](https://github.com/kivy/python-for-android/issues/142)
+- Carousel: add\_widget after build\(\) [\#69](https://github.com/kivy/python-for-android/issues/69)
+- sound.length not returning correctly [\#67](https://github.com/kivy/python-for-android/issues/67)
+- KEYCODE\_HOME and KEYCODE\_POWER can't be trapped [\#43](https://github.com/kivy/python-for-android/issues/43)
+
+**Merged pull requests:**
+
+- Removed '-j5' from openssl make [\#1180](https://github.com/kivy/python-for-android/pull/1180) ([inclement](https://github.com/inclement))
+- add recipes for pyrxp & reportlab [\#1173](https://github.com/kivy/python-for-android/pull/1173) ([replabrobin](https://github.com/replabrobin))
+- Add the ndk platform libs dir during biglink [\#1171](https://github.com/kivy/python-for-android/pull/1171) ([inclement](https://github.com/inclement))
+- Update troubleshooting.rst [\#1164](https://github.com/kivy/python-for-android/pull/1164) ([AndreMiras](https://github.com/AndreMiras))
+- Fix OpenSSL recipe crashes on x86 [\#1163](https://github.com/kivy/python-for-android/pull/1163) ([gdyuldin](https://github.com/gdyuldin))
+- Cleaned up some old comments [\#1160](https://github.com/kivy/python-for-android/pull/1160) ([inclement](https://github.com/inclement))
+- The pypi python return http 403 error on http [\#1157](https://github.com/kivy/python-for-android/pull/1157) ([brvier](https://github.com/brvier))
+- Accept pypi fragmented URLs \(\#md5...\) [\#1155](https://github.com/kivy/python-for-android/pull/1155) ([wexi](https://github.com/wexi))
+- Add note about NDK version on 32-bit OS [\#1150](https://github.com/kivy/python-for-android/pull/1150) ([ghost](https://github.com/ghost))
+- Adds zbar \(and dependencies\) support, refs \#854 [\#1145](https://github.com/kivy/python-for-android/pull/1145) ([AndreMiras](https://github.com/AndreMiras))
+- Remove unused `extract_source()` method [\#1143](https://github.com/kivy/python-for-android/pull/1143) ([AndreMiras](https://github.com/AndreMiras))
+- This current Twisted version actually runs! [\#1140](https://github.com/kivy/python-for-android/pull/1140) ([wexi](https://github.com/wexi))
+- Two humble changes [\#1139](https://github.com/kivy/python-for-android/pull/1139) ([wexi](https://github.com/wexi))
+- Made start.c search ANDROID\_UNPACK for crystax [\#1137](https://github.com/kivy/python-for-android/pull/1137) ([inclement](https://github.com/inclement))
+- Update ffpyplayer recipe and it's dependencies [\#1134](https://github.com/kivy/python-for-android/pull/1134) ([germn](https://github.com/germn))
+- 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))
+- Moved webview loading animation to bootstrap [\#1129](https://github.com/kivy/python-for-android/pull/1129) ([inclement](https://github.com/inclement))
+- Added --version argument [\#1118](https://github.com/kivy/python-for-android/pull/1118) ([inclement](https://github.com/inclement))
+- Add help regarding websocket-client & SSL [\#1113](https://github.com/kivy/python-for-android/pull/1113) ([brentpicasso](https://github.com/brentpicasso))
+- 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))
+- Try system python when performing compileall [\#1109](https://github.com/kivy/python-for-android/pull/1109) ([inclement](https://github.com/inclement))
+- 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))
+- Fixes for NDK 15+ [\#1103](https://github.com/kivy/python-for-android/pull/1103) ([inclement](https://github.com/inclement))
+- 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))
+- Added "regex" module recipe [\#1100](https://github.com/kivy/python-for-android/pull/1100) ([germn](https://github.com/germn))
+- SDL2/Gradle bootstrap with fixes [\#1071](https://github.com/kivy/python-for-android/pull/1071) ([inclement](https://github.com/inclement))
+- Updated Kivy icons to newer logo under sdl2 [\#1033](https://github.com/kivy/python-for-android/pull/1033) ([inclement](https://github.com/inclement))
+
+## [0.5.3](https://github.com/kivy/python-for-android/tree/0.5.3) (2017-08-26)
+
+[Full Changelog](https://github.com/kivy/python-for-android/compare/6234a5d11d35d4a1b7fee9433461499f68a915d9...0.5.3)
+
+**Closed issues:**
+
+- Building with Crystax NDK : "Android NDK : Could not find application project directory" [\#1084](https://github.com/kivy/python-for-android/issues/1084)
+- recipes \_\_init\_\_.py indentation error [\#1082](https://github.com/kivy/python-for-android/issues/1082)
+- AttributeError: 'Context' object has no attribute 'hostpython' [\#1077](https://github.com/kivy/python-for-android/issues/1077)
+- 'Context' object has no attribute 'hostpython' [\#1073](https://github.com/kivy/python-for-android/issues/1073)
+- Error after update of SDK [\#1070](https://github.com/kivy/python-for-android/issues/1070)
+- wakelock == 1 not preventing screen from locking on sdl2 [\#1061](https://github.com/kivy/python-for-android/issues/1061)
+- running p4a from git fails [\#1058](https://github.com/kivy/python-for-android/issues/1058)
+- 'Context' object has no attribute 'hostpython' [\#1056](https://github.com/kivy/python-for-android/issues/1056)
+- Can p4a be used without a bootstrap? [\#1055](https://github.com/kivy/python-for-android/issues/1055)
+- Screen rotation with "orientation=all" is broken [\#1054](https://github.com/kivy/python-for-android/issues/1054)
+- python-for-android doesn't work with current Android SDK [\#1050](https://github.com/kivy/python-for-android/issues/1050)
+- p4a should fetch Kivy 1.10 instead of master [\#1044](https://github.com/kivy/python-for-android/issues/1044)
+- Android Browser Not Launching for OAuth 2.0 [\#1032](https://github.com/kivy/python-for-android/issues/1032)
+- flash quite,adb log [\#1030](https://github.com/kivy/python-for-android/issues/1030)
+- Can't build, sh.py raise a exception. [\#1029](https://github.com/kivy/python-for-android/issues/1029)
+- Python 3 branch still uses python 2.x [\#1022](https://github.com/kivy/python-for-android/issues/1022)
+- service fails to start [\#1020](https://github.com/kivy/python-for-android/issues/1020)
+- path to service file [\#1019](https://github.com/kivy/python-for-android/issues/1019)
+- Crash trap with custom logger and sys.stdout.encoding [\#1018](https://github.com/kivy/python-for-android/issues/1018)
+- pyjnius build failed [\#1016](https://github.com/kivy/python-for-android/issues/1016)
+- 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)
+- Custom recipes hinders the downloading of other ones. [\#1001](https://github.com/kivy/python-for-android/issues/1001)
+- documentation: how to run without pip install \( development mode \) [\#996](https://github.com/kivy/python-for-android/issues/996)
+- PythonActivity.mActivity causes app crash with new toolchain [\#995](https://github.com/kivy/python-for-android/issues/995)
+- Failure deploying apk files using buildozer android debug [\#989](https://github.com/kivy/python-for-android/issues/989)
+- 'Window.request\_keyboard' without showing keyboard [\#986](https://github.com/kivy/python-for-android/issues/986)
+- TypeError: slice indices must be integers or None or have an \_\_index\_\_ method [\#984](https://github.com/kivy/python-for-android/issues/984)
+- --presplash and --icon aren't mentioned in revamp docs [\#975](https://github.com/kivy/python-for-android/issues/975)
+- NDK automatic lookup tries to pick a tarball [\#972](https://github.com/kivy/python-for-android/issues/972)
+- Kivy is broken on recent master [\#970](https://github.com/kivy/python-for-android/issues/970)
+- device doesnt go on sleep mode [\#969](https://github.com/kivy/python-for-android/issues/969)
+- The python2 build imports cython from the system python in /usr/lib/... [\#964](https://github.com/kivy/python-for-android/issues/964)
+- "Could not remove android presplash" if 'android' is not in requirements [\#963](https://github.com/kivy/python-for-android/issues/963)
+- "AndroidJoystick is not supported by your version of linux" confusing message in log [\#962](https://github.com/kivy/python-for-android/issues/962)
+- Could not ping localhost:5000 [\#960](https://github.com/kivy/python-for-android/issues/960)
+- Using pyjnius leads to crash \(sometimes?\) if app built by new toolchain [\#959](https://github.com/kivy/python-for-android/issues/959)
+- Android screen rotation is probably broken [\#955](https://github.com/kivy/python-for-android/issues/955)
+- Compling pyo with sdl is breaking ply / enaml [\#947](https://github.com/kivy/python-for-android/issues/947)
+- Access WiFi information? [\#940](https://github.com/kivy/python-for-android/issues/940)
+- p4a erroring on SSL connection [\#939](https://github.com/kivy/python-for-android/issues/939)
+- Compiling PIL seems to use pyconfig.h from the wrong directory [\#937](https://github.com/kivy/python-for-android/issues/937)
+- ImportError for ssl [\#934](https://github.com/kivy/python-for-android/issues/934)
+- 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)
+- \[launcher\] icon= and splash= parameters [\#932](https://github.com/kivy/python-for-android/issues/932)
+- \[launcher\] app update by http\(s\) from external website \(https:// for github required\) [\#931](https://github.com/kivy/python-for-android/issues/931)
+- Presplash delay [\#928](https://github.com/kivy/python-for-android/issues/928)
+- Python3 APK fails to build! [\#927](https://github.com/kivy/python-for-android/issues/927)
+- MQTT [\#926](https://github.com/kivy/python-for-android/issues/926)
+- Can't build apk on OS X El Capitan [\#922](https://github.com/kivy/python-for-android/issues/922)
+- command not found exception. [\#921](https://github.com/kivy/python-for-android/issues/921)
+- ffmpeg recipe possibly broken [\#920](https://github.com/kivy/python-for-android/issues/920)
+- ERROR: /usr/bin/ant failed! [\#918](https://github.com/kivy/python-for-android/issues/918)
+- Feature request / Idea / Poll: Create kex packages [\#917](https://github.com/kivy/python-for-android/issues/917)
+- AttributeError: 'module' object has no attribute 'recipe' [\#907](https://github.com/kivy/python-for-android/issues/907)
+- Kivy .so is too small to be an ELF executable \[pygame bootstrap\] [\#897](https://github.com/kivy/python-for-android/issues/897)
+- p4a recipes crashes on matplotlib [\#895](https://github.com/kivy/python-for-android/issues/895)
+- onResume deadlock with pyjnius/pygame/sdl on android [\#890](https://github.com/kivy/python-for-android/issues/890)
+- dlopen failed: cannot locate symbol "\_Py\_NoneStruct" [\#887](https://github.com/kivy/python-for-android/issues/887)
+- SDL2 continually passes joyaxismotion events [\#885](https://github.com/kivy/python-for-android/issues/885)
+- Cloud Builder - 500 Internal Server Error [\#883](https://github.com/kivy/python-for-android/issues/883)
+- 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)
+- Building apk problem for android on OSX EL Capitan 10.11.5 [\#878](https://github.com/kivy/python-for-android/issues/878)
+- python3crystax conflicts with python3 [\#877](https://github.com/kivy/python-for-android/issues/877)
+- No instructions for utilizing in Arch linux \(i686 / x86\_64\) [\#876](https://github.com/kivy/python-for-android/issues/876)
+- Can't compile with openssl [\#868](https://github.com/kivy/python-for-android/issues/868)
+- p4a recipes error: missing matplotlib [\#865](https://github.com/kivy/python-for-android/issues/865)
+- AndroidBrowser.open\(\) should return a value [\#855](https://github.com/kivy/python-for-android/issues/855)
+- Can't import PIL on python for android and kivy? [\#853](https://github.com/kivy/python-for-android/issues/853)
+- How can i use the custom broadcast by myself in the background service? [\#849](https://github.com/kivy/python-for-android/issues/849)
+- Building python for android's requirements for 64 bit Android processors [\#848](https://github.com/kivy/python-for-android/issues/848)
+- The Kivy Option "softinput\_mode" does not work on Android with bootstrap=sdl2 [\#847](https://github.com/kivy/python-for-android/issues/847)
+- webbrowser.open\(\) doesn't work on Android with bootstrap=sdl2 [\#846](https://github.com/kivy/python-for-android/issues/846)
+- non debug apk? [\#844](https://github.com/kivy/python-for-android/issues/844)
+- Rotation Lock Ignored [\#842](https://github.com/kivy/python-for-android/issues/842)
+- Plyer GPS example works on android but not android\_new toolchain [\#833](https://github.com/kivy/python-for-android/issues/833)
+- 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)
+- MD5sum - UnboundLocalError: current\_md5 referenced before assignment [\#828](https://github.com/kivy/python-for-android/issues/828)
+- Recipes are still not resolved properly sometimes [\#826](https://github.com/kivy/python-for-android/issues/826)
+- 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)
+- p4a create error: kivy/\_clock.pxd\:6\:4\: Executable statement not allowed here [\#822](https://github.com/kivy/python-for-android/issues/822)
+- No such file or directory: ".../whitelist.txt" [\#821](https://github.com/kivy/python-for-android/issues/821)
+- Docs - connected toctrees, too deep? [\#820](https://github.com/kivy/python-for-android/issues/820)
+- Showcase with launcher [\#814](https://github.com/kivy/python-for-android/issues/814)
+- Can't target api, --sdk argument broken [\#813](https://github.com/kivy/python-for-android/issues/813)
+- Lxml, docutils need recipe [\#812](https://github.com/kivy/python-for-android/issues/812)
+- \[Pygame\] start.c fatal error: Python.h: No such file or directory [\#809](https://github.com/kivy/python-for-android/issues/809)
+- Presplash does not work with SDL2. [\#806](https://github.com/kivy/python-for-android/issues/806)
+- netifaces recipe broken [\#802](https://github.com/kivy/python-for-android/issues/802)
+- sdl2 recipe builds wrong bootstrap jni source [\#801](https://github.com/kivy/python-for-android/issues/801)
+- On resume crash in SDL2 bootstrap [\#797](https://github.com/kivy/python-for-android/issues/797)
+- Pure python requirements does not install \(plyer for example\) [\#795](https://github.com/kivy/python-for-android/issues/795)
+- E/linker: site-packages/android/\_android.so too small to be an ELF executable [\#768](https://github.com/kivy/python-for-android/issues/768)
+- Cryptography recipe does not compile [\#766](https://github.com/kivy/python-for-android/issues/766)
+- Threads need a wrapper for calling detach\(\) [\#758](https://github.com/kivy/python-for-android/issues/758)
+- Activity \(android.activity\) piece of code [\#756](https://github.com/kivy/python-for-android/issues/756)
+- ImportError: Import by filename is not supported. [\#751](https://github.com/kivy/python-for-android/issues/751)
+- Hostpython not found by Recipe.py [\#748](https://github.com/kivy/python-for-android/issues/748)
+- P4A and C++/SDL2/Python2/OpenGL game [\#747](https://github.com/kivy/python-for-android/issues/747)
+- Why does the boot image is deformed? [\#745](https://github.com/kivy/python-for-android/issues/745)
+- problem with p4a recipe for kivent [\#744](https://github.com/kivy/python-for-android/issues/744)
+- Webview - back button bug [\#741](https://github.com/kivy/python-for-android/issues/741)
+- webview - flask server crashes immediately [\#739](https://github.com/kivy/python-for-android/issues/739)
+- p4a apk webview bug [\#738](https://github.com/kivy/python-for-android/issues/738)
+- Libffi recipe fails with "unrecognized options: --enable-shared" [\#733](https://github.com/kivy/python-for-android/issues/733)
+- App close when device is flipped [\#732](https://github.com/kivy/python-for-android/issues/732)
+- recipe for pyjinius fails [\#731](https://github.com/kivy/python-for-android/issues/731)
+- Doc clarification on p4a requirements for basic kivy app with SDL2 bootstrap [\#724](https://github.com/kivy/python-for-android/issues/724)
+- PIL recipe is broken [\#722](https://github.com/kivy/python-for-android/issues/722)
+- raise exc\_info\[0\], exc\_info\[1\], exc\_info\[2\] - Syntax Error [\#721](https://github.com/kivy/python-for-android/issues/721)
+- Some input files use or override a deprecated API. [\#719](https://github.com/kivy/python-for-android/issues/719)
+- Unexpected "malformed start tag" error with HTMLParser [\#715](https://github.com/kivy/python-for-android/issues/715)
+- open\(\) build-in function don't work as expected [\#706](https://github.com/kivy/python-for-android/issues/706)
+- Webview bootstrap. Where to start? \[$100\] [\#700](https://github.com/kivy/python-for-android/issues/700)
+- Back button doesn't work [\#699](https://github.com/kivy/python-for-android/issues/699)
+- FileNotFoundError '/bin/sh' with subprocess.py python3crystax [\#691](https://github.com/kivy/python-for-android/issues/691)
+- static jfieldID not valid for class java.lang.Class\<org.renpy.android.PythonActivity\> [\#686](https://github.com/kivy/python-for-android/issues/686)
+- Incorrect SDK variable in build.xml with pygame bootstrap and direct p4a invocation [\#684](https://github.com/kivy/python-for-android/issues/684)
+- Failure to build apk due to incorrect invocation of "ant" by "sh.ant"... [\#681](https://github.com/kivy/python-for-android/issues/681)
+- Missing recipes: cherrypy, libnacl, requests [\#674](https://github.com/kivy/python-for-android/issues/674)
+- Multiple permissions in .p4a [\#673](https://github.com/kivy/python-for-android/issues/673)
+- Python compiled components recipe: "bad gcc/glibc config?" [\#669](https://github.com/kivy/python-for-android/issues/669)
+- No "--window" option [\#666](https://github.com/kivy/python-for-android/issues/666)
+- .jam files not installed [\#661](https://github.com/kivy/python-for-android/issues/661)
+- AttributeError: 'NoneType' object has no attribute 'from\_crystax' [\#659](https://github.com/kivy/python-for-android/issues/659)
+- will\_build does not work as expected [\#657](https://github.com/kivy/python-for-android/issues/657)
+- Check presence of main.py during build time [\#656](https://github.com/kivy/python-for-android/issues/656)
+- md5 not handled yet [\#650](https://github.com/kivy/python-for-android/issues/650)
+- App crash on resume with new tool chain [\#646](https://github.com/kivy/python-for-android/issues/646)
+- Unable to find libpython2.7.so on older versions of Android [\#645](https://github.com/kivy/python-for-android/issues/645)
+- Corrupted window size with Window.softinput\_mode = 'below\_target' [\#635](https://github.com/kivy/python-for-android/issues/635)
+- Patch files not found [\#633](https://github.com/kivy/python-for-android/issues/633)
+- Unintended rollback patches [\#632](https://github.com/kivy/python-for-android/issues/632)
+- \[master\] AttributeError: 'tuple' object has no attribute 'startswith' [\#631](https://github.com/kivy/python-for-android/issues/631)
+- Python recipe from Crystax undefined [\#629](https://github.com/kivy/python-for-android/issues/629)
+- SDL2\_image error Unknown or unsupported ARM architecture [\#627](https://github.com/kivy/python-for-android/issues/627)
+- Building python2 for armeabi fails due to unknown option "-single\_module" \(OS X\) [\#623](https://github.com/kivy/python-for-android/issues/623)
+- Building python2 for armeabi fails due to space character in storage\_dir \(OS X\) [\#622](https://github.com/kivy/python-for-android/issues/622)
+- AttributeError: 'Context' object has no attribute 'hostpython' [\#620](https://github.com/kivy/python-for-android/issues/620)
+- Jpeg recipe is broken [\#617](https://github.com/kivy/python-for-android/issues/617)
+- build.py TypeError args.services object is not iterable [\#616](https://github.com/kivy/python-for-android/issues/616)
+- OpenSSL 1.0.2e outdated \(replaced by 1.0.2f\) [\#614](https://github.com/kivy/python-for-android/issues/614)
+- Matplotlib recipe [\#607](https://github.com/kivy/python-for-android/issues/607)
+- setup.py install doesn't include the recipes folder [\#591](https://github.com/kivy/python-for-android/issues/591)
+- missing recipes/pyjnius/getenv.patch [\#590](https://github.com/kivy/python-for-android/issues/590)
+- standard includes not found by boost [\#576](https://github.com/kivy/python-for-android/issues/576)
+- HTTP 302 recipe download file [\#573](https://github.com/kivy/python-for-android/issues/573)
+- SDL2 bootstrap broken with blacklist? [\#567](https://github.com/kivy/python-for-android/issues/567)
+- Kivy Launcher 1.9.1 APK doesn't work on Lollipop [\#548](https://github.com/kivy/python-for-android/issues/548)
+- Logo aspect ratio problem [\#545](https://github.com/kivy/python-for-android/issues/545)
+- 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)
+- native code in kivyAndroid, possible? [\#542](https://github.com/kivy/python-for-android/issues/542)
+- Error compiling [\#541](https://github.com/kivy/python-for-android/issues/541)
+- import sh module problem [\#540](https://github.com/kivy/python-for-android/issues/540)
+- Inconsistent dependency graph behaviour [\#515](https://github.com/kivy/python-for-android/issues/515)
+- We demand Python 3 support [\#512](https://github.com/kivy/python-for-android/issues/512)
+- CythonRecipe: how to handle different settings for different .pyx files? [\#511](https://github.com/kivy/python-for-android/issues/511)
+- Arch support is broken [\#492](https://github.com/kivy/python-for-android/issues/492)
+- function should\_build [\#491](https://github.com/kivy/python-for-android/issues/491)
+- verbose output [\#490](https://github.com/kivy/python-for-android/issues/490)
+- compiler problem with gcc \>= 4.8 [\#489](https://github.com/kivy/python-for-android/issues/489)
+- error when execute p4a in line from urlparse import urlparse [\#488](https://github.com/kivy/python-for-android/issues/488)
+- Can't get off the ground [\#485](https://github.com/kivy/python-for-android/issues/485)
+- Python3 doesn't work on Android [\#484](https://github.com/kivy/python-for-android/issues/484)
+- Allow scaling of the presplash image to device resolution [\#481](https://github.com/kivy/python-for-android/issues/481)
+- python multiprocess.dummy do not work [\#479](https://github.com/kivy/python-for-android/issues/479)
+- Question: compatibility with cx\_Freeze [\#478](https://github.com/kivy/python-for-android/issues/478)
+- Purge inclement where needed [\#477](https://github.com/kivy/python-for-android/issues/477)
+- Missing dependency in quickstart? [\#476](https://github.com/kivy/python-for-android/issues/476)
+- No service support with SDL2 bootstrap [\#467](https://github.com/kivy/python-for-android/issues/467)
+- Kivy can't get the keyboard height with SDL2 [\#466](https://github.com/kivy/python-for-android/issues/466)
+- SDL2 backend doesn't support a loading screen [\#465](https://github.com/kivy/python-for-android/issues/465)
+- Many recipes from the old toolchain need porting [\#464](https://github.com/kivy/python-for-android/issues/464)
+- \[revamp\] Android NDK API 21 issue [\#455](https://github.com/kivy/python-for-android/issues/455)
+- \[revamp\] Twisted [\#454](https://github.com/kivy/python-for-android/issues/454)
+- \[revamp\] Can't load unicodedata module [\#453](https://github.com/kivy/python-for-android/issues/453)
+- \[revamp\] The revamp branch always prints the ToolchainCL object after running [\#452](https://github.com/kivy/python-for-android/issues/452)
+- \[revamp\] setuptools "wrong ELF class" issues [\#451](https://github.com/kivy/python-for-android/issues/451)
+- \[revamp\] Unpack archives that don't list their root directory [\#450](https://github.com/kivy/python-for-android/issues/450)
+- \[revamp\] Recipes can only depend on other recipes [\#449](https://github.com/kivy/python-for-android/issues/449)
+- \[revamp\] p4a silently fails if --private is not absolute [\#448](https://github.com/kivy/python-for-android/issues/448)
+- \[revamp\] Invalid syntax for python3 [\#444](https://github.com/kivy/python-for-android/issues/444)
+- \[revamp\] toolchain.py ignores recipes with errrors [\#440](https://github.com/kivy/python-for-android/issues/440)
+- Error when trying to create an apk package with buildozer or with distribute.sh [\#435](https://github.com/kivy/python-for-android/issues/435)
+- pylibpd failes to compile [\#434](https://github.com/kivy/python-for-android/issues/434)
+- \[revamp\] --android\_api is ignored on SDL2 bootstrap [\#425](https://github.com/kivy/python-for-android/issues/425)
+- 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)
+- \[revamp\] - Darwin patches applied on Linux [\#423](https://github.com/kivy/python-for-android/issues/423)
+- swift: md5sum changed - fix URL on a static content [\#421](https://github.com/kivy/python-for-android/issues/421)
+- PLATFORM \> 19: there is no sys/timeb.h [\#419](https://github.com/kivy/python-for-android/issues/419)
+- Numpy build fails if it detects system libraries and tries to link with them [\#417](https://github.com/kivy/python-for-android/issues/417)
+- MD5 opencv is incorrect in recipe opencv [\#411](https://github.com/kivy/python-for-android/issues/411)
+- numpy fails to build [\#409](https://github.com/kivy/python-for-android/issues/409)
+- twisted [\#403](https://github.com/kivy/python-for-android/issues/403)
+- ctypes callback function SIGSEGV [\#401](https://github.com/kivy/python-for-android/issues/401)
+- gstreamer recipe [\#400](https://github.com/kivy/python-for-android/issues/400)
+- buildozer needs markupsafe to build [\#399](https://github.com/kivy/python-for-android/issues/399)
+- Ctypes still not found \[$50\] [\#397](https://github.com/kivy/python-for-android/issues/397)
+- Documentation: example using startActivityForResult with bind\(on\_activity\_result=\) [\#388](https://github.com/kivy/python-for-android/issues/388)
+- Does not build on OSX [\#387](https://github.com/kivy/python-for-android/issues/387)
+- 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)
+- android app crash on screen rotation [\#379](https://github.com/kivy/python-for-android/issues/379)
+- 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)
+- python fabric recipe fails [\#374](https://github.com/kivy/python-for-android/issues/374)
+- Build a release so this can be included in F-Droid [\#369](https://github.com/kivy/python-for-android/issues/369)
+- Enable armeabi-v7a-hard [\#366](https://github.com/kivy/python-for-android/issues/366)
+- bulldozer and distribute.sh [\#364](https://github.com/kivy/python-for-android/issues/364)
+- 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)
+- python 3 compatibility [\#359](https://github.com/kivy/python-for-android/issues/359)
+- softinput\_mode='pan' does not work well with orientation change of the device screen. [\#348](https://github.com/kivy/python-for-android/issues/348)
+- 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)
+- pygame.midi.init\(\) Failing on Android 4.4.4 - ImportError: No module named pypm [\#342](https://github.com/kivy/python-for-android/issues/342)
+- Error In building kivy android on Mac OSX [\#340](https://github.com/kivy/python-for-android/issues/340)
+- ButtonBehavior.on\_touch\_up dispatches on\_release immediately [\#339](https://github.com/kivy/python-for-android/issues/339)
+- Failed to build pure Python module included after `twisted` [\#337](https://github.com/kivy/python-for-android/issues/337)
+- The compiled APK crashes with error on \_imaging.so: not found [\#335](https://github.com/kivy/python-for-android/issues/335)
+- ctypes.py and \_ctypes.so are not available [\#333](https://github.com/kivy/python-for-android/issues/333)
+- After installing the Pillow does not compile the project. \(Mac OS\) [\#332](https://github.com/kivy/python-for-android/issues/332)
+- pygame.display.set\_mode runs out of memory [\#331](https://github.com/kivy/python-for-android/issues/331)
+- Python Service multiple instances [\#329](https://github.com/kivy/python-for-android/issues/329)
+- Kivy Launcher should include Plyer [\#328](https://github.com/kivy/python-for-android/issues/328)
+- Issue in Cython file compilation and building kivy.graphics.vertex\_instruction extentions [\#326](https://github.com/kivy/python-for-android/issues/326)
+- Failed Cython Compilation [\#325](https://github.com/kivy/python-for-android/issues/325)
+- Python3 Branch: jinja2 traceback on buildozer --verbose android debug [\#322](https://github.com/kivy/python-for-android/issues/322)
+- About ctypes [\#319](https://github.com/kivy/python-for-android/issues/319)
+- Audio loop not working on Android [\#318](https://github.com/kivy/python-for-android/issues/318)
+- 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)
+- Bug with android.p4a\_whitelist in buildozer.spec file. [\#302](https://github.com/kivy/python-for-android/issues/302)
+- ctypes module not loaded [\#301](https://github.com/kivy/python-for-android/issues/301)
+- P4A builds stable instead of master [\#300](https://github.com/kivy/python-for-android/issues/300)
+- Use of SL4A [\#299](https://github.com/kivy/python-for-android/issues/299)
+- Github zipball doesn't work anymore [\#297](https://github.com/kivy/python-for-android/issues/297)
+- My `./distribute.sh` suddenly stop building today. [\#294](https://github.com/kivy/python-for-android/issues/294)
+- Create recipes for storm and psycopg2 [\#293](https://github.com/kivy/python-for-android/issues/293)
+- error in your VM Image after ./distribute.sh -m kivy [\#291](https://github.com/kivy/python-for-android/issues/291)
+- Eror in Apk process building [\#290](https://github.com/kivy/python-for-android/issues/290)
+- IOError: \[Errno 2\] No usable temporary directory [\#289](https://github.com/kivy/python-for-android/issues/289)
+- Path commands on readme and official-website-toolchain don't seem to work [\#281](https://github.com/kivy/python-for-android/issues/281)
+- Twisted/recipe.sh can't find zope.interface [\#280](https://github.com/kivy/python-for-android/issues/280)
+- cython uses system python instead of hostpython [\#277](https://github.com/kivy/python-for-android/issues/277)
+- Twisted recipe doesn't work in 32-bit build environment [\#276](https://github.com/kivy/python-for-android/issues/276)
+- distribute fails to build flask or sqlite3 : Error: libpymodules.so - no input files [\#275](https://github.com/kivy/python-for-android/issues/275)
+- \# Command failed: ./distribute.sh -m "kivy" -d "myapp" [\#271](https://github.com/kivy/python-for-android/issues/271)
+- creating new service does not start the service [\#270](https://github.com/kivy/python-for-android/issues/270)
+- 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)
+- Sticky services [\#267](https://github.com/kivy/python-for-android/issues/267)
+- --private clobers /data/data/\[package name\]/files directory [\#263](https://github.com/kivy/python-for-android/issues/263)
+- touchtracer not working [\#262](https://github.com/kivy/python-for-android/issues/262)
+- compile error: sys/timeb.h not found [\#261](https://github.com/kivy/python-for-android/issues/261)
+- Python file is not converted to bytecode when building apk [\#257](https://github.com/kivy/python-for-android/issues/257)
+- New Version of NDK [\#256](https://github.com/kivy/python-for-android/issues/256)
+- Full control of the AndroidManifest.xml \[$20\] [\#255](https://github.com/kivy/python-for-android/issues/255)
+- extra characters in textinput on Android [\#247](https://github.com/kivy/python-for-android/issues/247)
+- Feature Request: add checkNetwork to \_android.pyx [\#244](https://github.com/kivy/python-for-android/issues/244)
+- Env variables for ANDROIDAPI ignored [\#241](https://github.com/kivy/python-for-android/issues/241)
+- Kivy Android VM doesn't have plyer recipe [\#239](https://github.com/kivy/python-for-android/issues/239)
+- Switch from .sh to pythonic toolchain [\#238](https://github.com/kivy/python-for-android/issues/238)
+- Update twisted to 14.0.0 [\#237](https://github.com/kivy/python-for-android/issues/237)
+- ./distribute.sh -u option doesn't work [\#236](https://github.com/kivy/python-for-android/issues/236)
+- \*.so is too small to be an ELF executable [\#234](https://github.com/kivy/python-for-android/issues/234)
+- Cannot run Py4A APKs on Android x86 [\#233](https://github.com/kivy/python-for-android/issues/233)
+- C extension overlap [\#232](https://github.com/kivy/python-for-android/issues/232)
+- Can't build django [\#231](https://github.com/kivy/python-for-android/issues/231)
+- System.currentTimeMillis\(\) returns a negative number [\#229](https://github.com/kivy/python-for-android/issues/229)
+- `future_builtins` shouldn't be blacklisted [\#228](https://github.com/kivy/python-for-android/issues/228)
+- Non-clear direction in readme guide [\#227](https://github.com/kivy/python-for-android/issues/227)
+- OSX build error if more than one pure-python module [\#226](https://github.com/kivy/python-for-android/issues/226)
+- unknown type name 'SDL\_BlitMap' while buildozer apk generation [\#225](https://github.com/kivy/python-for-android/issues/225)
+- error for package apk [\#223](https://github.com/kivy/python-for-android/issues/223)
+- collect2: ld returned 1 exit status [\#221](https://github.com/kivy/python-for-android/issues/221)
+- buildozer not works more [\#220](https://github.com/kivy/python-for-android/issues/220)
+- \[moved\] pyo files are not being recreated by ./build.py in python for android [\#216](https://github.com/kivy/python-for-android/issues/216)
+- error: could not create '/usr/local/lib/python2.7/site-packages/PIL': Permission denied [\#215](https://github.com/kivy/python-for-android/issues/215)
+- collect2: ld returned 1 exit status [\#213](https://github.com/kivy/python-for-android/issues/213)
+- virtualenv not entering [\#212](https://github.com/kivy/python-for-android/issues/212)
+- cymunk doesn't get copied to dist/default/ [\#211](https://github.com/kivy/python-for-android/issues/211)
+- No Python 3 Support [\#210](https://github.com/kivy/python-for-android/issues/210)
+- HELP! ./distribute.sh failed to locate arm-linux-androideabi-gcc [\#209](https://github.com/kivy/python-for-android/issues/209)
+- TextInput not behaving with SwiftKey [\#198](https://github.com/kivy/python-for-android/issues/198)
+- Example Applications? [\#197](https://github.com/kivy/python-for-android/issues/197)
+- installing Pydev has encountered a problem [\#196](https://github.com/kivy/python-for-android/issues/196)
+- buildozer apk build problem [\#195](https://github.com/kivy/python-for-android/issues/195)
+- Zope2 at "Downloading/unpacking zope.security" [\#190](https://github.com/kivy/python-for-android/issues/190)
+- \_scproxy import error when building on Mac with 'requests' lib [\#186](https://github.com/kivy/python-for-android/issues/186)
+- Kivy TextInput doesn't work with SwiftKey keyboard [\#184](https://github.com/kivy/python-for-android/issues/184)
+- Error in using debug flag, calling ANT debugger? [\#179](https://github.com/kivy/python-for-android/issues/179)
+- Update setuptools version [\#176](https://github.com/kivy/python-for-android/issues/176)
+- Problems with distibute.sh [\#175](https://github.com/kivy/python-for-android/issues/175)
+- Rst editor crashing on android [\#174](https://github.com/kivy/python-for-android/issues/174)
+- Doubt about distribute.sh [\#173](https://github.com/kivy/python-for-android/issues/173)
+- Own Activities in AndroidManifest.xml [\#172](https://github.com/kivy/python-for-android/issues/172)
+- Error to execute comand ./distribute.sh -m "openssl pil kivy" [\#167](https://github.com/kivy/python-for-android/issues/167)
+- keyboard stays on screen in android, after app exit [\#166](https://github.com/kivy/python-for-android/issues/166)
+- ffmpeg ndk r9 incompatibility [\#165](https://github.com/kivy/python-for-android/issues/165)
+- kivy touchtracer apk not running on emmulator [\#162](https://github.com/kivy/python-for-android/issues/162)
+- fatal error: stdlib.h: No such file or directory [\#159](https://github.com/kivy/python-for-android/issues/159)
+- Add psutil recipe [\#157](https://github.com/kivy/python-for-android/issues/157)
+- Failure to compile .py should cause exit [\#156](https://github.com/kivy/python-for-android/issues/156)
+- Docs on the readthedocs are old [\#155](https://github.com/kivy/python-for-android/issues/155)
+- non full screen touch offset on android [\#153](https://github.com/kivy/python-for-android/issues/153)
+- Android NDK r9 Fails [\#149](https://github.com/kivy/python-for-android/issues/149)
+- Android app crashes on rotation to landscape [\#148](https://github.com/kivy/python-for-android/issues/148)
+- configure: error: C compiler cannot create executables [\#145](https://github.com/kivy/python-for-android/issues/145)
+- GCC 4.4.3 depreciated in android NDK [\#143](https://github.com/kivy/python-for-android/issues/143)
+- dlopen fail on android 4.3 [\#141](https://github.com/kivy/python-for-android/issues/141)
+- new depencency ordering broke some usecases with buildozer [\#140](https://github.com/kivy/python-for-android/issues/140)
+- Android Keyboard information [\#139](https://github.com/kivy/python-for-android/issues/139)
+- Android keyboards do not recognize Password fields as secure passwords [\#138](https://github.com/kivy/python-for-android/issues/138)
+- kivy.network.urlrequest: No callback parameter: on\_failure [\#137](https://github.com/kivy/python-for-android/issues/137)
+- UnicodeDecodeError [\#136](https://github.com/kivy/python-for-android/issues/136)
+- arm-linux-androideabi-gcc: no input files [\#133](https://github.com/kivy/python-for-android/issues/133)
+- Unable to build APK [\#132](https://github.com/kivy/python-for-android/issues/132)
+- apk doesn't unpack on first load [\#131](https://github.com/kivy/python-for-android/issues/131)
+- kivy 1.8 \(testing\) UnicodeDecodeError: 'ascii' codec can't decode byte... [\#129](https://github.com/kivy/python-for-android/issues/129)
+- presplash "crazy" position when change the orientation the cellphone [\#127](https://github.com/kivy/python-for-android/issues/127)
+- subprocess.check\_output\(\["ping", "-c", "3", hostname\]\) non-zero exit code 2 [\#126](https://github.com/kivy/python-for-android/issues/126)
+- Testing/Enabling armeabi-v7a [\#123](https://github.com/kivy/python-for-android/issues/123)
+- distribute.sh fails when using the 64bit Android NDK [\#116](https://github.com/kivy/python-for-android/issues/116)
+- 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)
+- IOError: \[Errno 20\] Not a directory: [\#113](https://github.com/kivy/python-for-android/issues/113)
+- Add setting in building package to allow switching to sleep mode [\#111](https://github.com/kivy/python-for-android/issues/111)
+- Android keyboard autosuggestion bug [\#110](https://github.com/kivy/python-for-android/issues/110)
+- Command ./distribute.sh -m "pyjnius kivy" ends with error [\#109](https://github.com/kivy/python-for-android/issues/109)
+- Feature request: Android service [\#107](https://github.com/kivy/python-for-android/issues/107)
+- \[recipe - pylibpd\] unpacking does not work [\#103](https://github.com/kivy/python-for-android/issues/103)
+- \[master\] Unzip fails [\#102](https://github.com/kivy/python-for-android/issues/102)
+- old unixcompiler.py bug when using ccache g++ [\#100](https://github.com/kivy/python-for-android/issues/100)
+- Non ascii key inputs not dispatched [\#97](https://github.com/kivy/python-for-android/issues/97)
+- Github archives are named master by default, breaking build [\#95](https://github.com/kivy/python-for-android/issues/95)
+- Python for Android not compiling sqlite3 module [\#91](https://github.com/kivy/python-for-android/issues/91)
+- Buggy dependencies handling with multiple ./distribute.sh [\#90](https://github.com/kivy/python-for-android/issues/90)
+- build.py has "/usr/bin/python2" hard-coded [\#88](https://github.com/kivy/python-for-android/issues/88)
+- touchtracer bug - dp migration? [\#87](https://github.com/kivy/python-for-android/issues/87)
+- kivy build failing [\#86](https://github.com/kivy/python-for-android/issues/86)
+- Unable to resolve project target [\#85](https://github.com/kivy/python-for-android/issues/85)
+- Compile stop when it copy java code [\#83](https://github.com/kivy/python-for-android/issues/83)
+- touchtracer.apk not working [\#82](https://github.com/kivy/python-for-android/issues/82)
+- If the kivy module is built at the same time as others, it fails [\#81](https://github.com/kivy/python-for-android/issues/81)
+- Build with lxml not working [\#79](https://github.com/kivy/python-for-android/issues/79)
+- Compilation Error [\#78](https://github.com/kivy/python-for-android/issues/78)
+- properties.so is not a valid ELF object [\#77](https://github.com/kivy/python-for-android/issues/77)
+- Unfocusing kivy's TextInput [\#76](https://github.com/kivy/python-for-android/issues/76)
+- Camera on android [\#75](https://github.com/kivy/python-for-android/issues/75)
+- Using stable source... [\#74](https://github.com/kivy/python-for-android/issues/74)
+- fails to import text\_sdlttf [\#73](https://github.com/kivy/python-for-android/issues/73)
+- Standard module for SQLite not available [\#72](https://github.com/kivy/python-for-android/issues/72)
+- Kivy on Android galaxy s3 apk run exception [\#70](https://github.com/kivy/python-for-android/issues/70)
+- Check for build dependencies [\#68](https://github.com/kivy/python-for-android/issues/68)
+- Compile failing [\#66](https://github.com/kivy/python-for-android/issues/66)
+- First label not rendered on android [\#64](https://github.com/kivy/python-for-android/issues/64)
+- arm/limits.h: No such file or directory [\#63](https://github.com/kivy/python-for-android/issues/63)
+- Compiling hostpython doesn't work [\#62](https://github.com/kivy/python-for-android/issues/62)
+- when android-sdk platform tool is not installed. build process run into error. [\#61](https://github.com/kivy/python-for-android/issues/61)
+- Incorrect default Python executable [\#60](https://github.com/kivy/python-for-android/issues/60)
+- error when build distribution on debian squeeze. [\#57](https://github.com/kivy/python-for-android/issues/57)
+- Many small build issues in Ubuntu 12.04.1 x64 [\#55](https://github.com/kivy/python-for-android/issues/55)
+- csv module [\#54](https://github.com/kivy/python-for-android/issues/54)
+- Allow the screen to timeout in Android [\#53](https://github.com/kivy/python-for-android/issues/53)
+- Order matters in ./distribute.sh -m options [\#50](https://github.com/kivy/python-for-android/issues/50)
+- Initial distribution build fails with "unterminated substitute pattern" [\#47](https://github.com/kivy/python-for-android/issues/47)
+- Creating Widgets [\#46](https://github.com/kivy/python-for-android/issues/46)
+- Unable to use the crypt lib. [\#45](https://github.com/kivy/python-for-android/issues/45)
+- verify me [\#44](https://github.com/kivy/python-for-android/issues/44)
+- Graphics lost returning from "HOME" on Motorola Photon \(Sprint built 2.3.4\) [\#42](https://github.com/kivy/python-for-android/issues/42)
+- Name clash with http://code.google.com/p/python-for-android/ [\#39](https://github.com/kivy/python-for-android/issues/39)
+- Installed apk crash at loading... [\#38](https://github.com/kivy/python-for-android/issues/38)
+- Fail to include any module other than Kivy [\#37](https://github.com/kivy/python-for-android/issues/37)
+- Many issues on OS X running ./distribute.sh -m "kivy" [\#36](https://github.com/kivy/python-for-android/issues/36)
+- Unable to build python-for-android [\#34](https://github.com/kivy/python-for-android/issues/34)
+- Kivy / Python-for-android : Build.py fails to build an android package apk [\#33](https://github.com/kivy/python-for-android/issues/33)
+- Unable to use \<gstreamer\> as loader! [\#31](https://github.com/kivy/python-for-android/issues/31)
+- build stuck at `assets/private.mp3: private/include/python2.7/pyconfig.h` [\#29](https://github.com/kivy/python-for-android/issues/29)
+- Can't build with all modules [\#27](https://github.com/kivy/python-for-android/issues/27)
+- ask a question about the touchtracer demo [\#26](https://github.com/kivy/python-for-android/issues/26)
+- Error: Target id 'android-8' is not valid. [\#25](https://github.com/kivy/python-for-android/issues/25)
+- Build Error: "/usr/lib/libpython2.7.so: file not recognized: File format not recognized" [\#16](https://github.com/kivy/python-for-android/issues/16)
+- FFMpeg doesn't build [\#13](https://github.com/kivy/python-for-android/issues/13)
+- Problem init enviroment [\#12](https://github.com/kivy/python-for-android/issues/12)
+- error when build the APK [\#10](https://github.com/kivy/python-for-android/issues/10)
+- arm-linux-androideabi-gcc: Internal error: Killed \(program cc1\) [\#9](https://github.com/kivy/python-for-android/issues/9)
+- Build: Pyrex error [\#7](https://github.com/kivy/python-for-android/issues/7)
+- Where is the example? [\#5](https://github.com/kivy/python-for-android/issues/5)
+- Build error [\#4](https://github.com/kivy/python-for-android/issues/4)
+- Restore libpymodules.so behavior [\#3](https://github.com/kivy/python-for-android/issues/3)
+- Guide step 2 issues [\#2](https://github.com/kivy/python-for-android/issues/2)
+- compile failing [\#1](https://github.com/kivy/python-for-android/issues/1)
+
+**Merged pull requests:**
+
+- Update README.md [\#1096](https://github.com/kivy/python-for-android/pull/1096) ([zed](https://github.com/zed))
+- Deleted whitespace [\#1091](https://github.com/kivy/python-for-android/pull/1091) ([Fogapod](https://github.com/Fogapod))
+- Made graph recognise python\_depends [\#1089](https://github.com/kivy/python-for-android/pull/1089) ([inclement](https://github.com/inclement))
+- Moved logger bytes conversion to affect all lines [\#1088](https://github.com/kivy/python-for-android/pull/1088) ([inclement](https://github.com/inclement))
+- Made logger convert output to utf-8 including errors [\#1087](https://github.com/kivy/python-for-android/pull/1087) ([inclement](https://github.com/inclement))
+- Recipe import fixes [\#1086](https://github.com/kivy/python-for-android/pull/1086) ([inclement](https://github.com/inclement))
+- Various Ethereum related recipes fixes [\#1080](https://github.com/kivy/python-for-android/pull/1080) ([AndreMiras](https://github.com/AndreMiras))
+- Ethereum related recipes [\#1068](https://github.com/kivy/python-for-android/pull/1068) ([AndreMiras](https://github.com/AndreMiras))
+- implement wakelock for sdl2 [\#1066](https://github.com/kivy/python-for-android/pull/1066) ([brentpicasso](https://github.com/brentpicasso))
+- Fix typo in pythonforandroid/recipe.py [\#1065](https://github.com/kivy/python-for-android/pull/1065) ([jupart](https://github.com/jupart))
+- Rewrite recipe graph [\#1064](https://github.com/kivy/python-for-android/pull/1064) ([inclement](https://github.com/inclement))
+- Use Kivy setup.py flag instead of rmtree [\#1063](https://github.com/kivy/python-for-android/pull/1063) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- 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))
+- 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))
+- Call avdmanager instead of android in the SDK [\#1057](https://github.com/kivy/python-for-android/pull/1057) ([inclement](https://github.com/inclement))
+- Updated Kivy recipe to pull 1.10.0 [\#1048](https://github.com/kivy/python-for-android/pull/1048) ([inclement](https://github.com/inclement))
+- \[Core\] Get the latest version of requests. [\#1045](https://github.com/kivy/python-for-android/pull/1045) ([hobbestigrou](https://github.com/hobbestigrou))
+- Recipe for websocket-client [\#1039](https://github.com/kivy/python-for-android/pull/1039) ([debauchery1st](https://github.com/debauchery1st))
+- Recipe updates and small fixes to build process [\#1034](https://github.com/kivy/python-for-android/pull/1034) ([bobatsar](https://github.com/bobatsar))
+- 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))
+- Recipe for Pymunk [\#1026](https://github.com/kivy/python-for-android/pull/1026) ([viblo](https://github.com/viblo))
+- Fixed protobuf cpp [\#1021](https://github.com/kivy/python-for-android/pull/1021) ([Deniskore](https://github.com/Deniskore))
+- 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))
+- Remove the excessive ccache in the command [\#1014](https://github.com/kivy/python-for-android/pull/1014) ([MaChengxin](https://github.com/MaChengxin))
+- Added support for Python 3.6 [\#1011](https://github.com/kivy/python-for-android/pull/1011) ([inclement](https://github.com/inclement))
+- Made dist names different for py2/py3 testapps [\#1010](https://github.com/kivy/python-for-android/pull/1010) ([inclement](https://github.com/inclement))
+- Documentation mistake corrected [\#1005](https://github.com/kivy/python-for-android/pull/1005) ([yaki29](https://github.com/yaki29))
+- Fixed the blacklisting of sqlite3 under sdl2 [\#1000](https://github.com/kivy/python-for-android/pull/1000) ([inclement](https://github.com/inclement))
+- documentation recipe.rst spelling change [\#993](https://github.com/kivy/python-for-android/pull/993) ([yaki29](https://github.com/yaki29))
+- Move the unpackFiles step into an AsyncTask [\#990](https://github.com/kivy/python-for-android/pull/990) ([kollivier](https://github.com/kollivier))
+- Add recipe for google protobuf cpp implementation [\#987](https://github.com/kivy/python-for-android/pull/987) ([bakwc](https://github.com/bakwc))
+- Fix python2 for webview [\#981](https://github.com/kivy/python-for-android/pull/981) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- Updated Kivy recipe to work with Kivy master [\#968](https://github.com/kivy/python-for-android/pull/968) ([inclement](https://github.com/inclement))
+- Switch --permission to accept \>=1 parameters [\#966](https://github.com/kivy/python-for-android/pull/966) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- 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))
+- Add ffpyplayer and dependencies recipes for new toolchain. [\#954](https://github.com/kivy/python-for-android/pull/954) ([germn](https://github.com/germn))
+- Doc fixes [\#950](https://github.com/kivy/python-for-android/pull/950) ([inclement](https://github.com/inclement))
+- Fixed release mode \(--release\) with sdl2 [\#949](https://github.com/kivy/python-for-android/pull/949) ([inclement](https://github.com/inclement))
+- Made the sdl2 bootstrap strip unneeded symbols with python3 [\#948](https://github.com/kivy/python-for-android/pull/948) ([inclement](https://github.com/inclement))
+- Compile pyo with sdl2 [\#944](https://github.com/kivy/python-for-android/pull/944) ([inclement](https://github.com/inclement))
+- Update apis.rst [\#941](https://github.com/kivy/python-for-android/pull/941) ([codytrey](https://github.com/codytrey))
+- 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))
+- Add remove\_presplash [\#930](https://github.com/kivy/python-for-android/pull/930) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- Complete closure of the application with SDL2. [\#923](https://github.com/kivy/python-for-android/pull/923) ([dkrukouski](https://github.com/dkrukouski))
+- Testapp improvements [\#919](https://github.com/kivy/python-for-android/pull/919) ([inclement](https://github.com/inclement))
+- Make boost recipe compatible with Google NDK 13.0 [\#916](https://github.com/kivy/python-for-android/pull/916) ([brussee](https://github.com/brussee))
+- Completing md5sum comparison and download [\#915](https://github.com/kivy/python-for-android/pull/915) ([thopiekar](https://github.com/thopiekar))
+- Empty: Adding .gitkeep in bootstraps/empty/build [\#912](https://github.com/kivy/python-for-android/pull/912) ([thopiekar](https://github.com/thopiekar))
+- fix layout listener related issues. Closes \#890 [\#911](https://github.com/kivy/python-for-android/pull/911) ([akshayaurora](https://github.com/akshayaurora))
+- Fixed app path for SDL2 services [\#909](https://github.com/kivy/python-for-android/pull/909) ([inclement](https://github.com/inclement))
+- fixed recipe zope\_interface [\#908](https://github.com/kivy/python-for-android/pull/908) ([goffi-contrib](https://github.com/goffi-contrib))
+- Added "presplash"\_color argument [\#906](https://github.com/kivy/python-for-android/pull/906) ([mrhdias](https://github.com/mrhdias))
+- Added argument to set the loading screen background color [\#905](https://github.com/kivy/python-for-android/pull/905) ([mrhdias](https://github.com/mrhdias))
+- Easy way to set the presplash background color [\#904](https://github.com/kivy/python-for-android/pull/904) ([mrhdias](https://github.com/mrhdias))
+- Allow installation on Windows [\#902](https://github.com/kivy/python-for-android/pull/902) ([ethanhs](https://github.com/ethanhs))
+- Made clean-recipe-build delete dists [\#901](https://github.com/kivy/python-for-android/pull/901) ([inclement](https://github.com/inclement))
+- Improved log for missing python modules [\#900](https://github.com/kivy/python-for-android/pull/900) ([inclement](https://github.com/inclement))
+- Addded textinput scatter testapp [\#899](https://github.com/kivy/python-for-android/pull/899) ([inclement](https://github.com/inclement))
+- Allow list of permissions for bdist [\#898](https://github.com/kivy/python-for-android/pull/898) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- Fix bdistapk for launcher [\#896](https://github.com/kivy/python-for-android/pull/896) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+- Added symlink\_java\_src dev option [\#894](https://github.com/kivy/python-for-android/pull/894) ([inclement](https://github.com/inclement))
+- Port launcher to SDL2 bootstrap [\#891](https://github.com/kivy/python-for-android/pull/891) ([KeyWeeUsr](https://github.com/KeyWeeUsr))
+
+
+
+\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..29d18faa2
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,8 @@
+In the interest of fostering an open and welcoming community, we as
+contributors and maintainers need to ensure participation in our project and
+our sister projects is a harassment-free and positive experience for everyone.
+It is vital that all interaction is conducted in a manner conveying respect,
+open-mindedness and gratitude.
+
+Please consult the [latest Kivy Code of Conduct](https://github.com/kivy/kivy/blob/master/CODE_OF_CONDUCT.md).
+
diff --git a/CONTACT.md b/CONTACT.md
new file mode 100644
index 000000000..c778a77a1
--- /dev/null
+++ b/CONTACT.md
@@ -0,0 +1,6 @@
+# Contacting the Kivy Team
+
+If you are looking to contact the Kivy Team (who are responsible for managing
+the python-for-android project), including looking for support, please see our
+latest [Contact Us](https://github.com/kivy/kivy/blob/master/CONTACT.md)
+document.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..be864e06f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,250 @@
+# Contribution Guidelines
+
+python-for-android is part of the [Kivy](https://kivy.org) ecosystem - a large group of
+products used by many thousands of developers for free, but it
+is built entirely by the contributions of volunteers. We welcome (and rely on)
+users who want to give back to the community by contributing to the project.
+
+Contributions can come in many forms. See the latest
+[Contribution Guidelines](https://github.com/kivy/kivy/blob/master/CONTRIBUTING.md)
+for how you can help us.
+
+.. warning::
+ The python-for-android process differs in small but important ways from the
+ Kivy framework's process. See below.
+
+## Development model
+
+Unlike the Kivy framework, python-for-android is developed using the following
+model:
+
+- The `master` branch always represents the latest stable release.
+- The `develop` branch is the most up to date with new contributions.
+- Releases happen periodically, and consist of merging the current `develop`
+ branch into `master`.
+
+This means pull requests for python-for-android code and documentation
+submissions should be made to the `develop` branch, not the `master` branch.
+
+For reference, this is based on a
+[Git flow](https://nvie.com/posts/a-successful-git-branching-model/) model,
+although we don't follow this religiously.
+
+## Versioning
+
+python-for-android releases currently use
+[calendar versioning](https://calver.org/). Release numbers are of the form
+YYYY.MM.DD.
+
+We use calendar versioning because in practice, changes in
+python-for-android are often driven by updates or adjustments in the
+Android build tools. It's usually best for users to be working from
+the latest release. We try to maintain backwards compatibility even
+while internals are changing.
+
+## History
+
+In 2015, these tools were rewritten to provide a new, easier-to-use and
+easier-to-extend interface. If you'd like to browse the old toolchain,
+its status is
+[recorded for posterity](https://github.com/kivy/python-for-android/tree/old_toolchain).
+
+In the last quarter of 2018, the Python recipes were changed. The
+new recipe for Python3 (3.7.1) had a new build system which was
+applied to the ancient Python recipe, allowing us to bump the Python2
+version number to 2.7.15. This change unified the build process for
+both Python recipes, and probably solved various issues detected over the
+years. These **unified Python recipes** require a **minimum target api level of 21**,
+*Android 5.0 - Lollipop*. If you need to build targeting an
+api level below 21, you should use an older version of python-for-android
+(<=0.7.1).
+
+On March 2020, we dropped support for creating apps that use Python 2. The
+latest python-for-android release that supported building Python 2 was version
+2019.10.6.
+
+On August 2021, we added support for Android App Bundle (aab). As a
+collateral benefit, we now support multi-arch apk.
+
+## Creating a new release
+
+(These instructions are for core developers, not casual contributors.)
+
+New releases follow these steps:
+
+- Create a new branch `release-YYYY.MM.DD` based on the `develop` branch.
+ - `git checkout -b release-YYYY.MM.DD develop`
+- Create a Github pull request to merge `release-YYYY.MM.DD` into `master`.
+- Complete all steps in the [release checklist](#Release_checklist),
+ and document this in the pull request (copy the checklist into the PR text)
+
+At this point, wait for reviewer approval and conclude any discussion that
+arises. To complete the release:
+
+- Merge the release branch to the `master` branch.
+- Also merge the release branch to the `develop` branch.
+- Tag the release commit in `master`, with tag `vYYYY.MM.DD`. Include a short
+ summary of the changes.
+- Release distributions and PyPI upload should be
+ [handled by the CI](https://github.com/kivy/python-for-android/blob/v2020.04.29/.travis.yml#L60-L70).
+- Add to the GitHub release page (see e.g. [this example](https://github.com/kivy/python-for-android/releases/tag/v2019.06.06):
+ - The python-for-android README summary
+ - A short list of major changes in this release, if any
+ - A changelog summarising merge commits since the last release
+ - The release sdist and wheel(s)
+
+## Release checklist
+
+ - [ ] Check that the builds are passing
+ - [ ] [GitHub Action](https://github.com/kivy/python-for-android/actions)
+ - [ ] Run the tests locally via `tox`: this performs some long-running tests that are skipped on github-actions.
+ - [ ] 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.
+ - [ ] 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`:
+ - [ ] on_device_unit_tests
+ - [ ] `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`)
+ - [ ] `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`)
+ - [ ] Check that the version number is correct
+
+## How python-for-android uses `pip`
+
+*Last update: July 2019*
+
+This section is meant to provide a quick summary how
+python-for-android uses pip and Python packages in
+its build process.
+**It is written for a Python
+packager's point of view, not for regular end users or
+contributors,** to assist with making pip developers and
+other packaging experts aware of p4a's packaging needs.
+
+Please note this section just attempts to neutrally list the
+current mechanisms, so some of this isn't necessarily meant
+to stay but just how things work inside p4a in
+this very moment.
+
+
+### Basic concepts
+
+*(This part repeats other parts of the docs, for the sake of
+making this a more independent read)*
+
+p4a builds & packages a Python application for use on Android.
+It does this by providing a Java wrapper, and for graphical applications
+an SDL2-based wrapper which can be used with the Kivy framework if
+desired (or alternatively just plain PySDL2). Any such the Python application
+will likely have further library dependencies to do its work.
+
+p4a supports two types of package dependencies for a project:
+
+**Recipe:** Install a script in custom p4a format. Can either install
+C/C++ or other software that cannot be pulled in via pip, or software
+that can be installed via pip but break on Android by default.
+These are maintained primarily inside the p4a source tree by p4a
+contributors and interested folks.
+
+**Python package:** any random pip python package can be directly
+installed if it doesn't need adjustments to work for Android.
+
+p4a will map any dependency to an internal recipe if present, and
+otherwise use pip to obtain it regularly from whatever external source.
+
+
+### Install process regarding packages
+
+The install/build process of a p4a project, as triggered by the
+`p4a apk` command, roughly works as follows in regards to Python
+packages:
+
+1. The user has specified a project folder to install. This is either
+ just a folder with Python scripts and a `main.py`, or it may
+ also have a `pyproject.toml` for a more standardized install.
+
+2. Dependencies are collected: they can be either specified via
+ ``--requirements`` as a list of names or pip-style URLs, or p4a
+ can optionally scan them from a project folder via the
+ pep517 library (if there is a `pyproject.toml` or `setup.py`).
+
+3. The collected dependencies are mapped to p4a's recipes if any are
+ available for them, otherwise they're kept around as external
+ regular package references.
+
+4. All the dependencies mapped to recipes are built via p4a's internal
+ mechanisms to build these recipes. (This may or may not indirectly
+ use pip, depending on whether the recipe wraps a python package
+ or not and uses pip to install or not.)
+
+5. **If the user has specified to install the project in standardized
+ ways,** then the `setup.py`/whatever build system
+ of the project will be run. This happens with cross compilation set up
+ (`CC`/`CFLAGS`/... set to use the
+ proper toolchain) and a custom site-packages location.
+ The actual comand is a simple `pip install .` in the project folder
+ with some extra options: e.g. all dependencies that were already
+ installed by recipes will be pinned with a `-c` constraints file
+ to make sure pip won't install them, and build isolation will be
+ disabled via ``--no-build-isolation`` so pip doesn't reinstall
+ recipe-packages on its own.
+
+ **If the user has not specified to use standardized build approaches**,
+ p4a will simply install all the remaining dependencies that weren't
+ mapped to recipes directly and just plain copy in the user project
+ without installing. Any `setup.py` or `pyproject.toml` of the user
+ project will then be ignored in this step.
+
+6. Google's gradle is invoked to package it all up into an `.apk`.
+
+
+### Overall process / package relevant notes for p4a
+
+Here are some common things worth knowing about python-for-android's
+dealing with python packages:
+
+- Packages will work fine without a recipe if:
+
+ * they would also build on Linux ARM,
+ * don't use any API not available in the NDK if they use native code, and
+ * don't use any weird compiler flags the toolchain doesn't like if they use native code.
+ * works with cross compilation.
+
+- There is currently no easy way for a package to know it is being
+ cross-compiled (at least that we know of) other than examining the
+ `CC` compiler that was set, or that it is being cross-compiled for
+ Android specifically. If that breaks a package, it currently needs
+ to be worked around with a recipe.
+
+- If a package does **not** work, p4a developers will often create a
+ recipe instead of getting upstream to fix it because p4a simply
+ is too niche.
+
+- Most packages without native code will just work out of the box.
+ Many with native code tend not to, especially if complex, e.g. numpy.
+
+- Anything mapped to a p4a recipe cannot be just reinstalled by pip,
+ specifically also not inside build isolation as a dependency.
+ (It *may* work if the patches of the recipe are just relevant
+ to fix runtime issues.)
+ Therefore as of now, the best way to deal with this limitation seems
+ to be to keep build isolation always off.
+
+
+### Ideas for the future regarding packaging
+
+- We in overall prefer to use the recipe mechanism less if we can.
+ Overall, the recipes are just a collection of workarounds.
+ It may look quite hacky from the outside, since p4a
+ version pins recipe-wrapped packages usually to make the patches reliably
+ apply. This creates work for the recipes to be kept up-to-date, and
+ obviously this approach doesn't scale too well. However, it has ended
+ up as a quite practical interim solution until better ways are found.
+
+- Obviously, it would be nice if packages could know they are being
+ cross-compiled, and for Android specifically. We aren't currently aware
+ of any good mechanism for that.
+
+- If pip could actually run the recipes (instead of p4a wrapping pip and
+ doing so) then this might even allow build isolation to work - but
+ this might be too complex to get working. It might be more practical
+ to just gradually reduce the reliance on recipes instead and make
+ more packages work out of the box. This has been done e.g. with
+ improvements to the cross-compile environment being set up automatically,
+ and we're open for any ideas on how to improve this.
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..b5b2c597f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,109 @@
+# Dockerfile with:
+# - Android build environment
+# - python-for-android dependencies
+#
+# Build with:
+# docker build --tag=p4a --file Dockerfile .
+#
+# Run with:
+# docker run -it --rm p4a /bin/sh -c '. venv/bin/activate && p4a apk --help'
+#
+# Or for interactive shell:
+# docker run -it --rm p4a
+#
+# Note:
+# Use 'docker run' without '--rm' flag for keeping the container and use
+# 'docker commit <container hash> <new image>' to extend the original image
+
+# If platform is not specified, by default the target platform of the build request is used.
+# This is not what we want, as Google doesn't provide a linux/arm64 compatible NDK.
+# See: https://docs.docker.com/engine/reference/builder/#from
+FROM --platform=linux/amd64 ubuntu:22.04
+
+# configure locale
+RUN apt -y update -qq > /dev/null \
+ && DEBIAN_FRONTEND=noninteractive apt install -qq --yes --no-install-recommends \
+ locales && \
+ locale-gen en_US.UTF-8
+ENV LANG="en_US.UTF-8" \
+ LANGUAGE="en_US.UTF-8" \
+ LC_ALL="en_US.UTF-8"
+
+RUN apt -y update -qq > /dev/null \
+ && DEBIAN_FRONTEND=noninteractive apt install -qq --yes --no-install-recommends \
+ ca-certificates \
+ curl \
+ && apt -y autoremove \
+ && apt -y clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# retry helper script, refs:
+# https://github.com/kivy/python-for-android/issues/1306
+ENV RETRY="retry -t 3 --"
+RUN curl https://raw.githubusercontent.com/kadwanev/retry/1.0.1/retry \
+ --output /usr/local/bin/retry && chmod +x /usr/local/bin/retry
+
+ENV USER="user"
+ENV HOME_DIR="/home/${USER}"
+ENV WORK_DIR="${HOME_DIR}/app" \
+ PATH="${HOME_DIR}/.local/bin:${PATH}" \
+ ANDROID_HOME="${HOME_DIR}/.android" \
+ JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
+
+
+# install system dependencies
+RUN ${RETRY} apt -y update -qq > /dev/null \
+ && ${RETRY} DEBIAN_FRONTEND=noninteractive apt install -qq --yes --no-install-recommends \
+ ant \
+ autoconf \
+ automake \
+ ccache \
+ cmake \
+ g++ \
+ gcc \
+ git \
+ lbzip2 \
+ libffi-dev \
+ libltdl-dev \
+ libtool \
+ libssl-dev \
+ make \
+ openjdk-17-jdk \
+ patch \
+ pkg-config \
+ python3 \
+ python3-dev \
+ python3-pip \
+ python3-venv \
+ sudo \
+ unzip \
+ wget \
+ zip \
+ && apt -y autoremove \
+ && apt -y clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# prepare non root env
+RUN useradd --create-home --shell /bin/bash ${USER}
+
+# with sudo access and no password
+RUN usermod -append --groups sudo ${USER}
+RUN echo "%sudo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+
+WORKDIR ${WORK_DIR}
+RUN mkdir ${ANDROID_HOME} && chown --recursive ${USER} ${HOME_DIR} ${ANDROID_HOME}
+USER ${USER}
+
+# Download and install android's NDK/SDK
+COPY --chown=user:user ci/makefiles/android.mk /tmp/android.mk
+RUN make --file /tmp/android.mk \
+ && sudo rm /tmp/android.mk
+
+# install python-for-android from current branch
+COPY --chown=user:user Makefile README.md setup.py pythonforandroid/__init__.py ${WORK_DIR}/
+RUN mkdir pythonforandroid \
+ && mv __init__.py pythonforandroid/ \
+ && make virtualenv \
+ && rm -rf ~/.cache/
+
+COPY --chown=user:user . ${WORK_DIR}
diff --git a/FAQ.md b/FAQ.md
new file mode 100644
index 000000000..7450a6c44
--- /dev/null
+++ b/FAQ.md
@@ -0,0 +1,114 @@
+# FAQ for python-for-android (p4a)
+
+## Introduction
+
+python-for-android (p4a) is a development tool that packages Python apps into
+binaries that can run on Android devices.
+
+### Sibling Projects:
+
+This tool was originally developed for apps produced with
+the [Kivy framework](https://github.com/kivy/kivy), and is
+managed by the same team. However, it can be used to build other types of Python
+apps for Android.
+
+p4a is often used in conjunction
+with [Buildozer](https://github.com/kivy/buildozer), which can download, install
+and keep up-to-date any necessary prerequisites (including p4a itself), for a
+number of target platforms, using a specification file to define the build.
+
+### Is it possible to have a kiosk app on Android?
+
+Thomas Hansen wrote a detailed answer
+on [the (old) kivy-users mailing list](https://groups.google.com/d/msg/kivy-users/QKoCekAR1c0/yV-85Y_iAwoJ)
+
+Basically, you need to root the device, remove the SystemUI package, add some
+lines to the xml configuration, and you're done.
+
+### Common Errors
+
+The following are common problems and resolutions that users have reported.
+
+
+#### AttributeError: ‘Context’ object has no attribute ‘hostpython’
+
+This is a known bug in some releases. To work around it, add your python
+requirement explicitly, e.g. `--requirements=python3,kivy`. This also applies
+when using buildozer, in which case add python3 to your buildozer.spec
+requirements.
+
+#### linkname too long
+
+This can happen when you try to include a very long filename, which doesn’t
+normally happen but can occur accidentally if the p4a directory contains a
+`.buildozer` directory that is not excluded from the build (e.g. if buildozer
+was previously used). Removing this directory should fix the problem, and is
+desirable anyway since you don’t want it in the APK.
+
+#### Requested API target XX is not available, install it with the SDK android tool
+
+This means that your SDK is missing the required platform tools. You need to
+install the `platforms;android-XX` package in your SDK, using the android or
+sdkmanager tools (depending on SDK version).
+
+If using buildozer this should be done automatically, but as a workaround you
+can run these from `~/.buildozer/android/platform/android-sdk-XX/tools/android`
+
+#### SSLError(“Can’t connect to HTTPS URL because the SSL module is not available.”)
+Your hostpython3 was compiled without SSL support. You need to install the SSL
+development files before rebuilding the hostpython3 recipe. Remember to always
+clean the build before rebuilding (`p4a clean builds`, or with buildozer `buildozer
+android clean`).
+
+On Ubuntu and derivatives:
+
+ apt install libssl-dev
+ p4a clean builds # or with: buildozer `buildozer android clean
+
+On macOS:
+
+ brew install openssl
+ p4a clean builds # or with: buildozer `buildozer android clean
+
+
+#### AttributeError: 'AnsiCodes' object has no attribute 'LIGHTBLUE_EX'
+
+This occurs if your version of `colorama` is too low, install version
+0.3.3 or higher.
+
+If you install python-for-android with `pip` or via `setup.py`, this
+dependency should be taken care of automatically.
+
+#### AttributeError: 'Context' object has no attribute 'hostpython'
+
+This is a known bug in some releases. To work around it, add your
+python requirement explicitly,
+e.g. :code:`--requirements=python3,kivy`. This also applies when using
+buildozer, in which case add python3 to your buildozer.spec requirements.
+
+#### linkname too long
+
+This can happen when you try to include a very long filename, which
+doesn't normally happen but can occur accidentally if the p4a
+directory contains a .buildozer directory that is not excluded from
+the build (e.g. if buildozer was previously used). Removing this
+directory should fix the problem, and is desirable anyway since you
+don't want it in the APK.
+
+#### Requested API target 19 is not available, install it with the SDK android tool
+
+This means that your SDK is missing the required platform tools. You
+need to install the ``platforms;android-19`` package in your SDK,
+using the ``android`` or ``sdkmanager`` tools (depending on SDK
+version).
+
+If using buildozer this should be done automatically, but as a
+workaround you can run these from
+``~/.buildozer/android/platform/android-sdk-20/tools/android``.
+
+#### ModuleNotFoundError: No module named '_ctypes'
+
+You 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.
+
+After 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.
+
diff --git a/LICENSE b/LICENSE
index d5d6b13c8..4e3506010 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,6 @@
-Copyright (c) 2010-2017 Kivy Team and other contributors
+MIT License
+
+Copyright (c) 2010-2023 Kivy Team and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MANIFEST.in b/MANIFEST.in
index 06c844dd3..524ed313f 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,13 +1,13 @@
include LICENSE README.md
+include *.toml
recursive-include doc *
prune doc/build
recursive-include pythonforandroid *.py *.tmpl biglink liblink
-recursive-include pythonforandroid/recipes *.py *.patch *.c *.pyx Setup *.h
+recursive-include pythonforandroid/recipes *.py *.patch *.diff *.c *.pyx Setup *.h
-recursive-include pythonforandroid/bootstraps *.properties *.xml *.java *.tmpl *.txt *.png *.aidl *.py *.sh *.c *.h *.html
+recursive-include pythonforandroid/bootstraps *.properties *.xml *.java *.tmpl *.txt *.png *.aidl *.py *.sh *.c *.h *.html *.patch
prune .git
-prune pythonforandroid/bootstraps/pygame/build/libs
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..b894bed3e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,140 @@
+VIRTUAL_ENV ?= venv
+PIP=$(VIRTUAL_ENV)/bin/pip
+TOX=`which tox`
+ACTIVATE=$(VIRTUAL_ENV)/bin/activate
+PYTHON=$(VIRTUAL_ENV)/bin/python
+FLAKE8=$(VIRTUAL_ENV)/bin/flake8
+PYTEST=$(VIRTUAL_ENV)/bin/pytest
+SOURCES=src/ tests/
+PYTHON_MAJOR_VERSION=3
+PYTHON_MINOR_VERSION=6
+PYTHON_VERSION=$(PYTHON_MAJOR_VERSION).$(PYTHON_MINOR_VERSION)
+PYTHON_MAJOR_MINOR=$(PYTHON_MAJOR_VERSION)$(PYTHON_MINOR_VERSION)
+PYTHON_WITH_VERSION=python$(PYTHON_VERSION)
+DOCKER_IMAGE=kivy/python-for-android
+ANDROID_SDK_HOME ?= $(HOME)/.android/android-sdk
+ANDROID_NDK_HOME ?= $(HOME)/.android/android-ndk
+ANDROID_NDK_HOME_LEGACY ?= $(HOME)/.android/android-ndk-legacy
+REBUILD_UPDATED_RECIPES_EXTRA_ARGS ?= ''
+
+
+all: virtualenv
+
+$(VIRTUAL_ENV):
+ python3 -m venv $(VIRTUAL_ENV)
+ $(PIP) install Cython==0.29.36
+ $(PIP) install -e .
+
+virtualenv: $(VIRTUAL_ENV)
+
+# ignores test_pythonpackage.py since it runs for too long
+test:
+ $(TOX) -- tests/ --ignore tests/test_pythonpackage.py
+
+rebuild_updated_recipes: virtualenv
+ . $(ACTIVATE) && \
+ ANDROID_SDK_HOME=$(ANDROID_SDK_HOME) ANDROID_NDK_HOME=$(ANDROID_NDK_HOME) \
+ $(PYTHON) ci/rebuild_updated_recipes.py $(REBUILD_UPDATED_RECIPES_EXTRA_ARGS)
+
+testapps-with-numpy: testapps-with-numpy/debug/apk testapps-with-numpy/release/aab
+
+# testapps-with-numpy/MODE/ARTIFACT
+testapps-with-numpy/%: virtualenv
+ $(eval MODE := $(word 2, $(subst /, ,$@)))
+ $(eval ARTIFACT := $(word 3, $(subst /, ,$@)))
+ @echo Building testapps-with-numpy for $(MODE) mode and $(ARTIFACT) artifact
+ . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
+ python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
+ --requirements libffi,sdl2,pyjnius,kivy,python3,openssl,requests,urllib3,chardet,idna,sqlite3,setuptools,numpy \
+ --arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86 \
+ --permission "(name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)" --permission "(name=android.permission.INTERNET)"
+
+testapps-with-scipy: testapps-with-scipy/debug/apk testapps-with-scipy/release/aab
+
+# testapps-with-scipy/MODE/ARTIFACT
+testapps-with-scipy/%: virtualenv
+ $(eval MODE := $(word 2, $(subst /, ,$@)))
+ $(eval ARTIFACT := $(word 3, $(subst /, ,$@)))
+ @echo Building testapps-with-scipy for $(MODE) mode and $(ARTIFACT) artifact
+ . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
+ export LEGACY_NDK=$(ANDROID_NDK_HOME_LEGACY) && \
+ python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
+ --requirements python3,scipy,kivy \
+ --arch=armeabi-v7a --arch=arm64-v8a
+
+testapps-webview: testapps-webview/debug/apk testapps-webview/release/aab
+
+# testapps-webview/MODE/ARTIFACT
+testapps-webview/%: virtualenv
+ $(eval MODE := $(word 2, $(subst /, ,$@)))
+ $(eval ARTIFACT := $(word 3, $(subst /, ,$@)))
+ @echo Building testapps-webview for $(MODE) mode and $(ARTIFACT) artifact
+ . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
+ python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
+ --bootstrap webview \
+ --requirements sqlite3,libffi,openssl,pyjnius,flask,python3,genericndkbuild \
+ --arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86
+
+testapps-service_library-aar: virtualenv
+ . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
+ python setup.py aar --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
+ --bootstrap service_library \
+ --requirements python3 \
+ --arch=arm64-v8a --arch=x86 --release
+
+testapps-qt: testapps-qt/debug/apk testapps-qt/release/aab
+
+# testapps-webview/MODE/ARTIFACT
+testapps-qt/%: virtualenv
+ $(eval MODE := $(word 2, $(subst /, ,$@)))
+ $(eval ARTIFACT := $(word 3, $(subst /, ,$@)))
+ @echo Building testapps-qt for $(MODE) mode and $(ARTIFACT) artifact
+ . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
+ python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
+ --bootstrap qt \
+ --requirements python3,shiboken6,pyside6 \
+ --arch=arm64-v8a \
+ --local-recipes ./test_qt/recipes \
+ --qt-libs Core \
+ --load-local-libs plugins_platforms_qtforandroid \
+ --add-jar ./test_qt/jar/PySide6/jar/Qt6Android.jar \
+ --add-jar ./test_qt/jar/PySide6/jar/Qt6AndroidBindings.jar \
+ --permission android.permission.WRITE_EXTERNAL_STORAGE \
+ --permission android.permission.INTERNET
+
+testapps/%: virtualenv
+ $(eval $@_APP_ARCH := $(shell basename $*))
+ . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
+ python setup.py apk --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
+ --arch=$($@_APP_ARCH)
+
+clean:
+ find . -type d -name "__pycache__" -exec rm -r {} +
+ find . -type d -name "*.egg-info" -exec rm -r {} +
+
+clean/all: clean
+ rm -rf $(VIRTUAL_ENV) .tox/
+
+docker/pull:
+ docker pull $(DOCKER_IMAGE):latest || true
+
+docker/build:
+ docker build --cache-from=$(DOCKER_IMAGE) --tag=$(DOCKER_IMAGE) .
+
+docker/push:
+ docker push $(DOCKER_IMAGE)
+
+docker/run/test: docker/build
+ docker run --rm --env-file=.env $(DOCKER_IMAGE) 'make test'
+
+docker/run/command: docker/build
+ docker run --rm --env-file=.env $(DOCKER_IMAGE) /bin/sh -c "$(COMMAND)"
+
+docker/run/make/rebuild_updated_recipes: docker/build
+ docker run --name p4a-latest -e REBUILD_UPDATED_RECIPES_EXTRA_ARGS --env-file=.env $(DOCKER_IMAGE) make rebuild_updated_recipes
+
+docker/run/make/%: docker/build
+ docker run --rm --env-file=.env $(DOCKER_IMAGE) make $*
+
+docker/run/shell: docker/build
+ docker run --rm --env-file=.env -it $(DOCKER_IMAGE)
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..c7cbb96c9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,124 @@
+# python-for-android
+
+python-for-android (p4a) is a development tool that packages Python apps into
+binaries that can run on Android devices.
+
+It can generate:
+
+* [Android Package](https://en.wikipedia.org/wiki/Apk_(file_format)) (APK)
+ files, ready to install locally on a device, especially for testing. This format
+ is used by many [app stores](https://en.wikipedia.org/wiki/List_of_Android_app_stores)
+ but not [Google Play Store](https://play.google.com/store/).
+* [Android App Bundle](https://developer.android.com/guide/app-bundle/faq)
+ (AAB) files which can be shared on [Google Play Store](https://play.google.com/store/).
+* [Android Archive](https://developer.android.com/studio/projects/android-library)
+ (AAR) files which can be used as a re-usable bundle of resources for other
+ projects.
+
+It supports multiple CPU architectures.
+
+It supports apps developed with [Kivy framework](http://kivy.org), but was
+built to be flexible about the backend libraries (through "bootstraps"), and
+also supports [PySDL2](https://pypi.org/project/PySDL2/), and a
+[WebView](https://developer.android.com/reference/android/webkit/WebView) with
+a Python web server.
+
+It automatically supports dependencies on most pure Python packages. For other
+packages, including those that depend on C code, a special "recipe" must be
+written to support cross-compiling. python-for-android comes with recipes for
+many of the mosty popular libraries (e.g. numpy and sqlalchemy) built in.
+
+python-for-android works by cross-compiling the Python interpreter and its
+dependencies for Android devices, and bundling it with the app's python code
+and dependencies. The Python code is then interpreted on the Android device.
+
+It is recommended that python-for-android be used via
+[Buildozer](https://buildozer.readthedocs.io/), which ensures the correct
+dependencies are pre-installed, and centralizes the configuration. However,
+python-for-android is not limited to being used with Buildozer.
+
+[](#backers)
+[](#sponsors)
+[](https://github.com/kivy/python-for-android/graphs/contributors)
+[](CODE_OF_CONDUCT.md)
+
+
+
+
+[](https://github.com/kivy/python-for-android/actions?query=workflow%3A%22Unit+tests+%26+build+apps%22)
+[](https://coveralls.io/github/kivy/python-for-android?branch=develop)
+
+## Documentation
+
+More information is available in the
+[online documentation](https://python-for-android.readthedocs.io) including a
+[quickstart guide](https://python-for-android.readthedocs.io/en/latest/quickstart/).
+
+python-for-android is managed by the [Kivy team](https://kivy.org).
+
+## Support
+
+Are you having trouble using python-for-android or any of its related projects
+in the Kivy ecosystem?
+Is there an error you don’t understand? Are you trying to figure out how to use
+it? We have volunteers who can help!
+
+The best channels to contact us for support are listed in the latest
+[Contact Us](https://github.com/kivy/pyton-for-android/blob/master/CONTACT.md)
+document.
+
+## Code of Conduct
+
+In the interest of fostering an open and welcoming community, we as
+contributors and maintainers need to ensure participation in our project and
+our sister projects is a harassment-free and positive experience for everyone.
+It is vital that all interaction is conducted in a manner conveying respect,
+open-mindedness and gratitude.
+
+Please consult the [latest Code of Conduct](https://github.com/kivy/python-for-android/blob/master/CODE_OF_CONDUCT.md).
+
+## Contributors
+
+This project exists thanks to
+[all the people who contribute](https://github.com/kivy/python-for-android/graphs/contributors).
+[[Become a contributor](CONTRIBUTING.md)].
+
+<img src="https://contrib.nn.ci/api?repo=kivy/python-for-android&pages=5&no_bot=true&radius=22&cols=18">
+
+## Backers
+
+Thank you to [all of our backers](https://opencollective.com/kivy)!
+🙏 [[Become a backer](https://opencollective.com/kivy#backer)]
+
+<img src="https://opencollective.com/kivy/backers.svg?width=890&avatarHeight=44&button=false">
+
+## Sponsors
+
+Special thanks to
+[all of our sponsors, past and present](https://opencollective.com/kivy).
+Support this project by
+[[becoming a sponsor](https://opencollective.com/kivy#sponsor)].
+
+Here are our top current sponsors. Please click through to see their websites,
+and support them as they support us.
+
+<!--- See https://github.com/orgs/kivy/discussions/15 for explanation of this code. -->
+<a href="https://opencollective.com/kivy/sponsor/0/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/0/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/1/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/1/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/2/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/2/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/3/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/3/avatar.svg"></a>
+
+<a href="https://opencollective.com/kivy/sponsor/4/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/4/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/5/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/5/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/6/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/6/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/7/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/7/avatar.svg"></a>
+
+<a href="https://opencollective.com/kivy/sponsor/8/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/8/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/9/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/9/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/10/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/10/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/11/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/11/avatar.svg"></a>
+
+<a href="https://opencollective.com/kivy/sponsor/12/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/12/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/13/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/13/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/14/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/14/avatar.svg"></a>
+<a href="https://opencollective.com/kivy/sponsor/15/website" target="_blank"><img src="https://opencollective.com/kivy/sponsor/15/avatar.svg"></a>
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 63505a4ca..000000000
--- a/README.rst
+++ /dev/null
@@ -1,102 +0,0 @@
-python-for-android
-==================
-
-python-for-android is a packager for Python apps on Android. You can
-create your own Python distribution including the modules and
-dependencies you want, and bundle it in an APK along with your own code.
-
-Features include:
-
-- Support for building with both Python 2 and Python 3.
-- Different app backends including Kivy, PySDL2, and a WebView with
- Python webserver.
-- Automatic support for most pure Python modules, and built in support
- for many others, including popular dependencies such as numpy and
- sqlalchemy.
-- Multiple architecture targets, for APKs optimised on any given
- device.
-
-For documentation and support, see:
-
-- Website: http://python-for-android.readthedocs.io
-- Mailing list: https://groups.google.com/forum/#!forum/kivy-users or
- https://groups.google.com/forum/#!forum/python-android.
-
-In 2015 these tools were rewritten to provide a new, easier to use and
-extend interface. If you are looking for the old toolchain with
-distribute.sh and build.py, it is still available at
-https://github.com/kivy/python-for-android/tree/old\_toolchain, and
-issues and PRs relating to this branch are still accepted. However, the
-new toolchain contains all the same functionality via the built in
-pygame bootstrap.
-
-Documentation
-=============
-
-Follow the `quickstart
-instructions <https://python-for-android.readthedocs.org/en/latest/quickstart/>`__
-to install and begin creating APKs.
-
-Quick instructions to start would be::
-
- pip install python-for-android
-
-or to test the master branch::
-
- pip install git+https://github.com/kivy/python-for-android.git
-
-The executable is called ``python-for-android`` or ``p4a`` (both are
-equivalent). To test that the installation worked, try::
-
- python-for-android recipes
-
-This should return a list of recipes available to be built.
-
-To build any distributions, you need to set up the Android SDK and NDK
-as described in the documentation linked above.
-
-If you did this, to build an APK with SDL2 you can try e.g.::
-
- p4a apk --requirements=kivy --private /home/asandy/devel/planewave_frozen/ --package=net.inclem.planewavessdl2 --name="planewavessdl2" --version=0.5 --bootstrap=sdl2
-
-For full instructions and parameter options, see `the
-documentation <https://python-for-android.readthedocs.io/en/latest/quickstart/#usage>`__.
-
-Support
-=======
-
-If you need assistance, you can ask for help on our mailing list:
-
-- User Group: https://groups.google.com/group/kivy-users
-- Email: kivy-users@googlegroups.com
-
-We also have an IRC channel:
-
-- Server: irc.freenode.net
-- Port: 6667, 6697 (SSL only)
-- Channel: #kivy
-
-Contributing
-============
-
-We love pull requests and discussing novel ideas. Check out our
-`contribution guide <http://kivy.org/docs/contribute.html>`__ and feel
-free to improve python-for-android.
-
-The following mailing list and IRC channel are used exclusively for
-discussions about developing the Kivy framework and its sister projects:
-
-- Dev Group: https://groups.google.com/group/kivy-dev
-- Email: kivy-dev@googlegroups.com
-
-IRC channel:
-
-- Server: irc.freenode.net
-- Port: 6667, 6697 (SSL only)
-- Channel: #kivy or #kivy-dev
-
-License
-=======
-
-python-for-android is released under the terms of the MIT License.
-Please refer to the LICENSE file.
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/__init__.py b/ci/__init__.py
similarity index 100%
rename from doc/source/ext/sphinx_rtd_theme/demo_docs/source/__init__.py
rename to ci/__init__.py
diff --git a/ci/constants.py b/ci/constants.py
new file mode 100644
index 000000000..cc1d9ea70
--- /dev/null
+++ b/ci/constants.py
@@ -0,0 +1,55 @@
+from enum import Enum
+
+
+class TargetPython(Enum):
+ python3 = 2
+
+
+# recipes that currently break the build
+# a recipe could be broken for a target Python and not for the other,
+# hence we're maintaining one list per Python target
+BROKEN_RECIPES_PYTHON3 = set([
+ 'brokenrecipe',
+ # enum34 is not compatible with Python 3.6 standard library
+ # https://stackoverflow.com/a/45716067/185510
+ 'enum34',
+ # build_dir = glob.glob('build/lib.*')[0]
+ # IndexError: list index out of range
+ 'secp256k1',
+ # requires `libpq-dev` system dependency e.g. for `pg_config` binary
+ 'psycopg2',
+ # most likely some setup in the Docker container, because it works in host
+ 'pyjnius', 'pyopenal',
+ # SyntaxError: invalid syntax (Python2)
+ 'storm',
+ # mpmath package with a version >= 0.19 required
+ 'sympy',
+ 'vlc',
+ # need extra gfortran NDK system add-on
+ 'lapack', 'scipy',
+ # Outdated and there's a chance that is now useless.
+ 'zope_interface',
+ # Requires zope_interface, which is broken.
+ 'twisted',
+ # genericndkbuild is incompatible with sdl2 (which is build by default when targeting sdl2 bootstrap)
+ 'genericndkbuild',
+ # libmysqlclient gives a linker failure (See issue #2808)
+ 'libmysqlclient',
+ # boost gives errors (requires numpy? syntax error in .jam?)
+ 'boost',
+ # libtorrent gives errors (requires boost. Also, see issue #2809, to start with)
+ 'libtorrent',
+ # pybind11 build fails on macos
+ 'pybind11',
+ # pygame (likely need to be updated) is broken with newer SDL2 versions
+ 'pygame',
+])
+
+BROKEN_RECIPES = {
+ TargetPython.python3: BROKEN_RECIPES_PYTHON3,
+}
+# recipes that were already built will be skipped
+CORE_RECIPES = set([
+ 'pyjnius', 'kivy', 'openssl', 'requests', 'sqlite3', 'setuptools',
+ 'numpy', 'android', 'hostpython3', 'python3',
+])
diff --git a/ci/makefiles/android.mk b/ci/makefiles/android.mk
new file mode 100644
index 000000000..2041a6ce7
--- /dev/null
+++ b/ci/makefiles/android.mk
@@ -0,0 +1,114 @@
+# Downloads and installs the Android SDK depending on supplied platform: darwin or linux
+
+# Those android NDK/SDK variables can be override when running the file
+ANDROID_NDK_VERSION ?= 25b
+ANDROID_NDK_VERSION_LEGACY ?= 21e
+ANDROID_SDK_TOOLS_VERSION ?= 6514223
+ANDROID_SDK_BUILD_TOOLS_VERSION ?= 29.0.3
+ANDROID_HOME ?= $(HOME)/.android
+ANDROID_API_LEVEL ?= 27
+
+# per OS dictionary-like
+UNAME_S := $(shell uname -s)
+TARGET_OS_Linux = linux
+TARGET_OS_ALIAS_Linux = $(TARGET_OS_Linux)
+TARGET_OS_Darwin = darwin
+TARGET_OS_ALIAS_Darwin = mac
+TARGET_OS = $(TARGET_OS_$(UNAME_S))
+TARGET_OS_ALIAS = $(TARGET_OS_ALIAS_$(UNAME_S))
+
+ANDROID_SDK_HOME=$(ANDROID_HOME)/android-sdk
+ANDROID_SDK_TOOLS_ARCHIVE=commandlinetools-$(TARGET_OS_ALIAS)-$(ANDROID_SDK_TOOLS_VERSION)_latest.zip
+ANDROID_SDK_TOOLS_DL_URL=https://dl.google.com/android/repository/$(ANDROID_SDK_TOOLS_ARCHIVE)
+
+ANDROID_NDK_HOME=$(ANDROID_HOME)/android-ndk
+ANDROID_NDK_FOLDER=$(ANDROID_HOME)/android-ndk-r$(ANDROID_NDK_VERSION)
+ANDROID_NDK_ARCHIVE=android-ndk-r$(ANDROID_NDK_VERSION)-$(TARGET_OS).zip
+
+ANDROID_NDK_HOME_LEGACY=$(ANDROID_HOME)/android-ndk-legacy
+ANDROID_NDK_FOLDER_LEGACY=$(ANDROID_HOME)/android-ndk-r$(ANDROID_NDK_VERSION_LEGACY)
+ANDROID_NDK_ARCHIVE_LEGACY=android-ndk-r$(ANDROID_NDK_VERSION_LEGACY)-$(TARGET_OS)-x86_64.zip
+
+ANDROID_NDK_GFORTRAN_ARCHIVE_ARM64=gcc-arm64-linux-x86_64.tar.bz2
+ANDROID_NDK_GFORTRAN_ARCHIVE_ARM=gcc-arm-linux-x86_64.tar.bz2
+
+
+ANDROID_NDK_DL_URL=https://dl.google.com/android/repository/$(ANDROID_NDK_ARCHIVE)
+ANDROID_NDK_DL_URL_LEGACY=https://dl.google.com/android/repository/$(ANDROID_NDK_ARCHIVE_LEGACY)
+
+$(info Target install OS is : $(target_os))
+$(info Android SDK home is : $(ANDROID_SDK_HOME))
+$(info Android NDK home is : $(ANDROID_NDK_HOME))
+$(info Android NDK Legacy home is : $(ANDROID_NDK_HOME_LEGACY))
+$(info Android SDK download url is : $(ANDROID_SDK_TOOLS_DL_URL))
+$(info Android NDK download url is : $(ANDROID_NDK_DL_URL))
+$(info Android API level is : $(ANDROID_API_LEVEL))
+$(info Android NDK version is : $(ANDROID_NDK_VERSION))
+$(info Android NDK Legacy version is : $(ANDROID_NDK_VERSION_LEGACY))
+$(info JAVA_HOME is : $(JAVA_HOME))
+
+all: install_sdk install_ndk
+
+install_sdk: download_android_sdk extract_android_sdk update_android_sdk
+
+install_ndk: download_android_ndk download_android_ndk_legacy download_android_ndk_gfortran extract_android_ndk extract_android_ndk_legacy extract_android_ndk_gfortran
+
+download_android_sdk:
+ curl --location --progress-bar --continue-at - \
+ $(ANDROID_SDK_TOOLS_DL_URL) --output $(ANDROID_SDK_TOOLS_ARCHIVE)
+
+download_android_ndk:
+ curl --location --progress-bar --continue-at - \
+ $(ANDROID_NDK_DL_URL) --output $(ANDROID_NDK_ARCHIVE)
+
+download_android_ndk_legacy:
+ curl --location --progress-bar --continue-at - \
+ $(ANDROID_NDK_DL_URL_LEGACY) --output $(ANDROID_NDK_ARCHIVE_LEGACY)
+
+download_android_ndk_gfortran:
+ curl --location --progress-bar --continue-at - \
+ https://github.com/mzakharo/android-gfortran/releases/download/r$(ANDROID_NDK_VERSION_LEGACY)/$(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM64) --output $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM64)
+ curl --location --progress-bar --continue-at - \
+ https://github.com/mzakharo/android-gfortran/releases/download/r$(ANDROID_NDK_VERSION_LEGACY)/$(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM) --output $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM)
+
+
+# Extract android SDK and remove the compressed file
+extract_android_sdk:
+ mkdir -p $(ANDROID_SDK_HOME) \
+ && unzip -q $(ANDROID_SDK_TOOLS_ARCHIVE) -d $(ANDROID_SDK_HOME) \
+ && rm -f $(ANDROID_SDK_TOOLS_ARCHIVE)
+
+
+# Extract android NDK and remove the compressed file
+extract_android_ndk:
+ mkdir -p $(ANDROID_NDK_FOLDER) \
+ && unzip -q $(ANDROID_NDK_ARCHIVE) -d $(ANDROID_HOME) \
+ && mv $(ANDROID_NDK_FOLDER) $(ANDROID_NDK_HOME) \
+ && rm -f $(ANDROID_NDK_ARCHIVE)
+
+extract_android_ndk_legacy:
+ mkdir -p $(ANDROID_NDK_FOLDER_LEGACY) \
+ && unzip -q $(ANDROID_NDK_ARCHIVE_LEGACY) -d $(ANDROID_HOME) \
+ && mv $(ANDROID_NDK_FOLDER_LEGACY) $(ANDROID_NDK_HOME_LEGACY) \
+ && rm -f $(ANDROID_NDK_ARCHIVE_LEGACY)
+
+extract_android_ndk_gfortran:
+ rm -rf $(ANDROID_NDK_HOME_LEGACY)/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/ \
+ && mkdir $(ANDROID_NDK_HOME_LEGACY)/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/ \
+ && 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 \
+ && rm -f $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM64) \
+ && rm -rf $(ANDROID_NDK_HOME_LEGACY)/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/ \
+ && mkdir $(ANDROID_NDK_HOME_LEGACY)/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/ \
+ && 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 \
+ && rm -f $(ANDROID_NDK_GFORTRAN_ARCHIVE_ARM)
+
+
+
+# updates Android SDK, install Android API, Build Tools and accept licenses
+update_android_sdk:
+ touch $(ANDROID_HOME)/repositories.cfg
+ yes | $(ANDROID_SDK_HOME)/tools/bin/sdkmanager --sdk_root=$(ANDROID_SDK_HOME) --licenses > /dev/null
+ $(ANDROID_SDK_HOME)/tools/bin/sdkmanager --sdk_root=$(ANDROID_SDK_HOME) "build-tools;$(ANDROID_SDK_BUILD_TOOLS_VERSION)" > /dev/null
+ $(ANDROID_SDK_HOME)/tools/bin/sdkmanager --sdk_root=$(ANDROID_SDK_HOME) "platforms;android-$(ANDROID_API_LEVEL)" > /dev/null
+ # Set avdmanager permissions (executable)
+ chmod +x $(ANDROID_SDK_HOME)/tools/bin/avdmanager
diff --git a/ci/makefiles/osx.mk b/ci/makefiles/osx.mk
new file mode 100644
index 000000000..cb777ed1d
--- /dev/null
+++ b/ci/makefiles/osx.mk
@@ -0,0 +1,13 @@
+# installs Android's SDK/NDK, cython
+
+# The following variable/s can be override when running the file
+ANDROID_HOME ?= $(HOME)/.android
+
+all: upgrade_cython install_android_ndk_sdk
+
+upgrade_cython:
+ pip3 install --upgrade Cython
+
+install_android_ndk_sdk:
+ mkdir -p $(ANDROID_HOME)
+ make -f ci/makefiles/android.mk
diff --git a/ci/osx_ci.sh b/ci/osx_ci.sh
new file mode 100644
index 000000000..8cdd1ac1a
--- /dev/null
+++ b/ci/osx_ci.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -e -x
+
+arm64_set_path_and_python_version(){
+ python_version="$1"
+ if [[ $(/usr/bin/arch) = arm64 ]]; then
+ export PATH=/opt/homebrew/bin:$PATH
+ eval "$(pyenv init --path)"
+ pyenv install $python_version -s
+ pyenv global $python_version
+ export PATH=$(pyenv prefix)/bin:$PATH
+ fi
+}
\ No newline at end of file
diff --git a/ci/rebuild_updated_recipes.py b/ci/rebuild_updated_recipes.py
new file mode 100755
index 000000000..3a693a453
--- /dev/null
+++ b/ci/rebuild_updated_recipes.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Continuous Integration helper script.
+Automatically detects recipes modified in a changeset (compares with master)
+and recompiles them.
+
+To run locally, set the environment variables before running:
+```
+ANDROID_SDK_HOME=~/.buildozer/android/platform/android-sdk-20
+ANDROID_NDK_HOME=~/.buildozer/android/platform/android-ndk-r9c
+./ci/rebuild_update_recipes.py
+```
+
+Current limitations:
+- will fail on conflicting requirements
+ e.g. https://travis-ci.org/AndreMiras/python-for-android/builds/438840800
+ the list of recipes was huge and result was:
+ [ERROR]: Didn't find any valid dependency graphs.
+ [ERROR]: This means that some of your requirements pull in conflicting dependencies.
+- only rebuilds on sdl2 bootstrap
+"""
+import sh
+import os
+import sys
+import argparse
+from pythonforandroid.build import Context
+from pythonforandroid import logger
+from pythonforandroid.toolchain import current_directory
+from pythonforandroid.recipe import Recipe
+from ci.constants import TargetPython, CORE_RECIPES, BROKEN_RECIPES
+
+
+def modified_recipes(branch='origin/develop'):
+ """
+ Returns a set of modified recipes between the current branch and the one
+ in param.
+ """
+ # using the contrib version on purpose rather than sh.git, since it comes
+ # with a bunch of fixes, e.g. disabled TTY, see:
+ # https://stackoverflow.com/a/20128598/185510
+ git_diff = sh.contrib.git.diff('--name-only', branch)
+ recipes = set()
+ for file_path in git_diff:
+ if 'pythonforandroid/recipes/' in file_path:
+ recipe = file_path.split('/')[2]
+ recipes.add(recipe)
+ return recipes
+
+
+def build(target_python, requirements, archs):
+ """
+ Builds an APK given a target Python and a set of requirements.
+ """
+ if not requirements:
+ return
+ android_sdk_home = os.environ['ANDROID_SDK_HOME']
+ android_ndk_home = os.environ['ANDROID_NDK_HOME']
+ requirements.add(target_python.name)
+ requirements = ','.join(requirements)
+ logger.info('requirements: {}'.format(requirements))
+
+ with current_directory('testapps/on_device_unit_tests/'):
+ # iterates to stream the output
+ for line in sh.python(
+ 'setup.py', 'apk', '--sdk-dir', android_sdk_home,
+ '--ndk-dir', android_ndk_home, '--requirements',
+ requirements, *[f"--arch={arch}" for arch in archs],
+ _err_to_out=True, _iter=True):
+ print(line)
+
+
+def main():
+ parser = argparse.ArgumentParser("rebuild_updated_recipes")
+ parser.add_argument(
+ "--arch",
+ help="The archs to build for during tests",
+ action="append",
+ default=[],
+ )
+ args, unknown = parser.parse_known_args(sys.argv[1:])
+
+ logger.info(f"Building updated recipes for the following archs: {args.arch}")
+
+ target_python = TargetPython.python3
+ recipes = modified_recipes()
+ logger.info('recipes modified: {}'.format(recipes))
+ recipes -= CORE_RECIPES
+ logger.info('recipes to build: {}'.format(recipes))
+ context = Context()
+
+ # removing the deleted recipes for the given target (if any)
+ for recipe_name in recipes.copy():
+ try:
+ Recipe.get_recipe(recipe_name, context)
+ except ValueError:
+ # recipe doesn't exist, so probably we remove it
+ recipes.remove(recipe_name)
+ logger.warning(
+ 'removed {} from recipes because deleted'.format(recipe_name)
+ )
+
+ # removing the known broken recipe for the given target
+ broken_recipes = BROKEN_RECIPES[target_python]
+ recipes -= broken_recipes
+ logger.info('recipes to build (no broken): {}'.format(recipes))
+ build(target_python, recipes, args.arch)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 000000000..2a72e6870
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,2 @@
+Sphinx~=7.2.6
+furo==2023.9.10
\ No newline at end of file
diff --git a/doc/source/apis.rst b/doc/source/apis.rst
index 4d564ceda..7d94b7446 100644
--- a/doc/source/apis.rst
+++ b/doc/source/apis.rst
@@ -5,178 +5,97 @@ Working on Android
This page gives details on accessing Android APIs and managing other
interactions on Android.
+Storage paths
+-------------
-Accessing Android APIs
-----------------------
+If you want to store and retrieve data, you shouldn't just save to
+the current directory, and not hardcode `/sdcard/` or some other
+path either - it might differ per device.
-When writing an Android application you may want to access the normal
-Android Java APIs, in order to control your application's appearance
-(fullscreen, orientation etc.), interact with other apps or use
-hardware like vibration and sensors.
+Instead, the `android` module which you can add to your `--requirements`
+allows you to query the most commonly required paths::
-You can access these with `Pyjnius
-<http://pyjnius.readthedocs.org/en/latest/>`_, a Python library for
-automatically wrapping Java and making it callable from Python
-code. Pyjnius is fairly simple to use, but not very Pythonic and it
-inherits Java's verbosity. For this reason the Kivy organisation also
-created `Plyer <https://plyer.readthedocs.org/en/latest/>`_, which
-further wraps specific APIs in a Pythonic and cross-platform way; you
-can call the same code in Python but have it do the right thing also
-on platforms other than Android.
+ from android.storage import app_storage_path
+ settings_path = app_storage_path()
-Pyjnius and Plyer are independent projects whose documentation is
-linked above. See below for some simple introductory examples, and
-explanation of how to include these modules in your APKs.
+ from android.storage import primary_external_storage_path
+ primary_ext_storage = primary_external_storage_path()
-This page also documents the ``android`` module which you can include
-with p4a, but this is mostly replaced by Pyjnius and is not
-recommended for use in new applications.
+ from android.storage import secondary_external_storage_path
+ secondary_ext_storage = secondary_external_storage_path()
+`app_storage_path()` gives you Android's so-called "internal storage"
+which is specific to your app and cannot seen by others or the user.
+It compares best to the AppData directory on Windows.
-Using Pyjnius
-~~~~~~~~~~~~~
+`primary_external_storage_path()` returns Android's so-called
+"primary external storage", often found at `/sdcard/` and potentially
+accessible to any other app.
+It compares best to the Documents directory on Windows.
+Requires `Permission.WRITE_EXTERNAL_STORAGE` to read and write to.
-Pyjnius lets you call the Android API directly from Python Pyjnius is
-works by dynamically wrapping Java classes, so you don't have to wait
-for any particular feature to be pre-supported.
-
-You can include Pyjnius in your APKs by adding `pyjnius` to your build
-requirements, e.g. :code:`--requirements=flask,pyjnius`. It is
-automatically included in any APK containing Kivy, in which case you
-don't need to specify it manually.
-
-The basic mechanism of Pyjnius is the `autoclass` command, which wraps
-a Java class. For instance, here is the code to vibrate your device::
-
- from jnius import autoclass
-
- # We need a reference to the Java activity running the current
- # application, this reference is stored automatically by
- # Kivy's PythonActivity bootstrap
-
- # This one works with Pygame
- # PythonActivity = autoclass('org.renpy.android.PythonActivity')
-
- # This one works with SDL2
- PythonActivity = autoclass('org.kivy.android.PythonActivity')
-
- activity = PythonActivity.mActivity
-
- Context = autoclass('android.content.Context')
- vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
-
- vibrator.vibrate(10000) # the argument is in milliseconds
-
-Things to note here are:
-
-- The class that must be wrapped depends on the bootstrap. This is
- because Pyjnius is using the bootstrap's java source code to get a
- reference to the current activity, which both the Pygame and SDL2
- bootstraps store in the ``mActivity`` static variable. This
- difference isn't always important, but it's important to know about.
-- The code closely follows the Java API - this is exactly the same set
- of function calls that you'd use to achieve the same thing from Java
- code.
-- This is quite verbose - it's a lot of lines to achieve a simple
- vibration!
-
-These emphasise both the advantages and disadvantage of Pyjnius; you
-*can* achieve just about any API call with it (though the syntax is
-sometimes a little more involved, particularly if making Java classes
-from Python code), but it's not Pythonic and it's not short. These are
-problems that Plyer, explained below, attempts to address.
-
-You can check the `Pyjnius documentation <Pyjnius_>`_ for further details.
-
-
-Using Plyer
-~~~~~~~~~~~
-
-Plyer provides a much less verbose, Pythonic wrapper to
-platform-specific APIs. It supports Android as well as iOS and desktop
-operating systems, though plyer is a work in progress and not all
-platforms support all Plyer calls yet.
-
-Plyer does not support all APIs yet, but you can always Pyjnius to
-call anything that is currently missing.
+`secondary_external_storage_path()` returns Android's so-called
+"secondary external storage", often found at `/storage/External_SD/`.
+It compares best to an external disk plugged to a Desktop PC, and can
+after a device restart become inaccessible if removed.
+Requires `Permission.WRITE_EXTERNAL_STORAGE` to read and write to.
-You can include Plyer in your APKs by adding the `Plyer` recipe to
-your build requirements, e.g. :code:`--requirements=plyer`.
+.. warning::
+ Even if `secondary_external_storage_path` returns a path
+ the external sd card may still not be present.
+ Only non-empty contents or a successful write indicate that it is.
-You should check the `Plyer documentation <Plyer_>`_ for details of all supported
-facades (platform APIs), but as an example the following is how you
-would achieve vibration as described in the Pyjnius section above::
+Read more on all the different storage types and what to use them for
+in the Android documentation:
- from plyer.vibrator import vibrate
- vibrate(10) # in Plyer, the argument is in seconds
+https://developer.android.com/training/data-storage
-This is obviously *much* less verbose than with Pyjnius!
+A note on permissions
+~~~~~~~~~~~~~~~~~~~~~
+Only the internal storage is always accessible with no additional
+permissions. For both primary and secondary external storage, you need
+to obtain `Permission.WRITE_EXTERNAL_STORAGE` **and the user may deny it.**
+Also, if you get it, both forms of external storage may only allow
+your app to write to the common pre-existing folders like "Music",
+"Documents", and so on. (see the Android Docs linked above for details)
-Using ``android``
-~~~~~~~~~~~~~~~~~
+Runtime permissions
+-------------------
-This Cython module was used for Android API interaction with Kivy's old
-interface, but is now mostly replaced by Pyjnius.
+With API level >= 21, you will need to request runtime permissions
+to access the SD card, the camera, and other things.
-The ``android`` Python module can be included by adding it to your
-requirements, e.g. :code:`--requirements=kivy,android`. It is not
-automatically included by Kivy unless you use the old (Pygame)
-bootstrap.
+This can be done through the `android` module which is *available per default*
+unless you blacklist it. Use it in your app like this::
-This module is not separately documented. You can read the source `on
-Github
-<https://github.com/kivy/python-for-android/tree/master/pythonforandroid/recipes/android/src/android>`__.
-
-One useful facility of this module is to make
-:code:`webbrowser.open()` work on Android. You can replicate this
-effect without using the android module via the following
-code::
-
- from jnius import autoclass
+ from android.permissions import request_permissions, Permission
+ request_permissions([Permission.WRITE_EXTERNAL_STORAGE])
- def open_url(url):
- Intent = autoclass('android.content.Intent')
- Uri = autoclass('android.net.Uri')
- browserIntent = Intent()
- browserIntent.setAction(Intent.ACTION_VIEW)
- browserIntent.setData(Uri.parse(url))
- currentActivity = cast('android.app.Activity', mActivity)
- currentActivity.startActivity(browserIntent)
+The available permissions are listed here:
- class AndroidBrowser(object):
- def open(self, url, new=0, autoraise=True):
- open_url(url)
- def open_new(self, url):
- open_url(url)
- def open_new_tab(self, url):
- open_url(url)
+https://developer.android.com/reference/android/Manifest.permission
- import webbrowser
- webbrowser.register('android', AndroidBrowser, None, -1)
-
-Working with the App lifecycle
-------------------------------
+Other common tasks
+------------------
Dismissing the splash screen
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-With the SDL2 bootstrap, the app's splash screen may not be dismissed
-immediately when your app has finished loading, due to a limitation
-with the way we check if the app has properly started. In this case,
-the splash screen overlaps the app gui for a short time.
+With the SDL2 bootstrap, the app's splash screen may be visible
+longer than necessary (with your app already being loaded) due to a
+limitation with the way we check if the app has properly started.
+In this case, the splash screen overlaps the app gui for a short time.
-You can dismiss the splash screen as follows. Run this code from your
-app build method (or use ``kivy.clock.Clock.schedule_once`` to run it
-in the following frame)::
+To dismiss the loading screen explicitly in your code, use the `android`
+module::
- from jnius import autoclass
- activity = autoclass('org.kivy.android.PythonActivity').mActivity
- activity.removeLoadingScreen()
+ from android import loadingscreen
+ loadingscreen.hide_loading_screen()
-This problem does not affect the Pygame bootstrap, as it uses a
-different splash screen method.
+You can call it e.g. using ``kivy.clock.Clock.schedule_once`` to run it
+in the first active frame of your app, or use the app build method.
Handling the back button
@@ -222,3 +141,305 @@ With Kivy, add an ``on_pause`` method to your App class, which returns True::
With the webview bootstrap, pausing should work automatically.
Under SDL2, you can handle the `appropriate events <https://wiki.libsdl.org/SDL_EventType>`__ (see SDL_APP_WILLENTERBACKGROUND etc.).
+
+
+Observing Activity result
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. module:: android.activity
+
+The 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)>`_.
+
+.. function:: bind(eventname=callback, ...)
+
+ This allows you to bind a callback to an Android event:
+ - ``on_new_intent`` is the event associated to the onNewIntent java call
+ - ``on_activity_result`` is the event associated to the onActivityResult java call
+
+ .. warning::
+
+ This method is not thread-safe. Call it in the mainthread of your app. (tips: use kivy.clock.mainthread decorator)
+
+.. function:: unbind(eventname=callback, ...)
+
+ Unregister a previously registered callback with :func:`bind`.
+
+Example::
+
+ # This example is a snippet from an NFC p2p app implemented with Kivy.
+
+ from android import activity
+
+ def on_new_intent(self, intent):
+ if intent.getAction() != NfcAdapter.ACTION_NDEF_DISCOVERED:
+ return
+ rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
+ if not rawmsgs:
+ return
+ for message in rawmsgs:
+ message = cast(NdefMessage, message)
+ payload = message.getRecords()[0].getPayload()
+ print('payload: {}'.format(''.join(map(chr, payload))))
+
+ def nfc_enable(self):
+ activity.bind(on_new_intent=self.on_new_intent)
+ # ...
+
+ def nfc_disable(self):
+ activity.unbind(on_new_intent=self.on_new_intent)
+ # ...
+
+
+Activity lifecycle handling
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Android ``Application`` class provides the `ActivityLifecycleCallbacks
+<https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks>`_
+interface where callbacks can be registered corresponding to `activity
+lifecycle
+<https://developer.android.com/guide/components/activities/activity-lifecycle>`_
+changes. These callbacks can be used to implement logic in the Python app when
+the activity changes lifecycle states.
+
+Note that some of the callbacks are not useful in the Python app. For example,
+an `onActivityCreated` callback will never be run since the the activity's
+`onCreate` callback will complete before the Python app is running. Similarly,
+saving instance state in an `onActivitySaveInstanceState` callback will not be
+helpful since the Python app doesn't have access to the restored instance
+state.
+
+.. function:: register_activity_lifecycle_callbacks(callbackname=callback, ...)
+
+ This allows you to bind a callbacks to Activity lifecycle state changes.
+ The callback names correspond to ``ActivityLifecycleCallbacks`` method
+ names such as ``onActivityStarted``. See the `ActivityLifecycleCallbacks
+ <https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks>`_
+ documentation for names and function signatures for the callbacks.
+
+.. function:: unregister_activity_lifecycle_callbacks(instance)
+
+ Unregister a ``ActivityLifecycleCallbacks`` instance previously registered
+ with :func:`register_activity_lifecycle_callbacks`.
+
+Example::
+
+ from android.activity import register_activity_lifecycle_callbacks
+
+ def on_activity_stopped(activity):
+ print('Activity is stopping')
+
+ register_activity_lifecycle_callbacks(
+ onActivityStopped=on_activity_stopped,
+ )
+
+
+Receiving Broadcast message
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. module:: android.broadcast
+
+Implementation of the android `BroadcastReceiver
+<https://developer.android.com/reference/android/content/BroadcastReceiver.html>`_.
+You can specify the callback that will receive the broadcast event, and actions
+or categories filters.
+
+.. class:: BroadcastReceiver
+
+ .. warning::
+
+ The callback will be called in another thread than the main thread. In
+ that thread, be careful not to access OpenGL or something like that.
+
+ .. method:: __init__(callback, actions=None, categories=None)
+
+ :param callback: function or method that will receive the event. Will
+ receive the context and intent as argument.
+ :param actions: list of strings that represent an action.
+ :param categories: list of strings that represent a category.
+
+ For actions and categories, the string must be in lower case, without the prefix::
+
+ # In java: Intent.ACTION_HEADSET_PLUG
+ # In python: 'headset_plug'
+
+ .. method:: start()
+
+ Register the receiver with all the actions and categories, and start
+ handling events.
+
+ .. method:: stop()
+
+ Unregister the receiver with all the actions and categories, and stop
+ handling events.
+
+Example::
+
+ class TestApp(App):
+
+ def build(self):
+ self.br = BroadcastReceiver(
+ self.on_broadcast, actions=['headset_plug'])
+ self.br.start()
+ # ...
+
+ def on_broadcast(self, context, intent):
+ extras = intent.getExtras()
+ headset_state = bool(extras.get('state'))
+ if headset_state:
+ print('The headset is plugged')
+ else:
+ print('The headset is unplugged')
+
+ # Don't forget to stop and restart the receiver when the app is going
+ # to pause / resume mode
+
+ def on_pause(self):
+ self.br.stop()
+ return True
+
+ def on_resume(self):
+ self.br.start()
+
+Runnable
+~~~~~~~~
+
+.. module:: android.runnable
+
+:class:`Runnable` is a wrapper around the Java `Runnable
+<https://developer.android.com/reference/java/lang/Runnable.html>`_ class. This
+class can be used to schedule a call of a Python function into the
+`PythonActivity` thread.
+
+Example::
+
+ from android.runnable import Runnable
+
+ def helloworld(arg):
+ print 'Called from PythonActivity with arg:', arg
+
+ Runnable(helloworld)('hello')
+
+Or use our decorator::
+
+ from android.runnable import run_on_ui_thread
+
+ @run_on_ui_thread
+ def helloworld(arg):
+ print 'Called from PythonActivity with arg:', arg
+
+ helloworld('arg1')
+
+
+This can be used to prevent errors like:
+
+ - W/System.err( 9514): java.lang.RuntimeException: Can't create handler
+ inside thread that has not called Looper.prepare()
+ - NullPointerException in ActivityThread.currentActivityThread()
+
+.. warning::
+
+ Because the python function is called from the PythonActivity thread, you
+ need to be careful about your own calls.
+
+
+Advanced Android API use
+------------------------
+
+.. _reference-label-for-android-module:
+
+`android` for Android API access
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As mentioned above, the ``android`` Python module provides a simple
+wrapper around many native Android APIS, and it is *included by default*
+unless you blacklist it.
+
+The available functionality of this module is not separately documented.
+You can read the source `on
+Github
+<https://github.com/kivy/python-for-android/tree/master/pythonforandroid/recipes/android/src/android>`__.
+
+Also please note you can replicate most functionality without it using
+`pyjnius`. (see below)
+
+
+`Plyer` - a more comprehensive API wrapper
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Plyer provides a more thorough wrapper than `android` for a much larger
+area of platform-specific APIs, supporting not only Android but also
+iOS and desktop operating systems.
+(Though plyer is a work in progress and not all
+platforms support all Plyer calls yet)
+
+Plyer does not support all APIs yet, but you can always use Pyjnius to
+call anything that is currently missing.
+
+You can include Plyer in your APKs by adding the `Plyer` recipe to
+your build requirements, e.g. :code:`--requirements=plyer`.
+
+You should check the `Plyer documentation <https://plyer.readthedocs.io/en/stable/>`_ for details of all supported
+facades (platform APIs), but as an example the following is how you
+would achieve vibration as described in the Pyjnius section above::
+
+ from plyer.vibrator import vibrate
+ vibrate(10) # in Plyer, the argument is in seconds
+
+This is obviously *much* less verbose than with Pyjnius!
+
+
+`Pyjnius` - raw lowlevel API access
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Pyjnius lets you call the Android API directly from Python Pyjnius is
+works by dynamically wrapping Java classes, so you don't have to wait
+for any particular feature to be pre-supported.
+
+This is particularly useful when `android` and `plyer` don't already
+provide a convenient access to the API, or you need more control.
+
+You can include Pyjnius in your APKs by adding `pyjnius` to your build
+requirements, e.g. :code:`--requirements=flask,pyjnius`. It is
+automatically included in any APK containing Kivy, in which case you
+don't need to specify it manually.
+
+The basic mechanism of Pyjnius is the `autoclass` command, which wraps
+a Java class. For instance, here is the code to vibrate your device::
+
+ from jnius import autoclass
+
+ # We need a reference to the Java activity running the current
+ # application, this reference is stored automatically by
+ # Kivy's PythonActivity bootstrap
+
+ # This one works with SDL2
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+
+ activity = PythonActivity.mActivity
+
+ Context = autoclass('android.content.Context')
+ vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
+
+ vibrator.vibrate(10000) # the argument is in milliseconds
+
+Things to note here are:
+
+- The class that must be wrapped depends on the bootstrap. This is
+ because Pyjnius is using the bootstrap's java source code to get a
+ reference to the current activity, which the bootstraps store in the
+ ``mActivity`` static variable. This difference isn't always
+ important, but it's important to know about.
+- The code closely follows the Java API - this is exactly the same set
+ of function calls that you'd use to achieve the same thing from Java
+ code.
+- This is quite verbose - it's a lot of lines to achieve a simple
+ vibration!
+
+These emphasise both the advantages and disadvantage of Pyjnius; you
+*can* achieve just about any API call with it (though the syntax is
+sometimes a little more involved, particularly if making Java classes
+from Python code), but it's not Pythonic and it's not short. These are
+problems that Plyer, explained below, attempts to address.
+
+You can check the `Pyjnius documentation <https://pyjnius.readthedocs.io/en/latest/>`_ for further details.
+
diff --git a/doc/source/bootstraps.rst b/doc/source/bootstraps.rst
index c52293331..db82d56eb 100644
--- a/doc/source/bootstraps.rst
+++ b/doc/source/bootstraps.rst
@@ -3,7 +3,7 @@ Bootstraps
==========
This page is about creating new bootstrap backends. For build options
-of existing bootstraps (i.e. with SDL2, Pygame, Webview etc.), see
+of existing bootstraps (i.e. with SDL2, Webview, etc.), see
:ref:`build options <bootstrap_build_options>`.
python-for-android (p4a) supports multiple *bootstraps*. These fulfill a
@@ -14,13 +14,14 @@ components such as Android source code and various build files.
This page describes the basics of how bootstraps work so that you can
create and use your own if you like, making it easy to build new kinds
-of Python project for Android.
+of Python projects for Android.
Creating a new bootstrap
------------------------
-A bootstrap class consists of just a few basic components, though one of them must do a lot of work.
+A bootstrap class consists of just a few basic components, though one of them
+must do a lot of work.
For instance, the SDL2 bootstrap looks like the following::
diff --git a/doc/source/buildoptions.rst b/doc/source/buildoptions.rst
index bb95d3969..782bd6729 100644
--- a/doc/source/buildoptions.rst
+++ b/doc/source/buildoptions.rst
@@ -8,44 +8,13 @@ This page contains instructions for using different build options.
Python versions
---------------
-python2
-~~~~~~~
-
-Select this by adding it in your requirements, e.g. ``--requirements=python2``.
-
-This option builds Python 2.7.2 for your selected Android
-architecture. There are no special requirements, all the building is
-done locally.
-
-The python2 build is also the way python-for-android originally
-worked, even in the old toolchain.
-
-
-python3
-~~~~~~~
-
-.. warning::
- Python3 support is experimental, and some of these details
- may change as it is improved and fully stabilised.
+python-for-android supports using Python 3.8 or higher. To explicitly select a Python
+version in your requirements, use e.g. ``--requirements=python3==3.10.11,hostpython3==3.10.11``.
-.. note:: You must manually download the `CrystaX NDK
- <https://www.crystax.net/android/ndk>`__ and tell
- python-for-android to use it with ``--ndk-dir /path/to/NDK``.
+The last python-for-android version supporting Python2 was `v2019.10.06 <https://github.com/kivy/python-for-android/archive/v2019.10.06.zip>`__
-Select this by adding the ``python3crystax`` recipe to your
-requirements, e.g. ``--requirements=python3crystax``.
-
-This uses the prebuilt Python from the `CrystaX NDK
-<https://www.crystax.net/android/ndk>`__, a drop-in replacement for
-Google's official NDK which includes many improvements. You
-*must* use the CrystaX NDK 10.3.0 or higher when building with
-python3. You can get it `here
-<https://www.crystax.net/en/download>`__.
-
-The python3crystax build is handled quite differently to python2 so
-there may be bugs or surprising behaviours. If you come across any,
-feel free to `open an issue
-<https://github.com/kivy/python-for-android>`__.
+Python-for-android no longer supports building for Python 3 using the CrystaX
+NDK. The last python-for-android version supporting CrystaX was `0.7.0 <https://github.com/kivy/python-for-android/archive/0.7.0.zip>`__
.. _bootstrap_build_options:
@@ -65,7 +34,7 @@ sdl2
~~~~
Use this with ``--bootstrap=sdl2``, or just include the
-``sdl2`` recipe, e.g. ``--requirements=sdl2,python2``.
+``sdl2`` recipe, e.g. ``--requirements=sdl2,python3``.
SDL2 is a popular cross-platform depelopment library, particularly for
games. It has its own Android project support, which
@@ -85,24 +54,47 @@ The sdl2 bootstrap supports the following additional command line
options (this list may not be exhaustive):
- ``--private``: The directory containing your project files.
-- ``--package``: The Java package name for your project. Choose e.g. ``org.example.yourapp``.
+- ``--package``: The Java package name for your project. e.g. ``org.example.yourapp``.
- ``--name``: The app name.
- ``--version``: The version number.
-- ``--orientation``: Usually one of ``portait``, ``landscape``,
- ``sensor`` to automatically rotate according to the device
- orientation, or ``user`` to do the same but obeying the user's
- settings. The full list of valid options is given under
- ``android:screenOrientation`` in the `Android documentation
- <https://developer.android.com/guide/topics/manifest/activity-element.html>`__.
+- ``--orientation``: The orientations that the app will display in.
+ (Available options are ``portrait``, ``landscape``, ``portrait-reverse``, ``landscape-reverse``).
+ Since Android ignores ``android:screenOrientation`` when in multi-window mode
+ (Which is the default on Android 12+), this option will also set the window orientation hints
+ for the SDL bootstrap. If multiple orientations are given,
+ ``android:screenOrientation`` will be set to ``unspecified``.
+- ``--manifest-orientation``: The orientation that will be set for the ``android:screenOrientation``
+ attribute of the activity in the ``AndroidManifest.xml`` file. If not set, the value
+ will be synthesized from the ``--orientation`` option.
+ The full list of valid options is given under ``android:screenOrientation``
+ in the `Android documentation <https://developer.android.com/guide/topics/manifest/activity-element.html>`__.
- ``--icon``: A path to the png file to use as the application icon.
-- ``--permission``: A permission name for the app,
- e.g. ``--permission VIBRATE``. For multiple permissions, add
- multiple ``--permission`` arguments.
+- ``--permission``: A permission that needs to be declared into the App ``AndroidManifest.xml``.
+ For multiple permissions, add multiple ``--permission`` arguments.
+ ``--home-app`` Gives you the option to set your application as a home app (launcher) on your Android device.
+
+ .. Note ::
+ ``--permission`` accepts the following syntaxes:
+ ``--permission (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)``
+ or ``--permission android.permission.WRITE_EXTERNAL_STORAGE``.
+
+ The first syntax is used to set additional properties to the permission
+ (``android:maxSdkVersion`` and ``android:usesPermissionFlags`` are the only ones supported for now).
+
+ The second one can be used when there's no need to add any additional properties.
+
+ .. Warning ::
+ The syntax ``--permission VIBRATE`` (only the permission name, without the prefix),
+ is also supported for backward compatibility, but it will be removed in the future.
+
+
- ``--meta-data``: Custom key=value pairs to add in the application metadata.
- ``--presplash``: A path to the image file to use as a screen while
the application is loading.
- ``--presplash-color``: The presplash screen background color, of the
form ``#RRGGBB`` or a color name ``red``, ``green``, ``blue`` etc.
+- ``--presplash-lottie``: use a lottie (json) file as a presplash animation. If
+ used, this will replace the static presplash image.
- ``--wakelock``: If the argument is included, the application will
prevent the device from sleeping.
- ``--window``: If the argument is included, the application will not
@@ -118,14 +110,16 @@ options (this list may not be exhaustive):
- ``--service``: A service name and the Python script it should
run. See :ref:`arbitrary_scripts_services`.
- ``--add-source``: Add a source directory to the app's Java code.
-- ``--no-compile-pyo``: Do not optimise .py files to .pyo.
+- ``--no-byte-compile-python``: Skip byte compile for .py files.
+- ``--enable-androidx``: Enable AndroidX support library.
+- ``--add-resource``: Put this file or directory in the apk res directory.
webview
~~~~~~~
You can use this with ``--bootstrap=webview``, or include the
-``webviewjni`` recipe, e.g. ``--requirements=webviewjni,python2``.
+``webviewjni`` recipe, e.g. ``--requirements=webviewjni,python3``.
The webview bootstrap gui is, per the name, a WebView displaying a
webpage, but this page is hosted on the device via a Python
@@ -145,17 +139,21 @@ started), it will instead display a loading screen until the server is
ready.
- ``--private``: The directory containing your project files.
-- ``--package``: The Java package name for your project. Choose e.g. ``org.example.yourapp``.
+- ``--package``: The Java package name for your project. e.g. ``org.example.yourapp``.
- ``--name``: The app name.
- ``--version``: The version number.
-- ``--orientation``: Usually one of ``portait``, ``landscape``,
- ``sensor`` to automatically rotate according to the device
- orientation, or ``user`` to do the same but obeying the user's
- settings. The full list of valid options is given under
- ``android:screenOrientation`` in the `Android documentation
- <https://developer.android.com/guide/topics/manifest/activity-element.html>`__.
+- ``--orientation``: The orientations that the app will display in.
+ (Available options are ``portrait``, ``landscape``, ``portrait-reverse``, ``landscape-reverse``).
+ Since Android ignores ``android:screenOrientation`` when in multi-window mode
+ (Which is the default on Android 12+), this setting is not guaranteed to work, and
+ you should consider to implement a custom orientation change handler in your app.
+- ``--manifest-orientation``: The orientation that will be set in the ``android:screenOrientation``
+ attribute of the activity in the ``AndroidManifest.xml`` file. If not set, the value
+ will be synthesized from the ``--orientation`` option.
+ The full list of valid options is given under ``android:screenOrientation``
+ in the `Android documentation <https://developer.android.com/guide/topics/manifest/activity-element.html>`__.
- ``--icon``: A path to the png file to use as the application icon.
-- ``-- permission``: A permission name for the app,
+- ``--permission``: A permission name for the app,
e.g. ``--permission VIBRATE``. For multiple permissions, add
multiple ``--permission`` arguments.
- ``--meta-data``: Custom key=value pairs to add in the application metadata.
@@ -182,58 +180,91 @@ ready.
access. Defaults to 5000.
-pygame
-~~~~~~
+service_library
+~~~~~~~~~~~~~~~
-You can use this with ``--bootstrap=pygame``, or simply include the
-``pygame`` recipe in your ``--requirements``.
+You can use this with ``--bootstrap=service_library`` option.
-The pygame bootstrap is the original backend used by Kivy, and still
-works fine for use with Kivy apps. It may also work for pure pygame
-apps, but hasn't been developed with this in mind.
-This bootstrap will eventually be deprecated in favour of sdl2, but
-not before the sdl2 bootstrap includes all the features that would be
-lost.
-
-Build options
-%%%%%%%%%%%%%
-
-The pygame bootstrap supports the following additional command line
-options (this list may not be exhaustive):
+This bootstrap can be used together with ``aar`` output target to generate
+a library, containing Python services that can be used with other build
+systems and frameworks.
- ``--private``: The directory containing your project files.
-- ``--dir``: The directory containing your project files if you want
- them to be unpacked to the external storage directory rather than
- the app private directory.
-- ``--package``: The Java package name for your project. Choose e.g. ``org.example.yourapp``.
-- ``--name``: The app name.
+- ``--package``: The Java package name for your project. e.g. ``org.example.yourapp``.
+- ``--name``: The library name.
- ``--version``: The version number.
-- ``--orientation``: One of ``portait``, ``landscape`` or ``sensor``
- to automatically rotate according to the device orientation.
-- ``--icon``: A path to the png file to use as the application icon.
-- ``--ignore-path``: A path to ignore when including the app
- files. Pass multiple times to ignore multiple paths.
-- ``-- permission``: A permission name for the app,
- e.g. ``--permission VIBRATE``. For multiple permissions, add
- multiple ``--permission`` arguments.
-- ``--meta-data``: Custom key=value pairs to add in the application metadata.
-- ``--presplash``: A path to the image file to use as a screen while
- the application is loading.
-- ``--wakelock``: If the argument is included, the application will
- prevent the device from sleeping.
-- ``--window``: If the argument is included, the application will not
- cover the Android status bar.
+- ``--service``: A service name and the Python script it should
+ run. See :ref:`arbitrary_scripts_services`.
- ``--blacklist``: The path to a file containing blacklisted patterns
- that will be excluded from the final APK. Defaults to ``./blacklist.txt``.
+ that will be excluded from the final AAR. Defaults to ``./blacklist.txt``.
- ``--whitelist``: The path to a file containing whitelisted patterns
- that will be included in the APK even if also blacklisted.
+ that will be included in the AAR even if also blacklisted.
- ``--add-jar``: The path to a .jar file to include in the APK. To
include multiple jar files, pass this argument multiple times.
-- ``--intent-filters``: A file path containing intent filter xml to be
- included in AndroidManifest.xml.
-- ``--service``: A service name and the Python script it should
- run. See :ref:`arbitrary_scripts_services`.
- ``add-source``: Add a source directory to the app's Java code.
-- ``--compile-pyo``: Optimise .py files to .pyo.
-- ``--resource``: A key=value pair to add in the string.xml resource file.
+
+Qt
+~~
+
+This bootstrap can be used with ``--bootstrap=qt`` or by including the ``PySide6`` or
+``shiboken6`` recipe, e.g. ``--requirements=pyside6,shiboken6``. Currently, the only way
+to use this bootstrap is through `pyside6-android-deploy <https://www.qt.io/blog/taking-qt-for-python-to-android>`__
+tool shipped with ``PySide6``, as the recipes for ``PySide6`` and ``shiboken6`` are created
+dynamically. The tool builds ``PySide6`` and ``shiboken6`` wheels for a specific Android platform
+and 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>`__.
+
+.. note::
+ The ``pyside6-android-deploy`` tool and hence the Qt bootstrap does not support multi-architecture
+ builds currently.
+
+What are Qt and PySide?
+%%%%%%%%%%%%%%%%%%%%%%%%
+
+`Qt <https://www.qt.io/>`__ is a popularly used cross-platform C++ framework for developing
+GUI applications. `PySide6 <https://doc.qt.io/qtforpython-6/quickstart.html>`__ refers to the
+Python bindings for Qt6, and enables the Python developers access to the Qt6 API.
+`Shiboken6 <https://doc.qt.io/qtforpython-6/shiboken6/index.html>`__ is the binding generator
+tool used for generating the Python bindings from C++ code.
+
+.. note:: The `shiboken6` recipe is for the `Shiboken Python module <https://doc.qt.io/qtforpython-6/shiboken6/shibokenmodule.html>`__
+ which includes a couple of utility functions for inspecting and debugging PySide6 code.
+
+Build Options
+%%%%%%%%%%%%%
+
+``pyside6-android-deploy`` works by generating a ``buildozer.spec`` file and thereby using
+`buildozer <https://buildozer.readthedocs.io/en/latest/>`__ to control the build options used by
+``python-for-android`` with the Qt bootstrap. Apart from the general build options that works
+across all the other bootstraps, the Qt bootstrap introduces the following 3 new build options.
+
+- ``--qt-libs``: list of Qt libraries(modules) to be loaded.
+- ``--load-local-libs``: list of Qt plugin libraries to be loaded.
+- ``--init-classes``: list of Java class names to the loaded from the Qt jar files supplied through
+ the ``--add-jar`` option.
+
+These build options are automatically populated by the ``pyside6-android-deploy`` tool, but can be
+modified by updating the ``buildozer.spec`` file. Apart from the above 3 build options, the tool
+also automatically identifies the values to be fed into the cli options ``--permission``, ``--add-jar``
+depending on the PySide6 modules used by the applicaiton.
+
+Requirements blacklist (APK size optimization)
+----------------------------------------------
+
+To optimize the size of the `.apk` file that p4a builds for you,
+you can **blacklist** certain core components. Per default, p4a
+will add python *with batteries included* as would be expected on
+desktop, including openssl, sqlite3 and other components you may
+not use.
+
+To blacklist an item, specify the ``--blacklist-requirements`` option::
+
+ p4a apk ... --blacklist-requirements=sqlite3
+
+At the moment, the following core components can be blacklisted
+(if you don't want to use them) to decrease APK size:
+
+- ``android`` disables p4a's android module (see :ref:`reference-label-for-android-module`)
+- ``libffi`` disables ctypes stdlib module
+- ``openssl`` disables ssl stdlib module
+- ``sqlite3`` disables sqlite3 stdlib module
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index 02af5cfdb..dda644e47 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -26,7 +26,7 @@ behaviour, though not all commands make use of them.
``--debug``
Print extra debug information about the build, including all compilation output.
-
+
``--sdk_dir``
The filepath where the Android SDK is installed. This can
alternatively be set in several other ways.
@@ -34,7 +34,7 @@ behaviour, though not all commands make use of them.
``--android_api``
The Android API level to target; python-for-android will check if
the platform tools for this level are installed.
-
+
``--ndk_dir``
The filepath where the Android NDK is installed. This can
alternatively be set in several other ways.
@@ -67,19 +67,19 @@ supply those that you need.
distribution must contain, as a comma separated list. These must be
names of recipes or the pypi names of Python modules.
-``--force_build BOOL``
+``--force-build BOOL``
Whether the distribution must be compiled from scratch.
``--arch``
- The architecture to build for. Currently only one architecture can be
- targeted at a time, and a given distribution can only include one architecture.
-
+ The architecture to build for. You can specify multiple architectures to build for
+ at the same time. As an example ``p4a ... --arch arm64-v8a --arch armeabi-v7a ...``
+ will build a distribution for both ``arm64-v8a`` and ``armeabi-v7a``.
+
``--bootstrap BOOTSTRAP``
The Java bootstrap to use for your application. You mostly don't
need to worry about this or set it manually, as an appropriate
bootstrap will be chosen from your ``--requirements``. Current
- choices are ``sdl2`` or ``pygame``; ``sdl2`` is experimental but
- preferable where possible.
+ choices are ``sdl2`` (used with Kivy and most other apps), ``webview`` or ``qt``.
.. note:: These options are preliminary. Others will include toggles
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 32f7d3055..773083f98 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -12,19 +12,17 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys
+import datetime
import os
-import shlex
+import re
+import sys
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
-sys.path.append(os.path.abspath('ext/sphinx_rtd_theme'))
sys.path.append(os.path.abspath('../../pythonforandroid'))
-import sphinx_rtd_theme
-
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -54,25 +52,42 @@
master_doc = 'index'
# General information about the project.
-project = u'python-for-android'
-copyright = u'2015, Alexander Taylor'
-author = u'Alexander Taylor'
+project = 'python-for-android'
+
+_today = datetime.datetime.now()
+
+author = 'Kivy Team and other contributors'
+
+copyright = f'2015-{_today.year}, {author}'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
+
+# Lookup the version from the pyjnius module, without installing it
+# since readthedocs.org may have issue to install it.
+# Read the version from the __init__.py file, without importing it.
+def get_version():
+ with open(
+ os.path.join(os.path.abspath("../.."), "pythonforandroid", "__init__.py")
+ ) as fp:
+ for line in fp:
+ m = re.search(r'^\s*__version__\s*=\s*([\'"])([^\'"]+)\1\s*$', line)
+ if m:
+ return m.group(2)
+
# The short X.Y version.
-version = '0.1'
+version = get_version()
# The full version, including alpha/beta/rc tags.
-release = '0.1'
+release = get_version()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
-language = None
+language = 'en'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
@@ -82,7 +97,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = ['ext/*', ]
+exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
@@ -116,7 +131,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'sphinx_rtd_theme'
+html_theme = 'furo'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -124,7 +139,7 @@
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@@ -230,8 +245,8 @@
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
- (master_doc, 'python-for-android.tex', u'python-for-android Documentation',
- u'Alexander Taylor', 'manual'),
+ (master_doc, 'python-for-android.tex', 'python-for-android Documentation',
+ author, 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -275,7 +290,9 @@
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'python-for-android', u'python-for-android Documentation',
- author, 'python-for-android', 'One line description of project.',
+ author, 'python-for-android',
+ 'A development tool that packages Python apps into binaries that can run on '
+ 'Android devices',
'Miscellaneous'),
]
@@ -293,4 +310,14 @@
# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'https://docs.python.org/': None}
+intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
+
+# Ignore some troublesome links that are actually fine.
+linkcheck_ignore = [
+ # Special characters in URL seems to confuse link-checker.
+ r"https://developer.android.com/reference/android/app/Activity#onActivity.*",
+
+ # GitHub parses anchor tags differently to pure HTML
+ r"https://github.com/kivy/python-for-android/blob.*",
+ ]
+
diff --git a/doc/source/contact.rst b/doc/source/contact.rst
new file mode 100644
index 000000000..fedc04b47
--- /dev/null
+++ b/doc/source/contact.rst
@@ -0,0 +1,6 @@
+Contact Us
+==========
+
+If you are looking to contact the Kivy Team (who are responsible for managing the
+python-for-android project), including looking for support, please see our
+`latest contact details <https://github.com/kivy/python-for-android/blob/master/CONTACT.md>`_.
\ No newline at end of file
diff --git a/doc/source/contribute.rst b/doc/source/contribute.rst
index 8f9d661b2..653c67008 100644
--- a/doc/source/contribute.rst
+++ b/doc/source/contribute.rst
@@ -1,8 +1,19 @@
-Contributing
-============
+.. _contributing:
-The development of python-for-android is managed by the Kivy team `via
-Github <https://github.com/kivy/python-for-android>`_.
+.. _contribute:
-Issues and pull requests are welcome via the integrated `issue tracker
-<https://github.com/kivy/python-for-android/issues>`_.
+Contribution Guidelines
+=======================
+
+Buildozer is part of the `Kivy <https://kivy.org>`_ ecosystem - a large group of
+products used by many thousands of developers for free, but it
+is built entirely by the contributions of volunteers. We welcome (and rely on)
+users who want to give back to the community by contributing to the project.
+
+Contributions can come in many forms. See the latest
+`Contribution Guidelines <https://github.com/kivy/python-for-android/blob/master/CONTRIBUTING.md>`_
+for general guidelines of how you can help us, and specific instructions for python-for-android
+development.
+
+.. warning::
+ The python-for-android process differs in small but important ways from the Kivy framework's process.
diff --git a/doc/source/distutils.rst b/doc/source/distutils.rst
index aaa15f768..74055d0f7 100644
--- a/doc/source/distutils.rst
+++ b/doc/source/distutils.rst
@@ -2,8 +2,68 @@
distutils/setuptools integration
================================
-Instead of running p4a via the command line, you can integrate with
-distutils and setup.py.
+Have `p4a apk` run setup.py (replaces ``--requirements``)
+---------------------------------------------------------
+
+If your project has a `setup.py` file, then it can be executed by
+`p4a` when your app is packaged such that your app properly ends up
+in the packaged site-packages. (Use ``--use-setup-py`` to enable this,
+``--ignore-setup-py`` to prevent it)
+
+This is functionality to run **setup.py INSIDE `p4a apk`,** as opposed
+to the other section below, which is about running
+*p4a inside setup.py*.
+
+This however has these caveats:
+
+- **Only your ``main.py`` from your app's ``--private`` data is copied
+ into the .apk!** Everything else needs to be installed by your
+ ``setup.py`` into the site-packages, or it won't be packaged.
+
+- All dependencies that map to recipes can only be pinned to exact
+ versions, all other constraints will either just plain not work
+ or even cause build errors. (Sorry, our internal processing is
+ just not smart enough to honor them properly at this point)
+
+- The dependency analysis at the start may be quite slow and delay
+ your build
+
+Reasons why you would want to use a `setup.py` to be processed (and
+omit specifying ``--requirements``):
+
+- You want to use a more standard mechanism to specify dependencies
+ instead of ``--requirements``
+
+- You already use a `setup.py` for other platforms
+
+- Your application imports itself
+ in a way that won't work unless installed to site-packages)
+
+
+Reasons **not** to use a `setup.py` (that is to use the usual
+``--requirements`` mechanism instead):
+
+- You don't use a `setup.py` yet, and prefer the simplicity of
+ just specifying ``--requirements``
+
+- Your `setup.py` assumes a desktop platform and pulls in
+ Android-incompatible dependencies, and you are not willing
+ to change this, or you want to keep it separate from Android
+ deployment for other organizational reasons
+
+- You need data files to be around that aren't installed by
+ your `setup.py` into the site-packages folder
+
+
+Use your setup.py to call p4a
+-----------------------------
+
+Instead of running p4a via the command line, you can call it via
+`setup.py` instead, by it integrating with distutils and setup.py.
+
+This is functionality to run **p4a INSIDE setup.py,** as opposed
+to the other section above, which is about running
+*setup.py inside `p4a apk`*.
The base command is::
@@ -35,7 +95,7 @@ The Android package name uses ``org.test.lowercaseappname``
if not set explicitly.
The ``--private`` argument is set automatically using the
-package_data, you should *not* set this manually.
+package_data. You should *not* set this manually.
The target architecture defaults to ``--armeabi``.
@@ -44,7 +104,7 @@ All of these automatic arguments can be overridden by passing them manually on t
python setup.py apk --name="Testapp Setup" --version=2.5
Adding p4a arguments in setup.py
---------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instead of providing extra arguments on the command line, you can
store them in setup.py by passing the ``options`` parameter to
@@ -54,7 +114,7 @@ store them in setup.py by passing the ``options`` parameter to
from setuptools import find_packages
options = {'apk': {'debug': None, # use None for arguments that don't pass a value
- 'requirements': 'sdl2,pyjnius,kivy,python2',
+ 'requirements': 'sdl2,pyjnius,kivy,python3',
'android-api': 19,
'ndk-dir': '/path/to/ndk',
'dist-name': 'bdisttest',
@@ -79,7 +139,7 @@ setup.py apk``. Any options passed on the command line will override
these values.
Adding p4a arguments in setup.cfg
----------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can also provide p4a arguments in the setup.cfg file, as normal
for distutils. The syntax is::
diff --git a/doc/source/docker.rst b/doc/source/docker.rst
new file mode 100644
index 000000000..cba73883b
--- /dev/null
+++ b/doc/source/docker.rst
@@ -0,0 +1,68 @@
+.. _docker:
+
+Docker
+======
+
+Currently we use a containerized build for testing Python for Android recipes.
+Docker supports three big platforms either directly with the kernel or via
+using headless VirtualBox and a small distro to run itself on.
+
+While this is not the actively supported way to build applications, if you are
+willing to play with the approach, you can use the ``Dockerfile`` to build
+the Docker image we use for CI builds and create an Android
+application with that in a container. This approach allows you to build Android
+applications on all platforms Docker engine supports. These steps assume you
+already have Docker preinstalled and set up.
+
+.. warning::
+ This approach is highly space unfriendly! The more layers (``commit``) or
+ even Docker images (``build``) you create the more space it'll consume.
+ Within the Docker image there is Android SDK and NDK + various dependencies.
+ Within the custom diff made by building the distribution there is another
+ big chunk of space eaten. The very basic stuff such as a distribution with:
+ CPython 3, setuptools, Python for Android ``android`` module, SDL2 (+ deps),
+ PyJNIus and Kivy takes almost 2 GB. Check your free space first!
+
+1. Clone the repository::
+
+ git clone https://github.com/kivy/python-for-android
+
+2. Build the image with name ``p4a``::
+
+ docker build --tag p4a .
+
+ .. note::
+ You need to be in the ``python-for-android`` for the Docker build context
+ and you can optionally use ``--file`` flag to specify the path to the
+ ``Dockerfile`` location.
+
+3. Create a container from ``p4a`` image with copied ``testapps`` folder
+ in the image mounted to the same one in the cloned repo on the host::
+
+ docker run \
+ --interactive \
+ --tty \
+ --volume ".../testapps":/home/user/testapps \
+ p4a sh -c
+ '. venv/bin/activate \
+ && cd testapps \
+ && python setup_testapp_python3.py apk \
+ --sdk-dir $ANDROID_SDK_HOME \
+ --ndk-dir $ANDROID_NDK_HOME'
+
+ .. note::
+ On Windows you might need to use quotes and forward-slash path for volume
+ "/c/Users/.../python-for-android/testapps":/home/user/testapps
+
+ .. warning::
+ On Windows ``gradlew`` will attempt to use 'bash\r' command which is
+ a result of Windows line endings. For that you'll need to install
+ ``dos2unix`` package into the image.
+
+4. Preserve the distribution you've already built (optional, but recommended):
+
+ docker commit $(docker ps --last=1 --quiet) my_p4a_dist
+
+5. Find the ``.APK`` file on this location::
+
+ ls -lah testapps
diff --git a/doc/source/ext/sphinx_rtd_theme/.gitignore b/doc/source/ext/sphinx_rtd_theme/.gitignore
deleted file mode 100644
index 3076d7d8e..000000000
--- a/doc/source/ext/sphinx_rtd_theme/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-*.pyc
-*.egg-info
-*.egg
-*build/
-.tox
-.coverage
-*.DS_Store
-*.sass-cache
-.ruby-version
-dist/
-bower_components/
-node_modules/
-npm-debug.log
diff --git a/doc/source/ext/sphinx_rtd_theme/Gemfile b/doc/source/ext/sphinx_rtd_theme/Gemfile
deleted file mode 100644
index 9f2165c76..000000000
--- a/doc/source/ext/sphinx_rtd_theme/Gemfile
+++ /dev/null
@@ -1,4 +0,0 @@
-# A sample Gemfile
-source "https://rubygems.org"
-
-gem "compass"
diff --git a/doc/source/ext/sphinx_rtd_theme/Gemfile.lock b/doc/source/ext/sphinx_rtd_theme/Gemfile.lock
deleted file mode 100644
index 5e9db1660..000000000
--- a/doc/source/ext/sphinx_rtd_theme/Gemfile.lock
+++ /dev/null
@@ -1,16 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- chunky_png (1.2.9)
- compass (0.12.2)
- chunky_png (~> 1.2)
- fssm (>= 0.2.7)
- sass (~> 3.1)
- fssm (0.2.10)
- sass (3.2.12)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- compass
diff --git a/doc/source/ext/sphinx_rtd_theme/Gruntfile.js b/doc/source/ext/sphinx_rtd_theme/Gruntfile.js
deleted file mode 100644
index 8b2a20713..000000000
--- a/doc/source/ext/sphinx_rtd_theme/Gruntfile.js
+++ /dev/null
@@ -1,105 +0,0 @@
-module.exports = function(grunt) {
-
- // load all grunt tasks
- require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
-
- grunt.initConfig({
- open : {
- dev: {
- path: 'http://localhost:1919'
- }
- },
-
- connect: {
- server: {
- options: {
- port: 1919,
- base: 'demo_docs/build',
- livereload: true
- }
- }
- },
- copy: {
- fonts: {
- files: [
- // includes files within path
- {expand: true, flatten: true, src: ['bower_components/font-awesome/fonts/*'], dest: 'sphinx_rtd_theme/static/fonts/', filter: 'isFile'}
- ]
- }
- },
-
- sass: {
- dev: {
- options: {
- style: 'expanded',
- loadPath: ['bower_components/bourbon/dist', 'bower_components/neat/app/assets/stylesheets', 'bower_components/font-awesome/scss', 'bower_components/wyrm/sass']
- },
- files: [{
- expand: true,
- cwd: 'sass',
- src: ['*.sass'],
- dest: 'sphinx_rtd_theme/static/css',
- ext: '.css'
- }]
- },
- build: {
- options: {
- style: 'compressed',
- loadPath: ['bower_components/bourbon/dist', 'bower_components/neat/app/assets/stylesheets', 'bower_components/font-awesome/scss', 'bower_components/wyrm/sass']
- },
- files: [{
- expand: true,
- cwd: 'sass',
- src: ['*.sass'],
- dest: 'sphinx_rtd_theme/static/css',
- ext: '.css'
- }]
- }
- },
-
- exec: {
- bower_update: {
- cmd: 'bower update'
- },
- build_sphinx: {
- cmd: 'sphinx-build demo_docs/source demo_docs/build'
- }
- },
- clean: {
- build: ["demo_docs/build"],
- fonts: ["sphinx_rtd_theme/static/fonts"]
- },
-
- watch: {
- /* Compile sass changes into theme directory */
- sass: {
- files: ['sass/*.sass', 'bower_components/**/*.sass'],
- tasks: ['sass:dev']
- },
- /* Changes in theme dir rebuild sphinx */
- sphinx: {
- files: ['sphinx_rtd_theme/**/*', 'demo_docs/**/*.rst', 'demo_docs/**/*.py'],
- tasks: ['clean:build','exec:build_sphinx']
- },
- /* live-reload the demo_docs if sphinx re-builds */
- livereload: {
- files: ['demo_docs/build/**/*'],
- options: { livereload: true }
- }
- }
-
- });
-
- grunt.loadNpmTasks('grunt-exec');
- grunt.loadNpmTasks('grunt-contrib-connect');
- grunt.loadNpmTasks('grunt-contrib-watch');
- grunt.loadNpmTasks('grunt-contrib-sass');
- grunt.loadNpmTasks('grunt-contrib-clean');
- grunt.loadNpmTasks('grunt-contrib-copy');
- grunt.loadNpmTasks('grunt-open');
-
- grunt.registerTask('fonts', ['clean:fonts','copy:fonts']);
- grunt.registerTask('default', ['exec:bower_update','clean:build','sass:dev','exec:build_sphinx','connect','open','watch']);
- grunt.registerTask('build', ['exec:bower_update','clean:build','sass:build','exec:build_sphinx']);
-}
-
diff --git a/doc/source/ext/sphinx_rtd_theme/LICENSE b/doc/source/ext/sphinx_rtd_theme/LICENSE
deleted file mode 100644
index 921f07388..000000000
--- a/doc/source/ext/sphinx_rtd_theme/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2013 Dave Snider
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/doc/source/ext/sphinx_rtd_theme/MANIFEST.in b/doc/source/ext/sphinx_rtd_theme/MANIFEST.in
deleted file mode 100644
index eaca81d85..000000000
--- a/doc/source/ext/sphinx_rtd_theme/MANIFEST.in
+++ /dev/null
@@ -1,10 +0,0 @@
-include *.txt
-include LICENSE
-recursive-include sphinx_rtd_theme *.conf
-recursive-include sphinx_rtd_theme *.css
-recursive-include sphinx_rtd_theme *.eot
-recursive-include sphinx_rtd_theme *.html
-recursive-include sphinx_rtd_theme *.js
-recursive-include sphinx_rtd_theme *.svg
-recursive-include sphinx_rtd_theme *.ttf
-recursive-include sphinx_rtd_theme *.woff
diff --git a/doc/source/ext/sphinx_rtd_theme/README.rst b/doc/source/ext/sphinx_rtd_theme/README.rst
deleted file mode 100644
index d06078fda..000000000
--- a/doc/source/ext/sphinx_rtd_theme/README.rst
+++ /dev/null
@@ -1,176 +0,0 @@
-.. _readthedocs.org: http://www.readthedocs.org
-.. _bower: http://www.bower.io
-.. _sphinx: http://www.sphinx-doc.org
-.. _compass: http://www.compass-style.org
-.. _sass: http://www.sass-lang.com
-.. _wyrm: http://www.github.com/snide/wyrm/
-.. _grunt: http://www.gruntjs.com
-.. _node: http://www.nodejs.com
-.. _demo: http://docs.readthedocs.org
-.. _hidden: http://sphinx-doc.org/markup/toctree.html
-
-**************************
-Read the Docs Sphinx Theme
-**************************
-
-View a working demo_ over on readthedocs.org_.
-
-This is a mobile-friendly sphinx_ theme I made for readthedocs.org_. It's
-currently in development there and includes some rtd variable checks that can be ignored
-if you're just trying to use it on your project outside of that site.
-
-**This repo also exists as a submodule within the readthedocs itself**, so please make your edits to
-the SASS files here, rather than the .css files on RTD.
-
-.. image:: screen_mobile.png
- :width: 100%
-Installation
-============
-
-Via package
------------
-
-Download the package or add it to your ``requirements.txt`` file:
-
-.. code:: bash
-
- $ pip install sphinx_rtd_theme
-
-In your ``conf.py`` file:
-
-.. code:: python
-
- import sphinx_rtd_theme
-
- html_theme = "sphinx_rtd_theme"
-
- html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-
-Via git or download
--------------------
-
-Symlink or subtree the ``sphinx_rtd_theme/sphinx_rtd_theme`` repository into your documentation at
-``docs/_themes/sphinx_rtd_theme`` then add the following two settings to your Sphinx
-conf.py file:
-
-.. code:: python
-
- html_theme = "sphinx_rtd_theme"
- html_theme_path = ["_themes", ]
-
-How the Table of Contents builds
-================================
-
-Currently the left menu will build based upon any ``toctree(s)`` defined in your index.rst file.
-It outputs 2 levels of depth, which should give your visitors a high level of access to your
-docs. If no toctrees are set the theme reverts to sphinx's usual local toctree.
-
-It's important to note that if you don't follow the same styling for your rST headers across
-your documents, the toctree will misbuild, and the resulting menu might not show the correct
-depth when it renders.
-
-Also note that the table of contents is set with ``includehidden=true``. This allows you
-to set a hidden toc in your index file with the hidden_ property that will allow you
-to build a toc without it rendering in your index.
-
-By default, the navigation will "stick" to the screen as you scroll. However if your toc
-is vertically too large, it revert to static positioning. To disable the sticky nav
-alltogether change the setting in ``conf.py``.
-
-Contributing or modifying the theme
-===================================
-
-The sphinx_rtd_theme is primarily a sass_ project that requires a few other sass libraries. I'm
-using bower_ to manage these dependencies and sass_ to build the css. The good news is
-I have a very nice set of grunt_ operations that will not only load these dependecies, but watch
-for changes, rebuild the sphinx demo docs and build a distributable version of the theme.
-The bad news is this means you'll need to set up your environment similar to that
-of a front-end developer (vs. that of a python developer). That means installing node and ruby.
-
-Set up your environment
------------------------
-
-1. Install sphinx_ into a virtual environment.
-
-.. code::
-
- pip install sphinx
-
-2. Install sass
-
-.. code::
-
- gem install sass
-
-2. Install node, bower and grunt.
-
-.. code::
-
- // Install node
- brew install node
-
- // Install bower and grunt
- npm install -g bower grunt-cli
-
- // Now that everything is installed, let's install the theme dependecies.
- npm install
-
-Now that our environment is set up, make sure you're in your virtual environment, go to
-this repository in your terminal and run grunt:
-
-.. code::
-
- grunt
-
-This default task will do the following **very cool things that make it worth the trouble**.
-
-1. It'll install and update any bower dependencies.
-2. It'll run sphinx and build new docs.
-3. It'll watch for changes to the sass files and build css from the changes.
-4. It'll rebuild the sphinx docs anytime it notices a change to .rst, .html, .js
- or .css files.
-
-
-Before you create an issue
---------------------------
-
-I don't have a lot of time to maintain this project due to other responsibilities.
-I know there are a lot of Python engineers out there that can't code sass / css and
-are unable to submit pull requests. That said, submitting random style bugs without
-at least providing sample documentation that replicates your problem is a good
-way for me to ignore your request. RST unfortunately can spit out a lot of things
-in a lot of ways. I don't have time to research your problem for you, but I do
-have time to fix the actual styling issue if you can replicate the problem for me.
-
-
-Before you send a Pull Request
-------------------------------
-
-When you're done with your edits, you can run ``grunt build`` to clean out the old
-files and rebuild a new distribution, compressing the css and cleaning out
-extraneous files. Please do this before you send in a PR.
-
-Using this theme locally, then building on Read the Docs?
-==========================================================
-
-Currently if you import sphinx_rtd_theme in your local sphinx build, then pass
-that same config to Read the Docs, it will fail, since RTD gets confused. If
-you want to run this theme locally and then also have it build on RTD, then
-you can add something like this to your config. Thanks to Daniel Oaks for this.
-
-.. code:: python
-
- # on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
- on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
-
- if not on_rtd: # only import and set the theme if we're building docs locally
- import sphinx_rtd_theme
- html_theme = 'sphinx_rtd_theme'
- html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-
- # otherwise, readthedocs.org uses their theme by default, so no need to specify it
-
-TODO
-====
-* Separate some sass variables at the theme level so you can overwrite some basic colors.
-
diff --git a/doc/source/ext/sphinx_rtd_theme/bower.json b/doc/source/ext/sphinx_rtd_theme/bower.json
deleted file mode 100644
index c068a63e9..000000000
--- a/doc/source/ext/sphinx_rtd_theme/bower.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "name": "sphinx-rtd-theme",
- "version": "1.0",
- "homepage": "https://github.com/snide/wyrm",
- "authors": [
- "Dave Snider <dave.snider@gmail.com>"
- ],
- "description": "Sphinx theme for readthedocs.org.",
- "license": "MIT",
- "main": [
- "dist/**"
- ],
- "ignore": [
- "docs",
- "demo_docs",
- ".gitignore",
- ".DS_Store",
- ".sass-cache*",
- ".bowerrc",
- "bower.json",
- "package.json",
- "Gruntfile.js",
- "node_modules",
- "bower_components",
- "test",
- "tests",
- "src"
- ],
- "dependencies": {
- "wyrm": "~0.0.x"
- }
-}
-
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/Makefile b/doc/source/ext/sphinx_rtd_theme/demo_docs/Makefile
deleted file mode 100644
index 8c907fd42..000000000
--- a/doc/source/ext/sphinx_rtd_theme/demo_docs/Makefile
+++ /dev/null
@@ -1,153 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = build
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
-
-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " texinfo to make Texinfo files"
- @echo " info to make Texinfo files and run them through makeinfo"
- @echo " gettext to make PO message catalogs"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- -rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SphinxRTDthemedemo.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SphinxRTDthemedemo.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/SphinxRTDthemedemo"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SphinxRTDthemedemo"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo
- @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
- @echo "Run \`make' in that directory to run these through makeinfo" \
- "(use \`make info' here to do that automatically)."
-
-info:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo "Running Texinfo files through makeinfo..."
- make -C $(BUILDDIR)/texinfo info
- @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
- $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
- @echo
- @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/conf.py b/doc/source/ext/sphinx_rtd_theme/demo_docs/source/conf.py
deleted file mode 100644
index 56c44d0a3..000000000
--- a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/conf.py
+++ /dev/null
@@ -1,254 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Sphinx RTD theme demo documentation build configuration file, created by
-# sphinx-quickstart on Sun Nov 3 11:56:36 2013.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-
-sys.path.append(os.path.abspath('.'))
-sys.path.append(os.path.abspath('./test_py_module'))
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = [
- 'sphinx.ext.autodoc',
- 'sphinx.ext.mathjax',
- 'sphinx.ext.viewcode',
-]
-
-# Math
-mathjax_path = "http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Sphinx RTD theme demo'
-copyright = u'2013, Dave Snider'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '1'
-# The full version, including alpha/beta/rc tags.
-release = '1'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = []
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'sphinx_rtd_theme'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-html_theme_options = {
- # 'sticky_navigation' : True # Set to False to disable the sticky nav while scrolling.
-}
-
-# Add any paths that contain custom themes here, relative to this directory.
-html_theme_path = ["../.."]
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'SphinxRTDthemedemodoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'SphinxRTDthemedemo.tex', u'Sphinx RTD theme demo Documentation',
- u'Dave Snider', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'sphinxrtdthemedemo', u'Sphinx RTD theme demo Documentation',
- [u'Dave Snider'], 1)
-]
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
-# -- Options for Texinfo output ------------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-# dir menu entry, description, category)
-texinfo_documents = [
- ('index', 'SphinxRTDthemedemo', u'Sphinx RTD theme demo Documentation',
- u'Dave Snider', 'SphinxRTDthemedemo', 'One line description of project.',
- 'Miscellaneous'),
-]
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/demo.rst b/doc/source/ext/sphinx_rtd_theme/demo_docs/source/demo.rst
deleted file mode 100644
index 1af45c81d..000000000
--- a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/demo.rst
+++ /dev/null
@@ -1,577 +0,0 @@
-.. This is a comment. Note how any initial comments are moved by
- transforms to after the document title, subtitle, and docinfo.
-
-================================
- reStructuredText Demonstration
-================================
-
-.. Above is the document title, and below is the subtitle.
- They are transformed from section titles after parsing.
-
---------------------------------
- Examples of Syntax Constructs
---------------------------------
-
-.. bibliographic fields (which also require a transform):
-
-:Author: David Goodger
-:Address: 123 Example Street
- Example, EX Canada
- A1B 2C3
-:Contact: docutils-develop@lists.sourceforge.net
-:Authors: Me; Myself; I
-:organization: humankind
-:date: $Date: 2012-01-03 19:23:53 +0000 (Tue, 03 Jan 2012) $
-:status: This is a "work in progress"
-:revision: $Revision: 7302 $
-:version: 1
-:copyright: This document has been placed in the public domain. You
- may do with it as you wish. You may copy, modify,
- redistribute, reattribute, sell, buy, rent, lease,
- destroy, or improve it, quote it at length, excerpt,
- incorporate, collate, fold, staple, or mutilate it, or do
- anything else to it that your or anyone else's heart
- desires.
-:field name: This is a generic bibliographic field.
-:field name 2:
- Generic bibliographic fields may contain multiple body elements.
-
- Like this.
-
-:Dedication:
-
- For Docutils users & co-developers.
-
-:abstract:
-
- This document is a demonstration of the reStructuredText markup
- language, containing examples of all basic reStructuredText
- constructs and many advanced constructs.
-
-.. meta::
- :keywords: reStructuredText, demonstration, demo, parser
- :description lang=en: A demonstration of the reStructuredText
- markup language, containing examples of all basic
- constructs and many advanced constructs.
-
-.. contents:: Table of Contents
-.. section-numbering::
-
-
-Structural Elements
-===================
-
-Section Title
--------------
-
-That's it, the text just above this line.
-
-Transitions
------------
-
-Here's a transition:
-
----------
-
-It divides the section.
-
-Body Elements
-=============
-
-Paragraphs
-----------
-
-A paragraph.
-
-Inline Markup
-`````````````
-
-Paragraphs contain text and may contain inline markup: *emphasis*,
-**strong emphasis**, ``inline literals``, standalone hyperlinks
-(http://www.python.org), external hyperlinks (Python_), internal
-cross-references (example_), external hyperlinks with embedded URIs
-(`Python web site <http://www.python.org>`__), footnote references
-(manually numbered [1]_, anonymous auto-numbered [#]_, labeled
-auto-numbered [#label]_, or symbolic [*]_), citation references
-([CIT2002]_), substitution references (|example|), and _`inline
-hyperlink targets` (see Targets_ below for a reference back to here).
-Character-level inline markup is also possible (although exceedingly
-ugly!) in *re*\ ``Structured``\ *Text*. Problems are indicated by
-|problematic| text (generated by processing errors; this one is
-intentional).
-
-The default role for interpreted text is `Title Reference`. Here are
-some explicit interpreted text roles: a PEP reference (:PEP:`287`); an
-RFC reference (:RFC:`2822`); a :sub:`subscript`; a :sup:`superscript`;
-and explicit roles for :emphasis:`standard` :strong:`inline`
-:literal:`markup`.
-
-.. DO NOT RE-WRAP THE FOLLOWING PARAGRAPH!
-
-Let's test wrapping and whitespace significance in inline literals:
-``This is an example of --inline-literal --text, --including some--
-strangely--hyphenated-words. Adjust-the-width-of-your-browser-window
-to see how the text is wrapped. -- ---- -------- Now note the
-spacing between the words of this sentence (words
-should be grouped in pairs).``
-
-If the ``--pep-references`` option was supplied, there should be a
-live link to PEP 258 here.
-
-Bullet Lists
-------------
-
-- A bullet list
-
- + Nested bullet list.
- + Nested item 2.
-
-- Item 2.
-
- Paragraph 2 of item 2.
-
- * Nested bullet list.
- * Nested item 2.
-
- - Third level.
- - Item 2.
-
- * Nested item 3.
-
-Enumerated Lists
-----------------
-
-1. Arabic numerals.
-
- a) lower alpha)
-
- (i) (lower roman)
-
- A. upper alpha.
-
- I) upper roman)
-
-2. Lists that don't start at 1:
-
- 3. Three
-
- 4. Four
-
- C. C
-
- D. D
-
- iii. iii
-
- iv. iv
-
-#. List items may also be auto-enumerated.
-
-Definition Lists
-----------------
-
-Term
- Definition
-Term : classifier
- Definition paragraph 1.
-
- Definition paragraph 2.
-Term
- Definition
-
-Field Lists
------------
-
-:what: Field lists map field names to field bodies, like database
- records. They are often part of an extension syntax. They are
- an unambiguous variant of RFC 2822 fields.
-
-:how arg1 arg2:
-
- The field marker is a colon, the field name, and a colon.
-
- The field body may contain one or more body elements, indented
- relative to the field marker.
-
-Option Lists
-------------
-
-For listing command-line options:
-
--a command-line option "a"
--b file options can have arguments
- and long descriptions
---long options can be long also
---input=file long options can also have
- arguments
-
---very-long-option
- The description can also start on the next line.
-
- The description may contain multiple body elements,
- regardless of where it starts.
-
--x, -y, -z Multiple options are an "option group".
--v, --verbose Commonly-seen: short & long options.
--1 file, --one=file, --two file
- Multiple options with arguments.
-/V DOS/VMS-style options too
-
-There must be at least two spaces between the option and the
-description.
-
-Literal Blocks
---------------
-
-Literal blocks are indicated with a double-colon ("::") at the end of
-the preceding paragraph (over there ``-->``). They can be indented::
-
- if literal_block:
- text = 'is left as-is'
- spaces_and_linebreaks = 'are preserved'
- markup_processing = None
-
-Or they can be quoted without indentation::
-
->> Great idea!
->
-> Why didn't I think of that?
-
-Line Blocks
------------
-
-| This is a line block. It ends with a blank line.
-| Each new line begins with a vertical bar ("|").
-| Line breaks and initial indents are preserved.
-| Continuation lines are wrapped portions of long lines;
- they begin with a space in place of the vertical bar.
-| The left edge of a continuation line need not be aligned with
- the left edge of the text above it.
-
-| This is a second line block.
-|
-| Blank lines are permitted internally, but they must begin with a "|".
-
-Take it away, Eric the Orchestra Leader!
-
- | A one, two, a one two three four
- |
- | Half a bee, philosophically,
- | must, *ipso facto*, half not be.
- | But half the bee has got to be,
- | *vis a vis* its entity. D'you see?
- |
- | But can a bee be said to be
- | or not to be an entire bee,
- | when half the bee is not a bee,
- | due to some ancient injury?
- |
- | Singing...
-
-Block Quotes
-------------
-
-Block quotes consist of indented body elements:
-
- My theory by A. Elk. Brackets Miss, brackets. This theory goes
- as follows and begins now. All brontosauruses are thin at one
- end, much much thicker in the middle and then thin again at the
- far end. That is my theory, it is mine, and belongs to me and I
- own it, and what it is too.
-
- -- Anne Elk (Miss)
-
-Doctest Blocks
---------------
-
->>> print 'Python-specific usage examples; begun with ">>>"'
-Python-specific usage examples; begun with ">>>"
->>> print '(cut and pasted from interactive Python sessions)'
-(cut and pasted from interactive Python sessions)
-
-Tables
-------
-
-Here's a grid table followed by a simple table:
-
-+------------------------+------------+----------+----------+
-| Header row, column 1 | Header 2 | Header 3 | Header 4 |
-| (header rows optional) | | | |
-+========================+============+==========+==========+
-| body row 1, column 1 | column 2 | column 3 | column 4 |
-+------------------------+------------+----------+----------+
-| body row 2 | Cells may span columns. |
-+------------------------+------------+---------------------+
-| body row 3 | Cells may | - Table cells |
-+------------------------+ span rows. | - contain |
-| body row 4 | | - body elements. |
-+------------------------+------------+----------+----------+
-| body row 5 | Cells may also be | |
-| | empty: ``-->`` | |
-+------------------------+-----------------------+----------+
-
-===== ===== ======
- Inputs Output
------------- ------
- A B A or B
-===== ===== ======
-False False False
-True False True
-False True True
-True True True
-===== ===== ======
-
-Footnotes
----------
-
-.. [1] A footnote contains body elements, consistently indented by at
- least 3 spaces.
-
- This is the footnote's second paragraph.
-
-.. [#label] Footnotes may be numbered, either manually (as in [1]_) or
- automatically using a "#"-prefixed label. This footnote has a
- label so it can be referred to from multiple places, both as a
- footnote reference ([#label]_) and as a hyperlink reference
- (label_).
-
-.. [#] This footnote is numbered automatically and anonymously using a
- label of "#" only.
-
-.. [*] Footnotes may also use symbols, specified with a "*" label.
- Here's a reference to the next footnote: [*]_.
-
-.. [*] This footnote shows the next symbol in the sequence.
-
-.. [4] Here's an unreferenced footnote, with a reference to a
- nonexistent footnote: [5]_.
-
-Citations
----------
-
-.. [CIT2002] Citations are text-labeled footnotes. They may be
- rendered separately and differently from footnotes.
-
-Here's a reference to the above, [CIT2002]_, and a [nonexistent]_
-citation.
-
-Targets
--------
-
-.. _example:
-
-This paragraph is pointed to by the explicit "example" target. A
-reference can be found under `Inline Markup`_, above. `Inline
-hyperlink targets`_ are also possible.
-
-Section headers are implicit targets, referred to by name. See
-Targets_, which is a subsection of `Body Elements`_.
-
-Explicit external targets are interpolated into references such as
-"Python_".
-
-.. _Python: http://www.python.org/
-
-Targets may be indirect and anonymous. Thus `this phrase`__ may also
-refer to the Targets_ section.
-
-__ Targets_
-
-Here's a `hyperlink reference without a target`_, which generates an
-error.
-
-Duplicate Target Names
-``````````````````````
-
-Duplicate names in section headers or other implicit targets will
-generate "info" (level-1) system messages. Duplicate names in
-explicit targets will generate "warning" (level-2) system messages.
-
-Duplicate Target Names
-``````````````````````
-
-Since there are two "Duplicate Target Names" section headers, we
-cannot uniquely refer to either of them by name. If we try to (like
-this: `Duplicate Target Names`_), an error is generated.
-
-Directives
-----------
-
-.. contents:: :local:
-
-These are just a sample of the many reStructuredText Directives. For
-others, please see
-http://docutils.sourceforge.net/docs/ref/rst/directives.html.
-
-Document Parts
-``````````````
-
-An example of the "contents" directive can be seen above this section
-(a local, untitled table of contents_) and at the beginning of the
-document (a document-wide `table of contents`_).
-
-Images
-``````
-
-An image directive (also clickable -- a hyperlink reference):
-
-.. image:: images/title.png
- :target: directives_
-
-A figure directive:
-
-.. figure:: images/title.png
- :alt: reStructuredText, the markup syntax
-
- A figure is an image with a caption and/or a legend:
-
- +------------+-----------------------------------------------+
- | re | Revised, revisited, based on 're' module. |
- +------------+-----------------------------------------------+
- | Structured | Structure-enhanced text, structuredtext. |
- +------------+-----------------------------------------------+
- | Text | Well it is, isn't it? |
- +------------+-----------------------------------------------+
-
- This paragraph is also part of the legend.
-
-A figure directive with center alignment
-
-.. figure:: images/title.png
- :align: center
- :width: 300
-
-Admonitions
-```````````
-
-.. Attention:: Directives at large.
-
-.. Caution::
-
- Don't take any wooden nickels.
-
-.. DANGER:: Mad scientist at work!
-
-.. Error:: Does not compute.
-
-.. Hint:: It's bigger than a bread box.
-
-.. Important::
- - Wash behind your ears.
- - Clean up your room.
- - Call your mother.
- - Back up your data.
-
-.. Note:: This is a note.
-
-.. Tip:: 15% if the service is good.
-
-.. WARNING:: Strong prose may provoke extreme mental exertion.
- Reader discretion is strongly advised.
-
-.. admonition:: And, by the way...
-
- You can make up your own admonition too.
-
-Topics, Sidebars, and Rubrics
-`````````````````````````````
-
-.. sidebar:: Sidebar Title
- :subtitle: Optional Subtitle
-
- This is a sidebar. It is for text outside the flow of the main
- text.
-
- .. rubric:: This is a rubric inside a sidebar
-
- Sidebars often appears beside the main text with a border and
- background color.
-
-.. topic:: Topic Title
-
- This is a topic.
-
-.. rubric:: This is a rubric
-
-Target Footnotes
-````````````````
-
-.. target-notes::
-
-Replacement Text
-````````````````
-
-I recommend you try |Python|_.
-
-.. |Python| replace:: Python, *the* best language around
-
-Compound Paragraph
-``````````````````
-
-.. compound::
-
- This paragraph contains a literal block::
-
- Connecting... OK
- Transmitting data... OK
- Disconnecting... OK
-
- and thus consists of a simple paragraph, a literal block, and
- another simple paragraph. Nonetheless it is semantically *one*
- paragraph.
-
-This construct is called a *compound paragraph* and can be produced
-with the "compound" directive.
-
-Substitution Definitions
-------------------------
-
-An inline image (|example|) example:
-
-.. |EXAMPLE| image:: images/biohazard.png
-
-(Substitution definitions are not visible in the HTML source.)
-
-Comments
---------
-
-Here's one:
-
-.. Comments begin with two dots and a space. Anything may
- follow, except for the syntax of footnotes, hyperlink
- targets, directives, or substitution definitions.
-
- Double-dashes -- "--" -- must be escaped somehow in HTML output.
-
-(View the HTML source to see the comment.)
-
-Field Lists
-===========
-
-:Field List:
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
- minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat.
-
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
- dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
- proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-some text
-
-:Field List 2: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
-
-Error Handling
-==============
-
-Any errors caught during processing will generate system messages.
-
-|*** Expect 6 errors (including this one). ***|
-
-There should be six messages in the following, auto-generated
-section, "Docutils System Messages":
-
-.. section should be added by Docutils automatically
-
-demo.rst from: http://docutils.sourceforge.net/docs/user/rst/demo.txt
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/index.rst b/doc/source/ext/sphinx_rtd_theme/demo_docs/source/index.rst
deleted file mode 100644
index 23492420c..000000000
--- a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/index.rst
+++ /dev/null
@@ -1,217 +0,0 @@
-.. Sphinx RTD theme demo documentation master file, created by
- sphinx-quickstart on Sun Nov 3 11:56:36 2013.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
-=================================================
-Demo Docs
-=================================================
-
-:Page Status: Incomplete
-:Last Reviewed: 2013-10-29
-
-Contents:
-
-.. toctree::
- :maxdepth: 2
-
- demo
- list
-
-Maaaaath!
-=========
-
-This is a test. Here is an equation:
-:math:`X_{0:5} = (X_0, X_1, X_2, X_3, X_4)`.
-Here is another:
-
-.. math::
-
- \nabla^2 f =
- \frac{1}{r^2} \frac{\partial}{\partial r}
- \left( r^2 \frac{\partial f}{\partial r} \right) +
- \frac{1}{r^2 \sin \theta} \frac{\partial f}{\partial \theta}
- \left( \sin \theta \, \frac{\partial f}{\partial \theta} \right) +
- \frac{1}{r^2 \sin^2\theta} \frac{\partial^2 f}{\partial \phi^2}
-
-
-Giant tables
-============
-
-+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+
-| Header 1 | Header 2 | Header 3 | Header 1 | Header 2 | Header 3 | Header 1 | Header 2 | Header 3 | Header 1 | Header 2 | Header 3 |
-+============+============+===========+============+============+===========+============+============+===========+============+============+===========+
-| body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 |
-+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+
-| body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 |
-+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+
-| body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 |
-+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+
-| body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 | body row 1 | column 2 | column 3 |
-+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+------------+------------+-----------+
-
-API Test
-========
-
-.. automodule:: test_py_module.test
- :members:
- :private-members:
- :special-members:
-
-Optional parameter args
------------------------
-
-At this point optional parameters `cannot be generated from code`_.
-However, some projects will manually do it, like so:
-
-This example comes from `django-payments module docs`_.
-
-.. class:: payments.dotpay.DotpayProvider(seller_id, pin[, channel=0[, lock=False], lang='pl'])
-
- This backend implements payments using a popular Polish gateway, `Dotpay.pl <http://www.dotpay.pl>`_.
-
- Due to API limitations there is no support for transferring purchased items.
-
-
- :param seller_id: Seller ID assigned by Dotpay
- :param pin: PIN assigned by Dotpay
- :param channel: Default payment channel (consult reference guide)
- :param lang: UI language
- :param lock: Whether to disable channels other than the default selected above
-
-.. _cannot be generated from code: https://groups.google.com/forum/#!topic/sphinx-users/_qfsVT5Vxpw
-.. _django-payments module docs: http://django-payments.readthedocs.org/en/latest/modules.html#payments.authorizenet.AuthorizeNetProvider
-
-Code test
-=========
-
-.. parsed-literal::
-
- # parsed-literal test
- curl -O http://someurl/release-|version|.tar-gz
-
-
-.. code-block:: json
-
- {
- "windows": [
- {
- "panes": [
- {
- "shell_command": [
- "echo 'did you know'",
- "echo 'you can inline'"
- ]
- },
- {
- "shell_command": "echo 'single commands'"
- },
- "echo 'for panes'"
- ],
- "window_name": "long form"
- }
- ],
- "session_name": "shorthands"
- }
-
-Sidebar
-=======
-
-.. sidebar:: Ch'ien / The Creative
-
- .. image:: static/yi_jing_01_chien.jpg
-
- *Above* CH'IEN THE CREATIVE, HEAVEN
-
- *Below* CH'IEN THE CREATIVE, HEAVEN
-
-The first hexagram is made up of six unbroken lines. These unbroken lines stand for the primal power, which is light-giving, active, strong, and of the spirit. The hexagram is consistently strong in character, and since it is without weakness, its essence is power or energy. Its image is heaven. Its energy is represented as unrestricted by any fixed conditions in space and is therefore conceived of as motion. Time is regarded as the basis of this motion. Thus the hexagram includes also the power of time and the power of persisting in time, that is, duration.
-
-The power represented by the hexagram is to be interpreted in a dual sense in terms of its action on the universe and of its action on the world of men. In relation to the universe, the hexagram expresses the strong, creative action of the Deity. In relation to the human world, it denotes the creative action of the holy man or sage, of the ruler or leader of men, who through his power awakens and develops their higher nature.
-
-Code with Sidebar
-=================
-
-.. sidebar:: A code example
-
- With a sidebar on the right.
-
-.. literalinclude:: test_py_module/test.py
- :language: python
- :linenos:
- :lines: 1-40
-
-Boxes
-=====
-
-.. tip::
- Equations within a note
- :math:`G_{\mu\nu} = 8 \pi G (T_{\mu\nu} + \rho_\Lambda g_{\mu\nu})`.
-
-.. note::
- Equations within a note
- :math:`G_{\mu\nu} = 8 \pi G (T_{\mu\nu} + \rho_\Lambda g_{\mu\nu})`.
-
-.. danger::
- Equations within a note
- :math:`G_{\mu\nu} = 8 \pi G (T_{\mu\nu} + \rho_\Lambda g_{\mu\nu})`.
-
-.. warning::
- Equations within a note
- :math:`G_{\mu\nu} = 8 \pi G (T_{\mu\nu} + \rho_\Lambda g_{\mu\nu})`.
-
-
-Inline code and references
-==========================
-
-`reStructuredText`_ is a markup language. It can use roles and
-declarations to turn reST into HTML.
-
-In reST, ``*hello world*`` becomes ``<em>hello world</em>``. This is
-because a library called `Docutils`_ was able to parse the reST and use a
-``Writer`` to output it that way.
-
-If I type ````an inline literal```` it will wrap it in ``<tt>``. You can
-see more details on the `Inline Markup`_ on the Docutils homepage.
-
-Also with ``sphinx.ext.autodoc``, which I use in the demo, I can link to
-:class:`test_py_module.test.Foo`. It will link you right my code
-documentation for it.
-
-.. _reStructuredText: http://docutils.sourceforge.net/rst.html
-.. _Docutils: http://docutils.sourceforge.net/
-.. _Inline Markup: http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#inline-markup
-
-.. note:: Every other line in this table will have white text on a white background.
- This is bad.
-
- +---------+
- | Example |
- +=========+
- | Thing1 |
- +---------+
- | Thing2 |
- +---------+
- | Thing3 |
- +---------+
-
-Emphasized lines with line numbers
-==================================
-
-.. code-block:: python
- :linenos:
- :emphasize-lines: 3,5
-
- def some_function():
- interesting = False
- print 'This line is highlighted.'
- print 'This one is not...'
- print '...but this one is.'
-
-
-Citation
-========
-
-Here I am making a citation [1]_
-
-.. [1] This is the citation I made, let's make this extremely long so that we can tell that it doesn't follow the normal responsive table stuff.
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/list.rst b/doc/source/ext/sphinx_rtd_theme/demo_docs/source/list.rst
deleted file mode 100644
index 47affdd2c..000000000
--- a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/list.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-.. important::
-
- wanna play a game?
-
- - inside
- - this
-
- - list
- - ``in the world``
-
- - hi
- - his
-
- hi
-
-
-
-A list
-======
-
-- here
- - is
- - some
- - list
- - items
- - `yahoo <http://www.yahoo.com>`_
- - ``huh``
-- how
-- ``inline literall``
-- ``inline literall``
-- ``inline literall``
-
-Second list level
------------------
-
-- here is a list in a second-level section.
-- `yahoo <http://www.yahoo.com>`_
-- `yahoo <http://www.yahoo.com>`_
-
- - `yahoo <http://www.yahoo.com>`_
- - here is an inner bullet ``oh``
-
- - one more ``with an inline literally``. `yahoo <http://www.yahoo.com>`_
-
- heh heh. child. try to beat this embed:
-
- .. literalinclude:: test_py_module/test.py
- :language: python
- :linenos:
- :lines: 1-10
- - and another. `yahoo <http://www.yahoo.com>`_
- - `yahoo <http://www.yahoo.com>`_
- - ``hi``
-- and hehe
-
-But deeper down the rabbit hole
-"""""""""""""""""""""""""""""""
-
-- I kept saying that, "deeper down the rabbit hole". `yahoo <http://www.yahoo.com>`_
-
- - I cackle at night `yahoo <http://www.yahoo.com>`_.
-- I'm so lonely here in GZ ``guangzhou``
-- A man of python destiny, hopes and dreams. `yahoo <http://www.yahoo.com>`_
-
- - `yahoo <http://www.yahoo.com>`_
-
- - `yahoo <http://www.yahoo.com>`_ ``hi``
- - ``destiny``
-
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/static/yi_jing_01_chien.jpg b/doc/source/ext/sphinx_rtd_theme/demo_docs/source/static/yi_jing_01_chien.jpg
deleted file mode 100644
index 276df14df..000000000
Binary files a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/static/yi_jing_01_chien.jpg and /dev/null differ
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/test_py_module/test.py b/doc/source/ext/sphinx_rtd_theme/demo_docs/source/test_py_module/test.py
deleted file mode 100644
index ca7a8bc92..000000000
--- a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/test_py_module/test.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Test Module for sphinx_rtd_theme."""
-
-
-class Foo:
-
- r"""Docstring for class Foo.
-
- This text tests for the formatting of docstrings generated from output
- ``sphinx.ext.autodoc``. Which contain reST, but sphinx nests it in the
- ``<dl>``, and ``<dt>`` tags. Also, ``<tt>`` is used for class, method names
- and etc, but those will *always* have the ``.descname`` or
- ``.descclassname`` class.
-
- Normal ``<tt>`` (like the <tt> I just wrote here) needs to be shown with
- the same style as anything else with ````this type of markup````.
-
- It's common for programmers to give a code example inside of their
- docstring::
-
- from test_py_module import Foo
-
- myclass = Foo()
- myclass.dothismethod('with this argument')
- myclass.flush()
-
- print(myclass)
-
- """
-
- #: Doc comment for class attribute Foo.bar.
- #: It can have multiple lines.
- bar = 1
-
- flox = 1.5 #: Doc comment for Foo.flox. One line only.
-
- baz = 2
- """Docstring for class attribute Foo.baz."""
-
- def __init__(self, qux, spam=False):
- """Start the Foo.
-
- :param qux: The first argument to initialize class.
- :type qux: string
- :param spam: Spam me yes or no...
- :type spam: bool
-
- """
- #: Doc comment for instance attribute qux.
- self.qux = 3
-
- self.spam = 4
- """Docstring for instance attribute spam."""
-
- def add(self, val1, val2):
- """Return the added values.
-
- :param val1: First number to add.
- :type val1: int
- :param val2: Second number to add.
- :type val2: int
- :rtype: int
-
- """
-
- return val1 + val2
-
- def capitalize(self, myvalue):
- """Return a string as uppercase.
-
- :param myvalue: String to change
- :type myvalue: string
- :rtype: string
-
- """
-
- return myvalue.upper()
-
- def another_function(self, a, b, **kwargs):
- """
- Here is another function.
-
- :param a: The number of green hats you own.
- :type a: int
-
- :param b: The number of non-green hats you own.
- :type b: int
-
- :param kwargs: Additional keyword arguments. Each keyword parameter
- should specify the name of your favorite cuisine.
- The values should be floats, specifying the mean price
- of your favorite dish in that cooking style.
- :type kwargs: float
-
- :returns: A 2-tuple. The first element is the mean price of all dishes
- across cuisines. The second element is the total number of
- hats you own: :math:`a + b`.
- :rtype: tuple
-
- :raises ValueError: When ``a`` is not an integer.
-
- """
- return sum(kwargs.values()) / len(kwargs), a + b
diff --git a/doc/source/ext/sphinx_rtd_theme/package.json b/doc/source/ext/sphinx_rtd_theme/package.json
deleted file mode 100644
index d5bbf203d..000000000
--- a/doc/source/ext/sphinx_rtd_theme/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "sphinx_rtd_theme",
- "version": "0.0.11",
- "private": true,
- "dependencies": {},
- "devDependencies": {
- "grunt": "~0.4.1",
- "grunt-contrib-sass": "~0.7.2",
- "grunt-contrib-watch": "~0.4.3",
- "grunt-contrib-connect": "0.5.0",
- "grunt-contrib-clean": "0.5.0",
- "grunt-contrib-copy": "0.5.0",
- "connect-livereload": "~0.3.0",
- "grunt-exec": "~0.4.2",
- "grunt-open": "0.2.2",
- "matchdep": "~0.1.2"
- }
-}
diff --git a/doc/source/ext/sphinx_rtd_theme/requirements.txt b/doc/source/ext/sphinx_rtd_theme/requirements.txt
deleted file mode 100644
index b8f7aa674..000000000
--- a/doc/source/ext/sphinx_rtd_theme/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-sphinx>=1.1
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/_theme_badge.sass b/doc/source/ext/sphinx_rtd_theme/sass/_theme_badge.sass
deleted file mode 100644
index bde709278..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/_theme_badge.sass
+++ /dev/null
@@ -1,93 +0,0 @@
-.rst-versions
- position: fixed
- bottom: 0
- left: 0
- width: $nav-desktop-width
- color: $section-background-color
- background: darken($menu-background-color, 8%)
- border-top: solid 10px $menu-background-color
- font-family: $base-font-family
- z-index: $z-index-tray
- a
- color: $link_color
- text-decoration: none
- .rst-badge-small
- display: none
- .rst-current-version
- padding: $base-line-height / 2
- background-color: darken($menu-background-color, 5%)
- display: block
- text-align: right
- font-size: 90%
- cursor: pointer
- color: $green
- +clearfix
- .fa
- color: $section-background-color
- .fa-book
- float: left
- .icon-book
- float: left
- &.rst-out-of-date
- background-color: $red
- color: $white
- &.rst-active-old-version
- background-color: $yellow
- color: $black
- &.shift-up .rst-other-versions
- display: block
- .rst-other-versions
- font-size: 90%
- padding: $base-line-height / 2
- color: $text-medium
- display: none
- hr
- display: block
- height: 1px
- border: 0
- margin: 20px 0
- padding: 0
- border-top: solid 1px lighten($menu-background-color, 5%)
- dd
- display: inline-block
- margin: 0
- a
- display: inline-block
- padding: $base-line-height / 4
- color: $section-background-color
- &.rst-badge
- width: auto
- bottom: 20px
- right: 20px
- left: auto
- border: none
- max-width: $nav-desktop-width
- .icon-book
- float: none
- .fa-book
- float: none
- &.shift-up .rst-current-version
- text-align: right
- .fa-book
- float: left
- .icon-book
- float: left
- .rst-current-version
- width: auto
- height: 30px
- line-height: 30px
- padding: 0 $base-line-height / 4
- display: block
- text-align: center
-
-+media($tablet)
- .rst-versions
- width: 85%
- display: none
- &.shift
- display: block
- img
- width: 100%
- height: auto
-
-
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/_theme_badge_fa.sass b/doc/source/ext/sphinx_rtd_theme/sass/_theme_badge_fa.sass
deleted file mode 100644
index f265a80bd..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/_theme_badge_fa.sass
+++ /dev/null
@@ -1,68 +0,0 @@
-// Slimmer version of FA for use on the badge_only.sass file.
-
-+font-face(FontAwesome, '#{$font-awesome-dir}fontawesome_webfont')
-
-.fa:before
- display: inline-block
- font-family: FontAwesome
- font-style: normal
- font-weight: normal
- line-height: 1
- text-decoration: inherit
- +font-smooth
-
-a .fa
- display: inline-block
- text-decoration: inherit
-
-
-li
- .fa
- display: inline-block
- .fa-large:before,
- .fa-large:before
- /* 1.5 increased font size for fa-large * 1.25 width
- width: 1.5 * 1.25em
-
-ul.fas
- list-style-type: none
- margin-left: 2em
- text-indent: -0.8em
- li
- .fa
- width: .8em
- .fa-large:before,
- .fa-large:before
- /* 1.5 increased font size for fa-large * 1.25 width
- vertical-align: baseline
- // width: 1.5*1.25em
-
-.fa-book:before
- content: "\f02d"
-
-.icon-book:before
- content: "\f02d"
-
-.fa-caret-down:before
- content: "\f0d7"
-
-.icon-caret-down:before
- content: "\f0d7"
-
-.fa-caret-up:before
- content: "\f0d8"
-
-.icon-caret-up:before
- content: "\f0d8"
-
-.fa-caret-left:before
- content: "\f0d9"
-
-.icon-caret-left:before
- content: "\f0d9"
-
-.fa-caret-right:before
- content: "\f0da"
-
-.icon-caret-right:before
- content: "\f0da"
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/_theme_breadcrumbs.sass b/doc/source/ext/sphinx_rtd_theme/sass/_theme_breadcrumbs.sass
deleted file mode 100644
index a7c153696..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/_theme_breadcrumbs.sass
+++ /dev/null
@@ -1,25 +0,0 @@
-.wy-breadcrumbs li
- display: inline-block
- &.wy-breadcrumbs-aside
- float: right
- a
- display: inline-block
- padding: 5px
- &:first-child
- padding-left: 0
-.wy-breadcrumbs-extra
- margin-bottom: 0
- color: $text-light
- font-size: 80%
- display: inline-block
-
-
-+media($mobile)
- .wy-breadcrumbs-extra
- display: none
- .wy-breadcrumbs li.wy-breadcrumbs-aside
- display: none
-
-@media print
- .wy-breadcrumbs li.wy-breadcrumbs-aside
- display: none
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/_theme_font_awesome_compatability.sass b/doc/source/ext/sphinx_rtd_theme/sass/_theme_font_awesome_compatability.sass
deleted file mode 100644
index d97a55eb3..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/_theme_font_awesome_compatability.sass
+++ /dev/null
@@ -1,22 +0,0 @@
-.icon
- @extend .fa
-.icon-home
- @extend .fa-home
-.icon-search
- @extend .fa-search
-.icon-book
- @extend .fa-book
-.icon-caret-down
- @extend .fa-caret-down
-.icon-github
- @extend .fa-github
-.icon-bitbucket
- @extend .fa-bitbucket
-.icon-fire
- @extend .fa-fire
-.icon-circle-arrow-right
- @extend .fa-arrow-circle-right
-.icon-circle-arrow-left
- @extend .fa-arrow-circle-left
-.icon-link
- @extend .fa-link
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/_theme_layout.sass b/doc/source/ext/sphinx_rtd_theme/sass/_theme_layout.sass
deleted file mode 100644
index 1e9061247..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/_theme_layout.sass
+++ /dev/null
@@ -1,292 +0,0 @@
-.wy-affix
- position: fixed
- top: $gutter
-
-.wy-menu
- a:hover
- text-decoration: none
-
-.wy-menu-horiz
- +clearfix
- ul, li
- display: inline-block
- li:hover
- background: rgba(255,255,255,.1)
- li
- &.divide-left
- border-left: solid 1px hsl(0, 0%, 25%)
- &.divide-right
- border-right: solid 1px hsl(0, 0%, 25%)
- a
- height: $base-font-size * 2
- display: inline-block
- line-height: $base-font-size * 2
- padding: 0 $base-font-size
-
-.wy-menu-vertical
- header
- height: $base-font-size * 2
- display: inline-block
- line-height: $base-font-size * 2
- padding: 0 $gutter
- display: block
- font-weight: bold
- text-transform: uppercase
- font-size: 80%
- color: $menu-logo-color
- white-space: nowrap
-
- ul
- margin-bottom: 0
- li
- &.divide-top
- border-top: solid 1px hsl(0, 0%, 25%)
- &.divide-bottom
- border-bottom: solid 1px hsl(0, 0%, 25%)
- &.current
- background: darken($section-background-color, 10%)
- a
- color: $text-medium
- border-right: solid 1px darken($section-background-color, 20%)
- padding: $gutter / 4 $gutter * 1.5
- &:hover
- background: darken($section-background-color, 15%)
- // On state for the first level
- li.on a, li.current > a
- color: $text-color
- padding: $gutter / 4 $gutter
- font-weight: bold
- position: relative
- background: $section-background-color
- border: none
- border-bottom: solid 1px darken($section-background-color, 20%)
- border-top: solid 1px darken($section-background-color, 20%)
- padding-left: $gutter -4px
- +font-smooth
- &:hover
- background: $section-background-color
- // This is the on state for pages beyond second level
- li.toctree-l2.current > a
- background: darken($section-background-color, 20%)
- padding: $gutter / 4 $gutter * 1.5
- li.current ul
- display: block
- li ul
- margin-bottom: 0
- display: none
- .local-toc
- li ul
- display: block
- li ul li a
- margin-bottom: 0
- color: $text-light
- font-weight: normal
- a
- display: inline-block
- line-height: 18px
- padding: $gutter / 4 $gutter
- display: block
- position: relative
- font-size: 90%
- color: $text-light
- &:hover
- background-color: lighten($menu-background-color, 10%)
- cursor: pointer
- &:active
- background-color: $menu-logo-color
- cursor: pointer
- color: $white
-
-.wy-side-nav-search
- z-index: $z-index-popover
- background-color: $link-color
- text-align: center
- padding: $gutter / 2
- display: block
- color: $section-background-color
- margin-bottom: $gutter / 2
- input[type=text]
- width: 100%
- border-radius: 50px
- padding: 6px 12px
- border-color: darken($link-color, 5%)
- img
- display: block
- margin: auto auto $gutter / 2 auto
- height: 45px
- width: 45px
- background-color: $menu-logo-color
- padding: 5px
- border-radius: 100%
- > a, .wy-dropdown > a
- color: $section-background-color
- font-size: 100%
- font-weight: bold
- display: inline-block
- padding: $base-line-height / 6 $base-line-height / 4
- margin-bottom: $gutter / 2
- +font-smooth
- &:hover
- background: rgba(255,255,255,.1)
-
-.wy-nav .wy-menu-vertical
- header
- color: $link-color
- a
- color: $text-light
- &:hover
- background-color: $link-color
- color: $white
-
-[data-menu-wrap]
- +transition(all .2s ease-in)
- position: absolute
- opacity: 1
- width: 100%
- opacity: 0
- &.move-center
- left: 0
- right: auto
- opacity: 1
- &.move-left
- right: auto
- left: -100%
- opacity: 0
- &.move-right
- right: -100%
- left: auto
- opacity: 0
-
-
-.wy-body-for-nav
- background: left repeat-y $section-background-color
- background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC)
- background-size: $nav-desktop-width 1px
-
-.wy-grid-for-nav
- position: absolute
- width: 100%
- height: 100%
-
-.wy-nav-side
- position: absolute
- top: 0
- left: 0
- width: $nav-desktop-width
- overflow: hidden
- min-height: 100%
- background: $menu-background-color
- z-index: $z-index-popover
-
-.wy-nav-top
- display: none
- background: $link-color
- color: $white
- padding: $gutter / 4 $gutter / 2
- position: relative
- line-height: 50px
- text-align: center
- font-size: 100%
- +clearfix
- a
- color: $white
- font-weight: bold
- +font-smooth
- img
- margin-right: $base-line-height / 2
- height: 45px
- width: 45px
- background-color: $menu-logo-color
- padding: 5px
- border-radius: 100%
- i
- font-size: 30px
- float: left
- cursor: pointer
-
-.wy-nav-content-wrap
- margin-left: $nav-desktop-width
- background: $section-background-color
- min-height: 100%
-
-.wy-nav-content
- padding: $gutter $gutter * 2
- height: 100%
- max-width: 800px
- margin: auto
-
-.wy-body-mask
- position: fixed
- width: 100%
- height: 100%
- background: rgba(0,0,0,.2)
- display: none
- z-index: $z-index-modal - 1
- &.on
- display: block
-footer
- color: $gray-light
- p
- margin-bottom: $base-line-height / 2
-
-.rst-footer-buttons
- +clearfix
-
-#search-results
- .search li
- margin-bottom: $base-line-height
- border-bottom: solid 1px $table_border_color
- padding-bottom: $base-line-height
- .search li:first-child
- border-top: solid 1px $table_border_color
- padding-top: $base-line-height
- .search li a
- font-size: 120%
- margin-bottom: $base-line-height / 2
- display: inline-block
- .context
- color: $text-medium
- font-size: 90%
-
-
-+media($tablet)
- .wy-body-for-nav
- background: $section-background-color
- .wy-nav-top
- display: block
- .wy-nav-side
- @if $nav-desktop-position == left
- left: -$nav-desktop-width
- @else
- right: -$nav-desktop-width
- &.shift
- width: 85%
- left: 0
- .wy-nav-content-wrap
- margin-left: 0
- .wy-nav-content
- padding: $gutter
- &.shift
- position: fixed
- min-width: 100%
- left: 85%
- top: 0
- height: 100%
- overflow: hidden
-
-+media($desktop-wider)
- .wy-nav-content-wrap
- background: rgba(0,0,0,.05)
- .wy-nav-content
- margin: 0
- background: $section-background-color
-
-@media print
- .rst-versions, footer, .wy-nav-side
- display: none
- .wy-nav-content-wrap
- margin-left: 0
-
-nav.stickynav
- position: fixed
- top: 0
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/_theme_mathjax.sass b/doc/source/ext/sphinx_rtd_theme/sass/_theme_mathjax.sass
deleted file mode 100644
index 100499365..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/_theme_mathjax.sass
+++ /dev/null
@@ -1,5 +0,0 @@
-span[id*='MathJax-Span']
- color: $mathjax-color
-
-.math
- text-align: center
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/_theme_rst.sass b/doc/source/ext/sphinx_rtd_theme/sass/_theme_rst.sass
deleted file mode 100644
index 02581a46d..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/_theme_rst.sass
+++ /dev/null
@@ -1,282 +0,0 @@
-// -------------------------------------------------------------------------------------------------------------------
-// CONTRIBUTORS, PLEASE READ THIS!
-// -------------------------------------------------------------------------------------------------------------------
-// Couple things...
-// 1. Lots of this @extends from wyrm_core/_type.sass (http://www.github.com/snide/wyrm/.
-// * Try not to replace any @extends code. It's pretty generic stuff meant to work together.
-// * That said, know that I'm very unlikely to accept PRs from wyrm just to change style here.
-// 2. I plan to remove the !importants in here. Part of it is due to lazyness, part to sphinx's fondness for nesting.
-// 3. Try to use variables from wyrm_core/wy_variables.sass. Notable are...
-// * $base-line-height // All margins, padding and line-height should use this in .25 increments.
-// * $text-color, $text-light, $text-dark...etc
-// * $base-font-family, $custom-font-family, $code-font-family
-// 4. If you have changes for mobile/tablet, put them at the bottom of the sass file.
-// --------------------------------------------------------------------------------------------------------------------
-
-.rst-content
- // Sphinx by default applies HxW style attributes to images. This fixes that oversite.
- img
- max-width: 100%
- height: auto !important
-
- div.figure
- margin-bottom: $base-line-height
-
- div.figure.align-center
- text-align: center
-
- // Usually it's a good idea to give images some space.
- .section > img
- margin-bottom: $base-line-height
- // Questionable whether this is nice or not. It styles eternal links, but comes with some baggage.
- // a.reference.external:after
- // font-family: FontAwesome
- // content: " \f08e "
- // color: $text-light
- // vertical-align: super
- // font-size: 60%
-
- // For the most part, its safe to assume that sphinx wants you to use a blockquote as an indent. It gets
- // used in many different ways, so don't assume you can apply some fancy style, just leave it be.
- blockquote
- margin-left: $base-line-height
- line-height: $base-line-height
- margin-bottom: $base-line-height
- .literal-block, pre.literal-block
- @extend .codeblock
- // These are the various note pullouts that sphinx applies
- .note, .attention, .caution, .danger, .error, .hint, .important, .tip, .warning, .seealso, .admonition-todo
- @extend .wy-alert
- .last
- margin-bottom: 0
- .admonition-title
- @extend .wy-alert-title
- @extend .fa
- @extend .fa-exclamation-circle
- &:before
- margin-right: 4px
- .note, .seealso
- @extend .wy-alert.wy-alert-info
- .hint, .tip, .important
- @extend .wy-alert.wy-alert-success
- .error, .danger
- @extend .wy-alert.wy-alert-danger
- .warning, .caution, .attention, .admonition-todo
- @extend .wy-alert.wy-alert-warning
- // Some people put tables in notes. Let's give them very basic support.
- .admonition table
- border-color: rgba(0,0,0,.1)
- td, th
- background: transparent !important
- border-color: rgba(0,0,0,.1) !important
- .section ul, .toctree-wrapper ul
- @extend .wy-plain-list-disc
- .section ol.loweralpha, .section ol.loweralpha li
- list-style: lower-alpha
- .section ol.upperalpha, .section ol.upperalpha li
- list-style: upper-alpha
- .section ol, ol.arabic
- @extend .wy-plain-list-decimal
- .section ol p, .section ul p
- margin-bottom: $base-line-height / 2
- .line-block
- margin-left: $base-line-height
-
- // Generics handling of headings and toc stuff.
- .topic-title
- font-weight: bold
- margin-bottom: $base-line-height / 2
- .toc-backref
- color: $text-color
- .align-right
- float: right
- margin: 0px 0px $base-line-height $base-line-height
- .align-left
- float: left
- margin: 0px $base-line-height $base-line-height 0px
- .align-center
- margin: auto
- display: block
-
- // This is the #href that shows up on hover. Sphinx's is terrible so I hack it away.
- h1, h2, h3, h4, h5, h6, dl dt
- .headerlink
- display: none
- visibility: hidden
- font-size: 14px
- @extend .fa
- &:after
- visibility: visible
- content: "\f0c1"
- font-family: FontAwesome
- display: inline-block
- &:hover .headerlink
- display: inline-block
-
- // Sidebar content. You'll see at the bottom of this file I change it in mobile.
- .sidebar
- float: right
- width: 40%
- display: block
- margin: 0 0 $base-line-height $base-line-height
- padding: $base-line-height
- background: $table-stripe-color
- border: solid 1px $table-border-color
- // Sidebar content is usually less relevant, so adjust the margins and sizes.
- p, ul, dl
- font-size: 90%
- .last
- margin-bottom: 0
- .sidebar-title
- display: block
- font-family: $custom-font-family
- font-weight: bold
- background: $table-border-color
- padding: $base-line-height / 4 $base-line-height / 2
- margin: -$base-line-height
- margin-bottom: $base-line-height
- font-size: 100%
- // Sphinx can highlight searched text with ?highlighted=searchterm
- .highlighted
- background: $yellow
- display: inline-block
- font-weight: bold
- padding: 0 $base-line-height / 4
-
- // These are the little citation links [1] that show up within paragraphs.
- .footnote-reference, .citation-reference
- vertical-align: super
- font-size: 90%
-
- // Tables! Sphinx LOVES TABLES. Most of wyrm assumes you're only going to use a table as a table
- // so I have to write a bunch of unique stuff for Sphinx to style them up differently like it's 2003.
- table.docutils.citation, table.docutils.footnote
- background: none
- border: none
- color: $gray-light
- td, tr
- border: none
- background-color: transparent !important
- white-space: normal
- td.label
- padding-left: 0
- padding-right: 0
- vertical-align: top
- table.docutils
- @extend .wy-table
- @extend .wy-table-bordered-all
- &:not(.field-list)
- @extend .wy-table-striped
- // This table is what gets spit out for auto-generated API stuff. I style it smaller bits of padding.
- table.field-list
- @extend .wy-table
- border: none
- td
- border: none
- padding-top: 5px
- td > strong
- display: inline-block
- margin-top: 3px
- .field-name
- padding-right: 10px
- text-align: left
- white-space: nowrap
- .field-body
- text-align: left
- padding-left: 0
-
- // These are the "literals" that get spit out when you mark stuff as ``code`` as your write.
- tt
- @extend code
- color: $black
- big, em
- font-size: 100% !important
- line-height: normal
-
- .xref, a &
- font-weight: bold
- // If the literal is inside an a tag, let's color it like a link
- a tt
- color: $link-color
- dl
- margin-bottom: $base-line-height
- dt
- font-weight: bold
- // Most of the content within these dls are one liners, so I halve the normal margins.
- p, table, ul, ol
- margin-bottom: $base-line-height / 2 !important
- // rST seems to want dds to be treated as the browser would, indented.
- dd
- margin: 0 0 $base-line-height / 2 $base-line-height
- // This is what Sphinx spits out for it's autodocs. Depending upon what language the person is referencing
- // these things usually have a class of "method" or "class" or something similar, but really who knows.
- // Sphinx doesn't give me a generic class on these, so unfortunately I have to apply it to the root dl.
- // This makes me terribly unhappy and makes this code very nesty. Unfortunately I've seen hand-written docs
- // that output similar, but not quite the same nesting so this is really the best we can do.
- dl:not(.docutils)
- margin-bottom: $base-line-height
- // This would be the equivilant of a .. class::
- dt
- display: inline-block
- margin: $base-line-height / 4 0
- font-size: 90%
- line-height: normal
- background: lighten($blue, 50%)
- color: $blue
- border-top: solid 3px lighten($blue, 20%)
- padding: $base-line-height / 4
- position: relative
- &:before
- color: lighten($blue, 20%)
- .headerlink
- color: $text-color
- font-size: 100% !important
- // And this would be the .. method::
- dl dt
- margin-bottom: $base-line-height / 4
- border: none
- border-left: solid 3px hsl(0,0%,80%)
- background: hsl(0,0%,94%)
- color: $text-medium
- .headerlink
- color: $text-color
- font-size: 100% !important
- dt:first-child
- margin-top: 0
- // Since dts get the callout style, we treat this less as callouts.
- tt
- font-weight: bold
- &.descname, &.descclassname
- background-color: transparent
- border: none
- padding: 0
- font-size: 100% !important
- &.descname
- font-weight: bold
- // This is for more advanced parameter control
- .optional
- display: inline-block
- padding: 0 4px
- color: $black
- font-weight: bold
- .property
- display: inline-block
- padding-right: 8px
- // Doc links to sourcecode
- .viewcode-link, .viewcode-back
- display: inline-block
- color: $green
- font-size: 80%
- padding-left: $base-line-height
- .viewcode-back
- display: block
- float: right
- p.rubric
- margin-bottom: 12px
- font-weight: bold
-
-// Mobile specific
-+media($mobile)
- .rst-content
- .sidebar
- width: 100%
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/_theme_variables.sass b/doc/source/ext/sphinx_rtd_theme/sass/_theme_variables.sass
deleted file mode 100644
index e1760bbfb..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/_theme_variables.sass
+++ /dev/null
@@ -1,12 +0,0 @@
-// In here are varibles used for sphinx_rtd_theme, they either add to or overwrite the default ones
-// that are set in wyrm_core/wy_variables.sass. You'll find wyrm in bower_components if you're looking
-// for a reference.
-
-$font-awesome-dir: "../font/"
-$static-img: "../img/"
-$mathjax-color: $text-color
-
-$base-font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif
-$custom-font-family: "Roboto Slab", "ff-tisa-web-pro", "Georgia", Arial, sans-serif
-$custom-font-family2: Georgia, serif
-$code-font-family: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/badge_only.sass b/doc/source/ext/sphinx_rtd_theme/sass/badge_only.sass
deleted file mode 100644
index b8cdac3ac..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/badge_only.sass
+++ /dev/null
@@ -1,16 +0,0 @@
-// ------------------------------------------------------------
-// CONTRIBUTORS, PLEASE READ THIS!
-// ------------------------------------------------------------
-// This generates the RTD sticky badge for non RTD themes. As
-// always, only files labeled "theme_*.sass should be edited".
-// ------------------------------------------------------------
-$border-box-sizing: false !default
-
-@import wyrm_core/wy_variables
-@import theme_variables
-@import bourbon
-@import neat
-@import wyrm_core/mixin
-@import wyrm_core/grid_settings
-@import _theme_badge_fa
-@import _theme_badge
diff --git a/doc/source/ext/sphinx_rtd_theme/sass/theme.sass b/doc/source/ext/sphinx_rtd_theme/sass/theme.sass
deleted file mode 100644
index efacd7a0e..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sass/theme.sass
+++ /dev/null
@@ -1,53 +0,0 @@
-// ------------------------------------------------------------
-// CONTRIBUTORS, PLEASE READ THIS!
-// ------------------------------------------------------------
-// This theme pulls from other frontend projects. The only
-// things you should edit are the sass files that start with
-// "theme_*.sass". All other files are loaded through bower.
-// ------------------------------------------------------------
-
-// Variable defaults set by Wyrm
-@import wyrm_core/wy_variables
-
-// Variable overrides that change coloring and fonts for this theme.
-@import theme_variables
-
-// bourbon.io framework
-@import bourbon
-
-// Bourbon.io/neat framework, with some default media queries
-@import wyrm_core/grid_settings
-@import neat
-// Some corrections for neat
-@import wyrm_core/neat_extra
-
-// Custom reset
-@import wyrm_core/reset
-
-// Wyrm mixins
-@import wyrm_core/mixin
-
-// Font Awesome 4.0 with wyrm extras
-@import font-awesome
-@import wyrm_core/font_icon_defaults
-
-// Wyrm core styles used in this theme
-@import wyrm_core/alert
-@import wyrm_core/button
-@import wyrm_core/dropdown
-@import wyrm_core/form
-@import wyrm_core/generic
-@import wyrm_core/table
-@import wyrm_core/type
-
-// Pygments styling
-@import wyrm_addons/pygments/pygments
-@import wyrm_addons/pygments/pygments_light
-
-// Theme specific styles. These are likely the files you want to edit.
-@import theme_breadcrumbs
-@import theme_layout
-@import theme_badge
-@import theme_rst
-@import theme_mathjax
-@import theme_font_awesome_compatability
diff --git a/doc/source/ext/sphinx_rtd_theme/screen_desktop.png b/doc/source/ext/sphinx_rtd_theme/screen_desktop.png
deleted file mode 100644
index 4e0a5b757..000000000
Binary files a/doc/source/ext/sphinx_rtd_theme/screen_desktop.png and /dev/null differ
diff --git a/doc/source/ext/sphinx_rtd_theme/screen_mobile.png b/doc/source/ext/sphinx_rtd_theme/screen_mobile.png
deleted file mode 100644
index 2d27e31f0..000000000
Binary files a/doc/source/ext/sphinx_rtd_theme/screen_mobile.png and /dev/null differ
diff --git a/doc/source/ext/sphinx_rtd_theme/setup.py b/doc/source/ext/sphinx_rtd_theme/setup.py
deleted file mode 100644
index 47df30e3e..000000000
--- a/doc/source/ext/sphinx_rtd_theme/setup.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-"""`sphinx_rtd_theme` lives on `Github`_.
-
-.. _github: https://www.github.com/snide/sphinx_rtd_theme
-
-"""
-from setuptools import setup
-from sphinx_rtd_theme import __version__
-
-
-setup(
- name='sphinx_rtd_theme',
- version=__version__,
- url='https://github.com/snide/sphinx_rtd_theme/',
- license='MIT',
- author='Dave Snider',
- author_email='dave.snider@gmail.com',
- description='ReadTheDocs.org theme for Sphinx, 2013 version.',
- long_description=open('README.rst').read(),
- zip_safe=False,
- packages=['sphinx_rtd_theme'],
- package_data={'sphinx_rtd_theme': [
- 'theme.conf',
- '*.html',
- 'static/css/*.css',
- 'static/js/*.js',
- 'static/font/*.*'
- ]},
- include_package_data=True,
- install_requires=open('requirements.txt').read().splitlines(),
- classifiers=[
- 'Development Status :: 3 - Alpha',
- 'License :: OSI Approved :: BSD License',
- 'Environment :: Console',
- 'Environment :: Web Environment',
- 'Intended Audience :: Developers',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Operating System :: OS Independent',
- 'Topic :: Documentation',
- 'Topic :: Software Development :: Documentation',
- ],
-)
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/__init__.py b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/__init__.py
deleted file mode 100644
index 1440863d6..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""Sphinx ReadTheDocs theme.
-
-From https://github.com/ryan-roemer/sphinx-bootstrap-theme.
-
-"""
-import os
-
-VERSION = (0, 1, 5)
-
-__version__ = ".".join(str(v) for v in VERSION)
-__version_full__ = __version__
-
-
-def get_html_theme_path():
- """Return list of HTML theme paths."""
- cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
- return cur_dir
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/breadcrumbs.html b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/breadcrumbs.html
deleted file mode 100644
index ff0938e5c..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/breadcrumbs.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<div role="navigation" aria-label="breadcrumbs navigation">
- <ul class="wy-breadcrumbs">
- <li><a href="{{ pathto(master_doc) }}">Docs</a> »</li>
- {% for doc in parents %}
- <li><a href="{{ doc.link|e }}">{{ doc.title }}</a> »</li>
- {% endfor %}
- <li>{{ title }}</li>
- <li class="wy-breadcrumbs-aside">
- {% if display_github %}
- <a href="https://github.com/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-github"> Edit on GitHub</a>
- {% elif display_bitbucket %}
- <a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-bitbucket"> Edit on Bitbucket</a>
- {% elif show_source and has_source and sourcename %}
- <a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> View page source</a>
- {% endif %}
- </li>
- </ul>
- <hr/>
-</div>
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/footer.html b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/footer.html
deleted file mode 100644
index 3c7afed33..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/footer.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<footer>
- {% if next or prev %}
- <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
- {% if next %}
- <a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}">Next <span class="fa fa-arrow-circle-right"></span></a>
- {% endif %}
- {% if prev %}
- <a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}"><span class="fa fa-arrow-circle-left"></span> Previous</a>
- {% endif %}
- </div>
- {% endif %}
-
- <hr/>
-
- <div role="contentinfo">
- <p>
- {%- if show_copyright %}
- {%- if hasdoc('copyright') %}
- {% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
- {%- else %}
- {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
- {%- endif %}
- {%- endif %}
-
- {%- if last_updated %}
- {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
- {%- endif %}
- </p>
- </div>
-
- {% trans %}<a href="https://github.com/snide/sphinx_rtd_theme">Sphinx theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>{% endtrans %}
-</footer>
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/layout.html b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/layout.html
deleted file mode 100644
index 20a13eef8..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/layout.html
+++ /dev/null
@@ -1,162 +0,0 @@
-{# TEMPLATE VAR SETTINGS #}
-{%- set url_root = pathto('', 1) %}
-{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
-{%- if not embedded and docstitle %}
- {%- set titlesuffix = " — "|safe + docstitle|e %}
-{%- else %}
- {%- set titlesuffix = "" %}
-{%- endif %}
-
-<!DOCTYPE html>
-<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
-<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
-<head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- {% block htmltitle %}
- <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
- {% endblock %}
-
- {# FAVICON #}
- {% if favicon %}
- <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
- {% endif %}
-
- {# CSS #}
- <link href='https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic|Roboto+Slab:400,700|Inconsolata:400,700' rel='stylesheet' type='text/css'>
-
- {# OPENSEARCH #}
- {% if not embedded %}
- {% if use_opensearch %}
- <link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/>
- {% endif %}
-
- {% endif %}
-
- {# RTD hosts this file, so just load on non RTD builds #}
- {% if not READTHEDOCS %}
- <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
- {% endif %}
-
- {% for cssfile in css_files %}
- <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
- {% endfor %}
-
- {%- block linktags %}
- {%- if hasdoc('about') %}
- <link rel="author" title="{{ _('About these documents') }}"
- href="{{ pathto('about') }}"/>
- {%- endif %}
- {%- if hasdoc('genindex') %}
- <link rel="index" title="{{ _('Index') }}"
- href="{{ pathto('genindex') }}"/>
- {%- endif %}
- {%- if hasdoc('search') %}
- <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
- {%- endif %}
- {%- if hasdoc('copyright') %}
- <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/>
- {%- endif %}
- <link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}"/>
- {%- if parents %}
- <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}"/>
- {%- endif %}
- {%- if next %}
- <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}"/>
- {%- endif %}
- {%- if prev %}
- <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}"/>
- {%- endif %}
- {%- endblock %}
- {%- block extrahead %} {% endblock %}
-
- {# Keep modernizr in head - http://modernizr.com/docs/#installing #}
- <script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
-
-</head>
-
-<body class="wy-body-for-nav" role="document">
-
- <div class="wy-grid-for-nav">
-
- {# SIDE NAV, TOGGLES ON MOBILE #}
- <nav data-toggle="wy-nav-shift" class="wy-nav-side">
- <div class="wy-side-nav-search">
- {% block sidebartitle %}
- <a href="{{ pathto(master_doc) }}" class="fa fa-home"> {{ project }}</a>
- {% endblock %}
- {% include "searchbox.html" %}
- </div>
-
- <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
- {% set toctree = toctree(maxdepth=2, collapse=False, includehidden=True) %}
- {% if toctree %}
- {{ toctree }}
- {% else %}
- <!-- Local TOC -->
- <div class="local-toc">{{ toc }}</div>
- {% endif %}
- </div>
-
- </nav>
-
- <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
-
- {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
- <nav class="wy-nav-top" role="navigation" aria-label="top navigation">
- <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
- <a href="{{ pathto(master_doc) }}">{{ project }}</a>
- </nav>
-
-
- {# PAGE CONTENT #}
- <div class="wy-nav-content">
- <div class="rst-content">
- {% include "breadcrumbs.html" %}
- <div role="main" class="document">
- {% block body %}{% endblock %}
- </div>
- {% include "footer.html" %}
- </div>
- </div>
-
- </section>
-
- </div>
- {% include "versions.html" %}
-
- {% if not embedded %}
-
- <script type="text/javascript">
- var DOCUMENTATION_OPTIONS = {
- URL_ROOT:'{{ url_root }}',
- VERSION:'{{ release|e }}',
- COLLAPSE_INDEX:false,
- FILE_SUFFIX:'{{ '' if no_search_suffix else file_suffix }}',
- HAS_SOURCE: {{ has_source|lower }}
- };
- </script>
- {%- for scriptfile in script_files %}
- <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
- {%- endfor %}
-
- {% endif %}
-
- {# RTD hosts this file, so just load on non RTD builds #}
- {% if not READTHEDOCS %}
- <script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script>
- {% endif %}
-
- {# STICKY NAVIGATION #}
- {% if theme_sticky_navigation %}
- <script type="text/javascript">
- jQuery(function () {
- SphinxRtdTheme.StickyNav.enable();
- });
- </script>
- {% endif %}
-
- {%- block footer %} {% endblock %}
-
-</body>
-</html>
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/layout_old.html b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/layout_old.html
deleted file mode 100644
index deb8df2a1..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/layout_old.html
+++ /dev/null
@@ -1,205 +0,0 @@
-{#
- basic/layout.html
- ~~~~~~~~~~~~~~~~~
-
- Master layout template for Sphinx themes.
-
- :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-#}
-{%- block doctype -%}
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-{%- endblock %}
-{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %}
-{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
-{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
- (sidebars != []) %}
-{%- set url_root = pathto('', 1) %}
-{# XXX necessary? #}
-{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
-{%- if not embedded and docstitle %}
- {%- set titlesuffix = " — "|safe + docstitle|e %}
-{%- else %}
- {%- set titlesuffix = "" %}
-{%- endif %}
-
-{%- macro relbar() %}
- <div class="related">
- <h3>{{ _('Navigation') }}</h3>
- <ul>
- {%- for rellink in rellinks %}
- <li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
- <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
- {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
- {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
- {%- endfor %}
- {%- block rootrellink %}
- <li><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
- {%- endblock %}
- {%- for parent in parents %}
- <li><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
- {%- endfor %}
- {%- block relbaritems %} {% endblock %}
- </ul>
- </div>
-{%- endmacro %}
-
-{%- macro sidebar() %}
- {%- if render_sidebar %}
- <div class="sphinxsidebar">
- <div class="sphinxsidebarwrapper">
- {%- block sidebarlogo %}
- {%- if logo %}
- <p class="logo"><a href="{{ pathto(master_doc) }}">
- <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
- </a></p>
- {%- endif %}
- {%- endblock %}
- {%- if sidebars != None %}
- {#- new style sidebar: explicitly include/exclude templates #}
- {%- for sidebartemplate in sidebars %}
- {%- include sidebartemplate %}
- {%- endfor %}
- {%- else %}
- {#- old style sidebars: using blocks -- should be deprecated #}
- {%- block sidebartoc %}
- {%- include "localtoc.html" %}
- {%- endblock %}
- {%- block sidebarrel %}
- {%- include "relations.html" %}
- {%- endblock %}
- {%- block sidebarsourcelink %}
- {%- include "sourcelink.html" %}
- {%- endblock %}
- {%- if customsidebar %}
- {%- include customsidebar %}
- {%- endif %}
- {%- block sidebarsearch %}
- {%- include "searchbox.html" %}
- {%- endblock %}
- {%- endif %}
- </div>
- </div>
- {%- endif %}
-{%- endmacro %}
-
-{%- macro script() %}
- <script type="text/javascript">
- var DOCUMENTATION_OPTIONS = {
- URL_ROOT: '{{ url_root }}',
- VERSION: '{{ release|e }}',
- COLLAPSE_INDEX: false,
- FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
- HAS_SOURCE: {{ has_source|lower }}
- };
- </script>
- {%- for scriptfile in script_files %}
- <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
- {%- endfor %}
-{%- endmacro %}
-
-{%- macro css() %}
- <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
- <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
- {%- for cssfile in css_files %}
- <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
- {%- endfor %}
-{%- endmacro %}
-
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
- {{ metatags }}
- {%- block htmltitle %}
- <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
- {%- endblock %}
- {{ css() }}
- {%- if not embedded %}
- {{ script() }}
- {%- if use_opensearch %}
- <link rel="search" type="application/opensearchdescription+xml"
- title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
- href="{{ pathto('_static/opensearch.xml', 1) }}"/>
- {%- endif %}
- {%- if favicon %}
- <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
- {%- endif %}
- {%- endif %}
-{%- block linktags %}
- {%- if hasdoc('about') %}
- <link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
- {%- endif %}
- {%- if hasdoc('genindex') %}
- <link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
- {%- endif %}
- {%- if hasdoc('search') %}
- <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
- {%- endif %}
- {%- if hasdoc('copyright') %}
- <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
- {%- endif %}
- <link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
- {%- if parents %}
- <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
- {%- endif %}
- {%- if next %}
- <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
- {%- endif %}
- {%- if prev %}
- <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
- {%- endif %}
-{%- endblock %}
-{%- block extrahead %} {% endblock %}
- </head>
- <body>
-{%- block header %}{% endblock %}
-
-{%- block relbar1 %}{{ relbar() }}{% endblock %}
-
-{%- block content %}
- {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
-
- <div class="document">
- {%- block document %}
- <div class="documentwrapper">
- {%- if render_sidebar %}
- <div class="bodywrapper">
- {%- endif %}
- <div class="body">
- {% block body %} {% endblock %}
- </div>
- {%- if render_sidebar %}
- </div>
- {%- endif %}
- </div>
- {%- endblock %}
-
- {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
- <div class="clearer"></div>
- </div>
-{%- endblock %}
-
-{%- block relbar2 %}{{ relbar() }}{% endblock %}
-
-{%- block footer %}
- <div class="footer">
- {%- if show_copyright %}
- {%- if hasdoc('copyright') %}
- {% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
- {%- else %}
- {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
- {%- endif %}
- {%- endif %}
- {%- if last_updated %}
- {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
- {%- endif %}
- {%- if show_sphinx %}
- {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
- {%- endif %}
- </div>
- <p>asdf asdf asdf asdf 22</p>
-{%- endblock %}
- </body>
-</html>
-
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/search.html b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/search.html
deleted file mode 100644
index e3aa9b5c6..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/search.html
+++ /dev/null
@@ -1,50 +0,0 @@
-{#
- basic/search.html
- ~~~~~~~~~~~~~~~~~
-
- Template for the search page.
-
- :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-#}
-{%- extends "layout.html" %}
-{% set title = _('Search') %}
-{% set script_files = script_files + ['_static/searchtools.js'] %}
-{% block footer %}
- <script type="text/javascript">
- jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
- </script>
- {# this is used when loading the search index using $.ajax fails,
- such as on Chrome for documents on localhost #}
- <script type="text/javascript" id="searchindexloader"></script>
- {{ super() }}
-{% endblock %}
-{% block body %}
- <noscript>
- <div id="fallback" class="admonition warning">
- <p class="last">
- {% trans %}Please activate JavaScript to enable the search
- functionality.{% endtrans %}
- </p>
- </div>
- </noscript>
-
- {% if search_performed %}
- <h2>{{ _('Search Results') }}</h2>
- {% if not search_results %}
- <p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
- {% endif %}
- {% endif %}
- <div id="search-results">
- {% if search_results %}
- <ul>
- {% for href, caption, context in search_results %}
- <li>
- <a href="{{ pathto(item.href) }}">{{ caption }}</a>
- <p class="context">{{ context|e }}</p>
- </li>
- {% endfor %}
- </ul>
- {% endif %}
- </div>
-{% endblock %}
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/searchbox.html b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/searchbox.html
deleted file mode 100644
index 24418d32b..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/searchbox.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<div role="search">
- <form id ="rtd-search-form" class="wy-form" action="{{ pathto('search') }}" method="get">
- <input type="text" name="q" placeholder="Search docs" />
- <input type="hidden" name="check_keywords" value="yes" />
- <input type="hidden" name="area" value="default" />
- </form>
-</div>
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/css/badge_only.css b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/css/badge_only.css
deleted file mode 100644
index 7e17fb148..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/css/badge_only.css
+++ /dev/null
@@ -1,2 +0,0 @@
-.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}
-/*# sourceMappingURL=badge_only.css.map */
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/css/theme.css b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/css/theme.css
deleted file mode 100644
index 509536e6b..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/css/theme.css
+++ /dev/null
@@ -1,5 +0,0 @@
-*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*!
- * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome
- * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
- */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.1.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff?v=4.1.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.1.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.pull-left.icon{margin-right:.3em}.fa.pull-right,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-square:before,.fa-pied-piper:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .icon,.nav .fa,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .icon{display:inline}.btn .fa.fa-large,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#999;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:0.3125em;font-style:italic}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#f3f6f6;color:#cad2d3}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fff;color:#cad2d3;border-color:transparent}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{padding:6px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9B59B6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#EAF2F5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical header{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#2980B9;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:0.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-side-nav-search{z-index:200;background-color:#2980B9;text-align:center;padding:0.809em;display:block;color:#fcfcfc;margin-bottom:0.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto 0.809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:0.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:absolute;top:0;left:0;width:300px;overflow:hidden;min-height:100%;background:#343131;z-index:200}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:0.4045em 0.809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}nav.stickynav{position:fixed;top:0}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}.rst-content img{max-width:100%;height:auto !important}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after{visibility:visible;content:"";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt{color:#000}.rst-content tt big,.rst-content tt em{font-size:100% !important;line-height:normal}.rst-content tt .xref,a .rst-content tt{font-weight:bold}.rst-content a tt{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:gray}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center}
-/*# sourceMappingURL=theme.css.map */
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/FontAwesome.otf b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/FontAwesome.otf
deleted file mode 100644
index 8b0f54e47..000000000
Binary files a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/FontAwesome.otf and /dev/null differ
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot
deleted file mode 100644
index 7c79c6a6b..000000000
Binary files a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot and /dev/null differ
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg
deleted file mode 100644
index 45fdf3383..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg
+++ /dev/null
@@ -1,414 +0,0 @@
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata></metadata>
-<defs>
-<font id="fontawesomeregular" horiz-adv-x="1536" >
-<font-face units-per-em="1792" ascent="1536" descent="-256" />
-<missing-glyph horiz-adv-x="448" />
-<glyph unicode=" " horiz-adv-x="448" />
-<glyph unicode="	" horiz-adv-x="448" />
-<glyph unicode=" " horiz-adv-x="448" />
-<glyph unicode="¨" horiz-adv-x="1792" />
-<glyph unicode="©" horiz-adv-x="1792" />
-<glyph unicode="®" horiz-adv-x="1792" />
-<glyph unicode="´" horiz-adv-x="1792" />
-<glyph unicode="Æ" horiz-adv-x="1792" />
-<glyph unicode=" " horiz-adv-x="768" />
-<glyph unicode=" " />
-<glyph unicode=" " horiz-adv-x="768" />
-<glyph unicode=" " />
-<glyph unicode=" " horiz-adv-x="512" />
-<glyph unicode=" " horiz-adv-x="384" />
-<glyph unicode=" " horiz-adv-x="256" />
-<glyph unicode=" " horiz-adv-x="256" />
-<glyph unicode=" " horiz-adv-x="192" />
-<glyph unicode=" " horiz-adv-x="307" />
-<glyph unicode=" " horiz-adv-x="85" />
-<glyph unicode=" " horiz-adv-x="307" />
-<glyph unicode=" " horiz-adv-x="384" />
-<glyph unicode="™" horiz-adv-x="1792" />
-<glyph unicode="∞" horiz-adv-x="1792" />
-<glyph unicode="≠" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="500" d="M0 0z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" />
-<glyph unicode="" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
-<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " />
-<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" />
-<glyph unicode="" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" />
-<glyph unicode="" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" />
-<glyph unicode="" horiz-adv-x="1280" d="M128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280zM768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z " />
-<glyph unicode="" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" />
-<glyph unicode="" d="M1120 608q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273 t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1118 660q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198 t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" />
-<glyph unicode="" d="M1184 640q0 -37 -32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9 l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" />
-<glyph unicode="" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" />
-<glyph unicode="" horiz-adv-x="1152" d="M320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" />
-<glyph unicode="" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" />
-<glyph unicode="" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" />
-<glyph unicode="" horiz-adv-x="1792" d="M63 0h-63v1408h63v-1408zM126 1h-32v1407h32v-1407zM220 1h-31v1407h31v-1407zM377 1h-31v1407h31v-1407zM534 1h-62v1407h62v-1407zM660 1h-31v1407h31v-1407zM723 1h-31v1407h31v-1407zM786 1h-31v1407h31v-1407zM943 1h-63v1407h63v-1407zM1100 1h-63v1407h63v-1407z M1226 1h-63v1407h63v-1407zM1352 1h-63v1407h63v-1407zM1446 1h-63v1407h63v-1407zM1635 1h-94v1407h94v-1407zM1698 1h-32v1407h32v-1407zM1792 0h-63v1408h63v-1408z" />
-<glyph unicode="" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
-<glyph unicode="" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M725 977l-170 -450q73 -1 153.5 -2t119 -1.5t52.5 -0.5l29 2q-32 95 -92 241q-53 132 -92 211zM21 -128h-21l2 79q22 7 80 18q89 16 110 31q20 16 48 68l237 616l280 724h75h53l11 -21l205 -480q103 -242 124 -297q39 -102 96 -235q26 -58 65 -164q24 -67 65 -149 q22 -49 35 -57q22 -19 69 -23q47 -6 103 -27q6 -39 6 -57q0 -14 -1 -26q-80 0 -192 8q-93 8 -189 8q-79 0 -135 -2l-200 -11l-58 -2q0 45 4 78l131 28q56 13 68 23q12 12 12 27t-6 32l-47 114l-92 228l-450 2q-29 -65 -104 -274q-23 -64 -23 -84q0 -31 17 -43 q26 -21 103 -32q3 0 13.5 -2t30 -5t40.5 -6q1 -28 1 -58q0 -17 -2 -27q-66 0 -349 20l-48 -8q-81 -14 -167 -14z" />
-<glyph unicode="" horiz-adv-x="1408" d="M555 15q76 -32 140 -32q131 0 216 41t122 113q38 70 38 181q0 114 -41 180q-58 94 -141 126q-80 32 -247 32q-74 0 -101 -10v-144l-1 -173l3 -270q0 -15 12 -44zM541 761q43 -7 109 -7q175 0 264 65t89 224q0 112 -85 187q-84 75 -255 75q-52 0 -130 -13q0 -44 2 -77 q7 -122 6 -279l-1 -98q0 -43 1 -77zM0 -128l2 94q45 9 68 12q77 12 123 31q17 27 21 51q9 66 9 194l-2 497q-5 256 -9 404q-1 87 -11 109q-1 4 -12 12q-18 12 -69 15q-30 2 -114 13l-4 83l260 6l380 13l45 1q5 0 14 0.5t14 0.5q1 0 21.5 -0.5t40.5 -0.5h74q88 0 191 -27 q43 -13 96 -39q57 -29 102 -76q44 -47 65 -104t21 -122q0 -70 -32 -128t-95 -105q-26 -20 -150 -77q177 -41 267 -146q92 -106 92 -236q0 -76 -29 -161q-21 -62 -71 -117q-66 -72 -140 -108q-73 -36 -203 -60q-82 -15 -198 -11l-197 4q-84 2 -298 -11q-33 -3 -272 -11z" />
-<glyph unicode="" horiz-adv-x="1024" d="M0 -126l17 85q4 1 77 20q76 19 116 39q29 37 41 101l27 139l56 268l12 64q8 44 17 84.5t16 67t12.5 46.5t9 30.5t3.5 11.5l29 157l16 63l22 135l8 50v38q-41 22 -144 28q-28 2 -38 4l19 103l317 -14q39 -2 73 -2q66 0 214 9q33 2 68 4.5t36 2.5q-2 -19 -6 -38 q-7 -29 -13 -51q-55 -19 -109 -31q-64 -16 -101 -31q-12 -31 -24 -88q-9 -44 -13 -82q-44 -199 -66 -306l-61 -311l-38 -158l-43 -235l-12 -45q-2 -7 1 -27q64 -15 119 -21q36 -5 66 -10q-1 -29 -7 -58q-7 -31 -9 -41q-18 0 -23 -1q-24 -2 -42 -2q-9 0 -28 3q-19 4 -145 17 l-198 2q-41 1 -174 -11q-74 -7 -98 -9z" />
-<glyph unicode="" horiz-adv-x="1792" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l215 -1h293l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -42.5 2t-103.5 -1t-111 -1 q-34 0 -67 -5q-10 -97 -8 -136l1 -152v-332l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-88 0 -233 -14q-48 -4 -70 -4q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q8 192 6 433l-5 428q-1 62 -0.5 118.5t0.5 102.5t-2 57t-6 15q-6 5 -14 6q-38 6 -148 6q-43 0 -100 -13.5t-73 -24.5q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1744 128q33 0 42 -18.5t-11 -44.5 l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80z" />
-<glyph unicode="" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l446 -1h318l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -58.5 2t-138.5 -1t-128 -1 q-94 0 -127 -5q-10 -97 -8 -136l1 -152v52l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-82 0 -233 -13q-45 -5 -70 -5q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q6 137 6 433l-5 44q0 265 -2 278q-2 11 -6 15q-6 5 -14 6q-38 6 -148 6q-50 0 -168.5 -14t-132.5 -24q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1505 113q26 -20 26 -49t-26 -49l-162 -126 q-26 -20 -44.5 -11t-18.5 42v80h-1024v-80q0 -33 -18.5 -42t-44.5 11l-162 126q-26 20 -26 49t26 49l162 126q26 20 44.5 11t18.5 -42v-80h1024v80q0 33 18.5 42t44.5 -11z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 1184v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5 q39 -17 39 -59z" />
-<glyph unicode="" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
-<glyph unicode="" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" />
-<glyph unicode="" d="M768 96v1088q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" />
-<glyph unicode="" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" />
-<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
-<glyph unicode="" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
-<glyph unicode="" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" />
-<glyph unicode="" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" />
-<glyph unicode="" horiz-adv-x="1152" d="M742 -37l-652 651q-37 37 -37 90.5t37 90.5l652 651q37 37 90.5 37t90.5 -37l75 -75q37 -37 37 -90.5t-37 -90.5l-486 -486l486 -485q37 -38 37 -91t-37 -90l-75 -75q-37 -37 -90.5 -37t-90.5 37z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1099 704q0 -52 -37 -91l-652 -651q-37 -37 -90 -37t-90 37l-76 75q-37 39 -37 91q0 53 37 90l486 486l-486 485q-37 39 -37 91q0 53 37 90l76 75q36 38 90 38t90 -38l652 -651q37 -37 37 -90z" />
-<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
-<glyph unicode="" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M896 160v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h192q14 0 23 9t9 23zM1152 832q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26t37.5 -59 q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1024 160v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 1056v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M1097 457l-146 -146q-10 -10 -23 -10t-23 10l-137 137l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23t10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23 l-137 -137l137 -137q10 -10 10 -23t-10 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1171 723l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45t19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198 t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1312 643q0 161 -87 295l-754 -753q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5zM313 344l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199t-73 -274q0 -162 89 -299zM1536 643q0 -157 -61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61 t-245 164t-163.5 246t-61 300t61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5z" />
-<glyph unicode="" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" />
-<glyph unicode="" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" />
-<glyph unicode="" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" />
-<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" />
-<glyph unicode="" d="M928 180v56v468v192h-320v-192v-468v-56q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5zM472 1024h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-43 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5 t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" />
-<glyph unicode="" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " />
-<glyph unicode="" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1376 1376q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23q-1 13 9 25l96 97q9 9 23 9 q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12z" />
-<glyph unicode="" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
-<glyph unicode="" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" />
-<glyph unicode="" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1611 320q0 -53 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-486 485l-486 -485q-36 -38 -90 -38t-90 38l-75 75q-38 36 -38 90q0 53 38 91l651 651q37 37 90 37q52 0 91 -37l650 -651q38 -38 38 -91z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1611 832q0 -53 -37 -90l-651 -651q-38 -38 -91 -38q-54 0 -90 38l-651 651q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l486 -486l486 486q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " />
-<glyph unicode="" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" />
-<glyph unicode="" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1920" d="M512 512v-384h-256v384h256zM896 1024v-896h-256v896h256zM1280 768v-640h-256v640h256zM1664 1152v-1024h-256v1024h256zM1792 32v1216q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5z M1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
-<glyph unicode="" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1307 618l23 219h-198v109q0 49 15.5 68.5t71.5 19.5h110v219h-175q-152 0 -218 -72t-66 -213v-131h-131v-219h131v-635h262v635h175zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" />
-<glyph unicode="" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" />
-<glyph unicode="" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" />
-<glyph unicode="" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" />
-<glyph unicode="" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" />
-<glyph unicode="" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" />
-<glyph unicode="" d="M237 122h231v694h-231v-694zM483 1030q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5zM1068 122h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694h231v388q0 38 7 56q15 35 45 59.5t74 24.5 q116 0 116 -157v-371zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" />
-<glyph unicode="" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5 -68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" />
-<glyph unicode="" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
-<glyph unicode="" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" />
-<glyph unicode="" horiz-adv-x="768" d="M511 980h257l-30 -284h-227v-824h-341v824h-170v284h170v171q0 182 86 275.5t283 93.5h227v-284h-142q-39 0 -62.5 -6.5t-34 -23.5t-13.5 -34.5t-3 -49.5v-142z" />
-<glyph unicode="" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" />
-<glyph unicode="" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" />
-<glyph unicode="" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM183 128h1298q-164 181 -246.5 411.5t-82.5 484.5q0 256 -320 256t-320 -256q0 -254 -82.5 -484.5t-246.5 -411.5zM1664 128q0 -52 -38 -90t-90 -38 h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
-<glyph unicode="" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" />
-<glyph unicode="" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" />
-<glyph unicode="" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" />
-<glyph unicode="" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33 t55 33t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580 q0 -142 -77.5 -230t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100 q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" />
-<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1042 887q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11 q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5 q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5 q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17t10.5 17q9 6 14 5.5t14.5 -5.5 t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3 q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25 q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5q-16 0 -22 -1q-146 -80 -235 -222q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5 t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-15 25 -17 29q-3 5 -5.5 15.5 t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21 q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5 q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3 q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5t0 14t-1.5 12.5l-1 8v18l-1 8q-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5 t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5 q15 10 -7 16q-17 5 -43 -12zM879 10q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7t-10 1.5t-11.5 -7 q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5q0 -6 2 -16z" />
-<glyph unicode="" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" />
-<glyph unicode="" horiz-adv-x="1792" d="M640 1280h512v128h-512v-128zM1792 640v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 640v-128h-256v128h256zM1792 1120v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" />
-<glyph unicode="" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " />
-<glyph unicode="" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " />
-<glyph unicode="" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" />
-<glyph unicode="" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" />
-<glyph unicode="" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" />
-<glyph unicode="" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" />
-<glyph unicode="" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" />
-<glyph unicode="" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" />
-<glyph unicode="" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
-<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
-<glyph unicode="" d="M678 -57q0 -38 -10 -71h-380q-95 0 -171.5 56.5t-103.5 147.5q24 45 69 77.5t100 49.5t107 24t107 7q32 0 49 -2q6 -4 30.5 -21t33 -23t31 -23t32 -25.5t27.5 -25.5t26.5 -29.5t21 -30.5t17.5 -34.5t9.5 -36t4.5 -40.5zM385 294q-234 -7 -385 -85v433q103 -118 273 -118 q32 0 70 5q-21 -61 -21 -86q0 -67 63 -149zM558 805q0 -100 -43.5 -160.5t-140.5 -60.5q-51 0 -97 26t-78 67.5t-56 93.5t-35.5 104t-11.5 99q0 96 51.5 165t144.5 69q66 0 119 -41t84 -104t47 -130t16 -128zM1536 896v-736q0 -119 -84.5 -203.5t-203.5 -84.5h-468 q39 73 39 157q0 66 -22 122.5t-55.5 93t-72 71t-72 59.5t-55.5 54.5t-22 59.5q0 36 23 68t56 61.5t65.5 64.5t55.5 93t23 131t-26.5 145.5t-75.5 118.5q-6 6 -14 11t-12.5 7.5t-10 9.5t-10.5 17h135l135 64h-437q-138 0 -244.5 -38.5t-182.5 -133.5q0 126 81 213t207 87h960 q119 0 203.5 -84.5t84.5 -203.5v-96h-256v256h-128v-256h-256v-128h256v-256h128v256h256z" />
-<glyph unicode="" horiz-adv-x="1664" d="M876 71q0 21 -4.5 40.5t-9.5 36t-17.5 34.5t-21 30.5t-26.5 29.5t-27.5 25.5t-32 25.5t-31 23t-33 23t-30.5 21q-17 2 -50 2q-54 0 -106 -7t-108 -25t-98 -46t-69 -75t-27 -107q0 -68 35.5 -121.5t93 -84t120.5 -45.5t127 -15q59 0 112.5 12.5t100.5 39t74.5 73.5 t27.5 110zM756 933q0 60 -16.5 127.5t-47 130.5t-84 104t-119.5 41q-93 0 -144 -69t-51 -165q0 -47 11.5 -99t35.5 -104t56 -93.5t78 -67.5t97 -26q97 0 140.5 60.5t43.5 160.5zM625 1408h437l-135 -79h-135q71 -45 110 -126t39 -169q0 -74 -23 -131.5t-56 -92.5t-66 -64.5 t-56 -61t-23 -67.5q0 -26 16.5 -51t43 -48t58.5 -48t64 -55.5t58.5 -66t43 -85t16.5 -106.5q0 -160 -140 -282q-152 -131 -420 -131q-59 0 -119.5 10t-122 33.5t-108.5 58t-77 89t-30 121.5q0 61 37 135q32 64 96 110.5t145 71t155 36t150 13.5q-64 83 -64 149q0 12 2 23.5 t5 19.5t8 21.5t7 21.5q-40 -5 -70 -5q-149 0 -255.5 98t-106.5 246q0 140 95 250.5t234 141.5q94 20 187 20zM1664 1152v-128h-256v-256h-128v256h-256v128h256v256h128v-256h256z" />
-<glyph unicode="" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" />
-<glyph unicode="" d="M349 911v-991h-330v991h330zM370 1217q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5zM1536 488v-568h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329 q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5z" />
-<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" />
-<glyph unicode="" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
-<glyph unicode="" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" />
-<glyph unicode="" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" />
-<glyph unicode="" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" />
-<glyph unicode="" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" />
-<glyph unicode="" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" />
-<glyph unicode="" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1664 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5 q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1024 352v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM1024 608v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280z M768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1920 576q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96 q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93z" />
-<glyph unicode="" horiz-adv-x="1664" d="M640 640v384h-256v-256q0 -53 37.5 -90.5t90.5 -37.5h128zM1664 192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" />
-<glyph unicode="" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" />
-<glyph unicode="" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" />
-<glyph unicode="" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
-<glyph unicode="" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" />
-<glyph unicode="" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" />
-<glyph unicode="" horiz-adv-x="1568" d="M496 192q0 -60 -42.5 -102t-101.5 -42q-60 0 -102 42t-42 102t42 102t102 42q59 0 101.5 -42t42.5 -102zM928 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -66 -47 -113t-113 -47t-113 47t-47 113 t47 113t113 47t113 -47t47 -113zM1360 192q0 -46 -33 -79t-79 -33t-79 33t-33 79t33 79t79 33t79 -33t33 -79zM528 1088q0 -73 -51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5t51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5zM992 1280q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1536 640q0 -40 -28 -68t-68 -28t-68 28t-28 68t28 68t68 28t68 -28t28 -68zM1328 1088q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5z" />
-<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" />
-<glyph unicode="" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" d="M1134 461q-37 -121 -138 -195t-228 -74t-228 74t-138 195q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5 t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1134 307q8 -25 -4 -48.5t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5q37 121 138 195t228 74t228 -74t138 -195zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204 t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1152 448q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h640q26 0 45 -19t19 -45zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M832 448v128q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23zM1408 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1920 512q0 -212 -150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150 t-150 362t150 362t362 150h896q212 0 362 -150t150 -362z" />
-<glyph unicode="" horiz-adv-x="1920" d="M384 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM512 624v-96q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h224q16 0 16 -16zM384 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 368v-96q0 -16 -16 -16 h-864q-16 0 -16 16v96q0 16 16 16h864q16 0 16 -16zM768 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM640 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1024 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16 h96q16 0 16 -16zM896 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1280 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1152 880v-96 q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 880v-352q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h112v240q0 16 16 16h96q16 0 16 -16zM1792 128v896h-1664v-896 h1664zM1920 1024v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1664 491v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9 h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102 q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
-<glyph unicode="" horiz-adv-x="1792" d="M832 536v192q-181 -16 -384 -117v-185q205 96 384 110zM832 954v197q-172 -8 -384 -126v-189q215 111 384 118zM1664 491v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2 q-23 0 -49 -3v-222h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92zM1664 918v189q-169 -91 -306 -91q-45 0 -78 8v-196q148 -42 384 90zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266 q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8 q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
-<glyph unicode="" horiz-adv-x="1664" d="M585 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23zM1664 96v-64q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h960q14 0 23 -9 t9 -23z" />
-<glyph unicode="" horiz-adv-x="1920" d="M617 137l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23zM1208 1204l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5 l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5zM1865 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M640 454v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45zM1792 416q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1 q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221q169 -173 169 -509z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1186 579l257 250l-356 52l-66 10l-30 60l-159 322v-963l59 -31l318 -168l-60 355l-12 66zM1638 841l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5t54 34.5 l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1401 1187l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5t4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M557 256h595v595zM512 301l595 595h-595v-595zM1664 224v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23 v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1024" d="M288 64q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM288 1216q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM928 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1024 1088q0 -52 -26 -96.5t-70 -69.5 q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497 q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136z" />
-<glyph unicode="" horiz-adv-x="1664" d="M439 265l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23zM608 224v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM384 448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23t9 23t23 9h320 q14 0 23 -9t9 -23zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204zM1031 1044l-239 -18 l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56zM1664 960q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9 t-9 23t9 23t23 9h320q14 0 23 -9t9 -23zM1120 1504v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM1527 1353l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" />
-<glyph unicode="" horiz-adv-x="1024" d="M704 280v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28zM1020 880q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5 t-10.5 37.5v45q0 83 65 156.5t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25t5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5z" />
-<glyph unicode="" horiz-adv-x="640" d="M640 192v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45zM512 1344v-192q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v192 q0 26 19 45t45 19h256q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="640" d="M512 288v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45zM542 1344l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45l-28 768q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45z" />
-<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1534 846v-206h-514l-3 27 q-4 28 -4 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80 h126z" />
-<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1536 -50v-206h-514l-4 27 q-3 45 -3 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126z" />
-<glyph unicode="" horiz-adv-x="1920" d="M896 128l336 384h-768l-336 -384h768zM1909 1205q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5t30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1664 438q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5 t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89 q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117 q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1152 832v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5 t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45zM896 1216v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226v512q0 132 94 226t226 94t226 -94t94 -226z" />
-<glyph unicode="" horiz-adv-x="1408" d="M271 591l-101 -101q-42 103 -42 214v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113zM1385 1193l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128 q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23t10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23 t-10 -23zM1005 1325l-621 -621v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1088 576v640h-448v-1137q119 63 213 137q235 184 235 360zM1280 1344v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150 t-33.5 170.5v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
-<glyph unicode="" horiz-adv-x="1408" d="M512 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 1376v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800 q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37t3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113 q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1440 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1664 1376q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85q-3 -1 -9 -1 q-14 0 -23 9l-64 64q-17 19 -5 39l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1745 763l-164 -763h-334l178 832q13 56 -15 88q-27 33 -83 33h-169l-204 -953h-334l204 953h-286l-204 -953h-334l204 953l-153 327h1276q101 0 189.5 -40.5t147.5 -113.5q60 -73 81 -168.5t0 -194.5z" />
-<glyph unicode="" d="M909 141l102 102q19 19 19 45t-19 45l-307 307l307 307q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M717 141l454 454q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1165 397l102 102q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M813 237l454 454q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1130 939l16 175h-884l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674zM0 1408h1408l-128 -1438l-578 -162l-574 162z" />
-<glyph unicode="" horiz-adv-x="1792" d="M275 1408h1505l-266 -1333l-804 -267l-698 267l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208z" />
-<glyph unicode="" horiz-adv-x="1792" d="M960 1280q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1792 352v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30v352 q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192q26 0 45 -19 t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1056 768q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181 v-320h736z" />
-<glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM1152 640q0 159 -112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM1280 640q0 -212 -150 -362t-362 -150t-362 150 t-150 362t150 362t362 150t362 -150t150 -362zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM896 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM1408 800v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
-<glyph unicode="" horiz-adv-x="384" d="M384 288v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 1312v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
-<glyph unicode="" d="M512 256q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM863 162q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10 t9 24zM1247 161q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128q13 0 23 10q11 9 9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1152 585q32 18 32 55t-32 55l-544 320q-31 19 -64 1q-32 -19 -32 -56v-640q0 -37 32 -56 q16 -8 32 -8q17 0 32 9z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1024 1084l316 -316l-572 -572l-316 316zM813 105l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45t19 -45l362 -362q18 -18 45 -18t45 18zM1702 742l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136 t-136 56t-136 -56l-125 126q-37 37 -37 90.5t37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5z" />
-<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5 t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1018 933q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68z" />
-<glyph unicode="" horiz-adv-x="1024" d="M32 1280h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34q9 19 29 19z" />
-<glyph unicode="" d="M685 237l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l358 -358q19 -19 45 -19t45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5 t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M404 428l152 -152l-52 -52h-56v96h-96v56zM818 818q14 -13 -3 -30l-291 -291q-17 -17 -30 -3q-14 13 3 30l291 291q17 17 30 3zM544 128l544 544l-288 288l-544 -544v-288h288zM1152 736l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28l-92 -92zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1280 608v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1005 435l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5 t224 23.5v-160q0 -42 40 -59q12 -5 24 -5q26 0 45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M640 448l256 128l-256 128v-256zM1024 1039v-542l-512 -256v542zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1145 861q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66q17 35 57 35h640q40 0 57 -35zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1145 419q-17 -35 -57 -35h-640q-40 0 -57 35q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1088 640q0 -33 -27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52zM1280 160v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h960q14 0 23 9t9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M976 229l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9 t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26 l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5l12 3l5 2q13 5 26 -2q12 -7 15 -21z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1020 399v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7 q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M978 351q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5t-17.5 18q-17 21 -2 41l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43 t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5 t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50 t53 -63.5t31.5 -76.5t13 -94z" />
-<glyph unicode="" horiz-adv-x="898" d="M898 1066v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102 q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1027" d="M603 0h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214l-321 578q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61 l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1043 971q0 100 -65 162t-171 62h-320v-448h320q106 0 171 62t65 162zM1280 971q0 -193 -126.5 -315t-326.5 -122h-340v-118h505q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-505v-192q0 -14 -9.5 -23t-22.5 -9h-167q-14 0 -23 9t-9 23v192h-224q-14 0 -23 9t-9 23v128 q0 14 9 23t23 9h224v118h-224q-14 0 -23 9t-9 23v149q0 13 9 22.5t23 9.5h224v629q0 14 9 23t23 9h539q200 0 326.5 -122t126.5 -315z" />
-<glyph unicode="" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1024 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1024 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28 t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" />
-<glyph unicode="" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1216 -32v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192 q14 0 23 -9t9 -23zM1408 480v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1600 992v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1792 1504v-192q0 -14 -9 -23t-23 -9h-832 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23z" />
-<glyph unicode="" d="M1346 223q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23 zM1486 165q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5 t82 -252.5zM1456 882v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165z" />
-<glyph unicode="" d="M1346 1247q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9 t9 -23zM1456 -142v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165zM1486 1189q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13 q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M256 192q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5zM416 704v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45zM1600 704q0 -86 -55 -149q15 -44 15 -76 q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5v641q0 25 18 43.5t43 20.5q24 2 76 59t101 121q68 87 101 120q18 18 31 48t17.5 48.5 t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135z" />
-<glyph unicode="" horiz-adv-x="1664" d="M256 960q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5zM416 448v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640q0 -26 19 -45t45 -19h288q26 0 45 19t19 45zM1545 597q55 -61 55 -149q-1 -78 -57.5 -135 t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121 t-76 59q-25 2 -43 20.5t-18 43.5v641q0 26 19 44.5t45 19.5q35 1 158 44q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76z" />
-<glyph unicode="" d="M919 233v157q0 50 -29 50q-17 0 -33 -16v-224q16 -16 33 -16q29 0 29 49zM1103 355h66v34q0 51 -33 51t-33 -51v-34zM532 621v-70h-80v-423h-74v423h-78v70h232zM733 495v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54v290h66v-270q0 -24 1 -26q1 -15 15 -15 q20 0 42 31v280h67zM985 384v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74zM1236 255v-9q0 -29 -2 -43q-3 -22 -15 -40q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86v129q0 59 20 86q29 38 80 38t78 -38 q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68zM785 1079v-156q0 -51 -32 -51t-32 51v156q0 52 32 52t32 -52zM1318 366q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5 q-20 -87 -20 -260q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73q20 84 20 260zM563 1017l90 296h-75l-51 -195l-53 195h-78l24 -69t23 -69q35 -103 46 -158v-201h74v201zM852 936v130q0 58 -21 87q-29 38 -78 38q-51 0 -78 -38 q-21 -29 -21 -87v-130q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87zM1033 816h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293q0 -37 6 -55q11 -27 43 -27q36 0 77 45v-40zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M971 292v-211q0 -67 -39 -67q-23 0 -45 22v301q22 22 45 22q39 0 39 -67zM1309 291v-46h-90v46q0 68 45 68t45 -68zM343 509h107v94h-312v-94h105v-569h100v569zM631 -60h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391q0 -49 8 -73 q12 -37 58 -37q48 0 102 61v-54zM1060 88v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89v-663h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100zM1398 98v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51 q-28 -37 -28 -116v-173q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54q2 9 2 58zM790 1011v210q0 69 -43 69t-43 -69v-210q0 -70 43 -70t43 70zM1509 260q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99 q-26 112 -26 350q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350zM511 1536h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187h106l71 -263zM881 1203v-175q0 -81 -28 -118q-37 -51 -106 -51q-67 0 -105 51 q-28 38 -28 118v175q0 80 28 117q38 51 105 51q69 0 106 -51q28 -37 28 -117zM1216 1365v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91z" />
-<glyph unicode="" horiz-adv-x="1408" d="M597 869q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17t0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45zM1403 1511q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37q-10 -15 -32 -15h-239q-42 0 -66 45l-339 622q18 32 531 942 q25 45 64 45h241q22 0 31 -15z" />
-<glyph unicode="" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" />
-<glyph unicode="" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" />
-<glyph unicode="" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" />
-<glyph unicode="" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" />
-<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" />
-<glyph unicode="" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" />
-<glyph unicode="" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M390 1408h219v-388h364v-241h-364v-394q0 -136 14 -172q13 -37 52 -60q50 -31 117 -31q117 0 232 76v-242q-102 -48 -178 -65q-77 -19 -173 -19q-105 0 -186 27q-78 25 -138 75q-58 51 -79 105q-22 54 -22 161v539h-170v217q91 30 155 84q64 55 103 132q39 78 54 196z " />
-<glyph unicode="" d="M1123 127v181q-88 -56 -174 -56q-51 0 -88 23q-29 17 -39 45q-11 30 -11 129v295h274v181h-274v291h-164q-11 -90 -40 -147t-78 -99q-48 -40 -116 -63v-163h127v-404q0 -78 17 -121q17 -42 59 -78q43 -37 104 -57q62 -20 140 -20q67 0 129 14q57 13 134 49zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" />
-<glyph unicode="" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1728 643q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1393 321q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65 q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126zM1017 1494q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10z" />
-<glyph unicode="" horiz-adv-x="1664" d="M682 530v-651l-682 94v557h682zM682 1273v-659h-682v565zM1664 530v-786l-907 125v661h907zM1664 1408v-794h-907v669z" />
-<glyph unicode="" horiz-adv-x="1408" d="M493 1053q16 0 27.5 11.5t11.5 27.5t-11.5 27.5t-27.5 11.5t-27 -11.5t-11 -27.5t11 -27.5t27 -11.5zM915 1053q16 0 27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27.5t27.5 -11.5zM103 869q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30 t-73 30t-30 73v430q0 42 30 72t73 30zM1163 850v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78v666h918zM931 1255q107 -55 171 -153.5t64 -215.5 h-925q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20zM1408 767v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5z" />
-<glyph unicode="" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31 -29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 10.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" />
-<glyph unicode="" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1483 512l-587 -587q-52 -53 -127.5 -53t-128.5 53l-587 587q-53 53 -53 128t53 128l587 587q53 53 128 53t128 -53l265 -265l-398 -399l-188 188q-42 42 -99 42q-59 0 -100 -41l-120 -121q-42 -40 -42 -99q0 -58 42 -100l406 -408q30 -28 67 -37l6 -4h28q60 0 99 41 l619 619l2 -3q53 -53 53 -128t-53 -128zM1406 1138l120 -120q14 -15 14 -36t-14 -36l-730 -730q-17 -15 -37 -15v0q-4 0 -6 1q-18 2 -30 14l-407 408q-14 15 -14 36t14 35l121 120q13 15 35 15t36 -15l252 -252l574 575q15 15 36 15t36 -15z" />
-<glyph unicode="" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
-<glyph unicode="" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
-<glyph unicode="" d="M773 234l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85t24.5 -59zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1472 640q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5zM1748 363q-4 -15 -20 -20l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4 l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94 q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29z" />
-<glyph unicode="" d="M1262 233q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5zM1465 318q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38z" />
-<glyph unicode="" horiz-adv-x="1792" d="M1088 704q0 26 -19 45t-45 19h-256q-26 0 -45 -19t-19 -45t19 -45t45 -19h256q26 0 45 19t19 45zM1664 896v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1728 1344v-256q0 -26 -19 -45t-45 -19h-1536 q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1632 576q0 -26 -19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207 q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45t19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19 t19 -45zM1152 1152h-640q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5z" />
-<glyph unicode="" horiz-adv-x="1920" d="M1917 1016q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12q-30 21 -70 64t-68.5 77.5t-61 58 t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211t-130.5 272q-6 16 -6 27t3 16l4 6 q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24q17 19 38 30q53 26 239 24 q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10 10 17.5t11 10.5l4 3l5 2.5t13 3t20 0.5l288 2 q39 5 64 -2.5t31 -16.5z" />
-<glyph unicode="" horiz-adv-x="1792" d="M675 252q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5t43 -50.5t74.5 -2.5t62.5 47.5zM769 373q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5q-17 -31 13 -45q14 -5 29 0.5t22 18.5zM943 266q-45 -102 -158 -150t-224 -12 q-107 34 -147.5 126.5t6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5zM1255 426q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5zM1563 422 q0 -68 -37 -139.5t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178 q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5zM1489 1046q42 -47 54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37q-5 24 8 44.5t37 25.5q60 13 119 -5.5t101 -65.5z M1670 1209q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5t9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5z" />
-<glyph unicode="" d="M1133 -34q-171 -94 -368 -94q-196 0 -367 94q138 87 235.5 211t131.5 268q35 -144 132.5 -268t235.5 -211zM638 1394v-485q0 -252 -126.5 -459.5t-330.5 -306.5q-181 215 -181 495q0 187 83.5 349.5t229.5 269.5t325 137zM1536 638q0 -280 -181 -495 q-204 99 -330.5 306.5t-126.5 459.5v485q179 -30 325 -137t229.5 -269.5t83.5 -349.5z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1402 433q-32 -80 -76 -138t-91 -88.5t-99 -46.5t-101.5 -14.5t-96.5 8.5t-86.5 22t-69.5 27.5t-46 22.5l-17 10q-113 -228 -289.5 -359.5t-384.5 -132.5q-19 0 -32 13t-13 32t13 31.5t32 12.5q173 1 322.5 107.5t251.5 294.5q-36 -14 -72 -23t-83 -13t-91 2.5t-93 28.5 t-92 59t-84.5 100t-74.5 146q114 47 214 57t167.5 -7.5t124.5 -56.5t88.5 -77t56.5 -82q53 131 79 291q-7 -1 -18 -2.5t-46.5 -2.5t-69.5 0.5t-81.5 10t-88.5 23t-84 42.5t-75 65t-54.5 94.5t-28.5 127.5q70 28 133.5 36.5t112.5 -1t92 -30t73.5 -50t56 -61t42 -63t27.5 -56 t16 -39.5l4 -16q12 122 12 195q-8 6 -21.5 16t-49 44.5t-63.5 71.5t-54 93t-33 112.5t12 127t70 138.5q73 -25 127.5 -61.5t84.5 -76.5t48 -85t20.5 -89t-0.5 -85.5t-13 -76.5t-19 -62t-17 -42l-7 -15q1 -5 1 -50.5t-1 -71.5q3 7 10 18.5t30.5 43t50.5 58t71 55.5t91.5 44.5 t112 14.5t132.5 -24q-2 -78 -21.5 -141.5t-50 -104.5t-69.5 -71.5t-81.5 -45.5t-84.5 -24t-80 -9.5t-67.5 1t-46.5 4.5l-17 3q-23 -147 -73 -283q6 7 18 18.5t49.5 41t77.5 52.5t99.5 42t117.5 20t129 -23.5t137 -77.5z" />
-<glyph unicode="" horiz-adv-x="1280" d="M1259 283v-66q0 -85 -57.5 -144.5t-138.5 -59.5h-57l-260 -269v269h-529q-81 0 -138.5 59.5t-57.5 144.5v66h1238zM1259 609v-255h-1238v255h1238zM1259 937v-255h-1238v255h1238zM1259 1077v-67h-1238v67q0 84 57.5 143.5t138.5 59.5h846q81 0 138.5 -59.5t57.5 -143.5z " />
-<glyph unicode="" d="M1152 640q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1152 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-192q0 -14 -9 -23t-23 -9q-12 0 -24 10l-319 319q-9 9 -9 23t9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h352q13 0 22.5 -9.5t9.5 -22.5zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" d="M1024 960v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52t27 52l448 320q17 12 37 12q26 0 45 -19t19 -45zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5 t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
-<glyph unicode="" horiz-adv-x="1664" d="M1023 349l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5zM1571 249l58 -114l-256 -128 q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5l-96 779q-2 16 6 42q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455z" />
-<glyph unicode="" d="M1254 899q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69 t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237 q104 139 172.5 292.5t82.5 226.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" />
-<glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-<glyph unicode="" horiz-adv-x="1792" />
-</font>
-</defs></svg>
\ No newline at end of file
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf
deleted file mode 100644
index e89738de5..000000000
Binary files a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf and /dev/null differ
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff
deleted file mode 100644
index 8c1748aab..000000000
Binary files a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff and /dev/null differ
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/js/theme.js b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/js/theme.js
deleted file mode 100644
index 60520cc3a..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/static/js/theme.js
+++ /dev/null
@@ -1,47 +0,0 @@
-$( document ).ready(function() {
- // Shift nav in mobile when clicking the menu.
- $(document).on('click', "[data-toggle='wy-nav-top']", function() {
- $("[data-toggle='wy-nav-shift']").toggleClass("shift");
- $("[data-toggle='rst-versions']").toggleClass("shift");
- });
- // Close menu when you click a link.
- $(document).on('click', ".wy-menu-vertical .current ul li a", function() {
- $("[data-toggle='wy-nav-shift']").removeClass("shift");
- $("[data-toggle='rst-versions']").toggleClass("shift");
- });
- $(document).on('click', "[data-toggle='rst-current-version']", function() {
- $("[data-toggle='rst-versions']").toggleClass("shift-up");
- });
- // Make tables responsive
- $("table.docutils:not(.field-list)").wrap("<div class='wy-table-responsive'></div>");
-});
-
-window.SphinxRtdTheme = (function (jquery) {
- var stickyNav = (function () {
- var navBar,
- win,
- stickyNavCssClass = 'stickynav',
- applyStickNav = function () {
- if (navBar.height() <= win.height()) {
- navBar.addClass(stickyNavCssClass);
- } else {
- navBar.removeClass(stickyNavCssClass);
- }
- },
- enable = function () {
- applyStickNav();
- win.on('resize', applyStickNav);
- },
- init = function () {
- navBar = jquery('nav.wy-nav-side:first');
- win = jquery(window);
- };
- jquery(init);
- return {
- enable : enable
- };
- }());
- return {
- StickyNav : stickyNav
- };
-}($));
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/theme.conf b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/theme.conf
deleted file mode 100644
index dcfbf8c22..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/theme.conf
+++ /dev/null
@@ -1,8 +0,0 @@
-[theme]
-inherit = basic
-stylesheet = css/theme.css
-
-[options]
-typekit_id = hiw1hhg
-analytics_id =
-sticky_navigation = False
diff --git a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/versions.html b/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/versions.html
deleted file mode 100644
index 8b3eb79d2..000000000
--- a/doc/source/ext/sphinx_rtd_theme/sphinx_rtd_theme/versions.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{% if READTHEDOCS %}
-{# Add rst-badge after rst-versions for small badge style. #}
- <div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
- <span class="rst-current-version" data-toggle="rst-current-version">
- <span class="fa fa-book"> Read the Docs</span>
- v: {{ current_version }}
- <span class="fa fa-caret-down"></span>
- </span>
- <div class="rst-other-versions">
- <dl>
- <dt>Versions</dt>
- {% for slug, url in versions %}
- <dd><a href="{{ url }}">{{ slug }}</a></dd>
- {% endfor %}
- </dl>
- <dl>
- <dt>Downloads</dt>
- {% for type, url in downloads %}
- <dd><a href="{{ url }}">{{ type }}</a></dd>
- {% endfor %}
- </dl>
- <dl>
- <dt>On Read the Docs</dt>
- <dd>
- <a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">Project Home</a>
- </dd>
- <dd>
- <a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">Builds</a>
- </dd>
- </dl>
- <hr/>
- Free document hosting provided by <a href="http://www.readthedocs.org">Read the Docs</a>.
-
- </div>
- </div>
-{% endif %}
-
diff --git a/doc/source/faq.rst b/doc/source/faq.rst
new file mode 100644
index 000000000..6527cf3c2
--- /dev/null
+++ b/doc/source/faq.rst
@@ -0,0 +1,5 @@
+FAQ
+===
+
+python-for-android has an `online FAQ <https://github.com/kivy/python-for-android/blob/master/FAQ.md>`_. It contains
+the answers to questions that repeatedly come up.
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 991fc8007..929fa384c 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -1,23 +1,48 @@
python-for-android
==================
-python-for-android is an open source build tool to let you package
-Python code into standalone android APKs that can be passed around,
-installed, or uploaded to marketplaces such as the Play Store just
-like any other Android app. This tool was originally developed for the
-`Kivy cross-platform graphical framework <http://kivy.org/#home>`_,
-but now supports multiple bootstraps and can be easily extended to
-package other types of Python app for Android.
-
-python-for-android supports two major operations; first, it can
-compile the Python interpreter, its dependencies, backend libraries
-and python code for Android devices. This stage is fully customisable,
-you can install as many or few components as you like. The result is
-a standalone Android project which can be used to generate any number
-of different APKs, even with different names, icons, Python code etc.
-The second function of python-for-android is to provide a simple
-interface to these distributions, to generate from such a project a
-Python APK with build parameters and Python code to taste.
+python-for-android (p4a) is a development tool that packages Python apps into
+binaries that can run on Android devices.
+
+It can generate:
+
+* `Android Package <https://en.wikipedia.org/wiki/Apk_(file_format)>`_ (APK)
+ files, ready to install locally on a device, especially for testing. This format
+ is used by many `app stores <https://en.wikipedia.org/wiki/List_of_Android_app_stores>`_
+ but not `Google Play Store <https://play.google.com/store/>`_.
+* `Android App Bundle <https://developer.android.com/guide/app-bundle/faq>`_
+ (AAB) files which can be shared on `Google Play Store <https://play.google.com/store/>`_.
+* `Android Archive <https://developer.android.com/studio/projects/android-library>`_
+ (AAR) files which can be used as a re-usable bundle of resources for other
+ projects.
+
+It supports multiple CPU architectures.
+
+It supports apps developed with `Kivy framework <http://kivy.org>`_, but was
+built to be flexible about the backend libraries (through "bootstraps"), and
+also supports `PySDL2 <https://pypi.org/project/PySDL2/>`_, and a
+`WebView <https://developer.android.com/reference/android/webkit/WebView>`_ with
+a Python web server.
+
+It automatically supports dependencies on most pure Python packages. For other
+packages, including those that depend on C code, a special "recipe" must be
+written to support cross-compiling. python-for-android comes with recipes for
+many of the mosty popular libraries (e.g. numpy and sqlalchemy) built in.
+
+python-for-android works by cross-compiling the Python interpreter and its
+dependencies for Android devices, and bundling it with the app's python code
+and dependencies. The Python code is then interpreted on the Android device.
+
+It is recommended that python-for-android be used via
+`Buildozer <https://buildozer.readthedocs.io/>`_, which ensures the correct
+dependencies are pre-installed, and centralizes the configuration. However,
+python-for-android is not limited to being used with Buildozer.
+
+Buildozer is released and distributed under the terms of the MIT license. You
+should have received a
+copy of the MIT license alongside your distribution. Our
+`latest license <https://github.com/kivy/python-for-android/blob/master/LICENSE>`_
+is also available.
Contents
@@ -29,15 +54,17 @@ Contents
quickstart
buildoptions
commands
+ apis
distutils
recipes
bootstraps
services
- apis
troubleshooting
- launcher
+ docker
+ testing_pull_requests
+ faq
contribute
- old_toolchain/index.rst
+ contact
Indices and tables
diff --git a/doc/source/launcher.rst b/doc/source/launcher.rst
deleted file mode 100644
index 6d42b8c74..000000000
--- a/doc/source/launcher.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-.. _launcher:
-
-Launcher
-========
-
-The Kivy Launcher is an Android application that can run any Kivy app
-stored in `kivy` folder on SD Card. You can download the latest stable
-version for your android device from the
-`Play Store <https://play.google.com/store/apps/details?id=org.kivy.pygame>`_.
-
-The stable launcher comes with various Python packages and
-permissions, usually listed in the description in the store. Those
-aren't always enough for an application to run or even launch if you
-work with other dependencies that are not packaged.
-
-The Kivy Launcher is intended for quick and simple testing, for
-anything more advanced we recommend building your own APK with
-python-for-android.
-
-Building
---------
-
-The Kivy Launcher is built using python-for-android. To get the most recent
-versions of packages you need to clean them first, so that the packager won't
-grab an old (cached) package instead of fresh one.
-
-.. highlight:: none
-
-::
-
- p4a clean_download_cache requirements
- p4a clean_dists && p4a clean_builds
- p4a apk --requirements=requirements \
- --permission PERMISSION \
- --package=the.package.name \
- --name="App name" \
- --version=x.y.z \
- --android_api XY \
- --bootstrap=pygame or sdl2 \
- --launcher \
- --minsdk 13
-
-.. note::
-
- `--minsdk 13` is necessary for the new toolchain, otherwise you'll be able
- to run apps only in `landscape` orientation.
-
-.. warning::
-
- Do not use any of `--private`, `--public`, `--dir` or other arguments for
- adding `main.py` or `main.pyo` to the app. The argument `--launcher` is
- above them and tells the p4a to build the launcher version of the APK.
-
-Usage
------
-
-Once the launcher is installed, you need to create a folder in your
-external storage directory (e.g. ``/storage/emulated/0`` or
-``/sdcard``) - this is normally your 'home' directory in a file
-browser. Each new folder inside `kivy` represents a
-separate application::
-
- /sdcard/kivy/<yourapplication>
-
-Each application folder must contain an
-`android.txt` file. The file has to contain three basic
-lines::
-
- title=<Application Title>
- author=<Your Name>
- orientation=<portrait|landscape>
-
-The file is editable so you can change for example orientation or
-name. These are the only options dynamically configurable here,
-although when the app runs you can call the Android API with PyJNIus
-to change other settings.
-
-After you set your `android.txt` file, you can now run the launcher
-and start any available app from the list.
-
-To differentiate between apps in ``/sdcard/kivy`` you can include an icon
-named ``icon.png`` to the folder. The icon should be a square.
-
-Release on the market
----------------------
-
-Launcher is released on Google Play with each new Kivy stable
-branch. The master branch is not suitable for a regular user because
-it changes quickly and needs testing.
-
-Source code
------------
-
-.. |renpy| replace:: pygame org.renpy.android
-.. |kivy| replace:: sdl2 org.kivy.android
-
-.. _renpy:
- https://github.com/kivy/python-for-android/tree/master/\
- pythonforandroid/bootstraps/pygame/build/src/org/renpy/android
-.. _sdl2:
- https://github.com/kivy/python-for-android/tree/master/\
- pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android
-
-If you feel confident, feel free to improve the launcher. You can find the
-source code at |renpy|_ or at |kivy|_.
diff --git a/doc/source/old_toolchain/Screenshot_Kivy_Kompass.png b/doc/source/old_toolchain/Screenshot_Kivy_Kompass.png
deleted file mode 100644
index 828ce4195..000000000
Binary files a/doc/source/old_toolchain/Screenshot_Kivy_Kompass.png and /dev/null differ
diff --git a/doc/source/old_toolchain/android.rst b/doc/source/old_toolchain/android.rst
deleted file mode 100644
index 5d898749a..000000000
--- a/doc/source/old_toolchain/android.rst
+++ /dev/null
@@ -1,369 +0,0 @@
-Python API
-==========
-
-The Python for Android project includes a Python module called
-``android`` which consists of multiple parts that are mostly there to
-facilitate the use of the Java API.
-
-This module is not designed to be comprehensive. Most of the Java API
-is also accessible with PyJNIus, so if you can't find what you need
-here you can try using the Java API directly instead.
-
-
-Android (``android``)
----------------------
-
-.. module:: android
-
-.. function:: check_pause()
-
- This should be called on a regular basis to check to see if Android
- expects the application to pause. If it returns true, the app should call
- :func:`android.wait_for_resume()`, after storing its state as necessary.
-
-.. function:: wait_for_resume()
-
- This function should be called after :func:`android.check_pause()` and returns
- true. It does not return until Android has resumed from the pause. While in
- this function, Android may kill the app without further notice.
-
-.. function:: map_key(keycode, keysym)
-
- This maps an android keycode to a python keysym. The android
- keycodes are available as constants in the android module.
-
-
-Activity (``android.activity``)
--------------------------------
-
-.. module:: android.activity
-
-The default PythonActivity has a observer pattern for `onActivityResult <http://developer.android.com/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent)>`_ and `onNewIntent <http://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)>`_.
-
-.. function:: bind(eventname=callback, ...)
-
- This allows you to bind a callback to an Android event:
- - ``on_new_intent`` is the event associated to the onNewIntent java call
- - ``on_activity_result`` is the event associated to the onActivityResult java call
-
- .. warning::
-
- This method is not thread-safe. Call it in the mainthread of your app. (tips: use kivy.clock.mainthread decorator)
-
-.. function:: unbind(eventname=callback, ...)
-
- Unregister a previously registered callback with :func:`bind`.
-
-Example::
-
- # This example is a snippet from an NFC p2p app implemented with Kivy.
-
- from android import activity
-
- def on_new_intent(self, intent):
- if intent.getAction() != NfcAdapter.ACTION_NDEF_DISCOVERED:
- return
- rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
- if not rawmsgs:
- return
- for message in rawmsgs:
- message = cast(NdefMessage, message)
- payload = message.getRecords()[0].getPayload()
- print 'payload: {}'.format(''.join(map(chr, payload)))
-
- def nfc_enable(self):
- activity.bind(on_new_intent=self.on_new_intent)
- # ...
-
- def nfc_disable(self):
- activity.unbind(on_new_intent=self.on_new_intent)
- # ...
-
-
-Billing (``android.billing``)
------------------------------
-
-.. module:: android.billing
-
-This billing module gives an access to the `In-App Billing <http://developer.android.com/guide/google/play/billing/billing_overview.html>`_:
-
-#. `Setup a test account <http://developer.android.com/guide/google/play/billing/billing_admin.html#billing-testing-setup>`_, and get your Public Key
-#. Export your public key::
-
- export BILLING_PUBKEY="Your public key here"
-
-#. `Setup some In-App product <http://developer.android.com/guide/google/play/billing/billing_admin.html>`_ to buy. Let's say you've created a product with the id "org.kivy.gopremium"
-
-#. In your application, you can use the ``billing`` module like this::
-
-
- from android.billing import BillingService
- from kivy.clock import Clock
-
- class MyBillingService(object):
-
- def __init__(self):
- super(MyBillingService, self).__init__()
-
- # Start the billing service, and attach our callback
- self.service = BillingService(billing_callback)
-
- # Start a clock to check billing service message every second
- Clock.schedule_interval(self.service.check, 1)
-
- def billing_callback(self, action, *largs):
- '''Callback that will receive all the events from the Billing service
- '''
- if action == BillingService.BILLING_ACTION_ITEMSCHANGED:
- items = largs[0]
- if 'org.kivy.gopremium' in items:
- print "Congratulations, you have a premium acess"
- else:
- print "Unfortunately, you don't have premium access"
-
- def buy(self, sku):
- # Method to buy something.
- self.service.buy(sku)
-
- def get_purchased_items(self):
- # Return all the items purchased
- return self.service.get_purchased_items()
-
-#. To initiate an in-app purchase, just call the ``buy()`` method::
-
- # Note: start the service at the start, and never twice!
- bs = MyBillingService()
- bs.buy('org.kivy.gopremium')
-
- # Later, when you get the notification that items have been changed, you
- # can still check all the items you bought:
- print bs.get_purchased_items()
- {'org.kivy.gopremium': {'qt: 1}}
-
-#. You'll receive all the notifications about the billing process in the callback.
-
-#. Last step, create your application with ``--with-billing $BILLING_PUBKEY``::
-
- ./build.py ... --with-billing $BILLING_PUBKEY
-
-
-Broadcast (``android.broadcast``)
----------------------------------
-
-.. module:: android.broadcast
-
-Implementation of the android `BroadcastReceiver
-<http://developer.android.com/reference/android/content/BroadcastReceiver.html>`_.
-You can specify the callback that will receive the broadcast event, and actions
-or categories filters.
-
-.. class:: BroadcastReceiver
-
- .. warning::
-
- The callback will be called in another thread than the main thread. In
- that thread, be careful not to access OpenGL or something like that.
-
- .. method:: __init__(callback, actions=None, categories=None)
-
- :param callback: function or method that will receive the event. Will
- receive the context and intent as argument.
- :param actions: list of strings that represent an action.
- :param categories: list of strings that represent a category.
-
- For actions and categories, the string must be in lower case, without the prefix::
-
- # In java: Intent.ACTION_HEADSET_PLUG
- # In python: 'headset_plug'
-
- .. method:: start()
-
- Register the receiver with all the actions and categories, and start
- handling events.
-
- .. method:: stop()
-
- Unregister the receiver with all the actions and categories, and stop
- handling events.
-
-Example::
-
- class TestApp(App):
-
- def build(self):
- self.br = BroadcastReceiver(
- self.on_broadcast, actions=['headset_plug'])
- self.br.start()
- # ...
-
- def on_broadcast(self, context, intent):
- extras = intent.getExtras()
- headset_state = bool(extras.get('state'))
- if headset_state:
- print 'The headset is plugged'
- else:
- print 'The headset is unplugged'
-
- # Don't forget to stop and restart the receiver when the app is going
- # to pause / resume mode
-
- def on_pause(self):
- self.br.stop()
- return True
-
- def on_resume(self):
- self.br.start()
-
-
-Mixer (``android.mixer``)
--------------------------
-
-.. module:: android.mixer
-
-The `android.mixer` module contains a subset of the functionality in found
-in the `pygame.mixer <http://www.pygame.org/docs/ref/mixer.html>`_ module. It's
-intended to be imported as an alternative to pygame.mixer, using code like: ::
-
- try:
- import pygame.mixer as mixer
- except ImportError:
- import android.mixer as mixer
-
-Note that if you're using the `kivy.core.audio
-<http://kivy.org/docs/api-kivy.core.audio.html>`_ module, you don't have to do
-anything, it is all automatic.
-
-The `android.mixer` module is a wrapper around the Android MediaPlayer
-class. This allows it to take advantage of any hardware acceleration
-present, and also eliminates the need to ship codecs as part of an
-application.
-
-It has several differences with the pygame mixer:
-
-* The init() and pre_init() methods work, but are ignored - Android chooses
- appropriate settings automatically.
-
-* Only filenames and true file objects can be used - file-like objects
- will probably not work.
-
-* Fadeout does not work - it causes a stop to occur.
-
-* Looping is all or nothing, there is no way to choose the number of
- loops that occur. For looping to work, the
- :func:`android.mixer.periodic` function should be called on a
- regular basis.
-
-* Volume control is ignored.
-
-* End events are not implemented.
-
-* The mixer.music object is a class (with static methods on it),
- rather than a module. Calling methods like :func:`mixer.music.play`
- should work.
-
-
-Runnable (``android.runnable``)
--------------------------------
-
-.. module:: android.runnable
-
-:class:`Runnable` is a wrapper around the Java `Runnable
-<http://developer.android.com/reference/java/lang/Runnable.html>`_ class. This
-class can be used to schedule a call of a Python function into the
-`PythonActivity` thread.
-
-Example::
-
- from android.runnable import Runnable
-
- def helloworld(arg):
- print 'Called from PythonActivity with arg:', arg
-
- Runnable(helloworld)('hello')
-
-Or use our decorator::
-
- from android.runnable import run_on_ui_thread
-
- @run_on_ui_thread
- def helloworld(arg):
- print 'Called from PythonActivity with arg:', arg
-
- helloworld('arg1')
-
-
-This can be used to prevent errors like:
-
- - W/System.err( 9514): java.lang.RuntimeException: Can't create handler
- inside thread that has not called Looper.prepare()
- - NullPointerException in ActivityThread.currentActivityThread()
-
-.. warning::
-
- Because the python function is called from the PythonActivity thread, you
- need to be careful about your own calls.
-
-
-
-Service (``android.service``)
------------------------------
-
-Services of an application are controlled through the class :class:`AndroidService`.
-
-.. module:: android.service
-
-.. class:: AndroidService(title, description)
-
- Run ``service/main.py`` from the application directory as a service.
-
- :param title: Notification title, default to 'Python service'
- :param description: Notification text, default to 'Kivy Python service started'
- :type title: str
- :type description: str
-
- .. method:: start(arg)
-
- Start the service.
-
- :param arg: Argument to pass to a service, through the environment variable
- ``PYTHON_SERVICE_ARGUMENT``. Defaults to ''
- :type arg: str
-
- .. method:: stop()
-
- Stop the service.
-
-Application activity part example, ``main.py``:
-
-.. code-block:: python
-
- from android import AndroidService
-
- ...
-
- class ServiceExample(App):
-
- ...
-
- def start_service(self):
- self.service = AndroidService('Sevice example', 'service is running')
- self.service.start('Hello From Service')
-
- def stop_service(self):
- self.service.stop()
-
-Application service part example, ``service/main.py``:
-
-.. code-block:: python
-
- import os
- import time
-
- # get the argument passed
- arg = os.getenv('PYTHON_SERVICE_ARGUMENT')
-
- while True:
- # this will print 'Hello From Service' continually, even when the application is switched
- print arg
- time.sleep(1)
-
diff --git a/doc/source/old_toolchain/conf.py b/doc/source/old_toolchain/conf.py
deleted file mode 100644
index 02498acb2..000000000
--- a/doc/source/old_toolchain/conf.py
+++ /dev/null
@@ -1,242 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Python for Android documentation build configuration file, created by
-# sphinx-quickstart on Wed Jan 11 02:31:33 2012.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = []
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Python for Android'
-copyright = u'2012/2013, Kivy organisation'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '1.2'
-# The full version, including alpha/beta/rc tags.
-release = '1.2'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = []
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'PythonForAndroiddoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'PythonForAndroid.tex', u'Python for Android Documentation',
- u'Mathieu Virbel', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'pythonforandroid', u'Python for Android Documentation',
- [u'Mathieu Virbel'], 1)
-]
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
-# -- Options for Texinfo output ------------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-# dir menu entry, description, category)
-texinfo_documents = [
- ('index', 'PythonForAndroid', u'Python for Android Documentation',
- u'Mathieu Virbel', 'PythonForAndroid', 'One line description of project.',
- 'Miscellaneous'),
-]
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
diff --git a/doc/source/old_toolchain/contribute.rst b/doc/source/old_toolchain/contribute.rst
deleted file mode 100644
index dcc0fbe7c..000000000
--- a/doc/source/old_toolchain/contribute.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-Contribute
-==========
-
-Extending Python for android native support
--------------------------------------------
-
-So, you want to get into python-for-android and extend what's available
-to Python on Android ?
-
-Turns out it's not very complicated, here is a little introduction on how to go
-about it. Without Pyjnius, the schema to access the Java API from Cython is::
-
- [1] Cython -> [2] C JNI -> [3] Java
-
-Think about acceleration sensors: you want to get the acceleration
-values in Python, but nothing is available natively. Lukcily you have
-a Java API for that : the Google API is available here
-http://developer.android.com/reference/android/hardware/Sensor.html
-
-You can't use it directly, you need to do your own API to use it in python,
-this is done in 3 steps
-
-Step 1: write the java code to create very simple functions to use
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-like : accelerometer Enable/Reading
-In our project, this is done in the Hardware.java:
-https://github.com/kivy/python-for-android/blob/master/src/src/org/renpy/android/Hardware.java
-you can see how it's implemented
-
-Step 2 : write a jni wrapper
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This is a C file to be able to invoke/call Java functions from C, in our case,
-step 2 (and 3) are done in the android python module. The JNI part is done in
-the android_jni.c:
-https://github.com/kivy/python-for-android/blob/master/recipes/android/src/android_jni.c
-
-Step 3 : you have the java part, that you can call from the C
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can now do the Python extension around it, all the android python part is
-done in
-https://github.com/kivy/python-for-android/blob/master/recipes/android/src/android.pyx
-
-→ [python] android.accelerometer_reading ⇒ [C] android_accelerometer_reading
-⇒ [Java] Hardware.accelerometer_reading()
-
-The jni part is really a C api to call java methods. a little bit hard to get
-it with the syntax, but working with current example should be ok
-
-Example with bluetooth
-~~~~~~~~~~~~~~~~~~~~~~
-Start directly from a fork of https://github.com/kivy/python-for-android
-
-The first step is to identify where and how they are doing it in sl4a, it's
-really easy, because everything is already done as a client/server
-client/consumer approach, for bluetooth, they have a "Bluetooth facade" in
-java.
-
-http://code.google.com/p/android-scripting/source/browse/android/BluetoothFacade/src/com/googlecode/android_scripting/facade/BluetoothFacade.java
-
-You can learn from it, and see how is it's can be used as is, or if you can
-simplify / remove stuff you don't want.
-
-From this point, create a bluetooth file in
-python-for-android/tree/master/src/src/org/renpy/android in Java.
-
-Do a good API (enough simple to be able to write the jni in a very easy manner,
-like, don't pass any custom java object in argument).
-
-Then write the JNI, and then the python part.
-
-3 steps, once you get it, the real difficult part is to write the java part :)
-
-Jni gottchas
-~~~~~~~~~~~~
-
-- package must be org.renpy.android, don't change it.
-
-
-Create your own recipes
------------------------
-
-A recipe is a script that contains the "definition" of a module to compile.
-The directory layout of a recipe for a <modulename> is something like::
-
- python-for-android/recipes/<modulename>/recipe.sh
- python-for-android/recipes/<modulename>/patches/
- python-for-android/recipes/<modulename>/patches/fix-path.patch
-
-When building, all the recipe builds must go to::
-
- python-for-android/build/<modulename>/<archiveroot>
-
-For example, if you want to create a recipe for sdl, do::
-
- cd python-for-android/recipes
- mkdir sdl
- cp recipe.sh.tmpl sdl/recipe.sh
- sed -i 's#XXX#sdl#' sdl/recipe.sh
-
-Then, edit the sdl/recipe.sh to adjust other information (version, url) and
-complete the build function.
-
diff --git a/doc/source/old_toolchain/customize.rst b/doc/source/old_toolchain/customize.rst
deleted file mode 100644
index 5b9d954d2..000000000
--- a/doc/source/old_toolchain/customize.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-Customize your distribution
----------------------------
-
-The basic layout of a distribution is::
-
- AndroidManifest.xml - (*) android manifest (generated from templates)
- assets/
- private.mp3 - (*) fake package that will contain all the python installation
- public.mp3 - (*) fake package that will contain your application
- bin/ - contain all the apk generated from build.py
- blacklist.txt - list of file patterns to not include in the APK
- buildlib/ - internals libraries for build.py
- build.py - build script to use for packaging your application
- build.xml - (*) build settings (generated from templates)
- default.properties - settings generated from your distribute.sh
- libs/ - contain all the compiled libraries
- local.properties - settings generated from your distribute.sh
- private/ - private directory containing all the python files
- lib/ this is where you can remove or add python libs.
- python2.7/ by default, some modules are already removed (tests, idlelib, ...)
- project.properties - settings generated from your distribute.sh
- python-install/ - the whole python installation, generated from distribute.sh
- not included in the final package.
- res/ - (*) android resource (generated from build.py)
- src/ - Java bootstrap
- templates/ - Templates used by build.py
-
- (*): Theses files are automatically generated from build.py, don't change them directly !
-
-
diff --git a/doc/source/old_toolchain/example_compass.rst b/doc/source/old_toolchain/example_compass.rst
deleted file mode 100644
index bff430eaf..000000000
--- a/doc/source/old_toolchain/example_compass.rst
+++ /dev/null
@@ -1,61 +0,0 @@
-Compass
--------
-
-The following example is an extract from the Compass app as provided in the Kivy
-`examples/android/compass <https://github.com/kivy/kivy/tree/master/examples/android/compass/>`__
-folder:
-
-.. code-block:: python
-
- # ... imports
- Hardware = autoclass('org.renpy.android.Hardware')
-
- class CompassApp(App):
-
- needle_angle = NumericProperty(0)
-
- def build(self):
- self._anim = None
- Hardware.magneticFieldSensorEnable(True)
- Clock.schedule_interval(self.update_compass, 1 / 10.)
-
- def update_compass(self, *args):
- # read the magnetic sensor from the Hardware class
- (x, y, z) = Hardware.magneticFieldSensorReading()
-
- # calculate the angle
- needle_angle = Vector(x , y).angle((0, 1)) + 90.
-
- # animate the needle
- if self._anim:
- self._anim.stop(self)
- self._anim = Animation(needle_angle=needle_angle, d=.2, t='out_quad')
- self._anim.start(self)
-
- def on_pause(self):
- # when you are going on pause, don't forget to stop the sensor
- Hardware.magneticFieldSensorEnable(False)
- return True
-
- def on_resume(self):
- # reactivate the sensor when you are back to the app
- Hardware.magneticFieldSensorEnable(True)
-
- if __name__ == '__main__':
- CompassApp().run()
-
-
-If you compile this app, you will get an APK which outputs the following
-screen:
-
-.. figure:: Screenshot_Kivy_Kompass.png
- :width: 100%
- :scale: 60%
- :figwidth: 80%
- :alt: Screenshot Kivy Compass
-
- Screenshot of the Kivy Compass App
- (Source of the Compass Windrose: `Wikipedia <http://en.wikipedia.org/wiki/Compass_rose>`__)
-
-
-
diff --git a/doc/source/old_toolchain/example_helloworld.rst b/doc/source/old_toolchain/example_helloworld.rst
deleted file mode 100644
index dab760ad4..000000000
--- a/doc/source/old_toolchain/example_helloworld.rst
+++ /dev/null
@@ -1,96 +0,0 @@
-Hello world
------------
-
-If you don't know how to start with Python for Android, here is a simple
-tutorial for creating an UI using `Kivy <http://kivy.org/>`_, and make an APK
-with this project.
-
-.. note::
-
- Don't forget that Python for Android is not Kivy only, and you
- might want to use other toolkit libraries. When other toolkits
- will be available, this documentation will be enhanced.
-
-Let's create a simple Hello world application, with one Label and one Button.
-
-#. Ensure you've correctly installed and configured the project as said in the
- :doc:`prerequisites`
-
-#. Create a directory named ``helloworld``::
-
- mkdir helloworld
- cd helloworld
-
-#. Create a file named ``main.py``, with this content::
-
- import kivy
- kivy.require('1.0.9')
- from kivy.lang import Builder
- from kivy.uix.gridlayout import GridLayout
- from kivy.properties import NumericProperty
- from kivy.app import App
-
- Builder.load_string('''
- <HelloWorldScreen>:
- cols: 1
- Label:
- text: 'Welcome to the Hello world'
- Button:
- text: 'Click me! %d' % root.counter
- on_release: root.my_callback()
- ''')
-
- class HelloWorldScreen(GridLayout):
- counter = NumericProperty(0)
- def my_callback(self):
- print 'The button has been pushed'
- self.counter += 1
-
- class HelloWorldApp(App):
- def build(self):
- return HelloWorldScreen()
-
- if __name__ == '__main__':
- HelloWorldApp().run()
-
-#. Go to the ``python-for-android`` directory
-
-#. Create a distribution with kivy::
-
- ./distribute.sh -m kivy
-
-#. Go to the newly created ``default`` distribution::
-
- cd dist/default
-
-#. Plug your android device, and ensure you can install development
- application
-
-#. Build your hello world application in debug mode::
-
- ./build.py --package org.hello.world --name "Hello world" \
- --version 1.0 --dir /PATH/TO/helloworld debug installd
-
-#. Take your device, and start the application!
-
-#. If something goes wrong, open the logcat by doing::
-
- adb logcat
-
-The final debug APK will be located in ``bin/hello-world-1.0-debug.apk``.
-
-If you want to release your application instead of just making a debug APK, you must:
-
-#. Generate a non-signed APK::
-
- ./build.py --package org.hello.world --name "Hello world" \
- --version 1.0 --dir /PATH/TO/helloworld release
-
-#. Continue by reading http://developer.android.com/guide/publishing/app-signing.html
-
-
-.. seealso::
-
- `Kivy demos <https://github.com/kivy/kivy/tree/master/examples/demo>`_
- You can use them for creating APK too.
-
diff --git a/doc/source/old_toolchain/examples.rst b/doc/source/old_toolchain/examples.rst
deleted file mode 100644
index 4d7408fa0..000000000
--- a/doc/source/old_toolchain/examples.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-Examples
-========
-
-Prebuilt VirtualBox
--------------------
-
-A good starting point to build an APK are prebuilt VirtualBox images,
-where the Android NDK, the Android SDK, and the Kivy
-Python-For-Android sources are prebuilt in an VirtualBox image. Please
-search the `Download Section <http://kivy.org/#download>`__ for such
-an image. You will also need to create a device filter for the Android
-USB device using the VirtualBox OS settings.
-
-.. include:: example_helloworld.rst
-.. include:: example_compass.rst
-
-.. toctree::
- :hidden:
-
- example_helloworld
- example_compass
diff --git a/doc/source/old_toolchain/faq.rst b/doc/source/old_toolchain/faq.rst
deleted file mode 100644
index 22ffe1151..000000000
--- a/doc/source/old_toolchain/faq.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-FAQ
-===
-
-arm-linux-androideabi-gcc: Internal error: Killed (program cc1)
----------------------------------------------------------------
-
-This could happen if you are not using a validated SDK/NDK with Python for
-Android. Go to :doc:`prerequisites` to see which one are working.
-
-_sqlite3.so not found
----------------------
-
-We recently fixed sqlite3 compilation. In case of this error, you
-must:
-
-* Install development headers for sqlite3 if they are not already
- installed. On Ubuntu:
-
- apt-get install libsqlite3-dev
-
-* Compile the distribution with (sqlite3 must be the first argument)::
-
- ./distribute.sh -m 'sqlite3 kivy'
-
-* Go into your distribution at `dist/default`
-* Edit blacklist.txt, and remove all the lines concerning sqlite3::
-
- sqlite3/*
- lib-dynload/_sqlite3.so
-
-Then sqlite3 will be compiled and included in your APK.
-
-Too many levels of symbolic links
------------------------------------------------------
-
-Python for Android does not work within a virtual enviroment. The Python for
-Android directory must be outside of the virtual enviroment prior to running
-
- ./distribute.sh -m "kivy"
-
-or else you may encounter OSError: [Errno 40] Too many levels of symbolic links.
\ No newline at end of file
diff --git a/doc/source/old_toolchain/index.rst b/doc/source/old_toolchain/index.rst
deleted file mode 100644
index 1b873be65..000000000
--- a/doc/source/old_toolchain/index.rst
+++ /dev/null
@@ -1,38 +0,0 @@
-
-Old p4a toolchain doc
-=====================
-
-This is the documentation for the old python-for-android toolchain,
-using distribute.sh and build.py. This it entirely superseded by the
-new toolchain, you do not need to read it unless using this old
-method.
-
-.. warning:: The old toolchain is deprecated and no longer
- supported. You should instead use the :doc:`current version
- <../quickstart>`.
-
-Python for android is a project to create your own Python distribution
-including the modules you want, and create an apk including python, libs, and
-your application.
-
-- Forum: https://groups.google.com/forum/#!forum/python-android
-- Mailing list: python-android@googlegroups.com
-
-.. toctree::
- :maxdepth: 2
-
- toolchain.rst
- examples.rst
- android.rst
- javaapi.rst
- contribute.rst
- related.rst
- faq.rst
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
diff --git a/doc/source/old_toolchain/javaapi.rst b/doc/source/old_toolchain/javaapi.rst
deleted file mode 100644
index 73fc888f8..000000000
--- a/doc/source/old_toolchain/javaapi.rst
+++ /dev/null
@@ -1,239 +0,0 @@
-Java API (pyjnius)
-==================
-
-Using `PyJNIus <https://github.com/kivy/pyjnius>`__ to access the Android API
-restricts the usage to a simple call of the **autoclass** constructor function
-and a second call to instantiate this class.
-
-You can access through this method the entire Java Android API, e.g.,
-the ``DisplayMetrics`` of an Android device could be fetched using the
-following piece of code:
-
-.. code-block:: python
-
- DisplayMetrics = autoclass('android.util.DisplayMetrics')
- metrics = DisplayMetrics()
- metrics.setToDefaults()
- self.densityDpi = metrics.densityDpi
-
-You can access all fields and methods as described in the `Java
-Android DisplayMetrics API
-<http://developer.android.com/reference/android/util/DisplayMetrics.html>`__
-as shown here with the method `setToDefaults()` and the field
-`densityDpi`. Before you use a view field, you should always call
-`setToDefaults` to initiate to the default values of the device.
-
-Currently only JavaMethod, JavaStaticMethod, JavaField,
-JavaStaticField and JavaMultipleMethod are built into PyJNIus,
-therefore such constructs like registerListener or something like this
-must still be coded in Java. For this the Android module described
-below is available to access some of the hardware on Android devices.
-
-.. module:: org.renpy.android
-
-
-Activity
---------
-
-If you want the instance of the current Activity, use:
-
-- :data:`PythonActivity.mActivity` if you are running an application
-- :data:`PythonService.mService` if you are running a service
-
-.. class:: PythonActivity
-
- .. data:: mInfo
-
- Instance of an `ApplicationInfo
- <http://developer.android.com/reference/android/content/pm/ApplicationInfo.html>`_
-
- .. data:: mActivity
-
- Instance of :class:`PythonActivity`.
-
- .. method:: registerNewIntentListener(NewIntentListener listener)
-
- Register a new instance of :class:`NewIntentListener` to be called when
- `onNewIntent
- <http://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)>`_
- is called.
-
- .. method:: unregisterNewIntentListener(NewIntentListener listener)
-
- Unregister a previously registered listener from
- :meth:`registerNewIntentListener`
-
- .. method:: registerActivityResultListener(ActivityResultListener listener)
-
- Register a new instance of :class:`ActivityResultListener` to be called
- when `onActivityResult
- <http://developer.android.com/reference/android/app/Activity.html#onActivityResult(int,
- int, android.content.Intent)>`_ is called.
-
- .. method:: unregisterActivityResultListener(ActivityResultListener listener)
-
- Unregister a previously registered listener from
- :meth:`PythonActivity.registerActivityResultListener`
-
-.. class:: PythonActivity_ActivityResultListener
-
- .. note::
-
- This class is a subclass of PythonActivity, so the notation will be
- ``PythonActivity$ActivityResultListener``
-
- Listener interface for onActivityResult. You need to implementing it,
- create an instance and use it with :meth:`PythonActivity.registerActivityResultListener`.
-
- .. method:: onActivityResult(int requestCode, int resultCode, Intent data)
-
- Method to implement
-
-.. class:: PythonActivity_NewIntentListener
-
- .. note::
-
- This class is a subclass of PythonActivity, so the notation will be
- ``PythonActivity$NewIntentListener``
-
- Listener interface for onNewIntent. You need to implementing it, create
- an instance and use it with :meth:`registerNewIntentListener`.
-
- .. method:: onNewIntent(Intent intent)
-
- Method to implement
-
-
-Action
-------
-
-.. class:: Action
-
- This module is built to deliver data to someone else.
-
- .. method:: send(mimetype, filename, subject, text, chooser_title)
-
- Deliver data to someone else. This method is a wrapper around `ACTION_SEND
- <http://developer.android.com/reference/android/content/Intent.html#ACTION_SEND>`_
-
- :Parameters:
- `mimetype`: str
- Must be a valid mimetype, that represent the content to sent.
- `filename`: str, default to None
- (optional) Name of the file to attach. Must be a absolute path.
- `subject`: str, default to None
- (optional) Default subject
- `text`: str, default to None
- (optional) Content to send.
- `chooser_title`: str, default to None
- (optional) Title of the android chooser window, default to 'Send email...'
-
- Sending a simple hello world text::
-
- android.action_send('text/plain', text='Hello world',
- subject='Test from python')
-
- Sharing an image file::
-
- # let's say you've make an image in /sdcard/image.png
- android.action_send('image/png', filename='/sdcard/image.png')
-
- Sharing an image with a default text too::
-
- android.action_send('image/png', filename='/sdcard/image.png',
- text='Hi,\n\tThis is my awesome image, what do you think about it ?')
-
-
-Hardware
---------
-
-.. class:: Hardware
-
- This module is built for accessing hardware devices of an Android device.
- All the methods are static and public, you don't need an instance.
-
-
- .. method:: vibrate(s)
-
- Causes the phone to vibrate for `s` seconds. This requires that your
- application have the VIBRATE permission.
-
-
- .. method:: getHardwareSensors()
-
- Returns a string of all hardware sensors of an Android device where each
- line lists the informations about one sensor in the following format:
-
- Name=name,Vendor=vendor,Version=version,MaximumRange=maximumRange,MinDelay=minDelay,Power=power,Type=type
-
- For more information about this informations look into the original Java
- API for the `Sensors Class
- <http://developer.android.com/reference/android/hardware/Sensor.html>`__
-
- .. attribute:: accelerometerSensor
-
- This variable links to a generic3AxisSensor instance and their functions to
- access the accelerometer sensor
-
- .. attribute:: orientationSensor
-
- This variable links to a generic3AxisSensor instance and their functions to
- access the orientation sensor
-
- .. attribute:: magenticFieldSensor
-
-
- The following two instance methods of the generic3AxisSensor class should be
- used to enable/disable the sensor and to read the sensor
-
-
- .. method:: changeStatus(boolean enable)
-
- Changes the status of the sensor, the status of the sensor is enabled,
- if `enable` is true or disabled, if `enable` is false.
-
- .. method:: readSensor()
-
- Returns an (x, y, z) tuple of floats that gives the sensor reading, the
- units depend on the sensor as shown on the Java API page for
- `SensorEvent
- <http://developer.android.com/reference/android/hardware/SensorEvent.html>`_.
- The sesnor must be enabled before this function is called. If the tuple
- contains three zero values, the accelerometer is not enabled, not
- available, defective, has not returned a reading, or the device is in
- free-fall.
-
- .. method:: get_dpi()
-
- Returns the screen density in dots per inch.
-
- .. method:: show_keyboard()
-
- Shows the soft keyboard.
-
- .. method:: hide_keyboard()
-
- Hides the soft keyboard.
-
- .. method:: wifi_scanner_enable()
-
- Enables wifi scanning.
-
- .. note::
-
- ACCESS_WIFI_STATE and CHANGE_WIFI_STATE permissions are required.
-
- .. method:: wifi_scan()
-
- Returns a String for each visible WiFi access point
-
- (SSID, BSSID, SignalLevel)
-
-Further Modules
-~~~~~~~~~~~~~~~
-
-Some further modules are currently available but not yet documented. Please
-have a look into the code and you are very welcome to contribute to this
-documentation.
-
-
diff --git a/doc/source/old_toolchain/prerequisites.rst b/doc/source/old_toolchain/prerequisites.rst
deleted file mode 100644
index eb5cd6dcd..000000000
--- a/doc/source/old_toolchain/prerequisites.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-Prerequisites
--------------
-
-.. note:: There is a VirtualBox Image we provide with the
- prerequisites along with the Android SDK and NDK preinstalled to
- ease your installation woes. You can download it from `here
- <http://kivy.org/#download>`__.
-
-.. warning::
-
- The current version is tested only on Ubuntu oneiric (11.10) and
- precise (12.04). If it doesn't work on other platforms, send us a
- patch, not a bug report. Python for Android works on Linux and Mac
- OS X, not Windows.
-
-You need the minimal environment for building python. Note that other
-libraries might need other tools (cython is used by some recipes, and
-ccache to speedup the build)::
-
- sudo apt-get install build-essential patch git-core ccache ant python-pip python-dev
-
-If you are on a 64 bit distro, you should install these packages too ::
-
- sudo apt-get install ia32-libs libc6-dev-i386
-
-On debian Squeeze amd64, those packages were found to be necessary ::
-
- sudo apt-get install lib32stdc++6 lib32z1
-
-Ensure you have the latest Cython version::
-
- pip install --upgrade cython
-
-You must have android SDK and NDK. The SDK defines the Android
-functions you can use. The NDK is used for compilation. Right now,
-it's preferred to use:
-
-- SDK API 8 or 14 (15 will only work with a newly released NDK)
-- NDK r5b or r7
-
-You can download them at::
-
- http://developer.android.com/sdk/index.html
- http://developer.android.com/sdk/ndk/index.html
-
-
-In general, Python for Android currently works with Android 2.3 to L.
-
-If it's your very first time using the Android SDK, don't forget to
-follow the documentation for recommended components at::
-
- http://developer.android.com/sdk/installing/adding-packages.html
-
- You need to download at least one platform into your environment, so
- that you will be able to compile your application and set up an Android
- Virtual Device (AVD) to run it on (in the emulator). To start with,
- just download the latest version of the platform. Later, if you plan to
- publish your application, you will want to download other platforms as
- well, so that you can test your application on the full range of
- Android platform versions that your application supports.
-
-After installing them, export both installation paths, NDK version,
-and API to use::
-
- export ANDROIDSDK=/path/to/android-sdk
- export ANDROIDNDK=/path/to/android-ndk
- export ANDROIDNDKVER=rX
- export ANDROIDAPI=X
-
- # example
- export ANDROIDSDK="/home/tito/code/android/android-sdk-linux_86"
- export ANDROIDNDK="/home/tito/code/android/android-ndk-r7"
- export ANDROIDNDKVER=r7
- export ANDROIDAPI=14
-
-Also, you must configure your PATH to add the ``android`` binary::
-
- export PATH=$ANDROIDNDK:$ANDROIDSDK/platform-tools:$ANDROIDSDK/tools:$PATH
-
diff --git a/doc/source/old_toolchain/related.rst b/doc/source/old_toolchain/related.rst
deleted file mode 100644
index ea694f619..000000000
--- a/doc/source/old_toolchain/related.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Related projects
-================
-
-- PGS4A: http://pygame.renpy.org/ (thanks to Renpy to make it possible)
-- Android scripting: http://code.google.com/p/android-scripting/
-- Python on a chip: http://code.google.com/p/python-on-a-chip/
-
diff --git a/doc/source/old_toolchain/toolchain.rst b/doc/source/old_toolchain/toolchain.rst
deleted file mode 100644
index b301a5a9f..000000000
--- a/doc/source/old_toolchain/toolchain.rst
+++ /dev/null
@@ -1,66 +0,0 @@
-Toolchain
-=========
-
-Introduction
-------------
-
-In terms of comparaison, you can check how Python for android can be useful
-compared to other projects.
-
-+--------------------+---------------+---------------+----------------+--------------+
-| Project | Native Python | GUI libraries | APK generation | Custom build |
-+====================+===============+===============+================+==============+
-| Python for android | Yes | Yes | Yes | Yes |
-+--------------------+---------------+---------------+----------------+--------------+
-| PGS4A | Yes | Yes | Yes | No |
-+--------------------+---------------+---------------+----------------+--------------+
-| Android scripting | No | No | No | No |
-+--------------------+---------------+---------------+----------------+--------------+
-| Python on a chip | No | No | No | No |
-+--------------------+---------------+---------------+----------------+--------------+
-
-.. note::
-
- For the moment, we are shipping only one "java bootstrap" (needed for
- decompressing your packaged zip file project, create an OpenGL ES 2.0
- surface, handle touch input and manage an audio thread).
-
- If you want to use it without kivy module (an opengl es 2.0 ui toolkit),
- then you might want a lighter java bootstrap, that we don't have right now.
- Help is welcome :)
-
- So for the moment, Python for Android can only be used with the kivy GUI toolkit:
- http://kivy.org/#home
-
-
-How does it work ?
-------------------
-
-To be able to run Python on android, you need to compile it for android. And
-you need to compile all the libraries you want for android too.
-Since Python is a language, not a toolkit, you cannot draw any user interface
-with it: you need to use a toolkit for it. Kivy can be one of them.
-
-So for a simple ui project, the first step is to compile Python + Kivy + all
-others libraries. Then you'll have what we call a "distribution".
-A distribution is composed of:
-
-- Python
-- Python libraries
-- All selected libraries (kivy, pygame, pil...)
-- A java bootstrap
-- A build script
-
-You'll use the build script for create an "apk": an android package.
-
-
-.. include:: prerequisites.rst
-.. include:: usage.rst
-.. include:: customize.rst
-
-.. toctree::
- :hidden:
-
- prerequisites
- usage
- customize
diff --git a/doc/source/old_toolchain/usage.rst b/doc/source/old_toolchain/usage.rst
deleted file mode 100644
index 7d83b2388..000000000
--- a/doc/source/old_toolchain/usage.rst
+++ /dev/null
@@ -1,167 +0,0 @@
-Usage
------
-
-Step 1: compile the toolchain
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you want to compile the toolchain with only the kivy module::
-
- ./distribute.sh -m "kivy"
-
-.. warning::
- Do not run the above command from `within a virtual enviroment <../faq/#too-many-levels-of-symbolic-links>`_.
-
-After a long time, you'll get a "dist/default" directory containing
-all the compiled libraries and a build.py script to package your
-application using thoses libraries.
-
-You can include other modules (or "recipes") to compile using `-m`::
-
- ./distribute.sh -m "openssl kivy"
- ./distribute.sh -m "pil ffmpeg kivy"
-
-.. note::
-
- Recipes are instructions for compiling Python modules that require C extensions.
- The list of recipes we currently have is at:
- https://github.com/kivy/python-for-android/tree/master/recipes
-
-You can also specify a specific version for each package. Please note
-that the compilation might **break** if you don't use the default
-version. Most recipes have patches to fix Android issues, and might
-not apply if you specify a version. We also recommend to clean build
-before changing version.::
-
- ./distribute.sh -m "openssl kivy==master"
-
-Python modules that don't need C extensions don't need a recipe and
-can be included this way. From python-for-android 1.1 on, you can now
-specify pure-python package into the distribution. It will use
-virtualenv and pip to install pure-python modules into the
-distribution. Please note that the compiler is deactivated, and will
-break any module which tries to compile something. If compilation is
-needed, write a recipe::
-
- ./distribute.sh -m "requests pygments kivy"
-
-.. note::
-
- Recipes download a defined version of their needed package from the
- internet, and build from it. If you know what you are doing, and
- want to override that, you can export the env variable
- `P4A_recipe_name_DIR` and this directory will be copied and used
- instead.
-
-Available options to `distribute.sh`::
-
- -d directory Name of the distribution directory
- -h Show this help
- -l Show a list of available modules
- -m 'mod1 mod2' Modules to include
- -f Restart from scratch (remove the current build)
- -u 'mod1 mod2' Modules to update (if already compiled)
-
-Step 2: package your application
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Go to your custom Python distribution::
-
- cd dist/default
-
-Use the build.py for creating the APK::
-
- ./build.py --package org.test.touchtracer --name touchtracer \
- --version 1.0 --dir ~/code/kivy/examples/demo/touchtracer debug
-
-Then, the Android package (APK) will be generated at:
-
- bin/touchtracer-1.0-debug.apk
-
-.. warning::
-
- Some files and modules for python are blacklisted by default to
- save a few megabytes on the final APK file. In case your
- applications doesn't find a standard python module, check the
- src/blacklist.txt file, remove the module you need from the list,
- and try again.
-
-Available options to `build.py`::
-
- -h, --help show this help message and exit
- --package PACKAGE The name of the java package the project will be
- packaged under.
- --name NAME The human-readable name of the project.
- --version VERSION The version number of the project. This should consist
- of numbers and dots, and should have the same number
- of groups of numbers as previous versions.
- --numeric-version NUMERIC_VERSION
- The numeric version number of the project. If not
- given, this is automatically computed from the
- version.
- --dir DIR The directory containing public files for the project.
- --private PRIVATE The directory containing additional private files for
- the project.
- --launcher Provide this argument to build a multi-app launcher,
- rather than a single app.
- --icon-name ICON_NAME
- The name of the project's launcher icon.
- --orientation ORIENTATION
- The orientation that the game will display in. Usually
- one of "landscape", "portrait" or "sensor".
- --permission PERMISSIONS
- The permissions to give this app.
- --ignore-path IGNORE_PATH
- Ignore path when building the app
- --icon ICON A png file to use as the icon for the application.
- --presplash PRESPLASH
- A jpeg file to use as a screen while the application
- is loading.
- --install-location INSTALL_LOCATION
- The default install location. Should be "auto",
- "preferExternal" or "internalOnly".
- --compile-pyo Compile all .py files to .pyo, and only distribute the
- compiled bytecode.
- --intent-filters INTENT_FILTERS
- Add intent-filters xml rules to AndroidManifest.xml
- --blacklist BLACKLIST
- Use a blacklist file to match unwanted file in the
- final APK
- --sdk SDK_VERSION Android SDK version to use. Default to 8
- --minsdk MIN_SDK_VERSION
- Minimum Android SDK version to use. Default to 8
- --window Indicate if the application will be windowed
-
-Meta-data
----------
-
-.. versionadded:: 1.3
-
-You can extend the `AndroidManifest.xml` with application meta-data. If you are
-using external toolkits like Google Maps, you might want to set your API key in
-the meta-data. You could do it like this::
-
- ./build.py ... --meta-data com.google.android.maps.v2.API_KEY=YOURAPIKEY
-
-Some meta-data can be used to interact with the behavior of our internal
-component.
-
-.. list-table::
- :widths: 100 500
- :header-rows: 1
-
- * - Token
- - Description
- * - `surface.transparent`
- - If set to 1, the created surface will be transparent (can be used
- to add background Android widget in the background, or use accelerated
- widgets)
- * - `surface.depth`
- - Size of the depth component, default to 0. 0 means automatic, but you
- can force it to a specific value. Be warned, some old phone might not
- support the depth you want.
- * - `surface.stencil`
- - Size of the stencil component, default to 8.
- * - `android.background_color`
- - Color (32bits RGBA color), used for the background window. Usually, the
- background is covered by the OpenGL Background, unless
- `surface.transparent` is set.
diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst
index 129a1e14e..987a00cdd 100644
--- a/doc/source/quickstart.rst
+++ b/doc/source/quickstart.rst
@@ -9,27 +9,37 @@ for android as p4a in this documentation.
Concepts
--------
-- requirements: For p4a, your applications dependencies are
- requirements similar to the standard `requirements.txt`, but with
- one difference: p4a will search for a recipe first instead of
- installing requirements with pip.
+*Basic:*
-- recipe: A recipe is a file that defines how to compile a
- requirement. Any libraries that have a Python extension *must* have
- a recipe in p4a, or compilation will fail. If there is no recipe for
- a requirement, it will be downloaded using pip.
+- **requirements:** For p4a, all your app's dependencies must be specified
+ via ``--requirements`` similar to the standard `requirements.txt`.
+ (Unless you specify them via a `setup.py`/`install_requires`)
+ All dependencies will be mapped to "recipes" if any exist, so that
+ many common libraries will just work. See "recipe" below for details.
-- build: A build refers to a compiled recipe.
+- **distribution:** A distribution is the final "build" of your
+ compiled project + requirements, as an Android project assembled by
+ p4a that can be turned directly into an APK. p4a can contain multiple
+ distributions with different sets of requirements.
-- distribution: A distribution is the final "build" of all your
- compiled requirements, as an Android project that can be turned
- directly into an APK. p4a can contain multiple distributions with
- different sets of requirements.
+- **build:** A build refers to a compiled recipe or distribution.
-- bootstrap: A bootstrap is the app backend that will start your
- application. Your application could use SDL2 as a base, or Pygame,
- or a web backend like Flask with a WebView bootstrap. Different
- bootstraps can have different build options.
+- **bootstrap:** A bootstrap is the app backend that will start your
+ application. The default for graphical applications is SDL2.
+ You can also use e.g. the webview for web apps, or service_only/service_library for
+ background services, or qt for PySide6 apps. Different bootstraps have different additional
+ build options.
+
+*Advanced:*
+
+- **recipe:**
+ A recipe is a file telling p4a how to install a requirement
+ that isn't by default fully Android compatible.
+ This is often necessary for Cython or C/C++-using python extensions.
+ p4a has recipes for many common libraries already included, and any
+ dependency you specified will be automatically mapped to its recipe.
+ If a dependency doesn't work and has no recipe included in p4a,
+ then it may need one to work.
Installation
@@ -38,7 +48,7 @@ Installation
Installing p4a
~~~~~~~~~~~~~~
-p4a is now available on on Pypi, so you can install it using pip::
+p4a is now available on Pypi, so you can install it using pip::
pip install python-for-android
@@ -46,42 +56,56 @@ You can also test the master branch from Github using::
pip install git+https://github.com/kivy/python-for-android.git
-Installing Dependencies
+Installing Prerequisites
~~~~~~~~~~~~~~~~~~~~~~~
-p4a has several dependencies that must be installed:
-
-- git
-- ant
-- python2
-- cython (can be installed via pip)
-- a Java JDK (e.g. openjdk-7)
-- zlib (including 32 bit)
-- libncurses (including 32 bit)
-- unzip
-- virtualenv (can be installed via pip)
-- ccache (optional)
-- autoconf (for ffpyplayer_codecs recipe)
-- libtool (for ffpyplayer_codecs recipe)
-
-On recent versions of Ubuntu and its derivatives you may be able to
-install most of these with::
-
- sudo dpkg --add-architecture i386
- sudo apt-get update
- sudo apt-get install -y build-essential ccache git zlib1g-dev python2.7 python2.7-dev libncurses5:i386 libstdc++6:i386 zlib1g:i386 openjdk-7-jdk unzip ant ccache autoconf libtool
+p4a requires a few dependencies to be installed on your system to work
+properly. While we're working on a way to automate pre-requisites checks,
+suggestions and installation on all platforms (macOS is already supported),
+on Linux distros you'll need to install them manually.
-On Arch Linux (64 bit) you should be able to run the following to
-install most of the dependencies (note: this list may not be
-complete). gcc-multilib will conflict with (and replace) gcc if not
-already installed. If your installation is already 32-bit, install the
-same packages but without ``lib32-`` or ``-multilib``::
+On recent versions of Ubuntu and its derivatives you can easily install them via
+the following command (re-adapted from the `Dockerfile` we use to perform CI builds)::
+
+ sudo apt-get update
+ sudo apt-get install -y \
+ ant \
+ autoconf \
+ automake \
+ ccache \
+ cmake \
+ g++ \
+ gcc \
+ git \
+ lbzip2 \
+ libffi-dev \
+ libltdl-dev \
+ libtool \
+ libssl-dev \
+ make \
+ openjdk-17-jdk \
+ patch \
+ pkg-config \
+ python3 \
+ python3-dev \
+ python3-pip \
+ python3-venv \
+ sudo \
+ unzip \
+ wget \
+ zip
- sudo pacman -S jdk7-openjdk python2 python2-pip python2-kivy mesa-libgl lib32-mesa-libgl lib32-sdl2 lib32-sdl2_image lib32-sdl2_mixer sdl2_ttf unzip gcc-multilib gcc-libs-multilib
Installing Android SDK
~~~~~~~~~~~~~~~~~~~~~~
+.. warning::
+ python-for-android is often picky about the **SDK/NDK versions.**
+ Pick the recommended ones from below to avoid problems.
+
+Basic SDK install
+`````````````````
+
You need to download and unpack the Android SDK and NDK to a directory (let's say $HOME/Documents/):
- `Android SDK <https://developer.android.com/studio/index.html>`_
@@ -93,53 +117,81 @@ named ``tools``, and you will need to run extra commands to install
the SDK packages needed.
For Android NDK, note that modern releases will only work on a 64-bit
-operating system. If you are using a 32-bit distribution (or hardware),
-the latest useable NDK version is r10e, which can be downloaded here:
+operating system. **The minimal, and recommended, NDK version to use is r25b:**
+
+ - `Go to ndk downloads page <https://developer.android.com/ndk/downloads/>`_
+ - Windows users should create a virtual machine with an GNU Linux os
+ installed, and then you can follow the described instructions from within
+ your virtual machine.
+
-- `Legacy 32-bit Linux NDK r10e <http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86.bin>`_
+Platform and build tools
+````````````````````````
-First, install a platform to target (you can also replace ``19`` with
-a different platform number, this will be used again later)::
+First, install an API platform to target. **The recommended *target* API
+level is 27**, you can replace it with a different number but
+keep in mind other API versions are less well-tested and older devices
+are still supported down to the **recommended specified *minimum*
+API/NDK API level 21**::
+
+ $SDK_DIR/tools/bin/sdkmanager "platforms;android-27"
- $SDK_DIR/tools/bin/sdkmanager "platforms;android-19"
Second, install the build-tools. You can use
``$SDK_DIR/tools/bin/sdkmanager --list`` to see all the
-possibilities, but 26.0.2 is the latest version at the time of writing::
+possibilities, but 28.0.2 is the latest version at the time of writing::
+
+ $SDK_DIR/tools/bin/sdkmanager "build-tools;28.0.2"
- $SDK_DIR/tools/bin/sdkmanager "build-tools;26.0.2"
+Configure p4a to use your SDK/NDK
+`````````````````````````````````
-Then, you can edit your ``~/.bashrc`` or other favorite shell to include new environment variables necessary for building on android::
+Then, you can edit your ``~/.bashrc`` or other favorite shell to include new environment
+variables necessary for building on android::
# Adjust the paths!
- export ANDROIDSDK="$HOME/Documents/android-sdk-21"
- export ANDROIDNDK="$HOME/Documents/android-ndk-r10e"
- export ANDROIDAPI="19" # Minimum API version your application require
+ export ANDROIDSDK="$HOME/Documents/android-sdk-27"
+ export ANDROIDNDK="$HOME/Documents/android-ndk-r23b"
+ export ANDROIDAPI="27" # Target API version of your application
+ export NDKAPI="21" # Minimum supported API version of your application
export ANDROIDNDKVER="r10e" # Version of the NDK you installed
You have the possibility to configure on any command the PATH to the SDK, NDK and Android API using:
-- :code:`--sdk_dir PATH` as an equivalent of `$ANDROIDSDK`
-- :code:`--ndk_dir PATH` as an equivalent of `$ANDROIDNDK`
-- :code:`--android_api VERSION` as an equivalent of `$ANDROIDAPI`
-- :code:`--ndk_version PATH` as an equivalent of `$ANDROIDNDKVER`
+- :code:`--sdk-dir PATH` as an equivalent of `$ANDROIDSDK`
+- :code:`--ndk-dir PATH` as an equivalent of `$ANDROIDNDK`
+- :code:`--android-api VERSION` as an equivalent of `$ANDROIDAPI`
+- :code:`--ndk-api VERSION` as an equivalent of `$NDKAPI`
+- :code:`--ndk-version VERSION` as an equivalent of `$ANDROIDNDKVER`
Usage
-----
-Build a Kivy application
-~~~~~~~~~~~~~~~~~~~~~~~~
+Build a Kivy or SDL2 application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To build your application, you need to have a name, version, a package
-identifier, and explicitly write the bootstrap you want to use, as
-well as the requirements::
+To build your application, you need to specify name, version, a package
+identifier, the bootstrap you want to use (`sdl2` for kivy or sdl2 apps)
+and the requirements::
+
+ p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My application" --version 0.1 --bootstrap=sdl2 --requirements=python3,kivy
- p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My application" --version 0.1 --bootstrap=sdl2 --requirements=python2,kivy
+**Note on** ``--requirements``: **you must add all
+libraries/dependencies your app needs to run.**
+Example: ``--requirements=python3,kivy,vispy``. For an SDL2 app,
+`kivy` is not needed, but you need to add any wrappers you might
+use (e.g. `pysdl2`).
-This will first build a distribution that contains `python2` and `kivy`, and using a SDL2 bootstrap. Python2 is here explicitely written as kivy can work with python2 or python3.
+This `p4a apk ...` command builds a distribution with `python3`,
+`kivy`, and everything else you specified in the requirements.
+It will be packaged using a SDL2 bootstrap, and produce
+an `.apk` file.
+
+*Compatibility notes:*
+
+- Python 2 is no longer supported by python-for-android. The last release supporting Python 2 was v2019.10.06.
-You can also use ``--bootstrap=pygame``, but this bootstrap is deprecated for use with Kivy and SDL2 is preferred.
Build a WebView application
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -150,25 +202,44 @@ well as the requirements::
p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My WebView Application" --version 0.1 --bootstrap=webview --requirements=flask --port=5000
+**Please note as with kivy/SDL2, you need to specify all your
+additional requirements/dependencies.**
+
You can also replace flask with another web framework.
Replace ``--port=5000`` with the port on which your app will serve a
website. The default for Flask is 5000.
-Build an SDL2 based application
+
+Build a Service library archive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-This includes e.g. `PySDL2
-<https://pysdl2.readthedocs.io/en/latest/>`__.
+To build an android archive (.aar), containing an android service , you need a name, version, package identifier, explicitly use the
+service_library bootstrap, and declare service entry point (See :ref:`services <arbitrary_scripts_services>` for more options), as well as the requirements and arch(s)::
-To build your application, you need to have a name, version, a package
-identifier, and explicitly write the sdl2 bootstrap, as well as the
-requirements::
+ 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
+
+
+You can then call the generated Java entrypoint(s) for your Python service(s) in other apk build frameworks.
+
+
+Exporting the Android App Bundle (aab) for distributing it on Google Play
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My SDL2 application" --version 0.1 --bootstrap=sdl2 --requirements=your_requirements
+Starting from August 2021 for new apps and from November 2021 for updates to existings apps,
+Google Play Console will require the Android App Bundle instead of the long lived apk.
-Add your required modules in place of ``your_requirements``,
-e.g. ``--requirements=pysdl2`` or ``--requirements=vispy``.
+python-for-android handles by itself the needed work to accomplish the new requirements::
+
+ 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
+
+This `p4a aab ...` command builds a distribution with `python3`,
+`kivy`, and everything else you specified in the requirements.
+It will be packaged using a SDL2 bootstrap, and produce
+an `.aab` file that contains binaries for both `armeabi-v7a` and `arm64-v8a` ABIs.
+
+The Android App Bundle, is supposed to be used for distributing your app.
+If you need to test it locally, on your device, you can use `bundletool <https://developer.android.com/studio/command-line/bundletool>`
Other options
~~~~~~~~~~~~~
@@ -177,7 +248,7 @@ You can pass other command line arguments to control app behaviours
such as orientation, wakelock and app permissions. See
:ref:`bootstrap_build_options`.
-
+
Rebuild everything
~~~~~~~~~~~~~~~~~~
@@ -185,18 +256,18 @@ Rebuild everything
If anything goes wrong and you want to clean the downloads and builds to retry everything, run::
p4a clean_all
-
+
If you just want to clean the builds to avoid redownloading dependencies, run::
p4a clean_builds && p4a clean_dists
-
+
Getting help
~~~~~~~~~~~~
If something goes wrong and you don't know how to fix it, add the
``--debug`` option and post the output log to the `kivy-users Google
-group <https://groups.google.com/forum/#!forum/kivy-users>`__ or irc
-channel #kivy at irc.freenode.net .
+group <https://groups.google.com/forum/#!forum/kivy-users>`__ or the
+kivy `#support Discord channel <https://chat.kivy.org/>`_.
See :doc:`troubleshooting` for more information.
@@ -210,7 +281,7 @@ Recipe management
You can see the list of the available recipes with::
p4a recipes
-
+
If you are contributing to p4a and want to test a recipes again,
you need to clean the build and rebuild your distribution::
@@ -224,7 +295,6 @@ it (edit the ``__init__.py``)::
mkdir -p p4a-recipes/myrecipe
touch p4a-recipes/myrecipe/__init__.py
-
Distribution management
~~~~~~~~~~~~~~~~~~~~~~~
@@ -248,7 +318,7 @@ You can list the available distributions::
And clean all of them::
p4a clean_dists
-
+
Configuration file
~~~~~~~~~~~~~~~~~~
@@ -258,9 +328,28 @@ command line. For example, you can add the options you would always
include such as::
--dist_name my_example
- --android_api 19
+ --android_api 27
--requirements kivy,openssl
+Overriding recipes sources
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can override the source of any recipe using the
+``$P4A_recipename_DIR`` environment variable. For instance, to test
+your own Kivy branch you might set::
+
+ export P4A_kivy_DIR=/home/username/kivy
+
+The specified directory will be copied into python-for-android instead
+of downloading from the normal url specified in the recipe.
+
+setup.py file (experimental)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If your application is also packaged for desktop using `setup.py`,
+you may want to use your `setup.py` instead of the
+``--requirements`` option to avoid specifying things twice.
+For that purpose, check out :doc:`distutils`
Going further
~~~~~~~~~~~~~
@@ -273,5 +362,4 @@ See the other pages of this doc for more information on specific topics:
- :doc:`bootstraps`
- :doc:`apis`
- :doc:`troubleshooting`
-- :doc:`launcher`
- :doc:`contribute`
diff --git a/doc/source/recipes.rst b/doc/source/recipes.rst
index 98b5813d0..0a2a73659 100644
--- a/doc/source/recipes.rst
+++ b/doc/source/recipes.rst
@@ -12,9 +12,8 @@ to take care of compilation for any compiled components, as these must
be compiled for Android with the correct architecture.
python-for-android comes with many recipes for popular modules. No
-recipe is necessary to use of Python modules with no
-compiled components; these are installed automaticaly via pip.
-
+recipe is necessary for Python modules which have no
+compiled components; these are installed automatically via pip.
If you are new to building recipes, it is recommended that you first
read all of this page, at least up to the Recipe reference
documentation. The different recipe sections include a number of
@@ -42,7 +41,7 @@ The basic declaration of a recipe is as follows::
patches = ['some_fix.patch'] # Paths relative to the recipe dir
depends = ['kivy', 'sdl2'] # These are just examples
- conflicts = ['pygame']
+ conflicts = ['generickndkbuild']
recipe = YourRecipe()
@@ -62,21 +61,21 @@ when the recipe is imported.
The actual build process takes place via three core methods::
def prebuild_arch(self, arch):
- super(YourRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
# Do any pre-initialisation
def build_arch(self, arch):
- super(YourRecipe, self).build_arch(arch)
+ super().build_arch(arch)
# Do the main recipe build
def postbuild_arch(self, arch):
- super(YourRecipe, self).build_arch(arch)
+ super().build_arch(arch)
# Do any clearing up
These methods are always run in the listed order; prebuild, then
build, then postbuild.
-If you defined an url for your recipe, you do *not* need to manually
+If you defined a url for your recipe, you do *not* need to manually
download it, this is handled automatically.
The recipe will automatically be built in a special isolated build
@@ -87,9 +86,9 @@ context manager defined in toolchain.py::
from pythonforandroid.toolchain import current_directory
def build_arch(self, arch):
- super(YourRecipe, self).build_arch(arch)
+ super().build_arch(arch)
with current_directory(self.get_build_dir(arch.arch)):
- with open('example_file.txt', 'w'):
+ with open('example_file.txt', 'w') as fileh:
fileh.write('This is written to a file within the build dir')
The argument to each method, ``arch``, is an object relating to the
@@ -179,7 +178,7 @@ environment for any processes that you call. It is convenient to do
this using the ``sh`` module as follows::
def build_arch(self, arch):
- super(YourRecipe, self).build_arch(arch)
+ super().build_arch(arch)
env = self.get_recipe_env(arch)
sh.echo('$PATH', _env=env) # Will print the PATH entry from the
# env dict
@@ -192,12 +191,12 @@ its current status::
shprint(sh.echo, '$PATH', _env=env)
You can also override the ``get_recipe_env`` method to add new env
-vars for the use of your recipe. For instance, the Kivy recipe does
+vars for use in your recipe. For instance, the Kivy recipe does
the following when compiling for SDL2, in order to tell Kivy what
backend to use::
def get_recipe_env(self, arch):
- env = super(KivySDL2Recipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['USE_SDL2'] = '1'
env['KIVY_SDL2_PATH'] = ':'.join([
@@ -252,12 +251,12 @@ install`` with an appropriate environment.
For instance, the following is all that's necessary to create a recipe
for the Vispy module::
- from pythonforandroid.toolchain import PythonRecipe
+ from pythonforandroid.recipe import PythonRecipe
class VispyRecipe(PythonRecipe):
version = 'master'
url = 'https://github.com/vispy/vispy/archive/{version}.zip'
- depends = ['python2', 'numpy']
+ depends = ['python3', 'numpy']
site_packages_name = 'vispy'
@@ -273,7 +272,7 @@ Python installation.
For reference, the code that accomplishes this is the following::
def build_arch(self, arch):
- super(PythonRecipe, self).build_arch(arch)
+ super().build_arch(arch)
self.install_python_package()
def install_python_package(self):
@@ -307,14 +306,14 @@ the cython components and to install the Python module just like a
normal PythonRecipe.
For instance, the following is all that's necessary to make a recipe
-for Kivy (in this case, depending on Pygame rather than SDL2)::
+for Kivy::
class KivyRecipe(CythonRecipe):
- version = 'stable'
- url = 'https://github.com/kivy/kivy/archive/{version}.zip'
- name = 'kivy'
+ version = 'stable'
+ url = 'https://github.com/kivy/kivy/archive/{version}.zip'
+ name = 'kivy'
- depends = ['pygame', 'pyjnius', 'android']
+ depends = ['sdl2', 'pyjnius']
recipe = KivyRecipe()
@@ -350,12 +349,12 @@ For reference, the code that accomplishes this is the following::
shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
env['STRIP'], '{}', ';', _env=env)
-The failing build and manual cythonisation is necessary, first to
+The failing build and manual cythonisation is necessary, firstly to
make sure that any .pyx files have been generated by setup.py, and
-second because cython isn't installed in the hostpython build.
+secondly because cython isn't installed in the hostpython build.
This may actually fail if the setup.py tries to import cython before
-making any pyx files (in which case it crashes too early), although
+making any .pyx files (in which case it crashes too early), although
this is probably not usually an issue. If this happens to you, try
patching to remove this import or make it fail quietly.
@@ -377,7 +376,7 @@ Using an NDKRecipe
------------------
If you are writing a recipe not for a Python module but for something
-that would normall go in the JNI dir of an Android project (i.e. it
+that would normally go in the JNI dir of an Android project (i.e. it
has an ``Application.mk`` and ``Android.mk`` that the Android build
system can use), you can use an NDKRecipe to automatically set it
up. The NDKRecipe overrides the normal ``get_build_dir`` method to
@@ -427,7 +426,7 @@ overrides if you do not use them::
url = 'http://example.com/example-{version}.tar.gz'
# {version} will be replaced with self.version when downloading
- depends = ['python2', 'numpy'] # A list of any other recipe names
+ depends = ['python3', 'numpy'] # A list of any other recipe names
# that must be built before this
# one
@@ -435,29 +434,29 @@ overrides if you do not use them::
# alongside this one
def get_recipe_env(self, arch):
- env = super(YourRecipe, self).get_recipe_env()
+ env = super().get_recipe_env(arch)
# Manipulate the env here if you want
return env
- def should_build(self):
+ def should_build(self, arch):
# Add a check for whether the recipe is already built if you
# want, and return False if it is.
return True
def prebuild_arch(self, arch):
- super(YourRecipe, self).prebuild_arch(self)
+ super().prebuild_arch(self)
# Do any extra prebuilding you want, e.g.:
self.apply_patch('path/to/patch.patch')
def build_arch(self, arch):
- super(YourRecipe, self).build_arch(self)
+ super().build_arch(self)
# Build the code. Make sure to use the right build dir, e.g.
with current_directory(self.get_build_dir(arch.arch)):
sh.ls('-lathr') # Or run some commands that actually do
# something
def postbuild_arch(self, arch):
- super(YourRecipe, self).prebuild_arch(self)
+ super().prebuild_arch(self)
# Do anything you want after the build, e.g. deleting
# unnecessary files such as documentation
@@ -487,7 +486,7 @@ how to create your own Recipe subclass.
.. autoclass:: toolchain.Recipe
:members:
- :member-order: = 'bysource'
+ :member-order: bysource
diff --git a/doc/source/services.rst b/doc/source/services.rst
index a250c7bc4..c71b035a7 100644
--- a/doc/source/services.rst
+++ b/doc/source/services.rst
@@ -8,10 +8,15 @@ possible to use normal multiprocessing on Android. Services are also
the only way to run code when your app is not currently opened by the user.
Services must be declared when building your APK. Each one
-will have its own main.py file with the Python script to be run. You
-can communicate with the service process from your app using e.g. `osc
-<https://pypi.python.org/pypi/python-osc>`__ or (a heavier option)
-`twisted <https://twistedmatrix.com/trac/>`__.
+will have its own main.py file with the Python script to be run.
+Please note that python-for-android explicitly runs services as separated
+processes by having a colon ":" in the beginning of the name assigned to
+the ``android:process`` attribute of the ``AndroidManifest.xml`` file.
+This is not the default behavior, see `Android service documentation
+<https://developer.android.com/guide/topics/manifest/service-element>`__.
+You can communicate with the service process from your app using e.g.
+`osc <https://pypi.org/project/python-osc/>`__ or (a heavier option)
+`twisted <https://twisted.org/>`__.
Service creation
----------------
@@ -21,18 +26,16 @@ There are two ways to have services included in your APK.
Service folder
~~~~~~~~~~~~~~
-This basic method works with both the new SDL2 and old Pygame
-bootstraps. It is recommended to use the second method (below) where
-possible.
+This is the older method of handling services. It is
+recommended to use the second method (below) where possible.
Create a folder named ``service`` in your app directory, and add a
file ``service/main.py``. This file should contain the Python code
that you want the service to run.
To start the service, use the :code:`start_service` function from the
-:code:`android` module (included automatically with the Pygame
-bootstrap, you must add it to the requirements manually with SDL2 if
-you wish to use this method)::
+:code:`android` module (you may need to add ``android`` to your app
+requirements)::
import android
android.start_service(title='service name',
@@ -44,38 +47,75 @@ you wish to use this method)::
Arbitrary service scripts
~~~~~~~~~~~~~~~~~~~~~~~~~
-.. note:: This service method is *not supported* by the Pygame bootstrap.
-
This method is recommended for non-trivial use of services as it is
more flexible, supporting multiple services and a wider range of
options.
To create the service, create a python script with your service code
-and add a :code:`--service=myservice:/path/to/myservice.py` argument
-when calling python-for-android. The ``myservice`` name before the
-colon is the name of the service class, via which you will interact
-with it later. You can add multiple
-:code:`--service` arguments to include multiple services, which you
-will later be able to stop and start from your app.
+and add a :code:`--service=myservice:PATH_TO_SERVICE_PY` argument
+when calling python-for-android, or in buildozer.spec, a
+:code:`services = myservice:PATH_TO_SERVICE_PY` [app] setting.
+
+The ``myservice`` name before the colon is the name of the service
+class, via which you will interact with it later.
+
+The ``PATH_TO_SERVICE_PY`` is the relative path to the service entry point (like ``services/myservice.py``)
+
+You can optionally specify the following parameters:
+ - :code:`:foreground` for launching a service as an Android foreground service
+ - :code:`:sticky` for launching a service that gets restarted by the Android OS on exit/error
+
+Full command with all the optional parameters included would be:
+:code:`--service=myservice:services/myservice.py:foreground:sticky`
+
+You can add multiple
+:code:`--service` arguments to include multiple services, or separate
+them with a comma in buildozer.spec, all of which you will later be
+able to stop and start from your app.
To run the services (i.e. starting them from within your main app
code), you must use PyJNIus to interact with the java class
python-for-android creates for each one, as follows::
from jnius import autoclass
- service = autoclass('your.package.name.ServiceMyservice')
+ service = autoclass('your.package.domain.package.name.ServiceMyservice')
mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
argument = ''
service.start(mActivity, argument)
-Here, ``your.package.name`` refers to the package identifier of your
-APK as set by the ``--package`` argument to python-for-android, and
-the name of the service is ``ServiceYourservicename``, in which
-``Yourservicename`` is the identifier passed to the ``--service``
-argument with the first letter upper case. You must also pass the
+Here, ``your.package.domain.package.name`` refers to the package identifier
+of your APK.
+
+If you are using buildozer, the identifier is set by the ``package.name``
+and ``package.domain`` values in your buildozer.spec file.
+The name of the service is ``ServiceMyservice``, where ``Myservice``
+is the name specied by one of the ``services`` values, but with the first
+letter upper case.
+
+If you are using python-for-android directly, the identifier is set by the ``--package``
+argument to python-for-android. The name of the service is ``ServiceMyservice``,
+where ``Myservice`` is the identifier that was previously passed to the ``--service``
+argument, but with the first letter upper case. You must also pass the
``argument`` parameter even if (as here) it is an empty string. If you
do pass it, the service can make use of this argument.
+The service argument is made available to your service via the
+'PYTHON_SERVICE_ARGUMENT' environment variable. It is exposed as a simple
+string, so if you want to pass in multiple values, we would recommend using
+the json module to encode and decode more complex data.
+::
+
+ from os import environ
+ argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')
+
+To customize the notification icon, title, and text use three optional
+arguments to service.start()::
+
+ service.start(mActivity, 'small_icon', 'title', 'content' , argument)
+
+Where 'small_icon' is the name of an Android drawable or mipmap resource,
+and 'title' and 'content' are strings in the notification.
+
Services support a range of options and interactions not yet
documented here but all accessible via calling other methods of the
``service`` reference.
@@ -87,3 +127,14 @@ documented here but all accessible via calling other methods of the
your service folder you must use e.g. ``import service.module``
instead of ``import module``, if the service file is in the
``service/`` folder.
+
+Service auto-restart
+~~~~~~~~~~~~~~~~~~~~
+
+It is possible to make services restart automatically when they exit by
+calling ``setAutoRestartService(True)`` on the service object.
+The call to this method should be done within the service code::
+
+ from jnius import autoclass
+ PythonService = autoclass('org.kivy.android.PythonService')
+ PythonService.mService.setAutoRestartService(True)
diff --git a/doc/source/testing_pull_requests.rst b/doc/source/testing_pull_requests.rst
new file mode 100644
index 000000000..f77748e33
--- /dev/null
+++ b/doc/source/testing_pull_requests.rst
@@ -0,0 +1,226 @@
+Testing an python-for-android pull request
+==========================================
+
+In order to test a pull request, we recommend to consider the following points:
+
+ #. of course, check if the overall thing makes sense
+ #. is the CI passing? if not what specifically fails
+ #. is it working locally at compile time?
+ #. is it working on device at runtime?
+
+This document will focus on the third point:
+`is it working locally at compile time?` so we will give some hints about how
+to proceed in order to create a local copy of the pull requests and build an
+apk. We expect that the contributors has enough criteria/knowledge to perform
+the other steps mentioned, so let's begin...
+
+To create an apk from a python-for-android pull request we contemplate three
+possible scenarios:
+
+ - using python-for-android commands directly from the pull request files
+ that we want to test, without installing it (the recommended way for most
+ of the test cases)
+ - installing python-for-android using the github's branch of the pull request
+ - using buildozer and a custom app
+
+We will explain the first two methods using one of the distributed
+python-for-android test apps and we assume that you already have the
+python-for-android dependencies installed. For the `buildozer` method we also
+expect that you already have a a properly working app to test and a working
+installation/configuration of `buildozer`. There is one step that it's shared
+with all the testing methods that we propose in here...we named it
+`Common steps`.
+
+
+Common steps
+^^^^^^^^^^^^
+The first step to do it's to get a copy of the pull request, we can do it of
+several ways, and that it will depend of the circumstances but all the methods
+presented here will do the job, so...
+
+Fetch the pull request by number
+--------------------------------
+For the example, we will use `1901` for the example) and the pull request
+branch that we will use is `feature-fix-numpy`, then you will use a variation
+of the following git command:
+`git fetch origin pull/<#>/head:<local_branch_name>`, e.g.:
+
+.. code-block:: bash
+
+ git fetch upstream pull/1901/head:feature-fix-numpy
+
+.. note:: Notice that we fetch from `upstream`, since that is the original
+ project, where the pull request is supposed to be
+
+.. tip:: The amount of work of some users maybe worth it to add his remote
+ to your fork's git configuration, to do so with the imaginary
+ github user `Obi-Wan Kenobi` which nickname is `obiwankenobi`, you
+ will do:
+
+ .. code-block:: bash
+
+ git remote add obiwankenobi https://github.com/obiwankenobi/python-for-android.git
+
+ And to fetch the pull request branch that we put as example, you
+ would do:
+
+ .. code-block:: bash
+
+ git fetch obiwankenobi
+ git checkout obiwankenobi/feature-fix-numpy
+
+
+Clone the pull request branch from the user's fork
+--------------------------------------------------
+Sometimes you may prefer to use directly the fork of the user, so you will get
+the nickname of the user who created the pull request, let's take the same
+imaginary user than before `obiwankenobi`:
+
+ .. code-block:: bash
+
+ git clone -b feature-fix-numpy \
+ --single-branch \
+ https://github.com/obiwankenobi/python-for-android.git \
+ p4a-feature-fix-numpy
+
+Here's the above command explained line by line:
+
+- `git clone -b feature-fix-numpy`: we tell git that we want to clone the
+ branch named `feature-fix-numpy`
+- `--single-branch`: we tell git that we only want that branch
+- `https://github.com/obiwankenobi/python-for-android.git`: noticed the
+ nickname of the user that created the pull request: `obiwankenobi` in the
+ middle of the line? that should be changed as needed for each pull
+ request that you want to test
+- `p4a-feature-fix-numpy`: the name of the cloned repository, so we can
+ have multiple clones of different prs in the same folder
+
+.. note:: You can view the author/branch information looking at the
+ subtitle of the pull request, near the pull request status (expected
+ an `open` status)
+
+Using python-for-android commands directly from the pull request files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Enter inside the directory of the cloned repository in the above
+ step and run p4a command with proper args, e.g. (to test an modified
+ `pycryptodome` recipe)
+
+.. code-block:: bash
+
+ cd p4a-feature-fix-numpy
+ PYTHONPATH=. python3 -m pythonforandroid.toolchain apk \
+ --private=testapps/on_device_unit_tests/test_app \
+ --dist-name=dist_unit_tests_app_pycryptodome \
+ --package=org.kivy \
+ --name=unit_tests_app_pycryptodome \
+ --version=0.1 \
+ --requirements=sdl2,pyjnius,kivy,python3,pycryptodome \
+ --ndk-dir=/media/DEVEL/Android/android-ndk-r20 \
+ --sdk-dir=/media/DEVEL/Android/android-sdk-linux \
+ --android-api=27 \
+ --arch=arm64-v8a \
+ --permission=VIBRATE \
+ --debug
+
+Things that you should know:
+
+
+ - The example above will build an test app we will make use of the files of
+ the `on device unit tests` test app but we don't use the setup
+ file to build it so we must tell python-for-android what we want via
+ arguments
+ - be sure to at least edit the following arguments when running the above
+ command, since the default set in there it's unlikely that match your
+ installation:
+
+ - `--ndk-dir`: An absolute path to your android's NDK dir
+ - `--sdk-dir`: An absolute path to your android's SDK dir
+ - `--debug`: this one enables the debug mode of python-for-android,
+ which will show all log messages of the build. You can omit this
+ one but it's worth it to be mentioned, since this it's useful to us
+ when trying to find the source of the problem when things goes
+ wrong
+ - The apk generated by the above command should be located at the root of
+ of the cloned repository, were you run the command to build the apk
+ - The testapps distributed with python-for-android are located at
+ `testapps` folder under the main folder project
+ - All the builds of python-for-android are located at
+ `~/.local/share/python-for-android`
+ - You should have a downloaded copy of the android's NDK and SDK
+
+Installing python-for-android using the github's branch of the pull request
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Enter inside the directory of the cloned repository mentioned in
+ `Common steps` and install it via pip, e.g.:
+
+.. code-block:: bash
+
+ cd p4a-feature-fix-numpy
+ pip3 install . --upgrade --user
+
+- Now, go inside the `testapps/on_device_unit_tests` directory (we assume that
+ you still are inside the cloned repository)
+
+.. code-block:: bash
+
+ cd testapps/on_device_unit_tests
+
+- Run the build of the apk via the freshly installed copy of python-for-android
+ by running a similar command than below
+
+.. code-block:: bash
+
+ python3 setup.py apk \
+ --ndk-dir=/media/DEVEL/Android/android-ndk-r20 \
+ --sdk-dir=/media/DEVEL/Android/android-sdk-linux \
+ --android-api=27 \
+ --arch=arm64-v8a \
+ --debug
+
+
+Things that you should know:
+
+ - In the example above, we override some variables that are set in
+ `setup.py`, you could also override them by editing this file
+ - be sure to at least edit the following arguments when running the above
+ command, since the default set in there it's unlikely that match your
+ installation:
+
+ - `--ndk-dir`: An absolute path to your android's NDK dir
+ - `--sdk-dir`: An absolute path to your android's SDK dir
+
+.. tip:: if you don't want to mess up with the system's python, you could do
+ the same steps but inside a virtualenv
+
+.. warning:: Once you finish the pull request tests remember to go back to the
+ master or develop versions of python-for-android, since you just
+ installed the python-for-android files of the `pull request`
+
+Using buildozer with a custom app
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Edit your `buildozer.spec` file. You should search for the key
+ `p4a.source_dir` and set the right value so in the example posted in
+ `Common steps` it would look like this::
+
+ p4a.source_dir = /home/user/p4a_pull_requests/p4a-feature-fix-numpy
+
+- Run you buildozer command as usual, e.g.::
+
+ buildozer android debug p4a --dist-name=dist-test-feature-fix-numpy
+
+.. note:: this method has the advantage, can be run without installing the
+ pull request version of python-for-android nor the android's
+ dependencies but has one problem...when things goes wrong you must
+ determine if it's a buildozer issue or a python-for-android one
+
+.. warning:: Once you finish the pull request tests remember to comment/edit
+ the `p4a.source_dir` constant that you just edited to test the
+ pull request
+
+.. tip:: this method it's useful for developing pull requests since you can
+ edit `p4a.source_dir` to point to your python-for-android fork and you
+ can test any branch you want only switching branches with:
+ `git checkout <branch-name>` from inside your python-for-android fork
diff --git a/doc/source/troubleshooting.rst b/doc/source/troubleshooting.rst
index 975b29e34..25f8cbc04 100644
--- a/doc/source/troubleshooting.rst
+++ b/doc/source/troubleshooting.rst
@@ -10,25 +10,11 @@ Add the ``--debug`` option to any python-for-android command to see
full debug output including the output of all the external tools used
in the compilation and packaging steps.
-If reporting a problem by email or irc, it is usually helpful to
-include this full log, via e.g. a `pastebin
-<http://paste.ubuntu.com/>`_ or `Github gist
+If reporting a problem by email or Discord, it is usually helpful to
+include this full log, e.g. via a `pastebin
+<https://pastebin.ubuntu.com/>`_ or `Github gist
<https://gist.github.com/>`_.
-Getting help
-------------
-
-python-for-android is managed by the Kivy Organisation, and you can
-get help with any problems using the same channels as Kivy itself:
-
-- by email to the `kivy-users Google group
- <https://groups.google.com/forum/#!forum/kivy-users>`_
-- by irc in the #kivy room at irc.freenode.net
-
-If you find a bug, you can also post an issue on the
-`python-for-android Github page
-<https://github.com/kivy/python-for-android>`_.
-
Debugging on Android
--------------------
@@ -58,21 +44,21 @@ grepping this).
When your app crashes, you'll see the normal Python traceback here, as
well as the output of any print statements etc. that your app
runs. Use these to diagnose the problem just as normal.
-
+
The adb command passes its arguments straight to adb itself, so you
can also do other debugging tasks such as ``python-for-android adb
devices`` to get the list of connected devices.
For further information, see the Android docs on `adb
-<http://developer.android.com/intl/zh-cn/tools/help/adb.html>`_, and
+<https://developer.android.com/tools/adb>`_, and
on `logcat
-<http://developer.android.com/intl/zh-cn/tools/help/logcat.html>`_ in
+<https://developer.android.com/tools/logcat>`_ in
particular.
Unpacking an APK
----------------
-It is sometimes useful to unpack a pacakged APK to see what is inside,
+It is sometimes useful to unpack a packaged APK to see what is inside,
especially when debugging python-for-android itself.
APKs are just zip files, so you can extract the contents easily::
@@ -85,96 +71,37 @@ At the top level, this will always contain the same set of files::
AndroidManifest.xml classes.dex META-INF res
assets lib YourApk.apk resources.arsc
-The Python distribution is in the assets folder::
+The user app data (code, images, fonts ..) is packaged into a single tarball contained in the assets folder::
$ cd assets
- $ ls
- private.mp3
-
-``private.mp3`` is actually a tarball containing all your packaged
-data, and the Python distribution. Extract it::
-
- $ tar xf private.mp3
-
-This will reveal all the Python-related files::
-
$ ls
- android_runnable.pyo include interpreter_subprocess main.kv pipinterface.kv settings.pyo
- assets __init__.pyo interpreterwrapper.pyo main.pyo pipinterface.pyo utils.pyo
- editor.kv interpreter.kv lib menu.kv private.mp3 widgets.pyo
- editor.pyo interpreter.pyo libpymodules.so menu.pyo settings.kv
-
-Most of these files have been included by the user (in this case, they
-come from one of my own apps), the rest relate to the python
-distribution.
-
-With Python 2, the Python installation can mostly be found in the
-``lib`` folder. With Python 3 (using the ``python3crystax`` recipe),
-the Python installation can be found in a folder named
-``crystax_python``.
+ private.tar
+``private.tar`` is a tarball containing all your packaged
+data. Extract it::
-Common errors
--------------
+ $ tar xf private.tar
-The following are common problems and resolutions that users have reported.
+This will reveal all the user app data (the files shown below are from the touchtracer demo)::
+ $ ls
+ README.txt android.txt icon.png main.pyc p4a_env_vars.txt particle.png
+ private.tar touchtracer.kv
-AttributeError: 'AnsiCodes' object has no attribute 'LIGHTBLUE_EX'
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This occurs if your version of colorama is too low, install version
-0.3.3 or higher.
-
-If you install python-for-android with pip or via setup.py, this
-dependency should be taken care of automatically.
-
-AttributeError: 'Context' object has no attribute 'hostpython'
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This is a known bug in some releases. To work around it, add your
-python requirement explicitly,
-e.g. :code:`--requirements=python2,kivy`. This also applies when using
-buildozer, in which case add python2 to your buildozer.spec requirements.
-
-linkname too long
-~~~~~~~~~~~~~~~~~
-
-This can happen when you try to include a very long filename, which
-doesn't normally happen but can occur accidentally if the p4a
-directory contains a .buildozer directory that is not excluded from
-the build (e.g. if buildozer was previously used). Removing this
-directory should fix the problem, and is desirable anyway since you
-don't want it in the APK.
-
-Exception in thread "main" java.lang.UnsupportedClassVersionError: com/android/dx/command/Main : Unsupported major.minor version 52.0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This occurs due to a java version mismatch, it should be fixed by
-installing Java 8 (e.g. the openjdk-8-jdk package on Ubuntu).
-
-JNI DETECTED ERROR IN APPLICATION: static jfieldID 0x0000000 not valid for class java.lang.Class<org.renpy.android.PythonActivity>
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Due to how We're required to ship ABI-specific things in Android App Bundle,
+the Python installation is packaged separately, as (most of it) is ABI-specific.
-This error appears in the logcat log if you try to access
-``org.renpy.android.PythonActivity`` from within the new toolchain. To
-fix it, change your code to reference
-``org.kivy.android.PythonActivity`` instead.
+For example, the Python installation for ``arm64-v8a`` is available in ``lib/arm64-v8a/libpybundle.so``
-websocket-client: if you see errors relating to 'SSL not available'
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Ensure you have the package backports.ssl-match-hostname in the buildozer requirements, since Kivy targets python 2.7.x
-
-You may also need sslopt={"cert_reqs": ssl.CERT_NONE} as a parameter to ws.run_forever() if you get an error relating to host verification
+``libpybundle.so`` is a tarball (but named like a library for packaging requirements), that contains our ``_python_bundle``::
-Requested API target 19 is not available, install it with the SDK android tool
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ $ tar xf libpybundle.so
+ $ cd _python_bundle
+ $ ls
+ modules site-packages stdlib.zip
-This means that your SDK is missing the required platform tools. You
-need to install the ``platforms;android-19`` package in your SDK,
-using the ``android`` or ``sdkmanager`` tools (depending on SDK
-version).
+FAQ
+---
-If using buildozer this should be done automatically, but as a
-workaround you can run these from
-``~/.buildozer/android/platform/android-sdk-20/tools/android``.
+Check out the `online FAQ <https://github.com/kivy/python-for-android/blob/master/FAQ.md>`_ for common
+errors.
\ No newline at end of file
diff --git a/pythonforandroid/__init__.py b/pythonforandroid/__init__.py
index 1f199f195..ecdf256ef 100644
--- a/pythonforandroid/__init__.py
+++ b/pythonforandroid/__init__.py
@@ -1,2 +1 @@
-
-__version__ = '0.6.0'
+__version__ = '2024.01.21'
diff --git a/pythonforandroid/androidndk.py b/pythonforandroid/androidndk.py
new file mode 100644
index 000000000..83cb35574
--- /dev/null
+++ b/pythonforandroid/androidndk.py
@@ -0,0 +1,83 @@
+import sys
+import os
+
+
+class AndroidNDK:
+ """
+ This class is used to get the current NDK information.
+ """
+
+ ndk_dir = ""
+
+ def __init__(self, ndk_dir):
+ self.ndk_dir = ndk_dir
+
+ @property
+ def host_tag(self):
+ """
+ Returns the host tag for the current system.
+ Note: The host tag is ``darwin-x86_64`` even on Apple Silicon macs.
+ """
+ return f"{sys.platform}-x86_64"
+
+ @property
+ def llvm_prebuilt_dir(self):
+ return os.path.join(
+ self.ndk_dir, "toolchains", "llvm", "prebuilt", self.host_tag
+ )
+
+ @property
+ def llvm_bin_dir(self):
+ return os.path.join(self.llvm_prebuilt_dir, "bin")
+
+ @property
+ def clang(self):
+ return os.path.join(self.llvm_bin_dir, "clang")
+
+ @property
+ def clang_cxx(self):
+ return os.path.join(self.llvm_bin_dir, "clang++")
+
+ @property
+ def llvm_binutils_prefix(self):
+ return os.path.join(self.llvm_bin_dir, "llvm-")
+
+ @property
+ def llvm_ar(self):
+ return f"{self.llvm_binutils_prefix}ar"
+
+ @property
+ def llvm_ranlib(self):
+ return f"{self.llvm_binutils_prefix}ranlib"
+
+ @property
+ def llvm_objcopy(self):
+ return f"{self.llvm_binutils_prefix}objcopy"
+
+ @property
+ def llvm_objdump(self):
+ return f"{self.llvm_binutils_prefix}objdump"
+
+ @property
+ def llvm_readelf(self):
+ return f"{self.llvm_binutils_prefix}readelf"
+
+ @property
+ def llvm_strip(self):
+ return f"{self.llvm_binutils_prefix}strip"
+
+ @property
+ def sysroot(self):
+ return os.path.join(self.llvm_prebuilt_dir, "sysroot")
+
+ @property
+ def sysroot_include_dir(self):
+ return os.path.join(self.sysroot, "usr", "include")
+
+ @property
+ def sysroot_lib_dir(self):
+ return os.path.join(self.sysroot, "usr", "lib")
+
+ @property
+ def libcxx_include_dir(self):
+ return os.path.join(self.sysroot_include_dir, "c++", "v1")
diff --git a/pythonforandroid/archs.py b/pythonforandroid/archs.py
index e16db0096..b06517459 100644
--- a/pythonforandroid/archs.py
+++ b/pythonforandroid/archs.py
@@ -1,27 +1,65 @@
-from os.path import (exists, join, dirname)
-from os import environ, uname
-import sys
-from distutils.spawn import find_executable
+from os import environ
+from os.path import join
+from multiprocessing import cpu_count
+import shutil
-from pythonforandroid.logger import warning
from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import BuildInterruptingException, build_platform
-class Arch(object):
-
- toolchain_prefix = None
- '''The prefix for the toolchain dir in the NDK.'''
+class Arch:
command_prefix = None
'''The prefix for NDK commands such as gcc.'''
+ arch = ""
+ '''Name of the arch such as: `armeabi-v7a`, `arm64-v8a`, `x86`...'''
+
+ arch_cflags = []
+ '''Specific arch `cflags`, expect to be overwrote in subclass if needed.'''
+
+ common_cflags = [
+ '-target {target}',
+ '-fomit-frame-pointer'
+ ]
+
+ common_cppflags = [
+ '-DANDROID',
+ '-I{ctx.ndk.sysroot_include_dir}',
+ '-I{python_includes}',
+ ]
+
+ common_ldflags = ['-L{ctx_libs_dir}']
+
+ common_ldlibs = ['-lm']
+
+ common_ldshared = [
+ '-pthread',
+ '-shared',
+ '-Wl,-O1',
+ '-Wl,-Bsymbolic-functions',
+ ]
+
def __init__(self, ctx):
- super(Arch, self).__init__()
self.ctx = ctx
+ # Allows injecting additional linker paths used by any recipe.
+ # This can also be modified by recipes (like the librt recipe)
+ # to make sure that some sort of global resource is available &
+ # linked for all others.
+ self.extra_global_link_paths = []
+
def __str__(self):
return self.arch
+ @property
+ def ndk_lib_dir(self):
+ return join(self.ctx.ndk.sysroot_lib_dir, self.command_prefix)
+
+ @property
+ def ndk_lib_dir_versioned(self):
+ return join(self.ndk_lib_dir, str(self.ctx.ndk_api))
+
@property
def include_dirs(self):
return [
@@ -30,172 +68,237 @@ def include_dirs(self):
d.format(arch=self))
for d in self.ctx.include_dirs]
- def get_env(self, with_flags_in_cc=True):
- env = {}
-
- env['CFLAGS'] = ' '.join([
- '-DANDROID', '-mandroid', '-fomit-frame-pointer'
- ' -D__ANDROID_API__={}'.format(self.ctx._android_api),
- ])
- env['LDFLAGS'] = ' '
-
- sysroot = join(self.ctx._ndk_dir, 'sysroot')
- if exists(sysroot):
- # post-15 NDK per
- # https://android.googlesource.com/platform/ndk/+/ndk-r15-release/docs/UnifiedHeaders.md
- env['CFLAGS'] += ' -isystem {}/sysroot/usr/include/{}'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_prefix)
- else:
- sysroot = self.ctx.ndk_platform
- env['CFLAGS'] += ' -I{}'.format(self.ctx.ndk_platform)
- env['CFLAGS'] += ' -isysroot {} '.format(sysroot)
- env['CFLAGS'] += '-I' + join(self.ctx.get_python_install_dir(),
- 'include/python{}'.format(
- self.ctx.python_recipe.version[0:3])
- )
-
- env['LDFLAGS'] += '--sysroot {} '.format(self.ctx.ndk_platform)
-
- env["CXXFLAGS"] = env["CFLAGS"]
-
- env["LDFLAGS"] += " ".join(['-lm', '-L' + self.ctx.get_libs_dir(self.arch)])
+ @property
+ def target(self):
+ # As of NDK r19, the toolchains installed by default with the
+ # NDK may be used in-place. The make_standalone_toolchain.py script
+ # is no longer needed for interfacing with arbitrary build systems.
+ # See: https://developer.android.com/ndk/guides/other_build_systems
+ return '{triplet}{ndk_api}'.format(
+ triplet=self.command_prefix, ndk_api=self.ctx.ndk_api
+ )
- if self.ctx.ndk == 'crystax':
- env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(self.ctx.ndk_dir, self.arch)
+ @property
+ def clang_exe(self):
+ """Full path of the clang compiler depending on the android's ndk
+ version used."""
+ return self.get_clang_exe()
- py_platform = sys.platform
- if py_platform in ['linux2', 'linux3']:
- py_platform = 'linux'
+ @property
+ def clang_exe_cxx(self):
+ """Full path of the clang++ compiler depending on the android's ndk
+ version used."""
+ return self.get_clang_exe(plus_plus=True)
+
+ def get_clang_exe(self, with_target=False, plus_plus=False):
+ """Returns the full path of the clang/clang++ compiler, supports two
+ kwargs:
+
+ - `with_target`: prepend `target` to clang
+ - `plus_plus`: will return the clang++ compiler (defaults to `False`)
+ """
+ compiler = 'clang'
+ if with_target:
+ compiler = '{target}-{compiler}'.format(
+ target=self.target, compiler=compiler
+ )
+ if plus_plus:
+ compiler += '++'
+ return join(self.ctx.ndk.llvm_bin_dir, compiler)
- toolchain_prefix = self.ctx.toolchain_prefix
- toolchain_version = self.ctx.toolchain_version
- command_prefix = self.command_prefix
+ def get_env(self, with_flags_in_cc=True):
+ env = {}
- env['TOOLCHAIN_PREFIX'] = toolchain_prefix
- env['TOOLCHAIN_VERSION'] = toolchain_version
+ # HOME: User's home directory
+ #
+ # Many tools including p4a store outputs in the user's home
+ # directory. This is found from the HOME environment variable
+ # and falls back to the system account database. Setting HOME
+ # can be used to globally divert these tools to use a different
+ # path. Furthermore, in containerized environments the user may
+ # not exist in the account database, so if HOME isn't set than
+ # these tools will fail.
+ if 'HOME' in environ:
+ env['HOME'] = environ['HOME']
+
+ # CFLAGS/CXXFLAGS: the processor flags
+ env['CFLAGS'] = ' '.join(self.common_cflags).format(target=self.target)
+ if self.arch_cflags:
+ # each architecture may have has his own CFLAGS
+ env['CFLAGS'] += ' ' + ' '.join(self.arch_cflags)
+ env['CXXFLAGS'] = env['CFLAGS']
+ # CPPFLAGS (for macros and includes)
+ env['CPPFLAGS'] = ' '.join(self.common_cppflags).format(
+ ctx=self.ctx,
+ command_prefix=self.command_prefix,
+ python_includes=join(
+ self.ctx.get_python_install_dir(self.arch),
+ 'include/python{}'.format(self.ctx.python_recipe.version[0:3]),
+ ),
+ )
+
+ # LDFLAGS: Link the extra global link paths first before anything else
+ # (such that overriding system libraries with them is possible)
+ env['LDFLAGS'] = (
+ ' '
+ + " ".join(
+ [
+ "-L'"
+ + link_path.replace("'", "'\"'\"'")
+ + "'" # no shlex.quote in py2
+ for link_path in self.extra_global_link_paths
+ ]
+ )
+ + ' ' + ' '.join(self.common_ldflags).format(
+ ctx_libs_dir=self.ctx.get_libs_dir(self.arch)
+ )
+ )
+
+ # LDLIBS: Library flags or names given to compilers when they are
+ # supposed to invoke the linker.
+ env['LDLIBS'] = ' '.join(self.common_ldlibs)
+
+ # CCACHE
ccache = ''
if self.ctx.ccache and bool(int(environ.get('USE_CCACHE', '1'))):
# print('ccache found, will optimize builds')
ccache = self.ctx.ccache + ' '
env['USE_CCACHE'] = '1'
env['NDK_CCACHE'] = self.ctx.ccache
- env.update({k: v for k, v in environ.items() if k.startswith('CCACHE_')})
+ env.update(
+ {k: v for k, v in environ.items() if k.startswith('CCACHE_')}
+ )
- cc = find_executable('{command_prefix}-gcc'.format(
- command_prefix=command_prefix), path=environ['PATH'])
+ # Compiler: `CC` and `CXX` (and make sure that the compiler exists)
+ env['PATH'] = self.ctx.env['PATH']
+ cc = shutil.which(self.clang_exe, path=env['PATH'])
if cc is None:
- print('Searching path are: {!r}'.format(environ['PATH']))
- warning('Couldn\'t find executable for CC. This indicates a '
- 'problem locating the {} executable in the Android '
- 'NDK, not that you don\'t have a normal compiler '
- 'installed. Exiting.')
- exit(1)
+ print('Searching path are: {!r}'.format(env['PATH']))
+ raise BuildInterruptingException(
+ 'Couldn\'t find executable for CC. This indicates a '
+ 'problem locating the {} executable in the Android '
+ 'NDK, not that you don\'t have a normal compiler '
+ 'installed. Exiting.'.format(self.clang_exe))
if with_flags_in_cc:
- env['CC'] = '{ccache}{command_prefix}-gcc {cflags}'.format(
- command_prefix=command_prefix,
+ env['CC'] = '{ccache}{exe} {cflags}'.format(
+ exe=self.clang_exe,
ccache=ccache,
cflags=env['CFLAGS'])
- env['CXX'] = '{ccache}{command_prefix}-g++ {cxxflags}'.format(
- command_prefix=command_prefix,
+ env['CXX'] = '{ccache}{execxx} {cxxflags}'.format(
+ execxx=self.clang_exe_cxx,
ccache=ccache,
cxxflags=env['CXXFLAGS'])
else:
- env['CC'] = '{ccache}{command_prefix}-gcc'.format(
- command_prefix=command_prefix,
+ env['CC'] = '{ccache}{exe}'.format(
+ exe=self.clang_exe,
ccache=ccache)
- env['CXX'] = '{ccache}{command_prefix}-g++'.format(
- command_prefix=command_prefix,
+ env['CXX'] = '{ccache}{execxx}'.format(
+ execxx=self.clang_exe_cxx,
ccache=ccache)
- env['AR'] = '{}-ar'.format(command_prefix)
- env['RANLIB'] = '{}-ranlib'.format(command_prefix)
- env['LD'] = '{}-ld'.format(command_prefix)
- # env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
- # env['LDSHARED'] = env['LD']
- env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
- env['MAKE'] = 'make -j5'
- env['READELF'] = '{}-readelf'.format(command_prefix)
- env['NM'] = '{}-nm'.format(command_prefix)
+ # Android's LLVM binutils
+ env['AR'] = self.ctx.ndk.llvm_ar
+ env['RANLIB'] = self.ctx.ndk.llvm_ranlib
+ env['STRIP'] = f'{self.ctx.ndk.llvm_strip} --strip-unneeded'
+ env['READELF'] = self.ctx.ndk.llvm_readelf
+ env['OBJCOPY'] = self.ctx.ndk.llvm_objcopy
- hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)
-
- # This hardcodes python version 2.7, needs fixing
- env['BUILDLIB_PATH'] = join(
- hostpython_recipe.get_build_dir(self.arch),
- 'build', 'lib.linux-{}-2.7'.format(uname()[-1]))
-
- env['PATH'] = environ['PATH']
+ env['MAKE'] = 'make -j{}'.format(str(cpu_count()))
+ # Android's arch/toolchain
env['ARCH'] = self.arch
+ env['NDK_API'] = 'android-{}'.format(str(self.ctx.ndk_api))
- if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax:
- env['CRYSTAX_PYTHON_VERSION'] = self.ctx.python_recipe.version
+ # Custom linker options
+ env['LDSHARED'] = env['CC'] + ' ' + ' '.join(self.common_ldshared)
+
+ # Host python (used by some recipes)
+ hostpython_recipe = Recipe.get_recipe(
+ 'host' + self.ctx.python_recipe.name, self.ctx)
+ env['BUILDLIB_PATH'] = join(
+ hostpython_recipe.get_build_dir(self.arch),
+ 'native-build',
+ 'build',
+ 'lib.{}-{}'.format(
+ build_platform,
+ self.ctx.python_recipe.major_minor_version_string,
+ ),
+ )
+
+ # for reproducible builds
+ if 'SOURCE_DATE_EPOCH' in environ:
+ for k in 'LC_ALL TZ SOURCE_DATE_EPOCH PYTHONHASHSEED BUILD_DATE BUILD_TIME'.split():
+ if k in environ:
+ env[k] = environ[k]
return env
class ArchARM(Arch):
arch = "armeabi"
- toolchain_prefix = 'arm-linux-androideabi'
command_prefix = 'arm-linux-androideabi'
- platform_dir = 'arch-arm'
+
+ @property
+ def target(self):
+ target_data = self.command_prefix.split('-')
+ return '{triplet}{ndk_api}'.format(
+ triplet='-'.join(['armv7a', target_data[1], target_data[2]]),
+ ndk_api=self.ctx.ndk_api,
+ )
class ArchARMv7_a(ArchARM):
arch = 'armeabi-v7a'
-
- def get_env(self, with_flags_in_cc=True):
- env = super(ArchARMv7_a, self).get_env(with_flags_in_cc)
- env['CFLAGS'] = (env['CFLAGS'] +
- (' -march=armv7-a -mfloat-abi=softfp '
- '-mfpu=vfp -mthumb'))
- env['CXXFLAGS'] = env['CFLAGS']
- return env
+ arch_cflags = [
+ '-march=armv7-a',
+ '-mfloat-abi=softfp',
+ '-mfpu=vfp',
+ '-mthumb',
+ '-fPIC',
+ ]
class Archx86(Arch):
arch = 'x86'
- toolchain_prefix = 'x86'
command_prefix = 'i686-linux-android'
- platform_dir = 'arch-x86'
-
- def get_env(self, with_flags_in_cc=True):
- env = super(Archx86, self).get_env(with_flags_in_cc)
- env['CFLAGS'] = (env['CFLAGS'] +
- ' -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32')
- env['CXXFLAGS'] = env['CFLAGS']
- return env
+ arch_cflags = [
+ '-march=i686',
+ '-mssse3',
+ '-mfpmath=sse',
+ '-m32',
+ '-fPIC',
+ ]
class Archx86_64(Arch):
arch = 'x86_64'
- toolchain_prefix = 'x86'
command_prefix = 'x86_64-linux-android'
- platform_dir = 'arch-x86'
-
- def get_env(self, with_flags_in_cc=True):
- env = super(Archx86_64, self).get_env(with_flags_in_cc)
- env['CFLAGS'] = (env['CFLAGS'] +
- ' -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel')
- env['CXXFLAGS'] = env['CFLAGS']
- return env
+ arch_cflags = [
+ '-march=x86-64',
+ '-msse4.2',
+ '-mpopcnt',
+ '-m64',
+ '-fPIC',
+ ]
class ArchAarch_64(Arch):
arch = 'arm64-v8a'
- toolchain_prefix = 'aarch64-linux-android'
command_prefix = 'aarch64-linux-android'
- platform_dir = 'arch-arm64'
-
- def get_env(self, with_flags_in_cc=True):
- env = super(ArchAarch_64, self).get_env(with_flags_in_cc)
- incpath = ' -I' + join(dirname(__file__), 'includes', 'arm64-v8a')
- env['EXTRA_CFLAGS'] = incpath
- env['CFLAGS'] += incpath
- env['CXXFLAGS'] += incpath
- if with_flags_in_cc:
- env['CC'] += incpath
- env['CXX'] += incpath
- return env
+ arch_cflags = [
+ '-march=armv8-a',
+ '-fPIC'
+ # '-I' + join(dirname(__file__), 'includes', 'arm64-v8a'),
+ ]
+
+ # Note: This `EXTRA_CFLAGS` below should target the commented `include`
+ # above in `arch_cflags`. The original lines were added during the Sdl2's
+ # bootstrap creation, and modified/commented during the migration to the
+ # NDK r19 build system, because it seems that we don't need it anymore,
+ # do we need them?
+ # def get_env(self, with_flags_in_cc=True):
+ # env = super().get_env(with_flags_in_cc)
+ # env['EXTRA_CFLAGS'] = self.arch_cflags[-1]
+ # return env
diff --git a/pythonforandroid/bdistapk.py b/pythonforandroid/bdistapk.py
index b31368ec2..b4467b9f9 100644
--- a/pythonforandroid/bdistapk.py
+++ b/pythonforandroid/bdistapk.py
@@ -1,12 +1,10 @@
-from __future__ import print_function
+from glob import glob
+from os.path import realpath, join, dirname, curdir, basename, split
from setuptools import Command
-from pythonforandroid import toolchain
-
+from shutil import copyfile
import sys
-from os.path import realpath, join, exists, dirname, curdir, basename, split
-from os import makedirs
-from glob import glob
-from shutil import rmtree, copyfile
+
+from pythonforandroid.util import rmdir, ensure_dir
def argv_contains(t):
@@ -16,16 +14,16 @@ def argv_contains(t):
return False
-class BdistAPK(Command):
- description = 'Create an APK with python-for-android'
+class Bdist(Command):
user_options = []
+ package_type = None
def initialize_options(self):
for option in self.user_options:
setattr(self, option[0].strip('=').replace('-', '_'), None)
- option_dict = self.distribution.get_option_dict('apk')
+ option_dict = self.distribution.get_option_dict(self.package_type)
# This is a hack, we probably aren't supposed to loop through
# the option_dict so early because distutils does exactly the
@@ -34,10 +32,9 @@ def initialize_options(self):
for (option, (source, value)) in option_dict.items():
setattr(self, option, str(value))
-
def finalize_options(self):
- setup_options = self.distribution.get_option_dict('apk')
+ setup_options = self.distribution.get_option_dict(self.package_type)
for (option, (source, value)) in setup_options.items():
if source == 'command line':
continue
@@ -46,6 +43,9 @@ def finalize_options(self):
if option == 'permissions':
for perm in value:
sys.argv.append('--permission={}'.format(perm))
+ elif option == 'orientation':
+ for orient in value:
+ sys.argv.append('--orientation={}'.format(orient))
elif value in (None, 'None'):
sys.argv.append('--{}'.format(option))
else:
@@ -70,16 +70,15 @@ def finalize_options(self):
sys.argv.append('--version={}'.format(version))
if not argv_contains('--arch'):
- arch = 'armeabi'
+ arch = 'armeabi-v7a'
self.arch = arch
sys.argv.append('--arch={}'.format(arch))
def run(self):
-
self.prepare_build_dir()
- from pythonforandroid.toolchain import main
- sys.argv[1] = 'apk'
+ from pythonforandroid.entrypoints import main
+ sys.argv[1] = self.package_type
main()
def prepare_build_dir(self):
@@ -91,9 +90,8 @@ def prepare_build_dir(self):
'that.')
bdist_dir = 'build/bdist.android-{}'.format(self.arch)
- if exists(bdist_dir):
- rmtree(bdist_dir)
- makedirs(bdist_dir)
+ rmdir(bdist_dir)
+ ensure_dir(bdist_dir)
globs = []
for directory, patterns in self.distribution.package_data.items():
@@ -108,11 +106,10 @@ def prepare_build_dir(self):
if not argv_contains('--launcher'):
for filen in filens:
new_dir = join(bdist_dir, dirname(filen))
- if not exists(new_dir):
- makedirs(new_dir)
+ ensure_dir(new_dir)
print('Including {}'.format(filen))
copyfile(filen, join(bdist_dir, filen))
- if basename(filen) in ('main.py', 'main.pyo'):
+ if basename(filen) in ('main.py', 'main.pyc'):
main_py_dirs.append(filen)
# This feels ridiculous, but how else to define the main.py dir?
@@ -131,18 +128,39 @@ def prepare_build_dir(self):
)
+class BdistAPK(Bdist):
+ """distutil command handler for 'apk'."""
+ description = 'Create an APK with python-for-android'
+ package_type = 'apk'
+
+
+class BdistAAR(Bdist):
+ """distutil command handler for 'aar'."""
+ description = 'Create an AAR with python-for-android'
+ package_type = 'aar'
+
+
+class BdistAAB(Bdist):
+ """distutil command handler for 'aab'."""
+ description = 'Create an AAB with python-for-android'
+ package_type = 'aab'
+
+
def _set_user_options():
# This seems like a silly way to do things, but not sure if there's a
# better way to pass arbitrary options onwards to p4a
- user_options = [('requirements=', None, None),]
+ user_options = [('requirements=', None, None), ]
for i, arg in enumerate(sys.argv):
if arg.startswith('--'):
if ('=' in arg or
- (i < (len(sys.argv) - 1) and not sys.argv[i+1].startswith('-'))):
+ (i < (len(sys.argv) - 1) and not sys.argv[i+1].startswith('-'))):
user_options.append((arg[2:].split('=')[0] + '=', None, None))
else:
user_options.append((arg[2:], None, None))
BdistAPK.user_options = user_options
+ BdistAAB.user_options = user_options
+ BdistAAR.user_options = user_options
+
-_set_user_options()
\ No newline at end of file
+_set_user_options()
diff --git a/pythonforandroid/bootstrap.py b/pythonforandroid/bootstrap.py
old mode 100644
new mode 100755
index f7aa6c9bd..8bbbcf0eb
--- a/pythonforandroid/bootstrap.py
+++ b/pythonforandroid/bootstrap.py
@@ -1,33 +1,87 @@
-from os.path import (join, dirname, isdir, splitext, basename, realpath)
-from os import listdir, mkdir
-import sh
+import functools
import glob
-import json
import importlib
+import os
+from os.path import (join, dirname, isdir, normpath, splitext, basename)
+from os import listdir, walk, sep
+import sh
+import shlex
+import shutil
-from pythonforandroid.logger import (warning, shprint, info, logger,
- debug, error)
-from pythonforandroid.util import (current_directory, ensure_dir,
- temp_directory, which)
+from pythonforandroid.logger import (shprint, info, logger, debug)
+from pythonforandroid.util import (
+ current_directory, ensure_dir, temp_directory, BuildInterruptingException,
+ rmdir, move)
from pythonforandroid.recipe import Recipe
-class Bootstrap(object):
+def copy_files(src_root, dest_root, override=True, symlink=False):
+ for root, dirnames, filenames in walk(src_root):
+ for filename in filenames:
+ subdir = normpath(root.replace(src_root, ""))
+ if subdir.startswith(sep): # ensure it is relative
+ subdir = subdir[1:]
+ dest_dir = join(dest_root, subdir)
+ if not os.path.exists(dest_dir):
+ os.makedirs(dest_dir)
+ src_file = join(root, filename)
+ dest_file = join(dest_dir, filename)
+ if os.path.isfile(src_file):
+ if override and os.path.exists(dest_file):
+ os.unlink(dest_file)
+ if not os.path.exists(dest_file):
+ if symlink:
+ os.symlink(src_file, dest_file)
+ else:
+ shutil.copy(src_file, dest_file)
+ else:
+ os.makedirs(dest_file)
+
+
+default_recipe_priorities = [
+ "webview", "sdl2", "service_only" # last is highest
+]
+# ^^ NOTE: these are just the default priorities if no special rules
+# apply (which you can find in the code below), so basically if no
+# known graphical lib or web lib is used - in which case service_only
+# is the most reasonable guess.
+
+
+def _cmp_bootstraps_by_priority(a, b):
+ def rank_bootstrap(bootstrap):
+ """ Returns a ranking index for each bootstrap,
+ with higher priority ranked with higher number. """
+ if bootstrap.name in default_recipe_priorities:
+ return default_recipe_priorities.index(bootstrap.name) + 1
+ return 0
+
+ # Rank bootstraps in order:
+ rank_a = rank_bootstrap(a)
+ rank_b = rank_bootstrap(b)
+ if rank_a != rank_b:
+ return (rank_b - rank_a)
+ else:
+ if a.name < b.name: # alphabetic sort for determinism
+ return -1
+ else:
+ return 1
+
+
+class Bootstrap:
'''An Android project template, containing recipe stuff for
compilation and templated fields for APK info.
'''
- name = ''
jni_subdir = '/jni'
ctx = None
bootstrap_dir = None
build_dir = None
- dist_dir = None
dist_name = None
distribution = None
- recipe_depends = ['sdl2']
+ # All bootstraps should include Python in some way:
+ recipe_depends = ['python3', 'android']
can_be_chosen_automatically = True
'''Determines whether the bootstrap can be chosen as one that
@@ -44,9 +98,9 @@ class Bootstrap(object):
def dist_dir(self):
'''The dist dir at which to place the finished distribution.'''
if self.distribution is None:
- warning('Tried to access {}.dist_dir, but {}.distribution '
- 'is None'.format(self, self))
- exit(1)
+ raise BuildInterruptingException(
+ 'Internal error: tried to access {}.dist_dir, but {}.distribution '
+ 'is None'.format(self, self))
return self.distribution.dist_dir
@property
@@ -58,7 +112,7 @@ def check_recipe_choices(self):
and optional dependencies are being used,
and returns a list of these.'''
recipes = []
- built_recipes = self.ctx.recipe_build_order
+ built_recipes = self.ctx.recipe_build_order or []
for recipe in self.recipe_depends:
if isinstance(recipe, (tuple, list)):
for alternative in recipe:
@@ -83,92 +137,161 @@ def name(self):
modname = self.__class__.__module__
return modname.split(".", 2)[-1]
+ def get_bootstrap_dirs(self):
+ """get all bootstrap directories, following the MRO path"""
+
+ # get all bootstrap names along the __mro__, cutting off Bootstrap and object
+ classes = self.__class__.__mro__[:-2]
+ bootstrap_names = [cls.name for cls in classes] + ['common']
+ bootstrap_dirs = [
+ join(self.ctx.root_dir, 'bootstraps', bootstrap_name)
+ for bootstrap_name in reversed(bootstrap_names)
+ ]
+ return bootstrap_dirs
+
+ def _copy_in_final_files(self):
+ if self.name == "sdl2":
+ # Get the paths for copying SDL2's java source code:
+ sdl2_recipe = Recipe.get_recipe("sdl2", self.ctx)
+ sdl2_build_dir = sdl2_recipe.get_jni_dir()
+ src_dir = join(sdl2_build_dir, "SDL", "android-project",
+ "app", "src", "main", "java",
+ "org", "libsdl", "app")
+ target_dir = join(self.dist_dir, 'src', 'main', 'java', 'org',
+ 'libsdl', 'app')
+
+ # Do actual copying:
+ info('Copying in SDL2 .java files from: ' + str(src_dir))
+ if not os.path.exists(target_dir):
+ os.makedirs(target_dir)
+ copy_files(src_dir, target_dir, override=True)
+
def prepare_build_dir(self):
- '''Ensure that a build dir exists for the recipe. This same single
- dir will be used for building all different archs.'''
+ """Ensure that a build dir exists for the recipe. This same single
+ dir will be used for building all different archs."""
+ bootstrap_dirs = self.get_bootstrap_dirs()
+ # now do a cumulative copy of all bootstrap dirs
self.build_dir = self.get_build_dir()
- shprint(sh.cp, '-r',
- join(self.bootstrap_dir, 'build'),
- self.build_dir)
- if self.ctx.symlink_java_src:
- info('Symlinking java src instead of copying')
- shprint(sh.rm, '-r', join(self.build_dir, 'src'))
- shprint(sh.mkdir, join(self.build_dir, 'src'))
- for dirn in listdir(join(self.bootstrap_dir, 'build', 'src')):
- shprint(sh.ln, '-s', join(self.bootstrap_dir, 'build', 'src', dirn),
- join(self.build_dir, 'src'))
+ for bootstrap_dir in bootstrap_dirs:
+ copy_files(join(bootstrap_dir, 'build'), self.build_dir, symlink=self.ctx.symlink_bootstrap_files)
+
with current_directory(self.build_dir):
with open('project.properties', 'w') as fileh:
fileh.write('target=android-{}'.format(self.ctx.android_api))
- def prepare_dist_dir(self, name):
- # self.dist_dir = self.get_dist_dir(name)
+ def prepare_dist_dir(self):
ensure_dir(self.dist_dir)
- def run_distribute(self):
- # print('Default bootstrap being used doesn\'t know how '
- # 'to distribute...failing.')
- # exit(1)
- with current_directory(self.dist_dir):
- info('Saving distribution info')
- with open('dist_info.json', 'w') as fileh:
- json.dump({'dist_name': self.ctx.dist_name,
- 'bootstrap': self.ctx.bootstrap.name,
- 'archs': [arch.arch for arch in self.ctx.archs],
- 'recipes': self.ctx.recipe_build_order + self.ctx.python_modules},
- fileh)
+ def assemble_distribution(self):
+ ''' Copies all the files into the distribution (this function is
+ overridden by the specific bootstrap classes to do this)
+ and add in the distribution info.
+ '''
+ self._copy_in_final_files()
+ self.distribution.save_info(self.dist_dir)
@classmethod
- def list_bootstraps(cls):
+ def all_bootstraps(cls):
'''Find all the available bootstraps and return them.'''
- forbidden_dirs = ('__pycache__', )
+ forbidden_dirs = ('__pycache__', 'common')
bootstraps_dir = join(dirname(__file__), 'bootstraps')
+ result = set()
for name in listdir(bootstraps_dir):
if name in forbidden_dirs:
continue
filen = join(bootstraps_dir, name)
if isdir(filen):
- yield name
+ result.add(name)
+ return result
@classmethod
- def get_bootstrap_from_recipes(cls, recipes, ctx):
- '''Returns a bootstrap whose recipe requirements do not conflict with
- the given recipes.'''
+ def get_usable_bootstraps_for_recipes(cls, recipes, ctx):
+ '''Returns all bootstrap whose recipe requirements do not conflict
+ with the given recipes, in no particular order.'''
info('Trying to find a bootstrap that matches the given recipes.')
bootstraps = [cls.get_bootstrap(name, ctx)
- for name in cls.list_bootstraps()]
- acceptable_bootstraps = []
+ for name in cls.all_bootstraps()]
+ acceptable_bootstraps = set()
+
+ # Find out which bootstraps are acceptable:
for bs in bootstraps:
if not bs.can_be_chosen_automatically:
continue
- possible_dependency_lists = expand_dependencies(bs.recipe_depends)
+ possible_dependency_lists = expand_dependencies(bs.recipe_depends, ctx)
for possible_dependencies in possible_dependency_lists:
ok = True
+ # Check if the bootstap's dependencies have an internal conflict:
for recipe in possible_dependencies:
recipe = Recipe.get_recipe(recipe, ctx)
- if any([conflict in recipes for conflict in recipe.conflicts]):
+ if any(conflict in recipes for conflict in recipe.conflicts):
ok = False
break
+ # Check if bootstrap's dependencies conflict with chosen
+ # packages:
for recipe in recipes:
try:
recipe = Recipe.get_recipe(recipe, ctx)
- except IOError:
+ except ValueError:
conflicts = []
else:
conflicts = recipe.conflicts
- if any([conflict in possible_dependencies
- for conflict in conflicts]):
+ if any(conflict in possible_dependencies
+ for conflict in conflicts):
ok = False
break
- if ok:
- acceptable_bootstraps.append(bs)
+ if ok and bs not in acceptable_bootstraps:
+ acceptable_bootstraps.add(bs)
+
info('Found {} acceptable bootstraps: {}'.format(
len(acceptable_bootstraps),
[bs.name for bs in acceptable_bootstraps]))
- if acceptable_bootstraps:
- info('Using the first of these: {}'
- .format(acceptable_bootstraps[0].name))
- return acceptable_bootstraps[0]
+ return acceptable_bootstraps
+
+ @classmethod
+ def get_bootstrap_from_recipes(cls, recipes, ctx):
+ '''Picks a single recommended default bootstrap out of
+ all_usable_bootstraps_from_recipes() for the given reicpes,
+ and returns it.'''
+
+ known_web_packages = {"flask"} # to pick webview over service_only
+ recipes_with_deps_lists = expand_dependencies(recipes, ctx)
+ acceptable_bootstraps = cls.get_usable_bootstraps_for_recipes(
+ recipes, ctx
+ )
+
+ def have_dependency_in_recipes(dep):
+ for dep_list in recipes_with_deps_lists:
+ if dep in dep_list:
+ return True
+ return False
+
+ # Special rule: return SDL2 bootstrap if there's an sdl2 dep:
+ if (have_dependency_in_recipes("sdl2") and
+ "sdl2" in [b.name for b in acceptable_bootstraps]
+ ):
+ info('Using sdl2 bootstrap since it is in dependencies')
+ return cls.get_bootstrap("sdl2", ctx)
+
+ # Special rule: return "webview" if we depend on common web recipe:
+ for possible_web_dep in known_web_packages:
+ if have_dependency_in_recipes(possible_web_dep):
+ # We have a web package dep!
+ if "webview" in [b.name for b in acceptable_bootstraps]:
+ info('Using webview bootstrap since common web packages '
+ 'were found {}'.format(
+ known_web_packages.intersection(recipes)
+ ))
+ return cls.get_bootstrap("webview", ctx)
+
+ prioritized_acceptable_bootstraps = sorted(
+ list(acceptable_bootstraps),
+ key=functools.cmp_to_key(_cmp_bootstraps_by_priority)
+ )
+
+ if prioritized_acceptable_bootstraps:
+ info('Using the highest ranked/first of these: {}'
+ .format(prioritized_acceptable_bootstraps[0].name))
+ return prioritized_acceptable_bootstraps[0]
return None
@classmethod
@@ -199,15 +322,16 @@ def distribute_libs(self, arch, src_dirs, wildcard='*', dest_dir="libs"):
tgt_dir = join(dest_dir, arch.arch)
ensure_dir(tgt_dir)
for src_dir in src_dirs:
- for lib in glob.glob(join(src_dir, wildcard)):
- shprint(sh.cp, '-a', lib, tgt_dir)
+ libs = glob.glob(join(src_dir, wildcard))
+ if libs:
+ shprint(sh.cp, '-a', *libs, tgt_dir)
def distribute_javaclasses(self, javaclass_dir, dest_dir="src"):
'''Copy existing javaclasses from build dir to current dist dir.'''
info('Copying java files')
ensure_dir(dest_dir)
- for filename in glob.glob(javaclass_dir):
- shprint(sh.cp, '-a', filename, dest_dir)
+ filenames = glob.glob(javaclass_dir)
+ shprint(sh.cp, '-a', *filenames, dest_dir)
def distribute_aars(self, arch):
'''Process existing .aar bundles and copy to current dist dir.'''
@@ -240,25 +364,25 @@ def _unpack_aar(self, aar, arch):
debug(" to {}".format(so_tgt_dir))
ensure_dir(so_tgt_dir)
so_files = glob.glob(join(so_src_dir, '*.so'))
- for f in so_files:
- shprint(sh.cp, '-a', f, so_tgt_dir)
+ shprint(sh.cp, '-a', *so_files, so_tgt_dir)
def strip_libraries(self, arch):
info('Stripping libraries')
- if self.ctx.python_recipe.from_crystax:
- info('Python was loaded from CrystaX, skipping strip')
- return
env = arch.get_env()
- strip = which('arm-linux-androideabi-strip', env['PATH'])
- if strip is None:
- warning('Can\'t find strip in PATH...')
- return
- strip = sh.Command(strip)
- filens = shprint(sh.find, join(self.dist_dir, 'private'),
- join(self.dist_dir, 'libs'),
+ tokens = shlex.split(env['STRIP'])
+ strip = sh.Command(tokens[0])
+ if len(tokens) > 1:
+ strip = strip.bake(tokens[1:])
+
+ libs_dir = join(self.dist_dir, f'_python_bundle__{arch.arch}',
+ '_python_bundle', 'modules')
+ filens = shprint(sh.find, libs_dir, join(self.dist_dir, 'libs'),
'-iname', '*.so', _env=env).stdout.decode('utf-8')
+
logger.info('Stripping libraries in private dir')
for filen in filens.split('\n'):
+ if not filen:
+ continue # skip the last ''
try:
strip(filen, _env=env)
except sh.ErrorReturnCode_1:
@@ -271,14 +395,36 @@ def fry_eggs(self, sitepackages):
if isdir(rd) and d.endswith('.egg'):
info(' ' + d)
files = [join(rd, f) for f in listdir(rd) if f != 'EGG-INFO']
- if files:
- shprint(sh.mv, '-t', sitepackages, *files)
- shprint(sh.rm, '-rf', d)
-
-
-def expand_dependencies(recipes):
+ for f in files:
+ move(f, sitepackages)
+ rmdir(d)
+
+
+def expand_dependencies(recipes, ctx):
+ """ This function expands to lists of all different available
+ alternative recipe combinations, with the dependencies added in
+ ONLY for all the not-with-alternative recipes.
+ (So this is like the deps graph very simplified and incomplete, but
+ hopefully good enough for most basic bootstrap compatibility checks)
+ """
+
+ # Add in all the deps of recipes where there is no alternative:
+ recipes_with_deps = list(recipes)
+ for entry in recipes:
+ if not isinstance(entry, (tuple, list)) or len(entry) == 1:
+ if isinstance(entry, (tuple, list)):
+ entry = entry[0]
+ try:
+ recipe = Recipe.get_recipe(entry, ctx)
+ recipes_with_deps += recipe.depends
+ except ValueError:
+ # it's a pure python package without a recipe, so we
+ # don't know the dependencies...skipping for now
+ pass
+
+ # Split up lists by available alternatives:
recipe_lists = [[]]
- for recipe in recipes:
+ for recipe in recipes_with_deps:
if isinstance(recipe, (tuple, list)):
new_recipe_lists = []
for alternative in recipe:
@@ -288,6 +434,6 @@ def expand_dependencies(recipes):
new_recipe_lists.append(new_list)
recipe_lists = new_recipe_lists
else:
- for old_list in recipe_lists:
- old_list.append(recipe)
+ for existing_list in recipe_lists:
+ existing_list.append(recipe)
return recipe_lists
diff --git a/pythonforandroid/bootstraps/sdl2/build/ant.properties b/pythonforandroid/bootstraps/common/build/ant.properties
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/ant.properties
rename to pythonforandroid/bootstraps/common/build/ant.properties
diff --git a/pythonforandroid/bootstraps/common/build/build.py b/pythonforandroid/bootstraps/common/build/build.py
new file mode 100644
index 000000000..29d16ea9f
--- /dev/null
+++ b/pythonforandroid/bootstraps/common/build/build.py
@@ -0,0 +1,1085 @@
+#!/usr/bin/env python3
+
+from gzip import GzipFile
+import hashlib
+import json
+from os.path import (
+ dirname, join, isfile, realpath,
+ relpath, split, exists, basename
+)
+from os import environ, listdir, makedirs, remove
+import os
+import shlex
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+import time
+
+from fnmatch import fnmatch
+import jinja2
+
+from pythonforandroid.util import rmdir, ensure_dir, max_build_tool_version
+
+
+def get_dist_info_for(key, error_if_missing=True):
+ try:
+ with open(join(dirname(__file__), 'dist_info.json'), 'r') as fileh:
+ info = json.load(fileh)
+ value = info[key]
+ except (OSError, KeyError) as e:
+ if not error_if_missing:
+ return None
+ print("BUILD FAILURE: Couldn't extract the key `" + key + "` " +
+ "from dist_info.json: " + str(e))
+ sys.exit(1)
+ return value
+
+
+def get_hostpython():
+ return get_dist_info_for('hostpython')
+
+
+def get_bootstrap_name():
+ return get_dist_info_for('bootstrap')
+
+
+if os.name == 'nt':
+ ANDROID = 'android.bat'
+ ANT = 'ant.bat'
+else:
+ ANDROID = 'android'
+ ANT = 'ant'
+
+curdir = dirname(__file__)
+
+BLACKLIST_PATTERNS = [
+ # code versionning
+ '^*.hg/*',
+ '^*.git/*',
+ '^*.bzr/*',
+ '^*.svn/*',
+
+ # temp files
+ '~',
+ '*.bak',
+ '*.swp',
+
+ # Android artifacts
+ '*.apk',
+ '*.aab',
+]
+
+WHITELIST_PATTERNS = []
+
+if os.environ.get("P4A_BUILD_IS_RUNNING_UNITTESTS", "0") != "1":
+ PYTHON = get_hostpython()
+ _bootstrap_name = get_bootstrap_name()
+else:
+ PYTHON = "python3"
+ _bootstrap_name = "sdl2"
+
+if PYTHON is not None and not exists(PYTHON):
+ PYTHON = None
+
+if _bootstrap_name in ('sdl2', 'webview', 'service_only', 'qt'):
+ WHITELIST_PATTERNS.append('pyconfig.h')
+
+environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
+ join(curdir, 'templates')))
+
+
+DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS = 'org.kivy.android.PythonActivity'
+DEFAULT_PYTHON_SERVICE_JAVA_CLASS = 'org.kivy.android.PythonService'
+
+
+def render(template, dest, **kwargs):
+ '''Using jinja2, render `template` to the filename `dest`, supplying the
+
+ keyword arguments as template parameters.
+ '''
+
+ dest_dir = dirname(dest)
+ if dest_dir and not exists(dest_dir):
+ makedirs(dest_dir)
+
+ template = environment.get_template(template)
+ text = template.render(**kwargs)
+
+ f = open(dest, 'wb')
+ f.write(text.encode('utf-8'))
+ f.close()
+
+
+def is_whitelist(name):
+ return match_filename(WHITELIST_PATTERNS, name)
+
+
+def is_blacklist(name):
+ if is_whitelist(name):
+ return False
+ return match_filename(BLACKLIST_PATTERNS, name)
+
+
+def match_filename(pattern_list, name):
+ for pattern in pattern_list:
+ if pattern.startswith('^'):
+ pattern = pattern[1:]
+ else:
+ pattern = '*/' + pattern
+ if fnmatch(name, pattern):
+ return True
+
+
+def listfiles(d):
+ basedir = d
+ subdirlist = []
+ for item in os.listdir(d):
+ fn = join(d, item)
+ if isfile(fn):
+ yield fn
+ else:
+ subdirlist.append(join(basedir, item))
+ for subdir in subdirlist:
+ for fn in listfiles(subdir):
+ yield fn
+
+
+def make_tar(tfn, source_dirs, byte_compile_python=False, optimize_python=True):
+ '''
+ Make a zip file `fn` from the contents of source_dis.
+ '''
+
+ def clean(tinfo):
+ """cleaning function (for reproducible builds)"""
+ tinfo.uid = tinfo.gid = 0
+ tinfo.uname = tinfo.gname = ''
+ tinfo.mtime = 0
+ return tinfo
+
+ # get the files and relpath file of all the directory we asked for
+ files = []
+ for sd in source_dirs:
+ sd = realpath(sd)
+ for fn in listfiles(sd):
+ if is_blacklist(fn):
+ continue
+ if fn.endswith('.py') and byte_compile_python:
+ fn = compile_py_file(fn, optimize_python=optimize_python)
+ files.append((fn, relpath(realpath(fn), sd)))
+ files.sort() # deterministic
+
+ # create tar.gz of thoses files
+ gf = GzipFile(tfn, 'wb', mtime=0) # deterministic
+ tf = tarfile.open(None, 'w', gf, format=tarfile.USTAR_FORMAT)
+ dirs = []
+ for fn, afn in files:
+ dn = dirname(afn)
+ if dn not in dirs:
+ # create every dirs first if not exist yet
+ d = ''
+ for component in split(dn):
+ d = join(d, component)
+ if d.startswith('/'):
+ d = d[1:]
+ if d == '' or d in dirs:
+ continue
+ dirs.append(d)
+ tinfo = tarfile.TarInfo(d)
+ tinfo.type = tarfile.DIRTYPE
+ clean(tinfo)
+ tf.addfile(tinfo)
+
+ # put the file
+ tf.add(fn, afn, filter=clean)
+ tf.close()
+ gf.close()
+
+
+def compile_py_file(python_file, optimize_python=True):
+ '''
+ Compile python_file to *.pyc and return the filename of the *.pyc file.
+ '''
+
+ if PYTHON is None:
+ return
+
+ args = [PYTHON, '-m', 'compileall', '-b', '-f', python_file]
+ if optimize_python:
+ # -OO = strip docstrings
+ args.insert(1, '-OO')
+ return_code = subprocess.call(args)
+
+ if return_code != 0:
+ print('Error while running "{}"'.format(' '.join(args)))
+ print('This probably means one of your Python files has a syntax '
+ 'error, see logs above')
+ exit(1)
+
+ return ".".join([os.path.splitext(python_file)[0], "pyc"])
+
+
+def make_package(args):
+ # If no launcher is specified, require a main.py/main.pyc:
+ if (get_bootstrap_name() != "sdl" or args.launcher is None) and \
+ get_bootstrap_name() not in ["webview", "service_library"]:
+ # (webview doesn't need an entrypoint, apparently)
+ if args.private is None or (
+ not exists(join(realpath(args.private), 'main.py')) and
+ not exists(join(realpath(args.private), 'main.pyc'))):
+ print('''BUILD FAILURE: No main.py(c) found in your app directory. This
+file must exist to act as the entry point for you app. If your app is
+started by a file with a different name, rename it to main.py or add a
+main.py that loads it.''')
+ sys.exit(1)
+
+ assets_dir = "src/main/assets"
+
+ # Delete the old assets.
+ rmdir(assets_dir, ignore_errors=True)
+ ensure_dir(assets_dir)
+
+ # Add extra environment variable file into tar-able directory:
+ env_vars_tarpath = tempfile.mkdtemp(prefix="p4a-extra-env-")
+ with open(os.path.join(env_vars_tarpath, "p4a_env_vars.txt"), "w") as f:
+ if hasattr(args, "window"):
+ f.write("P4A_IS_WINDOWED=" + str(args.window) + "\n")
+ if hasattr(args, "sdl_orientation_hint"):
+ f.write("KIVY_ORIENTATION=" + str(args.sdl_orientation_hint) + "\n")
+ f.write("P4A_NUMERIC_VERSION=" + str(args.numeric_version) + "\n")
+ f.write("P4A_MINSDK=" + str(args.min_sdk_version) + "\n")
+
+ # Package up the private data (public not supported).
+ use_setup_py = get_dist_info_for("use_setup_py",
+ error_if_missing=False) is True
+ private_tar_dirs = [env_vars_tarpath]
+ _temp_dirs_to_clean = []
+ try:
+ if args.private:
+ if not use_setup_py or (
+ not exists(join(args.private, "setup.py")) and
+ not exists(join(args.private, "pyproject.toml"))
+ ):
+ print('No setup.py/pyproject.toml used, copying '
+ 'full private data into .apk.')
+ private_tar_dirs.append(args.private)
+ else:
+ print("Copying main.py's ONLY, since other app data is "
+ "expected in site-packages.")
+ main_py_only_dir = tempfile.mkdtemp()
+ _temp_dirs_to_clean.append(main_py_only_dir)
+
+ # Check all main.py files we need to copy:
+ copy_paths = ["main.py", join("service", "main.py")]
+ for copy_path in copy_paths:
+ variants = [
+ copy_path,
+ copy_path.partition(".")[0] + ".pyc",
+ ]
+ # Check in all variants with all possible endings:
+ for variant in variants:
+ if exists(join(args.private, variant)):
+ # Make sure surrounding directly exists:
+ dir_path = os.path.dirname(variant)
+ if (len(dir_path) > 0 and
+ not exists(
+ join(main_py_only_dir, dir_path)
+ )):
+ ensure_dir(join(main_py_only_dir, dir_path))
+ # Copy actual file:
+ shutil.copyfile(
+ join(args.private, variant),
+ join(main_py_only_dir, variant),
+ )
+
+ # Append directory with all main.py's to result apk paths:
+ private_tar_dirs.append(main_py_only_dir)
+ if get_bootstrap_name() == "webview":
+ for asset in listdir('webview_includes'):
+ shutil.copy(join('webview_includes', asset), join(assets_dir, asset))
+
+ for asset in args.assets:
+ asset_src, asset_dest = asset.split(":")
+ if isfile(realpath(asset_src)):
+ ensure_dir(dirname(join(assets_dir, asset_dest)))
+ shutil.copy(realpath(asset_src), join(assets_dir, asset_dest))
+ else:
+ shutil.copytree(realpath(asset_src), join(assets_dir, asset_dest))
+
+ if args.private or args.launcher:
+ for arch in get_dist_info_for("archs"):
+ libs_dir = f"libs/{arch}"
+ make_tar(
+ join(libs_dir, "libpybundle.so"),
+ [f"_python_bundle__{arch}"],
+ byte_compile_python=args.byte_compile_python,
+ optimize_python=args.optimize_python,
+ )
+ make_tar(
+ join(assets_dir, "private.tar"),
+ private_tar_dirs,
+ byte_compile_python=args.byte_compile_python,
+ optimize_python=args.optimize_python,
+ )
+ finally:
+ for directory in _temp_dirs_to_clean:
+ rmdir(directory)
+
+ # Remove extra env vars tar-able directory:
+ rmdir(env_vars_tarpath)
+
+ # Prepare some variables for templating process
+ res_dir = "src/main/res"
+ res_dir_initial = "src/res_initial"
+ # make res_dir stateless
+ if exists(res_dir_initial):
+ rmdir(res_dir, ignore_errors=True)
+ shutil.copytree(res_dir_initial, res_dir)
+ else:
+ shutil.copytree(res_dir, res_dir_initial)
+
+ # Add user resouces
+ for resource in args.resources:
+ resource_src, resource_dest = resource.split(":")
+ if isfile(realpath(resource_src)):
+ ensure_dir(dirname(join(res_dir, resource_dest)))
+ shutil.copy(realpath(resource_src), join(res_dir, resource_dest))
+ else:
+ shutil.copytree(realpath(resource_src),
+ join(res_dir, resource_dest), dirs_exist_ok=True)
+
+ default_icon = 'templates/kivy-icon.png'
+ default_presplash = 'templates/kivy-presplash.jpg'
+ shutil.copy(
+ args.icon or default_icon,
+ join(res_dir, 'mipmap/icon.png')
+ )
+ if args.icon_fg and args.icon_bg:
+ shutil.copy(args.icon_fg, join(res_dir, 'mipmap/icon_foreground.png'))
+ shutil.copy(args.icon_bg, join(res_dir, 'mipmap/icon_background.png'))
+ with open(join(res_dir, 'mipmap-anydpi-v26/icon.xml'), "w") as fd:
+ fd.write("""<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@mipmap/icon_background"/>
+ <foreground android:drawable="@mipmap/icon_foreground"/>
+</adaptive-icon>
+""")
+ elif args.icon_fg or args.icon_bg:
+ print("WARNING: Received an --icon_fg or an --icon_bg argument, but not both. "
+ "Ignoring.")
+
+ if get_bootstrap_name() != "service_only":
+ lottie_splashscreen = join(res_dir, 'raw/splashscreen.json')
+ if args.presplash_lottie:
+ shutil.copy(
+ 'templates/lottie.xml',
+ join(res_dir, 'layout/lottie.xml')
+ )
+ ensure_dir(join(res_dir, 'raw'))
+ shutil.copy(
+ args.presplash_lottie,
+ join(res_dir, 'raw/splashscreen.json')
+ )
+ else:
+ if exists(lottie_splashscreen):
+ remove(lottie_splashscreen)
+ remove(join(res_dir, 'layout/lottie.xml'))
+
+ shutil.copy(
+ args.presplash or default_presplash,
+ join(res_dir, 'drawable/presplash.jpg')
+ )
+
+ # If extra Java jars were requested, copy them into the libs directory
+ jars = []
+ if args.add_jar:
+ for jarname in args.add_jar:
+ if not exists(jarname):
+ print('Requested jar does not exist: {}'.format(jarname))
+ sys.exit(-1)
+ shutil.copy(jarname, 'src/main/libs')
+ jars.append(basename(jarname))
+
+ # If extra aar were requested, copy them into the libs directory
+ aars = []
+ if args.add_aar:
+ ensure_dir("libs")
+ for aarname in args.add_aar:
+ if not exists(aarname):
+ print('Requested aar does not exists: {}'.format(aarname))
+ sys.exit(-1)
+ shutil.copy(aarname, 'libs')
+ aars.append(basename(aarname).rsplit('.', 1)[0])
+
+ versioned_name = (args.name.replace(' ', '').replace('\'', '') +
+ '-' + args.version)
+
+ version_code = 0
+ if not args.numeric_version:
+ """
+ Set version code in format (10 + minsdk + app_version)
+ Historically versioning was (arch + minsdk + app_version),
+ with arch expressed with a single digit from 6 to 9.
+ Since the multi-arch support, has been changed to 10.
+ """
+ min_sdk = args.min_sdk_version
+ for i in args.version.split('.'):
+ version_code *= 100
+ version_code += int(i)
+ args.numeric_version = "{}{}{}".format("10", min_sdk, version_code)
+
+ if args.intent_filters:
+ with open(args.intent_filters) as fd:
+ args.intent_filters = fd.read()
+
+ if not args.add_activity:
+ args.add_activity = []
+
+ if not args.activity_launch_mode:
+ args.activity_launch_mode = ''
+
+ if args.extra_source_dirs:
+ esd = []
+ for spec in args.extra_source_dirs:
+ if ':' in spec:
+ specdir, specincludes = spec.split(':')
+ print('WARNING: Currently gradle builds only support including source '
+ 'directories, so when building using gradle all files in '
+ '{} will be included.'.format(specdir))
+ else:
+ specdir = spec
+ specincludes = '**'
+ esd.append((realpath(specdir), specincludes))
+ args.extra_source_dirs = esd
+ else:
+ args.extra_source_dirs = []
+
+ service = False
+ if args.private:
+ service_main = join(realpath(args.private), 'service', 'main.py')
+ if exists(service_main) or exists(service_main + 'o'):
+ service = True
+
+ service_names = []
+ base_service_class = args.service_class_name.split('.')[-1]
+ for sid, spec in enumerate(args.services):
+ spec = spec.split(':')
+ name = spec[0]
+ entrypoint = spec[1]
+ options = spec[2:]
+
+ foreground = 'foreground' in options
+ sticky = 'sticky' in options
+
+ service_names.append(name)
+ service_target_path =\
+ 'src/main/java/{}/Service{}.java'.format(
+ args.package.replace(".", "/"),
+ name.capitalize()
+ )
+ render(
+ 'Service.tmpl.java',
+ service_target_path,
+ name=name,
+ entrypoint=entrypoint,
+ args=args,
+ foreground=foreground,
+ sticky=sticky,
+ service_id=sid + 1,
+ base_service_class=base_service_class,
+ )
+
+ # Find the SDK directory and target API
+ with open('project.properties', 'r') as fileh:
+ target = fileh.read().strip()
+ android_api = target.split('-')[1]
+
+ if android_api.isdigit():
+ android_api = int(android_api)
+ else:
+ raise ValueError(
+ "failed to extract the Android API level from " +
+ "build.properties. expected int, got: '" +
+ str(android_api) + "'"
+ )
+
+ with open('local.properties', 'r') as fileh:
+ sdk_dir = fileh.read().strip()
+ sdk_dir = sdk_dir[8:]
+
+ # Try to build with the newest available build tools
+ ignored = {".DS_Store", ".ds_store"}
+ build_tools_versions = [x for x in listdir(join(sdk_dir, 'build-tools')) if x not in ignored]
+ build_tools_version = max_build_tool_version(build_tools_versions)
+
+ # Folder name for launcher (used by SDL2 bootstrap)
+ url_scheme = 'kivy'
+
+ # Copy backup rules file if specified and update the argument
+ res_xml_dir = join(res_dir, 'xml')
+ if args.backup_rules:
+ ensure_dir(res_xml_dir)
+ shutil.copy(join(args.private, args.backup_rules), res_xml_dir)
+ args.backup_rules = split(args.backup_rules)[1][:-4]
+
+ # Copy res_xml files to src/main/res/xml
+ if args.res_xmls:
+ ensure_dir(res_xml_dir)
+ for xmlpath in args.res_xmls:
+ if not os.path.exists(xmlpath):
+ xmlpath = join(args.private, xmlpath)
+ shutil.copy(xmlpath, res_xml_dir)
+
+ # Render out android manifest:
+ manifest_path = "src/main/AndroidManifest.xml"
+ render_args = {
+ "args": args,
+ "service": service,
+ "service_names": service_names,
+ "android_api": android_api,
+ "debug": "debug" in args.build_mode,
+ "native_services": args.native_services
+ }
+ if get_bootstrap_name() == "sdl2":
+ render_args["url_scheme"] = url_scheme
+
+ render(
+ 'AndroidManifest.tmpl.xml',
+ manifest_path,
+ **render_args)
+
+ # Copy the AndroidManifest.xml to the dist root dir so that ant
+ # can also use it
+ if exists('AndroidManifest.xml'):
+ remove('AndroidManifest.xml')
+ shutil.copy(manifest_path, 'AndroidManifest.xml')
+
+ # gradle build templates
+ render(
+ 'build.tmpl.gradle',
+ 'build.gradle',
+ args=args,
+ aars=aars,
+ jars=jars,
+ android_api=android_api,
+ build_tools_version=build_tools_version,
+ debug_build="debug" in args.build_mode,
+ is_library=(get_bootstrap_name() == 'service_library'),
+ )
+
+ # gradle properties
+ render(
+ 'gradle.tmpl.properties',
+ 'gradle.properties',
+ args=args,
+ bootstrap_name=get_bootstrap_name())
+
+ # ant build templates
+ render(
+ 'build.tmpl.xml',
+ 'build.xml',
+ args=args,
+ versioned_name=versioned_name)
+
+ # String resources:
+ timestamp = time.time()
+ if 'SOURCE_DATE_EPOCH' in environ:
+ # for reproducible builds
+ timestamp = int(environ['SOURCE_DATE_EPOCH'])
+ private_version = "{} {} {}".format(
+ args.version,
+ args.numeric_version,
+ timestamp
+ )
+ render_args = {
+ "args": args,
+ "private_version": hashlib.sha1(private_version.encode()).hexdigest()
+ }
+ if get_bootstrap_name() == "sdl2":
+ render_args["url_scheme"] = url_scheme
+ render(
+ 'strings.tmpl.xml',
+ join(res_dir, 'values/strings.xml'),
+ **render_args)
+
+ # Library resources from Qt
+ # These are referred by QtLoader.java in Qt6AndroidBindings.jar
+ # qt_libs and load_local_libs are loaded at App startup
+ if get_bootstrap_name() == "qt":
+ qt_libs = args.qt_libs.split(",")
+ load_local_libs = args.load_local_libs.split(",")
+ init_classes = args.init_classes
+ if init_classes:
+ init_classes = init_classes.split(",")
+ init_classes = ":".join(init_classes)
+ arch = get_dist_info_for("archs")[0]
+ render(
+ 'libs.tmpl.xml',
+ join(res_dir, 'values/libs.xml'),
+ qt_libs=qt_libs,
+ load_local_libs=load_local_libs,
+ init_classes=init_classes,
+ arch=arch
+ )
+
+ if exists(join("templates", "custom_rules.tmpl.xml")):
+ render(
+ 'custom_rules.tmpl.xml',
+ 'custom_rules.xml',
+ args=args)
+
+ if get_bootstrap_name() == "webview":
+ render('WebViewLoader.tmpl.java',
+ 'src/main/java/org/kivy/android/WebViewLoader.java',
+ args=args)
+
+ if args.sign:
+ render('build.properties', 'build.properties')
+ else:
+ if exists('build.properties'):
+ os.remove('build.properties')
+
+ # Apply java source patches if any are present:
+ if exists(join('src', 'patches')):
+ print("Applying Java source code patches...")
+ for patch_name in os.listdir(join('src', 'patches')):
+ patch_path = join('src', 'patches', patch_name)
+ print("Applying patch: " + str(patch_path))
+
+ # -N: insist this is FORWARD patch, don't reverse apply
+ # -p1: strip first path component
+ # -t: batch mode, don't ask questions
+ patch_command = ["patch", "-N", "-p1", "-t", "-i", patch_path]
+
+ try:
+ # Use a dry run to establish whether the patch is already applied.
+ # If we don't check this, the patch may be partially applied (which is bad!)
+ subprocess.check_output(patch_command + ["--dry-run"])
+ except subprocess.CalledProcessError as e:
+ if e.returncode == 1:
+ # Return code 1 means not all hunks could be applied, this usually
+ # means the patch is already applied.
+ print("Warning: failed to apply patch (exit code 1), "
+ "assuming it is already applied: ",
+ str(patch_path))
+ else:
+ raise e
+ else:
+ # The dry run worked, so do the real thing
+ subprocess.check_output(patch_command)
+
+
+def parse_permissions(args_permissions):
+ if args_permissions and isinstance(args_permissions[0], list):
+ args_permissions = [p for perm in args_permissions for p in perm]
+
+ def _is_advanced_permission(permission):
+ return permission.startswith("(") and permission.endswith(")")
+
+ def _decode_advanced_permission(permission):
+ SUPPORTED_PERMISSION_PROPERTIES = ["name", "maxSdkVersion", "usesPermissionFlags"]
+ _permission_args = permission[1:-1].split(";")
+ _permission_args = (arg.split("=") for arg in _permission_args)
+ advanced_permission = dict(_permission_args)
+
+ if "name" not in advanced_permission:
+ raise ValueError("Advanced permission must have a name property")
+
+ for key in advanced_permission.keys():
+ if key not in SUPPORTED_PERMISSION_PROPERTIES:
+ raise ValueError(
+ f"Property '{key}' is not supported. "
+ "Advanced permission only supports: "
+ f"{', '.join(SUPPORTED_PERMISSION_PROPERTIES)} properties"
+ )
+
+ return advanced_permission
+
+ _permissions = []
+ for permission in args_permissions:
+ if _is_advanced_permission(permission):
+ _permissions.append(_decode_advanced_permission(permission))
+ else:
+ if "." in permission:
+ _permissions.append(dict(name=permission))
+ else:
+ _permissions.append(dict(name=f"android.permission.{permission}"))
+ return _permissions
+
+
+def get_sdl_orientation_hint(orientations):
+ SDL_ORIENTATION_MAP = {
+ "landscape": "LandscapeLeft",
+ "portrait": "Portrait",
+ "portrait-reverse": "PortraitUpsideDown",
+ "landscape-reverse": "LandscapeRight",
+ }
+ return " ".join(
+ [SDL_ORIENTATION_MAP[x] for x in orientations if x in SDL_ORIENTATION_MAP]
+ )
+
+
+def get_manifest_orientation(orientations, manifest_orientation=None):
+ # If the user has specifically set an orientation to use in the manifest,
+ # use that.
+ if manifest_orientation is not None:
+ return manifest_orientation
+
+ # If multiple or no orientations are specified, use unspecified in the manifest,
+ # as we can only specify one orientation in the manifest.
+ if len(orientations) != 1:
+ return "unspecified"
+
+ # Convert the orientation to a value that can be used in the manifest.
+ # If the specified orientation is not supported, use unspecified.
+ MANIFEST_ORIENTATION_MAP = {
+ "landscape": "landscape",
+ "portrait": "portrait",
+ "portrait-reverse": "reversePortrait",
+ "landscape-reverse": "reverseLandscape",
+ }
+ return MANIFEST_ORIENTATION_MAP.get(orientations[0], "unspecified")
+
+
+def get_dist_ndk_min_api_level():
+ # Get the default minsdk, equal to the NDK API that this dist is built against
+ try:
+ with open('dist_info.json', 'r') as fileh:
+ info = json.load(fileh)
+ ndk_api = int(info['ndk_api'])
+ except (OSError, KeyError, ValueError, TypeError):
+ print('WARNING: Failed to read ndk_api from dist info, defaulting to 12')
+ ndk_api = 12 # The old default before ndk_api was introduced
+ return ndk_api
+
+
+def create_argument_parser():
+ ndk_api = get_dist_ndk_min_api_level()
+ import argparse
+ ap = argparse.ArgumentParser(description='''\
+Package a Python application for Android (using
+bootstrap ''' + get_bootstrap_name() + ''').
+
+For this to work, Java and Ant need to be in your path, as does the
+tools directory of the Android SDK.
+''')
+
+ # --private is required unless for sdl2, where there's also --launcher
+ ap.add_argument('--private', dest='private',
+ help='the directory with the app source code files' +
+ ' (containing your main.py entrypoint)',
+ required=(get_bootstrap_name() != "sdl2"))
+ ap.add_argument('--package', dest='package',
+ help=('The name of the java package the project will be'
+ ' packaged under.'),
+ required=True)
+ ap.add_argument('--name', dest='name',
+ help=('The human-readable name of the project.'),
+ required=True)
+ ap.add_argument('--numeric-version', dest='numeric_version',
+ help=('The numeric version number of the project. If not '
+ 'given, this is automatically computed from the '
+ 'version.'))
+ ap.add_argument('--version', dest='version',
+ help=('The version number of the project. This should '
+ 'consist of numbers and dots, and should have the '
+ 'same number of groups of numbers as previous '
+ 'versions.'),
+ required=True)
+ if get_bootstrap_name() == "sdl2":
+ ap.add_argument('--launcher', dest='launcher', action='store_true',
+ help=('Provide this argument to build a multi-app '
+ 'launcher, rather than a single app.'))
+ ap.add_argument('--home-app', dest='home_app', action='store_true', default=False,
+ help=('Turn your application into a home app (launcher)'))
+ ap.add_argument('--permission', dest='permissions', action='append', default=[],
+ help='The permissions to give this app.', nargs='+')
+ ap.add_argument('--meta-data', dest='meta_data', action='append', default=[],
+ help='Custom key=value to add in application metadata')
+ ap.add_argument('--uses-library', dest='android_used_libs', action='append', default=[],
+ help='Used shared libraries included using <uses-library> tag in AndroidManifest.xml')
+ ap.add_argument('--asset', dest='assets',
+ action="append", default=[],
+ metavar="/path/to/source:dest",
+ help='Put this in the assets folder at assets/dest')
+ ap.add_argument('--resource', dest='resources',
+ action="append", default=[],
+ metavar="/path/to/source:kind/asset",
+ help='Put this in the res folder at res/kind')
+ ap.add_argument('--icon', dest='icon',
+ help=('A png file to use as the icon for '
+ 'the application.'))
+ ap.add_argument('--icon-fg', dest='icon_fg',
+ help=('A png file to use as the foreground of the adaptive icon '
+ 'for the application.'))
+ ap.add_argument('--icon-bg', dest='icon_bg',
+ help=('A png file to use as the background of the adaptive icon '
+ 'for the application.'))
+ ap.add_argument('--service', dest='services', action='append', default=[],
+ help='Declare a new service entrypoint: '
+ 'NAME:PATH_TO_PY[:foreground]')
+ ap.add_argument('--native-service', dest='native_services', action='append', default=[],
+ help='Declare a new native service: '
+ 'package.name.service')
+ if get_bootstrap_name() != "service_only":
+ ap.add_argument('--presplash', dest='presplash',
+ help=('A jpeg file to use as a screen while the '
+ 'application is loading.'))
+ ap.add_argument('--presplash-lottie', dest='presplash_lottie',
+ help=('A lottie (json) file to use as an animation while the '
+ 'application is loading.'))
+ ap.add_argument('--presplash-color',
+ dest='presplash_color',
+ default='#000000',
+ help=('A string to set the loading screen '
+ 'background color. '
+ 'Supported formats are: '
+ '#RRGGBB #AARRGGBB or color names '
+ 'like red, green, blue, etc.'))
+ ap.add_argument('--window', dest='window', action='store_true',
+ default=False,
+ help='Indicate if the application will be windowed')
+ ap.add_argument('--manifest-orientation', dest='manifest_orientation',
+ help=('The orientation that will be set in the '
+ 'android:screenOrientation attribute of the activity '
+ 'in the AndroidManifest.xml file. If not set, '
+ 'the value will be synthesized from the --orientation option.'))
+ ap.add_argument('--orientation', dest='orientation',
+ action="append", default=[],
+ choices=['portrait', 'landscape', 'landscape-reverse', 'portrait-reverse'],
+ help=('The orientations that the app will display in. '
+ 'Since Android ignores android:screenOrientation '
+ 'when in multi-window mode (Which is the default on Android 12+), '
+ 'this option will also set the window orientation hints '
+ 'for apps using the (default) SDL bootstrap.'
+ 'If multiple orientations are given, android:screenOrientation '
+ 'will be set to "unspecified"'))
+
+ ap.add_argument('--enable-androidx', dest='enable_androidx',
+ action='store_true',
+ help=('Enable the AndroidX support library, '
+ 'requires api = 28 or greater'))
+ ap.add_argument('--android-entrypoint', dest='android_entrypoint',
+ default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS,
+ help='Defines which java class will be used for startup, usually a subclass of PythonActivity')
+ ap.add_argument('--android-apptheme', dest='android_apptheme',
+ default='@android:style/Theme.NoTitleBar',
+ help='Defines which app theme should be selected for the main activity')
+ ap.add_argument('--add-compile-option', dest='compile_options', default=[],
+ action='append', help='add compile options to gradle.build')
+ ap.add_argument('--add-gradle-repository', dest='gradle_repositories',
+ default=[],
+ action='append',
+ help='Ddd a repository for gradle')
+ ap.add_argument('--add-packaging-option', dest='packaging_options',
+ default=[],
+ action='append',
+ help='Dndroid packaging options')
+
+ ap.add_argument('--wakelock', dest='wakelock', action='store_true',
+ help=('Indicate if the application needs the device '
+ 'to stay on'))
+ ap.add_argument('--blacklist', dest='blacklist',
+ default=join(curdir, 'blacklist.txt'),
+ help=('Use a blacklist file to match unwanted file in '
+ 'the final APK'))
+ ap.add_argument('--whitelist', dest='whitelist',
+ default=join(curdir, 'whitelist.txt'),
+ help=('Use a whitelist file to prevent blacklisting of '
+ 'file in the final APK'))
+ ap.add_argument('--release', dest='build_mode', action='store_const',
+ const='release', default='debug',
+ help='Build your app as a non-debug release build. '
+ '(Disables gdb debugging among other things)')
+ ap.add_argument('--with-debug-symbols', dest='with_debug_symbols',
+ action='store_const', const=True, default=False,
+ help='Will keep debug symbols from `.so` files.')
+ ap.add_argument('--add-jar', dest='add_jar', action='append',
+ help=('Add a Java .jar to the libs, so you can access its '
+ 'classes with pyjnius. You can specify this '
+ 'argument more than once to include multiple jars'))
+ ap.add_argument('--add-aar', dest='add_aar', action='append',
+ help=('Add an aar dependency manually'))
+ ap.add_argument('--depend', dest='depends', action='append',
+ help=('Add a external dependency '
+ '(eg: com.android.support:appcompat-v7:19.0.1)'))
+ # The --sdk option has been removed, it is ignored in favour of
+ # --android-api handled by toolchain.py
+ ap.add_argument('--sdk', dest='sdk_version', default=-1,
+ type=int, help=('Deprecated argument, does nothing'))
+ ap.add_argument('--minsdk', dest='min_sdk_version',
+ default=ndk_api, type=int,
+ help=('Minimum Android SDK version that the app supports. '
+ 'Defaults to {}.'.format(ndk_api)))
+ ap.add_argument('--allow-minsdk-ndkapi-mismatch', default=False,
+ action='store_true',
+ help=('Allow the --minsdk argument to be different from '
+ 'the discovered ndk_api in the dist'))
+ ap.add_argument('--intent-filters', dest='intent_filters',
+ help=('Add intent-filters xml rules to the '
+ 'AndroidManifest.xml file. The argument is a '
+ 'filename containing xml. The filename should be '
+ 'located relative to the python-for-android '
+ 'directory'))
+ ap.add_argument('--res_xml', dest='res_xmls', action='append', default=[],
+ help='Add files to res/xml directory (for example device-filters)', nargs='+')
+ ap.add_argument('--with-billing', dest='billing_pubkey',
+ help='If set, the billing service will be added (not implemented)')
+ ap.add_argument('--add-source', dest='extra_source_dirs', action='append',
+ help='Include additional source dirs in Java build')
+ if get_bootstrap_name() == "webview":
+ ap.add_argument('--port',
+ help='The port on localhost that the WebView will access',
+ default='5000')
+ ap.add_argument('--try-system-python-compile', dest='try_system_python_compile',
+ action='store_true',
+ help='Use the system python during compileall if possible.')
+ ap.add_argument('--sign', action='store_true',
+ help=('Try to sign the APK with your credentials. You must set '
+ 'the appropriate environment variables.'))
+ ap.add_argument('--add-activity', dest='add_activity', action='append',
+ help='Add this Java class as an Activity to the manifest.')
+ ap.add_argument('--activity-launch-mode',
+ dest='activity_launch_mode',
+ default='singleTask',
+ help='Set the launch mode of the main activity in the manifest.')
+ ap.add_argument('--allow-backup', dest='allow_backup', default='true',
+ help="if set to 'false', then android won't backup the application.")
+ ap.add_argument('--backup-rules', dest='backup_rules', default='',
+ help=('Backup rules for Android Auto Backup. Argument is a '
+ 'filename containing xml. The filename should be '
+ 'located relative to the private directory containing your source code '
+ 'files (containing your main.py entrypoint). '
+ 'See https://developer.android.com/guide/topics/data/'
+ 'autobackup#IncludingFiles for more information'))
+ ap.add_argument('--no-byte-compile-python', dest='byte_compile_python',
+ action='store_false', default=True,
+ help='Skip byte compile for .py files.')
+ ap.add_argument('--no-optimize-python', dest='optimize_python',
+ action='store_false', default=True,
+ help=('Whether to compile to optimised .pyc files, using -OO '
+ '(strips docstrings and asserts)'))
+ ap.add_argument('--extra-manifest-xml', default='',
+ help=('Extra xml to write directly inside the <manifest> element of'
+ 'AndroidManifest.xml'))
+ ap.add_argument('--extra-manifest-application-arguments', default='',
+ help='Extra arguments to be added to the <manifest><application> tag of'
+ 'AndroidManifest.xml')
+ ap.add_argument('--manifest-placeholders', dest='manifest_placeholders',
+ default='[:]', help=('Inject build variables into the manifest '
+ 'via the manifestPlaceholders property'))
+ ap.add_argument('--service-class-name', dest='service_class_name', default=DEFAULT_PYTHON_SERVICE_JAVA_CLASS,
+ help='Use that parameter if you need to implement your own PythonServive Java class')
+ ap.add_argument('--activity-class-name', dest='activity_class_name', default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS,
+ help='The full java class name of the main activity')
+ if get_bootstrap_name() == "qt":
+ ap.add_argument('--qt-libs', dest='qt_libs', required=True,
+ help='comma separated list of Qt libraries to be loaded')
+ ap.add_argument('--load-local-libs', dest='load_local_libs', required=True,
+ help='comma separated list of Qt plugin libraries to be loaded')
+ ap.add_argument('--init-classes', dest='init_classes', default='',
+ help='comma separated list of java class names to be loaded from the Qt jar files, '
+ 'specified through add_jar cli option')
+
+ return ap
+
+
+def parse_args_and_make_package(args=None):
+ global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
+
+ ndk_api = get_dist_ndk_min_api_level()
+ ap = create_argument_parser()
+
+ # Put together arguments, and add those from .p4a config file:
+ if args is None:
+ args = sys.argv[1:]
+
+ def _read_configuration():
+ if not exists(".p4a"):
+ return
+ print("Reading .p4a configuration")
+ with open(".p4a") as fd:
+ lines = fd.readlines()
+ lines = [shlex.split(line)
+ for line in lines if not line.startswith("#")]
+ for line in lines:
+ for arg in line:
+ args.append(arg)
+ _read_configuration()
+
+ args = ap.parse_args(args)
+
+ if args.name and args.name[0] == '"' and args.name[-1] == '"':
+ args.name = args.name[1:-1]
+
+ if ndk_api != args.min_sdk_version:
+ print(('WARNING: --minsdk argument does not match the api that is '
+ 'compiled against. Only proceed if you know what you are '
+ 'doing, otherwise use --minsdk={} or recompile against api '
+ '{}').format(ndk_api, args.min_sdk_version))
+ if not args.allow_minsdk_ndkapi_mismatch:
+ print('You must pass --allow-minsdk-ndkapi-mismatch to build '
+ 'with --minsdk different to the target NDK api from the '
+ 'build step')
+ sys.exit(1)
+ else:
+ print('Proceeding with --minsdk not matching build target api')
+
+ if args.billing_pubkey:
+ print('Billing not yet supported!')
+ sys.exit(1)
+
+ if args.sdk_version != -1:
+ print('WARNING: Received a --sdk argument, but this argument is '
+ 'deprecated and does nothing.')
+ args.sdk_version = -1 # ensure it is not used
+
+ args.permissions = parse_permissions(args.permissions)
+
+ args.manifest_orientation = get_manifest_orientation(
+ args.orientation, args.manifest_orientation
+ )
+
+ if get_bootstrap_name() == "sdl2":
+ args.sdl_orientation_hint = get_sdl_orientation_hint(args.orientation)
+
+ if args.res_xmls and isinstance(args.res_xmls[0], list):
+ args.res_xmls = [x for res in args.res_xmls for x in res]
+
+ if args.try_system_python_compile:
+ # Hardcoding python2.7 is okay for now, as python3 skips the
+ # compilation anyway
+ python_executable = 'python2.7'
+ try:
+ subprocess.call([python_executable, '--version'])
+ except (OSError, subprocess.CalledProcessError):
+ pass
+ else:
+ PYTHON = python_executable
+
+ if args.blacklist:
+ with open(args.blacklist) as fd:
+ patterns = [x.strip() for x in fd.read().splitlines()
+ if x.strip() and not x.strip().startswith('#')]
+ BLACKLIST_PATTERNS += patterns
+
+ if args.whitelist:
+ with open(args.whitelist) as fd:
+ patterns = [x.strip() for x in fd.read().splitlines()
+ if x.strip() and not x.strip().startswith('#')]
+ WHITELIST_PATTERNS += patterns
+
+ if args.private is None and \
+ get_bootstrap_name() == 'sdl2' and args.launcher is None:
+ print('Need --private directory or ' +
+ '--launcher (SDL2 bootstrap only)' +
+ 'to have something to launch inside the .apk!')
+ sys.exit(1)
+ make_package(args)
+
+ return args
+
+
+if __name__ == "__main__":
+ parse_args_and_make_package()
diff --git a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar b/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.jar
rename to pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.jar
diff --git a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties b/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties
similarity index 93%
rename from pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties
rename to pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties
index ac1799fa2..8f174bc31 100644
--- a/pythonforandroid/bootstraps/sdl2/build/gradle/wrapper/gradle-wrapper.properties
+++ b/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
diff --git a/pythonforandroid/bootstraps/sdl2/build/gradlew b/pythonforandroid/bootstraps/common/build/gradlew
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/gradlew
rename to pythonforandroid/bootstraps/common/build/gradlew
diff --git a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat b/pythonforandroid/bootstraps/common/build/gradlew.bat
similarity index 96%
rename from pythonforandroid/bootstraps/sdl2/build/gradlew.bat
rename to pythonforandroid/bootstraps/common/build/gradlew.bat
index aec99730b..8a0b282aa 100644
--- a/pythonforandroid/bootstraps/sdl2/build/gradlew.bat
+++ b/pythonforandroid/bootstraps/common/build/gradlew.bat
@@ -1,90 +1,90 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/Android.mk b/pythonforandroid/bootstraps/common/build/jni/Android.mk
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/jni/Android.mk
rename to pythonforandroid/bootstraps/common/build/jni/Android.mk
diff --git a/pythonforandroid/bootstraps/common/build/jni/application/Android.mk b/pythonforandroid/bootstraps/common/build/jni/application/Android.mk
new file mode 100644
index 000000000..5053e7d64
--- /dev/null
+++ b/pythonforandroid/bootstraps/common/build/jni/application/Android.mk
@@ -0,0 +1 @@
+include $(call all-subdir-makefiles)
diff --git a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk
new file mode 100644
index 000000000..fb2b17719
--- /dev/null
+++ b/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk
@@ -0,0 +1,23 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := main
+
+SDL_PATH := ../../SDL
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
+
+# Add your application source files here...
+LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
+ start.c
+
+LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
+
+LOCAL_SHARED_LIBRARIES := SDL2 python_shared
+
+LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS)
+
+LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/bootstraps/webview/build/jni/src/start.c b/pythonforandroid/bootstraps/common/build/jni/application/src/start.c
similarity index 56%
rename from pythonforandroid/bootstraps/webview/build/jni/src/start.c
rename to pythonforandroid/bootstraps/common/build/jni/application/src/start.c
index 34372d2ee..ce93ca27f 100644
--- a/pythonforandroid/bootstraps/webview/build/jni/src/start.c
+++ b/pythonforandroid/bootstraps/common/build/jni/application/src/start.c
@@ -14,6 +14,12 @@
#include <sys/types.h>
#include <errno.h>
+#include "bootstrap_name.h"
+
+#ifndef BOOTSTRAP_USES_NO_SDL_HEADERS
+#include "SDL.h"
+#include "SDL_opengles2.h"
+#endif
#include "android/log.h"
#define ENTRYPOINT_MAXLEN 128
@@ -57,7 +63,7 @@ int dir_exists(char *filename) {
int file_exists(const char *filename) {
FILE *file;
- if (file = fopen(filename, "r")) {
+ if ((file = fopen(filename, "r"))) {
fclose(file);
return 1;
}
@@ -74,25 +80,79 @@ int main(int argc, char *argv[]) {
int ret = 0;
FILE *fd;
- /* AND: Several filepaths are hardcoded here, these must be made
- configurable */
- /* AND: P4A uses env vars...not sure what's best */
- LOGP("Initialize Python for Android");
+ LOGP("Initializing Python for Android");
+
+ // Set a couple of built-in environment vars:
+ setenv("P4A_BOOTSTRAP", bootstrap_name, 1); // env var to identify p4a to applications
env_argument = getenv("ANDROID_ARGUMENT");
setenv("ANDROID_APP_PATH", env_argument, 1);
env_entrypoint = getenv("ANDROID_ENTRYPOINT");
env_logname = getenv("PYTHON_NAME");
-
+ if (!getenv("ANDROID_UNPACK")) {
+ /* ANDROID_UNPACK currently isn't set in services */
+ setenv("ANDROID_UNPACK", env_argument, 1);
+ }
if (env_logname == NULL) {
env_logname = "python";
setenv("PYTHON_NAME", "python", 1);
}
+ // Set additional file-provided environment vars:
+ LOGP("Setting additional env vars from p4a_env_vars.txt");
+ char env_file_path[256];
+ snprintf(env_file_path, sizeof(env_file_path),
+ "%s/p4a_env_vars.txt", getenv("ANDROID_UNPACK"));
+ FILE *env_file_fd = fopen(env_file_path, "r");
+ if (env_file_fd) {
+ char* line = NULL;
+ size_t len = 0;
+ while (getline(&line, &len, env_file_fd) != -1) {
+ if (strlen(line) > 0) {
+ char *eqsubstr = strstr(line, "=");
+ if (eqsubstr) {
+ size_t eq_pos = eqsubstr - line;
+
+ // Extract name:
+ char env_name[256];
+ strncpy(env_name, line, sizeof(env_name));
+ env_name[eq_pos] = '\0';
+
+ // Extract value (with line break removed:
+ char env_value[256];
+ strncpy(env_value, (char*)(line + eq_pos + 1), sizeof(env_value));
+ if (strlen(env_value) > 0 &&
+ env_value[strlen(env_value)-1] == '\n') {
+ env_value[strlen(env_value)-1] = '\0';
+ if (strlen(env_value) > 0 &&
+ env_value[strlen(env_value)-1] == '\r') {
+ // Also remove windows line breaks (\r\n)
+ env_value[strlen(env_value)-1] = '\0';
+ }
+ }
+
+ // Set value:
+ setenv(env_name, env_value, 1);
+ }
+ }
+ }
+ fclose(env_file_fd);
+ } else {
+ LOGP("Warning: no p4a_env_vars.txt found / failed to open!");
+ }
+
LOGP("Changing directory to the one provided by ANDROID_ARGUMENT");
LOGP(env_argument);
chdir(env_argument);
+#if PY_MAJOR_VERSION < 3
+ Py_NoSiteFlag=1;
+#endif
+
+#if PY_MAJOR_VERSION < 3
+ Py_SetProgramName("android_python");
+#else
Py_SetProgramName(L"android_python");
+#endif
#if PY_MAJOR_VERSION >= 3
/* our logging module for android
@@ -102,37 +162,33 @@ int main(int argc, char *argv[]) {
LOGP("Preparing to initialize python");
- if (dir_exists("crystax_python/")) {
- LOGP("crystax_python exists");
- char paths[256];
+ // Set up the python path
+ char paths[256];
+
+ char python_bundle_dir[256];
+ snprintf(python_bundle_dir, 256,
+ "%s/_python_bundle", getenv("ANDROID_UNPACK"));
+ if (dir_exists(python_bundle_dir)) {
+ LOGP("_python_bundle dir exists");
snprintf(paths, 256,
- "%s/crystax_python/stdlib.zip:%s/crystax_python/modules",
- env_argument, env_argument);
- /* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument,
- * env_argument); */
+ "%s/stdlib.zip:%s/modules",
+ python_bundle_dir, python_bundle_dir);
+
LOGP("calculated paths to be...");
LOGP(paths);
-#if PY_MAJOR_VERSION >= 3
- wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
- Py_SetPath(wchar_paths);
-#else
- char *wchar_paths = paths;
- LOGP("Can't Py_SetPath in python2, so crystax python2 doesn't work yet");
- exit(1);
-#endif
+ #if PY_MAJOR_VERSION >= 3
+ wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
+ Py_SetPath(wchar_paths);
+ #endif
- LOGP("set wchar paths...");
+ LOGP("set wchar paths...");
} else {
- LOGP("crystax_python does not exist");
+ LOGP("_python_bundle does not exist...this not looks good, all python"
+ " recipes should have this folder, should we expect a crash soon?");
}
Py_Initialize();
-
-#if PY_MAJOR_VERSION < 3
- PySys_SetArgv(argc, argv);
-#endif
-
LOGP("Initialized python");
/* ensure threads will work.
@@ -150,25 +206,14 @@ int main(int argc, char *argv[]) {
/* inject our bootstrap code to redirect python stdin/stdout
* replace sys.path with our path
*/
- PyRun_SimpleString("import sys, posix\n");
- if (dir_exists("lib")) {
- /* If we built our own python, set up the paths correctly */
- LOGP("Setting up python from ANDROID_PRIVATE");
- PyRun_SimpleString("private = posix.environ['ANDROID_PRIVATE']\n"
- "argument = posix.environ['ANDROID_ARGUMENT']\n"
- "sys.path[:] = [ \n"
- " private + '/lib/python27.zip', \n"
- " private + '/lib/python2.7/', \n"
- " private + '/lib/python2.7/lib-dynload/', \n"
- " private + '/lib/python2.7/site-packages/', \n"
- " argument ]\n");
- }
+ PyRun_SimpleString("import io, sys, posix\n");
+
+ char add_site_packages_dir[256];
- if (dir_exists("crystax_python")) {
- char add_site_packages_dir[256];
+ if (dir_exists(python_bundle_dir)) {
snprintf(add_site_packages_dir, 256,
- "sys.path.append('%s/crystax_python/site-packages')",
- env_argument);
+ "sys.path.append('%s/site-packages')",
+ python_bundle_dir);
PyRun_SimpleString("import sys\n"
"sys.argv = ['notaninterpreterreally']\n"
@@ -179,17 +224,19 @@ int main(int argc, char *argv[]) {
}
PyRun_SimpleString(
- "class LogFile(object):\n"
+ "class LogFile(io.IOBase):\n"
" def __init__(self):\n"
- " self.buffer = ''\n"
+ " self.__buffer = ''\n"
+ " def readable(self):\n"
+ " return False\n"
+ " def writable(self):\n"
+ " return True\n"
" def write(self, s):\n"
- " s = self.buffer + s\n"
- " lines = s.split(\"\\n\")\n"
+ " s = self.__buffer + s\n"
+ " lines = s.split('\\n')\n"
" for l in lines[:-1]:\n"
- " androidembed.log(l)\n"
- " self.buffer = lines[-1]\n"
- " def flush(self):\n"
- " return\n"
+ " androidembed.log(l.replace('\\x00', ''))\n"
+ " self.__buffer = lines[-1]\n"
"sys.stdout = sys.stderr = LogFile()\n"
"print('Android path', sys.path)\n"
"import os\n"
@@ -206,9 +253,10 @@ int main(int argc, char *argv[]) {
*/
LOGP("Run user program, change dir and execute entrypoint");
- /* Get the entrypoint, search the .pyo then .py
+ /* Get the entrypoint, search the .pyc then .py
*/
char *dot = strrchr(env_entrypoint, '.');
+ char *ext = ".pyc";
if (dot <= 0) {
LOGP("Invalid entrypoint, abort.");
return -1;
@@ -217,24 +265,24 @@ int main(int argc, char *argv[]) {
LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN.");
return -1;
}
- if (!strcmp(dot, ".pyo")) {
+ if (!strcmp(dot, ext)) {
if (!file_exists(env_entrypoint)) {
/* fallback on .py */
strcpy(entrypoint, env_entrypoint);
entrypoint[strlen(env_entrypoint) - 1] = '\0';
LOGP(entrypoint);
if (!file_exists(entrypoint)) {
- LOGP("Entrypoint not found (.pyo, fallback on .py), abort");
+ LOGP("Entrypoint not found (.pyc, fallback on .py), abort");
return -1;
}
} else {
strcpy(entrypoint, env_entrypoint);
}
} else if (!strcmp(dot, ".py")) {
- /* if .py is passed, check the pyo version first */
+ /* if .py is passed, check the pyc version first */
strcpy(entrypoint, env_entrypoint);
entrypoint[strlen(env_entrypoint) + 1] = '\0';
- entrypoint[strlen(env_entrypoint)] = 'o';
+ entrypoint[strlen(env_entrypoint)] = 'c';
if (!file_exists(entrypoint)) {
/* fallback on pure python version */
if (!file_exists(env_entrypoint)) {
@@ -244,7 +292,7 @@ int main(int argc, char *argv[]) {
strcpy(entrypoint, env_entrypoint);
}
} else {
- LOGP("Entrypoint have an invalid extension (must be .py or .pyo), abort.");
+ LOGP("Entrypoint have an invalid extension (must be .py or .pyc), abort.");
return -1;
}
// LOGP("Entrypoint is:");
@@ -259,29 +307,58 @@ int main(int argc, char *argv[]) {
/* run python !
*/
ret = PyRun_SimpleFile(fd, entrypoint);
+ fclose(fd);
if (PyErr_Occurred() != NULL) {
ret = 1;
PyErr_Print(); /* This exits with the right code if SystemExit. */
PyObject *f = PySys_GetObject("stdout");
- if (PyFile_WriteString(
- "\n", f)) /* python2 used Py_FlushLine, but this no longer exists */
+ if (PyFile_WriteString("\n", f))
PyErr_Clear();
}
- /* close everything
+ LOGP("Python for android ended.");
+
+ /* Shut down: since regular shutdown causes issues sometimes
+ (seems to be an incomplete shutdown breaking next launch)
+ we'll use sys.exit(ret) to shutdown, since that one works.
+
+ Reference discussion:
+
+ https://github.com/kivy/kivy/pull/6107#issue-246120816
+ */
+ char terminatecmd[256];
+ snprintf(
+ terminatecmd, sizeof(terminatecmd),
+ "import sys; sys.exit(%d)\n", ret
+ );
+ PyRun_SimpleString(terminatecmd);
+
+ /* This should never actually be reached, but we'll leave the clean-up
+ * here just to be safe.
*/
+#if PY_MAJOR_VERSION < 3
Py_Finalize();
- fclose(fd);
+ LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful.");
+#else
+ if (Py_FinalizeEx() != 0) // properly check success on Python 3
+ LOGP("Unexpectedly reached Py_FinalizeEx(), and got error!");
+ else
+ LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful.");
+#endif
- LOGP("Python for android ended.");
return ret;
}
JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
- JNIEnv *env, jobject thiz, jstring j_android_private,
- jstring j_android_argument, jstring j_service_entrypoint,
- jstring j_python_name, jstring j_python_home, jstring j_python_path,
+ JNIEnv *env,
+ jobject thiz,
+ jstring j_android_private,
+ jstring j_android_argument,
+ jstring j_service_entrypoint,
+ jstring j_python_name,
+ jstring j_python_home,
+ jstring j_python_path,
jstring j_arg) {
jboolean iscopy;
const char *android_private =
@@ -300,12 +377,14 @@ JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
setenv("ANDROID_PRIVATE", android_private, 1);
setenv("ANDROID_ARGUMENT", android_argument, 1);
+ setenv("ANDROID_APP_PATH", android_argument, 1);
setenv("ANDROID_ENTRYPOINT", service_entrypoint, 1);
setenv("PYTHONOPTIMIZE", "2", 1);
setenv("PYTHON_NAME", python_name, 1);
setenv("PYTHONHOME", python_home, 1);
setenv("PYTHONPATH", python_path, 1);
setenv("PYTHON_SERVICE_ARGUMENT", arg, 1);
+ setenv("P4A_BOOTSTRAP", bootstrap_name, 1);
char *argv[] = {"."};
/* ANDROID_ARGUMENT points to service subdir,
@@ -314,19 +393,23 @@ JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
main(1, argv);
}
-void Java_org_kivy_android_PythonActivity_nativeSetEnv(
- JNIEnv* env, jclass jcls,
- jstring j_name, jstring j_value)
-/* JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeSetEnv( */
-/* JNIEnv* env, jclass jcls, */
-/* jstring j_name, jstring j_value) */
+#if defined(BOOTSTRAP_NAME_WEBVIEW) || defined(BOOTSTRAP_NAME_SERVICEONLY)
+// Webview and service_only uses some more functions:
+
+void Java_org_kivy_android_PythonActivity_nativeSetenv(
+ JNIEnv* env, jclass cls,
+ jstring name, jstring value)
+//JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
+// JNIEnv* env, jclass cls,
+// jstring name, jstring value)
{
- jboolean iscopy;
- const char *name = (*env)->GetStringUTFChars(env, j_name, &iscopy);
- const char *value = (*env)->GetStringUTFChars(env, j_value, &iscopy);
- setenv(name, value, 1);
- (*env)->ReleaseStringUTFChars(env, j_name, name);
- (*env)->ReleaseStringUTFChars(env, j_value, value);
+ const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
+ const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
+
+ setenv(utfname, utfvalue, 1);
+
+ (*env)->ReleaseStringUTFChars(env, name, utfname);
+ (*env)->ReleaseStringUTFChars(env, value, utfvalue);
}
@@ -336,20 +419,21 @@ void Java_org_kivy_android_PythonActivity_nativeInit(JNIEnv* env, jclass cls, jo
/* This interface could expand with ABI negotiation, calbacks, etc. */
/* SDL_Android_Init(env, cls); */
-
+
/* SDL_SetMainReady(); */
-
+
/* Run the application code! */
int status;
char *argv[2];
argv[0] = "Python_app";
argv[1] = NULL;
/* status = SDL_main(1, argv); */
-
+
main(1, argv);
-
+
/* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
/* exit(status); */
}
+#endif
#endif
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/Octal.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/Octal.java
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/Octal.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/Octal.java
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarConstants.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarConstants.java
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarConstants.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarConstants.java
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarEntry.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarEntry.java
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarEntry.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarEntry.java
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarHeader.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarHeader.java
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarHeader.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarHeader.java
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarInputStream.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarInputStream.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarOutputStream.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarOutputStream.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarUtils.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarUtils.java
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/src/org/kamranzafar/jtar/TarUtils.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/kamranzafar/jtar/TarUtils.java
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java
similarity index 55%
rename from pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java
index 046dc182f..76d3b2e77 100644
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonService.java
+++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java
@@ -14,10 +14,10 @@
import android.os.Process;
import java.io.File;
-import org.kivy.android.PythonUtil;
-
-import org.renpy.android.Hardware;
-
+//imports for channel definition
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+import android.graphics.Color;
public class PythonService extends Service implements Runnable {
@@ -33,6 +33,8 @@ public class PythonService extends Service implements Runnable {
private String serviceEntrypoint;
// Argument to pass to Python code,
private String pythonServiceArgument;
+
+
public static PythonService mService = null;
private Intent startIntent = null;
@@ -42,10 +44,6 @@ public void setAutoRestartService(boolean restart) {
autoRestartService = restart;
}
- public boolean canDisplayNotification() {
- return true;
- }
-
public int startType() {
return START_NOT_STICKY;
}
@@ -64,10 +62,15 @@ public void onCreate() {
public int onStartCommand(Intent intent, int flags, int startId) {
if (pythonThread != null) {
Log.v("python service", "service exists, do not start again");
- return START_NOT_STICKY;
+ return startType();
+ }
+ //intent is null if OS restarts a STICKY service
+ if (intent == null) {
+ Context context = getApplicationContext();
+ intent = getThisDefaultIntent(context, "");
}
- startIntent = intent;
+ startIntent = intent;
Bundle extras = intent.getExtras();
androidPrivate = extras.getString("androidPrivate");
androidArgument = extras.getString("androidArgument");
@@ -75,48 +78,88 @@ public int onStartCommand(Intent intent, int flags, int startId) {
pythonName = extras.getString("pythonName");
pythonHome = extras.getString("pythonHome");
pythonPath = extras.getString("pythonPath");
+ boolean serviceStartAsForeground = (
+ extras.getString("serviceStartAsForeground").equals("true")
+ );
pythonServiceArgument = extras.getString("pythonServiceArgument");
-
pythonThread = new Thread(this);
pythonThread.start();
- if (canDisplayNotification()) {
+ if (serviceStartAsForeground) {
doStartForeground(extras);
}
return startType();
}
+ protected int getServiceId() {
+ return 1;
+ }
+
+ protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {
+ return null;
+ }
+
protected void doStartForeground(Bundle extras) {
String serviceTitle = extras.getString("serviceTitle");
- String serviceDescription = extras.getString("serviceDescription");
-
+ String smallIconName = extras.getString("smallIconName");
+ String contentTitle = extras.getString("contentTitle");
+ String contentText = extras.getString("contentText");
Notification notification;
Context context = getApplicationContext();
Intent contextIntent = new Intent(context, PythonActivity.class);
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // Unspecified icon uses default.
+ int smallIconId = context.getApplicationInfo().icon;
+ if (smallIconName != null) {
+ if (!smallIconName.equals("")){
+ int resId = getResources().getIdentifier(smallIconName, "mipmap",
+ getPackageName());
+ if (resId ==0) {
+ resId = getResources().getIdentifier(smallIconName, "drawable",
+ getPackageName());
+ }
+ if (resId !=0) {
+ smallIconId = resId;
+ }
+ }
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ // This constructor is deprecated
notification = new Notification(
- context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis());
+ smallIconId, serviceTitle, System.currentTimeMillis());
try {
// prevent using NotificationCompat, this saves 100kb on apk
Method func = notification.getClass().getMethod(
"setLatestEventInfo", Context.class, CharSequence.class,
CharSequence.class, PendingIntent.class);
- func.invoke(notification, context, serviceTitle, serviceDescription, pIntent);
+ func.invoke(notification, context, contentTitle, contentText, pIntent);
} catch (NoSuchMethodException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException e) {
}
} else {
- Notification.Builder builder = new Notification.Builder(context);
- builder.setContentTitle(serviceTitle);
- builder.setContentText(serviceDescription);
+ // for android 8+ we need to create our own channel
+ // https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1
+ String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a" + getServiceId();
+ String channelName = "Background Service" + getServiceId();
+ NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
+
+ chan.setLightColor(Color.BLUE);
+ chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+ NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.createNotificationChannel(chan);
+
+ Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
+ builder.setContentTitle(contentTitle);
+ builder.setContentText(contentText);
builder.setContentIntent(pIntent);
- builder.setSmallIcon(context.getApplicationInfo().icon);
+ builder.setSmallIcon(smallIconId);
notification = builder.build();
}
- startForeground(1, notification);
+ startForeground(getServiceId(), notification);
}
@Override
@@ -130,11 +173,25 @@ public void onDestroy() {
Process.killProcess(Process.myPid());
}
+ /**
+ * Stops the task gracefully when killed.
+ * Calling stopSelf() will trigger a onDestroy() call from the system.
+ */
+ @Override
+ public void onTaskRemoved(Intent rootIntent) {
+ super.onTaskRemoved(rootIntent);
+ //sticky servcie runtime/restart is managed by the OS. leave it running when app is closed
+ if (startType() != START_STICKY) {
+ stopSelf();
+ }
+ }
+
@Override
public void run(){
String app_root = getFilesDir().getAbsolutePath() + "/app";
File app_root_file = new File(app_root);
- PythonUtil.loadLibraries(app_root_file);
+ PythonUtil.loadLibraries(app_root_file,
+ new File(getApplicationInfo().nativeLibraryDir));
this.mService = this;
nativeStart(
androidPrivate, androidArgument,
diff --git a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java
new file mode 100644
index 000000000..cc04d83f6
--- /dev/null
+++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java
@@ -0,0 +1,260 @@
+package org.kivy.android;
+
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.File;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+import org.renpy.android.AssetExtract;
+
+public class PythonUtil {
+ private static final String TAG = "pythonutil";
+
+ protected static void addLibraryIfExists(ArrayList<String> libsList, String pattern, File libsDir) {
+ // pattern should be the name of the lib file, without the
+ // preceding "lib" or suffix ".so", for instance "ssl.*" will
+ // match files of the form "libssl.*.so".
+ File [] files = libsDir.listFiles();
+
+ pattern = "lib" + pattern + "\\.so";
+ Pattern p = Pattern.compile(pattern);
+ for (int i = 0; i < files.length; ++i) {
+ File file = files[i];
+ String name = file.getName();
+ Log.v(TAG, "Checking pattern " + pattern + " against " + name);
+ if (p.matcher(name).matches()) {
+ Log.v(TAG, "Pattern " + pattern + " matched file " + name);
+ libsList.add(name.substring(3, name.length() - 3));
+ }
+ }
+ }
+
+ protected static ArrayList<String> getLibraries(File libsDir) {
+ ArrayList<String> libsList = new ArrayList<String>();
+ addLibraryIfExists(libsList, "sqlite3", libsDir);
+ addLibraryIfExists(libsList, "ffi", libsDir);
+ addLibraryIfExists(libsList, "png16", libsDir);
+ addLibraryIfExists(libsList, "ssl.*", libsDir);
+ addLibraryIfExists(libsList, "crypto.*", libsDir);
+ addLibraryIfExists(libsList, "SDL2", libsDir);
+ addLibraryIfExists(libsList, "SDL2_image", libsDir);
+ addLibraryIfExists(libsList, "SDL2_mixer", libsDir);
+ addLibraryIfExists(libsList, "SDL2_ttf", libsDir);
+ libsList.add("python3.5m");
+ libsList.add("python3.6m");
+ libsList.add("python3.7m");
+ libsList.add("python3.8");
+ libsList.add("python3.9");
+ libsList.add("python3.10");
+ libsList.add("python3.11");
+ libsList.add("main");
+ return libsList;
+ }
+
+ public static void loadLibraries(File filesDir, File libsDir) {
+ boolean foundPython = false;
+
+ for (String lib : getLibraries(libsDir)) {
+ Log.v(TAG, "Loading library: " + lib);
+ try {
+ System.loadLibrary(lib);
+ if (lib.startsWith("python")) {
+ foundPython = true;
+ }
+ } catch(UnsatisfiedLinkError e) {
+ // If this is the last possible libpython
+ // load, and it has failed, give a more
+ // general error
+ Log.v(TAG, "Library loading error: " + e.getMessage());
+ if (lib.startsWith("python3.11") && !foundPython) {
+ throw new RuntimeException("Could not load any libpythonXXX.so");
+ } else if (lib.startsWith("python")) {
+ continue;
+ } else {
+ Log.v(TAG, "An UnsatisfiedLinkError occurred loading " + lib);
+ throw e;
+ }
+ }
+ }
+
+ Log.v(TAG, "Loaded everything!");
+ }
+
+ public static String getAppRoot(Context ctx) {
+ String appRoot = ctx.getFilesDir().getAbsolutePath() + "/app";
+ return appRoot;
+ }
+
+ public static String getResourceString(Context ctx, String name) {
+ // Taken from org.renpy.android.ResourceManager
+ Resources res = ctx.getResources();
+ int id = res.getIdentifier(name, "string", ctx.getPackageName());
+ return res.getString(id);
+ }
+
+ /**
+ * Show an error using a toast. (Only makes sense from non-UI threads.)
+ */
+ protected static void toastError(final Activity activity, final String msg) {
+ activity.runOnUiThread(new Runnable () {
+ public void run() {
+ Toast.makeText(activity, msg, Toast.LENGTH_LONG).show();
+ }
+ });
+
+ // Wait to show the error.
+ synchronized (activity) {
+ try {
+ activity.wait(1000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ protected static void recursiveDelete(File f) {
+ if (f.isDirectory()) {
+ for (File r : f.listFiles()) {
+ recursiveDelete(r);
+ }
+ }
+ f.delete();
+ }
+
+ public static void unpackAsset(
+ Context ctx,
+ final String resource,
+ File target,
+ boolean cleanup_on_version_update) {
+
+ Log.v(TAG, "Unpacking " + resource + " " + target.getName());
+
+ // The version of data in memory and on disk.
+ String dataVersion = getResourceString(ctx, resource + "_version");
+ String diskVersion = null;
+
+ Log.v(TAG, "Data version is " + dataVersion);
+
+ // If no version, no unpacking is necessary.
+ if (dataVersion == null) {
+ return;
+ }
+
+ // Check the current disk version, if any.
+ String filesDir = target.getAbsolutePath();
+ String diskVersionFn = filesDir + "/" + resource + ".version";
+
+ try {
+ byte buf[] = new byte[64];
+ InputStream is = new FileInputStream(diskVersionFn);
+ int len = is.read(buf);
+ diskVersion = new String(buf, 0, len);
+ is.close();
+ } catch (Exception e) {
+ diskVersion = "";
+ }
+
+ // If the disk data is out of date, extract it and write the version file.
+ if (! dataVersion.equals(diskVersion)) {
+ Log.v(TAG, "Extracting " + resource + " assets.");
+
+ if (cleanup_on_version_update) {
+ recursiveDelete(target);
+ }
+ target.mkdirs();
+
+ AssetExtract ae = new AssetExtract(ctx);
+ if (!ae.extractTar(resource + ".tar", target.getAbsolutePath(), "private")) {
+ String msg = "Could not extract " + resource + " data.";
+ if (ctx instanceof Activity) {
+ toastError((Activity)ctx, msg);
+ } else {
+ Log.v(TAG, msg);
+ }
+ }
+
+ try {
+ // Write .nomedia.
+ new File(target, ".nomedia").createNewFile();
+
+ // Write version file.
+ FileOutputStream os = new FileOutputStream(diskVersionFn);
+ os.write(dataVersion.getBytes());
+ os.close();
+ } catch (Exception e) {
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ public static void unpackPyBundle(
+ Context ctx,
+ final String resource,
+ File target,
+ boolean cleanup_on_version_update) {
+
+ Log.v(TAG, "Unpacking " + resource + " " + target.getName());
+
+ // The version of data in memory and on disk.
+ String dataVersion = getResourceString(ctx, "private_version");
+ String diskVersion = null;
+
+ Log.v(TAG, "Data version is " + dataVersion);
+
+ // If no version, no unpacking is necessary.
+ if (dataVersion == null) {
+ return;
+ }
+
+ // Check the current disk version, if any.
+ String filesDir = target.getAbsolutePath();
+ String diskVersionFn = filesDir + "/" + "libpybundle" + ".version";
+
+ try {
+ byte buf[] = new byte[64];
+ InputStream is = new FileInputStream(diskVersionFn);
+ int len = is.read(buf);
+ diskVersion = new String(buf, 0, len);
+ is.close();
+ } catch (Exception e) {
+ diskVersion = "";
+ }
+
+ if (! dataVersion.equals(diskVersion)) {
+ // If the disk data is out of date, extract it and write the version file.
+ Log.v(TAG, "Extracting " + resource + " assets.");
+
+ if (cleanup_on_version_update) {
+ recursiveDelete(target);
+ }
+ target.mkdirs();
+
+ AssetExtract ae = new AssetExtract(ctx);
+ if (!ae.extractTar(resource + ".so", target.getAbsolutePath(), "pybundle")) {
+ String msg = "Could not extract " + resource + " data.";
+ if (ctx instanceof Activity) {
+ toastError((Activity)ctx, msg);
+ } else {
+ Log.v(TAG, msg);
+ }
+ }
+
+ try {
+ // Write version file.
+ FileOutputStream os = new FileOutputStream(diskVersionFn);
+ os.write(dataVersion.getBytes());
+ os.close();
+ } catch (Exception e) {
+ Log.w(TAG, e);
+ }
+ }
+ }
+}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java
similarity index 76%
rename from pythonforandroid/bootstraps/webview/build/src/org/renpy/android/AssetExtract.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java
index 52d6424e0..0a5dda656 100644
--- a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/AssetExtract.java
+++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java
@@ -2,36 +2,34 @@
// spaces amount
package org.renpy.android;
-import java.io.*;
-
-import android.app.Activity;
+import android.content.Context;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.FileInputStream;
+import java.io.OutputStream;
import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
import java.io.File;
+import java.io.FileInputStream;
import java.util.zip.GZIPInputStream;
import android.content.res.AssetManager;
-
-import org.kamranzafar.jtar.*;
+import org.kamranzafar.jtar.TarEntry;
+import org.kamranzafar.jtar.TarInputStream;
public class AssetExtract {
private AssetManager mAssetManager = null;
- private Activity mActivity = null;
- public AssetExtract(Activity act) {
- mActivity = act;
- mAssetManager = act.getAssets();
+ public AssetExtract(Context context) {
+ mAssetManager = context.getAssets();
}
- public boolean extractTar(String asset, String target) {
+ public boolean extractTar(String asset, String target, String method) {
byte buf[] = new byte[1024 * 1024];
@@ -39,7 +37,12 @@ public boolean extractTar(String asset, String target) {
TarInputStream tis = null;
try {
- assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
+ if(method == "private"){
+ assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
+ } else if (method == "pybundle") {
+ assetStream = new FileInputStream(asset);
+ }
+
tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
} catch (IOException e) {
Log.e("python", "opening up extract tar", e);
@@ -51,7 +54,7 @@ public boolean extractTar(String asset, String target) {
try {
entry = tis.getNextEntry();
- } catch ( java.io.IOException e ) {
+ } catch ( IOException e ) {
Log.e("python", "extracting tar", e);
return false;
}
@@ -76,8 +79,7 @@ public boolean extractTar(String asset, String target) {
try {
out = new BufferedOutputStream(new FileOutputStream(path), 8192);
- } catch ( FileNotFoundException e ) {
- } catch ( SecurityException e ) { };
+ } catch ( FileNotFoundException | SecurityException e ) {}
if ( out == null ) {
Log.e("python", "could not open " + path);
@@ -97,7 +99,7 @@ public boolean extractTar(String asset, String target) {
out.flush();
out.close();
- } catch ( java.io.IOException e ) {
+ } catch ( IOException e ) {
Log.e("python", "extracting zip", e);
return false;
}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/Hardware.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/Hardware.java
similarity index 95%
rename from pythonforandroid/bootstraps/webview/build/src/org/renpy/android/Hardware.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/Hardware.java
index c50692d71..847576282 100644
--- a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/Hardware.java
+++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/Hardware.java
@@ -8,11 +8,9 @@
import android.hardware.SensorManager;
import android.util.DisplayMetrics;
import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.EditorInfo;
import android.view.View;
import java.util.List;
-import java.util.ArrayList;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.content.BroadcastReceiver;
@@ -33,6 +31,7 @@ public class Hardware {
// The context.
static Context context;
static View view;
+ public static final float defaultRv[] = { 0f, 0f, 0f };
/**
* Vibrate for s seconds.
@@ -109,8 +108,7 @@ public float[] readSensor() {
if (sSensorEvent != null) {
return sSensorEvent.values;
} else {
- float rv[] = { 0f, 0f, 0f };
- return rv;
+ return defaultRv;
}
}
}
@@ -129,9 +127,8 @@ public static void accelerometerEnable(boolean enable) {
accelerometerSensor.changeStatus(enable);
}
public static float[] accelerometerReading() {
- float rv[] = { 0f, 0f, 0f };
if ( accelerometerSensor == null )
- return rv;
+ return defaultRv;
return (float[]) accelerometerSensor.readSensor();
}
public static void orientationSensorEnable(boolean enable) {
@@ -140,9 +137,8 @@ public static void orientationSensorEnable(boolean enable) {
orientationSensor.changeStatus(enable);
}
public static float[] orientationSensorReading() {
- float rv[] = { 0f, 0f, 0f };
if ( orientationSensor == null )
- return rv;
+ return defaultRv;
return (float[]) orientationSensor.readSensor();
}
public static void magneticFieldSensorEnable(boolean enable) {
@@ -151,9 +147,8 @@ public static void magneticFieldSensorEnable(boolean enable) {
magneticFieldSensor.changeStatus(enable);
}
public static float[] magneticFieldSensorReading() {
- float rv[] = { 0f, 0f, 0f };
if ( magneticFieldSensor == null )
- return rv;
+ return defaultRv;
return (float[]) magneticFieldSensor.readSensor();
}
@@ -223,9 +218,6 @@ public static String scanWifi() {
// Now you can call this and it should execute the broadcastReceiver's
// onReceive()
- WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- boolean a = wm.startScan();
-
if (latestResult != null){
String latestResultString = "";
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/ResourceManager.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/ResourceManager.java
similarity index 92%
rename from pythonforandroid/bootstraps/webview/build/src/org/renpy/android/ResourceManager.java
rename to pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/ResourceManager.java
index 47455abb0..a170c846b 100644
--- a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/ResourceManager.java
+++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/ResourceManager.java
@@ -1,8 +1,7 @@
/**
* This class takes care of managing resources for us. In our code, we
* can't use R, since the name of the package containing R will
- * change. (This same code is used in both org.renpy.android and
- * org.renpy.pygame.) So this is the next best thing.
+ * change. So this is the next best thing.
*/
package org.renpy.android;
diff --git a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java
new file mode 100644
index 000000000..9406f91d8
--- /dev/null
+++ b/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java
@@ -0,0 +1,69 @@
+package {{ args.package }};
+
+import android.content.Intent;
+import android.content.Context;
+import {{ args.service_class_name }};
+
+
+public class Service{{ name|capitalize }} extends {{ base_service_class }} {
+ {% if sticky %}
+ @Override
+ public int startType() {
+ return START_STICKY;
+ }
+ {% endif %}
+
+ @Override
+ protected int getServiceId() {
+ return {{ service_id }};
+ }
+
+ static private void _start(Context ctx, String smallIconName,
+ String contentTitle, String contentText,
+ String pythonServiceArgument) {
+ Intent intent = getDefaultIntent(ctx, smallIconName, contentTitle,
+ contentText, pythonServiceArgument);
+ ctx.startService(intent);
+ }
+
+ static public void start(Context ctx, String pythonServiceArgument) {
+ _start(ctx, "", "{{ args.name }}", "{{ name|capitalize }}", pythonServiceArgument);
+ }
+
+ static public void start(Context ctx, String smallIconName,
+ String contentTitle, String contentText,
+ String pythonServiceArgument) {
+ _start(ctx, smallIconName, contentTitle, contentText, pythonServiceArgument);
+ }
+
+ static public Intent getDefaultIntent(Context ctx, String smallIconName,
+ String contentTitle, String contentText,
+ String pythonServiceArgument) {
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
+ intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
+ intent.putExtra("androidArgument", argument);
+ intent.putExtra("serviceTitle", "{{ args.name }}");
+ intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
+ intent.putExtra("pythonName", "{{ name }}");
+ intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}");
+ intent.putExtra("pythonHome", argument);
+ intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
+ intent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ intent.putExtra("smallIconName", smallIconName);
+ intent.putExtra("contentTitle", contentTitle);
+ intent.putExtra("contentText", contentText);
+ return intent;
+ }
+
+ @Override
+ protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {
+ return Service{{ name|capitalize }}.getDefaultIntent(ctx, "", "", "",
+ pythonServiceArgument);
+ }
+
+ static public void stop(Context ctx) {
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ ctx.stopService(intent);
+ }
+}
diff --git a/pythonforandroid/bootstraps/pygame/build/build.properties b/pythonforandroid/bootstraps/common/build/templates/build.properties
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/build.properties
rename to pythonforandroid/bootstraps/common/build/templates/build.properties
diff --git a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle b/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle
new file mode 100644
index 000000000..750a435d9
--- /dev/null
+++ b/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle
@@ -0,0 +1,127 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.1.1'
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ {%- for repo in args.gradle_repositories %}
+ {{repo}}
+ {%- endfor %}
+ }
+}
+
+{% if is_library %}
+apply plugin: 'com.android.library'
+{% else %}
+apply plugin: 'com.android.application'
+{% endif %}
+
+android {
+ namespace '{{ args.package }}'
+ compileSdkVersion {{ android_api }}
+ buildToolsVersion '{{ build_tools_version }}'
+ defaultConfig {
+ minSdkVersion {{ args.min_sdk_version }}
+ targetSdkVersion {{ android_api }}
+ versionCode {{ args.numeric_version }}
+ versionName '{{ args.version }}'
+ manifestPlaceholders = {{ args.manifest_placeholders}}
+ }
+
+
+ packagingOptions {
+ jniLibs {
+ useLegacyPackaging = true
+ }
+ {% if debug_build -%}
+ doNotStrip '**/*.so'
+ {% else %}
+ exclude 'lib/**/gdbserver'
+ exclude 'lib/**/gdb.setup'
+ {%- endif %}
+ }
+
+
+ {% if args.sign -%}
+ signingConfigs {
+ release {
+ storeFile file(System.getenv("P4A_RELEASE_KEYSTORE"))
+ keyAlias System.getenv("P4A_RELEASE_KEYALIAS")
+ storePassword System.getenv("P4A_RELEASE_KEYSTORE_PASSWD")
+ keyPassword System.getenv("P4A_RELEASE_KEYALIAS_PASSWD")
+ }
+ }
+
+ {%- endif %}
+
+ {% if args.packaging_options -%}
+ packagingOptions {
+ {%- for option in args.packaging_options %}
+ {{option}}
+ {%- endfor %}
+ }
+ {%- endif %}
+
+ buildTypes {
+ debug {
+ }
+ release {
+ {% if args.sign -%}
+ signingConfig signingConfigs.release
+ {%- endif %}
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ {%- for option in args.compile_options %}
+ {{option}}
+ {%- endfor %}
+ }
+
+ sourceSets {
+ main {
+ jniLibs.srcDir 'libs'
+ java {
+
+ {%- for adir, pattern in args.extra_source_dirs -%}
+ srcDir '{{adir}}'
+ {%- endfor -%}
+
+ }
+ }
+ }
+
+ aaptOptions {
+ noCompress "tflite"
+ }
+
+}
+
+dependencies {
+ {%- for aar in aars %}
+ implementation(name: '{{ aar }}', ext: 'aar')
+ {%- endfor -%}
+ {%- for jar in jars %}
+ implementation files('src/main/libs/{{ jar }}')
+ {%- endfor -%}
+ {%- if args.depends -%}
+ {%- for depend in args.depends %}
+ implementation '{{ depend }}'
+ {%- endfor %}
+ {%- endif %}
+ {% if args.presplash_lottie %}
+ implementation 'com.airbnb.android:lottie:6.1.0'
+ {%- endif %}
+}
+
diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/build.tmpl.xml b/pythonforandroid/bootstraps/common/build/templates/build.tmpl.xml
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/templates/build.tmpl.xml
rename to pythonforandroid/bootstraps/common/build/templates/build.tmpl.xml
diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/custom_rules.tmpl.xml b/pythonforandroid/bootstraps/common/build/templates/custom_rules.tmpl.xml
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/templates/custom_rules.tmpl.xml
rename to pythonforandroid/bootstraps/common/build/templates/custom_rules.tmpl.xml
diff --git a/pythonforandroid/bootstraps/common/build/templates/gradle.tmpl.properties b/pythonforandroid/bootstraps/common/build/templates/gradle.tmpl.properties
new file mode 100644
index 000000000..cea16375d
--- /dev/null
+++ b/pythonforandroid/bootstraps/common/build/templates/gradle.tmpl.properties
@@ -0,0 +1,9 @@
+{% if bootstrap_name == "qt" %}
+# For tweaking memory settings. Otherwise, a p4a session with Qt bootstrap and PySide6 recipe
+# terminates with a Java out of memory exception
+org.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+{% endif %}
+{% if args.enable_androidx %}
+android.useAndroidX=true
+android.enableJetifier=true
+{% endif %}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/kivy-icon.png b/pythonforandroid/bootstraps/common/build/templates/kivy-icon.png
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/templates/kivy-icon.png
rename to pythonforandroid/bootstraps/common/build/templates/kivy-icon.png
diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/kivy-presplash.jpg b/pythonforandroid/bootstraps/common/build/templates/kivy-presplash.jpg
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/templates/kivy-presplash.jpg
rename to pythonforandroid/bootstraps/common/build/templates/kivy-presplash.jpg
diff --git a/pythonforandroid/bootstraps/common/build/templates/lottie.xml b/pythonforandroid/bootstraps/common/build/templates/lottie.xml
new file mode 100644
index 000000000..49fe8c92d
--- /dev/null
+++ b/pythonforandroid/bootstraps/common/build/templates/lottie.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+>
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/progressBar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:scaleType="centerInside"
+ android:layout_weight="4"
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true"
+ app:lottie_rawRes="@raw/splashscreen"
+ />
+</LinearLayout>
+
diff --git a/pythonforandroid/bootstraps/pygame/build/whitelist.txt b/pythonforandroid/bootstraps/common/build/whitelist.txt
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/whitelist.txt
rename to pythonforandroid/bootstraps/common/build/whitelist.txt
diff --git a/pythonforandroid/bootstraps/empty/__init__.py b/pythonforandroid/bootstraps/empty/__init__.py
index f4231a0aa..8d4c19630 100644
--- a/pythonforandroid/bootstraps/empty/__init__.py
+++ b/pythonforandroid/bootstraps/empty/__init__.py
@@ -1,8 +1,4 @@
from pythonforandroid.toolchain import Bootstrap
-from os.path import join, exists
-from os import walk
-import glob
-import sh
class EmptyBootstrap(Bootstrap):
@@ -12,8 +8,9 @@ class EmptyBootstrap(Bootstrap):
can_be_chosen_automatically = False
- def run_distribute(self):
+ def assemble_distribution(self):
print('empty bootstrap has no distribute')
exit(1)
+
bootstrap = EmptyBootstrap()
diff --git a/pythonforandroid/bootstraps/pygame/__init__.py b/pythonforandroid/bootstraps/pygame/__init__.py
deleted file mode 100644
index ec45482bc..000000000
--- a/pythonforandroid/bootstraps/pygame/__init__.py
+++ /dev/null
@@ -1,98 +0,0 @@
-from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main
-from os.path import join, exists
-from os import walk
-import glob
-import sh
-
-
-class PygameBootstrap(Bootstrap):
- name = 'pygame'
-
- recipe_depends = ['hostpython2', 'python2', 'pyjnius', 'sdl', 'pygame',
- 'android', 'kivy']
-
- def run_distribute(self):
- info_main('# Creating Android project from build and {} bootstrap'.format(
- self.name))
-
- # src_path = join(self.ctx.root_dir, 'bootstrap_templates',
- # self.name)
- src_path = join(self.bootstrap_dir, 'build')
-
- arch = self.ctx.archs[0]
- if len(self.ctx.archs) > 1:
- raise ValueError('built for more than one arch, but bootstrap cannot handle that yet')
- info('Bootstrap running with arch {}'.format(arch))
-
- with current_directory(self.dist_dir):
-
- info('Creating initial layout')
- for dirname in ('assets', 'bin', 'private', 'res', 'templates'):
- if not exists(dirname):
- shprint(sh.mkdir, dirname)
-
- info('Copying default files')
- shprint(sh.cp, '-a', join(self.build_dir, 'project.properties'), '.')
- shprint(sh.cp, '-a', join(src_path, 'build.py'), '.')
- shprint(sh.cp, '-a', join(src_path, 'buildlib'), '.')
- shprint(sh.cp, '-a', join(src_path, 'src'), '.')
- shprint(sh.cp, '-a', join(src_path, 'templates'), '.')
- shprint(sh.cp, '-a', join(src_path, 'res'), '.')
- shprint(sh.cp, '-a', join(src_path, 'blacklist.txt'), '.')
- shprint(sh.cp, '-a', join(src_path, 'whitelist.txt'), '.')
-
- with open('local.properties', 'w') as fileh:
- fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))
-
- info('Copying python distribution')
- hostpython = sh.Command(self.ctx.hostpython)
- try:
- shprint(hostpython, '-OO', '-m', 'compileall', self.ctx.get_python_install_dir(),
- _tail=10, _filterout="^Listing")
- except sh.ErrorReturnCode:
- pass
- if not exists('python-install'):
- shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install')
-
- self.distribute_libs(arch, [join(self.build_dir, 'libs', arch.arch), self.ctx.get_libs_dir(arch.arch)]);
- self.distribute_aars(arch)
- self.distribute_javaclasses(self.ctx.javaclass_dir)
-
- info('Filling private directory')
- if not exists(join('private', 'lib')):
- shprint(sh.cp, '-a', join('python-install', 'lib'), 'private')
- shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7'))
-
- shprint(sh.mv, join('libs', arch.arch, 'libpymodules.so'), 'private/')
- shprint(sh.cp, join('python-install', 'include' , 'python2.7', 'pyconfig.h'), join('private', 'include', 'python2.7/'))
-
- info('Removing some unwanted files')
- shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so'))
- shprint(sh.rm, '-rf', join('private', 'lib', 'pkgconfig'))
-
- with current_directory(join(self.dist_dir, 'private', 'lib', 'python2.7')):
- # shprint(sh.xargs, 'rm', sh.grep('-E', '*\.(py|pyx|so\.o|so\.a|so\.libs)$', sh.find('.')))
- removes = []
- for dirname, something, filens in walk('.'):
- for filename in filens:
- for suffix in ('py', 'pyc', 'so.o', 'so.a', 'so.libs'):
- if filename.endswith(suffix):
- removes.append(filename)
- shprint(sh.rm, '-f', *removes)
-
- info('Deleting some other stuff not used on android')
- # To quote the original distribute.sh, 'well...'
- # shprint(sh.rm, '-rf', 'ctypes')
- shprint(sh.rm, '-rf', 'lib2to3')
- shprint(sh.rm, '-rf', 'idlelib')
- for filename in glob.glob('config/libpython*.a'):
- shprint(sh.rm, '-f', filename)
- shprint(sh.rm, '-rf', 'config/python.o')
- shprint(sh.rm, '-rf', 'lib-dynload/_ctypes_test.so')
- shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so')
-
-
- self.strip_libraries(arch)
- super(PygameBootstrap, self).run_distribute()
-
-bootstrap = PygameBootstrap()
diff --git a/pythonforandroid/bootstraps/pygame/build/build.py b/pythonforandroid/bootstraps/pygame/build/build.py
deleted file mode 100755
index d9f2dfddf..000000000
--- a/pythonforandroid/bootstraps/pygame/build/build.py
+++ /dev/null
@@ -1,536 +0,0 @@
-#!/usr/bin/env python2.7
-
-from os.path import dirname, join, isfile, realpath, relpath, split, exists
-from zipfile import ZipFile
-import sys
-sys.path.insert(0, 'buildlib/jinja2.egg')
-sys.path.insert(0, 'buildlib')
-
-from fnmatch import fnmatch
-import tarfile
-import os
-import shutil
-import subprocess
-import time
-import jinja2
-
-# The extension of the android and ant commands.
-if os.name == 'nt':
- ANDROID = 'android.bat'
- ANT = 'ant.bat'
-else:
- ANDROID = 'android'
- ANT = 'ant'
-
-# if ANDROIDSDK is on path, use android from this path
-ANDROIDSDK = os.environ.get('ANDROIDSDK')
-if ANDROIDSDK:
- ANDROID = os.path.join(ANDROIDSDK, 'tools', ANDROID)
-
-curdir = dirname(__file__)
-
-# Try to find a host version of Python that matches our ARM version.
-PYTHON = join(curdir, 'python-install', 'bin', 'python.host')
-
-BLACKLIST_PATTERNS = [
- # code versionning
- '^*.hg/*',
- '^*.git/*',
- '^*.bzr/*',
- '^*.svn/*',
-
- # pyc/py
- '*.pyc',
- '*.py',
-
- # temp files
- '~',
- '*.bak',
- '*.swp',
-]
-
-WHITELIST_PATTERNS = []
-
-python_files = []
-
-
-# Used by render.
-environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
- join(curdir, 'templates')))
-
-
-def render(template, dest, **kwargs):
- '''Using jinja2, render `template` to the filename `dest`, supplying the
-
- keyword arguments as template parameters.
- '''
-
- template = environment.get_template(template)
- text = template.render(**kwargs)
-
- f = open(dest, 'wb')
- f.write(text.encode('utf-8'))
- f.close()
-
-
-def compile_dir(dfn):
- '''
- Compile *.py in directory `dfn` to *.pyo
- '''
-
- # -OO = strip docstrings
- subprocess.call([PYTHON, '-OO', '-m', 'compileall', '-f', dfn])
-
-
-def is_whitelist(name):
- return match_filename(WHITELIST_PATTERNS, name)
-
-
-def is_blacklist(name):
- if is_whitelist(name):
- return False
- return match_filename(BLACKLIST_PATTERNS, name)
-
-
-def match_filename(pattern_list, name):
- for pattern in pattern_list:
- if pattern.startswith('^'):
- pattern = pattern[1:]
- else:
- pattern = '*/' + pattern
- if fnmatch(name, pattern):
- return True
-
-
-def listfiles(d):
- basedir = d
- subdirlist = []
- for item in os.listdir(d):
- fn = join(d, item)
- if isfile(fn):
- yield fn
- else:
- subdirlist.append(os.path.join(basedir, item))
- for subdir in subdirlist:
- for fn in listfiles(subdir):
- yield fn
-
-
-def make_pythonzip():
- '''
- Search for all the python related files, and construct the pythonXX.zip
- According to
- # http://randomsplat.com/id5-cross-compiling-python-for-embedded-linux.html
- site-packages, config and lib-dynload will be not included.
- '''
- global python_files
- d = realpath(join('private', 'lib', 'python2.7'))
-
- # selector function
- def select(fn):
- if is_blacklist(fn):
- return False
- fn = realpath(fn)
- assert(fn.startswith(d))
- fn = fn[len(d):]
- if (fn.startswith('/site-packages/') or
- fn.startswith('/config/') or
- fn.startswith('/lib-dynload/') or
- fn.startswith('/libpymodules.so')):
- return False
- return fn
-
- # get a list of all python file
- python_files = [x for x in listfiles(d) if select(x)]
-
- # create the final zipfile
- zfn = join('private', 'lib', 'python27.zip')
- zf = ZipFile(zfn, 'w')
-
- # put all the python files in it
- for fn in python_files:
- afn = fn[len(d):]
- zf.write(fn, afn)
- zf.close()
-
-
-def make_tar(tfn, source_dirs, ignore_path=[]):
- '''
- Make a zip file `fn` from the contents of source_dis.
- '''
-
- # selector function
- def select(fn):
- rfn = realpath(fn)
- for p in ignore_path:
- if p.endswith('/'):
- p = p[:-1]
- if rfn.startswith(p):
- return False
- if rfn in python_files:
- return False
- return not is_blacklist(fn)
-
- # get the files and relpath file of all the directory we asked for
- files = []
- for sd in source_dirs:
- sd = realpath(sd)
- compile_dir(sd)
- files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd)
- if select(x)]
-
- # create tar.gz of thoses files
- tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT)
- dirs = []
- for fn, afn in files:
-# print('%s: %s' % (tfn, fn))
- dn = dirname(afn)
- if dn not in dirs:
- # create every dirs first if not exist yet
- d = ''
- for component in split(dn):
- d = join(d, component)
- if d.startswith('/'):
- d = d[1:]
- if d == '' or d in dirs:
- continue
- dirs.append(d)
- tinfo = tarfile.TarInfo(d)
- tinfo.type = tarfile.DIRTYPE
- tf.addfile(tinfo)
-
- # put the file
- tf.add(fn, afn)
- tf.close()
-
-
-def make_package(args):
- version_code = 0
- manifest_extra = ['<uses-feature android:glEsVersion="0x00020000" />']
- for filename in args.manifest_extra:
- with open(filename, "r") as fd:
- content = fd.read()
- manifest_extra.append(content)
- manifest_extra = '\n'.join(manifest_extra)
- url_scheme = 'kivy'
- default_icon = 'templates/kivy-icon.png'
- default_presplash = 'templates/kivy-presplash.jpg'
- default_ouya_icon = 'templates/kivy-ouya-icon.png'
- # Figure out the version code, if necessary.
- if not args.numeric_version:
- for i in args.version.split('.'):
- version_code *= 100
- version_code += int(i)
-
- args.numeric_version = str(version_code)
-
- # args.name = args.name.decode('utf-8')
- # if args.icon_name:
- # args.icon_name = args.icon_name.decode('utf-8')
-
- versioned_name = (args.name.replace(' ', '').replace('\'', '') +
- '-' + args.version)
-
- # Android SDK rev14 needs two ant execs (ex: debug installd) and
- # new build.xml
- build_tpl = 'build.xml'
-
- if not args.icon_name:
- args.icon_name = args.name
-
- # Annoying fixups.
- args.name = args.name.replace('\'', '\\\'')
- args.icon_name = args.icon_name.replace('\'', '\\\'')
- args.add_activity = args.add_activity or []
-
- # Figure out versions of the private and public data.
- private_version = str(time.time())
-
- if args.dir:
- public_version = private_version
- else:
- public_version = None
-
- if args.intent_filters:
- intent_filters = open(args.intent_filters).read()
- else:
- intent_filters = ''
-
- directory = args.dir if public_version else args.private
- # Ignore warning if the launcher is in args
- if not args.launcher:
- if not (exists(join(realpath(directory), 'main.py')) or
- exists(join(realpath(directory), 'main.pyo'))):
- print('''BUILD FAILURE: No main.py(o) found in your app directory.
-This file must exist to act as the entry point for you app. If your app is
-started by a file with a different name, rename it to main.py or add a
-main.py that loads it.''')
- exit(1)
-
- # Figure out if application has service part
- service = False
- if directory:
- service_main = join(realpath(directory), 'service', 'main.py')
- if os.path.exists(service_main) or os.path.exists(service_main + 'o'):
- service = True
-
- # Check if OUYA support is enabled
- if args.ouya_category:
- args.ouya_category = args.ouya_category.upper()
- if args.ouya_category not in ('GAME', 'APP'):
- print('Invalid --ouya-category argument. should be one of'
- 'GAME or APP')
- sys.exit(-1)
-
- # Render the various templates into control files.
- render(
- 'AndroidManifest.tmpl.xml',
- 'AndroidManifest.xml',
- args=args,
- service=service,
- url_scheme=url_scheme,
- intent_filters=intent_filters,
- manifest_extra=manifest_extra,
- )
-
- render(
- 'Configuration.tmpl.java',
- 'src/org/renpy/android/Configuration.java',
- args=args)
-
- render(
- build_tpl,
- 'build.xml',
- args=args,
- versioned_name=versioned_name)
-
- render(
- 'strings.xml',
- 'res/values/strings.xml',
- public_version=public_version,
- private_version=private_version,
- url_scheme=url_scheme,
- args=args)
-
- # Update the project to a recent version.
- try:
- subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t',
- 'android-{}'.format(args.sdk_version)])
- except (OSError, IOError):
- print('An error occured while calling', ANDROID, 'update')
- print('Your PATH must include android tools.')
- sys.exit(-1)
-
- # Delete the old assets.
- if os.path.exists('assets/public.mp3'):
- os.unlink('assets/public.mp3')
-
- if os.path.exists('assets/private.mp3'):
- os.unlink('assets/private.mp3')
-
- # In order to speedup import and initial depack,
- # construct a python27.zip
- make_pythonzip()
-
- # Package up the private and public data.
- if args.private:
- make_tar('assets/private.mp3', ['private', args.private], args.ignore_path)
- else:
- make_tar('assets/private.mp3', ['private'])
-
- if args.dir:
- make_tar('assets/public.mp3', [args.dir], args.ignore_path)
-
- # Copy over the icon and presplash files.
- shutil.copy(args.icon or default_icon, 'res/drawable/icon.png')
- shutil.copy(args.presplash or default_presplash,
- 'res/drawable/presplash.jpg')
-
- # If OUYA support was requested, copy over the OUYA icon
- if args.ouya_category:
- if not os.path.isdir('res/drawable-xhdpi'):
- os.mkdir('res/drawable-xhdpi')
- shutil.copy(args.ouya_icon or default_ouya_icon,
- 'res/drawable-xhdpi/ouya_icon.png')
-
- # If extra Java jars were requested, copy them into the libs directory
- if args.add_jar:
- for jarname in args.add_jar:
- if not os.path.exists(jarname):
- print('Requested jar does not exist: {}'.format(jarname))
- sys.exit(-1)
- shutil.copy(jarname, 'libs')
-
- # Build.
- try:
- for arg in args.command:
- subprocess.check_call([ANT, arg])
- except (OSError, IOError):
- print('An error occured while calling', ANT)
- print('Did you install ant on your system ?')
- sys.exit(-1)
-
-def parse_args(args=None):
- import argparse
-
- # get default SDK version from environment
- android_api = os.environ.get('ANDROIDAPI', 8)
-
- ap = argparse.ArgumentParser(description='''\
-Package a Python application for Android.
-
-For this to work, Java and Ant need to be in your path, as does the
-tools directory of the Android SDK.
-''')
-
- ap.add_argument('--package', dest='package',
- help=('The name of the java package the project will be'
- ' packaged under.'),
- required=True)
- ap.add_argument('--name', dest='name',
- help=('The human-readable name of the project.'),
- required=True)
- ap.add_argument('--version', dest='version',
- help=('The version number of the project. This should '
- 'consist of numbers and dots, and should have the '
- 'same number of groups of numbers as previous '
- 'versions.'),
- required=True)
- ap.add_argument('--numeric-version', dest='numeric_version',
- help=('The numeric version number of the project. If not '
- 'given, this is automatically computed from the '
- 'version.'))
- ap.add_argument('--dir', dest='dir',
- help=('The directory containing public files for the '
- 'project.'))
- ap.add_argument('--private', dest='private',
- help=('The directory containing additional private files '
- 'for the project.'))
- ap.add_argument('--launcher', dest='launcher', action='store_true',
- help=('Provide this argument to build a multi-app '
- 'launcher, rather than a single app.'))
- ap.add_argument('--icon-name', dest='icon_name',
- help='The name of the project\'s launcher icon.')
- ap.add_argument('--orientation', dest='orientation', default='landscape',
- help=('The orientation that the game will display in. '
- 'Usually one of "landscape", "portrait" or '
- '"sensor"'))
- ap.add_argument('--permission', dest='permissions', action='append',
- help='The permissions to give this app.', nargs='+')
- ap.add_argument('--ignore-path', dest='ignore_path', action='append',
- help='Ignore path when building the app')
- ap.add_argument('--icon', dest='icon',
- help='A png file to use as the icon for the application.')
- ap.add_argument('--presplash', dest='presplash',
- help=('A jpeg file to use as a screen while the '
- 'application is loading.'))
- ap.add_argument('--ouya-category', dest='ouya_category',
- help=('Valid values are GAME and APP. This must be '
- 'specified to enable OUYA console support.'))
- ap.add_argument('--ouya-icon', dest='ouya_icon',
- help=('A png file to use as the icon for the application '
- 'if it is installed on an OUYA console.'))
- ap.add_argument('--install-location', dest='install_location',
- default='auto',
- help=('The default install location. Should be "auto", '
- '"preferExternal" or "internalOnly".'))
- ap.add_argument('--compile-pyo', dest='compile_pyo', action='store_true',
- help=('Compile all .py files to .pyo, and only distribute '
- 'the compiled bytecode.'))
- ap.add_argument('--intent-filters', dest='intent_filters',
- help=('Add intent-filters xml rules to the '
- 'AndroidManifest.xml file. The argument is a '
- 'filename containing xml. The filename should be '
- 'located relative to the python-for-android '
- 'directory'))
- ap.add_argument('--with-billing', dest='billing_pubkey',
- help='If set, the billing service will be added')
- ap.add_argument('--blacklist', dest='blacklist',
- default=join(curdir, 'blacklist.txt'),
- help=('Use a blacklist file to match unwanted file in '
- 'the final APK'))
- ap.add_argument('--whitelist', dest='whitelist',
- default=join(curdir, 'whitelist.txt'),
- help=('Use a whitelist file to prevent blacklisting of '
- 'file in the final APK'))
- ap.add_argument('--sdk', dest='sdk_version', default=android_api,
- help='Android SDK version to use. Default to 8')
- ap.add_argument('--minsdk', dest='min_sdk_version', default=android_api,
- type=int,
- help='Minimum Android SDK version to use. Default to 8')
- ap.add_argument('--window', dest='window', action='store_true',
- help='Indicate if the application will be windowed')
- ap.add_argument('--wakelock', dest='wakelock', action='store_true',
- help=('Indicate if the application needs the device '
- 'to stay on'))
- ap.add_argument('command', nargs='*',
- help=('The command to pass to ant (debug, release, '
- 'installd, installr)'))
- ap.add_argument('--add-jar', dest='add_jar', action='append',
- help=('Add a Java .jar to the libs, so you can access its '
- 'classes with pyjnius. You can specify this '
- 'argument more than once to include multiple jars'))
- ap.add_argument('--meta-data', dest='meta_data', action='append',
- help='Custom key=value to add in application metadata')
- ap.add_argument('--resource', dest='resource', action='append',
- help='Custom key=value to add in strings.xml resource file')
- ap.add_argument('--manifest-extra', dest='manifest_extra', action='append',
- help='Custom file to add at the end of the manifest')
- ap.add_argument('--add-activity', dest='add_activity', action='append',
- help='Add this Java class as an Activity to the manifest.')
-
- if args is None:
- args = sys.argv[1:]
- args = ap.parse_args(args)
-
- if args.name and args.name[0] == '"' and args.name[-1] == '"':
- args.name = args.name[1:-1]
-
- if not args.dir and not args.private and not args.launcher:
- ap.error('One of --dir, --private, or --launcher must be supplied.')
-
- if args.permissions is None:
- args.permissions = []
- elif args.permissions:
- if isinstance(args.permissions[0], list):
- args.permissions = [p for perm in args.permissions for p in perm]
-
- if args.ignore_path is None:
- args.ignore_path = []
-
- if args.meta_data is None:
- args.meta_data = []
-
- if args.resource is None:
- args.resource = []
-
- if args.manifest_extra is None:
- args.manifest_extra = []
-
- if args.compile_pyo:
- if PYTHON is None:
- ap.error('To use --compile-pyo, you need Python 2.7.1 installed '
- 'and in your PATH.')
- global BLACKLIST_PATTERNS
- BLACKLIST_PATTERNS += ['*.py', '*.pyc']
-
- if args.blacklist:
- with open(args.blacklist) as fd:
- patterns = [x.strip() for x in fd.read().splitlines() if x.strip()
- and not x.startswith('#')]
- BLACKLIST_PATTERNS += patterns
-
- if args.whitelist:
- with open(args.whitelist) as fd:
- patterns = [x.strip() for x in fd.read().splitlines() if x.strip()
- and not x.startswith('#')]
- global WHITELIST_PATTERNS
- WHITELIST_PATTERNS += patterns
-
- make_package(args)
-
- return args
-
-
-if __name__ == '__main__':
- parse_args()
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/argparse.py b/pythonforandroid/bootstraps/pygame/build/buildlib/argparse.py
deleted file mode 100644
index 33d10dd29..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/argparse.py
+++ /dev/null
@@ -1,2354 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright © 2006-2009 Steven J. Bethard <steven.bethard@gmail.com>.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy
-# of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""Command-line parsing library
-
-This module is an optparse-inspired command-line parsing library that:
-
- - handles both optional and positional arguments
- - produces highly informative usage messages
- - supports parsers that dispatch to sub-parsers
-
-The following is a simple usage example that sums integers from the
-command-line and writes the result to a file::
-
- parser = argparse.ArgumentParser(
- description='sum the integers at the command line')
- parser.add_argument(
- 'integers', metavar='int', nargs='+', type=int,
- help='an integer to be summed')
- parser.add_argument(
- '--log', default=sys.stdout, type=argparse.FileType('w'),
- help='the file where the sum should be written')
- args = parser.parse_args()
- args.log.write('%s' % sum(args.integers))
- args.log.close()
-
-The module contains the following public classes:
-
- - ArgumentParser -- The main entry point for command-line parsing. As the
- example above shows, the add_argument() method is used to populate
- the parser with actions for optional and positional arguments. Then
- the parse_args() method is invoked to convert the args at the
- command-line into an object with attributes.
-
- - ArgumentError -- The exception raised by ArgumentParser objects when
- there are errors with the parser's actions. Errors raised while
- parsing the command-line are caught by ArgumentParser and emitted
- as command-line messages.
-
- - FileType -- A factory for defining types of files to be created. As the
- example above shows, instances of FileType are typically passed as
- the type= argument of add_argument() calls.
-
- - Action -- The base class for parser actions. Typically actions are
- selected by passing strings like 'store_true' or 'append_const' to
- the action= argument of add_argument(). However, for greater
- customization of ArgumentParser actions, subclasses of Action may
- be defined and passed as the action= argument.
-
- - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter,
- ArgumentDefaultsHelpFormatter -- Formatter classes which
- may be passed as the formatter_class= argument to the
- ArgumentParser constructor. HelpFormatter is the default,
- RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser
- not to change the formatting for help text, and
- ArgumentDefaultsHelpFormatter adds information about argument defaults
- to the help.
-
-All other classes in this module are considered implementation details.
-(Also note that HelpFormatter and RawDescriptionHelpFormatter are only
-considered public as object names -- the API of the formatter objects is
-still considered an implementation detail.)
-"""
-
-__version__ = '1.1'
-__all__ = [
- 'ArgumentParser',
- 'ArgumentError',
- 'Namespace',
- 'Action',
- 'FileType',
- 'HelpFormatter',
- 'RawDescriptionHelpFormatter',
- 'RawTextHelpFormatter',
- 'ArgumentDefaultsHelpFormatter',
-]
-
-
-import copy as _copy
-import os as _os
-import re as _re
-import sys as _sys
-import textwrap as _textwrap
-
-def _(s):
- return s
-
-try:
- _set = set
-except NameError:
- from sets import Set as _set
-
-try:
- _basestring = basestring
-except NameError:
- _basestring = str
-
-try:
- _sorted = sorted
-except NameError:
-
- def _sorted(iterable, reverse=False):
- result = list(iterable)
- result.sort()
- if reverse:
- result.reverse()
- return result
-
-
-def _callable(obj):
- return hasattr(obj, '__call__') or hasattr(obj, '__bases__')
-
-# silence Python 2.6 buggy warnings about Exception.message
-if _sys.version_info[:2] == (2, 6):
- import warnings
- warnings.filterwarnings(
- action='ignore',
- message='BaseException.message has been deprecated as of Python 2.6',
- category=DeprecationWarning,
- module='argparse')
-
-
-SUPPRESS = '==SUPPRESS=='
-
-OPTIONAL = '?'
-ZERO_OR_MORE = '*'
-ONE_OR_MORE = '+'
-PARSER = 'A...'
-REMAINDER = '...'
-
-# =============================
-# Utility functions and classes
-# =============================
-
-class _AttributeHolder(object):
- """Abstract base class that provides __repr__.
-
- The __repr__ method returns a string in the format::
- ClassName(attr=name, attr=name, ...)
- The attributes are determined either by a class-level attribute,
- '_kwarg_names', or by inspecting the instance __dict__.
- """
-
- def __repr__(self):
- type_name = type(self).__name__
- arg_strings = []
- for arg in self._get_args():
- arg_strings.append(repr(arg))
- for name, value in self._get_kwargs():
- arg_strings.append('%s=%r' % (name, value))
- return '%s(%s)' % (type_name, ', '.join(arg_strings))
-
- def _get_kwargs(self):
- return _sorted(self.__dict__.items())
-
- def _get_args(self):
- return []
-
-
-def _ensure_value(namespace, name, value):
- if getattr(namespace, name, None) is None:
- setattr(namespace, name, value)
- return getattr(namespace, name)
-
-
-# ===============
-# Formatting Help
-# ===============
-
-class HelpFormatter(object):
- """Formatter for generating usage messages and argument help strings.
-
- Only the name of this class is considered a public API. All the methods
- provided by the class are considered an implementation detail.
- """
-
- def __init__(self,
- prog,
- indent_increment=2,
- max_help_position=24,
- width=None):
-
- # default setting for width
- if width is None:
- try:
- width = int(_os.environ['COLUMNS'])
- except (KeyError, ValueError):
- width = 80
- width -= 2
-
- self._prog = prog
- self._indent_increment = indent_increment
- self._max_help_position = max_help_position
- self._width = width
-
- self._current_indent = 0
- self._level = 0
- self._action_max_length = 0
-
- self._root_section = self._Section(self, None)
- self._current_section = self._root_section
-
- self._whitespace_matcher = _re.compile(r'\s+')
- self._long_break_matcher = _re.compile(r'\n\n\n+')
-
- # ===============================
- # Section and indentation methods
- # ===============================
- def _indent(self):
- self._current_indent += self._indent_increment
- self._level += 1
-
- def _dedent(self):
- self._current_indent -= self._indent_increment
- assert self._current_indent >= 0, 'Indent decreased below 0.'
- self._level -= 1
-
- class _Section(object):
-
- def __init__(self, formatter, parent, heading=None):
- self.formatter = formatter
- self.parent = parent
- self.heading = heading
- self.items = []
-
- def format_help(self):
- # format the indented section
- if self.parent is not None:
- self.formatter._indent()
- join = self.formatter._join_parts
- for func, args in self.items:
- func(*args)
- item_help = join([func(*args) for func, args in self.items])
- if self.parent is not None:
- self.formatter._dedent()
-
- # return nothing if the section was empty
- if not item_help:
- return ''
-
- # add the heading if the section was non-empty
- if self.heading is not SUPPRESS and self.heading is not None:
- current_indent = self.formatter._current_indent
- heading = '%*s%s:\n' % (current_indent, '', self.heading)
- else:
- heading = ''
-
- # join the section-initial newline, the heading and the help
- return join(['\n', heading, item_help, '\n'])
-
- def _add_item(self, func, args):
- self._current_section.items.append((func, args))
-
- # ========================
- # Message building methods
- # ========================
- def start_section(self, heading):
- self._indent()
- section = self._Section(self, self._current_section, heading)
- self._add_item(section.format_help, [])
- self._current_section = section
-
- def end_section(self):
- self._current_section = self._current_section.parent
- self._dedent()
-
- def add_text(self, text):
- if text is not SUPPRESS and text is not None:
- self._add_item(self._format_text, [text])
-
- def add_usage(self, usage, actions, groups, prefix=None):
- if usage is not SUPPRESS:
- args = usage, actions, groups, prefix
- self._add_item(self._format_usage, args)
-
- def add_argument(self, action):
- if action.help is not SUPPRESS:
-
- # find all invocations
- get_invocation = self._format_action_invocation
- invocations = [get_invocation(action)]
- for subaction in self._iter_indented_subactions(action):
- invocations.append(get_invocation(subaction))
-
- # update the maximum item length
- invocation_length = max([len(s) for s in invocations])
- action_length = invocation_length + self._current_indent
- self._action_max_length = max(self._action_max_length,
- action_length)
-
- # add the item to the list
- self._add_item(self._format_action, [action])
-
- def add_arguments(self, actions):
- for action in actions:
- self.add_argument(action)
-
- # =======================
- # Help-formatting methods
- # =======================
- def format_help(self):
- help = self._root_section.format_help()
- if help:
- help = self._long_break_matcher.sub('\n\n', help)
- help = help.strip('\n') + '\n'
- return help
-
- def _join_parts(self, part_strings):
- return ''.join([part
- for part in part_strings
- if part and part is not SUPPRESS])
-
- def _format_usage(self, usage, actions, groups, prefix):
- if prefix is None:
- prefix = _('usage: ')
-
- # if usage is specified, use that
- if usage is not None:
- usage = usage % dict(prog=self._prog)
-
- # if no optionals or positionals are available, usage is just prog
- elif usage is None and not actions:
- usage = '%(prog)s' % dict(prog=self._prog)
-
- # if optionals and positionals are available, calculate usage
- elif usage is None:
- prog = '%(prog)s' % dict(prog=self._prog)
-
- # split optionals from positionals
- optionals = []
- positionals = []
- for action in actions:
- if action.option_strings:
- optionals.append(action)
- else:
- positionals.append(action)
-
- # build full usage string
- format = self._format_actions_usage
- action_usage = format(optionals + positionals, groups)
- usage = ' '.join([s for s in [prog, action_usage] if s])
-
- # wrap the usage parts if it's too long
- text_width = self._width - self._current_indent
- if len(prefix) + len(usage) > text_width:
-
- # break usage into wrappable parts
- part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
- opt_usage = format(optionals, groups)
- pos_usage = format(positionals, groups)
- opt_parts = _re.findall(part_regexp, opt_usage)
- pos_parts = _re.findall(part_regexp, pos_usage)
- assert ' '.join(opt_parts) == opt_usage
- assert ' '.join(pos_parts) == pos_usage
-
- # helper for wrapping lines
- def get_lines(parts, indent, prefix=None):
- lines = []
- line = []
- if prefix is not None:
- line_len = len(prefix) - 1
- else:
- line_len = len(indent) - 1
- for part in parts:
- if line_len + 1 + len(part) > text_width:
- lines.append(indent + ' '.join(line))
- line = []
- line_len = len(indent) - 1
- line.append(part)
- line_len += len(part) + 1
- if line:
- lines.append(indent + ' '.join(line))
- if prefix is not None:
- lines[0] = lines[0][len(indent):]
- return lines
-
- # if prog is short, follow it with optionals or positionals
- if len(prefix) + len(prog) <= 0.75 * text_width:
- indent = ' ' * (len(prefix) + len(prog) + 1)
- if opt_parts:
- lines = get_lines([prog] + opt_parts, indent, prefix)
- lines.extend(get_lines(pos_parts, indent))
- elif pos_parts:
- lines = get_lines([prog] + pos_parts, indent, prefix)
- else:
- lines = [prog]
-
- # if prog is long, put it on its own line
- else:
- indent = ' ' * len(prefix)
- parts = opt_parts + pos_parts
- lines = get_lines(parts, indent)
- if len(lines) > 1:
- lines = []
- lines.extend(get_lines(opt_parts, indent))
- lines.extend(get_lines(pos_parts, indent))
- lines = [prog] + lines
-
- # join lines into usage
- usage = '\n'.join(lines)
-
- # prefix with 'usage:'
- return '%s%s\n\n' % (prefix, usage)
-
- def _format_actions_usage(self, actions, groups):
- # find group indices and identify actions in groups
- group_actions = _set()
- inserts = {}
- for group in groups:
- try:
- start = actions.index(group._group_actions[0])
- except ValueError:
- continue
- else:
- end = start + len(group._group_actions)
- if actions[start:end] == group._group_actions:
- for action in group._group_actions:
- group_actions.add(action)
- if not group.required:
- inserts[start] = '['
- inserts[end] = ']'
- else:
- inserts[start] = '('
- inserts[end] = ')'
- for i in range(start + 1, end):
- inserts[i] = '|'
-
- # collect all actions format strings
- parts = []
- for i, action in enumerate(actions):
-
- # suppressed arguments are marked with None
- # remove | separators for suppressed arguments
- if action.help is SUPPRESS:
- parts.append(None)
- if inserts.get(i) == '|':
- inserts.pop(i)
- elif inserts.get(i + 1) == '|':
- inserts.pop(i + 1)
-
- # produce all arg strings
- elif not action.option_strings:
- part = self._format_args(action, action.dest)
-
- # if it's in a group, strip the outer []
- if action in group_actions:
- if part[0] == '[' and part[-1] == ']':
- part = part[1:-1]
-
- # add the action string to the list
- parts.append(part)
-
- # produce the first way to invoke the option in brackets
- else:
- option_string = action.option_strings[0]
-
- # if the Optional doesn't take a value, format is:
- # -s or --long
- if action.nargs == 0:
- part = '%s' % option_string
-
- # if the Optional takes a value, format is:
- # -s ARGS or --long ARGS
- else:
- default = action.dest.upper()
- args_string = self._format_args(action, default)
- part = '%s %s' % (option_string, args_string)
-
- # make it look optional if it's not required or in a group
- if not action.required and action not in group_actions:
- part = '[%s]' % part
-
- # add the action string to the list
- parts.append(part)
-
- # insert things at the necessary indices
- for i in _sorted(inserts, reverse=True):
- parts[i:i] = [inserts[i]]
-
- # join all the action items with spaces
- text = ' '.join([item for item in parts if item is not None])
-
- # clean up separators for mutually exclusive groups
- open = r'[\[(]'
- close = r'[\])]'
- text = _re.sub(r'(%s) ' % open, r'\1', text)
- text = _re.sub(r' (%s)' % close, r'\1', text)
- text = _re.sub(r'%s *%s' % (open, close), r'', text)
- text = _re.sub(r'\(([^|]*)\)', r'\1', text)
- text = text.strip()
-
- # return the text
- return text
-
- def _format_text(self, text):
- if '%(prog)' in text:
- text = text % dict(prog=self._prog)
- text_width = self._width - self._current_indent
- indent = ' ' * self._current_indent
- return self._fill_text(text, text_width, indent) + '\n\n'
-
- def _format_action(self, action):
- # determine the required width and the entry label
- help_position = min(self._action_max_length + 2,
- self._max_help_position)
- help_width = self._width - help_position
- action_width = help_position - self._current_indent - 2
- action_header = self._format_action_invocation(action)
-
- # ho nelp; start on same line and add a final newline
- if not action.help:
- tup = self._current_indent, '', action_header
- action_header = '%*s%s\n' % tup
-
- # short action name; start on the same line and pad two spaces
- elif len(action_header) <= action_width:
- tup = self._current_indent, '', action_width, action_header
- action_header = '%*s%-*s ' % tup
- indent_first = 0
-
- # long action name; start on the next line
- else:
- tup = self._current_indent, '', action_header
- action_header = '%*s%s\n' % tup
- indent_first = help_position
-
- # collect the pieces of the action help
- parts = [action_header]
-
- # if there was help for the action, add lines of help text
- if action.help:
- help_text = self._expand_help(action)
- help_lines = self._split_lines(help_text, help_width)
- parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
- for line in help_lines[1:]:
- parts.append('%*s%s\n' % (help_position, '', line))
-
- # or add a newline if the description doesn't end with one
- elif not action_header.endswith('\n'):
- parts.append('\n')
-
- # if there are any sub-actions, add their help as well
- for subaction in self._iter_indented_subactions(action):
- parts.append(self._format_action(subaction))
-
- # return a single string
- return self._join_parts(parts)
-
- def _format_action_invocation(self, action):
- if not action.option_strings:
- metavar, = self._metavar_formatter(action, action.dest)(1)
- return metavar
-
- else:
- parts = []
-
- # if the Optional doesn't take a value, format is:
- # -s, --long
- if action.nargs == 0:
- parts.extend(action.option_strings)
-
- # if the Optional takes a value, format is:
- # -s ARGS, --long ARGS
- else:
- default = action.dest.upper()
- args_string = self._format_args(action, default)
- for option_string in action.option_strings:
- parts.append('%s %s' % (option_string, args_string))
-
- return ', '.join(parts)
-
- def _metavar_formatter(self, action, default_metavar):
- if action.metavar is not None:
- result = action.metavar
- elif action.choices is not None:
- choice_strs = [str(choice) for choice in action.choices]
- result = '{%s}' % ','.join(choice_strs)
- else:
- result = default_metavar
-
- def format(tuple_size):
- if isinstance(result, tuple):
- return result
- else:
- return (result, ) * tuple_size
- return format
-
- def _format_args(self, action, default_metavar):
- get_metavar = self._metavar_formatter(action, default_metavar)
- if action.nargs is None:
- result = '%s' % get_metavar(1)
- elif action.nargs == OPTIONAL:
- result = '[%s]' % get_metavar(1)
- elif action.nargs == ZERO_OR_MORE:
- result = '[%s [%s ...]]' % get_metavar(2)
- elif action.nargs == ONE_OR_MORE:
- result = '%s [%s ...]' % get_metavar(2)
- elif action.nargs == REMAINDER:
- result = '...'
- elif action.nargs == PARSER:
- result = '%s ...' % get_metavar(1)
- else:
- formats = ['%s' for _ in range(action.nargs)]
- result = ' '.join(formats) % get_metavar(action.nargs)
- return result
-
- def _expand_help(self, action):
- params = dict(vars(action), prog=self._prog)
- for name in list(params):
- if params[name] is SUPPRESS:
- del params[name]
- for name in list(params):
- if hasattr(params[name], '__name__'):
- params[name] = params[name].__name__
- if params.get('choices') is not None:
- choices_str = ', '.join([str(c) for c in params['choices']])
- params['choices'] = choices_str
- return self._get_help_string(action) % params
-
- def _iter_indented_subactions(self, action):
- try:
- get_subactions = action._get_subactions
- except AttributeError:
- pass
- else:
- self._indent()
- for subaction in get_subactions():
- yield subaction
- self._dedent()
-
- def _split_lines(self, text, width):
- text = self._whitespace_matcher.sub(' ', text).strip()
- return _textwrap.wrap(text, width)
-
- def _fill_text(self, text, width, indent):
- text = self._whitespace_matcher.sub(' ', text).strip()
- return _textwrap.fill(text, width, initial_indent=indent,
- subsequent_indent=indent)
-
- def _get_help_string(self, action):
- return action.help
-
-
-class RawDescriptionHelpFormatter(HelpFormatter):
- """Help message formatter which retains any formatting in descriptions.
-
- Only the name of this class is considered a public API. All the methods
- provided by the class are considered an implementation detail.
- """
-
- def _fill_text(self, text, width, indent):
- return ''.join([indent + line for line in text.splitlines(True)])
-
-
-class RawTextHelpFormatter(RawDescriptionHelpFormatter):
- """Help message formatter which retains formatting of all help text.
-
- Only the name of this class is considered a public API. All the methods
- provided by the class are considered an implementation detail.
- """
-
- def _split_lines(self, text, width):
- return text.splitlines()
-
-
-class ArgumentDefaultsHelpFormatter(HelpFormatter):
- """Help message formatter which adds default values to argument help.
-
- Only the name of this class is considered a public API. All the methods
- provided by the class are considered an implementation detail.
- """
-
- def _get_help_string(self, action):
- help = action.help
- if '%(default)' not in action.help:
- if action.default is not SUPPRESS:
- defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
- if action.option_strings or action.nargs in defaulting_nargs:
- help += ' (default: %(default)s)'
- return help
-
-
-# =====================
-# Options and Arguments
-# =====================
-
-def _get_action_name(argument):
- if argument is None:
- return None
- elif argument.option_strings:
- return '/'.join(argument.option_strings)
- elif argument.metavar not in (None, SUPPRESS):
- return argument.metavar
- elif argument.dest not in (None, SUPPRESS):
- return argument.dest
- else:
- return None
-
-
-class ArgumentError(Exception):
- """An error from creating or using an argument (optional or positional).
-
- The string value of this exception is the message, augmented with
- information about the argument that caused it.
- """
-
- def __init__(self, argument, message):
- self.argument_name = _get_action_name(argument)
- self.message = message
-
- def __str__(self):
- if self.argument_name is None:
- format = '%(message)s'
- else:
- format = 'argument %(argument_name)s: %(message)s'
- return format % dict(message=self.message,
- argument_name=self.argument_name)
-
-
-class ArgumentTypeError(Exception):
- """An error from trying to convert a command line string to a type."""
- pass
-
-
-# ==============
-# Action classes
-# ==============
-
-class Action(_AttributeHolder):
- """Information about how to convert command line strings to Python objects.
-
- Action objects are used by an ArgumentParser to represent the information
- needed to parse a single argument from one or more strings from the
- command line. The keyword arguments to the Action constructor are also
- all attributes of Action instances.
-
- Keyword Arguments:
-
- - option_strings -- A list of command-line option strings which
- should be associated with this action.
-
- - dest -- The name of the attribute to hold the created object(s)
-
- - nargs -- The number of command-line arguments that should be
- consumed. By default, one argument will be consumed and a single
- value will be produced. Other values include:
- - N (an integer) consumes N arguments (and produces a list)
- - '?' consumes zero or one arguments
- - '*' consumes zero or more arguments (and produces a list)
- - '+' consumes one or more arguments (and produces a list)
- Note that the difference between the default and nargs=1 is that
- with the default, a single value will be produced, while with
- nargs=1, a list containing a single value will be produced.
-
- - const -- The value to be produced if the option is specified and the
- option uses an action that takes no values.
-
- - default -- The value to be produced if the option is not specified.
-
- - type -- The type which the command-line arguments should be converted
- to, should be one of 'string', 'int', 'float', 'complex' or a
- callable object that accepts a single string argument. If None,
- 'string' is assumed.
-
- - choices -- A container of values that should be allowed. If not None,
- after a command-line argument has been converted to the appropriate
- type, an exception will be raised if it is not a member of this
- collection.
-
- - required -- True if the action must always be specified at the
- command line. This is only meaningful for optional command-line
- arguments.
-
- - help -- The help string describing the argument.
-
- - metavar -- The name to be used for the option's argument with the
- help string. If None, the 'dest' value will be used as the name.
- """
-
- def __init__(self,
- option_strings,
- dest,
- nargs=None,
- const=None,
- default=None,
- type=None,
- choices=None,
- required=False,
- help=None,
- metavar=None):
- self.option_strings = option_strings
- self.dest = dest
- self.nargs = nargs
- self.const = const
- self.default = default
- self.type = type
- self.choices = choices
- self.required = required
- self.help = help
- self.metavar = metavar
-
- def _get_kwargs(self):
- names = [
- 'option_strings',
- 'dest',
- 'nargs',
- 'const',
- 'default',
- 'type',
- 'choices',
- 'help',
- 'metavar',
- ]
- return [(name, getattr(self, name)) for name in names]
-
- def __call__(self, parser, namespace, values, option_string=None):
- raise NotImplementedError(_('.__call__() not defined'))
-
-
-class _StoreAction(Action):
-
- def __init__(self,
- option_strings,
- dest,
- nargs=None,
- const=None,
- default=None,
- type=None,
- choices=None,
- required=False,
- help=None,
- metavar=None):
- if nargs == 0:
- raise ValueError('nargs for store actions must be > 0; if you '
- 'have nothing to store, actions such as store '
- 'true or store const may be more appropriate')
- if const is not None and nargs != OPTIONAL:
- raise ValueError('nargs must be %r to supply const' % OPTIONAL)
- super(_StoreAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- nargs=nargs,
- const=const,
- default=default,
- type=type,
- choices=choices,
- required=required,
- help=help,
- metavar=metavar)
-
- def __call__(self, parser, namespace, values, option_string=None):
- setattr(namespace, self.dest, values)
-
-
-class _StoreConstAction(Action):
-
- def __init__(self,
- option_strings,
- dest,
- const,
- default=None,
- required=False,
- help=None,
- metavar=None):
- super(_StoreConstAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- nargs=0,
- const=const,
- default=default,
- required=required,
- help=help)
-
- def __call__(self, parser, namespace, values, option_string=None):
- setattr(namespace, self.dest, self.const)
-
-
-class _StoreTrueAction(_StoreConstAction):
-
- def __init__(self,
- option_strings,
- dest,
- default=False,
- required=False,
- help=None):
- super(_StoreTrueAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- const=True,
- default=default,
- required=required,
- help=help)
-
-
-class _StoreFalseAction(_StoreConstAction):
-
- def __init__(self,
- option_strings,
- dest,
- default=True,
- required=False,
- help=None):
- super(_StoreFalseAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- const=False,
- default=default,
- required=required,
- help=help)
-
-
-class _AppendAction(Action):
-
- def __init__(self,
- option_strings,
- dest,
- nargs=None,
- const=None,
- default=None,
- type=None,
- choices=None,
- required=False,
- help=None,
- metavar=None):
- if nargs == 0:
- raise ValueError('nargs for append actions must be > 0; if arg '
- 'strings are not supplying the value to append, '
- 'the append const action may be more appropriate')
- if const is not None and nargs != OPTIONAL:
- raise ValueError('nargs must be %r to supply const' % OPTIONAL)
- super(_AppendAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- nargs=nargs,
- const=const,
- default=default,
- type=type,
- choices=choices,
- required=required,
- help=help,
- metavar=metavar)
-
- def __call__(self, parser, namespace, values, option_string=None):
- items = _copy.copy(_ensure_value(namespace, self.dest, []))
- items.append(values)
- setattr(namespace, self.dest, items)
-
-
-class _AppendConstAction(Action):
-
- def __init__(self,
- option_strings,
- dest,
- const,
- default=None,
- required=False,
- help=None,
- metavar=None):
- super(_AppendConstAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- nargs=0,
- const=const,
- default=default,
- required=required,
- help=help,
- metavar=metavar)
-
- def __call__(self, parser, namespace, values, option_string=None):
- items = _copy.copy(_ensure_value(namespace, self.dest, []))
- items.append(self.const)
- setattr(namespace, self.dest, items)
-
-
-class _CountAction(Action):
-
- def __init__(self,
- option_strings,
- dest,
- default=None,
- required=False,
- help=None):
- super(_CountAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- nargs=0,
- default=default,
- required=required,
- help=help)
-
- def __call__(self, parser, namespace, values, option_string=None):
- new_count = _ensure_value(namespace, self.dest, 0) + 1
- setattr(namespace, self.dest, new_count)
-
-
-class _HelpAction(Action):
-
- def __init__(self,
- option_strings,
- dest=SUPPRESS,
- default=SUPPRESS,
- help=None):
- super(_HelpAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- default=default,
- nargs=0,
- help=help)
-
- def __call__(self, parser, namespace, values, option_string=None):
- parser.print_help()
- parser.exit()
-
-
-class _VersionAction(Action):
-
- def __init__(self,
- option_strings,
- version=None,
- dest=SUPPRESS,
- default=SUPPRESS,
- help=None):
- super(_VersionAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- default=default,
- nargs=0,
- help=help)
- self.version = version
-
- def __call__(self, parser, namespace, values, option_string=None):
- version = self.version
- if version is None:
- version = parser.version
- formatter = parser._get_formatter()
- formatter.add_text(version)
- parser.exit(message=formatter.format_help())
-
-
-class _SubParsersAction(Action):
-
- class _ChoicesPseudoAction(Action):
-
- def __init__(self, name, help):
- sup = super(_SubParsersAction._ChoicesPseudoAction, self)
- sup.__init__(option_strings=[], dest=name, help=help)
-
- def __init__(self,
- option_strings,
- prog,
- parser_class,
- dest=SUPPRESS,
- help=None,
- metavar=None):
-
- self._prog_prefix = prog
- self._parser_class = parser_class
- self._name_parser_map = {}
- self._choices_actions = []
-
- super(_SubParsersAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- nargs=PARSER,
- choices=self._name_parser_map,
- help=help,
- metavar=metavar)
-
- def add_parser(self, name, **kwargs):
- # set prog from the existing prefix
- if kwargs.get('prog') is None:
- kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
-
- # create a pseudo-action to hold the choice help
- if 'help' in kwargs:
- help = kwargs.pop('help')
- choice_action = self._ChoicesPseudoAction(name, help)
- self._choices_actions.append(choice_action)
-
- # create the parser and add it to the map
- parser = self._parser_class(**kwargs)
- self._name_parser_map[name] = parser
- return parser
-
- def _get_subactions(self):
- return self._choices_actions
-
- def __call__(self, parser, namespace, values, option_string=None):
- parser_name = values[0]
- arg_strings = values[1:]
-
- # set the parser name if requested
- if self.dest is not SUPPRESS:
- setattr(namespace, self.dest, parser_name)
-
- # select the parser
- try:
- parser = self._name_parser_map[parser_name]
- except KeyError:
- tup = parser_name, ', '.join(self._name_parser_map)
- msg = _('unknown parser %r (choices: %s)' % tup)
- raise ArgumentError(self, msg)
-
- # parse all the remaining options into the namespace
- parser.parse_args(arg_strings, namespace)
-
-
-# ==============
-# Type classes
-# ==============
-
-class FileType(object):
- """Factory for creating file object types
-
- Instances of FileType are typically passed as type= arguments to the
- ArgumentParser add_argument() method.
-
- Keyword Arguments:
- - mode -- A string indicating how the file is to be opened. Accepts the
- same values as the builtin open() function.
- - bufsize -- The file's desired buffer size. Accepts the same values as
- the builtin open() function.
- """
-
- def __init__(self, mode='r', bufsize=None):
- self._mode = mode
- self._bufsize = bufsize
-
- def __call__(self, string):
- # the special argument "-" means sys.std{in,out}
- if string == '-':
- if 'r' in self._mode:
- return _sys.stdin
- elif 'w' in self._mode:
- return _sys.stdout
- else:
- msg = _('argument "-" with mode %r' % self._mode)
- raise ValueError(msg)
-
- # all other arguments are used as file names
- if self._bufsize:
- return open(string, self._mode, self._bufsize)
- else:
- return open(string, self._mode)
-
- def __repr__(self):
- args = [self._mode, self._bufsize]
- args_str = ', '.join([repr(arg) for arg in args if arg is not None])
- return '%s(%s)' % (type(self).__name__, args_str)
-
-# ===========================
-# Optional and Positional Parsing
-# ===========================
-
-class Namespace(_AttributeHolder):
- """Simple object for storing attributes.
-
- Implements equality by attribute names and values, and provides a simple
- string representation.
- """
-
- def __init__(self, **kwargs):
- for name in kwargs:
- setattr(self, name, kwargs[name])
-
- def __eq__(self, other):
- return vars(self) == vars(other)
-
- def __ne__(self, other):
- return not (self == other)
-
- def __contains__(self, key):
- return key in self.__dict__
-
-
-class _ActionsContainer(object):
-
- def __init__(self,
- description,
- prefix_chars,
- argument_default,
- conflict_handler):
- super(_ActionsContainer, self).__init__()
-
- self.description = description
- self.argument_default = argument_default
- self.prefix_chars = prefix_chars
- self.conflict_handler = conflict_handler
-
- # set up registries
- self._registries = {}
-
- # register actions
- self.register('action', None, _StoreAction)
- self.register('action', 'store', _StoreAction)
- self.register('action', 'store_const', _StoreConstAction)
- self.register('action', 'store_true', _StoreTrueAction)
- self.register('action', 'store_false', _StoreFalseAction)
- self.register('action', 'append', _AppendAction)
- self.register('action', 'append_const', _AppendConstAction)
- self.register('action', 'count', _CountAction)
- self.register('action', 'help', _HelpAction)
- self.register('action', 'version', _VersionAction)
- self.register('action', 'parsers', _SubParsersAction)
-
- # raise an exception if the conflict handler is invalid
- self._get_handler()
-
- # action storage
- self._actions = []
- self._option_string_actions = {}
-
- # groups
- self._action_groups = []
- self._mutually_exclusive_groups = []
-
- # defaults storage
- self._defaults = {}
-
- # determines whether an "option" looks like a negative number
- self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
-
- # whether or not there are any optionals that look like negative
- # numbers -- uses a list so it can be shared and edited
- self._has_negative_number_optionals = []
-
- # ====================
- # Registration methods
- # ====================
- def register(self, registry_name, value, object):
- registry = self._registries.setdefault(registry_name, {})
- registry[value] = object
-
- def _registry_get(self, registry_name, value, default=None):
- return self._registries[registry_name].get(value, default)
-
- # ==================================
- # Namespace default accessor methods
- # ==================================
- def set_defaults(self, **kwargs):
- self._defaults.update(kwargs)
-
- # if these defaults match any existing arguments, replace
- # the previous default on the object with the new one
- for action in self._actions:
- if action.dest in kwargs:
- action.default = kwargs[action.dest]
-
- def get_default(self, dest):
- for action in self._actions:
- if action.dest == dest and action.default is not None:
- return action.default
- return self._defaults.get(dest, None)
-
-
- # =======================
- # Adding argument actions
- # =======================
- def add_argument(self, *args, **kwargs):
- """
- add_argument(dest, ..., name=value, ...)
- add_argument(option_string, option_string, ..., name=value, ...)
- """
-
- # if no positional args are supplied or only one is supplied and
- # it doesn't look like an option string, parse a positional
- # argument
- chars = self.prefix_chars
- if not args or len(args) == 1 and args[0][0] not in chars:
- if args and 'dest' in kwargs:
- raise ValueError('dest supplied twice for positional argument')
- kwargs = self._get_positional_kwargs(*args, **kwargs)
-
- # otherwise, we're adding an optional argument
- else:
- kwargs = self._get_optional_kwargs(*args, **kwargs)
-
- # if no default was supplied, use the parser-level default
- if 'default' not in kwargs:
- dest = kwargs['dest']
- if dest in self._defaults:
- kwargs['default'] = self._defaults[dest]
- elif self.argument_default is not None:
- kwargs['default'] = self.argument_default
-
- # create the action object, and add it to the parser
- action_class = self._pop_action_class(kwargs)
- if not _callable(action_class):
- raise ValueError('unknown action "%s"' % action_class)
- action = action_class(**kwargs)
-
- # raise an error if the action type is not callable
- type_func = self._registry_get('type', action.type, action.type)
- if not _callable(type_func):
- raise ValueError('%r is not callable' % type_func)
-
- return self._add_action(action)
-
- def add_argument_group(self, *args, **kwargs):
- group = _ArgumentGroup(self, *args, **kwargs)
- self._action_groups.append(group)
- return group
-
- def add_mutually_exclusive_group(self, **kwargs):
- group = _MutuallyExclusiveGroup(self, **kwargs)
- self._mutually_exclusive_groups.append(group)
- return group
-
- def _add_action(self, action):
- # resolve any conflicts
- self._check_conflict(action)
-
- # add to actions list
- self._actions.append(action)
- action.container = self
-
- # index the action by any option strings it has
- for option_string in action.option_strings:
- self._option_string_actions[option_string] = action
-
- # set the flag if any option strings look like negative numbers
- for option_string in action.option_strings:
- if self._negative_number_matcher.match(option_string):
- if not self._has_negative_number_optionals:
- self._has_negative_number_optionals.append(True)
-
- # return the created action
- return action
-
- def _remove_action(self, action):
- self._actions.remove(action)
-
- def _add_container_actions(self, container):
- # collect groups by titles
- title_group_map = {}
- for group in self._action_groups:
- if group.title in title_group_map:
- msg = _('cannot merge actions - two groups are named %r')
- raise ValueError(msg % (group.title))
- title_group_map[group.title] = group
-
- # map each action to its group
- group_map = {}
- for group in container._action_groups:
-
- # if a group with the title exists, use that, otherwise
- # create a new group matching the container's group
- if group.title not in title_group_map:
- title_group_map[group.title] = self.add_argument_group(
- title=group.title,
- description=group.description,
- conflict_handler=group.conflict_handler)
-
- # map the actions to their new group
- for action in group._group_actions:
- group_map[action] = title_group_map[group.title]
-
- # add container's mutually exclusive groups
- # NOTE: if add_mutually_exclusive_group ever gains title= and
- # description= then this code will need to be expanded as above
- for group in container._mutually_exclusive_groups:
- mutex_group = self.add_mutually_exclusive_group(
- required=group.required)
-
- # map the actions to their new mutex group
- for action in group._group_actions:
- group_map[action] = mutex_group
-
- # add all actions to this container or their group
- for action in container._actions:
- group_map.get(action, self)._add_action(action)
-
- def _get_positional_kwargs(self, dest, **kwargs):
- # make sure required is not specified
- if 'required' in kwargs:
- msg = _("'required' is an invalid argument for positionals")
- raise TypeError(msg)
-
- # mark positional arguments as required if at least one is
- # always required
- if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
- kwargs['required'] = True
- if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
- kwargs['required'] = True
-
- # return the keyword arguments with no option strings
- return dict(kwargs, dest=dest, option_strings=[])
-
- def _get_optional_kwargs(self, *args, **kwargs):
- # determine short and long option strings
- option_strings = []
- long_option_strings = []
- for option_string in args:
- # error on strings that don't start with an appropriate prefix
- if not option_string[0] in self.prefix_chars:
- msg = _('invalid option string %r: '
- 'must start with a character %r')
- tup = option_string, self.prefix_chars
- raise ValueError(msg % tup)
-
- # strings starting with two prefix characters are long options
- option_strings.append(option_string)
- if option_string[0] in self.prefix_chars:
- if len(option_string) > 1:
- if option_string[1] in self.prefix_chars:
- long_option_strings.append(option_string)
-
- # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
- dest = kwargs.pop('dest', None)
- if dest is None:
- if long_option_strings:
- dest_option_string = long_option_strings[0]
- else:
- dest_option_string = option_strings[0]
- dest = dest_option_string.lstrip(self.prefix_chars)
- if not dest:
- msg = _('dest= is required for options like %r')
- raise ValueError(msg % option_string)
- dest = dest.replace('-', '_')
-
- # return the updated keyword arguments
- return dict(kwargs, dest=dest, option_strings=option_strings)
-
- def _pop_action_class(self, kwargs, default=None):
- action = kwargs.pop('action', default)
- return self._registry_get('action', action, action)
-
- def _get_handler(self):
- # determine function from conflict handler string
- handler_func_name = '_handle_conflict_%s' % self.conflict_handler
- try:
- return getattr(self, handler_func_name)
- except AttributeError:
- msg = _('invalid conflict_resolution value: %r')
- raise ValueError(msg % self.conflict_handler)
-
- def _check_conflict(self, action):
-
- # find all options that conflict with this option
- confl_optionals = []
- for option_string in action.option_strings:
- if option_string in self._option_string_actions:
- confl_optional = self._option_string_actions[option_string]
- confl_optionals.append((option_string, confl_optional))
-
- # resolve any conflicts
- if confl_optionals:
- conflict_handler = self._get_handler()
- conflict_handler(action, confl_optionals)
-
- def _handle_conflict_error(self, action, conflicting_actions):
- message = _('conflicting option string(s): %s')
- conflict_string = ', '.join([option_string
- for option_string, action
- in conflicting_actions])
- raise ArgumentError(action, message % conflict_string)
-
- def _handle_conflict_resolve(self, action, conflicting_actions):
-
- # remove all conflicting options
- for option_string, action in conflicting_actions:
-
- # remove the conflicting option
- action.option_strings.remove(option_string)
- self._option_string_actions.pop(option_string, None)
-
- # if the option now has no option string, remove it from the
- # container holding it
- if not action.option_strings:
- action.container._remove_action(action)
-
-
-class _ArgumentGroup(_ActionsContainer):
-
- def __init__(self, container, title=None, description=None, **kwargs):
- # add any missing keyword arguments by checking the container
- update = kwargs.setdefault
- update('conflict_handler', container.conflict_handler)
- update('prefix_chars', container.prefix_chars)
- update('argument_default', container.argument_default)
- super_init = super(_ArgumentGroup, self).__init__
- super_init(description=description, **kwargs)
-
- # group attributes
- self.title = title
- self._group_actions = []
-
- # share most attributes with the container
- self._registries = container._registries
- self._actions = container._actions
- self._option_string_actions = container._option_string_actions
- self._defaults = container._defaults
- self._has_negative_number_optionals = \
- container._has_negative_number_optionals
-
- def _add_action(self, action):
- action = super(_ArgumentGroup, self)._add_action(action)
- self._group_actions.append(action)
- return action
-
- def _remove_action(self, action):
- super(_ArgumentGroup, self)._remove_action(action)
- self._group_actions.remove(action)
-
-
-class _MutuallyExclusiveGroup(_ArgumentGroup):
-
- def __init__(self, container, required=False):
- super(_MutuallyExclusiveGroup, self).__init__(container)
- self.required = required
- self._container = container
-
- def _add_action(self, action):
- if action.required:
- msg = _('mutually exclusive arguments must be optional')
- raise ValueError(msg)
- action = self._container._add_action(action)
- self._group_actions.append(action)
- return action
-
- def _remove_action(self, action):
- self._container._remove_action(action)
- self._group_actions.remove(action)
-
-
-class ArgumentParser(_AttributeHolder, _ActionsContainer):
- """Object for parsing command line strings into Python objects.
-
- Keyword Arguments:
- - prog -- The name of the program (default: sys.argv[0])
- - usage -- A usage message (default: auto-generated from arguments)
- - description -- A description of what the program does
- - epilog -- Text following the argument descriptions
- - parents -- Parsers whose arguments should be copied into this one
- - formatter_class -- HelpFormatter class for printing help messages
- - prefix_chars -- Characters that prefix optional arguments
- - fromfile_prefix_chars -- Characters that prefix files containing
- additional arguments
- - argument_default -- The default value for all arguments
- - conflict_handler -- String indicating how to handle conflicts
- - add_help -- Add a -h/-help option
- """
-
- def __init__(self,
- prog=None,
- usage=None,
- description=None,
- epilog=None,
- version=None,
- parents=[],
- formatter_class=HelpFormatter,
- prefix_chars='-',
- fromfile_prefix_chars=None,
- argument_default=None,
- conflict_handler='error',
- add_help=True):
-
- if version is not None:
- import warnings
- warnings.warn(
- """The "version" argument to ArgumentParser is deprecated. """
- """Please use """
- """"add_argument(..., action='version', version="N", ...)" """
- """instead""", DeprecationWarning)
-
- superinit = super(ArgumentParser, self).__init__
- superinit(description=description,
- prefix_chars=prefix_chars,
- argument_default=argument_default,
- conflict_handler=conflict_handler)
-
- # default setting for prog
- if prog is None:
- prog = _os.path.basename(_sys.argv[0])
-
- self.prog = prog
- self.usage = usage
- self.epilog = epilog
- self.version = version
- self.formatter_class = formatter_class
- self.fromfile_prefix_chars = fromfile_prefix_chars
- self.add_help = add_help
-
- add_group = self.add_argument_group
- self._positionals = add_group(_('positional arguments'))
- self._optionals = add_group(_('optional arguments'))
- self._subparsers = None
-
- # register types
- def identity(string):
- return string
- self.register('type', None, identity)
-
- # add help and version arguments if necessary
- # (using explicit default to override global argument_default)
- if self.add_help:
- self.add_argument(
- '-h', '--help', action='help', default=SUPPRESS,
- help=_('show this help message and exit'))
- if self.version:
- self.add_argument(
- '-v', '--version', action='version', default=SUPPRESS,
- version=self.version,
- help=_("show program's version number and exit"))
-
- # add parent arguments and defaults
- for parent in parents:
- self._add_container_actions(parent)
- try:
- defaults = parent._defaults
- except AttributeError:
- pass
- else:
- self._defaults.update(defaults)
-
- # =======================
- # Pretty __repr__ methods
- # =======================
- def _get_kwargs(self):
- names = [
- 'prog',
- 'usage',
- 'description',
- 'version',
- 'formatter_class',
- 'conflict_handler',
- 'add_help',
- ]
- return [(name, getattr(self, name)) for name in names]
-
- # ==================================
- # Optional/Positional adding methods
- # ==================================
- def add_subparsers(self, **kwargs):
- if self._subparsers is not None:
- self.error(_('cannot have multiple subparser arguments'))
-
- # add the parser class to the arguments if it's not present
- kwargs.setdefault('parser_class', type(self))
-
- if 'title' in kwargs or 'description' in kwargs:
- title = _(kwargs.pop('title', 'subcommands'))
- description = _(kwargs.pop('description', None))
- self._subparsers = self.add_argument_group(title, description)
- else:
- self._subparsers = self._positionals
-
- # prog defaults to the usage message of this parser, skipping
- # optional arguments and with no "usage:" prefix
- if kwargs.get('prog') is None:
- formatter = self._get_formatter()
- positionals = self._get_positional_actions()
- groups = self._mutually_exclusive_groups
- formatter.add_usage(self.usage, positionals, groups, '')
- kwargs['prog'] = formatter.format_help().strip()
-
- # create the parsers action and add it to the positionals list
- parsers_class = self._pop_action_class(kwargs, 'parsers')
- action = parsers_class(option_strings=[], **kwargs)
- self._subparsers._add_action(action)
-
- # return the created parsers action
- return action
-
- def _add_action(self, action):
- if action.option_strings:
- self._optionals._add_action(action)
- else:
- self._positionals._add_action(action)
- return action
-
- def _get_optional_actions(self):
- return [action
- for action in self._actions
- if action.option_strings]
-
- def _get_positional_actions(self):
- return [action
- for action in self._actions
- if not action.option_strings]
-
- # =====================================
- # Command line argument parsing methods
- # =====================================
- def parse_args(self, args=None, namespace=None):
- args, argv = self.parse_known_args(args, namespace)
- if argv:
- msg = _('unrecognized arguments: %s')
- self.error(msg % ' '.join(argv))
- return args
-
- def parse_known_args(self, args=None, namespace=None):
- # args default to the system args
- if args is None:
- args = _sys.argv[1:]
-
- # default Namespace built from parser defaults
- if namespace is None:
- namespace = Namespace()
-
- # add any action defaults that aren't present
- for action in self._actions:
- if action.dest is not SUPPRESS:
- if not hasattr(namespace, action.dest):
- if action.default is not SUPPRESS:
- default = action.default
- if isinstance(action.default, _basestring):
- default = self._get_value(action, default)
- setattr(namespace, action.dest, default)
-
- # add any parser defaults that aren't present
- for dest in self._defaults:
- if not hasattr(namespace, dest):
- setattr(namespace, dest, self._defaults[dest])
-
- # parse the arguments and exit if there are any errors
- try:
- return self._parse_known_args(args, namespace)
- except ArgumentError:
- err = _sys.exc_info()[1]
- self.error(str(err))
-
- def _parse_known_args(self, arg_strings, namespace):
- # replace arg strings that are file references
- if self.fromfile_prefix_chars is not None:
- arg_strings = self._read_args_from_files(arg_strings)
-
- # map all mutually exclusive arguments to the other arguments
- # they can't occur with
- action_conflicts = {}
- for mutex_group in self._mutually_exclusive_groups:
- group_actions = mutex_group._group_actions
- for i, mutex_action in enumerate(mutex_group._group_actions):
- conflicts = action_conflicts.setdefault(mutex_action, [])
- conflicts.extend(group_actions[:i])
- conflicts.extend(group_actions[i + 1:])
-
- # find all option indices, and determine the arg_string_pattern
- # which has an 'O' if there is an option at an index,
- # an 'A' if there is an argument, or a '-' if there is a '--'
- option_string_indices = {}
- arg_string_pattern_parts = []
- arg_strings_iter = iter(arg_strings)
- for i, arg_string in enumerate(arg_strings_iter):
-
- # all args after -- are non-options
- if arg_string == '--':
- arg_string_pattern_parts.append('-')
- for arg_string in arg_strings_iter:
- arg_string_pattern_parts.append('A')
-
- # otherwise, add the arg to the arg strings
- # and note the index if it was an option
- else:
- option_tuple = self._parse_optional(arg_string)
- if option_tuple is None:
- pattern = 'A'
- else:
- option_string_indices[i] = option_tuple
- pattern = 'O'
- arg_string_pattern_parts.append(pattern)
-
- # join the pieces together to form the pattern
- arg_strings_pattern = ''.join(arg_string_pattern_parts)
-
- # converts arg strings to the appropriate and then takes the action
- seen_actions = _set()
- seen_non_default_actions = _set()
-
- def take_action(action, argument_strings, option_string=None):
- seen_actions.add(action)
- argument_values = self._get_values(action, argument_strings)
-
- # error if this argument is not allowed with other previously
- # seen arguments, assuming that actions that use the default
- # value don't really count as "present"
- if argument_values is not action.default:
- seen_non_default_actions.add(action)
- for conflict_action in action_conflicts.get(action, []):
- if conflict_action in seen_non_default_actions:
- msg = _('not allowed with argument %s')
- action_name = _get_action_name(conflict_action)
- raise ArgumentError(action, msg % action_name)
-
- # take the action if we didn't receive a SUPPRESS value
- # (e.g. from a default)
- if argument_values is not SUPPRESS:
- action(self, namespace, argument_values, option_string)
-
- # function to convert arg_strings into an optional action
- def consume_optional(start_index):
-
- # get the optional identified at this index
- option_tuple = option_string_indices[start_index]
- action, option_string, explicit_arg = option_tuple
-
- # identify additional optionals in the same arg string
- # (e.g. -xyz is the same as -x -y -z if no args are required)
- match_argument = self._match_argument
- action_tuples = []
- while True:
-
- # if we found no optional action, skip it
- if action is None:
- extras.append(arg_strings[start_index])
- return start_index + 1
-
- # if there is an explicit argument, try to match the
- # optional's string arguments to only this
- if explicit_arg is not None:
- arg_count = match_argument(action, 'A')
-
- # if the action is a single-dash option and takes no
- # arguments, try to parse more single-dash options out
- # of the tail of the option string
- chars = self.prefix_chars
- if arg_count == 0 and option_string[1] not in chars:
- action_tuples.append((action, [], option_string))
- for char in self.prefix_chars:
- option_string = char + explicit_arg[0]
- explicit_arg = explicit_arg[1:] or None
- optionals_map = self._option_string_actions
- if option_string in optionals_map:
- action = optionals_map[option_string]
- break
- else:
- msg = _('ignored explicit argument %r')
- raise ArgumentError(action, msg % explicit_arg)
-
- # if the action expect exactly one argument, we've
- # successfully matched the option; exit the loop
- elif arg_count == 1:
- stop = start_index + 1
- args = [explicit_arg]
- action_tuples.append((action, args, option_string))
- break
-
- # error if a double-dash option did not use the
- # explicit argument
- else:
- msg = _('ignored explicit argument %r')
- raise ArgumentError(action, msg % explicit_arg)
-
- # if there is no explicit argument, try to match the
- # optional's string arguments with the following strings
- # if successful, exit the loop
- else:
- start = start_index + 1
- selected_patterns = arg_strings_pattern[start:]
- arg_count = match_argument(action, selected_patterns)
- stop = start + arg_count
- args = arg_strings[start:stop]
- action_tuples.append((action, args, option_string))
- break
-
- # add the Optional to the list and return the index at which
- # the Optional's string args stopped
- assert action_tuples
- for action, args, option_string in action_tuples:
- take_action(action, args, option_string)
- return stop
-
- # the list of Positionals left to be parsed; this is modified
- # by consume_positionals()
- positionals = self._get_positional_actions()
-
- # function to convert arg_strings into positional actions
- def consume_positionals(start_index):
- # match as many Positionals as possible
- match_partial = self._match_arguments_partial
- selected_pattern = arg_strings_pattern[start_index:]
- arg_counts = match_partial(positionals, selected_pattern)
-
- # slice off the appropriate arg strings for each Positional
- # and add the Positional and its args to the list
- for action, arg_count in zip(positionals, arg_counts):
- args = arg_strings[start_index: start_index + arg_count]
- start_index += arg_count
- take_action(action, args)
-
- # slice off the Positionals that we just parsed and return the
- # index at which the Positionals' string args stopped
- positionals[:] = positionals[len(arg_counts):]
- return start_index
-
- # consume Positionals and Optionals alternately, until we have
- # passed the last option string
- extras = []
- start_index = 0
- if option_string_indices:
- max_option_string_index = max(option_string_indices)
- else:
- max_option_string_index = -1
- while start_index <= max_option_string_index:
-
- # consume any Positionals preceding the next option
- next_option_string_index = min([
- index
- for index in option_string_indices
- if index >= start_index])
- if start_index != next_option_string_index:
- positionals_end_index = consume_positionals(start_index)
-
- # only try to parse the next optional if we didn't consume
- # the option string during the positionals parsing
- if positionals_end_index > start_index:
- start_index = positionals_end_index
- continue
- else:
- start_index = positionals_end_index
-
- # if we consumed all the positionals we could and we're not
- # at the index of an option string, there were extra arguments
- if start_index not in option_string_indices:
- strings = arg_strings[start_index:next_option_string_index]
- extras.extend(strings)
- start_index = next_option_string_index
-
- # consume the next optional and any arguments for it
- start_index = consume_optional(start_index)
-
- # consume any positionals following the last Optional
- stop_index = consume_positionals(start_index)
-
- # if we didn't consume all the argument strings, there were extras
- extras.extend(arg_strings[stop_index:])
-
- # if we didn't use all the Positional objects, there were too few
- # arg strings supplied.
- if positionals:
- self.error(_('too few arguments'))
-
- # make sure all required actions were present
- for action in self._actions:
- if action.required:
- if action not in seen_actions:
- name = _get_action_name(action)
- self.error(_('argument %s is required') % name)
-
- # make sure all required groups had one option present
- for group in self._mutually_exclusive_groups:
- if group.required:
- for action in group._group_actions:
- if action in seen_non_default_actions:
- break
-
- # if no actions were used, report the error
- else:
- names = [_get_action_name(action)
- for action in group._group_actions
- if action.help is not SUPPRESS]
- msg = _('one of the arguments %s is required')
- self.error(msg % ' '.join(names))
-
- # return the updated namespace and the extra arguments
- return namespace, extras
-
- def _read_args_from_files(self, arg_strings):
- # expand arguments referencing files
- new_arg_strings = []
- for arg_string in arg_strings:
-
- # for regular arguments, just add them back into the list
- if arg_string[0] not in self.fromfile_prefix_chars:
- new_arg_strings.append(arg_string)
-
- # replace arguments referencing files with the file content
- else:
- try:
- args_file = open(arg_string[1:])
- try:
- arg_strings = []
- for arg_line in args_file.read().splitlines():
- for arg in self.convert_arg_line_to_args(arg_line):
- arg_strings.append(arg)
- arg_strings = self._read_args_from_files(arg_strings)
- new_arg_strings.extend(arg_strings)
- finally:
- args_file.close()
- except IOError:
- err = _sys.exc_info()[1]
- self.error(str(err))
-
- # return the modified argument list
- return new_arg_strings
-
- def convert_arg_line_to_args(self, arg_line):
- return [arg_line]
-
- def _match_argument(self, action, arg_strings_pattern):
- # match the pattern for this action to the arg strings
- nargs_pattern = self._get_nargs_pattern(action)
- match = _re.match(nargs_pattern, arg_strings_pattern)
-
- # raise an exception if we weren't able to find a match
- if match is None:
- nargs_errors = {
- None: _('expected one argument'),
- OPTIONAL: _('expected at most one argument'),
- ONE_OR_MORE: _('expected at least one argument'),
- }
- default = _('expected %s argument(s)') % action.nargs
- msg = nargs_errors.get(action.nargs, default)
- raise ArgumentError(action, msg)
-
- # return the number of arguments matched
- return len(match.group(1))
-
- def _match_arguments_partial(self, actions, arg_strings_pattern):
- # progressively shorten the actions list by slicing off the
- # final actions until we find a match
- result = []
- for i in range(len(actions), 0, -1):
- actions_slice = actions[:i]
- pattern = ''.join([self._get_nargs_pattern(action)
- for action in actions_slice])
- match = _re.match(pattern, arg_strings_pattern)
- if match is not None:
- result.extend([len(string) for string in match.groups()])
- break
-
- # return the list of arg string counts
- return result
-
- def _parse_optional(self, arg_string):
- # if it's an empty string, it was meant to be a positional
- if not arg_string:
- return None
-
- # if it doesn't start with a prefix, it was meant to be positional
- if not arg_string[0] in self.prefix_chars:
- return None
-
- # if the option string is present in the parser, return the action
- if arg_string in self._option_string_actions:
- action = self._option_string_actions[arg_string]
- return action, arg_string, None
-
- # if it's just a single character, it was meant to be positional
- if len(arg_string) == 1:
- return None
-
- # if the option string before the "=" is present, return the action
- if '=' in arg_string:
- option_string, explicit_arg = arg_string.split('=', 1)
- if option_string in self._option_string_actions:
- action = self._option_string_actions[option_string]
- return action, option_string, explicit_arg
-
- # search through all possible prefixes of the option string
- # and all actions in the parser for possible interpretations
- option_tuples = self._get_option_tuples(arg_string)
-
- # if multiple actions match, the option string was ambiguous
- if len(option_tuples) > 1:
- options = ', '.join([option_string
- for action, option_string, explicit_arg in option_tuples])
- tup = arg_string, options
- self.error(_('ambiguous option: %s could match %s') % tup)
-
- # if exactly one action matched, this segmentation is good,
- # so return the parsed action
- elif len(option_tuples) == 1:
- option_tuple, = option_tuples
- return option_tuple
-
- # if it was not found as an option, but it looks like a negative
- # number, it was meant to be positional
- # unless there are negative-number-like options
- if self._negative_number_matcher.match(arg_string):
- if not self._has_negative_number_optionals:
- return None
-
- # if it contains a space, it was meant to be a positional
- if ' ' in arg_string:
- return None
-
- # it was meant to be an optional but there is no such option
- # in this parser (though it might be a valid option in a subparser)
- return None, arg_string, None
-
- def _get_option_tuples(self, option_string):
- result = []
-
- # option strings starting with two prefix characters are only
- # split at the '='
- chars = self.prefix_chars
- if option_string[0] in chars and option_string[1] in chars:
- if '=' in option_string:
- option_prefix, explicit_arg = option_string.split('=', 1)
- else:
- option_prefix = option_string
- explicit_arg = None
- for option_string in self._option_string_actions:
- if option_string.startswith(option_prefix):
- action = self._option_string_actions[option_string]
- tup = action, option_string, explicit_arg
- result.append(tup)
-
- # single character options can be concatenated with their arguments
- # but multiple character options always have to have their argument
- # separate
- elif option_string[0] in chars and option_string[1] not in chars:
- option_prefix = option_string
- explicit_arg = None
- short_option_prefix = option_string[:2]
- short_explicit_arg = option_string[2:]
-
- for option_string in self._option_string_actions:
- if option_string == short_option_prefix:
- action = self._option_string_actions[option_string]
- tup = action, option_string, short_explicit_arg
- result.append(tup)
- elif option_string.startswith(option_prefix):
- action = self._option_string_actions[option_string]
- tup = action, option_string, explicit_arg
- result.append(tup)
-
- # shouldn't ever get here
- else:
- self.error(_('unexpected option string: %s') % option_string)
-
- # return the collected option tuples
- return result
-
- def _get_nargs_pattern(self, action):
- # in all examples below, we have to allow for '--' args
- # which are represented as '-' in the pattern
- nargs = action.nargs
-
- # the default (None) is assumed to be a single argument
- if nargs is None:
- nargs_pattern = '(-*A-*)'
-
- # allow zero or one arguments
- elif nargs == OPTIONAL:
- nargs_pattern = '(-*A?-*)'
-
- # allow zero or more arguments
- elif nargs == ZERO_OR_MORE:
- nargs_pattern = '(-*[A-]*)'
-
- # allow one or more arguments
- elif nargs == ONE_OR_MORE:
- nargs_pattern = '(-*A[A-]*)'
-
- # allow any number of options or arguments
- elif nargs == REMAINDER:
- nargs_pattern = '([-AO]*)'
-
- # allow one argument followed by any number of options or arguments
- elif nargs == PARSER:
- nargs_pattern = '(-*A[-AO]*)'
-
- # all others should be integers
- else:
- nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
-
- # if this is an optional action, -- is not allowed
- if action.option_strings:
- nargs_pattern = nargs_pattern.replace('-*', '')
- nargs_pattern = nargs_pattern.replace('-', '')
-
- # return the pattern
- return nargs_pattern
-
- # ========================
- # Value conversion methods
- # ========================
- def _get_values(self, action, arg_strings):
- # for everything but PARSER args, strip out '--'
- if action.nargs not in [PARSER, REMAINDER]:
- arg_strings = [s for s in arg_strings if s != '--']
-
- # optional argument produces a default when not present
- if not arg_strings and action.nargs == OPTIONAL:
- if action.option_strings:
- value = action.const
- else:
- value = action.default
- if isinstance(value, _basestring):
- value = self._get_value(action, value)
- self._check_value(action, value)
-
- # when nargs='*' on a positional, if there were no command-line
- # args, use the default if it is anything other than None
- elif (not arg_strings and action.nargs == ZERO_OR_MORE and
- not action.option_strings):
- if action.default is not None:
- value = action.default
- else:
- value = arg_strings
- self._check_value(action, value)
-
- # single argument or optional argument produces a single value
- elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
- arg_string, = arg_strings
- value = self._get_value(action, arg_string)
- self._check_value(action, value)
-
- # REMAINDER arguments convert all values, checking none
- elif action.nargs == REMAINDER:
- value = [self._get_value(action, v) for v in arg_strings]
-
- # PARSER arguments convert all values, but check only the first
- elif action.nargs == PARSER:
- value = [self._get_value(action, v) for v in arg_strings]
- self._check_value(action, value[0])
-
- # all other types of nargs produce a list
- else:
- value = [self._get_value(action, v) for v in arg_strings]
- for v in value:
- self._check_value(action, v)
-
- # return the converted value
- return value
-
- def _get_value(self, action, arg_string):
- type_func = self._registry_get('type', action.type, action.type)
- if not _callable(type_func):
- msg = _('%r is not callable')
- raise ArgumentError(action, msg % type_func)
-
- # convert the value to the appropriate type
- try:
- result = type_func(arg_string)
-
- # ArgumentTypeErrors indicate errors
- except ArgumentTypeError:
- name = getattr(action.type, '__name__', repr(action.type))
- msg = str(_sys.exc_info()[1])
- raise ArgumentError(action, msg)
-
- # TypeErrors or ValueErrors also indicate errors
- except (TypeError, ValueError):
- name = getattr(action.type, '__name__', repr(action.type))
- msg = _('invalid %s value: %r')
- raise ArgumentError(action, msg % (name, arg_string))
-
- # return the converted value
- return result
-
- def _check_value(self, action, value):
- # converted value must be one of the choices (if specified)
- if action.choices is not None and value not in action.choices:
- tup = value, ', '.join(map(repr, action.choices))
- msg = _('invalid choice: %r (choose from %s)') % tup
- raise ArgumentError(action, msg)
-
- # =======================
- # Help-formatting methods
- # =======================
- def format_usage(self):
- formatter = self._get_formatter()
- formatter.add_usage(self.usage, self._actions,
- self._mutually_exclusive_groups)
- return formatter.format_help()
-
- def format_help(self):
- formatter = self._get_formatter()
-
- # usage
- formatter.add_usage(self.usage, self._actions,
- self._mutually_exclusive_groups)
-
- # description
- formatter.add_text(self.description)
-
- # positionals, optionals and user-defined groups
- for action_group in self._action_groups:
- formatter.start_section(action_group.title)
- formatter.add_text(action_group.description)
- formatter.add_arguments(action_group._group_actions)
- formatter.end_section()
-
- # epilog
- formatter.add_text(self.epilog)
-
- # determine help from format above
- return formatter.format_help()
-
- def format_version(self):
- import warnings
- warnings.warn(
- 'The format_version method is deprecated -- the "version" '
- 'argument to ArgumentParser is no longer supported.',
- DeprecationWarning)
- formatter = self._get_formatter()
- formatter.add_text(self.version)
- return formatter.format_help()
-
- def _get_formatter(self):
- return self.formatter_class(prog=self.prog)
-
- # =====================
- # Help-printing methods
- # =====================
- def print_usage(self, file=None):
- if file is None:
- file = _sys.stdout
- self._print_message(self.format_usage(), file)
-
- def print_help(self, file=None):
- if file is None:
- file = _sys.stdout
- self._print_message(self.format_help(), file)
-
- def print_version(self, file=None):
- import warnings
- warnings.warn(
- 'The print_version method is deprecated -- the "version" '
- 'argument to ArgumentParser is no longer supported.',
- DeprecationWarning)
- self._print_message(self.format_version(), file)
-
- def _print_message(self, message, file=None):
- if message:
- if file is None:
- file = _sys.stderr
- file.write(message)
-
- # ===============
- # Exiting methods
- # ===============
- def exit(self, status=0, message=None):
- if message:
- self._print_message(message, _sys.stderr)
- _sys.exit(status)
-
- def error(self, message):
- """error(message: string)
-
- Prints a usage message incorporating the message to stderr and
- exits.
-
- If you override this in a subclass, it should not return -- it
- should either exit or raise an exception.
- """
- self.print_usage(_sys.stderr)
- self.exit(2, _('%s: error: %s\n') % (self.prog, message))
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/PKG-INFO b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/PKG-INFO
deleted file mode 100644
index 86e89d20c..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/PKG-INFO
+++ /dev/null
@@ -1,59 +0,0 @@
-Metadata-Version: 1.0
-Name: Jinja2
-Version: 2.4.1
-Summary: A small but fast and easy to use stand-alone template engine written in pure python.
-Home-page: http://jinja.pocoo.org/
-Author: Armin Ronacher
-Author-email: armin.ronacher@active-4.com
-License: BSD
-Description:
- Jinja2
- ~~~~~~
-
- Jinja2 is a template engine written in pure Python. It provides a
- `Django`_ inspired non-XML syntax but supports inline expressions and
- an optional `sandboxed`_ environment.
-
- Nutshell
- --------
-
- Here a small example of a Jinja template::
-
- {% extends 'base.html' %}
- {% block title %}Memberlist{% endblock %}
- {% block content %}
- <ul>
- {% for user in users %}
- <li><a href="{{ user.url }}">{{ user.username }}</a></li>
- {% endfor %}
- </ul>
- {% endblock %}
-
- Philosophy
- ----------
-
- Application logic is for the controller but don't try to make the life
- for the template designer too hard by giving him too few functionality.
-
- For more informations visit the new `Jinja2 webpage`_ and `documentation`_.
-
- The `Jinja2 tip`_ is installable via `easy_install` with ``easy_install
- Jinja2==dev``.
-
- .. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
- .. _Django: http://www.djangoproject.com/
- .. _Jinja2 webpage: http://jinja.pocoo.org/
- .. _documentation: http://jinja.pocoo.org/2/documentation/
- .. _Jinja2 tip: http://dev.pocoo.org/hg/jinja2-main/archive/tip.tar.gz#egg=Jinja2-dev
-
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Environment :: Web Environment
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: Topic :: Text Processing :: Markup :: HTML
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/SOURCES.txt b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/SOURCES.txt
deleted file mode 100644
index 69142db1a..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/SOURCES.txt
+++ /dev/null
@@ -1,186 +0,0 @@
-AUTHORS
-CHANGES
-LICENSE
-MANIFEST.in
-Makefile
-setup.cfg
-setup.py
-Jinja2.egg-info/PKG-INFO
-Jinja2.egg-info/SOURCES.txt
-Jinja2.egg-info/dependency_links.txt
-Jinja2.egg-info/entry_points.txt
-Jinja2.egg-info/not-zip-safe
-Jinja2.egg-info/requires.txt
-Jinja2.egg-info/top_level.txt
-artwork/jinjalogo.svg
-custom_fixers/__init__.py
-custom_fixers/fix_alt_unicode.py
-custom_fixers/fix_broken_reraising.py
-custom_fixers/fix_xrange2.py
-docs/Makefile
-docs/api.rst
-docs/cache_extension.py
-docs/changelog.rst
-docs/conf.py
-docs/extensions.rst
-docs/faq.rst
-docs/index.rst
-docs/integration.rst
-docs/intro.rst
-docs/jinjaext.py
-docs/jinjaext.pyc
-docs/sandbox.rst
-docs/switching.rst
-docs/templates.rst
-docs/tricks.rst
-docs/_build/.ignore
-docs/_build/html/.buildinfo
-docs/_build/html/api.html
-docs/_build/html/changelog.html
-docs/_build/html/extensions.html
-docs/_build/html/faq.html
-docs/_build/html/genindex.html
-docs/_build/html/index.html
-docs/_build/html/integration.html
-docs/_build/html/intro.html
-docs/_build/html/objects.inv
-docs/_build/html/sandbox.html
-docs/_build/html/search.html
-docs/_build/html/searchindex.js
-docs/_build/html/switching.html
-docs/_build/html/templates.html
-docs/_build/html/tricks.html
-docs/_build/html/_sources/api.txt
-docs/_build/html/_sources/changelog.txt
-docs/_build/html/_sources/extensions.txt
-docs/_build/html/_sources/faq.txt
-docs/_build/html/_sources/index.txt
-docs/_build/html/_sources/integration.txt
-docs/_build/html/_sources/intro.txt
-docs/_build/html/_sources/sandbox.txt
-docs/_build/html/_sources/switching.txt
-docs/_build/html/_sources/templates.txt
-docs/_build/html/_sources/tricks.txt
-docs/_build/html/_static/basic.css
-docs/_build/html/_static/darkmetal.png
-docs/_build/html/_static/default.css
-docs/_build/html/_static/doctools.js
-docs/_build/html/_static/file.png
-docs/_build/html/_static/headerbg.png
-docs/_build/html/_static/implementation.png
-docs/_build/html/_static/jinja.js
-docs/_build/html/_static/jinjabanner.png
-docs/_build/html/_static/jquery.js
-docs/_build/html/_static/metal.png
-docs/_build/html/_static/minus.png
-docs/_build/html/_static/navigation.png
-docs/_build/html/_static/note.png
-docs/_build/html/_static/plus.png
-docs/_build/html/_static/print.css
-docs/_build/html/_static/pygments.css
-docs/_build/html/_static/searchtools.js
-docs/_build/html/_static/style.css
-docs/_build/html/_static/underscore.js
-docs/_build/html/_static/watermark.png
-docs/_build/html/_static/watermark_blur.png
-docs/_static/.ignore
-docs/_static/darkmetal.png
-docs/_static/headerbg.png
-docs/_static/implementation.png
-docs/_static/jinja.js
-docs/_static/jinjabanner.png
-docs/_static/metal.png
-docs/_static/navigation.png
-docs/_static/note.png
-docs/_static/print.css
-docs/_static/style.css
-docs/_static/watermark.png
-docs/_static/watermark_blur.png
-docs/_templates/.ignore
-docs/_templates/genindex.html
-docs/_templates/layout.html
-docs/_templates/opensearch.xml
-docs/_templates/page.html
-docs/_templates/search.html
-examples/bench.py
-examples/profile.py
-examples/basic/cycle.py
-examples/basic/debugger.py
-examples/basic/inheritance.py
-examples/basic/test.py
-examples/basic/test_filter_and_linestatements.py
-examples/basic/test_loop_filter.py
-examples/basic/translate.py
-examples/basic/templates/broken.html
-examples/basic/templates/subbroken.html
-examples/rwbench/djangoext.py
-examples/rwbench/rwbench.py
-examples/rwbench/django/_form.html
-examples/rwbench/django/_input_field.html
-examples/rwbench/django/_textarea.html
-examples/rwbench/django/index.html
-examples/rwbench/django/layout.html
-examples/rwbench/genshi/helpers.html
-examples/rwbench/genshi/index.html
-examples/rwbench/genshi/layout.html
-examples/rwbench/jinja/helpers.html
-examples/rwbench/jinja/index.html
-examples/rwbench/jinja/layout.html
-examples/rwbench/mako/helpers.html
-examples/rwbench/mako/index.html
-examples/rwbench/mako/layout.html
-ext/JinjaTemplates.tmbundle.tar.gz
-ext/djangojinja2.py
-ext/inlinegettext.py
-ext/jinja.el
-ext/Vim/htmljinja.vim
-ext/Vim/jinja.vim
-ext/django2jinja/django2jinja.py
-ext/django2jinja/example.py
-ext/django2jinja/templates/index.html
-ext/django2jinja/templates/layout.html
-ext/django2jinja/templates/subtemplate.html
-jinja2/__init__.py
-jinja2/_speedups.c
-jinja2/_stringdefs.py
-jinja2/bccache.py
-jinja2/compiler.py
-jinja2/constants.py
-jinja2/debug.py
-jinja2/defaults.py
-jinja2/environment.py
-jinja2/exceptions.py
-jinja2/ext.py
-jinja2/filters.py
-jinja2/lexer.py
-jinja2/loaders.py
-jinja2/meta.py
-jinja2/nodes.py
-jinja2/optimizer.py
-jinja2/parser.py
-jinja2/runtime.py
-jinja2/sandbox.py
-jinja2/tests.py
-jinja2/utils.py
-jinja2/visitor.py
-jinja2/testsuite/__init__.py
-jinja2/testsuite/api.py
-jinja2/testsuite/core_tags.py
-jinja2/testsuite/debug.py
-jinja2/testsuite/doctests.py
-jinja2/testsuite/ext.py
-jinja2/testsuite/filters.py
-jinja2/testsuite/imports.py
-jinja2/testsuite/inheritance.py
-jinja2/testsuite/lexnparse.py
-jinja2/testsuite/loader.py
-jinja2/testsuite/regression.py
-jinja2/testsuite/security.py
-jinja2/testsuite/tests.py
-jinja2/testsuite/utils.py
-jinja2/testsuite/res/__init__.py
-jinja2/testsuite/res/__init__.pyc
-jinja2/testsuite/res/templates/broken.html
-jinja2/testsuite/res/templates/syntaxerror.html
-jinja2/testsuite/res/templates/test.html
-jinja2/testsuite/res/templates/foo/test.html
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/dependency_links.txt b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/dependency_links.txt
deleted file mode 100644
index 8b1378917..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/entry_points.txt b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/entry_points.txt
deleted file mode 100644
index 32e6b7530..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/entry_points.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-
- [babel.extractors]
- jinja2 = jinja2.ext:babel_extract[i18n]
-
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/not-zip-safe b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/not-zip-safe
deleted file mode 100644
index 8b1378917..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/not-zip-safe
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/requires.txt b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/requires.txt
deleted file mode 100644
index c17d53327..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/requires.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-[i18n]
-Babel>=0.8
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/top_level.txt b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/top_level.txt
deleted file mode 100644
index 7f7afbf3b..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/EGG-INFO/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-jinja2
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/__init__.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/__init__.py
deleted file mode 100755
index f944e11b6..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/__init__.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2
- ~~~~~~
-
- Jinja2 is a template engine written in pure Python. It provides a
- Django inspired non-XML syntax but supports inline expressions and
- an optional sandboxed environment.
-
- Nutshell
- --------
-
- Here a small example of a Jinja2 template::
-
- {% extends 'base.html' %}
- {% block title %}Memberlist{% endblock %}
- {% block content %}
- <ul>
- {% for user in users %}
- <li><a href="{{ user.url }}">{{ user.username }}</a></li>
- {% endfor %}
- </ul>
- {% endblock %}
-
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-__docformat__ = 'restructuredtext en'
-try:
- __version__ = __import__('pkg_resources') \
- .get_distribution('Jinja2').version
-except:
- __version__ = 'unknown'
-
-# high level interface
-from jinja2.environment import Environment, Template
-
-# loaders
-from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
- DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \
- ModuleLoader
-
-# bytecode caches
-from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
- MemcachedBytecodeCache
-
-# undefined types
-from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
-
-# exceptions
-from jinja2.exceptions import TemplateError, UndefinedError, \
- TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
- TemplateAssertionError
-
-# decorators and public utilities
-from jinja2.filters import environmentfilter, contextfilter, \
- evalcontextfilter
-from jinja2.utils import Markup, escape, clear_caches, \
- environmentfunction, evalcontextfunction, contextfunction, \
- is_undefined
-
-__all__ = [
- 'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
- 'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader',
- 'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache',
- 'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
- 'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
- 'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
- 'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
- 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
- 'evalcontextfilter', 'evalcontextfunction'
-]
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/_speedups.c b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/_speedups.c
deleted file mode 100644
index 3b2d71cfc..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/_speedups.c
+++ /dev/null
@@ -1,259 +0,0 @@
-/**
- * jinja2._speedups
- * ~~~~~~~~~~~~~~~~
- *
- * This module implements functions for automatic escaping in C for better
- * performance. Additionally it defines a `tb_set_next` function to patch the
- * debug traceback. If the speedups module is not compiled a ctypes
- * implementation of `tb_set_next` and Python implementations of the other
- * functions are used.
- *
- * :copyright: (c) 2009 by the Jinja Team.
- * :license: BSD.
- */
-
-#include <Python.h>
-
-#define ESCAPED_CHARS_TABLE_SIZE 63
-#define UNICHR(x) (((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL))->str);
-
-#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
-typedef int Py_ssize_t;
-#define PY_SSIZE_T_MAX INT_MAX
-#define PY_SSIZE_T_MIN INT_MIN
-#endif
-
-
-static PyObject* markup;
-static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE];
-static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE];
-
-static int
-init_constants(void)
-{
- PyObject *module;
- /* happing of characters to replace */
- escaped_chars_repl['"'] = UNICHR(""");
- escaped_chars_repl['\''] = UNICHR("'");
- escaped_chars_repl['&'] = UNICHR("&");
- escaped_chars_repl['<'] = UNICHR("<");
- escaped_chars_repl['>'] = UNICHR(">");
-
- /* lengths of those characters when replaced - 1 */
- memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len));
- escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \
- escaped_chars_delta_len['&'] = 4;
- escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3;
-
- /* import markup type so that we can mark the return value */
- module = PyImport_ImportModule("jinja2.utils");
- if (!module)
- return 0;
- markup = PyObject_GetAttrString(module, "Markup");
- Py_DECREF(module);
-
- return 1;
-}
-
-static PyObject*
-escape_unicode(PyUnicodeObject *in)
-{
- PyUnicodeObject *out;
- Py_UNICODE *inp = in->str;
- const Py_UNICODE *inp_end = in->str + in->length;
- Py_UNICODE *next_escp;
- Py_UNICODE *outp;
- Py_ssize_t delta=0, erepl=0, delta_len=0;
-
- /* First we need to figure out how long the escaped string will be */
- while (*(inp) || inp < inp_end) {
- if (*inp < ESCAPED_CHARS_TABLE_SIZE && escaped_chars_delta_len[*inp]) {
- delta += escaped_chars_delta_len[*inp];
- ++erepl;
- }
- ++inp;
- }
-
- /* Do we need to escape anything at all? */
- if (!erepl) {
- Py_INCREF(in);
- return (PyObject*)in;
- }
-
- out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, in->length + delta);
- if (!out)
- return NULL;
-
- outp = out->str;
- inp = in->str;
- while (erepl-- > 0) {
- /* look for the next substitution */
- next_escp = inp;
- while (next_escp < inp_end) {
- if (*next_escp < ESCAPED_CHARS_TABLE_SIZE &&
- (delta_len = escaped_chars_delta_len[*next_escp])) {
- ++delta_len;
- break;
- }
- ++next_escp;
- }
-
- if (next_escp > inp) {
- /* copy unescaped chars between inp and next_escp */
- Py_UNICODE_COPY(outp, inp, next_escp-inp);
- outp += next_escp - inp;
- }
-
- /* escape 'next_escp' */
- Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len);
- outp += delta_len;
-
- inp = next_escp + 1;
- }
- if (inp < inp_end)
- Py_UNICODE_COPY(outp, inp, in->length - (inp - in->str));
-
- return (PyObject*)out;
-}
-
-
-static PyObject*
-escape(PyObject *self, PyObject *text)
-{
- PyObject *s = NULL, *rv = NULL, *html;
-
- /* we don't have to escape integers, bools or floats */
- if (PyLong_CheckExact(text) ||
-#if PY_MAJOR_VERSION < 3
- PyInt_CheckExact(text) ||
-#endif
- PyFloat_CheckExact(text) || PyBool_Check(text) ||
- text == Py_None)
- return PyObject_CallFunctionObjArgs(markup, text, NULL);
-
- /* if the object has an __html__ method that performs the escaping */
- html = PyObject_GetAttrString(text, "__html__");
- if (html) {
- rv = PyObject_CallObject(html, NULL);
- Py_DECREF(html);
- return rv;
- }
-
- /* otherwise make the object unicode if it isn't, then escape */
- PyErr_Clear();
- if (!PyUnicode_Check(text)) {
-#if PY_MAJOR_VERSION < 3
- PyObject *unicode = PyObject_Unicode(text);
-#else
- PyObject *unicode = PyObject_Str(text);
-#endif
- if (!unicode)
- return NULL;
- s = escape_unicode((PyUnicodeObject*)unicode);
- Py_DECREF(unicode);
- }
- else
- s = escape_unicode((PyUnicodeObject*)text);
-
- /* convert the unicode string into a markup object. */
- rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
- Py_DECREF(s);
- return rv;
-}
-
-
-static PyObject*
-soft_unicode(PyObject *self, PyObject *s)
-{
- if (!PyUnicode_Check(s))
-#if PY_MAJOR_VERSION < 3
- return PyObject_Unicode(s);
-#else
- return PyObject_Str(s);
-#endif
- Py_INCREF(s);
- return s;
-}
-
-
-static PyObject*
-tb_set_next(PyObject *self, PyObject *args)
-{
- PyTracebackObject *tb, *old;
- PyObject *next;
-
- if (!PyArg_ParseTuple(args, "O!O:tb_set_next", &PyTraceBack_Type, &tb, &next))
- return NULL;
- if (next == Py_None)
- next = NULL;
- else if (!PyTraceBack_Check(next)) {
- PyErr_SetString(PyExc_TypeError,
- "tb_set_next arg 2 must be traceback or None");
- return NULL;
- }
- else
- Py_INCREF(next);
-
- old = tb->tb_next;
- tb->tb_next = (PyTracebackObject*)next;
- Py_XDECREF(old);
-
- Py_INCREF(Py_None);
- return Py_None;
-}
-
-
-static PyMethodDef module_methods[] = {
- {"escape", (PyCFunction)escape, METH_O,
- "escape(s) -> markup\n\n"
- "Convert the characters &, <, >, ', and \" in string s to HTML-safe\n"
- "sequences. Use this if you need to display text that might contain\n"
- "such characters in HTML. Marks return value as markup string."},
- {"soft_unicode", (PyCFunction)soft_unicode, METH_O,
- "soft_unicode(object) -> string\n\n"
- "Make a string unicode if it isn't already. That way a markup\n"
- "string is not converted back to unicode."},
- {"tb_set_next", (PyCFunction)tb_set_next, METH_VARARGS,
- "Set the tb_next member of a traceback object."},
- {NULL, NULL, 0, NULL} /* Sentinel */
-};
-
-
-#if PY_MAJOR_VERSION < 3
-
-#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
-#define PyMODINIT_FUNC void
-#endif
-PyMODINIT_FUNC
-init_speedups(void)
-{
- if (!init_constants())
- return;
-
- Py_InitModule3("jinja2._speedups", module_methods, "");
-}
-
-#else /* Python 3.x module initialization */
-
-static struct PyModuleDef module_definition = {
- PyModuleDef_HEAD_INIT,
- "jinja2._speedups",
- NULL,
- -1,
- module_methods,
- NULL,
- NULL,
- NULL,
- NULL
-};
-
-PyMODINIT_FUNC
-PyInit__speedups(void)
-{
- if (!init_constants())
- return NULL;
-
- return PyModule_Create(&module_definition);
-}
-
-#endif
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/_stringdefs.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/_stringdefs.py
deleted file mode 100755
index 1161b7f4a..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/_stringdefs.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2._stringdefs
- ~~~~~~~~~~~~~~~~~~
-
- Strings of all Unicode characters of a certain category.
- Used for matching in Unicode-aware languages. Run to regenerate.
-
- Inspired by chartypes_create.py from the MoinMoin project, original
- implementation from Pygments.
-
- :copyright: Copyright 2006-2009 by the Jinja team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-
-Cc = u'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f'
-
-Cf = u'\xad\u0600\u0601\u0602\u0603\u06dd\u070f\u17b4\u17b5\u200b\u200c\u200d\u200e\u200f\u202a\u202b\u202c\u202d\u202e\u2060\u2061\u2062\u2063\u206a\u206b\u206c\u206d\u206e\u206f\ufeff\ufff9\ufffa\ufffb'
-
-Cn = u'\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0370\u0371\u0372\u0373\u0376\u0377\u0378\u0379\u037b\u037c\u037d\u037f\u0380\u0381\u0382\u0383\u038b\u038d\u03a2\u03cf\u0487\u04cf\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0557\u0558\u0560\u0588\u058b\u058c\u058d\u058e\u058f\u0590\u05ba\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05eb\u05ec\u05ed\u05ee\u05ef\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa\u05fb\u05fc\u05fd\u05fe\u05ff\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u0616\u0617\u0618\u0619\u061a\u061c\u061d\u0620\u063b\u063c\u063d\u063e\u063f\u065f\u070e\u074b\u074c\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u07b2\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u093a\u093b\u094e\u094f\u0955\u0956\u0957\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097e\u097f\u0980\u0984\u098d\u098e\u0991\u0992\u09a9\u09b1\u09b3\u09b4\u09b5\u09ba\u09bb\u09c5\u09c6\u09c9\u09ca\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d8\u09d9\u09da\u09db\u09de\u09e4\u09e5\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a04\u0a0b\u0a0c\u0a0d\u0a0e\u0a11\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a\u0a3b\u0a3d\u0a43\u0a44\u0a45\u0a46\u0a49\u0a4a\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a5d\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a84\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba\u0abb\u0ac6\u0aca\u0ace\u0acf\u0ad1\u0ad2\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae4\u0ae5\u0af0\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b04\u0b0d\u0b0e\u0b11\u0b12\u0b29\u0b31\u0b34\u0b3a\u0b3b\u0b44\u0b45\u0b46\u0b49\u0b4a\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b58\u0b59\u0b5a\u0b5b\u0b5e\u0b62\u0b63\u0b64\u0b65\u0b72\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b84\u0b8b\u0b8c\u0b8d\u0b91\u0b96\u0b97\u0b98\u0b9b\u0b9d\u0ba0\u0ba1\u0ba2\u0ba5\u0ba6\u0ba7\u0bab\u0bac\u0bad\u0bba\u0bbb\u0bbc\u0bbd\u0bc3\u0bc4\u0bc5\u0bc9\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0bfb\u0bfc\u0bfd\u0bfe\u0bff\u0c00\u0c04\u0c0d\u0c11\u0c29\u0c34\u0c3a\u0c3b\u0c3c\u0c3d\u0c45\u0c49\u0c4e\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c62\u0c63\u0c64\u0c65\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c84\u0c8d\u0c91\u0ca9\u0cb4\u0cba\u0cbb\u0cc5\u0cc9\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd7\u0cd8\u0cd9\u0cda\u0cdb\u0cdc\u0cdd\u0cdf\u0ce2\u0ce3\u0ce4\u0ce5\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d04\u0d0d\u0d11\u0d29\u0d3a\u0d3b\u0d3c\u0d3d\u0d44\u0d45\u0d49\u0d4e\u0d4f\u0d50\u0d51\u0d52\u0d53\u0d54\u0d55\u0d56\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d62\u0d63\u0d64\u0d65\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d84\u0d97\u0d98\u0d99\u0db2\u0dbc\u0dbe\u0dbf\u0dc7\u0dc8\u0dc9\u0dcb\u0dcc\u0dcd\u0dce\u0dd5\u0dd7\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e3b\u0e3c\u0e3d\u0e3e\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e\u0e7f\u0e80\u0e83\u0e85\u0e86\u0e89\u0e8b\u0e8c\u0e8e\u0e8f\u0e90\u0e91\u0e92\u0e93\u0e98\u0ea0\u0ea4\u0ea6\u0ea8\u0ea9\u0eac\u0eba\u0ebe\u0ebf\u0ec5\u0ec7\u0ece\u0ecf\u0eda\u0edb\u0ede\u0edf\u0ee0\u0ee1\u0ee2\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f48\u0f6b\u0f6c\u0f6d\u0f6e\u0f6f\u0f70\u0f8c\u0f8d\u0f8e\u0f8f\u0f98\u0fbd\u0fcd\u0fce\u0fd2\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1022\u1028\u102b\u1033\u1034\u1035\u103a\u103b\u103c\u103d\u103e\u103f\u105a\u105b\u105c\u105d\u105e\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086\u1087\u1088\u1089\u108a\u108b\u108c\u108d\u108e\u108f\u1090\u1091\u1092\u1093\u1094\u1095\u1096\u1097\u1098\u1099\u109a\u109b\u109c\u109d\u109e\u109f\u10c6\u10c7\u10c8\u10c9\u10ca\u10cb\u10cc\u10cd\u10ce\u10cf\u10fd\u10fe\u10ff\u115a\u115b\u115c\u115d\u115e\u11a3\u11a4\u11a5\u11a6\u11a7\u11fa\u11fb\u11fc\u11fd\u11fe\u11ff\u1249\u124e\u124f\u1257\u1259\u125e\u125f\u1289\u128e\u128f\u12b1\u12b6\u12b7\u12bf\u12c1\u12c6\u12c7\u12d7\u1311\u1316\u1317\u135b\u135c\u135d\u135e\u137d\u137e\u137f\u139a\u139b\u139c\u139d\u139e\u139f\u13f5\u13f6\u13f7\u13f8\u13f9\u13fa\u13fb\u13fc\u13fd\u13fe\u13ff\u1400\u1677\u1678\u1679\u167a\u167b\u167c\u167d\u167e\u167f\u169d\u169e\u169f\u16f1\u16f2\u16f3\u16f4\u16f5\u16f6\u16f7\u16f8\u16f9\u16fa\u16fb\u16fc\u16fd\u16fe\u16ff\u170d\u1715\u1716\u1717\u1718\u1719\u171a\u171b\u171c\u171d\u171e\u171f\u1737\u1738\u1739\u173a\u173b\u173c\u173d\u173e\u173f\u1754\u1755\u1756\u1757\u1758\u1759\u175a\u175b\u175c\u175d\u175e\u175f\u176d\u1771\u1774\u1775\u1776\u1777\u1778\u1779\u177a\u177b\u177c\u177d\u177e\u177f\u17de\u17df\u17ea\u17eb\u17ec\u17ed\u17ee\u17ef\u17fa\u17fb\u17fc\u17fd\u17fe\u17ff\u180f\u181a\u181b\u181c\u181d\u181e\u181f\u1878\u1879\u187a\u187b\u187c\u187d\u187e\u187f\u18aa\u18ab\u18ac\u18ad\u18ae\u18af\u18b0\u18b1\u18b2\u18b3\u18b4\u18b5\u18b6\u18b7\u18b8\u18b9\u18ba\u18bb\u18bc\u18bd\u18be\u18bf\u18c0\u18c1\u18c2\u18c3\u18c4\u18c5\u18c6\u18c7\u18c8\u18c9\u18ca\u18cb\u18cc\u18cd\u18ce\u18cf\u18d0\u18d1\u18d2\u18d3\u18d4\u18d5\u18d6\u18d7\u18d8\u18d9\u18da\u18db\u18dc\u18dd\u18de\u18df\u18e0\u18e1\u18e2\u18e3\u18e4\u18e5\u18e6\u18e7\u18e8\u18e9\u18ea\u18eb\u18ec\u18ed\u18ee\u18ef\u18f0\u18f1\u18f2\u18f3\u18f4\u18f5\u18f6\u18f7\u18f8\u18f9\u18fa\u18fb\u18fc\u18fd\u18fe\u18ff\u191d\u191e\u191f\u192c\u192d\u192e\u192f\u193c\u193d\u193e\u193f\u1941\u1942\u1943\u196e\u196f\u1975\u1976\u1977\u1978\u1979\u197a\u197b\u197c\u197d\u197e\u197f\u19aa\u19ab\u19ac\u19ad\u19ae\u19af\u19ca\u19cb\u19cc\u19cd\u19ce\u19cf\u19da\u19db\u19dc\u19dd\u1a1c\u1a1d\u1a20\u1a21\u1a22\u1a23\u1a24\u1a25\u1a26\u1a27\u1a28\u1a29\u1a2a\u1a2b\u1a2c\u1a2d\u1a2e\u1a2f\u1a30\u1a31\u1a32\u1a33\u1a34\u1a35\u1a36\u1a37\u1a38\u1a39\u1a3a\u1a3b\u1a3c\u1a3d\u1a3e\u1a3f\u1a40\u1a41\u1a42\u1a43\u1a44\u1a45\u1a46\u1a47\u1a48\u1a49\u1a4a\u1a4b\u1a4c\u1a4d\u1a4e\u1a4f\u1a50\u1a51\u1a52\u1a53\u1a54\u1a55\u1a56\u1a57\u1a58\u1a59\u1a5a\u1a5b\u1a5c\u1a5d\u1a5e\u1a5f\u1a60\u1a61\u1a62\u1a63\u1a64\u1a65\u1a66\u1a67\u1a68\u1a69\u1a6a\u1a6b\u1a6c\u1a6d\u1a6e\u1a6f\u1a70\u1a71\u1a72\u1a73\u1a74\u1a75\u1a76\u1a77\u1a78\u1a79\u1a7a\u1a7b\u1a7c\u1a7d\u1a7e\u1a7f\u1a80\u1a81\u1a82\u1a83\u1a84\u1a85\u1a86\u1a87\u1a88\u1a89\u1a8a\u1a8b\u1a8c\u1a8d\u1a8e\u1a8f\u1a90\u1a91\u1a92\u1a93\u1a94\u1a95\u1a96\u1a97\u1a98\u1a99\u1a9a\u1a9b\u1a9c\u1a9d\u1a9e\u1a9f\u1aa0\u1aa1\u1aa2\u1aa3\u1aa4\u1aa5\u1aa6\u1aa7\u1aa8\u1aa9\u1aaa\u1aab\u1aac\u1aad\u1aae\u1aaf\u1ab0\u1ab1\u1ab2\u1ab3\u1ab4\u1ab5\u1ab6\u1ab7\u1ab8\u1ab9\u1aba\u1abb\u1abc\u1abd\u1abe\u1abf\u1ac0\u1ac1\u1ac2\u1ac3\u1ac4\u1ac5\u1ac6\u1ac7\u1ac8\u1ac9\u1aca\u1acb\u1acc\u1acd\u1ace\u1acf\u1ad0\u1ad1\u1ad2\u1ad3\u1ad4\u1ad5\u1ad6\u1ad7\u1ad8\u1ad9\u1ada\u1adb\u1adc\u1add\u1ade\u1adf\u1ae0\u1ae1\u1ae2\u1ae3\u1ae4\u1ae5\u1ae6\u1ae7\u1ae8\u1ae9\u1aea\u1aeb\u1aec\u1aed\u1aee\u1aef\u1af0\u1af1\u1af2\u1af3\u1af4\u1af5\u1af6\u1af7\u1af8\u1af9\u1afa\u1afb\u1afc\u1afd\u1afe\u1aff\u1b00\u1b01\u1b02\u1b03\u1b04\u1b05\u1b06\u1b07\u1b08\u1b09\u1b0a\u1b0b\u1b0c\u1b0d\u1b0e\u1b0f\u1b10\u1b11\u1b12\u1b13\u1b14\u1b15\u1b16\u1b17\u1b18\u1b19\u1b1a\u1b1b\u1b1c\u1b1d\u1b1e\u1b1f\u1b20\u1b21\u1b22\u1b23\u1b24\u1b25\u1b26\u1b27\u1b28\u1b29\u1b2a\u1b2b\u1b2c\u1b2d\u1b2e\u1b2f\u1b30\u1b31\u1b32\u1b33\u1b34\u1b35\u1b36\u1b37\u1b38\u1b39\u1b3a\u1b3b\u1b3c\u1b3d\u1b3e\u1b3f\u1b40\u1b41\u1b42\u1b43\u1b44\u1b45\u1b46\u1b47\u1b48\u1b49\u1b4a\u1b4b\u1b4c\u1b4d\u1b4e\u1b4f\u1b50\u1b51\u1b52\u1b53\u1b54\u1b55\u1b56\u1b57\u1b58\u1b59\u1b5a\u1b5b\u1b5c\u1b5d\u1b5e\u1b5f\u1b60\u1b61\u1b62\u1b63\u1b64\u1b65\u1b66\u1b67\u1b68\u1b69\u1b6a\u1b6b\u1b6c\u1b6d\u1b6e\u1b6f\u1b70\u1b71\u1b72\u1b73\u1b74\u1b75\u1b76\u1b77\u1b78\u1b79\u1b7a\u1b7b\u1b7c\u1b7d\u1b7e\u1b7f\u1b80\u1b81\u1b82\u1b83\u1b84\u1b85\u1b86\u1b87\u1b88\u1b89\u1b8a\u1b8b\u1b8c\u1b8d\u1b8e\u1b8f\u1b90\u1b91\u1b92\u1b93\u1b94\u1b95\u1b96\u1b97\u1b98\u1b99\u1b9a\u1b9b\u1b9c\u1b9d\u1b9e\u1b9f\u1ba0\u1ba1\u1ba2\u1ba3\u1ba4\u1ba5\u1ba6\u1ba7\u1ba8\u1ba9\u1baa\u1bab\u1bac\u1bad\u1bae\u1baf\u1bb0\u1bb1\u1bb2\u1bb3\u1bb4\u1bb5\u1bb6\u1bb7\u1bb8\u1bb9\u1bba\u1bbb\u1bbc\u1bbd\u1bbe\u1bbf\u1bc0\u1bc1\u1bc2\u1bc3\u1bc4\u1bc5\u1bc6\u1bc7\u1bc8\u1bc9\u1bca\u1bcb\u1bcc\u1bcd\u1bce\u1bcf\u1bd0\u1bd1\u1bd2\u1bd3\u1bd4\u1bd5\u1bd6\u1bd7\u1bd8\u1bd9\u1bda\u1bdb\u1bdc\u1bdd\u1bde\u1bdf\u1be0\u1be1\u1be2\u1be3\u1be4\u1be5\u1be6\u1be7\u1be8\u1be9\u1bea\u1beb\u1bec\u1bed\u1bee\u1bef\u1bf0\u1bf1\u1bf2\u1bf3\u1bf4\u1bf5\u1bf6\u1bf7\u1bf8\u1bf9\u1bfa\u1bfb\u1bfc\u1bfd\u1bfe\u1bff\u1c00\u1c01\u1c02\u1c03\u1c04\u1c05\u1c06\u1c07\u1c08\u1c09\u1c0a\u1c0b\u1c0c\u1c0d\u1c0e\u1c0f\u1c10\u1c11\u1c12\u1c13\u1c14\u1c15\u1c16\u1c17\u1c18\u1c19\u1c1a\u1c1b\u1c1c\u1c1d\u1c1e\u1c1f\u1c20\u1c21\u1c22\u1c23\u1c24\u1c25\u1c26\u1c27\u1c28\u1c29\u1c2a\u1c2b\u1c2c\u1c2d\u1c2e\u1c2f\u1c30\u1c31\u1c32\u1c33\u1c34\u1c35\u1c36\u1c37\u1c38\u1c39\u1c3a\u1c3b\u1c3c\u1c3d\u1c3e\u1c3f\u1c40\u1c41\u1c42\u1c43\u1c44\u1c45\u1c46\u1c47\u1c48\u1c49\u1c4a\u1c4b\u1c4c\u1c4d\u1c4e\u1c4f\u1c50\u1c51\u1c52\u1c53\u1c54\u1c55\u1c56\u1c57\u1c58\u1c59\u1c5a\u1c5b\u1c5c\u1c5d\u1c5e\u1c5f\u1c60\u1c61\u1c62\u1c63\u1c64\u1c65\u1c66\u1c67\u1c68\u1c69\u1c6a\u1c6b\u1c6c\u1c6d\u1c6e\u1c6f\u1c70\u1c71\u1c72\u1c73\u1c74\u1c75\u1c76\u1c77\u1c78\u1c79\u1c7a\u1c7b\u1c7c\u1c7d\u1c7e\u1c7f\u1c80\u1c81\u1c82\u1c83\u1c84\u1c85\u1c86\u1c87\u1c88\u1c89\u1c8a\u1c8b\u1c8c\u1c8d\u1c8e\u1c8f\u1c90\u1c91\u1c92\u1c93\u1c94\u1c95\u1c96\u1c97\u1c98\u1c99\u1c9a\u1c9b\u1c9c\u1c9d\u1c9e\u1c9f\u1ca0\u1ca1\u1ca2\u1ca3\u1ca4\u1ca5\u1ca6\u1ca7\u1ca8\u1ca9\u1caa\u1cab\u1cac\u1cad\u1cae\u1caf\u1cb0\u1cb1\u1cb2\u1cb3\u1cb4\u1cb5\u1cb6\u1cb7\u1cb8\u1cb9\u1cba\u1cbb\u1cbc\u1cbd\u1cbe\u1cbf\u1cc0\u1cc1\u1cc2\u1cc3\u1cc4\u1cc5\u1cc6\u1cc7\u1cc8\u1cc9\u1cca\u1ccb\u1ccc\u1ccd\u1cce\u1ccf\u1cd0\u1cd1\u1cd2\u1cd3\u1cd4\u1cd5\u1cd6\u1cd7\u1cd8\u1cd9\u1cda\u1cdb\u1cdc\u1cdd\u1cde\u1cdf\u1ce0\u1ce1\u1ce2\u1ce3\u1ce4\u1ce5\u1ce6\u1ce7\u1ce8\u1ce9\u1cea\u1ceb\u1cec\u1ced\u1cee\u1cef\u1cf0\u1cf1\u1cf2\u1cf3\u1cf4\u1cf5\u1cf6\u1cf7\u1cf8\u1cf9\u1cfa\u1cfb\u1cfc\u1cfd\u1cfe\u1cff\u1dc4\u1dc5\u1dc6\u1dc7\u1dc8\u1dc9\u1dca\u1dcb\u1dcc\u1dcd\u1dce\u1dcf\u1dd0\u1dd1\u1dd2\u1dd3\u1dd4\u1dd5\u1dd6\u1dd7\u1dd8\u1dd9\u1dda\u1ddb\u1ddc\u1ddd\u1dde\u1ddf\u1de0\u1de1\u1de2\u1de3\u1de4\u1de5\u1de6\u1de7\u1de8\u1de9\u1dea\u1deb\u1dec\u1ded\u1dee\u1def\u1df0\u1df1\u1df2\u1df3\u1df4\u1df5\u1df6\u1df7\u1df8\u1df9\u1dfa\u1dfb\u1dfc\u1dfd\u1dfe\u1dff\u1e9c\u1e9d\u1e9e\u1e9f\u1efa\u1efb\u1efc\u1efd\u1efe\u1eff\u1f16\u1f17\u1f1e\u1f1f\u1f46\u1f47\u1f4e\u1f4f\u1f58\u1f5a\u1f5c\u1f5e\u1f7e\u1f7f\u1fb5\u1fc5\u1fd4\u1fd5\u1fdc\u1ff0\u1ff1\u1ff5\u1fff\u2064\u2065\u2066\u2067\u2068\u2069\u2072\u2073\u208f\u2095\u2096\u2097\u2098\u2099\u209a\u209b\u209c\u209d\u209e\u209f\u20b6\u20b7\u20b8\u20b9\u20ba\u20bb\u20bc\u20bd\u20be\u20bf\u20c0\u20c1\u20c2\u20c3\u20c4\u20c5\u20c6\u20c7\u20c8\u20c9\u20ca\u20cb\u20cc\u20cd\u20ce\u20cf\u20ec\u20ed\u20ee\u20ef\u20f0\u20f1\u20f2\u20f3\u20f4\u20f5\u20f6\u20f7\u20f8\u20f9\u20fa\u20fb\u20fc\u20fd\u20fe\u20ff\u214d\u214e\u214f\u2150\u2151\u2152\u2184\u2185\u2186\u2187\u2188\u2189\u218a\u218b\u218c\u218d\u218e\u218f\u23dc\u23dd\u23de\u23df\u23e0\u23e1\u23e2\u23e3\u23e4\u23e5\u23e6\u23e7\u23e8\u23e9\u23ea\u23eb\u23ec\u23ed\u23ee\u23ef\u23f0\u23f1\u23f2\u23f3\u23f4\u23f5\u23f6\u23f7\u23f8\u23f9\u23fa\u23fb\u23fc\u23fd\u23fe\u23ff\u2427\u2428\u2429\u242a\u242b\u242c\u242d\u242e\u242f\u2430\u2431\u2432\u2433\u2434\u2435\u2436\u2437\u2438\u2439\u243a\u243b\u243c\u243d\u243e\u243f\u244b\u244c\u244d\u244e\u244f\u2450\u2451\u2452\u2453\u2454\u2455\u2456\u2457\u2458\u2459\u245a\u245b\u245c\u245d\u245e\u245f\u269d\u269e\u269f\u26b2\u26b3\u26b4\u26b5\u26b6\u26b7\u26b8\u26b9\u26ba\u26bb\u26bc\u26bd\u26be\u26bf\u26c0\u26c1\u26c2\u26c3\u26c4\u26c5\u26c6\u26c7\u26c8\u26c9\u26ca\u26cb\u26cc\u26cd\u26ce\u26cf\u26d0\u26d1\u26d2\u26d3\u26d4\u26d5\u26d6\u26d7\u26d8\u26d9\u26da\u26db\u26dc\u26dd\u26de\u26df\u26e0\u26e1\u26e2\u26e3\u26e4\u26e5\u26e6\u26e7\u26e8\u26e9\u26ea\u26eb\u26ec\u26ed\u26ee\u26ef\u26f0\u26f1\u26f2\u26f3\u26f4\u26f5\u26f6\u26f7\u26f8\u26f9\u26fa\u26fb\u26fc\u26fd\u26fe\u26ff\u2700\u2705\u270a\u270b\u2728\u274c\u274e\u2753\u2754\u2755\u2757\u275f\u2760\u2795\u2796\u2797\u27b0\u27bf\u27c7\u27c8\u27c9\u27ca\u27cb\u27cc\u27cd\u27ce\u27cf\u27ec\u27ed\u27ee\u27ef\u2b14\u2b15\u2b16\u2b17\u2b18\u2b19\u2b1a\u2b1b\u2b1c\u2b1d\u2b1e\u2b1f\u2b20\u2b21\u2b22\u2b23\u2b24\u2b25\u2b26\u2b27\u2b28\u2b29\u2b2a\u2b2b\u2b2c\u2b2d\u2b2e\u2b2f\u2b30\u2b31\u2b32\u2b33\u2b34\u2b35\u2b36\u2b37\u2b38\u2b39\u2b3a\u2b3b\u2b3c\u2b3d\u2b3e\u2b3f\u2b40\u2b41\u2b42\u2b43\u2b44\u2b45\u2b46\u2b47\u2b48\u2b49\u2b4a\u2b4b\u2b4c\u2b4d\u2b4e\u2b4f\u2b50\u2b51\u2b52\u2b53\u2b54\u2b55\u2b56\u2b57\u2b58\u2b59\u2b5a\u2b5b\u2b5c\u2b5d\u2b5e\u2b5f\u2b60\u2b61\u2b62\u2b63\u2b64\u2b65\u2b66\u2b67\u2b68\u2b69\u2b6a\u2b6b\u2b6c\u2b6d\u2b6e\u2b6f\u2b70\u2b71\u2b72\u2b73\u2b74\u2b75\u2b76\u2b77\u2b78\u2b79\u2b7a\u2b7b\u2b7c\u2b7d\u2b7e\u2b7f\u2b80\u2b81\u2b82\u2b83\u2b84\u2b85\u2b86\u2b87\u2b88\u2b89\u2b8a\u2b8b\u2b8c\u2b8d\u2b8e\u2b8f\u2b90\u2b91\u2b92\u2b93\u2b94\u2b95\u2b96\u2b97\u2b98\u2b99\u2b9a\u2b9b\u2b9c\u2b9d\u2b9e\u2b9f\u2ba0\u2ba1\u2ba2\u2ba3\u2ba4\u2ba5\u2ba6\u2ba7\u2ba8\u2ba9\u2baa\u2bab\u2bac\u2bad\u2bae\u2baf\u2bb0\u2bb1\u2bb2\u2bb3\u2bb4\u2bb5\u2bb6\u2bb7\u2bb8\u2bb9\u2bba\u2bbb\u2bbc\u2bbd\u2bbe\u2bbf\u2bc0\u2bc1\u2bc2\u2bc3\u2bc4\u2bc5\u2bc6\u2bc7\u2bc8\u2bc9\u2bca\u2bcb\u2bcc\u2bcd\u2bce\u2bcf\u2bd0\u2bd1\u2bd2\u2bd3\u2bd4\u2bd5\u2bd6\u2bd7\u2bd8\u2bd9\u2bda\u2bdb\u2bdc\u2bdd\u2bde\u2bdf\u2be0\u2be1\u2be2\u2be3\u2be4\u2be5\u2be6\u2be7\u2be8\u2be9\u2bea\u2beb\u2bec\u2bed\u2bee\u2bef\u2bf0\u2bf1\u2bf2\u2bf3\u2bf4\u2bf5\u2bf6\u2bf7\u2bf8\u2bf9\u2bfa\u2bfb\u2bfc\u2bfd\u2bfe\u2bff\u2c2f\u2c5f\u2c60\u2c61\u2c62\u2c63\u2c64\u2c65\u2c66\u2c67\u2c68\u2c69\u2c6a\u2c6b\u2c6c\u2c6d\u2c6e\u2c6f\u2c70\u2c71\u2c72\u2c73\u2c74\u2c75\u2c76\u2c77\u2c78\u2c79\u2c7a\u2c7b\u2c7c\u2c7d\u2c7e\u2c7f\u2ceb\u2cec\u2ced\u2cee\u2cef\u2cf0\u2cf1\u2cf2\u2cf3\u2cf4\u2cf5\u2cf6\u2cf7\u2cf8\u2d26\u2d27\u2d28\u2d29\u2d2a\u2d2b\u2d2c\u2d2d\u2d2e\u2d2f\u2d66\u2d67\u2d68\u2d69\u2d6a\u2d6b\u2d6c\u2d6d\u2d6e\u2d70\u2d71\u2d72\u2d73\u2d74\u2d75\u2d76\u2d77\u2d78\u2d79\u2d7a\u2d7b\u2d7c\u2d7d\u2d7e\u2d7f\u2d97\u2d98\u2d99\u2d9a\u2d9b\u2d9c\u2d9d\u2d9e\u2d9f\u2da7\u2daf\u2db7\u2dbf\u2dc7\u2dcf\u2dd7\u2ddf\u2de0\u2de1\u2de2\u2de3\u2de4\u2de5\u2de6\u2de7\u2de8\u2de9\u2dea\u2deb\u2dec\u2ded\u2dee\u2def\u2df0\u2df1\u2df2\u2df3\u2df4\u2df5\u2df6\u2df7\u2df8\u2df9\u2dfa\u2dfb\u2dfc\u2dfd\u2dfe\u2dff\u2e18\u2e19\u2e1a\u2e1b\u2e1e\u2e1f\u2e20\u2e21\u2e22\u2e23\u2e24\u2e25\u2e26\u2e27\u2e28\u2e29\u2e2a\u2e2b\u2e2c\u2e2d\u2e2e\u2e2f\u2e30\u2e31\u2e32\u2e33\u2e34\u2e35\u2e36\u2e37\u2e38\u2e39\u2e3a\u2e3b\u2e3c\u2e3d\u2e3e\u2e3f\u2e40\u2e41\u2e42\u2e43\u2e44\u2e45\u2e46\u2e47\u2e48\u2e49\u2e4a\u2e4b\u2e4c\u2e4d\u2e4e\u2e4f\u2e50\u2e51\u2e52\u2e53\u2e54\u2e55\u2e56\u2e57\u2e58\u2e59\u2e5a\u2e5b\u2e5c\u2e5d\u2e5e\u2e5f\u2e60\u2e61\u2e62\u2e63\u2e64\u2e65\u2e66\u2e67\u2e68\u2e69\u2e6a\u2e6b\u2e6c\u2e6d\u2e6e\u2e6f\u2e70\u2e71\u2e72\u2e73\u2e74\u2e75\u2e76\u2e77\u2e78\u2e79\u2e7a\u2e7b\u2e7c\u2e7d\u2e7e\u2e7f\u2e9a\u2ef4\u2ef5\u2ef6\u2ef7\u2ef8\u2ef9\u2efa\u2efb\u2efc\u2efd\u2efe\u2eff\u2fd6\u2fd7\u2fd8\u2fd9\u2fda\u2fdb\u2fdc\u2fdd\u2fde\u2fdf\u2fe0\u2fe1\u2fe2\u2fe3\u2fe4\u2fe5\u2fe6\u2fe7\u2fe8\u2fe9\u2fea\u2feb\u2fec\u2fed\u2fee\u2fef\u2ffc\u2ffd\u2ffe\u2fff\u3040\u3097\u3098\u3100\u3101\u3102\u3103\u3104\u312d\u312e\u312f\u3130\u318f\u31b8\u31b9\u31ba\u31bb\u31bc\u31bd\u31be\u31bf\u31d0\u31d1\u31d2\u31d3\u31d4\u31d5\u31d6\u31d7\u31d8\u31d9\u31da\u31db\u31dc\u31dd\u31de\u31df\u31e0\u31e1\u31e2\u31e3\u31e4\u31e5\u31e6\u31e7\u31e8\u31e9\u31ea\u31eb\u31ec\u31ed\u31ee\u31ef\u321f\u3244\u3245\u3246\u3247\u3248\u3249\u324a\u324b\u324c\u324d\u324e\u324f\u32ff\u4db6\u4db7\u4db8\u4db9\u4dba\u4dbb\u4dbc\u4dbd\u4dbe\u4dbf\u9fbc\u9fbd\u9fbe\u9fbf\u9fc0\u9fc1\u9fc2\u9fc3\u9fc4\u9fc5\u9fc6\u9fc7\u9fc8\u9fc9\u9fca\u9fcb\u9fcc\u9fcd\u9fce\u9fcf\u9fd0\u9fd1\u9fd2\u9fd3\u9fd4\u9fd5\u9fd6\u9fd7\u9fd8\u9fd9\u9fda\u9fdb\u9fdc\u9fdd\u9fde\u9fdf\u9fe0\u9fe1\u9fe2\u9fe3\u9fe4\u9fe5\u9fe6\u9fe7\u9fe8\u9fe9\u9fea\u9feb\u9fec\u9fed\u9fee\u9fef\u9ff0\u9ff1\u9ff2\u9ff3\u9ff4\u9ff5\u9ff6\u9ff7\u9ff8\u9ff9\u9ffa\u9ffb\u9ffc\u9ffd\u9ffe\u9fff\ua48d\ua48e\ua48f\ua4c7\ua4c8\ua4c9\ua4ca\ua4cb\ua4cc\ua4cd\ua4ce\ua4cf\ua4d0\ua4d1\ua4d2\ua4d3\ua4d4\ua4d5\ua4d6\ua4d7\ua4d8\ua4d9\ua4da\ua4db\ua4dc\ua4dd\ua4de\ua4df\ua4e0\ua4e1\ua4e2\ua4e3\ua4e4\ua4e5\ua4e6\ua4e7\ua4e8\ua4e9\ua4ea\ua4eb\ua4ec\ua4ed\ua4ee\ua4ef\ua4f0\ua4f1\ua4f2\ua4f3\ua4f4\ua4f5\ua4f6\ua4f7\ua4f8\ua4f9\ua4fa\ua4fb\ua4fc\ua4fd\ua4fe\ua4ff\ua500\ua501\ua502\ua503\ua504\ua505\ua506\ua507\ua508\ua509\ua50a\ua50b\ua50c\ua50d\ua50e\ua50f\ua510\ua511\ua512\ua513\ua514\ua515\ua516\ua517\ua518\ua519\ua51a\ua51b\ua51c\ua51d\ua51e\ua51f\ua520\ua521\ua522\ua523\ua524\ua525\ua526\ua527\ua528\ua529\ua52a\ua52b\ua52c\ua52d\ua52e\ua52f\ua530\ua531\ua532\ua533\ua534\ua535\ua536\ua537\ua538\ua539\ua53a\ua53b\ua53c\ua53d\ua53e\ua53f\ua540\ua541\ua542\ua543\ua544\ua545\ua546\ua547\ua548\ua549\ua54a\ua54b\ua54c\ua54d\ua54e\ua54f\ua550\ua551\ua552\ua553\ua554\ua555\ua556\ua557\ua558\ua559\ua55a\ua55b\ua55c\ua55d\ua55e\ua55f\ua560\ua561\ua562\ua563\ua564\ua565\ua566\ua567\ua568\ua569\ua56a\ua56b\ua56c\ua56d\ua56e\ua56f\ua570\ua571\ua572\ua573\ua574\ua575\ua576\ua577\ua578\ua579\ua57a\ua57b\ua57c\ua57d\ua57e\ua57f\ua580\ua581\ua582\ua583\ua584\ua585\ua586\ua587\ua588\ua589\ua58a\ua58b\ua58c\ua58d\ua58e\ua58f\ua590\ua591\ua592\ua593\ua594\ua595\ua596\ua597\ua598\ua599\ua59a\ua59b\ua59c\ua59d\ua59e\ua59f\ua5a0\ua5a1\ua5a2\ua5a3\ua5a4\ua5a5\ua5a6\ua5a7\ua5a8\ua5a9\ua5aa\ua5ab\ua5ac\ua5ad\ua5ae\ua5af\ua5b0\ua5b1\ua5b2\ua5b3\ua5b4\ua5b5\ua5b6\ua5b7\ua5b8\ua5b9\ua5ba\ua5bb\ua5bc\ua5bd\ua5be\ua5bf\ua5c0\ua5c1\ua5c2\ua5c3\ua5c4\ua5c5\ua5c6\ua5c7\ua5c8\ua5c9\ua5ca\ua5cb\ua5cc\ua5cd\ua5ce\ua5cf\ua5d0\ua5d1\ua5d2\ua5d3\ua5d4\ua5d5\ua5d6\ua5d7\ua5d8\ua5d9\ua5da\ua5db\ua5dc\ua5dd\ua5de\ua5df\ua5e0\ua5e1\ua5e2\ua5e3\ua5e4\ua5e5\ua5e6\ua5e7\ua5e8\ua5e9\ua5ea\ua5eb\ua5ec\ua5ed\ua5ee\ua5ef\ua5f0\ua5f1\ua5f2\ua5f3\ua5f4\ua5f5\ua5f6\ua5f7\ua5f8\ua5f9\ua5fa\ua5fb\ua5fc\ua5fd\ua5fe\ua5ff\ua600\ua601\ua602\ua603\ua604\ua605\ua606\ua607\ua608\ua609\ua60a\ua60b\ua60c\ua60d\ua60e\ua60f\ua610\ua611\ua612\ua613\ua614\ua615\ua616\ua617\ua618\ua619\ua61a\ua61b\ua61c\ua61d\ua61e\ua61f\ua620\ua621\ua622\ua623\ua624\ua625\ua626\ua627\ua628\ua629\ua62a\ua62b\ua62c\ua62d\ua62e\ua62f\ua630\ua631\ua632\ua633\ua634\ua635\ua636\ua637\ua638\ua639\ua63a\ua63b\ua63c\ua63d\ua63e\ua63f\ua640\ua641\ua642\ua643\ua644\ua645\ua646\ua647\ua648\ua649\ua64a\ua64b\ua64c\ua64d\ua64e\ua64f\ua650\ua651\ua652\ua653\ua654\ua655\ua656\ua657\ua658\ua659\ua65a\ua65b\ua65c\ua65d\ua65e\ua65f\ua660\ua661\ua662\ua663\ua664\ua665\ua666\ua667\ua668\ua669\ua66a\ua66b\ua66c\ua66d\ua66e\ua66f\ua670\ua671\ua672\ua673\ua674\ua675\ua676\ua677\ua678\ua679\ua67a\ua67b\ua67c\ua67d\ua67e\ua67f\ua680\ua681\ua682\ua683\ua684\ua685\ua686\ua687\ua688\ua689\ua68a\ua68b\ua68c\ua68d\ua68e\ua68f\ua690\ua691\ua692\ua693\ua694\ua695\ua696\ua697\ua698\ua699\ua69a\ua69b\ua69c\ua69d\ua69e\ua69f\ua6a0\ua6a1\ua6a2\ua6a3\ua6a4\ua6a5\ua6a6\ua6a7\ua6a8\ua6a9\ua6aa\ua6ab\ua6ac\ua6ad\ua6ae\ua6af\ua6b0\ua6b1\ua6b2\ua6b3\ua6b4\ua6b5\ua6b6\ua6b7\ua6b8\ua6b9\ua6ba\ua6bb\ua6bc\ua6bd\ua6be\ua6bf\ua6c0\ua6c1\ua6c2\ua6c3\ua6c4\ua6c5\ua6c6\ua6c7\ua6c8\ua6c9\ua6ca\ua6cb\ua6cc\ua6cd\ua6ce\ua6cf\ua6d0\ua6d1\ua6d2\ua6d3\ua6d4\ua6d5\ua6d6\ua6d7\ua6d8\ua6d9\ua6da\ua6db\ua6dc\ua6dd\ua6de\ua6df\ua6e0\ua6e1\ua6e2\ua6e3\ua6e4\ua6e5\ua6e6\ua6e7\ua6e8\ua6e9\ua6ea\ua6eb\ua6ec\ua6ed\ua6ee\ua6ef\ua6f0\ua6f1\ua6f2\ua6f3\ua6f4\ua6f5\ua6f6\ua6f7\ua6f8\ua6f9\ua6fa\ua6fb\ua6fc\ua6fd\ua6fe\ua6ff\ua717\ua718\ua719\ua71a\ua71b\ua71c\ua71d\ua71e\ua71f\ua720\ua721\ua722\ua723\ua724\ua725\ua726\ua727\ua728\ua729\ua72a\ua72b\ua72c\ua72d\ua72e\ua72f\ua730\ua731\ua732\ua733\ua734\ua735\ua736\ua737\ua738\ua739\ua73a\ua73b\ua73c\ua73d\ua73e\ua73f\ua740\ua741\ua742\ua743\ua744\ua745\ua746\ua747\ua748\ua749\ua74a\ua74b\ua74c\ua74d\ua74e\ua74f\ua750\ua751\ua752\ua753\ua754\ua755\ua756\ua757\ua758\ua759\ua75a\ua75b\ua75c\ua75d\ua75e\ua75f\ua760\ua761\ua762\ua763\ua764\ua765\ua766\ua767\ua768\ua769\ua76a\ua76b\ua76c\ua76d\ua76e\ua76f\ua770\ua771\ua772\ua773\ua774\ua775\ua776\ua777\ua778\ua779\ua77a\ua77b\ua77c\ua77d\ua77e\ua77f\ua780\ua781\ua782\ua783\ua784\ua785\ua786\ua787\ua788\ua789\ua78a\ua78b\ua78c\ua78d\ua78e\ua78f\ua790\ua791\ua792\ua793\ua794\ua795\ua796\ua797\ua798\ua799\ua79a\ua79b\ua79c\ua79d\ua79e\ua79f\ua7a0\ua7a1\ua7a2\ua7a3\ua7a4\ua7a5\ua7a6\ua7a7\ua7a8\ua7a9\ua7aa\ua7ab\ua7ac\ua7ad\ua7ae\ua7af\ua7b0\ua7b1\ua7b2\ua7b3\ua7b4\ua7b5\ua7b6\ua7b7\ua7b8\ua7b9\ua7ba\ua7bb\ua7bc\ua7bd\ua7be\ua7bf\ua7c0\ua7c1\ua7c2\ua7c3\ua7c4\ua7c5\ua7c6\ua7c7\ua7c8\ua7c9\ua7ca\ua7cb\ua7cc\ua7cd\ua7ce\ua7cf\ua7d0\ua7d1\ua7d2\ua7d3\ua7d4\ua7d5\ua7d6\ua7d7\ua7d8\ua7d9\ua7da\ua7db\ua7dc\ua7dd\ua7de\ua7df\ua7e0\ua7e1\ua7e2\ua7e3\ua7e4\ua7e5\ua7e6\ua7e7\ua7e8\ua7e9\ua7ea\ua7eb\ua7ec\ua7ed\ua7ee\ua7ef\ua7f0\ua7f1\ua7f2\ua7f3\ua7f4\ua7f5\ua7f6\ua7f7\ua7f8\ua7f9\ua7fa\ua7fb\ua7fc\ua7fd\ua7fe\ua7ff\ua82c\ua82d\ua82e\ua82f\ua830\ua831\ua832\ua833\ua834\ua835\ua836\ua837\ua838\ua839\ua83a\ua83b\ua83c\ua83d\ua83e\ua83f\ua840\ua841\ua842\ua843\ua844\ua845\ua846\ua847\ua848\ua849\ua84a\ua84b\ua84c\ua84d\ua84e\ua84f\ua850\ua851\ua852\ua853\ua854\ua855\ua856\ua857\ua858\ua859\ua85a\ua85b\ua85c\ua85d\ua85e\ua85f\ua860\ua861\ua862\ua863\ua864\ua865\ua866\ua867\ua868\ua869\ua86a\ua86b\ua86c\ua86d\ua86e\ua86f\ua870\ua871\ua872\ua873\ua874\ua875\ua876\ua877\ua878\ua879\ua87a\ua87b\ua87c\ua87d\ua87e\ua87f\ua880\ua881\ua882\ua883\ua884\ua885\ua886\ua887\ua888\ua889\ua88a\ua88b\ua88c\ua88d\ua88e\ua88f\ua890\ua891\ua892\ua893\ua894\ua895\ua896\ua897\ua898\ua899\ua89a\ua89b\ua89c\ua89d\ua89e\ua89f\ua8a0\ua8a1\ua8a2\ua8a3\ua8a4\ua8a5\ua8a6\ua8a7\ua8a8\ua8a9\ua8aa\ua8ab\ua8ac\ua8ad\ua8ae\ua8af\ua8b0\ua8b1\ua8b2\ua8b3\ua8b4\ua8b5\ua8b6\ua8b7\ua8b8\ua8b9\ua8ba\ua8bb\ua8bc\ua8bd\ua8be\ua8bf\ua8c0\ua8c1\ua8c2\ua8c3\ua8c4\ua8c5\ua8c6\ua8c7\ua8c8\ua8c9\ua8ca\ua8cb\ua8cc\ua8cd\ua8ce\ua8cf\ua8d0\ua8d1\ua8d2\ua8d3\ua8d4\ua8d5\ua8d6\ua8d7\ua8d8\ua8d9\ua8da\ua8db\ua8dc\ua8dd\ua8de\ua8df\ua8e0\ua8e1\ua8e2\ua8e3\ua8e4\ua8e5\ua8e6\ua8e7\ua8e8\ua8e9\ua8ea\ua8eb\ua8ec\ua8ed\ua8ee\ua8ef\ua8f0\ua8f1\ua8f2\ua8f3\ua8f4\ua8f5\ua8f6\ua8f7\ua8f8\ua8f9\ua8fa\ua8fb\ua8fc\ua8fd\ua8fe\ua8ff\ua900\ua901\ua902\ua903\ua904\ua905\ua906\ua907\ua908\ua909\ua90a\ua90b\ua90c\ua90d\ua90e\ua90f\ua910\ua911\ua912\ua913\ua914\ua915\ua916\ua917\ua918\ua919\ua91a\ua91b\ua91c\ua91d\ua91e\ua91f\ua920\ua921\ua922\ua923\ua924\ua925\ua926\ua927\ua928\ua929\ua92a\ua92b\ua92c\ua92d\ua92e\ua92f\ua930\ua931\ua932\ua933\ua934\ua935\ua936\ua937\ua938\ua939\ua93a\ua93b\ua93c\ua93d\ua93e\ua93f\ua940\ua941\ua942\ua943\ua944\ua945\ua946\ua947\ua948\ua949\ua94a\ua94b\ua94c\ua94d\ua94e\ua94f\ua950\ua951\ua952\ua953\ua954\ua955\ua956\ua957\ua958\ua959\ua95a\ua95b\ua95c\ua95d\ua95e\ua95f\ua960\ua961\ua962\ua963\ua964\ua965\ua966\ua967\ua968\ua969\ua96a\ua96b\ua96c\ua96d\ua96e\ua96f\ua970\ua971\ua972\ua973\ua974\ua975\ua976\ua977\ua978\ua979\ua97a\ua97b\ua97c\ua97d\ua97e\ua97f\ua980\ua981\ua982\ua983\ua984\ua985\ua986\ua987\ua988\ua989\ua98a\ua98b\ua98c\ua98d\ua98e\ua98f\ua990\ua991\ua992\ua993\ua994\ua995\ua996\ua997\ua998\ua999\ua99a\ua99b\ua99c\ua99d\ua99e\ua99f\ua9a0\ua9a1\ua9a2\ua9a3\ua9a4\ua9a5\ua9a6\ua9a7\ua9a8\ua9a9\ua9aa\ua9ab\ua9ac\ua9ad\ua9ae\ua9af\ua9b0\ua9b1\ua9b2\ua9b3\ua9b4\ua9b5\ua9b6\ua9b7\ua9b8\ua9b9\ua9ba\ua9bb\ua9bc\ua9bd\ua9be\ua9bf\ua9c0\ua9c1\ua9c2\ua9c3\ua9c4\ua9c5\ua9c6\ua9c7\ua9c8\ua9c9\ua9ca\ua9cb\ua9cc\ua9cd\ua9ce\ua9cf\ua9d0\ua9d1\ua9d2\ua9d3\ua9d4\ua9d5\ua9d6\ua9d7\ua9d8\ua9d9\ua9da\ua9db\ua9dc\ua9dd\ua9de\ua9df\ua9e0\ua9e1\ua9e2\ua9e3\ua9e4\ua9e5\ua9e6\ua9e7\ua9e8\ua9e9\ua9ea\ua9eb\ua9ec\ua9ed\ua9ee\ua9ef\ua9f0\ua9f1\ua9f2\ua9f3\ua9f4\ua9f5\ua9f6\ua9f7\ua9f8\ua9f9\ua9fa\ua9fb\ua9fc\ua9fd\ua9fe\ua9ff\uaa00\uaa01\uaa02\uaa03\uaa04\uaa05\uaa06\uaa07\uaa08\uaa09\uaa0a\uaa0b\uaa0c\uaa0d\uaa0e\uaa0f\uaa10\uaa11\uaa12\uaa13\uaa14\uaa15\uaa16\uaa17\uaa18\uaa19\uaa1a\uaa1b\uaa1c\uaa1d\uaa1e\uaa1f\uaa20\uaa21\uaa22\uaa23\uaa24\uaa25\uaa26\uaa27\uaa28\uaa29\uaa2a\uaa2b\uaa2c\uaa2d\uaa2e\uaa2f\uaa30\uaa31\uaa32\uaa33\uaa34\uaa35\uaa36\uaa37\uaa38\uaa39\uaa3a\uaa3b\uaa3c\uaa3d\uaa3e\uaa3f\uaa40\uaa41\uaa42\uaa43\uaa44\uaa45\uaa46\uaa47\uaa48\uaa49\uaa4a\uaa4b\uaa4c\uaa4d\uaa4e\uaa4f\uaa50\uaa51\uaa52\uaa53\uaa54\uaa55\uaa56\uaa57\uaa58\uaa59\uaa5a\uaa5b\uaa5c\uaa5d\uaa5e\uaa5f\uaa60\uaa61\uaa62\uaa63\uaa64\uaa65\uaa66\uaa67\uaa68\uaa69\uaa6a\uaa6b\uaa6c\uaa6d\uaa6e\uaa6f\uaa70\uaa71\uaa72\uaa73\uaa74\uaa75\uaa76\uaa77\uaa78\uaa79\uaa7a\uaa7b\uaa7c\uaa7d\uaa7e\uaa7f\uaa80\uaa81\uaa82\uaa83\uaa84\uaa85\uaa86\uaa87\uaa88\uaa89\uaa8a\uaa8b\uaa8c\uaa8d\uaa8e\uaa8f\uaa90\uaa91\uaa92\uaa93\uaa94\uaa95\uaa96\uaa97\uaa98\uaa99\uaa9a\uaa9b\uaa9c\uaa9d\uaa9e\uaa9f\uaaa0\uaaa1\uaaa2\uaaa3\uaaa4\uaaa5\uaaa6\uaaa7\uaaa8\uaaa9\uaaaa\uaaab\uaaac\uaaad\uaaae\uaaaf\uaab0\uaab1\uaab2\uaab3\uaab4\uaab5\uaab6\uaab7\uaab8\uaab9\uaaba\uaabb\uaabc\uaabd\uaabe\uaabf\uaac0\uaac1\uaac2\uaac3\uaac4\uaac5\uaac6\uaac7\uaac8\uaac9\uaaca\uaacb\uaacc\uaacd\uaace\uaacf\uaad0\uaad1\uaad2\uaad3\uaad4\uaad5\uaad6\uaad7\uaad8\uaad9\uaada\uaadb\uaadc\uaadd\uaade\uaadf\uaae0\uaae1\uaae2\uaae3\uaae4\uaae5\uaae6\uaae7\uaae8\uaae9\uaaea\uaaeb\uaaec\uaaed\uaaee\uaaef\uaaf0\uaaf1\uaaf2\uaaf3\uaaf4\uaaf5\uaaf6\uaaf7\uaaf8\uaaf9\uaafa\uaafb\uaafc\uaafd\uaafe\uaaff\uab00\uab01\uab02\uab03\uab04\uab05\uab06\uab07\uab08\uab09\uab0a\uab0b\uab0c\uab0d\uab0e\uab0f\uab10\uab11\uab12\uab13\uab14\uab15\uab16\uab17\uab18\uab19\uab1a\uab1b\uab1c\uab1d\uab1e\uab1f\uab20\uab21\uab22\uab23\uab24\uab25\uab26\uab27\uab28\uab29\uab2a\uab2b\uab2c\uab2d\uab2e\uab2f\uab30\uab31\uab32\uab33\uab34\uab35\uab36\uab37\uab38\uab39\uab3a\uab3b\uab3c\uab3d\uab3e\uab3f\uab40\uab41\uab42\uab43\uab44\uab45\uab46\uab47\uab48\uab49\uab4a\uab4b\uab4c\uab4d\uab4e\uab4f\uab50\uab51\uab52\uab53\uab54\uab55\uab56\uab57\uab58\uab59\uab5a\uab5b\uab5c\uab5d\uab5e\uab5f\uab60\uab61\uab62\uab63\uab64\uab65\uab66\uab67\uab68\uab69\uab6a\uab6b\uab6c\uab6d\uab6e\uab6f\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79\uab7a\uab7b\uab7c\uab7d\uab7e\uab7f\uab80\uab81\uab82\uab83\uab84\uab85\uab86\uab87\uab88\uab89\uab8a\uab8b\uab8c\uab8d\uab8e\uab8f\uab90\uab91\uab92\uab93\uab94\uab95\uab96\uab97\uab98\uab99\uab9a\uab9b\uab9c\uab9d\uab9e\uab9f\uaba0\uaba1\uaba2\uaba3\uaba4\uaba5\uaba6\uaba7\uaba8\uaba9\uabaa\uabab\uabac\uabad\uabae\uabaf\uabb0\uabb1\uabb2\uabb3\uabb4\uabb5\uabb6\uabb7\uabb8\uabb9\uabba\uabbb\uabbc\uabbd\uabbe\uabbf\uabc0\uabc1\uabc2\uabc3\uabc4\uabc5\uabc6\uabc7\uabc8\uabc9\uabca\uabcb\uabcc\uabcd\uabce\uabcf\uabd0\uabd1\uabd2\uabd3\uabd4\uabd5\uabd6\uabd7\uabd8\uabd9\uabda\uabdb\uabdc\uabdd\uabde\uabdf\uabe0\uabe1\uabe2\uabe3\uabe4\uabe5\uabe6\uabe7\uabe8\uabe9\uabea\uabeb\uabec\uabed\uabee\uabef\uabf0\uabf1\uabf2\uabf3\uabf4\uabf5\uabf6\uabf7\uabf8\uabf9\uabfa\uabfb\uabfc\uabfd\uabfe\uabff\ud7a4\ud7a5\ud7a6\ud7a7\ud7a8\ud7a9\ud7aa\ud7ab\ud7ac\ud7ad\ud7ae\ud7af\ud7b0\ud7b1\ud7b2\ud7b3\ud7b4\ud7b5\ud7b6\ud7b7\ud7b8\ud7b9\ud7ba\ud7bb\ud7bc\ud7bd\ud7be\ud7bf\ud7c0\ud7c1\ud7c2\ud7c3\ud7c4\ud7c5\ud7c6\ud7c7\ud7c8\ud7c9\ud7ca\ud7cb\ud7cc\ud7cd\ud7ce\ud7cf\ud7d0\ud7d1\ud7d2\ud7d3\ud7d4\ud7d5\ud7d6\ud7d7\ud7d8\ud7d9\ud7da\ud7db\ud7dc\ud7dd\ud7de\ud7df\ud7e0\ud7e1\ud7e2\ud7e3\ud7e4\ud7e5\ud7e6\ud7e7\ud7e8\ud7e9\ud7ea\ud7eb\ud7ec\ud7ed\ud7ee\ud7ef\ud7f0\ud7f1\ud7f2\ud7f3\ud7f4\ud7f5\ud7f6\ud7f7\ud7f8\ud7f9\ud7fa\ud7fb\ud7fc\ud7fd\ud7fe\ud7ff\ufa2e\ufa2f\ufa6b\ufa6c\ufa6d\ufa6e\ufa6f\ufada\ufadb\ufadc\ufadd\ufade\ufadf\ufae0\ufae1\ufae2\ufae3\ufae4\ufae5\ufae6\ufae7\ufae8\ufae9\ufaea\ufaeb\ufaec\ufaed\ufaee\ufaef\ufaf0\ufaf1\ufaf2\ufaf3\ufaf4\ufaf5\ufaf6\ufaf7\ufaf8\ufaf9\ufafa\ufafb\ufafc\ufafd\ufafe\ufaff\ufb07\ufb08\ufb09\ufb0a\ufb0b\ufb0c\ufb0d\ufb0e\ufb0f\ufb10\ufb11\ufb12\ufb18\ufb19\ufb1a\ufb1b\ufb1c\ufb37\ufb3d\ufb3f\ufb42\ufb45\ufbb2\ufbb3\ufbb4\ufbb5\ufbb6\ufbb7\ufbb8\ufbb9\ufbba\ufbbb\ufbbc\ufbbd\ufbbe\ufbbf\ufbc0\ufbc1\ufbc2\ufbc3\ufbc4\ufbc5\ufbc6\ufbc7\ufbc8\ufbc9\ufbca\ufbcb\ufbcc\ufbcd\ufbce\ufbcf\ufbd0\ufbd1\ufbd2\ufd40\ufd41\ufd42\ufd43\ufd44\ufd45\ufd46\ufd47\ufd48\ufd49\ufd4a\ufd4b\ufd4c\ufd4d\ufd4e\ufd4f\ufd90\ufd91\ufdc8\ufdc9\ufdca\ufdcb\ufdcc\ufdcd\ufdce\ufdcf\ufdd0\ufdd1\ufdd2\ufdd3\ufdd4\ufdd5\ufdd6\ufdd7\ufdd8\ufdd9\ufdda\ufddb\ufddc\ufddd\ufdde\ufddf\ufde0\ufde1\ufde2\ufde3\ufde4\ufde5\ufde6\ufde7\ufde8\ufde9\ufdea\ufdeb\ufdec\ufded\ufdee\ufdef\ufdfe\ufdff\ufe1a\ufe1b\ufe1c\ufe1d\ufe1e\ufe1f\ufe24\ufe25\ufe26\ufe27\ufe28\ufe29\ufe2a\ufe2b\ufe2c\ufe2d\ufe2e\ufe2f\ufe53\ufe67\ufe6c\ufe6d\ufe6e\ufe6f\ufe75\ufefd\ufefe\uff00\uffbf\uffc0\uffc1\uffc8\uffc9\uffd0\uffd1\uffd8\uffd9\uffdd\uffde\uffdf\uffe7\uffef\ufff0\ufff1\ufff2\ufff3\ufff4\ufff5\ufff6\ufff7\ufff8\ufffe'
-
-Co = u'\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009\ue00a\ue00b\ue00c\ue00d\ue00e\ue00f\ue010\ue011\ue012\ue013\ue014\ue015\ue016\ue017\ue018\ue019\ue01a\ue01b\ue01c\ue01d\ue01e\ue01f\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029\ue02a\ue02b\ue02c\ue02d\ue02e\ue02f\ue030\ue031\ue032\ue033\ue034\ue035\ue036\ue037\ue038\ue039\ue03a\ue03b\ue03c\ue03d\ue03e\ue03f\ue040\ue041\ue042\ue043\ue044\ue045\ue046\ue047\ue048\ue049\ue04a\ue04b\ue04c\ue04d\ue04e\ue04f\ue050\ue051\ue052\ue053\ue054\ue055\ue056\ue057\ue058\ue059\ue05a\ue05b\ue05c\ue05d\ue05e\ue05f\ue060\ue061\ue062\ue063\ue064\ue065\ue066\ue067\ue068\ue069\ue06a\ue06b\ue06c\ue06d\ue06e\ue06f\ue070\ue071\ue072\ue073\ue074\ue075\ue076\ue077\ue078\ue079\ue07a\ue07b\ue07c\ue07d\ue07e\ue07f\ue080\ue081\ue082\ue083\ue084\ue085\ue086\ue087\ue088\ue089\ue08a\ue08b\ue08c\ue08d\ue08e\ue08f\ue090\ue091\ue092\ue093\ue094\ue095\ue096\ue097\ue098\ue099\ue09a\ue09b\ue09c\ue09d\ue09e\ue09f\ue0a0\ue0a1\ue0a2\ue0a3\ue0a4\ue0a5\ue0a6\ue0a7\ue0a8\ue0a9\ue0aa\ue0ab\ue0ac\ue0ad\ue0ae\ue0af\ue0b0\ue0b1\ue0b2\ue0b3\ue0b4\ue0b5\ue0b6\ue0b7\ue0b8\ue0b9\ue0ba\ue0bb\ue0bc\ue0bd\ue0be\ue0bf\ue0c0\ue0c1\ue0c2\ue0c3\ue0c4\ue0c5\ue0c6\ue0c7\ue0c8\ue0c9\ue0ca\ue0cb\ue0cc\ue0cd\ue0ce\ue0cf\ue0d0\ue0d1\ue0d2\ue0d3\ue0d4\ue0d5\ue0d6\ue0d7\ue0d8\ue0d9\ue0da\ue0db\ue0dc\ue0dd\ue0de\ue0df\ue0e0\ue0e1\ue0e2\ue0e3\ue0e4\ue0e5\ue0e6\ue0e7\ue0e8\ue0e9\ue0ea\ue0eb\ue0ec\ue0ed\ue0ee\ue0ef\ue0f0\ue0f1\ue0f2\ue0f3\ue0f4\ue0f5\ue0f6\ue0f7\ue0f8\ue0f9\ue0fa\ue0fb\ue0fc\ue0fd\ue0fe\ue0ff\ue100\ue101\ue102\ue103\ue104\ue105\ue106\ue107\ue108\ue109\ue10a\ue10b\ue10c\ue10d\ue10e\ue10f\ue110\ue111\ue112\ue113\ue114\ue115\ue116\ue117\ue118\ue119\ue11a\ue11b\ue11c\ue11d\ue11e\ue11f\ue120\ue121\ue122\ue123\ue124\ue125\ue126\ue127\ue128\ue129\ue12a\ue12b\ue12c\ue12d\ue12e\ue12f\ue130\ue131\ue132\ue133\ue134\ue135\ue136\ue137\ue138\ue139\ue13a\ue13b\ue13c\ue13d\ue13e\ue13f\ue140\ue141\ue142\ue143\ue144\ue145\ue146\ue147\ue148\ue149\ue14a\ue14b\ue14c\ue14d\ue14e\ue14f\ue150\ue151\ue152\ue153\ue154\ue155\ue156\ue157\ue158\ue159\ue15a\ue15b\ue15c\ue15d\ue15e\ue15f\ue160\ue161\ue162\ue163\ue164\ue165\ue166\ue167\ue168\ue169\ue16a\ue16b\ue16c\ue16d\ue16e\ue16f\ue170\ue171\ue172\ue173\ue174\ue175\ue176\ue177\ue178\ue179\ue17a\ue17b\ue17c\ue17d\ue17e\ue17f\ue180\ue181\ue182\ue183\ue184\ue185\ue186\ue187\ue188\ue189\ue18a\ue18b\ue18c\ue18d\ue18e\ue18f\ue190\ue191\ue192\ue193\ue194\ue195\ue196\ue197\ue198\ue199\ue19a\ue19b\ue19c\ue19d\ue19e\ue19f\ue1a0\ue1a1\ue1a2\ue1a3\ue1a4\ue1a5\ue1a6\ue1a7\ue1a8\ue1a9\ue1aa\ue1ab\ue1ac\ue1ad\ue1ae\ue1af\ue1b0\ue1b1\ue1b2\ue1b3\ue1b4\ue1b5\ue1b6\ue1b7\ue1b8\ue1b9\ue1ba\ue1bb\ue1bc\ue1bd\ue1be\ue1bf\ue1c0\ue1c1\ue1c2\ue1c3\ue1c4\ue1c5\ue1c6\ue1c7\ue1c8\ue1c9\ue1ca\ue1cb\ue1cc\ue1cd\ue1ce\ue1cf\ue1d0\ue1d1\ue1d2\ue1d3\ue1d4\ue1d5\ue1d6\ue1d7\ue1d8\ue1d9\ue1da\ue1db\ue1dc\ue1dd\ue1de\ue1df\ue1e0\ue1e1\ue1e2\ue1e3\ue1e4\ue1e5\ue1e6\ue1e7\ue1e8\ue1e9\ue1ea\ue1eb\ue1ec\ue1ed\ue1ee\ue1ef\ue1f0\ue1f1\ue1f2\ue1f3\ue1f4\ue1f5\ue1f6\ue1f7\ue1f8\ue1f9\ue1fa\ue1fb\ue1fc\ue1fd\ue1fe\ue1ff\ue200\ue201\ue202\ue203\ue204\ue205\ue206\ue207\ue208\ue209\ue20a\ue20b\ue20c\ue20d\ue20e\ue20f\ue210\ue211\ue212\ue213\ue214\ue215\ue216\ue217\ue218\ue219\ue21a\ue21b\ue21c\ue21d\ue21e\ue21f\ue220\ue221\ue222\ue223\ue224\ue225\ue226\ue227\ue228\ue229\ue22a\ue22b\ue22c\ue22d\ue22e\ue22f\ue230\ue231\ue232\ue233\ue234\ue235\ue236\ue237\ue238\ue239\ue23a\ue23b\ue23c\ue23d\ue23e\ue23f\ue240\ue241\ue242\ue243\ue244\ue245\ue246\ue247\ue248\ue249\ue24a\ue24b\ue24c\ue24d\ue24e\ue24f\ue250\ue251\ue252\ue253\ue254\ue255\ue256\ue257\ue258\ue259\ue25a\ue25b\ue25c\ue25d\ue25e\ue25f\ue260\ue261\ue262\ue263\ue264\ue265\ue266\ue267\ue268\ue269\ue26a\ue26b\ue26c\ue26d\ue26e\ue26f\ue270\ue271\ue272\ue273\ue274\ue275\ue276\ue277\ue278\ue279\ue27a\ue27b\ue27c\ue27d\ue27e\ue27f\ue280\ue281\ue282\ue283\ue284\ue285\ue286\ue287\ue288\ue289\ue28a\ue28b\ue28c\ue28d\ue28e\ue28f\ue290\ue291\ue292\ue293\ue294\ue295\ue296\ue297\ue298\ue299\ue29a\ue29b\ue29c\ue29d\ue29e\ue29f\ue2a0\ue2a1\ue2a2\ue2a3\ue2a4\ue2a5\ue2a6\ue2a7\ue2a8\ue2a9\ue2aa\ue2ab\ue2ac\ue2ad\ue2ae\ue2af\ue2b0\ue2b1\ue2b2\ue2b3\ue2b4\ue2b5\ue2b6\ue2b7\ue2b8\ue2b9\ue2ba\ue2bb\ue2bc\ue2bd\ue2be\ue2bf\ue2c0\ue2c1\ue2c2\ue2c3\ue2c4\ue2c5\ue2c6\ue2c7\ue2c8\ue2c9\ue2ca\ue2cb\ue2cc\ue2cd\ue2ce\ue2cf\ue2d0\ue2d1\ue2d2\ue2d3\ue2d4\ue2d5\ue2d6\ue2d7\ue2d8\ue2d9\ue2da\ue2db\ue2dc\ue2dd\ue2de\ue2df\ue2e0\ue2e1\ue2e2\ue2e3\ue2e4\ue2e5\ue2e6\ue2e7\ue2e8\ue2e9\ue2ea\ue2eb\ue2ec\ue2ed\ue2ee\ue2ef\ue2f0\ue2f1\ue2f2\ue2f3\ue2f4\ue2f5\ue2f6\ue2f7\ue2f8\ue2f9\ue2fa\ue2fb\ue2fc\ue2fd\ue2fe\ue2ff\ue300\ue301\ue302\ue303\ue304\ue305\ue306\ue307\ue308\ue309\ue30a\ue30b\ue30c\ue30d\ue30e\ue30f\ue310\ue311\ue312\ue313\ue314\ue315\ue316\ue317\ue318\ue319\ue31a\ue31b\ue31c\ue31d\ue31e\ue31f\ue320\ue321\ue322\ue323\ue324\ue325\ue326\ue327\ue328\ue329\ue32a\ue32b\ue32c\ue32d\ue32e\ue32f\ue330\ue331\ue332\ue333\ue334\ue335\ue336\ue337\ue338\ue339\ue33a\ue33b\ue33c\ue33d\ue33e\ue33f\ue340\ue341\ue342\ue343\ue344\ue345\ue346\ue347\ue348\ue349\ue34a\ue34b\ue34c\ue34d\ue34e\ue34f\ue350\ue351\ue352\ue353\ue354\ue355\ue356\ue357\ue358\ue359\ue35a\ue35b\ue35c\ue35d\ue35e\ue35f\ue360\ue361\ue362\ue363\ue364\ue365\ue366\ue367\ue368\ue369\ue36a\ue36b\ue36c\ue36d\ue36e\ue36f\ue370\ue371\ue372\ue373\ue374\ue375\ue376\ue377\ue378\ue379\ue37a\ue37b\ue37c\ue37d\ue37e\ue37f\ue380\ue381\ue382\ue383\ue384\ue385\ue386\ue387\ue388\ue389\ue38a\ue38b\ue38c\ue38d\ue38e\ue38f\ue390\ue391\ue392\ue393\ue394\ue395\ue396\ue397\ue398\ue399\ue39a\ue39b\ue39c\ue39d\ue39e\ue39f\ue3a0\ue3a1\ue3a2\ue3a3\ue3a4\ue3a5\ue3a6\ue3a7\ue3a8\ue3a9\ue3aa\ue3ab\ue3ac\ue3ad\ue3ae\ue3af\ue3b0\ue3b1\ue3b2\ue3b3\ue3b4\ue3b5\ue3b6\ue3b7\ue3b8\ue3b9\ue3ba\ue3bb\ue3bc\ue3bd\ue3be\ue3bf\ue3c0\ue3c1\ue3c2\ue3c3\ue3c4\ue3c5\ue3c6\ue3c7\ue3c8\ue3c9\ue3ca\ue3cb\ue3cc\ue3cd\ue3ce\ue3cf\ue3d0\ue3d1\ue3d2\ue3d3\ue3d4\ue3d5\ue3d6\ue3d7\ue3d8\ue3d9\ue3da\ue3db\ue3dc\ue3dd\ue3de\ue3df\ue3e0\ue3e1\ue3e2\ue3e3\ue3e4\ue3e5\ue3e6\ue3e7\ue3e8\ue3e9\ue3ea\ue3eb\ue3ec\ue3ed\ue3ee\ue3ef\ue3f0\ue3f1\ue3f2\ue3f3\ue3f4\ue3f5\ue3f6\ue3f7\ue3f8\ue3f9\ue3fa\ue3fb\ue3fc\ue3fd\ue3fe\ue3ff\ue400\ue401\ue402\ue403\ue404\ue405\ue406\ue407\ue408\ue409\ue40a\ue40b\ue40c\ue40d\ue40e\ue40f\ue410\ue411\ue412\ue413\ue414\ue415\ue416\ue417\ue418\ue419\ue41a\ue41b\ue41c\ue41d\ue41e\ue41f\ue420\ue421\ue422\ue423\ue424\ue425\ue426\ue427\ue428\ue429\ue42a\ue42b\ue42c\ue42d\ue42e\ue42f\ue430\ue431\ue432\ue433\ue434\ue435\ue436\ue437\ue438\ue439\ue43a\ue43b\ue43c\ue43d\ue43e\ue43f\ue440\ue441\ue442\ue443\ue444\ue445\ue446\ue447\ue448\ue449\ue44a\ue44b\ue44c\ue44d\ue44e\ue44f\ue450\ue451\ue452\ue453\ue454\ue455\ue456\ue457\ue458\ue459\ue45a\ue45b\ue45c\ue45d\ue45e\ue45f\ue460\ue461\ue462\ue463\ue464\ue465\ue466\ue467\ue468\ue469\ue46a\ue46b\ue46c\ue46d\ue46e\ue46f\ue470\ue471\ue472\ue473\ue474\ue475\ue476\ue477\ue478\ue479\ue47a\ue47b\ue47c\ue47d\ue47e\ue47f\ue480\ue481\ue482\ue483\ue484\ue485\ue486\ue487\ue488\ue489\ue48a\ue48b\ue48c\ue48d\ue48e\ue48f\ue490\ue491\ue492\ue493\ue494\ue495\ue496\ue497\ue498\ue499\ue49a\ue49b\ue49c\ue49d\ue49e\ue49f\ue4a0\ue4a1\ue4a2\ue4a3\ue4a4\ue4a5\ue4a6\ue4a7\ue4a8\ue4a9\ue4aa\ue4ab\ue4ac\ue4ad\ue4ae\ue4af\ue4b0\ue4b1\ue4b2\ue4b3\ue4b4\ue4b5\ue4b6\ue4b7\ue4b8\ue4b9\ue4ba\ue4bb\ue4bc\ue4bd\ue4be\ue4bf\ue4c0\ue4c1\ue4c2\ue4c3\ue4c4\ue4c5\ue4c6\ue4c7\ue4c8\ue4c9\ue4ca\ue4cb\ue4cc\ue4cd\ue4ce\ue4cf\ue4d0\ue4d1\ue4d2\ue4d3\ue4d4\ue4d5\ue4d6\ue4d7\ue4d8\ue4d9\ue4da\ue4db\ue4dc\ue4dd\ue4de\ue4df\ue4e0\ue4e1\ue4e2\ue4e3\ue4e4\ue4e5\ue4e6\ue4e7\ue4e8\ue4e9\ue4ea\ue4eb\ue4ec\ue4ed\ue4ee\ue4ef\ue4f0\ue4f1\ue4f2\ue4f3\ue4f4\ue4f5\ue4f6\ue4f7\ue4f8\ue4f9\ue4fa\ue4fb\ue4fc\ue4fd\ue4fe\ue4ff\ue500\ue501\ue502\ue503\ue504\ue505\ue506\ue507\ue508\ue509\ue50a\ue50b\ue50c\ue50d\ue50e\ue50f\ue510\ue511\ue512\ue513\ue514\ue515\ue516\ue517\ue518\ue519\ue51a\ue51b\ue51c\ue51d\ue51e\ue51f\ue520\ue521\ue522\ue523\ue524\ue525\ue526\ue527\ue528\ue529\ue52a\ue52b\ue52c\ue52d\ue52e\ue52f\ue530\ue531\ue532\ue533\ue534\ue535\ue536\ue537\ue538\ue539\ue53a\ue53b\ue53c\ue53d\ue53e\ue53f\ue540\ue541\ue542\ue543\ue544\ue545\ue546\ue547\ue548\ue549\ue54a\ue54b\ue54c\ue54d\ue54e\ue54f\ue550\ue551\ue552\ue553\ue554\ue555\ue556\ue557\ue558\ue559\ue55a\ue55b\ue55c\ue55d\ue55e\ue55f\ue560\ue561\ue562\ue563\ue564\ue565\ue566\ue567\ue568\ue569\ue56a\ue56b\ue56c\ue56d\ue56e\ue56f\ue570\ue571\ue572\ue573\ue574\ue575\ue576\ue577\ue578\ue579\ue57a\ue57b\ue57c\ue57d\ue57e\ue57f\ue580\ue581\ue582\ue583\ue584\ue585\ue586\ue587\ue588\ue589\ue58a\ue58b\ue58c\ue58d\ue58e\ue58f\ue590\ue591\ue592\ue593\ue594\ue595\ue596\ue597\ue598\ue599\ue59a\ue59b\ue59c\ue59d\ue59e\ue59f\ue5a0\ue5a1\ue5a2\ue5a3\ue5a4\ue5a5\ue5a6\ue5a7\ue5a8\ue5a9\ue5aa\ue5ab\ue5ac\ue5ad\ue5ae\ue5af\ue5b0\ue5b1\ue5b2\ue5b3\ue5b4\ue5b5\ue5b6\ue5b7\ue5b8\ue5b9\ue5ba\ue5bb\ue5bc\ue5bd\ue5be\ue5bf\ue5c0\ue5c1\ue5c2\ue5c3\ue5c4\ue5c5\ue5c6\ue5c7\ue5c8\ue5c9\ue5ca\ue5cb\ue5cc\ue5cd\ue5ce\ue5cf\ue5d0\ue5d1\ue5d2\ue5d3\ue5d4\ue5d5\ue5d6\ue5d7\ue5d8\ue5d9\ue5da\ue5db\ue5dc\ue5dd\ue5de\ue5df\ue5e0\ue5e1\ue5e2\ue5e3\ue5e4\ue5e5\ue5e6\ue5e7\ue5e8\ue5e9\ue5ea\ue5eb\ue5ec\ue5ed\ue5ee\ue5ef\ue5f0\ue5f1\ue5f2\ue5f3\ue5f4\ue5f5\ue5f6\ue5f7\ue5f8\ue5f9\ue5fa\ue5fb\ue5fc\ue5fd\ue5fe\ue5ff\ue600\ue601\ue602\ue603\ue604\ue605\ue606\ue607\ue608\ue609\ue60a\ue60b\ue60c\ue60d\ue60e\ue60f\ue610\ue611\ue612\ue613\ue614\ue615\ue616\ue617\ue618\ue619\ue61a\ue61b\ue61c\ue61d\ue61e\ue61f\ue620\ue621\ue622\ue623\ue624\ue625\ue626\ue627\ue628\ue629\ue62a\ue62b\ue62c\ue62d\ue62e\ue62f\ue630\ue631\ue632\ue633\ue634\ue635\ue636\ue637\ue638\ue639\ue63a\ue63b\ue63c\ue63d\ue63e\ue63f\ue640\ue641\ue642\ue643\ue644\ue645\ue646\ue647\ue648\ue649\ue64a\ue64b\ue64c\ue64d\ue64e\ue64f\ue650\ue651\ue652\ue653\ue654\ue655\ue656\ue657\ue658\ue659\ue65a\ue65b\ue65c\ue65d\ue65e\ue65f\ue660\ue661\ue662\ue663\ue664\ue665\ue666\ue667\ue668\ue669\ue66a\ue66b\ue66c\ue66d\ue66e\ue66f\ue670\ue671\ue672\ue673\ue674\ue675\ue676\ue677\ue678\ue679\ue67a\ue67b\ue67c\ue67d\ue67e\ue67f\ue680\ue681\ue682\ue683\ue684\ue685\ue686\ue687\ue688\ue689\ue68a\ue68b\ue68c\ue68d\ue68e\ue68f\ue690\ue691\ue692\ue693\ue694\ue695\ue696\ue697\ue698\ue699\ue69a\ue69b\ue69c\ue69d\ue69e\ue69f\ue6a0\ue6a1\ue6a2\ue6a3\ue6a4\ue6a5\ue6a6\ue6a7\ue6a8\ue6a9\ue6aa\ue6ab\ue6ac\ue6ad\ue6ae\ue6af\ue6b0\ue6b1\ue6b2\ue6b3\ue6b4\ue6b5\ue6b6\ue6b7\ue6b8\ue6b9\ue6ba\ue6bb\ue6bc\ue6bd\ue6be\ue6bf\ue6c0\ue6c1\ue6c2\ue6c3\ue6c4\ue6c5\ue6c6\ue6c7\ue6c8\ue6c9\ue6ca\ue6cb\ue6cc\ue6cd\ue6ce\ue6cf\ue6d0\ue6d1\ue6d2\ue6d3\ue6d4\ue6d5\ue6d6\ue6d7\ue6d8\ue6d9\ue6da\ue6db\ue6dc\ue6dd\ue6de\ue6df\ue6e0\ue6e1\ue6e2\ue6e3\ue6e4\ue6e5\ue6e6\ue6e7\ue6e8\ue6e9\ue6ea\ue6eb\ue6ec\ue6ed\ue6ee\ue6ef\ue6f0\ue6f1\ue6f2\ue6f3\ue6f4\ue6f5\ue6f6\ue6f7\ue6f8\ue6f9\ue6fa\ue6fb\ue6fc\ue6fd\ue6fe\ue6ff\ue700\ue701\ue702\ue703\ue704\ue705\ue706\ue707\ue708\ue709\ue70a\ue70b\ue70c\ue70d\ue70e\ue70f\ue710\ue711\ue712\ue713\ue714\ue715\ue716\ue717\ue718\ue719\ue71a\ue71b\ue71c\ue71d\ue71e\ue71f\ue720\ue721\ue722\ue723\ue724\ue725\ue726\ue727\ue728\ue729\ue72a\ue72b\ue72c\ue72d\ue72e\ue72f\ue730\ue731\ue732\ue733\ue734\ue735\ue736\ue737\ue738\ue739\ue73a\ue73b\ue73c\ue73d\ue73e\ue73f\ue740\ue741\ue742\ue743\ue744\ue745\ue746\ue747\ue748\ue749\ue74a\ue74b\ue74c\ue74d\ue74e\ue74f\ue750\ue751\ue752\ue753\ue754\ue755\ue756\ue757\ue758\ue759\ue75a\ue75b\ue75c\ue75d\ue75e\ue75f\ue760\ue761\ue762\ue763\ue764\ue765\ue766\ue767\ue768\ue769\ue76a\ue76b\ue76c\ue76d\ue76e\ue76f\ue770\ue771\ue772\ue773\ue774\ue775\ue776\ue777\ue778\ue779\ue77a\ue77b\ue77c\ue77d\ue77e\ue77f\ue780\ue781\ue782\ue783\ue784\ue785\ue786\ue787\ue788\ue789\ue78a\ue78b\ue78c\ue78d\ue78e\ue78f\ue790\ue791\ue792\ue793\ue794\ue795\ue796\ue797\ue798\ue799\ue79a\ue79b\ue79c\ue79d\ue79e\ue79f\ue7a0\ue7a1\ue7a2\ue7a3\ue7a4\ue7a5\ue7a6\ue7a7\ue7a8\ue7a9\ue7aa\ue7ab\ue7ac\ue7ad\ue7ae\ue7af\ue7b0\ue7b1\ue7b2\ue7b3\ue7b4\ue7b5\ue7b6\ue7b7\ue7b8\ue7b9\ue7ba\ue7bb\ue7bc\ue7bd\ue7be\ue7bf\ue7c0\ue7c1\ue7c2\ue7c3\ue7c4\ue7c5\ue7c6\ue7c7\ue7c8\ue7c9\ue7ca\ue7cb\ue7cc\ue7cd\ue7ce\ue7cf\ue7d0\ue7d1\ue7d2\ue7d3\ue7d4\ue7d5\ue7d6\ue7d7\ue7d8\ue7d9\ue7da\ue7db\ue7dc\ue7dd\ue7de\ue7df\ue7e0\ue7e1\ue7e2\ue7e3\ue7e4\ue7e5\ue7e6\ue7e7\ue7e8\ue7e9\ue7ea\ue7eb\ue7ec\ue7ed\ue7ee\ue7ef\ue7f0\ue7f1\ue7f2\ue7f3\ue7f4\ue7f5\ue7f6\ue7f7\ue7f8\ue7f9\ue7fa\ue7fb\ue7fc\ue7fd\ue7fe\ue7ff\ue800\ue801\ue802\ue803\ue804\ue805\ue806\ue807\ue808\ue809\ue80a\ue80b\ue80c\ue80d\ue80e\ue80f\ue810\ue811\ue812\ue813\ue814\ue815\ue816\ue817\ue818\ue819\ue81a\ue81b\ue81c\ue81d\ue81e\ue81f\ue820\ue821\ue822\ue823\ue824\ue825\ue826\ue827\ue828\ue829\ue82a\ue82b\ue82c\ue82d\ue82e\ue82f\ue830\ue831\ue832\ue833\ue834\ue835\ue836\ue837\ue838\ue839\ue83a\ue83b\ue83c\ue83d\ue83e\ue83f\ue840\ue841\ue842\ue843\ue844\ue845\ue846\ue847\ue848\ue849\ue84a\ue84b\ue84c\ue84d\ue84e\ue84f\ue850\ue851\ue852\ue853\ue854\ue855\ue856\ue857\ue858\ue859\ue85a\ue85b\ue85c\ue85d\ue85e\ue85f\ue860\ue861\ue862\ue863\ue864\ue865\ue866\ue867\ue868\ue869\ue86a\ue86b\ue86c\ue86d\ue86e\ue86f\ue870\ue871\ue872\ue873\ue874\ue875\ue876\ue877\ue878\ue879\ue87a\ue87b\ue87c\ue87d\ue87e\ue87f\ue880\ue881\ue882\ue883\ue884\ue885\ue886\ue887\ue888\ue889\ue88a\ue88b\ue88c\ue88d\ue88e\ue88f\ue890\ue891\ue892\ue893\ue894\ue895\ue896\ue897\ue898\ue899\ue89a\ue89b\ue89c\ue89d\ue89e\ue89f\ue8a0\ue8a1\ue8a2\ue8a3\ue8a4\ue8a5\ue8a6\ue8a7\ue8a8\ue8a9\ue8aa\ue8ab\ue8ac\ue8ad\ue8ae\ue8af\ue8b0\ue8b1\ue8b2\ue8b3\ue8b4\ue8b5\ue8b6\ue8b7\ue8b8\ue8b9\ue8ba\ue8bb\ue8bc\ue8bd\ue8be\ue8bf\ue8c0\ue8c1\ue8c2\ue8c3\ue8c4\ue8c5\ue8c6\ue8c7\ue8c8\ue8c9\ue8ca\ue8cb\ue8cc\ue8cd\ue8ce\ue8cf\ue8d0\ue8d1\ue8d2\ue8d3\ue8d4\ue8d5\ue8d6\ue8d7\ue8d8\ue8d9\ue8da\ue8db\ue8dc\ue8dd\ue8de\ue8df\ue8e0\ue8e1\ue8e2\ue8e3\ue8e4\ue8e5\ue8e6\ue8e7\ue8e8\ue8e9\ue8ea\ue8eb\ue8ec\ue8ed\ue8ee\ue8ef\ue8f0\ue8f1\ue8f2\ue8f3\ue8f4\ue8f5\ue8f6\ue8f7\ue8f8\ue8f9\ue8fa\ue8fb\ue8fc\ue8fd\ue8fe\ue8ff\ue900\ue901\ue902\ue903\ue904\ue905\ue906\ue907\ue908\ue909\ue90a\ue90b\ue90c\ue90d\ue90e\ue90f\ue910\ue911\ue912\ue913\ue914\ue915\ue916\ue917\ue918\ue919\ue91a\ue91b\ue91c\ue91d\ue91e\ue91f\ue920\ue921\ue922\ue923\ue924\ue925\ue926\ue927\ue928\ue929\ue92a\ue92b\ue92c\ue92d\ue92e\ue92f\ue930\ue931\ue932\ue933\ue934\ue935\ue936\ue937\ue938\ue939\ue93a\ue93b\ue93c\ue93d\ue93e\ue93f\ue940\ue941\ue942\ue943\ue944\ue945\ue946\ue947\ue948\ue949\ue94a\ue94b\ue94c\ue94d\ue94e\ue94f\ue950\ue951\ue952\ue953\ue954\ue955\ue956\ue957\ue958\ue959\ue95a\ue95b\ue95c\ue95d\ue95e\ue95f\ue960\ue961\ue962\ue963\ue964\ue965\ue966\ue967\ue968\ue969\ue96a\ue96b\ue96c\ue96d\ue96e\ue96f\ue970\ue971\ue972\ue973\ue974\ue975\ue976\ue977\ue978\ue979\ue97a\ue97b\ue97c\ue97d\ue97e\ue97f\ue980\ue981\ue982\ue983\ue984\ue985\ue986\ue987\ue988\ue989\ue98a\ue98b\ue98c\ue98d\ue98e\ue98f\ue990\ue991\ue992\ue993\ue994\ue995\ue996\ue997\ue998\ue999\ue99a\ue99b\ue99c\ue99d\ue99e\ue99f\ue9a0\ue9a1\ue9a2\ue9a3\ue9a4\ue9a5\ue9a6\ue9a7\ue9a8\ue9a9\ue9aa\ue9ab\ue9ac\ue9ad\ue9ae\ue9af\ue9b0\ue9b1\ue9b2\ue9b3\ue9b4\ue9b5\ue9b6\ue9b7\ue9b8\ue9b9\ue9ba\ue9bb\ue9bc\ue9bd\ue9be\ue9bf\ue9c0\ue9c1\ue9c2\ue9c3\ue9c4\ue9c5\ue9c6\ue9c7\ue9c8\ue9c9\ue9ca\ue9cb\ue9cc\ue9cd\ue9ce\ue9cf\ue9d0\ue9d1\ue9d2\ue9d3\ue9d4\ue9d5\ue9d6\ue9d7\ue9d8\ue9d9\ue9da\ue9db\ue9dc\ue9dd\ue9de\ue9df\ue9e0\ue9e1\ue9e2\ue9e3\ue9e4\ue9e5\ue9e6\ue9e7\ue9e8\ue9e9\ue9ea\ue9eb\ue9ec\ue9ed\ue9ee\ue9ef\ue9f0\ue9f1\ue9f2\ue9f3\ue9f4\ue9f5\ue9f6\ue9f7\ue9f8\ue9f9\ue9fa\ue9fb\ue9fc\ue9fd\ue9fe\ue9ff\uea00\uea01\uea02\uea03\uea04\uea05\uea06\uea07\uea08\uea09\uea0a\uea0b\uea0c\uea0d\uea0e\uea0f\uea10\uea11\uea12\uea13\uea14\uea15\uea16\uea17\uea18\uea19\uea1a\uea1b\uea1c\uea1d\uea1e\uea1f\uea20\uea21\uea22\uea23\uea24\uea25\uea26\uea27\uea28\uea29\uea2a\uea2b\uea2c\uea2d\uea2e\uea2f\uea30\uea31\uea32\uea33\uea34\uea35\uea36\uea37\uea38\uea39\uea3a\uea3b\uea3c\uea3d\uea3e\uea3f\uea40\uea41\uea42\uea43\uea44\uea45\uea46\uea47\uea48\uea49\uea4a\uea4b\uea4c\uea4d\uea4e\uea4f\uea50\uea51\uea52\uea53\uea54\uea55\uea56\uea57\uea58\uea59\uea5a\uea5b\uea5c\uea5d\uea5e\uea5f\uea60\uea61\uea62\uea63\uea64\uea65\uea66\uea67\uea68\uea69\uea6a\uea6b\uea6c\uea6d\uea6e\uea6f\uea70\uea71\uea72\uea73\uea74\uea75\uea76\uea77\uea78\uea79\uea7a\uea7b\uea7c\uea7d\uea7e\uea7f\uea80\uea81\uea82\uea83\uea84\uea85\uea86\uea87\uea88\uea89\uea8a\uea8b\uea8c\uea8d\uea8e\uea8f\uea90\uea91\uea92\uea93\uea94\uea95\uea96\uea97\uea98\uea99\uea9a\uea9b\uea9c\uea9d\uea9e\uea9f\ueaa0\ueaa1\ueaa2\ueaa3\ueaa4\ueaa5\ueaa6\ueaa7\ueaa8\ueaa9\ueaaa\ueaab\ueaac\ueaad\ueaae\ueaaf\ueab0\ueab1\ueab2\ueab3\ueab4\ueab5\ueab6\ueab7\ueab8\ueab9\ueaba\ueabb\ueabc\ueabd\ueabe\ueabf\ueac0\ueac1\ueac2\ueac3\ueac4\ueac5\ueac6\ueac7\ueac8\ueac9\ueaca\ueacb\ueacc\ueacd\ueace\ueacf\uead0\uead1\uead2\uead3\uead4\uead5\uead6\uead7\uead8\uead9\ueada\ueadb\ueadc\ueadd\ueade\ueadf\ueae0\ueae1\ueae2\ueae3\ueae4\ueae5\ueae6\ueae7\ueae8\ueae9\ueaea\ueaeb\ueaec\ueaed\ueaee\ueaef\ueaf0\ueaf1\ueaf2\ueaf3\ueaf4\ueaf5\ueaf6\ueaf7\ueaf8\ueaf9\ueafa\ueafb\ueafc\ueafd\ueafe\ueaff\ueb00\ueb01\ueb02\ueb03\ueb04\ueb05\ueb06\ueb07\ueb08\ueb09\ueb0a\ueb0b\ueb0c\ueb0d\ueb0e\ueb0f\ueb10\ueb11\ueb12\ueb13\ueb14\ueb15\ueb16\ueb17\ueb18\ueb19\ueb1a\ueb1b\ueb1c\ueb1d\ueb1e\ueb1f\ueb20\ueb21\ueb22\ueb23\ueb24\ueb25\ueb26\ueb27\ueb28\ueb29\ueb2a\ueb2b\ueb2c\ueb2d\ueb2e\ueb2f\ueb30\ueb31\ueb32\ueb33\ueb34\ueb35\ueb36\ueb37\ueb38\ueb39\ueb3a\ueb3b\ueb3c\ueb3d\ueb3e\ueb3f\ueb40\ueb41\ueb42\ueb43\ueb44\ueb45\ueb46\ueb47\ueb48\ueb49\ueb4a\ueb4b\ueb4c\ueb4d\ueb4e\ueb4f\ueb50\ueb51\ueb52\ueb53\ueb54\ueb55\ueb56\ueb57\ueb58\ueb59\ueb5a\ueb5b\ueb5c\ueb5d\ueb5e\ueb5f\ueb60\ueb61\ueb62\ueb63\ueb64\ueb65\ueb66\ueb67\ueb68\ueb69\ueb6a\ueb6b\ueb6c\ueb6d\ueb6e\ueb6f\ueb70\ueb71\ueb72\ueb73\ueb74\ueb75\ueb76\ueb77\ueb78\ueb79\ueb7a\ueb7b\ueb7c\ueb7d\ueb7e\ueb7f\ueb80\ueb81\ueb82\ueb83\ueb84\ueb85\ueb86\ueb87\ueb88\ueb89\ueb8a\ueb8b\ueb8c\ueb8d\ueb8e\ueb8f\ueb90\ueb91\ueb92\ueb93\ueb94\ueb95\ueb96\ueb97\ueb98\ueb99\ueb9a\ueb9b\ueb9c\ueb9d\ueb9e\ueb9f\ueba0\ueba1\ueba2\ueba3\ueba4\ueba5\ueba6\ueba7\ueba8\ueba9\uebaa\uebab\uebac\uebad\uebae\uebaf\uebb0\uebb1\uebb2\uebb3\uebb4\uebb5\uebb6\uebb7\uebb8\uebb9\uebba\uebbb\uebbc\uebbd\uebbe\uebbf\uebc0\uebc1\uebc2\uebc3\uebc4\uebc5\uebc6\uebc7\uebc8\uebc9\uebca\uebcb\uebcc\uebcd\uebce\uebcf\uebd0\uebd1\uebd2\uebd3\uebd4\uebd5\uebd6\uebd7\uebd8\uebd9\uebda\uebdb\uebdc\uebdd\uebde\uebdf\uebe0\uebe1\uebe2\uebe3\uebe4\uebe5\uebe6\uebe7\uebe8\uebe9\uebea\uebeb\uebec\uebed\uebee\uebef\uebf0\uebf1\uebf2\uebf3\uebf4\uebf5\uebf6\uebf7\uebf8\uebf9\uebfa\uebfb\uebfc\uebfd\uebfe\uebff\uec00\uec01\uec02\uec03\uec04\uec05\uec06\uec07\uec08\uec09\uec0a\uec0b\uec0c\uec0d\uec0e\uec0f\uec10\uec11\uec12\uec13\uec14\uec15\uec16\uec17\uec18\uec19\uec1a\uec1b\uec1c\uec1d\uec1e\uec1f\uec20\uec21\uec22\uec23\uec24\uec25\uec26\uec27\uec28\uec29\uec2a\uec2b\uec2c\uec2d\uec2e\uec2f\uec30\uec31\uec32\uec33\uec34\uec35\uec36\uec37\uec38\uec39\uec3a\uec3b\uec3c\uec3d\uec3e\uec3f\uec40\uec41\uec42\uec43\uec44\uec45\uec46\uec47\uec48\uec49\uec4a\uec4b\uec4c\uec4d\uec4e\uec4f\uec50\uec51\uec52\uec53\uec54\uec55\uec56\uec57\uec58\uec59\uec5a\uec5b\uec5c\uec5d\uec5e\uec5f\uec60\uec61\uec62\uec63\uec64\uec65\uec66\uec67\uec68\uec69\uec6a\uec6b\uec6c\uec6d\uec6e\uec6f\uec70\uec71\uec72\uec73\uec74\uec75\uec76\uec77\uec78\uec79\uec7a\uec7b\uec7c\uec7d\uec7e\uec7f\uec80\uec81\uec82\uec83\uec84\uec85\uec86\uec87\uec88\uec89\uec8a\uec8b\uec8c\uec8d\uec8e\uec8f\uec90\uec91\uec92\uec93\uec94\uec95\uec96\uec97\uec98\uec99\uec9a\uec9b\uec9c\uec9d\uec9e\uec9f\ueca0\ueca1\ueca2\ueca3\ueca4\ueca5\ueca6\ueca7\ueca8\ueca9\uecaa\uecab\uecac\uecad\uecae\uecaf\uecb0\uecb1\uecb2\uecb3\uecb4\uecb5\uecb6\uecb7\uecb8\uecb9\uecba\uecbb\uecbc\uecbd\uecbe\uecbf\uecc0\uecc1\uecc2\uecc3\uecc4\uecc5\uecc6\uecc7\uecc8\uecc9\uecca\ueccb\ueccc\ueccd\uecce\ueccf\uecd0\uecd1\uecd2\uecd3\uecd4\uecd5\uecd6\uecd7\uecd8\uecd9\uecda\uecdb\uecdc\uecdd\uecde\uecdf\uece0\uece1\uece2\uece3\uece4\uece5\uece6\uece7\uece8\uece9\uecea\ueceb\uecec\ueced\uecee\uecef\uecf0\uecf1\uecf2\uecf3\uecf4\uecf5\uecf6\uecf7\uecf8\uecf9\uecfa\uecfb\uecfc\uecfd\uecfe\uecff\ued00\ued01\ued02\ued03\ued04\ued05\ued06\ued07\ued08\ued09\ued0a\ued0b\ued0c\ued0d\ued0e\ued0f\ued10\ued11\ued12\ued13\ued14\ued15\ued16\ued17\ued18\ued19\ued1a\ued1b\ued1c\ued1d\ued1e\ued1f\ued20\ued21\ued22\ued23\ued24\ued25\ued26\ued27\ued28\ued29\ued2a\ued2b\ued2c\ued2d\ued2e\ued2f\ued30\ued31\ued32\ued33\ued34\ued35\ued36\ued37\ued38\ued39\ued3a\ued3b\ued3c\ued3d\ued3e\ued3f\ued40\ued41\ued42\ued43\ued44\ued45\ued46\ued47\ued48\ued49\ued4a\ued4b\ued4c\ued4d\ued4e\ued4f\ued50\ued51\ued52\ued53\ued54\ued55\ued56\ued57\ued58\ued59\ued5a\ued5b\ued5c\ued5d\ued5e\ued5f\ued60\ued61\ued62\ued63\ued64\ued65\ued66\ued67\ued68\ued69\ued6a\ued6b\ued6c\ued6d\ued6e\ued6f\ued70\ued71\ued72\ued73\ued74\ued75\ued76\ued77\ued78\ued79\ued7a\ued7b\ued7c\ued7d\ued7e\ued7f\ued80\ued81\ued82\ued83\ued84\ued85\ued86\ued87\ued88\ued89\ued8a\ued8b\ued8c\ued8d\ued8e\ued8f\ued90\ued91\ued92\ued93\ued94\ued95\ued96\ued97\ued98\ued99\ued9a\ued9b\ued9c\ued9d\ued9e\ued9f\ueda0\ueda1\ueda2\ueda3\ueda4\ueda5\ueda6\ueda7\ueda8\ueda9\uedaa\uedab\uedac\uedad\uedae\uedaf\uedb0\uedb1\uedb2\uedb3\uedb4\uedb5\uedb6\uedb7\uedb8\uedb9\uedba\uedbb\uedbc\uedbd\uedbe\uedbf\uedc0\uedc1\uedc2\uedc3\uedc4\uedc5\uedc6\uedc7\uedc8\uedc9\uedca\uedcb\uedcc\uedcd\uedce\uedcf\uedd0\uedd1\uedd2\uedd3\uedd4\uedd5\uedd6\uedd7\uedd8\uedd9\uedda\ueddb\ueddc\ueddd\uedde\ueddf\uede0\uede1\uede2\uede3\uede4\uede5\uede6\uede7\uede8\uede9\uedea\uedeb\uedec\ueded\uedee\uedef\uedf0\uedf1\uedf2\uedf3\uedf4\uedf5\uedf6\uedf7\uedf8\uedf9\uedfa\uedfb\uedfc\uedfd\uedfe\uedff\uee00\uee01\uee02\uee03\uee04\uee05\uee06\uee07\uee08\uee09\uee0a\uee0b\uee0c\uee0d\uee0e\uee0f\uee10\uee11\uee12\uee13\uee14\uee15\uee16\uee17\uee18\uee19\uee1a\uee1b\uee1c\uee1d\uee1e\uee1f\uee20\uee21\uee22\uee23\uee24\uee25\uee26\uee27\uee28\uee29\uee2a\uee2b\uee2c\uee2d\uee2e\uee2f\uee30\uee31\uee32\uee33\uee34\uee35\uee36\uee37\uee38\uee39\uee3a\uee3b\uee3c\uee3d\uee3e\uee3f\uee40\uee41\uee42\uee43\uee44\uee45\uee46\uee47\uee48\uee49\uee4a\uee4b\uee4c\uee4d\uee4e\uee4f\uee50\uee51\uee52\uee53\uee54\uee55\uee56\uee57\uee58\uee59\uee5a\uee5b\uee5c\uee5d\uee5e\uee5f\uee60\uee61\uee62\uee63\uee64\uee65\uee66\uee67\uee68\uee69\uee6a\uee6b\uee6c\uee6d\uee6e\uee6f\uee70\uee71\uee72\uee73\uee74\uee75\uee76\uee77\uee78\uee79\uee7a\uee7b\uee7c\uee7d\uee7e\uee7f\uee80\uee81\uee82\uee83\uee84\uee85\uee86\uee87\uee88\uee89\uee8a\uee8b\uee8c\uee8d\uee8e\uee8f\uee90\uee91\uee92\uee93\uee94\uee95\uee96\uee97\uee98\uee99\uee9a\uee9b\uee9c\uee9d\uee9e\uee9f\ueea0\ueea1\ueea2\ueea3\ueea4\ueea5\ueea6\ueea7\ueea8\ueea9\ueeaa\ueeab\ueeac\ueead\ueeae\ueeaf\ueeb0\ueeb1\ueeb2\ueeb3\ueeb4\ueeb5\ueeb6\ueeb7\ueeb8\ueeb9\ueeba\ueebb\ueebc\ueebd\ueebe\ueebf\ueec0\ueec1\ueec2\ueec3\ueec4\ueec5\ueec6\ueec7\ueec8\ueec9\ueeca\ueecb\ueecc\ueecd\ueece\ueecf\ueed0\ueed1\ueed2\ueed3\ueed4\ueed5\ueed6\ueed7\ueed8\ueed9\ueeda\ueedb\ueedc\ueedd\ueede\ueedf\ueee0\ueee1\ueee2\ueee3\ueee4\ueee5\ueee6\ueee7\ueee8\ueee9\ueeea\ueeeb\ueeec\ueeed\ueeee\ueeef\ueef0\ueef1\ueef2\ueef3\ueef4\ueef5\ueef6\ueef7\ueef8\ueef9\ueefa\ueefb\ueefc\ueefd\ueefe\ueeff\uef00\uef01\uef02\uef03\uef04\uef05\uef06\uef07\uef08\uef09\uef0a\uef0b\uef0c\uef0d\uef0e\uef0f\uef10\uef11\uef12\uef13\uef14\uef15\uef16\uef17\uef18\uef19\uef1a\uef1b\uef1c\uef1d\uef1e\uef1f\uef20\uef21\uef22\uef23\uef24\uef25\uef26\uef27\uef28\uef29\uef2a\uef2b\uef2c\uef2d\uef2e\uef2f\uef30\uef31\uef32\uef33\uef34\uef35\uef36\uef37\uef38\uef39\uef3a\uef3b\uef3c\uef3d\uef3e\uef3f\uef40\uef41\uef42\uef43\uef44\uef45\uef46\uef47\uef48\uef49\uef4a\uef4b\uef4c\uef4d\uef4e\uef4f\uef50\uef51\uef52\uef53\uef54\uef55\uef56\uef57\uef58\uef59\uef5a\uef5b\uef5c\uef5d\uef5e\uef5f\uef60\uef61\uef62\uef63\uef64\uef65\uef66\uef67\uef68\uef69\uef6a\uef6b\uef6c\uef6d\uef6e\uef6f\uef70\uef71\uef72\uef73\uef74\uef75\uef76\uef77\uef78\uef79\uef7a\uef7b\uef7c\uef7d\uef7e\uef7f\uef80\uef81\uef82\uef83\uef84\uef85\uef86\uef87\uef88\uef89\uef8a\uef8b\uef8c\uef8d\uef8e\uef8f\uef90\uef91\uef92\uef93\uef94\uef95\uef96\uef97\uef98\uef99\uef9a\uef9b\uef9c\uef9d\uef9e\uef9f\uefa0\uefa1\uefa2\uefa3\uefa4\uefa5\uefa6\uefa7\uefa8\uefa9\uefaa\uefab\uefac\uefad\uefae\uefaf\uefb0\uefb1\uefb2\uefb3\uefb4\uefb5\uefb6\uefb7\uefb8\uefb9\uefba\uefbb\uefbc\uefbd\uefbe\uefbf\uefc0\uefc1\uefc2\uefc3\uefc4\uefc5\uefc6\uefc7\uefc8\uefc9\uefca\uefcb\uefcc\uefcd\uefce\uefcf\uefd0\uefd1\uefd2\uefd3\uefd4\uefd5\uefd6\uefd7\uefd8\uefd9\uefda\uefdb\uefdc\uefdd\uefde\uefdf\uefe0\uefe1\uefe2\uefe3\uefe4\uefe5\uefe6\uefe7\uefe8\uefe9\uefea\uefeb\uefec\uefed\uefee\uefef\ueff0\ueff1\ueff2\ueff3\ueff4\ueff5\ueff6\ueff7\ueff8\ueff9\ueffa\ueffb\ueffc\ueffd\ueffe\uefff\uf000\uf001\uf002\uf003\uf004\uf005\uf006\uf007\uf008\uf009\uf00a\uf00b\uf00c\uf00d\uf00e\uf00f\uf010\uf011\uf012\uf013\uf014\uf015\uf016\uf017\uf018\uf019\uf01a\uf01b\uf01c\uf01d\uf01e\uf01f\uf020\uf021\uf022\uf023\uf024\uf025\uf026\uf027\uf028\uf029\uf02a\uf02b\uf02c\uf02d\uf02e\uf02f\uf030\uf031\uf032\uf033\uf034\uf035\uf036\uf037\uf038\uf039\uf03a\uf03b\uf03c\uf03d\uf03e\uf03f\uf040\uf041\uf042\uf043\uf044\uf045\uf046\uf047\uf048\uf049\uf04a\uf04b\uf04c\uf04d\uf04e\uf04f\uf050\uf051\uf052\uf053\uf054\uf055\uf056\uf057\uf058\uf059\uf05a\uf05b\uf05c\uf05d\uf05e\uf05f\uf060\uf061\uf062\uf063\uf064\uf065\uf066\uf067\uf068\uf069\uf06a\uf06b\uf06c\uf06d\uf06e\uf06f\uf070\uf071\uf072\uf073\uf074\uf075\uf076\uf077\uf078\uf079\uf07a\uf07b\uf07c\uf07d\uf07e\uf07f\uf080\uf081\uf082\uf083\uf084\uf085\uf086\uf087\uf088\uf089\uf08a\uf08b\uf08c\uf08d\uf08e\uf08f\uf090\uf091\uf092\uf093\uf094\uf095\uf096\uf097\uf098\uf099\uf09a\uf09b\uf09c\uf09d\uf09e\uf09f\uf0a0\uf0a1\uf0a2\uf0a3\uf0a4\uf0a5\uf0a6\uf0a7\uf0a8\uf0a9\uf0aa\uf0ab\uf0ac\uf0ad\uf0ae\uf0af\uf0b0\uf0b1\uf0b2\uf0b3\uf0b4\uf0b5\uf0b6\uf0b7\uf0b8\uf0b9\uf0ba\uf0bb\uf0bc\uf0bd\uf0be\uf0bf\uf0c0\uf0c1\uf0c2\uf0c3\uf0c4\uf0c5\uf0c6\uf0c7\uf0c8\uf0c9\uf0ca\uf0cb\uf0cc\uf0cd\uf0ce\uf0cf\uf0d0\uf0d1\uf0d2\uf0d3\uf0d4\uf0d5\uf0d6\uf0d7\uf0d8\uf0d9\uf0da\uf0db\uf0dc\uf0dd\uf0de\uf0df\uf0e0\uf0e1\uf0e2\uf0e3\uf0e4\uf0e5\uf0e6\uf0e7\uf0e8\uf0e9\uf0ea\uf0eb\uf0ec\uf0ed\uf0ee\uf0ef\uf0f0\uf0f1\uf0f2\uf0f3\uf0f4\uf0f5\uf0f6\uf0f7\uf0f8\uf0f9\uf0fa\uf0fb\uf0fc\uf0fd\uf0fe\uf0ff\uf100\uf101\uf102\uf103\uf104\uf105\uf106\uf107\uf108\uf109\uf10a\uf10b\uf10c\uf10d\uf10e\uf10f\uf110\uf111\uf112\uf113\uf114\uf115\uf116\uf117\uf118\uf119\uf11a\uf11b\uf11c\uf11d\uf11e\uf11f\uf120\uf121\uf122\uf123\uf124\uf125\uf126\uf127\uf128\uf129\uf12a\uf12b\uf12c\uf12d\uf12e\uf12f\uf130\uf131\uf132\uf133\uf134\uf135\uf136\uf137\uf138\uf139\uf13a\uf13b\uf13c\uf13d\uf13e\uf13f\uf140\uf141\uf142\uf143\uf144\uf145\uf146\uf147\uf148\uf149\uf14a\uf14b\uf14c\uf14d\uf14e\uf14f\uf150\uf151\uf152\uf153\uf154\uf155\uf156\uf157\uf158\uf159\uf15a\uf15b\uf15c\uf15d\uf15e\uf15f\uf160\uf161\uf162\uf163\uf164\uf165\uf166\uf167\uf168\uf169\uf16a\uf16b\uf16c\uf16d\uf16e\uf16f\uf170\uf171\uf172\uf173\uf174\uf175\uf176\uf177\uf178\uf179\uf17a\uf17b\uf17c\uf17d\uf17e\uf17f\uf180\uf181\uf182\uf183\uf184\uf185\uf186\uf187\uf188\uf189\uf18a\uf18b\uf18c\uf18d\uf18e\uf18f\uf190\uf191\uf192\uf193\uf194\uf195\uf196\uf197\uf198\uf199\uf19a\uf19b\uf19c\uf19d\uf19e\uf19f\uf1a0\uf1a1\uf1a2\uf1a3\uf1a4\uf1a5\uf1a6\uf1a7\uf1a8\uf1a9\uf1aa\uf1ab\uf1ac\uf1ad\uf1ae\uf1af\uf1b0\uf1b1\uf1b2\uf1b3\uf1b4\uf1b5\uf1b6\uf1b7\uf1b8\uf1b9\uf1ba\uf1bb\uf1bc\uf1bd\uf1be\uf1bf\uf1c0\uf1c1\uf1c2\uf1c3\uf1c4\uf1c5\uf1c6\uf1c7\uf1c8\uf1c9\uf1ca\uf1cb\uf1cc\uf1cd\uf1ce\uf1cf\uf1d0\uf1d1\uf1d2\uf1d3\uf1d4\uf1d5\uf1d6\uf1d7\uf1d8\uf1d9\uf1da\uf1db\uf1dc\uf1dd\uf1de\uf1df\uf1e0\uf1e1\uf1e2\uf1e3\uf1e4\uf1e5\uf1e6\uf1e7\uf1e8\uf1e9\uf1ea\uf1eb\uf1ec\uf1ed\uf1ee\uf1ef\uf1f0\uf1f1\uf1f2\uf1f3\uf1f4\uf1f5\uf1f6\uf1f7\uf1f8\uf1f9\uf1fa\uf1fb\uf1fc\uf1fd\uf1fe\uf1ff\uf200\uf201\uf202\uf203\uf204\uf205\uf206\uf207\uf208\uf209\uf20a\uf20b\uf20c\uf20d\uf20e\uf20f\uf210\uf211\uf212\uf213\uf214\uf215\uf216\uf217\uf218\uf219\uf21a\uf21b\uf21c\uf21d\uf21e\uf21f\uf220\uf221\uf222\uf223\uf224\uf225\uf226\uf227\uf228\uf229\uf22a\uf22b\uf22c\uf22d\uf22e\uf22f\uf230\uf231\uf232\uf233\uf234\uf235\uf236\uf237\uf238\uf239\uf23a\uf23b\uf23c\uf23d\uf23e\uf23f\uf240\uf241\uf242\uf243\uf244\uf245\uf246\uf247\uf248\uf249\uf24a\uf24b\uf24c\uf24d\uf24e\uf24f\uf250\uf251\uf252\uf253\uf254\uf255\uf256\uf257\uf258\uf259\uf25a\uf25b\uf25c\uf25d\uf25e\uf25f\uf260\uf261\uf262\uf263\uf264\uf265\uf266\uf267\uf268\uf269\uf26a\uf26b\uf26c\uf26d\uf26e\uf26f\uf270\uf271\uf272\uf273\uf274\uf275\uf276\uf277\uf278\uf279\uf27a\uf27b\uf27c\uf27d\uf27e\uf27f\uf280\uf281\uf282\uf283\uf284\uf285\uf286\uf287\uf288\uf289\uf28a\uf28b\uf28c\uf28d\uf28e\uf28f\uf290\uf291\uf292\uf293\uf294\uf295\uf296\uf297\uf298\uf299\uf29a\uf29b\uf29c\uf29d\uf29e\uf29f\uf2a0\uf2a1\uf2a2\uf2a3\uf2a4\uf2a5\uf2a6\uf2a7\uf2a8\uf2a9\uf2aa\uf2ab\uf2ac\uf2ad\uf2ae\uf2af\uf2b0\uf2b1\uf2b2\uf2b3\uf2b4\uf2b5\uf2b6\uf2b7\uf2b8\uf2b9\uf2ba\uf2bb\uf2bc\uf2bd\uf2be\uf2bf\uf2c0\uf2c1\uf2c2\uf2c3\uf2c4\uf2c5\uf2c6\uf2c7\uf2c8\uf2c9\uf2ca\uf2cb\uf2cc\uf2cd\uf2ce\uf2cf\uf2d0\uf2d1\uf2d2\uf2d3\uf2d4\uf2d5\uf2d6\uf2d7\uf2d8\uf2d9\uf2da\uf2db\uf2dc\uf2dd\uf2de\uf2df\uf2e0\uf2e1\uf2e2\uf2e3\uf2e4\uf2e5\uf2e6\uf2e7\uf2e8\uf2e9\uf2ea\uf2eb\uf2ec\uf2ed\uf2ee\uf2ef\uf2f0\uf2f1\uf2f2\uf2f3\uf2f4\uf2f5\uf2f6\uf2f7\uf2f8\uf2f9\uf2fa\uf2fb\uf2fc\uf2fd\uf2fe\uf2ff\uf300\uf301\uf302\uf303\uf304\uf305\uf306\uf307\uf308\uf309\uf30a\uf30b\uf30c\uf30d\uf30e\uf30f\uf310\uf311\uf312\uf313\uf314\uf315\uf316\uf317\uf318\uf319\uf31a\uf31b\uf31c\uf31d\uf31e\uf31f\uf320\uf321\uf322\uf323\uf324\uf325\uf326\uf327\uf328\uf329\uf32a\uf32b\uf32c\uf32d\uf32e\uf32f\uf330\uf331\uf332\uf333\uf334\uf335\uf336\uf337\uf338\uf339\uf33a\uf33b\uf33c\uf33d\uf33e\uf33f\uf340\uf341\uf342\uf343\uf344\uf345\uf346\uf347\uf348\uf349\uf34a\uf34b\uf34c\uf34d\uf34e\uf34f\uf350\uf351\uf352\uf353\uf354\uf355\uf356\uf357\uf358\uf359\uf35a\uf35b\uf35c\uf35d\uf35e\uf35f\uf360\uf361\uf362\uf363\uf364\uf365\uf366\uf367\uf368\uf369\uf36a\uf36b\uf36c\uf36d\uf36e\uf36f\uf370\uf371\uf372\uf373\uf374\uf375\uf376\uf377\uf378\uf379\uf37a\uf37b\uf37c\uf37d\uf37e\uf37f\uf380\uf381\uf382\uf383\uf384\uf385\uf386\uf387\uf388\uf389\uf38a\uf38b\uf38c\uf38d\uf38e\uf38f\uf390\uf391\uf392\uf393\uf394\uf395\uf396\uf397\uf398\uf399\uf39a\uf39b\uf39c\uf39d\uf39e\uf39f\uf3a0\uf3a1\uf3a2\uf3a3\uf3a4\uf3a5\uf3a6\uf3a7\uf3a8\uf3a9\uf3aa\uf3ab\uf3ac\uf3ad\uf3ae\uf3af\uf3b0\uf3b1\uf3b2\uf3b3\uf3b4\uf3b5\uf3b6\uf3b7\uf3b8\uf3b9\uf3ba\uf3bb\uf3bc\uf3bd\uf3be\uf3bf\uf3c0\uf3c1\uf3c2\uf3c3\uf3c4\uf3c5\uf3c6\uf3c7\uf3c8\uf3c9\uf3ca\uf3cb\uf3cc\uf3cd\uf3ce\uf3cf\uf3d0\uf3d1\uf3d2\uf3d3\uf3d4\uf3d5\uf3d6\uf3d7\uf3d8\uf3d9\uf3da\uf3db\uf3dc\uf3dd\uf3de\uf3df\uf3e0\uf3e1\uf3e2\uf3e3\uf3e4\uf3e5\uf3e6\uf3e7\uf3e8\uf3e9\uf3ea\uf3eb\uf3ec\uf3ed\uf3ee\uf3ef\uf3f0\uf3f1\uf3f2\uf3f3\uf3f4\uf3f5\uf3f6\uf3f7\uf3f8\uf3f9\uf3fa\uf3fb\uf3fc\uf3fd\uf3fe\uf3ff\uf400\uf401\uf402\uf403\uf404\uf405\uf406\uf407\uf408\uf409\uf40a\uf40b\uf40c\uf40d\uf40e\uf40f\uf410\uf411\uf412\uf413\uf414\uf415\uf416\uf417\uf418\uf419\uf41a\uf41b\uf41c\uf41d\uf41e\uf41f\uf420\uf421\uf422\uf423\uf424\uf425\uf426\uf427\uf428\uf429\uf42a\uf42b\uf42c\uf42d\uf42e\uf42f\uf430\uf431\uf432\uf433\uf434\uf435\uf436\uf437\uf438\uf439\uf43a\uf43b\uf43c\uf43d\uf43e\uf43f\uf440\uf441\uf442\uf443\uf444\uf445\uf446\uf447\uf448\uf449\uf44a\uf44b\uf44c\uf44d\uf44e\uf44f\uf450\uf451\uf452\uf453\uf454\uf455\uf456\uf457\uf458\uf459\uf45a\uf45b\uf45c\uf45d\uf45e\uf45f\uf460\uf461\uf462\uf463\uf464\uf465\uf466\uf467\uf468\uf469\uf46a\uf46b\uf46c\uf46d\uf46e\uf46f\uf470\uf471\uf472\uf473\uf474\uf475\uf476\uf477\uf478\uf479\uf47a\uf47b\uf47c\uf47d\uf47e\uf47f\uf480\uf481\uf482\uf483\uf484\uf485\uf486\uf487\uf488\uf489\uf48a\uf48b\uf48c\uf48d\uf48e\uf48f\uf490\uf491\uf492\uf493\uf494\uf495\uf496\uf497\uf498\uf499\uf49a\uf49b\uf49c\uf49d\uf49e\uf49f\uf4a0\uf4a1\uf4a2\uf4a3\uf4a4\uf4a5\uf4a6\uf4a7\uf4a8\uf4a9\uf4aa\uf4ab\uf4ac\uf4ad\uf4ae\uf4af\uf4b0\uf4b1\uf4b2\uf4b3\uf4b4\uf4b5\uf4b6\uf4b7\uf4b8\uf4b9\uf4ba\uf4bb\uf4bc\uf4bd\uf4be\uf4bf\uf4c0\uf4c1\uf4c2\uf4c3\uf4c4\uf4c5\uf4c6\uf4c7\uf4c8\uf4c9\uf4ca\uf4cb\uf4cc\uf4cd\uf4ce\uf4cf\uf4d0\uf4d1\uf4d2\uf4d3\uf4d4\uf4d5\uf4d6\uf4d7\uf4d8\uf4d9\uf4da\uf4db\uf4dc\uf4dd\uf4de\uf4df\uf4e0\uf4e1\uf4e2\uf4e3\uf4e4\uf4e5\uf4e6\uf4e7\uf4e8\uf4e9\uf4ea\uf4eb\uf4ec\uf4ed\uf4ee\uf4ef\uf4f0\uf4f1\uf4f2\uf4f3\uf4f4\uf4f5\uf4f6\uf4f7\uf4f8\uf4f9\uf4fa\uf4fb\uf4fc\uf4fd\uf4fe\uf4ff\uf500\uf501\uf502\uf503\uf504\uf505\uf506\uf507\uf508\uf509\uf50a\uf50b\uf50c\uf50d\uf50e\uf50f\uf510\uf511\uf512\uf513\uf514\uf515\uf516\uf517\uf518\uf519\uf51a\uf51b\uf51c\uf51d\uf51e\uf51f\uf520\uf521\uf522\uf523\uf524\uf525\uf526\uf527\uf528\uf529\uf52a\uf52b\uf52c\uf52d\uf52e\uf52f\uf530\uf531\uf532\uf533\uf534\uf535\uf536\uf537\uf538\uf539\uf53a\uf53b\uf53c\uf53d\uf53e\uf53f\uf540\uf541\uf542\uf543\uf544\uf545\uf546\uf547\uf548\uf549\uf54a\uf54b\uf54c\uf54d\uf54e\uf54f\uf550\uf551\uf552\uf553\uf554\uf555\uf556\uf557\uf558\uf559\uf55a\uf55b\uf55c\uf55d\uf55e\uf55f\uf560\uf561\uf562\uf563\uf564\uf565\uf566\uf567\uf568\uf569\uf56a\uf56b\uf56c\uf56d\uf56e\uf56f\uf570\uf571\uf572\uf573\uf574\uf575\uf576\uf577\uf578\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf582\uf583\uf584\uf585\uf586\uf587\uf588\uf589\uf58a\uf58b\uf58c\uf58d\uf58e\uf58f\uf590\uf591\uf592\uf593\uf594\uf595\uf596\uf597\uf598\uf599\uf59a\uf59b\uf59c\uf59d\uf59e\uf59f\uf5a0\uf5a1\uf5a2\uf5a3\uf5a4\uf5a5\uf5a6\uf5a7\uf5a8\uf5a9\uf5aa\uf5ab\uf5ac\uf5ad\uf5ae\uf5af\uf5b0\uf5b1\uf5b2\uf5b3\uf5b4\uf5b5\uf5b6\uf5b7\uf5b8\uf5b9\uf5ba\uf5bb\uf5bc\uf5bd\uf5be\uf5bf\uf5c0\uf5c1\uf5c2\uf5c3\uf5c4\uf5c5\uf5c6\uf5c7\uf5c8\uf5c9\uf5ca\uf5cb\uf5cc\uf5cd\uf5ce\uf5cf\uf5d0\uf5d1\uf5d2\uf5d3\uf5d4\uf5d5\uf5d6\uf5d7\uf5d8\uf5d9\uf5da\uf5db\uf5dc\uf5dd\uf5de\uf5df\uf5e0\uf5e1\uf5e2\uf5e3\uf5e4\uf5e5\uf5e6\uf5e7\uf5e8\uf5e9\uf5ea\uf5eb\uf5ec\uf5ed\uf5ee\uf5ef\uf5f0\uf5f1\uf5f2\uf5f3\uf5f4\uf5f5\uf5f6\uf5f7\uf5f8\uf5f9\uf5fa\uf5fb\uf5fc\uf5fd\uf5fe\uf5ff\uf600\uf601\uf602\uf603\uf604\uf605\uf606\uf607\uf608\uf609\uf60a\uf60b\uf60c\uf60d\uf60e\uf60f\uf610\uf611\uf612\uf613\uf614\uf615\uf616\uf617\uf618\uf619\uf61a\uf61b\uf61c\uf61d\uf61e\uf61f\uf620\uf621\uf622\uf623\uf624\uf625\uf626\uf627\uf628\uf629\uf62a\uf62b\uf62c\uf62d\uf62e\uf62f\uf630\uf631\uf632\uf633\uf634\uf635\uf636\uf637\uf638\uf639\uf63a\uf63b\uf63c\uf63d\uf63e\uf63f\uf640\uf641\uf642\uf643\uf644\uf645\uf646\uf647\uf648\uf649\uf64a\uf64b\uf64c\uf64d\uf64e\uf64f\uf650\uf651\uf652\uf653\uf654\uf655\uf656\uf657\uf658\uf659\uf65a\uf65b\uf65c\uf65d\uf65e\uf65f\uf660\uf661\uf662\uf663\uf664\uf665\uf666\uf667\uf668\uf669\uf66a\uf66b\uf66c\uf66d\uf66e\uf66f\uf670\uf671\uf672\uf673\uf674\uf675\uf676\uf677\uf678\uf679\uf67a\uf67b\uf67c\uf67d\uf67e\uf67f\uf680\uf681\uf682\uf683\uf684\uf685\uf686\uf687\uf688\uf689\uf68a\uf68b\uf68c\uf68d\uf68e\uf68f\uf690\uf691\uf692\uf693\uf694\uf695\uf696\uf697\uf698\uf699\uf69a\uf69b\uf69c\uf69d\uf69e\uf69f\uf6a0\uf6a1\uf6a2\uf6a3\uf6a4\uf6a5\uf6a6\uf6a7\uf6a8\uf6a9\uf6aa\uf6ab\uf6ac\uf6ad\uf6ae\uf6af\uf6b0\uf6b1\uf6b2\uf6b3\uf6b4\uf6b5\uf6b6\uf6b7\uf6b8\uf6b9\uf6ba\uf6bb\uf6bc\uf6bd\uf6be\uf6bf\uf6c0\uf6c1\uf6c2\uf6c3\uf6c4\uf6c5\uf6c6\uf6c7\uf6c8\uf6c9\uf6ca\uf6cb\uf6cc\uf6cd\uf6ce\uf6cf\uf6d0\uf6d1\uf6d2\uf6d3\uf6d4\uf6d5\uf6d6\uf6d7\uf6d8\uf6d9\uf6da\uf6db\uf6dc\uf6dd\uf6de\uf6df\uf6e0\uf6e1\uf6e2\uf6e3\uf6e4\uf6e5\uf6e6\uf6e7\uf6e8\uf6e9\uf6ea\uf6eb\uf6ec\uf6ed\uf6ee\uf6ef\uf6f0\uf6f1\uf6f2\uf6f3\uf6f4\uf6f5\uf6f6\uf6f7\uf6f8\uf6f9\uf6fa\uf6fb\uf6fc\uf6fd\uf6fe\uf6ff\uf700\uf701\uf702\uf703\uf704\uf705\uf706\uf707\uf708\uf709\uf70a\uf70b\uf70c\uf70d\uf70e\uf70f\uf710\uf711\uf712\uf713\uf714\uf715\uf716\uf717\uf718\uf719\uf71a\uf71b\uf71c\uf71d\uf71e\uf71f\uf720\uf721\uf722\uf723\uf724\uf725\uf726\uf727\uf728\uf729\uf72a\uf72b\uf72c\uf72d\uf72e\uf72f\uf730\uf731\uf732\uf733\uf734\uf735\uf736\uf737\uf738\uf739\uf73a\uf73b\uf73c\uf73d\uf73e\uf73f\uf740\uf741\uf742\uf743\uf744\uf745\uf746\uf747\uf748\uf749\uf74a\uf74b\uf74c\uf74d\uf74e\uf74f\uf750\uf751\uf752\uf753\uf754\uf755\uf756\uf757\uf758\uf759\uf75a\uf75b\uf75c\uf75d\uf75e\uf75f\uf760\uf761\uf762\uf763\uf764\uf765\uf766\uf767\uf768\uf769\uf76a\uf76b\uf76c\uf76d\uf76e\uf76f\uf770\uf771\uf772\uf773\uf774\uf775\uf776\uf777\uf778\uf779\uf77a\uf77b\uf77c\uf77d\uf77e\uf77f\uf780\uf781\uf782\uf783\uf784\uf785\uf786\uf787\uf788\uf789\uf78a\uf78b\uf78c\uf78d\uf78e\uf78f\uf790\uf791\uf792\uf793\uf794\uf795\uf796\uf797\uf798\uf799\uf79a\uf79b\uf79c\uf79d\uf79e\uf79f\uf7a0\uf7a1\uf7a2\uf7a3\uf7a4\uf7a5\uf7a6\uf7a7\uf7a8\uf7a9\uf7aa\uf7ab\uf7ac\uf7ad\uf7ae\uf7af\uf7b0\uf7b1\uf7b2\uf7b3\uf7b4\uf7b5\uf7b6\uf7b7\uf7b8\uf7b9\uf7ba\uf7bb\uf7bc\uf7bd\uf7be\uf7bf\uf7c0\uf7c1\uf7c2\uf7c3\uf7c4\uf7c5\uf7c6\uf7c7\uf7c8\uf7c9\uf7ca\uf7cb\uf7cc\uf7cd\uf7ce\uf7cf\uf7d0\uf7d1\uf7d2\uf7d3\uf7d4\uf7d5\uf7d6\uf7d7\uf7d8\uf7d9\uf7da\uf7db\uf7dc\uf7dd\uf7de\uf7df\uf7e0\uf7e1\uf7e2\uf7e3\uf7e4\uf7e5\uf7e6\uf7e7\uf7e8\uf7e9\uf7ea\uf7eb\uf7ec\uf7ed\uf7ee\uf7ef\uf7f0\uf7f1\uf7f2\uf7f3\uf7f4\uf7f5\uf7f6\uf7f7\uf7f8\uf7f9\uf7fa\uf7fb\uf7fc\uf7fd\uf7fe\uf7ff\uf800\uf801\uf802\uf803\uf804\uf805\uf806\uf807\uf808\uf809\uf80a\uf80b\uf80c\uf80d\uf80e\uf80f\uf810\uf811\uf812\uf813\uf814\uf815\uf816\uf817\uf818\uf819\uf81a\uf81b\uf81c\uf81d\uf81e\uf81f\uf820\uf821\uf822\uf823\uf824\uf825\uf826\uf827\uf828\uf829\uf82a\uf82b\uf82c\uf82d\uf82e\uf82f\uf830\uf831\uf832\uf833\uf834\uf835\uf836\uf837\uf838\uf839\uf83a\uf83b\uf83c\uf83d\uf83e\uf83f\uf840\uf841\uf842\uf843\uf844\uf845\uf846\uf847\uf848\uf849\uf84a\uf84b\uf84c\uf84d\uf84e\uf84f\uf850\uf851\uf852\uf853\uf854\uf855\uf856\uf857\uf858\uf859\uf85a\uf85b\uf85c\uf85d\uf85e\uf85f\uf860\uf861\uf862\uf863\uf864\uf865\uf866\uf867\uf868\uf869\uf86a\uf86b\uf86c\uf86d\uf86e\uf86f\uf870\uf871\uf872\uf873\uf874\uf875\uf876\uf877\uf878\uf879\uf87a\uf87b\uf87c\uf87d\uf87e\uf87f\uf880\uf881\uf882\uf883\uf884\uf885\uf886\uf887\uf888\uf889\uf88a\uf88b\uf88c\uf88d\uf88e\uf88f\uf890\uf891\uf892\uf893\uf894\uf895\uf896\uf897\uf898\uf899\uf89a\uf89b\uf89c\uf89d\uf89e\uf89f\uf8a0\uf8a1\uf8a2\uf8a3\uf8a4\uf8a5\uf8a6\uf8a7\uf8a8\uf8a9\uf8aa\uf8ab\uf8ac\uf8ad\uf8ae\uf8af\uf8b0\uf8b1\uf8b2\uf8b3\uf8b4\uf8b5\uf8b6\uf8b7\uf8b8\uf8b9\uf8ba\uf8bb\uf8bc\uf8bd\uf8be\uf8bf\uf8c0\uf8c1\uf8c2\uf8c3\uf8c4\uf8c5\uf8c6\uf8c7\uf8c8\uf8c9\uf8ca\uf8cb\uf8cc\uf8cd\uf8ce\uf8cf\uf8d0\uf8d1\uf8d2\uf8d3\uf8d4\uf8d5\uf8d6\uf8d7\uf8d8\uf8d9\uf8da\uf8db\uf8dc\uf8dd\uf8de\uf8df\uf8e0\uf8e1\uf8e2\uf8e3\uf8e4\uf8e5\uf8e6\uf8e7\uf8e8\uf8e9\uf8ea\uf8eb\uf8ec\uf8ed\uf8ee\uf8ef\uf8f0\uf8f1\uf8f2\uf8f3\uf8f4\uf8f5\uf8f6\uf8f7\uf8f8\uf8f9\uf8fa\uf8fb\uf8fc\uf8fd\uf8fe\uf8ff'
-
-try:
- Cs = eval(r"'\ud800\ud801\ud802\ud803\ud804\ud805\ud806\ud807\ud808\ud809\ud80a\ud80b\ud80c\ud80d\ud80e\ud80f\ud810\ud811\ud812\ud813\ud814\ud815\ud816\ud817\ud818\ud819\ud81a\ud81b\ud81c\ud81d\ud81e\ud81f\ud820\ud821\ud822\ud823\ud824\ud825\ud826\ud827\ud828\ud829\ud82a\ud82b\ud82c\ud82d\ud82e\ud82f\ud830\ud831\ud832\ud833\ud834\ud835\ud836\ud837\ud838\ud839\ud83a\ud83b\ud83c\ud83d\ud83e\ud83f\ud840\ud841\ud842\ud843\ud844\ud845\ud846\ud847\ud848\ud849\ud84a\ud84b\ud84c\ud84d\ud84e\ud84f\ud850\ud851\ud852\ud853\ud854\ud855\ud856\ud857\ud858\ud859\ud85a\ud85b\ud85c\ud85d\ud85e\ud85f\ud860\ud861\ud862\ud863\ud864\ud865\ud866\ud867\ud868\ud869\ud86a\ud86b\ud86c\ud86d\ud86e\ud86f\ud870\ud871\ud872\ud873\ud874\ud875\ud876\ud877\ud878\ud879\ud87a\ud87b\ud87c\ud87d\ud87e\ud87f\ud880\ud881\ud882\ud883\ud884\ud885\ud886\ud887\ud888\ud889\ud88a\ud88b\ud88c\ud88d\ud88e\ud88f\ud890\ud891\ud892\ud893\ud894\ud895\ud896\ud897\ud898\ud899\ud89a\ud89b\ud89c\ud89d\ud89e\ud89f\ud8a0\ud8a1\ud8a2\ud8a3\ud8a4\ud8a5\ud8a6\ud8a7\ud8a8\ud8a9\ud8aa\ud8ab\ud8ac\ud8ad\ud8ae\ud8af\ud8b0\ud8b1\ud8b2\ud8b3\ud8b4\ud8b5\ud8b6\ud8b7\ud8b8\ud8b9\ud8ba\ud8bb\ud8bc\ud8bd\ud8be\ud8bf\ud8c0\ud8c1\ud8c2\ud8c3\ud8c4\ud8c5\ud8c6\ud8c7\ud8c8\ud8c9\ud8ca\ud8cb\ud8cc\ud8cd\ud8ce\ud8cf\ud8d0\ud8d1\ud8d2\ud8d3\ud8d4\ud8d5\ud8d6\ud8d7\ud8d8\ud8d9\ud8da\ud8db\ud8dc\ud8dd\ud8de\ud8df\ud8e0\ud8e1\ud8e2\ud8e3\ud8e4\ud8e5\ud8e6\ud8e7\ud8e8\ud8e9\ud8ea\ud8eb\ud8ec\ud8ed\ud8ee\ud8ef\ud8f0\ud8f1\ud8f2\ud8f3\ud8f4\ud8f5\ud8f6\ud8f7\ud8f8\ud8f9\ud8fa\ud8fb\ud8fc\ud8fd\ud8fe\ud8ff\ud900\ud901\ud902\ud903\ud904\ud905\ud906\ud907\ud908\ud909\ud90a\ud90b\ud90c\ud90d\ud90e\ud90f\ud910\ud911\ud912\ud913\ud914\ud915\ud916\ud917\ud918\ud919\ud91a\ud91b\ud91c\ud91d\ud91e\ud91f\ud920\ud921\ud922\ud923\ud924\ud925\ud926\ud927\ud928\ud929\ud92a\ud92b\ud92c\ud92d\ud92e\ud92f\ud930\ud931\ud932\ud933\ud934\ud935\ud936\ud937\ud938\ud939\ud93a\ud93b\ud93c\ud93d\ud93e\ud93f\ud940\ud941\ud942\ud943\ud944\ud945\ud946\ud947\ud948\ud949\ud94a\ud94b\ud94c\ud94d\ud94e\ud94f\ud950\ud951\ud952\ud953\ud954\ud955\ud956\ud957\ud958\ud959\ud95a\ud95b\ud95c\ud95d\ud95e\ud95f\ud960\ud961\ud962\ud963\ud964\ud965\ud966\ud967\ud968\ud969\ud96a\ud96b\ud96c\ud96d\ud96e\ud96f\ud970\ud971\ud972\ud973\ud974\ud975\ud976\ud977\ud978\ud979\ud97a\ud97b\ud97c\ud97d\ud97e\ud97f\ud980\ud981\ud982\ud983\ud984\ud985\ud986\ud987\ud988\ud989\ud98a\ud98b\ud98c\ud98d\ud98e\ud98f\ud990\ud991\ud992\ud993\ud994\ud995\ud996\ud997\ud998\ud999\ud99a\ud99b\ud99c\ud99d\ud99e\ud99f\ud9a0\ud9a1\ud9a2\ud9a3\ud9a4\ud9a5\ud9a6\ud9a7\ud9a8\ud9a9\ud9aa\ud9ab\ud9ac\ud9ad\ud9ae\ud9af\ud9b0\ud9b1\ud9b2\ud9b3\ud9b4\ud9b5\ud9b6\ud9b7\ud9b8\ud9b9\ud9ba\ud9bb\ud9bc\ud9bd\ud9be\ud9bf\ud9c0\ud9c1\ud9c2\ud9c3\ud9c4\ud9c5\ud9c6\ud9c7\ud9c8\ud9c9\ud9ca\ud9cb\ud9cc\ud9cd\ud9ce\ud9cf\ud9d0\ud9d1\ud9d2\ud9d3\ud9d4\ud9d5\ud9d6\ud9d7\ud9d8\ud9d9\ud9da\ud9db\ud9dc\ud9dd\ud9de\ud9df\ud9e0\ud9e1\ud9e2\ud9e3\ud9e4\ud9e5\ud9e6\ud9e7\ud9e8\ud9e9\ud9ea\ud9eb\ud9ec\ud9ed\ud9ee\ud9ef\ud9f0\ud9f1\ud9f2\ud9f3\ud9f4\ud9f5\ud9f6\ud9f7\ud9f8\ud9f9\ud9fa\ud9fb\ud9fc\ud9fd\ud9fe\ud9ff\uda00\uda01\uda02\uda03\uda04\uda05\uda06\uda07\uda08\uda09\uda0a\uda0b\uda0c\uda0d\uda0e\uda0f\uda10\uda11\uda12\uda13\uda14\uda15\uda16\uda17\uda18\uda19\uda1a\uda1b\uda1c\uda1d\uda1e\uda1f\uda20\uda21\uda22\uda23\uda24\uda25\uda26\uda27\uda28\uda29\uda2a\uda2b\uda2c\uda2d\uda2e\uda2f\uda30\uda31\uda32\uda33\uda34\uda35\uda36\uda37\uda38\uda39\uda3a\uda3b\uda3c\uda3d\uda3e\uda3f\uda40\uda41\uda42\uda43\uda44\uda45\uda46\uda47\uda48\uda49\uda4a\uda4b\uda4c\uda4d\uda4e\uda4f\uda50\uda51\uda52\uda53\uda54\uda55\uda56\uda57\uda58\uda59\uda5a\uda5b\uda5c\uda5d\uda5e\uda5f\uda60\uda61\uda62\uda63\uda64\uda65\uda66\uda67\uda68\uda69\uda6a\uda6b\uda6c\uda6d\uda6e\uda6f\uda70\uda71\uda72\uda73\uda74\uda75\uda76\uda77\uda78\uda79\uda7a\uda7b\uda7c\uda7d\uda7e\uda7f\uda80\uda81\uda82\uda83\uda84\uda85\uda86\uda87\uda88\uda89\uda8a\uda8b\uda8c\uda8d\uda8e\uda8f\uda90\uda91\uda92\uda93\uda94\uda95\uda96\uda97\uda98\uda99\uda9a\uda9b\uda9c\uda9d\uda9e\uda9f\udaa0\udaa1\udaa2\udaa3\udaa4\udaa5\udaa6\udaa7\udaa8\udaa9\udaaa\udaab\udaac\udaad\udaae\udaaf\udab0\udab1\udab2\udab3\udab4\udab5\udab6\udab7\udab8\udab9\udaba\udabb\udabc\udabd\udabe\udabf\udac0\udac1\udac2\udac3\udac4\udac5\udac6\udac7\udac8\udac9\udaca\udacb\udacc\udacd\udace\udacf\udad0\udad1\udad2\udad3\udad4\udad5\udad6\udad7\udad8\udad9\udada\udadb\udadc\udadd\udade\udadf\udae0\udae1\udae2\udae3\udae4\udae5\udae6\udae7\udae8\udae9\udaea\udaeb\udaec\udaed\udaee\udaef\udaf0\udaf1\udaf2\udaf3\udaf4\udaf5\udaf6\udaf7\udaf8\udaf9\udafa\udafb\udafc\udafd\udafe\udaff\udb00\udb01\udb02\udb03\udb04\udb05\udb06\udb07\udb08\udb09\udb0a\udb0b\udb0c\udb0d\udb0e\udb0f\udb10\udb11\udb12\udb13\udb14\udb15\udb16\udb17\udb18\udb19\udb1a\udb1b\udb1c\udb1d\udb1e\udb1f\udb20\udb21\udb22\udb23\udb24\udb25\udb26\udb27\udb28\udb29\udb2a\udb2b\udb2c\udb2d\udb2e\udb2f\udb30\udb31\udb32\udb33\udb34\udb35\udb36\udb37\udb38\udb39\udb3a\udb3b\udb3c\udb3d\udb3e\udb3f\udb40\udb41\udb42\udb43\udb44\udb45\udb46\udb47\udb48\udb49\udb4a\udb4b\udb4c\udb4d\udb4e\udb4f\udb50\udb51\udb52\udb53\udb54\udb55\udb56\udb57\udb58\udb59\udb5a\udb5b\udb5c\udb5d\udb5e\udb5f\udb60\udb61\udb62\udb63\udb64\udb65\udb66\udb67\udb68\udb69\udb6a\udb6b\udb6c\udb6d\udb6e\udb6f\udb70\udb71\udb72\udb73\udb74\udb75\udb76\udb77\udb78\udb79\udb7a\udb7b\udb7c\udb7d\udb7e\udb7f\udb80\udb81\udb82\udb83\udb84\udb85\udb86\udb87\udb88\udb89\udb8a\udb8b\udb8c\udb8d\udb8e\udb8f\udb90\udb91\udb92\udb93\udb94\udb95\udb96\udb97\udb98\udb99\udb9a\udb9b\udb9c\udb9d\udb9e\udb9f\udba0\udba1\udba2\udba3\udba4\udba5\udba6\udba7\udba8\udba9\udbaa\udbab\udbac\udbad\udbae\udbaf\udbb0\udbb1\udbb2\udbb3\udbb4\udbb5\udbb6\udbb7\udbb8\udbb9\udbba\udbbb\udbbc\udbbd\udbbe\udbbf\udbc0\udbc1\udbc2\udbc3\udbc4\udbc5\udbc6\udbc7\udbc8\udbc9\udbca\udbcb\udbcc\udbcd\udbce\udbcf\udbd0\udbd1\udbd2\udbd3\udbd4\udbd5\udbd6\udbd7\udbd8\udbd9\udbda\udbdb\udbdc\udbdd\udbde\udbdf\udbe0\udbe1\udbe2\udbe3\udbe4\udbe5\udbe6\udbe7\udbe8\udbe9\udbea\udbeb\udbec\udbed\udbee\udbef\udbf0\udbf1\udbf2\udbf3\udbf4\udbf5\udbf6\udbf7\udbf8\udbf9\udbfa\udbfb\udbfc\udbfd\udbfe\U0010fc00\udc01\udc02\udc03\udc04\udc05\udc06\udc07\udc08\udc09\udc0a\udc0b\udc0c\udc0d\udc0e\udc0f\udc10\udc11\udc12\udc13\udc14\udc15\udc16\udc17\udc18\udc19\udc1a\udc1b\udc1c\udc1d\udc1e\udc1f\udc20\udc21\udc22\udc23\udc24\udc25\udc26\udc27\udc28\udc29\udc2a\udc2b\udc2c\udc2d\udc2e\udc2f\udc30\udc31\udc32\udc33\udc34\udc35\udc36\udc37\udc38\udc39\udc3a\udc3b\udc3c\udc3d\udc3e\udc3f\udc40\udc41\udc42\udc43\udc44\udc45\udc46\udc47\udc48\udc49\udc4a\udc4b\udc4c\udc4d\udc4e\udc4f\udc50\udc51\udc52\udc53\udc54\udc55\udc56\udc57\udc58\udc59\udc5a\udc5b\udc5c\udc5d\udc5e\udc5f\udc60\udc61\udc62\udc63\udc64\udc65\udc66\udc67\udc68\udc69\udc6a\udc6b\udc6c\udc6d\udc6e\udc6f\udc70\udc71\udc72\udc73\udc74\udc75\udc76\udc77\udc78\udc79\udc7a\udc7b\udc7c\udc7d\udc7e\udc7f\udc80\udc81\udc82\udc83\udc84\udc85\udc86\udc87\udc88\udc89\udc8a\udc8b\udc8c\udc8d\udc8e\udc8f\udc90\udc91\udc92\udc93\udc94\udc95\udc96\udc97\udc98\udc99\udc9a\udc9b\udc9c\udc9d\udc9e\udc9f\udca0\udca1\udca2\udca3\udca4\udca5\udca6\udca7\udca8\udca9\udcaa\udcab\udcac\udcad\udcae\udcaf\udcb0\udcb1\udcb2\udcb3\udcb4\udcb5\udcb6\udcb7\udcb8\udcb9\udcba\udcbb\udcbc\udcbd\udcbe\udcbf\udcc0\udcc1\udcc2\udcc3\udcc4\udcc5\udcc6\udcc7\udcc8\udcc9\udcca\udccb\udccc\udccd\udcce\udccf\udcd0\udcd1\udcd2\udcd3\udcd4\udcd5\udcd6\udcd7\udcd8\udcd9\udcda\udcdb\udcdc\udcdd\udcde\udcdf\udce0\udce1\udce2\udce3\udce4\udce5\udce6\udce7\udce8\udce9\udcea\udceb\udcec\udced\udcee\udcef\udcf0\udcf1\udcf2\udcf3\udcf4\udcf5\udcf6\udcf7\udcf8\udcf9\udcfa\udcfb\udcfc\udcfd\udcfe\udcff\udd00\udd01\udd02\udd03\udd04\udd05\udd06\udd07\udd08\udd09\udd0a\udd0b\udd0c\udd0d\udd0e\udd0f\udd10\udd11\udd12\udd13\udd14\udd15\udd16\udd17\udd18\udd19\udd1a\udd1b\udd1c\udd1d\udd1e\udd1f\udd20\udd21\udd22\udd23\udd24\udd25\udd26\udd27\udd28\udd29\udd2a\udd2b\udd2c\udd2d\udd2e\udd2f\udd30\udd31\udd32\udd33\udd34\udd35\udd36\udd37\udd38\udd39\udd3a\udd3b\udd3c\udd3d\udd3e\udd3f\udd40\udd41\udd42\udd43\udd44\udd45\udd46\udd47\udd48\udd49\udd4a\udd4b\udd4c\udd4d\udd4e\udd4f\udd50\udd51\udd52\udd53\udd54\udd55\udd56\udd57\udd58\udd59\udd5a\udd5b\udd5c\udd5d\udd5e\udd5f\udd60\udd61\udd62\udd63\udd64\udd65\udd66\udd67\udd68\udd69\udd6a\udd6b\udd6c\udd6d\udd6e\udd6f\udd70\udd71\udd72\udd73\udd74\udd75\udd76\udd77\udd78\udd79\udd7a\udd7b\udd7c\udd7d\udd7e\udd7f\udd80\udd81\udd82\udd83\udd84\udd85\udd86\udd87\udd88\udd89\udd8a\udd8b\udd8c\udd8d\udd8e\udd8f\udd90\udd91\udd92\udd93\udd94\udd95\udd96\udd97\udd98\udd99\udd9a\udd9b\udd9c\udd9d\udd9e\udd9f\udda0\udda1\udda2\udda3\udda4\udda5\udda6\udda7\udda8\udda9\uddaa\uddab\uddac\uddad\uddae\uddaf\uddb0\uddb1\uddb2\uddb3\uddb4\uddb5\uddb6\uddb7\uddb8\uddb9\uddba\uddbb\uddbc\uddbd\uddbe\uddbf\uddc0\uddc1\uddc2\uddc3\uddc4\uddc5\uddc6\uddc7\uddc8\uddc9\uddca\uddcb\uddcc\uddcd\uddce\uddcf\uddd0\uddd1\uddd2\uddd3\uddd4\uddd5\uddd6\uddd7\uddd8\uddd9\uddda\udddb\udddc\udddd\uddde\udddf\udde0\udde1\udde2\udde3\udde4\udde5\udde6\udde7\udde8\udde9\uddea\uddeb\uddec\udded\uddee\uddef\uddf0\uddf1\uddf2\uddf3\uddf4\uddf5\uddf6\uddf7\uddf8\uddf9\uddfa\uddfb\uddfc\uddfd\uddfe\uddff\ude00\ude01\ude02\ude03\ude04\ude05\ude06\ude07\ude08\ude09\ude0a\ude0b\ude0c\ude0d\ude0e\ude0f\ude10\ude11\ude12\ude13\ude14\ude15\ude16\ude17\ude18\ude19\ude1a\ude1b\ude1c\ude1d\ude1e\ude1f\ude20\ude21\ude22\ude23\ude24\ude25\ude26\ude27\ude28\ude29\ude2a\ude2b\ude2c\ude2d\ude2e\ude2f\ude30\ude31\ude32\ude33\ude34\ude35\ude36\ude37\ude38\ude39\ude3a\ude3b\ude3c\ude3d\ude3e\ude3f\ude40\ude41\ude42\ude43\ude44\ude45\ude46\ude47\ude48\ude49\ude4a\ude4b\ude4c\ude4d\ude4e\ude4f\ude50\ude51\ude52\ude53\ude54\ude55\ude56\ude57\ude58\ude59\ude5a\ude5b\ude5c\ude5d\ude5e\ude5f\ude60\ude61\ude62\ude63\ude64\ude65\ude66\ude67\ude68\ude69\ude6a\ude6b\ude6c\ude6d\ude6e\ude6f\ude70\ude71\ude72\ude73\ude74\ude75\ude76\ude77\ude78\ude79\ude7a\ude7b\ude7c\ude7d\ude7e\ude7f\ude80\ude81\ude82\ude83\ude84\ude85\ude86\ude87\ude88\ude89\ude8a\ude8b\ude8c\ude8d\ude8e\ude8f\ude90\ude91\ude92\ude93\ude94\ude95\ude96\ude97\ude98\ude99\ude9a\ude9b\ude9c\ude9d\ude9e\ude9f\udea0\udea1\udea2\udea3\udea4\udea5\udea6\udea7\udea8\udea9\udeaa\udeab\udeac\udead\udeae\udeaf\udeb0\udeb1\udeb2\udeb3\udeb4\udeb5\udeb6\udeb7\udeb8\udeb9\udeba\udebb\udebc\udebd\udebe\udebf\udec0\udec1\udec2\udec3\udec4\udec5\udec6\udec7\udec8\udec9\udeca\udecb\udecc\udecd\udece\udecf\uded0\uded1\uded2\uded3\uded4\uded5\uded6\uded7\uded8\uded9\udeda\udedb\udedc\udedd\udede\udedf\udee0\udee1\udee2\udee3\udee4\udee5\udee6\udee7\udee8\udee9\udeea\udeeb\udeec\udeed\udeee\udeef\udef0\udef1\udef2\udef3\udef4\udef5\udef6\udef7\udef8\udef9\udefa\udefb\udefc\udefd\udefe\udeff\udf00\udf01\udf02\udf03\udf04\udf05\udf06\udf07\udf08\udf09\udf0a\udf0b\udf0c\udf0d\udf0e\udf0f\udf10\udf11\udf12\udf13\udf14\udf15\udf16\udf17\udf18\udf19\udf1a\udf1b\udf1c\udf1d\udf1e\udf1f\udf20\udf21\udf22\udf23\udf24\udf25\udf26\udf27\udf28\udf29\udf2a\udf2b\udf2c\udf2d\udf2e\udf2f\udf30\udf31\udf32\udf33\udf34\udf35\udf36\udf37\udf38\udf39\udf3a\udf3b\udf3c\udf3d\udf3e\udf3f\udf40\udf41\udf42\udf43\udf44\udf45\udf46\udf47\udf48\udf49\udf4a\udf4b\udf4c\udf4d\udf4e\udf4f\udf50\udf51\udf52\udf53\udf54\udf55\udf56\udf57\udf58\udf59\udf5a\udf5b\udf5c\udf5d\udf5e\udf5f\udf60\udf61\udf62\udf63\udf64\udf65\udf66\udf67\udf68\udf69\udf6a\udf6b\udf6c\udf6d\udf6e\udf6f\udf70\udf71\udf72\udf73\udf74\udf75\udf76\udf77\udf78\udf79\udf7a\udf7b\udf7c\udf7d\udf7e\udf7f\udf80\udf81\udf82\udf83\udf84\udf85\udf86\udf87\udf88\udf89\udf8a\udf8b\udf8c\udf8d\udf8e\udf8f\udf90\udf91\udf92\udf93\udf94\udf95\udf96\udf97\udf98\udf99\udf9a\udf9b\udf9c\udf9d\udf9e\udf9f\udfa0\udfa1\udfa2\udfa3\udfa4\udfa5\udfa6\udfa7\udfa8\udfa9\udfaa\udfab\udfac\udfad\udfae\udfaf\udfb0\udfb1\udfb2\udfb3\udfb4\udfb5\udfb6\udfb7\udfb8\udfb9\udfba\udfbb\udfbc\udfbd\udfbe\udfbf\udfc0\udfc1\udfc2\udfc3\udfc4\udfc5\udfc6\udfc7\udfc8\udfc9\udfca\udfcb\udfcc\udfcd\udfce\udfcf\udfd0\udfd1\udfd2\udfd3\udfd4\udfd5\udfd6\udfd7\udfd8\udfd9\udfda\udfdb\udfdc\udfdd\udfde\udfdf\udfe0\udfe1\udfe2\udfe3\udfe4\udfe5\udfe6\udfe7\udfe8\udfe9\udfea\udfeb\udfec\udfed\udfee\udfef\udff0\udff1\udff2\udff3\udff4\udff5\udff6\udff7\udff8\udff9\udffa\udffb\udffc\udffd\udffe\udfff'")
-except UnicodeDecodeError:
- Cs = '' # Jython can't handle isolated surrogates
-
-Ll = u'abcdefghijklmnopqrstuvwxyz\xaa\xb5\xba\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0221\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023c\u023f\u0240\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u03f8\u03fb\u03fc\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f7\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1d00\u1d01\u1d02\u1d03\u1d04\u1d05\u1d06\u1d07\u1d08\u1d09\u1d0a\u1d0b\u1d0c\u1d0d\u1d0e\u1d0f\u1d10\u1d11\u1d12\u1d13\u1d14\u1d15\u1d16\u1d17\u1d18\u1d19\u1d1a\u1d1b\u1d1c\u1d1d\u1d1e\u1d1f\u1d20\u1d21\u1d22\u1d23\u1d24\u1d25\u1d26\u1d27\u1d28\u1d29\u1d2a\u1d2b\u1d62\u1d63\u1d64\u1d65\u1d66\u1d67\u1d68\u1d69\u1d6a\u1d6b\u1d6c\u1d6d\u1d6e\u1d6f\u1d70\u1d71\u1d72\u1d73\u1d74\u1d75\u1d76\u1d77\u1d79\u1d7a\u1d7b\u1d7c\u1d7d\u1d7e\u1d7f\u1d80\u1d81\u1d82\u1d83\u1d84\u1d85\u1d86\u1d87\u1d88\u1d89\u1d8a\u1d8b\u1d8c\u1d8d\u1d8e\u1d8f\u1d90\u1d91\u1d92\u1d93\u1d94\u1d95\u1d96\u1d97\u1d98\u1d99\u1d9a\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\u1fc3\u1fc4\u1fc6\u1fc7\u1fd0\u1fd1\u1fd2\u1fd3\u1fd6\u1fd7\u1fe0\u1fe1\u1fe2\u1fe3\u1fe4\u1fe5\u1fe6\u1fe7\u1ff2\u1ff3\u1ff4\u1ff6\u1ff7\u2071\u207f\u210a\u210e\u210f\u2113\u212f\u2134\u2139\u213c\u213d\u2146\u2147\u2148\u2149\u2c30\u2c31\u2c32\u2c33\u2c34\u2c35\u2c36\u2c37\u2c38\u2c39\u2c3a\u2c3b\u2c3c\u2c3d\u2c3e\u2c3f\u2c40\u2c41\u2c42\u2c43\u2c44\u2c45\u2c46\u2c47\u2c48\u2c49\u2c4a\u2c4b\u2c4c\u2c4d\u2c4e\u2c4f\u2c50\u2c51\u2c52\u2c53\u2c54\u2c55\u2c56\u2c57\u2c58\u2c59\u2c5a\u2c5b\u2c5c\u2c5d\u2c5e\u2c81\u2c83\u2c85\u2c87\u2c89\u2c8b\u2c8d\u2c8f\u2c91\u2c93\u2c95\u2c97\u2c99\u2c9b\u2c9d\u2c9f\u2ca1\u2ca3\u2ca5\u2ca7\u2ca9\u2cab\u2cad\u2caf\u2cb1\u2cb3\u2cb5\u2cb7\u2cb9\u2cbb\u2cbd\u2cbf\u2cc1\u2cc3\u2cc5\u2cc7\u2cc9\u2ccb\u2ccd\u2ccf\u2cd1\u2cd3\u2cd5\u2cd7\u2cd9\u2cdb\u2cdd\u2cdf\u2ce1\u2ce3\u2ce4\u2d00\u2d01\u2d02\u2d03\u2d04\u2d05\u2d06\u2d07\u2d08\u2d09\u2d0a\u2d0b\u2d0c\u2d0d\u2d0e\u2d0f\u2d10\u2d11\u2d12\u2d13\u2d14\u2d15\u2d16\u2d17\u2d18\u2d19\u2d1a\u2d1b\u2d1c\u2d1d\u2d1e\u2d1f\u2d20\u2d21\u2d22\u2d23\u2d24\u2d25\ufb00\ufb01\ufb02\ufb03\ufb04\ufb05\ufb06\ufb13\ufb14\ufb15\ufb16\ufb17\uff41\uff42\uff43\uff44\uff45\uff46\uff47\uff48\uff49\uff4a\uff4b\uff4c\uff4d\uff4e\uff4f\uff50\uff51\uff52\uff53\uff54\uff55\uff56\uff57\uff58\uff59\uff5a'
-
-Lm = u'\u02b0\u02b1\u02b2\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c6\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02e0\u02e1\u02e2\u02e3\u02e4\u02ee\u037a\u0559\u0640\u06e5\u06e6\u0e46\u0ec6\u10fc\u17d7\u1843\u1d2c\u1d2d\u1d2e\u1d2f\u1d30\u1d31\u1d32\u1d33\u1d34\u1d35\u1d36\u1d37\u1d38\u1d39\u1d3a\u1d3b\u1d3c\u1d3d\u1d3e\u1d3f\u1d40\u1d41\u1d42\u1d43\u1d44\u1d45\u1d46\u1d47\u1d48\u1d49\u1d4a\u1d4b\u1d4c\u1d4d\u1d4e\u1d4f\u1d50\u1d51\u1d52\u1d53\u1d54\u1d55\u1d56\u1d57\u1d58\u1d59\u1d5a\u1d5b\u1d5c\u1d5d\u1d5e\u1d5f\u1d60\u1d61\u1d78\u1d9b\u1d9c\u1d9d\u1d9e\u1d9f\u1da0\u1da1\u1da2\u1da3\u1da4\u1da5\u1da6\u1da7\u1da8\u1da9\u1daa\u1dab\u1dac\u1dad\u1dae\u1daf\u1db0\u1db1\u1db2\u1db3\u1db4\u1db5\u1db6\u1db7\u1db8\u1db9\u1dba\u1dbb\u1dbc\u1dbd\u1dbe\u1dbf\u2090\u2091\u2092\u2093\u2094\u2d6f\u3005\u3031\u3032\u3033\u3034\u3035\u303b\u309d\u309e\u30fc\u30fd\u30fe\ua015\uff70\uff9e\uff9f'
-
-Lo = u'\u01bb\u01c0\u01c1\u01c2\u01c3\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6\u05e7\u05e8\u05e9\u05ea\u05f0\u05f1\u05f2\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u066e\u066f\u0671\u0672\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d5\u06ee\u06ef\u06fa\u06fb\u06fc\u06ff\u0710\u0712\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u074d\u074e\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07b1\u0904\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093d\u0950\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u097d\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9\u09bd\u09ce\u09dc\u09dd\u09df\u09e0\u09e1\u09f0\u09f1\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a\u0a0f\u0a10\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59\u0a5a\u0a5b\u0a5c\u0a5e\u0a72\u0a73\u0a74\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8f\u0a90\u0a91\u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aaa\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab2\u0ab3\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0f\u0b10\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b32\u0b33\u0b35\u0b36\u0b37\u0b38\u0b39\u0b3d\u0b5c\u0b5d\u0b5f\u0b60\u0b61\u0b71\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a\u0b8e\u0b8f\u0b90\u0b92\u0b93\u0b94\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8\u0ba9\u0baa\u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0e\u0c0f\u0c10\u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26\u0c27\u0c28\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c35\u0c36\u0c37\u0c38\u0c39\u0c60\u0c61\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\u0c8c\u0c8e\u0c8f\u0c90\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0e\u0d0f\u0d10\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d2a\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d60\u0d61\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db3\u0db4\u0db5\u0db6\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbd\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e\u0e2f\u0e30\u0e32\u0e33\u0e40\u0e41\u0e42\u0e43\u0e44\u0e45\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94\u0e95\u0e96\u0e97\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea1\u0ea2\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead\u0eae\u0eaf\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0edc\u0edd\u0f00\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46\u0f47\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f88\u0f89\u0f8a\u0f8b\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1023\u1024\u1025\u1026\u1027\u1029\u102a\u1050\u1051\u1052\u1053\u1054\u1055\u10d0\u10d1\u10d2\u10d3\u10d4\u10d5\u10d6\u10d7\u10d8\u10d9\u10da\u10db\u10dc\u10dd\u10de\u10df\u10e0\u10e1\u10e2\u10e3\u10e4\u10e5\u10e6\u10e7\u10e8\u10e9\u10ea\u10eb\u10ec\u10ed\u10ee\u10ef\u10f0\u10f1\u10f2\u10f3\u10f4\u10f5\u10f6\u10f7\u10f8\u10f9\u10fa\u1100\u1101\u1102\u1103\u1104\u1105\u1106\u1107\u1108\u1109\u110a\u110b\u110c\u110d\u110e\u110f\u1110\u1111\u1112\u1113\u1114\u1115\u1116\u1117\u1118\u1119\u111a\u111b\u111c\u111d\u111e\u111f\u1120\u1121\u1122\u1123\u1124\u1125\u1126\u1127\u1128\u1129\u112a\u112b\u112c\u112d\u112e\u112f\u1130\u1131\u1132\u1133\u1134\u1135\u1136\u1137\u1138\u1139\u113a\u113b\u113c\u113d\u113e\u113f\u1140\u1141\u1142\u1143\u1144\u1145\u1146\u1147\u1148\u1149\u114a\u114b\u114c\u114d\u114e\u114f\u1150\u1151\u1152\u1153\u1154\u1155\u1156\u1157\u1158\u1159\u115f\u1160\u1161\u1162\u1163\u1164\u1165\u1166\u1167\u1168\u1169\u116a\u116b\u116c\u116d\u116e\u116f\u1170\u1171\u1172\u1173\u1174\u1175\u1176\u1177\u1178\u1179\u117a\u117b\u117c\u117d\u117e\u117f\u1180\u1181\u1182\u1183\u1184\u1185\u1186\u1187\u1188\u1189\u118a\u118b\u118c\u118d\u118e\u118f\u1190\u1191\u1192\u1193\u1194\u1195\u1196\u1197\u1198\u1199\u119a\u119b\u119c\u119d\u119e\u119f\u11a0\u11a1\u11a2\u11a8\u11a9\u11aa\u11ab\u11ac\u11ad\u11ae\u11af\u11b0\u11b1\u11b2\u11b3\u11b4\u11b5\u11b6\u11b7\u11b8\u11b9\u11ba\u11bb\u11bc\u11bd\u11be\u11bf\u11c0\u11c1\u11c2\u11c3\u11c4\u11c5\u11c6\u11c7\u11c8\u11c9\u11ca\u11cb\u11cc\u11cd\u11ce\u11cf\u11d0\u11d1\u11d2\u11d3\u11d4\u11d5\u11d6\u11d7\u11d8\u11d9\u11da\u11db\u11dc\u11dd\u11de\u11df\u11e0\u11e1\u11e2\u11e3\u11e4\u11e5\u11e6\u11e7\u11e8\u11e9\u11ea\u11eb\u11ec\u11ed\u11ee\u11ef\u11f0\u11f1\u11f2\u11f3\u11f4\u11f5\u11f6\u11f7\u11f8\u11f9\u1200\u1201\u1202\u1203\u1204\u1205\u1206\u1207\u1208\u1209\u120a\u120b\u120c\u120d\u120e\u120f\u1210\u1211\u1212\u1213\u1214\u1215\u1216\u1217\u1218\u1219\u121a\u121b\u121c\u121d\u121e\u121f\u1220\u1221\u1222\u1223\u1224\u1225\u1226\u1227\u1228\u1229\u122a\u122b\u122c\u122d\u122e\u122f\u1230\u1231\u1232\u1233\u1234\u1235\u1236\u1237\u1238\u1239\u123a\u123b\u123c\u123d\u123e\u123f\u1240\u1241\u1242\u1243\u1244\u1245\u1246\u1247\u1248\u124a\u124b\u124c\u124d\u1250\u1251\u1252\u1253\u1254\u1255\u1256\u1258\u125a\u125b\u125c\u125d\u1260\u1261\u1262\u1263\u1264\u1265\u1266\u1267\u1268\u1269\u126a\u126b\u126c\u126d\u126e\u126f\u1270\u1271\u1272\u1273\u1274\u1275\u1276\u1277\u1278\u1279\u127a\u127b\u127c\u127d\u127e\u127f\u1280\u1281\u1282\u1283\u1284\u1285\u1286\u1287\u1288\u128a\u128b\u128c\u128d\u1290\u1291\u1292\u1293\u1294\u1295\u1296\u1297\u1298\u1299\u129a\u129b\u129c\u129d\u129e\u129f\u12a0\u12a1\u12a2\u12a3\u12a4\u12a5\u12a6\u12a7\u12a8\u12a9\u12aa\u12ab\u12ac\u12ad\u12ae\u12af\u12b0\u12b2\u12b3\u12b4\u12b5\u12b8\u12b9\u12ba\u12bb\u12bc\u12bd\u12be\u12c0\u12c2\u12c3\u12c4\u12c5\u12c8\u12c9\u12ca\u12cb\u12cc\u12cd\u12ce\u12cf\u12d0\u12d1\u12d2\u12d3\u12d4\u12d5\u12d6\u12d8\u12d9\u12da\u12db\u12dc\u12dd\u12de\u12df\u12e0\u12e1\u12e2\u12e3\u12e4\u12e5\u12e6\u12e7\u12e8\u12e9\u12ea\u12eb\u12ec\u12ed\u12ee\u12ef\u12f0\u12f1\u12f2\u12f3\u12f4\u12f5\u12f6\u12f7\u12f8\u12f9\u12fa\u12fb\u12fc\u12fd\u12fe\u12ff\u1300\u1301\u1302\u1303\u1304\u1305\u1306\u1307\u1308\u1309\u130a\u130b\u130c\u130d\u130e\u130f\u1310\u1312\u1313\u1314\u1315\u1318\u1319\u131a\u131b\u131c\u131d\u131e\u131f\u1320\u1321\u1322\u1323\u1324\u1325\u1326\u1327\u1328\u1329\u132a\u132b\u132c\u132d\u132e\u132f\u1330\u1331\u1332\u1333\u1334\u1335\u1336\u1337\u1338\u1339\u133a\u133b\u133c\u133d\u133e\u133f\u1340\u1341\u1342\u1343\u1344\u1345\u1346\u1347\u1348\u1349\u134a\u134b\u134c\u134d\u134e\u134f\u1350\u1351\u1352\u1353\u1354\u1355\u1356\u1357\u1358\u1359\u135a\u1380\u1381\u1382\u1383\u1384\u1385\u1386\u1387\u1388\u1389\u138a\u138b\u138c\u138d\u138e\u138f\u13a0\u13a1\u13a2\u13a3\u13a4\u13a5\u13a6\u13a7\u13a8\u13a9\u13aa\u13ab\u13ac\u13ad\u13ae\u13af\u13b0\u13b1\u13b2\u13b3\u13b4\u13b5\u13b6\u13b7\u13b8\u13b9\u13ba\u13bb\u13bc\u13bd\u13be\u13bf\u13c0\u13c1\u13c2\u13c3\u13c4\u13c5\u13c6\u13c7\u13c8\u13c9\u13ca\u13cb\u13cc\u13cd\u13ce\u13cf\u13d0\u13d1\u13d2\u13d3\u13d4\u13d5\u13d6\u13d7\u13d8\u13d9\u13da\u13db\u13dc\u13dd\u13de\u13df\u13e0\u13e1\u13e2\u13e3\u13e4\u13e5\u13e6\u13e7\u13e8\u13e9\u13ea\u13eb\u13ec\u13ed\u13ee\u13ef\u13f0\u13f1\u13f2\u13f3\u13f4\u1401\u1402\u1403\u1404\u1405\u1406\u1407\u1408\u1409\u140a\u140b\u140c\u140d\u140e\u140f\u1410\u1411\u1412\u1413\u1414\u1415\u1416\u1417\u1418\u1419\u141a\u141b\u141c\u141d\u141e\u141f\u1420\u1421\u1422\u1423\u1424\u1425\u1426\u1427\u1428\u1429\u142a\u142b\u142c\u142d\u142e\u142f\u1430\u1431\u1432\u1433\u1434\u1435\u1436\u1437\u1438\u1439\u143a\u143b\u143c\u143d\u143e\u143f\u1440\u1441\u1442\u1443\u1444\u1445\u1446\u1447\u1448\u1449\u144a\u144b\u144c\u144d\u144e\u144f\u1450\u1451\u1452\u1453\u1454\u1455\u1456\u1457\u1458\u1459\u145a\u145b\u145c\u145d\u145e\u145f\u1460\u1461\u1462\u1463\u1464\u1465\u1466\u1467\u1468\u1469\u146a\u146b\u146c\u146d\u146e\u146f\u1470\u1471\u1472\u1473\u1474\u1475\u1476\u1477\u1478\u1479\u147a\u147b\u147c\u147d\u147e\u147f\u1480\u1481\u1482\u1483\u1484\u1485\u1486\u1487\u1488\u1489\u148a\u148b\u148c\u148d\u148e\u148f\u1490\u1491\u1492\u1493\u1494\u1495\u1496\u1497\u1498\u1499\u149a\u149b\u149c\u149d\u149e\u149f\u14a0\u14a1\u14a2\u14a3\u14a4\u14a5\u14a6\u14a7\u14a8\u14a9\u14aa\u14ab\u14ac\u14ad\u14ae\u14af\u14b0\u14b1\u14b2\u14b3\u14b4\u14b5\u14b6\u14b7\u14b8\u14b9\u14ba\u14bb\u14bc\u14bd\u14be\u14bf\u14c0\u14c1\u14c2\u14c3\u14c4\u14c5\u14c6\u14c7\u14c8\u14c9\u14ca\u14cb\u14cc\u14cd\u14ce\u14cf\u14d0\u14d1\u14d2\u14d3\u14d4\u14d5\u14d6\u14d7\u14d8\u14d9\u14da\u14db\u14dc\u14dd\u14de\u14df\u14e0\u14e1\u14e2\u14e3\u14e4\u14e5\u14e6\u14e7\u14e8\u14e9\u14ea\u14eb\u14ec\u14ed\u14ee\u14ef\u14f0\u14f1\u14f2\u14f3\u14f4\u14f5\u14f6\u14f7\u14f8\u14f9\u14fa\u14fb\u14fc\u14fd\u14fe\u14ff\u1500\u1501\u1502\u1503\u1504\u1505\u1506\u1507\u1508\u1509\u150a\u150b\u150c\u150d\u150e\u150f\u1510\u1511\u1512\u1513\u1514\u1515\u1516\u1517\u1518\u1519\u151a\u151b\u151c\u151d\u151e\u151f\u1520\u1521\u1522\u1523\u1524\u1525\u1526\u1527\u1528\u1529\u152a\u152b\u152c\u152d\u152e\u152f\u1530\u1531\u1532\u1533\u1534\u1535\u1536\u1537\u1538\u1539\u153a\u153b\u153c\u153d\u153e\u153f\u1540\u1541\u1542\u1543\u1544\u1545\u1546\u1547\u1548\u1549\u154a\u154b\u154c\u154d\u154e\u154f\u1550\u1551\u1552\u1553\u1554\u1555\u1556\u1557\u1558\u1559\u155a\u155b\u155c\u155d\u155e\u155f\u1560\u1561\u1562\u1563\u1564\u1565\u1566\u1567\u1568\u1569\u156a\u156b\u156c\u156d\u156e\u156f\u1570\u1571\u1572\u1573\u1574\u1575\u1576\u1577\u1578\u1579\u157a\u157b\u157c\u157d\u157e\u157f\u1580\u1581\u1582\u1583\u1584\u1585\u1586\u1587\u1588\u1589\u158a\u158b\u158c\u158d\u158e\u158f\u1590\u1591\u1592\u1593\u1594\u1595\u1596\u1597\u1598\u1599\u159a\u159b\u159c\u159d\u159e\u159f\u15a0\u15a1\u15a2\u15a3\u15a4\u15a5\u15a6\u15a7\u15a8\u15a9\u15aa\u15ab\u15ac\u15ad\u15ae\u15af\u15b0\u15b1\u15b2\u15b3\u15b4\u15b5\u15b6\u15b7\u15b8\u15b9\u15ba\u15bb\u15bc\u15bd\u15be\u15bf\u15c0\u15c1\u15c2\u15c3\u15c4\u15c5\u15c6\u15c7\u15c8\u15c9\u15ca\u15cb\u15cc\u15cd\u15ce\u15cf\u15d0\u15d1\u15d2\u15d3\u15d4\u15d5\u15d6\u15d7\u15d8\u15d9\u15da\u15db\u15dc\u15dd\u15de\u15df\u15e0\u15e1\u15e2\u15e3\u15e4\u15e5\u15e6\u15e7\u15e8\u15e9\u15ea\u15eb\u15ec\u15ed\u15ee\u15ef\u15f0\u15f1\u15f2\u15f3\u15f4\u15f5\u15f6\u15f7\u15f8\u15f9\u15fa\u15fb\u15fc\u15fd\u15fe\u15ff\u1600\u1601\u1602\u1603\u1604\u1605\u1606\u1607\u1608\u1609\u160a\u160b\u160c\u160d\u160e\u160f\u1610\u1611\u1612\u1613\u1614\u1615\u1616\u1617\u1618\u1619\u161a\u161b\u161c\u161d\u161e\u161f\u1620\u1621\u1622\u1623\u1624\u1625\u1626\u1627\u1628\u1629\u162a\u162b\u162c\u162d\u162e\u162f\u1630\u1631\u1632\u1633\u1634\u1635\u1636\u1637\u1638\u1639\u163a\u163b\u163c\u163d\u163e\u163f\u1640\u1641\u1642\u1643\u1644\u1645\u1646\u1647\u1648\u1649\u164a\u164b\u164c\u164d\u164e\u164f\u1650\u1651\u1652\u1653\u1654\u1655\u1656\u1657\u1658\u1659\u165a\u165b\u165c\u165d\u165e\u165f\u1660\u1661\u1662\u1663\u1664\u1665\u1666\u1667\u1668\u1669\u166a\u166b\u166c\u166f\u1670\u1671\u1672\u1673\u1674\u1675\u1676\u1681\u1682\u1683\u1684\u1685\u1686\u1687\u1688\u1689\u168a\u168b\u168c\u168d\u168e\u168f\u1690\u1691\u1692\u1693\u1694\u1695\u1696\u1697\u1698\u1699\u169a\u16a0\u16a1\u16a2\u16a3\u16a4\u16a5\u16a6\u16a7\u16a8\u16a9\u16aa\u16ab\u16ac\u16ad\u16ae\u16af\u16b0\u16b1\u16b2\u16b3\u16b4\u16b5\u16b6\u16b7\u16b8\u16b9\u16ba\u16bb\u16bc\u16bd\u16be\u16bf\u16c0\u16c1\u16c2\u16c3\u16c4\u16c5\u16c6\u16c7\u16c8\u16c9\u16ca\u16cb\u16cc\u16cd\u16ce\u16cf\u16d0\u16d1\u16d2\u16d3\u16d4\u16d5\u16d6\u16d7\u16d8\u16d9\u16da\u16db\u16dc\u16dd\u16de\u16df\u16e0\u16e1\u16e2\u16e3\u16e4\u16e5\u16e6\u16e7\u16e8\u16e9\u16ea\u1700\u1701\u1702\u1703\u1704\u1705\u1706\u1707\u1708\u1709\u170a\u170b\u170c\u170e\u170f\u1710\u1711\u1720\u1721\u1722\u1723\u1724\u1725\u1726\u1727\u1728\u1729\u172a\u172b\u172c\u172d\u172e\u172f\u1730\u1731\u1740\u1741\u1742\u1743\u1744\u1745\u1746\u1747\u1748\u1749\u174a\u174b\u174c\u174d\u174e\u174f\u1750\u1751\u1760\u1761\u1762\u1763\u1764\u1765\u1766\u1767\u1768\u1769\u176a\u176b\u176c\u176e\u176f\u1770\u1780\u1781\u1782\u1783\u1784\u1785\u1786\u1787\u1788\u1789\u178a\u178b\u178c\u178d\u178e\u178f\u1790\u1791\u1792\u1793\u1794\u1795\u1796\u1797\u1798\u1799\u179a\u179b\u179c\u179d\u179e\u179f\u17a0\u17a1\u17a2\u17a3\u17a4\u17a5\u17a6\u17a7\u17a8\u17a9\u17aa\u17ab\u17ac\u17ad\u17ae\u17af\u17b0\u17b1\u17b2\u17b3\u17dc\u1820\u1821\u1822\u1823\u1824\u1825\u1826\u1827\u1828\u1829\u182a\u182b\u182c\u182d\u182e\u182f\u1830\u1831\u1832\u1833\u1834\u1835\u1836\u1837\u1838\u1839\u183a\u183b\u183c\u183d\u183e\u183f\u1840\u1841\u1842\u1844\u1845\u1846\u1847\u1848\u1849\u184a\u184b\u184c\u184d\u184e\u184f\u1850\u1851\u1852\u1853\u1854\u1855\u1856\u1857\u1858\u1859\u185a\u185b\u185c\u185d\u185e\u185f\u1860\u1861\u1862\u1863\u1864\u1865\u1866\u1867\u1868\u1869\u186a\u186b\u186c\u186d\u186e\u186f\u1870\u1871\u1872\u1873\u1874\u1875\u1876\u1877\u1880\u1881\u1882\u1883\u1884\u1885\u1886\u1887\u1888\u1889\u188a\u188b\u188c\u188d\u188e\u188f\u1890\u1891\u1892\u1893\u1894\u1895\u1896\u1897\u1898\u1899\u189a\u189b\u189c\u189d\u189e\u189f\u18a0\u18a1\u18a2\u18a3\u18a4\u18a5\u18a6\u18a7\u18a8\u1900\u1901\u1902\u1903\u1904\u1905\u1906\u1907\u1908\u1909\u190a\u190b\u190c\u190d\u190e\u190f\u1910\u1911\u1912\u1913\u1914\u1915\u1916\u1917\u1918\u1919\u191a\u191b\u191c\u1950\u1951\u1952\u1953\u1954\u1955\u1956\u1957\u1958\u1959\u195a\u195b\u195c\u195d\u195e\u195f\u1960\u1961\u1962\u1963\u1964\u1965\u1966\u1967\u1968\u1969\u196a\u196b\u196c\u196d\u1970\u1971\u1972\u1973\u1974\u1980\u1981\u1982\u1983\u1984\u1985\u1986\u1987\u1988\u1989\u198a\u198b\u198c\u198d\u198e\u198f\u1990\u1991\u1992\u1993\u1994\u1995\u1996\u1997\u1998\u1999\u199a\u199b\u199c\u199d\u199e\u199f\u19a0\u19a1\u19a2\u19a3\u19a4\u19a5\u19a6\u19a7\u19a8\u19a9\u19c1\u19c2\u19c3\u19c4\u19c5\u19c6\u19c7\u1a00\u1a01\u1a02\u1a03\u1a04\u1a05\u1a06\u1a07\u1a08\u1a09\u1a0a\u1a0b\u1a0c\u1a0d\u1a0e\u1a0f\u1a10\u1a11\u1a12\u1a13\u1a14\u1a15\u1a16\u2135\u2136\u2137\u2138\u2d30\u2d31\u2d32\u2d33\u2d34\u2d35\u2d36\u2d37\u2d38\u2d39\u2d3a\u2d3b\u2d3c\u2d3d\u2d3e\u2d3f\u2d40\u2d41\u2d42\u2d43\u2d44\u2d45\u2d46\u2d47\u2d48\u2d49\u2d4a\u2d4b\u2d4c\u2d4d\u2d4e\u2d4f\u2d50\u2d51\u2d52\u2d53\u2d54\u2d55\u2d56\u2d57\u2d58\u2d59\u2d5a\u2d5b\u2d5c\u2d5d\u2d5e\u2d5f\u2d60\u2d61\u2d62\u2d63\u2d64\u2d65\u2d80\u2d81\u2d82\u2d83\u2d84\u2d85\u2d86\u2d87\u2d88\u2d89\u2d8a\u2d8b\u2d8c\u2d8d\u2d8e\u2d8f\u2d90\u2d91\u2d92\u2d93\u2d94\u2d95\u2d96\u2da0\u2da1\u2da2\u2da3\u2da4\u2da5\u2da6\u2da8\u2da9\u2daa\u2dab\u2dac\u2dad\u2dae\u2db0\u2db1\u2db2\u2db3\u2db4\u2db5\u2db6\u2db8\u2db9\u2dba\u2dbb\u2dbc\u2dbd\u2dbe\u2dc0\u2dc1\u2dc2\u2dc3\u2dc4\u2dc5\u2dc6\u2dc8\u2dc9\u2dca\u2dcb\u2dcc\u2dcd\u2dce\u2dd0\u2dd1\u2dd2\u2dd3\u2dd4\u2dd5\u2dd6\u2dd8\u2dd9\u2dda\u2ddb\u2ddc\u2ddd\u2dde\u3006\u303c\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304a\u304b\u304c\u304d\u304e\u304f\u3050\u3051\u3052\u3053\u3054\u3055\u3056\u3057\u3058\u3059\u305a\u305b\u305c\u305d\u305e\u305f\u3060\u3061\u3062\u3063\u3064\u3065\u3066\u3067\u3068\u3069\u306a\u306b\u306c\u306d\u306e\u306f\u3070\u3071\u3072\u3073\u3074\u3075\u3076\u3077\u3078\u3079\u307a\u307b\u307c\u307d\u307e\u307f\u3080\u3081\u3082\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308a\u308b\u308c\u308d\u308e\u308f\u3090\u3091\u3092\u3093\u3094\u3095\u3096\u309f\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8\u30a9\u30aa\u30ab\u30ac\u30ad\u30ae\u30af\u30b0\u30b1\u30b2\u30b3\u30b4\u30b5\u30b6\u30b7\u30b8\u30b9\u30ba\u30bb\u30bc\u30bd\u30be\u30bf\u30c0\u30c1\u30c2\u30c3\u30c4\u30c5\u30c6\u30c7\u30c8\u30c9\u30ca\u30cb\u30cc\u30cd\u30ce\u30cf\u30d0\u30d1\u30d2\u30d3\u30d4\u30d5\u30d6\u30d7\u30d8\u30d9\u30da\u30db\u30dc\u30dd\u30de\u30df\u30e0\u30e1\u30e2\u30e3\u30e4\u30e5\u30e6\u30e7\u30e8\u30e9\u30ea\u30eb\u30ec\u30ed\u30ee\u30ef\u30f0\u30f1\u30f2\u30f3\u30f4\u30f5\u30f6\u30f7\u30f8\u30f9\u30fa\u30ff\u3105\u3106\u3107\u3108\u3109\u310a\u310b\u310c\u310d\u310e\u310f\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311a\u311b\u311c\u311d\u311e\u311f\u3120\u3121\u3122\u3123\u3124\u3125\u3126\u3127\u3128\u3129\u312a\u312b\u312c\u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3138\u3139\u313a\u313b\u313c\u313d\u313e\u313f\u3140\u3141\u3142\u3143\u3144\u3145\u3146\u3147\u3148\u3149\u314a\u314b\u314c\u314d\u314e\u314f\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315a\u315b\u315c\u315d\u315e\u315f\u3160\u3161\u3162\u3163\u3164\u3165\u3166\u3167\u3168\u3169\u316a\u316b\u316c\u316d\u316e\u316f\u3170\u3171\u3172\u3173\u3174\u3175\u3176\u3177\u3178\u3179\u317a\u317b\u317c\u317d\u317e\u317f\u3180\u3181\u3182\u3183\u3184\u3185\u3186\u3187\u3188\u3189\u318a\u318b\u318c\u318d\u318e\u31a0\u31a1\u31a2\u31a3\u31a4\u31a5\u31a6\u31a7\u31a8\u31a9\u31aa\u31ab\u31ac\u31ad\u31ae\u31af\u31b0\u31b1\u31b2\u31b3\u31b4\u31b5\u31b6\u31b7\u31f0\u31f1\u31f2\u31f3\u31f4\u31f5\u31f6\u31f7\u31f8\u31f9\u31fa\u31fb\u31fc\u31fd\u31fe\u31ff\u3400\u3401\u3402\u3403\u3404\u3405\u3406\u3407\u3408\u3409\u340a\u340b\u340c\u340d\u340e\u340f\u3410\u3411\u3412\u3413\u3414\u3415\u3416\u3417\u3418\u3419\u341a\u341b\u341c\u341d\u341e\u341f\u3420\u3421\u3422\u3423\u3424\u3425\u3426\u3427\u3428\u3429\u342a\u342b\u342c\u342d\u342e\u342f\u3430\u3431\u3432\u3433\u3434\u3435\u3436\u3437\u3438\u3439\u343a\u343b\u343c\u343d\u343e\u343f\u3440\u3441\u3442\u3443\u3444\u3445\u3446\u3447\u3448\u3449\u344a\u344b\u344c\u344d\u344e\u344f\u3450\u3451\u3452\u3453\u3454\u3455\u3456\u3457\u3458\u3459\u345a\u345b\u345c\u345d\u345e\u345f\u3460\u3461\u3462\u3463\u3464\u3465\u3466\u3467\u3468\u3469\u346a\u346b\u346c\u346d\u346e\u346f\u3470\u3471\u3472\u3473\u3474\u3475\u3476\u3477\u3478\u3479\u347a\u347b\u347c\u347d\u347e\u347f\u3480\u3481\u3482\u3483\u3484\u3485\u3486\u3487\u3488\u3489\u348a\u348b\u348c\u348d\u348e\u348f\u3490\u3491\u3492\u3493\u3494\u3495\u3496\u3497\u3498\u3499\u349a\u349b\u349c\u349d\u349e\u349f\u34a0\u34a1\u34a2\u34a3\u34a4\u34a5\u34a6\u34a7\u34a8\u34a9\u34aa\u34ab\u34ac\u34ad\u34ae\u34af\u34b0\u34b1\u34b2\u34b3\u34b4\u34b5\u34b6\u34b7\u34b8\u34b9\u34ba\u34bb\u34bc\u34bd\u34be\u34bf\u34c0\u34c1\u34c2\u34c3\u34c4\u34c5\u34c6\u34c7\u34c8\u34c9\u34ca\u34cb\u34cc\u34cd\u34ce\u34cf\u34d0\u34d1\u34d2\u34d3\u34d4\u34d5\u34d6\u34d7\u34d8\u34d9\u34da\u34db\u34dc\u34dd\u34de\u34df\u34e0\u34e1\u34e2\u34e3\u34e4\u34e5\u34e6\u34e7\u34e8\u34e9\u34ea\u34eb\u34ec\u34ed\u34ee\u34ef\u34f0\u34f1\u34f2\u34f3\u34f4\u34f5\u34f6\u34f7\u34f8\u34f9\u34fa\u34fb\u34fc\u34fd\u34fe\u34ff\u3500\u3501\u3502\u3503\u3504\u3505\u3506\u3507\u3508\u3509\u350a\u350b\u350c\u350d\u350e\u350f\u3510\u3511\u3512\u3513\u3514\u3515\u3516\u3517\u3518\u3519\u351a\u351b\u351c\u351d\u351e\u351f\u3520\u3521\u3522\u3523\u3524\u3525\u3526\u3527\u3528\u3529\u352a\u352b\u352c\u352d\u352e\u352f\u3530\u3531\u3532\u3533\u3534\u3535\u3536\u3537\u3538\u3539\u353a\u353b\u353c\u353d\u353e\u353f\u3540\u3541\u3542\u3543\u3544\u3545\u3546\u3547\u3548\u3549\u354a\u354b\u354c\u354d\u354e\u354f\u3550\u3551\u3552\u3553\u3554\u3555\u3556\u3557\u3558\u3559\u355a\u355b\u355c\u355d\u355e\u355f\u3560\u3561\u3562\u3563\u3564\u3565\u3566\u3567\u3568\u3569\u356a\u356b\u356c\u356d\u356e\u356f\u3570\u3571\u3572\u3573\u3574\u3575\u3576\u3577\u3578\u3579\u357a\u357b\u357c\u357d\u357e\u357f\u3580\u3581\u3582\u3583\u3584\u3585\u3586\u3587\u3588\u3589\u358a\u358b\u358c\u358d\u358e\u358f\u3590\u3591\u3592\u3593\u3594\u3595\u3596\u3597\u3598\u3599\u359a\u359b\u359c\u359d\u359e\u359f\u35a0\u35a1\u35a2\u35a3\u35a4\u35a5\u35a6\u35a7\u35a8\u35a9\u35aa\u35ab\u35ac\u35ad\u35ae\u35af\u35b0\u35b1\u35b2\u35b3\u35b4\u35b5\u35b6\u35b7\u35b8\u35b9\u35ba\u35bb\u35bc\u35bd\u35be\u35bf\u35c0\u35c1\u35c2\u35c3\u35c4\u35c5\u35c6\u35c7\u35c8\u35c9\u35ca\u35cb\u35cc\u35cd\u35ce\u35cf\u35d0\u35d1\u35d2\u35d3\u35d4\u35d5\u35d6\u35d7\u35d8\u35d9\u35da\u35db\u35dc\u35dd\u35de\u35df\u35e0\u35e1\u35e2\u35e3\u35e4\u35e5\u35e6\u35e7\u35e8\u35e9\u35ea\u35eb\u35ec\u35ed\u35ee\u35ef\u35f0\u35f1\u35f2\u35f3\u35f4\u35f5\u35f6\u35f7\u35f8\u35f9\u35fa\u35fb\u35fc\u35fd\u35fe\u35ff\u3600\u3601\u3602\u3603\u3604\u3605\u3606\u3607\u3608\u3609\u360a\u360b\u360c\u360d\u360e\u360f\u3610\u3611\u3612\u3613\u3614\u3615\u3616\u3617\u3618\u3619\u361a\u361b\u361c\u361d\u361e\u361f\u3620\u3621\u3622\u3623\u3624\u3625\u3626\u3627\u3628\u3629\u362a\u362b\u362c\u362d\u362e\u362f\u3630\u3631\u3632\u3633\u3634\u3635\u3636\u3637\u3638\u3639\u363a\u363b\u363c\u363d\u363e\u363f\u3640\u3641\u3642\u3643\u3644\u3645\u3646\u3647\u3648\u3649\u364a\u364b\u364c\u364d\u364e\u364f\u3650\u3651\u3652\u3653\u3654\u3655\u3656\u3657\u3658\u3659\u365a\u365b\u365c\u365d\u365e\u365f\u3660\u3661\u3662\u3663\u3664\u3665\u3666\u3667\u3668\u3669\u366a\u366b\u366c\u366d\u366e\u366f\u3670\u3671\u3672\u3673\u3674\u3675\u3676\u3677\u3678\u3679\u367a\u367b\u367c\u367d\u367e\u367f\u3680\u3681\u3682\u3683\u3684\u3685\u3686\u3687\u3688\u3689\u368a\u368b\u368c\u368d\u368e\u368f\u3690\u3691\u3692\u3693\u3694\u3695\u3696\u3697\u3698\u3699\u369a\u369b\u369c\u369d\u369e\u369f\u36a0\u36a1\u36a2\u36a3\u36a4\u36a5\u36a6\u36a7\u36a8\u36a9\u36aa\u36ab\u36ac\u36ad\u36ae\u36af\u36b0\u36b1\u36b2\u36b3\u36b4\u36b5\u36b6\u36b7\u36b8\u36b9\u36ba\u36bb\u36bc\u36bd\u36be\u36bf\u36c0\u36c1\u36c2\u36c3\u36c4\u36c5\u36c6\u36c7\u36c8\u36c9\u36ca\u36cb\u36cc\u36cd\u36ce\u36cf\u36d0\u36d1\u36d2\u36d3\u36d4\u36d5\u36d6\u36d7\u36d8\u36d9\u36da\u36db\u36dc\u36dd\u36de\u36df\u36e0\u36e1\u36e2\u36e3\u36e4\u36e5\u36e6\u36e7\u36e8\u36e9\u36ea\u36eb\u36ec\u36ed\u36ee\u36ef\u36f0\u36f1\u36f2\u36f3\u36f4\u36f5\u36f6\u36f7\u36f8\u36f9\u36fa\u36fb\u36fc\u36fd\u36fe\u36ff\u3700\u3701\u3702\u3703\u3704\u3705\u3706\u3707\u3708\u3709\u370a\u370b\u370c\u370d\u370e\u370f\u3710\u3711\u3712\u3713\u3714\u3715\u3716\u3717\u3718\u3719\u371a\u371b\u371c\u371d\u371e\u371f\u3720\u3721\u3722\u3723\u3724\u3725\u3726\u3727\u3728\u3729\u372a\u372b\u372c\u372d\u372e\u372f\u3730\u3731\u3732\u3733\u3734\u3735\u3736\u3737\u3738\u3739\u373a\u373b\u373c\u373d\u373e\u373f\u3740\u3741\u3742\u3743\u3744\u3745\u3746\u3747\u3748\u3749\u374a\u374b\u374c\u374d\u374e\u374f\u3750\u3751\u3752\u3753\u3754\u3755\u3756\u3757\u3758\u3759\u375a\u375b\u375c\u375d\u375e\u375f\u3760\u3761\u3762\u3763\u3764\u3765\u3766\u3767\u3768\u3769\u376a\u376b\u376c\u376d\u376e\u376f\u3770\u3771\u3772\u3773\u3774\u3775\u3776\u3777\u3778\u3779\u377a\u377b\u377c\u377d\u377e\u377f\u3780\u3781\u3782\u3783\u3784\u3785\u3786\u3787\u3788\u3789\u378a\u378b\u378c\u378d\u378e\u378f\u3790\u3791\u3792\u3793\u3794\u3795\u3796\u3797\u3798\u3799\u379a\u379b\u379c\u379d\u379e\u379f\u37a0\u37a1\u37a2\u37a3\u37a4\u37a5\u37a6\u37a7\u37a8\u37a9\u37aa\u37ab\u37ac\u37ad\u37ae\u37af\u37b0\u37b1\u37b2\u37b3\u37b4\u37b5\u37b6\u37b7\u37b8\u37b9\u37ba\u37bb\u37bc\u37bd\u37be\u37bf\u37c0\u37c1\u37c2\u37c3\u37c4\u37c5\u37c6\u37c7\u37c8\u37c9\u37ca\u37cb\u37cc\u37cd\u37ce\u37cf\u37d0\u37d1\u37d2\u37d3\u37d4\u37d5\u37d6\u37d7\u37d8\u37d9\u37da\u37db\u37dc\u37dd\u37de\u37df\u37e0\u37e1\u37e2\u37e3\u37e4\u37e5\u37e6\u37e7\u37e8\u37e9\u37ea\u37eb\u37ec\u37ed\u37ee\u37ef\u37f0\u37f1\u37f2\u37f3\u37f4\u37f5\u37f6\u37f7\u37f8\u37f9\u37fa\u37fb\u37fc\u37fd\u37fe\u37ff\u3800\u3801\u3802\u3803\u3804\u3805\u3806\u3807\u3808\u3809\u380a\u380b\u380c\u380d\u380e\u380f\u3810\u3811\u3812\u3813\u3814\u3815\u3816\u3817\u3818\u3819\u381a\u381b\u381c\u381d\u381e\u381f\u3820\u3821\u3822\u3823\u3824\u3825\u3826\u3827\u3828\u3829\u382a\u382b\u382c\u382d\u382e\u382f\u3830\u3831\u3832\u3833\u3834\u3835\u3836\u3837\u3838\u3839\u383a\u383b\u383c\u383d\u383e\u383f\u3840\u3841\u3842\u3843\u3844\u3845\u3846\u3847\u3848\u3849\u384a\u384b\u384c\u384d\u384e\u384f\u3850\u3851\u3852\u3853\u3854\u3855\u3856\u3857\u3858\u3859\u385a\u385b\u385c\u385d\u385e\u385f\u3860\u3861\u3862\u3863\u3864\u3865\u3866\u3867\u3868\u3869\u386a\u386b\u386c\u386d\u386e\u386f\u3870\u3871\u3872\u3873\u3874\u3875\u3876\u3877\u3878\u3879\u387a\u387b\u387c\u387d\u387e\u387f\u3880\u3881\u3882\u3883\u3884\u3885\u3886\u3887\u3888\u3889\u388a\u388b\u388c\u388d\u388e\u388f\u3890\u3891\u3892\u3893\u3894\u3895\u3896\u3897\u3898\u3899\u389a\u389b\u389c\u389d\u389e\u389f\u38a0\u38a1\u38a2\u38a3\u38a4\u38a5\u38a6\u38a7\u38a8\u38a9\u38aa\u38ab\u38ac\u38ad\u38ae\u38af\u38b0\u38b1\u38b2\u38b3\u38b4\u38b5\u38b6\u38b7\u38b8\u38b9\u38ba\u38bb\u38bc\u38bd\u38be\u38bf\u38c0\u38c1\u38c2\u38c3\u38c4\u38c5\u38c6\u38c7\u38c8\u38c9\u38ca\u38cb\u38cc\u38cd\u38ce\u38cf\u38d0\u38d1\u38d2\u38d3\u38d4\u38d5\u38d6\u38d7\u38d8\u38d9\u38da\u38db\u38dc\u38dd\u38de\u38df\u38e0\u38e1\u38e2\u38e3\u38e4\u38e5\u38e6\u38e7\u38e8\u38e9\u38ea\u38eb\u38ec\u38ed\u38ee\u38ef\u38f0\u38f1\u38f2\u38f3\u38f4\u38f5\u38f6\u38f7\u38f8\u38f9\u38fa\u38fb\u38fc\u38fd\u38fe\u38ff\u3900\u3901\u3902\u3903\u3904\u3905\u3906\u3907\u3908\u3909\u390a\u390b\u390c\u390d\u390e\u390f\u3910\u3911\u3912\u3913\u3914\u3915\u3916\u3917\u3918\u3919\u391a\u391b\u391c\u391d\u391e\u391f\u3920\u3921\u3922\u3923\u3924\u3925\u3926\u3927\u3928\u3929\u392a\u392b\u392c\u392d\u392e\u392f\u3930\u3931\u3932\u3933\u3934\u3935\u3936\u3937\u3938\u3939\u393a\u393b\u393c\u393d\u393e\u393f\u3940\u3941\u3942\u3943\u3944\u3945\u3946\u3947\u3948\u3949\u394a\u394b\u394c\u394d\u394e\u394f\u3950\u3951\u3952\u3953\u3954\u3955\u3956\u3957\u3958\u3959\u395a\u395b\u395c\u395d\u395e\u395f\u3960\u3961\u3962\u3963\u3964\u3965\u3966\u3967\u3968\u3969\u396a\u396b\u396c\u396d\u396e\u396f\u3970\u3971\u3972\u3973\u3974\u3975\u3976\u3977\u3978\u3979\u397a\u397b\u397c\u397d\u397e\u397f\u3980\u3981\u3982\u3983\u3984\u3985\u3986\u3987\u3988\u3989\u398a\u398b\u398c\u398d\u398e\u398f\u3990\u3991\u3992\u3993\u3994\u3995\u3996\u3997\u3998\u3999\u399a\u399b\u399c\u399d\u399e\u399f\u39a0\u39a1\u39a2\u39a3\u39a4\u39a5\u39a6\u39a7\u39a8\u39a9\u39aa\u39ab\u39ac\u39ad\u39ae\u39af\u39b0\u39b1\u39b2\u39b3\u39b4\u39b5\u39b6\u39b7\u39b8\u39b9\u39ba\u39bb\u39bc\u39bd\u39be\u39bf\u39c0\u39c1\u39c2\u39c3\u39c4\u39c5\u39c6\u39c7\u39c8\u39c9\u39ca\u39cb\u39cc\u39cd\u39ce\u39cf\u39d0\u39d1\u39d2\u39d3\u39d4\u39d5\u39d6\u39d7\u39d8\u39d9\u39da\u39db\u39dc\u39dd\u39de\u39df\u39e0\u39e1\u39e2\u39e3\u39e4\u39e5\u39e6\u39e7\u39e8\u39e9\u39ea\u39eb\u39ec\u39ed\u39ee\u39ef\u39f0\u39f1\u39f2\u39f3\u39f4\u39f5\u39f6\u39f7\u39f8\u39f9\u39fa\u39fb\u39fc\u39fd\u39fe\u39ff\u3a00\u3a01\u3a02\u3a03\u3a04\u3a05\u3a06\u3a07\u3a08\u3a09\u3a0a\u3a0b\u3a0c\u3a0d\u3a0e\u3a0f\u3a10\u3a11\u3a12\u3a13\u3a14\u3a15\u3a16\u3a17\u3a18\u3a19\u3a1a\u3a1b\u3a1c\u3a1d\u3a1e\u3a1f\u3a20\u3a21\u3a22\u3a23\u3a24\u3a25\u3a26\u3a27\u3a28\u3a29\u3a2a\u3a2b\u3a2c\u3a2d\u3a2e\u3a2f\u3a30\u3a31\u3a32\u3a33\u3a34\u3a35\u3a36\u3a37\u3a38\u3a39\u3a3a\u3a3b\u3a3c\u3a3d\u3a3e\u3a3f\u3a40\u3a41\u3a42\u3a43\u3a44\u3a45\u3a46\u3a47\u3a48\u3a49\u3a4a\u3a4b\u3a4c\u3a4d\u3a4e\u3a4f\u3a50\u3a51\u3a52\u3a53\u3a54\u3a55\u3a56\u3a57\u3a58\u3a59\u3a5a\u3a5b\u3a5c\u3a5d\u3a5e\u3a5f\u3a60\u3a61\u3a62\u3a63\u3a64\u3a65\u3a66\u3a67\u3a68\u3a69\u3a6a\u3a6b\u3a6c\u3a6d\u3a6e\u3a6f\u3a70\u3a71\u3a72\u3a73\u3a74\u3a75\u3a76\u3a77\u3a78\u3a79\u3a7a\u3a7b\u3a7c\u3a7d\u3a7e\u3a7f\u3a80\u3a81\u3a82\u3a83\u3a84\u3a85\u3a86\u3a87\u3a88\u3a89\u3a8a\u3a8b\u3a8c\u3a8d\u3a8e\u3a8f\u3a90\u3a91\u3a92\u3a93\u3a94\u3a95\u3a96\u3a97\u3a98\u3a99\u3a9a\u3a9b\u3a9c\u3a9d\u3a9e\u3a9f\u3aa0\u3aa1\u3aa2\u3aa3\u3aa4\u3aa5\u3aa6\u3aa7\u3aa8\u3aa9\u3aaa\u3aab\u3aac\u3aad\u3aae\u3aaf\u3ab0\u3ab1\u3ab2\u3ab3\u3ab4\u3ab5\u3ab6\u3ab7\u3ab8\u3ab9\u3aba\u3abb\u3abc\u3abd\u3abe\u3abf\u3ac0\u3ac1\u3ac2\u3ac3\u3ac4\u3ac5\u3ac6\u3ac7\u3ac8\u3ac9\u3aca\u3acb\u3acc\u3acd\u3ace\u3acf\u3ad0\u3ad1\u3ad2\u3ad3\u3ad4\u3ad5\u3ad6\u3ad7\u3ad8\u3ad9\u3ada\u3adb\u3adc\u3add\u3ade\u3adf\u3ae0\u3ae1\u3ae2\u3ae3\u3ae4\u3ae5\u3ae6\u3ae7\u3ae8\u3ae9\u3aea\u3aeb\u3aec\u3aed\u3aee\u3aef\u3af0\u3af1\u3af2\u3af3\u3af4\u3af5\u3af6\u3af7\u3af8\u3af9\u3afa\u3afb\u3afc\u3afd\u3afe\u3aff\u3b00\u3b01\u3b02\u3b03\u3b04\u3b05\u3b06\u3b07\u3b08\u3b09\u3b0a\u3b0b\u3b0c\u3b0d\u3b0e\u3b0f\u3b10\u3b11\u3b12\u3b13\u3b14\u3b15\u3b16\u3b17\u3b18\u3b19\u3b1a\u3b1b\u3b1c\u3b1d\u3b1e\u3b1f\u3b20\u3b21\u3b22\u3b23\u3b24\u3b25\u3b26\u3b27\u3b28\u3b29\u3b2a\u3b2b\u3b2c\u3b2d\u3b2e\u3b2f\u3b30\u3b31\u3b32\u3b33\u3b34\u3b35\u3b36\u3b37\u3b38\u3b39\u3b3a\u3b3b\u3b3c\u3b3d\u3b3e\u3b3f\u3b40\u3b41\u3b42\u3b43\u3b44\u3b45\u3b46\u3b47\u3b48\u3b49\u3b4a\u3b4b\u3b4c\u3b4d\u3b4e\u3b4f\u3b50\u3b51\u3b52\u3b53\u3b54\u3b55\u3b56\u3b57\u3b58\u3b59\u3b5a\u3b5b\u3b5c\u3b5d\u3b5e\u3b5f\u3b60\u3b61\u3b62\u3b63\u3b64\u3b65\u3b66\u3b67\u3b68\u3b69\u3b6a\u3b6b\u3b6c\u3b6d\u3b6e\u3b6f\u3b70\u3b71\u3b72\u3b73\u3b74\u3b75\u3b76\u3b77\u3b78\u3b79\u3b7a\u3b7b\u3b7c\u3b7d\u3b7e\u3b7f\u3b80\u3b81\u3b82\u3b83\u3b84\u3b85\u3b86\u3b87\u3b88\u3b89\u3b8a\u3b8b\u3b8c\u3b8d\u3b8e\u3b8f\u3b90\u3b91\u3b92\u3b93\u3b94\u3b95\u3b96\u3b97\u3b98\u3b99\u3b9a\u3b9b\u3b9c\u3b9d\u3b9e\u3b9f\u3ba0\u3ba1\u3ba2\u3ba3\u3ba4\u3ba5\u3ba6\u3ba7\u3ba8\u3ba9\u3baa\u3bab\u3bac\u3bad\u3bae\u3baf\u3bb0\u3bb1\u3bb2\u3bb3\u3bb4\u3bb5\u3bb6\u3bb7\u3bb8\u3bb9\u3bba\u3bbb\u3bbc\u3bbd\u3bbe\u3bbf\u3bc0\u3bc1\u3bc2\u3bc3\u3bc4\u3bc5\u3bc6\u3bc7\u3bc8\u3bc9\u3bca\u3bcb\u3bcc\u3bcd\u3bce\u3bcf\u3bd0\u3bd1\u3bd2\u3bd3\u3bd4\u3bd5\u3bd6\u3bd7\u3bd8\u3bd9\u3bda\u3bdb\u3bdc\u3bdd\u3bde\u3bdf\u3be0\u3be1\u3be2\u3be3\u3be4\u3be5\u3be6\u3be7\u3be8\u3be9\u3bea\u3beb\u3bec\u3bed\u3bee\u3bef\u3bf0\u3bf1\u3bf2\u3bf3\u3bf4\u3bf5\u3bf6\u3bf7\u3bf8\u3bf9\u3bfa\u3bfb\u3bfc\u3bfd\u3bfe\u3bff\u3c00\u3c01\u3c02\u3c03\u3c04\u3c05\u3c06\u3c07\u3c08\u3c09\u3c0a\u3c0b\u3c0c\u3c0d\u3c0e\u3c0f\u3c10\u3c11\u3c12\u3c13\u3c14\u3c15\u3c16\u3c17\u3c18\u3c19\u3c1a\u3c1b\u3c1c\u3c1d\u3c1e\u3c1f\u3c20\u3c21\u3c22\u3c23\u3c24\u3c25\u3c26\u3c27\u3c28\u3c29\u3c2a\u3c2b\u3c2c\u3c2d\u3c2e\u3c2f\u3c30\u3c31\u3c32\u3c33\u3c34\u3c35\u3c36\u3c37\u3c38\u3c39\u3c3a\u3c3b\u3c3c\u3c3d\u3c3e\u3c3f\u3c40\u3c41\u3c42\u3c43\u3c44\u3c45\u3c46\u3c47\u3c48\u3c49\u3c4a\u3c4b\u3c4c\u3c4d\u3c4e\u3c4f\u3c50\u3c51\u3c52\u3c53\u3c54\u3c55\u3c56\u3c57\u3c58\u3c59\u3c5a\u3c5b\u3c5c\u3c5d\u3c5e\u3c5f\u3c60\u3c61\u3c62\u3c63\u3c64\u3c65\u3c66\u3c67\u3c68\u3c69\u3c6a\u3c6b\u3c6c\u3c6d\u3c6e\u3c6f\u3c70\u3c71\u3c72\u3c73\u3c74\u3c75\u3c76\u3c77\u3c78\u3c79\u3c7a\u3c7b\u3c7c\u3c7d\u3c7e\u3c7f\u3c80\u3c81\u3c82\u3c83\u3c84\u3c85\u3c86\u3c87\u3c88\u3c89\u3c8a\u3c8b\u3c8c\u3c8d\u3c8e\u3c8f\u3c90\u3c91\u3c92\u3c93\u3c94\u3c95\u3c96\u3c97\u3c98\u3c99\u3c9a\u3c9b\u3c9c\u3c9d\u3c9e\u3c9f\u3ca0\u3ca1\u3ca2\u3ca3\u3ca4\u3ca5\u3ca6\u3ca7\u3ca8\u3ca9\u3caa\u3cab\u3cac\u3cad\u3cae\u3caf\u3cb0\u3cb1\u3cb2\u3cb3\u3cb4\u3cb5\u3cb6\u3cb7\u3cb8\u3cb9\u3cba\u3cbb\u3cbc\u3cbd\u3cbe\u3cbf\u3cc0\u3cc1\u3cc2\u3cc3\u3cc4\u3cc5\u3cc6\u3cc7\u3cc8\u3cc9\u3cca\u3ccb\u3ccc\u3ccd\u3cce\u3ccf\u3cd0\u3cd1\u3cd2\u3cd3\u3cd4\u3cd5\u3cd6\u3cd7\u3cd8\u3cd9\u3cda\u3cdb\u3cdc\u3cdd\u3cde\u3cdf\u3ce0\u3ce1\u3ce2\u3ce3\u3ce4\u3ce5\u3ce6\u3ce7\u3ce8\u3ce9\u3cea\u3ceb\u3cec\u3ced\u3cee\u3cef\u3cf0\u3cf1\u3cf2\u3cf3\u3cf4\u3cf5\u3cf6\u3cf7\u3cf8\u3cf9\u3cfa\u3cfb\u3cfc\u3cfd\u3cfe\u3cff\u3d00\u3d01\u3d02\u3d03\u3d04\u3d05\u3d06\u3d07\u3d08\u3d09\u3d0a\u3d0b\u3d0c\u3d0d\u3d0e\u3d0f\u3d10\u3d11\u3d12\u3d13\u3d14\u3d15\u3d16\u3d17\u3d18\u3d19\u3d1a\u3d1b\u3d1c\u3d1d\u3d1e\u3d1f\u3d20\u3d21\u3d22\u3d23\u3d24\u3d25\u3d26\u3d27\u3d28\u3d29\u3d2a\u3d2b\u3d2c\u3d2d\u3d2e\u3d2f\u3d30\u3d31\u3d32\u3d33\u3d34\u3d35\u3d36\u3d37\u3d38\u3d39\u3d3a\u3d3b\u3d3c\u3d3d\u3d3e\u3d3f\u3d40\u3d41\u3d42\u3d43\u3d44\u3d45\u3d46\u3d47\u3d48\u3d49\u3d4a\u3d4b\u3d4c\u3d4d\u3d4e\u3d4f\u3d50\u3d51\u3d52\u3d53\u3d54\u3d55\u3d56\u3d57\u3d58\u3d59\u3d5a\u3d5b\u3d5c\u3d5d\u3d5e\u3d5f\u3d60\u3d61\u3d62\u3d63\u3d64\u3d65\u3d66\u3d67\u3d68\u3d69\u3d6a\u3d6b\u3d6c\u3d6d\u3d6e\u3d6f\u3d70\u3d71\u3d72\u3d73\u3d74\u3d75\u3d76\u3d77\u3d78\u3d79\u3d7a\u3d7b\u3d7c\u3d7d\u3d7e\u3d7f\u3d80\u3d81\u3d82\u3d83\u3d84\u3d85\u3d86\u3d87\u3d88\u3d89\u3d8a\u3d8b\u3d8c\u3d8d\u3d8e\u3d8f\u3d90\u3d91\u3d92\u3d93\u3d94\u3d95\u3d96\u3d97\u3d98\u3d99\u3d9a\u3d9b\u3d9c\u3d9d\u3d9e\u3d9f\u3da0\u3da1\u3da2\u3da3\u3da4\u3da5\u3da6\u3da7\u3da8\u3da9\u3daa\u3dab\u3dac\u3dad\u3dae\u3daf\u3db0\u3db1\u3db2\u3db3\u3db4\u3db5\u3db6\u3db7\u3db8\u3db9\u3dba\u3dbb\u3dbc\u3dbd\u3dbe\u3dbf\u3dc0\u3dc1\u3dc2\u3dc3\u3dc4\u3dc5\u3dc6\u3dc7\u3dc8\u3dc9\u3dca\u3dcb\u3dcc\u3dcd\u3dce\u3dcf\u3dd0\u3dd1\u3dd2\u3dd3\u3dd4\u3dd5\u3dd6\u3dd7\u3dd8\u3dd9\u3dda\u3ddb\u3ddc\u3ddd\u3dde\u3ddf\u3de0\u3de1\u3de2\u3de3\u3de4\u3de5\u3de6\u3de7\u3de8\u3de9\u3dea\u3deb\u3dec\u3ded\u3dee\u3def\u3df0\u3df1\u3df2\u3df3\u3df4\u3df5\u3df6\u3df7\u3df8\u3df9\u3dfa\u3dfb\u3dfc\u3dfd\u3dfe\u3dff\u3e00\u3e01\u3e02\u3e03\u3e04\u3e05\u3e06\u3e07\u3e08\u3e09\u3e0a\u3e0b\u3e0c\u3e0d\u3e0e\u3e0f\u3e10\u3e11\u3e12\u3e13\u3e14\u3e15\u3e16\u3e17\u3e18\u3e19\u3e1a\u3e1b\u3e1c\u3e1d\u3e1e\u3e1f\u3e20\u3e21\u3e22\u3e23\u3e24\u3e25\u3e26\u3e27\u3e28\u3e29\u3e2a\u3e2b\u3e2c\u3e2d\u3e2e\u3e2f\u3e30\u3e31\u3e32\u3e33\u3e34\u3e35\u3e36\u3e37\u3e38\u3e39\u3e3a\u3e3b\u3e3c\u3e3d\u3e3e\u3e3f\u3e40\u3e41\u3e42\u3e43\u3e44\u3e45\u3e46\u3e47\u3e48\u3e49\u3e4a\u3e4b\u3e4c\u3e4d\u3e4e\u3e4f\u3e50\u3e51\u3e52\u3e53\u3e54\u3e55\u3e56\u3e57\u3e58\u3e59\u3e5a\u3e5b\u3e5c\u3e5d\u3e5e\u3e5f\u3e60\u3e61\u3e62\u3e63\u3e64\u3e65\u3e66\u3e67\u3e68\u3e69\u3e6a\u3e6b\u3e6c\u3e6d\u3e6e\u3e6f\u3e70\u3e71\u3e72\u3e73\u3e74\u3e75\u3e76\u3e77\u3e78\u3e79\u3e7a\u3e7b\u3e7c\u3e7d\u3e7e\u3e7f\u3e80\u3e81\u3e82\u3e83\u3e84\u3e85\u3e86\u3e87\u3e88\u3e89\u3e8a\u3e8b\u3e8c\u3e8d\u3e8e\u3e8f\u3e90\u3e91\u3e92\u3e93\u3e94\u3e95\u3e96\u3e97\u3e98\u3e99\u3e9a\u3e9b\u3e9c\u3e9d\u3e9e\u3e9f\u3ea0\u3ea1\u3ea2\u3ea3\u3ea4\u3ea5\u3ea6\u3ea7\u3ea8\u3ea9\u3eaa\u3eab\u3eac\u3ead\u3eae\u3eaf\u3eb0\u3eb1\u3eb2\u3eb3\u3eb4\u3eb5\u3eb6\u3eb7\u3eb8\u3eb9\u3eba\u3ebb\u3ebc\u3ebd\u3ebe\u3ebf\u3ec0\u3ec1\u3ec2\u3ec3\u3ec4\u3ec5\u3ec6\u3ec7\u3ec8\u3ec9\u3eca\u3ecb\u3ecc\u3ecd\u3ece\u3ecf\u3ed0\u3ed1\u3ed2\u3ed3\u3ed4\u3ed5\u3ed6\u3ed7\u3ed8\u3ed9\u3eda\u3edb\u3edc\u3edd\u3ede\u3edf\u3ee0\u3ee1\u3ee2\u3ee3\u3ee4\u3ee5\u3ee6\u3ee7\u3ee8\u3ee9\u3eea\u3eeb\u3eec\u3eed\u3eee\u3eef\u3ef0\u3ef1\u3ef2\u3ef3\u3ef4\u3ef5\u3ef6\u3ef7\u3ef8\u3ef9\u3efa\u3efb\u3efc\u3efd\u3efe\u3eff\u3f00\u3f01\u3f02\u3f03\u3f04\u3f05\u3f06\u3f07\u3f08\u3f09\u3f0a\u3f0b\u3f0c\u3f0d\u3f0e\u3f0f\u3f10\u3f11\u3f12\u3f13\u3f14\u3f15\u3f16\u3f17\u3f18\u3f19\u3f1a\u3f1b\u3f1c\u3f1d\u3f1e\u3f1f\u3f20\u3f21\u3f22\u3f23\u3f24\u3f25\u3f26\u3f27\u3f28\u3f29\u3f2a\u3f2b\u3f2c\u3f2d\u3f2e\u3f2f\u3f30\u3f31\u3f32\u3f33\u3f34\u3f35\u3f36\u3f37\u3f38\u3f39\u3f3a\u3f3b\u3f3c\u3f3d\u3f3e\u3f3f\u3f40\u3f41\u3f42\u3f43\u3f44\u3f45\u3f46\u3f47\u3f48\u3f49\u3f4a\u3f4b\u3f4c\u3f4d\u3f4e\u3f4f\u3f50\u3f51\u3f52\u3f53\u3f54\u3f55\u3f56\u3f57\u3f58\u3f59\u3f5a\u3f5b\u3f5c\u3f5d\u3f5e\u3f5f\u3f60\u3f61\u3f62\u3f63\u3f64\u3f65\u3f66\u3f67\u3f68\u3f69\u3f6a\u3f6b\u3f6c\u3f6d\u3f6e\u3f6f\u3f70\u3f71\u3f72\u3f73\u3f74\u3f75\u3f76\u3f77\u3f78\u3f79\u3f7a\u3f7b\u3f7c\u3f7d\u3f7e\u3f7f\u3f80\u3f81\u3f82\u3f83\u3f84\u3f85\u3f86\u3f87\u3f88\u3f89\u3f8a\u3f8b\u3f8c\u3f8d\u3f8e\u3f8f\u3f90\u3f91\u3f92\u3f93\u3f94\u3f95\u3f96\u3f97\u3f98\u3f99\u3f9a\u3f9b\u3f9c\u3f9d\u3f9e\u3f9f\u3fa0\u3fa1\u3fa2\u3fa3\u3fa4\u3fa5\u3fa6\u3fa7\u3fa8\u3fa9\u3faa\u3fab\u3fac\u3fad\u3fae\u3faf\u3fb0\u3fb1\u3fb2\u3fb3\u3fb4\u3fb5\u3fb6\u3fb7\u3fb8\u3fb9\u3fba\u3fbb\u3fbc\u3fbd\u3fbe\u3fbf\u3fc0\u3fc1\u3fc2\u3fc3\u3fc4\u3fc5\u3fc6\u3fc7\u3fc8\u3fc9\u3fca\u3fcb\u3fcc\u3fcd\u3fce\u3fcf\u3fd0\u3fd1\u3fd2\u3fd3\u3fd4\u3fd5\u3fd6\u3fd7\u3fd8\u3fd9\u3fda\u3fdb\u3fdc\u3fdd\u3fde\u3fdf\u3fe0\u3fe1\u3fe2\u3fe3\u3fe4\u3fe5\u3fe6\u3fe7\u3fe8\u3fe9\u3fea\u3feb\u3fec\u3fed\u3fee\u3fef\u3ff0\u3ff1\u3ff2\u3ff3\u3ff4\u3ff5\u3ff6\u3ff7\u3ff8\u3ff9\u3ffa\u3ffb\u3ffc\u3ffd\u3ffe\u3fff\u4000\u4001\u4002\u4003\u4004\u4005\u4006\u4007\u4008\u4009\u400a\u400b\u400c\u400d\u400e\u400f\u4010\u4011\u4012\u4013\u4014\u4015\u4016\u4017\u4018\u4019\u401a\u401b\u401c\u401d\u401e\u401f\u4020\u4021\u4022\u4023\u4024\u4025\u4026\u4027\u4028\u4029\u402a\u402b\u402c\u402d\u402e\u402f\u4030\u4031\u4032\u4033\u4034\u4035\u4036\u4037\u4038\u4039\u403a\u403b\u403c\u403d\u403e\u403f\u4040\u4041\u4042\u4043\u4044\u4045\u4046\u4047\u4048\u4049\u404a\u404b\u404c\u404d\u404e\u404f\u4050\u4051\u4052\u4053\u4054\u4055\u4056\u4057\u4058\u4059\u405a\u405b\u405c\u405d\u405e\u405f\u4060\u4061\u4062\u4063\u4064\u4065\u4066\u4067\u4068\u4069\u406a\u406b\u406c\u406d\u406e\u406f\u4070\u4071\u4072\u4073\u4074\u4075\u4076\u4077\u4078\u4079\u407a\u407b\u407c\u407d\u407e\u407f\u4080\u4081\u4082\u4083\u4084\u4085\u4086\u4087\u4088\u4089\u408a\u408b\u408c\u408d\u408e\u408f\u4090\u4091\u4092\u4093\u4094\u4095\u4096\u4097\u4098\u4099\u409a\u409b\u409c\u409d\u409e\u409f\u40a0\u40a1\u40a2\u40a3\u40a4\u40a5\u40a6\u40a7\u40a8\u40a9\u40aa\u40ab\u40ac\u40ad\u40ae\u40af\u40b0\u40b1\u40b2\u40b3\u40b4\u40b5\u40b6\u40b7\u40b8\u40b9\u40ba\u40bb\u40bc\u40bd\u40be\u40bf\u40c0\u40c1\u40c2\u40c3\u40c4\u40c5\u40c6\u40c7\u40c8\u40c9\u40ca\u40cb\u40cc\u40cd\u40ce\u40cf\u40d0\u40d1\u40d2\u40d3\u40d4\u40d5\u40d6\u40d7\u40d8\u40d9\u40da\u40db\u40dc\u40dd\u40de\u40df\u40e0\u40e1\u40e2\u40e3\u40e4\u40e5\u40e6\u40e7\u40e8\u40e9\u40ea\u40eb\u40ec\u40ed\u40ee\u40ef\u40f0\u40f1\u40f2\u40f3\u40f4\u40f5\u40f6\u40f7\u40f8\u40f9\u40fa\u40fb\u40fc\u40fd\u40fe\u40ff\u4100\u4101\u4102\u4103\u4104\u4105\u4106\u4107\u4108\u4109\u410a\u410b\u410c\u410d\u410e\u410f\u4110\u4111\u4112\u4113\u4114\u4115\u4116\u4117\u4118\u4119\u411a\u411b\u411c\u411d\u411e\u411f\u4120\u4121\u4122\u4123\u4124\u4125\u4126\u4127\u4128\u4129\u412a\u412b\u412c\u412d\u412e\u412f\u4130\u4131\u4132\u4133\u4134\u4135\u4136\u4137\u4138\u4139\u413a\u413b\u413c\u413d\u413e\u413f\u4140\u4141\u4142\u4143\u4144\u4145\u4146\u4147\u4148\u4149\u414a\u414b\u414c\u414d\u414e\u414f\u4150\u4151\u4152\u4153\u4154\u4155\u4156\u4157\u4158\u4159\u415a\u415b\u415c\u415d\u415e\u415f\u4160\u4161\u4162\u4163\u4164\u4165\u4166\u4167\u4168\u4169\u416a\u416b\u416c\u416d\u416e\u416f\u4170\u4171\u4172\u4173\u4174\u4175\u4176\u4177\u4178\u4179\u417a\u417b\u417c\u417d\u417e\u417f\u4180\u4181\u4182\u4183\u4184\u4185\u4186\u4187\u4188\u4189\u418a\u418b\u418c\u418d\u418e\u418f\u4190\u4191\u4192\u4193\u4194\u4195\u4196\u4197\u4198\u4199\u419a\u419b\u419c\u419d\u419e\u419f\u41a0\u41a1\u41a2\u41a3\u41a4\u41a5\u41a6\u41a7\u41a8\u41a9\u41aa\u41ab\u41ac\u41ad\u41ae\u41af\u41b0\u41b1\u41b2\u41b3\u41b4\u41b5\u41b6\u41b7\u41b8\u41b9\u41ba\u41bb\u41bc\u41bd\u41be\u41bf\u41c0\u41c1\u41c2\u41c3\u41c4\u41c5\u41c6\u41c7\u41c8\u41c9\u41ca\u41cb\u41cc\u41cd\u41ce\u41cf\u41d0\u41d1\u41d2\u41d3\u41d4\u41d5\u41d6\u41d7\u41d8\u41d9\u41da\u41db\u41dc\u41dd\u41de\u41df\u41e0\u41e1\u41e2\u41e3\u41e4\u41e5\u41e6\u41e7\u41e8\u41e9\u41ea\u41eb\u41ec\u41ed\u41ee\u41ef\u41f0\u41f1\u41f2\u41f3\u41f4\u41f5\u41f6\u41f7\u41f8\u41f9\u41fa\u41fb\u41fc\u41fd\u41fe\u41ff\u4200\u4201\u4202\u4203\u4204\u4205\u4206\u4207\u4208\u4209\u420a\u420b\u420c\u420d\u420e\u420f\u4210\u4211\u4212\u4213\u4214\u4215\u4216\u4217\u4218\u4219\u421a\u421b\u421c\u421d\u421e\u421f\u4220\u4221\u4222\u4223\u4224\u4225\u4226\u4227\u4228\u4229\u422a\u422b\u422c\u422d\u422e\u422f\u4230\u4231\u4232\u4233\u4234\u4235\u4236\u4237\u4238\u4239\u423a\u423b\u423c\u423d\u423e\u423f\u4240\u4241\u4242\u4243\u4244\u4245\u4246\u4247\u4248\u4249\u424a\u424b\u424c\u424d\u424e\u424f\u4250\u4251\u4252\u4253\u4254\u4255\u4256\u4257\u4258\u4259\u425a\u425b\u425c\u425d\u425e\u425f\u4260\u4261\u4262\u4263\u4264\u4265\u4266\u4267\u4268\u4269\u426a\u426b\u426c\u426d\u426e\u426f\u4270\u4271\u4272\u4273\u4274\u4275\u4276\u4277\u4278\u4279\u427a\u427b\u427c\u427d\u427e\u427f\u4280\u4281\u4282\u4283\u4284\u4285\u4286\u4287\u4288\u4289\u428a\u428b\u428c\u428d\u428e\u428f\u4290\u4291\u4292\u4293\u4294\u4295\u4296\u4297\u4298\u4299\u429a\u429b\u429c\u429d\u429e\u429f\u42a0\u42a1\u42a2\u42a3\u42a4\u42a5\u42a6\u42a7\u42a8\u42a9\u42aa\u42ab\u42ac\u42ad\u42ae\u42af\u42b0\u42b1\u42b2\u42b3\u42b4\u42b5\u42b6\u42b7\u42b8\u42b9\u42ba\u42bb\u42bc\u42bd\u42be\u42bf\u42c0\u42c1\u42c2\u42c3\u42c4\u42c5\u42c6\u42c7\u42c8\u42c9\u42ca\u42cb\u42cc\u42cd\u42ce\u42cf\u42d0\u42d1\u42d2\u42d3\u42d4\u42d5\u42d6\u42d7\u42d8\u42d9\u42da\u42db\u42dc\u42dd\u42de\u42df\u42e0\u42e1\u42e2\u42e3\u42e4\u42e5\u42e6\u42e7\u42e8\u42e9\u42ea\u42eb\u42ec\u42ed\u42ee\u42ef\u42f0\u42f1\u42f2\u42f3\u42f4\u42f5\u42f6\u42f7\u42f8\u42f9\u42fa\u42fb\u42fc\u42fd\u42fe\u42ff\u4300\u4301\u4302\u4303\u4304\u4305\u4306\u4307\u4308\u4309\u430a\u430b\u430c\u430d\u430e\u430f\u4310\u4311\u4312\u4313\u4314\u4315\u4316\u4317\u4318\u4319\u431a\u431b\u431c\u431d\u431e\u431f\u4320\u4321\u4322\u4323\u4324\u4325\u4326\u4327\u4328\u4329\u432a\u432b\u432c\u432d\u432e\u432f\u4330\u4331\u4332\u4333\u4334\u4335\u4336\u4337\u4338\u4339\u433a\u433b\u433c\u433d\u433e\u433f\u4340\u4341\u4342\u4343\u4344\u4345\u4346\u4347\u4348\u4349\u434a\u434b\u434c\u434d\u434e\u434f\u4350\u4351\u4352\u4353\u4354\u4355\u4356\u4357\u4358\u4359\u435a\u435b\u435c\u435d\u435e\u435f\u4360\u4361\u4362\u4363\u4364\u4365\u4366\u4367\u4368\u4369\u436a\u436b\u436c\u436d\u436e\u436f\u4370\u4371\u4372\u4373\u4374\u4375\u4376\u4377\u4378\u4379\u437a\u437b\u437c\u437d\u437e\u437f\u4380\u4381\u4382\u4383\u4384\u4385\u4386\u4387\u4388\u4389\u438a\u438b\u438c\u438d\u438e\u438f\u4390\u4391\u4392\u4393\u4394\u4395\u4396\u4397\u4398\u4399\u439a\u439b\u439c\u439d\u439e\u439f\u43a0\u43a1\u43a2\u43a3\u43a4\u43a5\u43a6\u43a7\u43a8\u43a9\u43aa\u43ab\u43ac\u43ad\u43ae\u43af\u43b0\u43b1\u43b2\u43b3\u43b4\u43b5\u43b6\u43b7\u43b8\u43b9\u43ba\u43bb\u43bc\u43bd\u43be\u43bf\u43c0\u43c1\u43c2\u43c3\u43c4\u43c5\u43c6\u43c7\u43c8\u43c9\u43ca\u43cb\u43cc\u43cd\u43ce\u43cf\u43d0\u43d1\u43d2\u43d3\u43d4\u43d5\u43d6\u43d7\u43d8\u43d9\u43da\u43db\u43dc\u43dd\u43de\u43df\u43e0\u43e1\u43e2\u43e3\u43e4\u43e5\u43e6\u43e7\u43e8\u43e9\u43ea\u43eb\u43ec\u43ed\u43ee\u43ef\u43f0\u43f1\u43f2\u43f3\u43f4\u43f5\u43f6\u43f7\u43f8\u43f9\u43fa\u43fb\u43fc\u43fd\u43fe\u43ff\u4400\u4401\u4402\u4403\u4404\u4405\u4406\u4407\u4408\u4409\u440a\u440b\u440c\u440d\u440e\u440f\u4410\u4411\u4412\u4413\u4414\u4415\u4416\u4417\u4418\u4419\u441a\u441b\u441c\u441d\u441e\u441f\u4420\u4421\u4422\u4423\u4424\u4425\u4426\u4427\u4428\u4429\u442a\u442b\u442c\u442d\u442e\u442f\u4430\u4431\u4432\u4433\u4434\u4435\u4436\u4437\u4438\u4439\u443a\u443b\u443c\u443d\u443e\u443f\u4440\u4441\u4442\u4443\u4444\u4445\u4446\u4447\u4448\u4449\u444a\u444b\u444c\u444d\u444e\u444f\u4450\u4451\u4452\u4453\u4454\u4455\u4456\u4457\u4458\u4459\u445a\u445b\u445c\u445d\u445e\u445f\u4460\u4461\u4462\u4463\u4464\u4465\u4466\u4467\u4468\u4469\u446a\u446b\u446c\u446d\u446e\u446f\u4470\u4471\u4472\u4473\u4474\u4475\u4476\u4477\u4478\u4479\u447a\u447b\u447c\u447d\u447e\u447f\u4480\u4481\u4482\u4483\u4484\u4485\u4486\u4487\u4488\u4489\u448a\u448b\u448c\u448d\u448e\u448f\u4490\u4491\u4492\u4493\u4494\u4495\u4496\u4497\u4498\u4499\u449a\u449b\u449c\u449d\u449e\u449f\u44a0\u44a1\u44a2\u44a3\u44a4\u44a5\u44a6\u44a7\u44a8\u44a9\u44aa\u44ab\u44ac\u44ad\u44ae\u44af\u44b0\u44b1\u44b2\u44b3\u44b4\u44b5\u44b6\u44b7\u44b8\u44b9\u44ba\u44bb\u44bc\u44bd\u44be\u44bf\u44c0\u44c1\u44c2\u44c3\u44c4\u44c5\u44c6\u44c7\u44c8\u44c9\u44ca\u44cb\u44cc\u44cd\u44ce\u44cf\u44d0\u44d1\u44d2\u44d3\u44d4\u44d5\u44d6\u44d7\u44d8\u44d9\u44da\u44db\u44dc\u44dd\u44de\u44df\u44e0\u44e1\u44e2\u44e3\u44e4\u44e5\u44e6\u44e7\u44e8\u44e9\u44ea\u44eb\u44ec\u44ed\u44ee\u44ef\u44f0\u44f1\u44f2\u44f3\u44f4\u44f5\u44f6\u44f7\u44f8\u44f9\u44fa\u44fb\u44fc\u44fd\u44fe\u44ff\u4500\u4501\u4502\u4503\u4504\u4505\u4506\u4507\u4508\u4509\u450a\u450b\u450c\u450d\u450e\u450f\u4510\u4511\u4512\u4513\u4514\u4515\u4516\u4517\u4518\u4519\u451a\u451b\u451c\u451d\u451e\u451f\u4520\u4521\u4522\u4523\u4524\u4525\u4526\u4527\u4528\u4529\u452a\u452b\u452c\u452d\u452e\u452f\u4530\u4531\u4532\u4533\u4534\u4535\u4536\u4537\u4538\u4539\u453a\u453b\u453c\u453d\u453e\u453f\u4540\u4541\u4542\u4543\u4544\u4545\u4546\u4547\u4548\u4549\u454a\u454b\u454c\u454d\u454e\u454f\u4550\u4551\u4552\u4553\u4554\u4555\u4556\u4557\u4558\u4559\u455a\u455b\u455c\u455d\u455e\u455f\u4560\u4561\u4562\u4563\u4564\u4565\u4566\u4567\u4568\u4569\u456a\u456b\u456c\u456d\u456e\u456f\u4570\u4571\u4572\u4573\u4574\u4575\u4576\u4577\u4578\u4579\u457a\u457b\u457c\u457d\u457e\u457f\u4580\u4581\u4582\u4583\u4584\u4585\u4586\u4587\u4588\u4589\u458a\u458b\u458c\u458d\u458e\u458f\u4590\u4591\u4592\u4593\u4594\u4595\u4596\u4597\u4598\u4599\u459a\u459b\u459c\u459d\u459e\u459f\u45a0\u45a1\u45a2\u45a3\u45a4\u45a5\u45a6\u45a7\u45a8\u45a9\u45aa\u45ab\u45ac\u45ad\u45ae\u45af\u45b0\u45b1\u45b2\u45b3\u45b4\u45b5\u45b6\u45b7\u45b8\u45b9\u45ba\u45bb\u45bc\u45bd\u45be\u45bf\u45c0\u45c1\u45c2\u45c3\u45c4\u45c5\u45c6\u45c7\u45c8\u45c9\u45ca\u45cb\u45cc\u45cd\u45ce\u45cf\u45d0\u45d1\u45d2\u45d3\u45d4\u45d5\u45d6\u45d7\u45d8\u45d9\u45da\u45db\u45dc\u45dd\u45de\u45df\u45e0\u45e1\u45e2\u45e3\u45e4\u45e5\u45e6\u45e7\u45e8\u45e9\u45ea\u45eb\u45ec\u45ed\u45ee\u45ef\u45f0\u45f1\u45f2\u45f3\u45f4\u45f5\u45f6\u45f7\u45f8\u45f9\u45fa\u45fb\u45fc\u45fd\u45fe\u45ff\u4600\u4601\u4602\u4603\u4604\u4605\u4606\u4607\u4608\u4609\u460a\u460b\u460c\u460d\u460e\u460f\u4610\u4611\u4612\u4613\u4614\u4615\u4616\u4617\u4618\u4619\u461a\u461b\u461c\u461d\u461e\u461f\u4620\u4621\u4622\u4623\u4624\u4625\u4626\u4627\u4628\u4629\u462a\u462b\u462c\u462d\u462e\u462f\u4630\u4631\u4632\u4633\u4634\u4635\u4636\u4637\u4638\u4639\u463a\u463b\u463c\u463d\u463e\u463f\u4640\u4641\u4642\u4643\u4644\u4645\u4646\u4647\u4648\u4649\u464a\u464b\u464c\u464d\u464e\u464f\u4650\u4651\u4652\u4653\u4654\u4655\u4656\u4657\u4658\u4659\u465a\u465b\u465c\u465d\u465e\u465f\u4660\u4661\u4662\u4663\u4664\u4665\u4666\u4667\u4668\u4669\u466a\u466b\u466c\u466d\u466e\u466f\u4670\u4671\u4672\u4673\u4674\u4675\u4676\u4677\u4678\u4679\u467a\u467b\u467c\u467d\u467e\u467f\u4680\u4681\u4682\u4683\u4684\u4685\u4686\u4687\u4688\u4689\u468a\u468b\u468c\u468d\u468e\u468f\u4690\u4691\u4692\u4693\u4694\u4695\u4696\u4697\u4698\u4699\u469a\u469b\u469c\u469d\u469e\u469f\u46a0\u46a1\u46a2\u46a3\u46a4\u46a5\u46a6\u46a7\u46a8\u46a9\u46aa\u46ab\u46ac\u46ad\u46ae\u46af\u46b0\u46b1\u46b2\u46b3\u46b4\u46b5\u46b6\u46b7\u46b8\u46b9\u46ba\u46bb\u46bc\u46bd\u46be\u46bf\u46c0\u46c1\u46c2\u46c3\u46c4\u46c5\u46c6\u46c7\u46c8\u46c9\u46ca\u46cb\u46cc\u46cd\u46ce\u46cf\u46d0\u46d1\u46d2\u46d3\u46d4\u46d5\u46d6\u46d7\u46d8\u46d9\u46da\u46db\u46dc\u46dd\u46de\u46df\u46e0\u46e1\u46e2\u46e3\u46e4\u46e5\u46e6\u46e7\u46e8\u46e9\u46ea\u46eb\u46ec\u46ed\u46ee\u46ef\u46f0\u46f1\u46f2\u46f3\u46f4\u46f5\u46f6\u46f7\u46f8\u46f9\u46fa\u46fb\u46fc\u46fd\u46fe\u46ff\u4700\u4701\u4702\u4703\u4704\u4705\u4706\u4707\u4708\u4709\u470a\u470b\u470c\u470d\u470e\u470f\u4710\u4711\u4712\u4713\u4714\u4715\u4716\u4717\u4718\u4719\u471a\u471b\u471c\u471d\u471e\u471f\u4720\u4721\u4722\u4723\u4724\u4725\u4726\u4727\u4728\u4729\u472a\u472b\u472c\u472d\u472e\u472f\u4730\u4731\u4732\u4733\u4734\u4735\u4736\u4737\u4738\u4739\u473a\u473b\u473c\u473d\u473e\u473f\u4740\u4741\u4742\u4743\u4744\u4745\u4746\u4747\u4748\u4749\u474a\u474b\u474c\u474d\u474e\u474f\u4750\u4751\u4752\u4753\u4754\u4755\u4756\u4757\u4758\u4759\u475a\u475b\u475c\u475d\u475e\u475f\u4760\u4761\u4762\u4763\u4764\u4765\u4766\u4767\u4768\u4769\u476a\u476b\u476c\u476d\u476e\u476f\u4770\u4771\u4772\u4773\u4774\u4775\u4776\u4777\u4778\u4779\u477a\u477b\u477c\u477d\u477e\u477f\u4780\u4781\u4782\u4783\u4784\u4785\u4786\u4787\u4788\u4789\u478a\u478b\u478c\u478d\u478e\u478f\u4790\u4791\u4792\u4793\u4794\u4795\u4796\u4797\u4798\u4799\u479a\u479b\u479c\u479d\u479e\u479f\u47a0\u47a1\u47a2\u47a3\u47a4\u47a5\u47a6\u47a7\u47a8\u47a9\u47aa\u47ab\u47ac\u47ad\u47ae\u47af\u47b0\u47b1\u47b2\u47b3\u47b4\u47b5\u47b6\u47b7\u47b8\u47b9\u47ba\u47bb\u47bc\u47bd\u47be\u47bf\u47c0\u47c1\u47c2\u47c3\u47c4\u47c5\u47c6\u47c7\u47c8\u47c9\u47ca\u47cb\u47cc\u47cd\u47ce\u47cf\u47d0\u47d1\u47d2\u47d3\u47d4\u47d5\u47d6\u47d7\u47d8\u47d9\u47da\u47db\u47dc\u47dd\u47de\u47df\u47e0\u47e1\u47e2\u47e3\u47e4\u47e5\u47e6\u47e7\u47e8\u47e9\u47ea\u47eb\u47ec\u47ed\u47ee\u47ef\u47f0\u47f1\u47f2\u47f3\u47f4\u47f5\u47f6\u47f7\u47f8\u47f9\u47fa\u47fb\u47fc\u47fd\u47fe\u47ff\u4800\u4801\u4802\u4803\u4804\u4805\u4806\u4807\u4808\u4809\u480a\u480b\u480c\u480d\u480e\u480f\u4810\u4811\u4812\u4813\u4814\u4815\u4816\u4817\u4818\u4819\u481a\u481b\u481c\u481d\u481e\u481f\u4820\u4821\u4822\u4823\u4824\u4825\u4826\u4827\u4828\u4829\u482a\u482b\u482c\u482d\u482e\u482f\u4830\u4831\u4832\u4833\u4834\u4835\u4836\u4837\u4838\u4839\u483a\u483b\u483c\u483d\u483e\u483f\u4840\u4841\u4842\u4843\u4844\u4845\u4846\u4847\u4848\u4849\u484a\u484b\u484c\u484d\u484e\u484f\u4850\u4851\u4852\u4853\u4854\u4855\u4856\u4857\u4858\u4859\u485a\u485b\u485c\u485d\u485e\u485f\u4860\u4861\u4862\u4863\u4864\u4865\u4866\u4867\u4868\u4869\u486a\u486b\u486c\u486d\u486e\u486f\u4870\u4871\u4872\u4873\u4874\u4875\u4876\u4877\u4878\u4879\u487a\u487b\u487c\u487d\u487e\u487f\u4880\u4881\u4882\u4883\u4884\u4885\u4886\u4887\u4888\u4889\u488a\u488b\u488c\u488d\u488e\u488f\u4890\u4891\u4892\u4893\u4894\u4895\u4896\u4897\u4898\u4899\u489a\u489b\u489c\u489d\u489e\u489f\u48a0\u48a1\u48a2\u48a3\u48a4\u48a5\u48a6\u48a7\u48a8\u48a9\u48aa\u48ab\u48ac\u48ad\u48ae\u48af\u48b0\u48b1\u48b2\u48b3\u48b4\u48b5\u48b6\u48b7\u48b8\u48b9\u48ba\u48bb\u48bc\u48bd\u48be\u48bf\u48c0\u48c1\u48c2\u48c3\u48c4\u48c5\u48c6\u48c7\u48c8\u48c9\u48ca\u48cb\u48cc\u48cd\u48ce\u48cf\u48d0\u48d1\u48d2\u48d3\u48d4\u48d5\u48d6\u48d7\u48d8\u48d9\u48da\u48db\u48dc\u48dd\u48de\u48df\u48e0\u48e1\u48e2\u48e3\u48e4\u48e5\u48e6\u48e7\u48e8\u48e9\u48ea\u48eb\u48ec\u48ed\u48ee\u48ef\u48f0\u48f1\u48f2\u48f3\u48f4\u48f5\u48f6\u48f7\u48f8\u48f9\u48fa\u48fb\u48fc\u48fd\u48fe\u48ff\u4900\u4901\u4902\u4903\u4904\u4905\u4906\u4907\u4908\u4909\u490a\u490b\u490c\u490d\u490e\u490f\u4910\u4911\u4912\u4913\u4914\u4915\u4916\u4917\u4918\u4919\u491a\u491b\u491c\u491d\u491e\u491f\u4920\u4921\u4922\u4923\u4924\u4925\u4926\u4927\u4928\u4929\u492a\u492b\u492c\u492d\u492e\u492f\u4930\u4931\u4932\u4933\u4934\u4935\u4936\u4937\u4938\u4939\u493a\u493b\u493c\u493d\u493e\u493f\u4940\u4941\u4942\u4943\u4944\u4945\u4946\u4947\u4948\u4949\u494a\u494b\u494c\u494d\u494e\u494f\u4950\u4951\u4952\u4953\u4954\u4955\u4956\u4957\u4958\u4959\u495a\u495b\u495c\u495d\u495e\u495f\u4960\u4961\u4962\u4963\u4964\u4965\u4966\u4967\u4968\u4969\u496a\u496b\u496c\u496d\u496e\u496f\u4970\u4971\u4972\u4973\u4974\u4975\u4976\u4977\u4978\u4979\u497a\u497b\u497c\u497d\u497e\u497f\u4980\u4981\u4982\u4983\u4984\u4985\u4986\u4987\u4988\u4989\u498a\u498b\u498c\u498d\u498e\u498f\u4990\u4991\u4992\u4993\u4994\u4995\u4996\u4997\u4998\u4999\u499a\u499b\u499c\u499d\u499e\u499f\u49a0\u49a1\u49a2\u49a3\u49a4\u49a5\u49a6\u49a7\u49a8\u49a9\u49aa\u49ab\u49ac\u49ad\u49ae\u49af\u49b0\u49b1\u49b2\u49b3\u49b4\u49b5\u49b6\u49b7\u49b8\u49b9\u49ba\u49bb\u49bc\u49bd\u49be\u49bf\u49c0\u49c1\u49c2\u49c3\u49c4\u49c5\u49c6\u49c7\u49c8\u49c9\u49ca\u49cb\u49cc\u49cd\u49ce\u49cf\u49d0\u49d1\u49d2\u49d3\u49d4\u49d5\u49d6\u49d7\u49d8\u49d9\u49da\u49db\u49dc\u49dd\u49de\u49df\u49e0\u49e1\u49e2\u49e3\u49e4\u49e5\u49e6\u49e7\u49e8\u49e9\u49ea\u49eb\u49ec\u49ed\u49ee\u49ef\u49f0\u49f1\u49f2\u49f3\u49f4\u49f5\u49f6\u49f7\u49f8\u49f9\u49fa\u49fb\u49fc\u49fd\u49fe\u49ff\u4a00\u4a01\u4a02\u4a03\u4a04\u4a05\u4a06\u4a07\u4a08\u4a09\u4a0a\u4a0b\u4a0c\u4a0d\u4a0e\u4a0f\u4a10\u4a11\u4a12\u4a13\u4a14\u4a15\u4a16\u4a17\u4a18\u4a19\u4a1a\u4a1b\u4a1c\u4a1d\u4a1e\u4a1f\u4a20\u4a21\u4a22\u4a23\u4a24\u4a25\u4a26\u4a27\u4a28\u4a29\u4a2a\u4a2b\u4a2c\u4a2d\u4a2e\u4a2f\u4a30\u4a31\u4a32\u4a33\u4a34\u4a35\u4a36\u4a37\u4a38\u4a39\u4a3a\u4a3b\u4a3c\u4a3d\u4a3e\u4a3f\u4a40\u4a41\u4a42\u4a43\u4a44\u4a45\u4a46\u4a47\u4a48\u4a49\u4a4a\u4a4b\u4a4c\u4a4d\u4a4e\u4a4f\u4a50\u4a51\u4a52\u4a53\u4a54\u4a55\u4a56\u4a57\u4a58\u4a59\u4a5a\u4a5b\u4a5c\u4a5d\u4a5e\u4a5f\u4a60\u4a61\u4a62\u4a63\u4a64\u4a65\u4a66\u4a67\u4a68\u4a69\u4a6a\u4a6b\u4a6c\u4a6d\u4a6e\u4a6f\u4a70\u4a71\u4a72\u4a73\u4a74\u4a75\u4a76\u4a77\u4a78\u4a79\u4a7a\u4a7b\u4a7c\u4a7d\u4a7e\u4a7f\u4a80\u4a81\u4a82\u4a83\u4a84\u4a85\u4a86\u4a87\u4a88\u4a89\u4a8a\u4a8b\u4a8c\u4a8d\u4a8e\u4a8f\u4a90\u4a91\u4a92\u4a93\u4a94\u4a95\u4a96\u4a97\u4a98\u4a99\u4a9a\u4a9b\u4a9c\u4a9d\u4a9e\u4a9f\u4aa0\u4aa1\u4aa2\u4aa3\u4aa4\u4aa5\u4aa6\u4aa7\u4aa8\u4aa9\u4aaa\u4aab\u4aac\u4aad\u4aae\u4aaf\u4ab0\u4ab1\u4ab2\u4ab3\u4ab4\u4ab5\u4ab6\u4ab7\u4ab8\u4ab9\u4aba\u4abb\u4abc\u4abd\u4abe\u4abf\u4ac0\u4ac1\u4ac2\u4ac3\u4ac4\u4ac5\u4ac6\u4ac7\u4ac8\u4ac9\u4aca\u4acb\u4acc\u4acd\u4ace\u4acf\u4ad0\u4ad1\u4ad2\u4ad3\u4ad4\u4ad5\u4ad6\u4ad7\u4ad8\u4ad9\u4ada\u4adb\u4adc\u4add\u4ade\u4adf\u4ae0\u4ae1\u4ae2\u4ae3\u4ae4\u4ae5\u4ae6\u4ae7\u4ae8\u4ae9\u4aea\u4aeb\u4aec\u4aed\u4aee\u4aef\u4af0\u4af1\u4af2\u4af3\u4af4\u4af5\u4af6\u4af7\u4af8\u4af9\u4afa\u4afb\u4afc\u4afd\u4afe\u4aff\u4b00\u4b01\u4b02\u4b03\u4b04\u4b05\u4b06\u4b07\u4b08\u4b09\u4b0a\u4b0b\u4b0c\u4b0d\u4b0e\u4b0f\u4b10\u4b11\u4b12\u4b13\u4b14\u4b15\u4b16\u4b17\u4b18\u4b19\u4b1a\u4b1b\u4b1c\u4b1d\u4b1e\u4b1f\u4b20\u4b21\u4b22\u4b23\u4b24\u4b25\u4b26\u4b27\u4b28\u4b29\u4b2a\u4b2b\u4b2c\u4b2d\u4b2e\u4b2f\u4b30\u4b31\u4b32\u4b33\u4b34\u4b35\u4b36\u4b37\u4b38\u4b39\u4b3a\u4b3b\u4b3c\u4b3d\u4b3e\u4b3f\u4b40\u4b41\u4b42\u4b43\u4b44\u4b45\u4b46\u4b47\u4b48\u4b49\u4b4a\u4b4b\u4b4c\u4b4d\u4b4e\u4b4f\u4b50\u4b51\u4b52\u4b53\u4b54\u4b55\u4b56\u4b57\u4b58\u4b59\u4b5a\u4b5b\u4b5c\u4b5d\u4b5e\u4b5f\u4b60\u4b61\u4b62\u4b63\u4b64\u4b65\u4b66\u4b67\u4b68\u4b69\u4b6a\u4b6b\u4b6c\u4b6d\u4b6e\u4b6f\u4b70\u4b71\u4b72\u4b73\u4b74\u4b75\u4b76\u4b77\u4b78\u4b79\u4b7a\u4b7b\u4b7c\u4b7d\u4b7e\u4b7f\u4b80\u4b81\u4b82\u4b83\u4b84\u4b85\u4b86\u4b87\u4b88\u4b89\u4b8a\u4b8b\u4b8c\u4b8d\u4b8e\u4b8f\u4b90\u4b91\u4b92\u4b93\u4b94\u4b95\u4b96\u4b97\u4b98\u4b99\u4b9a\u4b9b\u4b9c\u4b9d\u4b9e\u4b9f\u4ba0\u4ba1\u4ba2\u4ba3\u4ba4\u4ba5\u4ba6\u4ba7\u4ba8\u4ba9\u4baa\u4bab\u4bac\u4bad\u4bae\u4baf\u4bb0\u4bb1\u4bb2\u4bb3\u4bb4\u4bb5\u4bb6\u4bb7\u4bb8\u4bb9\u4bba\u4bbb\u4bbc\u4bbd\u4bbe\u4bbf\u4bc0\u4bc1\u4bc2\u4bc3\u4bc4\u4bc5\u4bc6\u4bc7\u4bc8\u4bc9\u4bca\u4bcb\u4bcc\u4bcd\u4bce\u4bcf\u4bd0\u4bd1\u4bd2\u4bd3\u4bd4\u4bd5\u4bd6\u4bd7\u4bd8\u4bd9\u4bda\u4bdb\u4bdc\u4bdd\u4bde\u4bdf\u4be0\u4be1\u4be2\u4be3\u4be4\u4be5\u4be6\u4be7\u4be8\u4be9\u4bea\u4beb\u4bec\u4bed\u4bee\u4bef\u4bf0\u4bf1\u4bf2\u4bf3\u4bf4\u4bf5\u4bf6\u4bf7\u4bf8\u4bf9\u4bfa\u4bfb\u4bfc\u4bfd\u4bfe\u4bff\u4c00\u4c01\u4c02\u4c03\u4c04\u4c05\u4c06\u4c07\u4c08\u4c09\u4c0a\u4c0b\u4c0c\u4c0d\u4c0e\u4c0f\u4c10\u4c11\u4c12\u4c13\u4c14\u4c15\u4c16\u4c17\u4c18\u4c19\u4c1a\u4c1b\u4c1c\u4c1d\u4c1e\u4c1f\u4c20\u4c21\u4c22\u4c23\u4c24\u4c25\u4c26\u4c27\u4c28\u4c29\u4c2a\u4c2b\u4c2c\u4c2d\u4c2e\u4c2f\u4c30\u4c31\u4c32\u4c33\u4c34\u4c35\u4c36\u4c37\u4c38\u4c39\u4c3a\u4c3b\u4c3c\u4c3d\u4c3e\u4c3f\u4c40\u4c41\u4c42\u4c43\u4c44\u4c45\u4c46\u4c47\u4c48\u4c49\u4c4a\u4c4b\u4c4c\u4c4d\u4c4e\u4c4f\u4c50\u4c51\u4c52\u4c53\u4c54\u4c55\u4c56\u4c57\u4c58\u4c59\u4c5a\u4c5b\u4c5c\u4c5d\u4c5e\u4c5f\u4c60\u4c61\u4c62\u4c63\u4c64\u4c65\u4c66\u4c67\u4c68\u4c69\u4c6a\u4c6b\u4c6c\u4c6d\u4c6e\u4c6f\u4c70\u4c71\u4c72\u4c73\u4c74\u4c75\u4c76\u4c77\u4c78\u4c79\u4c7a\u4c7b\u4c7c\u4c7d\u4c7e\u4c7f\u4c80\u4c81\u4c82\u4c83\u4c84\u4c85\u4c86\u4c87\u4c88\u4c89\u4c8a\u4c8b\u4c8c\u4c8d\u4c8e\u4c8f\u4c90\u4c91\u4c92\u4c93\u4c94\u4c95\u4c96\u4c97\u4c98\u4c99\u4c9a\u4c9b\u4c9c\u4c9d\u4c9e\u4c9f\u4ca0\u4ca1\u4ca2\u4ca3\u4ca4\u4ca5\u4ca6\u4ca7\u4ca8\u4ca9\u4caa\u4cab\u4cac\u4cad\u4cae\u4caf\u4cb0\u4cb1\u4cb2\u4cb3\u4cb4\u4cb5\u4cb6\u4cb7\u4cb8\u4cb9\u4cba\u4cbb\u4cbc\u4cbd\u4cbe\u4cbf\u4cc0\u4cc1\u4cc2\u4cc3\u4cc4\u4cc5\u4cc6\u4cc7\u4cc8\u4cc9\u4cca\u4ccb\u4ccc\u4ccd\u4cce\u4ccf\u4cd0\u4cd1\u4cd2\u4cd3\u4cd4\u4cd5\u4cd6\u4cd7\u4cd8\u4cd9\u4cda\u4cdb\u4cdc\u4cdd\u4cde\u4cdf\u4ce0\u4ce1\u4ce2\u4ce3\u4ce4\u4ce5\u4ce6\u4ce7\u4ce8\u4ce9\u4cea\u4ceb\u4cec\u4ced\u4cee\u4cef\u4cf0\u4cf1\u4cf2\u4cf3\u4cf4\u4cf5\u4cf6\u4cf7\u4cf8\u4cf9\u4cfa\u4cfb\u4cfc\u4cfd\u4cfe\u4cff\u4d00\u4d01\u4d02\u4d03\u4d04\u4d05\u4d06\u4d07\u4d08\u4d09\u4d0a\u4d0b\u4d0c\u4d0d\u4d0e\u4d0f\u4d10\u4d11\u4d12\u4d13\u4d14\u4d15\u4d16\u4d17\u4d18\u4d19\u4d1a\u4d1b\u4d1c\u4d1d\u4d1e\u4d1f\u4d20\u4d21\u4d22\u4d23\u4d24\u4d25\u4d26\u4d27\u4d28\u4d29\u4d2a\u4d2b\u4d2c\u4d2d\u4d2e\u4d2f\u4d30\u4d31\u4d32\u4d33\u4d34\u4d35\u4d36\u4d37\u4d38\u4d39\u4d3a\u4d3b\u4d3c\u4d3d\u4d3e\u4d3f\u4d40\u4d41\u4d42\u4d43\u4d44\u4d45\u4d46\u4d47\u4d48\u4d49\u4d4a\u4d4b\u4d4c\u4d4d\u4d4e\u4d4f\u4d50\u4d51\u4d52\u4d53\u4d54\u4d55\u4d56\u4d57\u4d58\u4d59\u4d5a\u4d5b\u4d5c\u4d5d\u4d5e\u4d5f\u4d60\u4d61\u4d62\u4d63\u4d64\u4d65\u4d66\u4d67\u4d68\u4d69\u4d6a\u4d6b\u4d6c\u4d6d\u4d6e\u4d6f\u4d70\u4d71\u4d72\u4d73\u4d74\u4d75\u4d76\u4d77\u4d78\u4d79\u4d7a\u4d7b\u4d7c\u4d7d\u4d7e\u4d7f\u4d80\u4d81\u4d82\u4d83\u4d84\u4d85\u4d86\u4d87\u4d88\u4d89\u4d8a\u4d8b\u4d8c\u4d8d\u4d8e\u4d8f\u4d90\u4d91\u4d92\u4d93\u4d94\u4d95\u4d96\u4d97\u4d98\u4d99\u4d9a\u4d9b\u4d9c\u4d9d\u4d9e\u4d9f\u4da0\u4da1\u4da2\u4da3\u4da4\u4da5\u4da6\u4da7\u4da8\u4da9\u4daa\u4dab\u4dac\u4dad\u4dae\u4daf\u4db0\u4db1\u4db2\u4db3\u4db4\u4db5\u4e00\u4e01\u4e02\u4e03\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d\u4e0e\u4e0f\u4e10\u4e11\u4e12\u4e13\u4e14\u4e15\u4e16\u4e17\u4e18\u4e19\u4e1a\u4e1b\u4e1c\u4e1d\u4e1e\u4e1f\u4e20\u4e21\u4e22\u4e23\u4e24\u4e25\u4e26\u4e27\u4e28\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f\u4e30\u4e31\u4e32\u4e33\u4e34\u4e35\u4e36\u4e37\u4e38\u4e39\u4e3a\u4e3b\u4e3c\u4e3d\u4e3e\u4e3f\u4e40\u4e41\u4e42\u4e43\u4e44\u4e45\u4e46\u4e47\u4e48\u4e49\u4e4a\u4e4b\u4e4c\u4e4d\u4e4e\u4e4f\u4e50\u4e51\u4e52\u4e53\u4e54\u4e55\u4e56\u4e57\u4e58\u4e59\u4e5a\u4e5b\u4e5c\u4e5d\u4e5e\u4e5f\u4e60\u4e61\u4e62\u4e63\u4e64\u4e65\u4e66\u4e67\u4e68\u4e69\u4e6a\u4e6b\u4e6c\u4e6d\u4e6e\u4e6f\u4e70\u4e71\u4e72\u4e73\u4e74\u4e75\u4e76\u4e77\u4e78\u4e79\u4e7a\u4e7b\u4e7c\u4e7d\u4e7e\u4e7f\u4e80\u4e81\u4e82\u4e83\u4e84\u4e85\u4e86\u4e87\u4e88\u4e89\u4e8a\u4e8b\u4e8c\u4e8d\u4e8e\u4e8f\u4e90\u4e91\u4e92\u4e93\u4e94\u4e95\u4e96\u4e97\u4e98\u4e99\u4e9a\u4e9b\u4e9c\u4e9d\u4e9e\u4e9f\u4ea0\u4ea1\u4ea2\u4ea3\u4ea4\u4ea5\u4ea6\u4ea7\u4ea8\u4ea9\u4eaa\u4eab\u4eac\u4ead\u4eae\u4eaf\u4eb0\u4eb1\u4eb2\u4eb3\u4eb4\u4eb5\u4eb6\u4eb7\u4eb8\u4eb9\u4eba\u4ebb\u4ebc\u4ebd\u4ebe\u4ebf\u4ec0\u4ec1\u4ec2\u4ec3\u4ec4\u4ec5\u4ec6\u4ec7\u4ec8\u4ec9\u4eca\u4ecb\u4ecc\u4ecd\u4ece\u4ecf\u4ed0\u4ed1\u4ed2\u4ed3\u4ed4\u4ed5\u4ed6\u4ed7\u4ed8\u4ed9\u4eda\u4edb\u4edc\u4edd\u4ede\u4edf\u4ee0\u4ee1\u4ee2\u4ee3\u4ee4\u4ee5\u4ee6\u4ee7\u4ee8\u4ee9\u4eea\u4eeb\u4eec\u4eed\u4eee\u4eef\u4ef0\u4ef1\u4ef2\u4ef3\u4ef4\u4ef5\u4ef6\u4ef7\u4ef8\u4ef9\u4efa\u4efb\u4efc\u4efd\u4efe\u4eff\u4f00\u4f01\u4f02\u4f03\u4f04\u4f05\u4f06\u4f07\u4f08\u4f09\u4f0a\u4f0b\u4f0c\u4f0d\u4f0e\u4f0f\u4f10\u4f11\u4f12\u4f13\u4f14\u4f15\u4f16\u4f17\u4f18\u4f19\u4f1a\u4f1b\u4f1c\u4f1d\u4f1e\u4f1f\u4f20\u4f21\u4f22\u4f23\u4f24\u4f25\u4f26\u4f27\u4f28\u4f29\u4f2a\u4f2b\u4f2c\u4f2d\u4f2e\u4f2f\u4f30\u4f31\u4f32\u4f33\u4f34\u4f35\u4f36\u4f37\u4f38\u4f39\u4f3a\u4f3b\u4f3c\u4f3d\u4f3e\u4f3f\u4f40\u4f41\u4f42\u4f43\u4f44\u4f45\u4f46\u4f47\u4f48\u4f49\u4f4a\u4f4b\u4f4c\u4f4d\u4f4e\u4f4f\u4f50\u4f51\u4f52\u4f53\u4f54\u4f55\u4f56\u4f57\u4f58\u4f59\u4f5a\u4f5b\u4f5c\u4f5d\u4f5e\u4f5f\u4f60\u4f61\u4f62\u4f63\u4f64\u4f65\u4f66\u4f67\u4f68\u4f69\u4f6a\u4f6b\u4f6c\u4f6d\u4f6e\u4f6f\u4f70\u4f71\u4f72\u4f73\u4f74\u4f75\u4f76\u4f77\u4f78\u4f79\u4f7a\u4f7b\u4f7c\u4f7d\u4f7e\u4f7f\u4f80\u4f81\u4f82\u4f83\u4f84\u4f85\u4f86\u4f87\u4f88\u4f89\u4f8a\u4f8b\u4f8c\u4f8d\u4f8e\u4f8f\u4f90\u4f91\u4f92\u4f93\u4f94\u4f95\u4f96\u4f97\u4f98\u4f99\u4f9a\u4f9b\u4f9c\u4f9d\u4f9e\u4f9f\u4fa0\u4fa1\u4fa2\u4fa3\u4fa4\u4fa5\u4fa6\u4fa7\u4fa8\u4fa9\u4faa\u4fab\u4fac\u4fad\u4fae\u4faf\u4fb0\u4fb1\u4fb2\u4fb3\u4fb4\u4fb5\u4fb6\u4fb7\u4fb8\u4fb9\u4fba\u4fbb\u4fbc\u4fbd\u4fbe\u4fbf\u4fc0\u4fc1\u4fc2\u4fc3\u4fc4\u4fc5\u4fc6\u4fc7\u4fc8\u4fc9\u4fca\u4fcb\u4fcc\u4fcd\u4fce\u4fcf\u4fd0\u4fd1\u4fd2\u4fd3\u4fd4\u4fd5\u4fd6\u4fd7\u4fd8\u4fd9\u4fda\u4fdb\u4fdc\u4fdd\u4fde\u4fdf\u4fe0\u4fe1\u4fe2\u4fe3\u4fe4\u4fe5\u4fe6\u4fe7\u4fe8\u4fe9\u4fea\u4feb\u4fec\u4fed\u4fee\u4fef\u4ff0\u4ff1\u4ff2\u4ff3\u4ff4\u4ff5\u4ff6\u4ff7\u4ff8\u4ff9\u4ffa\u4ffb\u4ffc\u4ffd\u4ffe\u4fff\u5000\u5001\u5002\u5003\u5004\u5005\u5006\u5007\u5008\u5009\u500a\u500b\u500c\u500d\u500e\u500f\u5010\u5011\u5012\u5013\u5014\u5015\u5016\u5017\u5018\u5019\u501a\u501b\u501c\u501d\u501e\u501f\u5020\u5021\u5022\u5023\u5024\u5025\u5026\u5027\u5028\u5029\u502a\u502b\u502c\u502d\u502e\u502f\u5030\u5031\u5032\u5033\u5034\u5035\u5036\u5037\u5038\u5039\u503a\u503b\u503c\u503d\u503e\u503f\u5040\u5041\u5042\u5043\u5044\u5045\u5046\u5047\u5048\u5049\u504a\u504b\u504c\u504d\u504e\u504f\u5050\u5051\u5052\u5053\u5054\u5055\u5056\u5057\u5058\u5059\u505a\u505b\u505c\u505d\u505e\u505f\u5060\u5061\u5062\u5063\u5064\u5065\u5066\u5067\u5068\u5069\u506a\u506b\u506c\u506d\u506e\u506f\u5070\u5071\u5072\u5073\u5074\u5075\u5076\u5077\u5078\u5079\u507a\u507b\u507c\u507d\u507e\u507f\u5080\u5081\u5082\u5083\u5084\u5085\u5086\u5087\u5088\u5089\u508a\u508b\u508c\u508d\u508e\u508f\u5090\u5091\u5092\u5093\u5094\u5095\u5096\u5097\u5098\u5099\u509a\u509b\u509c\u509d\u509e\u509f\u50a0\u50a1\u50a2\u50a3\u50a4\u50a5\u50a6\u50a7\u50a8\u50a9\u50aa\u50ab\u50ac\u50ad\u50ae\u50af\u50b0\u50b1\u50b2\u50b3\u50b4\u50b5\u50b6\u50b7\u50b8\u50b9\u50ba\u50bb\u50bc\u50bd\u50be\u50bf\u50c0\u50c1\u50c2\u50c3\u50c4\u50c5\u50c6\u50c7\u50c8\u50c9\u50ca\u50cb\u50cc\u50cd\u50ce\u50cf\u50d0\u50d1\u50d2\u50d3\u50d4\u50d5\u50d6\u50d7\u50d8\u50d9\u50da\u50db\u50dc\u50dd\u50de\u50df\u50e0\u50e1\u50e2\u50e3\u50e4\u50e5\u50e6\u50e7\u50e8\u50e9\u50ea\u50eb\u50ec\u50ed\u50ee\u50ef\u50f0\u50f1\u50f2\u50f3\u50f4\u50f5\u50f6\u50f7\u50f8\u50f9\u50fa\u50fb\u50fc\u50fd\u50fe\u50ff\u5100\u5101\u5102\u5103\u5104\u5105\u5106\u5107\u5108\u5109\u510a\u510b\u510c\u510d\u510e\u510f\u5110\u5111\u5112\u5113\u5114\u5115\u5116\u5117\u5118\u5119\u511a\u511b\u511c\u511d\u511e\u511f\u5120\u5121\u5122\u5123\u5124\u5125\u5126\u5127\u5128\u5129\u512a\u512b\u512c\u512d\u512e\u512f\u5130\u5131\u5132\u5133\u5134\u5135\u5136\u5137\u5138\u5139\u513a\u513b\u513c\u513d\u513e\u513f\u5140\u5141\u5142\u5143\u5144\u5145\u5146\u5147\u5148\u5149\u514a\u514b\u514c\u514d\u514e\u514f\u5150\u5151\u5152\u5153\u5154\u5155\u5156\u5157\u5158\u5159\u515a\u515b\u515c\u515d\u515e\u515f\u5160\u5161\u5162\u5163\u5164\u5165\u5166\u5167\u5168\u5169\u516a\u516b\u516c\u516d\u516e\u516f\u5170\u5171\u5172\u5173\u5174\u5175\u5176\u5177\u5178\u5179\u517a\u517b\u517c\u517d\u517e\u517f\u5180\u5181\u5182\u5183\u5184\u5185\u5186\u5187\u5188\u5189\u518a\u518b\u518c\u518d\u518e\u518f\u5190\u5191\u5192\u5193\u5194\u5195\u5196\u5197\u5198\u5199\u519a\u519b\u519c\u519d\u519e\u519f\u51a0\u51a1\u51a2\u51a3\u51a4\u51a5\u51a6\u51a7\u51a8\u51a9\u51aa\u51ab\u51ac\u51ad\u51ae\u51af\u51b0\u51b1\u51b2\u51b3\u51b4\u51b5\u51b6\u51b7\u51b8\u51b9\u51ba\u51bb\u51bc\u51bd\u51be\u51bf\u51c0\u51c1\u51c2\u51c3\u51c4\u51c5\u51c6\u51c7\u51c8\u51c9\u51ca\u51cb\u51cc\u51cd\u51ce\u51cf\u51d0\u51d1\u51d2\u51d3\u51d4\u51d5\u51d6\u51d7\u51d8\u51d9\u51da\u51db\u51dc\u51dd\u51de\u51df\u51e0\u51e1\u51e2\u51e3\u51e4\u51e5\u51e6\u51e7\u51e8\u51e9\u51ea\u51eb\u51ec\u51ed\u51ee\u51ef\u51f0\u51f1\u51f2\u51f3\u51f4\u51f5\u51f6\u51f7\u51f8\u51f9\u51fa\u51fb\u51fc\u51fd\u51fe\u51ff\u5200\u5201\u5202\u5203\u5204\u5205\u5206\u5207\u5208\u5209\u520a\u520b\u520c\u520d\u520e\u520f\u5210\u5211\u5212\u5213\u5214\u5215\u5216\u5217\u5218\u5219\u521a\u521b\u521c\u521d\u521e\u521f\u5220\u5221\u5222\u5223\u5224\u5225\u5226\u5227\u5228\u5229\u522a\u522b\u522c\u522d\u522e\u522f\u5230\u5231\u5232\u5233\u5234\u5235\u5236\u5237\u5238\u5239\u523a\u523b\u523c\u523d\u523e\u523f\u5240\u5241\u5242\u5243\u5244\u5245\u5246\u5247\u5248\u5249\u524a\u524b\u524c\u524d\u524e\u524f\u5250\u5251\u5252\u5253\u5254\u5255\u5256\u5257\u5258\u5259\u525a\u525b\u525c\u525d\u525e\u525f\u5260\u5261\u5262\u5263\u5264\u5265\u5266\u5267\u5268\u5269\u526a\u526b\u526c\u526d\u526e\u526f\u5270\u5271\u5272\u5273\u5274\u5275\u5276\u5277\u5278\u5279\u527a\u527b\u527c\u527d\u527e\u527f\u5280\u5281\u5282\u5283\u5284\u5285\u5286\u5287\u5288\u5289\u528a\u528b\u528c\u528d\u528e\u528f\u5290\u5291\u5292\u5293\u5294\u5295\u5296\u5297\u5298\u5299\u529a\u529b\u529c\u529d\u529e\u529f\u52a0\u52a1\u52a2\u52a3\u52a4\u52a5\u52a6\u52a7\u52a8\u52a9\u52aa\u52ab\u52ac\u52ad\u52ae\u52af\u52b0\u52b1\u52b2\u52b3\u52b4\u52b5\u52b6\u52b7\u52b8\u52b9\u52ba\u52bb\u52bc\u52bd\u52be\u52bf\u52c0\u52c1\u52c2\u52c3\u52c4\u52c5\u52c6\u52c7\u52c8\u52c9\u52ca\u52cb\u52cc\u52cd\u52ce\u52cf\u52d0\u52d1\u52d2\u52d3\u52d4\u52d5\u52d6\u52d7\u52d8\u52d9\u52da\u52db\u52dc\u52dd\u52de\u52df\u52e0\u52e1\u52e2\u52e3\u52e4\u52e5\u52e6\u52e7\u52e8\u52e9\u52ea\u52eb\u52ec\u52ed\u52ee\u52ef\u52f0\u52f1\u52f2\u52f3\u52f4\u52f5\u52f6\u52f7\u52f8\u52f9\u52fa\u52fb\u52fc\u52fd\u52fe\u52ff\u5300\u5301\u5302\u5303\u5304\u5305\u5306\u5307\u5308\u5309\u530a\u530b\u530c\u530d\u530e\u530f\u5310\u5311\u5312\u5313\u5314\u5315\u5316\u5317\u5318\u5319\u531a\u531b\u531c\u531d\u531e\u531f\u5320\u5321\u5322\u5323\u5324\u5325\u5326\u5327\u5328\u5329\u532a\u532b\u532c\u532d\u532e\u532f\u5330\u5331\u5332\u5333\u5334\u5335\u5336\u5337\u5338\u5339\u533a\u533b\u533c\u533d\u533e\u533f\u5340\u5341\u5342\u5343\u5344\u5345\u5346\u5347\u5348\u5349\u534a\u534b\u534c\u534d\u534e\u534f\u5350\u5351\u5352\u5353\u5354\u5355\u5356\u5357\u5358\u5359\u535a\u535b\u535c\u535d\u535e\u535f\u5360\u5361\u5362\u5363\u5364\u5365\u5366\u5367\u5368\u5369\u536a\u536b\u536c\u536d\u536e\u536f\u5370\u5371\u5372\u5373\u5374\u5375\u5376\u5377\u5378\u5379\u537a\u537b\u537c\u537d\u537e\u537f\u5380\u5381\u5382\u5383\u5384\u5385\u5386\u5387\u5388\u5389\u538a\u538b\u538c\u538d\u538e\u538f\u5390\u5391\u5392\u5393\u5394\u5395\u5396\u5397\u5398\u5399\u539a\u539b\u539c\u539d\u539e\u539f\u53a0\u53a1\u53a2\u53a3\u53a4\u53a5\u53a6\u53a7\u53a8\u53a9\u53aa\u53ab\u53ac\u53ad\u53ae\u53af\u53b0\u53b1\u53b2\u53b3\u53b4\u53b5\u53b6\u53b7\u53b8\u53b9\u53ba\u53bb\u53bc\u53bd\u53be\u53bf\u53c0\u53c1\u53c2\u53c3\u53c4\u53c5\u53c6\u53c7\u53c8\u53c9\u53ca\u53cb\u53cc\u53cd\u53ce\u53cf\u53d0\u53d1\u53d2\u53d3\u53d4\u53d5\u53d6\u53d7\u53d8\u53d9\u53da\u53db\u53dc\u53dd\u53de\u53df\u53e0\u53e1\u53e2\u53e3\u53e4\u53e5\u53e6\u53e7\u53e8\u53e9\u53ea\u53eb\u53ec\u53ed\u53ee\u53ef\u53f0\u53f1\u53f2\u53f3\u53f4\u53f5\u53f6\u53f7\u53f8\u53f9\u53fa\u53fb\u53fc\u53fd\u53fe\u53ff\u5400\u5401\u5402\u5403\u5404\u5405\u5406\u5407\u5408\u5409\u540a\u540b\u540c\u540d\u540e\u540f\u5410\u5411\u5412\u5413\u5414\u5415\u5416\u5417\u5418\u5419\u541a\u541b\u541c\u541d\u541e\u541f\u5420\u5421\u5422\u5423\u5424\u5425\u5426\u5427\u5428\u5429\u542a\u542b\u542c\u542d\u542e\u542f\u5430\u5431\u5432\u5433\u5434\u5435\u5436\u5437\u5438\u5439\u543a\u543b\u543c\u543d\u543e\u543f\u5440\u5441\u5442\u5443\u5444\u5445\u5446\u5447\u5448\u5449\u544a\u544b\u544c\u544d\u544e\u544f\u5450\u5451\u5452\u5453\u5454\u5455\u5456\u5457\u5458\u5459\u545a\u545b\u545c\u545d\u545e\u545f\u5460\u5461\u5462\u5463\u5464\u5465\u5466\u5467\u5468\u5469\u546a\u546b\u546c\u546d\u546e\u546f\u5470\u5471\u5472\u5473\u5474\u5475\u5476\u5477\u5478\u5479\u547a\u547b\u547c\u547d\u547e\u547f\u5480\u5481\u5482\u5483\u5484\u5485\u5486\u5487\u5488\u5489\u548a\u548b\u548c\u548d\u548e\u548f\u5490\u5491\u5492\u5493\u5494\u5495\u5496\u5497\u5498\u5499\u549a\u549b\u549c\u549d\u549e\u549f\u54a0\u54a1\u54a2\u54a3\u54a4\u54a5\u54a6\u54a7\u54a8\u54a9\u54aa\u54ab\u54ac\u54ad\u54ae\u54af\u54b0\u54b1\u54b2\u54b3\u54b4\u54b5\u54b6\u54b7\u54b8\u54b9\u54ba\u54bb\u54bc\u54bd\u54be\u54bf\u54c0\u54c1\u54c2\u54c3\u54c4\u54c5\u54c6\u54c7\u54c8\u54c9\u54ca\u54cb\u54cc\u54cd\u54ce\u54cf\u54d0\u54d1\u54d2\u54d3\u54d4\u54d5\u54d6\u54d7\u54d8\u54d9\u54da\u54db\u54dc\u54dd\u54de\u54df\u54e0\u54e1\u54e2\u54e3\u54e4\u54e5\u54e6\u54e7\u54e8\u54e9\u54ea\u54eb\u54ec\u54ed\u54ee\u54ef\u54f0\u54f1\u54f2\u54f3\u54f4\u54f5\u54f6\u54f7\u54f8\u54f9\u54fa\u54fb\u54fc\u54fd\u54fe\u54ff\u5500\u5501\u5502\u5503\u5504\u5505\u5506\u5507\u5508\u5509\u550a\u550b\u550c\u550d\u550e\u550f\u5510\u5511\u5512\u5513\u5514\u5515\u5516\u5517\u5518\u5519\u551a\u551b\u551c\u551d\u551e\u551f\u5520\u5521\u5522\u5523\u5524\u5525\u5526\u5527\u5528\u5529\u552a\u552b\u552c\u552d\u552e\u552f\u5530\u5531\u5532\u5533\u5534\u5535\u5536\u5537\u5538\u5539\u553a\u553b\u553c\u553d\u553e\u553f\u5540\u5541\u5542\u5543\u5544\u5545\u5546\u5547\u5548\u5549\u554a\u554b\u554c\u554d\u554e\u554f\u5550\u5551\u5552\u5553\u5554\u5555\u5556\u5557\u5558\u5559\u555a\u555b\u555c\u555d\u555e\u555f\u5560\u5561\u5562\u5563\u5564\u5565\u5566\u5567\u5568\u5569\u556a\u556b\u556c\u556d\u556e\u556f\u5570\u5571\u5572\u5573\u5574\u5575\u5576\u5577\u5578\u5579\u557a\u557b\u557c\u557d\u557e\u557f\u5580\u5581\u5582\u5583\u5584\u5585\u5586\u5587\u5588\u5589\u558a\u558b\u558c\u558d\u558e\u558f\u5590\u5591\u5592\u5593\u5594\u5595\u5596\u5597\u5598\u5599\u559a\u559b\u559c\u559d\u559e\u559f\u55a0\u55a1\u55a2\u55a3\u55a4\u55a5\u55a6\u55a7\u55a8\u55a9\u55aa\u55ab\u55ac\u55ad\u55ae\u55af\u55b0\u55b1\u55b2\u55b3\u55b4\u55b5\u55b6\u55b7\u55b8\u55b9\u55ba\u55bb\u55bc\u55bd\u55be\u55bf\u55c0\u55c1\u55c2\u55c3\u55c4\u55c5\u55c6\u55c7\u55c8\u55c9\u55ca\u55cb\u55cc\u55cd\u55ce\u55cf\u55d0\u55d1\u55d2\u55d3\u55d4\u55d5\u55d6\u55d7\u55d8\u55d9\u55da\u55db\u55dc\u55dd\u55de\u55df\u55e0\u55e1\u55e2\u55e3\u55e4\u55e5\u55e6\u55e7\u55e8\u55e9\u55ea\u55eb\u55ec\u55ed\u55ee\u55ef\u55f0\u55f1\u55f2\u55f3\u55f4\u55f5\u55f6\u55f7\u55f8\u55f9\u55fa\u55fb\u55fc\u55fd\u55fe\u55ff\u5600\u5601\u5602\u5603\u5604\u5605\u5606\u5607\u5608\u5609\u560a\u560b\u560c\u560d\u560e\u560f\u5610\u5611\u5612\u5613\u5614\u5615\u5616\u5617\u5618\u5619\u561a\u561b\u561c\u561d\u561e\u561f\u5620\u5621\u5622\u5623\u5624\u5625\u5626\u5627\u5628\u5629\u562a\u562b\u562c\u562d\u562e\u562f\u5630\u5631\u5632\u5633\u5634\u5635\u5636\u5637\u5638\u5639\u563a\u563b\u563c\u563d\u563e\u563f\u5640\u5641\u5642\u5643\u5644\u5645\u5646\u5647\u5648\u5649\u564a\u564b\u564c\u564d\u564e\u564f\u5650\u5651\u5652\u5653\u5654\u5655\u5656\u5657\u5658\u5659\u565a\u565b\u565c\u565d\u565e\u565f\u5660\u5661\u5662\u5663\u5664\u5665\u5666\u5667\u5668\u5669\u566a\u566b\u566c\u566d\u566e\u566f\u5670\u5671\u5672\u5673\u5674\u5675\u5676\u5677\u5678\u5679\u567a\u567b\u567c\u567d\u567e\u567f\u5680\u5681\u5682\u5683\u5684\u5685\u5686\u5687\u5688\u5689\u568a\u568b\u568c\u568d\u568e\u568f\u5690\u5691\u5692\u5693\u5694\u5695\u5696\u5697\u5698\u5699\u569a\u569b\u569c\u569d\u569e\u569f\u56a0\u56a1\u56a2\u56a3\u56a4\u56a5\u56a6\u56a7\u56a8\u56a9\u56aa\u56ab\u56ac\u56ad\u56ae\u56af\u56b0\u56b1\u56b2\u56b3\u56b4\u56b5\u56b6\u56b7\u56b8\u56b9\u56ba\u56bb\u56bc\u56bd\u56be\u56bf\u56c0\u56c1\u56c2\u56c3\u56c4\u56c5\u56c6\u56c7\u56c8\u56c9\u56ca\u56cb\u56cc\u56cd\u56ce\u56cf\u56d0\u56d1\u56d2\u56d3\u56d4\u56d5\u56d6\u56d7\u56d8\u56d9\u56da\u56db\u56dc\u56dd\u56de\u56df\u56e0\u56e1\u56e2\u56e3\u56e4\u56e5\u56e6\u56e7\u56e8\u56e9\u56ea\u56eb\u56ec\u56ed\u56ee\u56ef\u56f0\u56f1\u56f2\u56f3\u56f4\u56f5\u56f6\u56f7\u56f8\u56f9\u56fa\u56fb\u56fc\u56fd\u56fe\u56ff\u5700\u5701\u5702\u5703\u5704\u5705\u5706\u5707\u5708\u5709\u570a\u570b\u570c\u570d\u570e\u570f\u5710\u5711\u5712\u5713\u5714\u5715\u5716\u5717\u5718\u5719\u571a\u571b\u571c\u571d\u571e\u571f\u5720\u5721\u5722\u5723\u5724\u5725\u5726\u5727\u5728\u5729\u572a\u572b\u572c\u572d\u572e\u572f\u5730\u5731\u5732\u5733\u5734\u5735\u5736\u5737\u5738\u5739\u573a\u573b\u573c\u573d\u573e\u573f\u5740\u5741\u5742\u5743\u5744\u5745\u5746\u5747\u5748\u5749\u574a\u574b\u574c\u574d\u574e\u574f\u5750\u5751\u5752\u5753\u5754\u5755\u5756\u5757\u5758\u5759\u575a\u575b\u575c\u575d\u575e\u575f\u5760\u5761\u5762\u5763\u5764\u5765\u5766\u5767\u5768\u5769\u576a\u576b\u576c\u576d\u576e\u576f\u5770\u5771\u5772\u5773\u5774\u5775\u5776\u5777\u5778\u5779\u577a\u577b\u577c\u577d\u577e\u577f\u5780\u5781\u5782\u5783\u5784\u5785\u5786\u5787\u5788\u5789\u578a\u578b\u578c\u578d\u578e\u578f\u5790\u5791\u5792\u5793\u5794\u5795\u5796\u5797\u5798\u5799\u579a\u579b\u579c\u579d\u579e\u579f\u57a0\u57a1\u57a2\u57a3\u57a4\u57a5\u57a6\u57a7\u57a8\u57a9\u57aa\u57ab\u57ac\u57ad\u57ae\u57af\u57b0\u57b1\u57b2\u57b3\u57b4\u57b5\u57b6\u57b7\u57b8\u57b9\u57ba\u57bb\u57bc\u57bd\u57be\u57bf\u57c0\u57c1\u57c2\u57c3\u57c4\u57c5\u57c6\u57c7\u57c8\u57c9\u57ca\u57cb\u57cc\u57cd\u57ce\u57cf\u57d0\u57d1\u57d2\u57d3\u57d4\u57d5\u57d6\u57d7\u57d8\u57d9\u57da\u57db\u57dc\u57dd\u57de\u57df\u57e0\u57e1\u57e2\u57e3\u57e4\u57e5\u57e6\u57e7\u57e8\u57e9\u57ea\u57eb\u57ec\u57ed\u57ee\u57ef\u57f0\u57f1\u57f2\u57f3\u57f4\u57f5\u57f6\u57f7\u57f8\u57f9\u57fa\u57fb\u57fc\u57fd\u57fe\u57ff\u5800\u5801\u5802\u5803\u5804\u5805\u5806\u5807\u5808\u5809\u580a\u580b\u580c\u580d\u580e\u580f\u5810\u5811\u5812\u5813\u5814\u5815\u5816\u5817\u5818\u5819\u581a\u581b\u581c\u581d\u581e\u581f\u5820\u5821\u5822\u5823\u5824\u5825\u5826\u5827\u5828\u5829\u582a\u582b\u582c\u582d\u582e\u582f\u5830\u5831\u5832\u5833\u5834\u5835\u5836\u5837\u5838\u5839\u583a\u583b\u583c\u583d\u583e\u583f\u5840\u5841\u5842\u5843\u5844\u5845\u5846\u5847\u5848\u5849\u584a\u584b\u584c\u584d\u584e\u584f\u5850\u5851\u5852\u5853\u5854\u5855\u5856\u5857\u5858\u5859\u585a\u585b\u585c\u585d\u585e\u585f\u5860\u5861\u5862\u5863\u5864\u5865\u5866\u5867\u5868\u5869\u586a\u586b\u586c\u586d\u586e\u586f\u5870\u5871\u5872\u5873\u5874\u5875\u5876\u5877\u5878\u5879\u587a\u587b\u587c\u587d\u587e\u587f\u5880\u5881\u5882\u5883\u5884\u5885\u5886\u5887\u5888\u5889\u588a\u588b\u588c\u588d\u588e\u588f\u5890\u5891\u5892\u5893\u5894\u5895\u5896\u5897\u5898\u5899\u589a\u589b\u589c\u589d\u589e\u589f\u58a0\u58a1\u58a2\u58a3\u58a4\u58a5\u58a6\u58a7\u58a8\u58a9\u58aa\u58ab\u58ac\u58ad\u58ae\u58af\u58b0\u58b1\u58b2\u58b3\u58b4\u58b5\u58b6\u58b7\u58b8\u58b9\u58ba\u58bb\u58bc\u58bd\u58be\u58bf\u58c0\u58c1\u58c2\u58c3\u58c4\u58c5\u58c6\u58c7\u58c8\u58c9\u58ca\u58cb\u58cc\u58cd\u58ce\u58cf\u58d0\u58d1\u58d2\u58d3\u58d4\u58d5\u58d6\u58d7\u58d8\u58d9\u58da\u58db\u58dc\u58dd\u58de\u58df\u58e0\u58e1\u58e2\u58e3\u58e4\u58e5\u58e6\u58e7\u58e8\u58e9\u58ea\u58eb\u58ec\u58ed\u58ee\u58ef\u58f0\u58f1\u58f2\u58f3\u58f4\u58f5\u58f6\u58f7\u58f8\u58f9\u58fa\u58fb\u58fc\u58fd\u58fe\u58ff\u5900\u5901\u5902\u5903\u5904\u5905\u5906\u5907\u5908\u5909\u590a\u590b\u590c\u590d\u590e\u590f\u5910\u5911\u5912\u5913\u5914\u5915\u5916\u5917\u5918\u5919\u591a\u591b\u591c\u591d\u591e\u591f\u5920\u5921\u5922\u5923\u5924\u5925\u5926\u5927\u5928\u5929\u592a\u592b\u592c\u592d\u592e\u592f\u5930\u5931\u5932\u5933\u5934\u5935\u5936\u5937\u5938\u5939\u593a\u593b\u593c\u593d\u593e\u593f\u5940\u5941\u5942\u5943\u5944\u5945\u5946\u5947\u5948\u5949\u594a\u594b\u594c\u594d\u594e\u594f\u5950\u5951\u5952\u5953\u5954\u5955\u5956\u5957\u5958\u5959\u595a\u595b\u595c\u595d\u595e\u595f\u5960\u5961\u5962\u5963\u5964\u5965\u5966\u5967\u5968\u5969\u596a\u596b\u596c\u596d\u596e\u596f\u5970\u5971\u5972\u5973\u5974\u5975\u5976\u5977\u5978\u5979\u597a\u597b\u597c\u597d\u597e\u597f\u5980\u5981\u5982\u5983\u5984\u5985\u5986\u5987\u5988\u5989\u598a\u598b\u598c\u598d\u598e\u598f\u5990\u5991\u5992\u5993\u5994\u5995\u5996\u5997\u5998\u5999\u599a\u599b\u599c\u599d\u599e\u599f\u59a0\u59a1\u59a2\u59a3\u59a4\u59a5\u59a6\u59a7\u59a8\u59a9\u59aa\u59ab\u59ac\u59ad\u59ae\u59af\u59b0\u59b1\u59b2\u59b3\u59b4\u59b5\u59b6\u59b7\u59b8\u59b9\u59ba\u59bb\u59bc\u59bd\u59be\u59bf\u59c0\u59c1\u59c2\u59c3\u59c4\u59c5\u59c6\u59c7\u59c8\u59c9\u59ca\u59cb\u59cc\u59cd\u59ce\u59cf\u59d0\u59d1\u59d2\u59d3\u59d4\u59d5\u59d6\u59d7\u59d8\u59d9\u59da\u59db\u59dc\u59dd\u59de\u59df\u59e0\u59e1\u59e2\u59e3\u59e4\u59e5\u59e6\u59e7\u59e8\u59e9\u59ea\u59eb\u59ec\u59ed\u59ee\u59ef\u59f0\u59f1\u59f2\u59f3\u59f4\u59f5\u59f6\u59f7\u59f8\u59f9\u59fa\u59fb\u59fc\u59fd\u59fe\u59ff\u5a00\u5a01\u5a02\u5a03\u5a04\u5a05\u5a06\u5a07\u5a08\u5a09\u5a0a\u5a0b\u5a0c\u5a0d\u5a0e\u5a0f\u5a10\u5a11\u5a12\u5a13\u5a14\u5a15\u5a16\u5a17\u5a18\u5a19\u5a1a\u5a1b\u5a1c\u5a1d\u5a1e\u5a1f\u5a20\u5a21\u5a22\u5a23\u5a24\u5a25\u5a26\u5a27\u5a28\u5a29\u5a2a\u5a2b\u5a2c\u5a2d\u5a2e\u5a2f\u5a30\u5a31\u5a32\u5a33\u5a34\u5a35\u5a36\u5a37\u5a38\u5a39\u5a3a\u5a3b\u5a3c\u5a3d\u5a3e\u5a3f\u5a40\u5a41\u5a42\u5a43\u5a44\u5a45\u5a46\u5a47\u5a48\u5a49\u5a4a\u5a4b\u5a4c\u5a4d\u5a4e\u5a4f\u5a50\u5a51\u5a52\u5a53\u5a54\u5a55\u5a56\u5a57\u5a58\u5a59\u5a5a\u5a5b\u5a5c\u5a5d\u5a5e\u5a5f\u5a60\u5a61\u5a62\u5a63\u5a64\u5a65\u5a66\u5a67\u5a68\u5a69\u5a6a\u5a6b\u5a6c\u5a6d\u5a6e\u5a6f\u5a70\u5a71\u5a72\u5a73\u5a74\u5a75\u5a76\u5a77\u5a78\u5a79\u5a7a\u5a7b\u5a7c\u5a7d\u5a7e\u5a7f\u5a80\u5a81\u5a82\u5a83\u5a84\u5a85\u5a86\u5a87\u5a88\u5a89\u5a8a\u5a8b\u5a8c\u5a8d\u5a8e\u5a8f\u5a90\u5a91\u5a92\u5a93\u5a94\u5a95\u5a96\u5a97\u5a98\u5a99\u5a9a\u5a9b\u5a9c\u5a9d\u5a9e\u5a9f\u5aa0\u5aa1\u5aa2\u5aa3\u5aa4\u5aa5\u5aa6\u5aa7\u5aa8\u5aa9\u5aaa\u5aab\u5aac\u5aad\u5aae\u5aaf\u5ab0\u5ab1\u5ab2\u5ab3\u5ab4\u5ab5\u5ab6\u5ab7\u5ab8\u5ab9\u5aba\u5abb\u5abc\u5abd\u5abe\u5abf\u5ac0\u5ac1\u5ac2\u5ac3\u5ac4\u5ac5\u5ac6\u5ac7\u5ac8\u5ac9\u5aca\u5acb\u5acc\u5acd\u5ace\u5acf\u5ad0\u5ad1\u5ad2\u5ad3\u5ad4\u5ad5\u5ad6\u5ad7\u5ad8\u5ad9\u5ada\u5adb\u5adc\u5add\u5ade\u5adf\u5ae0\u5ae1\u5ae2\u5ae3\u5ae4\u5ae5\u5ae6\u5ae7\u5ae8\u5ae9\u5aea\u5aeb\u5aec\u5aed\u5aee\u5aef\u5af0\u5af1\u5af2\u5af3\u5af4\u5af5\u5af6\u5af7\u5af8\u5af9\u5afa\u5afb\u5afc\u5afd\u5afe\u5aff\u5b00\u5b01\u5b02\u5b03\u5b04\u5b05\u5b06\u5b07\u5b08\u5b09\u5b0a\u5b0b\u5b0c\u5b0d\u5b0e\u5b0f\u5b10\u5b11\u5b12\u5b13\u5b14\u5b15\u5b16\u5b17\u5b18\u5b19\u5b1a\u5b1b\u5b1c\u5b1d\u5b1e\u5b1f\u5b20\u5b21\u5b22\u5b23\u5b24\u5b25\u5b26\u5b27\u5b28\u5b29\u5b2a\u5b2b\u5b2c\u5b2d\u5b2e\u5b2f\u5b30\u5b31\u5b32\u5b33\u5b34\u5b35\u5b36\u5b37\u5b38\u5b39\u5b3a\u5b3b\u5b3c\u5b3d\u5b3e\u5b3f\u5b40\u5b41\u5b42\u5b43\u5b44\u5b45\u5b46\u5b47\u5b48\u5b49\u5b4a\u5b4b\u5b4c\u5b4d\u5b4e\u5b4f\u5b50\u5b51\u5b52\u5b53\u5b54\u5b55\u5b56\u5b57\u5b58\u5b59\u5b5a\u5b5b\u5b5c\u5b5d\u5b5e\u5b5f\u5b60\u5b61\u5b62\u5b63\u5b64\u5b65\u5b66\u5b67\u5b68\u5b69\u5b6a\u5b6b\u5b6c\u5b6d\u5b6e\u5b6f\u5b70\u5b71\u5b72\u5b73\u5b74\u5b75\u5b76\u5b77\u5b78\u5b79\u5b7a\u5b7b\u5b7c\u5b7d\u5b7e\u5b7f\u5b80\u5b81\u5b82\u5b83\u5b84\u5b85\u5b86\u5b87\u5b88\u5b89\u5b8a\u5b8b\u5b8c\u5b8d\u5b8e\u5b8f\u5b90\u5b91\u5b92\u5b93\u5b94\u5b95\u5b96\u5b97\u5b98\u5b99\u5b9a\u5b9b\u5b9c\u5b9d\u5b9e\u5b9f\u5ba0\u5ba1\u5ba2\u5ba3\u5ba4\u5ba5\u5ba6\u5ba7\u5ba8\u5ba9\u5baa\u5bab\u5bac\u5bad\u5bae\u5baf\u5bb0\u5bb1\u5bb2\u5bb3\u5bb4\u5bb5\u5bb6\u5bb7\u5bb8\u5bb9\u5bba\u5bbb\u5bbc\u5bbd\u5bbe\u5bbf\u5bc0\u5bc1\u5bc2\u5bc3\u5bc4\u5bc5\u5bc6\u5bc7\u5bc8\u5bc9\u5bca\u5bcb\u5bcc\u5bcd\u5bce\u5bcf\u5bd0\u5bd1\u5bd2\u5bd3\u5bd4\u5bd5\u5bd6\u5bd7\u5bd8\u5bd9\u5bda\u5bdb\u5bdc\u5bdd\u5bde\u5bdf\u5be0\u5be1\u5be2\u5be3\u5be4\u5be5\u5be6\u5be7\u5be8\u5be9\u5bea\u5beb\u5bec\u5bed\u5bee\u5bef\u5bf0\u5bf1\u5bf2\u5bf3\u5bf4\u5bf5\u5bf6\u5bf7\u5bf8\u5bf9\u5bfa\u5bfb\u5bfc\u5bfd\u5bfe\u5bff\u5c00\u5c01\u5c02\u5c03\u5c04\u5c05\u5c06\u5c07\u5c08\u5c09\u5c0a\u5c0b\u5c0c\u5c0d\u5c0e\u5c0f\u5c10\u5c11\u5c12\u5c13\u5c14\u5c15\u5c16\u5c17\u5c18\u5c19\u5c1a\u5c1b\u5c1c\u5c1d\u5c1e\u5c1f\u5c20\u5c21\u5c22\u5c23\u5c24\u5c25\u5c26\u5c27\u5c28\u5c29\u5c2a\u5c2b\u5c2c\u5c2d\u5c2e\u5c2f\u5c30\u5c31\u5c32\u5c33\u5c34\u5c35\u5c36\u5c37\u5c38\u5c39\u5c3a\u5c3b\u5c3c\u5c3d\u5c3e\u5c3f\u5c40\u5c41\u5c42\u5c43\u5c44\u5c45\u5c46\u5c47\u5c48\u5c49\u5c4a\u5c4b\u5c4c\u5c4d\u5c4e\u5c4f\u5c50\u5c51\u5c52\u5c53\u5c54\u5c55\u5c56\u5c57\u5c58\u5c59\u5c5a\u5c5b\u5c5c\u5c5d\u5c5e\u5c5f\u5c60\u5c61\u5c62\u5c63\u5c64\u5c65\u5c66\u5c67\u5c68\u5c69\u5c6a\u5c6b\u5c6c\u5c6d\u5c6e\u5c6f\u5c70\u5c71\u5c72\u5c73\u5c74\u5c75\u5c76\u5c77\u5c78\u5c79\u5c7a\u5c7b\u5c7c\u5c7d\u5c7e\u5c7f\u5c80\u5c81\u5c82\u5c83\u5c84\u5c85\u5c86\u5c87\u5c88\u5c89\u5c8a\u5c8b\u5c8c\u5c8d\u5c8e\u5c8f\u5c90\u5c91\u5c92\u5c93\u5c94\u5c95\u5c96\u5c97\u5c98\u5c99\u5c9a\u5c9b\u5c9c\u5c9d\u5c9e\u5c9f\u5ca0\u5ca1\u5ca2\u5ca3\u5ca4\u5ca5\u5ca6\u5ca7\u5ca8\u5ca9\u5caa\u5cab\u5cac\u5cad\u5cae\u5caf\u5cb0\u5cb1\u5cb2\u5cb3\u5cb4\u5cb5\u5cb6\u5cb7\u5cb8\u5cb9\u5cba\u5cbb\u5cbc\u5cbd\u5cbe\u5cbf\u5cc0\u5cc1\u5cc2\u5cc3\u5cc4\u5cc5\u5cc6\u5cc7\u5cc8\u5cc9\u5cca\u5ccb\u5ccc\u5ccd\u5cce\u5ccf\u5cd0\u5cd1\u5cd2\u5cd3\u5cd4\u5cd5\u5cd6\u5cd7\u5cd8\u5cd9\u5cda\u5cdb\u5cdc\u5cdd\u5cde\u5cdf\u5ce0\u5ce1\u5ce2\u5ce3\u5ce4\u5ce5\u5ce6\u5ce7\u5ce8\u5ce9\u5cea\u5ceb\u5cec\u5ced\u5cee\u5cef\u5cf0\u5cf1\u5cf2\u5cf3\u5cf4\u5cf5\u5cf6\u5cf7\u5cf8\u5cf9\u5cfa\u5cfb\u5cfc\u5cfd\u5cfe\u5cff\u5d00\u5d01\u5d02\u5d03\u5d04\u5d05\u5d06\u5d07\u5d08\u5d09\u5d0a\u5d0b\u5d0c\u5d0d\u5d0e\u5d0f\u5d10\u5d11\u5d12\u5d13\u5d14\u5d15\u5d16\u5d17\u5d18\u5d19\u5d1a\u5d1b\u5d1c\u5d1d\u5d1e\u5d1f\u5d20\u5d21\u5d22\u5d23\u5d24\u5d25\u5d26\u5d27\u5d28\u5d29\u5d2a\u5d2b\u5d2c\u5d2d\u5d2e\u5d2f\u5d30\u5d31\u5d32\u5d33\u5d34\u5d35\u5d36\u5d37\u5d38\u5d39\u5d3a\u5d3b\u5d3c\u5d3d\u5d3e\u5d3f\u5d40\u5d41\u5d42\u5d43\u5d44\u5d45\u5d46\u5d47\u5d48\u5d49\u5d4a\u5d4b\u5d4c\u5d4d\u5d4e\u5d4f\u5d50\u5d51\u5d52\u5d53\u5d54\u5d55\u5d56\u5d57\u5d58\u5d59\u5d5a\u5d5b\u5d5c\u5d5d\u5d5e\u5d5f\u5d60\u5d61\u5d62\u5d63\u5d64\u5d65\u5d66\u5d67\u5d68\u5d69\u5d6a\u5d6b\u5d6c\u5d6d\u5d6e\u5d6f\u5d70\u5d71\u5d72\u5d73\u5d74\u5d75\u5d76\u5d77\u5d78\u5d79\u5d7a\u5d7b\u5d7c\u5d7d\u5d7e\u5d7f\u5d80\u5d81\u5d82\u5d83\u5d84\u5d85\u5d86\u5d87\u5d88\u5d89\u5d8a\u5d8b\u5d8c\u5d8d\u5d8e\u5d8f\u5d90\u5d91\u5d92\u5d93\u5d94\u5d95\u5d96\u5d97\u5d98\u5d99\u5d9a\u5d9b\u5d9c\u5d9d\u5d9e\u5d9f\u5da0\u5da1\u5da2\u5da3\u5da4\u5da5\u5da6\u5da7\u5da8\u5da9\u5daa\u5dab\u5dac\u5dad\u5dae\u5daf\u5db0\u5db1\u5db2\u5db3\u5db4\u5db5\u5db6\u5db7\u5db8\u5db9\u5dba\u5dbb\u5dbc\u5dbd\u5dbe\u5dbf\u5dc0\u5dc1\u5dc2\u5dc3\u5dc4\u5dc5\u5dc6\u5dc7\u5dc8\u5dc9\u5dca\u5dcb\u5dcc\u5dcd\u5dce\u5dcf\u5dd0\u5dd1\u5dd2\u5dd3\u5dd4\u5dd5\u5dd6\u5dd7\u5dd8\u5dd9\u5dda\u5ddb\u5ddc\u5ddd\u5dde\u5ddf\u5de0\u5de1\u5de2\u5de3\u5de4\u5de5\u5de6\u5de7\u5de8\u5de9\u5dea\u5deb\u5dec\u5ded\u5dee\u5def\u5df0\u5df1\u5df2\u5df3\u5df4\u5df5\u5df6\u5df7\u5df8\u5df9\u5dfa\u5dfb\u5dfc\u5dfd\u5dfe\u5dff\u5e00\u5e01\u5e02\u5e03\u5e04\u5e05\u5e06\u5e07\u5e08\u5e09\u5e0a\u5e0b\u5e0c\u5e0d\u5e0e\u5e0f\u5e10\u5e11\u5e12\u5e13\u5e14\u5e15\u5e16\u5e17\u5e18\u5e19\u5e1a\u5e1b\u5e1c\u5e1d\u5e1e\u5e1f\u5e20\u5e21\u5e22\u5e23\u5e24\u5e25\u5e26\u5e27\u5e28\u5e29\u5e2a\u5e2b\u5e2c\u5e2d\u5e2e\u5e2f\u5e30\u5e31\u5e32\u5e33\u5e34\u5e35\u5e36\u5e37\u5e38\u5e39\u5e3a\u5e3b\u5e3c\u5e3d\u5e3e\u5e3f\u5e40\u5e41\u5e42\u5e43\u5e44\u5e45\u5e46\u5e47\u5e48\u5e49\u5e4a\u5e4b\u5e4c\u5e4d\u5e4e\u5e4f\u5e50\u5e51\u5e52\u5e53\u5e54\u5e55\u5e56\u5e57\u5e58\u5e59\u5e5a\u5e5b\u5e5c\u5e5d\u5e5e\u5e5f\u5e60\u5e61\u5e62\u5e63\u5e64\u5e65\u5e66\u5e67\u5e68\u5e69\u5e6a\u5e6b\u5e6c\u5e6d\u5e6e\u5e6f\u5e70\u5e71\u5e72\u5e73\u5e74\u5e75\u5e76\u5e77\u5e78\u5e79\u5e7a\u5e7b\u5e7c\u5e7d\u5e7e\u5e7f\u5e80\u5e81\u5e82\u5e83\u5e84\u5e85\u5e86\u5e87\u5e88\u5e89\u5e8a\u5e8b\u5e8c\u5e8d\u5e8e\u5e8f\u5e90\u5e91\u5e92\u5e93\u5e94\u5e95\u5e96\u5e97\u5e98\u5e99\u5e9a\u5e9b\u5e9c\u5e9d\u5e9e\u5e9f\u5ea0\u5ea1\u5ea2\u5ea3\u5ea4\u5ea5\u5ea6\u5ea7\u5ea8\u5ea9\u5eaa\u5eab\u5eac\u5ead\u5eae\u5eaf\u5eb0\u5eb1\u5eb2\u5eb3\u5eb4\u5eb5\u5eb6\u5eb7\u5eb8\u5eb9\u5eba\u5ebb\u5ebc\u5ebd\u5ebe\u5ebf\u5ec0\u5ec1\u5ec2\u5ec3\u5ec4\u5ec5\u5ec6\u5ec7\u5ec8\u5ec9\u5eca\u5ecb\u5ecc\u5ecd\u5ece\u5ecf\u5ed0\u5ed1\u5ed2\u5ed3\u5ed4\u5ed5\u5ed6\u5ed7\u5ed8\u5ed9\u5eda\u5edb\u5edc\u5edd\u5ede\u5edf\u5ee0\u5ee1\u5ee2\u5ee3\u5ee4\u5ee5\u5ee6\u5ee7\u5ee8\u5ee9\u5eea\u5eeb\u5eec\u5eed\u5eee\u5eef\u5ef0\u5ef1\u5ef2\u5ef3\u5ef4\u5ef5\u5ef6\u5ef7\u5ef8\u5ef9\u5efa\u5efb\u5efc\u5efd\u5efe\u5eff\u5f00\u5f01\u5f02\u5f03\u5f04\u5f05\u5f06\u5f07\u5f08\u5f09\u5f0a\u5f0b\u5f0c\u5f0d\u5f0e\u5f0f\u5f10\u5f11\u5f12\u5f13\u5f14\u5f15\u5f16\u5f17\u5f18\u5f19\u5f1a\u5f1b\u5f1c\u5f1d\u5f1e\u5f1f\u5f20\u5f21\u5f22\u5f23\u5f24\u5f25\u5f26\u5f27\u5f28\u5f29\u5f2a\u5f2b\u5f2c\u5f2d\u5f2e\u5f2f\u5f30\u5f31\u5f32\u5f33\u5f34\u5f35\u5f36\u5f37\u5f38\u5f39\u5f3a\u5f3b\u5f3c\u5f3d\u5f3e\u5f3f\u5f40\u5f41\u5f42\u5f43\u5f44\u5f45\u5f46\u5f47\u5f48\u5f49\u5f4a\u5f4b\u5f4c\u5f4d\u5f4e\u5f4f\u5f50\u5f51\u5f52\u5f53\u5f54\u5f55\u5f56\u5f57\u5f58\u5f59\u5f5a\u5f5b\u5f5c\u5f5d\u5f5e\u5f5f\u5f60\u5f61\u5f62\u5f63\u5f64\u5f65\u5f66\u5f67\u5f68\u5f69\u5f6a\u5f6b\u5f6c\u5f6d\u5f6e\u5f6f\u5f70\u5f71\u5f72\u5f73\u5f74\u5f75\u5f76\u5f77\u5f78\u5f79\u5f7a\u5f7b\u5f7c\u5f7d\u5f7e\u5f7f\u5f80\u5f81\u5f82\u5f83\u5f84\u5f85\u5f86\u5f87\u5f88\u5f89\u5f8a\u5f8b\u5f8c\u5f8d\u5f8e\u5f8f\u5f90\u5f91\u5f92\u5f93\u5f94\u5f95\u5f96\u5f97\u5f98\u5f99\u5f9a\u5f9b\u5f9c\u5f9d\u5f9e\u5f9f\u5fa0\u5fa1\u5fa2\u5fa3\u5fa4\u5fa5\u5fa6\u5fa7\u5fa8\u5fa9\u5faa\u5fab\u5fac\u5fad\u5fae\u5faf\u5fb0\u5fb1\u5fb2\u5fb3\u5fb4\u5fb5\u5fb6\u5fb7\u5fb8\u5fb9\u5fba\u5fbb\u5fbc\u5fbd\u5fbe\u5fbf\u5fc0\u5fc1\u5fc2\u5fc3\u5fc4\u5fc5\u5fc6\u5fc7\u5fc8\u5fc9\u5fca\u5fcb\u5fcc\u5fcd\u5fce\u5fcf\u5fd0\u5fd1\u5fd2\u5fd3\u5fd4\u5fd5\u5fd6\u5fd7\u5fd8\u5fd9\u5fda\u5fdb\u5fdc\u5fdd\u5fde\u5fdf\u5fe0\u5fe1\u5fe2\u5fe3\u5fe4\u5fe5\u5fe6\u5fe7\u5fe8\u5fe9\u5fea\u5feb\u5fec\u5fed\u5fee\u5fef\u5ff0\u5ff1\u5ff2\u5ff3\u5ff4\u5ff5\u5ff6\u5ff7\u5ff8\u5ff9\u5ffa\u5ffb\u5ffc\u5ffd\u5ffe\u5fff\u6000\u6001\u6002\u6003\u6004\u6005\u6006\u6007\u6008\u6009\u600a\u600b\u600c\u600d\u600e\u600f\u6010\u6011\u6012\u6013\u6014\u6015\u6016\u6017\u6018\u6019\u601a\u601b\u601c\u601d\u601e\u601f\u6020\u6021\u6022\u6023\u6024\u6025\u6026\u6027\u6028\u6029\u602a\u602b\u602c\u602d\u602e\u602f\u6030\u6031\u6032\u6033\u6034\u6035\u6036\u6037\u6038\u6039\u603a\u603b\u603c\u603d\u603e\u603f\u6040\u6041\u6042\u6043\u6044\u6045\u6046\u6047\u6048\u6049\u604a\u604b\u604c\u604d\u604e\u604f\u6050\u6051\u6052\u6053\u6054\u6055\u6056\u6057\u6058\u6059\u605a\u605b\u605c\u605d\u605e\u605f\u6060\u6061\u6062\u6063\u6064\u6065\u6066\u6067\u6068\u6069\u606a\u606b\u606c\u606d\u606e\u606f\u6070\u6071\u6072\u6073\u6074\u6075\u6076\u6077\u6078\u6079\u607a\u607b\u607c\u607d\u607e\u607f\u6080\u6081\u6082\u6083\u6084\u6085\u6086\u6087\u6088\u6089\u608a\u608b\u608c\u608d\u608e\u608f\u6090\u6091\u6092\u6093\u6094\u6095\u6096\u6097\u6098\u6099\u609a\u609b\u609c\u609d\u609e\u609f\u60a0\u60a1\u60a2\u60a3\u60a4\u60a5\u60a6\u60a7\u60a8\u60a9\u60aa\u60ab\u60ac\u60ad\u60ae\u60af\u60b0\u60b1\u60b2\u60b3\u60b4\u60b5\u60b6\u60b7\u60b8\u60b9\u60ba\u60bb\u60bc\u60bd\u60be\u60bf\u60c0\u60c1\u60c2\u60c3\u60c4\u60c5\u60c6\u60c7\u60c8\u60c9\u60ca\u60cb\u60cc\u60cd\u60ce\u60cf\u60d0\u60d1\u60d2\u60d3\u60d4\u60d5\u60d6\u60d7\u60d8\u60d9\u60da\u60db\u60dc\u60dd\u60de\u60df\u60e0\u60e1\u60e2\u60e3\u60e4\u60e5\u60e6\u60e7\u60e8\u60e9\u60ea\u60eb\u60ec\u60ed\u60ee\u60ef\u60f0\u60f1\u60f2\u60f3\u60f4\u60f5\u60f6\u60f7\u60f8\u60f9\u60fa\u60fb\u60fc\u60fd\u60fe\u60ff\u6100\u6101\u6102\u6103\u6104\u6105\u6106\u6107\u6108\u6109\u610a\u610b\u610c\u610d\u610e\u610f\u6110\u6111\u6112\u6113\u6114\u6115\u6116\u6117\u6118\u6119\u611a\u611b\u611c\u611d\u611e\u611f\u6120\u6121\u6122\u6123\u6124\u6125\u6126\u6127\u6128\u6129\u612a\u612b\u612c\u612d\u612e\u612f\u6130\u6131\u6132\u6133\u6134\u6135\u6136\u6137\u6138\u6139\u613a\u613b\u613c\u613d\u613e\u613f\u6140\u6141\u6142\u6143\u6144\u6145\u6146\u6147\u6148\u6149\u614a\u614b\u614c\u614d\u614e\u614f\u6150\u6151\u6152\u6153\u6154\u6155\u6156\u6157\u6158\u6159\u615a\u615b\u615c\u615d\u615e\u615f\u6160\u6161\u6162\u6163\u6164\u6165\u6166\u6167\u6168\u6169\u616a\u616b\u616c\u616d\u616e\u616f\u6170\u6171\u6172\u6173\u6174\u6175\u6176\u6177\u6178\u6179\u617a\u617b\u617c\u617d\u617e\u617f\u6180\u6181\u6182\u6183\u6184\u6185\u6186\u6187\u6188\u6189\u618a\u618b\u618c\u618d\u618e\u618f\u6190\u6191\u6192\u6193\u6194\u6195\u6196\u6197\u6198\u6199\u619a\u619b\u619c\u619d\u619e\u619f\u61a0\u61a1\u61a2\u61a3\u61a4\u61a5\u61a6\u61a7\u61a8\u61a9\u61aa\u61ab\u61ac\u61ad\u61ae\u61af\u61b0\u61b1\u61b2\u61b3\u61b4\u61b5\u61b6\u61b7\u61b8\u61b9\u61ba\u61bb\u61bc\u61bd\u61be\u61bf\u61c0\u61c1\u61c2\u61c3\u61c4\u61c5\u61c6\u61c7\u61c8\u61c9\u61ca\u61cb\u61cc\u61cd\u61ce\u61cf\u61d0\u61d1\u61d2\u61d3\u61d4\u61d5\u61d6\u61d7\u61d8\u61d9\u61da\u61db\u61dc\u61dd\u61de\u61df\u61e0\u61e1\u61e2\u61e3\u61e4\u61e5\u61e6\u61e7\u61e8\u61e9\u61ea\u61eb\u61ec\u61ed\u61ee\u61ef\u61f0\u61f1\u61f2\u61f3\u61f4\u61f5\u61f6\u61f7\u61f8\u61f9\u61fa\u61fb\u61fc\u61fd\u61fe\u61ff\u6200\u6201\u6202\u6203\u6204\u6205\u6206\u6207\u6208\u6209\u620a\u620b\u620c\u620d\u620e\u620f\u6210\u6211\u6212\u6213\u6214\u6215\u6216\u6217\u6218\u6219\u621a\u621b\u621c\u621d\u621e\u621f\u6220\u6221\u6222\u6223\u6224\u6225\u6226\u6227\u6228\u6229\u622a\u622b\u622c\u622d\u622e\u622f\u6230\u6231\u6232\u6233\u6234\u6235\u6236\u6237\u6238\u6239\u623a\u623b\u623c\u623d\u623e\u623f\u6240\u6241\u6242\u6243\u6244\u6245\u6246\u6247\u6248\u6249\u624a\u624b\u624c\u624d\u624e\u624f\u6250\u6251\u6252\u6253\u6254\u6255\u6256\u6257\u6258\u6259\u625a\u625b\u625c\u625d\u625e\u625f\u6260\u6261\u6262\u6263\u6264\u6265\u6266\u6267\u6268\u6269\u626a\u626b\u626c\u626d\u626e\u626f\u6270\u6271\u6272\u6273\u6274\u6275\u6276\u6277\u6278\u6279\u627a\u627b\u627c\u627d\u627e\u627f\u6280\u6281\u6282\u6283\u6284\u6285\u6286\u6287\u6288\u6289\u628a\u628b\u628c\u628d\u628e\u628f\u6290\u6291\u6292\u6293\u6294\u6295\u6296\u6297\u6298\u6299\u629a\u629b\u629c\u629d\u629e\u629f\u62a0\u62a1\u62a2\u62a3\u62a4\u62a5\u62a6\u62a7\u62a8\u62a9\u62aa\u62ab\u62ac\u62ad\u62ae\u62af\u62b0\u62b1\u62b2\u62b3\u62b4\u62b5\u62b6\u62b7\u62b8\u62b9\u62ba\u62bb\u62bc\u62bd\u62be\u62bf\u62c0\u62c1\u62c2\u62c3\u62c4\u62c5\u62c6\u62c7\u62c8\u62c9\u62ca\u62cb\u62cc\u62cd\u62ce\u62cf\u62d0\u62d1\u62d2\u62d3\u62d4\u62d5\u62d6\u62d7\u62d8\u62d9\u62da\u62db\u62dc\u62dd\u62de\u62df\u62e0\u62e1\u62e2\u62e3\u62e4\u62e5\u62e6\u62e7\u62e8\u62e9\u62ea\u62eb\u62ec\u62ed\u62ee\u62ef\u62f0\u62f1\u62f2\u62f3\u62f4\u62f5\u62f6\u62f7\u62f8\u62f9\u62fa\u62fb\u62fc\u62fd\u62fe\u62ff\u6300\u6301\u6302\u6303\u6304\u6305\u6306\u6307\u6308\u6309\u630a\u630b\u630c\u630d\u630e\u630f\u6310\u6311\u6312\u6313\u6314\u6315\u6316\u6317\u6318\u6319\u631a\u631b\u631c\u631d\u631e\u631f\u6320\u6321\u6322\u6323\u6324\u6325\u6326\u6327\u6328\u6329\u632a\u632b\u632c\u632d\u632e\u632f\u6330\u6331\u6332\u6333\u6334\u6335\u6336\u6337\u6338\u6339\u633a\u633b\u633c\u633d\u633e\u633f\u6340\u6341\u6342\u6343\u6344\u6345\u6346\u6347\u6348\u6349\u634a\u634b\u634c\u634d\u634e\u634f\u6350\u6351\u6352\u6353\u6354\u6355\u6356\u6357\u6358\u6359\u635a\u635b\u635c\u635d\u635e\u635f\u6360\u6361\u6362\u6363\u6364\u6365\u6366\u6367\u6368\u6369\u636a\u636b\u636c\u636d\u636e\u636f\u6370\u6371\u6372\u6373\u6374\u6375\u6376\u6377\u6378\u6379\u637a\u637b\u637c\u637d\u637e\u637f\u6380\u6381\u6382\u6383\u6384\u6385\u6386\u6387\u6388\u6389\u638a\u638b\u638c\u638d\u638e\u638f\u6390\u6391\u6392\u6393\u6394\u6395\u6396\u6397\u6398\u6399\u639a\u639b\u639c\u639d\u639e\u639f\u63a0\u63a1\u63a2\u63a3\u63a4\u63a5\u63a6\u63a7\u63a8\u63a9\u63aa\u63ab\u63ac\u63ad\u63ae\u63af\u63b0\u63b1\u63b2\u63b3\u63b4\u63b5\u63b6\u63b7\u63b8\u63b9\u63ba\u63bb\u63bc\u63bd\u63be\u63bf\u63c0\u63c1\u63c2\u63c3\u63c4\u63c5\u63c6\u63c7\u63c8\u63c9\u63ca\u63cb\u63cc\u63cd\u63ce\u63cf\u63d0\u63d1\u63d2\u63d3\u63d4\u63d5\u63d6\u63d7\u63d8\u63d9\u63da\u63db\u63dc\u63dd\u63de\u63df\u63e0\u63e1\u63e2\u63e3\u63e4\u63e5\u63e6\u63e7\u63e8\u63e9\u63ea\u63eb\u63ec\u63ed\u63ee\u63ef\u63f0\u63f1\u63f2\u63f3\u63f4\u63f5\u63f6\u63f7\u63f8\u63f9\u63fa\u63fb\u63fc\u63fd\u63fe\u63ff\u6400\u6401\u6402\u6403\u6404\u6405\u6406\u6407\u6408\u6409\u640a\u640b\u640c\u640d\u640e\u640f\u6410\u6411\u6412\u6413\u6414\u6415\u6416\u6417\u6418\u6419\u641a\u641b\u641c\u641d\u641e\u641f\u6420\u6421\u6422\u6423\u6424\u6425\u6426\u6427\u6428\u6429\u642a\u642b\u642c\u642d\u642e\u642f\u6430\u6431\u6432\u6433\u6434\u6435\u6436\u6437\u6438\u6439\u643a\u643b\u643c\u643d\u643e\u643f\u6440\u6441\u6442\u6443\u6444\u6445\u6446\u6447\u6448\u6449\u644a\u644b\u644c\u644d\u644e\u644f\u6450\u6451\u6452\u6453\u6454\u6455\u6456\u6457\u6458\u6459\u645a\u645b\u645c\u645d\u645e\u645f\u6460\u6461\u6462\u6463\u6464\u6465\u6466\u6467\u6468\u6469\u646a\u646b\u646c\u646d\u646e\u646f\u6470\u6471\u6472\u6473\u6474\u6475\u6476\u6477\u6478\u6479\u647a\u647b\u647c\u647d\u647e\u647f\u6480\u6481\u6482\u6483\u6484\u6485\u6486\u6487\u6488\u6489\u648a\u648b\u648c\u648d\u648e\u648f\u6490\u6491\u6492\u6493\u6494\u6495\u6496\u6497\u6498\u6499\u649a\u649b\u649c\u649d\u649e\u649f\u64a0\u64a1\u64a2\u64a3\u64a4\u64a5\u64a6\u64a7\u64a8\u64a9\u64aa\u64ab\u64ac\u64ad\u64ae\u64af\u64b0\u64b1\u64b2\u64b3\u64b4\u64b5\u64b6\u64b7\u64b8\u64b9\u64ba\u64bb\u64bc\u64bd\u64be\u64bf\u64c0\u64c1\u64c2\u64c3\u64c4\u64c5\u64c6\u64c7\u64c8\u64c9\u64ca\u64cb\u64cc\u64cd\u64ce\u64cf\u64d0\u64d1\u64d2\u64d3\u64d4\u64d5\u64d6\u64d7\u64d8\u64d9\u64da\u64db\u64dc\u64dd\u64de\u64df\u64e0\u64e1\u64e2\u64e3\u64e4\u64e5\u64e6\u64e7\u64e8\u64e9\u64ea\u64eb\u64ec\u64ed\u64ee\u64ef\u64f0\u64f1\u64f2\u64f3\u64f4\u64f5\u64f6\u64f7\u64f8\u64f9\u64fa\u64fb\u64fc\u64fd\u64fe\u64ff\u6500\u6501\u6502\u6503\u6504\u6505\u6506\u6507\u6508\u6509\u650a\u650b\u650c\u650d\u650e\u650f\u6510\u6511\u6512\u6513\u6514\u6515\u6516\u6517\u6518\u6519\u651a\u651b\u651c\u651d\u651e\u651f\u6520\u6521\u6522\u6523\u6524\u6525\u6526\u6527\u6528\u6529\u652a\u652b\u652c\u652d\u652e\u652f\u6530\u6531\u6532\u6533\u6534\u6535\u6536\u6537\u6538\u6539\u653a\u653b\u653c\u653d\u653e\u653f\u6540\u6541\u6542\u6543\u6544\u6545\u6546\u6547\u6548\u6549\u654a\u654b\u654c\u654d\u654e\u654f\u6550\u6551\u6552\u6553\u6554\u6555\u6556\u6557\u6558\u6559\u655a\u655b\u655c\u655d\u655e\u655f\u6560\u6561\u6562\u6563\u6564\u6565\u6566\u6567\u6568\u6569\u656a\u656b\u656c\u656d\u656e\u656f\u6570\u6571\u6572\u6573\u6574\u6575\u6576\u6577\u6578\u6579\u657a\u657b\u657c\u657d\u657e\u657f\u6580\u6581\u6582\u6583\u6584\u6585\u6586\u6587\u6588\u6589\u658a\u658b\u658c\u658d\u658e\u658f\u6590\u6591\u6592\u6593\u6594\u6595\u6596\u6597\u6598\u6599\u659a\u659b\u659c\u659d\u659e\u659f\u65a0\u65a1\u65a2\u65a3\u65a4\u65a5\u65a6\u65a7\u65a8\u65a9\u65aa\u65ab\u65ac\u65ad\u65ae\u65af\u65b0\u65b1\u65b2\u65b3\u65b4\u65b5\u65b6\u65b7\u65b8\u65b9\u65ba\u65bb\u65bc\u65bd\u65be\u65bf\u65c0\u65c1\u65c2\u65c3\u65c4\u65c5\u65c6\u65c7\u65c8\u65c9\u65ca\u65cb\u65cc\u65cd\u65ce\u65cf\u65d0\u65d1\u65d2\u65d3\u65d4\u65d5\u65d6\u65d7\u65d8\u65d9\u65da\u65db\u65dc\u65dd\u65de\u65df\u65e0\u65e1\u65e2\u65e3\u65e4\u65e5\u65e6\u65e7\u65e8\u65e9\u65ea\u65eb\u65ec\u65ed\u65ee\u65ef\u65f0\u65f1\u65f2\u65f3\u65f4\u65f5\u65f6\u65f7\u65f8\u65f9\u65fa\u65fb\u65fc\u65fd\u65fe\u65ff\u6600\u6601\u6602\u6603\u6604\u6605\u6606\u6607\u6608\u6609\u660a\u660b\u660c\u660d\u660e\u660f\u6610\u6611\u6612\u6613\u6614\u6615\u6616\u6617\u6618\u6619\u661a\u661b\u661c\u661d\u661e\u661f\u6620\u6621\u6622\u6623\u6624\u6625\u6626\u6627\u6628\u6629\u662a\u662b\u662c\u662d\u662e\u662f\u6630\u6631\u6632\u6633\u6634\u6635\u6636\u6637\u6638\u6639\u663a\u663b\u663c\u663d\u663e\u663f\u6640\u6641\u6642\u6643\u6644\u6645\u6646\u6647\u6648\u6649\u664a\u664b\u664c\u664d\u664e\u664f\u6650\u6651\u6652\u6653\u6654\u6655\u6656\u6657\u6658\u6659\u665a\u665b\u665c\u665d\u665e\u665f\u6660\u6661\u6662\u6663\u6664\u6665\u6666\u6667\u6668\u6669\u666a\u666b\u666c\u666d\u666e\u666f\u6670\u6671\u6672\u6673\u6674\u6675\u6676\u6677\u6678\u6679\u667a\u667b\u667c\u667d\u667e\u667f\u6680\u6681\u6682\u6683\u6684\u6685\u6686\u6687\u6688\u6689\u668a\u668b\u668c\u668d\u668e\u668f\u6690\u6691\u6692\u6693\u6694\u6695\u6696\u6697\u6698\u6699\u669a\u669b\u669c\u669d\u669e\u669f\u66a0\u66a1\u66a2\u66a3\u66a4\u66a5\u66a6\u66a7\u66a8\u66a9\u66aa\u66ab\u66ac\u66ad\u66ae\u66af\u66b0\u66b1\u66b2\u66b3\u66b4\u66b5\u66b6\u66b7\u66b8\u66b9\u66ba\u66bb\u66bc\u66bd\u66be\u66bf\u66c0\u66c1\u66c2\u66c3\u66c4\u66c5\u66c6\u66c7\u66c8\u66c9\u66ca\u66cb\u66cc\u66cd\u66ce\u66cf\u66d0\u66d1\u66d2\u66d3\u66d4\u66d5\u66d6\u66d7\u66d8\u66d9\u66da\u66db\u66dc\u66dd\u66de\u66df\u66e0\u66e1\u66e2\u66e3\u66e4\u66e5\u66e6\u66e7\u66e8\u66e9\u66ea\u66eb\u66ec\u66ed\u66ee\u66ef\u66f0\u66f1\u66f2\u66f3\u66f4\u66f5\u66f6\u66f7\u66f8\u66f9\u66fa\u66fb\u66fc\u66fd\u66fe\u66ff\u6700\u6701\u6702\u6703\u6704\u6705\u6706\u6707\u6708\u6709\u670a\u670b\u670c\u670d\u670e\u670f\u6710\u6711\u6712\u6713\u6714\u6715\u6716\u6717\u6718\u6719\u671a\u671b\u671c\u671d\u671e\u671f\u6720\u6721\u6722\u6723\u6724\u6725\u6726\u6727\u6728\u6729\u672a\u672b\u672c\u672d\u672e\u672f\u6730\u6731\u6732\u6733\u6734\u6735\u6736\u6737\u6738\u6739\u673a\u673b\u673c\u673d\u673e\u673f\u6740\u6741\u6742\u6743\u6744\u6745\u6746\u6747\u6748\u6749\u674a\u674b\u674c\u674d\u674e\u674f\u6750\u6751\u6752\u6753\u6754\u6755\u6756\u6757\u6758\u6759\u675a\u675b\u675c\u675d\u675e\u675f\u6760\u6761\u6762\u6763\u6764\u6765\u6766\u6767\u6768\u6769\u676a\u676b\u676c\u676d\u676e\u676f\u6770\u6771\u6772\u6773\u6774\u6775\u6776\u6777\u6778\u6779\u677a\u677b\u677c\u677d\u677e\u677f\u6780\u6781\u6782\u6783\u6784\u6785\u6786\u6787\u6788\u6789\u678a\u678b\u678c\u678d\u678e\u678f\u6790\u6791\u6792\u6793\u6794\u6795\u6796\u6797\u6798\u6799\u679a\u679b\u679c\u679d\u679e\u679f\u67a0\u67a1\u67a2\u67a3\u67a4\u67a5\u67a6\u67a7\u67a8\u67a9\u67aa\u67ab\u67ac\u67ad\u67ae\u67af\u67b0\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7\u67b8\u67b9\u67ba\u67bb\u67bc\u67bd\u67be\u67bf\u67c0\u67c1\u67c2\u67c3\u67c4\u67c5\u67c6\u67c7\u67c8\u67c9\u67ca\u67cb\u67cc\u67cd\u67ce\u67cf\u67d0\u67d1\u67d2\u67d3\u67d4\u67d5\u67d6\u67d7\u67d8\u67d9\u67da\u67db\u67dc\u67dd\u67de\u67df\u67e0\u67e1\u67e2\u67e3\u67e4\u67e5\u67e6\u67e7\u67e8\u67e9\u67ea\u67eb\u67ec\u67ed\u67ee\u67ef\u67f0\u67f1\u67f2\u67f3\u67f4\u67f5\u67f6\u67f7\u67f8\u67f9\u67fa\u67fb\u67fc\u67fd\u67fe\u67ff\u6800\u6801\u6802\u6803\u6804\u6805\u6806\u6807\u6808\u6809\u680a\u680b\u680c\u680d\u680e\u680f\u6810\u6811\u6812\u6813\u6814\u6815\u6816\u6817\u6818\u6819\u681a\u681b\u681c\u681d\u681e\u681f\u6820\u6821\u6822\u6823\u6824\u6825\u6826\u6827\u6828\u6829\u682a\u682b\u682c\u682d\u682e\u682f\u6830\u6831\u6832\u6833\u6834\u6835\u6836\u6837\u6838\u6839\u683a\u683b\u683c\u683d\u683e\u683f\u6840\u6841\u6842\u6843\u6844\u6845\u6846\u6847\u6848\u6849\u684a\u684b\u684c\u684d\u684e\u684f\u6850\u6851\u6852\u6853\u6854\u6855\u6856\u6857\u6858\u6859\u685a\u685b\u685c\u685d\u685e\u685f\u6860\u6861\u6862\u6863\u6864\u6865\u6866\u6867\u6868\u6869\u686a\u686b\u686c\u686d\u686e\u686f\u6870\u6871\u6872\u6873\u6874\u6875\u6876\u6877\u6878\u6879\u687a\u687b\u687c\u687d\u687e\u687f\u6880\u6881\u6882\u6883\u6884\u6885\u6886\u6887\u6888\u6889\u688a\u688b\u688c\u688d\u688e\u688f\u6890\u6891\u6892\u6893\u6894\u6895\u6896\u6897\u6898\u6899\u689a\u689b\u689c\u689d\u689e\u689f\u68a0\u68a1\u68a2\u68a3\u68a4\u68a5\u68a6\u68a7\u68a8\u68a9\u68aa\u68ab\u68ac\u68ad\u68ae\u68af\u68b0\u68b1\u68b2\u68b3\u68b4\u68b5\u68b6\u68b7\u68b8\u68b9\u68ba\u68bb\u68bc\u68bd\u68be\u68bf\u68c0\u68c1\u68c2\u68c3\u68c4\u68c5\u68c6\u68c7\u68c8\u68c9\u68ca\u68cb\u68cc\u68cd\u68ce\u68cf\u68d0\u68d1\u68d2\u68d3\u68d4\u68d5\u68d6\u68d7\u68d8\u68d9\u68da\u68db\u68dc\u68dd\u68de\u68df\u68e0\u68e1\u68e2\u68e3\u68e4\u68e5\u68e6\u68e7\u68e8\u68e9\u68ea\u68eb\u68ec\u68ed\u68ee\u68ef\u68f0\u68f1\u68f2\u68f3\u68f4\u68f5\u68f6\u68f7\u68f8\u68f9\u68fa\u68fb\u68fc\u68fd\u68fe\u68ff\u6900\u6901\u6902\u6903\u6904\u6905\u6906\u6907\u6908\u6909\u690a\u690b\u690c\u690d\u690e\u690f\u6910\u6911\u6912\u6913\u6914\u6915\u6916\u6917\u6918\u6919\u691a\u691b\u691c\u691d\u691e\u691f\u6920\u6921\u6922\u6923\u6924\u6925\u6926\u6927\u6928\u6929\u692a\u692b\u692c\u692d\u692e\u692f\u6930\u6931\u6932\u6933\u6934\u6935\u6936\u6937\u6938\u6939\u693a\u693b\u693c\u693d\u693e\u693f\u6940\u6941\u6942\u6943\u6944\u6945\u6946\u6947\u6948\u6949\u694a\u694b\u694c\u694d\u694e\u694f\u6950\u6951\u6952\u6953\u6954\u6955\u6956\u6957\u6958\u6959\u695a\u695b\u695c\u695d\u695e\u695f\u6960\u6961\u6962\u6963\u6964\u6965\u6966\u6967\u6968\u6969\u696a\u696b\u696c\u696d\u696e\u696f\u6970\u6971\u6972\u6973\u6974\u6975\u6976\u6977\u6978\u6979\u697a\u697b\u697c\u697d\u697e\u697f\u6980\u6981\u6982\u6983\u6984\u6985\u6986\u6987\u6988\u6989\u698a\u698b\u698c\u698d\u698e\u698f\u6990\u6991\u6992\u6993\u6994\u6995\u6996\u6997\u6998\u6999\u699a\u699b\u699c\u699d\u699e\u699f\u69a0\u69a1\u69a2\u69a3\u69a4\u69a5\u69a6\u69a7\u69a8\u69a9\u69aa\u69ab\u69ac\u69ad\u69ae\u69af\u69b0\u69b1\u69b2\u69b3\u69b4\u69b5\u69b6\u69b7\u69b8\u69b9\u69ba\u69bb\u69bc\u69bd\u69be\u69bf\u69c0\u69c1\u69c2\u69c3\u69c4\u69c5\u69c6\u69c7\u69c8\u69c9\u69ca\u69cb\u69cc\u69cd\u69ce\u69cf\u69d0\u69d1\u69d2\u69d3\u69d4\u69d5\u69d6\u69d7\u69d8\u69d9\u69da\u69db\u69dc\u69dd\u69de\u69df\u69e0\u69e1\u69e2\u69e3\u69e4\u69e5\u69e6\u69e7\u69e8\u69e9\u69ea\u69eb\u69ec\u69ed\u69ee\u69ef\u69f0\u69f1\u69f2\u69f3\u69f4\u69f5\u69f6\u69f7\u69f8\u69f9\u69fa\u69fb\u69fc\u69fd\u69fe\u69ff\u6a00\u6a01\u6a02\u6a03\u6a04\u6a05\u6a06\u6a07\u6a08\u6a09\u6a0a\u6a0b\u6a0c\u6a0d\u6a0e\u6a0f\u6a10\u6a11\u6a12\u6a13\u6a14\u6a15\u6a16\u6a17\u6a18\u6a19\u6a1a\u6a1b\u6a1c\u6a1d\u6a1e\u6a1f\u6a20\u6a21\u6a22\u6a23\u6a24\u6a25\u6a26\u6a27\u6a28\u6a29\u6a2a\u6a2b\u6a2c\u6a2d\u6a2e\u6a2f\u6a30\u6a31\u6a32\u6a33\u6a34\u6a35\u6a36\u6a37\u6a38\u6a39\u6a3a\u6a3b\u6a3c\u6a3d\u6a3e\u6a3f\u6a40\u6a41\u6a42\u6a43\u6a44\u6a45\u6a46\u6a47\u6a48\u6a49\u6a4a\u6a4b\u6a4c\u6a4d\u6a4e\u6a4f\u6a50\u6a51\u6a52\u6a53\u6a54\u6a55\u6a56\u6a57\u6a58\u6a59\u6a5a\u6a5b\u6a5c\u6a5d\u6a5e\u6a5f\u6a60\u6a61\u6a62\u6a63\u6a64\u6a65\u6a66\u6a67\u6a68\u6a69\u6a6a\u6a6b\u6a6c\u6a6d\u6a6e\u6a6f\u6a70\u6a71\u6a72\u6a73\u6a74\u6a75\u6a76\u6a77\u6a78\u6a79\u6a7a\u6a7b\u6a7c\u6a7d\u6a7e\u6a7f\u6a80\u6a81\u6a82\u6a83\u6a84\u6a85\u6a86\u6a87\u6a88\u6a89\u6a8a\u6a8b\u6a8c\u6a8d\u6a8e\u6a8f\u6a90\u6a91\u6a92\u6a93\u6a94\u6a95\u6a96\u6a97\u6a98\u6a99\u6a9a\u6a9b\u6a9c\u6a9d\u6a9e\u6a9f\u6aa0\u6aa1\u6aa2\u6aa3\u6aa4\u6aa5\u6aa6\u6aa7\u6aa8\u6aa9\u6aaa\u6aab\u6aac\u6aad\u6aae\u6aaf\u6ab0\u6ab1\u6ab2\u6ab3\u6ab4\u6ab5\u6ab6\u6ab7\u6ab8\u6ab9\u6aba\u6abb\u6abc\u6abd\u6abe\u6abf\u6ac0\u6ac1\u6ac2\u6ac3\u6ac4\u6ac5\u6ac6\u6ac7\u6ac8\u6ac9\u6aca\u6acb\u6acc\u6acd\u6ace\u6acf\u6ad0\u6ad1\u6ad2\u6ad3\u6ad4\u6ad5\u6ad6\u6ad7\u6ad8\u6ad9\u6ada\u6adb\u6adc\u6add\u6ade\u6adf\u6ae0\u6ae1\u6ae2\u6ae3\u6ae4\u6ae5\u6ae6\u6ae7\u6ae8\u6ae9\u6aea\u6aeb\u6aec\u6aed\u6aee\u6aef\u6af0\u6af1\u6af2\u6af3\u6af4\u6af5\u6af6\u6af7\u6af8\u6af9\u6afa\u6afb\u6afc\u6afd\u6afe\u6aff\u6b00\u6b01\u6b02\u6b03\u6b04\u6b05\u6b06\u6b07\u6b08\u6b09\u6b0a\u6b0b\u6b0c\u6b0d\u6b0e\u6b0f\u6b10\u6b11\u6b12\u6b13\u6b14\u6b15\u6b16\u6b17\u6b18\u6b19\u6b1a\u6b1b\u6b1c\u6b1d\u6b1e\u6b1f\u6b20\u6b21\u6b22\u6b23\u6b24\u6b25\u6b26\u6b27\u6b28\u6b29\u6b2a\u6b2b\u6b2c\u6b2d\u6b2e\u6b2f\u6b30\u6b31\u6b32\u6b33\u6b34\u6b35\u6b36\u6b37\u6b38\u6b39\u6b3a\u6b3b\u6b3c\u6b3d\u6b3e\u6b3f\u6b40\u6b41\u6b42\u6b43\u6b44\u6b45\u6b46\u6b47\u6b48\u6b49\u6b4a\u6b4b\u6b4c\u6b4d\u6b4e\u6b4f\u6b50\u6b51\u6b52\u6b53\u6b54\u6b55\u6b56\u6b57\u6b58\u6b59\u6b5a\u6b5b\u6b5c\u6b5d\u6b5e\u6b5f\u6b60\u6b61\u6b62\u6b63\u6b64\u6b65\u6b66\u6b67\u6b68\u6b69\u6b6a\u6b6b\u6b6c\u6b6d\u6b6e\u6b6f\u6b70\u6b71\u6b72\u6b73\u6b74\u6b75\u6b76\u6b77\u6b78\u6b79\u6b7a\u6b7b\u6b7c\u6b7d\u6b7e\u6b7f\u6b80\u6b81\u6b82\u6b83\u6b84\u6b85\u6b86\u6b87\u6b88\u6b89\u6b8a\u6b8b\u6b8c\u6b8d\u6b8e\u6b8f\u6b90\u6b91\u6b92\u6b93\u6b94\u6b95\u6b96\u6b97\u6b98\u6b99\u6b9a\u6b9b\u6b9c\u6b9d\u6b9e\u6b9f\u6ba0\u6ba1\u6ba2\u6ba3\u6ba4\u6ba5\u6ba6\u6ba7\u6ba8\u6ba9\u6baa\u6bab\u6bac\u6bad\u6bae\u6baf\u6bb0\u6bb1\u6bb2\u6bb3\u6bb4\u6bb5\u6bb6\u6bb7\u6bb8\u6bb9\u6bba\u6bbb\u6bbc\u6bbd\u6bbe\u6bbf\u6bc0\u6bc1\u6bc2\u6bc3\u6bc4\u6bc5\u6bc6\u6bc7\u6bc8\u6bc9\u6bca\u6bcb\u6bcc\u6bcd\u6bce\u6bcf\u6bd0\u6bd1\u6bd2\u6bd3\u6bd4\u6bd5\u6bd6\u6bd7\u6bd8\u6bd9\u6bda\u6bdb\u6bdc\u6bdd\u6bde\u6bdf\u6be0\u6be1\u6be2\u6be3\u6be4\u6be5\u6be6\u6be7\u6be8\u6be9\u6bea\u6beb\u6bec\u6bed\u6bee\u6bef\u6bf0\u6bf1\u6bf2\u6bf3\u6bf4\u6bf5\u6bf6\u6bf7\u6bf8\u6bf9\u6bfa\u6bfb\u6bfc\u6bfd\u6bfe\u6bff\u6c00\u6c01\u6c02\u6c03\u6c04\u6c05\u6c06\u6c07\u6c08\u6c09\u6c0a\u6c0b\u6c0c\u6c0d\u6c0e\u6c0f\u6c10\u6c11\u6c12\u6c13\u6c14\u6c15\u6c16\u6c17\u6c18\u6c19\u6c1a\u6c1b\u6c1c\u6c1d\u6c1e\u6c1f\u6c20\u6c21\u6c22\u6c23\u6c24\u6c25\u6c26\u6c27\u6c28\u6c29\u6c2a\u6c2b\u6c2c\u6c2d\u6c2e\u6c2f\u6c30\u6c31\u6c32\u6c33\u6c34\u6c35\u6c36\u6c37\u6c38\u6c39\u6c3a\u6c3b\u6c3c\u6c3d\u6c3e\u6c3f\u6c40\u6c41\u6c42\u6c43\u6c44\u6c45\u6c46\u6c47\u6c48\u6c49\u6c4a\u6c4b\u6c4c\u6c4d\u6c4e\u6c4f\u6c50\u6c51\u6c52\u6c53\u6c54\u6c55\u6c56\u6c57\u6c58\u6c59\u6c5a\u6c5b\u6c5c\u6c5d\u6c5e\u6c5f\u6c60\u6c61\u6c62\u6c63\u6c64\u6c65\u6c66\u6c67\u6c68\u6c69\u6c6a\u6c6b\u6c6c\u6c6d\u6c6e\u6c6f\u6c70\u6c71\u6c72\u6c73\u6c74\u6c75\u6c76\u6c77\u6c78\u6c79\u6c7a\u6c7b\u6c7c\u6c7d\u6c7e\u6c7f\u6c80\u6c81\u6c82\u6c83\u6c84\u6c85\u6c86\u6c87\u6c88\u6c89\u6c8a\u6c8b\u6c8c\u6c8d\u6c8e\u6c8f\u6c90\u6c91\u6c92\u6c93\u6c94\u6c95\u6c96\u6c97\u6c98\u6c99\u6c9a\u6c9b\u6c9c\u6c9d\u6c9e\u6c9f\u6ca0\u6ca1\u6ca2\u6ca3\u6ca4\u6ca5\u6ca6\u6ca7\u6ca8\u6ca9\u6caa\u6cab\u6cac\u6cad\u6cae\u6caf\u6cb0\u6cb1\u6cb2\u6cb3\u6cb4\u6cb5\u6cb6\u6cb7\u6cb8\u6cb9\u6cba\u6cbb\u6cbc\u6cbd\u6cbe\u6cbf\u6cc0\u6cc1\u6cc2\u6cc3\u6cc4\u6cc5\u6cc6\u6cc7\u6cc8\u6cc9\u6cca\u6ccb\u6ccc\u6ccd\u6cce\u6ccf\u6cd0\u6cd1\u6cd2\u6cd3\u6cd4\u6cd5\u6cd6\u6cd7\u6cd8\u6cd9\u6cda\u6cdb\u6cdc\u6cdd\u6cde\u6cdf\u6ce0\u6ce1\u6ce2\u6ce3\u6ce4\u6ce5\u6ce6\u6ce7\u6ce8\u6ce9\u6cea\u6ceb\u6cec\u6ced\u6cee\u6cef\u6cf0\u6cf1\u6cf2\u6cf3\u6cf4\u6cf5\u6cf6\u6cf7\u6cf8\u6cf9\u6cfa\u6cfb\u6cfc\u6cfd\u6cfe\u6cff\u6d00\u6d01\u6d02\u6d03\u6d04\u6d05\u6d06\u6d07\u6d08\u6d09\u6d0a\u6d0b\u6d0c\u6d0d\u6d0e\u6d0f\u6d10\u6d11\u6d12\u6d13\u6d14\u6d15\u6d16\u6d17\u6d18\u6d19\u6d1a\u6d1b\u6d1c\u6d1d\u6d1e\u6d1f\u6d20\u6d21\u6d22\u6d23\u6d24\u6d25\u6d26\u6d27\u6d28\u6d29\u6d2a\u6d2b\u6d2c\u6d2d\u6d2e\u6d2f\u6d30\u6d31\u6d32\u6d33\u6d34\u6d35\u6d36\u6d37\u6d38\u6d39\u6d3a\u6d3b\u6d3c\u6d3d\u6d3e\u6d3f\u6d40\u6d41\u6d42\u6d43\u6d44\u6d45\u6d46\u6d47\u6d48\u6d49\u6d4a\u6d4b\u6d4c\u6d4d\u6d4e\u6d4f\u6d50\u6d51\u6d52\u6d53\u6d54\u6d55\u6d56\u6d57\u6d58\u6d59\u6d5a\u6d5b\u6d5c\u6d5d\u6d5e\u6d5f\u6d60\u6d61\u6d62\u6d63\u6d64\u6d65\u6d66\u6d67\u6d68\u6d69\u6d6a\u6d6b\u6d6c\u6d6d\u6d6e\u6d6f\u6d70\u6d71\u6d72\u6d73\u6d74\u6d75\u6d76\u6d77\u6d78\u6d79\u6d7a\u6d7b\u6d7c\u6d7d\u6d7e\u6d7f\u6d80\u6d81\u6d82\u6d83\u6d84\u6d85\u6d86\u6d87\u6d88\u6d89\u6d8a\u6d8b\u6d8c\u6d8d\u6d8e\u6d8f\u6d90\u6d91\u6d92\u6d93\u6d94\u6d95\u6d96\u6d97\u6d98\u6d99\u6d9a\u6d9b\u6d9c\u6d9d\u6d9e\u6d9f\u6da0\u6da1\u6da2\u6da3\u6da4\u6da5\u6da6\u6da7\u6da8\u6da9\u6daa\u6dab\u6dac\u6dad\u6dae\u6daf\u6db0\u6db1\u6db2\u6db3\u6db4\u6db5\u6db6\u6db7\u6db8\u6db9\u6dba\u6dbb\u6dbc\u6dbd\u6dbe\u6dbf\u6dc0\u6dc1\u6dc2\u6dc3\u6dc4\u6dc5\u6dc6\u6dc7\u6dc8\u6dc9\u6dca\u6dcb\u6dcc\u6dcd\u6dce\u6dcf\u6dd0\u6dd1\u6dd2\u6dd3\u6dd4\u6dd5\u6dd6\u6dd7\u6dd8\u6dd9\u6dda\u6ddb\u6ddc\u6ddd\u6dde\u6ddf\u6de0\u6de1\u6de2\u6de3\u6de4\u6de5\u6de6\u6de7\u6de8\u6de9\u6dea\u6deb\u6dec\u6ded\u6dee\u6def\u6df0\u6df1\u6df2\u6df3\u6df4\u6df5\u6df6\u6df7\u6df8\u6df9\u6dfa\u6dfb\u6dfc\u6dfd\u6dfe\u6dff\u6e00\u6e01\u6e02\u6e03\u6e04\u6e05\u6e06\u6e07\u6e08\u6e09\u6e0a\u6e0b\u6e0c\u6e0d\u6e0e\u6e0f\u6e10\u6e11\u6e12\u6e13\u6e14\u6e15\u6e16\u6e17\u6e18\u6e19\u6e1a\u6e1b\u6e1c\u6e1d\u6e1e\u6e1f\u6e20\u6e21\u6e22\u6e23\u6e24\u6e25\u6e26\u6e27\u6e28\u6e29\u6e2a\u6e2b\u6e2c\u6e2d\u6e2e\u6e2f\u6e30\u6e31\u6e32\u6e33\u6e34\u6e35\u6e36\u6e37\u6e38\u6e39\u6e3a\u6e3b\u6e3c\u6e3d\u6e3e\u6e3f\u6e40\u6e41\u6e42\u6e43\u6e44\u6e45\u6e46\u6e47\u6e48\u6e49\u6e4a\u6e4b\u6e4c\u6e4d\u6e4e\u6e4f\u6e50\u6e51\u6e52\u6e53\u6e54\u6e55\u6e56\u6e57\u6e58\u6e59\u6e5a\u6e5b\u6e5c\u6e5d\u6e5e\u6e5f\u6e60\u6e61\u6e62\u6e63\u6e64\u6e65\u6e66\u6e67\u6e68\u6e69\u6e6a\u6e6b\u6e6c\u6e6d\u6e6e\u6e6f\u6e70\u6e71\u6e72\u6e73\u6e74\u6e75\u6e76\u6e77\u6e78\u6e79\u6e7a\u6e7b\u6e7c\u6e7d\u6e7e\u6e7f\u6e80\u6e81\u6e82\u6e83\u6e84\u6e85\u6e86\u6e87\u6e88\u6e89\u6e8a\u6e8b\u6e8c\u6e8d\u6e8e\u6e8f\u6e90\u6e91\u6e92\u6e93\u6e94\u6e95\u6e96\u6e97\u6e98\u6e99\u6e9a\u6e9b\u6e9c\u6e9d\u6e9e\u6e9f\u6ea0\u6ea1\u6ea2\u6ea3\u6ea4\u6ea5\u6ea6\u6ea7\u6ea8\u6ea9\u6eaa\u6eab\u6eac\u6ead\u6eae\u6eaf\u6eb0\u6eb1\u6eb2\u6eb3\u6eb4\u6eb5\u6eb6\u6eb7\u6eb8\u6eb9\u6eba\u6ebb\u6ebc\u6ebd\u6ebe\u6ebf\u6ec0\u6ec1\u6ec2\u6ec3\u6ec4\u6ec5\u6ec6\u6ec7\u6ec8\u6ec9\u6eca\u6ecb\u6ecc\u6ecd\u6ece\u6ecf\u6ed0\u6ed1\u6ed2\u6ed3\u6ed4\u6ed5\u6ed6\u6ed7\u6ed8\u6ed9\u6eda\u6edb\u6edc\u6edd\u6ede\u6edf\u6ee0\u6ee1\u6ee2\u6ee3\u6ee4\u6ee5\u6ee6\u6ee7\u6ee8\u6ee9\u6eea\u6eeb\u6eec\u6eed\u6eee\u6eef\u6ef0\u6ef1\u6ef2\u6ef3\u6ef4\u6ef5\u6ef6\u6ef7\u6ef8\u6ef9\u6efa\u6efb\u6efc\u6efd\u6efe\u6eff\u6f00\u6f01\u6f02\u6f03\u6f04\u6f05\u6f06\u6f07\u6f08\u6f09\u6f0a\u6f0b\u6f0c\u6f0d\u6f0e\u6f0f\u6f10\u6f11\u6f12\u6f13\u6f14\u6f15\u6f16\u6f17\u6f18\u6f19\u6f1a\u6f1b\u6f1c\u6f1d\u6f1e\u6f1f\u6f20\u6f21\u6f22\u6f23\u6f24\u6f25\u6f26\u6f27\u6f28\u6f29\u6f2a\u6f2b\u6f2c\u6f2d\u6f2e\u6f2f\u6f30\u6f31\u6f32\u6f33\u6f34\u6f35\u6f36\u6f37\u6f38\u6f39\u6f3a\u6f3b\u6f3c\u6f3d\u6f3e\u6f3f\u6f40\u6f41\u6f42\u6f43\u6f44\u6f45\u6f46\u6f47\u6f48\u6f49\u6f4a\u6f4b\u6f4c\u6f4d\u6f4e\u6f4f\u6f50\u6f51\u6f52\u6f53\u6f54\u6f55\u6f56\u6f57\u6f58\u6f59\u6f5a\u6f5b\u6f5c\u6f5d\u6f5e\u6f5f\u6f60\u6f61\u6f62\u6f63\u6f64\u6f65\u6f66\u6f67\u6f68\u6f69\u6f6a\u6f6b\u6f6c\u6f6d\u6f6e\u6f6f\u6f70\u6f71\u6f72\u6f73\u6f74\u6f75\u6f76\u6f77\u6f78\u6f79\u6f7a\u6f7b\u6f7c\u6f7d\u6f7e\u6f7f\u6f80\u6f81\u6f82\u6f83\u6f84\u6f85\u6f86\u6f87\u6f88\u6f89\u6f8a\u6f8b\u6f8c\u6f8d\u6f8e\u6f8f\u6f90\u6f91\u6f92\u6f93\u6f94\u6f95\u6f96\u6f97\u6f98\u6f99\u6f9a\u6f9b\u6f9c\u6f9d\u6f9e\u6f9f\u6fa0\u6fa1\u6fa2\u6fa3\u6fa4\u6fa5\u6fa6\u6fa7\u6fa8\u6fa9\u6faa\u6fab\u6fac\u6fad\u6fae\u6faf\u6fb0\u6fb1\u6fb2\u6fb3\u6fb4\u6fb5\u6fb6\u6fb7\u6fb8\u6fb9\u6fba\u6fbb\u6fbc\u6fbd\u6fbe\u6fbf\u6fc0\u6fc1\u6fc2\u6fc3\u6fc4\u6fc5\u6fc6\u6fc7\u6fc8\u6fc9\u6fca\u6fcb\u6fcc\u6fcd\u6fce\u6fcf\u6fd0\u6fd1\u6fd2\u6fd3\u6fd4\u6fd5\u6fd6\u6fd7\u6fd8\u6fd9\u6fda\u6fdb\u6fdc\u6fdd\u6fde\u6fdf\u6fe0\u6fe1\u6fe2\u6fe3\u6fe4\u6fe5\u6fe6\u6fe7\u6fe8\u6fe9\u6fea\u6feb\u6fec\u6fed\u6fee\u6fef\u6ff0\u6ff1\u6ff2\u6ff3\u6ff4\u6ff5\u6ff6\u6ff7\u6ff8\u6ff9\u6ffa\u6ffb\u6ffc\u6ffd\u6ffe\u6fff\u7000\u7001\u7002\u7003\u7004\u7005\u7006\u7007\u7008\u7009\u700a\u700b\u700c\u700d\u700e\u700f\u7010\u7011\u7012\u7013\u7014\u7015\u7016\u7017\u7018\u7019\u701a\u701b\u701c\u701d\u701e\u701f\u7020\u7021\u7022\u7023\u7024\u7025\u7026\u7027\u7028\u7029\u702a\u702b\u702c\u702d\u702e\u702f\u7030\u7031\u7032\u7033\u7034\u7035\u7036\u7037\u7038\u7039\u703a\u703b\u703c\u703d\u703e\u703f\u7040\u7041\u7042\u7043\u7044\u7045\u7046\u7047\u7048\u7049\u704a\u704b\u704c\u704d\u704e\u704f\u7050\u7051\u7052\u7053\u7054\u7055\u7056\u7057\u7058\u7059\u705a\u705b\u705c\u705d\u705e\u705f\u7060\u7061\u7062\u7063\u7064\u7065\u7066\u7067\u7068\u7069\u706a\u706b\u706c\u706d\u706e\u706f\u7070\u7071\u7072\u7073\u7074\u7075\u7076\u7077\u7078\u7079\u707a\u707b\u707c\u707d\u707e\u707f\u7080\u7081\u7082\u7083\u7084\u7085\u7086\u7087\u7088\u7089\u708a\u708b\u708c\u708d\u708e\u708f\u7090\u7091\u7092\u7093\u7094\u7095\u7096\u7097\u7098\u7099\u709a\u709b\u709c\u709d\u709e\u709f\u70a0\u70a1\u70a2\u70a3\u70a4\u70a5\u70a6\u70a7\u70a8\u70a9\u70aa\u70ab\u70ac\u70ad\u70ae\u70af\u70b0\u70b1\u70b2\u70b3\u70b4\u70b5\u70b6\u70b7\u70b8\u70b9\u70ba\u70bb\u70bc\u70bd\u70be\u70bf\u70c0\u70c1\u70c2\u70c3\u70c4\u70c5\u70c6\u70c7\u70c8\u70c9\u70ca\u70cb\u70cc\u70cd\u70ce\u70cf\u70d0\u70d1\u70d2\u70d3\u70d4\u70d5\u70d6\u70d7\u70d8\u70d9\u70da\u70db\u70dc\u70dd\u70de\u70df\u70e0\u70e1\u70e2\u70e3\u70e4\u70e5\u70e6\u70e7\u70e8\u70e9\u70ea\u70eb\u70ec\u70ed\u70ee\u70ef\u70f0\u70f1\u70f2\u70f3\u70f4\u70f5\u70f6\u70f7\u70f8\u70f9\u70fa\u70fb\u70fc\u70fd\u70fe\u70ff\u7100\u7101\u7102\u7103\u7104\u7105\u7106\u7107\u7108\u7109\u710a\u710b\u710c\u710d\u710e\u710f\u7110\u7111\u7112\u7113\u7114\u7115\u7116\u7117\u7118\u7119\u711a\u711b\u711c\u711d\u711e\u711f\u7120\u7121\u7122\u7123\u7124\u7125\u7126\u7127\u7128\u7129\u712a\u712b\u712c\u712d\u712e\u712f\u7130\u7131\u7132\u7133\u7134\u7135\u7136\u7137\u7138\u7139\u713a\u713b\u713c\u713d\u713e\u713f\u7140\u7141\u7142\u7143\u7144\u7145\u7146\u7147\u7148\u7149\u714a\u714b\u714c\u714d\u714e\u714f\u7150\u7151\u7152\u7153\u7154\u7155\u7156\u7157\u7158\u7159\u715a\u715b\u715c\u715d\u715e\u715f\u7160\u7161\u7162\u7163\u7164\u7165\u7166\u7167\u7168\u7169\u716a\u716b\u716c\u716d\u716e\u716f\u7170\u7171\u7172\u7173\u7174\u7175\u7176\u7177\u7178\u7179\u717a\u717b\u717c\u717d\u717e\u717f\u7180\u7181\u7182\u7183\u7184\u7185\u7186\u7187\u7188\u7189\u718a\u718b\u718c\u718d\u718e\u718f\u7190\u7191\u7192\u7193\u7194\u7195\u7196\u7197\u7198\u7199\u719a\u719b\u719c\u719d\u719e\u719f\u71a0\u71a1\u71a2\u71a3\u71a4\u71a5\u71a6\u71a7\u71a8\u71a9\u71aa\u71ab\u71ac\u71ad\u71ae\u71af\u71b0\u71b1\u71b2\u71b3\u71b4\u71b5\u71b6\u71b7\u71b8\u71b9\u71ba\u71bb\u71bc\u71bd\u71be\u71bf\u71c0\u71c1\u71c2\u71c3\u71c4\u71c5\u71c6\u71c7\u71c8\u71c9\u71ca\u71cb\u71cc\u71cd\u71ce\u71cf\u71d0\u71d1\u71d2\u71d3\u71d4\u71d5\u71d6\u71d7\u71d8\u71d9\u71da\u71db\u71dc\u71dd\u71de\u71df\u71e0\u71e1\u71e2\u71e3\u71e4\u71e5\u71e6\u71e7\u71e8\u71e9\u71ea\u71eb\u71ec\u71ed\u71ee\u71ef\u71f0\u71f1\u71f2\u71f3\u71f4\u71f5\u71f6\u71f7\u71f8\u71f9\u71fa\u71fb\u71fc\u71fd\u71fe\u71ff\u7200\u7201\u7202\u7203\u7204\u7205\u7206\u7207\u7208\u7209\u720a\u720b\u720c\u720d\u720e\u720f\u7210\u7211\u7212\u7213\u7214\u7215\u7216\u7217\u7218\u7219\u721a\u721b\u721c\u721d\u721e\u721f\u7220\u7221\u7222\u7223\u7224\u7225\u7226\u7227\u7228\u7229\u722a\u722b\u722c\u722d\u722e\u722f\u7230\u7231\u7232\u7233\u7234\u7235\u7236\u7237\u7238\u7239\u723a\u723b\u723c\u723d\u723e\u723f\u7240\u7241\u7242\u7243\u7244\u7245\u7246\u7247\u7248\u7249\u724a\u724b\u724c\u724d\u724e\u724f\u7250\u7251\u7252\u7253\u7254\u7255\u7256\u7257\u7258\u7259\u725a\u725b\u725c\u725d\u725e\u725f\u7260\u7261\u7262\u7263\u7264\u7265\u7266\u7267\u7268\u7269\u726a\u726b\u726c\u726d\u726e\u726f\u7270\u7271\u7272\u7273\u7274\u7275\u7276\u7277\u7278\u7279\u727a\u727b\u727c\u727d\u727e\u727f\u7280\u7281\u7282\u7283\u7284\u7285\u7286\u7287\u7288\u7289\u728a\u728b\u728c\u728d\u728e\u728f\u7290\u7291\u7292\u7293\u7294\u7295\u7296\u7297\u7298\u7299\u729a\u729b\u729c\u729d\u729e\u729f\u72a0\u72a1\u72a2\u72a3\u72a4\u72a5\u72a6\u72a7\u72a8\u72a9\u72aa\u72ab\u72ac\u72ad\u72ae\u72af\u72b0\u72b1\u72b2\u72b3\u72b4\u72b5\u72b6\u72b7\u72b8\u72b9\u72ba\u72bb\u72bc\u72bd\u72be\u72bf\u72c0\u72c1\u72c2\u72c3\u72c4\u72c5\u72c6\u72c7\u72c8\u72c9\u72ca\u72cb\u72cc\u72cd\u72ce\u72cf\u72d0\u72d1\u72d2\u72d3\u72d4\u72d5\u72d6\u72d7\u72d8\u72d9\u72da\u72db\u72dc\u72dd\u72de\u72df\u72e0\u72e1\u72e2\u72e3\u72e4\u72e5\u72e6\u72e7\u72e8\u72e9\u72ea\u72eb\u72ec\u72ed\u72ee\u72ef\u72f0\u72f1\u72f2\u72f3\u72f4\u72f5\u72f6\u72f7\u72f8\u72f9\u72fa\u72fb\u72fc\u72fd\u72fe\u72ff\u7300\u7301\u7302\u7303\u7304\u7305\u7306\u7307\u7308\u7309\u730a\u730b\u730c\u730d\u730e\u730f\u7310\u7311\u7312\u7313\u7314\u7315\u7316\u7317\u7318\u7319\u731a\u731b\u731c\u731d\u731e\u731f\u7320\u7321\u7322\u7323\u7324\u7325\u7326\u7327\u7328\u7329\u732a\u732b\u732c\u732d\u732e\u732f\u7330\u7331\u7332\u7333\u7334\u7335\u7336\u7337\u7338\u7339\u733a\u733b\u733c\u733d\u733e\u733f\u7340\u7341\u7342\u7343\u7344\u7345\u7346\u7347\u7348\u7349\u734a\u734b\u734c\u734d\u734e\u734f\u7350\u7351\u7352\u7353\u7354\u7355\u7356\u7357\u7358\u7359\u735a\u735b\u735c\u735d\u735e\u735f\u7360\u7361\u7362\u7363\u7364\u7365\u7366\u7367\u7368\u7369\u736a\u736b\u736c\u736d\u736e\u736f\u7370\u7371\u7372\u7373\u7374\u7375\u7376\u7377\u7378\u7379\u737a\u737b\u737c\u737d\u737e\u737f\u7380\u7381\u7382\u7383\u7384\u7385\u7386\u7387\u7388\u7389\u738a\u738b\u738c\u738d\u738e\u738f\u7390\u7391\u7392\u7393\u7394\u7395\u7396\u7397\u7398\u7399\u739a\u739b\u739c\u739d\u739e\u739f\u73a0\u73a1\u73a2\u73a3\u73a4\u73a5\u73a6\u73a7\u73a8\u73a9\u73aa\u73ab\u73ac\u73ad\u73ae\u73af\u73b0\u73b1\u73b2\u73b3\u73b4\u73b5\u73b6\u73b7\u73b8\u73b9\u73ba\u73bb\u73bc\u73bd\u73be\u73bf\u73c0\u73c1\u73c2\u73c3\u73c4\u73c5\u73c6\u73c7\u73c8\u73c9\u73ca\u73cb\u73cc\u73cd\u73ce\u73cf\u73d0\u73d1\u73d2\u73d3\u73d4\u73d5\u73d6\u73d7\u73d8\u73d9\u73da\u73db\u73dc\u73dd\u73de\u73df\u73e0\u73e1\u73e2\u73e3\u73e4\u73e5\u73e6\u73e7\u73e8\u73e9\u73ea\u73eb\u73ec\u73ed\u73ee\u73ef\u73f0\u73f1\u73f2\u73f3\u73f4\u73f5\u73f6\u73f7\u73f8\u73f9\u73fa\u73fb\u73fc\u73fd\u73fe\u73ff\u7400\u7401\u7402\u7403\u7404\u7405\u7406\u7407\u7408\u7409\u740a\u740b\u740c\u740d\u740e\u740f\u7410\u7411\u7412\u7413\u7414\u7415\u7416\u7417\u7418\u7419\u741a\u741b\u741c\u741d\u741e\u741f\u7420\u7421\u7422\u7423\u7424\u7425\u7426\u7427\u7428\u7429\u742a\u742b\u742c\u742d\u742e\u742f\u7430\u7431\u7432\u7433\u7434\u7435\u7436\u7437\u7438\u7439\u743a\u743b\u743c\u743d\u743e\u743f\u7440\u7441\u7442\u7443\u7444\u7445\u7446\u7447\u7448\u7449\u744a\u744b\u744c\u744d\u744e\u744f\u7450\u7451\u7452\u7453\u7454\u7455\u7456\u7457\u7458\u7459\u745a\u745b\u745c\u745d\u745e\u745f\u7460\u7461\u7462\u7463\u7464\u7465\u7466\u7467\u7468\u7469\u746a\u746b\u746c\u746d\u746e\u746f\u7470\u7471\u7472\u7473\u7474\u7475\u7476\u7477\u7478\u7479\u747a\u747b\u747c\u747d\u747e\u747f\u7480\u7481\u7482\u7483\u7484\u7485\u7486\u7487\u7488\u7489\u748a\u748b\u748c\u748d\u748e\u748f\u7490\u7491\u7492\u7493\u7494\u7495\u7496\u7497\u7498\u7499\u749a\u749b\u749c\u749d\u749e\u749f\u74a0\u74a1\u74a2\u74a3\u74a4\u74a5\u74a6\u74a7\u74a8\u74a9\u74aa\u74ab\u74ac\u74ad\u74ae\u74af\u74b0\u74b1\u74b2\u74b3\u74b4\u74b5\u74b6\u74b7\u74b8\u74b9\u74ba\u74bb\u74bc\u74bd\u74be\u74bf\u74c0\u74c1\u74c2\u74c3\u74c4\u74c5\u74c6\u74c7\u74c8\u74c9\u74ca\u74cb\u74cc\u74cd\u74ce\u74cf\u74d0\u74d1\u74d2\u74d3\u74d4\u74d5\u74d6\u74d7\u74d8\u74d9\u74da\u74db\u74dc\u74dd\u74de\u74df\u74e0\u74e1\u74e2\u74e3\u74e4\u74e5\u74e6\u74e7\u74e8\u74e9\u74ea\u74eb\u74ec\u74ed\u74ee\u74ef\u74f0\u74f1\u74f2\u74f3\u74f4\u74f5\u74f6\u74f7\u74f8\u74f9\u74fa\u74fb\u74fc\u74fd\u74fe\u74ff\u7500\u7501\u7502\u7503\u7504\u7505\u7506\u7507\u7508\u7509\u750a\u750b\u750c\u750d\u750e\u750f\u7510\u7511\u7512\u7513\u7514\u7515\u7516\u7517\u7518\u7519\u751a\u751b\u751c\u751d\u751e\u751f\u7520\u7521\u7522\u7523\u7524\u7525\u7526\u7527\u7528\u7529\u752a\u752b\u752c\u752d\u752e\u752f\u7530\u7531\u7532\u7533\u7534\u7535\u7536\u7537\u7538\u7539\u753a\u753b\u753c\u753d\u753e\u753f\u7540\u7541\u7542\u7543\u7544\u7545\u7546\u7547\u7548\u7549\u754a\u754b\u754c\u754d\u754e\u754f\u7550\u7551\u7552\u7553\u7554\u7555\u7556\u7557\u7558\u7559\u755a\u755b\u755c\u755d\u755e\u755f\u7560\u7561\u7562\u7563\u7564\u7565\u7566\u7567\u7568\u7569\u756a\u756b\u756c\u756d\u756e\u756f\u7570\u7571\u7572\u7573\u7574\u7575\u7576\u7577\u7578\u7579\u757a\u757b\u757c\u757d\u757e\u757f\u7580\u7581\u7582\u7583\u7584\u7585\u7586\u7587\u7588\u7589\u758a\u758b\u758c\u758d\u758e\u758f\u7590\u7591\u7592\u7593\u7594\u7595\u7596\u7597\u7598\u7599\u759a\u759b\u759c\u759d\u759e\u759f\u75a0\u75a1\u75a2\u75a3\u75a4\u75a5\u75a6\u75a7\u75a8\u75a9\u75aa\u75ab\u75ac\u75ad\u75ae\u75af\u75b0\u75b1\u75b2\u75b3\u75b4\u75b5\u75b6\u75b7\u75b8\u75b9\u75ba\u75bb\u75bc\u75bd\u75be\u75bf\u75c0\u75c1\u75c2\u75c3\u75c4\u75c5\u75c6\u75c7\u75c8\u75c9\u75ca\u75cb\u75cc\u75cd\u75ce\u75cf\u75d0\u75d1\u75d2\u75d3\u75d4\u75d5\u75d6\u75d7\u75d8\u75d9\u75da\u75db\u75dc\u75dd\u75de\u75df\u75e0\u75e1\u75e2\u75e3\u75e4\u75e5\u75e6\u75e7\u75e8\u75e9\u75ea\u75eb\u75ec\u75ed\u75ee\u75ef\u75f0\u75f1\u75f2\u75f3\u75f4\u75f5\u75f6\u75f7\u75f8\u75f9\u75fa\u75fb\u75fc\u75fd\u75fe\u75ff\u7600\u7601\u7602\u7603\u7604\u7605\u7606\u7607\u7608\u7609\u760a\u760b\u760c\u760d\u760e\u760f\u7610\u7611\u7612\u7613\u7614\u7615\u7616\u7617\u7618\u7619\u761a\u761b\u761c\u761d\u761e\u761f\u7620\u7621\u7622\u7623\u7624\u7625\u7626\u7627\u7628\u7629\u762a\u762b\u762c\u762d\u762e\u762f\u7630\u7631\u7632\u7633\u7634\u7635\u7636\u7637\u7638\u7639\u763a\u763b\u763c\u763d\u763e\u763f\u7640\u7641\u7642\u7643\u7644\u7645\u7646\u7647\u7648\u7649\u764a\u764b\u764c\u764d\u764e\u764f\u7650\u7651\u7652\u7653\u7654\u7655\u7656\u7657\u7658\u7659\u765a\u765b\u765c\u765d\u765e\u765f\u7660\u7661\u7662\u7663\u7664\u7665\u7666\u7667\u7668\u7669\u766a\u766b\u766c\u766d\u766e\u766f\u7670\u7671\u7672\u7673\u7674\u7675\u7676\u7677\u7678\u7679\u767a\u767b\u767c\u767d\u767e\u767f\u7680\u7681\u7682\u7683\u7684\u7685\u7686\u7687\u7688\u7689\u768a\u768b\u768c\u768d\u768e\u768f\u7690\u7691\u7692\u7693\u7694\u7695\u7696\u7697\u7698\u7699\u769a\u769b\u769c\u769d\u769e\u769f\u76a0\u76a1\u76a2\u76a3\u76a4\u76a5\u76a6\u76a7\u76a8\u76a9\u76aa\u76ab\u76ac\u76ad\u76ae\u76af\u76b0\u76b1\u76b2\u76b3\u76b4\u76b5\u76b6\u76b7\u76b8\u76b9\u76ba\u76bb\u76bc\u76bd\u76be\u76bf\u76c0\u76c1\u76c2\u76c3\u76c4\u76c5\u76c6\u76c7\u76c8\u76c9\u76ca\u76cb\u76cc\u76cd\u76ce\u76cf\u76d0\u76d1\u76d2\u76d3\u76d4\u76d5\u76d6\u76d7\u76d8\u76d9\u76da\u76db\u76dc\u76dd\u76de\u76df\u76e0\u76e1\u76e2\u76e3\u76e4\u76e5\u76e6\u76e7\u76e8\u76e9\u76ea\u76eb\u76ec\u76ed\u76ee\u76ef\u76f0\u76f1\u76f2\u76f3\u76f4\u76f5\u76f6\u76f7\u76f8\u76f9\u76fa\u76fb\u76fc\u76fd\u76fe\u76ff\u7700\u7701\u7702\u7703\u7704\u7705\u7706\u7707\u7708\u7709\u770a\u770b\u770c\u770d\u770e\u770f\u7710\u7711\u7712\u7713\u7714\u7715\u7716\u7717\u7718\u7719\u771a\u771b\u771c\u771d\u771e\u771f\u7720\u7721\u7722\u7723\u7724\u7725\u7726\u7727\u7728\u7729\u772a\u772b\u772c\u772d\u772e\u772f\u7730\u7731\u7732\u7733\u7734\u7735\u7736\u7737\u7738\u7739\u773a\u773b\u773c\u773d\u773e\u773f\u7740\u7741\u7742\u7743\u7744\u7745\u7746\u7747\u7748\u7749\u774a\u774b\u774c\u774d\u774e\u774f\u7750\u7751\u7752\u7753\u7754\u7755\u7756\u7757\u7758\u7759\u775a\u775b\u775c\u775d\u775e\u775f\u7760\u7761\u7762\u7763\u7764\u7765\u7766\u7767\u7768\u7769\u776a\u776b\u776c\u776d\u776e\u776f\u7770\u7771\u7772\u7773\u7774\u7775\u7776\u7777\u7778\u7779\u777a\u777b\u777c\u777d\u777e\u777f\u7780\u7781\u7782\u7783\u7784\u7785\u7786\u7787\u7788\u7789\u778a\u778b\u778c\u778d\u778e\u778f\u7790\u7791\u7792\u7793\u7794\u7795\u7796\u7797\u7798\u7799\u779a\u779b\u779c\u779d\u779e\u779f\u77a0\u77a1\u77a2\u77a3\u77a4\u77a5\u77a6\u77a7\u77a8\u77a9\u77aa\u77ab\u77ac\u77ad\u77ae\u77af\u77b0\u77b1\u77b2\u77b3\u77b4\u77b5\u77b6\u77b7\u77b8\u77b9\u77ba\u77bb\u77bc\u77bd\u77be\u77bf\u77c0\u77c1\u77c2\u77c3\u77c4\u77c5\u77c6\u77c7\u77c8\u77c9\u77ca\u77cb\u77cc\u77cd\u77ce\u77cf\u77d0\u77d1\u77d2\u77d3\u77d4\u77d5\u77d6\u77d7\u77d8\u77d9\u77da\u77db\u77dc\u77dd\u77de\u77df\u77e0\u77e1\u77e2\u77e3\u77e4\u77e5\u77e6\u77e7\u77e8\u77e9\u77ea\u77eb\u77ec\u77ed\u77ee\u77ef\u77f0\u77f1\u77f2\u77f3\u77f4\u77f5\u77f6\u77f7\u77f8\u77f9\u77fa\u77fb\u77fc\u77fd\u77fe\u77ff\u7800\u7801\u7802\u7803\u7804\u7805\u7806\u7807\u7808\u7809\u780a\u780b\u780c\u780d\u780e\u780f\u7810\u7811\u7812\u7813\u7814\u7815\u7816\u7817\u7818\u7819\u781a\u781b\u781c\u781d\u781e\u781f\u7820\u7821\u7822\u7823\u7824\u7825\u7826\u7827\u7828\u7829\u782a\u782b\u782c\u782d\u782e\u782f\u7830\u7831\u7832\u7833\u7834\u7835\u7836\u7837\u7838\u7839\u783a\u783b\u783c\u783d\u783e\u783f\u7840\u7841\u7842\u7843\u7844\u7845\u7846\u7847\u7848\u7849\u784a\u784b\u784c\u784d\u784e\u784f\u7850\u7851\u7852\u7853\u7854\u7855\u7856\u7857\u7858\u7859\u785a\u785b\u785c\u785d\u785e\u785f\u7860\u7861\u7862\u7863\u7864\u7865\u7866\u7867\u7868\u7869\u786a\u786b\u786c\u786d\u786e\u786f\u7870\u7871\u7872\u7873\u7874\u7875\u7876\u7877\u7878\u7879\u787a\u787b\u787c\u787d\u787e\u787f\u7880\u7881\u7882\u7883\u7884\u7885\u7886\u7887\u7888\u7889\u788a\u788b\u788c\u788d\u788e\u788f\u7890\u7891\u7892\u7893\u7894\u7895\u7896\u7897\u7898\u7899\u789a\u789b\u789c\u789d\u789e\u789f\u78a0\u78a1\u78a2\u78a3\u78a4\u78a5\u78a6\u78a7\u78a8\u78a9\u78aa\u78ab\u78ac\u78ad\u78ae\u78af\u78b0\u78b1\u78b2\u78b3\u78b4\u78b5\u78b6\u78b7\u78b8\u78b9\u78ba\u78bb\u78bc\u78bd\u78be\u78bf\u78c0\u78c1\u78c2\u78c3\u78c4\u78c5\u78c6\u78c7\u78c8\u78c9\u78ca\u78cb\u78cc\u78cd\u78ce\u78cf\u78d0\u78d1\u78d2\u78d3\u78d4\u78d5\u78d6\u78d7\u78d8\u78d9\u78da\u78db\u78dc\u78dd\u78de\u78df\u78e0\u78e1\u78e2\u78e3\u78e4\u78e5\u78e6\u78e7\u78e8\u78e9\u78ea\u78eb\u78ec\u78ed\u78ee\u78ef\u78f0\u78f1\u78f2\u78f3\u78f4\u78f5\u78f6\u78f7\u78f8\u78f9\u78fa\u78fb\u78fc\u78fd\u78fe\u78ff\u7900\u7901\u7902\u7903\u7904\u7905\u7906\u7907\u7908\u7909\u790a\u790b\u790c\u790d\u790e\u790f\u7910\u7911\u7912\u7913\u7914\u7915\u7916\u7917\u7918\u7919\u791a\u791b\u791c\u791d\u791e\u791f\u7920\u7921\u7922\u7923\u7924\u7925\u7926\u7927\u7928\u7929\u792a\u792b\u792c\u792d\u792e\u792f\u7930\u7931\u7932\u7933\u7934\u7935\u7936\u7937\u7938\u7939\u793a\u793b\u793c\u793d\u793e\u793f\u7940\u7941\u7942\u7943\u7944\u7945\u7946\u7947\u7948\u7949\u794a\u794b\u794c\u794d\u794e\u794f\u7950\u7951\u7952\u7953\u7954\u7955\u7956\u7957\u7958\u7959\u795a\u795b\u795c\u795d\u795e\u795f\u7960\u7961\u7962\u7963\u7964\u7965\u7966\u7967\u7968\u7969\u796a\u796b\u796c\u796d\u796e\u796f\u7970\u7971\u7972\u7973\u7974\u7975\u7976\u7977\u7978\u7979\u797a\u797b\u797c\u797d\u797e\u797f\u7980\u7981\u7982\u7983\u7984\u7985\u7986\u7987\u7988\u7989\u798a\u798b\u798c\u798d\u798e\u798f\u7990\u7991\u7992\u7993\u7994\u7995\u7996\u7997\u7998\u7999\u799a\u799b\u799c\u799d\u799e\u799f\u79a0\u79a1\u79a2\u79a3\u79a4\u79a5\u79a6\u79a7\u79a8\u79a9\u79aa\u79ab\u79ac\u79ad\u79ae\u79af\u79b0\u79b1\u79b2\u79b3\u79b4\u79b5\u79b6\u79b7\u79b8\u79b9\u79ba\u79bb\u79bc\u79bd\u79be\u79bf\u79c0\u79c1\u79c2\u79c3\u79c4\u79c5\u79c6\u79c7\u79c8\u79c9\u79ca\u79cb\u79cc\u79cd\u79ce\u79cf\u79d0\u79d1\u79d2\u79d3\u79d4\u79d5\u79d6\u79d7\u79d8\u79d9\u79da\u79db\u79dc\u79dd\u79de\u79df\u79e0\u79e1\u79e2\u79e3\u79e4\u79e5\u79e6\u79e7\u79e8\u79e9\u79ea\u79eb\u79ec\u79ed\u79ee\u79ef\u79f0\u79f1\u79f2\u79f3\u79f4\u79f5\u79f6\u79f7\u79f8\u79f9\u79fa\u79fb\u79fc\u79fd\u79fe\u79ff\u7a00\u7a01\u7a02\u7a03\u7a04\u7a05\u7a06\u7a07\u7a08\u7a09\u7a0a\u7a0b\u7a0c\u7a0d\u7a0e\u7a0f\u7a10\u7a11\u7a12\u7a13\u7a14\u7a15\u7a16\u7a17\u7a18\u7a19\u7a1a\u7a1b\u7a1c\u7a1d\u7a1e\u7a1f\u7a20\u7a21\u7a22\u7a23\u7a24\u7a25\u7a26\u7a27\u7a28\u7a29\u7a2a\u7a2b\u7a2c\u7a2d\u7a2e\u7a2f\u7a30\u7a31\u7a32\u7a33\u7a34\u7a35\u7a36\u7a37\u7a38\u7a39\u7a3a\u7a3b\u7a3c\u7a3d\u7a3e\u7a3f\u7a40\u7a41\u7a42\u7a43\u7a44\u7a45\u7a46\u7a47\u7a48\u7a49\u7a4a\u7a4b\u7a4c\u7a4d\u7a4e\u7a4f\u7a50\u7a51\u7a52\u7a53\u7a54\u7a55\u7a56\u7a57\u7a58\u7a59\u7a5a\u7a5b\u7a5c\u7a5d\u7a5e\u7a5f\u7a60\u7a61\u7a62\u7a63\u7a64\u7a65\u7a66\u7a67\u7a68\u7a69\u7a6a\u7a6b\u7a6c\u7a6d\u7a6e\u7a6f\u7a70\u7a71\u7a72\u7a73\u7a74\u7a75\u7a76\u7a77\u7a78\u7a79\u7a7a\u7a7b\u7a7c\u7a7d\u7a7e\u7a7f\u7a80\u7a81\u7a82\u7a83\u7a84\u7a85\u7a86\u7a87\u7a88\u7a89\u7a8a\u7a8b\u7a8c\u7a8d\u7a8e\u7a8f\u7a90\u7a91\u7a92\u7a93\u7a94\u7a95\u7a96\u7a97\u7a98\u7a99\u7a9a\u7a9b\u7a9c\u7a9d\u7a9e\u7a9f\u7aa0\u7aa1\u7aa2\u7aa3\u7aa4\u7aa5\u7aa6\u7aa7\u7aa8\u7aa9\u7aaa\u7aab\u7aac\u7aad\u7aae\u7aaf\u7ab0\u7ab1\u7ab2\u7ab3\u7ab4\u7ab5\u7ab6\u7ab7\u7ab8\u7ab9\u7aba\u7abb\u7abc\u7abd\u7abe\u7abf\u7ac0\u7ac1\u7ac2\u7ac3\u7ac4\u7ac5\u7ac6\u7ac7\u7ac8\u7ac9\u7aca\u7acb\u7acc\u7acd\u7ace\u7acf\u7ad0\u7ad1\u7ad2\u7ad3\u7ad4\u7ad5\u7ad6\u7ad7\u7ad8\u7ad9\u7ada\u7adb\u7adc\u7add\u7ade\u7adf\u7ae0\u7ae1\u7ae2\u7ae3\u7ae4\u7ae5\u7ae6\u7ae7\u7ae8\u7ae9\u7aea\u7aeb\u7aec\u7aed\u7aee\u7aef\u7af0\u7af1\u7af2\u7af3\u7af4\u7af5\u7af6\u7af7\u7af8\u7af9\u7afa\u7afb\u7afc\u7afd\u7afe\u7aff\u7b00\u7b01\u7b02\u7b03\u7b04\u7b05\u7b06\u7b07\u7b08\u7b09\u7b0a\u7b0b\u7b0c\u7b0d\u7b0e\u7b0f\u7b10\u7b11\u7b12\u7b13\u7b14\u7b15\u7b16\u7b17\u7b18\u7b19\u7b1a\u7b1b\u7b1c\u7b1d\u7b1e\u7b1f\u7b20\u7b21\u7b22\u7b23\u7b24\u7b25\u7b26\u7b27\u7b28\u7b29\u7b2a\u7b2b\u7b2c\u7b2d\u7b2e\u7b2f\u7b30\u7b31\u7b32\u7b33\u7b34\u7b35\u7b36\u7b37\u7b38\u7b39\u7b3a\u7b3b\u7b3c\u7b3d\u7b3e\u7b3f\u7b40\u7b41\u7b42\u7b43\u7b44\u7b45\u7b46\u7b47\u7b48\u7b49\u7b4a\u7b4b\u7b4c\u7b4d\u7b4e\u7b4f\u7b50\u7b51\u7b52\u7b53\u7b54\u7b55\u7b56\u7b57\u7b58\u7b59\u7b5a\u7b5b\u7b5c\u7b5d\u7b5e\u7b5f\u7b60\u7b61\u7b62\u7b63\u7b64\u7b65\u7b66\u7b67\u7b68\u7b69\u7b6a\u7b6b\u7b6c\u7b6d\u7b6e\u7b6f\u7b70\u7b71\u7b72\u7b73\u7b74\u7b75\u7b76\u7b77\u7b78\u7b79\u7b7a\u7b7b\u7b7c\u7b7d\u7b7e\u7b7f\u7b80\u7b81\u7b82\u7b83\u7b84\u7b85\u7b86\u7b87\u7b88\u7b89\u7b8a\u7b8b\u7b8c\u7b8d\u7b8e\u7b8f\u7b90\u7b91\u7b92\u7b93\u7b94\u7b95\u7b96\u7b97\u7b98\u7b99\u7b9a\u7b9b\u7b9c\u7b9d\u7b9e\u7b9f\u7ba0\u7ba1\u7ba2\u7ba3\u7ba4\u7ba5\u7ba6\u7ba7\u7ba8\u7ba9\u7baa\u7bab\u7bac\u7bad\u7bae\u7baf\u7bb0\u7bb1\u7bb2\u7bb3\u7bb4\u7bb5\u7bb6\u7bb7\u7bb8\u7bb9\u7bba\u7bbb\u7bbc\u7bbd\u7bbe\u7bbf\u7bc0\u7bc1\u7bc2\u7bc3\u7bc4\u7bc5\u7bc6\u7bc7\u7bc8\u7bc9\u7bca\u7bcb\u7bcc\u7bcd\u7bce\u7bcf\u7bd0\u7bd1\u7bd2\u7bd3\u7bd4\u7bd5\u7bd6\u7bd7\u7bd8\u7bd9\u7bda\u7bdb\u7bdc\u7bdd\u7bde\u7bdf\u7be0\u7be1\u7be2\u7be3\u7be4\u7be5\u7be6\u7be7\u7be8\u7be9\u7bea\u7beb\u7bec\u7bed\u7bee\u7bef\u7bf0\u7bf1\u7bf2\u7bf3\u7bf4\u7bf5\u7bf6\u7bf7\u7bf8\u7bf9\u7bfa\u7bfb\u7bfc\u7bfd\u7bfe\u7bff\u7c00\u7c01\u7c02\u7c03\u7c04\u7c05\u7c06\u7c07\u7c08\u7c09\u7c0a\u7c0b\u7c0c\u7c0d\u7c0e\u7c0f\u7c10\u7c11\u7c12\u7c13\u7c14\u7c15\u7c16\u7c17\u7c18\u7c19\u7c1a\u7c1b\u7c1c\u7c1d\u7c1e\u7c1f\u7c20\u7c21\u7c22\u7c23\u7c24\u7c25\u7c26\u7c27\u7c28\u7c29\u7c2a\u7c2b\u7c2c\u7c2d\u7c2e\u7c2f\u7c30\u7c31\u7c32\u7c33\u7c34\u7c35\u7c36\u7c37\u7c38\u7c39\u7c3a\u7c3b\u7c3c\u7c3d\u7c3e\u7c3f\u7c40\u7c41\u7c42\u7c43\u7c44\u7c45\u7c46\u7c47\u7c48\u7c49\u7c4a\u7c4b\u7c4c\u7c4d\u7c4e\u7c4f\u7c50\u7c51\u7c52\u7c53\u7c54\u7c55\u7c56\u7c57\u7c58\u7c59\u7c5a\u7c5b\u7c5c\u7c5d\u7c5e\u7c5f\u7c60\u7c61\u7c62\u7c63\u7c64\u7c65\u7c66\u7c67\u7c68\u7c69\u7c6a\u7c6b\u7c6c\u7c6d\u7c6e\u7c6f\u7c70\u7c71\u7c72\u7c73\u7c74\u7c75\u7c76\u7c77\u7c78\u7c79\u7c7a\u7c7b\u7c7c\u7c7d\u7c7e\u7c7f\u7c80\u7c81\u7c82\u7c83\u7c84\u7c85\u7c86\u7c87\u7c88\u7c89\u7c8a\u7c8b\u7c8c\u7c8d\u7c8e\u7c8f\u7c90\u7c91\u7c92\u7c93\u7c94\u7c95\u7c96\u7c97\u7c98\u7c99\u7c9a\u7c9b\u7c9c\u7c9d\u7c9e\u7c9f\u7ca0\u7ca1\u7ca2\u7ca3\u7ca4\u7ca5\u7ca6\u7ca7\u7ca8\u7ca9\u7caa\u7cab\u7cac\u7cad\u7cae\u7caf\u7cb0\u7cb1\u7cb2\u7cb3\u7cb4\u7cb5\u7cb6\u7cb7\u7cb8\u7cb9\u7cba\u7cbb\u7cbc\u7cbd\u7cbe\u7cbf\u7cc0\u7cc1\u7cc2\u7cc3\u7cc4\u7cc5\u7cc6\u7cc7\u7cc8\u7cc9\u7cca\u7ccb\u7ccc\u7ccd\u7cce\u7ccf\u7cd0\u7cd1\u7cd2\u7cd3\u7cd4\u7cd5\u7cd6\u7cd7\u7cd8\u7cd9\u7cda\u7cdb\u7cdc\u7cdd\u7cde\u7cdf\u7ce0\u7ce1\u7ce2\u7ce3\u7ce4\u7ce5\u7ce6\u7ce7\u7ce8\u7ce9\u7cea\u7ceb\u7cec\u7ced\u7cee\u7cef\u7cf0\u7cf1\u7cf2\u7cf3\u7cf4\u7cf5\u7cf6\u7cf7\u7cf8\u7cf9\u7cfa\u7cfb\u7cfc\u7cfd\u7cfe\u7cff\u7d00\u7d01\u7d02\u7d03\u7d04\u7d05\u7d06\u7d07\u7d08\u7d09\u7d0a\u7d0b\u7d0c\u7d0d\u7d0e\u7d0f\u7d10\u7d11\u7d12\u7d13\u7d14\u7d15\u7d16\u7d17\u7d18\u7d19\u7d1a\u7d1b\u7d1c\u7d1d\u7d1e\u7d1f\u7d20\u7d21\u7d22\u7d23\u7d24\u7d25\u7d26\u7d27\u7d28\u7d29\u7d2a\u7d2b\u7d2c\u7d2d\u7d2e\u7d2f\u7d30\u7d31\u7d32\u7d33\u7d34\u7d35\u7d36\u7d37\u7d38\u7d39\u7d3a\u7d3b\u7d3c\u7d3d\u7d3e\u7d3f\u7d40\u7d41\u7d42\u7d43\u7d44\u7d45\u7d46\u7d47\u7d48\u7d49\u7d4a\u7d4b\u7d4c\u7d4d\u7d4e\u7d4f\u7d50\u7d51\u7d52\u7d53\u7d54\u7d55\u7d56\u7d57\u7d58\u7d59\u7d5a\u7d5b\u7d5c\u7d5d\u7d5e\u7d5f\u7d60\u7d61\u7d62\u7d63\u7d64\u7d65\u7d66\u7d67\u7d68\u7d69\u7d6a\u7d6b\u7d6c\u7d6d\u7d6e\u7d6f\u7d70\u7d71\u7d72\u7d73\u7d74\u7d75\u7d76\u7d77\u7d78\u7d79\u7d7a\u7d7b\u7d7c\u7d7d\u7d7e\u7d7f\u7d80\u7d81\u7d82\u7d83\u7d84\u7d85\u7d86\u7d87\u7d88\u7d89\u7d8a\u7d8b\u7d8c\u7d8d\u7d8e\u7d8f\u7d90\u7d91\u7d92\u7d93\u7d94\u7d95\u7d96\u7d97\u7d98\u7d99\u7d9a\u7d9b\u7d9c\u7d9d\u7d9e\u7d9f\u7da0\u7da1\u7da2\u7da3\u7da4\u7da5\u7da6\u7da7\u7da8\u7da9\u7daa\u7dab\u7dac\u7dad\u7dae\u7daf\u7db0\u7db1\u7db2\u7db3\u7db4\u7db5\u7db6\u7db7\u7db8\u7db9\u7dba\u7dbb\u7dbc\u7dbd\u7dbe\u7dbf\u7dc0\u7dc1\u7dc2\u7dc3\u7dc4\u7dc5\u7dc6\u7dc7\u7dc8\u7dc9\u7dca\u7dcb\u7dcc\u7dcd\u7dce\u7dcf\u7dd0\u7dd1\u7dd2\u7dd3\u7dd4\u7dd5\u7dd6\u7dd7\u7dd8\u7dd9\u7dda\u7ddb\u7ddc\u7ddd\u7dde\u7ddf\u7de0\u7de1\u7de2\u7de3\u7de4\u7de5\u7de6\u7de7\u7de8\u7de9\u7dea\u7deb\u7dec\u7ded\u7dee\u7def\u7df0\u7df1\u7df2\u7df3\u7df4\u7df5\u7df6\u7df7\u7df8\u7df9\u7dfa\u7dfb\u7dfc\u7dfd\u7dfe\u7dff\u7e00\u7e01\u7e02\u7e03\u7e04\u7e05\u7e06\u7e07\u7e08\u7e09\u7e0a\u7e0b\u7e0c\u7e0d\u7e0e\u7e0f\u7e10\u7e11\u7e12\u7e13\u7e14\u7e15\u7e16\u7e17\u7e18\u7e19\u7e1a\u7e1b\u7e1c\u7e1d\u7e1e\u7e1f\u7e20\u7e21\u7e22\u7e23\u7e24\u7e25\u7e26\u7e27\u7e28\u7e29\u7e2a\u7e2b\u7e2c\u7e2d\u7e2e\u7e2f\u7e30\u7e31\u7e32\u7e33\u7e34\u7e35\u7e36\u7e37\u7e38\u7e39\u7e3a\u7e3b\u7e3c\u7e3d\u7e3e\u7e3f\u7e40\u7e41\u7e42\u7e43\u7e44\u7e45\u7e46\u7e47\u7e48\u7e49\u7e4a\u7e4b\u7e4c\u7e4d\u7e4e\u7e4f\u7e50\u7e51\u7e52\u7e53\u7e54\u7e55\u7e56\u7e57\u7e58\u7e59\u7e5a\u7e5b\u7e5c\u7e5d\u7e5e\u7e5f\u7e60\u7e61\u7e62\u7e63\u7e64\u7e65\u7e66\u7e67\u7e68\u7e69\u7e6a\u7e6b\u7e6c\u7e6d\u7e6e\u7e6f\u7e70\u7e71\u7e72\u7e73\u7e74\u7e75\u7e76\u7e77\u7e78\u7e79\u7e7a\u7e7b\u7e7c\u7e7d\u7e7e\u7e7f\u7e80\u7e81\u7e82\u7e83\u7e84\u7e85\u7e86\u7e87\u7e88\u7e89\u7e8a\u7e8b\u7e8c\u7e8d\u7e8e\u7e8f\u7e90\u7e91\u7e92\u7e93\u7e94\u7e95\u7e96\u7e97\u7e98\u7e99\u7e9a\u7e9b\u7e9c\u7e9d\u7e9e\u7e9f\u7ea0\u7ea1\u7ea2\u7ea3\u7ea4\u7ea5\u7ea6\u7ea7\u7ea8\u7ea9\u7eaa\u7eab\u7eac\u7ead\u7eae\u7eaf\u7eb0\u7eb1\u7eb2\u7eb3\u7eb4\u7eb5\u7eb6\u7eb7\u7eb8\u7eb9\u7eba\u7ebb\u7ebc\u7ebd\u7ebe\u7ebf\u7ec0\u7ec1\u7ec2\u7ec3\u7ec4\u7ec5\u7ec6\u7ec7\u7ec8\u7ec9\u7eca\u7ecb\u7ecc\u7ecd\u7ece\u7ecf\u7ed0\u7ed1\u7ed2\u7ed3\u7ed4\u7ed5\u7ed6\u7ed7\u7ed8\u7ed9\u7eda\u7edb\u7edc\u7edd\u7ede\u7edf\u7ee0\u7ee1\u7ee2\u7ee3\u7ee4\u7ee5\u7ee6\u7ee7\u7ee8\u7ee9\u7eea\u7eeb\u7eec\u7eed\u7eee\u7eef\u7ef0\u7ef1\u7ef2\u7ef3\u7ef4\u7ef5\u7ef6\u7ef7\u7ef8\u7ef9\u7efa\u7efb\u7efc\u7efd\u7efe\u7eff\u7f00\u7f01\u7f02\u7f03\u7f04\u7f05\u7f06\u7f07\u7f08\u7f09\u7f0a\u7f0b\u7f0c\u7f0d\u7f0e\u7f0f\u7f10\u7f11\u7f12\u7f13\u7f14\u7f15\u7f16\u7f17\u7f18\u7f19\u7f1a\u7f1b\u7f1c\u7f1d\u7f1e\u7f1f\u7f20\u7f21\u7f22\u7f23\u7f24\u7f25\u7f26\u7f27\u7f28\u7f29\u7f2a\u7f2b\u7f2c\u7f2d\u7f2e\u7f2f\u7f30\u7f31\u7f32\u7f33\u7f34\u7f35\u7f36\u7f37\u7f38\u7f39\u7f3a\u7f3b\u7f3c\u7f3d\u7f3e\u7f3f\u7f40\u7f41\u7f42\u7f43\u7f44\u7f45\u7f46\u7f47\u7f48\u7f49\u7f4a\u7f4b\u7f4c\u7f4d\u7f4e\u7f4f\u7f50\u7f51\u7f52\u7f53\u7f54\u7f55\u7f56\u7f57\u7f58\u7f59\u7f5a\u7f5b\u7f5c\u7f5d\u7f5e\u7f5f\u7f60\u7f61\u7f62\u7f63\u7f64\u7f65\u7f66\u7f67\u7f68\u7f69\u7f6a\u7f6b\u7f6c\u7f6d\u7f6e\u7f6f\u7f70\u7f71\u7f72\u7f73\u7f74\u7f75\u7f76\u7f77\u7f78\u7f79\u7f7a\u7f7b\u7f7c\u7f7d\u7f7e\u7f7f\u7f80\u7f81\u7f82\u7f83\u7f84\u7f85\u7f86\u7f87\u7f88\u7f89\u7f8a\u7f8b\u7f8c\u7f8d\u7f8e\u7f8f\u7f90\u7f91\u7f92\u7f93\u7f94\u7f95\u7f96\u7f97\u7f98\u7f99\u7f9a\u7f9b\u7f9c\u7f9d\u7f9e\u7f9f\u7fa0\u7fa1\u7fa2\u7fa3\u7fa4\u7fa5\u7fa6\u7fa7\u7fa8\u7fa9\u7faa\u7fab\u7fac\u7fad\u7fae\u7faf\u7fb0\u7fb1\u7fb2\u7fb3\u7fb4\u7fb5\u7fb6\u7fb7\u7fb8\u7fb9\u7fba\u7fbb\u7fbc\u7fbd\u7fbe\u7fbf\u7fc0\u7fc1\u7fc2\u7fc3\u7fc4\u7fc5\u7fc6\u7fc7\u7fc8\u7fc9\u7fca\u7fcb\u7fcc\u7fcd\u7fce\u7fcf\u7fd0\u7fd1\u7fd2\u7fd3\u7fd4\u7fd5\u7fd6\u7fd7\u7fd8\u7fd9\u7fda\u7fdb\u7fdc\u7fdd\u7fde\u7fdf\u7fe0\u7fe1\u7fe2\u7fe3\u7fe4\u7fe5\u7fe6\u7fe7\u7fe8\u7fe9\u7fea\u7feb\u7fec\u7fed\u7fee\u7fef\u7ff0\u7ff1\u7ff2\u7ff3\u7ff4\u7ff5\u7ff6\u7ff7\u7ff8\u7ff9\u7ffa\u7ffb\u7ffc\u7ffd\u7ffe\u7fff\u8000\u8001\u8002\u8003\u8004\u8005\u8006\u8007\u8008\u8009\u800a\u800b\u800c\u800d\u800e\u800f\u8010\u8011\u8012\u8013\u8014\u8015\u8016\u8017\u8018\u8019\u801a\u801b\u801c\u801d\u801e\u801f\u8020\u8021\u8022\u8023\u8024\u8025\u8026\u8027\u8028\u8029\u802a\u802b\u802c\u802d\u802e\u802f\u8030\u8031\u8032\u8033\u8034\u8035\u8036\u8037\u8038\u8039\u803a\u803b\u803c\u803d\u803e\u803f\u8040\u8041\u8042\u8043\u8044\u8045\u8046\u8047\u8048\u8049\u804a\u804b\u804c\u804d\u804e\u804f\u8050\u8051\u8052\u8053\u8054\u8055\u8056\u8057\u8058\u8059\u805a\u805b\u805c\u805d\u805e\u805f\u8060\u8061\u8062\u8063\u8064\u8065\u8066\u8067\u8068\u8069\u806a\u806b\u806c\u806d\u806e\u806f\u8070\u8071\u8072\u8073\u8074\u8075\u8076\u8077\u8078\u8079\u807a\u807b\u807c\u807d\u807e\u807f\u8080\u8081\u8082\u8083\u8084\u8085\u8086\u8087\u8088\u8089\u808a\u808b\u808c\u808d\u808e\u808f\u8090\u8091\u8092\u8093\u8094\u8095\u8096\u8097\u8098\u8099\u809a\u809b\u809c\u809d\u809e\u809f\u80a0\u80a1\u80a2\u80a3\u80a4\u80a5\u80a6\u80a7\u80a8\u80a9\u80aa\u80ab\u80ac\u80ad\u80ae\u80af\u80b0\u80b1\u80b2\u80b3\u80b4\u80b5\u80b6\u80b7\u80b8\u80b9\u80ba\u80bb\u80bc\u80bd\u80be\u80bf\u80c0\u80c1\u80c2\u80c3\u80c4\u80c5\u80c6\u80c7\u80c8\u80c9\u80ca\u80cb\u80cc\u80cd\u80ce\u80cf\u80d0\u80d1\u80d2\u80d3\u80d4\u80d5\u80d6\u80d7\u80d8\u80d9\u80da\u80db\u80dc\u80dd\u80de\u80df\u80e0\u80e1\u80e2\u80e3\u80e4\u80e5\u80e6\u80e7\u80e8\u80e9\u80ea\u80eb\u80ec\u80ed\u80ee\u80ef\u80f0\u80f1\u80f2\u80f3\u80f4\u80f5\u80f6\u80f7\u80f8\u80f9\u80fa\u80fb\u80fc\u80fd\u80fe\u80ff\u8100\u8101\u8102\u8103\u8104\u8105\u8106\u8107\u8108\u8109\u810a\u810b\u810c\u810d\u810e\u810f\u8110\u8111\u8112\u8113\u8114\u8115\u8116\u8117\u8118\u8119\u811a\u811b\u811c\u811d\u811e\u811f\u8120\u8121\u8122\u8123\u8124\u8125\u8126\u8127\u8128\u8129\u812a\u812b\u812c\u812d\u812e\u812f\u8130\u8131\u8132\u8133\u8134\u8135\u8136\u8137\u8138\u8139\u813a\u813b\u813c\u813d\u813e\u813f\u8140\u8141\u8142\u8143\u8144\u8145\u8146\u8147\u8148\u8149\u814a\u814b\u814c\u814d\u814e\u814f\u8150\u8151\u8152\u8153\u8154\u8155\u8156\u8157\u8158\u8159\u815a\u815b\u815c\u815d\u815e\u815f\u8160\u8161\u8162\u8163\u8164\u8165\u8166\u8167\u8168\u8169\u816a\u816b\u816c\u816d\u816e\u816f\u8170\u8171\u8172\u8173\u8174\u8175\u8176\u8177\u8178\u8179\u817a\u817b\u817c\u817d\u817e\u817f\u8180\u8181\u8182\u8183\u8184\u8185\u8186\u8187\u8188\u8189\u818a\u818b\u818c\u818d\u818e\u818f\u8190\u8191\u8192\u8193\u8194\u8195\u8196\u8197\u8198\u8199\u819a\u819b\u819c\u819d\u819e\u819f\u81a0\u81a1\u81a2\u81a3\u81a4\u81a5\u81a6\u81a7\u81a8\u81a9\u81aa\u81ab\u81ac\u81ad\u81ae\u81af\u81b0\u81b1\u81b2\u81b3\u81b4\u81b5\u81b6\u81b7\u81b8\u81b9\u81ba\u81bb\u81bc\u81bd\u81be\u81bf\u81c0\u81c1\u81c2\u81c3\u81c4\u81c5\u81c6\u81c7\u81c8\u81c9\u81ca\u81cb\u81cc\u81cd\u81ce\u81cf\u81d0\u81d1\u81d2\u81d3\u81d4\u81d5\u81d6\u81d7\u81d8\u81d9\u81da\u81db\u81dc\u81dd\u81de\u81df\u81e0\u81e1\u81e2\u81e3\u81e4\u81e5\u81e6\u81e7\u81e8\u81e9\u81ea\u81eb\u81ec\u81ed\u81ee\u81ef\u81f0\u81f1\u81f2\u81f3\u81f4\u81f5\u81f6\u81f7\u81f8\u81f9\u81fa\u81fb\u81fc\u81fd\u81fe\u81ff\u8200\u8201\u8202\u8203\u8204\u8205\u8206\u8207\u8208\u8209\u820a\u820b\u820c\u820d\u820e\u820f\u8210\u8211\u8212\u8213\u8214\u8215\u8216\u8217\u8218\u8219\u821a\u821b\u821c\u821d\u821e\u821f\u8220\u8221\u8222\u8223\u8224\u8225\u8226\u8227\u8228\u8229\u822a\u822b\u822c\u822d\u822e\u822f\u8230\u8231\u8232\u8233\u8234\u8235\u8236\u8237\u8238\u8239\u823a\u823b\u823c\u823d\u823e\u823f\u8240\u8241\u8242\u8243\u8244\u8245\u8246\u8247\u8248\u8249\u824a\u824b\u824c\u824d\u824e\u824f\u8250\u8251\u8252\u8253\u8254\u8255\u8256\u8257\u8258\u8259\u825a\u825b\u825c\u825d\u825e\u825f\u8260\u8261\u8262\u8263\u8264\u8265\u8266\u8267\u8268\u8269\u826a\u826b\u826c\u826d\u826e\u826f\u8270\u8271\u8272\u8273\u8274\u8275\u8276\u8277\u8278\u8279\u827a\u827b\u827c\u827d\u827e\u827f\u8280\u8281\u8282\u8283\u8284\u8285\u8286\u8287\u8288\u8289\u828a\u828b\u828c\u828d\u828e\u828f\u8290\u8291\u8292\u8293\u8294\u8295\u8296\u8297\u8298\u8299\u829a\u829b\u829c\u829d\u829e\u829f\u82a0\u82a1\u82a2\u82a3\u82a4\u82a5\u82a6\u82a7\u82a8\u82a9\u82aa\u82ab\u82ac\u82ad\u82ae\u82af\u82b0\u82b1\u82b2\u82b3\u82b4\u82b5\u82b6\u82b7\u82b8\u82b9\u82ba\u82bb\u82bc\u82bd\u82be\u82bf\u82c0\u82c1\u82c2\u82c3\u82c4\u82c5\u82c6\u82c7\u82c8\u82c9\u82ca\u82cb\u82cc\u82cd\u82ce\u82cf\u82d0\u82d1\u82d2\u82d3\u82d4\u82d5\u82d6\u82d7\u82d8\u82d9\u82da\u82db\u82dc\u82dd\u82de\u82df\u82e0\u82e1\u82e2\u82e3\u82e4\u82e5\u82e6\u82e7\u82e8\u82e9\u82ea\u82eb\u82ec\u82ed\u82ee\u82ef\u82f0\u82f1\u82f2\u82f3\u82f4\u82f5\u82f6\u82f7\u82f8\u82f9\u82fa\u82fb\u82fc\u82fd\u82fe\u82ff\u8300\u8301\u8302\u8303\u8304\u8305\u8306\u8307\u8308\u8309\u830a\u830b\u830c\u830d\u830e\u830f\u8310\u8311\u8312\u8313\u8314\u8315\u8316\u8317\u8318\u8319\u831a\u831b\u831c\u831d\u831e\u831f\u8320\u8321\u8322\u8323\u8324\u8325\u8326\u8327\u8328\u8329\u832a\u832b\u832c\u832d\u832e\u832f\u8330\u8331\u8332\u8333\u8334\u8335\u8336\u8337\u8338\u8339\u833a\u833b\u833c\u833d\u833e\u833f\u8340\u8341\u8342\u8343\u8344\u8345\u8346\u8347\u8348\u8349\u834a\u834b\u834c\u834d\u834e\u834f\u8350\u8351\u8352\u8353\u8354\u8355\u8356\u8357\u8358\u8359\u835a\u835b\u835c\u835d\u835e\u835f\u8360\u8361\u8362\u8363\u8364\u8365\u8366\u8367\u8368\u8369\u836a\u836b\u836c\u836d\u836e\u836f\u8370\u8371\u8372\u8373\u8374\u8375\u8376\u8377\u8378\u8379\u837a\u837b\u837c\u837d\u837e\u837f\u8380\u8381\u8382\u8383\u8384\u8385\u8386\u8387\u8388\u8389\u838a\u838b\u838c\u838d\u838e\u838f\u8390\u8391\u8392\u8393\u8394\u8395\u8396\u8397\u8398\u8399\u839a\u839b\u839c\u839d\u839e\u839f\u83a0\u83a1\u83a2\u83a3\u83a4\u83a5\u83a6\u83a7\u83a8\u83a9\u83aa\u83ab\u83ac\u83ad\u83ae\u83af\u83b0\u83b1\u83b2\u83b3\u83b4\u83b5\u83b6\u83b7\u83b8\u83b9\u83ba\u83bb\u83bc\u83bd\u83be\u83bf\u83c0\u83c1\u83c2\u83c3\u83c4\u83c5\u83c6\u83c7\u83c8\u83c9\u83ca\u83cb\u83cc\u83cd\u83ce\u83cf\u83d0\u83d1\u83d2\u83d3\u83d4\u83d5\u83d6\u83d7\u83d8\u83d9\u83da\u83db\u83dc\u83dd\u83de\u83df\u83e0\u83e1\u83e2\u83e3\u83e4\u83e5\u83e6\u83e7\u83e8\u83e9\u83ea\u83eb\u83ec\u83ed\u83ee\u83ef\u83f0\u83f1\u83f2\u83f3\u83f4\u83f5\u83f6\u83f7\u83f8\u83f9\u83fa\u83fb\u83fc\u83fd\u83fe\u83ff\u8400\u8401\u8402\u8403\u8404\u8405\u8406\u8407\u8408\u8409\u840a\u840b\u840c\u840d\u840e\u840f\u8410\u8411\u8412\u8413\u8414\u8415\u8416\u8417\u8418\u8419\u841a\u841b\u841c\u841d\u841e\u841f\u8420\u8421\u8422\u8423\u8424\u8425\u8426\u8427\u8428\u8429\u842a\u842b\u842c\u842d\u842e\u842f\u8430\u8431\u8432\u8433\u8434\u8435\u8436\u8437\u8438\u8439\u843a\u843b\u843c\u843d\u843e\u843f\u8440\u8441\u8442\u8443\u8444\u8445\u8446\u8447\u8448\u8449\u844a\u844b\u844c\u844d\u844e\u844f\u8450\u8451\u8452\u8453\u8454\u8455\u8456\u8457\u8458\u8459\u845a\u845b\u845c\u845d\u845e\u845f\u8460\u8461\u8462\u8463\u8464\u8465\u8466\u8467\u8468\u8469\u846a\u846b\u846c\u846d\u846e\u846f\u8470\u8471\u8472\u8473\u8474\u8475\u8476\u8477\u8478\u8479\u847a\u847b\u847c\u847d\u847e\u847f\u8480\u8481\u8482\u8483\u8484\u8485\u8486\u8487\u8488\u8489\u848a\u848b\u848c\u848d\u848e\u848f\u8490\u8491\u8492\u8493\u8494\u8495\u8496\u8497\u8498\u8499\u849a\u849b\u849c\u849d\u849e\u849f\u84a0\u84a1\u84a2\u84a3\u84a4\u84a5\u84a6\u84a7\u84a8\u84a9\u84aa\u84ab\u84ac\u84ad\u84ae\u84af\u84b0\u84b1\u84b2\u84b3\u84b4\u84b5\u84b6\u84b7\u84b8\u84b9\u84ba\u84bb\u84bc\u84bd\u84be\u84bf\u84c0\u84c1\u84c2\u84c3\u84c4\u84c5\u84c6\u84c7\u84c8\u84c9\u84ca\u84cb\u84cc\u84cd\u84ce\u84cf\u84d0\u84d1\u84d2\u84d3\u84d4\u84d5\u84d6\u84d7\u84d8\u84d9\u84da\u84db\u84dc\u84dd\u84de\u84df\u84e0\u84e1\u84e2\u84e3\u84e4\u84e5\u84e6\u84e7\u84e8\u84e9\u84ea\u84eb\u84ec\u84ed\u84ee\u84ef\u84f0\u84f1\u84f2\u84f3\u84f4\u84f5\u84f6\u84f7\u84f8\u84f9\u84fa\u84fb\u84fc\u84fd\u84fe\u84ff\u8500\u8501\u8502\u8503\u8504\u8505\u8506\u8507\u8508\u8509\u850a\u850b\u850c\u850d\u850e\u850f\u8510\u8511\u8512\u8513\u8514\u8515\u8516\u8517\u8518\u8519\u851a\u851b\u851c\u851d\u851e\u851f\u8520\u8521\u8522\u8523\u8524\u8525\u8526\u8527\u8528\u8529\u852a\u852b\u852c\u852d\u852e\u852f\u8530\u8531\u8532\u8533\u8534\u8535\u8536\u8537\u8538\u8539\u853a\u853b\u853c\u853d\u853e\u853f\u8540\u8541\u8542\u8543\u8544\u8545\u8546\u8547\u8548\u8549\u854a\u854b\u854c\u854d\u854e\u854f\u8550\u8551\u8552\u8553\u8554\u8555\u8556\u8557\u8558\u8559\u855a\u855b\u855c\u855d\u855e\u855f\u8560\u8561\u8562\u8563\u8564\u8565\u8566\u8567\u8568\u8569\u856a\u856b\u856c\u856d\u856e\u856f\u8570\u8571\u8572\u8573\u8574\u8575\u8576\u8577\u8578\u8579\u857a\u857b\u857c\u857d\u857e\u857f\u8580\u8581\u8582\u8583\u8584\u8585\u8586\u8587\u8588\u8589\u858a\u858b\u858c\u858d\u858e\u858f\u8590\u8591\u8592\u8593\u8594\u8595\u8596\u8597\u8598\u8599\u859a\u859b\u859c\u859d\u859e\u859f\u85a0\u85a1\u85a2\u85a3\u85a4\u85a5\u85a6\u85a7\u85a8\u85a9\u85aa\u85ab\u85ac\u85ad\u85ae\u85af\u85b0\u85b1\u85b2\u85b3\u85b4\u85b5\u85b6\u85b7\u85b8\u85b9\u85ba\u85bb\u85bc\u85bd\u85be\u85bf\u85c0\u85c1\u85c2\u85c3\u85c4\u85c5\u85c6\u85c7\u85c8\u85c9\u85ca\u85cb\u85cc\u85cd\u85ce\u85cf\u85d0\u85d1\u85d2\u85d3\u85d4\u85d5\u85d6\u85d7\u85d8\u85d9\u85da\u85db\u85dc\u85dd\u85de\u85df\u85e0\u85e1\u85e2\u85e3\u85e4\u85e5\u85e6\u85e7\u85e8\u85e9\u85ea\u85eb\u85ec\u85ed\u85ee\u85ef\u85f0\u85f1\u85f2\u85f3\u85f4\u85f5\u85f6\u85f7\u85f8\u85f9\u85fa\u85fb\u85fc\u85fd\u85fe\u85ff\u8600\u8601\u8602\u8603\u8604\u8605\u8606\u8607\u8608\u8609\u860a\u860b\u860c\u860d\u860e\u860f\u8610\u8611\u8612\u8613\u8614\u8615\u8616\u8617\u8618\u8619\u861a\u861b\u861c\u861d\u861e\u861f\u8620\u8621\u8622\u8623\u8624\u8625\u8626\u8627\u8628\u8629\u862a\u862b\u862c\u862d\u862e\u862f\u8630\u8631\u8632\u8633\u8634\u8635\u8636\u8637\u8638\u8639\u863a\u863b\u863c\u863d\u863e\u863f\u8640\u8641\u8642\u8643\u8644\u8645\u8646\u8647\u8648\u8649\u864a\u864b\u864c\u864d\u864e\u864f\u8650\u8651\u8652\u8653\u8654\u8655\u8656\u8657\u8658\u8659\u865a\u865b\u865c\u865d\u865e\u865f\u8660\u8661\u8662\u8663\u8664\u8665\u8666\u8667\u8668\u8669\u866a\u866b\u866c\u866d\u866e\u866f\u8670\u8671\u8672\u8673\u8674\u8675\u8676\u8677\u8678\u8679\u867a\u867b\u867c\u867d\u867e\u867f\u8680\u8681\u8682\u8683\u8684\u8685\u8686\u8687\u8688\u8689\u868a\u868b\u868c\u868d\u868e\u868f\u8690\u8691\u8692\u8693\u8694\u8695\u8696\u8697\u8698\u8699\u869a\u869b\u869c\u869d\u869e\u869f\u86a0\u86a1\u86a2\u86a3\u86a4\u86a5\u86a6\u86a7\u86a8\u86a9\u86aa\u86ab\u86ac\u86ad\u86ae\u86af\u86b0\u86b1\u86b2\u86b3\u86b4\u86b5\u86b6\u86b7\u86b8\u86b9\u86ba\u86bb\u86bc\u86bd\u86be\u86bf\u86c0\u86c1\u86c2\u86c3\u86c4\u86c5\u86c6\u86c7\u86c8\u86c9\u86ca\u86cb\u86cc\u86cd\u86ce\u86cf\u86d0\u86d1\u86d2\u86d3\u86d4\u86d5\u86d6\u86d7\u86d8\u86d9\u86da\u86db\u86dc\u86dd\u86de\u86df\u86e0\u86e1\u86e2\u86e3\u86e4\u86e5\u86e6\u86e7\u86e8\u86e9\u86ea\u86eb\u86ec\u86ed\u86ee\u86ef\u86f0\u86f1\u86f2\u86f3\u86f4\u86f5\u86f6\u86f7\u86f8\u86f9\u86fa\u86fb\u86fc\u86fd\u86fe\u86ff\u8700\u8701\u8702\u8703\u8704\u8705\u8706\u8707\u8708\u8709\u870a\u870b\u870c\u870d\u870e\u870f\u8710\u8711\u8712\u8713\u8714\u8715\u8716\u8717\u8718\u8719\u871a\u871b\u871c\u871d\u871e\u871f\u8720\u8721\u8722\u8723\u8724\u8725\u8726\u8727\u8728\u8729\u872a\u872b\u872c\u872d\u872e\u872f\u8730\u8731\u8732\u8733\u8734\u8735\u8736\u8737\u8738\u8739\u873a\u873b\u873c\u873d\u873e\u873f\u8740\u8741\u8742\u8743\u8744\u8745\u8746\u8747\u8748\u8749\u874a\u874b\u874c\u874d\u874e\u874f\u8750\u8751\u8752\u8753\u8754\u8755\u8756\u8757\u8758\u8759\u875a\u875b\u875c\u875d\u875e\u875f\u8760\u8761\u8762\u8763\u8764\u8765\u8766\u8767\u8768\u8769\u876a\u876b\u876c\u876d\u876e\u876f\u8770\u8771\u8772\u8773\u8774\u8775\u8776\u8777\u8778\u8779\u877a\u877b\u877c\u877d\u877e\u877f\u8780\u8781\u8782\u8783\u8784\u8785\u8786\u8787\u8788\u8789\u878a\u878b\u878c\u878d\u878e\u878f\u8790\u8791\u8792\u8793\u8794\u8795\u8796\u8797\u8798\u8799\u879a\u879b\u879c\u879d\u879e\u879f\u87a0\u87a1\u87a2\u87a3\u87a4\u87a5\u87a6\u87a7\u87a8\u87a9\u87aa\u87ab\u87ac\u87ad\u87ae\u87af\u87b0\u87b1\u87b2\u87b3\u87b4\u87b5\u87b6\u87b7\u87b8\u87b9\u87ba\u87bb\u87bc\u87bd\u87be\u87bf\u87c0\u87c1\u87c2\u87c3\u87c4\u87c5\u87c6\u87c7\u87c8\u87c9\u87ca\u87cb\u87cc\u87cd\u87ce\u87cf\u87d0\u87d1\u87d2\u87d3\u87d4\u87d5\u87d6\u87d7\u87d8\u87d9\u87da\u87db\u87dc\u87dd\u87de\u87df\u87e0\u87e1\u87e2\u87e3\u87e4\u87e5\u87e6\u87e7\u87e8\u87e9\u87ea\u87eb\u87ec\u87ed\u87ee\u87ef\u87f0\u87f1\u87f2\u87f3\u87f4\u87f5\u87f6\u87f7\u87f8\u87f9\u87fa\u87fb\u87fc\u87fd\u87fe\u87ff\u8800\u8801\u8802\u8803\u8804\u8805\u8806\u8807\u8808\u8809\u880a\u880b\u880c\u880d\u880e\u880f\u8810\u8811\u8812\u8813\u8814\u8815\u8816\u8817\u8818\u8819\u881a\u881b\u881c\u881d\u881e\u881f\u8820\u8821\u8822\u8823\u8824\u8825\u8826\u8827\u8828\u8829\u882a\u882b\u882c\u882d\u882e\u882f\u8830\u8831\u8832\u8833\u8834\u8835\u8836\u8837\u8838\u8839\u883a\u883b\u883c\u883d\u883e\u883f\u8840\u8841\u8842\u8843\u8844\u8845\u8846\u8847\u8848\u8849\u884a\u884b\u884c\u884d\u884e\u884f\u8850\u8851\u8852\u8853\u8854\u8855\u8856\u8857\u8858\u8859\u885a\u885b\u885c\u885d\u885e\u885f\u8860\u8861\u8862\u8863\u8864\u8865\u8866\u8867\u8868\u8869\u886a\u886b\u886c\u886d\u886e\u886f\u8870\u8871\u8872\u8873\u8874\u8875\u8876\u8877\u8878\u8879\u887a\u887b\u887c\u887d\u887e\u887f\u8880\u8881\u8882\u8883\u8884\u8885\u8886\u8887\u8888\u8889\u888a\u888b\u888c\u888d\u888e\u888f\u8890\u8891\u8892\u8893\u8894\u8895\u8896\u8897\u8898\u8899\u889a\u889b\u889c\u889d\u889e\u889f\u88a0\u88a1\u88a2\u88a3\u88a4\u88a5\u88a6\u88a7\u88a8\u88a9\u88aa\u88ab\u88ac\u88ad\u88ae\u88af\u88b0\u88b1\u88b2\u88b3\u88b4\u88b5\u88b6\u88b7\u88b8\u88b9\u88ba\u88bb\u88bc\u88bd\u88be\u88bf\u88c0\u88c1\u88c2\u88c3\u88c4\u88c5\u88c6\u88c7\u88c8\u88c9\u88ca\u88cb\u88cc\u88cd\u88ce\u88cf\u88d0\u88d1\u88d2\u88d3\u88d4\u88d5\u88d6\u88d7\u88d8\u88d9\u88da\u88db\u88dc\u88dd\u88de\u88df\u88e0\u88e1\u88e2\u88e3\u88e4\u88e5\u88e6\u88e7\u88e8\u88e9\u88ea\u88eb\u88ec\u88ed\u88ee\u88ef\u88f0\u88f1\u88f2\u88f3\u88f4\u88f5\u88f6\u88f7\u88f8\u88f9\u88fa\u88fb\u88fc\u88fd\u88fe\u88ff\u8900\u8901\u8902\u8903\u8904\u8905\u8906\u8907\u8908\u8909\u890a\u890b\u890c\u890d\u890e\u890f\u8910\u8911\u8912\u8913\u8914\u8915\u8916\u8917\u8918\u8919\u891a\u891b\u891c\u891d\u891e\u891f\u8920\u8921\u8922\u8923\u8924\u8925\u8926\u8927\u8928\u8929\u892a\u892b\u892c\u892d\u892e\u892f\u8930\u8931\u8932\u8933\u8934\u8935\u8936\u8937\u8938\u8939\u893a\u893b\u893c\u893d\u893e\u893f\u8940\u8941\u8942\u8943\u8944\u8945\u8946\u8947\u8948\u8949\u894a\u894b\u894c\u894d\u894e\u894f\u8950\u8951\u8952\u8953\u8954\u8955\u8956\u8957\u8958\u8959\u895a\u895b\u895c\u895d\u895e\u895f\u8960\u8961\u8962\u8963\u8964\u8965\u8966\u8967\u8968\u8969\u896a\u896b\u896c\u896d\u896e\u896f\u8970\u8971\u8972\u8973\u8974\u8975\u8976\u8977\u8978\u8979\u897a\u897b\u897c\u897d\u897e\u897f\u8980\u8981\u8982\u8983\u8984\u8985\u8986\u8987\u8988\u8989\u898a\u898b\u898c\u898d\u898e\u898f\u8990\u8991\u8992\u8993\u8994\u8995\u8996\u8997\u8998\u8999\u899a\u899b\u899c\u899d\u899e\u899f\u89a0\u89a1\u89a2\u89a3\u89a4\u89a5\u89a6\u89a7\u89a8\u89a9\u89aa\u89ab\u89ac\u89ad\u89ae\u89af\u89b0\u89b1\u89b2\u89b3\u89b4\u89b5\u89b6\u89b7\u89b8\u89b9\u89ba\u89bb\u89bc\u89bd\u89be\u89bf\u89c0\u89c1\u89c2\u89c3\u89c4\u89c5\u89c6\u89c7\u89c8\u89c9\u89ca\u89cb\u89cc\u89cd\u89ce\u89cf\u89d0\u89d1\u89d2\u89d3\u89d4\u89d5\u89d6\u89d7\u89d8\u89d9\u89da\u89db\u89dc\u89dd\u89de\u89df\u89e0\u89e1\u89e2\u89e3\u89e4\u89e5\u89e6\u89e7\u89e8\u89e9\u89ea\u89eb\u89ec\u89ed\u89ee\u89ef\u89f0\u89f1\u89f2\u89f3\u89f4\u89f5\u89f6\u89f7\u89f8\u89f9\u89fa\u89fb\u89fc\u89fd\u89fe\u89ff\u8a00\u8a01\u8a02\u8a03\u8a04\u8a05\u8a06\u8a07\u8a08\u8a09\u8a0a\u8a0b\u8a0c\u8a0d\u8a0e\u8a0f\u8a10\u8a11\u8a12\u8a13\u8a14\u8a15\u8a16\u8a17\u8a18\u8a19\u8a1a\u8a1b\u8a1c\u8a1d\u8a1e\u8a1f\u8a20\u8a21\u8a22\u8a23\u8a24\u8a25\u8a26\u8a27\u8a28\u8a29\u8a2a\u8a2b\u8a2c\u8a2d\u8a2e\u8a2f\u8a30\u8a31\u8a32\u8a33\u8a34\u8a35\u8a36\u8a37\u8a38\u8a39\u8a3a\u8a3b\u8a3c\u8a3d\u8a3e\u8a3f\u8a40\u8a41\u8a42\u8a43\u8a44\u8a45\u8a46\u8a47\u8a48\u8a49\u8a4a\u8a4b\u8a4c\u8a4d\u8a4e\u8a4f\u8a50\u8a51\u8a52\u8a53\u8a54\u8a55\u8a56\u8a57\u8a58\u8a59\u8a5a\u8a5b\u8a5c\u8a5d\u8a5e\u8a5f\u8a60\u8a61\u8a62\u8a63\u8a64\u8a65\u8a66\u8a67\u8a68\u8a69\u8a6a\u8a6b\u8a6c\u8a6d\u8a6e\u8a6f\u8a70\u8a71\u8a72\u8a73\u8a74\u8a75\u8a76\u8a77\u8a78\u8a79\u8a7a\u8a7b\u8a7c\u8a7d\u8a7e\u8a7f\u8a80\u8a81\u8a82\u8a83\u8a84\u8a85\u8a86\u8a87\u8a88\u8a89\u8a8a\u8a8b\u8a8c\u8a8d\u8a8e\u8a8f\u8a90\u8a91\u8a92\u8a93\u8a94\u8a95\u8a96\u8a97\u8a98\u8a99\u8a9a\u8a9b\u8a9c\u8a9d\u8a9e\u8a9f\u8aa0\u8aa1\u8aa2\u8aa3\u8aa4\u8aa5\u8aa6\u8aa7\u8aa8\u8aa9\u8aaa\u8aab\u8aac\u8aad\u8aae\u8aaf\u8ab0\u8ab1\u8ab2\u8ab3\u8ab4\u8ab5\u8ab6\u8ab7\u8ab8\u8ab9\u8aba\u8abb\u8abc\u8abd\u8abe\u8abf\u8ac0\u8ac1\u8ac2\u8ac3\u8ac4\u8ac5\u8ac6\u8ac7\u8ac8\u8ac9\u8aca\u8acb\u8acc\u8acd\u8ace\u8acf\u8ad0\u8ad1\u8ad2\u8ad3\u8ad4\u8ad5\u8ad6\u8ad7\u8ad8\u8ad9\u8ada\u8adb\u8adc\u8add\u8ade\u8adf\u8ae0\u8ae1\u8ae2\u8ae3\u8ae4\u8ae5\u8ae6\u8ae7\u8ae8\u8ae9\u8aea\u8aeb\u8aec\u8aed\u8aee\u8aef\u8af0\u8af1\u8af2\u8af3\u8af4\u8af5\u8af6\u8af7\u8af8\u8af9\u8afa\u8afb\u8afc\u8afd\u8afe\u8aff\u8b00\u8b01\u8b02\u8b03\u8b04\u8b05\u8b06\u8b07\u8b08\u8b09\u8b0a\u8b0b\u8b0c\u8b0d\u8b0e\u8b0f\u8b10\u8b11\u8b12\u8b13\u8b14\u8b15\u8b16\u8b17\u8b18\u8b19\u8b1a\u8b1b\u8b1c\u8b1d\u8b1e\u8b1f\u8b20\u8b21\u8b22\u8b23\u8b24\u8b25\u8b26\u8b27\u8b28\u8b29\u8b2a\u8b2b\u8b2c\u8b2d\u8b2e\u8b2f\u8b30\u8b31\u8b32\u8b33\u8b34\u8b35\u8b36\u8b37\u8b38\u8b39\u8b3a\u8b3b\u8b3c\u8b3d\u8b3e\u8b3f\u8b40\u8b41\u8b42\u8b43\u8b44\u8b45\u8b46\u8b47\u8b48\u8b49\u8b4a\u8b4b\u8b4c\u8b4d\u8b4e\u8b4f\u8b50\u8b51\u8b52\u8b53\u8b54\u8b55\u8b56\u8b57\u8b58\u8b59\u8b5a\u8b5b\u8b5c\u8b5d\u8b5e\u8b5f\u8b60\u8b61\u8b62\u8b63\u8b64\u8b65\u8b66\u8b67\u8b68\u8b69\u8b6a\u8b6b\u8b6c\u8b6d\u8b6e\u8b6f\u8b70\u8b71\u8b72\u8b73\u8b74\u8b75\u8b76\u8b77\u8b78\u8b79\u8b7a\u8b7b\u8b7c\u8b7d\u8b7e\u8b7f\u8b80\u8b81\u8b82\u8b83\u8b84\u8b85\u8b86\u8b87\u8b88\u8b89\u8b8a\u8b8b\u8b8c\u8b8d\u8b8e\u8b8f\u8b90\u8b91\u8b92\u8b93\u8b94\u8b95\u8b96\u8b97\u8b98\u8b99\u8b9a\u8b9b\u8b9c\u8b9d\u8b9e\u8b9f\u8ba0\u8ba1\u8ba2\u8ba3\u8ba4\u8ba5\u8ba6\u8ba7\u8ba8\u8ba9\u8baa\u8bab\u8bac\u8bad\u8bae\u8baf\u8bb0\u8bb1\u8bb2\u8bb3\u8bb4\u8bb5\u8bb6\u8bb7\u8bb8\u8bb9\u8bba\u8bbb\u8bbc\u8bbd\u8bbe\u8bbf\u8bc0\u8bc1\u8bc2\u8bc3\u8bc4\u8bc5\u8bc6\u8bc7\u8bc8\u8bc9\u8bca\u8bcb\u8bcc\u8bcd\u8bce\u8bcf\u8bd0\u8bd1\u8bd2\u8bd3\u8bd4\u8bd5\u8bd6\u8bd7\u8bd8\u8bd9\u8bda\u8bdb\u8bdc\u8bdd\u8bde\u8bdf\u8be0\u8be1\u8be2\u8be3\u8be4\u8be5\u8be6\u8be7\u8be8\u8be9\u8bea\u8beb\u8bec\u8bed\u8bee\u8bef\u8bf0\u8bf1\u8bf2\u8bf3\u8bf4\u8bf5\u8bf6\u8bf7\u8bf8\u8bf9\u8bfa\u8bfb\u8bfc\u8bfd\u8bfe\u8bff\u8c00\u8c01\u8c02\u8c03\u8c04\u8c05\u8c06\u8c07\u8c08\u8c09\u8c0a\u8c0b\u8c0c\u8c0d\u8c0e\u8c0f\u8c10\u8c11\u8c12\u8c13\u8c14\u8c15\u8c16\u8c17\u8c18\u8c19\u8c1a\u8c1b\u8c1c\u8c1d\u8c1e\u8c1f\u8c20\u8c21\u8c22\u8c23\u8c24\u8c25\u8c26\u8c27\u8c28\u8c29\u8c2a\u8c2b\u8c2c\u8c2d\u8c2e\u8c2f\u8c30\u8c31\u8c32\u8c33\u8c34\u8c35\u8c36\u8c37\u8c38\u8c39\u8c3a\u8c3b\u8c3c\u8c3d\u8c3e\u8c3f\u8c40\u8c41\u8c42\u8c43\u8c44\u8c45\u8c46\u8c47\u8c48\u8c49\u8c4a\u8c4b\u8c4c\u8c4d\u8c4e\u8c4f\u8c50\u8c51\u8c52\u8c53\u8c54\u8c55\u8c56\u8c57\u8c58\u8c59\u8c5a\u8c5b\u8c5c\u8c5d\u8c5e\u8c5f\u8c60\u8c61\u8c62\u8c63\u8c64\u8c65\u8c66\u8c67\u8c68\u8c69\u8c6a\u8c6b\u8c6c\u8c6d\u8c6e\u8c6f\u8c70\u8c71\u8c72\u8c73\u8c74\u8c75\u8c76\u8c77\u8c78\u8c79\u8c7a\u8c7b\u8c7c\u8c7d\u8c7e\u8c7f\u8c80\u8c81\u8c82\u8c83\u8c84\u8c85\u8c86\u8c87\u8c88\u8c89\u8c8a\u8c8b\u8c8c\u8c8d\u8c8e\u8c8f\u8c90\u8c91\u8c92\u8c93\u8c94\u8c95\u8c96\u8c97\u8c98\u8c99\u8c9a\u8c9b\u8c9c\u8c9d\u8c9e\u8c9f\u8ca0\u8ca1\u8ca2\u8ca3\u8ca4\u8ca5\u8ca6\u8ca7\u8ca8\u8ca9\u8caa\u8cab\u8cac\u8cad\u8cae\u8caf\u8cb0\u8cb1\u8cb2\u8cb3\u8cb4\u8cb5\u8cb6\u8cb7\u8cb8\u8cb9\u8cba\u8cbb\u8cbc\u8cbd\u8cbe\u8cbf\u8cc0\u8cc1\u8cc2\u8cc3\u8cc4\u8cc5\u8cc6\u8cc7\u8cc8\u8cc9\u8cca\u8ccb\u8ccc\u8ccd\u8cce\u8ccf\u8cd0\u8cd1\u8cd2\u8cd3\u8cd4\u8cd5\u8cd6\u8cd7\u8cd8\u8cd9\u8cda\u8cdb\u8cdc\u8cdd\u8cde\u8cdf\u8ce0\u8ce1\u8ce2\u8ce3\u8ce4\u8ce5\u8ce6\u8ce7\u8ce8\u8ce9\u8cea\u8ceb\u8cec\u8ced\u8cee\u8cef\u8cf0\u8cf1\u8cf2\u8cf3\u8cf4\u8cf5\u8cf6\u8cf7\u8cf8\u8cf9\u8cfa\u8cfb\u8cfc\u8cfd\u8cfe\u8cff\u8d00\u8d01\u8d02\u8d03\u8d04\u8d05\u8d06\u8d07\u8d08\u8d09\u8d0a\u8d0b\u8d0c\u8d0d\u8d0e\u8d0f\u8d10\u8d11\u8d12\u8d13\u8d14\u8d15\u8d16\u8d17\u8d18\u8d19\u8d1a\u8d1b\u8d1c\u8d1d\u8d1e\u8d1f\u8d20\u8d21\u8d22\u8d23\u8d24\u8d25\u8d26\u8d27\u8d28\u8d29\u8d2a\u8d2b\u8d2c\u8d2d\u8d2e\u8d2f\u8d30\u8d31\u8d32\u8d33\u8d34\u8d35\u8d36\u8d37\u8d38\u8d39\u8d3a\u8d3b\u8d3c\u8d3d\u8d3e\u8d3f\u8d40\u8d41\u8d42\u8d43\u8d44\u8d45\u8d46\u8d47\u8d48\u8d49\u8d4a\u8d4b\u8d4c\u8d4d\u8d4e\u8d4f\u8d50\u8d51\u8d52\u8d53\u8d54\u8d55\u8d56\u8d57\u8d58\u8d59\u8d5a\u8d5b\u8d5c\u8d5d\u8d5e\u8d5f\u8d60\u8d61\u8d62\u8d63\u8d64\u8d65\u8d66\u8d67\u8d68\u8d69\u8d6a\u8d6b\u8d6c\u8d6d\u8d6e\u8d6f\u8d70\u8d71\u8d72\u8d73\u8d74\u8d75\u8d76\u8d77\u8d78\u8d79\u8d7a\u8d7b\u8d7c\u8d7d\u8d7e\u8d7f\u8d80\u8d81\u8d82\u8d83\u8d84\u8d85\u8d86\u8d87\u8d88\u8d89\u8d8a\u8d8b\u8d8c\u8d8d\u8d8e\u8d8f\u8d90\u8d91\u8d92\u8d93\u8d94\u8d95\u8d96\u8d97\u8d98\u8d99\u8d9a\u8d9b\u8d9c\u8d9d\u8d9e\u8d9f\u8da0\u8da1\u8da2\u8da3\u8da4\u8da5\u8da6\u8da7\u8da8\u8da9\u8daa\u8dab\u8dac\u8dad\u8dae\u8daf\u8db0\u8db1\u8db2\u8db3\u8db4\u8db5\u8db6\u8db7\u8db8\u8db9\u8dba\u8dbb\u8dbc\u8dbd\u8dbe\u8dbf\u8dc0\u8dc1\u8dc2\u8dc3\u8dc4\u8dc5\u8dc6\u8dc7\u8dc8\u8dc9\u8dca\u8dcb\u8dcc\u8dcd\u8dce\u8dcf\u8dd0\u8dd1\u8dd2\u8dd3\u8dd4\u8dd5\u8dd6\u8dd7\u8dd8\u8dd9\u8dda\u8ddb\u8ddc\u8ddd\u8dde\u8ddf\u8de0\u8de1\u8de2\u8de3\u8de4\u8de5\u8de6\u8de7\u8de8\u8de9\u8dea\u8deb\u8dec\u8ded\u8dee\u8def\u8df0\u8df1\u8df2\u8df3\u8df4\u8df5\u8df6\u8df7\u8df8\u8df9\u8dfa\u8dfb\u8dfc\u8dfd\u8dfe\u8dff\u8e00\u8e01\u8e02\u8e03\u8e04\u8e05\u8e06\u8e07\u8e08\u8e09\u8e0a\u8e0b\u8e0c\u8e0d\u8e0e\u8e0f\u8e10\u8e11\u8e12\u8e13\u8e14\u8e15\u8e16\u8e17\u8e18\u8e19\u8e1a\u8e1b\u8e1c\u8e1d\u8e1e\u8e1f\u8e20\u8e21\u8e22\u8e23\u8e24\u8e25\u8e26\u8e27\u8e28\u8e29\u8e2a\u8e2b\u8e2c\u8e2d\u8e2e\u8e2f\u8e30\u8e31\u8e32\u8e33\u8e34\u8e35\u8e36\u8e37\u8e38\u8e39\u8e3a\u8e3b\u8e3c\u8e3d\u8e3e\u8e3f\u8e40\u8e41\u8e42\u8e43\u8e44\u8e45\u8e46\u8e47\u8e48\u8e49\u8e4a\u8e4b\u8e4c\u8e4d\u8e4e\u8e4f\u8e50\u8e51\u8e52\u8e53\u8e54\u8e55\u8e56\u8e57\u8e58\u8e59\u8e5a\u8e5b\u8e5c\u8e5d\u8e5e\u8e5f\u8e60\u8e61\u8e62\u8e63\u8e64\u8e65\u8e66\u8e67\u8e68\u8e69\u8e6a\u8e6b\u8e6c\u8e6d\u8e6e\u8e6f\u8e70\u8e71\u8e72\u8e73\u8e74\u8e75\u8e76\u8e77\u8e78\u8e79\u8e7a\u8e7b\u8e7c\u8e7d\u8e7e\u8e7f\u8e80\u8e81\u8e82\u8e83\u8e84\u8e85\u8e86\u8e87\u8e88\u8e89\u8e8a\u8e8b\u8e8c\u8e8d\u8e8e\u8e8f\u8e90\u8e91\u8e92\u8e93\u8e94\u8e95\u8e96\u8e97\u8e98\u8e99\u8e9a\u8e9b\u8e9c\u8e9d\u8e9e\u8e9f\u8ea0\u8ea1\u8ea2\u8ea3\u8ea4\u8ea5\u8ea6\u8ea7\u8ea8\u8ea9\u8eaa\u8eab\u8eac\u8ead\u8eae\u8eaf\u8eb0\u8eb1\u8eb2\u8eb3\u8eb4\u8eb5\u8eb6\u8eb7\u8eb8\u8eb9\u8eba\u8ebb\u8ebc\u8ebd\u8ebe\u8ebf\u8ec0\u8ec1\u8ec2\u8ec3\u8ec4\u8ec5\u8ec6\u8ec7\u8ec8\u8ec9\u8eca\u8ecb\u8ecc\u8ecd\u8ece\u8ecf\u8ed0\u8ed1\u8ed2\u8ed3\u8ed4\u8ed5\u8ed6\u8ed7\u8ed8\u8ed9\u8eda\u8edb\u8edc\u8edd\u8ede\u8edf\u8ee0\u8ee1\u8ee2\u8ee3\u8ee4\u8ee5\u8ee6\u8ee7\u8ee8\u8ee9\u8eea\u8eeb\u8eec\u8eed\u8eee\u8eef\u8ef0\u8ef1\u8ef2\u8ef3\u8ef4\u8ef5\u8ef6\u8ef7\u8ef8\u8ef9\u8efa\u8efb\u8efc\u8efd\u8efe\u8eff\u8f00\u8f01\u8f02\u8f03\u8f04\u8f05\u8f06\u8f07\u8f08\u8f09\u8f0a\u8f0b\u8f0c\u8f0d\u8f0e\u8f0f\u8f10\u8f11\u8f12\u8f13\u8f14\u8f15\u8f16\u8f17\u8f18\u8f19\u8f1a\u8f1b\u8f1c\u8f1d\u8f1e\u8f1f\u8f20\u8f21\u8f22\u8f23\u8f24\u8f25\u8f26\u8f27\u8f28\u8f29\u8f2a\u8f2b\u8f2c\u8f2d\u8f2e\u8f2f\u8f30\u8f31\u8f32\u8f33\u8f34\u8f35\u8f36\u8f37\u8f38\u8f39\u8f3a\u8f3b\u8f3c\u8f3d\u8f3e\u8f3f\u8f40\u8f41\u8f42\u8f43\u8f44\u8f45\u8f46\u8f47\u8f48\u8f49\u8f4a\u8f4b\u8f4c\u8f4d\u8f4e\u8f4f\u8f50\u8f51\u8f52\u8f53\u8f54\u8f55\u8f56\u8f57\u8f58\u8f59\u8f5a\u8f5b\u8f5c\u8f5d\u8f5e\u8f5f\u8f60\u8f61\u8f62\u8f63\u8f64\u8f65\u8f66\u8f67\u8f68\u8f69\u8f6a\u8f6b\u8f6c\u8f6d\u8f6e\u8f6f\u8f70\u8f71\u8f72\u8f73\u8f74\u8f75\u8f76\u8f77\u8f78\u8f79\u8f7a\u8f7b\u8f7c\u8f7d\u8f7e\u8f7f\u8f80\u8f81\u8f82\u8f83\u8f84\u8f85\u8f86\u8f87\u8f88\u8f89\u8f8a\u8f8b\u8f8c\u8f8d\u8f8e\u8f8f\u8f90\u8f91\u8f92\u8f93\u8f94\u8f95\u8f96\u8f97\u8f98\u8f99\u8f9a\u8f9b\u8f9c\u8f9d\u8f9e\u8f9f\u8fa0\u8fa1\u8fa2\u8fa3\u8fa4\u8fa5\u8fa6\u8fa7\u8fa8\u8fa9\u8faa\u8fab\u8fac\u8fad\u8fae\u8faf\u8fb0\u8fb1\u8fb2\u8fb3\u8fb4\u8fb5\u8fb6\u8fb7\u8fb8\u8fb9\u8fba\u8fbb\u8fbc\u8fbd\u8fbe\u8fbf\u8fc0\u8fc1\u8fc2\u8fc3\u8fc4\u8fc5\u8fc6\u8fc7\u8fc8\u8fc9\u8fca\u8fcb\u8fcc\u8fcd\u8fce\u8fcf\u8fd0\u8fd1\u8fd2\u8fd3\u8fd4\u8fd5\u8fd6\u8fd7\u8fd8\u8fd9\u8fda\u8fdb\u8fdc\u8fdd\u8fde\u8fdf\u8fe0\u8fe1\u8fe2\u8fe3\u8fe4\u8fe5\u8fe6\u8fe7\u8fe8\u8fe9\u8fea\u8feb\u8fec\u8fed\u8fee\u8fef\u8ff0\u8ff1\u8ff2\u8ff3\u8ff4\u8ff5\u8ff6\u8ff7\u8ff8\u8ff9\u8ffa\u8ffb\u8ffc\u8ffd\u8ffe\u8fff\u9000\u9001\u9002\u9003\u9004\u9005\u9006\u9007\u9008\u9009\u900a\u900b\u900c\u900d\u900e\u900f\u9010\u9011\u9012\u9013\u9014\u9015\u9016\u9017\u9018\u9019\u901a\u901b\u901c\u901d\u901e\u901f\u9020\u9021\u9022\u9023\u9024\u9025\u9026\u9027\u9028\u9029\u902a\u902b\u902c\u902d\u902e\u902f\u9030\u9031\u9032\u9033\u9034\u9035\u9036\u9037\u9038\u9039\u903a\u903b\u903c\u903d\u903e\u903f\u9040\u9041\u9042\u9043\u9044\u9045\u9046\u9047\u9048\u9049\u904a\u904b\u904c\u904d\u904e\u904f\u9050\u9051\u9052\u9053\u9054\u9055\u9056\u9057\u9058\u9059\u905a\u905b\u905c\u905d\u905e\u905f\u9060\u9061\u9062\u9063\u9064\u9065\u9066\u9067\u9068\u9069\u906a\u906b\u906c\u906d\u906e\u906f\u9070\u9071\u9072\u9073\u9074\u9075\u9076\u9077\u9078\u9079\u907a\u907b\u907c\u907d\u907e\u907f\u9080\u9081\u9082\u9083\u9084\u9085\u9086\u9087\u9088\u9089\u908a\u908b\u908c\u908d\u908e\u908f\u9090\u9091\u9092\u9093\u9094\u9095\u9096\u9097\u9098\u9099\u909a\u909b\u909c\u909d\u909e\u909f\u90a0\u90a1\u90a2\u90a3\u90a4\u90a5\u90a6\u90a7\u90a8\u90a9\u90aa\u90ab\u90ac\u90ad\u90ae\u90af\u90b0\u90b1\u90b2\u90b3\u90b4\u90b5\u90b6\u90b7\u90b8\u90b9\u90ba\u90bb\u90bc\u90bd\u90be\u90bf\u90c0\u90c1\u90c2\u90c3\u90c4\u90c5\u90c6\u90c7\u90c8\u90c9\u90ca\u90cb\u90cc\u90cd\u90ce\u90cf\u90d0\u90d1\u90d2\u90d3\u90d4\u90d5\u90d6\u90d7\u90d8\u90d9\u90da\u90db\u90dc\u90dd\u90de\u90df\u90e0\u90e1\u90e2\u90e3\u90e4\u90e5\u90e6\u90e7\u90e8\u90e9\u90ea\u90eb\u90ec\u90ed\u90ee\u90ef\u90f0\u90f1\u90f2\u90f3\u90f4\u90f5\u90f6\u90f7\u90f8\u90f9\u90fa\u90fb\u90fc\u90fd\u90fe\u90ff\u9100\u9101\u9102\u9103\u9104\u9105\u9106\u9107\u9108\u9109\u910a\u910b\u910c\u910d\u910e\u910f\u9110\u9111\u9112\u9113\u9114\u9115\u9116\u9117\u9118\u9119\u911a\u911b\u911c\u911d\u911e\u911f\u9120\u9121\u9122\u9123\u9124\u9125\u9126\u9127\u9128\u9129\u912a\u912b\u912c\u912d\u912e\u912f\u9130\u9131\u9132\u9133\u9134\u9135\u9136\u9137\u9138\u9139\u913a\u913b\u913c\u913d\u913e\u913f\u9140\u9141\u9142\u9143\u9144\u9145\u9146\u9147\u9148\u9149\u914a\u914b\u914c\u914d\u914e\u914f\u9150\u9151\u9152\u9153\u9154\u9155\u9156\u9157\u9158\u9159\u915a\u915b\u915c\u915d\u915e\u915f\u9160\u9161\u9162\u9163\u9164\u9165\u9166\u9167\u9168\u9169\u916a\u916b\u916c\u916d\u916e\u916f\u9170\u9171\u9172\u9173\u9174\u9175\u9176\u9177\u9178\u9179\u917a\u917b\u917c\u917d\u917e\u917f\u9180\u9181\u9182\u9183\u9184\u9185\u9186\u9187\u9188\u9189\u918a\u918b\u918c\u918d\u918e\u918f\u9190\u9191\u9192\u9193\u9194\u9195\u9196\u9197\u9198\u9199\u919a\u919b\u919c\u919d\u919e\u919f\u91a0\u91a1\u91a2\u91a3\u91a4\u91a5\u91a6\u91a7\u91a8\u91a9\u91aa\u91ab\u91ac\u91ad\u91ae\u91af\u91b0\u91b1\u91b2\u91b3\u91b4\u91b5\u91b6\u91b7\u91b8\u91b9\u91ba\u91bb\u91bc\u91bd\u91be\u91bf\u91c0\u91c1\u91c2\u91c3\u91c4\u91c5\u91c6\u91c7\u91c8\u91c9\u91ca\u91cb\u91cc\u91cd\u91ce\u91cf\u91d0\u91d1\u91d2\u91d3\u91d4\u91d5\u91d6\u91d7\u91d8\u91d9\u91da\u91db\u91dc\u91dd\u91de\u91df\u91e0\u91e1\u91e2\u91e3\u91e4\u91e5\u91e6\u91e7\u91e8\u91e9\u91ea\u91eb\u91ec\u91ed\u91ee\u91ef\u91f0\u91f1\u91f2\u91f3\u91f4\u91f5\u91f6\u91f7\u91f8\u91f9\u91fa\u91fb\u91fc\u91fd\u91fe\u91ff\u9200\u9201\u9202\u9203\u9204\u9205\u9206\u9207\u9208\u9209\u920a\u920b\u920c\u920d\u920e\u920f\u9210\u9211\u9212\u9213\u9214\u9215\u9216\u9217\u9218\u9219\u921a\u921b\u921c\u921d\u921e\u921f\u9220\u9221\u9222\u9223\u9224\u9225\u9226\u9227\u9228\u9229\u922a\u922b\u922c\u922d\u922e\u922f\u9230\u9231\u9232\u9233\u9234\u9235\u9236\u9237\u9238\u9239\u923a\u923b\u923c\u923d\u923e\u923f\u9240\u9241\u9242\u9243\u9244\u9245\u9246\u9247\u9248\u9249\u924a\u924b\u924c\u924d\u924e\u924f\u9250\u9251\u9252\u9253\u9254\u9255\u9256\u9257\u9258\u9259\u925a\u925b\u925c\u925d\u925e\u925f\u9260\u9261\u9262\u9263\u9264\u9265\u9266\u9267\u9268\u9269\u926a\u926b\u926c\u926d\u926e\u926f\u9270\u9271\u9272\u9273\u9274\u9275\u9276\u9277\u9278\u9279\u927a\u927b\u927c\u927d\u927e\u927f\u9280\u9281\u9282\u9283\u9284\u9285\u9286\u9287\u9288\u9289\u928a\u928b\u928c\u928d\u928e\u928f\u9290\u9291\u9292\u9293\u9294\u9295\u9296\u9297\u9298\u9299\u929a\u929b\u929c\u929d\u929e\u929f\u92a0\u92a1\u92a2\u92a3\u92a4\u92a5\u92a6\u92a7\u92a8\u92a9\u92aa\u92ab\u92ac\u92ad\u92ae\u92af\u92b0\u92b1\u92b2\u92b3\u92b4\u92b5\u92b6\u92b7\u92b8\u92b9\u92ba\u92bb\u92bc\u92bd\u92be\u92bf\u92c0\u92c1\u92c2\u92c3\u92c4\u92c5\u92c6\u92c7\u92c8\u92c9\u92ca\u92cb\u92cc\u92cd\u92ce\u92cf\u92d0\u92d1\u92d2\u92d3\u92d4\u92d5\u92d6\u92d7\u92d8\u92d9\u92da\u92db\u92dc\u92dd\u92de\u92df\u92e0\u92e1\u92e2\u92e3\u92e4\u92e5\u92e6\u92e7\u92e8\u92e9\u92ea\u92eb\u92ec\u92ed\u92ee\u92ef\u92f0\u92f1\u92f2\u92f3\u92f4\u92f5\u92f6\u92f7\u92f8\u92f9\u92fa\u92fb\u92fc\u92fd\u92fe\u92ff\u9300\u9301\u9302\u9303\u9304\u9305\u9306\u9307\u9308\u9309\u930a\u930b\u930c\u930d\u930e\u930f\u9310\u9311\u9312\u9313\u9314\u9315\u9316\u9317\u9318\u9319\u931a\u931b\u931c\u931d\u931e\u931f\u9320\u9321\u9322\u9323\u9324\u9325\u9326\u9327\u9328\u9329\u932a\u932b\u932c\u932d\u932e\u932f\u9330\u9331\u9332\u9333\u9334\u9335\u9336\u9337\u9338\u9339\u933a\u933b\u933c\u933d\u933e\u933f\u9340\u9341\u9342\u9343\u9344\u9345\u9346\u9347\u9348\u9349\u934a\u934b\u934c\u934d\u934e\u934f\u9350\u9351\u9352\u9353\u9354\u9355\u9356\u9357\u9358\u9359\u935a\u935b\u935c\u935d\u935e\u935f\u9360\u9361\u9362\u9363\u9364\u9365\u9366\u9367\u9368\u9369\u936a\u936b\u936c\u936d\u936e\u936f\u9370\u9371\u9372\u9373\u9374\u9375\u9376\u9377\u9378\u9379\u937a\u937b\u937c\u937d\u937e\u937f\u9380\u9381\u9382\u9383\u9384\u9385\u9386\u9387\u9388\u9389\u938a\u938b\u938c\u938d\u938e\u938f\u9390\u9391\u9392\u9393\u9394\u9395\u9396\u9397\u9398\u9399\u939a\u939b\u939c\u939d\u939e\u939f\u93a0\u93a1\u93a2\u93a3\u93a4\u93a5\u93a6\u93a7\u93a8\u93a9\u93aa\u93ab\u93ac\u93ad\u93ae\u93af\u93b0\u93b1\u93b2\u93b3\u93b4\u93b5\u93b6\u93b7\u93b8\u93b9\u93ba\u93bb\u93bc\u93bd\u93be\u93bf\u93c0\u93c1\u93c2\u93c3\u93c4\u93c5\u93c6\u93c7\u93c8\u93c9\u93ca\u93cb\u93cc\u93cd\u93ce\u93cf\u93d0\u93d1\u93d2\u93d3\u93d4\u93d5\u93d6\u93d7\u93d8\u93d9\u93da\u93db\u93dc\u93dd\u93de\u93df\u93e0\u93e1\u93e2\u93e3\u93e4\u93e5\u93e6\u93e7\u93e8\u93e9\u93ea\u93eb\u93ec\u93ed\u93ee\u93ef\u93f0\u93f1\u93f2\u93f3\u93f4\u93f5\u93f6\u93f7\u93f8\u93f9\u93fa\u93fb\u93fc\u93fd\u93fe\u93ff\u9400\u9401\u9402\u9403\u9404\u9405\u9406\u9407\u9408\u9409\u940a\u940b\u940c\u940d\u940e\u940f\u9410\u9411\u9412\u9413\u9414\u9415\u9416\u9417\u9418\u9419\u941a\u941b\u941c\u941d\u941e\u941f\u9420\u9421\u9422\u9423\u9424\u9425\u9426\u9427\u9428\u9429\u942a\u942b\u942c\u942d\u942e\u942f\u9430\u9431\u9432\u9433\u9434\u9435\u9436\u9437\u9438\u9439\u943a\u943b\u943c\u943d\u943e\u943f\u9440\u9441\u9442\u9443\u9444\u9445\u9446\u9447\u9448\u9449\u944a\u944b\u944c\u944d\u944e\u944f\u9450\u9451\u9452\u9453\u9454\u9455\u9456\u9457\u9458\u9459\u945a\u945b\u945c\u945d\u945e\u945f\u9460\u9461\u9462\u9463\u9464\u9465\u9466\u9467\u9468\u9469\u946a\u946b\u946c\u946d\u946e\u946f\u9470\u9471\u9472\u9473\u9474\u9475\u9476\u9477\u9478\u9479\u947a\u947b\u947c\u947d\u947e\u947f\u9480\u9481\u9482\u9483\u9484\u9485\u9486\u9487\u9488\u9489\u948a\u948b\u948c\u948d\u948e\u948f\u9490\u9491\u9492\u9493\u9494\u9495\u9496\u9497\u9498\u9499\u949a\u949b\u949c\u949d\u949e\u949f\u94a0\u94a1\u94a2\u94a3\u94a4\u94a5\u94a6\u94a7\u94a8\u94a9\u94aa\u94ab\u94ac\u94ad\u94ae\u94af\u94b0\u94b1\u94b2\u94b3\u94b4\u94b5\u94b6\u94b7\u94b8\u94b9\u94ba\u94bb\u94bc\u94bd\u94be\u94bf\u94c0\u94c1\u94c2\u94c3\u94c4\u94c5\u94c6\u94c7\u94c8\u94c9\u94ca\u94cb\u94cc\u94cd\u94ce\u94cf\u94d0\u94d1\u94d2\u94d3\u94d4\u94d5\u94d6\u94d7\u94d8\u94d9\u94da\u94db\u94dc\u94dd\u94de\u94df\u94e0\u94e1\u94e2\u94e3\u94e4\u94e5\u94e6\u94e7\u94e8\u94e9\u94ea\u94eb\u94ec\u94ed\u94ee\u94ef\u94f0\u94f1\u94f2\u94f3\u94f4\u94f5\u94f6\u94f7\u94f8\u94f9\u94fa\u94fb\u94fc\u94fd\u94fe\u94ff\u9500\u9501\u9502\u9503\u9504\u9505\u9506\u9507\u9508\u9509\u950a\u950b\u950c\u950d\u950e\u950f\u9510\u9511\u9512\u9513\u9514\u9515\u9516\u9517\u9518\u9519\u951a\u951b\u951c\u951d\u951e\u951f\u9520\u9521\u9522\u9523\u9524\u9525\u9526\u9527\u9528\u9529\u952a\u952b\u952c\u952d\u952e\u952f\u9530\u9531\u9532\u9533\u9534\u9535\u9536\u9537\u9538\u9539\u953a\u953b\u953c\u953d\u953e\u953f\u9540\u9541\u9542\u9543\u9544\u9545\u9546\u9547\u9548\u9549\u954a\u954b\u954c\u954d\u954e\u954f\u9550\u9551\u9552\u9553\u9554\u9555\u9556\u9557\u9558\u9559\u955a\u955b\u955c\u955d\u955e\u955f\u9560\u9561\u9562\u9563\u9564\u9565\u9566\u9567\u9568\u9569\u956a\u956b\u956c\u956d\u956e\u956f\u9570\u9571\u9572\u9573\u9574\u9575\u9576\u9577\u9578\u9579\u957a\u957b\u957c\u957d\u957e\u957f\u9580\u9581\u9582\u9583\u9584\u9585\u9586\u9587\u9588\u9589\u958a\u958b\u958c\u958d\u958e\u958f\u9590\u9591\u9592\u9593\u9594\u9595\u9596\u9597\u9598\u9599\u959a\u959b\u959c\u959d\u959e\u959f\u95a0\u95a1\u95a2\u95a3\u95a4\u95a5\u95a6\u95a7\u95a8\u95a9\u95aa\u95ab\u95ac\u95ad\u95ae\u95af\u95b0\u95b1\u95b2\u95b3\u95b4\u95b5\u95b6\u95b7\u95b8\u95b9\u95ba\u95bb\u95bc\u95bd\u95be\u95bf\u95c0\u95c1\u95c2\u95c3\u95c4\u95c5\u95c6\u95c7\u95c8\u95c9\u95ca\u95cb\u95cc\u95cd\u95ce\u95cf\u95d0\u95d1\u95d2\u95d3\u95d4\u95d5\u95d6\u95d7\u95d8\u95d9\u95da\u95db\u95dc\u95dd\u95de\u95df\u95e0\u95e1\u95e2\u95e3\u95e4\u95e5\u95e6\u95e7\u95e8\u95e9\u95ea\u95eb\u95ec\u95ed\u95ee\u95ef\u95f0\u95f1\u95f2\u95f3\u95f4\u95f5\u95f6\u95f7\u95f8\u95f9\u95fa\u95fb\u95fc\u95fd\u95fe\u95ff\u9600\u9601\u9602\u9603\u9604\u9605\u9606\u9607\u9608\u9609\u960a\u960b\u960c\u960d\u960e\u960f\u9610\u9611\u9612\u9613\u9614\u9615\u9616\u9617\u9618\u9619\u961a\u961b\u961c\u961d\u961e\u961f\u9620\u9621\u9622\u9623\u9624\u9625\u9626\u9627\u9628\u9629\u962a\u962b\u962c\u962d\u962e\u962f\u9630\u9631\u9632\u9633\u9634\u9635\u9636\u9637\u9638\u9639\u963a\u963b\u963c\u963d\u963e\u963f\u9640\u9641\u9642\u9643\u9644\u9645\u9646\u9647\u9648\u9649\u964a\u964b\u964c\u964d\u964e\u964f\u9650\u9651\u9652\u9653\u9654\u9655\u9656\u9657\u9658\u9659\u965a\u965b\u965c\u965d\u965e\u965f\u9660\u9661\u9662\u9663\u9664\u9665\u9666\u9667\u9668\u9669\u966a\u966b\u966c\u966d\u966e\u966f\u9670\u9671\u9672\u9673\u9674\u9675\u9676\u9677\u9678\u9679\u967a\u967b\u967c\u967d\u967e\u967f\u9680\u9681\u9682\u9683\u9684\u9685\u9686\u9687\u9688\u9689\u968a\u968b\u968c\u968d\u968e\u968f\u9690\u9691\u9692\u9693\u9694\u9695\u9696\u9697\u9698\u9699\u969a\u969b\u969c\u969d\u969e\u969f\u96a0\u96a1\u96a2\u96a3\u96a4\u96a5\u96a6\u96a7\u96a8\u96a9\u96aa\u96ab\u96ac\u96ad\u96ae\u96af\u96b0\u96b1\u96b2\u96b3\u96b4\u96b5\u96b6\u96b7\u96b8\u96b9\u96ba\u96bb\u96bc\u96bd\u96be\u96bf\u96c0\u96c1\u96c2\u96c3\u96c4\u96c5\u96c6\u96c7\u96c8\u96c9\u96ca\u96cb\u96cc\u96cd\u96ce\u96cf\u96d0\u96d1\u96d2\u96d3\u96d4\u96d5\u96d6\u96d7\u96d8\u96d9\u96da\u96db\u96dc\u96dd\u96de\u96df\u96e0\u96e1\u96e2\u96e3\u96e4\u96e5\u96e6\u96e7\u96e8\u96e9\u96ea\u96eb\u96ec\u96ed\u96ee\u96ef\u96f0\u96f1\u96f2\u96f3\u96f4\u96f5\u96f6\u96f7\u96f8\u96f9\u96fa\u96fb\u96fc\u96fd\u96fe\u96ff\u9700\u9701\u9702\u9703\u9704\u9705\u9706\u9707\u9708\u9709\u970a\u970b\u970c\u970d\u970e\u970f\u9710\u9711\u9712\u9713\u9714\u9715\u9716\u9717\u9718\u9719\u971a\u971b\u971c\u971d\u971e\u971f\u9720\u9721\u9722\u9723\u9724\u9725\u9726\u9727\u9728\u9729\u972a\u972b\u972c\u972d\u972e\u972f\u9730\u9731\u9732\u9733\u9734\u9735\u9736\u9737\u9738\u9739\u973a\u973b\u973c\u973d\u973e\u973f\u9740\u9741\u9742\u9743\u9744\u9745\u9746\u9747\u9748\u9749\u974a\u974b\u974c\u974d\u974e\u974f\u9750\u9751\u9752\u9753\u9754\u9755\u9756\u9757\u9758\u9759\u975a\u975b\u975c\u975d\u975e\u975f\u9760\u9761\u9762\u9763\u9764\u9765\u9766\u9767\u9768\u9769\u976a\u976b\u976c\u976d\u976e\u976f\u9770\u9771\u9772\u9773\u9774\u9775\u9776\u9777\u9778\u9779\u977a\u977b\u977c\u977d\u977e\u977f\u9780\u9781\u9782\u9783\u9784\u9785\u9786\u9787\u9788\u9789\u978a\u978b\u978c\u978d\u978e\u978f\u9790\u9791\u9792\u9793\u9794\u9795\u9796\u9797\u9798\u9799\u979a\u979b\u979c\u979d\u979e\u979f\u97a0\u97a1\u97a2\u97a3\u97a4\u97a5\u97a6\u97a7\u97a8\u97a9\u97aa\u97ab\u97ac\u97ad\u97ae\u97af\u97b0\u97b1\u97b2\u97b3\u97b4\u97b5\u97b6\u97b7\u97b8\u97b9\u97ba\u97bb\u97bc\u97bd\u97be\u97bf\u97c0\u97c1\u97c2\u97c3\u97c4\u97c5\u97c6\u97c7\u97c8\u97c9\u97ca\u97cb\u97cc\u97cd\u97ce\u97cf\u97d0\u97d1\u97d2\u97d3\u97d4\u97d5\u97d6\u97d7\u97d8\u97d9\u97da\u97db\u97dc\u97dd\u97de\u97df\u97e0\u97e1\u97e2\u97e3\u97e4\u97e5\u97e6\u97e7\u97e8\u97e9\u97ea\u97eb\u97ec\u97ed\u97ee\u97ef\u97f0\u97f1\u97f2\u97f3\u97f4\u97f5\u97f6\u97f7\u97f8\u97f9\u97fa\u97fb\u97fc\u97fd\u97fe\u97ff\u9800\u9801\u9802\u9803\u9804\u9805\u9806\u9807\u9808\u9809\u980a\u980b\u980c\u980d\u980e\u980f\u9810\u9811\u9812\u9813\u9814\u9815\u9816\u9817\u9818\u9819\u981a\u981b\u981c\u981d\u981e\u981f\u9820\u9821\u9822\u9823\u9824\u9825\u9826\u9827\u9828\u9829\u982a\u982b\u982c\u982d\u982e\u982f\u9830\u9831\u9832\u9833\u9834\u9835\u9836\u9837\u9838\u9839\u983a\u983b\u983c\u983d\u983e\u983f\u9840\u9841\u9842\u9843\u9844\u9845\u9846\u9847\u9848\u9849\u984a\u984b\u984c\u984d\u984e\u984f\u9850\u9851\u9852\u9853\u9854\u9855\u9856\u9857\u9858\u9859\u985a\u985b\u985c\u985d\u985e\u985f\u9860\u9861\u9862\u9863\u9864\u9865\u9866\u9867\u9868\u9869\u986a\u986b\u986c\u986d\u986e\u986f\u9870\u9871\u9872\u9873\u9874\u9875\u9876\u9877\u9878\u9879\u987a\u987b\u987c\u987d\u987e\u987f\u9880\u9881\u9882\u9883\u9884\u9885\u9886\u9887\u9888\u9889\u988a\u988b\u988c\u988d\u988e\u988f\u9890\u9891\u9892\u9893\u9894\u9895\u9896\u9897\u9898\u9899\u989a\u989b\u989c\u989d\u989e\u989f\u98a0\u98a1\u98a2\u98a3\u98a4\u98a5\u98a6\u98a7\u98a8\u98a9\u98aa\u98ab\u98ac\u98ad\u98ae\u98af\u98b0\u98b1\u98b2\u98b3\u98b4\u98b5\u98b6\u98b7\u98b8\u98b9\u98ba\u98bb\u98bc\u98bd\u98be\u98bf\u98c0\u98c1\u98c2\u98c3\u98c4\u98c5\u98c6\u98c7\u98c8\u98c9\u98ca\u98cb\u98cc\u98cd\u98ce\u98cf\u98d0\u98d1\u98d2\u98d3\u98d4\u98d5\u98d6\u98d7\u98d8\u98d9\u98da\u98db\u98dc\u98dd\u98de\u98df\u98e0\u98e1\u98e2\u98e3\u98e4\u98e5\u98e6\u98e7\u98e8\u98e9\u98ea\u98eb\u98ec\u98ed\u98ee\u98ef\u98f0\u98f1\u98f2\u98f3\u98f4\u98f5\u98f6\u98f7\u98f8\u98f9\u98fa\u98fb\u98fc\u98fd\u98fe\u98ff\u9900\u9901\u9902\u9903\u9904\u9905\u9906\u9907\u9908\u9909\u990a\u990b\u990c\u990d\u990e\u990f\u9910\u9911\u9912\u9913\u9914\u9915\u9916\u9917\u9918\u9919\u991a\u991b\u991c\u991d\u991e\u991f\u9920\u9921\u9922\u9923\u9924\u9925\u9926\u9927\u9928\u9929\u992a\u992b\u992c\u992d\u992e\u992f\u9930\u9931\u9932\u9933\u9934\u9935\u9936\u9937\u9938\u9939\u993a\u993b\u993c\u993d\u993e\u993f\u9940\u9941\u9942\u9943\u9944\u9945\u9946\u9947\u9948\u9949\u994a\u994b\u994c\u994d\u994e\u994f\u9950\u9951\u9952\u9953\u9954\u9955\u9956\u9957\u9958\u9959\u995a\u995b\u995c\u995d\u995e\u995f\u9960\u9961\u9962\u9963\u9964\u9965\u9966\u9967\u9968\u9969\u996a\u996b\u996c\u996d\u996e\u996f\u9970\u9971\u9972\u9973\u9974\u9975\u9976\u9977\u9978\u9979\u997a\u997b\u997c\u997d\u997e\u997f\u9980\u9981\u9982\u9983\u9984\u9985\u9986\u9987\u9988\u9989\u998a\u998b\u998c\u998d\u998e\u998f\u9990\u9991\u9992\u9993\u9994\u9995\u9996\u9997\u9998\u9999\u999a\u999b\u999c\u999d\u999e\u999f\u99a0\u99a1\u99a2\u99a3\u99a4\u99a5\u99a6\u99a7\u99a8\u99a9\u99aa\u99ab\u99ac\u99ad\u99ae\u99af\u99b0\u99b1\u99b2\u99b3\u99b4\u99b5\u99b6\u99b7\u99b8\u99b9\u99ba\u99bb\u99bc\u99bd\u99be\u99bf\u99c0\u99c1\u99c2\u99c3\u99c4\u99c5\u99c6\u99c7\u99c8\u99c9\u99ca\u99cb\u99cc\u99cd\u99ce\u99cf\u99d0\u99d1\u99d2\u99d3\u99d4\u99d5\u99d6\u99d7\u99d8\u99d9\u99da\u99db\u99dc\u99dd\u99de\u99df\u99e0\u99e1\u99e2\u99e3\u99e4\u99e5\u99e6\u99e7\u99e8\u99e9\u99ea\u99eb\u99ec\u99ed\u99ee\u99ef\u99f0\u99f1\u99f2\u99f3\u99f4\u99f5\u99f6\u99f7\u99f8\u99f9\u99fa\u99fb\u99fc\u99fd\u99fe\u99ff\u9a00\u9a01\u9a02\u9a03\u9a04\u9a05\u9a06\u9a07\u9a08\u9a09\u9a0a\u9a0b\u9a0c\u9a0d\u9a0e\u9a0f\u9a10\u9a11\u9a12\u9a13\u9a14\u9a15\u9a16\u9a17\u9a18\u9a19\u9a1a\u9a1b\u9a1c\u9a1d\u9a1e\u9a1f\u9a20\u9a21\u9a22\u9a23\u9a24\u9a25\u9a26\u9a27\u9a28\u9a29\u9a2a\u9a2b\u9a2c\u9a2d\u9a2e\u9a2f\u9a30\u9a31\u9a32\u9a33\u9a34\u9a35\u9a36\u9a37\u9a38\u9a39\u9a3a\u9a3b\u9a3c\u9a3d\u9a3e\u9a3f\u9a40\u9a41\u9a42\u9a43\u9a44\u9a45\u9a46\u9a47\u9a48\u9a49\u9a4a\u9a4b\u9a4c\u9a4d\u9a4e\u9a4f\u9a50\u9a51\u9a52\u9a53\u9a54\u9a55\u9a56\u9a57\u9a58\u9a59\u9a5a\u9a5b\u9a5c\u9a5d\u9a5e\u9a5f\u9a60\u9a61\u9a62\u9a63\u9a64\u9a65\u9a66\u9a67\u9a68\u9a69\u9a6a\u9a6b\u9a6c\u9a6d\u9a6e\u9a6f\u9a70\u9a71\u9a72\u9a73\u9a74\u9a75\u9a76\u9a77\u9a78\u9a79\u9a7a\u9a7b\u9a7c\u9a7d\u9a7e\u9a7f\u9a80\u9a81\u9a82\u9a83\u9a84\u9a85\u9a86\u9a87\u9a88\u9a89\u9a8a\u9a8b\u9a8c\u9a8d\u9a8e\u9a8f\u9a90\u9a91\u9a92\u9a93\u9a94\u9a95\u9a96\u9a97\u9a98\u9a99\u9a9a\u9a9b\u9a9c\u9a9d\u9a9e\u9a9f\u9aa0\u9aa1\u9aa2\u9aa3\u9aa4\u9aa5\u9aa6\u9aa7\u9aa8\u9aa9\u9aaa\u9aab\u9aac\u9aad\u9aae\u9aaf\u9ab0\u9ab1\u9ab2\u9ab3\u9ab4\u9ab5\u9ab6\u9ab7\u9ab8\u9ab9\u9aba\u9abb\u9abc\u9abd\u9abe\u9abf\u9ac0\u9ac1\u9ac2\u9ac3\u9ac4\u9ac5\u9ac6\u9ac7\u9ac8\u9ac9\u9aca\u9acb\u9acc\u9acd\u9ace\u9acf\u9ad0\u9ad1\u9ad2\u9ad3\u9ad4\u9ad5\u9ad6\u9ad7\u9ad8\u9ad9\u9ada\u9adb\u9adc\u9add\u9ade\u9adf\u9ae0\u9ae1\u9ae2\u9ae3\u9ae4\u9ae5\u9ae6\u9ae7\u9ae8\u9ae9\u9aea\u9aeb\u9aec\u9aed\u9aee\u9aef\u9af0\u9af1\u9af2\u9af3\u9af4\u9af5\u9af6\u9af7\u9af8\u9af9\u9afa\u9afb\u9afc\u9afd\u9afe\u9aff\u9b00\u9b01\u9b02\u9b03\u9b04\u9b05\u9b06\u9b07\u9b08\u9b09\u9b0a\u9b0b\u9b0c\u9b0d\u9b0e\u9b0f\u9b10\u9b11\u9b12\u9b13\u9b14\u9b15\u9b16\u9b17\u9b18\u9b19\u9b1a\u9b1b\u9b1c\u9b1d\u9b1e\u9b1f\u9b20\u9b21\u9b22\u9b23\u9b24\u9b25\u9b26\u9b27\u9b28\u9b29\u9b2a\u9b2b\u9b2c\u9b2d\u9b2e\u9b2f\u9b30\u9b31\u9b32\u9b33\u9b34\u9b35\u9b36\u9b37\u9b38\u9b39\u9b3a\u9b3b\u9b3c\u9b3d\u9b3e\u9b3f\u9b40\u9b41\u9b42\u9b43\u9b44\u9b45\u9b46\u9b47\u9b48\u9b49\u9b4a\u9b4b\u9b4c\u9b4d\u9b4e\u9b4f\u9b50\u9b51\u9b52\u9b53\u9b54\u9b55\u9b56\u9b57\u9b58\u9b59\u9b5a\u9b5b\u9b5c\u9b5d\u9b5e\u9b5f\u9b60\u9b61\u9b62\u9b63\u9b64\u9b65\u9b66\u9b67\u9b68\u9b69\u9b6a\u9b6b\u9b6c\u9b6d\u9b6e\u9b6f\u9b70\u9b71\u9b72\u9b73\u9b74\u9b75\u9b76\u9b77\u9b78\u9b79\u9b7a\u9b7b\u9b7c\u9b7d\u9b7e\u9b7f\u9b80\u9b81\u9b82\u9b83\u9b84\u9b85\u9b86\u9b87\u9b88\u9b89\u9b8a\u9b8b\u9b8c\u9b8d\u9b8e\u9b8f\u9b90\u9b91\u9b92\u9b93\u9b94\u9b95\u9b96\u9b97\u9b98\u9b99\u9b9a\u9b9b\u9b9c\u9b9d\u9b9e\u9b9f\u9ba0\u9ba1\u9ba2\u9ba3\u9ba4\u9ba5\u9ba6\u9ba7\u9ba8\u9ba9\u9baa\u9bab\u9bac\u9bad\u9bae\u9baf\u9bb0\u9bb1\u9bb2\u9bb3\u9bb4\u9bb5\u9bb6\u9bb7\u9bb8\u9bb9\u9bba\u9bbb\u9bbc\u9bbd\u9bbe\u9bbf\u9bc0\u9bc1\u9bc2\u9bc3\u9bc4\u9bc5\u9bc6\u9bc7\u9bc8\u9bc9\u9bca\u9bcb\u9bcc\u9bcd\u9bce\u9bcf\u9bd0\u9bd1\u9bd2\u9bd3\u9bd4\u9bd5\u9bd6\u9bd7\u9bd8\u9bd9\u9bda\u9bdb\u9bdc\u9bdd\u9bde\u9bdf\u9be0\u9be1\u9be2\u9be3\u9be4\u9be5\u9be6\u9be7\u9be8\u9be9\u9bea\u9beb\u9bec\u9bed\u9bee\u9bef\u9bf0\u9bf1\u9bf2\u9bf3\u9bf4\u9bf5\u9bf6\u9bf7\u9bf8\u9bf9\u9bfa\u9bfb\u9bfc\u9bfd\u9bfe\u9bff\u9c00\u9c01\u9c02\u9c03\u9c04\u9c05\u9c06\u9c07\u9c08\u9c09\u9c0a\u9c0b\u9c0c\u9c0d\u9c0e\u9c0f\u9c10\u9c11\u9c12\u9c13\u9c14\u9c15\u9c16\u9c17\u9c18\u9c19\u9c1a\u9c1b\u9c1c\u9c1d\u9c1e\u9c1f\u9c20\u9c21\u9c22\u9c23\u9c24\u9c25\u9c26\u9c27\u9c28\u9c29\u9c2a\u9c2b\u9c2c\u9c2d\u9c2e\u9c2f\u9c30\u9c31\u9c32\u9c33\u9c34\u9c35\u9c36\u9c37\u9c38\u9c39\u9c3a\u9c3b\u9c3c\u9c3d\u9c3e\u9c3f\u9c40\u9c41\u9c42\u9c43\u9c44\u9c45\u9c46\u9c47\u9c48\u9c49\u9c4a\u9c4b\u9c4c\u9c4d\u9c4e\u9c4f\u9c50\u9c51\u9c52\u9c53\u9c54\u9c55\u9c56\u9c57\u9c58\u9c59\u9c5a\u9c5b\u9c5c\u9c5d\u9c5e\u9c5f\u9c60\u9c61\u9c62\u9c63\u9c64\u9c65\u9c66\u9c67\u9c68\u9c69\u9c6a\u9c6b\u9c6c\u9c6d\u9c6e\u9c6f\u9c70\u9c71\u9c72\u9c73\u9c74\u9c75\u9c76\u9c77\u9c78\u9c79\u9c7a\u9c7b\u9c7c\u9c7d\u9c7e\u9c7f\u9c80\u9c81\u9c82\u9c83\u9c84\u9c85\u9c86\u9c87\u9c88\u9c89\u9c8a\u9c8b\u9c8c\u9c8d\u9c8e\u9c8f\u9c90\u9c91\u9c92\u9c93\u9c94\u9c95\u9c96\u9c97\u9c98\u9c99\u9c9a\u9c9b\u9c9c\u9c9d\u9c9e\u9c9f\u9ca0\u9ca1\u9ca2\u9ca3\u9ca4\u9ca5\u9ca6\u9ca7\u9ca8\u9ca9\u9caa\u9cab\u9cac\u9cad\u9cae\u9caf\u9cb0\u9cb1\u9cb2\u9cb3\u9cb4\u9cb5\u9cb6\u9cb7\u9cb8\u9cb9\u9cba\u9cbb\u9cbc\u9cbd\u9cbe\u9cbf\u9cc0\u9cc1\u9cc2\u9cc3\u9cc4\u9cc5\u9cc6\u9cc7\u9cc8\u9cc9\u9cca\u9ccb\u9ccc\u9ccd\u9cce\u9ccf\u9cd0\u9cd1\u9cd2\u9cd3\u9cd4\u9cd5\u9cd6\u9cd7\u9cd8\u9cd9\u9cda\u9cdb\u9cdc\u9cdd\u9cde\u9cdf\u9ce0\u9ce1\u9ce2\u9ce3\u9ce4\u9ce5\u9ce6\u9ce7\u9ce8\u9ce9\u9cea\u9ceb\u9cec\u9ced\u9cee\u9cef\u9cf0\u9cf1\u9cf2\u9cf3\u9cf4\u9cf5\u9cf6\u9cf7\u9cf8\u9cf9\u9cfa\u9cfb\u9cfc\u9cfd\u9cfe\u9cff\u9d00\u9d01\u9d02\u9d03\u9d04\u9d05\u9d06\u9d07\u9d08\u9d09\u9d0a\u9d0b\u9d0c\u9d0d\u9d0e\u9d0f\u9d10\u9d11\u9d12\u9d13\u9d14\u9d15\u9d16\u9d17\u9d18\u9d19\u9d1a\u9d1b\u9d1c\u9d1d\u9d1e\u9d1f\u9d20\u9d21\u9d22\u9d23\u9d24\u9d25\u9d26\u9d27\u9d28\u9d29\u9d2a\u9d2b\u9d2c\u9d2d\u9d2e\u9d2f\u9d30\u9d31\u9d32\u9d33\u9d34\u9d35\u9d36\u9d37\u9d38\u9d39\u9d3a\u9d3b\u9d3c\u9d3d\u9d3e\u9d3f\u9d40\u9d41\u9d42\u9d43\u9d44\u9d45\u9d46\u9d47\u9d48\u9d49\u9d4a\u9d4b\u9d4c\u9d4d\u9d4e\u9d4f\u9d50\u9d51\u9d52\u9d53\u9d54\u9d55\u9d56\u9d57\u9d58\u9d59\u9d5a\u9d5b\u9d5c\u9d5d\u9d5e\u9d5f\u9d60\u9d61\u9d62\u9d63\u9d64\u9d65\u9d66\u9d67\u9d68\u9d69\u9d6a\u9d6b\u9d6c\u9d6d\u9d6e\u9d6f\u9d70\u9d71\u9d72\u9d73\u9d74\u9d75\u9d76\u9d77\u9d78\u9d79\u9d7a\u9d7b\u9d7c\u9d7d\u9d7e\u9d7f\u9d80\u9d81\u9d82\u9d83\u9d84\u9d85\u9d86\u9d87\u9d88\u9d89\u9d8a\u9d8b\u9d8c\u9d8d\u9d8e\u9d8f\u9d90\u9d91\u9d92\u9d93\u9d94\u9d95\u9d96\u9d97\u9d98\u9d99\u9d9a\u9d9b\u9d9c\u9d9d\u9d9e\u9d9f\u9da0\u9da1\u9da2\u9da3\u9da4\u9da5\u9da6\u9da7\u9da8\u9da9\u9daa\u9dab\u9dac\u9dad\u9dae\u9daf\u9db0\u9db1\u9db2\u9db3\u9db4\u9db5\u9db6\u9db7\u9db8\u9db9\u9dba\u9dbb\u9dbc\u9dbd\u9dbe\u9dbf\u9dc0\u9dc1\u9dc2\u9dc3\u9dc4\u9dc5\u9dc6\u9dc7\u9dc8\u9dc9\u9dca\u9dcb\u9dcc\u9dcd\u9dce\u9dcf\u9dd0\u9dd1\u9dd2\u9dd3\u9dd4\u9dd5\u9dd6\u9dd7\u9dd8\u9dd9\u9dda\u9ddb\u9ddc\u9ddd\u9dde\u9ddf\u9de0\u9de1\u9de2\u9de3\u9de4\u9de5\u9de6\u9de7\u9de8\u9de9\u9dea\u9deb\u9dec\u9ded\u9dee\u9def\u9df0\u9df1\u9df2\u9df3\u9df4\u9df5\u9df6\u9df7\u9df8\u9df9\u9dfa\u9dfb\u9dfc\u9dfd\u9dfe\u9dff\u9e00\u9e01\u9e02\u9e03\u9e04\u9e05\u9e06\u9e07\u9e08\u9e09\u9e0a\u9e0b\u9e0c\u9e0d\u9e0e\u9e0f\u9e10\u9e11\u9e12\u9e13\u9e14\u9e15\u9e16\u9e17\u9e18\u9e19\u9e1a\u9e1b\u9e1c\u9e1d\u9e1e\u9e1f\u9e20\u9e21\u9e22\u9e23\u9e24\u9e25\u9e26\u9e27\u9e28\u9e29\u9e2a\u9e2b\u9e2c\u9e2d\u9e2e\u9e2f\u9e30\u9e31\u9e32\u9e33\u9e34\u9e35\u9e36\u9e37\u9e38\u9e39\u9e3a\u9e3b\u9e3c\u9e3d\u9e3e\u9e3f\u9e40\u9e41\u9e42\u9e43\u9e44\u9e45\u9e46\u9e47\u9e48\u9e49\u9e4a\u9e4b\u9e4c\u9e4d\u9e4e\u9e4f\u9e50\u9e51\u9e52\u9e53\u9e54\u9e55\u9e56\u9e57\u9e58\u9e59\u9e5a\u9e5b\u9e5c\u9e5d\u9e5e\u9e5f\u9e60\u9e61\u9e62\u9e63\u9e64\u9e65\u9e66\u9e67\u9e68\u9e69\u9e6a\u9e6b\u9e6c\u9e6d\u9e6e\u9e6f\u9e70\u9e71\u9e72\u9e73\u9e74\u9e75\u9e76\u9e77\u9e78\u9e79\u9e7a\u9e7b\u9e7c\u9e7d\u9e7e\u9e7f\u9e80\u9e81\u9e82\u9e83\u9e84\u9e85\u9e86\u9e87\u9e88\u9e89\u9e8a\u9e8b\u9e8c\u9e8d\u9e8e\u9e8f\u9e90\u9e91\u9e92\u9e93\u9e94\u9e95\u9e96\u9e97\u9e98\u9e99\u9e9a\u9e9b\u9e9c\u9e9d\u9e9e\u9e9f\u9ea0\u9ea1\u9ea2\u9ea3\u9ea4\u9ea5\u9ea6\u9ea7\u9ea8\u9ea9\u9eaa\u9eab\u9eac\u9ead\u9eae\u9eaf\u9eb0\u9eb1\u9eb2\u9eb3\u9eb4\u9eb5\u9eb6\u9eb7\u9eb8\u9eb9\u9eba\u9ebb\u9ebc\u9ebd\u9ebe\u9ebf\u9ec0\u9ec1\u9ec2\u9ec3\u9ec4\u9ec5\u9ec6\u9ec7\u9ec8\u9ec9\u9eca\u9ecb\u9ecc\u9ecd\u9ece\u9ecf\u9ed0\u9ed1\u9ed2\u9ed3\u9ed4\u9ed5\u9ed6\u9ed7\u9ed8\u9ed9\u9eda\u9edb\u9edc\u9edd\u9ede\u9edf\u9ee0\u9ee1\u9ee2\u9ee3\u9ee4\u9ee5\u9ee6\u9ee7\u9ee8\u9ee9\u9eea\u9eeb\u9eec\u9eed\u9eee\u9eef\u9ef0\u9ef1\u9ef2\u9ef3\u9ef4\u9ef5\u9ef6\u9ef7\u9ef8\u9ef9\u9efa\u9efb\u9efc\u9efd\u9efe\u9eff\u9f00\u9f01\u9f02\u9f03\u9f04\u9f05\u9f06\u9f07\u9f08\u9f09\u9f0a\u9f0b\u9f0c\u9f0d\u9f0e\u9f0f\u9f10\u9f11\u9f12\u9f13\u9f14\u9f15\u9f16\u9f17\u9f18\u9f19\u9f1a\u9f1b\u9f1c\u9f1d\u9f1e\u9f1f\u9f20\u9f21\u9f22\u9f23\u9f24\u9f25\u9f26\u9f27\u9f28\u9f29\u9f2a\u9f2b\u9f2c\u9f2d\u9f2e\u9f2f\u9f30\u9f31\u9f32\u9f33\u9f34\u9f35\u9f36\u9f37\u9f38\u9f39\u9f3a\u9f3b\u9f3c\u9f3d\u9f3e\u9f3f\u9f40\u9f41\u9f42\u9f43\u9f44\u9f45\u9f46\u9f47\u9f48\u9f49\u9f4a\u9f4b\u9f4c\u9f4d\u9f4e\u9f4f\u9f50\u9f51\u9f52\u9f53\u9f54\u9f55\u9f56\u9f57\u9f58\u9f59\u9f5a\u9f5b\u9f5c\u9f5d\u9f5e\u9f5f\u9f60\u9f61\u9f62\u9f63\u9f64\u9f65\u9f66\u9f67\u9f68\u9f69\u9f6a\u9f6b\u9f6c\u9f6d\u9f6e\u9f6f\u9f70\u9f71\u9f72\u9f73\u9f74\u9f75\u9f76\u9f77\u9f78\u9f79\u9f7a\u9f7b\u9f7c\u9f7d\u9f7e\u9f7f\u9f80\u9f81\u9f82\u9f83\u9f84\u9f85\u9f86\u9f87\u9f88\u9f89\u9f8a\u9f8b\u9f8c\u9f8d\u9f8e\u9f8f\u9f90\u9f91\u9f92\u9f93\u9f94\u9f95\u9f96\u9f97\u9f98\u9f99\u9f9a\u9f9b\u9f9c\u9f9d\u9f9e\u9f9f\u9fa0\u9fa1\u9fa2\u9fa3\u9fa4\u9fa5\u9fa6\u9fa7\u9fa8\u9fa9\u9faa\u9fab\u9fac\u9fad\u9fae\u9faf\u9fb0\u9fb1\u9fb2\u9fb3\u9fb4\u9fb5\u9fb6\u9fb7\u9fb8\u9fb9\u9fba\u9fbb\ua000\ua001\ua002\ua003\ua004\ua005\ua006\ua007\ua008\ua009\ua00a\ua00b\ua00c\ua00d\ua00e\ua00f\ua010\ua011\ua012\ua013\ua014\ua016\ua017\ua018\ua019\ua01a\ua01b\ua01c\ua01d\ua01e\ua01f\ua020\ua021\ua022\ua023\ua024\ua025\ua026\ua027\ua028\ua029\ua02a\ua02b\ua02c\ua02d\ua02e\ua02f\ua030\ua031\ua032\ua033\ua034\ua035\ua036\ua037\ua038\ua039\ua03a\ua03b\ua03c\ua03d\ua03e\ua03f\ua040\ua041\ua042\ua043\ua044\ua045\ua046\ua047\ua048\ua049\ua04a\ua04b\ua04c\ua04d\ua04e\ua04f\ua050\ua051\ua052\ua053\ua054\ua055\ua056\ua057\ua058\ua059\ua05a\ua05b\ua05c\ua05d\ua05e\ua05f\ua060\ua061\ua062\ua063\ua064\ua065\ua066\ua067\ua068\ua069\ua06a\ua06b\ua06c\ua06d\ua06e\ua06f\ua070\ua071\ua072\ua073\ua074\ua075\ua076\ua077\ua078\ua079\ua07a\ua07b\ua07c\ua07d\ua07e\ua07f\ua080\ua081\ua082\ua083\ua084\ua085\ua086\ua087\ua088\ua089\ua08a\ua08b\ua08c\ua08d\ua08e\ua08f\ua090\ua091\ua092\ua093\ua094\ua095\ua096\ua097\ua098\ua099\ua09a\ua09b\ua09c\ua09d\ua09e\ua09f\ua0a0\ua0a1\ua0a2\ua0a3\ua0a4\ua0a5\ua0a6\ua0a7\ua0a8\ua0a9\ua0aa\ua0ab\ua0ac\ua0ad\ua0ae\ua0af\ua0b0\ua0b1\ua0b2\ua0b3\ua0b4\ua0b5\ua0b6\ua0b7\ua0b8\ua0b9\ua0ba\ua0bb\ua0bc\ua0bd\ua0be\ua0bf\ua0c0\ua0c1\ua0c2\ua0c3\ua0c4\ua0c5\ua0c6\ua0c7\ua0c8\ua0c9\ua0ca\ua0cb\ua0cc\ua0cd\ua0ce\ua0cf\ua0d0\ua0d1\ua0d2\ua0d3\ua0d4\ua0d5\ua0d6\ua0d7\ua0d8\ua0d9\ua0da\ua0db\ua0dc\ua0dd\ua0de\ua0df\ua0e0\ua0e1\ua0e2\ua0e3\ua0e4\ua0e5\ua0e6\ua0e7\ua0e8\ua0e9\ua0ea\ua0eb\ua0ec\ua0ed\ua0ee\ua0ef\ua0f0\ua0f1\ua0f2\ua0f3\ua0f4\ua0f5\ua0f6\ua0f7\ua0f8\ua0f9\ua0fa\ua0fb\ua0fc\ua0fd\ua0fe\ua0ff\ua100\ua101\ua102\ua103\ua104\ua105\ua106\ua107\ua108\ua109\ua10a\ua10b\ua10c\ua10d\ua10e\ua10f\ua110\ua111\ua112\ua113\ua114\ua115\ua116\ua117\ua118\ua119\ua11a\ua11b\ua11c\ua11d\ua11e\ua11f\ua120\ua121\ua122\ua123\ua124\ua125\ua126\ua127\ua128\ua129\ua12a\ua12b\ua12c\ua12d\ua12e\ua12f\ua130\ua131\ua132\ua133\ua134\ua135\ua136\ua137\ua138\ua139\ua13a\ua13b\ua13c\ua13d\ua13e\ua13f\ua140\ua141\ua142\ua143\ua144\ua145\ua146\ua147\ua148\ua149\ua14a\ua14b\ua14c\ua14d\ua14e\ua14f\ua150\ua151\ua152\ua153\ua154\ua155\ua156\ua157\ua158\ua159\ua15a\ua15b\ua15c\ua15d\ua15e\ua15f\ua160\ua161\ua162\ua163\ua164\ua165\ua166\ua167\ua168\ua169\ua16a\ua16b\ua16c\ua16d\ua16e\ua16f\ua170\ua171\ua172\ua173\ua174\ua175\ua176\ua177\ua178\ua179\ua17a\ua17b\ua17c\ua17d\ua17e\ua17f\ua180\ua181\ua182\ua183\ua184\ua185\ua186\ua187\ua188\ua189\ua18a\ua18b\ua18c\ua18d\ua18e\ua18f\ua190\ua191\ua192\ua193\ua194\ua195\ua196\ua197\ua198\ua199\ua19a\ua19b\ua19c\ua19d\ua19e\ua19f\ua1a0\ua1a1\ua1a2\ua1a3\ua1a4\ua1a5\ua1a6\ua1a7\ua1a8\ua1a9\ua1aa\ua1ab\ua1ac\ua1ad\ua1ae\ua1af\ua1b0\ua1b1\ua1b2\ua1b3\ua1b4\ua1b5\ua1b6\ua1b7\ua1b8\ua1b9\ua1ba\ua1bb\ua1bc\ua1bd\ua1be\ua1bf\ua1c0\ua1c1\ua1c2\ua1c3\ua1c4\ua1c5\ua1c6\ua1c7\ua1c8\ua1c9\ua1ca\ua1cb\ua1cc\ua1cd\ua1ce\ua1cf\ua1d0\ua1d1\ua1d2\ua1d3\ua1d4\ua1d5\ua1d6\ua1d7\ua1d8\ua1d9\ua1da\ua1db\ua1dc\ua1dd\ua1de\ua1df\ua1e0\ua1e1\ua1e2\ua1e3\ua1e4\ua1e5\ua1e6\ua1e7\ua1e8\ua1e9\ua1ea\ua1eb\ua1ec\ua1ed\ua1ee\ua1ef\ua1f0\ua1f1\ua1f2\ua1f3\ua1f4\ua1f5\ua1f6\ua1f7\ua1f8\ua1f9\ua1fa\ua1fb\ua1fc\ua1fd\ua1fe\ua1ff\ua200\ua201\ua202\ua203\ua204\ua205\ua206\ua207\ua208\ua209\ua20a\ua20b\ua20c\ua20d\ua20e\ua20f\ua210\ua211\ua212\ua213\ua214\ua215\ua216\ua217\ua218\ua219\ua21a\ua21b\ua21c\ua21d\ua21e\ua21f\ua220\ua221\ua222\ua223\ua224\ua225\ua226\ua227\ua228\ua229\ua22a\ua22b\ua22c\ua22d\ua22e\ua22f\ua230\ua231\ua232\ua233\ua234\ua235\ua236\ua237\ua238\ua239\ua23a\ua23b\ua23c\ua23d\ua23e\ua23f\ua240\ua241\ua242\ua243\ua244\ua245\ua246\ua247\ua248\ua249\ua24a\ua24b\ua24c\ua24d\ua24e\ua24f\ua250\ua251\ua252\ua253\ua254\ua255\ua256\ua257\ua258\ua259\ua25a\ua25b\ua25c\ua25d\ua25e\ua25f\ua260\ua261\ua262\ua263\ua264\ua265\ua266\ua267\ua268\ua269\ua26a\ua26b\ua26c\ua26d\ua26e\ua26f\ua270\ua271\ua272\ua273\ua274\ua275\ua276\ua277\ua278\ua279\ua27a\ua27b\ua27c\ua27d\ua27e\ua27f\ua280\ua281\ua282\ua283\ua284\ua285\ua286\ua287\ua288\ua289\ua28a\ua28b\ua28c\ua28d\ua28e\ua28f\ua290\ua291\ua292\ua293\ua294\ua295\ua296\ua297\ua298\ua299\ua29a\ua29b\ua29c\ua29d\ua29e\ua29f\ua2a0\ua2a1\ua2a2\ua2a3\ua2a4\ua2a5\ua2a6\ua2a7\ua2a8\ua2a9\ua2aa\ua2ab\ua2ac\ua2ad\ua2ae\ua2af\ua2b0\ua2b1\ua2b2\ua2b3\ua2b4\ua2b5\ua2b6\ua2b7\ua2b8\ua2b9\ua2ba\ua2bb\ua2bc\ua2bd\ua2be\ua2bf\ua2c0\ua2c1\ua2c2\ua2c3\ua2c4\ua2c5\ua2c6\ua2c7\ua2c8\ua2c9\ua2ca\ua2cb\ua2cc\ua2cd\ua2ce\ua2cf\ua2d0\ua2d1\ua2d2\ua2d3\ua2d4\ua2d5\ua2d6\ua2d7\ua2d8\ua2d9\ua2da\ua2db\ua2dc\ua2dd\ua2de\ua2df\ua2e0\ua2e1\ua2e2\ua2e3\ua2e4\ua2e5\ua2e6\ua2e7\ua2e8\ua2e9\ua2ea\ua2eb\ua2ec\ua2ed\ua2ee\ua2ef\ua2f0\ua2f1\ua2f2\ua2f3\ua2f4\ua2f5\ua2f6\ua2f7\ua2f8\ua2f9\ua2fa\ua2fb\ua2fc\ua2fd\ua2fe\ua2ff\ua300\ua301\ua302\ua303\ua304\ua305\ua306\ua307\ua308\ua309\ua30a\ua30b\ua30c\ua30d\ua30e\ua30f\ua310\ua311\ua312\ua313\ua314\ua315\ua316\ua317\ua318\ua319\ua31a\ua31b\ua31c\ua31d\ua31e\ua31f\ua320\ua321\ua322\ua323\ua324\ua325\ua326\ua327\ua328\ua329\ua32a\ua32b\ua32c\ua32d\ua32e\ua32f\ua330\ua331\ua332\ua333\ua334\ua335\ua336\ua337\ua338\ua339\ua33a\ua33b\ua33c\ua33d\ua33e\ua33f\ua340\ua341\ua342\ua343\ua344\ua345\ua346\ua347\ua348\ua349\ua34a\ua34b\ua34c\ua34d\ua34e\ua34f\ua350\ua351\ua352\ua353\ua354\ua355\ua356\ua357\ua358\ua359\ua35a\ua35b\ua35c\ua35d\ua35e\ua35f\ua360\ua361\ua362\ua363\ua364\ua365\ua366\ua367\ua368\ua369\ua36a\ua36b\ua36c\ua36d\ua36e\ua36f\ua370\ua371\ua372\ua373\ua374\ua375\ua376\ua377\ua378\ua379\ua37a\ua37b\ua37c\ua37d\ua37e\ua37f\ua380\ua381\ua382\ua383\ua384\ua385\ua386\ua387\ua388\ua389\ua38a\ua38b\ua38c\ua38d\ua38e\ua38f\ua390\ua391\ua392\ua393\ua394\ua395\ua396\ua397\ua398\ua399\ua39a\ua39b\ua39c\ua39d\ua39e\ua39f\ua3a0\ua3a1\ua3a2\ua3a3\ua3a4\ua3a5\ua3a6\ua3a7\ua3a8\ua3a9\ua3aa\ua3ab\ua3ac\ua3ad\ua3ae\ua3af\ua3b0\ua3b1\ua3b2\ua3b3\ua3b4\ua3b5\ua3b6\ua3b7\ua3b8\ua3b9\ua3ba\ua3bb\ua3bc\ua3bd\ua3be\ua3bf\ua3c0\ua3c1\ua3c2\ua3c3\ua3c4\ua3c5\ua3c6\ua3c7\ua3c8\ua3c9\ua3ca\ua3cb\ua3cc\ua3cd\ua3ce\ua3cf\ua3d0\ua3d1\ua3d2\ua3d3\ua3d4\ua3d5\ua3d6\ua3d7\ua3d8\ua3d9\ua3da\ua3db\ua3dc\ua3dd\ua3de\ua3df\ua3e0\ua3e1\ua3e2\ua3e3\ua3e4\ua3e5\ua3e6\ua3e7\ua3e8\ua3e9\ua3ea\ua3eb\ua3ec\ua3ed\ua3ee\ua3ef\ua3f0\ua3f1\ua3f2\ua3f3\ua3f4\ua3f5\ua3f6\ua3f7\ua3f8\ua3f9\ua3fa\ua3fb\ua3fc\ua3fd\ua3fe\ua3ff\ua400\ua401\ua402\ua403\ua404\ua405\ua406\ua407\ua408\ua409\ua40a\ua40b\ua40c\ua40d\ua40e\ua40f\ua410\ua411\ua412\ua413\ua414\ua415\ua416\ua417\ua418\ua419\ua41a\ua41b\ua41c\ua41d\ua41e\ua41f\ua420\ua421\ua422\ua423\ua424\ua425\ua426\ua427\ua428\ua429\ua42a\ua42b\ua42c\ua42d\ua42e\ua42f\ua430\ua431\ua432\ua433\ua434\ua435\ua436\ua437\ua438\ua439\ua43a\ua43b\ua43c\ua43d\ua43e\ua43f\ua440\ua441\ua442\ua443\ua444\ua445\ua446\ua447\ua448\ua449\ua44a\ua44b\ua44c\ua44d\ua44e\ua44f\ua450\ua451\ua452\ua453\ua454\ua455\ua456\ua457\ua458\ua459\ua45a\ua45b\ua45c\ua45d\ua45e\ua45f\ua460\ua461\ua462\ua463\ua464\ua465\ua466\ua467\ua468\ua469\ua46a\ua46b\ua46c\ua46d\ua46e\ua46f\ua470\ua471\ua472\ua473\ua474\ua475\ua476\ua477\ua478\ua479\ua47a\ua47b\ua47c\ua47d\ua47e\ua47f\ua480\ua481\ua482\ua483\ua484\ua485\ua486\ua487\ua488\ua489\ua48a\ua48b\ua48c\ua800\ua801\ua803\ua804\ua805\ua807\ua808\ua809\ua80a\ua80c\ua80d\ua80e\ua80f\ua810\ua811\ua812\ua813\ua814\ua815\ua816\ua817\ua818\ua819\ua81a\ua81b\ua81c\ua81d\ua81e\ua81f\ua820\ua821\ua822\uac00\uac01\uac02\uac03\uac04\uac05\uac06\uac07\uac08\uac09\uac0a\uac0b\uac0c\uac0d\uac0e\uac0f\uac10\uac11\uac12\uac13\uac14\uac15\uac16\uac17\uac18\uac19\uac1a\uac1b\uac1c\uac1d\uac1e\uac1f\uac20\uac21\uac22\uac23\uac24\uac25\uac26\uac27\uac28\uac29\uac2a\uac2b\uac2c\uac2d\uac2e\uac2f\uac30\uac31\uac32\uac33\uac34\uac35\uac36\uac37\uac38\uac39\uac3a\uac3b\uac3c\uac3d\uac3e\uac3f\uac40\uac41\uac42\uac43\uac44\uac45\uac46\uac47\uac48\uac49\uac4a\uac4b\uac4c\uac4d\uac4e\uac4f\uac50\uac51\uac52\uac53\uac54\uac55\uac56\uac57\uac58\uac59\uac5a\uac5b\uac5c\uac5d\uac5e\uac5f\uac60\uac61\uac62\uac63\uac64\uac65\uac66\uac67\uac68\uac69\uac6a\uac6b\uac6c\uac6d\uac6e\uac6f\uac70\uac71\uac72\uac73\uac74\uac75\uac76\uac77\uac78\uac79\uac7a\uac7b\uac7c\uac7d\uac7e\uac7f\uac80\uac81\uac82\uac83\uac84\uac85\uac86\uac87\uac88\uac89\uac8a\uac8b\uac8c\uac8d\uac8e\uac8f\uac90\uac91\uac92\uac93\uac94\uac95\uac96\uac97\uac98\uac99\uac9a\uac9b\uac9c\uac9d\uac9e\uac9f\uaca0\uaca1\uaca2\uaca3\uaca4\uaca5\uaca6\uaca7\uaca8\uaca9\uacaa\uacab\uacac\uacad\uacae\uacaf\uacb0\uacb1\uacb2\uacb3\uacb4\uacb5\uacb6\uacb7\uacb8\uacb9\uacba\uacbb\uacbc\uacbd\uacbe\uacbf\uacc0\uacc1\uacc2\uacc3\uacc4\uacc5\uacc6\uacc7\uacc8\uacc9\uacca\uaccb\uaccc\uaccd\uacce\uaccf\uacd0\uacd1\uacd2\uacd3\uacd4\uacd5\uacd6\uacd7\uacd8\uacd9\uacda\uacdb\uacdc\uacdd\uacde\uacdf\uace0\uace1\uace2\uace3\uace4\uace5\uace6\uace7\uace8\uace9\uacea\uaceb\uacec\uaced\uacee\uacef\uacf0\uacf1\uacf2\uacf3\uacf4\uacf5\uacf6\uacf7\uacf8\uacf9\uacfa\uacfb\uacfc\uacfd\uacfe\uacff\uad00\uad01\uad02\uad03\uad04\uad05\uad06\uad07\uad08\uad09\uad0a\uad0b\uad0c\uad0d\uad0e\uad0f\uad10\uad11\uad12\uad13\uad14\uad15\uad16\uad17\uad18\uad19\uad1a\uad1b\uad1c\uad1d\uad1e\uad1f\uad20\uad21\uad22\uad23\uad24\uad25\uad26\uad27\uad28\uad29\uad2a\uad2b\uad2c\uad2d\uad2e\uad2f\uad30\uad31\uad32\uad33\uad34\uad35\uad36\uad37\uad38\uad39\uad3a\uad3b\uad3c\uad3d\uad3e\uad3f\uad40\uad41\uad42\uad43\uad44\uad45\uad46\uad47\uad48\uad49\uad4a\uad4b\uad4c\uad4d\uad4e\uad4f\uad50\uad51\uad52\uad53\uad54\uad55\uad56\uad57\uad58\uad59\uad5a\uad5b\uad5c\uad5d\uad5e\uad5f\uad60\uad61\uad62\uad63\uad64\uad65\uad66\uad67\uad68\uad69\uad6a\uad6b\uad6c\uad6d\uad6e\uad6f\uad70\uad71\uad72\uad73\uad74\uad75\uad76\uad77\uad78\uad79\uad7a\uad7b\uad7c\uad7d\uad7e\uad7f\uad80\uad81\uad82\uad83\uad84\uad85\uad86\uad87\uad88\uad89\uad8a\uad8b\uad8c\uad8d\uad8e\uad8f\uad90\uad91\uad92\uad93\uad94\uad95\uad96\uad97\uad98\uad99\uad9a\uad9b\uad9c\uad9d\uad9e\uad9f\uada0\uada1\uada2\uada3\uada4\uada5\uada6\uada7\uada8\uada9\uadaa\uadab\uadac\uadad\uadae\uadaf\uadb0\uadb1\uadb2\uadb3\uadb4\uadb5\uadb6\uadb7\uadb8\uadb9\uadba\uadbb\uadbc\uadbd\uadbe\uadbf\uadc0\uadc1\uadc2\uadc3\uadc4\uadc5\uadc6\uadc7\uadc8\uadc9\uadca\uadcb\uadcc\uadcd\uadce\uadcf\uadd0\uadd1\uadd2\uadd3\uadd4\uadd5\uadd6\uadd7\uadd8\uadd9\uadda\uaddb\uaddc\uaddd\uadde\uaddf\uade0\uade1\uade2\uade3\uade4\uade5\uade6\uade7\uade8\uade9\uadea\uadeb\uadec\uaded\uadee\uadef\uadf0\uadf1\uadf2\uadf3\uadf4\uadf5\uadf6\uadf7\uadf8\uadf9\uadfa\uadfb\uadfc\uadfd\uadfe\uadff\uae00\uae01\uae02\uae03\uae04\uae05\uae06\uae07\uae08\uae09\uae0a\uae0b\uae0c\uae0d\uae0e\uae0f\uae10\uae11\uae12\uae13\uae14\uae15\uae16\uae17\uae18\uae19\uae1a\uae1b\uae1c\uae1d\uae1e\uae1f\uae20\uae21\uae22\uae23\uae24\uae25\uae26\uae27\uae28\uae29\uae2a\uae2b\uae2c\uae2d\uae2e\uae2f\uae30\uae31\uae32\uae33\uae34\uae35\uae36\uae37\uae38\uae39\uae3a\uae3b\uae3c\uae3d\uae3e\uae3f\uae40\uae41\uae42\uae43\uae44\uae45\uae46\uae47\uae48\uae49\uae4a\uae4b\uae4c\uae4d\uae4e\uae4f\uae50\uae51\uae52\uae53\uae54\uae55\uae56\uae57\uae58\uae59\uae5a\uae5b\uae5c\uae5d\uae5e\uae5f\uae60\uae61\uae62\uae63\uae64\uae65\uae66\uae67\uae68\uae69\uae6a\uae6b\uae6c\uae6d\uae6e\uae6f\uae70\uae71\uae72\uae73\uae74\uae75\uae76\uae77\uae78\uae79\uae7a\uae7b\uae7c\uae7d\uae7e\uae7f\uae80\uae81\uae82\uae83\uae84\uae85\uae86\uae87\uae88\uae89\uae8a\uae8b\uae8c\uae8d\uae8e\uae8f\uae90\uae91\uae92\uae93\uae94\uae95\uae96\uae97\uae98\uae99\uae9a\uae9b\uae9c\uae9d\uae9e\uae9f\uaea0\uaea1\uaea2\uaea3\uaea4\uaea5\uaea6\uaea7\uaea8\uaea9\uaeaa\uaeab\uaeac\uaead\uaeae\uaeaf\uaeb0\uaeb1\uaeb2\uaeb3\uaeb4\uaeb5\uaeb6\uaeb7\uaeb8\uaeb9\uaeba\uaebb\uaebc\uaebd\uaebe\uaebf\uaec0\uaec1\uaec2\uaec3\uaec4\uaec5\uaec6\uaec7\uaec8\uaec9\uaeca\uaecb\uaecc\uaecd\uaece\uaecf\uaed0\uaed1\uaed2\uaed3\uaed4\uaed5\uaed6\uaed7\uaed8\uaed9\uaeda\uaedb\uaedc\uaedd\uaede\uaedf\uaee0\uaee1\uaee2\uaee3\uaee4\uaee5\uaee6\uaee7\uaee8\uaee9\uaeea\uaeeb\uaeec\uaeed\uaeee\uaeef\uaef0\uaef1\uaef2\uaef3\uaef4\uaef5\uaef6\uaef7\uaef8\uaef9\uaefa\uaefb\uaefc\uaefd\uaefe\uaeff\uaf00\uaf01\uaf02\uaf03\uaf04\uaf05\uaf06\uaf07\uaf08\uaf09\uaf0a\uaf0b\uaf0c\uaf0d\uaf0e\uaf0f\uaf10\uaf11\uaf12\uaf13\uaf14\uaf15\uaf16\uaf17\uaf18\uaf19\uaf1a\uaf1b\uaf1c\uaf1d\uaf1e\uaf1f\uaf20\uaf21\uaf22\uaf23\uaf24\uaf25\uaf26\uaf27\uaf28\uaf29\uaf2a\uaf2b\uaf2c\uaf2d\uaf2e\uaf2f\uaf30\uaf31\uaf32\uaf33\uaf34\uaf35\uaf36\uaf37\uaf38\uaf39\uaf3a\uaf3b\uaf3c\uaf3d\uaf3e\uaf3f\uaf40\uaf41\uaf42\uaf43\uaf44\uaf45\uaf46\uaf47\uaf48\uaf49\uaf4a\uaf4b\uaf4c\uaf4d\uaf4e\uaf4f\uaf50\uaf51\uaf52\uaf53\uaf54\uaf55\uaf56\uaf57\uaf58\uaf59\uaf5a\uaf5b\uaf5c\uaf5d\uaf5e\uaf5f\uaf60\uaf61\uaf62\uaf63\uaf64\uaf65\uaf66\uaf67\uaf68\uaf69\uaf6a\uaf6b\uaf6c\uaf6d\uaf6e\uaf6f\uaf70\uaf71\uaf72\uaf73\uaf74\uaf75\uaf76\uaf77\uaf78\uaf79\uaf7a\uaf7b\uaf7c\uaf7d\uaf7e\uaf7f\uaf80\uaf81\uaf82\uaf83\uaf84\uaf85\uaf86\uaf87\uaf88\uaf89\uaf8a\uaf8b\uaf8c\uaf8d\uaf8e\uaf8f\uaf90\uaf91\uaf92\uaf93\uaf94\uaf95\uaf96\uaf97\uaf98\uaf99\uaf9a\uaf9b\uaf9c\uaf9d\uaf9e\uaf9f\uafa0\uafa1\uafa2\uafa3\uafa4\uafa5\uafa6\uafa7\uafa8\uafa9\uafaa\uafab\uafac\uafad\uafae\uafaf\uafb0\uafb1\uafb2\uafb3\uafb4\uafb5\uafb6\uafb7\uafb8\uafb9\uafba\uafbb\uafbc\uafbd\uafbe\uafbf\uafc0\uafc1\uafc2\uafc3\uafc4\uafc5\uafc6\uafc7\uafc8\uafc9\uafca\uafcb\uafcc\uafcd\uafce\uafcf\uafd0\uafd1\uafd2\uafd3\uafd4\uafd5\uafd6\uafd7\uafd8\uafd9\uafda\uafdb\uafdc\uafdd\uafde\uafdf\uafe0\uafe1\uafe2\uafe3\uafe4\uafe5\uafe6\uafe7\uafe8\uafe9\uafea\uafeb\uafec\uafed\uafee\uafef\uaff0\uaff1\uaff2\uaff3\uaff4\uaff5\uaff6\uaff7\uaff8\uaff9\uaffa\uaffb\uaffc\uaffd\uaffe\uafff\ub000\ub001\ub002\ub003\ub004\ub005\ub006\ub007\ub008\ub009\ub00a\ub00b\ub00c\ub00d\ub00e\ub00f\ub010\ub011\ub012\ub013\ub014\ub015\ub016\ub017\ub018\ub019\ub01a\ub01b\ub01c\ub01d\ub01e\ub01f\ub020\ub021\ub022\ub023\ub024\ub025\ub026\ub027\ub028\ub029\ub02a\ub02b\ub02c\ub02d\ub02e\ub02f\ub030\ub031\ub032\ub033\ub034\ub035\ub036\ub037\ub038\ub039\ub03a\ub03b\ub03c\ub03d\ub03e\ub03f\ub040\ub041\ub042\ub043\ub044\ub045\ub046\ub047\ub048\ub049\ub04a\ub04b\ub04c\ub04d\ub04e\ub04f\ub050\ub051\ub052\ub053\ub054\ub055\ub056\ub057\ub058\ub059\ub05a\ub05b\ub05c\ub05d\ub05e\ub05f\ub060\ub061\ub062\ub063\ub064\ub065\ub066\ub067\ub068\ub069\ub06a\ub06b\ub06c\ub06d\ub06e\ub06f\ub070\ub071\ub072\ub073\ub074\ub075\ub076\ub077\ub078\ub079\ub07a\ub07b\ub07c\ub07d\ub07e\ub07f\ub080\ub081\ub082\ub083\ub084\ub085\ub086\ub087\ub088\ub089\ub08a\ub08b\ub08c\ub08d\ub08e\ub08f\ub090\ub091\ub092\ub093\ub094\ub095\ub096\ub097\ub098\ub099\ub09a\ub09b\ub09c\ub09d\ub09e\ub09f\ub0a0\ub0a1\ub0a2\ub0a3\ub0a4\ub0a5\ub0a6\ub0a7\ub0a8\ub0a9\ub0aa\ub0ab\ub0ac\ub0ad\ub0ae\ub0af\ub0b0\ub0b1\ub0b2\ub0b3\ub0b4\ub0b5\ub0b6\ub0b7\ub0b8\ub0b9\ub0ba\ub0bb\ub0bc\ub0bd\ub0be\ub0bf\ub0c0\ub0c1\ub0c2\ub0c3\ub0c4\ub0c5\ub0c6\ub0c7\ub0c8\ub0c9\ub0ca\ub0cb\ub0cc\ub0cd\ub0ce\ub0cf\ub0d0\ub0d1\ub0d2\ub0d3\ub0d4\ub0d5\ub0d6\ub0d7\ub0d8\ub0d9\ub0da\ub0db\ub0dc\ub0dd\ub0de\ub0df\ub0e0\ub0e1\ub0e2\ub0e3\ub0e4\ub0e5\ub0e6\ub0e7\ub0e8\ub0e9\ub0ea\ub0eb\ub0ec\ub0ed\ub0ee\ub0ef\ub0f0\ub0f1\ub0f2\ub0f3\ub0f4\ub0f5\ub0f6\ub0f7\ub0f8\ub0f9\ub0fa\ub0fb\ub0fc\ub0fd\ub0fe\ub0ff\ub100\ub101\ub102\ub103\ub104\ub105\ub106\ub107\ub108\ub109\ub10a\ub10b\ub10c\ub10d\ub10e\ub10f\ub110\ub111\ub112\ub113\ub114\ub115\ub116\ub117\ub118\ub119\ub11a\ub11b\ub11c\ub11d\ub11e\ub11f\ub120\ub121\ub122\ub123\ub124\ub125\ub126\ub127\ub128\ub129\ub12a\ub12b\ub12c\ub12d\ub12e\ub12f\ub130\ub131\ub132\ub133\ub134\ub135\ub136\ub137\ub138\ub139\ub13a\ub13b\ub13c\ub13d\ub13e\ub13f\ub140\ub141\ub142\ub143\ub144\ub145\ub146\ub147\ub148\ub149\ub14a\ub14b\ub14c\ub14d\ub14e\ub14f\ub150\ub151\ub152\ub153\ub154\ub155\ub156\ub157\ub158\ub159\ub15a\ub15b\ub15c\ub15d\ub15e\ub15f\ub160\ub161\ub162\ub163\ub164\ub165\ub166\ub167\ub168\ub169\ub16a\ub16b\ub16c\ub16d\ub16e\ub16f\ub170\ub171\ub172\ub173\ub174\ub175\ub176\ub177\ub178\ub179\ub17a\ub17b\ub17c\ub17d\ub17e\ub17f\ub180\ub181\ub182\ub183\ub184\ub185\ub186\ub187\ub188\ub189\ub18a\ub18b\ub18c\ub18d\ub18e\ub18f\ub190\ub191\ub192\ub193\ub194\ub195\ub196\ub197\ub198\ub199\ub19a\ub19b\ub19c\ub19d\ub19e\ub19f\ub1a0\ub1a1\ub1a2\ub1a3\ub1a4\ub1a5\ub1a6\ub1a7\ub1a8\ub1a9\ub1aa\ub1ab\ub1ac\ub1ad\ub1ae\ub1af\ub1b0\ub1b1\ub1b2\ub1b3\ub1b4\ub1b5\ub1b6\ub1b7\ub1b8\ub1b9\ub1ba\ub1bb\ub1bc\ub1bd\ub1be\ub1bf\ub1c0\ub1c1\ub1c2\ub1c3\ub1c4\ub1c5\ub1c6\ub1c7\ub1c8\ub1c9\ub1ca\ub1cb\ub1cc\ub1cd\ub1ce\ub1cf\ub1d0\ub1d1\ub1d2\ub1d3\ub1d4\ub1d5\ub1d6\ub1d7\ub1d8\ub1d9\ub1da\ub1db\ub1dc\ub1dd\ub1de\ub1df\ub1e0\ub1e1\ub1e2\ub1e3\ub1e4\ub1e5\ub1e6\ub1e7\ub1e8\ub1e9\ub1ea\ub1eb\ub1ec\ub1ed\ub1ee\ub1ef\ub1f0\ub1f1\ub1f2\ub1f3\ub1f4\ub1f5\ub1f6\ub1f7\ub1f8\ub1f9\ub1fa\ub1fb\ub1fc\ub1fd\ub1fe\ub1ff\ub200\ub201\ub202\ub203\ub204\ub205\ub206\ub207\ub208\ub209\ub20a\ub20b\ub20c\ub20d\ub20e\ub20f\ub210\ub211\ub212\ub213\ub214\ub215\ub216\ub217\ub218\ub219\ub21a\ub21b\ub21c\ub21d\ub21e\ub21f\ub220\ub221\ub222\ub223\ub224\ub225\ub226\ub227\ub228\ub229\ub22a\ub22b\ub22c\ub22d\ub22e\ub22f\ub230\ub231\ub232\ub233\ub234\ub235\ub236\ub237\ub238\ub239\ub23a\ub23b\ub23c\ub23d\ub23e\ub23f\ub240\ub241\ub242\ub243\ub244\ub245\ub246\ub247\ub248\ub249\ub24a\ub24b\ub24c\ub24d\ub24e\ub24f\ub250\ub251\ub252\ub253\ub254\ub255\ub256\ub257\ub258\ub259\ub25a\ub25b\ub25c\ub25d\ub25e\ub25f\ub260\ub261\ub262\ub263\ub264\ub265\ub266\ub267\ub268\ub269\ub26a\ub26b\ub26c\ub26d\ub26e\ub26f\ub270\ub271\ub272\ub273\ub274\ub275\ub276\ub277\ub278\ub279\ub27a\ub27b\ub27c\ub27d\ub27e\ub27f\ub280\ub281\ub282\ub283\ub284\ub285\ub286\ub287\ub288\ub289\ub28a\ub28b\ub28c\ub28d\ub28e\ub28f\ub290\ub291\ub292\ub293\ub294\ub295\ub296\ub297\ub298\ub299\ub29a\ub29b\ub29c\ub29d\ub29e\ub29f\ub2a0\ub2a1\ub2a2\ub2a3\ub2a4\ub2a5\ub2a6\ub2a7\ub2a8\ub2a9\ub2aa\ub2ab\ub2ac\ub2ad\ub2ae\ub2af\ub2b0\ub2b1\ub2b2\ub2b3\ub2b4\ub2b5\ub2b6\ub2b7\ub2b8\ub2b9\ub2ba\ub2bb\ub2bc\ub2bd\ub2be\ub2bf\ub2c0\ub2c1\ub2c2\ub2c3\ub2c4\ub2c5\ub2c6\ub2c7\ub2c8\ub2c9\ub2ca\ub2cb\ub2cc\ub2cd\ub2ce\ub2cf\ub2d0\ub2d1\ub2d2\ub2d3\ub2d4\ub2d5\ub2d6\ub2d7\ub2d8\ub2d9\ub2da\ub2db\ub2dc\ub2dd\ub2de\ub2df\ub2e0\ub2e1\ub2e2\ub2e3\ub2e4\ub2e5\ub2e6\ub2e7\ub2e8\ub2e9\ub2ea\ub2eb\ub2ec\ub2ed\ub2ee\ub2ef\ub2f0\ub2f1\ub2f2\ub2f3\ub2f4\ub2f5\ub2f6\ub2f7\ub2f8\ub2f9\ub2fa\ub2fb\ub2fc\ub2fd\ub2fe\ub2ff\ub300\ub301\ub302\ub303\ub304\ub305\ub306\ub307\ub308\ub309\ub30a\ub30b\ub30c\ub30d\ub30e\ub30f\ub310\ub311\ub312\ub313\ub314\ub315\ub316\ub317\ub318\ub319\ub31a\ub31b\ub31c\ub31d\ub31e\ub31f\ub320\ub321\ub322\ub323\ub324\ub325\ub326\ub327\ub328\ub329\ub32a\ub32b\ub32c\ub32d\ub32e\ub32f\ub330\ub331\ub332\ub333\ub334\ub335\ub336\ub337\ub338\ub339\ub33a\ub33b\ub33c\ub33d\ub33e\ub33f\ub340\ub341\ub342\ub343\ub344\ub345\ub346\ub347\ub348\ub349\ub34a\ub34b\ub34c\ub34d\ub34e\ub34f\ub350\ub351\ub352\ub353\ub354\ub355\ub356\ub357\ub358\ub359\ub35a\ub35b\ub35c\ub35d\ub35e\ub35f\ub360\ub361\ub362\ub363\ub364\ub365\ub366\ub367\ub368\ub369\ub36a\ub36b\ub36c\ub36d\ub36e\ub36f\ub370\ub371\ub372\ub373\ub374\ub375\ub376\ub377\ub378\ub379\ub37a\ub37b\ub37c\ub37d\ub37e\ub37f\ub380\ub381\ub382\ub383\ub384\ub385\ub386\ub387\ub388\ub389\ub38a\ub38b\ub38c\ub38d\ub38e\ub38f\ub390\ub391\ub392\ub393\ub394\ub395\ub396\ub397\ub398\ub399\ub39a\ub39b\ub39c\ub39d\ub39e\ub39f\ub3a0\ub3a1\ub3a2\ub3a3\ub3a4\ub3a5\ub3a6\ub3a7\ub3a8\ub3a9\ub3aa\ub3ab\ub3ac\ub3ad\ub3ae\ub3af\ub3b0\ub3b1\ub3b2\ub3b3\ub3b4\ub3b5\ub3b6\ub3b7\ub3b8\ub3b9\ub3ba\ub3bb\ub3bc\ub3bd\ub3be\ub3bf\ub3c0\ub3c1\ub3c2\ub3c3\ub3c4\ub3c5\ub3c6\ub3c7\ub3c8\ub3c9\ub3ca\ub3cb\ub3cc\ub3cd\ub3ce\ub3cf\ub3d0\ub3d1\ub3d2\ub3d3\ub3d4\ub3d5\ub3d6\ub3d7\ub3d8\ub3d9\ub3da\ub3db\ub3dc\ub3dd\ub3de\ub3df\ub3e0\ub3e1\ub3e2\ub3e3\ub3e4\ub3e5\ub3e6\ub3e7\ub3e8\ub3e9\ub3ea\ub3eb\ub3ec\ub3ed\ub3ee\ub3ef\ub3f0\ub3f1\ub3f2\ub3f3\ub3f4\ub3f5\ub3f6\ub3f7\ub3f8\ub3f9\ub3fa\ub3fb\ub3fc\ub3fd\ub3fe\ub3ff\ub400\ub401\ub402\ub403\ub404\ub405\ub406\ub407\ub408\ub409\ub40a\ub40b\ub40c\ub40d\ub40e\ub40f\ub410\ub411\ub412\ub413\ub414\ub415\ub416\ub417\ub418\ub419\ub41a\ub41b\ub41c\ub41d\ub41e\ub41f\ub420\ub421\ub422\ub423\ub424\ub425\ub426\ub427\ub428\ub429\ub42a\ub42b\ub42c\ub42d\ub42e\ub42f\ub430\ub431\ub432\ub433\ub434\ub435\ub436\ub437\ub438\ub439\ub43a\ub43b\ub43c\ub43d\ub43e\ub43f\ub440\ub441\ub442\ub443\ub444\ub445\ub446\ub447\ub448\ub449\ub44a\ub44b\ub44c\ub44d\ub44e\ub44f\ub450\ub451\ub452\ub453\ub454\ub455\ub456\ub457\ub458\ub459\ub45a\ub45b\ub45c\ub45d\ub45e\ub45f\ub460\ub461\ub462\ub463\ub464\ub465\ub466\ub467\ub468\ub469\ub46a\ub46b\ub46c\ub46d\ub46e\ub46f\ub470\ub471\ub472\ub473\ub474\ub475\ub476\ub477\ub478\ub479\ub47a\ub47b\ub47c\ub47d\ub47e\ub47f\ub480\ub481\ub482\ub483\ub484\ub485\ub486\ub487\ub488\ub489\ub48a\ub48b\ub48c\ub48d\ub48e\ub48f\ub490\ub491\ub492\ub493\ub494\ub495\ub496\ub497\ub498\ub499\ub49a\ub49b\ub49c\ub49d\ub49e\ub49f\ub4a0\ub4a1\ub4a2\ub4a3\ub4a4\ub4a5\ub4a6\ub4a7\ub4a8\ub4a9\ub4aa\ub4ab\ub4ac\ub4ad\ub4ae\ub4af\ub4b0\ub4b1\ub4b2\ub4b3\ub4b4\ub4b5\ub4b6\ub4b7\ub4b8\ub4b9\ub4ba\ub4bb\ub4bc\ub4bd\ub4be\ub4bf\ub4c0\ub4c1\ub4c2\ub4c3\ub4c4\ub4c5\ub4c6\ub4c7\ub4c8\ub4c9\ub4ca\ub4cb\ub4cc\ub4cd\ub4ce\ub4cf\ub4d0\ub4d1\ub4d2\ub4d3\ub4d4\ub4d5\ub4d6\ub4d7\ub4d8\ub4d9\ub4da\ub4db\ub4dc\ub4dd\ub4de\ub4df\ub4e0\ub4e1\ub4e2\ub4e3\ub4e4\ub4e5\ub4e6\ub4e7\ub4e8\ub4e9\ub4ea\ub4eb\ub4ec\ub4ed\ub4ee\ub4ef\ub4f0\ub4f1\ub4f2\ub4f3\ub4f4\ub4f5\ub4f6\ub4f7\ub4f8\ub4f9\ub4fa\ub4fb\ub4fc\ub4fd\ub4fe\ub4ff\ub500\ub501\ub502\ub503\ub504\ub505\ub506\ub507\ub508\ub509\ub50a\ub50b\ub50c\ub50d\ub50e\ub50f\ub510\ub511\ub512\ub513\ub514\ub515\ub516\ub517\ub518\ub519\ub51a\ub51b\ub51c\ub51d\ub51e\ub51f\ub520\ub521\ub522\ub523\ub524\ub525\ub526\ub527\ub528\ub529\ub52a\ub52b\ub52c\ub52d\ub52e\ub52f\ub530\ub531\ub532\ub533\ub534\ub535\ub536\ub537\ub538\ub539\ub53a\ub53b\ub53c\ub53d\ub53e\ub53f\ub540\ub541\ub542\ub543\ub544\ub545\ub546\ub547\ub548\ub549\ub54a\ub54b\ub54c\ub54d\ub54e\ub54f\ub550\ub551\ub552\ub553\ub554\ub555\ub556\ub557\ub558\ub559\ub55a\ub55b\ub55c\ub55d\ub55e\ub55f\ub560\ub561\ub562\ub563\ub564\ub565\ub566\ub567\ub568\ub569\ub56a\ub56b\ub56c\ub56d\ub56e\ub56f\ub570\ub571\ub572\ub573\ub574\ub575\ub576\ub577\ub578\ub579\ub57a\ub57b\ub57c\ub57d\ub57e\ub57f\ub580\ub581\ub582\ub583\ub584\ub585\ub586\ub587\ub588\ub589\ub58a\ub58b\ub58c\ub58d\ub58e\ub58f\ub590\ub591\ub592\ub593\ub594\ub595\ub596\ub597\ub598\ub599\ub59a\ub59b\ub59c\ub59d\ub59e\ub59f\ub5a0\ub5a1\ub5a2\ub5a3\ub5a4\ub5a5\ub5a6\ub5a7\ub5a8\ub5a9\ub5aa\ub5ab\ub5ac\ub5ad\ub5ae\ub5af\ub5b0\ub5b1\ub5b2\ub5b3\ub5b4\ub5b5\ub5b6\ub5b7\ub5b8\ub5b9\ub5ba\ub5bb\ub5bc\ub5bd\ub5be\ub5bf\ub5c0\ub5c1\ub5c2\ub5c3\ub5c4\ub5c5\ub5c6\ub5c7\ub5c8\ub5c9\ub5ca\ub5cb\ub5cc\ub5cd\ub5ce\ub5cf\ub5d0\ub5d1\ub5d2\ub5d3\ub5d4\ub5d5\ub5d6\ub5d7\ub5d8\ub5d9\ub5da\ub5db\ub5dc\ub5dd\ub5de\ub5df\ub5e0\ub5e1\ub5e2\ub5e3\ub5e4\ub5e5\ub5e6\ub5e7\ub5e8\ub5e9\ub5ea\ub5eb\ub5ec\ub5ed\ub5ee\ub5ef\ub5f0\ub5f1\ub5f2\ub5f3\ub5f4\ub5f5\ub5f6\ub5f7\ub5f8\ub5f9\ub5fa\ub5fb\ub5fc\ub5fd\ub5fe\ub5ff\ub600\ub601\ub602\ub603\ub604\ub605\ub606\ub607\ub608\ub609\ub60a\ub60b\ub60c\ub60d\ub60e\ub60f\ub610\ub611\ub612\ub613\ub614\ub615\ub616\ub617\ub618\ub619\ub61a\ub61b\ub61c\ub61d\ub61e\ub61f\ub620\ub621\ub622\ub623\ub624\ub625\ub626\ub627\ub628\ub629\ub62a\ub62b\ub62c\ub62d\ub62e\ub62f\ub630\ub631\ub632\ub633\ub634\ub635\ub636\ub637\ub638\ub639\ub63a\ub63b\ub63c\ub63d\ub63e\ub63f\ub640\ub641\ub642\ub643\ub644\ub645\ub646\ub647\ub648\ub649\ub64a\ub64b\ub64c\ub64d\ub64e\ub64f\ub650\ub651\ub652\ub653\ub654\ub655\ub656\ub657\ub658\ub659\ub65a\ub65b\ub65c\ub65d\ub65e\ub65f\ub660\ub661\ub662\ub663\ub664\ub665\ub666\ub667\ub668\ub669\ub66a\ub66b\ub66c\ub66d\ub66e\ub66f\ub670\ub671\ub672\ub673\ub674\ub675\ub676\ub677\ub678\ub679\ub67a\ub67b\ub67c\ub67d\ub67e\ub67f\ub680\ub681\ub682\ub683\ub684\ub685\ub686\ub687\ub688\ub689\ub68a\ub68b\ub68c\ub68d\ub68e\ub68f\ub690\ub691\ub692\ub693\ub694\ub695\ub696\ub697\ub698\ub699\ub69a\ub69b\ub69c\ub69d\ub69e\ub69f\ub6a0\ub6a1\ub6a2\ub6a3\ub6a4\ub6a5\ub6a6\ub6a7\ub6a8\ub6a9\ub6aa\ub6ab\ub6ac\ub6ad\ub6ae\ub6af\ub6b0\ub6b1\ub6b2\ub6b3\ub6b4\ub6b5\ub6b6\ub6b7\ub6b8\ub6b9\ub6ba\ub6bb\ub6bc\ub6bd\ub6be\ub6bf\ub6c0\ub6c1\ub6c2\ub6c3\ub6c4\ub6c5\ub6c6\ub6c7\ub6c8\ub6c9\ub6ca\ub6cb\ub6cc\ub6cd\ub6ce\ub6cf\ub6d0\ub6d1\ub6d2\ub6d3\ub6d4\ub6d5\ub6d6\ub6d7\ub6d8\ub6d9\ub6da\ub6db\ub6dc\ub6dd\ub6de\ub6df\ub6e0\ub6e1\ub6e2\ub6e3\ub6e4\ub6e5\ub6e6\ub6e7\ub6e8\ub6e9\ub6ea\ub6eb\ub6ec\ub6ed\ub6ee\ub6ef\ub6f0\ub6f1\ub6f2\ub6f3\ub6f4\ub6f5\ub6f6\ub6f7\ub6f8\ub6f9\ub6fa\ub6fb\ub6fc\ub6fd\ub6fe\ub6ff\ub700\ub701\ub702\ub703\ub704\ub705\ub706\ub707\ub708\ub709\ub70a\ub70b\ub70c\ub70d\ub70e\ub70f\ub710\ub711\ub712\ub713\ub714\ub715\ub716\ub717\ub718\ub719\ub71a\ub71b\ub71c\ub71d\ub71e\ub71f\ub720\ub721\ub722\ub723\ub724\ub725\ub726\ub727\ub728\ub729\ub72a\ub72b\ub72c\ub72d\ub72e\ub72f\ub730\ub731\ub732\ub733\ub734\ub735\ub736\ub737\ub738\ub739\ub73a\ub73b\ub73c\ub73d\ub73e\ub73f\ub740\ub741\ub742\ub743\ub744\ub745\ub746\ub747\ub748\ub749\ub74a\ub74b\ub74c\ub74d\ub74e\ub74f\ub750\ub751\ub752\ub753\ub754\ub755\ub756\ub757\ub758\ub759\ub75a\ub75b\ub75c\ub75d\ub75e\ub75f\ub760\ub761\ub762\ub763\ub764\ub765\ub766\ub767\ub768\ub769\ub76a\ub76b\ub76c\ub76d\ub76e\ub76f\ub770\ub771\ub772\ub773\ub774\ub775\ub776\ub777\ub778\ub779\ub77a\ub77b\ub77c\ub77d\ub77e\ub77f\ub780\ub781\ub782\ub783\ub784\ub785\ub786\ub787\ub788\ub789\ub78a\ub78b\ub78c\ub78d\ub78e\ub78f\ub790\ub791\ub792\ub793\ub794\ub795\ub796\ub797\ub798\ub799\ub79a\ub79b\ub79c\ub79d\ub79e\ub79f\ub7a0\ub7a1\ub7a2\ub7a3\ub7a4\ub7a5\ub7a6\ub7a7\ub7a8\ub7a9\ub7aa\ub7ab\ub7ac\ub7ad\ub7ae\ub7af\ub7b0\ub7b1\ub7b2\ub7b3\ub7b4\ub7b5\ub7b6\ub7b7\ub7b8\ub7b9\ub7ba\ub7bb\ub7bc\ub7bd\ub7be\ub7bf\ub7c0\ub7c1\ub7c2\ub7c3\ub7c4\ub7c5\ub7c6\ub7c7\ub7c8\ub7c9\ub7ca\ub7cb\ub7cc\ub7cd\ub7ce\ub7cf\ub7d0\ub7d1\ub7d2\ub7d3\ub7d4\ub7d5\ub7d6\ub7d7\ub7d8\ub7d9\ub7da\ub7db\ub7dc\ub7dd\ub7de\ub7df\ub7e0\ub7e1\ub7e2\ub7e3\ub7e4\ub7e5\ub7e6\ub7e7\ub7e8\ub7e9\ub7ea\ub7eb\ub7ec\ub7ed\ub7ee\ub7ef\ub7f0\ub7f1\ub7f2\ub7f3\ub7f4\ub7f5\ub7f6\ub7f7\ub7f8\ub7f9\ub7fa\ub7fb\ub7fc\ub7fd\ub7fe\ub7ff\ub800\ub801\ub802\ub803\ub804\ub805\ub806\ub807\ub808\ub809\ub80a\ub80b\ub80c\ub80d\ub80e\ub80f\ub810\ub811\ub812\ub813\ub814\ub815\ub816\ub817\ub818\ub819\ub81a\ub81b\ub81c\ub81d\ub81e\ub81f\ub820\ub821\ub822\ub823\ub824\ub825\ub826\ub827\ub828\ub829\ub82a\ub82b\ub82c\ub82d\ub82e\ub82f\ub830\ub831\ub832\ub833\ub834\ub835\ub836\ub837\ub838\ub839\ub83a\ub83b\ub83c\ub83d\ub83e\ub83f\ub840\ub841\ub842\ub843\ub844\ub845\ub846\ub847\ub848\ub849\ub84a\ub84b\ub84c\ub84d\ub84e\ub84f\ub850\ub851\ub852\ub853\ub854\ub855\ub856\ub857\ub858\ub859\ub85a\ub85b\ub85c\ub85d\ub85e\ub85f\ub860\ub861\ub862\ub863\ub864\ub865\ub866\ub867\ub868\ub869\ub86a\ub86b\ub86c\ub86d\ub86e\ub86f\ub870\ub871\ub872\ub873\ub874\ub875\ub876\ub877\ub878\ub879\ub87a\ub87b\ub87c\ub87d\ub87e\ub87f\ub880\ub881\ub882\ub883\ub884\ub885\ub886\ub887\ub888\ub889\ub88a\ub88b\ub88c\ub88d\ub88e\ub88f\ub890\ub891\ub892\ub893\ub894\ub895\ub896\ub897\ub898\ub899\ub89a\ub89b\ub89c\ub89d\ub89e\ub89f\ub8a0\ub8a1\ub8a2\ub8a3\ub8a4\ub8a5\ub8a6\ub8a7\ub8a8\ub8a9\ub8aa\ub8ab\ub8ac\ub8ad\ub8ae\ub8af\ub8b0\ub8b1\ub8b2\ub8b3\ub8b4\ub8b5\ub8b6\ub8b7\ub8b8\ub8b9\ub8ba\ub8bb\ub8bc\ub8bd\ub8be\ub8bf\ub8c0\ub8c1\ub8c2\ub8c3\ub8c4\ub8c5\ub8c6\ub8c7\ub8c8\ub8c9\ub8ca\ub8cb\ub8cc\ub8cd\ub8ce\ub8cf\ub8d0\ub8d1\ub8d2\ub8d3\ub8d4\ub8d5\ub8d6\ub8d7\ub8d8\ub8d9\ub8da\ub8db\ub8dc\ub8dd\ub8de\ub8df\ub8e0\ub8e1\ub8e2\ub8e3\ub8e4\ub8e5\ub8e6\ub8e7\ub8e8\ub8e9\ub8ea\ub8eb\ub8ec\ub8ed\ub8ee\ub8ef\ub8f0\ub8f1\ub8f2\ub8f3\ub8f4\ub8f5\ub8f6\ub8f7\ub8f8\ub8f9\ub8fa\ub8fb\ub8fc\ub8fd\ub8fe\ub8ff\ub900\ub901\ub902\ub903\ub904\ub905\ub906\ub907\ub908\ub909\ub90a\ub90b\ub90c\ub90d\ub90e\ub90f\ub910\ub911\ub912\ub913\ub914\ub915\ub916\ub917\ub918\ub919\ub91a\ub91b\ub91c\ub91d\ub91e\ub91f\ub920\ub921\ub922\ub923\ub924\ub925\ub926\ub927\ub928\ub929\ub92a\ub92b\ub92c\ub92d\ub92e\ub92f\ub930\ub931\ub932\ub933\ub934\ub935\ub936\ub937\ub938\ub939\ub93a\ub93b\ub93c\ub93d\ub93e\ub93f\ub940\ub941\ub942\ub943\ub944\ub945\ub946\ub947\ub948\ub949\ub94a\ub94b\ub94c\ub94d\ub94e\ub94f\ub950\ub951\ub952\ub953\ub954\ub955\ub956\ub957\ub958\ub959\ub95a\ub95b\ub95c\ub95d\ub95e\ub95f\ub960\ub961\ub962\ub963\ub964\ub965\ub966\ub967\ub968\ub969\ub96a\ub96b\ub96c\ub96d\ub96e\ub96f\ub970\ub971\ub972\ub973\ub974\ub975\ub976\ub977\ub978\ub979\ub97a\ub97b\ub97c\ub97d\ub97e\ub97f\ub980\ub981\ub982\ub983\ub984\ub985\ub986\ub987\ub988\ub989\ub98a\ub98b\ub98c\ub98d\ub98e\ub98f\ub990\ub991\ub992\ub993\ub994\ub995\ub996\ub997\ub998\ub999\ub99a\ub99b\ub99c\ub99d\ub99e\ub99f\ub9a0\ub9a1\ub9a2\ub9a3\ub9a4\ub9a5\ub9a6\ub9a7\ub9a8\ub9a9\ub9aa\ub9ab\ub9ac\ub9ad\ub9ae\ub9af\ub9b0\ub9b1\ub9b2\ub9b3\ub9b4\ub9b5\ub9b6\ub9b7\ub9b8\ub9b9\ub9ba\ub9bb\ub9bc\ub9bd\ub9be\ub9bf\ub9c0\ub9c1\ub9c2\ub9c3\ub9c4\ub9c5\ub9c6\ub9c7\ub9c8\ub9c9\ub9ca\ub9cb\ub9cc\ub9cd\ub9ce\ub9cf\ub9d0\ub9d1\ub9d2\ub9d3\ub9d4\ub9d5\ub9d6\ub9d7\ub9d8\ub9d9\ub9da\ub9db\ub9dc\ub9dd\ub9de\ub9df\ub9e0\ub9e1\ub9e2\ub9e3\ub9e4\ub9e5\ub9e6\ub9e7\ub9e8\ub9e9\ub9ea\ub9eb\ub9ec\ub9ed\ub9ee\ub9ef\ub9f0\ub9f1\ub9f2\ub9f3\ub9f4\ub9f5\ub9f6\ub9f7\ub9f8\ub9f9\ub9fa\ub9fb\ub9fc\ub9fd\ub9fe\ub9ff\uba00\uba01\uba02\uba03\uba04\uba05\uba06\uba07\uba08\uba09\uba0a\uba0b\uba0c\uba0d\uba0e\uba0f\uba10\uba11\uba12\uba13\uba14\uba15\uba16\uba17\uba18\uba19\uba1a\uba1b\uba1c\uba1d\uba1e\uba1f\uba20\uba21\uba22\uba23\uba24\uba25\uba26\uba27\uba28\uba29\uba2a\uba2b\uba2c\uba2d\uba2e\uba2f\uba30\uba31\uba32\uba33\uba34\uba35\uba36\uba37\uba38\uba39\uba3a\uba3b\uba3c\uba3d\uba3e\uba3f\uba40\uba41\uba42\uba43\uba44\uba45\uba46\uba47\uba48\uba49\uba4a\uba4b\uba4c\uba4d\uba4e\uba4f\uba50\uba51\uba52\uba53\uba54\uba55\uba56\uba57\uba58\uba59\uba5a\uba5b\uba5c\uba5d\uba5e\uba5f\uba60\uba61\uba62\uba63\uba64\uba65\uba66\uba67\uba68\uba69\uba6a\uba6b\uba6c\uba6d\uba6e\uba6f\uba70\uba71\uba72\uba73\uba74\uba75\uba76\uba77\uba78\uba79\uba7a\uba7b\uba7c\uba7d\uba7e\uba7f\uba80\uba81\uba82\uba83\uba84\uba85\uba86\uba87\uba88\uba89\uba8a\uba8b\uba8c\uba8d\uba8e\uba8f\uba90\uba91\uba92\uba93\uba94\uba95\uba96\uba97\uba98\uba99\uba9a\uba9b\uba9c\uba9d\uba9e\uba9f\ubaa0\ubaa1\ubaa2\ubaa3\ubaa4\ubaa5\ubaa6\ubaa7\ubaa8\ubaa9\ubaaa\ubaab\ubaac\ubaad\ubaae\ubaaf\ubab0\ubab1\ubab2\ubab3\ubab4\ubab5\ubab6\ubab7\ubab8\ubab9\ubaba\ubabb\ubabc\ubabd\ubabe\ubabf\ubac0\ubac1\ubac2\ubac3\ubac4\ubac5\ubac6\ubac7\ubac8\ubac9\ubaca\ubacb\ubacc\ubacd\ubace\ubacf\ubad0\ubad1\ubad2\ubad3\ubad4\ubad5\ubad6\ubad7\ubad8\ubad9\ubada\ubadb\ubadc\ubadd\ubade\ubadf\ubae0\ubae1\ubae2\ubae3\ubae4\ubae5\ubae6\ubae7\ubae8\ubae9\ubaea\ubaeb\ubaec\ubaed\ubaee\ubaef\ubaf0\ubaf1\ubaf2\ubaf3\ubaf4\ubaf5\ubaf6\ubaf7\ubaf8\ubaf9\ubafa\ubafb\ubafc\ubafd\ubafe\ubaff\ubb00\ubb01\ubb02\ubb03\ubb04\ubb05\ubb06\ubb07\ubb08\ubb09\ubb0a\ubb0b\ubb0c\ubb0d\ubb0e\ubb0f\ubb10\ubb11\ubb12\ubb13\ubb14\ubb15\ubb16\ubb17\ubb18\ubb19\ubb1a\ubb1b\ubb1c\ubb1d\ubb1e\ubb1f\ubb20\ubb21\ubb22\ubb23\ubb24\ubb25\ubb26\ubb27\ubb28\ubb29\ubb2a\ubb2b\ubb2c\ubb2d\ubb2e\ubb2f\ubb30\ubb31\ubb32\ubb33\ubb34\ubb35\ubb36\ubb37\ubb38\ubb39\ubb3a\ubb3b\ubb3c\ubb3d\ubb3e\ubb3f\ubb40\ubb41\ubb42\ubb43\ubb44\ubb45\ubb46\ubb47\ubb48\ubb49\ubb4a\ubb4b\ubb4c\ubb4d\ubb4e\ubb4f\ubb50\ubb51\ubb52\ubb53\ubb54\ubb55\ubb56\ubb57\ubb58\ubb59\ubb5a\ubb5b\ubb5c\ubb5d\ubb5e\ubb5f\ubb60\ubb61\ubb62\ubb63\ubb64\ubb65\ubb66\ubb67\ubb68\ubb69\ubb6a\ubb6b\ubb6c\ubb6d\ubb6e\ubb6f\ubb70\ubb71\ubb72\ubb73\ubb74\ubb75\ubb76\ubb77\ubb78\ubb79\ubb7a\ubb7b\ubb7c\ubb7d\ubb7e\ubb7f\ubb80\ubb81\ubb82\ubb83\ubb84\ubb85\ubb86\ubb87\ubb88\ubb89\ubb8a\ubb8b\ubb8c\ubb8d\ubb8e\ubb8f\ubb90\ubb91\ubb92\ubb93\ubb94\ubb95\ubb96\ubb97\ubb98\ubb99\ubb9a\ubb9b\ubb9c\ubb9d\ubb9e\ubb9f\ubba0\ubba1\ubba2\ubba3\ubba4\ubba5\ubba6\ubba7\ubba8\ubba9\ubbaa\ubbab\ubbac\ubbad\ubbae\ubbaf\ubbb0\ubbb1\ubbb2\ubbb3\ubbb4\ubbb5\ubbb6\ubbb7\ubbb8\ubbb9\ubbba\ubbbb\ubbbc\ubbbd\ubbbe\ubbbf\ubbc0\ubbc1\ubbc2\ubbc3\ubbc4\ubbc5\ubbc6\ubbc7\ubbc8\ubbc9\ubbca\ubbcb\ubbcc\ubbcd\ubbce\ubbcf\ubbd0\ubbd1\ubbd2\ubbd3\ubbd4\ubbd5\ubbd6\ubbd7\ubbd8\ubbd9\ubbda\ubbdb\ubbdc\ubbdd\ubbde\ubbdf\ubbe0\ubbe1\ubbe2\ubbe3\ubbe4\ubbe5\ubbe6\ubbe7\ubbe8\ubbe9\ubbea\ubbeb\ubbec\ubbed\ubbee\ubbef\ubbf0\ubbf1\ubbf2\ubbf3\ubbf4\ubbf5\ubbf6\ubbf7\ubbf8\ubbf9\ubbfa\ubbfb\ubbfc\ubbfd\ubbfe\ubbff\ubc00\ubc01\ubc02\ubc03\ubc04\ubc05\ubc06\ubc07\ubc08\ubc09\ubc0a\ubc0b\ubc0c\ubc0d\ubc0e\ubc0f\ubc10\ubc11\ubc12\ubc13\ubc14\ubc15\ubc16\ubc17\ubc18\ubc19\ubc1a\ubc1b\ubc1c\ubc1d\ubc1e\ubc1f\ubc20\ubc21\ubc22\ubc23\ubc24\ubc25\ubc26\ubc27\ubc28\ubc29\ubc2a\ubc2b\ubc2c\ubc2d\ubc2e\ubc2f\ubc30\ubc31\ubc32\ubc33\ubc34\ubc35\ubc36\ubc37\ubc38\ubc39\ubc3a\ubc3b\ubc3c\ubc3d\ubc3e\ubc3f\ubc40\ubc41\ubc42\ubc43\ubc44\ubc45\ubc46\ubc47\ubc48\ubc49\ubc4a\ubc4b\ubc4c\ubc4d\ubc4e\ubc4f\ubc50\ubc51\ubc52\ubc53\ubc54\ubc55\ubc56\ubc57\ubc58\ubc59\ubc5a\ubc5b\ubc5c\ubc5d\ubc5e\ubc5f\ubc60\ubc61\ubc62\ubc63\ubc64\ubc65\ubc66\ubc67\ubc68\ubc69\ubc6a\ubc6b\ubc6c\ubc6d\ubc6e\ubc6f\ubc70\ubc71\ubc72\ubc73\ubc74\ubc75\ubc76\ubc77\ubc78\ubc79\ubc7a\ubc7b\ubc7c\ubc7d\ubc7e\ubc7f\ubc80\ubc81\ubc82\ubc83\ubc84\ubc85\ubc86\ubc87\ubc88\ubc89\ubc8a\ubc8b\ubc8c\ubc8d\ubc8e\ubc8f\ubc90\ubc91\ubc92\ubc93\ubc94\ubc95\ubc96\ubc97\ubc98\ubc99\ubc9a\ubc9b\ubc9c\ubc9d\ubc9e\ubc9f\ubca0\ubca1\ubca2\ubca3\ubca4\ubca5\ubca6\ubca7\ubca8\ubca9\ubcaa\ubcab\ubcac\ubcad\ubcae\ubcaf\ubcb0\ubcb1\ubcb2\ubcb3\ubcb4\ubcb5\ubcb6\ubcb7\ubcb8\ubcb9\ubcba\ubcbb\ubcbc\ubcbd\ubcbe\ubcbf\ubcc0\ubcc1\ubcc2\ubcc3\ubcc4\ubcc5\ubcc6\ubcc7\ubcc8\ubcc9\ubcca\ubccb\ubccc\ubccd\ubcce\ubccf\ubcd0\ubcd1\ubcd2\ubcd3\ubcd4\ubcd5\ubcd6\ubcd7\ubcd8\ubcd9\ubcda\ubcdb\ubcdc\ubcdd\ubcde\ubcdf\ubce0\ubce1\ubce2\ubce3\ubce4\ubce5\ubce6\ubce7\ubce8\ubce9\ubcea\ubceb\ubcec\ubced\ubcee\ubcef\ubcf0\ubcf1\ubcf2\ubcf3\ubcf4\ubcf5\ubcf6\ubcf7\ubcf8\ubcf9\ubcfa\ubcfb\ubcfc\ubcfd\ubcfe\ubcff\ubd00\ubd01\ubd02\ubd03\ubd04\ubd05\ubd06\ubd07\ubd08\ubd09\ubd0a\ubd0b\ubd0c\ubd0d\ubd0e\ubd0f\ubd10\ubd11\ubd12\ubd13\ubd14\ubd15\ubd16\ubd17\ubd18\ubd19\ubd1a\ubd1b\ubd1c\ubd1d\ubd1e\ubd1f\ubd20\ubd21\ubd22\ubd23\ubd24\ubd25\ubd26\ubd27\ubd28\ubd29\ubd2a\ubd2b\ubd2c\ubd2d\ubd2e\ubd2f\ubd30\ubd31\ubd32\ubd33\ubd34\ubd35\ubd36\ubd37\ubd38\ubd39\ubd3a\ubd3b\ubd3c\ubd3d\ubd3e\ubd3f\ubd40\ubd41\ubd42\ubd43\ubd44\ubd45\ubd46\ubd47\ubd48\ubd49\ubd4a\ubd4b\ubd4c\ubd4d\ubd4e\ubd4f\ubd50\ubd51\ubd52\ubd53\ubd54\ubd55\ubd56\ubd57\ubd58\ubd59\ubd5a\ubd5b\ubd5c\ubd5d\ubd5e\ubd5f\ubd60\ubd61\ubd62\ubd63\ubd64\ubd65\ubd66\ubd67\ubd68\ubd69\ubd6a\ubd6b\ubd6c\ubd6d\ubd6e\ubd6f\ubd70\ubd71\ubd72\ubd73\ubd74\ubd75\ubd76\ubd77\ubd78\ubd79\ubd7a\ubd7b\ubd7c\ubd7d\ubd7e\ubd7f\ubd80\ubd81\ubd82\ubd83\ubd84\ubd85\ubd86\ubd87\ubd88\ubd89\ubd8a\ubd8b\ubd8c\ubd8d\ubd8e\ubd8f\ubd90\ubd91\ubd92\ubd93\ubd94\ubd95\ubd96\ubd97\ubd98\ubd99\ubd9a\ubd9b\ubd9c\ubd9d\ubd9e\ubd9f\ubda0\ubda1\ubda2\ubda3\ubda4\ubda5\ubda6\ubda7\ubda8\ubda9\ubdaa\ubdab\ubdac\ubdad\ubdae\ubdaf\ubdb0\ubdb1\ubdb2\ubdb3\ubdb4\ubdb5\ubdb6\ubdb7\ubdb8\ubdb9\ubdba\ubdbb\ubdbc\ubdbd\ubdbe\ubdbf\ubdc0\ubdc1\ubdc2\ubdc3\ubdc4\ubdc5\ubdc6\ubdc7\ubdc8\ubdc9\ubdca\ubdcb\ubdcc\ubdcd\ubdce\ubdcf\ubdd0\ubdd1\ubdd2\ubdd3\ubdd4\ubdd5\ubdd6\ubdd7\ubdd8\ubdd9\ubdda\ubddb\ubddc\ubddd\ubdde\ubddf\ubde0\ubde1\ubde2\ubde3\ubde4\ubde5\ubde6\ubde7\ubde8\ubde9\ubdea\ubdeb\ubdec\ubded\ubdee\ubdef\ubdf0\ubdf1\ubdf2\ubdf3\ubdf4\ubdf5\ubdf6\ubdf7\ubdf8\ubdf9\ubdfa\ubdfb\ubdfc\ubdfd\ubdfe\ubdff\ube00\ube01\ube02\ube03\ube04\ube05\ube06\ube07\ube08\ube09\ube0a\ube0b\ube0c\ube0d\ube0e\ube0f\ube10\ube11\ube12\ube13\ube14\ube15\ube16\ube17\ube18\ube19\ube1a\ube1b\ube1c\ube1d\ube1e\ube1f\ube20\ube21\ube22\ube23\ube24\ube25\ube26\ube27\ube28\ube29\ube2a\ube2b\ube2c\ube2d\ube2e\ube2f\ube30\ube31\ube32\ube33\ube34\ube35\ube36\ube37\ube38\ube39\ube3a\ube3b\ube3c\ube3d\ube3e\ube3f\ube40\ube41\ube42\ube43\ube44\ube45\ube46\ube47\ube48\ube49\ube4a\ube4b\ube4c\ube4d\ube4e\ube4f\ube50\ube51\ube52\ube53\ube54\ube55\ube56\ube57\ube58\ube59\ube5a\ube5b\ube5c\ube5d\ube5e\ube5f\ube60\ube61\ube62\ube63\ube64\ube65\ube66\ube67\ube68\ube69\ube6a\ube6b\ube6c\ube6d\ube6e\ube6f\ube70\ube71\ube72\ube73\ube74\ube75\ube76\ube77\ube78\ube79\ube7a\ube7b\ube7c\ube7d\ube7e\ube7f\ube80\ube81\ube82\ube83\ube84\ube85\ube86\ube87\ube88\ube89\ube8a\ube8b\ube8c\ube8d\ube8e\ube8f\ube90\ube91\ube92\ube93\ube94\ube95\ube96\ube97\ube98\ube99\ube9a\ube9b\ube9c\ube9d\ube9e\ube9f\ubea0\ubea1\ubea2\ubea3\ubea4\ubea5\ubea6\ubea7\ubea8\ubea9\ubeaa\ubeab\ubeac\ubead\ubeae\ubeaf\ubeb0\ubeb1\ubeb2\ubeb3\ubeb4\ubeb5\ubeb6\ubeb7\ubeb8\ubeb9\ubeba\ubebb\ubebc\ubebd\ubebe\ubebf\ubec0\ubec1\ubec2\ubec3\ubec4\ubec5\ubec6\ubec7\ubec8\ubec9\ubeca\ubecb\ubecc\ubecd\ubece\ubecf\ubed0\ubed1\ubed2\ubed3\ubed4\ubed5\ubed6\ubed7\ubed8\ubed9\ubeda\ubedb\ubedc\ubedd\ubede\ubedf\ubee0\ubee1\ubee2\ubee3\ubee4\ubee5\ubee6\ubee7\ubee8\ubee9\ubeea\ubeeb\ubeec\ubeed\ubeee\ubeef\ubef0\ubef1\ubef2\ubef3\ubef4\ubef5\ubef6\ubef7\ubef8\ubef9\ubefa\ubefb\ubefc\ubefd\ubefe\ubeff\ubf00\ubf01\ubf02\ubf03\ubf04\ubf05\ubf06\ubf07\ubf08\ubf09\ubf0a\ubf0b\ubf0c\ubf0d\ubf0e\ubf0f\ubf10\ubf11\ubf12\ubf13\ubf14\ubf15\ubf16\ubf17\ubf18\ubf19\ubf1a\ubf1b\ubf1c\ubf1d\ubf1e\ubf1f\ubf20\ubf21\ubf22\ubf23\ubf24\ubf25\ubf26\ubf27\ubf28\ubf29\ubf2a\ubf2b\ubf2c\ubf2d\ubf2e\ubf2f\ubf30\ubf31\ubf32\ubf33\ubf34\ubf35\ubf36\ubf37\ubf38\ubf39\ubf3a\ubf3b\ubf3c\ubf3d\ubf3e\ubf3f\ubf40\ubf41\ubf42\ubf43\ubf44\ubf45\ubf46\ubf47\ubf48\ubf49\ubf4a\ubf4b\ubf4c\ubf4d\ubf4e\ubf4f\ubf50\ubf51\ubf52\ubf53\ubf54\ubf55\ubf56\ubf57\ubf58\ubf59\ubf5a\ubf5b\ubf5c\ubf5d\ubf5e\ubf5f\ubf60\ubf61\ubf62\ubf63\ubf64\ubf65\ubf66\ubf67\ubf68\ubf69\ubf6a\ubf6b\ubf6c\ubf6d\ubf6e\ubf6f\ubf70\ubf71\ubf72\ubf73\ubf74\ubf75\ubf76\ubf77\ubf78\ubf79\ubf7a\ubf7b\ubf7c\ubf7d\ubf7e\ubf7f\ubf80\ubf81\ubf82\ubf83\ubf84\ubf85\ubf86\ubf87\ubf88\ubf89\ubf8a\ubf8b\ubf8c\ubf8d\ubf8e\ubf8f\ubf90\ubf91\ubf92\ubf93\ubf94\ubf95\ubf96\ubf97\ubf98\ubf99\ubf9a\ubf9b\ubf9c\ubf9d\ubf9e\ubf9f\ubfa0\ubfa1\ubfa2\ubfa3\ubfa4\ubfa5\ubfa6\ubfa7\ubfa8\ubfa9\ubfaa\ubfab\ubfac\ubfad\ubfae\ubfaf\ubfb0\ubfb1\ubfb2\ubfb3\ubfb4\ubfb5\ubfb6\ubfb7\ubfb8\ubfb9\ubfba\ubfbb\ubfbc\ubfbd\ubfbe\ubfbf\ubfc0\ubfc1\ubfc2\ubfc3\ubfc4\ubfc5\ubfc6\ubfc7\ubfc8\ubfc9\ubfca\ubfcb\ubfcc\ubfcd\ubfce\ubfcf\ubfd0\ubfd1\ubfd2\ubfd3\ubfd4\ubfd5\ubfd6\ubfd7\ubfd8\ubfd9\ubfda\ubfdb\ubfdc\ubfdd\ubfde\ubfdf\ubfe0\ubfe1\ubfe2\ubfe3\ubfe4\ubfe5\ubfe6\ubfe7\ubfe8\ubfe9\ubfea\ubfeb\ubfec\ubfed\ubfee\ubfef\ubff0\ubff1\ubff2\ubff3\ubff4\ubff5\ubff6\ubff7\ubff8\ubff9\ubffa\ubffb\ubffc\ubffd\ubffe\ubfff\uc000\uc001\uc002\uc003\uc004\uc005\uc006\uc007\uc008\uc009\uc00a\uc00b\uc00c\uc00d\uc00e\uc00f\uc010\uc011\uc012\uc013\uc014\uc015\uc016\uc017\uc018\uc019\uc01a\uc01b\uc01c\uc01d\uc01e\uc01f\uc020\uc021\uc022\uc023\uc024\uc025\uc026\uc027\uc028\uc029\uc02a\uc02b\uc02c\uc02d\uc02e\uc02f\uc030\uc031\uc032\uc033\uc034\uc035\uc036\uc037\uc038\uc039\uc03a\uc03b\uc03c\uc03d\uc03e\uc03f\uc040\uc041\uc042\uc043\uc044\uc045\uc046\uc047\uc048\uc049\uc04a\uc04b\uc04c\uc04d\uc04e\uc04f\uc050\uc051\uc052\uc053\uc054\uc055\uc056\uc057\uc058\uc059\uc05a\uc05b\uc05c\uc05d\uc05e\uc05f\uc060\uc061\uc062\uc063\uc064\uc065\uc066\uc067\uc068\uc069\uc06a\uc06b\uc06c\uc06d\uc06e\uc06f\uc070\uc071\uc072\uc073\uc074\uc075\uc076\uc077\uc078\uc079\uc07a\uc07b\uc07c\uc07d\uc07e\uc07f\uc080\uc081\uc082\uc083\uc084\uc085\uc086\uc087\uc088\uc089\uc08a\uc08b\uc08c\uc08d\uc08e\uc08f\uc090\uc091\uc092\uc093\uc094\uc095\uc096\uc097\uc098\uc099\uc09a\uc09b\uc09c\uc09d\uc09e\uc09f\uc0a0\uc0a1\uc0a2\uc0a3\uc0a4\uc0a5\uc0a6\uc0a7\uc0a8\uc0a9\uc0aa\uc0ab\uc0ac\uc0ad\uc0ae\uc0af\uc0b0\uc0b1\uc0b2\uc0b3\uc0b4\uc0b5\uc0b6\uc0b7\uc0b8\uc0b9\uc0ba\uc0bb\uc0bc\uc0bd\uc0be\uc0bf\uc0c0\uc0c1\uc0c2\uc0c3\uc0c4\uc0c5\uc0c6\uc0c7\uc0c8\uc0c9\uc0ca\uc0cb\uc0cc\uc0cd\uc0ce\uc0cf\uc0d0\uc0d1\uc0d2\uc0d3\uc0d4\uc0d5\uc0d6\uc0d7\uc0d8\uc0d9\uc0da\uc0db\uc0dc\uc0dd\uc0de\uc0df\uc0e0\uc0e1\uc0e2\uc0e3\uc0e4\uc0e5\uc0e6\uc0e7\uc0e8\uc0e9\uc0ea\uc0eb\uc0ec\uc0ed\uc0ee\uc0ef\uc0f0\uc0f1\uc0f2\uc0f3\uc0f4\uc0f5\uc0f6\uc0f7\uc0f8\uc0f9\uc0fa\uc0fb\uc0fc\uc0fd\uc0fe\uc0ff\uc100\uc101\uc102\uc103\uc104\uc105\uc106\uc107\uc108\uc109\uc10a\uc10b\uc10c\uc10d\uc10e\uc10f\uc110\uc111\uc112\uc113\uc114\uc115\uc116\uc117\uc118\uc119\uc11a\uc11b\uc11c\uc11d\uc11e\uc11f\uc120\uc121\uc122\uc123\uc124\uc125\uc126\uc127\uc128\uc129\uc12a\uc12b\uc12c\uc12d\uc12e\uc12f\uc130\uc131\uc132\uc133\uc134\uc135\uc136\uc137\uc138\uc139\uc13a\uc13b\uc13c\uc13d\uc13e\uc13f\uc140\uc141\uc142\uc143\uc144\uc145\uc146\uc147\uc148\uc149\uc14a\uc14b\uc14c\uc14d\uc14e\uc14f\uc150\uc151\uc152\uc153\uc154\uc155\uc156\uc157\uc158\uc159\uc15a\uc15b\uc15c\uc15d\uc15e\uc15f\uc160\uc161\uc162\uc163\uc164\uc165\uc166\uc167\uc168\uc169\uc16a\uc16b\uc16c\uc16d\uc16e\uc16f\uc170\uc171\uc172\uc173\uc174\uc175\uc176\uc177\uc178\uc179\uc17a\uc17b\uc17c\uc17d\uc17e\uc17f\uc180\uc181\uc182\uc183\uc184\uc185\uc186\uc187\uc188\uc189\uc18a\uc18b\uc18c\uc18d\uc18e\uc18f\uc190\uc191\uc192\uc193\uc194\uc195\uc196\uc197\uc198\uc199\uc19a\uc19b\uc19c\uc19d\uc19e\uc19f\uc1a0\uc1a1\uc1a2\uc1a3\uc1a4\uc1a5\uc1a6\uc1a7\uc1a8\uc1a9\uc1aa\uc1ab\uc1ac\uc1ad\uc1ae\uc1af\uc1b0\uc1b1\uc1b2\uc1b3\uc1b4\uc1b5\uc1b6\uc1b7\uc1b8\uc1b9\uc1ba\uc1bb\uc1bc\uc1bd\uc1be\uc1bf\uc1c0\uc1c1\uc1c2\uc1c3\uc1c4\uc1c5\uc1c6\uc1c7\uc1c8\uc1c9\uc1ca\uc1cb\uc1cc\uc1cd\uc1ce\uc1cf\uc1d0\uc1d1\uc1d2\uc1d3\uc1d4\uc1d5\uc1d6\uc1d7\uc1d8\uc1d9\uc1da\uc1db\uc1dc\uc1dd\uc1de\uc1df\uc1e0\uc1e1\uc1e2\uc1e3\uc1e4\uc1e5\uc1e6\uc1e7\uc1e8\uc1e9\uc1ea\uc1eb\uc1ec\uc1ed\uc1ee\uc1ef\uc1f0\uc1f1\uc1f2\uc1f3\uc1f4\uc1f5\uc1f6\uc1f7\uc1f8\uc1f9\uc1fa\uc1fb\uc1fc\uc1fd\uc1fe\uc1ff\uc200\uc201\uc202\uc203\uc204\uc205\uc206\uc207\uc208\uc209\uc20a\uc20b\uc20c\uc20d\uc20e\uc20f\uc210\uc211\uc212\uc213\uc214\uc215\uc216\uc217\uc218\uc219\uc21a\uc21b\uc21c\uc21d\uc21e\uc21f\uc220\uc221\uc222\uc223\uc224\uc225\uc226\uc227\uc228\uc229\uc22a\uc22b\uc22c\uc22d\uc22e\uc22f\uc230\uc231\uc232\uc233\uc234\uc235\uc236\uc237\uc238\uc239\uc23a\uc23b\uc23c\uc23d\uc23e\uc23f\uc240\uc241\uc242\uc243\uc244\uc245\uc246\uc247\uc248\uc249\uc24a\uc24b\uc24c\uc24d\uc24e\uc24f\uc250\uc251\uc252\uc253\uc254\uc255\uc256\uc257\uc258\uc259\uc25a\uc25b\uc25c\uc25d\uc25e\uc25f\uc260\uc261\uc262\uc263\uc264\uc265\uc266\uc267\uc268\uc269\uc26a\uc26b\uc26c\uc26d\uc26e\uc26f\uc270\uc271\uc272\uc273\uc274\uc275\uc276\uc277\uc278\uc279\uc27a\uc27b\uc27c\uc27d\uc27e\uc27f\uc280\uc281\uc282\uc283\uc284\uc285\uc286\uc287\uc288\uc289\uc28a\uc28b\uc28c\uc28d\uc28e\uc28f\uc290\uc291\uc292\uc293\uc294\uc295\uc296\uc297\uc298\uc299\uc29a\uc29b\uc29c\uc29d\uc29e\uc29f\uc2a0\uc2a1\uc2a2\uc2a3\uc2a4\uc2a5\uc2a6\uc2a7\uc2a8\uc2a9\uc2aa\uc2ab\uc2ac\uc2ad\uc2ae\uc2af\uc2b0\uc2b1\uc2b2\uc2b3\uc2b4\uc2b5\uc2b6\uc2b7\uc2b8\uc2b9\uc2ba\uc2bb\uc2bc\uc2bd\uc2be\uc2bf\uc2c0\uc2c1\uc2c2\uc2c3\uc2c4\uc2c5\uc2c6\uc2c7\uc2c8\uc2c9\uc2ca\uc2cb\uc2cc\uc2cd\uc2ce\uc2cf\uc2d0\uc2d1\uc2d2\uc2d3\uc2d4\uc2d5\uc2d6\uc2d7\uc2d8\uc2d9\uc2da\uc2db\uc2dc\uc2dd\uc2de\uc2df\uc2e0\uc2e1\uc2e2\uc2e3\uc2e4\uc2e5\uc2e6\uc2e7\uc2e8\uc2e9\uc2ea\uc2eb\uc2ec\uc2ed\uc2ee\uc2ef\uc2f0\uc2f1\uc2f2\uc2f3\uc2f4\uc2f5\uc2f6\uc2f7\uc2f8\uc2f9\uc2fa\uc2fb\uc2fc\uc2fd\uc2fe\uc2ff\uc300\uc301\uc302\uc303\uc304\uc305\uc306\uc307\uc308\uc309\uc30a\uc30b\uc30c\uc30d\uc30e\uc30f\uc310\uc311\uc312\uc313\uc314\uc315\uc316\uc317\uc318\uc319\uc31a\uc31b\uc31c\uc31d\uc31e\uc31f\uc320\uc321\uc322\uc323\uc324\uc325\uc326\uc327\uc328\uc329\uc32a\uc32b\uc32c\uc32d\uc32e\uc32f\uc330\uc331\uc332\uc333\uc334\uc335\uc336\uc337\uc338\uc339\uc33a\uc33b\uc33c\uc33d\uc33e\uc33f\uc340\uc341\uc342\uc343\uc344\uc345\uc346\uc347\uc348\uc349\uc34a\uc34b\uc34c\uc34d\uc34e\uc34f\uc350\uc351\uc352\uc353\uc354\uc355\uc356\uc357\uc358\uc359\uc35a\uc35b\uc35c\uc35d\uc35e\uc35f\uc360\uc361\uc362\uc363\uc364\uc365\uc366\uc367\uc368\uc369\uc36a\uc36b\uc36c\uc36d\uc36e\uc36f\uc370\uc371\uc372\uc373\uc374\uc375\uc376\uc377\uc378\uc379\uc37a\uc37b\uc37c\uc37d\uc37e\uc37f\uc380\uc381\uc382\uc383\uc384\uc385\uc386\uc387\uc388\uc389\uc38a\uc38b\uc38c\uc38d\uc38e\uc38f\uc390\uc391\uc392\uc393\uc394\uc395\uc396\uc397\uc398\uc399\uc39a\uc39b\uc39c\uc39d\uc39e\uc39f\uc3a0\uc3a1\uc3a2\uc3a3\uc3a4\uc3a5\uc3a6\uc3a7\uc3a8\uc3a9\uc3aa\uc3ab\uc3ac\uc3ad\uc3ae\uc3af\uc3b0\uc3b1\uc3b2\uc3b3\uc3b4\uc3b5\uc3b6\uc3b7\uc3b8\uc3b9\uc3ba\uc3bb\uc3bc\uc3bd\uc3be\uc3bf\uc3c0\uc3c1\uc3c2\uc3c3\uc3c4\uc3c5\uc3c6\uc3c7\uc3c8\uc3c9\uc3ca\uc3cb\uc3cc\uc3cd\uc3ce\uc3cf\uc3d0\uc3d1\uc3d2\uc3d3\uc3d4\uc3d5\uc3d6\uc3d7\uc3d8\uc3d9\uc3da\uc3db\uc3dc\uc3dd\uc3de\uc3df\uc3e0\uc3e1\uc3e2\uc3e3\uc3e4\uc3e5\uc3e6\uc3e7\uc3e8\uc3e9\uc3ea\uc3eb\uc3ec\uc3ed\uc3ee\uc3ef\uc3f0\uc3f1\uc3f2\uc3f3\uc3f4\uc3f5\uc3f6\uc3f7\uc3f8\uc3f9\uc3fa\uc3fb\uc3fc\uc3fd\uc3fe\uc3ff\uc400\uc401\uc402\uc403\uc404\uc405\uc406\uc407\uc408\uc409\uc40a\uc40b\uc40c\uc40d\uc40e\uc40f\uc410\uc411\uc412\uc413\uc414\uc415\uc416\uc417\uc418\uc419\uc41a\uc41b\uc41c\uc41d\uc41e\uc41f\uc420\uc421\uc422\uc423\uc424\uc425\uc426\uc427\uc428\uc429\uc42a\uc42b\uc42c\uc42d\uc42e\uc42f\uc430\uc431\uc432\uc433\uc434\uc435\uc436\uc437\uc438\uc439\uc43a\uc43b\uc43c\uc43d\uc43e\uc43f\uc440\uc441\uc442\uc443\uc444\uc445\uc446\uc447\uc448\uc449\uc44a\uc44b\uc44c\uc44d\uc44e\uc44f\uc450\uc451\uc452\uc453\uc454\uc455\uc456\uc457\uc458\uc459\uc45a\uc45b\uc45c\uc45d\uc45e\uc45f\uc460\uc461\uc462\uc463\uc464\uc465\uc466\uc467\uc468\uc469\uc46a\uc46b\uc46c\uc46d\uc46e\uc46f\uc470\uc471\uc472\uc473\uc474\uc475\uc476\uc477\uc478\uc479\uc47a\uc47b\uc47c\uc47d\uc47e\uc47f\uc480\uc481\uc482\uc483\uc484\uc485\uc486\uc487\uc488\uc489\uc48a\uc48b\uc48c\uc48d\uc48e\uc48f\uc490\uc491\uc492\uc493\uc494\uc495\uc496\uc497\uc498\uc499\uc49a\uc49b\uc49c\uc49d\uc49e\uc49f\uc4a0\uc4a1\uc4a2\uc4a3\uc4a4\uc4a5\uc4a6\uc4a7\uc4a8\uc4a9\uc4aa\uc4ab\uc4ac\uc4ad\uc4ae\uc4af\uc4b0\uc4b1\uc4b2\uc4b3\uc4b4\uc4b5\uc4b6\uc4b7\uc4b8\uc4b9\uc4ba\uc4bb\uc4bc\uc4bd\uc4be\uc4bf\uc4c0\uc4c1\uc4c2\uc4c3\uc4c4\uc4c5\uc4c6\uc4c7\uc4c8\uc4c9\uc4ca\uc4cb\uc4cc\uc4cd\uc4ce\uc4cf\uc4d0\uc4d1\uc4d2\uc4d3\uc4d4\uc4d5\uc4d6\uc4d7\uc4d8\uc4d9\uc4da\uc4db\uc4dc\uc4dd\uc4de\uc4df\uc4e0\uc4e1\uc4e2\uc4e3\uc4e4\uc4e5\uc4e6\uc4e7\uc4e8\uc4e9\uc4ea\uc4eb\uc4ec\uc4ed\uc4ee\uc4ef\uc4f0\uc4f1\uc4f2\uc4f3\uc4f4\uc4f5\uc4f6\uc4f7\uc4f8\uc4f9\uc4fa\uc4fb\uc4fc\uc4fd\uc4fe\uc4ff\uc500\uc501\uc502\uc503\uc504\uc505\uc506\uc507\uc508\uc509\uc50a\uc50b\uc50c\uc50d\uc50e\uc50f\uc510\uc511\uc512\uc513\uc514\uc515\uc516\uc517\uc518\uc519\uc51a\uc51b\uc51c\uc51d\uc51e\uc51f\uc520\uc521\uc522\uc523\uc524\uc525\uc526\uc527\uc528\uc529\uc52a\uc52b\uc52c\uc52d\uc52e\uc52f\uc530\uc531\uc532\uc533\uc534\uc535\uc536\uc537\uc538\uc539\uc53a\uc53b\uc53c\uc53d\uc53e\uc53f\uc540\uc541\uc542\uc543\uc544\uc545\uc546\uc547\uc548\uc549\uc54a\uc54b\uc54c\uc54d\uc54e\uc54f\uc550\uc551\uc552\uc553\uc554\uc555\uc556\uc557\uc558\uc559\uc55a\uc55b\uc55c\uc55d\uc55e\uc55f\uc560\uc561\uc562\uc563\uc564\uc565\uc566\uc567\uc568\uc569\uc56a\uc56b\uc56c\uc56d\uc56e\uc56f\uc570\uc571\uc572\uc573\uc574\uc575\uc576\uc577\uc578\uc579\uc57a\uc57b\uc57c\uc57d\uc57e\uc57f\uc580\uc581\uc582\uc583\uc584\uc585\uc586\uc587\uc588\uc589\uc58a\uc58b\uc58c\uc58d\uc58e\uc58f\uc590\uc591\uc592\uc593\uc594\uc595\uc596\uc597\uc598\uc599\uc59a\uc59b\uc59c\uc59d\uc59e\uc59f\uc5a0\uc5a1\uc5a2\uc5a3\uc5a4\uc5a5\uc5a6\uc5a7\uc5a8\uc5a9\uc5aa\uc5ab\uc5ac\uc5ad\uc5ae\uc5af\uc5b0\uc5b1\uc5b2\uc5b3\uc5b4\uc5b5\uc5b6\uc5b7\uc5b8\uc5b9\uc5ba\uc5bb\uc5bc\uc5bd\uc5be\uc5bf\uc5c0\uc5c1\uc5c2\uc5c3\uc5c4\uc5c5\uc5c6\uc5c7\uc5c8\uc5c9\uc5ca\uc5cb\uc5cc\uc5cd\uc5ce\uc5cf\uc5d0\uc5d1\uc5d2\uc5d3\uc5d4\uc5d5\uc5d6\uc5d7\uc5d8\uc5d9\uc5da\uc5db\uc5dc\uc5dd\uc5de\uc5df\uc5e0\uc5e1\uc5e2\uc5e3\uc5e4\uc5e5\uc5e6\uc5e7\uc5e8\uc5e9\uc5ea\uc5eb\uc5ec\uc5ed\uc5ee\uc5ef\uc5f0\uc5f1\uc5f2\uc5f3\uc5f4\uc5f5\uc5f6\uc5f7\uc5f8\uc5f9\uc5fa\uc5fb\uc5fc\uc5fd\uc5fe\uc5ff\uc600\uc601\uc602\uc603\uc604\uc605\uc606\uc607\uc608\uc609\uc60a\uc60b\uc60c\uc60d\uc60e\uc60f\uc610\uc611\uc612\uc613\uc614\uc615\uc616\uc617\uc618\uc619\uc61a\uc61b\uc61c\uc61d\uc61e\uc61f\uc620\uc621\uc622\uc623\uc624\uc625\uc626\uc627\uc628\uc629\uc62a\uc62b\uc62c\uc62d\uc62e\uc62f\uc630\uc631\uc632\uc633\uc634\uc635\uc636\uc637\uc638\uc639\uc63a\uc63b\uc63c\uc63d\uc63e\uc63f\uc640\uc641\uc642\uc643\uc644\uc645\uc646\uc647\uc648\uc649\uc64a\uc64b\uc64c\uc64d\uc64e\uc64f\uc650\uc651\uc652\uc653\uc654\uc655\uc656\uc657\uc658\uc659\uc65a\uc65b\uc65c\uc65d\uc65e\uc65f\uc660\uc661\uc662\uc663\uc664\uc665\uc666\uc667\uc668\uc669\uc66a\uc66b\uc66c\uc66d\uc66e\uc66f\uc670\uc671\uc672\uc673\uc674\uc675\uc676\uc677\uc678\uc679\uc67a\uc67b\uc67c\uc67d\uc67e\uc67f\uc680\uc681\uc682\uc683\uc684\uc685\uc686\uc687\uc688\uc689\uc68a\uc68b\uc68c\uc68d\uc68e\uc68f\uc690\uc691\uc692\uc693\uc694\uc695\uc696\uc697\uc698\uc699\uc69a\uc69b\uc69c\uc69d\uc69e\uc69f\uc6a0\uc6a1\uc6a2\uc6a3\uc6a4\uc6a5\uc6a6\uc6a7\uc6a8\uc6a9\uc6aa\uc6ab\uc6ac\uc6ad\uc6ae\uc6af\uc6b0\uc6b1\uc6b2\uc6b3\uc6b4\uc6b5\uc6b6\uc6b7\uc6b8\uc6b9\uc6ba\uc6bb\uc6bc\uc6bd\uc6be\uc6bf\uc6c0\uc6c1\uc6c2\uc6c3\uc6c4\uc6c5\uc6c6\uc6c7\uc6c8\uc6c9\uc6ca\uc6cb\uc6cc\uc6cd\uc6ce\uc6cf\uc6d0\uc6d1\uc6d2\uc6d3\uc6d4\uc6d5\uc6d6\uc6d7\uc6d8\uc6d9\uc6da\uc6db\uc6dc\uc6dd\uc6de\uc6df\uc6e0\uc6e1\uc6e2\uc6e3\uc6e4\uc6e5\uc6e6\uc6e7\uc6e8\uc6e9\uc6ea\uc6eb\uc6ec\uc6ed\uc6ee\uc6ef\uc6f0\uc6f1\uc6f2\uc6f3\uc6f4\uc6f5\uc6f6\uc6f7\uc6f8\uc6f9\uc6fa\uc6fb\uc6fc\uc6fd\uc6fe\uc6ff\uc700\uc701\uc702\uc703\uc704\uc705\uc706\uc707\uc708\uc709\uc70a\uc70b\uc70c\uc70d\uc70e\uc70f\uc710\uc711\uc712\uc713\uc714\uc715\uc716\uc717\uc718\uc719\uc71a\uc71b\uc71c\uc71d\uc71e\uc71f\uc720\uc721\uc722\uc723\uc724\uc725\uc726\uc727\uc728\uc729\uc72a\uc72b\uc72c\uc72d\uc72e\uc72f\uc730\uc731\uc732\uc733\uc734\uc735\uc736\uc737\uc738\uc739\uc73a\uc73b\uc73c\uc73d\uc73e\uc73f\uc740\uc741\uc742\uc743\uc744\uc745\uc746\uc747\uc748\uc749\uc74a\uc74b\uc74c\uc74d\uc74e\uc74f\uc750\uc751\uc752\uc753\uc754\uc755\uc756\uc757\uc758\uc759\uc75a\uc75b\uc75c\uc75d\uc75e\uc75f\uc760\uc761\uc762\uc763\uc764\uc765\uc766\uc767\uc768\uc769\uc76a\uc76b\uc76c\uc76d\uc76e\uc76f\uc770\uc771\uc772\uc773\uc774\uc775\uc776\uc777\uc778\uc779\uc77a\uc77b\uc77c\uc77d\uc77e\uc77f\uc780\uc781\uc782\uc783\uc784\uc785\uc786\uc787\uc788\uc789\uc78a\uc78b\uc78c\uc78d\uc78e\uc78f\uc790\uc791\uc792\uc793\uc794\uc795\uc796\uc797\uc798\uc799\uc79a\uc79b\uc79c\uc79d\uc79e\uc79f\uc7a0\uc7a1\uc7a2\uc7a3\uc7a4\uc7a5\uc7a6\uc7a7\uc7a8\uc7a9\uc7aa\uc7ab\uc7ac\uc7ad\uc7ae\uc7af\uc7b0\uc7b1\uc7b2\uc7b3\uc7b4\uc7b5\uc7b6\uc7b7\uc7b8\uc7b9\uc7ba\uc7bb\uc7bc\uc7bd\uc7be\uc7bf\uc7c0\uc7c1\uc7c2\uc7c3\uc7c4\uc7c5\uc7c6\uc7c7\uc7c8\uc7c9\uc7ca\uc7cb\uc7cc\uc7cd\uc7ce\uc7cf\uc7d0\uc7d1\uc7d2\uc7d3\uc7d4\uc7d5\uc7d6\uc7d7\uc7d8\uc7d9\uc7da\uc7db\uc7dc\uc7dd\uc7de\uc7df\uc7e0\uc7e1\uc7e2\uc7e3\uc7e4\uc7e5\uc7e6\uc7e7\uc7e8\uc7e9\uc7ea\uc7eb\uc7ec\uc7ed\uc7ee\uc7ef\uc7f0\uc7f1\uc7f2\uc7f3\uc7f4\uc7f5\uc7f6\uc7f7\uc7f8\uc7f9\uc7fa\uc7fb\uc7fc\uc7fd\uc7fe\uc7ff\uc800\uc801\uc802\uc803\uc804\uc805\uc806\uc807\uc808\uc809\uc80a\uc80b\uc80c\uc80d\uc80e\uc80f\uc810\uc811\uc812\uc813\uc814\uc815\uc816\uc817\uc818\uc819\uc81a\uc81b\uc81c\uc81d\uc81e\uc81f\uc820\uc821\uc822\uc823\uc824\uc825\uc826\uc827\uc828\uc829\uc82a\uc82b\uc82c\uc82d\uc82e\uc82f\uc830\uc831\uc832\uc833\uc834\uc835\uc836\uc837\uc838\uc839\uc83a\uc83b\uc83c\uc83d\uc83e\uc83f\uc840\uc841\uc842\uc843\uc844\uc845\uc846\uc847\uc848\uc849\uc84a\uc84b\uc84c\uc84d\uc84e\uc84f\uc850\uc851\uc852\uc853\uc854\uc855\uc856\uc857\uc858\uc859\uc85a\uc85b\uc85c\uc85d\uc85e\uc85f\uc860\uc861\uc862\uc863\uc864\uc865\uc866\uc867\uc868\uc869\uc86a\uc86b\uc86c\uc86d\uc86e\uc86f\uc870\uc871\uc872\uc873\uc874\uc875\uc876\uc877\uc878\uc879\uc87a\uc87b\uc87c\uc87d\uc87e\uc87f\uc880\uc881\uc882\uc883\uc884\uc885\uc886\uc887\uc888\uc889\uc88a\uc88b\uc88c\uc88d\uc88e\uc88f\uc890\uc891\uc892\uc893\uc894\uc895\uc896\uc897\uc898\uc899\uc89a\uc89b\uc89c\uc89d\uc89e\uc89f\uc8a0\uc8a1\uc8a2\uc8a3\uc8a4\uc8a5\uc8a6\uc8a7\uc8a8\uc8a9\uc8aa\uc8ab\uc8ac\uc8ad\uc8ae\uc8af\uc8b0\uc8b1\uc8b2\uc8b3\uc8b4\uc8b5\uc8b6\uc8b7\uc8b8\uc8b9\uc8ba\uc8bb\uc8bc\uc8bd\uc8be\uc8bf\uc8c0\uc8c1\uc8c2\uc8c3\uc8c4\uc8c5\uc8c6\uc8c7\uc8c8\uc8c9\uc8ca\uc8cb\uc8cc\uc8cd\uc8ce\uc8cf\uc8d0\uc8d1\uc8d2\uc8d3\uc8d4\uc8d5\uc8d6\uc8d7\uc8d8\uc8d9\uc8da\uc8db\uc8dc\uc8dd\uc8de\uc8df\uc8e0\uc8e1\uc8e2\uc8e3\uc8e4\uc8e5\uc8e6\uc8e7\uc8e8\uc8e9\uc8ea\uc8eb\uc8ec\uc8ed\uc8ee\uc8ef\uc8f0\uc8f1\uc8f2\uc8f3\uc8f4\uc8f5\uc8f6\uc8f7\uc8f8\uc8f9\uc8fa\uc8fb\uc8fc\uc8fd\uc8fe\uc8ff\uc900\uc901\uc902\uc903\uc904\uc905\uc906\uc907\uc908\uc909\uc90a\uc90b\uc90c\uc90d\uc90e\uc90f\uc910\uc911\uc912\uc913\uc914\uc915\uc916\uc917\uc918\uc919\uc91a\uc91b\uc91c\uc91d\uc91e\uc91f\uc920\uc921\uc922\uc923\uc924\uc925\uc926\uc927\uc928\uc929\uc92a\uc92b\uc92c\uc92d\uc92e\uc92f\uc930\uc931\uc932\uc933\uc934\uc935\uc936\uc937\uc938\uc939\uc93a\uc93b\uc93c\uc93d\uc93e\uc93f\uc940\uc941\uc942\uc943\uc944\uc945\uc946\uc947\uc948\uc949\uc94a\uc94b\uc94c\uc94d\uc94e\uc94f\uc950\uc951\uc952\uc953\uc954\uc955\uc956\uc957\uc958\uc959\uc95a\uc95b\uc95c\uc95d\uc95e\uc95f\uc960\uc961\uc962\uc963\uc964\uc965\uc966\uc967\uc968\uc969\uc96a\uc96b\uc96c\uc96d\uc96e\uc96f\uc970\uc971\uc972\uc973\uc974\uc975\uc976\uc977\uc978\uc979\uc97a\uc97b\uc97c\uc97d\uc97e\uc97f\uc980\uc981\uc982\uc983\uc984\uc985\uc986\uc987\uc988\uc989\uc98a\uc98b\uc98c\uc98d\uc98e\uc98f\uc990\uc991\uc992\uc993\uc994\uc995\uc996\uc997\uc998\uc999\uc99a\uc99b\uc99c\uc99d\uc99e\uc99f\uc9a0\uc9a1\uc9a2\uc9a3\uc9a4\uc9a5\uc9a6\uc9a7\uc9a8\uc9a9\uc9aa\uc9ab\uc9ac\uc9ad\uc9ae\uc9af\uc9b0\uc9b1\uc9b2\uc9b3\uc9b4\uc9b5\uc9b6\uc9b7\uc9b8\uc9b9\uc9ba\uc9bb\uc9bc\uc9bd\uc9be\uc9bf\uc9c0\uc9c1\uc9c2\uc9c3\uc9c4\uc9c5\uc9c6\uc9c7\uc9c8\uc9c9\uc9ca\uc9cb\uc9cc\uc9cd\uc9ce\uc9cf\uc9d0\uc9d1\uc9d2\uc9d3\uc9d4\uc9d5\uc9d6\uc9d7\uc9d8\uc9d9\uc9da\uc9db\uc9dc\uc9dd\uc9de\uc9df\uc9e0\uc9e1\uc9e2\uc9e3\uc9e4\uc9e5\uc9e6\uc9e7\uc9e8\uc9e9\uc9ea\uc9eb\uc9ec\uc9ed\uc9ee\uc9ef\uc9f0\uc9f1\uc9f2\uc9f3\uc9f4\uc9f5\uc9f6\uc9f7\uc9f8\uc9f9\uc9fa\uc9fb\uc9fc\uc9fd\uc9fe\uc9ff\uca00\uca01\uca02\uca03\uca04\uca05\uca06\uca07\uca08\uca09\uca0a\uca0b\uca0c\uca0d\uca0e\uca0f\uca10\uca11\uca12\uca13\uca14\uca15\uca16\uca17\uca18\uca19\uca1a\uca1b\uca1c\uca1d\uca1e\uca1f\uca20\uca21\uca22\uca23\uca24\uca25\uca26\uca27\uca28\uca29\uca2a\uca2b\uca2c\uca2d\uca2e\uca2f\uca30\uca31\uca32\uca33\uca34\uca35\uca36\uca37\uca38\uca39\uca3a\uca3b\uca3c\uca3d\uca3e\uca3f\uca40\uca41\uca42\uca43\uca44\uca45\uca46\uca47\uca48\uca49\uca4a\uca4b\uca4c\uca4d\uca4e\uca4f\uca50\uca51\uca52\uca53\uca54\uca55\uca56\uca57\uca58\uca59\uca5a\uca5b\uca5c\uca5d\uca5e\uca5f\uca60\uca61\uca62\uca63\uca64\uca65\uca66\uca67\uca68\uca69\uca6a\uca6b\uca6c\uca6d\uca6e\uca6f\uca70\uca71\uca72\uca73\uca74\uca75\uca76\uca77\uca78\uca79\uca7a\uca7b\uca7c\uca7d\uca7e\uca7f\uca80\uca81\uca82\uca83\uca84\uca85\uca86\uca87\uca88\uca89\uca8a\uca8b\uca8c\uca8d\uca8e\uca8f\uca90\uca91\uca92\uca93\uca94\uca95\uca96\uca97\uca98\uca99\uca9a\uca9b\uca9c\uca9d\uca9e\uca9f\ucaa0\ucaa1\ucaa2\ucaa3\ucaa4\ucaa5\ucaa6\ucaa7\ucaa8\ucaa9\ucaaa\ucaab\ucaac\ucaad\ucaae\ucaaf\ucab0\ucab1\ucab2\ucab3\ucab4\ucab5\ucab6\ucab7\ucab8\ucab9\ucaba\ucabb\ucabc\ucabd\ucabe\ucabf\ucac0\ucac1\ucac2\ucac3\ucac4\ucac5\ucac6\ucac7\ucac8\ucac9\ucaca\ucacb\ucacc\ucacd\ucace\ucacf\ucad0\ucad1\ucad2\ucad3\ucad4\ucad5\ucad6\ucad7\ucad8\ucad9\ucada\ucadb\ucadc\ucadd\ucade\ucadf\ucae0\ucae1\ucae2\ucae3\ucae4\ucae5\ucae6\ucae7\ucae8\ucae9\ucaea\ucaeb\ucaec\ucaed\ucaee\ucaef\ucaf0\ucaf1\ucaf2\ucaf3\ucaf4\ucaf5\ucaf6\ucaf7\ucaf8\ucaf9\ucafa\ucafb\ucafc\ucafd\ucafe\ucaff\ucb00\ucb01\ucb02\ucb03\ucb04\ucb05\ucb06\ucb07\ucb08\ucb09\ucb0a\ucb0b\ucb0c\ucb0d\ucb0e\ucb0f\ucb10\ucb11\ucb12\ucb13\ucb14\ucb15\ucb16\ucb17\ucb18\ucb19\ucb1a\ucb1b\ucb1c\ucb1d\ucb1e\ucb1f\ucb20\ucb21\ucb22\ucb23\ucb24\ucb25\ucb26\ucb27\ucb28\ucb29\ucb2a\ucb2b\ucb2c\ucb2d\ucb2e\ucb2f\ucb30\ucb31\ucb32\ucb33\ucb34\ucb35\ucb36\ucb37\ucb38\ucb39\ucb3a\ucb3b\ucb3c\ucb3d\ucb3e\ucb3f\ucb40\ucb41\ucb42\ucb43\ucb44\ucb45\ucb46\ucb47\ucb48\ucb49\ucb4a\ucb4b\ucb4c\ucb4d\ucb4e\ucb4f\ucb50\ucb51\ucb52\ucb53\ucb54\ucb55\ucb56\ucb57\ucb58\ucb59\ucb5a\ucb5b\ucb5c\ucb5d\ucb5e\ucb5f\ucb60\ucb61\ucb62\ucb63\ucb64\ucb65\ucb66\ucb67\ucb68\ucb69\ucb6a\ucb6b\ucb6c\ucb6d\ucb6e\ucb6f\ucb70\ucb71\ucb72\ucb73\ucb74\ucb75\ucb76\ucb77\ucb78\ucb79\ucb7a\ucb7b\ucb7c\ucb7d\ucb7e\ucb7f\ucb80\ucb81\ucb82\ucb83\ucb84\ucb85\ucb86\ucb87\ucb88\ucb89\ucb8a\ucb8b\ucb8c\ucb8d\ucb8e\ucb8f\ucb90\ucb91\ucb92\ucb93\ucb94\ucb95\ucb96\ucb97\ucb98\ucb99\ucb9a\ucb9b\ucb9c\ucb9d\ucb9e\ucb9f\ucba0\ucba1\ucba2\ucba3\ucba4\ucba5\ucba6\ucba7\ucba8\ucba9\ucbaa\ucbab\ucbac\ucbad\ucbae\ucbaf\ucbb0\ucbb1\ucbb2\ucbb3\ucbb4\ucbb5\ucbb6\ucbb7\ucbb8\ucbb9\ucbba\ucbbb\ucbbc\ucbbd\ucbbe\ucbbf\ucbc0\ucbc1\ucbc2\ucbc3\ucbc4\ucbc5\ucbc6\ucbc7\ucbc8\ucbc9\ucbca\ucbcb\ucbcc\ucbcd\ucbce\ucbcf\ucbd0\ucbd1\ucbd2\ucbd3\ucbd4\ucbd5\ucbd6\ucbd7\ucbd8\ucbd9\ucbda\ucbdb\ucbdc\ucbdd\ucbde\ucbdf\ucbe0\ucbe1\ucbe2\ucbe3\ucbe4\ucbe5\ucbe6\ucbe7\ucbe8\ucbe9\ucbea\ucbeb\ucbec\ucbed\ucbee\ucbef\ucbf0\ucbf1\ucbf2\ucbf3\ucbf4\ucbf5\ucbf6\ucbf7\ucbf8\ucbf9\ucbfa\ucbfb\ucbfc\ucbfd\ucbfe\ucbff\ucc00\ucc01\ucc02\ucc03\ucc04\ucc05\ucc06\ucc07\ucc08\ucc09\ucc0a\ucc0b\ucc0c\ucc0d\ucc0e\ucc0f\ucc10\ucc11\ucc12\ucc13\ucc14\ucc15\ucc16\ucc17\ucc18\ucc19\ucc1a\ucc1b\ucc1c\ucc1d\ucc1e\ucc1f\ucc20\ucc21\ucc22\ucc23\ucc24\ucc25\ucc26\ucc27\ucc28\ucc29\ucc2a\ucc2b\ucc2c\ucc2d\ucc2e\ucc2f\ucc30\ucc31\ucc32\ucc33\ucc34\ucc35\ucc36\ucc37\ucc38\ucc39\ucc3a\ucc3b\ucc3c\ucc3d\ucc3e\ucc3f\ucc40\ucc41\ucc42\ucc43\ucc44\ucc45\ucc46\ucc47\ucc48\ucc49\ucc4a\ucc4b\ucc4c\ucc4d\ucc4e\ucc4f\ucc50\ucc51\ucc52\ucc53\ucc54\ucc55\ucc56\ucc57\ucc58\ucc59\ucc5a\ucc5b\ucc5c\ucc5d\ucc5e\ucc5f\ucc60\ucc61\ucc62\ucc63\ucc64\ucc65\ucc66\ucc67\ucc68\ucc69\ucc6a\ucc6b\ucc6c\ucc6d\ucc6e\ucc6f\ucc70\ucc71\ucc72\ucc73\ucc74\ucc75\ucc76\ucc77\ucc78\ucc79\ucc7a\ucc7b\ucc7c\ucc7d\ucc7e\ucc7f\ucc80\ucc81\ucc82\ucc83\ucc84\ucc85\ucc86\ucc87\ucc88\ucc89\ucc8a\ucc8b\ucc8c\ucc8d\ucc8e\ucc8f\ucc90\ucc91\ucc92\ucc93\ucc94\ucc95\ucc96\ucc97\ucc98\ucc99\ucc9a\ucc9b\ucc9c\ucc9d\ucc9e\ucc9f\ucca0\ucca1\ucca2\ucca3\ucca4\ucca5\ucca6\ucca7\ucca8\ucca9\uccaa\uccab\uccac\uccad\uccae\uccaf\uccb0\uccb1\uccb2\uccb3\uccb4\uccb5\uccb6\uccb7\uccb8\uccb9\uccba\uccbb\uccbc\uccbd\uccbe\uccbf\uccc0\uccc1\uccc2\uccc3\uccc4\uccc5\uccc6\uccc7\uccc8\uccc9\uccca\ucccb\ucccc\ucccd\uccce\ucccf\uccd0\uccd1\uccd2\uccd3\uccd4\uccd5\uccd6\uccd7\uccd8\uccd9\uccda\uccdb\uccdc\uccdd\uccde\uccdf\ucce0\ucce1\ucce2\ucce3\ucce4\ucce5\ucce6\ucce7\ucce8\ucce9\uccea\ucceb\uccec\ucced\uccee\uccef\uccf0\uccf1\uccf2\uccf3\uccf4\uccf5\uccf6\uccf7\uccf8\uccf9\uccfa\uccfb\uccfc\uccfd\uccfe\uccff\ucd00\ucd01\ucd02\ucd03\ucd04\ucd05\ucd06\ucd07\ucd08\ucd09\ucd0a\ucd0b\ucd0c\ucd0d\ucd0e\ucd0f\ucd10\ucd11\ucd12\ucd13\ucd14\ucd15\ucd16\ucd17\ucd18\ucd19\ucd1a\ucd1b\ucd1c\ucd1d\ucd1e\ucd1f\ucd20\ucd21\ucd22\ucd23\ucd24\ucd25\ucd26\ucd27\ucd28\ucd29\ucd2a\ucd2b\ucd2c\ucd2d\ucd2e\ucd2f\ucd30\ucd31\ucd32\ucd33\ucd34\ucd35\ucd36\ucd37\ucd38\ucd39\ucd3a\ucd3b\ucd3c\ucd3d\ucd3e\ucd3f\ucd40\ucd41\ucd42\ucd43\ucd44\ucd45\ucd46\ucd47\ucd48\ucd49\ucd4a\ucd4b\ucd4c\ucd4d\ucd4e\ucd4f\ucd50\ucd51\ucd52\ucd53\ucd54\ucd55\ucd56\ucd57\ucd58\ucd59\ucd5a\ucd5b\ucd5c\ucd5d\ucd5e\ucd5f\ucd60\ucd61\ucd62\ucd63\ucd64\ucd65\ucd66\ucd67\ucd68\ucd69\ucd6a\ucd6b\ucd6c\ucd6d\ucd6e\ucd6f\ucd70\ucd71\ucd72\ucd73\ucd74\ucd75\ucd76\ucd77\ucd78\ucd79\ucd7a\ucd7b\ucd7c\ucd7d\ucd7e\ucd7f\ucd80\ucd81\ucd82\ucd83\ucd84\ucd85\ucd86\ucd87\ucd88\ucd89\ucd8a\ucd8b\ucd8c\ucd8d\ucd8e\ucd8f\ucd90\ucd91\ucd92\ucd93\ucd94\ucd95\ucd96\ucd97\ucd98\ucd99\ucd9a\ucd9b\ucd9c\ucd9d\ucd9e\ucd9f\ucda0\ucda1\ucda2\ucda3\ucda4\ucda5\ucda6\ucda7\ucda8\ucda9\ucdaa\ucdab\ucdac\ucdad\ucdae\ucdaf\ucdb0\ucdb1\ucdb2\ucdb3\ucdb4\ucdb5\ucdb6\ucdb7\ucdb8\ucdb9\ucdba\ucdbb\ucdbc\ucdbd\ucdbe\ucdbf\ucdc0\ucdc1\ucdc2\ucdc3\ucdc4\ucdc5\ucdc6\ucdc7\ucdc8\ucdc9\ucdca\ucdcb\ucdcc\ucdcd\ucdce\ucdcf\ucdd0\ucdd1\ucdd2\ucdd3\ucdd4\ucdd5\ucdd6\ucdd7\ucdd8\ucdd9\ucdda\ucddb\ucddc\ucddd\ucdde\ucddf\ucde0\ucde1\ucde2\ucde3\ucde4\ucde5\ucde6\ucde7\ucde8\ucde9\ucdea\ucdeb\ucdec\ucded\ucdee\ucdef\ucdf0\ucdf1\ucdf2\ucdf3\ucdf4\ucdf5\ucdf6\ucdf7\ucdf8\ucdf9\ucdfa\ucdfb\ucdfc\ucdfd\ucdfe\ucdff\uce00\uce01\uce02\uce03\uce04\uce05\uce06\uce07\uce08\uce09\uce0a\uce0b\uce0c\uce0d\uce0e\uce0f\uce10\uce11\uce12\uce13\uce14\uce15\uce16\uce17\uce18\uce19\uce1a\uce1b\uce1c\uce1d\uce1e\uce1f\uce20\uce21\uce22\uce23\uce24\uce25\uce26\uce27\uce28\uce29\uce2a\uce2b\uce2c\uce2d\uce2e\uce2f\uce30\uce31\uce32\uce33\uce34\uce35\uce36\uce37\uce38\uce39\uce3a\uce3b\uce3c\uce3d\uce3e\uce3f\uce40\uce41\uce42\uce43\uce44\uce45\uce46\uce47\uce48\uce49\uce4a\uce4b\uce4c\uce4d\uce4e\uce4f\uce50\uce51\uce52\uce53\uce54\uce55\uce56\uce57\uce58\uce59\uce5a\uce5b\uce5c\uce5d\uce5e\uce5f\uce60\uce61\uce62\uce63\uce64\uce65\uce66\uce67\uce68\uce69\uce6a\uce6b\uce6c\uce6d\uce6e\uce6f\uce70\uce71\uce72\uce73\uce74\uce75\uce76\uce77\uce78\uce79\uce7a\uce7b\uce7c\uce7d\uce7e\uce7f\uce80\uce81\uce82\uce83\uce84\uce85\uce86\uce87\uce88\uce89\uce8a\uce8b\uce8c\uce8d\uce8e\uce8f\uce90\uce91\uce92\uce93\uce94\uce95\uce96\uce97\uce98\uce99\uce9a\uce9b\uce9c\uce9d\uce9e\uce9f\ucea0\ucea1\ucea2\ucea3\ucea4\ucea5\ucea6\ucea7\ucea8\ucea9\uceaa\uceab\uceac\ucead\uceae\uceaf\uceb0\uceb1\uceb2\uceb3\uceb4\uceb5\uceb6\uceb7\uceb8\uceb9\uceba\ucebb\ucebc\ucebd\ucebe\ucebf\ucec0\ucec1\ucec2\ucec3\ucec4\ucec5\ucec6\ucec7\ucec8\ucec9\uceca\ucecb\ucecc\ucecd\ucece\ucecf\uced0\uced1\uced2\uced3\uced4\uced5\uced6\uced7\uced8\uced9\uceda\ucedb\ucedc\ucedd\ucede\ucedf\ucee0\ucee1\ucee2\ucee3\ucee4\ucee5\ucee6\ucee7\ucee8\ucee9\uceea\uceeb\uceec\uceed\uceee\uceef\ucef0\ucef1\ucef2\ucef3\ucef4\ucef5\ucef6\ucef7\ucef8\ucef9\ucefa\ucefb\ucefc\ucefd\ucefe\uceff\ucf00\ucf01\ucf02\ucf03\ucf04\ucf05\ucf06\ucf07\ucf08\ucf09\ucf0a\ucf0b\ucf0c\ucf0d\ucf0e\ucf0f\ucf10\ucf11\ucf12\ucf13\ucf14\ucf15\ucf16\ucf17\ucf18\ucf19\ucf1a\ucf1b\ucf1c\ucf1d\ucf1e\ucf1f\ucf20\ucf21\ucf22\ucf23\ucf24\ucf25\ucf26\ucf27\ucf28\ucf29\ucf2a\ucf2b\ucf2c\ucf2d\ucf2e\ucf2f\ucf30\ucf31\ucf32\ucf33\ucf34\ucf35\ucf36\ucf37\ucf38\ucf39\ucf3a\ucf3b\ucf3c\ucf3d\ucf3e\ucf3f\ucf40\ucf41\ucf42\ucf43\ucf44\ucf45\ucf46\ucf47\ucf48\ucf49\ucf4a\ucf4b\ucf4c\ucf4d\ucf4e\ucf4f\ucf50\ucf51\ucf52\ucf53\ucf54\ucf55\ucf56\ucf57\ucf58\ucf59\ucf5a\ucf5b\ucf5c\ucf5d\ucf5e\ucf5f\ucf60\ucf61\ucf62\ucf63\ucf64\ucf65\ucf66\ucf67\ucf68\ucf69\ucf6a\ucf6b\ucf6c\ucf6d\ucf6e\ucf6f\ucf70\ucf71\ucf72\ucf73\ucf74\ucf75\ucf76\ucf77\ucf78\ucf79\ucf7a\ucf7b\ucf7c\ucf7d\ucf7e\ucf7f\ucf80\ucf81\ucf82\ucf83\ucf84\ucf85\ucf86\ucf87\ucf88\ucf89\ucf8a\ucf8b\ucf8c\ucf8d\ucf8e\ucf8f\ucf90\ucf91\ucf92\ucf93\ucf94\ucf95\ucf96\ucf97\ucf98\ucf99\ucf9a\ucf9b\ucf9c\ucf9d\ucf9e\ucf9f\ucfa0\ucfa1\ucfa2\ucfa3\ucfa4\ucfa5\ucfa6\ucfa7\ucfa8\ucfa9\ucfaa\ucfab\ucfac\ucfad\ucfae\ucfaf\ucfb0\ucfb1\ucfb2\ucfb3\ucfb4\ucfb5\ucfb6\ucfb7\ucfb8\ucfb9\ucfba\ucfbb\ucfbc\ucfbd\ucfbe\ucfbf\ucfc0\ucfc1\ucfc2\ucfc3\ucfc4\ucfc5\ucfc6\ucfc7\ucfc8\ucfc9\ucfca\ucfcb\ucfcc\ucfcd\ucfce\ucfcf\ucfd0\ucfd1\ucfd2\ucfd3\ucfd4\ucfd5\ucfd6\ucfd7\ucfd8\ucfd9\ucfda\ucfdb\ucfdc\ucfdd\ucfde\ucfdf\ucfe0\ucfe1\ucfe2\ucfe3\ucfe4\ucfe5\ucfe6\ucfe7\ucfe8\ucfe9\ucfea\ucfeb\ucfec\ucfed\ucfee\ucfef\ucff0\ucff1\ucff2\ucff3\ucff4\ucff5\ucff6\ucff7\ucff8\ucff9\ucffa\ucffb\ucffc\ucffd\ucffe\ucfff\ud000\ud001\ud002\ud003\ud004\ud005\ud006\ud007\ud008\ud009\ud00a\ud00b\ud00c\ud00d\ud00e\ud00f\ud010\ud011\ud012\ud013\ud014\ud015\ud016\ud017\ud018\ud019\ud01a\ud01b\ud01c\ud01d\ud01e\ud01f\ud020\ud021\ud022\ud023\ud024\ud025\ud026\ud027\ud028\ud029\ud02a\ud02b\ud02c\ud02d\ud02e\ud02f\ud030\ud031\ud032\ud033\ud034\ud035\ud036\ud037\ud038\ud039\ud03a\ud03b\ud03c\ud03d\ud03e\ud03f\ud040\ud041\ud042\ud043\ud044\ud045\ud046\ud047\ud048\ud049\ud04a\ud04b\ud04c\ud04d\ud04e\ud04f\ud050\ud051\ud052\ud053\ud054\ud055\ud056\ud057\ud058\ud059\ud05a\ud05b\ud05c\ud05d\ud05e\ud05f\ud060\ud061\ud062\ud063\ud064\ud065\ud066\ud067\ud068\ud069\ud06a\ud06b\ud06c\ud06d\ud06e\ud06f\ud070\ud071\ud072\ud073\ud074\ud075\ud076\ud077\ud078\ud079\ud07a\ud07b\ud07c\ud07d\ud07e\ud07f\ud080\ud081\ud082\ud083\ud084\ud085\ud086\ud087\ud088\ud089\ud08a\ud08b\ud08c\ud08d\ud08e\ud08f\ud090\ud091\ud092\ud093\ud094\ud095\ud096\ud097\ud098\ud099\ud09a\ud09b\ud09c\ud09d\ud09e\ud09f\ud0a0\ud0a1\ud0a2\ud0a3\ud0a4\ud0a5\ud0a6\ud0a7\ud0a8\ud0a9\ud0aa\ud0ab\ud0ac\ud0ad\ud0ae\ud0af\ud0b0\ud0b1\ud0b2\ud0b3\ud0b4\ud0b5\ud0b6\ud0b7\ud0b8\ud0b9\ud0ba\ud0bb\ud0bc\ud0bd\ud0be\ud0bf\ud0c0\ud0c1\ud0c2\ud0c3\ud0c4\ud0c5\ud0c6\ud0c7\ud0c8\ud0c9\ud0ca\ud0cb\ud0cc\ud0cd\ud0ce\ud0cf\ud0d0\ud0d1\ud0d2\ud0d3\ud0d4\ud0d5\ud0d6\ud0d7\ud0d8\ud0d9\ud0da\ud0db\ud0dc\ud0dd\ud0de\ud0df\ud0e0\ud0e1\ud0e2\ud0e3\ud0e4\ud0e5\ud0e6\ud0e7\ud0e8\ud0e9\ud0ea\ud0eb\ud0ec\ud0ed\ud0ee\ud0ef\ud0f0\ud0f1\ud0f2\ud0f3\ud0f4\ud0f5\ud0f6\ud0f7\ud0f8\ud0f9\ud0fa\ud0fb\ud0fc\ud0fd\ud0fe\ud0ff\ud100\ud101\ud102\ud103\ud104\ud105\ud106\ud107\ud108\ud109\ud10a\ud10b\ud10c\ud10d\ud10e\ud10f\ud110\ud111\ud112\ud113\ud114\ud115\ud116\ud117\ud118\ud119\ud11a\ud11b\ud11c\ud11d\ud11e\ud11f\ud120\ud121\ud122\ud123\ud124\ud125\ud126\ud127\ud128\ud129\ud12a\ud12b\ud12c\ud12d\ud12e\ud12f\ud130\ud131\ud132\ud133\ud134\ud135\ud136\ud137\ud138\ud139\ud13a\ud13b\ud13c\ud13d\ud13e\ud13f\ud140\ud141\ud142\ud143\ud144\ud145\ud146\ud147\ud148\ud149\ud14a\ud14b\ud14c\ud14d\ud14e\ud14f\ud150\ud151\ud152\ud153\ud154\ud155\ud156\ud157\ud158\ud159\ud15a\ud15b\ud15c\ud15d\ud15e\ud15f\ud160\ud161\ud162\ud163\ud164\ud165\ud166\ud167\ud168\ud169\ud16a\ud16b\ud16c\ud16d\ud16e\ud16f\ud170\ud171\ud172\ud173\ud174\ud175\ud176\ud177\ud178\ud179\ud17a\ud17b\ud17c\ud17d\ud17e\ud17f\ud180\ud181\ud182\ud183\ud184\ud185\ud186\ud187\ud188\ud189\ud18a\ud18b\ud18c\ud18d\ud18e\ud18f\ud190\ud191\ud192\ud193\ud194\ud195\ud196\ud197\ud198\ud199\ud19a\ud19b\ud19c\ud19d\ud19e\ud19f\ud1a0\ud1a1\ud1a2\ud1a3\ud1a4\ud1a5\ud1a6\ud1a7\ud1a8\ud1a9\ud1aa\ud1ab\ud1ac\ud1ad\ud1ae\ud1af\ud1b0\ud1b1\ud1b2\ud1b3\ud1b4\ud1b5\ud1b6\ud1b7\ud1b8\ud1b9\ud1ba\ud1bb\ud1bc\ud1bd\ud1be\ud1bf\ud1c0\ud1c1\ud1c2\ud1c3\ud1c4\ud1c5\ud1c6\ud1c7\ud1c8\ud1c9\ud1ca\ud1cb\ud1cc\ud1cd\ud1ce\ud1cf\ud1d0\ud1d1\ud1d2\ud1d3\ud1d4\ud1d5\ud1d6\ud1d7\ud1d8\ud1d9\ud1da\ud1db\ud1dc\ud1dd\ud1de\ud1df\ud1e0\ud1e1\ud1e2\ud1e3\ud1e4\ud1e5\ud1e6\ud1e7\ud1e8\ud1e9\ud1ea\ud1eb\ud1ec\ud1ed\ud1ee\ud1ef\ud1f0\ud1f1\ud1f2\ud1f3\ud1f4\ud1f5\ud1f6\ud1f7\ud1f8\ud1f9\ud1fa\ud1fb\ud1fc\ud1fd\ud1fe\ud1ff\ud200\ud201\ud202\ud203\ud204\ud205\ud206\ud207\ud208\ud209\ud20a\ud20b\ud20c\ud20d\ud20e\ud20f\ud210\ud211\ud212\ud213\ud214\ud215\ud216\ud217\ud218\ud219\ud21a\ud21b\ud21c\ud21d\ud21e\ud21f\ud220\ud221\ud222\ud223\ud224\ud225\ud226\ud227\ud228\ud229\ud22a\ud22b\ud22c\ud22d\ud22e\ud22f\ud230\ud231\ud232\ud233\ud234\ud235\ud236\ud237\ud238\ud239\ud23a\ud23b\ud23c\ud23d\ud23e\ud23f\ud240\ud241\ud242\ud243\ud244\ud245\ud246\ud247\ud248\ud249\ud24a\ud24b\ud24c\ud24d\ud24e\ud24f\ud250\ud251\ud252\ud253\ud254\ud255\ud256\ud257\ud258\ud259\ud25a\ud25b\ud25c\ud25d\ud25e\ud25f\ud260\ud261\ud262\ud263\ud264\ud265\ud266\ud267\ud268\ud269\ud26a\ud26b\ud26c\ud26d\ud26e\ud26f\ud270\ud271\ud272\ud273\ud274\ud275\ud276\ud277\ud278\ud279\ud27a\ud27b\ud27c\ud27d\ud27e\ud27f\ud280\ud281\ud282\ud283\ud284\ud285\ud286\ud287\ud288\ud289\ud28a\ud28b\ud28c\ud28d\ud28e\ud28f\ud290\ud291\ud292\ud293\ud294\ud295\ud296\ud297\ud298\ud299\ud29a\ud29b\ud29c\ud29d\ud29e\ud29f\ud2a0\ud2a1\ud2a2\ud2a3\ud2a4\ud2a5\ud2a6\ud2a7\ud2a8\ud2a9\ud2aa\ud2ab\ud2ac\ud2ad\ud2ae\ud2af\ud2b0\ud2b1\ud2b2\ud2b3\ud2b4\ud2b5\ud2b6\ud2b7\ud2b8\ud2b9\ud2ba\ud2bb\ud2bc\ud2bd\ud2be\ud2bf\ud2c0\ud2c1\ud2c2\ud2c3\ud2c4\ud2c5\ud2c6\ud2c7\ud2c8\ud2c9\ud2ca\ud2cb\ud2cc\ud2cd\ud2ce\ud2cf\ud2d0\ud2d1\ud2d2\ud2d3\ud2d4\ud2d5\ud2d6\ud2d7\ud2d8\ud2d9\ud2da\ud2db\ud2dc\ud2dd\ud2de\ud2df\ud2e0\ud2e1\ud2e2\ud2e3\ud2e4\ud2e5\ud2e6\ud2e7\ud2e8\ud2e9\ud2ea\ud2eb\ud2ec\ud2ed\ud2ee\ud2ef\ud2f0\ud2f1\ud2f2\ud2f3\ud2f4\ud2f5\ud2f6\ud2f7\ud2f8\ud2f9\ud2fa\ud2fb\ud2fc\ud2fd\ud2fe\ud2ff\ud300\ud301\ud302\ud303\ud304\ud305\ud306\ud307\ud308\ud309\ud30a\ud30b\ud30c\ud30d\ud30e\ud30f\ud310\ud311\ud312\ud313\ud314\ud315\ud316\ud317\ud318\ud319\ud31a\ud31b\ud31c\ud31d\ud31e\ud31f\ud320\ud321\ud322\ud323\ud324\ud325\ud326\ud327\ud328\ud329\ud32a\ud32b\ud32c\ud32d\ud32e\ud32f\ud330\ud331\ud332\ud333\ud334\ud335\ud336\ud337\ud338\ud339\ud33a\ud33b\ud33c\ud33d\ud33e\ud33f\ud340\ud341\ud342\ud343\ud344\ud345\ud346\ud347\ud348\ud349\ud34a\ud34b\ud34c\ud34d\ud34e\ud34f\ud350\ud351\ud352\ud353\ud354\ud355\ud356\ud357\ud358\ud359\ud35a\ud35b\ud35c\ud35d\ud35e\ud35f\ud360\ud361\ud362\ud363\ud364\ud365\ud366\ud367\ud368\ud369\ud36a\ud36b\ud36c\ud36d\ud36e\ud36f\ud370\ud371\ud372\ud373\ud374\ud375\ud376\ud377\ud378\ud379\ud37a\ud37b\ud37c\ud37d\ud37e\ud37f\ud380\ud381\ud382\ud383\ud384\ud385\ud386\ud387\ud388\ud389\ud38a\ud38b\ud38c\ud38d\ud38e\ud38f\ud390\ud391\ud392\ud393\ud394\ud395\ud396\ud397\ud398\ud399\ud39a\ud39b\ud39c\ud39d\ud39e\ud39f\ud3a0\ud3a1\ud3a2\ud3a3\ud3a4\ud3a5\ud3a6\ud3a7\ud3a8\ud3a9\ud3aa\ud3ab\ud3ac\ud3ad\ud3ae\ud3af\ud3b0\ud3b1\ud3b2\ud3b3\ud3b4\ud3b5\ud3b6\ud3b7\ud3b8\ud3b9\ud3ba\ud3bb\ud3bc\ud3bd\ud3be\ud3bf\ud3c0\ud3c1\ud3c2\ud3c3\ud3c4\ud3c5\ud3c6\ud3c7\ud3c8\ud3c9\ud3ca\ud3cb\ud3cc\ud3cd\ud3ce\ud3cf\ud3d0\ud3d1\ud3d2\ud3d3\ud3d4\ud3d5\ud3d6\ud3d7\ud3d8\ud3d9\ud3da\ud3db\ud3dc\ud3dd\ud3de\ud3df\ud3e0\ud3e1\ud3e2\ud3e3\ud3e4\ud3e5\ud3e6\ud3e7\ud3e8\ud3e9\ud3ea\ud3eb\ud3ec\ud3ed\ud3ee\ud3ef\ud3f0\ud3f1\ud3f2\ud3f3\ud3f4\ud3f5\ud3f6\ud3f7\ud3f8\ud3f9\ud3fa\ud3fb\ud3fc\ud3fd\ud3fe\ud3ff\ud400\ud401\ud402\ud403\ud404\ud405\ud406\ud407\ud408\ud409\ud40a\ud40b\ud40c\ud40d\ud40e\ud40f\ud410\ud411\ud412\ud413\ud414\ud415\ud416\ud417\ud418\ud419\ud41a\ud41b\ud41c\ud41d\ud41e\ud41f\ud420\ud421\ud422\ud423\ud424\ud425\ud426\ud427\ud428\ud429\ud42a\ud42b\ud42c\ud42d\ud42e\ud42f\ud430\ud431\ud432\ud433\ud434\ud435\ud436\ud437\ud438\ud439\ud43a\ud43b\ud43c\ud43d\ud43e\ud43f\ud440\ud441\ud442\ud443\ud444\ud445\ud446\ud447\ud448\ud449\ud44a\ud44b\ud44c\ud44d\ud44e\ud44f\ud450\ud451\ud452\ud453\ud454\ud455\ud456\ud457\ud458\ud459\ud45a\ud45b\ud45c\ud45d\ud45e\ud45f\ud460\ud461\ud462\ud463\ud464\ud465\ud466\ud467\ud468\ud469\ud46a\ud46b\ud46c\ud46d\ud46e\ud46f\ud470\ud471\ud472\ud473\ud474\ud475\ud476\ud477\ud478\ud479\ud47a\ud47b\ud47c\ud47d\ud47e\ud47f\ud480\ud481\ud482\ud483\ud484\ud485\ud486\ud487\ud488\ud489\ud48a\ud48b\ud48c\ud48d\ud48e\ud48f\ud490\ud491\ud492\ud493\ud494\ud495\ud496\ud497\ud498\ud499\ud49a\ud49b\ud49c\ud49d\ud49e\ud49f\ud4a0\ud4a1\ud4a2\ud4a3\ud4a4\ud4a5\ud4a6\ud4a7\ud4a8\ud4a9\ud4aa\ud4ab\ud4ac\ud4ad\ud4ae\ud4af\ud4b0\ud4b1\ud4b2\ud4b3\ud4b4\ud4b5\ud4b6\ud4b7\ud4b8\ud4b9\ud4ba\ud4bb\ud4bc\ud4bd\ud4be\ud4bf\ud4c0\ud4c1\ud4c2\ud4c3\ud4c4\ud4c5\ud4c6\ud4c7\ud4c8\ud4c9\ud4ca\ud4cb\ud4cc\ud4cd\ud4ce\ud4cf\ud4d0\ud4d1\ud4d2\ud4d3\ud4d4\ud4d5\ud4d6\ud4d7\ud4d8\ud4d9\ud4da\ud4db\ud4dc\ud4dd\ud4de\ud4df\ud4e0\ud4e1\ud4e2\ud4e3\ud4e4\ud4e5\ud4e6\ud4e7\ud4e8\ud4e9\ud4ea\ud4eb\ud4ec\ud4ed\ud4ee\ud4ef\ud4f0\ud4f1\ud4f2\ud4f3\ud4f4\ud4f5\ud4f6\ud4f7\ud4f8\ud4f9\ud4fa\ud4fb\ud4fc\ud4fd\ud4fe\ud4ff\ud500\ud501\ud502\ud503\ud504\ud505\ud506\ud507\ud508\ud509\ud50a\ud50b\ud50c\ud50d\ud50e\ud50f\ud510\ud511\ud512\ud513\ud514\ud515\ud516\ud517\ud518\ud519\ud51a\ud51b\ud51c\ud51d\ud51e\ud51f\ud520\ud521\ud522\ud523\ud524\ud525\ud526\ud527\ud528\ud529\ud52a\ud52b\ud52c\ud52d\ud52e\ud52f\ud530\ud531\ud532\ud533\ud534\ud535\ud536\ud537\ud538\ud539\ud53a\ud53b\ud53c\ud53d\ud53e\ud53f\ud540\ud541\ud542\ud543\ud544\ud545\ud546\ud547\ud548\ud549\ud54a\ud54b\ud54c\ud54d\ud54e\ud54f\ud550\ud551\ud552\ud553\ud554\ud555\ud556\ud557\ud558\ud559\ud55a\ud55b\ud55c\ud55d\ud55e\ud55f\ud560\ud561\ud562\ud563\ud564\ud565\ud566\ud567\ud568\ud569\ud56a\ud56b\ud56c\ud56d\ud56e\ud56f\ud570\ud571\ud572\ud573\ud574\ud575\ud576\ud577\ud578\ud579\ud57a\ud57b\ud57c\ud57d\ud57e\ud57f\ud580\ud581\ud582\ud583\ud584\ud585\ud586\ud587\ud588\ud589\ud58a\ud58b\ud58c\ud58d\ud58e\ud58f\ud590\ud591\ud592\ud593\ud594\ud595\ud596\ud597\ud598\ud599\ud59a\ud59b\ud59c\ud59d\ud59e\ud59f\ud5a0\ud5a1\ud5a2\ud5a3\ud5a4\ud5a5\ud5a6\ud5a7\ud5a8\ud5a9\ud5aa\ud5ab\ud5ac\ud5ad\ud5ae\ud5af\ud5b0\ud5b1\ud5b2\ud5b3\ud5b4\ud5b5\ud5b6\ud5b7\ud5b8\ud5b9\ud5ba\ud5bb\ud5bc\ud5bd\ud5be\ud5bf\ud5c0\ud5c1\ud5c2\ud5c3\ud5c4\ud5c5\ud5c6\ud5c7\ud5c8\ud5c9\ud5ca\ud5cb\ud5cc\ud5cd\ud5ce\ud5cf\ud5d0\ud5d1\ud5d2\ud5d3\ud5d4\ud5d5\ud5d6\ud5d7\ud5d8\ud5d9\ud5da\ud5db\ud5dc\ud5dd\ud5de\ud5df\ud5e0\ud5e1\ud5e2\ud5e3\ud5e4\ud5e5\ud5e6\ud5e7\ud5e8\ud5e9\ud5ea\ud5eb\ud5ec\ud5ed\ud5ee\ud5ef\ud5f0\ud5f1\ud5f2\ud5f3\ud5f4\ud5f5\ud5f6\ud5f7\ud5f8\ud5f9\ud5fa\ud5fb\ud5fc\ud5fd\ud5fe\ud5ff\ud600\ud601\ud602\ud603\ud604\ud605\ud606\ud607\ud608\ud609\ud60a\ud60b\ud60c\ud60d\ud60e\ud60f\ud610\ud611\ud612\ud613\ud614\ud615\ud616\ud617\ud618\ud619\ud61a\ud61b\ud61c\ud61d\ud61e\ud61f\ud620\ud621\ud622\ud623\ud624\ud625\ud626\ud627\ud628\ud629\ud62a\ud62b\ud62c\ud62d\ud62e\ud62f\ud630\ud631\ud632\ud633\ud634\ud635\ud636\ud637\ud638\ud639\ud63a\ud63b\ud63c\ud63d\ud63e\ud63f\ud640\ud641\ud642\ud643\ud644\ud645\ud646\ud647\ud648\ud649\ud64a\ud64b\ud64c\ud64d\ud64e\ud64f\ud650\ud651\ud652\ud653\ud654\ud655\ud656\ud657\ud658\ud659\ud65a\ud65b\ud65c\ud65d\ud65e\ud65f\ud660\ud661\ud662\ud663\ud664\ud665\ud666\ud667\ud668\ud669\ud66a\ud66b\ud66c\ud66d\ud66e\ud66f\ud670\ud671\ud672\ud673\ud674\ud675\ud676\ud677\ud678\ud679\ud67a\ud67b\ud67c\ud67d\ud67e\ud67f\ud680\ud681\ud682\ud683\ud684\ud685\ud686\ud687\ud688\ud689\ud68a\ud68b\ud68c\ud68d\ud68e\ud68f\ud690\ud691\ud692\ud693\ud694\ud695\ud696\ud697\ud698\ud699\ud69a\ud69b\ud69c\ud69d\ud69e\ud69f\ud6a0\ud6a1\ud6a2\ud6a3\ud6a4\ud6a5\ud6a6\ud6a7\ud6a8\ud6a9\ud6aa\ud6ab\ud6ac\ud6ad\ud6ae\ud6af\ud6b0\ud6b1\ud6b2\ud6b3\ud6b4\ud6b5\ud6b6\ud6b7\ud6b8\ud6b9\ud6ba\ud6bb\ud6bc\ud6bd\ud6be\ud6bf\ud6c0\ud6c1\ud6c2\ud6c3\ud6c4\ud6c5\ud6c6\ud6c7\ud6c8\ud6c9\ud6ca\ud6cb\ud6cc\ud6cd\ud6ce\ud6cf\ud6d0\ud6d1\ud6d2\ud6d3\ud6d4\ud6d5\ud6d6\ud6d7\ud6d8\ud6d9\ud6da\ud6db\ud6dc\ud6dd\ud6de\ud6df\ud6e0\ud6e1\ud6e2\ud6e3\ud6e4\ud6e5\ud6e6\ud6e7\ud6e8\ud6e9\ud6ea\ud6eb\ud6ec\ud6ed\ud6ee\ud6ef\ud6f0\ud6f1\ud6f2\ud6f3\ud6f4\ud6f5\ud6f6\ud6f7\ud6f8\ud6f9\ud6fa\ud6fb\ud6fc\ud6fd\ud6fe\ud6ff\ud700\ud701\ud702\ud703\ud704\ud705\ud706\ud707\ud708\ud709\ud70a\ud70b\ud70c\ud70d\ud70e\ud70f\ud710\ud711\ud712\ud713\ud714\ud715\ud716\ud717\ud718\ud719\ud71a\ud71b\ud71c\ud71d\ud71e\ud71f\ud720\ud721\ud722\ud723\ud724\ud725\ud726\ud727\ud728\ud729\ud72a\ud72b\ud72c\ud72d\ud72e\ud72f\ud730\ud731\ud732\ud733\ud734\ud735\ud736\ud737\ud738\ud739\ud73a\ud73b\ud73c\ud73d\ud73e\ud73f\ud740\ud741\ud742\ud743\ud744\ud745\ud746\ud747\ud748\ud749\ud74a\ud74b\ud74c\ud74d\ud74e\ud74f\ud750\ud751\ud752\ud753\ud754\ud755\ud756\ud757\ud758\ud759\ud75a\ud75b\ud75c\ud75d\ud75e\ud75f\ud760\ud761\ud762\ud763\ud764\ud765\ud766\ud767\ud768\ud769\ud76a\ud76b\ud76c\ud76d\ud76e\ud76f\ud770\ud771\ud772\ud773\ud774\ud775\ud776\ud777\ud778\ud779\ud77a\ud77b\ud77c\ud77d\ud77e\ud77f\ud780\ud781\ud782\ud783\ud784\ud785\ud786\ud787\ud788\ud789\ud78a\ud78b\ud78c\ud78d\ud78e\ud78f\ud790\ud791\ud792\ud793\ud794\ud795\ud796\ud797\ud798\ud799\ud79a\ud79b\ud79c\ud79d\ud79e\ud79f\ud7a0\ud7a1\ud7a2\ud7a3\uf900\uf901\uf902\uf903\uf904\uf905\uf906\uf907\uf908\uf909\uf90a\uf90b\uf90c\uf90d\uf90e\uf90f\uf910\uf911\uf912\uf913\uf914\uf915\uf916\uf917\uf918\uf919\uf91a\uf91b\uf91c\uf91d\uf91e\uf91f\uf920\uf921\uf922\uf923\uf924\uf925\uf926\uf927\uf928\uf929\uf92a\uf92b\uf92c\uf92d\uf92e\uf92f\uf930\uf931\uf932\uf933\uf934\uf935\uf936\uf937\uf938\uf939\uf93a\uf93b\uf93c\uf93d\uf93e\uf93f\uf940\uf941\uf942\uf943\uf944\uf945\uf946\uf947\uf948\uf949\uf94a\uf94b\uf94c\uf94d\uf94e\uf94f\uf950\uf951\uf952\uf953\uf954\uf955\uf956\uf957\uf958\uf959\uf95a\uf95b\uf95c\uf95d\uf95e\uf95f\uf960\uf961\uf962\uf963\uf964\uf965\uf966\uf967\uf968\uf969\uf96a\uf96b\uf96c\uf96d\uf96e\uf96f\uf970\uf971\uf972\uf973\uf974\uf975\uf976\uf977\uf978\uf979\uf97a\uf97b\uf97c\uf97d\uf97e\uf97f\uf980\uf981\uf982\uf983\uf984\uf985\uf986\uf987\uf988\uf989\uf98a\uf98b\uf98c\uf98d\uf98e\uf98f\uf990\uf991\uf992\uf993\uf994\uf995\uf996\uf997\uf998\uf999\uf99a\uf99b\uf99c\uf99d\uf99e\uf99f\uf9a0\uf9a1\uf9a2\uf9a3\uf9a4\uf9a5\uf9a6\uf9a7\uf9a8\uf9a9\uf9aa\uf9ab\uf9ac\uf9ad\uf9ae\uf9af\uf9b0\uf9b1\uf9b2\uf9b3\uf9b4\uf9b5\uf9b6\uf9b7\uf9b8\uf9b9\uf9ba\uf9bb\uf9bc\uf9bd\uf9be\uf9bf\uf9c0\uf9c1\uf9c2\uf9c3\uf9c4\uf9c5\uf9c6\uf9c7\uf9c8\uf9c9\uf9ca\uf9cb\uf9cc\uf9cd\uf9ce\uf9cf\uf9d0\uf9d1\uf9d2\uf9d3\uf9d4\uf9d5\uf9d6\uf9d7\uf9d8\uf9d9\uf9da\uf9db\uf9dc\uf9dd\uf9de\uf9df\uf9e0\uf9e1\uf9e2\uf9e3\uf9e4\uf9e5\uf9e6\uf9e7\uf9e8\uf9e9\uf9ea\uf9eb\uf9ec\uf9ed\uf9ee\uf9ef\uf9f0\uf9f1\uf9f2\uf9f3\uf9f4\uf9f5\uf9f6\uf9f7\uf9f8\uf9f9\uf9fa\uf9fb\uf9fc\uf9fd\uf9fe\uf9ff\ufa00\ufa01\ufa02\ufa03\ufa04\ufa05\ufa06\ufa07\ufa08\ufa09\ufa0a\ufa0b\ufa0c\ufa0d\ufa0e\ufa0f\ufa10\ufa11\ufa12\ufa13\ufa14\ufa15\ufa16\ufa17\ufa18\ufa19\ufa1a\ufa1b\ufa1c\ufa1d\ufa1e\ufa1f\ufa20\ufa21\ufa22\ufa23\ufa24\ufa25\ufa26\ufa27\ufa28\ufa29\ufa2a\ufa2b\ufa2c\ufa2d\ufa30\ufa31\ufa32\ufa33\ufa34\ufa35\ufa36\ufa37\ufa38\ufa39\ufa3a\ufa3b\ufa3c\ufa3d\ufa3e\ufa3f\ufa40\ufa41\ufa42\ufa43\ufa44\ufa45\ufa46\ufa47\ufa48\ufa49\ufa4a\ufa4b\ufa4c\ufa4d\ufa4e\ufa4f\ufa50\ufa51\ufa52\ufa53\ufa54\ufa55\ufa56\ufa57\ufa58\ufa59\ufa5a\ufa5b\ufa5c\ufa5d\ufa5e\ufa5f\ufa60\ufa61\ufa62\ufa63\ufa64\ufa65\ufa66\ufa67\ufa68\ufa69\ufa6a\ufa70\ufa71\ufa72\ufa73\ufa74\ufa75\ufa76\ufa77\ufa78\ufa79\ufa7a\ufa7b\ufa7c\ufa7d\ufa7e\ufa7f\ufa80\ufa81\ufa82\ufa83\ufa84\ufa85\ufa86\ufa87\ufa88\ufa89\ufa8a\ufa8b\ufa8c\ufa8d\ufa8e\ufa8f\ufa90\ufa91\ufa92\ufa93\ufa94\ufa95\ufa96\ufa97\ufa98\ufa99\ufa9a\ufa9b\ufa9c\ufa9d\ufa9e\ufa9f\ufaa0\ufaa1\ufaa2\ufaa3\ufaa4\ufaa5\ufaa6\ufaa7\ufaa8\ufaa9\ufaaa\ufaab\ufaac\ufaad\ufaae\ufaaf\ufab0\ufab1\ufab2\ufab3\ufab4\ufab5\ufab6\ufab7\ufab8\ufab9\ufaba\ufabb\ufabc\ufabd\ufabe\ufabf\ufac0\ufac1\ufac2\ufac3\ufac4\ufac5\ufac6\ufac7\ufac8\ufac9\ufaca\ufacb\ufacc\ufacd\uface\ufacf\ufad0\ufad1\ufad2\ufad3\ufad4\ufad5\ufad6\ufad7\ufad8\ufad9\ufb1d\ufb1f\ufb20\ufb21\ufb22\ufb23\ufb24\ufb25\ufb26\ufb27\ufb28\ufb2a\ufb2b\ufb2c\ufb2d\ufb2e\ufb2f\ufb30\ufb31\ufb32\ufb33\ufb34\ufb35\ufb36\ufb38\ufb39\ufb3a\ufb3b\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46\ufb47\ufb48\ufb49\ufb4a\ufb4b\ufb4c\ufb4d\ufb4e\ufb4f\ufb50\ufb51\ufb52\ufb53\ufb54\ufb55\ufb56\ufb57\ufb58\ufb59\ufb5a\ufb5b\ufb5c\ufb5d\ufb5e\ufb5f\ufb60\ufb61\ufb62\ufb63\ufb64\ufb65\ufb66\ufb67\ufb68\ufb69\ufb6a\ufb6b\ufb6c\ufb6d\ufb6e\ufb6f\ufb70\ufb71\ufb72\ufb73\ufb74\ufb75\ufb76\ufb77\ufb78\ufb79\ufb7a\ufb7b\ufb7c\ufb7d\ufb7e\ufb7f\ufb80\ufb81\ufb82\ufb83\ufb84\ufb85\ufb86\ufb87\ufb88\ufb89\ufb8a\ufb8b\ufb8c\ufb8d\ufb8e\ufb8f\ufb90\ufb91\ufb92\ufb93\ufb94\ufb95\ufb96\ufb97\ufb98\ufb99\ufb9a\ufb9b\ufb9c\ufb9d\ufb9e\ufb9f\ufba0\ufba1\ufba2\ufba3\ufba4\ufba5\ufba6\ufba7\ufba8\ufba9\ufbaa\ufbab\ufbac\ufbad\ufbae\ufbaf\ufbb0\ufbb1\ufbd3\ufbd4\ufbd5\ufbd6\ufbd7\ufbd8\ufbd9\ufbda\ufbdb\ufbdc\ufbdd\ufbde\ufbdf\ufbe0\ufbe1\ufbe2\ufbe3\ufbe4\ufbe5\ufbe6\ufbe7\ufbe8\ufbe9\ufbea\ufbeb\ufbec\ufbed\ufbee\ufbef\ufbf0\ufbf1\ufbf2\ufbf3\ufbf4\ufbf5\ufbf6\ufbf7\ufbf8\ufbf9\ufbfa\ufbfb\ufbfc\ufbfd\ufbfe\ufbff\ufc00\ufc01\ufc02\ufc03\ufc04\ufc05\ufc06\ufc07\ufc08\ufc09\ufc0a\ufc0b\ufc0c\ufc0d\ufc0e\ufc0f\ufc10\ufc11\ufc12\ufc13\ufc14\ufc15\ufc16\ufc17\ufc18\ufc19\ufc1a\ufc1b\ufc1c\ufc1d\ufc1e\ufc1f\ufc20\ufc21\ufc22\ufc23\ufc24\ufc25\ufc26\ufc27\ufc28\ufc29\ufc2a\ufc2b\ufc2c\ufc2d\ufc2e\ufc2f\ufc30\ufc31\ufc32\ufc33\ufc34\ufc35\ufc36\ufc37\ufc38\ufc39\ufc3a\ufc3b\ufc3c\ufc3d\ufc3e\ufc3f\ufc40\ufc41\ufc42\ufc43\ufc44\ufc45\ufc46\ufc47\ufc48\ufc49\ufc4a\ufc4b\ufc4c\ufc4d\ufc4e\ufc4f\ufc50\ufc51\ufc52\ufc53\ufc54\ufc55\ufc56\ufc57\ufc58\ufc59\ufc5a\ufc5b\ufc5c\ufc5d\ufc5e\ufc5f\ufc60\ufc61\ufc62\ufc63\ufc64\ufc65\ufc66\ufc67\ufc68\ufc69\ufc6a\ufc6b\ufc6c\ufc6d\ufc6e\ufc6f\ufc70\ufc71\ufc72\ufc73\ufc74\ufc75\ufc76\ufc77\ufc78\ufc79\ufc7a\ufc7b\ufc7c\ufc7d\ufc7e\ufc7f\ufc80\ufc81\ufc82\ufc83\ufc84\ufc85\ufc86\ufc87\ufc88\ufc89\ufc8a\ufc8b\ufc8c\ufc8d\ufc8e\ufc8f\ufc90\ufc91\ufc92\ufc93\ufc94\ufc95\ufc96\ufc97\ufc98\ufc99\ufc9a\ufc9b\ufc9c\ufc9d\ufc9e\ufc9f\ufca0\ufca1\ufca2\ufca3\ufca4\ufca5\ufca6\ufca7\ufca8\ufca9\ufcaa\ufcab\ufcac\ufcad\ufcae\ufcaf\ufcb0\ufcb1\ufcb2\ufcb3\ufcb4\ufcb5\ufcb6\ufcb7\ufcb8\ufcb9\ufcba\ufcbb\ufcbc\ufcbd\ufcbe\ufcbf\ufcc0\ufcc1\ufcc2\ufcc3\ufcc4\ufcc5\ufcc6\ufcc7\ufcc8\ufcc9\ufcca\ufccb\ufccc\ufccd\ufcce\ufccf\ufcd0\ufcd1\ufcd2\ufcd3\ufcd4\ufcd5\ufcd6\ufcd7\ufcd8\ufcd9\ufcda\ufcdb\ufcdc\ufcdd\ufcde\ufcdf\ufce0\ufce1\ufce2\ufce3\ufce4\ufce5\ufce6\ufce7\ufce8\ufce9\ufcea\ufceb\ufcec\ufced\ufcee\ufcef\ufcf0\ufcf1\ufcf2\ufcf3\ufcf4\ufcf5\ufcf6\ufcf7\ufcf8\ufcf9\ufcfa\ufcfb\ufcfc\ufcfd\ufcfe\ufcff\ufd00\ufd01\ufd02\ufd03\ufd04\ufd05\ufd06\ufd07\ufd08\ufd09\ufd0a\ufd0b\ufd0c\ufd0d\ufd0e\ufd0f\ufd10\ufd11\ufd12\ufd13\ufd14\ufd15\ufd16\ufd17\ufd18\ufd19\ufd1a\ufd1b\ufd1c\ufd1d\ufd1e\ufd1f\ufd20\ufd21\ufd22\ufd23\ufd24\ufd25\ufd26\ufd27\ufd28\ufd29\ufd2a\ufd2b\ufd2c\ufd2d\ufd2e\ufd2f\ufd30\ufd31\ufd32\ufd33\ufd34\ufd35\ufd36\ufd37\ufd38\ufd39\ufd3a\ufd3b\ufd3c\ufd3d\ufd50\ufd51\ufd52\ufd53\ufd54\ufd55\ufd56\ufd57\ufd58\ufd59\ufd5a\ufd5b\ufd5c\ufd5d\ufd5e\ufd5f\ufd60\ufd61\ufd62\ufd63\ufd64\ufd65\ufd66\ufd67\ufd68\ufd69\ufd6a\ufd6b\ufd6c\ufd6d\ufd6e\ufd6f\ufd70\ufd71\ufd72\ufd73\ufd74\ufd75\ufd76\ufd77\ufd78\ufd79\ufd7a\ufd7b\ufd7c\ufd7d\ufd7e\ufd7f\ufd80\ufd81\ufd82\ufd83\ufd84\ufd85\ufd86\ufd87\ufd88\ufd89\ufd8a\ufd8b\ufd8c\ufd8d\ufd8e\ufd8f\ufd92\ufd93\ufd94\ufd95\ufd96\ufd97\ufd98\ufd99\ufd9a\ufd9b\ufd9c\ufd9d\ufd9e\ufd9f\ufda0\ufda1\ufda2\ufda3\ufda4\ufda5\ufda6\ufda7\ufda8\ufda9\ufdaa\ufdab\ufdac\ufdad\ufdae\ufdaf\ufdb0\ufdb1\ufdb2\ufdb3\ufdb4\ufdb5\ufdb6\ufdb7\ufdb8\ufdb9\ufdba\ufdbb\ufdbc\ufdbd\ufdbe\ufdbf\ufdc0\ufdc1\ufdc2\ufdc3\ufdc4\ufdc5\ufdc6\ufdc7\ufdf0\ufdf1\ufdf2\ufdf3\ufdf4\ufdf5\ufdf6\ufdf7\ufdf8\ufdf9\ufdfa\ufdfb\ufe70\ufe71\ufe72\ufe73\ufe74\ufe76\ufe77\ufe78\ufe79\ufe7a\ufe7b\ufe7c\ufe7d\ufe7e\ufe7f\ufe80\ufe81\ufe82\ufe83\ufe84\ufe85\ufe86\ufe87\ufe88\ufe89\ufe8a\ufe8b\ufe8c\ufe8d\ufe8e\ufe8f\ufe90\ufe91\ufe92\ufe93\ufe94\ufe95\ufe96\ufe97\ufe98\ufe99\ufe9a\ufe9b\ufe9c\ufe9d\ufe9e\ufe9f\ufea0\ufea1\ufea2\ufea3\ufea4\ufea5\ufea6\ufea7\ufea8\ufea9\ufeaa\ufeab\ufeac\ufead\ufeae\ufeaf\ufeb0\ufeb1\ufeb2\ufeb3\ufeb4\ufeb5\ufeb6\ufeb7\ufeb8\ufeb9\ufeba\ufebb\ufebc\ufebd\ufebe\ufebf\ufec0\ufec1\ufec2\ufec3\ufec4\ufec5\ufec6\ufec7\ufec8\ufec9\ufeca\ufecb\ufecc\ufecd\ufece\ufecf\ufed0\ufed1\ufed2\ufed3\ufed4\ufed5\ufed6\ufed7\ufed8\ufed9\ufeda\ufedb\ufedc\ufedd\ufede\ufedf\ufee0\ufee1\ufee2\ufee3\ufee4\ufee5\ufee6\ufee7\ufee8\ufee9\ufeea\ufeeb\ufeec\ufeed\ufeee\ufeef\ufef0\ufef1\ufef2\ufef3\ufef4\ufef5\ufef6\ufef7\ufef8\ufef9\ufefa\ufefb\ufefc\uff66\uff67\uff68\uff69\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff80\uff81\uff82\uff83\uff84\uff85\uff86\uff87\uff88\uff89\uff8a\uff8b\uff8c\uff8d\uff8e\uff8f\uff90\uff91\uff92\uff93\uff94\uff95\uff96\uff97\uff98\uff99\uff9a\uff9b\uff9c\uff9d\uffa0\uffa1\uffa2\uffa3\uffa4\uffa5\uffa6\uffa7\uffa8\uffa9\uffaa\uffab\uffac\uffad\uffae\uffaf\uffb0\uffb1\uffb2\uffb3\uffb4\uffb5\uffb6\uffb7\uffb8\uffb9\uffba\uffbb\uffbc\uffbd\uffbe\uffc2\uffc3\uffc4\uffc5\uffc6\uffc7\uffca\uffcb\uffcc\uffcd\uffce\uffcf\uffd2\uffd3\uffd4\uffd5\uffd6\uffd7\uffda\uffdb\uffdc'
-
-Lt = u'\u01c5\u01c8\u01cb\u01f2\u1f88\u1f89\u1f8a\u1f8b\u1f8c\u1f8d\u1f8e\u1f8f\u1f98\u1f99\u1f9a\u1f9b\u1f9c\u1f9d\u1f9e\u1f9f\u1fa8\u1fa9\u1faa\u1fab\u1fac\u1fad\u1fae\u1faf\u1fbc\u1fcc\u1ffc'
-
-Lu = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a\u023b\u023d\u023e\u0241\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9\u03fa\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\u2c00\u2c01\u2c02\u2c03\u2c04\u2c05\u2c06\u2c07\u2c08\u2c09\u2c0a\u2c0b\u2c0c\u2c0d\u2c0e\u2c0f\u2c10\u2c11\u2c12\u2c13\u2c14\u2c15\u2c16\u2c17\u2c18\u2c19\u2c1a\u2c1b\u2c1c\u2c1d\u2c1e\u2c1f\u2c20\u2c21\u2c22\u2c23\u2c24\u2c25\u2c26\u2c27\u2c28\u2c29\u2c2a\u2c2b\u2c2c\u2c2d\u2c2e\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a'
-
-Mc = u'\u0903\u093e\u093f\u0940\u0949\u094a\u094b\u094c\u0982\u0983\u09be\u09bf\u09c0\u09c7\u09c8\u09cb\u09cc\u09d7\u0a03\u0a3e\u0a3f\u0a40\u0a83\u0abe\u0abf\u0ac0\u0ac9\u0acb\u0acc\u0b02\u0b03\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6\u0bc7\u0bc8\u0bca\u0bcb\u0bcc\u0bd7\u0c01\u0c02\u0c03\u0c41\u0c42\u0c43\u0c44\u0c82\u0c83\u0cbe\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc7\u0cc8\u0cca\u0ccb\u0cd5\u0cd6\u0d02\u0d03\u0d3e\u0d3f\u0d40\u0d46\u0d47\u0d48\u0d4a\u0d4b\u0d4c\u0d57\u0d82\u0d83\u0dcf\u0dd0\u0dd1\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde\u0ddf\u0df2\u0df3\u0f3e\u0f3f\u0f7f\u102c\u1031\u1038\u1056\u1057\u17b6\u17be\u17bf\u17c0\u17c1\u17c2\u17c3\u17c4\u17c5\u17c7\u17c8\u1923\u1924\u1925\u1926\u1929\u192a\u192b\u1930\u1931\u1933\u1934\u1935\u1936\u1937\u1938\u19b0\u19b1\u19b2\u19b3\u19b4\u19b5\u19b6\u19b7\u19b8\u19b9\u19ba\u19bb\u19bc\u19bd\u19be\u19bf\u19c0\u19c8\u19c9\u1a19\u1a1a\u1a1b\ua802\ua823\ua824\ua827'
-
-Me = u'\u0488\u0489\u06de\u20dd\u20de\u20df\u20e0\u20e2\u20e3\u20e4'
-
-Mn = u'\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0483\u0484\u0485\u0486\u0591\u0592\u0593\u0594\u0595\u0596\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05bb\u05bc\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610\u0611\u0612\u0613\u0614\u0615\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e\u0670\u06d6\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e7\u06e8\u06ea\u06eb\u06ec\u06ed\u0711\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u0901\u0902\u093c\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u094d\u0951\u0952\u0953\u0954\u0962\u0963\u0981\u09bc\u09c1\u09c2\u09c3\u09c4\u09cd\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b\u0a4c\u0a4d\u0a70\u0a71\u0a81\u0a82\u0abc\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3f\u0b41\u0b42\u0b43\u0b4d\u0b56\u0b82\u0bc0\u0bcd\u0c3e\u0c3f\u0c40\u0c46\u0c47\u0c48\u0c4a\u0c4b\u0c4c\u0c4d\u0c55\u0c56\u0cbc\u0cbf\u0cc6\u0ccc\u0ccd\u0d41\u0d42\u0d43\u0d4d\u0dca\u0dd2\u0dd3\u0dd4\u0dd6\u0e31\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0eb1\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0ebb\u0ebc\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f80\u0f81\u0f82\u0f83\u0f84\u0f86\u0f87\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96\u0f97\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fc6\u102d\u102e\u102f\u1030\u1032\u1036\u1037\u1039\u1058\u1059\u135f\u1712\u1713\u1714\u1732\u1733\u1734\u1752\u1753\u1772\u1773\u17b7\u17b8\u17b9\u17ba\u17bb\u17bc\u17bd\u17c6\u17c9\u17ca\u17cb\u17cc\u17cd\u17ce\u17cf\u17d0\u17d1\u17d2\u17d3\u17dd\u180b\u180c\u180d\u18a9\u1920\u1921\u1922\u1927\u1928\u1932\u1939\u193a\u193b\u1a17\u1a18\u1dc0\u1dc1\u1dc2\u1dc3\u20d0\u20d1\u20d2\u20d3\u20d4\u20d5\u20d6\u20d7\u20d8\u20d9\u20da\u20db\u20dc\u20e1\u20e5\u20e6\u20e7\u20e8\u20e9\u20ea\u20eb\u302a\u302b\u302c\u302d\u302e\u302f\u3099\u309a\ua806\ua80b\ua825\ua826\ufb1e\ufe00\ufe01\ufe02\ufe03\ufe04\ufe05\ufe06\ufe07\ufe08\ufe09\ufe0a\ufe0b\ufe0c\ufe0d\ufe0e\ufe0f\ufe20\ufe21\ufe22\ufe23'
-
-Nd = u'0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u1946\u1947\u1948\u1949\u194a\u194b\u194c\u194d\u194e\u194f\u19d0\u19d1\u19d2\u19d3\u19d4\u19d5\u19d6\u19d7\u19d8\u19d9\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19'
-
-Nl = u'\u16ee\u16ef\u16f0\u2160\u2161\u2162\u2163\u2164\u2165\u2166\u2167\u2168\u2169\u216a\u216b\u216c\u216d\u216e\u216f\u2170\u2171\u2172\u2173\u2174\u2175\u2176\u2177\u2178\u2179\u217a\u217b\u217c\u217d\u217e\u217f\u2180\u2181\u2182\u2183\u3007\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u3038\u3039\u303a'
-
-No = u'\xb2\xb3\xb9\xbc\xbd\xbe\u09f4\u09f5\u09f6\u09f7\u09f8\u09f9\u0bf0\u0bf1\u0bf2\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32\u0f33\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u1372\u1373\u1374\u1375\u1376\u1377\u1378\u1379\u137a\u137b\u137c\u17f0\u17f1\u17f2\u17f3\u17f4\u17f5\u17f6\u17f7\u17f8\u17f9\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2153\u2154\u2155\u2156\u2157\u2158\u2159\u215a\u215b\u215c\u215d\u215e\u215f\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246a\u246b\u246c\u246d\u246e\u246f\u2470\u2471\u2472\u2473\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u247d\u247e\u247f\u2480\u2481\u2482\u2483\u2484\u2485\u2486\u2487\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u2491\u2492\u2493\u2494\u2495\u2496\u2497\u2498\u2499\u249a\u249b\u24ea\u24eb\u24ec\u24ed\u24ee\u24ef\u24f0\u24f1\u24f2\u24f3\u24f4\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u24fe\u24ff\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u277f\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u2789\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\u2793\u2cfd\u3192\u3193\u3194\u3195\u3220\u3221\u3222\u3223\u3224\u3225\u3226\u3227\u3228\u3229\u3251\u3252\u3253\u3254\u3255\u3256\u3257\u3258\u3259\u325a\u325b\u325c\u325d\u325e\u325f\u3280\u3281\u3282\u3283\u3284\u3285\u3286\u3287\u3288\u3289\u32b1\u32b2\u32b3\u32b4\u32b5\u32b6\u32b7\u32b8\u32b9\u32ba\u32bb\u32bc\u32bd\u32be\u32bf'
-
-Pc = u'_\u203f\u2040\u2054\ufe33\ufe34\ufe4d\ufe4e\ufe4f\uff3f'
-
-Pd = u'-\u058a\u1806\u2010\u2011\u2012\u2013\u2014\u2015\u2e17\u301c\u3030\u30a0\ufe31\ufe32\ufe58\ufe63\uff0d'
-
-Pe = u')]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u232a\u23b5\u2769\u276b\u276d\u276f\u2771\u2773\u2775\u27c6\u27e7\u27e9\u27eb\u2984\u2986\u2988\u298a\u298c\u298e\u2990\u2992\u2994\u2996\u2998\u29d9\u29db\u29fd\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\u301e\u301f\ufd3f\ufe18\ufe36\ufe38\ufe3a\ufe3c\ufe3e\ufe40\ufe42\ufe44\ufe48\ufe5a\ufe5c\ufe5e\uff09\uff3d\uff5d\uff60\uff63'
-
-Pf = u'\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d'
-
-Pi = u'\xab\u2018\u201b\u201c\u201f\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c'
-
-Po = u'!"#%&\'*,./:;?@\\\xa1\xb7\xbf\u037e\u0387\u055a\u055b\u055c\u055d\u055e\u055f\u0589\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u060c\u060d\u061b\u061e\u061f\u066a\u066b\u066c\u066d\u06d4\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u0964\u0965\u0970\u0df4\u0e4f\u0e5a\u0e5b\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f85\u0fd0\u0fd1\u104a\u104b\u104c\u104d\u104e\u104f\u10fb\u1361\u1362\u1363\u1364\u1365\u1366\u1367\u1368\u166d\u166e\u16eb\u16ec\u16ed\u1735\u1736\u17d4\u17d5\u17d6\u17d8\u17d9\u17da\u1800\u1801\u1802\u1803\u1804\u1805\u1807\u1808\u1809\u180a\u1944\u1945\u19de\u19df\u1a1e\u1a1f\u2016\u2017\u2020\u2021\u2022\u2023\u2024\u2025\u2026\u2027\u2030\u2031\u2032\u2033\u2034\u2035\u2036\u2037\u2038\u203b\u203c\u203d\u203e\u2041\u2042\u2043\u2047\u2048\u2049\u204a\u204b\u204c\u204d\u204e\u204f\u2050\u2051\u2053\u2055\u2056\u2057\u2058\u2059\u205a\u205b\u205c\u205d\u205e\u23b6\u2cf9\u2cfa\u2cfb\u2cfc\u2cfe\u2cff\u2e00\u2e01\u2e06\u2e07\u2e08\u2e0b\u2e0e\u2e0f\u2e10\u2e11\u2e12\u2e13\u2e14\u2e15\u2e16\u3001\u3002\u3003\u303d\u30fb\ufe10\ufe11\ufe12\ufe13\ufe14\ufe15\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49\ufe4a\ufe4b\ufe4c\ufe50\ufe51\ufe52\ufe54\ufe55\ufe56\ufe57\ufe5f\ufe60\ufe61\ufe68\ufe6a\ufe6b\uff01\uff02\uff03\uff05\uff06\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65'
-
-Ps = u'([{\u0f3a\u0f3c\u169b\u201a\u201e\u2045\u207d\u208d\u2329\u23b4\u2768\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea\u2983\u2985\u2987\u2989\u298b\u298d\u298f\u2991\u2993\u2995\u2997\u29d8\u29da\u29fc\u3008\u300a\u300c\u300e\u3010\u3014\u3016\u3018\u301a\u301d\ufd3e\ufe17\ufe35\ufe37\ufe39\ufe3b\ufe3d\ufe3f\ufe41\ufe43\ufe47\ufe59\ufe5b\ufe5d\uff08\uff3b\uff5b\uff5f\uff62'
-
-Sc = u'$\xa2\xa3\xa4\xa5\u060b\u09f2\u09f3\u0af1\u0bf9\u0e3f\u17db\u20a0\u20a1\u20a2\u20a3\u20a4\u20a5\u20a6\u20a7\u20a8\u20a9\u20aa\u20ab\u20ac\u20ad\u20ae\u20af\u20b0\u20b1\u20b2\u20b3\u20b4\u20b5\ufdfc\ufe69\uff04\uffe0\uffe1\uffe5\uffe6'
-
-Sk = u'^`\xa8\xaf\xb4\xb8\u02c2\u02c3\u02c4\u02c5\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da\u02db\u02dc\u02dd\u02de\u02df\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0374\u0375\u0384\u0385\u1fbd\u1fbf\u1fc0\u1fc1\u1fcd\u1fce\u1fcf\u1fdd\u1fde\u1fdf\u1fed\u1fee\u1fef\u1ffd\u1ffe\u309b\u309c\ua700\ua701\ua702\ua703\ua704\ua705\ua706\ua707\ua708\ua709\ua70a\ua70b\ua70c\ua70d\ua70e\ua70f\ua710\ua711\ua712\ua713\ua714\ua715\ua716\uff3e\uff40\uffe3'
-
-Sm = u'+<=>|~\xac\xb1\xd7\xf7\u03f6\u2044\u2052\u207a\u207b\u207c\u208a\u208b\u208c\u2140\u2141\u2142\u2143\u2144\u214b\u2190\u2191\u2192\u2193\u2194\u219a\u219b\u21a0\u21a3\u21a6\u21ae\u21ce\u21cf\u21d2\u21d4\u21f4\u21f5\u21f6\u21f7\u21f8\u21f9\u21fa\u21fb\u21fc\u21fd\u21fe\u21ff\u2200\u2201\u2202\u2203\u2204\u2205\u2206\u2207\u2208\u2209\u220a\u220b\u220c\u220d\u220e\u220f\u2210\u2211\u2212\u2213\u2214\u2215\u2216\u2217\u2218\u2219\u221a\u221b\u221c\u221d\u221e\u221f\u2220\u2221\u2222\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222a\u222b\u222c\u222d\u222e\u222f\u2230\u2231\u2232\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223a\u223b\u223c\u223d\u223e\u223f\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224a\u224b\u224c\u224d\u224e\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226a\u226b\u226c\u226d\u226e\u226f\u2270\u2271\u2272\u2273\u2274\u2275\u2276\u2277\u2278\u2279\u227a\u227b\u227c\u227d\u227e\u227f\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228a\u228b\u228c\u228d\u228e\u228f\u2290\u2291\u2292\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229a\u229b\u229c\u229d\u229e\u229f\u22a0\u22a1\u22a2\u22a3\u22a4\u22a5\u22a6\u22a7\u22a8\u22a9\u22aa\u22ab\u22ac\u22ad\u22ae\u22af\u22b0\u22b1\u22b2\u22b3\u22b4\u22b5\u22b6\u22b7\u22b8\u22b9\u22ba\u22bb\u22bc\u22bd\u22be\u22bf\u22c0\u22c1\u22c2\u22c3\u22c4\u22c5\u22c6\u22c7\u22c8\u22c9\u22ca\u22cb\u22cc\u22cd\u22ce\u22cf\u22d0\u22d1\u22d2\u22d3\u22d4\u22d5\u22d6\u22d7\u22d8\u22d9\u22da\u22db\u22dc\u22dd\u22de\u22df\u22e0\u22e1\u22e2\u22e3\u22e4\u22e5\u22e6\u22e7\u22e8\u22e9\u22ea\u22eb\u22ec\u22ed\u22ee\u22ef\u22f0\u22f1\u22f2\u22f3\u22f4\u22f5\u22f6\u22f7\u22f8\u22f9\u22fa\u22fb\u22fc\u22fd\u22fe\u22ff\u2308\u2309\u230a\u230b\u2320\u2321\u237c\u239b\u239c\u239d\u239e\u239f\u23a0\u23a1\u23a2\u23a3\u23a4\u23a5\u23a6\u23a7\u23a8\u23a9\u23aa\u23ab\u23ac\u23ad\u23ae\u23af\u23b0\u23b1\u23b2\u23b3\u25b7\u25c1\u25f8\u25f9\u25fa\u25fb\u25fc\u25fd\u25fe\u25ff\u266f\u27c0\u27c1\u27c2\u27c3\u27c4\u27d0\u27d1\u27d2\u27d3\u27d4\u27d5\u27d6\u27d7\u27d8\u27d9\u27da\u27db\u27dc\u27dd\u27de\u27df\u27e0\u27e1\u27e2\u27e3\u27e4\u27e5\u27f0\u27f1\u27f2\u27f3\u27f4\u27f5\u27f6\u27f7\u27f8\u27f9\u27fa\u27fb\u27fc\u27fd\u27fe\u27ff\u2900\u2901\u2902\u2903\u2904\u2905\u2906\u2907\u2908\u2909\u290a\u290b\u290c\u290d\u290e\u290f\u2910\u2911\u2912\u2913\u2914\u2915\u2916\u2917\u2918\u2919\u291a\u291b\u291c\u291d\u291e\u291f\u2920\u2921\u2922\u2923\u2924\u2925\u2926\u2927\u2928\u2929\u292a\u292b\u292c\u292d\u292e\u292f\u2930\u2931\u2932\u2933\u2934\u2935\u2936\u2937\u2938\u2939\u293a\u293b\u293c\u293d\u293e\u293f\u2940\u2941\u2942\u2943\u2944\u2945\u2946\u2947\u2948\u2949\u294a\u294b\u294c\u294d\u294e\u294f\u2950\u2951\u2952\u2953\u2954\u2955\u2956\u2957\u2958\u2959\u295a\u295b\u295c\u295d\u295e\u295f\u2960\u2961\u2962\u2963\u2964\u2965\u2966\u2967\u2968\u2969\u296a\u296b\u296c\u296d\u296e\u296f\u2970\u2971\u2972\u2973\u2974\u2975\u2976\u2977\u2978\u2979\u297a\u297b\u297c\u297d\u297e\u297f\u2980\u2981\u2982\u2999\u299a\u299b\u299c\u299d\u299e\u299f\u29a0\u29a1\u29a2\u29a3\u29a4\u29a5\u29a6\u29a7\u29a8\u29a9\u29aa\u29ab\u29ac\u29ad\u29ae\u29af\u29b0\u29b1\u29b2\u29b3\u29b4\u29b5\u29b6\u29b7\u29b8\u29b9\u29ba\u29bb\u29bc\u29bd\u29be\u29bf\u29c0\u29c1\u29c2\u29c3\u29c4\u29c5\u29c6\u29c7\u29c8\u29c9\u29ca\u29cb\u29cc\u29cd\u29ce\u29cf\u29d0\u29d1\u29d2\u29d3\u29d4\u29d5\u29d6\u29d7\u29dc\u29dd\u29de\u29df\u29e0\u29e1\u29e2\u29e3\u29e4\u29e5\u29e6\u29e7\u29e8\u29e9\u29ea\u29eb\u29ec\u29ed\u29ee\u29ef\u29f0\u29f1\u29f2\u29f3\u29f4\u29f5\u29f6\u29f7\u29f8\u29f9\u29fa\u29fb\u29fe\u29ff\u2a00\u2a01\u2a02\u2a03\u2a04\u2a05\u2a06\u2a07\u2a08\u2a09\u2a0a\u2a0b\u2a0c\u2a0d\u2a0e\u2a0f\u2a10\u2a11\u2a12\u2a13\u2a14\u2a15\u2a16\u2a17\u2a18\u2a19\u2a1a\u2a1b\u2a1c\u2a1d\u2a1e\u2a1f\u2a20\u2a21\u2a22\u2a23\u2a24\u2a25\u2a26\u2a27\u2a28\u2a29\u2a2a\u2a2b\u2a2c\u2a2d\u2a2e\u2a2f\u2a30\u2a31\u2a32\u2a33\u2a34\u2a35\u2a36\u2a37\u2a38\u2a39\u2a3a\u2a3b\u2a3c\u2a3d\u2a3e\u2a3f\u2a40\u2a41\u2a42\u2a43\u2a44\u2a45\u2a46\u2a47\u2a48\u2a49\u2a4a\u2a4b\u2a4c\u2a4d\u2a4e\u2a4f\u2a50\u2a51\u2a52\u2a53\u2a54\u2a55\u2a56\u2a57\u2a58\u2a59\u2a5a\u2a5b\u2a5c\u2a5d\u2a5e\u2a5f\u2a60\u2a61\u2a62\u2a63\u2a64\u2a65\u2a66\u2a67\u2a68\u2a69\u2a6a\u2a6b\u2a6c\u2a6d\u2a6e\u2a6f\u2a70\u2a71\u2a72\u2a73\u2a74\u2a75\u2a76\u2a77\u2a78\u2a79\u2a7a\u2a7b\u2a7c\u2a7d\u2a7e\u2a7f\u2a80\u2a81\u2a82\u2a83\u2a84\u2a85\u2a86\u2a87\u2a88\u2a89\u2a8a\u2a8b\u2a8c\u2a8d\u2a8e\u2a8f\u2a90\u2a91\u2a92\u2a93\u2a94\u2a95\u2a96\u2a97\u2a98\u2a99\u2a9a\u2a9b\u2a9c\u2a9d\u2a9e\u2a9f\u2aa0\u2aa1\u2aa2\u2aa3\u2aa4\u2aa5\u2aa6\u2aa7\u2aa8\u2aa9\u2aaa\u2aab\u2aac\u2aad\u2aae\u2aaf\u2ab0\u2ab1\u2ab2\u2ab3\u2ab4\u2ab5\u2ab6\u2ab7\u2ab8\u2ab9\u2aba\u2abb\u2abc\u2abd\u2abe\u2abf\u2ac0\u2ac1\u2ac2\u2ac3\u2ac4\u2ac5\u2ac6\u2ac7\u2ac8\u2ac9\u2aca\u2acb\u2acc\u2acd\u2ace\u2acf\u2ad0\u2ad1\u2ad2\u2ad3\u2ad4\u2ad5\u2ad6\u2ad7\u2ad8\u2ad9\u2ada\u2adb\u2adc\u2add\u2ade\u2adf\u2ae0\u2ae1\u2ae2\u2ae3\u2ae4\u2ae5\u2ae6\u2ae7\u2ae8\u2ae9\u2aea\u2aeb\u2aec\u2aed\u2aee\u2aef\u2af0\u2af1\u2af2\u2af3\u2af4\u2af5\u2af6\u2af7\u2af8\u2af9\u2afa\u2afb\u2afc\u2afd\u2afe\u2aff\ufb29\ufe62\ufe64\ufe65\ufe66\uff0b\uff1c\uff1d\uff1e\uff5c\uff5e\uffe2\uffe9\uffea\uffeb\uffec'
-
-So = u'\xa6\xa7\xa9\xae\xb0\xb6\u0482\u060e\u060f\u06e9\u06fd\u06fe\u09fa\u0b70\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\u0f01\u0f02\u0f03\u0f13\u0f14\u0f15\u0f16\u0f17\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e\u0f1f\u0f34\u0f36\u0f38\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcf\u1360\u1390\u1391\u1392\u1393\u1394\u1395\u1396\u1397\u1398\u1399\u1940\u19e0\u19e1\u19e2\u19e3\u19e4\u19e5\u19e6\u19e7\u19e8\u19e9\u19ea\u19eb\u19ec\u19ed\u19ee\u19ef\u19f0\u19f1\u19f2\u19f3\u19f4\u19f5\u19f6\u19f7\u19f8\u19f9\u19fa\u19fb\u19fc\u19fd\u19fe\u19ff\u2100\u2101\u2103\u2104\u2105\u2106\u2108\u2109\u2114\u2116\u2117\u2118\u211e\u211f\u2120\u2121\u2122\u2123\u2125\u2127\u2129\u212e\u2132\u213a\u213b\u214a\u214c\u2195\u2196\u2197\u2198\u2199\u219c\u219d\u219e\u219f\u21a1\u21a2\u21a4\u21a5\u21a7\u21a8\u21a9\u21aa\u21ab\u21ac\u21ad\u21af\u21b0\u21b1\u21b2\u21b3\u21b4\u21b5\u21b6\u21b7\u21b8\u21b9\u21ba\u21bb\u21bc\u21bd\u21be\u21bf\u21c0\u21c1\u21c2\u21c3\u21c4\u21c5\u21c6\u21c7\u21c8\u21c9\u21ca\u21cb\u21cc\u21cd\u21d0\u21d1\u21d3\u21d5\u21d6\u21d7\u21d8\u21d9\u21da\u21db\u21dc\u21dd\u21de\u21df\u21e0\u21e1\u21e2\u21e3\u21e4\u21e5\u21e6\u21e7\u21e8\u21e9\u21ea\u21eb\u21ec\u21ed\u21ee\u21ef\u21f0\u21f1\u21f2\u21f3\u2300\u2301\u2302\u2303\u2304\u2305\u2306\u2307\u230c\u230d\u230e\u230f\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231a\u231b\u231c\u231d\u231e\u231f\u2322\u2323\u2324\u2325\u2326\u2327\u2328\u232b\u232c\u232d\u232e\u232f\u2330\u2331\u2332\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233a\u233b\u233c\u233d\u233e\u233f\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234a\u234b\u234c\u234d\u234e\u234f\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2358\u2359\u235a\u235b\u235c\u235d\u235e\u235f\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236a\u236b\u236c\u236d\u236e\u236f\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237a\u237b\u237d\u237e\u237f\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238a\u238b\u238c\u238d\u238e\u238f\u2390\u2391\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239a\u23b7\u23b8\u23b9\u23ba\u23bb\u23bc\u23bd\u23be\u23bf\u23c0\u23c1\u23c2\u23c3\u23c4\u23c5\u23c6\u23c7\u23c8\u23c9\u23ca\u23cb\u23cc\u23cd\u23ce\u23cf\u23d0\u23d1\u23d2\u23d3\u23d4\u23d5\u23d6\u23d7\u23d8\u23d9\u23da\u23db\u2400\u2401\u2402\u2403\u2404\u2405\u2406\u2407\u2408\u2409\u240a\u240b\u240c\u240d\u240e\u240f\u2410\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u2419\u241a\u241b\u241c\u241d\u241e\u241f\u2420\u2421\u2422\u2423\u2424\u2425\u2426\u2440\u2441\u2442\u2443\u2444\u2445\u2446\u2447\u2448\u2449\u244a\u249c\u249d\u249e\u249f\u24a0\u24a1\u24a2\u24a3\u24a4\u24a5\u24a6\u24a7\u24a8\u24a9\u24aa\u24ab\u24ac\u24ad\u24ae\u24af\u24b0\u24b1\u24b2\u24b3\u24b4\u24b5\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb\u24cc\u24cd\u24ce\u24cf\u24d0\u24d1\u24d2\u24d3\u24d4\u24d5\u24d6\u24d7\u24d8\u24d9\u24da\u24db\u24dc\u24dd\u24de\u24df\u24e0\u24e1\u24e2\u24e3\u24e4\u24e5\u24e6\u24e7\u24e8\u24e9\u2500\u2501\u2502\u2503\u2504\u2505\u2506\u2507\u2508\u2509\u250a\u250b\u250c\u250d\u250e\u250f\u2510\u2511\u2512\u2513\u2514\u2515\u2516\u2517\u2518\u2519\u251a\u251b\u251c\u251d\u251e\u251f\u2520\u2521\u2522\u2523\u2524\u2525\u2526\u2527\u2528\u2529\u252a\u252b\u252c\u252d\u252e\u252f\u2530\u2531\u2532\u2533\u2534\u2535\u2536\u2537\u2538\u2539\u253a\u253b\u253c\u253d\u253e\u253f\u2540\u2541\u2542\u2543\u2544\u2545\u2546\u2547\u2548\u2549\u254a\u254b\u254c\u254d\u254e\u254f\u2550\u2551\u2552\u2553\u2554\u2555\u2556\u2557\u2558\u2559\u255a\u255b\u255c\u255d\u255e\u255f\u2560\u2561\u2562\u2563\u2564\u2565\u2566\u2567\u2568\u2569\u256a\u256b\u256c\u256d\u256e\u256f\u2570\u2571\u2572\u2573\u2574\u2575\u2576\u2577\u2578\u2579\u257a\u257b\u257c\u257d\u257e\u257f\u2580\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2589\u258a\u258b\u258c\u258d\u258e\u258f\u2590\u2591\u2592\u2593\u2594\u2595\u2596\u2597\u2598\u2599\u259a\u259b\u259c\u259d\u259e\u259f\u25a0\u25a1\u25a2\u25a3\u25a4\u25a5\u25a6\u25a7\u25a8\u25a9\u25aa\u25ab\u25ac\u25ad\u25ae\u25af\u25b0\u25b1\u25b2\u25b3\u25b4\u25b5\u25b6\u25b8\u25b9\u25ba\u25bb\u25bc\u25bd\u25be\u25bf\u25c0\u25c2\u25c3\u25c4\u25c5\u25c6\u25c7\u25c8\u25c9\u25ca\u25cb\u25cc\u25cd\u25ce\u25cf\u25d0\u25d1\u25d2\u25d3\u25d4\u25d5\u25d6\u25d7\u25d8\u25d9\u25da\u25db\u25dc\u25dd\u25de\u25df\u25e0\u25e1\u25e2\u25e3\u25e4\u25e5\u25e6\u25e7\u25e8\u25e9\u25ea\u25eb\u25ec\u25ed\u25ee\u25ef\u25f0\u25f1\u25f2\u25f3\u25f4\u25f5\u25f6\u25f7\u2600\u2601\u2602\u2603\u2604\u2605\u2606\u2607\u2608\u2609\u260a\u260b\u260c\u260d\u260e\u260f\u2610\u2611\u2612\u2613\u2614\u2615\u2616\u2617\u2618\u2619\u261a\u261b\u261c\u261d\u261e\u261f\u2620\u2621\u2622\u2623\u2624\u2625\u2626\u2627\u2628\u2629\u262a\u262b\u262c\u262d\u262e\u262f\u2630\u2631\u2632\u2633\u2634\u2635\u2636\u2637\u2638\u2639\u263a\u263b\u263c\u263d\u263e\u263f\u2640\u2641\u2642\u2643\u2644\u2645\u2646\u2647\u2648\u2649\u264a\u264b\u264c\u264d\u264e\u264f\u2650\u2651\u2652\u2653\u2654\u2655\u2656\u2657\u2658\u2659\u265a\u265b\u265c\u265d\u265e\u265f\u2660\u2661\u2662\u2663\u2664\u2665\u2666\u2667\u2668\u2669\u266a\u266b\u266c\u266d\u266e\u2670\u2671\u2672\u2673\u2674\u2675\u2676\u2677\u2678\u2679\u267a\u267b\u267c\u267d\u267e\u267f\u2680\u2681\u2682\u2683\u2684\u2685\u2686\u2687\u2688\u2689\u268a\u268b\u268c\u268d\u268e\u268f\u2690\u2691\u2692\u2693\u2694\u2695\u2696\u2697\u2698\u2699\u269a\u269b\u269c\u26a0\u26a1\u26a2\u26a3\u26a4\u26a5\u26a6\u26a7\u26a8\u26a9\u26aa\u26ab\u26ac\u26ad\u26ae\u26af\u26b0\u26b1\u2701\u2702\u2703\u2704\u2706\u2707\u2708\u2709\u270c\u270d\u270e\u270f\u2710\u2711\u2712\u2713\u2714\u2715\u2716\u2717\u2718\u2719\u271a\u271b\u271c\u271d\u271e\u271f\u2720\u2721\u2722\u2723\u2724\u2725\u2726\u2727\u2729\u272a\u272b\u272c\u272d\u272e\u272f\u2730\u2731\u2732\u2733\u2734\u2735\u2736\u2737\u2738\u2739\u273a\u273b\u273c\u273d\u273e\u273f\u2740\u2741\u2742\u2743\u2744\u2745\u2746\u2747\u2748\u2749\u274a\u274b\u274d\u274f\u2750\u2751\u2752\u2756\u2758\u2759\u275a\u275b\u275c\u275d\u275e\u2761\u2762\u2763\u2764\u2765\u2766\u2767\u2794\u2798\u2799\u279a\u279b\u279c\u279d\u279e\u279f\u27a0\u27a1\u27a2\u27a3\u27a4\u27a5\u27a6\u27a7\u27a8\u27a9\u27aa\u27ab\u27ac\u27ad\u27ae\u27af\u27b1\u27b2\u27b3\u27b4\u27b5\u27b6\u27b7\u27b8\u27b9\u27ba\u27bb\u27bc\u27bd\u27be\u2800\u2801\u2802\u2803\u2804\u2805\u2806\u2807\u2808\u2809\u280a\u280b\u280c\u280d\u280e\u280f\u2810\u2811\u2812\u2813\u2814\u2815\u2816\u2817\u2818\u2819\u281a\u281b\u281c\u281d\u281e\u281f\u2820\u2821\u2822\u2823\u2824\u2825\u2826\u2827\u2828\u2829\u282a\u282b\u282c\u282d\u282e\u282f\u2830\u2831\u2832\u2833\u2834\u2835\u2836\u2837\u2838\u2839\u283a\u283b\u283c\u283d\u283e\u283f\u2840\u2841\u2842\u2843\u2844\u2845\u2846\u2847\u2848\u2849\u284a\u284b\u284c\u284d\u284e\u284f\u2850\u2851\u2852\u2853\u2854\u2855\u2856\u2857\u2858\u2859\u285a\u285b\u285c\u285d\u285e\u285f\u2860\u2861\u2862\u2863\u2864\u2865\u2866\u2867\u2868\u2869\u286a\u286b\u286c\u286d\u286e\u286f\u2870\u2871\u2872\u2873\u2874\u2875\u2876\u2877\u2878\u2879\u287a\u287b\u287c\u287d\u287e\u287f\u2880\u2881\u2882\u2883\u2884\u2885\u2886\u2887\u2888\u2889\u288a\u288b\u288c\u288d\u288e\u288f\u2890\u2891\u2892\u2893\u2894\u2895\u2896\u2897\u2898\u2899\u289a\u289b\u289c\u289d\u289e\u289f\u28a0\u28a1\u28a2\u28a3\u28a4\u28a5\u28a6\u28a7\u28a8\u28a9\u28aa\u28ab\u28ac\u28ad\u28ae\u28af\u28b0\u28b1\u28b2\u28b3\u28b4\u28b5\u28b6\u28b7\u28b8\u28b9\u28ba\u28bb\u28bc\u28bd\u28be\u28bf\u28c0\u28c1\u28c2\u28c3\u28c4\u28c5\u28c6\u28c7\u28c8\u28c9\u28ca\u28cb\u28cc\u28cd\u28ce\u28cf\u28d0\u28d1\u28d2\u28d3\u28d4\u28d5\u28d6\u28d7\u28d8\u28d9\u28da\u28db\u28dc\u28dd\u28de\u28df\u28e0\u28e1\u28e2\u28e3\u28e4\u28e5\u28e6\u28e7\u28e8\u28e9\u28ea\u28eb\u28ec\u28ed\u28ee\u28ef\u28f0\u28f1\u28f2\u28f3\u28f4\u28f5\u28f6\u28f7\u28f8\u28f9\u28fa\u28fb\u28fc\u28fd\u28fe\u28ff\u2b00\u2b01\u2b02\u2b03\u2b04\u2b05\u2b06\u2b07\u2b08\u2b09\u2b0a\u2b0b\u2b0c\u2b0d\u2b0e\u2b0f\u2b10\u2b11\u2b12\u2b13\u2ce5\u2ce6\u2ce7\u2ce8\u2ce9\u2cea\u2e80\u2e81\u2e82\u2e83\u2e84\u2e85\u2e86\u2e87\u2e88\u2e89\u2e8a\u2e8b\u2e8c\u2e8d\u2e8e\u2e8f\u2e90\u2e91\u2e92\u2e93\u2e94\u2e95\u2e96\u2e97\u2e98\u2e99\u2e9b\u2e9c\u2e9d\u2e9e\u2e9f\u2ea0\u2ea1\u2ea2\u2ea3\u2ea4\u2ea5\u2ea6\u2ea7\u2ea8\u2ea9\u2eaa\u2eab\u2eac\u2ead\u2eae\u2eaf\u2eb0\u2eb1\u2eb2\u2eb3\u2eb4\u2eb5\u2eb6\u2eb7\u2eb8\u2eb9\u2eba\u2ebb\u2ebc\u2ebd\u2ebe\u2ebf\u2ec0\u2ec1\u2ec2\u2ec3\u2ec4\u2ec5\u2ec6\u2ec7\u2ec8\u2ec9\u2eca\u2ecb\u2ecc\u2ecd\u2ece\u2ecf\u2ed0\u2ed1\u2ed2\u2ed3\u2ed4\u2ed5\u2ed6\u2ed7\u2ed8\u2ed9\u2eda\u2edb\u2edc\u2edd\u2ede\u2edf\u2ee0\u2ee1\u2ee2\u2ee3\u2ee4\u2ee5\u2ee6\u2ee7\u2ee8\u2ee9\u2eea\u2eeb\u2eec\u2eed\u2eee\u2eef\u2ef0\u2ef1\u2ef2\u2ef3\u2f00\u2f01\u2f02\u2f03\u2f04\u2f05\u2f06\u2f07\u2f08\u2f09\u2f0a\u2f0b\u2f0c\u2f0d\u2f0e\u2f0f\u2f10\u2f11\u2f12\u2f13\u2f14\u2f15\u2f16\u2f17\u2f18\u2f19\u2f1a\u2f1b\u2f1c\u2f1d\u2f1e\u2f1f\u2f20\u2f21\u2f22\u2f23\u2f24\u2f25\u2f26\u2f27\u2f28\u2f29\u2f2a\u2f2b\u2f2c\u2f2d\u2f2e\u2f2f\u2f30\u2f31\u2f32\u2f33\u2f34\u2f35\u2f36\u2f37\u2f38\u2f39\u2f3a\u2f3b\u2f3c\u2f3d\u2f3e\u2f3f\u2f40\u2f41\u2f42\u2f43\u2f44\u2f45\u2f46\u2f47\u2f48\u2f49\u2f4a\u2f4b\u2f4c\u2f4d\u2f4e\u2f4f\u2f50\u2f51\u2f52\u2f53\u2f54\u2f55\u2f56\u2f57\u2f58\u2f59\u2f5a\u2f5b\u2f5c\u2f5d\u2f5e\u2f5f\u2f60\u2f61\u2f62\u2f63\u2f64\u2f65\u2f66\u2f67\u2f68\u2f69\u2f6a\u2f6b\u2f6c\u2f6d\u2f6e\u2f6f\u2f70\u2f71\u2f72\u2f73\u2f74\u2f75\u2f76\u2f77\u2f78\u2f79\u2f7a\u2f7b\u2f7c\u2f7d\u2f7e\u2f7f\u2f80\u2f81\u2f82\u2f83\u2f84\u2f85\u2f86\u2f87\u2f88\u2f89\u2f8a\u2f8b\u2f8c\u2f8d\u2f8e\u2f8f\u2f90\u2f91\u2f92\u2f93\u2f94\u2f95\u2f96\u2f97\u2f98\u2f99\u2f9a\u2f9b\u2f9c\u2f9d\u2f9e\u2f9f\u2fa0\u2fa1\u2fa2\u2fa3\u2fa4\u2fa5\u2fa6\u2fa7\u2fa8\u2fa9\u2faa\u2fab\u2fac\u2fad\u2fae\u2faf\u2fb0\u2fb1\u2fb2\u2fb3\u2fb4\u2fb5\u2fb6\u2fb7\u2fb8\u2fb9\u2fba\u2fbb\u2fbc\u2fbd\u2fbe\u2fbf\u2fc0\u2fc1\u2fc2\u2fc3\u2fc4\u2fc5\u2fc6\u2fc7\u2fc8\u2fc9\u2fca\u2fcb\u2fcc\u2fcd\u2fce\u2fcf\u2fd0\u2fd1\u2fd2\u2fd3\u2fd4\u2fd5\u2ff0\u2ff1\u2ff2\u2ff3\u2ff4\u2ff5\u2ff6\u2ff7\u2ff8\u2ff9\u2ffa\u2ffb\u3004\u3012\u3013\u3020\u3036\u3037\u303e\u303f\u3190\u3191\u3196\u3197\u3198\u3199\u319a\u319b\u319c\u319d\u319e\u319f\u31c0\u31c1\u31c2\u31c3\u31c4\u31c5\u31c6\u31c7\u31c8\u31c9\u31ca\u31cb\u31cc\u31cd\u31ce\u31cf\u3200\u3201\u3202\u3203\u3204\u3205\u3206\u3207\u3208\u3209\u320a\u320b\u320c\u320d\u320e\u320f\u3210\u3211\u3212\u3213\u3214\u3215\u3216\u3217\u3218\u3219\u321a\u321b\u321c\u321d\u321e\u322a\u322b\u322c\u322d\u322e\u322f\u3230\u3231\u3232\u3233\u3234\u3235\u3236\u3237\u3238\u3239\u323a\u323b\u323c\u323d\u323e\u323f\u3240\u3241\u3242\u3243\u3250\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326a\u326b\u326c\u326d\u326e\u326f\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327a\u327b\u327c\u327d\u327e\u327f\u328a\u328b\u328c\u328d\u328e\u328f\u3290\u3291\u3292\u3293\u3294\u3295\u3296\u3297\u3298\u3299\u329a\u329b\u329c\u329d\u329e\u329f\u32a0\u32a1\u32a2\u32a3\u32a4\u32a5\u32a6\u32a7\u32a8\u32a9\u32aa\u32ab\u32ac\u32ad\u32ae\u32af\u32b0\u32c0\u32c1\u32c2\u32c3\u32c4\u32c5\u32c6\u32c7\u32c8\u32c9\u32ca\u32cb\u32cc\u32cd\u32ce\u32cf\u32d0\u32d1\u32d2\u32d3\u32d4\u32d5\u32d6\u32d7\u32d8\u32d9\u32da\u32db\u32dc\u32dd\u32de\u32df\u32e0\u32e1\u32e2\u32e3\u32e4\u32e5\u32e6\u32e7\u32e8\u32e9\u32ea\u32eb\u32ec\u32ed\u32ee\u32ef\u32f0\u32f1\u32f2\u32f3\u32f4\u32f5\u32f6\u32f7\u32f8\u32f9\u32fa\u32fb\u32fc\u32fd\u32fe\u3300\u3301\u3302\u3303\u3304\u3305\u3306\u3307\u3308\u3309\u330a\u330b\u330c\u330d\u330e\u330f\u3310\u3311\u3312\u3313\u3314\u3315\u3316\u3317\u3318\u3319\u331a\u331b\u331c\u331d\u331e\u331f\u3320\u3321\u3322\u3323\u3324\u3325\u3326\u3327\u3328\u3329\u332a\u332b\u332c\u332d\u332e\u332f\u3330\u3331\u3332\u3333\u3334\u3335\u3336\u3337\u3338\u3339\u333a\u333b\u333c\u333d\u333e\u333f\u3340\u3341\u3342\u3343\u3344\u3345\u3346\u3347\u3348\u3349\u334a\u334b\u334c\u334d\u334e\u334f\u3350\u3351\u3352\u3353\u3354\u3355\u3356\u3357\u3358\u3359\u335a\u335b\u335c\u335d\u335e\u335f\u3360\u3361\u3362\u3363\u3364\u3365\u3366\u3367\u3368\u3369\u336a\u336b\u336c\u336d\u336e\u336f\u3370\u3371\u3372\u3373\u3374\u3375\u3376\u3377\u3378\u3379\u337a\u337b\u337c\u337d\u337e\u337f\u3380\u3381\u3382\u3383\u3384\u3385\u3386\u3387\u3388\u3389\u338a\u338b\u338c\u338d\u338e\u338f\u3390\u3391\u3392\u3393\u3394\u3395\u3396\u3397\u3398\u3399\u339a\u339b\u339c\u339d\u339e\u339f\u33a0\u33a1\u33a2\u33a3\u33a4\u33a5\u33a6\u33a7\u33a8\u33a9\u33aa\u33ab\u33ac\u33ad\u33ae\u33af\u33b0\u33b1\u33b2\u33b3\u33b4\u33b5\u33b6\u33b7\u33b8\u33b9\u33ba\u33bb\u33bc\u33bd\u33be\u33bf\u33c0\u33c1\u33c2\u33c3\u33c4\u33c5\u33c6\u33c7\u33c8\u33c9\u33ca\u33cb\u33cc\u33cd\u33ce\u33cf\u33d0\u33d1\u33d2\u33d3\u33d4\u33d5\u33d6\u33d7\u33d8\u33d9\u33da\u33db\u33dc\u33dd\u33de\u33df\u33e0\u33e1\u33e2\u33e3\u33e4\u33e5\u33e6\u33e7\u33e8\u33e9\u33ea\u33eb\u33ec\u33ed\u33ee\u33ef\u33f0\u33f1\u33f2\u33f3\u33f4\u33f5\u33f6\u33f7\u33f8\u33f9\u33fa\u33fb\u33fc\u33fd\u33fe\u33ff\u4dc0\u4dc1\u4dc2\u4dc3\u4dc4\u4dc5\u4dc6\u4dc7\u4dc8\u4dc9\u4dca\u4dcb\u4dcc\u4dcd\u4dce\u4dcf\u4dd0\u4dd1\u4dd2\u4dd3\u4dd4\u4dd5\u4dd6\u4dd7\u4dd8\u4dd9\u4dda\u4ddb\u4ddc\u4ddd\u4dde\u4ddf\u4de0\u4de1\u4de2\u4de3\u4de4\u4de5\u4de6\u4de7\u4de8\u4de9\u4dea\u4deb\u4dec\u4ded\u4dee\u4def\u4df0\u4df1\u4df2\u4df3\u4df4\u4df5\u4df6\u4df7\u4df8\u4df9\u4dfa\u4dfb\u4dfc\u4dfd\u4dfe\u4dff\ua490\ua491\ua492\ua493\ua494\ua495\ua496\ua497\ua498\ua499\ua49a\ua49b\ua49c\ua49d\ua49e\ua49f\ua4a0\ua4a1\ua4a2\ua4a3\ua4a4\ua4a5\ua4a6\ua4a7\ua4a8\ua4a9\ua4aa\ua4ab\ua4ac\ua4ad\ua4ae\ua4af\ua4b0\ua4b1\ua4b2\ua4b3\ua4b4\ua4b5\ua4b6\ua4b7\ua4b8\ua4b9\ua4ba\ua4bb\ua4bc\ua4bd\ua4be\ua4bf\ua4c0\ua4c1\ua4c2\ua4c3\ua4c4\ua4c5\ua4c6\ua828\ua829\ua82a\ua82b\ufdfd\uffe4\uffe8\uffed\uffee\ufffc\ufffd'
-
-Zl = u'\u2028'
-
-Zp = u'\u2029'
-
-Zs = u' \xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
-
-cats = ['Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu', 'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf', 'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs']
-
-def combine(*args):
- return u''.join([globals()[cat] for cat in args])
-
-xid_start = u'\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0640\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u076D\u0780-\u07A5\u07B1\u0904-\u0939\u093D\u0950\u0958-\u0961\u097D\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E40-\u0E45\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6A\u0F88-\u0F8B\u1000-\u1021\u1023-\u1027\u1029-\u102A\u1050-\u1055\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19C1-\u19C7\u1A00-\u1A16\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
-
-xid_continue = u'\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00B7\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0300-\u036F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u0483-\u0486\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u0615\u0621-\u063A\u0640\u0641-\u064A\u064B-\u065E\u0660-\u0669\u066E-\u066F\u0670\u0671-\u06D3\u06D5\u06D6-\u06DC\u06DF-\u06E4\u06E5-\u06E6\u06E7-\u06E8\u06EA-\u06ED\u06EE-\u06EF\u06F0-\u06F9\u06FA-\u06FC\u06FF\u0710\u0711\u0712-\u072F\u0730-\u074A\u074D-\u076D\u0780-\u07A5\u07A6-\u07B0\u07B1\u0901-\u0902\u0903\u0904-\u0939\u093C\u093D\u093E-\u0940\u0941-\u0948\u0949-\u094C\u094D\u0950\u0951-\u0954\u0958-\u0961\u0962-\u0963\u0966-\u096F\u097D\u0981\u0982-\u0983\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC\u09BD\u09BE-\u09C0\u09C1-\u09C4\u09C7-\u09C8\u09CB-\u09CC\u09CD\u09CE\u09D7\u09DC-\u09DD\u09DF-\u09E1\u09E2-\u09E3\u09E6-\u09EF\u09F0-\u09F1\u0A01-\u0A02\u0A03\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A3C\u0A3E-\u0A40\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A70-\u0A71\u0A72-\u0A74\u0A81-\u0A82\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABC\u0ABD\u0ABE-\u0AC0\u0AC1-\u0AC5\u0AC7-\u0AC8\u0AC9\u0ACB-\u0ACC\u0ACD\u0AD0\u0AE0-\u0AE1\u0AE2-\u0AE3\u0AE6-\u0AEF\u0B01\u0B02-\u0B03\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3C\u0B3D\u0B3E\u0B3F\u0B40\u0B41-\u0B43\u0B47-\u0B48\u0B4B-\u0B4C\u0B4D\u0B56\u0B57\u0B5C-\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BBF\u0BC0\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BCD\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C40\u0C41-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C60-\u0C61\u0C66-\u0C6F\u0C82-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC\u0CBD\u0CBE\u0CBF\u0CC0-\u0CC4\u0CC6\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CDE\u0CE0-\u0CE1\u0CE6-\u0CEF\u0D02-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D40\u0D41-\u0D43\u0D46-\u0D48\u0D4A-\u0D4C\u0D4D\u0D57\u0D60-\u0D61\u0D66-\u0D6F\u0D82-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD1\u0DD2-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF3\u0E01-\u0E30\u0E31\u0E32-\u0E33\u0E34-\u0E3A\u0E40-\u0E45\u0E46\u0E47-\u0E4E\u0E50-\u0E59\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB1\u0EB2-\u0EB3\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDD\u0F00\u0F18-\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F3F\u0F40-\u0F47\u0F49-\u0F6A\u0F71-\u0F7E\u0F7F\u0F80-\u0F84\u0F86-\u0F87\u0F88-\u0F8B\u0F90-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1021\u1023-\u1027\u1029-\u102A\u102C\u102D-\u1030\u1031\u1032\u1036-\u1037\u1038\u1039\u1040-\u1049\u1050-\u1055\u1056-\u1057\u1058-\u1059\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1712-\u1714\u1720-\u1731\u1732-\u1734\u1740-\u1751\u1752-\u1753\u1760-\u176C\u176E-\u1770\u1772-\u1773\u1780-\u17B3\u17B6\u17B7-\u17BD\u17BE-\u17C5\u17C6\u17C7-\u17C8\u17C9-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u18A9\u1900-\u191C\u1920-\u1922\u1923-\u1926\u1927-\u1928\u1929-\u192B\u1930-\u1931\u1932\u1933-\u1938\u1939-\u193B\u1946-\u194F\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19B0-\u19C0\u19C1-\u19C7\u19C8-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A17-\u1A18\u1A19-\u1A1B\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1DC0-\u1DC3\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F-\u2040\u2054\u2071\u207F\u2090-\u2094\u20D0-\u20DC\u20E1\u20E5-\u20EB\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u302A-\u302F\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u3099-\u309A\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA802\uA803-\uA805\uA806\uA807-\uA80A\uA80B\uA80C-\uA822\uA823-\uA824\uA825-\uA826\uA827\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1E\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE00-\uFE0F\uFE20-\uFE23\uFE33-\uFE34\uFE4D-\uFE4F\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFF9E-\uFF9F\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
-
-def allexcept(*args):
- newcats = cats[:]
- for arg in args:
- newcats.remove(arg)
- return u''.join([globals()[cat] for cat in newcats])
-
-if __name__ == '__main__':
- import unicodedata
-
- categories = {}
-
- f = open(__file__.rstrip('co'))
- try:
- content = f.read()
- finally:
- f.close()
-
- header = content[:content.find('Cc =')]
- footer = content[content.find("def combine("):]
-
- for code in range(65535):
- c = unichr(code)
- cat = unicodedata.category(c)
- categories.setdefault(cat, []).append(c)
-
- f = open(__file__, 'w')
- f.write(header)
-
- for cat in sorted(categories):
- val = u''.join(categories[cat])
- if cat == 'Cs':
- # Jython can't handle isolated surrogates
- f.write("""\
-try:
- Cs = eval(r"%r")
-except UnicodeDecodeError:
- Cs = '' # Jython can't handle isolated surrogates\n\n""" % val)
- else:
- f.write('%s = %r\n\n' % (cat, val))
- f.write('cats = %r\n\n' % sorted(categories.keys()))
-
- f.write(footer)
- f.close()
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/bccache.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/bccache.py
deleted file mode 100755
index 1e2236c3a..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/bccache.py
+++ /dev/null
@@ -1,280 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.bccache
- ~~~~~~~~~~~~~~
-
- This module implements the bytecode cache system Jinja is optionally
- using. This is useful if you have very complex template situations and
- the compiliation of all those templates slow down your application too
- much.
-
- Situations where this is useful are often forking web applications that
- are initialized on the first request.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-from os import path, listdir
-import marshal
-import tempfile
-import cPickle as pickle
-import fnmatch
-from cStringIO import StringIO
-try:
- from hashlib import sha1
-except ImportError:
- from sha import new as sha1
-from jinja2.utils import open_if_exists
-
-
-bc_version = 1
-bc_magic = 'j2'.encode('ascii') + pickle.dumps(bc_version, 2)
-
-
-class Bucket(object):
- """Buckets are used to store the bytecode for one template. It's created
- and initialized by the bytecode cache and passed to the loading functions.
-
- The buckets get an internal checksum from the cache assigned and use this
- to automatically reject outdated cache material. Individual bytecode
- cache subclasses don't have to care about cache invalidation.
- """
-
- def __init__(self, environment, key, checksum):
- self.environment = environment
- self.key = key
- self.checksum = checksum
- self.reset()
-
- def reset(self):
- """Resets the bucket (unloads the bytecode)."""
- self.code = None
-
- def load_bytecode(self, f):
- """Loads bytecode from a file or file like object."""
- # make sure the magic header is correct
- magic = f.read(len(bc_magic))
- if magic != bc_magic:
- self.reset()
- return
- # the source code of the file changed, we need to reload
- checksum = pickle.load(f)
- if self.checksum != checksum:
- self.reset()
- return
- # now load the code. Because marshal is not able to load
- # from arbitrary streams we have to work around that
- if isinstance(f, file):
- self.code = marshal.load(f)
- else:
- self.code = marshal.loads(f.read())
-
- def write_bytecode(self, f):
- """Dump the bytecode into the file or file like object passed."""
- if self.code is None:
- raise TypeError('can\'t write empty bucket')
- f.write(bc_magic)
- pickle.dump(self.checksum, f, 2)
- if isinstance(f, file):
- marshal.dump(self.code, f)
- else:
- f.write(marshal.dumps(self.code))
-
- def bytecode_from_string(self, string):
- """Load bytecode from a string."""
- self.load_bytecode(StringIO(string))
-
- def bytecode_to_string(self):
- """Return the bytecode as string."""
- out = StringIO()
- self.write_bytecode(out)
- return out.getvalue()
-
-
-class BytecodeCache(object):
- """To implement your own bytecode cache you have to subclass this class
- and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
- these methods are passed a :class:`~jinja2.bccache.Bucket`.
-
- A very basic bytecode cache that saves the bytecode on the file system::
-
- from os import path
-
- class MyCache(BytecodeCache):
-
- def __init__(self, directory):
- self.directory = directory
-
- def load_bytecode(self, bucket):
- filename = path.join(self.directory, bucket.key)
- if path.exists(filename):
- with open(filename, 'rb') as f:
- bucket.load_bytecode(f)
-
- def dump_bytecode(self, bucket):
- filename = path.join(self.directory, bucket.key)
- with open(filename, 'wb') as f:
- bucket.write_bytecode(f)
-
- A more advanced version of a filesystem based bytecode cache is part of
- Jinja2.
- """
-
- def load_bytecode(self, bucket):
- """Subclasses have to override this method to load bytecode into a
- bucket. If they are not able to find code in the cache for the
- bucket, it must not do anything.
- """
- raise NotImplementedError()
-
- def dump_bytecode(self, bucket):
- """Subclasses have to override this method to write the bytecode
- from a bucket back to the cache. If it unable to do so it must not
- fail silently but raise an exception.
- """
- raise NotImplementedError()
-
- def clear(self):
- """Clears the cache. This method is not used by Jinja2 but should be
- implemented to allow applications to clear the bytecode cache used
- by a particular environment.
- """
-
- def get_cache_key(self, name, filename=None):
- """Returns the unique hash key for this template name."""
- hash = sha1(name.encode('utf-8'))
- if filename is not None:
- if isinstance(filename, unicode):
- filename = filename.encode('utf-8')
- hash.update('|' + filename)
- return hash.hexdigest()
-
- def get_source_checksum(self, source):
- """Returns a checksum for the source."""
- return sha1(source.encode('utf-8')).hexdigest()
-
- def get_bucket(self, environment, name, filename, source):
- """Return a cache bucket for the given template. All arguments are
- mandatory but filename may be `None`.
- """
- key = self.get_cache_key(name, filename)
- checksum = self.get_source_checksum(source)
- bucket = Bucket(environment, key, checksum)
- self.load_bytecode(bucket)
- return bucket
-
- def set_bucket(self, bucket):
- """Put the bucket into the cache."""
- self.dump_bytecode(bucket)
-
-
-class FileSystemBytecodeCache(BytecodeCache):
- """A bytecode cache that stores bytecode on the filesystem. It accepts
- two arguments: The directory where the cache items are stored and a
- pattern string that is used to build the filename.
-
- If no directory is specified the system temporary items folder is used.
-
- The pattern can be used to have multiple separate caches operate on the
- same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
- is replaced with the cache key.
-
- >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
-
- This bytecode cache supports clearing of the cache using the clear method.
- """
-
- def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
- if directory is None:
- directory = tempfile.gettempdir()
- self.directory = directory
- self.pattern = pattern
-
- def _get_cache_filename(self, bucket):
- return path.join(self.directory, self.pattern % bucket.key)
-
- def load_bytecode(self, bucket):
- f = open_if_exists(self._get_cache_filename(bucket), 'rb')
- if f is not None:
- try:
- bucket.load_bytecode(f)
- finally:
- f.close()
-
- def dump_bytecode(self, bucket):
- f = open(self._get_cache_filename(bucket), 'wb')
- try:
- bucket.write_bytecode(f)
- finally:
- f.close()
-
- def clear(self):
- # imported lazily here because google app-engine doesn't support
- # write access on the file system and the function does not exist
- # normally.
- from os import remove
- files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
- for filename in files:
- try:
- remove(path.join(self.directory, filename))
- except OSError:
- pass
-
-
-class MemcachedBytecodeCache(BytecodeCache):
- """This class implements a bytecode cache that uses a memcache cache for
- storing the information. It does not enforce a specific memcache library
- (tummy's memcache or cmemcache) but will accept any class that provides
- the minimal interface required.
-
- Libraries compatible with this class:
-
- - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
- - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
- - `cmemcache <http://gijsbert.org/cmemcache/>`_
-
- (Unfortunately the django cache interface is not compatible because it
- does not support storing binary data, only unicode. You can however pass
- the underlying cache client to the bytecode cache which is available
- as `django.core.cache.cache._client`.)
-
- The minimal interface for the client passed to the constructor is this:
-
- .. class:: MinimalClientInterface
-
- .. method:: set(key, value[, timeout])
-
- Stores the bytecode in the cache. `value` is a string and
- `timeout` the timeout of the key. If timeout is not provided
- a default timeout or no timeout should be assumed, if it's
- provided it's an integer with the number of seconds the cache
- item should exist.
-
- .. method:: get(key)
-
- Returns the value for the cache key. If the item does not
- exist in the cache the return value must be `None`.
-
- The other arguments to the constructor are the prefix for all keys that
- is added before the actual cache key and the timeout for the bytecode in
- the cache system. We recommend a high (or no) timeout.
-
- This bytecode cache does not support clearing of used items in the cache.
- The clear method is a no-operation function.
- """
-
- def __init__(self, client, prefix='jinja2/bytecode/', timeout=None):
- self.client = client
- self.prefix = prefix
- self.timeout = timeout
-
- def load_bytecode(self, bucket):
- code = self.client.get(self.prefix + bucket.key)
- if code is not None:
- bucket.bytecode_from_string(code)
-
- def dump_bytecode(self, bucket):
- args = (self.prefix + bucket.key, bucket.bytecode_to_string())
- if self.timeout is not None:
- args += (self.timeout,)
- self.client.set(*args)
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/compiler.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/compiler.py
deleted file mode 100755
index c3600a08d..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/compiler.py
+++ /dev/null
@@ -1,1633 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.compiler
- ~~~~~~~~~~~~~~~
-
- Compiles nodes into python code.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-from cStringIO import StringIO
-from itertools import chain
-from copy import deepcopy
-from jinja2 import nodes
-from jinja2.nodes import EvalContext
-from jinja2.visitor import NodeVisitor, NodeTransformer
-from jinja2.exceptions import TemplateAssertionError
-from jinja2.utils import Markup, concat, escape, is_python_keyword, next
-
-
-operators = {
- 'eq': '==',
- 'ne': '!=',
- 'gt': '>',
- 'gteq': '>=',
- 'lt': '<',
- 'lteq': '<=',
- 'in': 'in',
- 'notin': 'not in'
-}
-
-try:
- exec('(0 if 0 else 0)')
-except SyntaxError:
- have_condexpr = False
-else:
- have_condexpr = True
-
-
-# what method to iterate over items do we want to use for dict iteration
-# in generated code? on 2.x let's go with iteritems, on 3.x with items
-if hasattr(dict, 'iteritems'):
- dict_item_iter = 'iteritems'
-else:
- dict_item_iter = 'items'
-
-
-# does if 0: dummy(x) get us x into the scope?
-def unoptimize_before_dead_code():
- x = 42
- def f():
- if 0: dummy(x)
- return f
-unoptimize_before_dead_code = bool(unoptimize_before_dead_code().func_closure)
-
-
-def generate(node, environment, name, filename, stream=None,
- defer_init=False):
- """Generate the python source for a node tree."""
- if not isinstance(node, nodes.Template):
- raise TypeError('Can\'t compile non template nodes')
- generator = CodeGenerator(environment, name, filename, stream, defer_init)
- generator.visit(node)
- if stream is None:
- return generator.stream.getvalue()
-
-
-def has_safe_repr(value):
- """Does the node have a safe representation?"""
- if value is None or value is NotImplemented or value is Ellipsis:
- return True
- if isinstance(value, (bool, int, long, float, complex, basestring,
- xrange, Markup)):
- return True
- if isinstance(value, (tuple, list, set, frozenset)):
- for item in value:
- if not has_safe_repr(item):
- return False
- return True
- elif isinstance(value, dict):
- for key, value in value.iteritems():
- if not has_safe_repr(key):
- return False
- if not has_safe_repr(value):
- return False
- return True
- return False
-
-
-def find_undeclared(nodes, names):
- """Check if the names passed are accessed undeclared. The return value
- is a set of all the undeclared names from the sequence of names found.
- """
- visitor = UndeclaredNameVisitor(names)
- try:
- for node in nodes:
- visitor.visit(node)
- except VisitorExit:
- pass
- return visitor.undeclared
-
-
-class Identifiers(object):
- """Tracks the status of identifiers in frames."""
-
- def __init__(self):
- # variables that are known to be declared (probably from outer
- # frames or because they are special for the frame)
- self.declared = set()
-
- # undeclared variables from outer scopes
- self.outer_undeclared = set()
-
- # names that are accessed without being explicitly declared by
- # this one or any of the outer scopes. Names can appear both in
- # declared and undeclared.
- self.undeclared = set()
-
- # names that are declared locally
- self.declared_locally = set()
-
- # names that are declared by parameters
- self.declared_parameter = set()
-
- def add_special(self, name):
- """Register a special name like `loop`."""
- self.undeclared.discard(name)
- self.declared.add(name)
-
- def is_declared(self, name, local_only=False):
- """Check if a name is declared in this or an outer scope."""
- if name in self.declared_locally or name in self.declared_parameter:
- return True
- if local_only:
- return False
- return name in self.declared
-
- def copy(self):
- return deepcopy(self)
-
-
-class Frame(object):
- """Holds compile time information for us."""
-
- def __init__(self, eval_ctx, parent=None):
- self.eval_ctx = eval_ctx
- self.identifiers = Identifiers()
-
- # a toplevel frame is the root + soft frames such as if conditions.
- self.toplevel = False
-
- # the root frame is basically just the outermost frame, so no if
- # conditions. This information is used to optimize inheritance
- # situations.
- self.rootlevel = False
-
- # in some dynamic inheritance situations the compiler needs to add
- # write tests around output statements.
- self.require_output_check = parent and parent.require_output_check
-
- # inside some tags we are using a buffer rather than yield statements.
- # this for example affects {% filter %} or {% macro %}. If a frame
- # is buffered this variable points to the name of the list used as
- # buffer.
- self.buffer = None
-
- # the name of the block we're in, otherwise None.
- self.block = parent and parent.block or None
-
- # a set of actually assigned names
- self.assigned_names = set()
-
- # the parent of this frame
- self.parent = parent
-
- if parent is not None:
- self.identifiers.declared.update(
- parent.identifiers.declared |
- parent.identifiers.declared_parameter |
- parent.assigned_names
- )
- self.identifiers.outer_undeclared.update(
- parent.identifiers.undeclared -
- self.identifiers.declared
- )
- self.buffer = parent.buffer
-
- def copy(self):
- """Create a copy of the current one."""
- rv = object.__new__(self.__class__)
- rv.__dict__.update(self.__dict__)
- rv.identifiers = object.__new__(self.identifiers.__class__)
- rv.identifiers.__dict__.update(self.identifiers.__dict__)
- return rv
-
- def inspect(self, nodes, hard_scope=False):
- """Walk the node and check for identifiers. If the scope is hard (eg:
- enforce on a python level) overrides from outer scopes are tracked
- differently.
- """
- visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
- for node in nodes:
- visitor.visit(node)
-
- def find_shadowed(self, extra=()):
- """Find all the shadowed names. extra is an iterable of variables
- that may be defined with `add_special` which may occour scoped.
- """
- i = self.identifiers
- return (i.declared | i.outer_undeclared) & \
- (i.declared_locally | i.declared_parameter) | \
- set(x for x in extra if i.is_declared(x))
-
- def inner(self):
- """Return an inner frame."""
- return Frame(self.eval_ctx, self)
-
- def soft(self):
- """Return a soft frame. A soft frame may not be modified as
- standalone thing as it shares the resources with the frame it
- was created of, but it's not a rootlevel frame any longer.
- """
- rv = self.copy()
- rv.rootlevel = False
- return rv
-
- __copy__ = copy
-
-
-class VisitorExit(RuntimeError):
- """Exception used by the `UndeclaredNameVisitor` to signal a stop."""
-
-
-class DependencyFinderVisitor(NodeVisitor):
- """A visitor that collects filter and test calls."""
-
- def __init__(self):
- self.filters = set()
- self.tests = set()
-
- def visit_Filter(self, node):
- self.generic_visit(node)
- self.filters.add(node.name)
-
- def visit_Test(self, node):
- self.generic_visit(node)
- self.tests.add(node.name)
-
- def visit_Block(self, node):
- """Stop visiting at blocks."""
-
-
-class UndeclaredNameVisitor(NodeVisitor):
- """A visitor that checks if a name is accessed without being
- declared. This is different from the frame visitor as it will
- not stop at closure frames.
- """
-
- def __init__(self, names):
- self.names = set(names)
- self.undeclared = set()
-
- def visit_Name(self, node):
- if node.ctx == 'load' and node.name in self.names:
- self.undeclared.add(node.name)
- if self.undeclared == self.names:
- raise VisitorExit()
- else:
- self.names.discard(node.name)
-
- def visit_Block(self, node):
- """Stop visiting a blocks."""
-
-
-class FrameIdentifierVisitor(NodeVisitor):
- """A visitor for `Frame.inspect`."""
-
- def __init__(self, identifiers, hard_scope):
- self.identifiers = identifiers
- self.hard_scope = hard_scope
-
- def visit_Name(self, node):
- """All assignments to names go through this function."""
- if node.ctx == 'store':
- self.identifiers.declared_locally.add(node.name)
- elif node.ctx == 'param':
- self.identifiers.declared_parameter.add(node.name)
- elif node.ctx == 'load' and not \
- self.identifiers.is_declared(node.name, self.hard_scope):
- self.identifiers.undeclared.add(node.name)
-
- def visit_If(self, node):
- self.visit(node.test)
- real_identifiers = self.identifiers
-
- old_names = real_identifiers.declared_locally | \
- real_identifiers.declared_parameter
-
- def inner_visit(nodes):
- if not nodes:
- return set()
- self.identifiers = real_identifiers.copy()
- for subnode in nodes:
- self.visit(subnode)
- rv = self.identifiers.declared_locally - old_names
- # we have to remember the undeclared variables of this branch
- # because we will have to pull them.
- real_identifiers.undeclared.update(self.identifiers.undeclared)
- self.identifiers = real_identifiers
- return rv
-
- body = inner_visit(node.body)
- else_ = inner_visit(node.else_ or ())
-
- # the differences between the two branches are also pulled as
- # undeclared variables
- real_identifiers.undeclared.update(body.symmetric_difference(else_) -
- real_identifiers.declared)
-
- # remember those that are declared.
- real_identifiers.declared_locally.update(body | else_)
-
- def visit_Macro(self, node):
- self.identifiers.declared_locally.add(node.name)
-
- def visit_Import(self, node):
- self.generic_visit(node)
- self.identifiers.declared_locally.add(node.target)
-
- def visit_FromImport(self, node):
- self.generic_visit(node)
- for name in node.names:
- if isinstance(name, tuple):
- self.identifiers.declared_locally.add(name[1])
- else:
- self.identifiers.declared_locally.add(name)
-
- def visit_Assign(self, node):
- """Visit assignments in the correct order."""
- self.visit(node.node)
- self.visit(node.target)
-
- def visit_For(self, node):
- """Visiting stops at for blocks. However the block sequence
- is visited as part of the outer scope.
- """
- self.visit(node.iter)
-
- def visit_CallBlock(self, node):
- self.visit(node.call)
-
- def visit_FilterBlock(self, node):
- self.visit(node.filter)
-
- def visit_Scope(self, node):
- """Stop visiting at scopes."""
-
- def visit_Block(self, node):
- """Stop visiting at blocks."""
-
-
-class CompilerExit(Exception):
- """Raised if the compiler encountered a situation where it just
- doesn't make sense to further process the code. Any block that
- raises such an exception is not further processed.
- """
-
-
-class CodeGenerator(NodeVisitor):
-
- def __init__(self, environment, name, filename, stream=None,
- defer_init=False):
- if stream is None:
- stream = StringIO()
- self.environment = environment
- self.name = name
- self.filename = filename
- self.stream = stream
- self.created_block_context = False
- self.defer_init = defer_init
-
- # aliases for imports
- self.import_aliases = {}
-
- # a registry for all blocks. Because blocks are moved out
- # into the global python scope they are registered here
- self.blocks = {}
-
- # the number of extends statements so far
- self.extends_so_far = 0
-
- # some templates have a rootlevel extends. In this case we
- # can safely assume that we're a child template and do some
- # more optimizations.
- self.has_known_extends = False
-
- # the current line number
- self.code_lineno = 1
-
- # registry of all filters and tests (global, not block local)
- self.tests = {}
- self.filters = {}
-
- # the debug information
- self.debug_info = []
- self._write_debug_info = None
-
- # the number of new lines before the next write()
- self._new_lines = 0
-
- # the line number of the last written statement
- self._last_line = 0
-
- # true if nothing was written so far.
- self._first_write = True
-
- # used by the `temporary_identifier` method to get new
- # unique, temporary identifier
- self._last_identifier = 0
-
- # the current indentation
- self._indentation = 0
-
- # -- Various compilation helpers
-
- def fail(self, msg, lineno):
- """Fail with a :exc:`TemplateAssertionError`."""
- raise TemplateAssertionError(msg, lineno, self.name, self.filename)
-
- def temporary_identifier(self):
- """Get a new unique identifier."""
- self._last_identifier += 1
- return 't_%d' % self._last_identifier
-
- def buffer(self, frame):
- """Enable buffering for the frame from that point onwards."""
- frame.buffer = self.temporary_identifier()
- self.writeline('%s = []' % frame.buffer)
-
- def return_buffer_contents(self, frame):
- """Return the buffer contents of the frame."""
- if frame.eval_ctx.volatile:
- self.writeline('if context.eval_ctx.autoescape:')
- self.indent()
- self.writeline('return Markup(concat(%s))' % frame.buffer)
- self.outdent()
- self.writeline('else:')
- self.indent()
- self.writeline('return concat(%s)' % frame.buffer)
- self.outdent()
- elif frame.eval_ctx.autoescape:
- self.writeline('return Markup(concat(%s))' % frame.buffer)
- else:
- self.writeline('return concat(%s)' % frame.buffer)
-
- def indent(self):
- """Indent by one."""
- self._indentation += 1
-
- def outdent(self, step=1):
- """Outdent by step."""
- self._indentation -= step
-
- def start_write(self, frame, node=None):
- """Yield or write into the frame buffer."""
- if frame.buffer is None:
- self.writeline('yield ', node)
- else:
- self.writeline('%s.append(' % frame.buffer, node)
-
- def end_write(self, frame):
- """End the writing process started by `start_write`."""
- if frame.buffer is not None:
- self.write(')')
-
- def simple_write(self, s, frame, node=None):
- """Simple shortcut for start_write + write + end_write."""
- self.start_write(frame, node)
- self.write(s)
- self.end_write(frame)
-
- def blockvisit(self, nodes, frame):
- """Visit a list of nodes as block in a frame. If the current frame
- is no buffer a dummy ``if 0: yield None`` is written automatically
- unless the force_generator parameter is set to False.
- """
- if frame.buffer is None:
- self.writeline('if 0: yield None')
- else:
- self.writeline('pass')
- try:
- for node in nodes:
- self.visit(node, frame)
- except CompilerExit:
- pass
-
- def write(self, x):
- """Write a string into the output stream."""
- if self._new_lines:
- if not self._first_write:
- self.stream.write('\n' * self._new_lines)
- self.code_lineno += self._new_lines
- if self._write_debug_info is not None:
- self.debug_info.append((self._write_debug_info,
- self.code_lineno))
- self._write_debug_info = None
- self._first_write = False
- self.stream.write(' ' * self._indentation)
- self._new_lines = 0
- self.stream.write(x)
-
- def writeline(self, x, node=None, extra=0):
- """Combination of newline and write."""
- self.newline(node, extra)
- self.write(x)
-
- def newline(self, node=None, extra=0):
- """Add one or more newlines before the next write."""
- self._new_lines = max(self._new_lines, 1 + extra)
- if node is not None and node.lineno != self._last_line:
- self._write_debug_info = node.lineno
- self._last_line = node.lineno
-
- def signature(self, node, frame, extra_kwargs=None):
- """Writes a function call to the stream for the current node.
- A leading comma is added automatically. The extra keyword
- arguments may not include python keywords otherwise a syntax
- error could occour. The extra keyword arguments should be given
- as python dict.
- """
- # if any of the given keyword arguments is a python keyword
- # we have to make sure that no invalid call is created.
- kwarg_workaround = False
- for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
- if is_python_keyword(kwarg):
- kwarg_workaround = True
- break
-
- for arg in node.args:
- self.write(', ')
- self.visit(arg, frame)
-
- if not kwarg_workaround:
- for kwarg in node.kwargs:
- self.write(', ')
- self.visit(kwarg, frame)
- if extra_kwargs is not None:
- for key, value in extra_kwargs.iteritems():
- self.write(', %s=%s' % (key, value))
- if node.dyn_args:
- self.write(', *')
- self.visit(node.dyn_args, frame)
-
- if kwarg_workaround:
- if node.dyn_kwargs is not None:
- self.write(', **dict({')
- else:
- self.write(', **{')
- for kwarg in node.kwargs:
- self.write('%r: ' % kwarg.key)
- self.visit(kwarg.value, frame)
- self.write(', ')
- if extra_kwargs is not None:
- for key, value in extra_kwargs.iteritems():
- self.write('%r: %s, ' % (key, value))
- if node.dyn_kwargs is not None:
- self.write('}, **')
- self.visit(node.dyn_kwargs, frame)
- self.write(')')
- else:
- self.write('}')
-
- elif node.dyn_kwargs is not None:
- self.write(', **')
- self.visit(node.dyn_kwargs, frame)
-
- def pull_locals(self, frame):
- """Pull all the references identifiers into the local scope."""
- for name in frame.identifiers.undeclared:
- self.writeline('l_%s = context.resolve(%r)' % (name, name))
-
- def pull_dependencies(self, nodes):
- """Pull all the dependencies."""
- visitor = DependencyFinderVisitor()
- for node in nodes:
- visitor.visit(node)
- for dependency in 'filters', 'tests':
- mapping = getattr(self, dependency)
- for name in getattr(visitor, dependency):
- if name not in mapping:
- mapping[name] = self.temporary_identifier()
- self.writeline('%s = environment.%s[%r]' %
- (mapping[name], dependency, name))
-
- def unoptimize_scope(self, frame):
- """Disable Python optimizations for the frame."""
- # XXX: this is not that nice but it has no real overhead. It
- # mainly works because python finds the locals before dead code
- # is removed. If that breaks we have to add a dummy function
- # that just accepts the arguments and does nothing.
- if frame.identifiers.declared:
- self.writeline('%sdummy(%s)' % (
- unoptimize_before_dead_code and 'if 0: ' or '',
- ', '.join('l_' + name for name in frame.identifiers.declared)
- ))
-
- def push_scope(self, frame, extra_vars=()):
- """This function returns all the shadowed variables in a dict
- in the form name: alias and will write the required assignments
- into the current scope. No indentation takes place.
-
- This also predefines locally declared variables from the loop
- body because under some circumstances it may be the case that
-
- `extra_vars` is passed to `Frame.find_shadowed`.
- """
- aliases = {}
- for name in frame.find_shadowed(extra_vars):
- aliases[name] = ident = self.temporary_identifier()
- self.writeline('%s = l_%s' % (ident, name))
- to_declare = set()
- for name in frame.identifiers.declared_locally:
- if name not in aliases:
- to_declare.add('l_' + name)
- if to_declare:
- self.writeline(' = '.join(to_declare) + ' = missing')
- return aliases
-
- def pop_scope(self, aliases, frame):
- """Restore all aliases and delete unused variables."""
- for name, alias in aliases.iteritems():
- self.writeline('l_%s = %s' % (name, alias))
- to_delete = set()
- for name in frame.identifiers.declared_locally:
- if name not in aliases:
- to_delete.add('l_' + name)
- if to_delete:
- # we cannot use the del statement here because enclosed
- # scopes can trigger a SyntaxError:
- # a = 42; b = lambda: a; del a
- self.writeline(' = '.join(to_delete) + ' = missing')
-
- def function_scoping(self, node, frame, children=None,
- find_special=True):
- """In Jinja a few statements require the help of anonymous
- functions. Those are currently macros and call blocks and in
- the future also recursive loops. As there is currently
- technical limitation that doesn't allow reading and writing a
- variable in a scope where the initial value is coming from an
- outer scope, this function tries to fall back with a common
- error message. Additionally the frame passed is modified so
- that the argumetns are collected and callers are looked up.
-
- This will return the modified frame.
- """
- # we have to iterate twice over it, make sure that works
- if children is None:
- children = node.iter_child_nodes()
- children = list(children)
- func_frame = frame.inner()
- func_frame.inspect(children, hard_scope=True)
-
- # variables that are undeclared (accessed before declaration) and
- # declared locally *and* part of an outside scope raise a template
- # assertion error. Reason: we can't generate reasonable code from
- # it without aliasing all the variables.
- # this could be fixed in Python 3 where we have the nonlocal
- # keyword or if we switch to bytecode generation
- overriden_closure_vars = (
- func_frame.identifiers.undeclared &
- func_frame.identifiers.declared &
- (func_frame.identifiers.declared_locally |
- func_frame.identifiers.declared_parameter)
- )
- if overriden_closure_vars:
- self.fail('It\'s not possible to set and access variables '
- 'derived from an outer scope! (affects: %s)' %
- ', '.join(sorted(overriden_closure_vars)), node.lineno)
-
- # remove variables from a closure from the frame's undeclared
- # identifiers.
- func_frame.identifiers.undeclared -= (
- func_frame.identifiers.undeclared &
- func_frame.identifiers.declared
- )
-
- # no special variables for this scope, abort early
- if not find_special:
- return func_frame
-
- func_frame.accesses_kwargs = False
- func_frame.accesses_varargs = False
- func_frame.accesses_caller = False
- func_frame.arguments = args = ['l_' + x.name for x in node.args]
-
- undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs'))
-
- if 'caller' in undeclared:
- func_frame.accesses_caller = True
- func_frame.identifiers.add_special('caller')
- args.append('l_caller')
- if 'kwargs' in undeclared:
- func_frame.accesses_kwargs = True
- func_frame.identifiers.add_special('kwargs')
- args.append('l_kwargs')
- if 'varargs' in undeclared:
- func_frame.accesses_varargs = True
- func_frame.identifiers.add_special('varargs')
- args.append('l_varargs')
- return func_frame
-
- def macro_body(self, node, frame, children=None):
- """Dump the function def of a macro or call block."""
- frame = self.function_scoping(node, frame, children)
- # macros are delayed, they never require output checks
- frame.require_output_check = False
- args = frame.arguments
- # XXX: this is an ugly fix for the loop nesting bug
- # (tests.test_old_bugs.test_loop_call_bug). This works around
- # a identifier nesting problem we have in general. It's just more
- # likely to happen in loops which is why we work around it. The
- # real solution would be "nonlocal" all the identifiers that are
- # leaking into a new python frame and might be used both unassigned
- # and assigned.
- if 'loop' in frame.identifiers.declared:
- args = args + ['l_loop=l_loop']
- self.writeline('def macro(%s):' % ', '.join(args), node)
- self.indent()
- self.buffer(frame)
- self.pull_locals(frame)
- self.blockvisit(node.body, frame)
- self.return_buffer_contents(frame)
- self.outdent()
- return frame
-
- def macro_def(self, node, frame):
- """Dump the macro definition for the def created by macro_body."""
- arg_tuple = ', '.join(repr(x.name) for x in node.args)
- name = getattr(node, 'name', None)
- if len(node.args) == 1:
- arg_tuple += ','
- self.write('Macro(environment, macro, %r, (%s), (' %
- (name, arg_tuple))
- for arg in node.defaults:
- self.visit(arg, frame)
- self.write(', ')
- self.write('), %r, %r, %r)' % (
- bool(frame.accesses_kwargs),
- bool(frame.accesses_varargs),
- bool(frame.accesses_caller)
- ))
-
- def position(self, node):
- """Return a human readable position for the node."""
- rv = 'line %d' % node.lineno
- if self.name is not None:
- rv += ' in ' + repr(self.name)
- return rv
-
- # -- Statement Visitors
-
- def visit_Template(self, node, frame=None):
- assert frame is None, 'no root frame allowed'
- eval_ctx = EvalContext(self.environment, self.name)
-
- from jinja2.runtime import __all__ as exported
- self.writeline('from __future__ import division')
- self.writeline('from jinja2.runtime import ' + ', '.join(exported))
- if not unoptimize_before_dead_code:
- self.writeline('dummy = lambda *x: None')
-
- # if we want a deferred initialization we cannot move the
- # environment into a local name
- envenv = not self.defer_init and ', environment=environment' or ''
-
- # do we have an extends tag at all? If not, we can save some
- # overhead by just not processing any inheritance code.
- have_extends = node.find(nodes.Extends) is not None
-
- # find all blocks
- for block in node.find_all(nodes.Block):
- if block.name in self.blocks:
- self.fail('block %r defined twice' % block.name, block.lineno)
- self.blocks[block.name] = block
-
- # find all imports and import them
- for import_ in node.find_all(nodes.ImportedName):
- if import_.importname not in self.import_aliases:
- imp = import_.importname
- self.import_aliases[imp] = alias = self.temporary_identifier()
- if '.' in imp:
- module, obj = imp.rsplit('.', 1)
- self.writeline('from %s import %s as %s' %
- (module, obj, alias))
- else:
- self.writeline('import %s as %s' % (imp, alias))
-
- # add the load name
- self.writeline('name = %r' % self.name)
-
- # generate the root render function.
- self.writeline('def root(context%s):' % envenv, extra=1)
-
- # process the root
- frame = Frame(eval_ctx)
- frame.inspect(node.body)
- frame.toplevel = frame.rootlevel = True
- frame.require_output_check = have_extends and not self.has_known_extends
- self.indent()
- if have_extends:
- self.writeline('parent_template = None')
- if 'self' in find_undeclared(node.body, ('self',)):
- frame.identifiers.add_special('self')
- self.writeline('l_self = TemplateReference(context)')
- self.pull_locals(frame)
- self.pull_dependencies(node.body)
- self.blockvisit(node.body, frame)
- self.outdent()
-
- # make sure that the parent root is called.
- if have_extends:
- if not self.has_known_extends:
- self.indent()
- self.writeline('if parent_template is not None:')
- self.indent()
- self.writeline('for event in parent_template.'
- 'root_render_func(context):')
- self.indent()
- self.writeline('yield event')
- self.outdent(2 + (not self.has_known_extends))
-
- # at this point we now have the blocks collected and can visit them too.
- for name, block in self.blocks.iteritems():
- block_frame = Frame(eval_ctx)
- block_frame.inspect(block.body)
- block_frame.block = name
- self.writeline('def block_%s(context%s):' % (name, envenv),
- block, 1)
- self.indent()
- undeclared = find_undeclared(block.body, ('self', 'super'))
- if 'self' in undeclared:
- block_frame.identifiers.add_special('self')
- self.writeline('l_self = TemplateReference(context)')
- if 'super' in undeclared:
- block_frame.identifiers.add_special('super')
- self.writeline('l_super = context.super(%r, '
- 'block_%s)' % (name, name))
- self.pull_locals(block_frame)
- self.pull_dependencies(block.body)
- self.blockvisit(block.body, block_frame)
- self.outdent()
-
- self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x)
- for x in self.blocks),
- extra=1)
-
- # add a function that returns the debug info
- self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x
- in self.debug_info))
-
- def visit_Block(self, node, frame):
- """Call a block and register it for the template."""
- level = 1
- if frame.toplevel:
- # if we know that we are a child template, there is no need to
- # check if we are one
- if self.has_known_extends:
- return
- if self.extends_so_far > 0:
- self.writeline('if parent_template is None:')
- self.indent()
- level += 1
- context = node.scoped and 'context.derived(locals())' or 'context'
- self.writeline('for event in context.blocks[%r][0](%s):' % (
- node.name, context), node)
- self.indent()
- self.simple_write('event', frame)
- self.outdent(level)
-
- def visit_Extends(self, node, frame):
- """Calls the extender."""
- if not frame.toplevel:
- self.fail('cannot use extend from a non top-level scope',
- node.lineno)
-
- # if the number of extends statements in general is zero so
- # far, we don't have to add a check if something extended
- # the template before this one.
- if self.extends_so_far > 0:
-
- # if we have a known extends we just add a template runtime
- # error into the generated code. We could catch that at compile
- # time too, but i welcome it not to confuse users by throwing the
- # same error at different times just "because we can".
- if not self.has_known_extends:
- self.writeline('if parent_template is not None:')
- self.indent()
- self.writeline('raise TemplateRuntimeError(%r)' %
- 'extended multiple times')
- self.outdent()
-
- # if we have a known extends already we don't need that code here
- # as we know that the template execution will end here.
- if self.has_known_extends:
- raise CompilerExit()
-
- self.writeline('parent_template = environment.get_template(', node)
- self.visit(node.template, frame)
- self.write(', %r)' % self.name)
- self.writeline('for name, parent_block in parent_template.'
- 'blocks.%s():' % dict_item_iter)
- self.indent()
- self.writeline('context.blocks.setdefault(name, []).'
- 'append(parent_block)')
- self.outdent()
-
- # if this extends statement was in the root level we can take
- # advantage of that information and simplify the generated code
- # in the top level from this point onwards
- if frame.rootlevel:
- self.has_known_extends = True
-
- # and now we have one more
- self.extends_so_far += 1
-
- def visit_Include(self, node, frame):
- """Handles includes."""
- if node.with_context:
- self.unoptimize_scope(frame)
- if node.ignore_missing:
- self.writeline('try:')
- self.indent()
-
- func_name = 'get_or_select_template'
- if isinstance(node.template, nodes.Const):
- if isinstance(node.template.value, basestring):
- func_name = 'get_template'
- elif isinstance(node.template.value, (tuple, list)):
- func_name = 'select_template'
- elif isinstance(node.template, (nodes.Tuple, nodes.List)):
- func_name = 'select_template'
-
- self.writeline('template = environment.%s(' % func_name, node)
- self.visit(node.template, frame)
- self.write(', %r)' % self.name)
- if node.ignore_missing:
- self.outdent()
- self.writeline('except TemplateNotFound:')
- self.indent()
- self.writeline('pass')
- self.outdent()
- self.writeline('else:')
- self.indent()
-
- if node.with_context:
- self.writeline('for event in template.root_render_func('
- 'template.new_context(context.parent, True, '
- 'locals())):')
- else:
- self.writeline('for event in template.module._body_stream:')
-
- self.indent()
- self.simple_write('event', frame)
- self.outdent()
-
- if node.ignore_missing:
- self.outdent()
-
- def visit_Import(self, node, frame):
- """Visit regular imports."""
- if node.with_context:
- self.unoptimize_scope(frame)
- self.writeline('l_%s = ' % node.target, node)
- if frame.toplevel:
- self.write('context.vars[%r] = ' % node.target)
- self.write('environment.get_template(')
- self.visit(node.template, frame)
- self.write(', %r).' % self.name)
- if node.with_context:
- self.write('make_module(context.parent, True, locals())')
- else:
- self.write('module')
- if frame.toplevel and not node.target.startswith('_'):
- self.writeline('context.exported_vars.discard(%r)' % node.target)
- frame.assigned_names.add(node.target)
-
- def visit_FromImport(self, node, frame):
- """Visit named imports."""
- self.newline(node)
- self.write('included_template = environment.get_template(')
- self.visit(node.template, frame)
- self.write(', %r).' % self.name)
- if node.with_context:
- self.write('make_module(context.parent, True)')
- else:
- self.write('module')
-
- var_names = []
- discarded_names = []
- for name in node.names:
- if isinstance(name, tuple):
- name, alias = name
- else:
- alias = name
- self.writeline('l_%s = getattr(included_template, '
- '%r, missing)' % (alias, name))
- self.writeline('if l_%s is missing:' % alias)
- self.indent()
- self.writeline('l_%s = environment.undefined(%r %% '
- 'included_template.__name__, '
- 'name=%r)' %
- (alias, 'the template %%r (imported on %s) does '
- 'not export the requested name %s' % (
- self.position(node),
- repr(name)
- ), name))
- self.outdent()
- if frame.toplevel:
- var_names.append(alias)
- if not alias.startswith('_'):
- discarded_names.append(alias)
- frame.assigned_names.add(alias)
-
- if var_names:
- if len(var_names) == 1:
- name = var_names[0]
- self.writeline('context.vars[%r] = l_%s' % (name, name))
- else:
- self.writeline('context.vars.update({%s})' % ', '.join(
- '%r: l_%s' % (name, name) for name in var_names
- ))
- if discarded_names:
- if len(discarded_names) == 1:
- self.writeline('context.exported_vars.discard(%r)' %
- discarded_names[0])
- else:
- self.writeline('context.exported_vars.difference_'
- 'update((%s))' % ', '.join(map(repr, discarded_names)))
-
- def visit_For(self, node, frame):
- # when calculating the nodes for the inner frame we have to exclude
- # the iterator contents from it
- children = node.iter_child_nodes(exclude=('iter',))
- if node.recursive:
- loop_frame = self.function_scoping(node, frame, children,
- find_special=False)
- else:
- loop_frame = frame.inner()
- loop_frame.inspect(children)
-
- # try to figure out if we have an extended loop. An extended loop
- # is necessary if the loop is in recursive mode if the special loop
- # variable is accessed in the body.
- extended_loop = node.recursive or 'loop' in \
- find_undeclared(node.iter_child_nodes(
- only=('body',)), ('loop',))
-
- # if we don't have an recursive loop we have to find the shadowed
- # variables at that point. Because loops can be nested but the loop
- # variable is a special one we have to enforce aliasing for it.
- if not node.recursive:
- aliases = self.push_scope(loop_frame, ('loop',))
-
- # otherwise we set up a buffer and add a function def
- else:
- self.writeline('def loop(reciter, loop_render_func):', node)
- self.indent()
- self.buffer(loop_frame)
- aliases = {}
-
- # make sure the loop variable is a special one and raise a template
- # assertion error if a loop tries to write to loop
- if extended_loop:
- loop_frame.identifiers.add_special('loop')
- for name in node.find_all(nodes.Name):
- if name.ctx == 'store' and name.name == 'loop':
- self.fail('Can\'t assign to special loop variable '
- 'in for-loop target', name.lineno)
-
- self.pull_locals(loop_frame)
- if node.else_:
- iteration_indicator = self.temporary_identifier()
- self.writeline('%s = 1' % iteration_indicator)
-
- # Create a fake parent loop if the else or test section of a
- # loop is accessing the special loop variable and no parent loop
- # exists.
- if 'loop' not in aliases and 'loop' in find_undeclared(
- node.iter_child_nodes(only=('else_', 'test')), ('loop',)):
- self.writeline("l_loop = environment.undefined(%r, name='loop')" %
- ("'loop' is undefined. the filter section of a loop as well "
- "as the else block doesn't have access to the special 'loop'"
- " variable of the current loop. Because there is no parent "
- "loop it's undefined. Happened in loop on %s" %
- self.position(node)))
-
- self.writeline('for ', node)
- self.visit(node.target, loop_frame)
- self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
-
- # if we have an extened loop and a node test, we filter in the
- # "outer frame".
- if extended_loop and node.test is not None:
- self.write('(')
- self.visit(node.target, loop_frame)
- self.write(' for ')
- self.visit(node.target, loop_frame)
- self.write(' in ')
- if node.recursive:
- self.write('reciter')
- else:
- self.visit(node.iter, loop_frame)
- self.write(' if (')
- test_frame = loop_frame.copy()
- self.visit(node.test, test_frame)
- self.write('))')
-
- elif node.recursive:
- self.write('reciter')
- else:
- self.visit(node.iter, loop_frame)
-
- if node.recursive:
- self.write(', recurse=loop_render_func):')
- else:
- self.write(extended_loop and '):' or ':')
-
- # tests in not extended loops become a continue
- if not extended_loop and node.test is not None:
- self.indent()
- self.writeline('if not ')
- self.visit(node.test, loop_frame)
- self.write(':')
- self.indent()
- self.writeline('continue')
- self.outdent(2)
-
- self.indent()
- self.blockvisit(node.body, loop_frame)
- if node.else_:
- self.writeline('%s = 0' % iteration_indicator)
- self.outdent()
-
- if node.else_:
- self.writeline('if %s:' % iteration_indicator)
- self.indent()
- self.blockvisit(node.else_, loop_frame)
- self.outdent()
-
- # reset the aliases if there are any.
- if not node.recursive:
- self.pop_scope(aliases, loop_frame)
-
- # if the node was recursive we have to return the buffer contents
- # and start the iteration code
- if node.recursive:
- self.return_buffer_contents(loop_frame)
- self.outdent()
- self.start_write(frame, node)
- self.write('loop(')
- self.visit(node.iter, frame)
- self.write(', loop)')
- self.end_write(frame)
-
- def visit_If(self, node, frame):
- if_frame = frame.soft()
- self.writeline('if ', node)
- self.visit(node.test, if_frame)
- self.write(':')
- self.indent()
- self.blockvisit(node.body, if_frame)
- self.outdent()
- if node.else_:
- self.writeline('else:')
- self.indent()
- self.blockvisit(node.else_, if_frame)
- self.outdent()
-
- def visit_Macro(self, node, frame):
- macro_frame = self.macro_body(node, frame)
- self.newline()
- if frame.toplevel:
- if not node.name.startswith('_'):
- self.write('context.exported_vars.add(%r)' % node.name)
- self.writeline('context.vars[%r] = ' % node.name)
- self.write('l_%s = ' % node.name)
- self.macro_def(node, macro_frame)
- frame.assigned_names.add(node.name)
-
- def visit_CallBlock(self, node, frame):
- children = node.iter_child_nodes(exclude=('call',))
- call_frame = self.macro_body(node, frame, children)
- self.writeline('caller = ')
- self.macro_def(node, call_frame)
- self.start_write(frame, node)
- self.visit_Call(node.call, call_frame, forward_caller=True)
- self.end_write(frame)
-
- def visit_FilterBlock(self, node, frame):
- filter_frame = frame.inner()
- filter_frame.inspect(node.iter_child_nodes())
- aliases = self.push_scope(filter_frame)
- self.pull_locals(filter_frame)
- self.buffer(filter_frame)
- self.blockvisit(node.body, filter_frame)
- self.start_write(frame, node)
- self.visit_Filter(node.filter, filter_frame)
- self.end_write(frame)
- self.pop_scope(aliases, filter_frame)
-
- def visit_ExprStmt(self, node, frame):
- self.newline(node)
- self.visit(node.node, frame)
-
- def visit_Output(self, node, frame):
- # if we have a known extends statement, we don't output anything
- # if we are in a require_output_check section
- if self.has_known_extends and frame.require_output_check:
- return
-
- if self.environment.finalize:
- finalize = lambda x: unicode(self.environment.finalize(x))
- else:
- finalize = unicode
-
- self.newline(node)
-
- # if we are inside a frame that requires output checking, we do so
- outdent_later = False
- if frame.require_output_check:
- self.writeline('if parent_template is None:')
- self.indent()
- outdent_later = True
-
- # try to evaluate as many chunks as possible into a static
- # string at compile time.
- body = []
- for child in node.nodes:
- try:
- const = child.as_const(frame.eval_ctx)
- except nodes.Impossible:
- body.append(child)
- continue
- # the frame can't be volatile here, becaus otherwise the
- # as_const() function would raise an Impossible exception
- # at that point.
- try:
- if frame.eval_ctx.autoescape:
- if hasattr(const, '__html__'):
- const = const.__html__()
- else:
- const = escape(const)
- const = finalize(const)
- except:
- # if something goes wrong here we evaluate the node
- # at runtime for easier debugging
- body.append(child)
- continue
- if body and isinstance(body[-1], list):
- body[-1].append(const)
- else:
- body.append([const])
-
- # if we have less than 3 nodes or a buffer we yield or extend/append
- if len(body) < 3 or frame.buffer is not None:
- if frame.buffer is not None:
- # for one item we append, for more we extend
- if len(body) == 1:
- self.writeline('%s.append(' % frame.buffer)
- else:
- self.writeline('%s.extend((' % frame.buffer)
- self.indent()
- for item in body:
- if isinstance(item, list):
- val = repr(concat(item))
- if frame.buffer is None:
- self.writeline('yield ' + val)
- else:
- self.writeline(val + ', ')
- else:
- if frame.buffer is None:
- self.writeline('yield ', item)
- else:
- self.newline(item)
- close = 1
- if frame.eval_ctx.volatile:
- self.write('(context.eval_ctx.autoescape and'
- ' escape or to_string)(')
- elif frame.eval_ctx.autoescape:
- self.write('escape(')
- else:
- self.write('to_string(')
- if self.environment.finalize is not None:
- self.write('environment.finalize(')
- close += 1
- self.visit(item, frame)
- self.write(')' * close)
- if frame.buffer is not None:
- self.write(', ')
- if frame.buffer is not None:
- # close the open parentheses
- self.outdent()
- self.writeline(len(body) == 1 and ')' or '))')
-
- # otherwise we create a format string as this is faster in that case
- else:
- format = []
- arguments = []
- for item in body:
- if isinstance(item, list):
- format.append(concat(item).replace('%', '%%'))
- else:
- format.append('%s')
- arguments.append(item)
- self.writeline('yield ')
- self.write(repr(concat(format)) + ' % (')
- idx = -1
- self.indent()
- for argument in arguments:
- self.newline(argument)
- close = 0
- if frame.eval_ctx.volatile:
- self.write('(context.eval_ctx.autoescape and'
- ' escape or to_string)(')
- close += 1
- elif frame.eval_ctx.autoescape:
- self.write('escape(')
- close += 1
- if self.environment.finalize is not None:
- self.write('environment.finalize(')
- close += 1
- self.visit(argument, frame)
- self.write(')' * close + ', ')
- self.outdent()
- self.writeline(')')
-
- if outdent_later:
- self.outdent()
-
- def visit_Assign(self, node, frame):
- self.newline(node)
- # toplevel assignments however go into the local namespace and
- # the current template's context. We create a copy of the frame
- # here and add a set so that the Name visitor can add the assigned
- # names here.
- if frame.toplevel:
- assignment_frame = frame.copy()
- assignment_frame.toplevel_assignments = set()
- else:
- assignment_frame = frame
- self.visit(node.target, assignment_frame)
- self.write(' = ')
- self.visit(node.node, frame)
-
- # make sure toplevel assignments are added to the context.
- if frame.toplevel:
- public_names = [x for x in assignment_frame.toplevel_assignments
- if not x.startswith('_')]
- if len(assignment_frame.toplevel_assignments) == 1:
- name = next(iter(assignment_frame.toplevel_assignments))
- self.writeline('context.vars[%r] = l_%s' % (name, name))
- else:
- self.writeline('context.vars.update({')
- for idx, name in enumerate(assignment_frame.toplevel_assignments):
- if idx:
- self.write(', ')
- self.write('%r: l_%s' % (name, name))
- self.write('})')
- if public_names:
- if len(public_names) == 1:
- self.writeline('context.exported_vars.add(%r)' %
- public_names[0])
- else:
- self.writeline('context.exported_vars.update((%s))' %
- ', '.join(map(repr, public_names)))
-
- # -- Expression Visitors
-
- def visit_Name(self, node, frame):
- if node.ctx == 'store' and frame.toplevel:
- frame.toplevel_assignments.add(node.name)
- self.write('l_' + node.name)
- frame.assigned_names.add(node.name)
-
- def visit_Const(self, node, frame):
- val = node.value
- if isinstance(val, float):
- self.write(str(val))
- else:
- self.write(repr(val))
-
- def visit_TemplateData(self, node, frame):
- self.write(repr(node.as_const(frame.eval_ctx)))
-
- def visit_Tuple(self, node, frame):
- self.write('(')
- idx = -1
- for idx, item in enumerate(node.items):
- if idx:
- self.write(', ')
- self.visit(item, frame)
- self.write(idx == 0 and ',)' or ')')
-
- def visit_List(self, node, frame):
- self.write('[')
- for idx, item in enumerate(node.items):
- if idx:
- self.write(', ')
- self.visit(item, frame)
- self.write(']')
-
- def visit_Dict(self, node, frame):
- self.write('{')
- for idx, item in enumerate(node.items):
- if idx:
- self.write(', ')
- self.visit(item.key, frame)
- self.write(': ')
- self.visit(item.value, frame)
- self.write('}')
-
- def binop(operator):
- def visitor(self, node, frame):
- self.write('(')
- self.visit(node.left, frame)
- self.write(' %s ' % operator)
- self.visit(node.right, frame)
- self.write(')')
- return visitor
-
- def uaop(operator):
- def visitor(self, node, frame):
- self.write('(' + operator)
- self.visit(node.node, frame)
- self.write(')')
- return visitor
-
- visit_Add = binop('+')
- visit_Sub = binop('-')
- visit_Mul = binop('*')
- visit_Div = binop('/')
- visit_FloorDiv = binop('//')
- visit_Pow = binop('**')
- visit_Mod = binop('%')
- visit_And = binop('and')
- visit_Or = binop('or')
- visit_Pos = uaop('+')
- visit_Neg = uaop('-')
- visit_Not = uaop('not ')
- del binop, uaop
-
- def visit_Concat(self, node, frame):
- if frame.eval_ctx.volatile:
- func_name = '(context.eval_ctx.volatile and' \
- ' markup_join or unicode_join)'
- elif frame.eval_ctx.autoescape:
- func_name = 'markup_join'
- else:
- func_name = 'unicode_join'
- self.write('%s((' % func_name)
- for arg in node.nodes:
- self.visit(arg, frame)
- self.write(', ')
- self.write('))')
-
- def visit_Compare(self, node, frame):
- self.visit(node.expr, frame)
- for op in node.ops:
- self.visit(op, frame)
-
- def visit_Operand(self, node, frame):
- self.write(' %s ' % operators[node.op])
- self.visit(node.expr, frame)
-
- def visit_Getattr(self, node, frame):
- self.write('environment.getattr(')
- self.visit(node.node, frame)
- self.write(', %r)' % node.attr)
-
- def visit_Getitem(self, node, frame):
- # slices bypass the environment getitem method.
- if isinstance(node.arg, nodes.Slice):
- self.visit(node.node, frame)
- self.write('[')
- self.visit(node.arg, frame)
- self.write(']')
- else:
- self.write('environment.getitem(')
- self.visit(node.node, frame)
- self.write(', ')
- self.visit(node.arg, frame)
- self.write(')')
-
- def visit_Slice(self, node, frame):
- if node.start is not None:
- self.visit(node.start, frame)
- self.write(':')
- if node.stop is not None:
- self.visit(node.stop, frame)
- if node.step is not None:
- self.write(':')
- self.visit(node.step, frame)
-
- def visit_Filter(self, node, frame):
- self.write(self.filters[node.name] + '(')
- func = self.environment.filters.get(node.name)
- if func is None:
- self.fail('no filter named %r' % node.name, node.lineno)
- if getattr(func, 'contextfilter', False):
- self.write('context, ')
- elif getattr(func, 'evalcontextfilter', False):
- self.write('context.eval_ctx, ')
- elif getattr(func, 'environmentfilter', False):
- self.write('environment, ')
-
- # if the filter node is None we are inside a filter block
- # and want to write to the current buffer
- if node.node is not None:
- self.visit(node.node, frame)
- elif frame.eval_ctx.volatile:
- self.write('(context.eval_ctx.autoescape and'
- ' Markup(concat(%s)) or concat(%s))' %
- (frame.buffer, frame.buffer))
- elif frame.eval_ctx.autoescape:
- self.write('Markup(concat(%s))' % frame.buffer)
- else:
- self.write('concat(%s)' % frame.buffer)
- self.signature(node, frame)
- self.write(')')
-
- def visit_Test(self, node, frame):
- self.write(self.tests[node.name] + '(')
- if node.name not in self.environment.tests:
- self.fail('no test named %r' % node.name, node.lineno)
- self.visit(node.node, frame)
- self.signature(node, frame)
- self.write(')')
-
- def visit_CondExpr(self, node, frame):
- def write_expr2():
- if node.expr2 is not None:
- return self.visit(node.expr2, frame)
- self.write('environment.undefined(%r)' % ('the inline if-'
- 'expression on %s evaluated to false and '
- 'no else section was defined.' % self.position(node)))
-
- if not have_condexpr:
- self.write('((')
- self.visit(node.test, frame)
- self.write(') and (')
- self.visit(node.expr1, frame)
- self.write(',) or (')
- write_expr2()
- self.write(',))[0]')
- else:
- self.write('(')
- self.visit(node.expr1, frame)
- self.write(' if ')
- self.visit(node.test, frame)
- self.write(' else ')
- write_expr2()
- self.write(')')
-
- def visit_Call(self, node, frame, forward_caller=False):
- if self.environment.sandboxed:
- self.write('environment.call(context, ')
- else:
- self.write('context.call(')
- self.visit(node.node, frame)
- extra_kwargs = forward_caller and {'caller': 'caller'} or None
- self.signature(node, frame, extra_kwargs)
- self.write(')')
-
- def visit_Keyword(self, node, frame):
- self.write(node.key + '=')
- self.visit(node.value, frame)
-
- # -- Unused nodes for extensions
-
- def visit_MarkSafe(self, node, frame):
- self.write('Markup(')
- self.visit(node.expr, frame)
- self.write(')')
-
- def visit_EnvironmentAttribute(self, node, frame):
- self.write('environment.' + node.name)
-
- def visit_ExtensionAttribute(self, node, frame):
- self.write('environment.extensions[%r].%s' % (node.identifier, node.name))
-
- def visit_ImportedName(self, node, frame):
- self.write(self.import_aliases[node.importname])
-
- def visit_InternalName(self, node, frame):
- self.write(node.name)
-
- def visit_ContextReference(self, node, frame):
- self.write('context')
-
- def visit_Continue(self, node, frame):
- self.writeline('continue', node)
-
- def visit_Break(self, node, frame):
- self.writeline('break', node)
-
- def visit_Scope(self, node, frame):
- scope_frame = frame.inner()
- scope_frame.inspect(node.iter_child_nodes())
- aliases = self.push_scope(scope_frame)
- self.pull_locals(scope_frame)
- self.blockvisit(node.body, scope_frame)
- self.pop_scope(aliases, scope_frame)
-
- def visit_EvalContextModifier(self, node, frame):
- for keyword in node.options:
- self.writeline('context.eval_ctx.%s = ' % keyword.key)
- self.visit(keyword.value, frame)
- try:
- val = keyword.value.as_const(frame.eval_ctx)
- except nodes.Impossible:
- frame.eval_ctx.volatile = True
- else:
- setattr(frame.eval_ctx, keyword.key, val)
-
- def visit_ScopedEvalContextModifier(self, node, frame):
- old_ctx_name = self.temporary_identifier()
- safed_ctx = frame.eval_ctx.save()
- self.writeline('%s = context.eval_ctx.save()' % old_ctx_name)
- self.visit_EvalContextModifier(node, frame)
- for child in node.body:
- self.visit(child, frame)
- frame.eval_ctx.revert(safed_ctx)
- self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name)
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/constants.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/constants.py
deleted file mode 100755
index d83e44bbf..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/constants.py
+++ /dev/null
@@ -1,290 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja.constants
- ~~~~~~~~~~~~~~~
-
- Various constants.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-
-
-#: list of lorem ipsum words used by the lipsum() helper function
-LOREM_IPSUM_WORDS = u'''\
-a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
-auctor augue bibendum blandit class commodo condimentum congue consectetuer
-consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
-diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
-elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
-faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
-hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
-justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
-luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
-mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
-nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
-penatibus per pharetra phasellus placerat platea porta porttitor posuere
-potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
-ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
-sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
-tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
-ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
-viverra volutpat vulputate'''
-
-
-#: a dict of all html entities + apos
-HTML_ENTITIES = {
- 'AElig': 198,
- 'Aacute': 193,
- 'Acirc': 194,
- 'Agrave': 192,
- 'Alpha': 913,
- 'Aring': 197,
- 'Atilde': 195,
- 'Auml': 196,
- 'Beta': 914,
- 'Ccedil': 199,
- 'Chi': 935,
- 'Dagger': 8225,
- 'Delta': 916,
- 'ETH': 208,
- 'Eacute': 201,
- 'Ecirc': 202,
- 'Egrave': 200,
- 'Epsilon': 917,
- 'Eta': 919,
- 'Euml': 203,
- 'Gamma': 915,
- 'Iacute': 205,
- 'Icirc': 206,
- 'Igrave': 204,
- 'Iota': 921,
- 'Iuml': 207,
- 'Kappa': 922,
- 'Lambda': 923,
- 'Mu': 924,
- 'Ntilde': 209,
- 'Nu': 925,
- 'OElig': 338,
- 'Oacute': 211,
- 'Ocirc': 212,
- 'Ograve': 210,
- 'Omega': 937,
- 'Omicron': 927,
- 'Oslash': 216,
- 'Otilde': 213,
- 'Ouml': 214,
- 'Phi': 934,
- 'Pi': 928,
- 'Prime': 8243,
- 'Psi': 936,
- 'Rho': 929,
- 'Scaron': 352,
- 'Sigma': 931,
- 'THORN': 222,
- 'Tau': 932,
- 'Theta': 920,
- 'Uacute': 218,
- 'Ucirc': 219,
- 'Ugrave': 217,
- 'Upsilon': 933,
- 'Uuml': 220,
- 'Xi': 926,
- 'Yacute': 221,
- 'Yuml': 376,
- 'Zeta': 918,
- 'aacute': 225,
- 'acirc': 226,
- 'acute': 180,
- 'aelig': 230,
- 'agrave': 224,
- 'alefsym': 8501,
- 'alpha': 945,
- 'amp': 38,
- 'and': 8743,
- 'ang': 8736,
- 'apos': 39,
- 'aring': 229,
- 'asymp': 8776,
- 'atilde': 227,
- 'auml': 228,
- 'bdquo': 8222,
- 'beta': 946,
- 'brvbar': 166,
- 'bull': 8226,
- 'cap': 8745,
- 'ccedil': 231,
- 'cedil': 184,
- 'cent': 162,
- 'chi': 967,
- 'circ': 710,
- 'clubs': 9827,
- 'cong': 8773,
- 'copy': 169,
- 'crarr': 8629,
- 'cup': 8746,
- 'curren': 164,
- 'dArr': 8659,
- 'dagger': 8224,
- 'darr': 8595,
- 'deg': 176,
- 'delta': 948,
- 'diams': 9830,
- 'divide': 247,
- 'eacute': 233,
- 'ecirc': 234,
- 'egrave': 232,
- 'empty': 8709,
- 'emsp': 8195,
- 'ensp': 8194,
- 'epsilon': 949,
- 'equiv': 8801,
- 'eta': 951,
- 'eth': 240,
- 'euml': 235,
- 'euro': 8364,
- 'exist': 8707,
- 'fnof': 402,
- 'forall': 8704,
- 'frac12': 189,
- 'frac14': 188,
- 'frac34': 190,
- 'frasl': 8260,
- 'gamma': 947,
- 'ge': 8805,
- 'gt': 62,
- 'hArr': 8660,
- 'harr': 8596,
- 'hearts': 9829,
- 'hellip': 8230,
- 'iacute': 237,
- 'icirc': 238,
- 'iexcl': 161,
- 'igrave': 236,
- 'image': 8465,
- 'infin': 8734,
- 'int': 8747,
- 'iota': 953,
- 'iquest': 191,
- 'isin': 8712,
- 'iuml': 239,
- 'kappa': 954,
- 'lArr': 8656,
- 'lambda': 955,
- 'lang': 9001,
- 'laquo': 171,
- 'larr': 8592,
- 'lceil': 8968,
- 'ldquo': 8220,
- 'le': 8804,
- 'lfloor': 8970,
- 'lowast': 8727,
- 'loz': 9674,
- 'lrm': 8206,
- 'lsaquo': 8249,
- 'lsquo': 8216,
- 'lt': 60,
- 'macr': 175,
- 'mdash': 8212,
- 'micro': 181,
- 'middot': 183,
- 'minus': 8722,
- 'mu': 956,
- 'nabla': 8711,
- 'nbsp': 160,
- 'ndash': 8211,
- 'ne': 8800,
- 'ni': 8715,
- 'not': 172,
- 'notin': 8713,
- 'nsub': 8836,
- 'ntilde': 241,
- 'nu': 957,
- 'oacute': 243,
- 'ocirc': 244,
- 'oelig': 339,
- 'ograve': 242,
- 'oline': 8254,
- 'omega': 969,
- 'omicron': 959,
- 'oplus': 8853,
- 'or': 8744,
- 'ordf': 170,
- 'ordm': 186,
- 'oslash': 248,
- 'otilde': 245,
- 'otimes': 8855,
- 'ouml': 246,
- 'para': 182,
- 'part': 8706,
- 'permil': 8240,
- 'perp': 8869,
- 'phi': 966,
- 'pi': 960,
- 'piv': 982,
- 'plusmn': 177,
- 'pound': 163,
- 'prime': 8242,
- 'prod': 8719,
- 'prop': 8733,
- 'psi': 968,
- 'quot': 34,
- 'rArr': 8658,
- 'radic': 8730,
- 'rang': 9002,
- 'raquo': 187,
- 'rarr': 8594,
- 'rceil': 8969,
- 'rdquo': 8221,
- 'real': 8476,
- 'reg': 174,
- 'rfloor': 8971,
- 'rho': 961,
- 'rlm': 8207,
- 'rsaquo': 8250,
- 'rsquo': 8217,
- 'sbquo': 8218,
- 'scaron': 353,
- 'sdot': 8901,
- 'sect': 167,
- 'shy': 173,
- 'sigma': 963,
- 'sigmaf': 962,
- 'sim': 8764,
- 'spades': 9824,
- 'sub': 8834,
- 'sube': 8838,
- 'sum': 8721,
- 'sup': 8835,
- 'sup1': 185,
- 'sup2': 178,
- 'sup3': 179,
- 'supe': 8839,
- 'szlig': 223,
- 'tau': 964,
- 'there4': 8756,
- 'theta': 952,
- 'thetasym': 977,
- 'thinsp': 8201,
- 'thorn': 254,
- 'tilde': 732,
- 'times': 215,
- 'trade': 8482,
- 'uArr': 8657,
- 'uacute': 250,
- 'uarr': 8593,
- 'ucirc': 251,
- 'ugrave': 249,
- 'uml': 168,
- 'upsih': 978,
- 'upsilon': 965,
- 'uuml': 252,
- 'weierp': 8472,
- 'xi': 958,
- 'yacute': 253,
- 'yen': 165,
- 'yuml': 255,
- 'zeta': 950,
- 'zwj': 8205,
- 'zwnj': 8204
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/debug.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/debug.py
deleted file mode 100755
index 4cb556287..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/debug.py
+++ /dev/null
@@ -1,308 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.debug
- ~~~~~~~~~~~~
-
- Implements the debug interface for Jinja. This module does some pretty
- ugly stuff with the Python traceback system in order to achieve tracebacks
- with correct line numbers, locals and contents.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import sys
-import traceback
-from jinja2.utils import CodeType, missing, internal_code
-from jinja2.exceptions import TemplateSyntaxError
-
-
-# how does the raise helper look like?
-try:
- exec("raise TypeError, 'foo'")
-except SyntaxError:
- raise_helper = 'raise __jinja_exception__[1]'
-except TypeError:
- raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
-
-
-class TracebackFrameProxy(object):
- """Proxies a traceback frame."""
-
- def __init__(self, tb):
- self.tb = tb
-
- def _set_tb_next(self, next):
- if tb_set_next is not None:
- tb_set_next(self.tb, next and next.tb or None)
- self._tb_next = next
-
- def _get_tb_next(self):
- return self._tb_next
-
- tb_next = property(_get_tb_next, _set_tb_next)
- del _get_tb_next, _set_tb_next
-
- @property
- def is_jinja_frame(self):
- return '__jinja_template__' in self.tb.tb_frame.f_globals
-
- def __getattr__(self, name):
- return getattr(self.tb, name)
-
-
-class ProcessedTraceback(object):
- """Holds a Jinja preprocessed traceback for priting or reraising."""
-
- def __init__(self, exc_type, exc_value, frames):
- assert frames, 'no frames for this traceback?'
- self.exc_type = exc_type
- self.exc_value = exc_value
- self.frames = frames
-
- def chain_frames(self):
- """Chains the frames. Requires ctypes or the speedups extension."""
- prev_tb = None
- for tb in self.frames:
- if prev_tb is not None:
- prev_tb.tb_next = tb
- prev_tb = tb
- prev_tb.tb_next = None
-
- def render_as_text(self, limit=None):
- """Return a string with the traceback."""
- lines = traceback.format_exception(self.exc_type, self.exc_value,
- self.frames[0], limit=limit)
- return ''.join(lines).rstrip()
-
- def render_as_html(self, full=False):
- """Return a unicode string with the traceback as rendered HTML."""
- from jinja2.debugrenderer import render_traceback
- return u'%s\n\n<!--\n%s\n-->' % (
- render_traceback(self, full=full),
- self.render_as_text().decode('utf-8', 'replace')
- )
-
- @property
- def is_template_syntax_error(self):
- """`True` if this is a template syntax error."""
- return isinstance(self.exc_value, TemplateSyntaxError)
-
- @property
- def exc_info(self):
- """Exception info tuple with a proxy around the frame objects."""
- return self.exc_type, self.exc_value, self.frames[0]
-
- @property
- def standard_exc_info(self):
- """Standard python exc_info for re-raising"""
- return self.exc_type, self.exc_value, self.frames[0].tb
-
-
-def make_traceback(exc_info, source_hint=None):
- """Creates a processed traceback object from the exc_info."""
- exc_type, exc_value, tb = exc_info
- if isinstance(exc_value, TemplateSyntaxError):
- exc_info = translate_syntax_error(exc_value, source_hint)
- initial_skip = 0
- else:
- initial_skip = 1
- return translate_exception(exc_info, initial_skip)
-
-
-def translate_syntax_error(error, source=None):
- """Rewrites a syntax error to please traceback systems."""
- error.source = source
- error.translated = True
- exc_info = (error.__class__, error, None)
- filename = error.filename
- if filename is None:
- filename = '<unknown>'
- return fake_exc_info(exc_info, filename, error.lineno)
-
-
-def translate_exception(exc_info, initial_skip=0):
- """If passed an exc_info it will automatically rewrite the exceptions
- all the way down to the correct line numbers and frames.
- """
- tb = exc_info[2]
- frames = []
-
- # skip some internal frames if wanted
- for x in xrange(initial_skip):
- if tb is not None:
- tb = tb.tb_next
- initial_tb = tb
-
- while tb is not None:
- # skip frames decorated with @internalcode. These are internal
- # calls we can't avoid and that are useless in template debugging
- # output.
- if tb.tb_frame.f_code in internal_code:
- tb = tb.tb_next
- continue
-
- # save a reference to the next frame if we override the current
- # one with a faked one.
- next = tb.tb_next
-
- # fake template exceptions
- template = tb.tb_frame.f_globals.get('__jinja_template__')
- if template is not None:
- lineno = template.get_corresponding_lineno(tb.tb_lineno)
- tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
- lineno)[2]
-
- frames.append(TracebackFrameProxy(tb))
- tb = next
-
- # if we don't have any exceptions in the frames left, we have to
- # reraise it unchanged.
- # XXX: can we backup here? when could this happen?
- if not frames:
- raise exc_info[0], exc_info[1], exc_info[2]
-
- traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames)
- if tb_set_next is not None:
- traceback.chain_frames()
- return traceback
-
-
-def fake_exc_info(exc_info, filename, lineno):
- """Helper for `translate_exception`."""
- exc_type, exc_value, tb = exc_info
-
- # figure the real context out
- if tb is not None:
- real_locals = tb.tb_frame.f_locals.copy()
- ctx = real_locals.get('context')
- if ctx:
- locals = ctx.get_all()
- else:
- locals = {}
- for name, value in real_locals.iteritems():
- if name.startswith('l_') and value is not missing:
- locals[name[2:]] = value
-
- # if there is a local called __jinja_exception__, we get
- # rid of it to not break the debug functionality.
- locals.pop('__jinja_exception__', None)
- else:
- locals = {}
-
- # assamble fake globals we need
- globals = {
- '__name__': filename,
- '__file__': filename,
- '__jinja_exception__': exc_info[:2],
-
- # we don't want to keep the reference to the template around
- # to not cause circular dependencies, but we mark it as Jinja
- # frame for the ProcessedTraceback
- '__jinja_template__': None
- }
-
- # and fake the exception
- code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
-
- # if it's possible, change the name of the code. This won't work
- # on some python environments such as google appengine
- try:
- if tb is None:
- location = 'template'
- else:
- function = tb.tb_frame.f_code.co_name
- if function == 'root':
- location = 'top-level template code'
- elif function.startswith('block_'):
- location = 'block "%s"' % function[6:]
- else:
- location = 'template'
- code = CodeType(0, code.co_nlocals, code.co_stacksize,
- code.co_flags, code.co_code, code.co_consts,
- code.co_names, code.co_varnames, filename,
- location, code.co_firstlineno,
- code.co_lnotab, (), ())
- except:
- pass
-
- # execute the code and catch the new traceback
- try:
- exec code in globals, locals
- except:
- exc_info = sys.exc_info()
- new_tb = exc_info[2].tb_next
-
- # return without this frame
- return exc_info[:2] + (new_tb,)
-
-
-def _init_ugly_crap():
- """This function implements a few ugly things so that we can patch the
- traceback objects. The function returned allows resetting `tb_next` on
- any python traceback object.
- """
- import ctypes
- from types import TracebackType
-
- # figure out side of _Py_ssize_t
- if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
- _Py_ssize_t = ctypes.c_int64
- else:
- _Py_ssize_t = ctypes.c_int
-
- # regular python
- class _PyObject(ctypes.Structure):
- pass
- _PyObject._fields_ = [
- ('ob_refcnt', _Py_ssize_t),
- ('ob_type', ctypes.POINTER(_PyObject))
- ]
-
- # python with trace
- if object.__basicsize__ != ctypes.sizeof(_PyObject):
- class _PyObject(ctypes.Structure):
- pass
- _PyObject._fields_ = [
- ('_ob_next', ctypes.POINTER(_PyObject)),
- ('_ob_prev', ctypes.POINTER(_PyObject)),
- ('ob_refcnt', _Py_ssize_t),
- ('ob_type', ctypes.POINTER(_PyObject))
- ]
-
- class _Traceback(_PyObject):
- pass
- _Traceback._fields_ = [
- ('tb_next', ctypes.POINTER(_Traceback)),
- ('tb_frame', ctypes.POINTER(_PyObject)),
- ('tb_lasti', ctypes.c_int),
- ('tb_lineno', ctypes.c_int)
- ]
-
- def tb_set_next(tb, next):
- """Set the tb_next attribute of a traceback object."""
- if not (isinstance(tb, TracebackType) and
- (next is None or isinstance(next, TracebackType))):
- raise TypeError('tb_set_next arguments must be traceback objects')
- obj = _Traceback.from_address(id(tb))
- if tb.tb_next is not None:
- old = _Traceback.from_address(id(tb.tb_next))
- old.ob_refcnt -= 1
- if next is None:
- obj.tb_next = ctypes.POINTER(_Traceback)()
- else:
- next = _Traceback.from_address(id(next))
- next.ob_refcnt += 1
- obj.tb_next = ctypes.pointer(next)
-
- return tb_set_next
-
-
-# try to get a tb_set_next implementation
-try:
- from jinja2._speedups import tb_set_next
-except ImportError:
- try:
- tb_set_next = _init_ugly_crap()
- except:
- tb_set_next = None
-del _init_ugly_crap
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/defaults.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/defaults.py
deleted file mode 100755
index d2d45443a..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/defaults.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.defaults
- ~~~~~~~~~~~~~~~
-
- Jinja default filters and tags.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
-
-
-# defaults for the parser / lexer
-BLOCK_START_STRING = '{%'
-BLOCK_END_STRING = '%}'
-VARIABLE_START_STRING = '{{'
-VARIABLE_END_STRING = '}}'
-COMMENT_START_STRING = '{#'
-COMMENT_END_STRING = '#}'
-LINE_STATEMENT_PREFIX = None
-LINE_COMMENT_PREFIX = None
-TRIM_BLOCKS = False
-NEWLINE_SEQUENCE = '\n'
-
-
-# default filters, tests and namespace
-from jinja2.filters import FILTERS as DEFAULT_FILTERS
-from jinja2.tests import TESTS as DEFAULT_TESTS
-DEFAULT_NAMESPACE = {
- 'range': xrange,
- 'dict': lambda **kw: kw,
- 'lipsum': generate_lorem_ipsum,
- 'cycler': Cycler,
- 'joiner': Joiner
-}
-
-
-# export all constants
-__all__ = tuple(x for x in locals().keys() if x.isupper())
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/environment.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/environment.py
deleted file mode 100755
index 233398de7..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/environment.py
+++ /dev/null
@@ -1,1095 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.environment
- ~~~~~~~~~~~~~~~~~~
-
- Provides a class that holds runtime and parsing time options.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import os
-import sys
-from jinja2 import nodes
-from jinja2.defaults import *
-from jinja2.lexer import get_lexer, TokenStream
-from jinja2.parser import Parser
-from jinja2.optimizer import optimize
-from jinja2.compiler import generate
-from jinja2.runtime import Undefined, new_context
-from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
- TemplatesNotFound
-from jinja2.utils import import_string, LRUCache, Markup, missing, \
- concat, consume, internalcode, _encode_filename
-
-
-# for direct template usage we have up to ten living environments
-_spontaneous_environments = LRUCache(10)
-
-# the function to create jinja traceback objects. This is dynamically
-# imported on the first exception in the exception handler.
-_make_traceback = None
-
-
-def get_spontaneous_environment(*args):
- """Return a new spontaneous environment. A spontaneous environment is an
- unnamed and unaccessible (in theory) environment that is used for
- templates generated from a string and not from the file system.
- """
- try:
- env = _spontaneous_environments.get(args)
- except TypeError:
- return Environment(*args)
- if env is not None:
- return env
- _spontaneous_environments[args] = env = Environment(*args)
- env.shared = True
- return env
-
-
-def create_cache(size):
- """Return the cache class for the given size."""
- if size == 0:
- return None
- if size < 0:
- return {}
- return LRUCache(size)
-
-
-def copy_cache(cache):
- """Create an empty copy of the given cache."""
- if cache is None:
- return None
- elif type(cache) is dict:
- return {}
- return LRUCache(cache.capacity)
-
-
-def load_extensions(environment, extensions):
- """Load the extensions from the list and bind it to the environment.
- Returns a dict of instanciated environments.
- """
- result = {}
- for extension in extensions:
- if isinstance(extension, basestring):
- extension = import_string(extension)
- result[extension.identifier] = extension(environment)
- return result
-
-
-def _environment_sanity_check(environment):
- """Perform a sanity check on the environment."""
- assert issubclass(environment.undefined, Undefined), 'undefined must ' \
- 'be a subclass of undefined because filters depend on it.'
- assert environment.block_start_string != \
- environment.variable_start_string != \
- environment.comment_start_string, 'block, variable and comment ' \
- 'start strings must be different'
- assert environment.newline_sequence in ('\r', '\r\n', '\n'), \
- 'newline_sequence set to unknown line ending string.'
- return environment
-
-
-class Environment(object):
- r"""The core component of Jinja is the `Environment`. It contains
- important shared variables like configuration, filters, tests,
- globals and others. Instances of this class may be modified if
- they are not shared and if no template was loaded so far.
- Modifications on environments after the first template was loaded
- will lead to surprising effects and undefined behavior.
-
- Here the possible initialization parameters:
-
- `block_start_string`
- The string marking the begin of a block. Defaults to ``'{%'``.
-
- `block_end_string`
- The string marking the end of a block. Defaults to ``'%}'``.
-
- `variable_start_string`
- The string marking the begin of a print statement.
- Defaults to ``'{{'``.
-
- `variable_end_string`
- The string marking the end of a print statement. Defaults to
- ``'}}'``.
-
- `comment_start_string`
- The string marking the begin of a comment. Defaults to ``'{#'``.
-
- `comment_end_string`
- The string marking the end of a comment. Defaults to ``'#}'``.
-
- `line_statement_prefix`
- If given and a string, this will be used as prefix for line based
- statements. See also :ref:`line-statements`.
-
- `line_comment_prefix`
- If given and a string, this will be used as prefix for line based
- based comments. See also :ref:`line-statements`.
-
- .. versionadded:: 2.2
-
- `trim_blocks`
- If this is set to ``True`` the first newline after a block is
- removed (block, not variable tag!). Defaults to `False`.
-
- `newline_sequence`
- The sequence that starts a newline. Must be one of ``'\r'``,
- ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a
- useful default for Linux and OS X systems as well as web
- applications.
-
- `extensions`
- List of Jinja extensions to use. This can either be import paths
- as strings or extension classes. For more information have a
- look at :ref:`the extensions documentation <jinja-extensions>`.
-
- `optimized`
- should the optimizer be enabled? Default is `True`.
-
- `undefined`
- :class:`Undefined` or a subclass of it that is used to represent
- undefined values in the template.
-
- `finalize`
- A callable that can be used to process the result of a variable
- expression before it is output. For example one can convert
- `None` implicitly into an empty string here.
-
- `autoescape`
- If set to true the XML/HTML autoescaping feature is enabled by
- default. For more details about auto escaping see
- :class:`~jinja2.utils.Markup`. As of Jinja 2.4 this can also
- be a callable that is passed the template name and has to
- return `True` or `False` depending on autoescape should be
- enabled by default.
-
- .. versionchanged:: 2.4
- `autoescape` can now be a function
-
- `loader`
- The template loader for this environment.
-
- `cache_size`
- The size of the cache. Per default this is ``50`` which means
- that if more than 50 templates are loaded the loader will clean
- out the least recently used template. If the cache size is set to
- ``0`` templates are recompiled all the time, if the cache size is
- ``-1`` the cache will not be cleaned.
-
- `auto_reload`
- Some loaders load templates from locations where the template
- sources may change (ie: file system or database). If
- `auto_reload` is set to `True` (default) every time a template is
- requested the loader checks if the source changed and if yes, it
- will reload the template. For higher performance it's possible to
- disable that.
-
- `bytecode_cache`
- If set to a bytecode cache object, this object will provide a
- cache for the internal Jinja bytecode so that templates don't
- have to be parsed if they were not changed.
-
- See :ref:`bytecode-cache` for more information.
- """
-
- #: if this environment is sandboxed. Modifying this variable won't make
- #: the environment sandboxed though. For a real sandboxed environment
- #: have a look at jinja2.sandbox
- sandboxed = False
-
- #: True if the environment is just an overlay
- overlayed = False
-
- #: the environment this environment is linked to if it is an overlay
- linked_to = None
-
- #: shared environments have this set to `True`. A shared environment
- #: must not be modified
- shared = False
-
- #: these are currently EXPERIMENTAL undocumented features.
- exception_handler = None
- exception_formatter = None
-
- def __init__(self,
- block_start_string=BLOCK_START_STRING,
- block_end_string=BLOCK_END_STRING,
- variable_start_string=VARIABLE_START_STRING,
- variable_end_string=VARIABLE_END_STRING,
- comment_start_string=COMMENT_START_STRING,
- comment_end_string=COMMENT_END_STRING,
- line_statement_prefix=LINE_STATEMENT_PREFIX,
- line_comment_prefix=LINE_COMMENT_PREFIX,
- trim_blocks=TRIM_BLOCKS,
- newline_sequence=NEWLINE_SEQUENCE,
- extensions=(),
- optimized=True,
- undefined=Undefined,
- finalize=None,
- autoescape=False,
- loader=None,
- cache_size=50,
- auto_reload=True,
- bytecode_cache=None):
- # !!Important notice!!
- # The constructor accepts quite a few arguments that should be
- # passed by keyword rather than position. However it's important to
- # not change the order of arguments because it's used at least
- # internally in those cases:
- # - spontaneus environments (i18n extension and Template)
- # - unittests
- # If parameter changes are required only add parameters at the end
- # and don't change the arguments (or the defaults!) of the arguments
- # existing already.
-
- # lexer / parser information
- self.block_start_string = block_start_string
- self.block_end_string = block_end_string
- self.variable_start_string = variable_start_string
- self.variable_end_string = variable_end_string
- self.comment_start_string = comment_start_string
- self.comment_end_string = comment_end_string
- self.line_statement_prefix = line_statement_prefix
- self.line_comment_prefix = line_comment_prefix
- self.trim_blocks = trim_blocks
- self.newline_sequence = newline_sequence
-
- # runtime information
- self.undefined = undefined
- self.optimized = optimized
- self.finalize = finalize
- self.autoescape = autoescape
-
- # defaults
- self.filters = DEFAULT_FILTERS.copy()
- self.tests = DEFAULT_TESTS.copy()
- self.globals = DEFAULT_NAMESPACE.copy()
-
- # set the loader provided
- self.loader = loader
- self.bytecode_cache = None
- self.cache = create_cache(cache_size)
- self.bytecode_cache = bytecode_cache
- self.auto_reload = auto_reload
-
- # load extensions
- self.extensions = load_extensions(self, extensions)
-
- _environment_sanity_check(self)
-
- def extend(self, **attributes):
- """Add the items to the instance of the environment if they do not exist
- yet. This is used by :ref:`extensions <writing-extensions>` to register
- callbacks and configuration values without breaking inheritance.
- """
- for key, value in attributes.iteritems():
- if not hasattr(self, key):
- setattr(self, key, value)
-
- def overlay(self, block_start_string=missing, block_end_string=missing,
- variable_start_string=missing, variable_end_string=missing,
- comment_start_string=missing, comment_end_string=missing,
- line_statement_prefix=missing, line_comment_prefix=missing,
- trim_blocks=missing, extensions=missing, optimized=missing,
- undefined=missing, finalize=missing, autoescape=missing,
- loader=missing, cache_size=missing, auto_reload=missing,
- bytecode_cache=missing):
- """Create a new overlay environment that shares all the data with the
- current environment except of cache and the overridden attributes.
- Extensions cannot be removed for an overlayed environment. An overlayed
- environment automatically gets all the extensions of the environment it
- is linked to plus optional extra extensions.
-
- Creating overlays should happen after the initial environment was set
- up completely. Not all attributes are truly linked, some are just
- copied over so modifications on the original environment may not shine
- through.
- """
- args = dict(locals())
- del args['self'], args['cache_size'], args['extensions']
-
- rv = object.__new__(self.__class__)
- rv.__dict__.update(self.__dict__)
- rv.overlayed = True
- rv.linked_to = self
-
- for key, value in args.iteritems():
- if value is not missing:
- setattr(rv, key, value)
-
- if cache_size is not missing:
- rv.cache = create_cache(cache_size)
- else:
- rv.cache = copy_cache(self.cache)
-
- rv.extensions = {}
- for key, value in self.extensions.iteritems():
- rv.extensions[key] = value.bind(rv)
- if extensions is not missing:
- rv.extensions.update(load_extensions(extensions))
-
- return _environment_sanity_check(rv)
-
- lexer = property(get_lexer, doc="The lexer for this environment.")
-
- def iter_extensions(self):
- """Iterates over the extensions by priority."""
- return iter(sorted(self.extensions.values(),
- key=lambda x: x.priority))
-
- def getitem(self, obj, argument):
- """Get an item or attribute of an object but prefer the item."""
- try:
- return obj[argument]
- except (TypeError, LookupError):
- if isinstance(argument, basestring):
- try:
- attr = str(argument)
- except:
- pass
- else:
- try:
- return getattr(obj, attr)
- except AttributeError:
- pass
- return self.undefined(obj=obj, name=argument)
-
- def getattr(self, obj, attribute):
- """Get an item or attribute of an object but prefer the attribute.
- Unlike :meth:`getitem` the attribute *must* be a bytestring.
- """
- try:
- return getattr(obj, attribute)
- except AttributeError:
- pass
- try:
- return obj[attribute]
- except (TypeError, LookupError, AttributeError):
- return self.undefined(obj=obj, name=attribute)
-
- @internalcode
- def parse(self, source, name=None, filename=None):
- """Parse the sourcecode and return the abstract syntax tree. This
- tree of nodes is used by the compiler to convert the template into
- executable source- or bytecode. This is useful for debugging or to
- extract information from templates.
-
- If you are :ref:`developing Jinja2 extensions <writing-extensions>`
- this gives you a good overview of the node tree generated.
- """
- try:
- return self._parse(source, name, filename)
- except TemplateSyntaxError:
- exc_info = sys.exc_info()
- self.handle_exception(exc_info, source_hint=source)
-
- def _parse(self, source, name, filename):
- """Internal parsing function used by `parse` and `compile`."""
- return Parser(self, source, name, _encode_filename(filename)).parse()
-
- def lex(self, source, name=None, filename=None):
- """Lex the given sourcecode and return a generator that yields
- tokens as tuples in the form ``(lineno, token_type, value)``.
- This can be useful for :ref:`extension development <writing-extensions>`
- and debugging templates.
-
- This does not perform preprocessing. If you want the preprocessing
- of the extensions to be applied you have to filter source through
- the :meth:`preprocess` method.
- """
- source = unicode(source)
- try:
- return self.lexer.tokeniter(source, name, filename)
- except TemplateSyntaxError:
- exc_info = sys.exc_info()
- self.handle_exception(exc_info, source_hint=source)
-
- def preprocess(self, source, name=None, filename=None):
- """Preprocesses the source with all extensions. This is automatically
- called for all parsing and compiling methods but *not* for :meth:`lex`
- because there you usually only want the actual source tokenized.
- """
- return reduce(lambda s, e: e.preprocess(s, name, filename),
- self.iter_extensions(), unicode(source))
-
- def _tokenize(self, source, name, filename=None, state=None):
- """Called by the parser to do the preprocessing and filtering
- for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
- """
- source = self.preprocess(source, name, filename)
- stream = self.lexer.tokenize(source, name, filename, state)
- for ext in self.iter_extensions():
- stream = ext.filter_stream(stream)
- if not isinstance(stream, TokenStream):
- stream = TokenStream(stream, name, filename)
- return stream
-
- @internalcode
- def compile(self, source, name=None, filename=None, raw=False,
- defer_init=False):
- """Compile a node or template source code. The `name` parameter is
- the load name of the template after it was joined using
- :meth:`join_path` if necessary, not the filename on the file system.
- the `filename` parameter is the estimated filename of the template on
- the file system. If the template came from a database or memory this
- can be omitted.
-
- The return value of this method is a python code object. If the `raw`
- parameter is `True` the return value will be a string with python
- code equivalent to the bytecode returned otherwise. This method is
- mainly used internally.
-
- `defer_init` is use internally to aid the module code generator. This
- causes the generated code to be able to import without the global
- environment variable to be set.
-
- .. versionadded:: 2.4
- `defer_init` parameter added.
- """
- source_hint = None
- try:
- if isinstance(source, basestring):
- source_hint = source
- source = self._parse(source, name, filename)
- if self.optimized:
- source = optimize(source, self)
- source = generate(source, self, name, filename,
- defer_init=defer_init)
- if raw:
- return source
- if filename is None:
- filename = '<template>'
- else:
- filename = _encode_filename(filename)
- return compile(source, filename, 'exec')
- except TemplateSyntaxError:
- exc_info = sys.exc_info()
- self.handle_exception(exc_info, source_hint=source)
-
- def compile_expression(self, source, undefined_to_none=True):
- """A handy helper method that returns a callable that accepts keyword
- arguments that appear as variables in the expression. If called it
- returns the result of the expression.
-
- This is useful if applications want to use the same rules as Jinja
- in template "configuration files" or similar situations.
-
- Example usage:
-
- >>> env = Environment()
- >>> expr = env.compile_expression('foo == 42')
- >>> expr(foo=23)
- False
- >>> expr(foo=42)
- True
-
- Per default the return value is converted to `None` if the
- expression returns an undefined value. This can be changed
- by setting `undefined_to_none` to `False`.
-
- >>> env.compile_expression('var')() is None
- True
- >>> env.compile_expression('var', undefined_to_none=False)()
- Undefined
-
- .. versionadded:: 2.1
- """
- parser = Parser(self, source, state='variable')
- exc_info = None
- try:
- expr = parser.parse_expression()
- if not parser.stream.eos:
- raise TemplateSyntaxError('chunk after expression',
- parser.stream.current.lineno,
- None, None)
- expr.set_environment(self)
- except TemplateSyntaxError:
- exc_info = sys.exc_info()
- if exc_info is not None:
- self.handle_exception(exc_info, source_hint=source)
- body = [nodes.Assign(nodes.Name('result', 'store'), expr, lineno=1)]
- template = self.from_string(nodes.Template(body, lineno=1))
- return TemplateExpression(template, undefined_to_none)
-
- def compile_templates(self, target, extensions=None, filter_func=None,
- zip='deflated', log_function=None,
- ignore_errors=True, py_compile=False):
- """Compiles all the templates the loader can find, compiles them
- and stores them in `target`. If `zip` is `None`, instead of in a
- zipfile, the templates will be will be stored in a directory.
- By default a deflate zip algorithm is used, to switch to
- the stored algorithm, `zip` can be set to ``'stored'``.
-
- `extensions` and `filter_func` are passed to :meth:`list_templates`.
- Each template returned will be compiled to the target folder or
- zipfile.
-
- By default template compilation errors are ignored. In case a
- log function is provided, errors are logged. If you want template
- syntax errors to abort the compilation you can set `ignore_errors`
- to `False` and you will get an exception on syntax errors.
-
- If `py_compile` is set to `True` .pyc files will be written to the
- target instead of standard .py files.
-
- .. versionadded:: 2.4
- """
- from jinja2.loaders import ModuleLoader
-
- if log_function is None:
- log_function = lambda x: None
-
- if py_compile:
- import imp, struct, marshal
- py_header = imp.get_magic() + \
- u'\xff\xff\xff\xff'.encode('iso-8859-15')
-
- def write_file(filename, data, mode):
- if zip:
- info = ZipInfo(filename)
- info.external_attr = 0o755 << 16L
- zip_file.writestr(info, data)
- else:
- f = open(os.path.join(target, filename), mode)
- try:
- f.write(data)
- finally:
- f.close()
-
- if zip is not None:
- from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
- zip_file = ZipFile(target, 'w', dict(deflated=ZIP_DEFLATED,
- stored=ZIP_STORED)[zip])
- log_function('Compiling into Zip archive "%s"' % target)
- else:
- if not os.path.isdir(target):
- os.makedirs(target)
- log_function('Compiling into folder "%s"' % target)
-
- try:
- for name in self.list_templates(extensions, filter_func):
- source, filename, _ = self.loader.get_source(self, name)
- try:
- code = self.compile(source, name, filename, True, True)
- except TemplateSyntaxError, e:
- if not ignore_errors:
- raise
- log_function('Could not compile "%s": %s' % (name, e))
- continue
-
- filename = ModuleLoader.get_module_filename(name)
-
- if py_compile:
- c = compile(code, _encode_filename(filename), 'exec')
- write_file(filename + 'c', py_header +
- marshal.dumps(c), 'wb')
- log_function('Byte-compiled "%s" as %s' %
- (name, filename + 'c'))
- else:
- write_file(filename, code, 'w')
- log_function('Compiled "%s" as %s' % (name, filename))
- finally:
- if zip:
- zip_file.close()
-
- log_function('Finished compiling templates')
-
- def list_templates(self, extensions=None, filter_func=None):
- """Returns a list of templates for this environment. This requires
- that the loader supports the loader's
- :meth:`~BaseLoader.list_templates` method.
-
- If there are other files in the template folder besides the
- actual templates, the returned list can be filtered. There are two
- ways: either `extensions` is set to a list of file extensions for
- templates, or a `filter_func` can be provided which is a callable that
- is passed a template name and should return `True` if it should end up
- in the result list.
-
- If the loader does not support that, a :exc:`TypeError` is raised.
- """
- x = self.loader.list_templates()
- if extensions is not None:
- if filter_func is not None:
- raise TypeError('either extensions or filter_func '
- 'can be passed, but not both')
- filter_func = lambda x: '.' in x and \
- x.rsplit('.', 1)[1] in extensions
- if filter_func is not None:
- x = filter(filter_func, x)
- return x
-
- def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
- """Exception handling helper. This is used internally to either raise
- rewritten exceptions or return a rendered traceback for the template.
- """
- global _make_traceback
- if exc_info is None:
- exc_info = sys.exc_info()
-
- # the debugging module is imported when it's used for the first time.
- # we're doing a lot of stuff there and for applications that do not
- # get any exceptions in template rendering there is no need to load
- # all of that.
- if _make_traceback is None:
- from jinja2.debug import make_traceback as _make_traceback
- traceback = _make_traceback(exc_info, source_hint)
- if rendered and self.exception_formatter is not None:
- return self.exception_formatter(traceback)
- if self.exception_handler is not None:
- self.exception_handler(traceback)
- exc_type, exc_value, tb = traceback.standard_exc_info
- raise exc_type, exc_value, tb
-
- def join_path(self, template, parent):
- """Join a template with the parent. By default all the lookups are
- relative to the loader root so this method returns the `template`
- parameter unchanged, but if the paths should be relative to the
- parent template, this function can be used to calculate the real
- template name.
-
- Subclasses may override this method and implement template path
- joining here.
- """
- return template
-
- @internalcode
- def _load_template(self, name, globals):
- if self.loader is None:
- raise TypeError('no loader for this environment specified')
- if self.cache is not None:
- template = self.cache.get(name)
- if template is not None and (not self.auto_reload or \
- template.is_up_to_date):
- return template
- template = self.loader.load(self, name, globals)
- if self.cache is not None:
- self.cache[name] = template
- return template
-
- @internalcode
- def get_template(self, name, parent=None, globals=None):
- """Load a template from the loader. If a loader is configured this
- method ask the loader for the template and returns a :class:`Template`.
- If the `parent` parameter is not `None`, :meth:`join_path` is called
- to get the real template name before loading.
-
- The `globals` parameter can be used to provide template wide globals.
- These variables are available in the context at render time.
-
- If the template does not exist a :exc:`TemplateNotFound` exception is
- raised.
-
- .. versionchanged:: 2.4
- If `name` is a :class:`Template` object it is returned from the
- function unchanged.
- """
- if isinstance(name, Template):
- return name
- if parent is not None:
- name = self.join_path(name, parent)
- return self._load_template(name, self.make_globals(globals))
-
- @internalcode
- def select_template(self, names, parent=None, globals=None):
- """Works like :meth:`get_template` but tries a number of templates
- before it fails. If it cannot find any of the templates, it will
- raise a :exc:`TemplatesNotFound` exception.
-
- .. versionadded:: 2.3
-
- .. versionchanged:: 2.4
- If `names` contains a :class:`Template` object it is returned
- from the function unchanged.
- """
- if not names:
- raise TemplatesNotFound(message=u'Tried to select from an empty list '
- u'of templates.')
- globals = self.make_globals(globals)
- for name in names:
- if isinstance(name, Template):
- return name
- if parent is not None:
- name = self.join_path(name, parent)
- try:
- return self._load_template(name, globals)
- except TemplateNotFound:
- pass
- raise TemplatesNotFound(names)
-
- @internalcode
- def get_or_select_template(self, template_name_or_list,
- parent=None, globals=None):
- """Does a typecheck and dispatches to :meth:`select_template`
- if an iterable of template names is given, otherwise to
- :meth:`get_template`.
-
- .. versionadded:: 2.3
- """
- if isinstance(template_name_or_list, basestring):
- return self.get_template(template_name_or_list, parent, globals)
- elif isinstance(template_name_or_list, Template):
- return template_name_or_list
- return self.select_template(template_name_or_list, parent, globals)
-
- def from_string(self, source, globals=None, template_class=None):
- """Load a template from a string. This parses the source given and
- returns a :class:`Template` object.
- """
- globals = self.make_globals(globals)
- cls = template_class or self.template_class
- return cls.from_code(self, self.compile(source), globals, None)
-
- def make_globals(self, d):
- """Return a dict for the globals."""
- if not d:
- return self.globals
- return dict(self.globals, **d)
-
-
-class Template(object):
- """The central template object. This class represents a compiled template
- and is used to evaluate it.
-
- Normally the template object is generated from an :class:`Environment` but
- it also has a constructor that makes it possible to create a template
- instance directly using the constructor. It takes the same arguments as
- the environment constructor but it's not possible to specify a loader.
-
- Every template object has a few methods and members that are guaranteed
- to exist. However it's important that a template object should be
- considered immutable. Modifications on the object are not supported.
-
- Template objects created from the constructor rather than an environment
- do have an `environment` attribute that points to a temporary environment
- that is probably shared with other templates created with the constructor
- and compatible settings.
-
- >>> template = Template('Hello {{ name }}!')
- >>> template.render(name='John Doe')
- u'Hello John Doe!'
-
- >>> stream = template.stream(name='John Doe')
- >>> stream.next()
- u'Hello John Doe!'
- >>> stream.next()
- Traceback (most recent call last):
- ...
- StopIteration
- """
-
- def __new__(cls, source,
- block_start_string=BLOCK_START_STRING,
- block_end_string=BLOCK_END_STRING,
- variable_start_string=VARIABLE_START_STRING,
- variable_end_string=VARIABLE_END_STRING,
- comment_start_string=COMMENT_START_STRING,
- comment_end_string=COMMENT_END_STRING,
- line_statement_prefix=LINE_STATEMENT_PREFIX,
- line_comment_prefix=LINE_COMMENT_PREFIX,
- trim_blocks=TRIM_BLOCKS,
- newline_sequence=NEWLINE_SEQUENCE,
- extensions=(),
- optimized=True,
- undefined=Undefined,
- finalize=None,
- autoescape=False):
- env = get_spontaneous_environment(
- block_start_string, block_end_string, variable_start_string,
- variable_end_string, comment_start_string, comment_end_string,
- line_statement_prefix, line_comment_prefix, trim_blocks,
- newline_sequence, frozenset(extensions), optimized, undefined,
- finalize, autoescape, None, 0, False, None)
- return env.from_string(source, template_class=cls)
-
- @classmethod
- def from_code(cls, environment, code, globals, uptodate=None):
- """Creates a template object from compiled code and the globals. This
- is used by the loaders and environment to create a template object.
- """
- namespace = {
- 'environment': environment,
- '__file__': code.co_filename
- }
- exec code in namespace
- rv = cls._from_namespace(environment, namespace, globals)
- rv._uptodate = uptodate
- return rv
-
- @classmethod
- def from_module_dict(cls, environment, module_dict, globals):
- """Creates a template object from a module. This is used by the
- module loader to create a template object.
-
- .. versionadded:: 2.4
- """
- return cls._from_namespace(environment, module_dict, globals)
-
- @classmethod
- def _from_namespace(cls, environment, namespace, globals):
- t = object.__new__(cls)
- t.environment = environment
- t.globals = globals
- t.name = namespace['name']
- t.filename = namespace['__file__']
- t.blocks = namespace['blocks']
-
- # render function and module
- t.root_render_func = namespace['root']
- t._module = None
-
- # debug and loader helpers
- t._debug_info = namespace['debug_info']
- t._uptodate = None
-
- # store the reference
- namespace['environment'] = environment
- namespace['__jinja_template__'] = t
-
- return t
-
- def render(self, *args, **kwargs):
- """This method accepts the same arguments as the `dict` constructor:
- A dict, a dict subclass or some keyword arguments. If no arguments
- are given the context will be empty. These two calls do the same::
-
- template.render(knights='that say nih')
- template.render({'knights': 'that say nih'})
-
- This will return the rendered template as unicode string.
- """
- vars = dict(*args, **kwargs)
- try:
- return concat(self.root_render_func(self.new_context(vars)))
- except:
- exc_info = sys.exc_info()
- return self.environment.handle_exception(exc_info, True)
-
- def stream(self, *args, **kwargs):
- """Works exactly like :meth:`generate` but returns a
- :class:`TemplateStream`.
- """
- return TemplateStream(self.generate(*args, **kwargs))
-
- def generate(self, *args, **kwargs):
- """For very large templates it can be useful to not render the whole
- template at once but evaluate each statement after another and yield
- piece for piece. This method basically does exactly that and returns
- a generator that yields one item after another as unicode strings.
-
- It accepts the same arguments as :meth:`render`.
- """
- vars = dict(*args, **kwargs)
- try:
- for event in self.root_render_func(self.new_context(vars)):
- yield event
- except:
- exc_info = sys.exc_info()
- else:
- return
- yield self.environment.handle_exception(exc_info, True)
-
- def new_context(self, vars=None, shared=False, locals=None):
- """Create a new :class:`Context` for this template. The vars
- provided will be passed to the template. Per default the globals
- are added to the context. If shared is set to `True` the data
- is passed as it to the context without adding the globals.
-
- `locals` can be a dict of local variables for internal usage.
- """
- return new_context(self.environment, self.name, self.blocks,
- vars, shared, self.globals, locals)
-
- def make_module(self, vars=None, shared=False, locals=None):
- """This method works like the :attr:`module` attribute when called
- without arguments but it will evaluate the template on every call
- rather than caching it. It's also possible to provide
- a dict which is then used as context. The arguments are the same
- as for the :meth:`new_context` method.
- """
- return TemplateModule(self, self.new_context(vars, shared, locals))
-
- @property
- def module(self):
- """The template as module. This is used for imports in the
- template runtime but is also useful if one wants to access
- exported template variables from the Python layer:
-
- >>> t = Template('{% macro foo() %}42{% endmacro %}23')
- >>> unicode(t.module)
- u'23'
- >>> t.module.foo()
- u'42'
- """
- if self._module is not None:
- return self._module
- self._module = rv = self.make_module()
- return rv
-
- def get_corresponding_lineno(self, lineno):
- """Return the source line number of a line number in the
- generated bytecode as they are not in sync.
- """
- for template_line, code_line in reversed(self.debug_info):
- if code_line <= lineno:
- return template_line
- return 1
-
- @property
- def is_up_to_date(self):
- """If this variable is `False` there is a newer version available."""
- if self._uptodate is None:
- return True
- return self._uptodate()
-
- @property
- def debug_info(self):
- """The debug info mapping."""
- return [tuple(map(int, x.split('='))) for x in
- self._debug_info.split('&')]
-
- def __repr__(self):
- if self.name is None:
- name = 'memory:%x' % id(self)
- else:
- name = repr(self.name)
- return '<%s %s>' % (self.__class__.__name__, name)
-
-
-class TemplateModule(object):
- """Represents an imported template. All the exported names of the
- template are available as attributes on this object. Additionally
- converting it into an unicode- or bytestrings renders the contents.
- """
-
- def __init__(self, template, context):
- self._body_stream = list(template.root_render_func(context))
- self.__dict__.update(context.get_exported())
- self.__name__ = template.name
-
- def __html__(self):
- return Markup(concat(self._body_stream))
-
- def __str__(self):
- return unicode(self).encode('utf-8')
-
- # unicode goes after __str__ because we configured 2to3 to rename
- # __unicode__ to __str__. because the 2to3 tree is not designed to
- # remove nodes from it, we leave the above __str__ around and let
- # it override at runtime.
- def __unicode__(self):
- return concat(self._body_stream)
-
- def __repr__(self):
- if self.__name__ is None:
- name = 'memory:%x' % id(self)
- else:
- name = repr(self.__name__)
- return '<%s %s>' % (self.__class__.__name__, name)
-
-
-class TemplateExpression(object):
- """The :meth:`jinja2.Environment.compile_expression` method returns an
- instance of this object. It encapsulates the expression-like access
- to the template with an expression it wraps.
- """
-
- def __init__(self, template, undefined_to_none):
- self._template = template
- self._undefined_to_none = undefined_to_none
-
- def __call__(self, *args, **kwargs):
- context = self._template.new_context(dict(*args, **kwargs))
- consume(self._template.root_render_func(context))
- rv = context.vars['result']
- if self._undefined_to_none and isinstance(rv, Undefined):
- rv = None
- return rv
-
-
-class TemplateStream(object):
- """A template stream works pretty much like an ordinary python generator
- but it can buffer multiple items to reduce the number of total iterations.
- Per default the output is unbuffered which means that for every unbuffered
- instruction in the template one unicode string is yielded.
-
- If buffering is enabled with a buffer size of 5, five items are combined
- into a new unicode string. This is mainly useful if you are streaming
- big templates to a client via WSGI which flushes after each iteration.
- """
-
- def __init__(self, gen):
- self._gen = gen
- self.disable_buffering()
-
- def dump(self, fp, encoding=None, errors='strict'):
- """Dump the complete stream into a file or file-like object.
- Per default unicode strings are written, if you want to encode
- before writing specifiy an `encoding`.
-
- Example usage::
-
- Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
- """
- close = False
- if isinstance(fp, basestring):
- fp = file(fp, 'w')
- close = True
- try:
- if encoding is not None:
- iterable = (x.encode(encoding, errors) for x in self)
- else:
- iterable = self
- if hasattr(fp, 'writelines'):
- fp.writelines(iterable)
- else:
- for item in iterable:
- fp.write(item)
- finally:
- if close:
- fp.close()
-
- def disable_buffering(self):
- """Disable the output buffering."""
- self._next = self._gen.next
- self.buffered = False
-
- def enable_buffering(self, size=5):
- """Enable buffering. Buffer `size` items before yielding them."""
- if size <= 1:
- raise ValueError('buffer size too small')
-
- def generator(next):
- buf = []
- c_size = 0
- push = buf.append
-
- while 1:
- try:
- while c_size < size:
- c = next()
- push(c)
- if c:
- c_size += 1
- except StopIteration:
- if not c_size:
- return
- yield concat(buf)
- del buf[:]
- c_size = 0
-
- self.buffered = True
- self._next = generator(self._gen.next).next
-
- def __iter__(self):
- return self
-
- def next(self):
- return self._next()
-
-
-# hook in default template class. if anyone reads this comment: ignore that
-# it's possible to use custom templates ;-)
-Environment.template_class = Template
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/exceptions.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/exceptions.py
deleted file mode 100755
index 771f6a8d7..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/exceptions.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.exceptions
- ~~~~~~~~~~~~~~~~~
-
- Jinja exceptions.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-
-
-class TemplateError(Exception):
- """Baseclass for all template errors."""
-
- def __init__(self, message=None):
- if message is not None:
- message = unicode(message).encode('utf-8')
- Exception.__init__(self, message)
-
- @property
- def message(self):
- if self.args:
- message = self.args[0]
- if message is not None:
- return message.decode('utf-8', 'replace')
-
-
-class TemplateNotFound(IOError, LookupError, TemplateError):
- """Raised if a template does not exist."""
-
- # looks weird, but removes the warning descriptor that just
- # bogusly warns us about message being deprecated
- message = None
-
- def __init__(self, name, message=None):
- IOError.__init__(self)
- if message is None:
- message = name
- self.message = message
- self.name = name
- self.templates = [name]
-
- def __str__(self):
- return self.message.encode('utf-8')
-
- # unicode goes after __str__ because we configured 2to3 to rename
- # __unicode__ to __str__. because the 2to3 tree is not designed to
- # remove nodes from it, we leave the above __str__ around and let
- # it override at runtime.
- def __unicode__(self):
- return self.message
-
-
-class TemplatesNotFound(TemplateNotFound):
- """Like :class:`TemplateNotFound` but raised if multiple templates
- are selected. This is a subclass of :class:`TemplateNotFound`
- exception, so just catching the base exception will catch both.
-
- .. versionadded:: 2.2
- """
-
- def __init__(self, names=(), message=None):
- if message is None:
- message = u'non of the templates given were found: ' + \
- u', '.join(map(unicode, names))
- TemplateNotFound.__init__(self, names and names[-1] or None, message)
- self.templates = list(names)
-
-
-class TemplateSyntaxError(TemplateError):
- """Raised to tell the user that there is a problem with the template."""
-
- def __init__(self, message, lineno, name=None, filename=None):
- TemplateError.__init__(self, message)
- self.lineno = lineno
- self.name = name
- self.filename = filename
- self.source = None
-
- # this is set to True if the debug.translate_syntax_error
- # function translated the syntax error into a new traceback
- self.translated = False
-
- def __str__(self):
- return unicode(self).encode('utf-8')
-
- # unicode goes after __str__ because we configured 2to3 to rename
- # __unicode__ to __str__. because the 2to3 tree is not designed to
- # remove nodes from it, we leave the above __str__ around and let
- # it override at runtime.
- def __unicode__(self):
- # for translated errors we only return the message
- if self.translated:
- return self.message
-
- # otherwise attach some stuff
- location = 'line %d' % self.lineno
- name = self.filename or self.name
- if name:
- location = 'File "%s", %s' % (name, location)
- lines = [self.message, ' ' + location]
-
- # if the source is set, add the line to the output
- if self.source is not None:
- try:
- line = self.source.splitlines()[self.lineno - 1]
- except IndexError:
- line = None
- if line:
- lines.append(' ' + line.strip())
-
- return u'\n'.join(lines)
-
-
-class TemplateAssertionError(TemplateSyntaxError):
- """Like a template syntax error, but covers cases where something in the
- template caused an error at compile time that wasn't necessarily caused
- by a syntax error. However it's a direct subclass of
- :exc:`TemplateSyntaxError` and has the same attributes.
- """
-
-
-class TemplateRuntimeError(TemplateError):
- """A generic runtime error in the template engine. Under some situations
- Jinja may raise this exception.
- """
-
-
-class UndefinedError(TemplateRuntimeError):
- """Raised if a template tries to operate on :class:`Undefined`."""
-
-
-class SecurityError(TemplateRuntimeError):
- """Raised if a template tries to do something insecure if the
- sandbox is enabled.
- """
-
-
-class FilterArgumentError(TemplateRuntimeError):
- """This error is raised if a filter was called with inappropriate
- arguments
- """
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/ext.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/ext.py
deleted file mode 100755
index 15a877157..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/ext.py
+++ /dev/null
@@ -1,553 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.ext
- ~~~~~~~~~~
-
- Jinja extensions allow to add custom tags similar to the way django custom
- tags work. By default two example extensions exist: an i18n and a cache
- extension.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-from collections import deque
-from jinja2 import nodes
-from jinja2.defaults import *
-from jinja2.environment import get_spontaneous_environment
-from jinja2.runtime import Undefined, concat
-from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
-from jinja2.utils import contextfunction, import_string, Markup, next
-
-
-# the only real useful gettext functions for a Jinja template. Note
-# that ugettext must be assigned to gettext as Jinja doesn't support
-# non unicode strings.
-GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
-
-
-class ExtensionRegistry(type):
- """Gives the extension an unique identifier."""
-
- def __new__(cls, name, bases, d):
- rv = type.__new__(cls, name, bases, d)
- rv.identifier = rv.__module__ + '.' + rv.__name__
- return rv
-
-
-class Extension(object):
- """Extensions can be used to add extra functionality to the Jinja template
- system at the parser level. Custom extensions are bound to an environment
- but may not store environment specific data on `self`. The reason for
- this is that an extension can be bound to another environment (for
- overlays) by creating a copy and reassigning the `environment` attribute.
-
- As extensions are created by the environment they cannot accept any
- arguments for configuration. One may want to work around that by using
- a factory function, but that is not possible as extensions are identified
- by their import name. The correct way to configure the extension is
- storing the configuration values on the environment. Because this way the
- environment ends up acting as central configuration storage the
- attributes may clash which is why extensions have to ensure that the names
- they choose for configuration are not too generic. ``prefix`` for example
- is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
- name as includes the name of the extension (fragment cache).
- """
- __metaclass__ = ExtensionRegistry
-
- #: if this extension parses this is the list of tags it's listening to.
- tags = set()
-
- #: the priority of that extension. This is especially useful for
- #: extensions that preprocess values. A lower value means higher
- #: priority.
- #:
- #: .. versionadded:: 2.4
- priority = 100
-
- def __init__(self, environment):
- self.environment = environment
-
- def bind(self, environment):
- """Create a copy of this extension bound to another environment."""
- rv = object.__new__(self.__class__)
- rv.__dict__.update(self.__dict__)
- rv.environment = environment
- return rv
-
- def preprocess(self, source, name, filename=None):
- """This method is called before the actual lexing and can be used to
- preprocess the source. The `filename` is optional. The return value
- must be the preprocessed source.
- """
- return source
-
- def filter_stream(self, stream):
- """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
- to filter tokens returned. This method has to return an iterable of
- :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
- :class:`~jinja2.lexer.TokenStream`.
-
- In the `ext` folder of the Jinja2 source distribution there is a file
- called `inlinegettext.py` which implements a filter that utilizes this
- method.
- """
- return stream
-
- def parse(self, parser):
- """If any of the :attr:`tags` matched this method is called with the
- parser as first argument. The token the parser stream is pointing at
- is the name token that matched. This method has to return one or a
- list of multiple nodes.
- """
- raise NotImplementedError()
-
- def attr(self, name, lineno=None):
- """Return an attribute node for the current extension. This is useful
- to pass constants on extensions to generated template code::
-
- self.attr('_my_attribute', lineno=lineno)
- """
- return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
-
- def call_method(self, name, args=None, kwargs=None, dyn_args=None,
- dyn_kwargs=None, lineno=None):
- """Call a method of the extension. This is a shortcut for
- :meth:`attr` + :class:`jinja2.nodes.Call`.
- """
- if args is None:
- args = []
- if kwargs is None:
- kwargs = []
- return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
- dyn_args, dyn_kwargs, lineno=lineno)
-
-
-@contextfunction
-def _gettext_alias(context, string):
- return context.resolve('gettext')(string)
-
-
-class InternationalizationExtension(Extension):
- """This extension adds gettext support to Jinja2."""
- tags = set(['trans'])
-
- # TODO: the i18n extension is currently reevaluating values in a few
- # situations. Take this example:
- # {% trans count=something() %}{{ count }} foo{% pluralize
- # %}{{ count }} fooss{% endtrans %}
- # something is called twice here. One time for the gettext value and
- # the other time for the n-parameter of the ngettext function.
-
- def __init__(self, environment):
- Extension.__init__(self, environment)
- environment.globals['_'] = _gettext_alias
- environment.extend(
- install_gettext_translations=self._install,
- install_null_translations=self._install_null,
- uninstall_gettext_translations=self._uninstall,
- extract_translations=self._extract
- )
-
- def _install(self, translations):
- gettext = getattr(translations, 'ugettext', None)
- if gettext is None:
- gettext = translations.gettext
- ngettext = getattr(translations, 'ungettext', None)
- if ngettext is None:
- ngettext = translations.ngettext
- self.environment.globals.update(gettext=gettext, ngettext=ngettext)
-
- def _install_null(self):
- self.environment.globals.update(
- gettext=lambda x: x,
- ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0]
- )
-
- def _uninstall(self, translations):
- for key in 'gettext', 'ngettext':
- self.environment.globals.pop(key, None)
-
- def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
- if isinstance(source, basestring):
- source = self.environment.parse(source)
- return extract_from_ast(source, gettext_functions)
-
- def parse(self, parser):
- """Parse a translatable tag."""
- lineno = next(parser.stream).lineno
-
- # find all the variables referenced. Additionally a variable can be
- # defined in the body of the trans block too, but this is checked at
- # a later state.
- plural_expr = None
- variables = {}
- while parser.stream.current.type != 'block_end':
- if variables:
- parser.stream.expect('comma')
-
- # skip colon for python compatibility
- if parser.stream.skip_if('colon'):
- break
-
- name = parser.stream.expect('name')
- if name.value in variables:
- parser.fail('translatable variable %r defined twice.' %
- name.value, name.lineno,
- exc=TemplateAssertionError)
-
- # expressions
- if parser.stream.current.type == 'assign':
- next(parser.stream)
- variables[name.value] = var = parser.parse_expression()
- else:
- variables[name.value] = var = nodes.Name(name.value, 'load')
- if plural_expr is None:
- plural_expr = var
-
- parser.stream.expect('block_end')
-
- plural = plural_names = None
- have_plural = False
- referenced = set()
-
- # now parse until endtrans or pluralize
- singular_names, singular = self._parse_block(parser, True)
- if singular_names:
- referenced.update(singular_names)
- if plural_expr is None:
- plural_expr = nodes.Name(singular_names[0], 'load')
-
- # if we have a pluralize block, we parse that too
- if parser.stream.current.test('name:pluralize'):
- have_plural = True
- next(parser.stream)
- if parser.stream.current.type != 'block_end':
- name = parser.stream.expect('name')
- if name.value not in variables:
- parser.fail('unknown variable %r for pluralization' %
- name.value, name.lineno,
- exc=TemplateAssertionError)
- plural_expr = variables[name.value]
- parser.stream.expect('block_end')
- plural_names, plural = self._parse_block(parser, False)
- next(parser.stream)
- referenced.update(plural_names)
- else:
- next(parser.stream)
-
- # register free names as simple name expressions
- for var in referenced:
- if var not in variables:
- variables[var] = nodes.Name(var, 'load')
-
- # no variables referenced? no need to escape
- if not referenced:
- singular = singular.replace('%%', '%')
- if plural:
- plural = plural.replace('%%', '%')
-
- if not have_plural:
- plural_expr = None
- elif plural_expr is None:
- parser.fail('pluralize without variables', lineno)
-
- if variables:
- variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
- for x, y in variables.items()])
- else:
- variables = None
-
- node = self._make_node(singular, plural, variables, plural_expr)
- node.set_lineno(lineno)
- return node
-
- def _parse_block(self, parser, allow_pluralize):
- """Parse until the next block tag with a given name."""
- referenced = []
- buf = []
- while 1:
- if parser.stream.current.type == 'data':
- buf.append(parser.stream.current.value.replace('%', '%%'))
- next(parser.stream)
- elif parser.stream.current.type == 'variable_begin':
- next(parser.stream)
- name = parser.stream.expect('name').value
- referenced.append(name)
- buf.append('%%(%s)s' % name)
- parser.stream.expect('variable_end')
- elif parser.stream.current.type == 'block_begin':
- next(parser.stream)
- if parser.stream.current.test('name:endtrans'):
- break
- elif parser.stream.current.test('name:pluralize'):
- if allow_pluralize:
- break
- parser.fail('a translatable section can have only one '
- 'pluralize section')
- parser.fail('control structures in translatable sections are '
- 'not allowed')
- elif parser.stream.eos:
- parser.fail('unclosed translation block')
- else:
- assert False, 'internal parser error'
-
- return referenced, concat(buf)
-
- def _make_node(self, singular, plural, variables, plural_expr):
- """Generates a useful node from the data provided."""
- # singular only:
- if plural_expr is None:
- gettext = nodes.Name('gettext', 'load')
- node = nodes.Call(gettext, [nodes.Const(singular)],
- [], None, None)
-
- # singular and plural
- else:
- ngettext = nodes.Name('ngettext', 'load')
- node = nodes.Call(ngettext, [
- nodes.Const(singular),
- nodes.Const(plural),
- plural_expr
- ], [], None, None)
-
- # mark the return value as safe if we are in an
- # environment with autoescaping turned on
- if self.environment.autoescape:
- node = nodes.MarkSafe(node)
-
- if variables:
- node = nodes.Mod(node, variables)
- return nodes.Output([node])
-
-
-class ExprStmtExtension(Extension):
- """Adds a `do` tag to Jinja2 that works like the print statement just
- that it doesn't print the return value.
- """
- tags = set(['do'])
-
- def parse(self, parser):
- node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
- node.node = parser.parse_tuple()
- return node
-
-
-class LoopControlExtension(Extension):
- """Adds break and continue to the template engine."""
- tags = set(['break', 'continue'])
-
- def parse(self, parser):
- token = next(parser.stream)
- if token.value == 'break':
- return nodes.Break(lineno=token.lineno)
- return nodes.Continue(lineno=token.lineno)
-
-
-class WithExtension(Extension):
- """Adds support for a django-like with block."""
- tags = set(['with'])
-
- def parse(self, parser):
- node = nodes.Scope(lineno=next(parser.stream).lineno)
- assignments = []
- while parser.stream.current.type != 'block_end':
- lineno = parser.stream.current.lineno
- if assignments:
- parser.stream.expect('comma')
- target = parser.parse_assign_target()
- parser.stream.expect('assign')
- expr = parser.parse_expression()
- assignments.append(nodes.Assign(target, expr, lineno=lineno))
- node.body = assignments + \
- list(parser.parse_statements(('name:endwith',),
- drop_needle=True))
- return node
-
-
-class AutoEscapeExtension(Extension):
- """Changes auto escape rules for a scope."""
- tags = set(['autoescape'])
-
- def parse(self, parser):
- node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
- node.options = [
- nodes.Keyword('autoescape', parser.parse_expression())
- ]
- node.body = parser.parse_statements(('name:endautoescape',),
- drop_needle=True)
- return nodes.Scope([node])
-
-
-def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
- babel_style=True):
- """Extract localizable strings from the given template node. Per
- default this function returns matches in babel style that means non string
- parameters as well as keyword arguments are returned as `None`. This
- allows Babel to figure out what you really meant if you are using
- gettext functions that allow keyword arguments for placeholder expansion.
- If you don't want that behavior set the `babel_style` parameter to `False`
- which causes only strings to be returned and parameters are always stored
- in tuples. As a consequence invalid gettext calls (calls without a single
- string parameter or string parameters after non-string parameters) are
- skipped.
-
- This example explains the behavior:
-
- >>> from jinja2 import Environment
- >>> env = Environment()
- >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
- >>> list(extract_from_ast(node))
- [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
- >>> list(extract_from_ast(node, babel_style=False))
- [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
-
- For every string found this function yields a ``(lineno, function,
- message)`` tuple, where:
-
- * ``lineno`` is the number of the line on which the string was found,
- * ``function`` is the name of the ``gettext`` function used (if the
- string was extracted from embedded Python code), and
- * ``message`` is the string itself (a ``unicode`` object, or a tuple
- of ``unicode`` objects for functions with multiple string arguments).
-
- This extraction function operates on the AST and is because of that unable
- to extract any comments. For comment support you have to use the babel
- extraction interface or extract comments yourself.
- """
- for node in node.find_all(nodes.Call):
- if not isinstance(node.node, nodes.Name) or \
- node.node.name not in gettext_functions:
- continue
-
- strings = []
- for arg in node.args:
- if isinstance(arg, nodes.Const) and \
- isinstance(arg.value, basestring):
- strings.append(arg.value)
- else:
- strings.append(None)
-
- for arg in node.kwargs:
- strings.append(None)
- if node.dyn_args is not None:
- strings.append(None)
- if node.dyn_kwargs is not None:
- strings.append(None)
-
- if not babel_style:
- strings = tuple(x for x in strings if x is not None)
- if not strings:
- continue
- else:
- if len(strings) == 1:
- strings = strings[0]
- else:
- strings = tuple(strings)
- yield node.lineno, node.node.name, strings
-
-
-class _CommentFinder(object):
- """Helper class to find comments in a token stream. Can only
- find comments for gettext calls forwards. Once the comment
- from line 4 is found, a comment for line 1 will not return a
- usable value.
- """
-
- def __init__(self, tokens, comment_tags):
- self.tokens = tokens
- self.comment_tags = comment_tags
- self.offset = 0
- self.last_lineno = 0
-
- def find_backwards(self, offset):
- try:
- for _, token_type, token_value in \
- reversed(self.tokens[self.offset:offset]):
- if token_type in ('comment', 'linecomment'):
- try:
- prefix, comment = token_value.split(None, 1)
- except ValueError:
- continue
- if prefix in self.comment_tags:
- return [comment.rstrip()]
- return []
- finally:
- self.offset = offset
-
- def find_comments(self, lineno):
- if not self.comment_tags or self.last_lineno > lineno:
- return []
- for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
- if token_lineno > lineno:
- return self.find_backwards(self.offset + idx)
- return self.find_backwards(len(self.tokens))
-
-
-def babel_extract(fileobj, keywords, comment_tags, options):
- """Babel extraction method for Jinja templates.
-
- .. versionchanged:: 2.3
- Basic support for translation comments was added. If `comment_tags`
- is now set to a list of keywords for extraction, the extractor will
- try to find the best preceeding comment that begins with one of the
- keywords. For best results, make sure to not have more than one
- gettext call in one line of code and the matching comment in the
- same line or the line before.
-
- :param fileobj: the file-like object the messages should be extracted from
- :param keywords: a list of keywords (i.e. function names) that should be
- recognized as translation functions
- :param comment_tags: a list of translator tags to search for and include
- in the results.
- :param options: a dictionary of additional options (optional)
- :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
- (comments will be empty currently)
- """
- extensions = set()
- for extension in options.get('extensions', '').split(','):
- extension = extension.strip()
- if not extension:
- continue
- extensions.add(import_string(extension))
- if InternationalizationExtension not in extensions:
- extensions.add(InternationalizationExtension)
-
- environment = get_spontaneous_environment(
- options.get('block_start_string', BLOCK_START_STRING),
- options.get('block_end_string', BLOCK_END_STRING),
- options.get('variable_start_string', VARIABLE_START_STRING),
- options.get('variable_end_string', VARIABLE_END_STRING),
- options.get('comment_start_string', COMMENT_START_STRING),
- options.get('comment_end_string', COMMENT_END_STRING),
- options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
- options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
- str(options.get('trim_blocks', TRIM_BLOCKS)).lower() in \
- ('1', 'on', 'yes', 'true'),
- NEWLINE_SEQUENCE, frozenset(extensions),
- # fill with defaults so that environments are shared
- # with other spontaneus environments. The rest of the
- # arguments are optimizer, undefined, finalize, autoescape,
- # loader, cache size, auto reloading setting and the
- # bytecode cache
- True, Undefined, None, False, None, 0, False, None
- )
-
- source = fileobj.read().decode(options.get('encoding', 'utf-8'))
- try:
- node = environment.parse(source)
- tokens = list(environment.lex(environment.preprocess(source)))
- except TemplateSyntaxError as e:
- # skip templates with syntax errors
- return
-
- finder = _CommentFinder(tokens, comment_tags)
- for lineno, func, message in extract_from_ast(node, keywords):
- yield lineno, func, message, finder.find_comments(lineno)
-
-
-#: nicer import names
-i18n = InternationalizationExtension
-do = ExprStmtExtension
-loopcontrols = LoopControlExtension
-with_ = WithExtension
-autoescape = AutoEscapeExtension
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/filters.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/filters.py
deleted file mode 100755
index 1b7447d2f..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/filters.py
+++ /dev/null
@@ -1,731 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.filters
- ~~~~~~~~~~~~~~
-
- Bundled jinja filters.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-import math
-from random import choice
-from operator import itemgetter
-from itertools import imap, groupby
-from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
-from jinja2.runtime import Undefined
-from jinja2.exceptions import FilterArgumentError, SecurityError
-
-
-_word_re = re.compile(r'\w+(?u)')
-
-
-def contextfilter(f):
- """Decorator for marking context dependent filters. The current
- :class:`Context` will be passed as first argument.
- """
- f.contextfilter = True
- return f
-
-
-def evalcontextfilter(f):
- """Decorator for marking eval-context dependent filters. An eval
- context object is passed as first argument. For more information
- about the eval context, see :ref:`eval-context`.
-
- .. versionadded:: 2.4
- """
- f.evalcontextfilter = True
- return f
-
-
-def environmentfilter(f):
- """Decorator for marking evironment dependent filters. The current
- :class:`Environment` is passed to the filter as first argument.
- """
- f.environmentfilter = True
- return f
-
-
-def do_forceescape(value):
- """Enforce HTML escaping. This will probably double escape variables."""
- if hasattr(value, '__html__'):
- value = value.__html__()
- return escape(unicode(value))
-
-
-@evalcontextfilter
-def do_replace(eval_ctx, s, old, new, count=None):
- """Return a copy of the value with all occurrences of a substring
- replaced with a new one. The first argument is the substring
- that should be replaced, the second is the replacement string.
- If the optional third argument ``count`` is given, only the first
- ``count`` occurrences are replaced:
-
- .. sourcecode:: jinja
-
- {{ "Hello World"|replace("Hello", "Goodbye") }}
- -> Goodbye World
-
- {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
- -> d'oh, d'oh, aaargh
- """
- if count is None:
- count = -1
- if not eval_ctx.autoescape:
- return unicode(s).replace(unicode(old), unicode(new), count)
- if hasattr(old, '__html__') or hasattr(new, '__html__') and \
- not hasattr(s, '__html__'):
- s = escape(s)
- else:
- s = soft_unicode(s)
- return s.replace(soft_unicode(old), soft_unicode(new), count)
-
-
-def do_upper(s):
- """Convert a value to uppercase."""
- return soft_unicode(s).upper()
-
-
-def do_lower(s):
- """Convert a value to lowercase."""
- return soft_unicode(s).lower()
-
-
-@evalcontextfilter
-def do_xmlattr(_eval_ctx, d, autospace=True):
- """Create an SGML/XML attribute string based on the items in a dict.
- All values that are neither `none` nor `undefined` are automatically
- escaped:
-
- .. sourcecode:: html+jinja
-
- <ul{{ {'class': 'my_list', 'missing': none,
- 'id': 'list-%d'|format(variable)}|xmlattr }}>
- ...
- </ul>
-
- Results in something like this:
-
- .. sourcecode:: html
-
- <ul class="my_list" id="list-42">
- ...
- </ul>
-
- As you can see it automatically prepends a space in front of the item
- if the filter returned something unless the second parameter is false.
- """
- rv = u' '.join(
- u'%s="%s"' % (escape(key), escape(value))
- for key, value in d.iteritems()
- if value is not None and not isinstance(value, Undefined)
- )
- if autospace and rv:
- rv = u' ' + rv
- if _eval_ctx.autoescape:
- rv = Markup(rv)
- return rv
-
-
-def do_capitalize(s):
- """Capitalize a value. The first character will be uppercase, all others
- lowercase.
- """
- return soft_unicode(s).capitalize()
-
-
-def do_title(s):
- """Return a titlecased version of the value. I.e. words will start with
- uppercase letters, all remaining characters are lowercase.
- """
- return soft_unicode(s).title()
-
-
-def do_dictsort(value, case_sensitive=False, by='key'):
- """Sort a dict and yield (key, value) pairs. Because python dicts are
- unsorted you may want to use this function to order them by either
- key or value:
-
- .. sourcecode:: jinja
-
- {% for item in mydict|dictsort %}
- sort the dict by key, case insensitive
-
- {% for item in mydict|dicsort(true) %}
- sort the dict by key, case sensitive
-
- {% for item in mydict|dictsort(false, 'value') %}
- sort the dict by key, case insensitive, sorted
- normally and ordered by value.
- """
- if by == 'key':
- pos = 0
- elif by == 'value':
- pos = 1
- else:
- raise FilterArgumentError('You can only sort by either '
- '"key" or "value"')
- def sort_func(item):
- value = item[pos]
- if isinstance(value, basestring) and not case_sensitive:
- value = value.lower()
- return value
-
- return sorted(value.items(), key=sort_func)
-
-
-def do_sort(value, case_sensitive=False):
- """Sort an iterable. If the iterable is made of strings the second
- parameter can be used to control the case sensitiveness of the
- comparison which is disabled by default.
-
- .. sourcecode:: jinja
-
- {% for item in iterable|sort %}
- ...
- {% endfor %}
- """
- if not case_sensitive:
- def sort_func(item):
- if isinstance(item, basestring):
- item = item.lower()
- return item
- else:
- sort_func = None
- return sorted(seq, key=sort_func)
-
-
-def do_default(value, default_value=u'', boolean=False):
- """If the value is undefined it will return the passed default value,
- otherwise the value of the variable:
-
- .. sourcecode:: jinja
-
- {{ my_variable|default('my_variable is not defined') }}
-
- This will output the value of ``my_variable`` if the variable was
- defined, otherwise ``'my_variable is not defined'``. If you want
- to use default with variables that evaluate to false you have to
- set the second parameter to `true`:
-
- .. sourcecode:: jinja
-
- {{ ''|default('the string was empty', true) }}
- """
- if (boolean and not value) or isinstance(value, Undefined):
- return default_value
- return value
-
-
-@evalcontextfilter
-def do_join(eval_ctx, value, d=u''):
- """Return a string which is the concatenation of the strings in the
- sequence. The separator between elements is an empty string per
- default, you can define it with the optional parameter:
-
- .. sourcecode:: jinja
-
- {{ [1, 2, 3]|join('|') }}
- -> 1|2|3
-
- {{ [1, 2, 3]|join }}
- -> 123
- """
- # no automatic escaping? joining is a lot eaiser then
- if not eval_ctx.autoescape:
- return unicode(d).join(imap(unicode, value))
-
- # if the delimiter doesn't have an html representation we check
- # if any of the items has. If yes we do a coercion to Markup
- if not hasattr(d, '__html__'):
- value = list(value)
- do_escape = False
- for idx, item in enumerate(value):
- if hasattr(item, '__html__'):
- do_escape = True
- else:
- value[idx] = unicode(item)
- if do_escape:
- d = escape(d)
- else:
- d = unicode(d)
- return d.join(value)
-
- # no html involved, to normal joining
- return soft_unicode(d).join(imap(soft_unicode, value))
-
-
-def do_center(value, width=80):
- """Centers the value in a field of a given width."""
- return unicode(value).center(width)
-
-
-@environmentfilter
-def do_first(environment, seq):
- """Return the first item of a sequence."""
- try:
- return iter(seq).next()
- except StopIteration:
- return environment.undefined('No first item, sequence was empty.')
-
-
-@environmentfilter
-def do_last(environment, seq):
- """Return the last item of a sequence."""
- try:
- return iter(reversed(seq)).next()
- except StopIteration:
- return environment.undefined('No last item, sequence was empty.')
-
-
-@environmentfilter
-def do_random(environment, seq):
- """Return a random item from the sequence."""
- try:
- return choice(seq)
- except IndexError:
- return environment.undefined('No random item, sequence was empty.')
-
-
-def do_filesizeformat(value, binary=False):
- """Format the value like a 'human-readable' file size (i.e. 13 KB,
- 4.1 MB, 102 bytes, etc). Per default decimal prefixes are used (mega,
- giga, etc.), if the second parameter is set to `True` the binary
- prefixes are used (mebi, gibi).
- """
- bytes = float(value)
- base = binary and 1024 or 1000
- middle = binary and 'i' or ''
- if bytes < base:
- return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
- elif bytes < base * base:
- return "%.1f K%sB" % (bytes / base, middle)
- elif bytes < base * base * base:
- return "%.1f M%sB" % (bytes / (base * base), middle)
- return "%.1f G%sB" % (bytes / (base * base * base), middle)
-
-
-def do_pprint(value, verbose=False):
- """Pretty print a variable. Useful for debugging.
-
- With Jinja 1.2 onwards you can pass it a parameter. If this parameter
- is truthy the output will be more verbose (this requires `pretty`)
- """
- return pformat(value, verbose=verbose)
-
-
-@evalcontextfilter
-def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
- """Converts URLs in plain text into clickable links.
-
- If you pass the filter an additional integer it will shorten the urls
- to that number. Also a third argument exists that makes the urls
- "nofollow":
-
- .. sourcecode:: jinja
-
- {{ mytext|urlize(40, true) }}
- links are shortened to 40 chars and defined with rel="nofollow"
- """
- rv = urlize(value, trim_url_limit, nofollow)
- if eval_ctx.autoescape:
- rv = Markup(rv)
- return rv
-
-
-def do_indent(s, width=4, indentfirst=False):
- """Return a copy of the passed string, each line indented by
- 4 spaces. The first line is not indented. If you want to
- change the number of spaces or indent the first line too
- you can pass additional parameters to the filter:
-
- .. sourcecode:: jinja
-
- {{ mytext|indent(2, true) }}
- indent by two spaces and indent the first line too.
- """
- indention = u' ' * width
- rv = (u'\n' + indention).join(s.splitlines())
- if indentfirst:
- rv = indention + rv
- return rv
-
-
-def do_truncate(s, length=255, killwords=False, end='...'):
- """Return a truncated copy of the string. The length is specified
- with the first parameter which defaults to ``255``. If the second
- parameter is ``true`` the filter will cut the text at length. Otherwise
- it will try to save the last word. If the text was in fact
- truncated it will append an ellipsis sign (``"..."``). If you want a
- different ellipsis sign than ``"..."`` you can specify it using the
- third parameter.
-
- .. sourcecode jinja::
-
- {{ mytext|truncate(300, false, '»') }}
- truncate mytext to 300 chars, don't split up words, use a
- right pointing double arrow as ellipsis sign.
- """
- if len(s) <= length:
- return s
- elif killwords:
- return s[:length] + end
- words = s.split(' ')
- result = []
- m = 0
- for word in words:
- m += len(word) + 1
- if m > length:
- break
- result.append(word)
- result.append(end)
- return u' '.join(result)
-
-
-def do_wordwrap(s, width=79, break_long_words=True):
- """
- Return a copy of the string passed to the filter wrapped after
- ``79`` characters. You can override this default using the first
- parameter. If you set the second parameter to `false` Jinja will not
- split words apart if they are longer than `width`.
- """
- import textwrap
- return u'\n'.join(textwrap.wrap(s, width=width, expand_tabs=False,
- replace_whitespace=False,
- break_long_words=break_long_words))
-
-
-def do_wordcount(s):
- """Count the words in that string."""
- return len(_word_re.findall(s))
-
-
-def do_int(value, default=0):
- """Convert the value into an integer. If the
- conversion doesn't work it will return ``0``. You can
- override this default using the first parameter.
- """
- try:
- return int(value)
- except (TypeError, ValueError):
- # this quirk is necessary so that "42.23"|int gives 42.
- try:
- return int(float(value))
- except (TypeError, ValueError):
- return default
-
-
-def do_float(value, default=0.0):
- """Convert the value into a floating point number. If the
- conversion doesn't work it will return ``0.0``. You can
- override this default using the first parameter.
- """
- try:
- return float(value)
- except (TypeError, ValueError):
- return default
-
-
-def do_format(value, *args, **kwargs):
- """
- Apply python string formatting on an object:
-
- .. sourcecode:: jinja
-
- {{ "%s - %s"|format("Hello?", "Foo!") }}
- -> Hello? - Foo!
- """
- if args and kwargs:
- raise FilterArgumentError('can\'t handle positional and keyword '
- 'arguments at the same time')
- return soft_unicode(value) % (kwargs or args)
-
-
-def do_trim(value):
- """Strip leading and trailing whitespace."""
- return soft_unicode(value).strip()
-
-
-def do_striptags(value):
- """Strip SGML/XML tags and replace adjacent whitespace by one space.
- """
- if hasattr(value, '__html__'):
- value = value.__html__()
- return Markup(unicode(value)).striptags()
-
-
-def do_slice(value, slices, fill_with=None):
- """Slice an iterator and return a list of lists containing
- those items. Useful if you want to create a div containing
- three ul tags that represent columns:
-
- .. sourcecode:: html+jinja
-
- <div class="columwrapper">
- {%- for column in items|slice(3) %}
- <ul class="column-{{ loop.index }}">
- {%- for item in column %}
- <li>{{ item }}</li>
- {%- endfor %}
- </ul>
- {%- endfor %}
- </div>
-
- If you pass it a second argument it's used to fill missing
- values on the last iteration.
- """
- seq = list(value)
- length = len(seq)
- items_per_slice = length // slices
- slices_with_extra = length % slices
- offset = 0
- for slice_number in xrange(slices):
- start = offset + slice_number * items_per_slice
- if slice_number < slices_with_extra:
- offset += 1
- end = offset + (slice_number + 1) * items_per_slice
- tmp = seq[start:end]
- if fill_with is not None and slice_number >= slices_with_extra:
- tmp.append(fill_with)
- yield tmp
-
-
-def do_batch(value, linecount, fill_with=None):
- """
- A filter that batches items. It works pretty much like `slice`
- just the other way round. It returns a list of lists with the
- given number of items. If you provide a second parameter this
- is used to fill missing items. See this example:
-
- .. sourcecode:: html+jinja
-
- <table>
- {%- for row in items|batch(3, ' ') %}
- <tr>
- {%- for column in row %}
- <td>{{ column }}</td>
- {%- endfor %}
- </tr>
- {%- endfor %}
- </table>
- """
- result = []
- tmp = []
- for item in value:
- if len(tmp) == linecount:
- yield tmp
- tmp = []
- tmp.append(item)
- if tmp:
- if fill_with is not None and len(tmp) < linecount:
- tmp += [fill_with] * (linecount - len(tmp))
- yield tmp
-
-
-def do_round(value, precision=0, method='common'):
- """Round the number to a given precision. The first
- parameter specifies the precision (default is ``0``), the
- second the rounding method:
-
- - ``'common'`` rounds either up or down
- - ``'ceil'`` always rounds up
- - ``'floor'`` always rounds down
-
- If you don't specify a method ``'common'`` is used.
-
- .. sourcecode:: jinja
-
- {{ 42.55|round }}
- -> 43.0
- {{ 42.55|round(1, 'floor') }}
- -> 42.5
-
- Note that even if rounded to 0 precision, a float is returned. If
- you need a real integer, pipe it through `int`:
-
- .. sourcecode:: jinja
-
- {{ 42.55|round|int }}
- -> 43
- """
- if not method in ('common', 'ceil', 'floor'):
- raise FilterArgumentError('method must be common, ceil or floor')
- if precision < 0:
- raise FilterArgumentError('precision must be a postive integer '
- 'or zero.')
- if method == 'common':
- return round(value, precision)
- func = getattr(math, method)
- if precision:
- return func(value * 10 * precision) / (10 * precision)
- else:
- return func(value)
-
-
-def do_sort(value, reverse=False):
- """Sort a sequence. Per default it sorts ascending, if you pass it
- true as first argument it will reverse the sorting.
- """
- return sorted(value, reverse=reverse)
-
-
-@environmentfilter
-def do_groupby(environment, value, attribute):
- """Group a sequence of objects by a common attribute.
-
- If you for example have a list of dicts or objects that represent persons
- with `gender`, `first_name` and `last_name` attributes and you want to
- group all users by genders you can do something like the following
- snippet:
-
- .. sourcecode:: html+jinja
-
- <ul>
- {% for group in persons|groupby('gender') %}
- <li>{{ group.grouper }}<ul>
- {% for person in group.list %}
- <li>{{ person.first_name }} {{ person.last_name }}</li>
- {% endfor %}</ul></li>
- {% endfor %}
- </ul>
-
- Additionally it's possible to use tuple unpacking for the grouper and
- list:
-
- .. sourcecode:: html+jinja
-
- <ul>
- {% for grouper, list in persons|groupby('gender') %}
- ...
- {% endfor %}
- </ul>
-
- As you can see the item we're grouping by is stored in the `grouper`
- attribute and the `list` contains all the objects that have this grouper
- in common.
- """
- expr = lambda x: environment.getitem(x, attribute)
- return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
-
-
-class _GroupTuple(tuple):
- __slots__ = ()
- grouper = property(itemgetter(0))
- list = property(itemgetter(1))
-
- #def __new__(cls, (key, value)):
- def __new__(cls, kv):
- return tuple.__new__(cls, (kv[0], list(kv[1])))
-
-
-def do_list(value):
- """Convert the value into a list. If it was a string the returned list
- will be a list of characters.
- """
- return list(value)
-
-
-def do_mark_safe(value):
- """Mark the value as safe which means that in an environment with automatic
- escaping enabled this variable will not be escaped.
- """
- return Markup(value)
-
-
-def do_mark_unsafe(value):
- """Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
- return unicode(value)
-
-
-def do_reverse(value):
- """Reverse the object or return an iterator the iterates over it the other
- way round.
- """
- if isinstance(value, basestring):
- return value[::-1]
- try:
- return reversed(value)
- except TypeError:
- try:
- rv = list(value)
- rv.reverse()
- return rv
- except TypeError:
- raise FilterArgumentError('argument must be iterable')
-
-
-@environmentfilter
-def do_attr(environment, obj, name):
- """Get an attribute of an object. ``foo|attr("bar")`` works like
- ``foo["bar"]`` just that always an attribute is returned and items are not
- looked up.
-
- See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
- """
- try:
- name = str(name)
- except UnicodeError:
- pass
- else:
- try:
- value = getattr(obj, name)
- except AttributeError:
- pass
- else:
- if environment.sandboxed and not \
- environment.is_safe_attribute(obj, name, value):
- return environment.unsafe_undefined(obj, name)
- return value
- return environment.undefined(obj=obj, name=name)
-
-
-FILTERS = {
- 'attr': do_attr,
- 'replace': do_replace,
- 'upper': do_upper,
- 'lower': do_lower,
- 'escape': escape,
- 'e': escape,
- 'forceescape': do_forceescape,
- 'capitalize': do_capitalize,
- 'title': do_title,
- 'default': do_default,
- 'd': do_default,
- 'join': do_join,
- 'count': len,
- 'dictsort': do_dictsort,
- 'sort': do_sort,
- 'length': len,
- 'reverse': do_reverse,
- 'center': do_center,
- 'indent': do_indent,
- 'title': do_title,
- 'capitalize': do_capitalize,
- 'first': do_first,
- 'last': do_last,
- 'random': do_random,
- 'filesizeformat': do_filesizeformat,
- 'pprint': do_pprint,
- 'truncate': do_truncate,
- 'wordwrap': do_wordwrap,
- 'wordcount': do_wordcount,
- 'int': do_int,
- 'float': do_float,
- 'string': soft_unicode,
- 'list': do_list,
- 'urlize': do_urlize,
- 'format': do_format,
- 'trim': do_trim,
- 'striptags': do_striptags,
- 'slice': do_slice,
- 'batch': do_batch,
- 'sum': sum,
- 'abs': abs,
- 'round': do_round,
- 'sort': do_sort,
- 'groupby': do_groupby,
- 'safe': do_mark_safe,
- 'xmlattr': do_xmlattr
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/lexer.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/lexer.py
deleted file mode 100755
index 487e35701..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/lexer.py
+++ /dev/null
@@ -1,680 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.lexer
- ~~~~~~~~~~~~
-
- This module implements a Jinja / Python combination lexer. The
- `Lexer` class provided by this module is used to do some preprocessing
- for Jinja.
-
- On the one hand it filters out invalid operators like the bitshift
- operators we don't allow in templates. On the other hand it separates
- template code and python code in expressions.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-from operator import itemgetter
-from collections import deque
-from jinja2.exceptions import TemplateSyntaxError
-from jinja2.utils import LRUCache, next
-
-
-# cache for the lexers. Exists in order to be able to have multiple
-# environments with the same lexer
-_lexer_cache = LRUCache(50)
-
-# static regular expressions
-whitespace_re = re.compile(r'\s+', re.U)
-string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
- r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
-integer_re = re.compile(r'\d+')
-
-# we use the unicode identifier rule if this python version is able
-# to handle unicode identifiers, otherwise the standard ASCII one.
-try:
- compile('föö', '<unknown>', 'eval')
-except SyntaxError:
- name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
-else:
- from jinja2 import _stringdefs
- name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
- _stringdefs.xid_continue))
-
-float_re = re.compile(r'(?<!\.)\d+\.\d+')
-newline_re = re.compile(r'(\r\n|\r|\n)')
-
-# internal the tokens and keep references to them
-TOKEN_ADD = intern('add')
-TOKEN_ASSIGN = intern('assign')
-TOKEN_COLON = intern('colon')
-TOKEN_COMMA = intern('comma')
-TOKEN_DIV = intern('div')
-TOKEN_DOT = intern('dot')
-TOKEN_EQ = intern('eq')
-TOKEN_FLOORDIV = intern('floordiv')
-TOKEN_GT = intern('gt')
-TOKEN_GTEQ = intern('gteq')
-TOKEN_LBRACE = intern('lbrace')
-TOKEN_LBRACKET = intern('lbracket')
-TOKEN_LPAREN = intern('lparen')
-TOKEN_LT = intern('lt')
-TOKEN_LTEQ = intern('lteq')
-TOKEN_MOD = intern('mod')
-TOKEN_MUL = intern('mul')
-TOKEN_NE = intern('ne')
-TOKEN_PIPE = intern('pipe')
-TOKEN_POW = intern('pow')
-TOKEN_RBRACE = intern('rbrace')
-TOKEN_RBRACKET = intern('rbracket')
-TOKEN_RPAREN = intern('rparen')
-TOKEN_SEMICOLON = intern('semicolon')
-TOKEN_SUB = intern('sub')
-TOKEN_TILDE = intern('tilde')
-TOKEN_WHITESPACE = intern('whitespace')
-TOKEN_FLOAT = intern('float')
-TOKEN_INTEGER = intern('integer')
-TOKEN_NAME = intern('name')
-TOKEN_STRING = intern('string')
-TOKEN_OPERATOR = intern('operator')
-TOKEN_BLOCK_BEGIN = intern('block_begin')
-TOKEN_BLOCK_END = intern('block_end')
-TOKEN_VARIABLE_BEGIN = intern('variable_begin')
-TOKEN_VARIABLE_END = intern('variable_end')
-TOKEN_RAW_BEGIN = intern('raw_begin')
-TOKEN_RAW_END = intern('raw_end')
-TOKEN_COMMENT_BEGIN = intern('comment_begin')
-TOKEN_COMMENT_END = intern('comment_end')
-TOKEN_COMMENT = intern('comment')
-TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin')
-TOKEN_LINESTATEMENT_END = intern('linestatement_end')
-TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin')
-TOKEN_LINECOMMENT_END = intern('linecomment_end')
-TOKEN_LINECOMMENT = intern('linecomment')
-TOKEN_DATA = intern('data')
-TOKEN_INITIAL = intern('initial')
-TOKEN_EOF = intern('eof')
-
-# bind operators to token types
-operators = {
- '+': TOKEN_ADD,
- '-': TOKEN_SUB,
- '/': TOKEN_DIV,
- '//': TOKEN_FLOORDIV,
- '*': TOKEN_MUL,
- '%': TOKEN_MOD,
- '**': TOKEN_POW,
- '~': TOKEN_TILDE,
- '[': TOKEN_LBRACKET,
- ']': TOKEN_RBRACKET,
- '(': TOKEN_LPAREN,
- ')': TOKEN_RPAREN,
- '{': TOKEN_LBRACE,
- '}': TOKEN_RBRACE,
- '==': TOKEN_EQ,
- '!=': TOKEN_NE,
- '>': TOKEN_GT,
- '>=': TOKEN_GTEQ,
- '<': TOKEN_LT,
- '<=': TOKEN_LTEQ,
- '=': TOKEN_ASSIGN,
- '.': TOKEN_DOT,
- ':': TOKEN_COLON,
- '|': TOKEN_PIPE,
- ',': TOKEN_COMMA,
- ';': TOKEN_SEMICOLON
-}
-
-reverse_operators = dict([(v, k) for k, v in operators.iteritems()])
-assert len(operators) == len(reverse_operators), 'operators dropped'
-operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in
- sorted(operators, key=lambda x: -len(x))))
-
-ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
- TOKEN_COMMENT_END, TOKEN_WHITESPACE,
- TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN,
- TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT])
-ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA,
- TOKEN_COMMENT, TOKEN_LINECOMMENT])
-
-
-def _describe_token_type(token_type):
- if token_type in reverse_operators:
- return reverse_operators[token_type]
- return {
- TOKEN_COMMENT_BEGIN: 'begin of comment',
- TOKEN_COMMENT_END: 'end of comment',
- TOKEN_COMMENT: 'comment',
- TOKEN_LINECOMMENT: 'comment',
- TOKEN_BLOCK_BEGIN: 'begin of statement block',
- TOKEN_BLOCK_END: 'end of statement block',
- TOKEN_VARIABLE_BEGIN: 'begin of print statement',
- TOKEN_VARIABLE_END: 'end of print statement',
- TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement',
- TOKEN_LINESTATEMENT_END: 'end of line statement',
- TOKEN_DATA: 'template data / text',
- TOKEN_EOF: 'end of template'
- }.get(token_type, token_type)
-
-
-def describe_token(token):
- """Returns a description of the token."""
- if token.type == 'name':
- return token.value
- return _describe_token_type(token.type)
-
-
-def describe_token_expr(expr):
- """Like `describe_token` but for token expressions."""
- if ':' in expr:
- type, value = expr.split(':', 1)
- if type == 'name':
- return value
- else:
- type = expr
- return _describe_token_type(type)
-
-
-def count_newlines(value):
- """Count the number of newline characters in the string. This is
- useful for extensions that filter a stream.
- """
- return len(newline_re.findall(value))
-
-
-def compile_rules(environment):
- """Compiles all the rules from the environment into a list of rules."""
- e = re.escape
- rules = [
- (len(environment.comment_start_string), 'comment',
- e(environment.comment_start_string)),
- (len(environment.block_start_string), 'block',
- e(environment.block_start_string)),
- (len(environment.variable_start_string), 'variable',
- e(environment.variable_start_string))
- ]
-
- if environment.line_statement_prefix is not None:
- rules.append((len(environment.line_statement_prefix), 'linestatement',
- r'^\s*' + e(environment.line_statement_prefix)))
- if environment.line_comment_prefix is not None:
- rules.append((len(environment.line_comment_prefix), 'linecomment',
- r'(?:^|(?<=\S))[^\S\r\n]*' +
- e(environment.line_comment_prefix)))
-
- return [x[1:] for x in sorted(rules, reverse=True)]
-
-
-class Failure(object):
- """Class that raises a `TemplateSyntaxError` if called.
- Used by the `Lexer` to specify known errors.
- """
-
- def __init__(self, message, cls=TemplateSyntaxError):
- self.message = message
- self.error_class = cls
-
- def __call__(self, lineno, filename):
- raise self.error_class(self.message, lineno, filename)
-
-
-class Token(tuple):
- """Token class."""
- __slots__ = ()
- lineno, type, value = (property(itemgetter(x)) for x in range(3))
-
- def __new__(cls, lineno, type, value):
- return tuple.__new__(cls, (lineno, intern(str(type)), value))
-
- def __str__(self):
- if self.type in reverse_operators:
- return reverse_operators[self.type]
- elif self.type == 'name':
- return self.value
- return self.type
-
- def test(self, expr):
- """Test a token against a token expression. This can either be a
- token type or ``'token_type:token_value'``. This can only test
- against string values and types.
- """
- # here we do a regular string equality check as test_any is usually
- # passed an iterable of not interned strings.
- if self.type == expr:
- return True
- elif ':' in expr:
- return expr.split(':', 1) == [self.type, self.value]
- return False
-
- def test_any(self, *iterable):
- """Test against multiple token expressions."""
- for expr in iterable:
- if self.test(expr):
- return True
- return False
-
- def __repr__(self):
- return 'Token(%r, %r, %r)' % (
- self.lineno,
- self.type,
- self.value
- )
-
-
-class TokenStreamIterator(object):
- """The iterator for tokenstreams. Iterate over the stream
- until the eof token is reached.
- """
-
- def __init__(self, stream):
- self.stream = stream
-
- def __iter__(self):
- return self
-
- def next(self):
- token = self.stream.current
- if token.type is TOKEN_EOF:
- self.stream.close()
- raise StopIteration()
- next(self.stream)
- return token
-
-
-class TokenStream(object):
- """A token stream is an iterable that yields :class:`Token`\s. The
- parser however does not iterate over it but calls :meth:`next` to go
- one token ahead. The current active token is stored as :attr:`current`.
- """
-
- def __init__(self, generator, name, filename):
- self._next = iter(generator).next
- self._pushed = deque()
- self.name = name
- self.filename = filename
- self.closed = False
- self.current = Token(1, TOKEN_INITIAL, '')
- next(self)
-
- def __iter__(self):
- return TokenStreamIterator(self)
-
- def __nonzero__(self):
- return bool(self._pushed) or self.current.type is not TOKEN_EOF
-
- eos = property(lambda x: not x, doc="Are we at the end of the stream?")
-
- def push(self, token):
- """Push a token back to the stream."""
- self._pushed.append(token)
-
- def look(self):
- """Look at the next token."""
- old_token = next(self)
- result = self.current
- self.push(result)
- self.current = old_token
- return result
-
- def skip(self, n=1):
- """Got n tokens ahead."""
- for x in xrange(n):
- next(self)
-
- def next_if(self, expr):
- """Perform the token test and return the token if it matched.
- Otherwise the return value is `None`.
- """
- if self.current.test(expr):
- return next(self)
-
- def skip_if(self, expr):
- """Like :meth:`next_if` but only returns `True` or `False`."""
- return self.next_if(expr) is not None
-
- def next(self):
- """Go one token ahead and return the old one"""
- rv = self.current
- if self._pushed:
- self.current = self._pushed.popleft()
- elif self.current.type is not TOKEN_EOF:
- try:
- self.current = self._next()
- except StopIteration:
- self.close()
- return rv
-
- def close(self):
- """Close the stream."""
- self.current = Token(self.current.lineno, TOKEN_EOF, '')
- self._next = None
- self.closed = True
-
- def expect(self, expr):
- """Expect a given token type and return it. This accepts the same
- argument as :meth:`jinja2.lexer.Token.test`.
- """
- if not self.current.test(expr):
- expr = describe_token_expr(expr)
- if self.current.type is TOKEN_EOF:
- raise TemplateSyntaxError('unexpected end of template, '
- 'expected %r.' % expr,
- self.current.lineno,
- self.name, self.filename)
- raise TemplateSyntaxError("expected token %r, got %r" %
- (expr, describe_token(self.current)),
- self.current.lineno,
- self.name, self.filename)
- try:
- return self.current
- finally:
- next(self)
-
-
-def get_lexer(environment):
- """Return a lexer which is probably cached."""
- key = (environment.block_start_string,
- environment.block_end_string,
- environment.variable_start_string,
- environment.variable_end_string,
- environment.comment_start_string,
- environment.comment_end_string,
- environment.line_statement_prefix,
- environment.line_comment_prefix,
- environment.trim_blocks,
- environment.newline_sequence)
- lexer = _lexer_cache.get(key)
- if lexer is None:
- lexer = Lexer(environment)
- _lexer_cache[key] = lexer
- return lexer
-
-
-class Lexer(object):
- """Class that implements a lexer for a given environment. Automatically
- created by the environment class, usually you don't have to do that.
-
- Note that the lexer is not automatically bound to an environment.
- Multiple environments can share the same lexer.
- """
-
- def __init__(self, environment):
- # shortcuts
- c = lambda x: re.compile(x, re.M | re.S)
- e = re.escape
-
- # lexing rules for tags
- tag_rules = [
- (whitespace_re, TOKEN_WHITESPACE, None),
- (float_re, TOKEN_FLOAT, None),
- (integer_re, TOKEN_INTEGER, None),
- (name_re, TOKEN_NAME, None),
- (string_re, TOKEN_STRING, None),
- (operator_re, TOKEN_OPERATOR, None)
- ]
-
- # assamble the root lexing rule. because "|" is ungreedy
- # we have to sort by length so that the lexer continues working
- # as expected when we have parsing rules like <% for block and
- # <%= for variables. (if someone wants asp like syntax)
- # variables are just part of the rules if variable processing
- # is required.
- root_tag_rules = compile_rules(environment)
-
- # block suffix if trimming is enabled
- block_suffix_re = environment.trim_blocks and '\\n?' or ''
-
- self.newline_sequence = environment.newline_sequence
-
- # global lexing rules
- self.rules = {
- 'root': [
- # directives
- (c('(.*?)(?:%s)' % '|'.join(
- [r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*%s)' % (
- e(environment.block_start_string),
- e(environment.block_start_string),
- e(environment.block_end_string)
- )] + [
- r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, r)
- for n, r in root_tag_rules
- ])), (TOKEN_DATA, '#bygroup'), '#bygroup'),
- # data
- (c('.+'), TOKEN_DATA, None)
- ],
- # comments
- TOKEN_COMMENT_BEGIN: [
- (c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
- e(environment.comment_end_string),
- e(environment.comment_end_string),
- block_suffix_re
- )), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'),
- (c('(.)'), (Failure('Missing end of comment tag'),), None)
- ],
- # blocks
- TOKEN_BLOCK_BEGIN: [
- (c('(?:\-%s\s*|%s)%s' % (
- e(environment.block_end_string),
- e(environment.block_end_string),
- block_suffix_re
- )), TOKEN_BLOCK_END, '#pop'),
- ] + tag_rules,
- # variables
- TOKEN_VARIABLE_BEGIN: [
- (c('\-%s\s*|%s' % (
- e(environment.variable_end_string),
- e(environment.variable_end_string)
- )), TOKEN_VARIABLE_END, '#pop')
- ] + tag_rules,
- # raw block
- TOKEN_RAW_BEGIN: [
- (c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
- e(environment.block_start_string),
- e(environment.block_start_string),
- e(environment.block_end_string),
- e(environment.block_end_string),
- block_suffix_re
- )), (TOKEN_DATA, TOKEN_RAW_END), '#pop'),
- (c('(.)'), (Failure('Missing end of raw directive'),), None)
- ],
- # line statements
- TOKEN_LINESTATEMENT_BEGIN: [
- (c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop')
- ] + tag_rules,
- # line comments
- TOKEN_LINECOMMENT_BEGIN: [
- (c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT,
- TOKEN_LINECOMMENT_END), '#pop')
- ]
- }
-
- def _normalize_newlines(self, value):
- """Called for strings and template data to normlize it to unicode."""
- return newline_re.sub(self.newline_sequence, value)
-
- def tokenize(self, source, name=None, filename=None, state=None):
- """Calls tokeniter + tokenize and wraps it in a token stream.
- """
- stream = self.tokeniter(source, name, filename, state)
- return TokenStream(self.wrap(stream, name, filename), name, filename)
-
- def wrap(self, stream, name=None, filename=None):
- """This is called with the stream as returned by `tokenize` and wraps
- every token in a :class:`Token` and converts the value.
- """
- for lineno, token, value in stream:
- if token in ignored_tokens:
- continue
- elif token == 'linestatement_begin':
- token = 'block_begin'
- elif token == 'linestatement_end':
- token = 'block_end'
- # we are not interested in those tokens in the parser
- elif token in ('raw_begin', 'raw_end'):
- continue
- elif token == 'data':
- value = self._normalize_newlines(value)
- elif token == 'keyword':
- token = value
- elif token == 'name':
- value = str(value)
- elif token == 'string':
- # try to unescape string
- try:
- value = self._normalize_newlines(value[1:-1]) \
- .encode('ascii', 'backslashreplace') \
- .decode('unicode-escape')
- except Exception as e:
- msg = str(e).split(':')[-1].strip()
- raise TemplateSyntaxError(msg, lineno, name, filename)
- # if we can express it as bytestring (ascii only)
- # we do that for support of semi broken APIs
- # as datetime.datetime.strftime. On python 3 this
- # call becomes a noop thanks to 2to3
- try:
- value = str(value)
- except UnicodeError:
- pass
- elif token == 'integer':
- value = int(value)
- elif token == 'float':
- value = float(value)
- elif token == 'operator':
- token = operators[value]
- yield Token(lineno, token, value)
-
- def tokeniter(self, source, name, filename=None, state=None):
- """This method tokenizes the text and returns the tokens in a
- generator. Use this method if you just want to tokenize a template.
- """
- source = '\n'.join(unicode(source).splitlines())
- pos = 0
- lineno = 1
- stack = ['root']
- if state is not None and state != 'root':
- assert state in ('variable', 'block'), 'invalid state'
- stack.append(state + '_begin')
- else:
- state = 'root'
- statetokens = self.rules[stack[-1]]
- source_length = len(source)
-
- balancing_stack = []
-
- while 1:
- # tokenizer loop
- for regex, tokens, new_state in statetokens:
- m = regex.match(source, pos)
- # if no match we try again with the next rule
- if m is None:
- continue
-
- # we only match blocks and variables if brances / parentheses
- # are balanced. continue parsing with the lower rule which
- # is the operator rule. do this only if the end tags look
- # like operators
- if balancing_stack and \
- tokens in ('variable_end', 'block_end',
- 'linestatement_end'):
- continue
-
- # tuples support more options
- if isinstance(tokens, tuple):
- for idx, token in enumerate(tokens):
- # failure group
- if token.__class__ is Failure:
- raise token(lineno, filename)
- # bygroup is a bit more complex, in that case we
- # yield for the current token the first named
- # group that matched
- elif token == '#bygroup':
- for key, value in m.groupdict().iteritems():
- if value is not None:
- yield lineno, key, value
- lineno += value.count('\n')
- break
- else:
- raise RuntimeError('%r wanted to resolve '
- 'the token dynamically'
- ' but no group matched'
- % regex)
- # normal group
- else:
- data = m.group(idx + 1)
- if data or token not in ignore_if_empty:
- yield lineno, token, data
- lineno += data.count('\n')
-
- # strings as token just are yielded as it.
- else:
- data = m.group()
- # update brace/parentheses balance
- if tokens == 'operator':
- if data == '{':
- balancing_stack.append('}')
- elif data == '(':
- balancing_stack.append(')')
- elif data == '[':
- balancing_stack.append(']')
- elif data in ('}', ')', ']'):
- if not balancing_stack:
- raise TemplateSyntaxError('unexpected \'%s\'' %
- data, lineno, name,
- filename)
- expected_op = balancing_stack.pop()
- if expected_op != data:
- raise TemplateSyntaxError('unexpected \'%s\', '
- 'expected \'%s\'' %
- (data, expected_op),
- lineno, name,
- filename)
- # yield items
- if data or tokens not in ignore_if_empty:
- yield lineno, tokens, data
- lineno += data.count('\n')
-
- # fetch new position into new variable so that we can check
- # if there is a internal parsing error which would result
- # in an infinite loop
- pos2 = m.end()
-
- # handle state changes
- if new_state is not None:
- # remove the uppermost state
- if new_state == '#pop':
- stack.pop()
- # resolve the new state by group checking
- elif new_state == '#bygroup':
- for key, value in m.groupdict().iteritems():
- if value is not None:
- stack.append(key)
- break
- else:
- raise RuntimeError('%r wanted to resolve the '
- 'new state dynamically but'
- ' no group matched' %
- regex)
- # direct state name given
- else:
- stack.append(new_state)
- statetokens = self.rules[stack[-1]]
- # we are still at the same position and no stack change.
- # this means a loop without break condition, avoid that and
- # raise error
- elif pos2 == pos:
- raise RuntimeError('%r yielded empty string without '
- 'stack change' % regex)
- # publish new function and start again
- pos = pos2
- break
- # if loop terminated without break we havn't found a single match
- # either we are at the end of the file or we have a problem
- else:
- # end of text
- if pos >= source_length:
- return
- # something went wrong
- raise TemplateSyntaxError('unexpected char %r at %d' %
- (source[pos], pos), lineno,
- name, filename)
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/loaders.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/loaders.py
deleted file mode 100755
index bd435e8b0..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/loaders.py
+++ /dev/null
@@ -1,449 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.loaders
- ~~~~~~~~~~~~~~
-
- Jinja loader classes.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import os
-import sys
-import weakref
-from types import ModuleType
-from os import path
-try:
- from hashlib import sha1
-except ImportError:
- from sha import new as sha1
-from jinja2.exceptions import TemplateNotFound
-from jinja2.utils import LRUCache, open_if_exists, internalcode
-
-
-def split_template_path(template):
- """Split a path into segments and perform a sanity check. If it detects
- '..' in the path it will raise a `TemplateNotFound` error.
- """
- pieces = []
- for piece in template.split('/'):
- if path.sep in piece \
- or (path.altsep and path.altsep in piece) or \
- piece == path.pardir:
- raise TemplateNotFound(template)
- elif piece and piece != '.':
- pieces.append(piece)
- return pieces
-
-
-class BaseLoader(object):
- """Baseclass for all loaders. Subclass this and override `get_source` to
- implement a custom loading mechanism. The environment provides a
- `get_template` method that calls the loader's `load` method to get the
- :class:`Template` object.
-
- A very basic example for a loader that looks up templates on the file
- system could look like this::
-
- from jinja2 import BaseLoader, TemplateNotFound
- from os.path import join, exists, getmtime
-
- class MyLoader(BaseLoader):
-
- def __init__(self, path):
- self.path = path
-
- def get_source(self, environment, template):
- path = join(self.path, template)
- if not exists(path):
- raise TemplateNotFound(template)
- mtime = getmtime(path)
- with file(path) as f:
- source = f.read().decode('utf-8')
- return source, path, lambda: mtime == getmtime(path)
- """
-
- #: if set to `False` it indicates that the loader cannot provide access
- #: to the source of templates.
- #:
- #: .. versionadded:: 2.4
- has_source_access = True
-
- def get_source(self, environment, template):
- """Get the template source, filename and reload helper for a template.
- It's passed the environment and template name and has to return a
- tuple in the form ``(source, filename, uptodate)`` or raise a
- `TemplateNotFound` error if it can't locate the template.
-
- The source part of the returned tuple must be the source of the
- template as unicode string or a ASCII bytestring. The filename should
- be the name of the file on the filesystem if it was loaded from there,
- otherwise `None`. The filename is used by python for the tracebacks
- if no loader extension is used.
-
- The last item in the tuple is the `uptodate` function. If auto
- reloading is enabled it's always called to check if the template
- changed. No arguments are passed so the function must store the
- old state somewhere (for example in a closure). If it returns `False`
- the template will be reloaded.
- """
- if not self.has_source_access:
- raise RuntimeError('%s cannot provide access to the source' %
- self.__class__.__name__)
- raise TemplateNotFound(template)
-
- def list_templates(self):
- """Iterates over all templates. If the loader does not support that
- it should raise a :exc:`TypeError` which is the default behavior.
- """
- raise TypeError('this loader cannot iterate over all templates')
-
- @internalcode
- def load(self, environment, name, globals=None):
- """Loads a template. This method looks up the template in the cache
- or loads one by calling :meth:`get_source`. Subclasses should not
- override this method as loaders working on collections of other
- loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
- will not call this method but `get_source` directly.
- """
- code = None
- if globals is None:
- globals = {}
-
- # first we try to get the source for this template together
- # with the filename and the uptodate function.
- source, filename, uptodate = self.get_source(environment, name)
-
- # try to load the code from the bytecode cache if there is a
- # bytecode cache configured.
- bcc = environment.bytecode_cache
- if bcc is not None:
- bucket = bcc.get_bucket(environment, name, filename, source)
- code = bucket.code
-
- # if we don't have code so far (not cached, no longer up to
- # date) etc. we compile the template
- if code is None:
- code = environment.compile(source, name, filename)
-
- # if the bytecode cache is available and the bucket doesn't
- # have a code so far, we give the bucket the new code and put
- # it back to the bytecode cache.
- if bcc is not None and bucket.code is None:
- bucket.code = code
- bcc.set_bucket(bucket)
-
- return environment.template_class.from_code(environment, code,
- globals, uptodate)
-
-
-class FileSystemLoader(BaseLoader):
- """Loads templates from the file system. This loader can find templates
- in folders on the file system and is the preferred way to load them.
-
- The loader takes the path to the templates as string, or if multiple
- locations are wanted a list of them which is then looked up in the
- given order:
-
- >>> loader = FileSystemLoader('/path/to/templates')
- >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
-
- Per default the template encoding is ``'utf-8'`` which can be changed
- by setting the `encoding` parameter to something else.
- """
-
- def __init__(self, searchpath, encoding='utf-8'):
- if isinstance(searchpath, basestring):
- searchpath = [searchpath]
- self.searchpath = list(searchpath)
- self.encoding = encoding
-
- def get_source(self, environment, template):
- pieces = split_template_path(template)
- for searchpath in self.searchpath:
- filename = path.join(searchpath, *pieces)
- f = open_if_exists(filename)
- if f is None:
- continue
- try:
- contents = f.read().decode(self.encoding)
- finally:
- f.close()
-
- mtime = path.getmtime(filename)
- def uptodate():
- try:
- return path.getmtime(filename) == mtime
- except OSError:
- return False
- return contents, filename, uptodate
- raise TemplateNotFound(template)
-
- def list_templates(self):
- found = set()
- for searchpath in self.searchpath:
- for dirpath, dirnames, filenames in os.walk(searchpath):
- for filename in filenames:
- template = os.path.join(dirpath, filename) \
- [len(searchpath):].strip(os.path.sep) \
- .replace(os.path.sep, '/')
- if template[:2] == './':
- template = template[2:]
- if template not in found:
- found.add(template)
- return sorted(found)
-
-
-class PackageLoader(BaseLoader):
- """Load templates from python eggs or packages. It is constructed with
- the name of the python package and the path to the templates in that
- package::
-
- loader = PackageLoader('mypackage', 'views')
-
- If the package path is not given, ``'templates'`` is assumed.
-
- Per default the template encoding is ``'utf-8'`` which can be changed
- by setting the `encoding` parameter to something else. Due to the nature
- of eggs it's only possible to reload templates if the package was loaded
- from the file system and not a zip file.
- """
-
- def __init__(self, package_name, package_path='templates',
- encoding='utf-8'):
- from pkg_resources import DefaultProvider, ResourceManager, \
- get_provider
- provider = get_provider(package_name)
- self.encoding = encoding
- self.manager = ResourceManager()
- self.filesystem_bound = isinstance(provider, DefaultProvider)
- self.provider = provider
- self.package_path = package_path
-
- def get_source(self, environment, template):
- pieces = split_template_path(template)
- p = '/'.join((self.package_path,) + tuple(pieces))
- if not self.provider.has_resource(p):
- raise TemplateNotFound(template)
-
- filename = uptodate = None
- if self.filesystem_bound:
- filename = self.provider.get_resource_filename(self.manager, p)
- mtime = path.getmtime(filename)
- def uptodate():
- try:
- return path.getmtime(filename) == mtime
- except OSError:
- return False
-
- source = self.provider.get_resource_string(self.manager, p)
- return source.decode(self.encoding), filename, uptodate
-
- def list_templates(self):
- path = self.package_path
- if path[:2] == './':
- path = path[2:]
- elif path == '.':
- path = ''
- offset = len(path)
- results = []
- def _walk(path):
- for filename in self.provider.resource_listdir(path):
- fullname = path + '/' + filename
- if self.provider.resource_isdir(fullname):
- for item in _walk(fullname):
- results.append(item)
- else:
- results.append(fullname[offset:].lstrip('/'))
- _walk(path)
- results.sort()
- return results
-
-
-class DictLoader(BaseLoader):
- """Loads a template from a python dict. It's passed a dict of unicode
- strings bound to template names. This loader is useful for unittesting:
-
- >>> loader = DictLoader({'index.html': 'source here'})
-
- Because auto reloading is rarely useful this is disabled per default.
- """
-
- def __init__(self, mapping):
- self.mapping = mapping
-
- def get_source(self, environment, template):
- if template in self.mapping:
- source = self.mapping[template]
- return source, None, lambda: source != self.mapping.get(template)
- raise TemplateNotFound(template)
-
- def list_templates(self):
- return sorted(self.mapping)
-
-
-class FunctionLoader(BaseLoader):
- """A loader that is passed a function which does the loading. The
- function becomes the name of the template passed and has to return either
- an unicode string with the template source, a tuple in the form ``(source,
- filename, uptodatefunc)`` or `None` if the template does not exist.
-
- >>> def load_template(name):
- ... if name == 'index.html':
- ... return '...'
- ...
- >>> loader = FunctionLoader(load_template)
-
- The `uptodatefunc` is a function that is called if autoreload is enabled
- and has to return `True` if the template is still up to date. For more
- details have a look at :meth:`BaseLoader.get_source` which has the same
- return value.
- """
-
- def __init__(self, load_func):
- self.load_func = load_func
-
- def get_source(self, environment, template):
- rv = self.load_func(template)
- if rv is None:
- raise TemplateNotFound(template)
- elif isinstance(rv, basestring):
- return rv, None, None
- return rv
-
-
-class PrefixLoader(BaseLoader):
- """A loader that is passed a dict of loaders where each loader is bound
- to a prefix. The prefix is delimited from the template by a slash per
- default, which can be changed by setting the `delimiter` argument to
- something else::
-
- loader = PrefixLoader({
- 'app1': PackageLoader('mypackage.app1'),
- 'app2': PackageLoader('mypackage.app2')
- })
-
- By loading ``'app1/index.html'`` the file from the app1 package is loaded,
- by loading ``'app2/index.html'`` the file from the second.
- """
-
- def __init__(self, mapping, delimiter='/'):
- self.mapping = mapping
- self.delimiter = delimiter
-
- def get_source(self, environment, template):
- try:
- prefix, name = template.split(self.delimiter, 1)
- loader = self.mapping[prefix]
- except (ValueError, KeyError):
- raise TemplateNotFound(template)
- try:
- return loader.get_source(environment, name)
- except TemplateNotFound:
- # re-raise the exception with the correct fileame here.
- # (the one that includes the prefix)
- raise TemplateNotFound(template)
-
- def list_templates(self):
- result = []
- for prefix, loader in self.mapping.iteritems():
- for template in loader.list_templates():
- result.append(prefix + self.delimiter + template)
- return result
-
-
-class ChoiceLoader(BaseLoader):
- """This loader works like the `PrefixLoader` just that no prefix is
- specified. If a template could not be found by one loader the next one
- is tried.
-
- >>> loader = ChoiceLoader([
- ... FileSystemLoader('/path/to/user/templates'),
- ... FileSystemLoader('/path/to/system/templates')
- ... ])
-
- This is useful if you want to allow users to override builtin templates
- from a different location.
- """
-
- def __init__(self, loaders):
- self.loaders = loaders
-
- def get_source(self, environment, template):
- for loader in self.loaders:
- try:
- return loader.get_source(environment, template)
- except TemplateNotFound:
- pass
- raise TemplateNotFound(template)
-
- def list_templates(self):
- found = set()
- for loader in self.loaders:
- found.update(loader.list_templates())
- return sorted(found)
-
-
-class _TemplateModule(ModuleType):
- """Like a normal module but with support for weak references"""
-
-
-class ModuleLoader(BaseLoader):
- """This loader loads templates from precompiled templates.
-
- Example usage:
-
- >>> loader = ChoiceLoader([
- ... ModuleLoader('/path/to/compiled/templates'),
- ... FileSystemLoader('/path/to/templates')
- ... ])
- """
-
- has_source_access = False
-
- def __init__(self, path):
- package_name = '_jinja2_module_templates_%x' % id(self)
-
- # create a fake module that looks for the templates in the
- # path given.
- mod = _TemplateModule(package_name)
- if isinstance(path, basestring):
- path = [path]
- else:
- path = list(path)
- mod.__path__ = path
-
- sys.modules[package_name] = weakref.proxy(mod,
- lambda x: sys.modules.pop(package_name, None))
-
- # the only strong reference, the sys.modules entry is weak
- # so that the garbage collector can remove it once the
- # loader that created it goes out of business.
- self.module = mod
- self.package_name = package_name
-
- @staticmethod
- def get_template_key(name):
- return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
-
- @staticmethod
- def get_module_filename(name):
- return ModuleLoader.get_template_key(name) + '.py'
-
- @internalcode
- def load(self, environment, name, globals=None):
- key = self.get_template_key(name)
- module = '%s.%s' % (self.package_name, key)
- mod = getattr(self.module, module, None)
- if mod is None:
- try:
- mod = __import__(module, None, None, ['root'])
- except ImportError:
- raise TemplateNotFound(name)
-
- # remove the entry from sys.modules, we only want the attribute
- # on the module object we have stored on the loader.
- sys.modules.pop(module, None)
-
- return environment.template_class.from_module_dict(
- environment, mod.__dict__, globals)
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/meta.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/meta.py
deleted file mode 100755
index 3a779a5e9..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/meta.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.meta
- ~~~~~~~~~~~
-
- This module implements various functions that exposes information about
- templates that might be interesting for various kinds of applications.
-
- :copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details.
- :license: BSD, see LICENSE for more details.
-"""
-from jinja2 import nodes
-from jinja2.compiler import CodeGenerator
-
-
-class TrackingCodeGenerator(CodeGenerator):
- """We abuse the code generator for introspection."""
-
- def __init__(self, environment):
- CodeGenerator.__init__(self, environment, '<introspection>',
- '<introspection>')
- self.undeclared_identifiers = set()
-
- def write(self, x):
- """Don't write."""
-
- def pull_locals(self, frame):
- """Remember all undeclared identifiers."""
- self.undeclared_identifiers.update(frame.identifiers.undeclared)
-
-
-def find_undeclared_variables(ast):
- """Returns a set of all variables in the AST that will be looked up from
- the context at runtime. Because at compile time it's not known which
- variables will be used depending on the path the execution takes at
- runtime, all variables are returned.
-
- >>> from jinja2 import Environment, meta
- >>> env = Environment()
- >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
- >>> meta.find_undeclared_variables(ast)
- set(['bar'])
-
- .. admonition:: Implementation
-
- Internally the code generator is used for finding undeclared variables.
- This is good to know because the code generator might raise a
- :exc:`TemplateAssertionError` during compilation and as a matter of
- fact this function can currently raise that exception as well.
- """
- codegen = TrackingCodeGenerator(ast.environment)
- codegen.visit(ast)
- return codegen.undeclared_identifiers
-
-
-def find_referenced_templates(ast):
- """Finds all the referenced templates from the AST. This will return an
- iterator over all the hardcoded template extensions, inclusions and
- imports. If dynamic inheritance or inclusion is used, `None` will be
- yielded.
-
- >>> from jinja2 import Environment, meta
- >>> env = Environment()
- >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
- >>> list(meta.find_referenced_templates(ast))
- ['layout.html', None]
-
- This function is useful for dependency tracking. For example if you want
- to rebuild parts of the website after a layout template has changed.
- """
- for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import,
- nodes.Include)):
- if not isinstance(node.template, nodes.Const):
- # a tuple with some non consts in there
- if isinstance(node.template, (nodes.Tuple, nodes.List)):
- for template_name in node.template.items:
- # something const, only yield the strings and ignore
- # non-string consts that really just make no sense
- if isinstance(template_name, nodes.Const):
- if isinstance(template_name.value, basestring):
- yield template_name.value
- # something dynamic in there
- else:
- yield None
- # something dynamic we don't know about here
- else:
- yield None
- continue
- # constant is a basestring, direct template name
- if isinstance(node.template.value, basestring):
- yield node.template.value
- # a tuple or list (latter *should* not happen) made of consts,
- # yield the consts that are strings. We could warn here for
- # non string values
- elif isinstance(node, nodes.Include) and \
- isinstance(node.template.value, (tuple, list)):
- for template_name in node.template.value:
- if isinstance(template_name, basestring):
- yield template_name
- # something else we don't care about, we could warn here
- else:
- yield None
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/nodes.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/nodes.py
deleted file mode 100755
index b6696c7e6..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/nodes.py
+++ /dev/null
@@ -1,874 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.nodes
- ~~~~~~~~~~~~
-
- This module implements additional nodes derived from the ast base node.
-
- It also provides some node tree helper functions like `in_lineno` and
- `get_nodes` used by the parser and translator in order to normalize
- python and jinja nodes.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import operator
-from itertools import chain, izip
-from collections import deque
-from jinja2.utils import Markup
-
-
-_binop_to_func = {
- '*': operator.mul,
- '/': operator.truediv,
- '//': operator.floordiv,
- '**': operator.pow,
- '%': operator.mod,
- '+': operator.add,
- '-': operator.sub
-}
-
-_uaop_to_func = {
- 'not': operator.not_,
- '+': operator.pos,
- '-': operator.neg
-}
-
-_cmpop_to_func = {
- 'eq': operator.eq,
- 'ne': operator.ne,
- 'gt': operator.gt,
- 'gteq': operator.ge,
- 'lt': operator.lt,
- 'lteq': operator.le,
- 'in': lambda a, b: a in b,
- 'notin': lambda a, b: a not in b
-}
-
-
-class Impossible(Exception):
- """Raised if the node could not perform a requested action."""
-
-
-class NodeType(type):
- """A metaclass for nodes that handles the field and attribute
- inheritance. fields and attributes from the parent class are
- automatically forwarded to the child."""
-
- def __new__(cls, name, bases, d):
- for attr in 'fields', 'attributes':
- storage = []
- storage.extend(getattr(bases[0], attr, ()))
- storage.extend(d.get(attr, ()))
- assert len(bases) == 1, 'multiple inheritance not allowed'
- assert len(storage) == len(set(storage)), 'layout conflict'
- d[attr] = tuple(storage)
- d.setdefault('abstract', False)
- return type.__new__(cls, name, bases, d)
-
-
-class EvalContext(object):
- """Holds evaluation time information. Custom attributes can be attached
- to it in extensions.
- """
-
- def __init__(self, environment, template_name=None):
- if callable(environment.autoescape):
- self.autoescape = environment.autoescape(template_name)
- else:
- self.autoescape = environment.autoescape
- self.volatile = False
-
- def save(self):
- return self.__dict__.copy()
-
- def revert(self, old):
- self.__dict__.clear()
- self.__dict__.update(old)
-
-
-def get_eval_context(node, ctx):
- if ctx is None:
- if node.environment is None:
- raise RuntimeError('if no eval context is passed, the '
- 'node must have an attached '
- 'environment.')
- return EvalContext(node.environment)
- return ctx
-
-
-class Node(object):
- """Baseclass for all Jinja2 nodes. There are a number of nodes available
- of different types. There are three major types:
-
- - :class:`Stmt`: statements
- - :class:`Expr`: expressions
- - :class:`Helper`: helper nodes
- - :class:`Template`: the outermost wrapper node
-
- All nodes have fields and attributes. Fields may be other nodes, lists,
- or arbitrary values. Fields are passed to the constructor as regular
- positional arguments, attributes as keyword arguments. Each node has
- two attributes: `lineno` (the line number of the node) and `environment`.
- The `environment` attribute is set at the end of the parsing process for
- all nodes automatically.
- """
- __metaclass__ = NodeType
- fields = ()
- attributes = ('lineno', 'environment')
- abstract = True
-
- def __init__(self, *fields, **attributes):
- if self.abstract:
- raise TypeError('abstract nodes are not instanciable')
- if fields:
- if len(fields) != len(self.fields):
- if not self.fields:
- raise TypeError('%r takes 0 arguments' %
- self.__class__.__name__)
- raise TypeError('%r takes 0 or %d argument%s' % (
- self.__class__.__name__,
- len(self.fields),
- len(self.fields) != 1 and 's' or ''
- ))
- for name, arg in izip(self.fields, fields):
- setattr(self, name, arg)
- for attr in self.attributes:
- setattr(self, attr, attributes.pop(attr, None))
- if attributes:
- raise TypeError('unknown attribute %r' %
- iter(attributes).next())
-
- def iter_fields(self, exclude=None, only=None):
- """This method iterates over all fields that are defined and yields
- ``(key, value)`` tuples. Per default all fields are returned, but
- it's possible to limit that to some fields by providing the `only`
- parameter or to exclude some using the `exclude` parameter. Both
- should be sets or tuples of field names.
- """
- for name in self.fields:
- if (exclude is only is None) or \
- (exclude is not None and name not in exclude) or \
- (only is not None and name in only):
- try:
- yield name, getattr(self, name)
- except AttributeError:
- pass
-
- def iter_child_nodes(self, exclude=None, only=None):
- """Iterates over all direct child nodes of the node. This iterates
- over all fields and yields the values of they are nodes. If the value
- of a field is a list all the nodes in that list are returned.
- """
- for field, item in self.iter_fields(exclude, only):
- if isinstance(item, list):
- for n in item:
- if isinstance(n, Node):
- yield n
- elif isinstance(item, Node):
- yield item
-
- def find(self, node_type):
- """Find the first node of a given type. If no such node exists the
- return value is `None`.
- """
- for result in self.find_all(node_type):
- return result
-
- def find_all(self, node_type):
- """Find all the nodes of a given type. If the type is a tuple,
- the check is performed for any of the tuple items.
- """
- for child in self.iter_child_nodes():
- if isinstance(child, node_type):
- yield child
- for result in child.find_all(node_type):
- yield result
-
- def set_ctx(self, ctx):
- """Reset the context of a node and all child nodes. Per default the
- parser will all generate nodes that have a 'load' context as it's the
- most common one. This method is used in the parser to set assignment
- targets and other nodes to a store context.
- """
- todo = deque([self])
- while todo:
- node = todo.popleft()
- if 'ctx' in node.fields:
- node.ctx = ctx
- todo.extend(node.iter_child_nodes())
- return self
-
- def set_lineno(self, lineno, override=False):
- """Set the line numbers of the node and children."""
- todo = deque([self])
- while todo:
- node = todo.popleft()
- if 'lineno' in node.attributes:
- if node.lineno is None or override:
- node.lineno = lineno
- todo.extend(node.iter_child_nodes())
- return self
-
- def set_environment(self, environment):
- """Set the environment for all nodes."""
- todo = deque([self])
- while todo:
- node = todo.popleft()
- node.environment = environment
- todo.extend(node.iter_child_nodes())
- return self
-
- def __eq__(self, other):
- return type(self) is type(other) and \
- tuple(self.iter_fields()) == tuple(other.iter_fields())
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __repr__(self):
- return '%s(%s)' % (
- self.__class__.__name__,
- ', '.join('%s=%r' % (arg, getattr(self, arg, None)) for
- arg in self.fields)
- )
-
-
-class Stmt(Node):
- """Base node for all statements."""
- abstract = True
-
-
-class Helper(Node):
- """Nodes that exist in a specific context only."""
- abstract = True
-
-
-class Template(Node):
- """Node that represents a template. This must be the outermost node that
- is passed to the compiler.
- """
- fields = ('body',)
-
-
-class Output(Stmt):
- """A node that holds multiple expressions which are then printed out.
- This is used both for the `print` statement and the regular template data.
- """
- fields = ('nodes',)
-
-
-class Extends(Stmt):
- """Represents an extends statement."""
- fields = ('template',)
-
-
-class For(Stmt):
- """The for loop. `target` is the target for the iteration (usually a
- :class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list
- of nodes that are used as loop-body, and `else_` a list of nodes for the
- `else` block. If no else node exists it has to be an empty list.
-
- For filtered nodes an expression can be stored as `test`, otherwise `None`.
- """
- fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive')
-
-
-class If(Stmt):
- """If `test` is true, `body` is rendered, else `else_`."""
- fields = ('test', 'body', 'else_')
-
-
-class Macro(Stmt):
- """A macro definition. `name` is the name of the macro, `args` a list of
- arguments and `defaults` a list of defaults if there are any. `body` is
- a list of nodes for the macro body.
- """
- fields = ('name', 'args', 'defaults', 'body')
-
-
-class CallBlock(Stmt):
- """Like a macro without a name but a call instead. `call` is called with
- the unnamed macro as `caller` argument this node holds.
- """
- fields = ('call', 'args', 'defaults', 'body')
-
-
-class FilterBlock(Stmt):
- """Node for filter sections."""
- fields = ('body', 'filter')
-
-
-class Block(Stmt):
- """A node that represents a block."""
- fields = ('name', 'body', 'scoped')
-
-
-class Include(Stmt):
- """A node that represents the include tag."""
- fields = ('template', 'with_context', 'ignore_missing')
-
-
-class Import(Stmt):
- """A node that represents the import tag."""
- fields = ('template', 'target', 'with_context')
-
-
-class FromImport(Stmt):
- """A node that represents the from import tag. It's important to not
- pass unsafe names to the name attribute. The compiler translates the
- attribute lookups directly into getattr calls and does *not* use the
- subscript callback of the interface. As exported variables may not
- start with double underscores (which the parser asserts) this is not a
- problem for regular Jinja code, but if this node is used in an extension
- extra care must be taken.
-
- The list of names may contain tuples if aliases are wanted.
- """
- fields = ('template', 'names', 'with_context')
-
-
-class ExprStmt(Stmt):
- """A statement that evaluates an expression and discards the result."""
- fields = ('node',)
-
-
-class Assign(Stmt):
- """Assigns an expression to a target."""
- fields = ('target', 'node')
-
-
-class Expr(Node):
- """Baseclass for all expressions."""
- abstract = True
-
- def as_const(self, eval_ctx=None):
- """Return the value of the expression as constant or raise
- :exc:`Impossible` if this was not possible.
-
- An :class:`EvalContext` can be provided, if none is given
- a default context is created which requires the nodes to have
- an attached environment.
-
- .. versionchanged:: 2.4
- the `eval_ctx` parameter was added.
- """
- raise Impossible()
-
- def can_assign(self):
- """Check if it's possible to assign something to this node."""
- return False
-
-
-class BinExpr(Expr):
- """Baseclass for all binary expressions."""
- fields = ('left', 'right')
- operator = None
- abstract = True
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- f = _binop_to_func[self.operator]
- try:
- return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
- except:
- raise Impossible()
-
-
-class UnaryExpr(Expr):
- """Baseclass for all unary expressions."""
- fields = ('node',)
- operator = None
- abstract = True
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- f = _uaop_to_func[self.operator]
- try:
- return f(self.node.as_const(eval_ctx))
- except:
- raise Impossible()
-
-
-class Name(Expr):
- """Looks up a name or stores a value in a name.
- The `ctx` of the node can be one of the following values:
-
- - `store`: store a value in the name
- - `load`: load that name
- - `param`: like `store` but if the name was defined as function parameter.
- """
- fields = ('name', 'ctx')
-
- def can_assign(self):
- return self.name not in ('true', 'false', 'none',
- 'True', 'False', 'None')
-
-
-class Literal(Expr):
- """Baseclass for literals."""
- abstract = True
-
-
-class Const(Literal):
- """All constant values. The parser will return this node for simple
- constants such as ``42`` or ``"foo"`` but it can be used to store more
- complex values such as lists too. Only constants with a safe
- representation (objects where ``eval(repr(x)) == x`` is true).
- """
- fields = ('value',)
-
- def as_const(self, eval_ctx=None):
- return self.value
-
- @classmethod
- def from_untrusted(cls, value, lineno=None, environment=None):
- """Return a const object if the value is representable as
- constant value in the generated code, otherwise it will raise
- an `Impossible` exception.
- """
- from compiler import has_safe_repr
- if not has_safe_repr(value):
- raise Impossible()
- return cls(value, lineno=lineno, environment=environment)
-
-
-class TemplateData(Literal):
- """A constant template string."""
- fields = ('data',)
-
- def as_const(self, eval_ctx=None):
- if get_eval_context(self, eval_ctx).autoescape:
- return Markup(self.data)
- return self.data
-
-
-class Tuple(Literal):
- """For loop unpacking and some other things like multiple arguments
- for subscripts. Like for :class:`Name` `ctx` specifies if the tuple
- is used for loading the names or storing.
- """
- fields = ('items', 'ctx')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return tuple(x.as_const(eval_ctx) for x in self.items)
-
- def can_assign(self):
- for item in self.items:
- if not item.can_assign():
- return False
- return True
-
-
-class List(Literal):
- """Any list literal such as ``[1, 2, 3]``"""
- fields = ('items',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return [x.as_const(eval_ctx) for x in self.items]
-
-
-class Dict(Literal):
- """Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of
- :class:`Pair` nodes.
- """
- fields = ('items',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return dict(x.as_const(eval_ctx) for x in self.items)
-
-
-class Pair(Helper):
- """A key, value pair for dicts."""
- fields = ('key', 'value')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
-
-
-class Keyword(Helper):
- """A key, value pair for keyword arguments where key is a string."""
- fields = ('key', 'value')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.key, self.value.as_const(eval_ctx)
-
-
-class CondExpr(Expr):
- """A conditional expression (inline if expression). (``{{
- foo if bar else baz }}``)
- """
- fields = ('test', 'expr1', 'expr2')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if self.test.as_const(eval_ctx):
- return self.expr1.as_const(eval_ctx)
-
- # if we evaluate to an undefined object, we better do that at runtime
- if self.expr2 is None:
- raise Impossible()
-
- return self.expr2.as_const(eval_ctx)
-
-
-class Filter(Expr):
- """This node applies a filter on an expression. `name` is the name of
- the filter, the rest of the fields are the same as for :class:`Call`.
-
- If the `node` of a filter is `None` the contents of the last buffer are
- filtered. Buffers are created by macros and filter blocks.
- """
- fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile or self.node is None:
- raise Impossible()
- # we have to be careful here because we call filter_ below.
- # if this variable would be called filter, 2to3 would wrap the
- # call in a list beause it is assuming we are talking about the
- # builtin filter function here which no longer returns a list in
- # python 3. because of that, do not rename filter_ to filter!
- filter_ = self.environment.filters.get(self.name)
- if filter_ is None or getattr(filter_, 'contextfilter', False):
- raise Impossible()
- obj = self.node.as_const(eval_ctx)
- args = [x.as_const(eval_ctx) for x in self.args]
- if getattr(filter_, 'evalcontextfilter', False):
- args.insert(0, eval_ctx)
- elif getattr(filter_, 'environmentfilter', False):
- args.insert(0, self.environment)
- kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
- if self.dyn_args is not None:
- try:
- args.extend(self.dyn_args.as_const(eval_ctx))
- except:
- raise Impossible()
- if self.dyn_kwargs is not None:
- try:
- kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
- except:
- raise Impossible()
- try:
- return filter_(obj, *args, **kwargs)
- except:
- raise Impossible()
-
-
-class Test(Expr):
- """Applies a test on an expression. `name` is the name of the test, the
- rest of the fields are the same as for :class:`Call`.
- """
- fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
-
-
-class Call(Expr):
- """Calls an expression. `args` is a list of arguments, `kwargs` a list
- of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
- and `dyn_kwargs` has to be either `None` or a node that is used as
- node for dynamic positional (``*args``) or keyword (``**kwargs``)
- arguments.
- """
- fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile:
- raise Impossible()
- obj = self.node.as_const(eval_ctx)
-
- # don't evaluate context functions
- args = [x.as_const(eval_ctx) for x in self.args]
- if getattr(obj, 'contextfunction', False):
- raise Impossible()
- elif getattr(obj, 'evalcontextfunction', False):
- args.insert(0, eval_ctx)
- elif getattr(obj, 'environmentfunction', False):
- args.insert(0, self.environment)
-
- kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
- if self.dyn_args is not None:
- try:
- args.extend(self.dyn_args.as_const(eval_ctx))
- except:
- raise Impossible()
- if self.dyn_kwargs is not None:
- try:
- kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
- except:
- raise Impossible()
- try:
- return obj(*args, **kwargs)
- except:
- raise Impossible()
-
-
-class Getitem(Expr):
- """Get an attribute or item from an expression and prefer the item."""
- fields = ('node', 'arg', 'ctx')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if self.ctx != 'load':
- raise Impossible()
- try:
- return self.environment.getitem(self.node.as_const(eval_ctx),
- self.arg.as_const(eval_ctx))
- except:
- raise Impossible()
-
- def can_assign(self):
- return False
-
-
-class Getattr(Expr):
- """Get an attribute or item from an expression that is a ascii-only
- bytestring and prefer the attribute.
- """
- fields = ('node', 'attr', 'ctx')
-
- def as_const(self, eval_ctx=None):
- if self.ctx != 'load':
- raise Impossible()
- try:
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.environment.getattr(self.node.as_const(eval_ctx), arg)
- except:
- raise Impossible()
-
- def can_assign(self):
- return False
-
-
-class Slice(Expr):
- """Represents a slice object. This must only be used as argument for
- :class:`Subscript`.
- """
- fields = ('start', 'stop', 'step')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- def const(obj):
- if obj is None:
- return None
- return obj.as_const(eval_ctx)
- return slice(const(self.start), const(self.stop), const(self.step))
-
-
-class Concat(Expr):
- """Concatenates the list of expressions provided after converting them to
- unicode.
- """
- fields = ('nodes',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return ''.join(unicode(x.as_const(eval_ctx)) for x in self.nodes)
-
-
-class Compare(Expr):
- """Compares an expression with some other expressions. `ops` must be a
- list of :class:`Operand`\s.
- """
- fields = ('expr', 'ops')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- result = value = self.expr.as_const(eval_ctx)
- try:
- for op in self.ops:
- new_value = op.expr.as_const(eval_ctx)
- result = _cmpop_to_func[op.op](value, new_value)
- value = new_value
- except:
- raise Impossible()
- return result
-
-
-class Operand(Helper):
- """Holds an operator and an expression."""
- fields = ('op', 'expr')
-
-if __debug__:
- Operand.__doc__ += '\nThe following operators are available: ' + \
- ', '.join(sorted('``%s``' % x for x in set(_binop_to_func) |
- set(_uaop_to_func) | set(_cmpop_to_func)))
-
-
-class Mul(BinExpr):
- """Multiplies the left with the right node."""
- operator = '*'
-
-
-class Div(BinExpr):
- """Divides the left by the right node."""
- operator = '/'
-
-
-class FloorDiv(BinExpr):
- """Divides the left by the right node and truncates conver the
- result into an integer by truncating.
- """
- operator = '//'
-
-
-class Add(BinExpr):
- """Add the left to the right node."""
- operator = '+'
-
-
-class Sub(BinExpr):
- """Substract the right from the left node."""
- operator = '-'
-
-
-class Mod(BinExpr):
- """Left modulo right."""
- operator = '%'
-
-
-class Pow(BinExpr):
- """Left to the power of right."""
- operator = '**'
-
-
-class And(BinExpr):
- """Short circuited AND."""
- operator = 'and'
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
-
-
-class Or(BinExpr):
- """Short circuited OR."""
- operator = 'or'
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
-
-
-class Not(UnaryExpr):
- """Negate the expression."""
- operator = 'not'
-
-
-class Neg(UnaryExpr):
- """Make the expression negative."""
- operator = '-'
-
-
-class Pos(UnaryExpr):
- """Make the expression positive (noop for most expressions)"""
- operator = '+'
-
-
-# Helpers for extensions
-
-
-class EnvironmentAttribute(Expr):
- """Loads an attribute from the environment object. This is useful for
- extensions that want to call a callback stored on the environment.
- """
- fields = ('name',)
-
-
-class ExtensionAttribute(Expr):
- """Returns the attribute of an extension bound to the environment.
- The identifier is the identifier of the :class:`Extension`.
-
- This node is usually constructed by calling the
- :meth:`~jinja2.ext.Extension.attr` method on an extension.
- """
- fields = ('identifier', 'name')
-
-
-class ImportedName(Expr):
- """If created with an import name the import name is returned on node
- access. For example ``ImportedName('cgi.escape')`` returns the `escape`
- function from the cgi module on evaluation. Imports are optimized by the
- compiler so there is no need to assign them to local variables.
- """
- fields = ('importname',)
-
-
-class InternalName(Expr):
- """An internal name in the compiler. You cannot create these nodes
- yourself but the parser provides a
- :meth:`~jinja2.parser.Parser.free_identifier` method that creates
- a new identifier for you. This identifier is not available from the
- template and is not threated specially by the compiler.
- """
- fields = ('name',)
-
- def __init__(self):
- raise TypeError('Can\'t create internal names. Use the '
- '`free_identifier` method on a parser.')
-
-
-class MarkSafe(Expr):
- """Mark the wrapped expression as safe (wrap it as `Markup`)."""
- fields = ('expr',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return Markup(self.expr.as_const(eval_ctx))
-
-
-class ContextReference(Expr):
- """Returns the current template context. It can be used like a
- :class:`Name` node, with a ``'load'`` ctx and will return the
- current :class:`~jinja2.runtime.Context` object.
-
- Here an example that assigns the current template name to a
- variable named `foo`::
-
- Assign(Name('foo', ctx='store'),
- Getattr(ContextReference(), 'name'))
- """
-
-
-class Continue(Stmt):
- """Continue a loop."""
-
-
-class Break(Stmt):
- """Break a loop."""
-
-
-class Scope(Stmt):
- """An artificial scope."""
- fields = ('body',)
-
-
-class EvalContextModifier(Stmt):
- """Modifies the eval context. For each option that should be modified,
- a :class:`Keyword` has to be added to the :attr:`options` list.
-
- Example to change the `autoescape` setting::
-
- EvalContextModifier(options=[Keyword('autoescape', Const(True))])
- """
- fields = ('options',)
-
-
-class ScopedEvalContextModifier(EvalContextModifier):
- """Modifies the eval context and reverts it later. Works exactly like
- :class:`EvalContextModifier` but will only modify the
- :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
- """
- fields = ('body',)
-
-
-# make sure nobody creates custom nodes
-def _failing_new(*args, **kwargs):
- raise TypeError('can\'t create custom node types')
-NodeType.__new__ = staticmethod(_failing_new); del _failing_new
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/optimizer.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/optimizer.py
deleted file mode 100755
index 00eab115e..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/optimizer.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.optimizer
- ~~~~~~~~~~~~~~~~
-
- The jinja optimizer is currently trying to constant fold a few expressions
- and modify the AST in place so that it should be easier to evaluate it.
-
- Because the AST does not contain all the scoping information and the
- compiler has to find that out, we cannot do all the optimizations we
- want. For example loop unrolling doesn't work because unrolled loops would
- have a different scoping.
-
- The solution would be a second syntax tree that has the scoping rules stored.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-from jinja2 import nodes
-from jinja2.visitor import NodeTransformer
-
-
-def optimize(node, environment):
- """The context hint can be used to perform an static optimization
- based on the context given."""
- optimizer = Optimizer(environment)
- return optimizer.visit(node)
-
-
-class Optimizer(NodeTransformer):
-
- def __init__(self, environment):
- self.environment = environment
-
- def visit_If(self, node):
- """Eliminate dead code."""
- # do not optimize ifs that have a block inside so that it doesn't
- # break super().
- if node.find(nodes.Block) is not None:
- return self.generic_visit(node)
- try:
- val = self.visit(node.test).as_const()
- except nodes.Impossible:
- return self.generic_visit(node)
- if val:
- body = node.body
- else:
- body = node.else_
- result = []
- for node in body:
- result.extend(self.visit_list(node))
- return result
-
- def fold(self, node):
- """Do constant folding."""
- node = self.generic_visit(node)
- try:
- return nodes.Const.from_untrusted(node.as_const(),
- lineno=node.lineno,
- environment=self.environment)
- except nodes.Impossible:
- return node
-
- visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
- visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
- visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \
- visit_Filter = visit_Test = visit_CondExpr = fold
- del fold
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/parser.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/parser.py
deleted file mode 100755
index 9814c939c..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/parser.py
+++ /dev/null
@@ -1,882 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.parser
- ~~~~~~~~~~~~~
-
- Implements the template parser.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-from jinja2 import nodes
-from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
-from jinja2.utils import next
-from jinja2.lexer import describe_token, describe_token_expr
-
-
-#: statements that callinto
-_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
- 'macro', 'include', 'from', 'import',
- 'set'])
-_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq'])
-
-
-class Parser(object):
- """This is the central parsing class Jinja2 uses. It's passed to
- extensions and can be used to parse expressions or statements.
- """
-
- def __init__(self, environment, source, name=None, filename=None,
- state=None):
- self.environment = environment
- self.stream = environment._tokenize(source, name, filename, state)
- self.name = name
- self.filename = filename
- self.closed = False
- self.extensions = {}
- for extension in environment.iter_extensions():
- for tag in extension.tags:
- self.extensions[tag] = extension.parse
- self._last_identifier = 0
- self._tag_stack = []
- self._end_token_stack = []
-
- def fail(self, msg, lineno=None, exc=TemplateSyntaxError):
- """Convenience method that raises `exc` with the message, passed
- line number or last line number as well as the current name and
- filename.
- """
- if lineno is None:
- lineno = self.stream.current.lineno
- raise exc(msg, lineno, self.name, self.filename)
-
- def _fail_ut_eof(self, name, end_token_stack, lineno):
- expected = []
- for exprs in end_token_stack:
- expected.extend(map(describe_token_expr, exprs))
- if end_token_stack:
- currently_looking = ' or '.join(
- "'%s'" % describe_token_expr(expr)
- for expr in end_token_stack[-1])
- else:
- currently_looking = None
-
- if name is None:
- message = ['Unexpected end of template.']
- else:
- message = ['Encountered unknown tag \'%s\'.' % name]
-
- if currently_looking:
- if name is not None and name in expected:
- message.append('You probably made a nesting mistake. Jinja '
- 'is expecting this tag, but currently looking '
- 'for %s.' % currently_looking)
- else:
- message.append('Jinja was looking for the following tags: '
- '%s.' % currently_looking)
-
- if self._tag_stack:
- message.append('The innermost block that needs to be '
- 'closed is \'%s\'.' % self._tag_stack[-1])
-
- self.fail(' '.join(message), lineno)
-
- def fail_unknown_tag(self, name, lineno=None):
- """Called if the parser encounters an unknown tag. Tries to fail
- with a human readable error message that could help to identify
- the problem.
- """
- return self._fail_ut_eof(name, self._end_token_stack, lineno)
-
- def fail_eof(self, end_tokens=None, lineno=None):
- """Like fail_unknown_tag but for end of template situations."""
- stack = list(self._end_token_stack)
- if end_tokens is not None:
- stack.append(end_tokens)
- return self._fail_ut_eof(None, stack, lineno)
-
- def is_tuple_end(self, extra_end_rules=None):
- """Are we at the end of a tuple?"""
- if self.stream.current.type in ('variable_end', 'block_end', 'rparen'):
- return True
- elif extra_end_rules is not None:
- return self.stream.current.test_any(extra_end_rules)
- return False
-
- def free_identifier(self, lineno=None):
- """Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
- self._last_identifier += 1
- rv = object.__new__(nodes.InternalName)
- nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno)
- return rv
-
- def parse_statement(self):
- """Parse a single statement."""
- token = self.stream.current
- if token.type != 'name':
- self.fail('tag name expected', token.lineno)
- self._tag_stack.append(token.value)
- pop_tag = True
- try:
- if token.value in _statement_keywords:
- return getattr(self, 'parse_' + self.stream.current.value)()
- if token.value == 'call':
- return self.parse_call_block()
- if token.value == 'filter':
- return self.parse_filter_block()
- ext = self.extensions.get(token.value)
- if ext is not None:
- return ext(self)
-
- # did not work out, remove the token we pushed by accident
- # from the stack so that the unknown tag fail function can
- # produce a proper error message.
- self._tag_stack.pop()
- pop_tag = False
- self.fail_unknown_tag(token.value, token.lineno)
- finally:
- if pop_tag:
- self._tag_stack.pop()
-
- def parse_statements(self, end_tokens, drop_needle=False):
- """Parse multiple statements into a list until one of the end tokens
- is reached. This is used to parse the body of statements as it also
- parses template data if appropriate. The parser checks first if the
- current token is a colon and skips it if there is one. Then it checks
- for the block end and parses until if one of the `end_tokens` is
- reached. Per default the active token in the stream at the end of
- the call is the matched end token. If this is not wanted `drop_needle`
- can be set to `True` and the end token is removed.
- """
- # the first token may be a colon for python compatibility
- self.stream.skip_if('colon')
-
- # in the future it would be possible to add whole code sections
- # by adding some sort of end of statement token and parsing those here.
- self.stream.expect('block_end')
- result = self.subparse(end_tokens)
-
- # we reached the end of the template too early, the subparser
- # does not check for this, so we do that now
- if self.stream.current.type == 'eof':
- self.fail_eof(end_tokens)
-
- if drop_needle:
- next(self.stream)
- return result
-
- def parse_set(self):
- """Parse an assign statement."""
- lineno = next(self.stream).lineno
- target = self.parse_assign_target()
- self.stream.expect('assign')
- expr = self.parse_tuple()
- return nodes.Assign(target, expr, lineno=lineno)
-
- def parse_for(self):
- """Parse a for loop."""
- lineno = self.stream.expect('name:for').lineno
- target = self.parse_assign_target(extra_end_rules=('name:in',))
- self.stream.expect('name:in')
- iter = self.parse_tuple(with_condexpr=False,
- extra_end_rules=('name:recursive',))
- test = None
- if self.stream.skip_if('name:if'):
- test = self.parse_expression()
- recursive = self.stream.skip_if('name:recursive')
- body = self.parse_statements(('name:endfor', 'name:else'))
- if next(self.stream).value == 'endfor':
- else_ = []
- else:
- else_ = self.parse_statements(('name:endfor',), drop_needle=True)
- return nodes.For(target, iter, body, else_, test,
- recursive, lineno=lineno)
-
- def parse_if(self):
- """Parse an if construct."""
- node = result = nodes.If(lineno=self.stream.expect('name:if').lineno)
- while 1:
- node.test = self.parse_tuple(with_condexpr=False)
- node.body = self.parse_statements(('name:elif', 'name:else',
- 'name:endif'))
- token = next(self.stream)
- if token.test('name:elif'):
- new_node = nodes.If(lineno=self.stream.current.lineno)
- node.else_ = [new_node]
- node = new_node
- continue
- elif token.test('name:else'):
- node.else_ = self.parse_statements(('name:endif',),
- drop_needle=True)
- else:
- node.else_ = []
- break
- return result
-
- def parse_block(self):
- node = nodes.Block(lineno=next(self.stream).lineno)
- node.name = self.stream.expect('name').value
- node.scoped = self.stream.skip_if('name:scoped')
-
- # common problem people encounter when switching from django
- # to jinja. we do not support hyphens in block names, so let's
- # raise a nicer error message in that case.
- if self.stream.current.type == 'sub':
- self.fail('Block names in Jinja have to be valid Python '
- 'identifiers and may not contain hypens, use an '
- 'underscore instead.')
-
- node.body = self.parse_statements(('name:endblock',), drop_needle=True)
- self.stream.skip_if('name:' + node.name)
- return node
-
- def parse_extends(self):
- node = nodes.Extends(lineno=next(self.stream).lineno)
- node.template = self.parse_expression()
- return node
-
- def parse_import_context(self, node, default):
- if self.stream.current.test_any('name:with', 'name:without') and \
- self.stream.look().test('name:context'):
- node.with_context = next(self.stream).value == 'with'
- self.stream.skip()
- else:
- node.with_context = default
- return node
-
- def parse_include(self):
- node = nodes.Include(lineno=next(self.stream).lineno)
- node.template = self.parse_expression()
- if self.stream.current.test('name:ignore') and \
- self.stream.look().test('name:missing'):
- node.ignore_missing = True
- self.stream.skip(2)
- else:
- node.ignore_missing = False
- return self.parse_import_context(node, True)
-
- def parse_import(self):
- node = nodes.Import(lineno=next(self.stream).lineno)
- node.template = self.parse_expression()
- self.stream.expect('name:as')
- node.target = self.parse_assign_target(name_only=True).name
- return self.parse_import_context(node, False)
-
- def parse_from(self):
- node = nodes.FromImport(lineno=next(self.stream).lineno)
- node.template = self.parse_expression()
- self.stream.expect('name:import')
- node.names = []
-
- def parse_context():
- if self.stream.current.value in ('with', 'without') and \
- self.stream.look().test('name:context'):
- node.with_context = next(self.stream).value == 'with'
- self.stream.skip()
- return True
- return False
-
- while 1:
- if node.names:
- self.stream.expect('comma')
- if self.stream.current.type == 'name':
- if parse_context():
- break
- target = self.parse_assign_target(name_only=True)
- if target.name.startswith('_'):
- self.fail('names starting with an underline can not '
- 'be imported', target.lineno,
- exc=TemplateAssertionError)
- if self.stream.skip_if('name:as'):
- alias = self.parse_assign_target(name_only=True)
- node.names.append((target.name, alias.name))
- else:
- node.names.append(target.name)
- if parse_context() or self.stream.current.type != 'comma':
- break
- else:
- break
- if not hasattr(node, 'with_context'):
- node.with_context = False
- self.stream.skip_if('comma')
- return node
-
- def parse_signature(self, node):
- node.args = args = []
- node.defaults = defaults = []
- self.stream.expect('lparen')
- while self.stream.current.type != 'rparen':
- if args:
- self.stream.expect('comma')
- arg = self.parse_assign_target(name_only=True)
- arg.set_ctx('param')
- if self.stream.skip_if('assign'):
- defaults.append(self.parse_expression())
- args.append(arg)
- self.stream.expect('rparen')
-
- def parse_call_block(self):
- node = nodes.CallBlock(lineno=next(self.stream).lineno)
- if self.stream.current.type == 'lparen':
- self.parse_signature(node)
- else:
- node.args = []
- node.defaults = []
-
- node.call = self.parse_expression()
- if not isinstance(node.call, nodes.Call):
- self.fail('expected call', node.lineno)
- node.body = self.parse_statements(('name:endcall',), drop_needle=True)
- return node
-
- def parse_filter_block(self):
- node = nodes.FilterBlock(lineno=next(self.stream).lineno)
- node.filter = self.parse_filter(None, start_inline=True)
- node.body = self.parse_statements(('name:endfilter',),
- drop_needle=True)
- return node
-
- def parse_macro(self):
- node = nodes.Macro(lineno=next(self.stream).lineno)
- node.name = self.parse_assign_target(name_only=True).name
- self.parse_signature(node)
- node.body = self.parse_statements(('name:endmacro',),
- drop_needle=True)
- return node
-
- def parse_print(self):
- node = nodes.Output(lineno=next(self.stream).lineno)
- node.nodes = []
- while self.stream.current.type != 'block_end':
- if node.nodes:
- self.stream.expect('comma')
- node.nodes.append(self.parse_expression())
- return node
-
- def parse_assign_target(self, with_tuple=True, name_only=False,
- extra_end_rules=None):
- """Parse an assignment target. As Jinja2 allows assignments to
- tuples, this function can parse all allowed assignment targets. Per
- default assignments to tuples are parsed, that can be disable however
- by setting `with_tuple` to `False`. If only assignments to names are
- wanted `name_only` can be set to `True`. The `extra_end_rules`
- parameter is forwarded to the tuple parsing function.
- """
- if name_only:
- token = self.stream.expect('name')
- target = nodes.Name(token.value, 'store', lineno=token.lineno)
- else:
- if with_tuple:
- target = self.parse_tuple(simplified=True,
- extra_end_rules=extra_end_rules)
- else:
- target = self.parse_primary(with_postfix=False)
- target.set_ctx('store')
- if not target.can_assign():
- self.fail('can\'t assign to %r' % target.__class__.
- __name__.lower(), target.lineno)
- return target
-
- def parse_expression(self, with_condexpr=True):
- """Parse an expression. Per default all expressions are parsed, if
- the optional `with_condexpr` parameter is set to `False` conditional
- expressions are not parsed.
- """
- if with_condexpr:
- return self.parse_condexpr()
- return self.parse_or()
-
- def parse_condexpr(self):
- lineno = self.stream.current.lineno
- expr1 = self.parse_or()
- while self.stream.skip_if('name:if'):
- expr2 = self.parse_or()
- if self.stream.skip_if('name:else'):
- expr3 = self.parse_condexpr()
- else:
- expr3 = None
- expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
- lineno = self.stream.current.lineno
- return expr1
-
- def parse_or(self):
- lineno = self.stream.current.lineno
- left = self.parse_and()
- while self.stream.skip_if('name:or'):
- right = self.parse_and()
- left = nodes.Or(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_and(self):
- lineno = self.stream.current.lineno
- left = self.parse_not()
- while self.stream.skip_if('name:and'):
- right = self.parse_not()
- left = nodes.And(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_not(self):
- if self.stream.current.test('name:not'):
- lineno = next(self.stream).lineno
- return nodes.Not(self.parse_not(), lineno=lineno)
- return self.parse_compare()
-
- def parse_compare(self):
- lineno = self.stream.current.lineno
- expr = self.parse_add()
- ops = []
- while 1:
- token_type = self.stream.current.type
- if token_type in _compare_operators:
- next(self.stream)
- ops.append(nodes.Operand(token_type, self.parse_add()))
- elif self.stream.skip_if('name:in'):
- ops.append(nodes.Operand('in', self.parse_add()))
- elif self.stream.current.test('name:not') and \
- self.stream.look().test('name:in'):
- self.stream.skip(2)
- ops.append(nodes.Operand('notin', self.parse_add()))
- else:
- break
- lineno = self.stream.current.lineno
- if not ops:
- return expr
- return nodes.Compare(expr, ops, lineno=lineno)
-
- def parse_add(self):
- lineno = self.stream.current.lineno
- left = self.parse_sub()
- while self.stream.current.type == 'add':
- next(self.stream)
- right = self.parse_sub()
- left = nodes.Add(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_sub(self):
- lineno = self.stream.current.lineno
- left = self.parse_concat()
- while self.stream.current.type == 'sub':
- next(self.stream)
- right = self.parse_concat()
- left = nodes.Sub(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_concat(self):
- lineno = self.stream.current.lineno
- args = [self.parse_mul()]
- while self.stream.current.type == 'tilde':
- next(self.stream)
- args.append(self.parse_mul())
- if len(args) == 1:
- return args[0]
- return nodes.Concat(args, lineno=lineno)
-
- def parse_mul(self):
- lineno = self.stream.current.lineno
- left = self.parse_div()
- while self.stream.current.type == 'mul':
- next(self.stream)
- right = self.parse_div()
- left = nodes.Mul(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_div(self):
- lineno = self.stream.current.lineno
- left = self.parse_floordiv()
- while self.stream.current.type == 'div':
- next(self.stream)
- right = self.parse_floordiv()
- left = nodes.Div(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_floordiv(self):
- lineno = self.stream.current.lineno
- left = self.parse_mod()
- while self.stream.current.type == 'floordiv':
- next(self.stream)
- right = self.parse_mod()
- left = nodes.FloorDiv(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_mod(self):
- lineno = self.stream.current.lineno
- left = self.parse_pow()
- while self.stream.current.type == 'mod':
- next(self.stream)
- right = self.parse_pow()
- left = nodes.Mod(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_pow(self):
- lineno = self.stream.current.lineno
- left = self.parse_unary()
- while self.stream.current.type == 'pow':
- next(self.stream)
- right = self.parse_unary()
- left = nodes.Pow(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_unary(self):
- token_type = self.stream.current.type
- lineno = self.stream.current.lineno
- if token_type == 'sub':
- next(self.stream)
- node = self.parse_unary()
- return nodes.Neg(node, lineno=lineno)
- if token_type == 'add':
- next(self.stream)
- node = self.parse_unary()
- return nodes.Pos(node, lineno=lineno)
- return self.parse_primary()
-
- def parse_primary(self, with_postfix=True):
- token = self.stream.current
- if token.type == 'name':
- if token.value in ('true', 'false', 'True', 'False'):
- node = nodes.Const(token.value in ('true', 'True'),
- lineno=token.lineno)
- elif token.value in ('none', 'None'):
- node = nodes.Const(None, lineno=token.lineno)
- else:
- node = nodes.Name(token.value, 'load', lineno=token.lineno)
- next(self.stream)
- elif token.type == 'string':
- next(self.stream)
- buf = [token.value]
- lineno = token.lineno
- while self.stream.current.type == 'string':
- buf.append(self.stream.current.value)
- next(self.stream)
- node = nodes.Const(''.join(buf), lineno=lineno)
- elif token.type in ('integer', 'float'):
- next(self.stream)
- node = nodes.Const(token.value, lineno=token.lineno)
- elif token.type == 'lparen':
- next(self.stream)
- node = self.parse_tuple(explicit_parentheses=True)
- self.stream.expect('rparen')
- elif token.type == 'lbracket':
- node = self.parse_list()
- elif token.type == 'lbrace':
- node = self.parse_dict()
- else:
- self.fail("unexpected '%s'" % describe_token(token), token.lineno)
- if with_postfix:
- node = self.parse_postfix(node)
- return node
-
- def parse_tuple(self, simplified=False, with_condexpr=True,
- extra_end_rules=None, explicit_parentheses=False):
- """Works like `parse_expression` but if multiple expressions are
- delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
- This method could also return a regular expression instead of a tuple
- if no commas where found.
-
- The default parsing mode is a full tuple. If `simplified` is `True`
- only names and literals are parsed. The `no_condexpr` parameter is
- forwarded to :meth:`parse_expression`.
-
- Because tuples do not require delimiters and may end in a bogus comma
- an extra hint is needed that marks the end of a tuple. For example
- for loops support tuples between `for` and `in`. In that case the
- `extra_end_rules` is set to ``['name:in']``.
-
- `explicit_parentheses` is true if the parsing was triggered by an
- expression in parentheses. This is used to figure out if an empty
- tuple is a valid expression or not.
- """
- lineno = self.stream.current.lineno
- if simplified:
- parse = lambda: self.parse_primary(with_postfix=False)
- elif with_condexpr:
- parse = self.parse_expression
- else:
- parse = lambda: self.parse_expression(with_condexpr=False)
- args = []
- is_tuple = False
- while 1:
- if args:
- self.stream.expect('comma')
- if self.is_tuple_end(extra_end_rules):
- break
- args.append(parse())
- if self.stream.current.type == 'comma':
- is_tuple = True
- else:
- break
- lineno = self.stream.current.lineno
-
- if not is_tuple:
- if args:
- return args[0]
-
- # if we don't have explicit parentheses, an empty tuple is
- # not a valid expression. This would mean nothing (literally
- # nothing) in the spot of an expression would be an empty
- # tuple.
- if not explicit_parentheses:
- self.fail('Expected an expression, got \'%s\'' %
- describe_token(self.stream.current))
-
- return nodes.Tuple(args, 'load', lineno=lineno)
-
- def parse_list(self):
- token = self.stream.expect('lbracket')
- items = []
- while self.stream.current.type != 'rbracket':
- if items:
- self.stream.expect('comma')
- if self.stream.current.type == 'rbracket':
- break
- items.append(self.parse_expression())
- self.stream.expect('rbracket')
- return nodes.List(items, lineno=token.lineno)
-
- def parse_dict(self):
- token = self.stream.expect('lbrace')
- items = []
- while self.stream.current.type != 'rbrace':
- if items:
- self.stream.expect('comma')
- if self.stream.current.type == 'rbrace':
- break
- key = self.parse_expression()
- self.stream.expect('colon')
- value = self.parse_expression()
- items.append(nodes.Pair(key, value, lineno=key.lineno))
- self.stream.expect('rbrace')
- return nodes.Dict(items, lineno=token.lineno)
-
- def parse_postfix(self, node):
- while 1:
- token_type = self.stream.current.type
- if token_type == 'dot' or token_type == 'lbracket':
- node = self.parse_subscript(node)
- elif token_type == 'lparen':
- node = self.parse_call(node)
- elif token_type == 'pipe':
- node = self.parse_filter(node)
- elif token_type == 'name' and self.stream.current.value == 'is':
- node = self.parse_test(node)
- else:
- break
- return node
-
- def parse_subscript(self, node):
- token = next(self.stream)
- if token.type == 'dot':
- attr_token = self.stream.current
- next(self.stream)
- if attr_token.type == 'name':
- return nodes.Getattr(node, attr_token.value, 'load',
- lineno=token.lineno)
- elif attr_token.type != 'integer':
- self.fail('expected name or number', attr_token.lineno)
- arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
- return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
- if token.type == 'lbracket':
- priority_on_attribute = False
- args = []
- while self.stream.current.type != 'rbracket':
- if args:
- self.stream.expect('comma')
- args.append(self.parse_subscribed())
- self.stream.expect('rbracket')
- if len(args) == 1:
- arg = args[0]
- else:
- arg = nodes.Tuple(args, 'load', lineno=token.lineno)
- return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
- self.fail('expected subscript expression', self.lineno)
-
- def parse_subscribed(self):
- lineno = self.stream.current.lineno
-
- if self.stream.current.type == 'colon':
- next(self.stream)
- args = [None]
- else:
- node = self.parse_expression()
- if self.stream.current.type != 'colon':
- return node
- next(self.stream)
- args = [node]
-
- if self.stream.current.type == 'colon':
- args.append(None)
- elif self.stream.current.type not in ('rbracket', 'comma'):
- args.append(self.parse_expression())
- else:
- args.append(None)
-
- if self.stream.current.type == 'colon':
- next(self.stream)
- if self.stream.current.type not in ('rbracket', 'comma'):
- args.append(self.parse_expression())
- else:
- args.append(None)
- else:
- args.append(None)
-
- return nodes.Slice(lineno=lineno, *args)
-
- def parse_call(self, node):
- token = self.stream.expect('lparen')
- args = []
- kwargs = []
- dyn_args = dyn_kwargs = None
- require_comma = False
-
- def ensure(expr):
- if not expr:
- self.fail('invalid syntax for function call expression',
- token.lineno)
-
- while self.stream.current.type != 'rparen':
- if require_comma:
- self.stream.expect('comma')
- # support for trailing comma
- if self.stream.current.type == 'rparen':
- break
- if self.stream.current.type == 'mul':
- ensure(dyn_args is None and dyn_kwargs is None)
- next(self.stream)
- dyn_args = self.parse_expression()
- elif self.stream.current.type == 'pow':
- ensure(dyn_kwargs is None)
- next(self.stream)
- dyn_kwargs = self.parse_expression()
- else:
- ensure(dyn_args is None and dyn_kwargs is None)
- if self.stream.current.type == 'name' and \
- self.stream.look().type == 'assign':
- key = self.stream.current.value
- self.stream.skip(2)
- value = self.parse_expression()
- kwargs.append(nodes.Keyword(key, value,
- lineno=value.lineno))
- else:
- ensure(not kwargs)
- args.append(self.parse_expression())
-
- require_comma = True
- self.stream.expect('rparen')
-
- if node is None:
- return args, kwargs, dyn_args, dyn_kwargs
- return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
- lineno=token.lineno)
-
- def parse_filter(self, node, start_inline=False):
- while self.stream.current.type == 'pipe' or start_inline:
- if not start_inline:
- next(self.stream)
- token = self.stream.expect('name')
- name = token.value
- while self.stream.current.type == 'dot':
- next(self.stream)
- name += '.' + self.stream.expect('name').value
- if self.stream.current.type == 'lparen':
- args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
- else:
- args = []
- kwargs = []
- dyn_args = dyn_kwargs = None
- node = nodes.Filter(node, name, args, kwargs, dyn_args,
- dyn_kwargs, lineno=token.lineno)
- start_inline = False
- return node
-
- def parse_test(self, node):
- token = next(self.stream)
- if self.stream.current.test('name:not'):
- next(self.stream)
- negated = True
- else:
- negated = False
- name = self.stream.expect('name').value
- while self.stream.current.type == 'dot':
- next(self.stream)
- name += '.' + self.stream.expect('name').value
- dyn_args = dyn_kwargs = None
- kwargs = []
- if self.stream.current.type == 'lparen':
- args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
- elif self.stream.current.type in ('name', 'string', 'integer',
- 'float', 'lparen', 'lbracket',
- 'lbrace') and not \
- self.stream.current.test_any('name:else', 'name:or',
- 'name:and'):
- if self.stream.current.test('name:is'):
- self.fail('You cannot chain multiple tests with is')
- args = [self.parse_expression()]
- else:
- args = []
- node = nodes.Test(node, name, args, kwargs, dyn_args,
- dyn_kwargs, lineno=token.lineno)
- if negated:
- node = nodes.Not(node, lineno=token.lineno)
- return node
-
- def subparse(self, end_tokens=None):
- body = []
- data_buffer = []
- add_data = data_buffer.append
-
- if end_tokens is not None:
- self._end_token_stack.append(end_tokens)
-
- def flush_data():
- if data_buffer:
- lineno = data_buffer[0].lineno
- body.append(nodes.Output(data_buffer[:], lineno=lineno))
- del data_buffer[:]
-
- try:
- while self.stream:
- token = self.stream.current
- if token.type == 'data':
- if token.value:
- add_data(nodes.TemplateData(token.value,
- lineno=token.lineno))
- next(self.stream)
- elif token.type == 'variable_begin':
- next(self.stream)
- add_data(self.parse_tuple(with_condexpr=True))
- self.stream.expect('variable_end')
- elif token.type == 'block_begin':
- flush_data()
- next(self.stream)
- if end_tokens is not None and \
- self.stream.current.test_any(*end_tokens):
- return body
- rv = self.parse_statement()
- if isinstance(rv, list):
- body.extend(rv)
- else:
- body.append(rv)
- self.stream.expect('block_end')
- else:
- raise AssertionError('internal parsing error')
-
- flush_data()
- finally:
- if end_tokens is not None:
- self._end_token_stack.pop()
-
- return body
-
- def parse(self):
- """Parse the whole template into a `Template` node."""
- result = nodes.Template(self.subparse(), lineno=1)
- result.set_environment(self.environment)
- return result
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/runtime.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/runtime.py
deleted file mode 100755
index 1961e9f12..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/runtime.py
+++ /dev/null
@@ -1,537 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.runtime
- ~~~~~~~~~~~~~~
-
- Runtime helpers.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-import sys
-from itertools import chain, imap
-from jinja2.nodes import EvalContext
-from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
- concat, MethodType, FunctionType, internalcode, next, \
- object_type_repr
-from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
- TemplateNotFound
-
-
-# these variables are exported to the template runtime
-__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
- 'TemplateRuntimeError', 'missing', 'concat', 'escape',
- 'markup_join', 'unicode_join', 'to_string',
- 'TemplateNotFound']
-
-
-#: the types we support for context functions
-_context_function_types = (FunctionType, MethodType)
-
-#: the name of the function that is used to convert something into
-#: a string. 2to3 will adopt that automatically and the generated
-#: code can take advantage of it.
-to_string = unicode
-
-
-def markup_join(seq):
- """Concatenation that escapes if necessary and converts to unicode."""
- buf = []
- iterator = imap(soft_unicode, seq)
- for arg in iterator:
- buf.append(arg)
- if hasattr(arg, '__html__'):
- return Markup(u'').join(chain(buf, iterator))
- return concat(buf)
-
-
-def unicode_join(seq):
- """Simple args to unicode conversion and concatenation."""
- return concat(imap(unicode, seq))
-
-
-def new_context(environment, template_name, blocks, vars=None,
- shared=None, globals=None, locals=None):
- """Internal helper to for context creation."""
- if vars is None:
- vars = {}
- if shared:
- parent = vars
- else:
- parent = dict(globals or (), **vars)
- if locals:
- # if the parent is shared a copy should be created because
- # we don't want to modify the dict passed
- if shared:
- parent = dict(parent)
- for key, value in locals.iteritems():
- if key[:2] == 'l_' and value is not missing:
- parent[key[2:]] = value
- return Context(environment, parent, template_name, blocks)
-
-
-class TemplateReference(object):
- """The `self` in templates."""
-
- def __init__(self, context):
- self.__context = context
-
- def __getitem__(self, name):
- blocks = self.__context.blocks[name]
- wrap = self.__context.eval_ctx.autoescape and \
- Markup or (lambda x: x)
- return BlockReference(name, self.__context, blocks, 0)
-
- def __repr__(self):
- return '<%s %r>' % (
- self.__class__.__name__,
- self.__context.name
- )
-
-
-class Context(object):
- """The template context holds the variables of a template. It stores the
- values passed to the template and also the names the template exports.
- Creating instances is neither supported nor useful as it's created
- automatically at various stages of the template evaluation and should not
- be created by hand.
-
- The context is immutable. Modifications on :attr:`parent` **must not**
- happen and modifications on :attr:`vars` are allowed from generated
- template code only. Template filters and global functions marked as
- :func:`contextfunction`\s get the active context passed as first argument
- and are allowed to access the context read-only.
-
- The template context supports read only dict operations (`get`,
- `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
- `__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`
- method that doesn't fail with a `KeyError` but returns an
- :class:`Undefined` object for missing variables.
- """
- __slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
- 'name', 'blocks', '__weakref__')
-
- def __init__(self, environment, parent, name, blocks):
- self.parent = parent
- self.vars = {}
- self.environment = environment
- self.eval_ctx = EvalContext(self.environment, name)
- self.exported_vars = set()
- self.name = name
-
- # create the initial mapping of blocks. Whenever template inheritance
- # takes place the runtime will update this mapping with the new blocks
- # from the template.
- self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
-
- def super(self, name, current):
- """Render a parent block."""
- try:
- blocks = self.blocks[name]
- index = blocks.index(current) + 1
- blocks[index]
- except LookupError:
- return self.environment.undefined('there is no parent block '
- 'called %r.' % name,
- name='super')
- return BlockReference(name, self, blocks, index)
-
- def get(self, key, default=None):
- """Returns an item from the template context, if it doesn't exist
- `default` is returned.
- """
- try:
- return self[key]
- except KeyError:
- return default
-
- def resolve(self, key):
- """Looks up a variable like `__getitem__` or `get` but returns an
- :class:`Undefined` object with the name of the name looked up.
- """
- if key in self.vars:
- return self.vars[key]
- if key in self.parent:
- return self.parent[key]
- return self.environment.undefined(name=key)
-
- def get_exported(self):
- """Get a new dict with the exported variables."""
- return dict((k, self.vars[k]) for k in self.exported_vars)
-
- def get_all(self):
- """Return a copy of the complete context as dict including the
- exported variables.
- """
- return dict(self.parent, **self.vars)
-
- @internalcode
- def call(__self, __obj, *args, **kwargs):
- """Call the callable with the arguments and keyword arguments
- provided but inject the active context or environment as first
- argument if the callable is a :func:`contextfunction` or
- :func:`environmentfunction`.
- """
- if __debug__:
- __traceback_hide__ = True
- if isinstance(__obj, _context_function_types):
- if getattr(__obj, 'contextfunction', 0):
- args = (__self,) + args
- elif getattr(__obj, 'evalcontextfunction', 0):
- args = (__self.eval_ctx,) + args
- elif getattr(__obj, 'environmentfunction', 0):
- args = (__self.environment,) + args
- return __obj(*args, **kwargs)
-
- def derived(self, locals=None):
- """Internal helper function to create a derived context."""
- context = new_context(self.environment, self.name, {},
- self.parent, True, None, locals)
- context.eval_ctx = self.eval_ctx
- context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems())
- return context
-
- def _all(meth):
- proxy = lambda self: getattr(self.get_all(), meth)()
- proxy.__doc__ = getattr(dict, meth).__doc__
- proxy.__name__ = meth
- return proxy
-
- keys = _all('keys')
- values = _all('values')
- items = _all('items')
-
- # not available on python 3
- if hasattr(dict, 'iterkeys'):
- iterkeys = _all('iterkeys')
- itervalues = _all('itervalues')
- iteritems = _all('iteritems')
- del _all
-
- def __contains__(self, name):
- return name in self.vars or name in self.parent
-
- def __getitem__(self, key):
- """Lookup a variable or raise `KeyError` if the variable is
- undefined.
- """
- item = self.resolve(key)
- if isinstance(item, Undefined):
- raise KeyError(key)
- return item
-
- def __repr__(self):
- return '<%s %s of %r>' % (
- self.__class__.__name__,
- repr(self.get_all()),
- self.name
- )
-
-
-# register the context as mapping if possible
-try:
- from collections import Mapping
- Mapping.register(Context)
-except ImportError:
- pass
-
-
-class BlockReference(object):
- """One block on a template reference."""
-
- def __init__(self, name, context, stack, depth):
- self.name = name
- self._context = context
- self._stack = stack
- self._depth = depth
-
- @property
- def super(self):
- """Super the block."""
- if self._depth + 1 >= len(self._stack):
- return self._context.environment. \
- undefined('there is no parent block called %r.' %
- self.name, name='super')
- return BlockReference(self.name, self._context, self._stack,
- self._depth + 1)
-
- @internalcode
- def __call__(self):
- rv = concat(self._stack[self._depth](self._context))
- if self._context.eval_ctx.autoescape:
- rv = Markup(rv)
- return rv
-
-
-class LoopContext(object):
- """A loop context for dynamic iteration."""
-
- def __init__(self, iterable, recurse=None):
- self._iterator = iter(iterable)
- self._recurse = recurse
- self.index0 = -1
-
- # try to get the length of the iterable early. This must be done
- # here because there are some broken iterators around where there
- # __len__ is the number of iterations left (i'm looking at your
- # listreverseiterator!).
- try:
- self._length = len(iterable)
- except (TypeError, AttributeError):
- self._length = None
-
- def cycle(self, *args):
- """Cycles among the arguments with the current loop index."""
- if not args:
- raise TypeError('no items for cycling given')
- return args[self.index0 % len(args)]
-
- first = property(lambda x: x.index0 == 0)
- last = property(lambda x: x.index0 + 1 == x.length)
- index = property(lambda x: x.index0 + 1)
- revindex = property(lambda x: x.length - x.index0)
- revindex0 = property(lambda x: x.length - x.index)
-
- def __len__(self):
- return self.length
-
- def __iter__(self):
- return LoopContextIterator(self)
-
- @internalcode
- def loop(self, iterable):
- if self._recurse is None:
- raise TypeError('Tried to call non recursive loop. Maybe you '
- "forgot the 'recursive' modifier.")
- return self._recurse(iterable, self._recurse)
-
- # a nifty trick to enhance the error message if someone tried to call
- # the the loop without or with too many arguments.
- __call__ = loop
- del loop
-
- @property
- def length(self):
- if self._length is None:
- # if was not possible to get the length of the iterator when
- # the loop context was created (ie: iterating over a generator)
- # we have to convert the iterable into a sequence and use the
- # length of that.
- iterable = tuple(self._iterator)
- self._iterator = iter(iterable)
- self._length = len(iterable) + self.index0 + 1
- return self._length
-
- def __repr__(self):
- return '<%s %r/%r>' % (
- self.__class__.__name__,
- self.index,
- self.length
- )
-
-
-class LoopContextIterator(object):
- """The iterator for a loop context."""
- __slots__ = ('context',)
-
- def __init__(self, context):
- self.context = context
-
- def __iter__(self):
- return self
-
- def next(self):
- ctx = self.context
- ctx.index0 += 1
- return next(ctx._iterator), ctx
-
-
-class Macro(object):
- """Wraps a macro."""
-
- def __init__(self, environment, func, name, arguments, defaults,
- catch_kwargs, catch_varargs, caller):
- self._environment = environment
- self._func = func
- self._argument_count = len(arguments)
- self.name = name
- self.arguments = arguments
- self.defaults = defaults
- self.catch_kwargs = catch_kwargs
- self.catch_varargs = catch_varargs
- self.caller = caller
-
- @internalcode
- def __call__(self, *args, **kwargs):
- arguments = []
- for idx, name in enumerate(self.arguments):
- try:
- value = args[idx]
- except:
- try:
- value = kwargs.pop(name)
- except:
- try:
- value = self.defaults[idx - self._argument_count]
- except:
- value = self._environment.undefined(
- 'parameter %r was not provided' % name, name=name)
- arguments.append(value)
-
- # it's important that the order of these arguments does not change
- # if not also changed in the compiler's `function_scoping` method.
- # the order is caller, keyword arguments, positional arguments!
- if self.caller:
- caller = kwargs.pop('caller', None)
- if caller is None:
- caller = self._environment.undefined('No caller defined',
- name='caller')
- arguments.append(caller)
- if self.catch_kwargs:
- arguments.append(kwargs)
- elif kwargs:
- raise TypeError('macro %r takes no keyword argument %r' %
- (self.name, next(iter(kwargs))))
- if self.catch_varargs:
- arguments.append(args[self._argument_count:])
- elif len(args) > self._argument_count:
- raise TypeError('macro %r takes not more than %d argument(s)' %
- (self.name, len(self.arguments)))
- return self._func(*arguments)
-
- def __repr__(self):
- return '<%s %s>' % (
- self.__class__.__name__,
- self.name is None and 'anonymous' or repr(self.name)
- )
-
-
-class Undefined(object):
- """The default undefined type. This undefined type can be printed and
- iterated over, but every other access will raise an :exc:`UndefinedError`:
-
- >>> foo = Undefined(name='foo')
- >>> str(foo)
- ''
- >>> not foo
- True
- >>> foo + 42
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- """
- __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
- '_undefined_exception')
-
- def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
- self._undefined_hint = hint
- self._undefined_obj = obj
- self._undefined_name = name
- self._undefined_exception = exc
-
- @internalcode
- def _fail_with_undefined_error(self, *args, **kwargs):
- """Regular callback function for undefined objects that raises an
- `UndefinedError` on call.
- """
- if self._undefined_hint is None:
- if self._undefined_obj is missing:
- hint = '%r is undefined' % self._undefined_name
- elif not isinstance(self._undefined_name, basestring):
- hint = '%s has no element %r' % (
- object_type_repr(self._undefined_obj),
- self._undefined_name
- )
- else:
- hint = '%r has no attribute %r' % (
- object_type_repr(self._undefined_obj),
- self._undefined_name
- )
- else:
- hint = self._undefined_hint
- raise self._undefined_exception(hint)
-
- __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
- __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
- __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
- __getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \
- __int__ = __float__ = __complex__ = __pow__ = __rpow__ = \
- _fail_with_undefined_error
-
- def __str__(self):
- return unicode(self).encode('utf-8')
-
- # unicode goes after __str__ because we configured 2to3 to rename
- # __unicode__ to __str__. because the 2to3 tree is not designed to
- # remove nodes from it, we leave the above __str__ around and let
- # it override at runtime.
- def __unicode__(self):
- return u''
-
- def __len__(self):
- return 0
-
- def __iter__(self):
- if 0:
- yield None
-
- def __nonzero__(self):
- return False
-
- def __repr__(self):
- return 'Undefined'
-
-
-class DebugUndefined(Undefined):
- """An undefined that returns the debug info when printed.
-
- >>> foo = DebugUndefined(name='foo')
- >>> str(foo)
- '{{ foo }}'
- >>> not foo
- True
- >>> foo + 42
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- """
- __slots__ = ()
-
- def __unicode__(self):
- if self._undefined_hint is None:
- if self._undefined_obj is missing:
- return u'{{ %s }}' % self._undefined_name
- return '{{ no such element: %s[%r] }}' % (
- object_type_repr(self._undefined_obj),
- self._undefined_name
- )
- return u'{{ undefined value printed: %s }}' % self._undefined_hint
-
-
-class StrictUndefined(Undefined):
- """An undefined that barks on print and iteration as well as boolean
- tests and all kinds of comparisons. In other words: you can do nothing
- with it except checking if it's defined using the `defined` test.
-
- >>> foo = StrictUndefined(name='foo')
- >>> str(foo)
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- >>> not foo
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- >>> foo + 42
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- """
- __slots__ = ()
- __iter__ = __unicode__ = __str__ = __len__ = __nonzero__ = __eq__ = \
- __ne__ = Undefined._fail_with_undefined_error
-
-
-# remove remaining slots attributes, after the metaclass did the magic they
-# are unneeded and irritating as they contain wrong data for the subclasses.
-del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/sandbox.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/sandbox.py
deleted file mode 100755
index 749719548..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/sandbox.py
+++ /dev/null
@@ -1,271 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.sandbox
- ~~~~~~~~~~~~~~
-
- Adds a sandbox layer to Jinja as it was the default behavior in the old
- Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the
- default behavior is easier to use.
-
- The behavior can be changed by subclassing the environment.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-import operator
-from jinja2.runtime import Undefined
-from jinja2.environment import Environment
-from jinja2.exceptions import SecurityError
-from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \
- FrameType, GeneratorType
-
-
-#: maximum number of items a range may produce
-MAX_RANGE = 100000
-
-#: attributes of function objects that are considered unsafe.
-UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
- 'func_defaults', 'func_globals'])
-
-#: unsafe method attributes. function attributes are unsafe for methods too
-UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
-
-
-import warnings
-
-# make sure we don't warn in python 2.6 about stuff we don't care about
-warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning,
- module='jinja2.sandbox')
-
-from collections import deque
-
-_mutable_set_types = (set,)
-_mutable_mapping_types = (dict,)
-_mutable_sequence_types = (list,)
-
-
-# on python 2.x we can register the user collection types
-try:
- from UserDict import UserDict, DictMixin
- from UserList import UserList
- _mutable_mapping_types += (UserDict, DictMixin)
- _mutable_set_types += (UserList,)
-except ImportError:
- pass
-
-# if sets is still available, register the mutable set from there as well
-try:
- from sets import Set
- _mutable_set_types += (Set,)
-except ImportError:
- pass
-
-#: register Python 2.6 abstract base classes
-try:
- from collections import MutableSet, MutableMapping, MutableSequence
- _mutable_set_types += (MutableSet,)
- _mutable_mapping_types += (MutableMapping,)
- _mutable_sequence_types += (MutableSequence,)
-except ImportError:
- pass
-
-_mutable_spec = (
- (_mutable_set_types, frozenset([
- 'add', 'clear', 'difference_update', 'discard', 'pop', 'remove',
- 'symmetric_difference_update', 'update'
- ])),
- (_mutable_mapping_types, frozenset([
- 'clear', 'pop', 'popitem', 'setdefault', 'update'
- ])),
- (_mutable_sequence_types, frozenset([
- 'append', 'reverse', 'insert', 'sort', 'extend', 'remove'
- ])),
- (deque, frozenset([
- 'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop',
- 'popleft', 'remove', 'rotate'
- ]))
-)
-
-
-def safe_range(*args):
- """A range that can't generate ranges with a length of more than
- MAX_RANGE items.
- """
- rng = xrange(*args)
- if len(rng) > MAX_RANGE:
- raise OverflowError('range too big, maximum size for range is %d' %
- MAX_RANGE)
- return rng
-
-
-def unsafe(f):
- """
- Mark a function or method as unsafe::
-
- @unsafe
- def delete(self):
- pass
- """
- f.unsafe_callable = True
- return f
-
-
-def is_internal_attribute(obj, attr):
- """Test if the attribute given is an internal python attribute. For
- example this function returns `True` for the `func_code` attribute of
- python objects. This is useful if the environment method
- :meth:`~SandboxedEnvironment.is_safe_attribute` is overriden.
-
- >>> from jinja2.sandbox import is_internal_attribute
- >>> is_internal_attribute(lambda: None, "func_code")
- True
- >>> is_internal_attribute((lambda x:x).func_code, 'co_code')
- True
- >>> is_internal_attribute(str, "upper")
- False
- """
- if isinstance(obj, FunctionType):
- if attr in UNSAFE_FUNCTION_ATTRIBUTES:
- return True
- elif isinstance(obj, MethodType):
- if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
- attr in UNSAFE_METHOD_ATTRIBUTES:
- return True
- elif isinstance(obj, type):
- if attr == 'mro':
- return True
- elif isinstance(obj, (CodeType, TracebackType, FrameType)):
- return True
- elif isinstance(obj, GeneratorType):
- if attr == 'gi_frame':
- return True
- return attr.startswith('__')
-
-
-def modifies_known_mutable(obj, attr):
- """This function checks if an attribute on a builtin mutable object
- (list, dict, set or deque) would modify it if called. It also supports
- the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and
- with Python 2.6 onwards the abstract base classes `MutableSet`,
- `MutableMapping`, and `MutableSequence`.
-
- >>> modifies_known_mutable({}, "clear")
- True
- >>> modifies_known_mutable({}, "keys")
- False
- >>> modifies_known_mutable([], "append")
- True
- >>> modifies_known_mutable([], "index")
- False
-
- If called with an unsupported object (such as unicode) `False` is
- returned.
-
- >>> modifies_known_mutable("foo", "upper")
- False
- """
- for typespec, unsafe in _mutable_spec:
- if isinstance(obj, typespec):
- return attr in unsafe
- return False
-
-
-class SandboxedEnvironment(Environment):
- """The sandboxed environment. It works like the regular environment but
- tells the compiler to generate sandboxed code. Additionally subclasses of
- this environment may override the methods that tell the runtime what
- attributes or functions are safe to access.
-
- If the template tries to access insecure code a :exc:`SecurityError` is
- raised. However also other exceptions may occour during the rendering so
- the caller has to ensure that all exceptions are catched.
- """
- sandboxed = True
-
- def __init__(self, *args, **kwargs):
- Environment.__init__(self, *args, **kwargs)
- self.globals['range'] = safe_range
-
- def is_safe_attribute(self, obj, attr, value):
- """The sandboxed environment will call this method to check if the
- attribute of an object is safe to access. Per default all attributes
- starting with an underscore are considered private as well as the
- special attributes of internal python objects as returned by the
- :func:`is_internal_attribute` function.
- """
- return not (attr.startswith('_') or is_internal_attribute(obj, attr))
-
- def is_safe_callable(self, obj):
- """Check if an object is safely callable. Per default a function is
- considered safe unless the `unsafe_callable` attribute exists and is
- True. Override this method to alter the behavior, but this won't
- affect the `unsafe` decorator from this module.
- """
- return not (getattr(obj, 'unsafe_callable', False) or \
- getattr(obj, 'alters_data', False))
-
- def getitem(self, obj, argument):
- """Subscribe an object from sandboxed code."""
- try:
- return obj[argument]
- except (TypeError, LookupError):
- if isinstance(argument, basestring):
- try:
- attr = str(argument)
- except:
- pass
- else:
- try:
- value = getattr(obj, attr)
- except AttributeError:
- pass
- else:
- if self.is_safe_attribute(obj, argument, value):
- return value
- return self.unsafe_undefined(obj, argument)
- return self.undefined(obj=obj, name=argument)
-
- def getattr(self, obj, attribute):
- """Subscribe an object from sandboxed code and prefer the
- attribute. The attribute passed *must* be a bytestring.
- """
- try:
- value = getattr(obj, attribute)
- except AttributeError:
- try:
- return obj[attribute]
- except (TypeError, LookupError):
- pass
- else:
- if self.is_safe_attribute(obj, attribute, value):
- return value
- return self.unsafe_undefined(obj, attribute)
- return self.undefined(obj=obj, name=attribute)
-
- def unsafe_undefined(self, obj, attribute):
- """Return an undefined object for unsafe attributes."""
- return self.undefined('access to attribute %r of %r '
- 'object is unsafe.' % (
- attribute,
- obj.__class__.__name__
- ), name=attribute, obj=obj, exc=SecurityError)
-
- def call(__self, __context, __obj, *args, **kwargs):
- """Call an object from sandboxed code."""
- # the double prefixes are to avoid double keyword argument
- # errors when proxying the call.
- if not __self.is_safe_callable(__obj):
- raise SecurityError('%r is not safely callable' % (__obj,))
- return __context.call(__obj, *args, **kwargs)
-
-
-class ImmutableSandboxedEnvironment(SandboxedEnvironment):
- """Works exactly like the regular `SandboxedEnvironment` but does not
- permit modifications on the builtin mutable objects `list`, `set`, and
- `dict` by using the :func:`modifies_known_mutable` function.
- """
-
- def is_safe_attribute(self, obj, attr, value):
- if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
- return False
- return not modifies_known_mutable(obj, attr)
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/tests.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/tests.py
deleted file mode 100755
index d257eca0a..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/tests.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.tests
- ~~~~~~~~~~~~
-
- Jinja test functions. Used with the "is" operator.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-from jinja2.runtime import Undefined
-
-# nose, nothing here to test
-__test__ = False
-
-
-number_re = re.compile(r'^-?\d+(\.\d+)?$')
-regex_type = type(number_re)
-
-
-try:
- test_callable = callable
-except NameError:
- def test_callable(x):
- return hasattr(x, '__call__')
-
-
-def test_odd(value):
- """Return true if the variable is odd."""
- return value % 2 == 1
-
-
-def test_even(value):
- """Return true if the variable is even."""
- return value % 2 == 0
-
-
-def test_divisibleby(value, num):
- """Check if a variable is divisible by a number."""
- return value % num == 0
-
-
-def test_defined(value):
- """Return true if the variable is defined:
-
- .. sourcecode:: jinja
-
- {% if variable is defined %}
- value of variable: {{ variable }}
- {% else %}
- variable is not defined
- {% endif %}
-
- See the :func:`default` filter for a simple way to set undefined
- variables.
- """
- return not isinstance(value, Undefined)
-
-
-def test_undefined(value):
- """Like :func:`defined` but the other way round."""
- return isinstance(value, Undefined)
-
-
-def test_none(value):
- """Return true if the variable is none."""
- return value is None
-
-
-def test_lower(value):
- """Return true if the variable is lowercased."""
- return unicode(value).islower()
-
-
-def test_upper(value):
- """Return true if the variable is uppercased."""
- return unicode(value).isupper()
-
-
-def test_string(value):
- """Return true if the object is a string."""
- return isinstance(value, basestring)
-
-
-def test_number(value):
- """Return true if the variable is a number."""
- return isinstance(value, (int, long, float, complex))
-
-
-def test_sequence(value):
- """Return true if the variable is a sequence. Sequences are variables
- that are iterable.
- """
- try:
- len(value)
- value.__getitem__
- except:
- return False
- return True
-
-
-def test_sameas(value, other):
- """Check if an object points to the same memory address than another
- object:
-
- .. sourcecode:: jinja
-
- {% if foo.attribute is sameas false %}
- the foo attribute really is the `False` singleton
- {% endif %}
- """
- return value is other
-
-
-def test_iterable(value):
- """Check if it's possible to iterate over an object."""
- try:
- iter(value)
- except TypeError:
- return False
- return True
-
-
-def test_escaped(value):
- """Check if the value is escaped."""
- return hasattr(value, '__html__')
-
-
-TESTS = {
- 'odd': test_odd,
- 'even': test_even,
- 'divisibleby': test_divisibleby,
- 'defined': test_defined,
- 'undefined': test_undefined,
- 'none': test_none,
- 'lower': test_lower,
- 'upper': test_upper,
- 'string': test_string,
- 'number': test_number,
- 'sequence': test_sequence,
- 'iterable': test_iterable,
- 'callable': test_callable,
- 'sameas': test_sameas,
- 'escaped': test_escaped
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/utils.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/utils.py
deleted file mode 100755
index d3a2813ec..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/utils.py
+++ /dev/null
@@ -1,818 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.utils
- ~~~~~~~~~~~~
-
- Utility functions.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-import sys
-import errno
-try:
- from thread import allocate_lock
-except ImportError:
- from dummy_thread import allocate_lock
-from collections import deque
-from itertools import imap
-
-
-_word_split_re = re.compile(r'(\s+)')
-_punctuation_re = re.compile(
- '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
- '|'.join(imap(re.escape, ('(', '<', '<'))),
- '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '>')))
- )
-)
-_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
-_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
-_entity_re = re.compile(r'&([^;]+);')
-_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
-_digits = '0123456789'
-
-# special singleton representing missing values for the runtime
-missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
-
-# internal code
-internal_code = set()
-
-
-# concatenate a list of strings and convert them to unicode.
-# unfortunately there is a bug in python 2.4 and lower that causes
-# unicode.join trash the traceback.
-_concat = u''.join
-try:
- def _test_gen_bug():
- raise TypeError(_test_gen_bug)
- yield None
- _concat(_test_gen_bug())
-except TypeError as _error:
- if not _error.args or _error.args[0] is not _test_gen_bug:
- def concat(gen):
- try:
- return _concat(list(gen))
- except:
- # this hack is needed so that the current frame
- # does not show up in the traceback.
- exc_type, exc_value, tb = sys.exc_info()
- raise exc_type, exc_value, tb.tb_next
- else:
- concat = _concat
- del _test_gen_bug, _error
-
-
-# for python 2.x we create outselves a next() function that does the
-# basics without exception catching.
-try:
- next = next
-except NameError:
- def next(x):
- return x.next()
-
-
-# if this python version is unable to deal with unicode filenames
-# when passed to encode we let this function encode it properly.
-# This is used in a couple of places. As far as Jinja is concerned
-# filenames are unicode *or* bytestrings in 2.x and unicode only in
-# 3.x because compile cannot handle bytes
-if sys.version_info < (3, 0):
- def _encode_filename(filename):
- if isinstance(filename, unicode):
- return filename.encode('utf-8')
- return filename
-else:
- def _encode_filename(filename):
- assert filename is None or isinstance(filename, str), \
- 'filenames must be strings'
- return filename
-
-from keyword import iskeyword as is_python_keyword
-
-
-# common types. These do exist in the special types module too which however
-# does not exist in IronPython out of the box. Also that way we don't have
-# to deal with implementation specific stuff here
-class _C(object):
- def method(self): pass
-def _func():
- yield None
-FunctionType = type(_func)
-GeneratorType = type(_func())
-MethodType = type(_C.method)
-CodeType = type(_C.method.func_code)
-try:
- raise TypeError()
-except TypeError:
- _tb = sys.exc_info()[2]
- TracebackType = type(_tb)
- FrameType = type(_tb.tb_frame)
-del _C, _tb, _func
-
-
-def contextfunction(f):
- """This decorator can be used to mark a function or method context callable.
- A context callable is passed the active :class:`Context` as first argument when
- called from the template. This is useful if a function wants to get access
- to the context or functions provided on the context object. For example
- a function that returns a sorted list of template variables the current
- template exports could look like this::
-
- @contextfunction
- def get_exported_names(context):
- return sorted(context.exported_vars)
- """
- f.contextfunction = True
- return f
-
-
-def evalcontextfunction(f):
- """This decoraotr can be used to mark a function or method as an eval
- context callable. This is similar to the :func:`contextfunction`
- but instead of passing the context, an evaluation context object is
- passed. For more information about the eval context, see
- :ref:`eval-context`.
-
- .. versionadded:: 2.4
- """
- f.evalcontextfunction = True
- return f
-
-
-def environmentfunction(f):
- """This decorator can be used to mark a function or method as environment
- callable. This decorator works exactly like the :func:`contextfunction`
- decorator just that the first argument is the active :class:`Environment`
- and not context.
- """
- f.environmentfunction = True
- return f
-
-
-def internalcode(f):
- """Marks the function as internally used"""
- internal_code.add(f.func_code)
- return f
-
-
-def is_undefined(obj):
- """Check if the object passed is undefined. This does nothing more than
- performing an instance check against :class:`Undefined` but looks nicer.
- This can be used for custom filters or tests that want to react to
- undefined variables. For example a custom default filter can look like
- this::
-
- def default(var, default=''):
- if is_undefined(var):
- return default
- return var
- """
- from jinja2.runtime import Undefined
- return isinstance(obj, Undefined)
-
-
-def consume(iterable):
- """Consumes an iterable without doing anything with it."""
- for event in iterable:
- pass
-
-
-def clear_caches():
- """Jinja2 keeps internal caches for environments and lexers. These are
- used so that Jinja2 doesn't have to recreate environments and lexers all
- the time. Normally you don't have to care about that but if you are
- messuring memory consumption you may want to clean the caches.
- """
- from jinja2.environment import _spontaneous_environments
- from jinja2.lexer import _lexer_cache
- _spontaneous_environments.clear()
- _lexer_cache.clear()
-
-
-def import_string(import_name, silent=False):
- """Imports an object based on a string. This use useful if you want to
- use import paths as endpoints or something similar. An import path can
- be specified either in dotted notation (``xml.sax.saxutils.escape``)
- or with a colon as object delimiter (``xml.sax.saxutils:escape``).
-
- If the `silent` is True the return value will be `None` if the import
- fails.
-
- :return: imported object
- """
- try:
- if ':' in import_name:
- module, obj = import_name.split(':', 1)
- elif '.' in import_name:
- items = import_name.split('.')
- module = '.'.join(items[:-1])
- obj = items[-1]
- else:
- return __import__(import_name)
- return getattr(__import__(module, None, None, [obj]), obj)
- except (ImportError, AttributeError):
- if not silent:
- raise
-
-
-def open_if_exists(filename, mode='rb'):
- """Returns a file descriptor for the filename if that file exists,
- otherwise `None`.
- """
- try:
- return open(filename, mode)
- except IOError, e:
- if e.errno not in (errno.ENOENT, errno.EISDIR):
- raise
-
-
-def object_type_repr(obj):
- """Returns the name of the object's type. For some recognized
- singletons the name of the object is returned instead. (For
- example for `None` and `Ellipsis`).
- """
- if obj is None:
- return 'None'
- elif obj is Ellipsis:
- return 'Ellipsis'
- if obj.__class__.__module__ == '__builtin__':
- name = obj.__class__.__name__
- else:
- name = obj.__class__.__module__ + '.' + obj.__class__.__name__
- return '%s object' % name
-
-
-def pformat(obj, verbose=False):
- """Prettyprint an object. Either use the `pretty` library or the
- builtin `pprint`.
- """
- try:
- from pretty import pretty
- return pretty(obj, verbose=verbose)
- except ImportError:
- from pprint import pformat
- return pformat(obj)
-
-
-def urlize(text, trim_url_limit=None, nofollow=False):
- """Converts any URLs in text into clickable links. Works on http://,
- https:// and www. links. Links can have trailing punctuation (periods,
- commas, close-parens) and leading punctuation (opening parens) and
- it'll still do the right thing.
-
- If trim_url_limit is not None, the URLs in link text will be limited
- to trim_url_limit characters.
-
- If nofollow is True, the URLs in link text will get a rel="nofollow"
- attribute.
- """
- trim_url = lambda x, limit=trim_url_limit: limit is not None \
- and (x[:limit] + (len(x) >=limit and '...'
- or '')) or x
- words = _word_split_re.split(unicode(escape(text)))
- nofollow_attr = nofollow and ' rel="nofollow"' or ''
- for i, word in enumerate(words):
- match = _punctuation_re.match(word)
- if match:
- lead, middle, trail = match.groups()
- if middle.startswith('www.') or (
- '@' not in middle and
- not middle.startswith('http://') and
- len(middle) > 0 and
- middle[0] in _letters + _digits and (
- middle.endswith('.org') or
- middle.endswith('.net') or
- middle.endswith('.com')
- )):
- middle = '<a href="http://%s"%s>%s</a>' % (middle,
- nofollow_attr, trim_url(middle))
- if middle.startswith('http://') or \
- middle.startswith('https://'):
- middle = '<a href="%s"%s>%s</a>' % (middle,
- nofollow_attr, trim_url(middle))
- if '@' in middle and not middle.startswith('www.') and \
- not ':' in middle and _simple_email_re.match(middle):
- middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
- if lead + middle + trail != word:
- words[i] = lead + middle + trail
- return u''.join(words)
-
-
-def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
- """Generate some lorem impsum for the template."""
- from jinja2.constants import LOREM_IPSUM_WORDS
- from random import choice, randrange
- words = LOREM_IPSUM_WORDS.split()
- result = []
-
- for _ in xrange(n):
- next_capitalized = True
- last_comma = last_fullstop = 0
- word = None
- last = None
- p = []
-
- # each paragraph contains out of 20 to 100 words.
- for idx, _ in enumerate(xrange(randrange(min, max))):
- while True:
- word = choice(words)
- if word != last:
- last = word
- break
- if next_capitalized:
- word = word.capitalize()
- next_capitalized = False
- # add commas
- if idx - randrange(3, 8) > last_comma:
- last_comma = idx
- last_fullstop += 2
- word += ','
- # add end of sentences
- if idx - randrange(10, 20) > last_fullstop:
- last_comma = last_fullstop = idx
- word += '.'
- next_capitalized = True
- p.append(word)
-
- # ensure that the paragraph ends with a dot.
- p = u' '.join(p)
- if p.endswith(','):
- p = p[:-1] + '.'
- elif not p.endswith('.'):
- p += '.'
- result.append(p)
-
- if not html:
- return u'\n\n'.join(result)
- return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
-
-
-class Markup(unicode):
- r"""Marks a string as being safe for inclusion in HTML/XML output without
- needing to be escaped. This implements the `__html__` interface a couple
- of frameworks and web applications use. :class:`Markup` is a direct
- subclass of `unicode` and provides all the methods of `unicode` just that
- it escapes arguments passed and always returns `Markup`.
-
- The `escape` function returns markup objects so that double escaping can't
- happen. If you want to use autoescaping in Jinja just enable the
- autoescaping feature in the environment.
-
- The constructor of the :class:`Markup` class can be used for three
- different things: When passed an unicode object it's assumed to be safe,
- when passed an object with an HTML representation (has an `__html__`
- method) that representation is used, otherwise the object passed is
- converted into a unicode string and then assumed to be safe:
-
- >>> Markup("Hello <em>World</em>!")
- Markup(u'Hello <em>World</em>!')
- >>> class Foo(object):
- ... def __html__(self):
- ... return '<a href="#">foo</a>'
- ...
- >>> Markup(Foo())
- Markup(u'<a href="#">foo</a>')
-
- If you want object passed being always treated as unsafe you can use the
- :meth:`escape` classmethod to create a :class:`Markup` object:
-
- >>> Markup.escape("Hello <em>World</em>!")
- Markup(u'Hello <em>World</em>!')
-
- Operations on a markup string are markup aware which means that all
- arguments are passed through the :func:`escape` function:
-
- >>> em = Markup("<em>%s</em>")
- >>> em % "foo & bar"
- Markup(u'<em>foo & bar</em>')
- >>> strong = Markup("<strong>%(text)s</strong>")
- >>> strong % {'text': '<blink>hacker here</blink>'}
- Markup(u'<strong><blink>hacker here</blink></strong>')
- >>> Markup("<em>Hello</em> ") + "<foo>"
- Markup(u'<em>Hello</em> <foo>')
- """
- __slots__ = ()
-
- def __new__(cls, base=u'', encoding=None, errors='strict'):
- if hasattr(base, '__html__'):
- base = base.__html__()
- if encoding is None:
- return unicode.__new__(cls, base)
- return unicode.__new__(cls, base, encoding, errors)
-
- def __html__(self):
- return self
-
- def __add__(self, other):
- if hasattr(other, '__html__') or isinstance(other, basestring):
- return self.__class__(unicode(self) + unicode(escape(other)))
- return NotImplemented
-
- def __radd__(self, other):
- if hasattr(other, '__html__') or isinstance(other, basestring):
- return self.__class__(unicode(escape(other)) + unicode(self))
- return NotImplemented
-
- def __mul__(self, num):
- if isinstance(num, (int, long)):
- return self.__class__(unicode.__mul__(self, num))
- return NotImplemented
- __rmul__ = __mul__
-
- def __mod__(self, arg):
- if isinstance(arg, tuple):
- arg = tuple(imap(_MarkupEscapeHelper, arg))
- else:
- arg = _MarkupEscapeHelper(arg)
- return self.__class__(unicode.__mod__(self, arg))
-
- def __repr__(self):
- return '%s(%s)' % (
- self.__class__.__name__,
- unicode.__repr__(self)
- )
-
- def join(self, seq):
- return self.__class__(unicode.join(self, imap(escape, seq)))
- join.__doc__ = unicode.join.__doc__
-
- def split(self, *args, **kwargs):
- return map(self.__class__, unicode.split(self, *args, **kwargs))
- split.__doc__ = unicode.split.__doc__
-
- def rsplit(self, *args, **kwargs):
- return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
- rsplit.__doc__ = unicode.rsplit.__doc__
-
- def splitlines(self, *args, **kwargs):
- return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
- splitlines.__doc__ = unicode.splitlines.__doc__
-
- def unescape(self):
- r"""Unescape markup again into an unicode string. This also resolves
- known HTML4 and XHTML entities:
-
- >>> Markup("Main » <em>About</em>").unescape()
- u'Main \xbb <em>About</em>'
- """
- from jinja2.constants import HTML_ENTITIES
- def handle_match(m):
- name = m.group(1)
- if name in HTML_ENTITIES:
- return unichr(HTML_ENTITIES[name])
- try:
- if name[:2] in ('#x', '#X'):
- return unichr(int(name[2:], 16))
- elif name.startswith('#'):
- return unichr(int(name[1:]))
- except ValueError:
- pass
- return u''
- return _entity_re.sub(handle_match, unicode(self))
-
- def striptags(self):
- r"""Unescape markup into an unicode string and strip all tags. This
- also resolves known HTML4 and XHTML entities. Whitespace is
- normalized to one:
-
- >>> Markup("Main » <em>About</em>").striptags()
- u'Main \xbb About'
- """
- stripped = u' '.join(_striptags_re.sub('', self).split())
- return Markup(stripped).unescape()
-
- @classmethod
- def escape(cls, s):
- """Escape the string. Works like :func:`escape` with the difference
- that for subclasses of :class:`Markup` this function would return the
- correct subclass.
- """
- rv = escape(s)
- if rv.__class__ is not cls:
- return cls(rv)
- return rv
-
- def make_wrapper(name):
- orig = getattr(unicode, name)
- def func(self, *args, **kwargs):
- args = _escape_argspec(list(args), enumerate(args))
- _escape_argspec(kwargs, kwargs.iteritems())
- return self.__class__(orig(self, *args, **kwargs))
- func.__name__ = orig.__name__
- func.__doc__ = orig.__doc__
- return func
-
- for method in '__getitem__', 'capitalize', \
- 'title', 'lower', 'upper', 'replace', 'ljust', \
- 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
- 'translate', 'expandtabs', 'swapcase', 'zfill':
- locals()[method] = make_wrapper(method)
-
- # new in python 2.5
- if hasattr(unicode, 'partition'):
- partition = make_wrapper('partition'),
- rpartition = make_wrapper('rpartition')
-
- # new in python 2.6
- if hasattr(unicode, 'format'):
- format = make_wrapper('format')
-
- # not in python 3
- if hasattr(unicode, '__getslice__'):
- __getslice__ = make_wrapper('__getslice__')
-
- del method, make_wrapper
-
-
-def _escape_argspec(obj, iterable):
- """Helper for various string-wrapped functions."""
- for key, value in iterable:
- if hasattr(value, '__html__') or isinstance(value, basestring):
- obj[key] = escape(value)
- return obj
-
-
-class _MarkupEscapeHelper(object):
- """Helper for Markup.__mod__"""
-
- def __init__(self, obj):
- self.obj = obj
-
- __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
- __str__ = lambda s: str(escape(s.obj))
- __unicode__ = lambda s: unicode(escape(s.obj))
- __repr__ = lambda s: str(escape(repr(s.obj)))
- __int__ = lambda s: int(s.obj)
- __float__ = lambda s: float(s.obj)
-
-
-class LRUCache(object):
- """A simple LRU Cache implementation."""
-
- # this is fast for small capacities (something below 1000) but doesn't
- # scale. But as long as it's only used as storage for templates this
- # won't do any harm.
-
- def __init__(self, capacity):
- self.capacity = capacity
- self._mapping = {}
- self._queue = deque()
- self._postinit()
-
- def _postinit(self):
- # alias all queue methods for faster lookup
- self._popleft = self._queue.popleft
- self._pop = self._queue.pop
- if hasattr(self._queue, 'remove'):
- self._remove = self._queue.remove
- self._wlock = allocate_lock()
- self._append = self._queue.append
-
- def _remove(self, obj):
- """Python 2.4 compatibility."""
- for idx, item in enumerate(self._queue):
- if item == obj:
- del self._queue[idx]
- break
-
- def __getstate__(self):
- return {
- 'capacity': self.capacity,
- '_mapping': self._mapping,
- '_queue': self._queue
- }
-
- def __setstate__(self, d):
- self.__dict__.update(d)
- self._postinit()
-
- def __getnewargs__(self):
- return (self.capacity,)
-
- def copy(self):
- """Return an shallow copy of the instance."""
- rv = self.__class__(self.capacity)
- rv._mapping.update(self._mapping)
- rv._queue = deque(self._queue)
- return rv
-
- def get(self, key, default=None):
- """Return an item from the cache dict or `default`"""
- try:
- return self[key]
- except KeyError:
- return default
-
- def setdefault(self, key, default=None):
- """Set `default` if the key is not in the cache otherwise
- leave unchanged. Return the value of this key.
- """
- try:
- return self[key]
- except KeyError:
- self[key] = default
- return default
-
- def clear(self):
- """Clear the cache."""
- self._wlock.acquire()
- try:
- self._mapping.clear()
- self._queue.clear()
- finally:
- self._wlock.release()
-
- def __contains__(self, key):
- """Check if a key exists in this cache."""
- return key in self._mapping
-
- def __len__(self):
- """Return the current size of the cache."""
- return len(self._mapping)
-
- def __repr__(self):
- return '<%s %r>' % (
- self.__class__.__name__,
- self._mapping
- )
-
- def __getitem__(self, key):
- """Get an item from the cache. Moves the item up so that it has the
- highest priority then.
-
- Raise an `KeyError` if it does not exist.
- """
- rv = self._mapping[key]
- if self._queue[-1] != key:
- try:
- self._remove(key)
- except ValueError:
- # if something removed the key from the container
- # when we read, ignore the ValueError that we would
- # get otherwise.
- pass
- self._append(key)
- return rv
-
- def __setitem__(self, key, value):
- """Sets the value for an item. Moves the item up so that it
- has the highest priority then.
- """
- self._wlock.acquire()
- try:
- if key in self._mapping:
- try:
- self._remove(key)
- except ValueError:
- # __getitem__ is not locked, it might happen
- pass
- elif len(self._mapping) == self.capacity:
- del self._mapping[self._popleft()]
- self._append(key)
- self._mapping[key] = value
- finally:
- self._wlock.release()
-
- def __delitem__(self, key):
- """Remove an item from the cache dict.
- Raise an `KeyError` if it does not exist.
- """
- self._wlock.acquire()
- try:
- del self._mapping[key]
- try:
- self._remove(key)
- except ValueError:
- # __getitem__ is not locked, it might happen
- pass
- finally:
- self._wlock.release()
-
- def items(self):
- """Return a list of items."""
- result = [(key, self._mapping[key]) for key in list(self._queue)]
- result.reverse()
- return result
-
- def iteritems(self):
- """Iterate over all items."""
- return iter(self.items())
-
- def values(self):
- """Return a list of all values."""
- return [x[1] for x in self.items()]
-
- def itervalue(self):
- """Iterate over all values."""
- return iter(self.values())
-
- def keys(self):
- """Return a list of all keys ordered by most recent usage."""
- return list(self)
-
- def iterkeys(self):
- """Iterate over all keys in the cache dict, ordered by
- the most recent usage.
- """
- return reversed(tuple(self._queue))
-
- __iter__ = iterkeys
-
- def __reversed__(self):
- """Iterate over the values in the cache dict, oldest items
- coming first.
- """
- return iter(tuple(self._queue))
-
- __copy__ = copy
-
-
-# register the LRU cache as mutable mapping if possible
-try:
- from collections import MutableMapping
- MutableMapping.register(LRUCache)
-except ImportError:
- pass
-
-
-class Cycler(object):
- """A cycle helper for templates."""
-
- def __init__(self, *items):
- if not items:
- raise RuntimeError('at least one item has to be provided')
- self.items = items
- self.reset()
-
- def reset(self):
- """Resets the cycle."""
- self.pos = 0
-
- @property
- def current(self):
- """Returns the current item."""
- return self.items[self.pos]
-
- def next(self):
- """Goes one item ahead and returns it."""
- rv = self.current
- self.pos = (self.pos + 1) % len(self.items)
- return rv
-
-
-class Joiner(object):
- """A joining helper for templates."""
-
- def __init__(self, sep=u', '):
- self.sep = sep
- self.used = False
-
- def __call__(self):
- if not self.used:
- self.used = True
- return u''
- return self.sep
-
-
-# we have to import it down here as the speedups module imports the
-# markup type which is define above.
-try:
- from jinja2._speedups import escape, soft_unicode
-except ImportError:
- def escape(s):
- """Convert the characters &, <, >, ' and " in string s to HTML-safe
- sequences. Use this if you need to display text that might contain
- such characters in HTML. Marks return value as markup string.
- """
- if hasattr(s, '__html__'):
- return s.__html__()
- return Markup(unicode(s)
- .replace('&', '&')
- .replace('>', '>')
- .replace('<', '<')
- .replace("'", ''')
- .replace('"', '"')
- )
-
- def soft_unicode(s):
- """Make a string unicode if it isn't already. That way a markup
- string is not converted back to unicode.
- """
- if not isinstance(s, unicode):
- s = unicode(s)
- return s
-
-
-# partials
-try:
- from functools import partial
-except ImportError:
- class partial(object):
- def __init__(self, _func, *args, **kwargs):
- self._func = _func
- self._args = args
- self._kwargs = kwargs
- def __call__(self, *args, **kwargs):
- kwargs.update(self._kwargs)
- return self._func(*(self._args + args), **kwargs)
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/visitor.py b/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/visitor.py
deleted file mode 100755
index 413e7c309..000000000
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/jinja2.egg/jinja2/visitor.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.visitor
- ~~~~~~~~~~~~~~
-
- This module implements a visitor for the nodes.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-from jinja2.nodes import Node
-
-
-class NodeVisitor(object):
- """Walks the abstract syntax tree and call visitor functions for every
- node found. The visitor functions may return values which will be
- forwarded by the `visit` method.
-
- Per default the visitor functions for the nodes are ``'visit_'`` +
- class name of the node. So a `TryFinally` node visit function would
- be `visit_TryFinally`. This behavior can be changed by overriding
- the `get_visitor` function. If no visitor function exists for a node
- (return value `None`) the `generic_visit` visitor is used instead.
- """
-
- def get_visitor(self, node):
- """Return the visitor function for this node or `None` if no visitor
- exists for this node. In that case the generic visit function is
- used instead.
- """
- method = 'visit_' + node.__class__.__name__
- return getattr(self, method, None)
-
- def visit(self, node, *args, **kwargs):
- """Visit a node."""
- f = self.get_visitor(node)
- if f is not None:
- return f(node, *args, **kwargs)
- return self.generic_visit(node, *args, **kwargs)
-
- def generic_visit(self, node, *args, **kwargs):
- """Called if no explicit visitor function exists for a node."""
- for node in node.iter_child_nodes():
- self.visit(node, *args, **kwargs)
-
-
-class NodeTransformer(NodeVisitor):
- """Walks the abstract syntax tree and allows modifications of nodes.
-
- The `NodeTransformer` will walk the AST and use the return value of the
- visitor functions to replace or remove the old node. If the return
- value of the visitor function is `None` the node will be removed
- from the previous location otherwise it's replaced with the return
- value. The return value may be the original node in which case no
- replacement takes place.
- """
-
- def generic_visit(self, node, *args, **kwargs):
- for field, old_value in node.iter_fields():
- if isinstance(old_value, list):
- new_values = []
- for value in old_value:
- if isinstance(value, Node):
- value = self.visit(value, *args, **kwargs)
- if value is None:
- continue
- elif not isinstance(value, Node):
- new_values.extend(value)
- continue
- new_values.append(value)
- old_value[:] = new_values
- elif isinstance(old_value, Node):
- new_node = self.visit(old_value, *args, **kwargs)
- if new_node is None:
- delattr(node, field)
- else:
- setattr(node, field, new_node)
- return node
-
- def visit_list(self, node, *args, **kwargs):
- """As transformers may return lists in some places this method
- can be used to enforce a list as return value.
- """
- rv = self.visit(node, *args, **kwargs)
- if not isinstance(rv, list):
- rv = [rv]
- return rv
diff --git a/pythonforandroid/bootstraps/pygame/build/jni/Android.mk b/pythonforandroid/bootstraps/pygame/build/jni/Android.mk
deleted file mode 100644
index 01729763c..000000000
--- a/pythonforandroid/bootstraps/pygame/build/jni/Android.mk
+++ /dev/null
@@ -1,40 +0,0 @@
-
-# The namespace in Java file, with dots replaced with underscores
-SDL_JAVA_PACKAGE_PATH := org_renpy_android
-
-# Path to shared libraries - Android 1.6 cannot load them properly, thus we have to specify absolute path here
-# SDL_SHARED_LIBRARIES_PATH := /data/data/de.schwardtnet.alienblaster/lib
-
-# Path to files with application data - they should be downloaded from Internet on first app run inside
-# Java sources, or unpacked from resources (TODO)
-# Typically /sdcard/alienblaster
-# Or /data/data/de.schwardtnet.alienblaster/files if you're planning to unpack data in application private folder
-# Your application will just set current directory there
-SDL_CURDIR_PATH := org.renpy.android
-
-# Android Dev Phone G1 has trackball instead of cursor keys, and
-# sends trackball movement events as rapid KeyDown/KeyUp events,
-# this will make Up/Down/Left/Right key up events with X frames delay,
-# so if application expects you to press and hold button it will process the event correctly.
-# TODO: create a libsdl config file for that option and for key mapping/on-screen keyboard
-SDL_TRACKBALL_KEYUP_DELAY := 1
-
-# If the application designed for higher screen resolution enable this to get the screen
-# resized in HW-accelerated way, however it eats a tiny bit of CPU
-SDL_VIDEO_RENDER_RESIZE := 0
-
-COMPILED_LIBRARIES := sdl_ttf sdl_image sdl_mixer
-
-APPLICATION_ADDITIONAL_CFLAGS := -finline-functions -O2 -DSDL_JAVA_PACKAGE_PATH=$(SDL_JAVA_PACKAGE_PATH)
-
-APPLICATION_ADDITIONAL_LDFLAGS := -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions
-
-SDL_ADDITIONAL_CFLAGS := -DSDL_ANDROID_KEYCODE_MOUSE=UNKNOWN -DSDL_ANDROID_KEYCODE_0=SPACE -DSDL_ANDROID_KEYCODE_1=RETURN -DSDL_ANDROID_KEYCODE_2=LCTRL -DSDL_ANDROID_KEYCODE_3=LALT -DSDL_ANDROID_KEYCODE_4=RETURN
-
-# If SDL_Mixer should link to libMAD
-SDL_MIXER_USE_LIBMAD :=
-ifneq ($(strip $(filter mad, $(COMPILED_LIBRARIES))),)
-SDL_MIXER_USE_LIBMAD := 1
-endif
-
-include $(call all-subdir-makefiles)
diff --git a/pythonforandroid/bootstraps/pygame/build/jni/Application.mk b/pythonforandroid/bootstraps/pygame/build/jni/Application.mk
deleted file mode 100644
index d2f9b3021..000000000
--- a/pythonforandroid/bootstraps/pygame/build/jni/Application.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-APP_PROJECT_PATH := $(call my-dir)/..
-
-# Available libraries: mad sdl_mixer sdl_image sdl_ttf sdl_net sdl_blitpool sdl_gfx intl
-# sdl_mixer depends on tremor and optionally mad
-# sdl_image depends on png and jpeg
-# sdl_ttf depends on freetype
-
-APP_MODULES := application sdl sdl_main tremor png jpeg freetype sdl_ttf sdl_image sqlite3
-
-APP_ABI := $(ARCH)
-# AND: I have no idea why I have to specify app_platform when distribute.sh seems to just set the sysroot cflag
-# AND: Either way, this has to *at least* be configurable
-APP_PLATFORM := android-14
-APP_STL := gnustl_static
-APP_CFLAGS += $(OFLAG)
diff --git a/pythonforandroid/bootstraps/pygame/build/jni/application/Android.mk b/pythonforandroid/bootstraps/pygame/build/jni/application/Android.mk
deleted file mode 100644
index 51109a7e1..000000000
--- a/pythonforandroid/bootstraps/pygame/build/jni/application/Android.mk
+++ /dev/null
@@ -1,68 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := application
-
-APP_SUBDIRS := $(patsubst $(LOCAL_PATH)/%, %, $(shell find $(LOCAL_PATH)/src/ -type d))
-
-LOCAL_CFLAGS := $(foreach D, $(APP_SUBDIRS), -I$(LOCAL_PATH)/$(D)) \
- -I$(LOCAL_PATH)/../sdl/include \
- -I$(LOCAL_PATH)/../sdl_mixer \
- -I$(LOCAL_PATH)/../sdl_image \
- -I$(LOCAL_PATH)/../sdl_ttf \
- -I$(LOCAL_PATH)/../sdl_net \
- -I$(LOCAL_PATH)/../sdl_blitpool \
- -I$(LOCAL_PATH)/../sdl_gfx \
- -I$(LOCAL_PATH)/../png \
- -I$(LOCAL_PATH)/../jpeg \
- -I$(LOCAL_PATH)/../intl \
- -I$(LOCAL_PATH)/.. \
- -I$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/include/python2.7
- # -I$(LOCAL_PATH)/../../../../python-install/include/python2.7
- # -I$(LOCAL_PATH)/../../../build/python-install/include/python2.7
-
-
-LOCAL_CFLAGS += $(APPLICATION_ADDITIONAL_CFLAGS)
-
-#Change C++ file extension as appropriate
-LOCAL_CPP_EXTENSION := .cpp
-
-LOCAL_SRC_FILES := $(foreach F, $(APP_SUBDIRS), $(addprefix $(F)/,$(notdir $(wildcard $(LOCAL_PATH)/$(F)/*.cpp))))
-# Uncomment to also add C sources
-LOCAL_SRC_FILES += $(foreach F, $(APP_SUBDIRS), $(addprefix $(F)/,$(notdir $(wildcard $(LOCAL_PATH)/$(F)/*.c))))
-
-LOCAL_SHARED_LIBRARIES := sdl $(COMPILED_LIBRARIES)
-
-LOCAL_STATIC_LIBRARIES := jpeg png
-
-LOCAL_LDLIBS := -lpython2.7 -lGLESv1_CM -ldl -llog -lz
-
-# AND: Another hardcoded path that should be templated
-# AND: NOT TEMPALTED! We can use $ARCH
-LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/lib $(APPLICATION_ADDITIONAL_LDFLAGS)
-
-LIBS_WITH_LONG_SYMBOLS := $(strip $(shell \
- for f in $(LOCAL_PATH)/../../libs/$ARCH/*.so ; do \
- if echo $$f | grep "libapplication[.]so" > /dev/null ; then \
- continue ; \
- fi ; \
- if [ -e "$$f" ] ; then \
- if nm -g $$f | cut -c 12- | egrep '.{128}' > /dev/null ; then \
- echo $$f | grep -o 'lib[^/]*[.]so' ; \
- fi ; \
- fi ; \
- done \
-) )
-
-ifneq "$(LIBS_WITH_LONG_SYMBOLS)" ""
-$(foreach F, $(LIBS_WITH_LONG_SYMBOLS), \
-$(info Library $(F): abusing symbol names are: \
-$(shell nm -g $(LOCAL_PATH)/../../libs/$ARCH/$(F) | cut -c 12- | egrep '.{128}' ) ) \
-$(info Library $(F) contains symbol names longer than 128 bytes, \
-YOUR CODE WILL DEADLOCK WITHOUT ANY WARNING when you'll access such function - \
-please make this library static to avoid problems. ) )
-$(error Detected libraries with too long symbol names. Remove all files under project/libs/$ARCH, make these libs static, and recompile)
-endif
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/bootstraps/pygame/build/jni/application/launchConfigure.sh b/pythonforandroid/bootstraps/pygame/build/jni/application/launchConfigure.sh
deleted file mode 100755
index 7467c5c7d..000000000
--- a/pythonforandroid/bootstraps/pygame/build/jni/application/launchConfigure.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/sh
-
-# Set here your own NDK path if needed
-# export PATH=$PATH:~/src/endless_space/android-ndk-r4
-
-IFS='
-'
-
-NDK=`which ndk-build`
-NDK=`dirname $NDK`
-GCCVER=4.4.0
-PLATFORMVER=android-8
-LOCAL_PATH=`dirname $0`
-LOCAL_PATH=`cd $LOCAL_PATH && pwd`
-
-# Hacks for broken configure scripts
-ln -sf libsdl_mixer.so $LOCAL_PATH/../../bin/ndk/local/armeabi/libSDL_mixer.so
-ln -sf libsdl_mixer.so $LOCAL_PATH/../../bin/ndk/local/armeabi/libSDL_Mixer.so
-ln -sf libsdl_net.so $LOCAL_PATH/../../bin/ndk/local/armeabi/libSDL_net.so
-
-CFLAGS="-I$NDK/build/platforms/$PLATFORMVER/arch-arm/usr/include \
--fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums \
--D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -DANDROID \
--Wno-psabi -march=armv5te -mtune=xscale -msoft-float -fno-exceptions -fno-rtti -mthumb -Os \
--fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 \
--Wa,--noexecstack -O2 -DNDEBUG -g \
-`grep '[-]I[$][(]LOCAL_PATH[)]/[.][.]/' $LOCAL_PATH/Android.mk | tr '\n' ' ' | sed 's/[\\]//g' | sed \"s@[\$][(]LOCAL_PATH[)]/@$LOCAL_PATH/@g\" | sed 's/[ ][ ]*/ /g'`"
-
-LDFLAGS="-nostdlib -Wl,-soname,libapplication.so -Wl,-shared,-Bsymbolic \
--Wl,--whole-archive -Wl,--no-whole-archive \
-$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/lib/gcc/arm-eabi/4.4.0/libgcc.a \
-`echo $LOCAL_PATH/../../bin/ndk/local/armeabi/*.so | sed "s@$LOCAL_PATH/../../bin/ndk/local/armeabi/libsdl_main.so@@" | sed "s@$LOCAL_PATH/../../bin/ndk/local/armeabi/libapplication.so@@"` \
-$NDK/build/platforms/$PLATFORMVER/arch-arm/usr/lib/libc.so \
-$NDK/build/platforms/$PLATFORMVER/arch-arm/usr/lib/libstdc++.so \
-$NDK/build/platforms/$PLATFORMVER/arch-arm/usr/lib/libm.so \
--Wl,--no-undefined -Wl,-z,noexecstack \
--L$NDK/build/platforms/$PLATFORMVER/arch-arm/usr/lib \
--lGLESv1_CM -ldl -llog -lz \
--Wl,-rpath-link=$NDK/build/platforms/$PLATFORMVER/arch-arm/usr/lib \
--L$LOCAL_PATH/../../bin/ndk/local/armeabi"
-
-env PATH=$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin:$LOCAL_PATH:$PATH \
-CFLAGS="$CFLAGS" \
-CXXFLAGS="$CFLAGS" \
-LDFLAGS="$LDFLAGS" \
-CC="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-gcc" \
-CXX="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-g++" \
-RANLIB="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-ranlib" \
-LD="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-gcc" \
-AR="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-ar" \
-CPP="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-cpp $CFLAGS" \
-NM="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-nm" \
-AS="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-as" \
-STRIP="$NDK/build/prebuilt/linux-x86/arm-eabi-$GCCVER/bin/arm-eabi-strip" \
-./configure --host=arm-eabi "$@"
diff --git a/pythonforandroid/bootstraps/pygame/build/jni/application/sdl-config b/pythonforandroid/bootstraps/pygame/build/jni/application/sdl-config
deleted file mode 100755
index 199c16fd1..000000000
--- a/pythonforandroid/bootstraps/pygame/build/jni/application/sdl-config
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/bin/sh
-
-LOCAL_PATH=`dirname $0`
-LOCAL_PATH=`cd $LOCAL_PATH && pwd`
-
-prefix=$LOCAL_PATH/../sdl
-exec_prefix=$LOCAL_PATH/../../bin/ndk/local/$ARCH
-exec_prefix_set=no
-
-#usage="\
-#Usage: sdl-config [--prefix[=DIR]] [--exec-prefix[=DIR]] [--version] [--cflags] [--libs]"
-usage="\
-Usage: sdl-config [--prefix[=DIR]] [--exec-prefix[=DIR]] [--version] [--cflags] [--libs] [--static-libs]"
-
-if test $# -eq 0; then
- echo "${usage}" 1>&2
- exit 1
-fi
-
-while test $# -gt 0; do
- case "$1" in
- -*=*) optarg=`echo "$1" | LC_ALL="C" sed 's/[-_a-zA-Z0-9]*=//'` ;;
- *) optarg= ;;
- esac
-
- case $1 in
- --prefix=*)
- prefix=$optarg
- if test $exec_prefix_set = no ; then
- exec_prefix=$optarg
- fi
- ;;
- --prefix)
- echo $prefix
- ;;
- --exec-prefix=*)
- exec_prefix=$optarg
- exec_prefix_set=yes
- ;;
- --exec-prefix)
- echo $exec_prefix
- ;;
- --version)
- echo 1.2.14
- ;;
- --cflags)
- echo -I${prefix}/include -D_GNU_SOURCE=1 -D_REENTRANT
- ;;
- --libs)
- echo -L${exec_prefix} -lsdl
- ;;
- --static-libs)
-# --libs|--static-libs)
- echo -L${exec_prefix} -lsdl
- ;;
- *)
- echo "${usage}" 1>&2
- exit 1
- ;;
- esac
- shift
-done
diff --git a/pythonforandroid/bootstraps/pygame/build/jni/application/src/jniwrapperstuff.h b/pythonforandroid/bootstraps/pygame/build/jni/application/src/jniwrapperstuff.h
deleted file mode 100644
index 6a37980aa..000000000
--- a/pythonforandroid/bootstraps/pygame/build/jni/application/src/jniwrapperstuff.h
+++ /dev/null
@@ -1,13 +0,0 @@
-
-/* JNI-C++ wrapper stuff */
-#ifndef _JNI_WRAPPER_STUFF_H_
-#define _JNI_WRAPPER_STUFF_H_
-
-#ifndef SDL_JAVA_PACKAGE_PATH
-#error You have to define SDL_JAVA_PACKAGE_PATH to your package path with dots replaced with underscores, for example "com_example_SanAngeles"
-#endif
-#define JAVA_EXPORT_NAME2(name,package) Java_##package##_##name
-#define JAVA_EXPORT_NAME1(name,package) JAVA_EXPORT_NAME2(name,package)
-#define JAVA_EXPORT_NAME(name) JAVA_EXPORT_NAME1(name,SDL_JAVA_PACKAGE_PATH)
-
-#endif
diff --git a/pythonforandroid/bootstraps/pygame/build/jni/application/src/start.c b/pythonforandroid/bootstraps/pygame/build/jni/application/src/start.c
deleted file mode 100644
index 2bb768383..000000000
--- a/pythonforandroid/bootstraps/pygame/build/jni/application/src/start.c
+++ /dev/null
@@ -1,172 +0,0 @@
-#define PY_SSIZE_T_CLEAN
-#include "Python.h"
-#ifndef Py_PYTHON_H
- #error Python headers needed to compile C extensions, please install development version of Python.
-#else
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <jni.h>
-#include "SDL.h"
-#include "android/log.h"
-#include "jniwrapperstuff.h"
-
-#define LOG(x) __android_log_write(ANDROID_LOG_INFO, "python", (x))
-
-static PyObject *androidembed_log(PyObject *self, PyObject *args) {
- char *logstr = NULL;
- if (!PyArg_ParseTuple(args, "s", &logstr)) {
- return NULL;
- }
- LOG(logstr);
- Py_RETURN_NONE;
-}
-
-static PyMethodDef AndroidEmbedMethods[] = {
- {"log", androidembed_log, METH_VARARGS,
- "Log on android platform"},
- {NULL, NULL, 0, NULL}
-};
-
-PyMODINIT_FUNC initandroidembed(void) {
- (void) Py_InitModule("androidembed", AndroidEmbedMethods);
-}
-
-int file_exists(const char * filename)
-{
- FILE *file;
- if (file = fopen(filename, "r")) {
- fclose(file);
- return 1;
- }
- return 0;
-}
-
-int main(int argc, char **argv) {
-
- char *env_argument = NULL;
- int ret = 0;
- FILE *fd;
-
- LOG("Initialize Python for Android");
- env_argument = getenv("ANDROID_ARGUMENT");
- setenv("ANDROID_APP_PATH", env_argument, 1);
- //setenv("PYTHONVERBOSE", "2", 1);
- Py_SetProgramName(argv[0]);
- Py_Initialize();
- PySys_SetArgv(argc, argv);
-
- /* ensure threads will work.
- */
- PyEval_InitThreads();
-
- /* our logging module for android
- */
- initandroidembed();
-
- /* inject our bootstrap code to redirect python stdin/stdout
- * replace sys.path with our path
- */
- PyRun_SimpleString(
- "import sys, posix\n" \
- "private = posix.environ['ANDROID_PRIVATE']\n" \
- "argument = posix.environ['ANDROID_ARGUMENT']\n" \
- "sys.path[:] = [ \n" \
- " private + '/lib/python27.zip', \n" \
- " private + '/lib/python2.7/', \n" \
- " private + '/lib/python2.7/lib-dynload/', \n" \
- " private + '/lib/python2.7/site-packages/', \n" \
- " argument ]\n" \
- "import androidembed\n" \
- "class LogFile(object):\n" \
- " def __init__(self):\n" \
- " self.buffer = ''\n" \
- " def write(self, s):\n" \
- " s = self.buffer + s\n" \
- " lines = s.split(\"\\n\")\n" \
- " for l in lines[:-1]:\n" \
- " androidembed.log(l)\n" \
- " self.buffer = lines[-1]\n" \
- " def flush(self):\n" \
- " return\n" \
- "sys.stdout = sys.stderr = LogFile()\n" \
- "import site; print site.getsitepackages()\n"\
- "print 'Android path', sys.path\n" \
- "print 'Android kivy bootstrap done. __name__ is', __name__");
-
- /* run it !
- */
- LOG("Run user program, change dir and execute main.py");
- chdir(env_argument);
-
- /* search the initial main.py
- */
- char *main_py = "main.pyo";
- if ( file_exists(main_py) == 0 ) {
- if ( file_exists("main.py") )
- main_py = "main.py";
- else
- main_py = NULL;
- }
-
- if ( main_py == NULL ) {
- LOG("No main.pyo / main.py found.");
- return -1;
- }
-
- fd = fopen(main_py, "r");
- if ( fd == NULL ) {
- LOG("Open the main.py(o) failed");
- return -1;
- }
-
- /* run python !
- */
- ret = PyRun_SimpleFile(fd, main_py);
-
- if (PyErr_Occurred() != NULL) {
- ret = 1;
- PyErr_Print(); /* This exits with the right code if SystemExit. */
- if (Py_FlushLine())
- PyErr_Clear();
- }
-
- /* close everything
- */
- Py_Finalize();
- fclose(fd);
-
- LOG("Python for android ended.");
- return ret;
-}
-
-JNIEXPORT void JNICALL JAVA_EXPORT_NAME(PythonService_nativeStart) ( JNIEnv* env, jobject thiz,
- jstring j_android_private,
- jstring j_android_argument,
- jstring j_python_home,
- jstring j_python_path,
- jstring j_arg )
-{
- jboolean iscopy;
- const char *android_private = (*env)->GetStringUTFChars(env, j_android_private, &iscopy);
- const char *android_argument = (*env)->GetStringUTFChars(env, j_android_argument, &iscopy);
- const char *python_home = (*env)->GetStringUTFChars(env, j_python_home, &iscopy);
- const char *python_path = (*env)->GetStringUTFChars(env, j_python_path, &iscopy);
- const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy);
-
- setenv("ANDROID_PRIVATE", android_private, 1);
- setenv("ANDROID_ARGUMENT", android_argument, 1);
- setenv("PYTHONOPTIMIZE", "2", 1);
- setenv("PYTHONHOME", python_home, 1);
- setenv("PYTHONPATH", python_path, 1);
- setenv("PYTHON_SERVICE_ARGUMENT", arg, 1);
-
- char *argv[] = { "service" };
- /* ANDROID_ARGUMENT points to service subdir,
- * so main() will run main.py from this dir
- */
- main(1, argv);
-}
-
-#endif
diff --git a/pythonforandroid/bootstraps/pygame/build/local.properties b/pythonforandroid/bootstraps/pygame/build/local.properties
deleted file mode 100644
index daae45e61..000000000
--- a/pythonforandroid/bootstraps/pygame/build/local.properties
+++ /dev/null
@@ -1 +0,0 @@
-sdk.dir=/opt/android-sdk
diff --git a/pythonforandroid/bootstraps/pygame/build/res/drawable/presplash.jpg b/pythonforandroid/bootstraps/pygame/build/res/drawable/presplash.jpg
deleted file mode 100644
index 1990f6ddf..000000000
Binary files a/pythonforandroid/bootstraps/pygame/build/res/drawable/presplash.jpg and /dev/null differ
diff --git a/pythonforandroid/bootstraps/pygame/build/res/layout/chooser_item.xml b/pythonforandroid/bootstraps/pygame/build/res/layout/chooser_item.xml
deleted file mode 100644
index 1823b1322..000000000
--- a/pythonforandroid/bootstraps/pygame/build/res/layout/chooser_item.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:gravity="center"
- >
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="64sp"
- android:layout_height="64sp"
- android:scaleType="fitCenter"
- android:padding="2sp"
- />
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- >
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/title"
- android:textSize="18sp"
- android:textColor="#fff"
- android:singleLine="true"
- />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:id="@+id/author"
- />
-
- </LinearLayout>
-</LinearLayout>
diff --git a/pythonforandroid/bootstraps/pygame/build/res/layout/main.xml b/pythonforandroid/bootstraps/pygame/build/res/layout/main.xml
deleted file mode 100644
index 0da95fc50..000000000
--- a/pythonforandroid/bootstraps/pygame/build/res/layout/main.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
-</LinearLayout>
-
diff --git a/pythonforandroid/bootstraps/pygame/build/res/layout/project_chooser.xml b/pythonforandroid/bootstraps/pygame/build/res/layout/project_chooser.xml
deleted file mode 100644
index 23828e644..000000000
--- a/pythonforandroid/bootstraps/pygame/build/res/layout/project_chooser.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- >
-
- <TextView
- android:text="Please choose a project:"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:padding="4sp"
- />
-
- <ListView
- android:id="@+id/projectList"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- />
-
-
-</LinearLayout>
diff --git a/pythonforandroid/bootstraps/pygame/build/res/layout/project_empty.xml b/pythonforandroid/bootstraps/pygame/build/res/layout/project_empty.xml
deleted file mode 100644
index ee5481421..000000000
--- a/pythonforandroid/bootstraps/pygame/build/res/layout/project_empty.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- >
-
- <TextView
- android:id="@+id/emptyText"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:padding="4sp"
- />
-
-</LinearLayout>
diff --git a/pythonforandroid/bootstraps/pygame/build/res/values/strings.xml b/pythonforandroid/bootstraps/pygame/build/res/values/strings.xml
deleted file mode 100644
index 468d970fe..000000000
--- a/pythonforandroid/bootstraps/pygame/build/res/values/strings.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-<string name="appName">Kivy Launcher</string>
-<string name="iconName">Kivy Launcher</string>
-
-<string name="private_version">1323531558.3</string>
-
-
-<string name="urlScheme">kivy</string>
-</resources>
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/pygame/build/src/com/android/vending/billing/IMarketBillingService.aidl b/pythonforandroid/bootstraps/pygame/build/src/com/android/vending/billing/IMarketBillingService.aidl
deleted file mode 100644
index 6884b41f6..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/com/android/vending/billing/IMarketBillingService.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.vending.billing;
-
-import android.os.Bundle;
-
-interface IMarketBillingService {
- /** Given the arguments in bundle form, returns a bundle for results. */
- Bundle sendBillingRequest(in Bundle bundle);
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Action.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Action.java
deleted file mode 100644
index fb70b3b02..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Action.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.renpy.android;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.app.Activity;
-import android.util.Log;
-
-public class Action {
-
- static Context context;
-
- /* Deliver some data to someone else
- */
- static void send(String mimeType, String filename, String subject, String text, String chooser_title) {
- Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
- emailIntent.setType(mimeType);
- /** tryied with String [] emails, but hard to code the whole C/Cython part.
- if (emails != null)
- emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, emails);
- **/
- if (subject != null)
- emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
- if (text != null)
- emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, text);
- if (filename != null)
- emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://"+ filename));
- if (chooser_title == null)
- chooser_title = "Send mail";
- context.startActivity(Intent.createChooser(emailIntent, chooser_title));
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/AssetExtract.java
deleted file mode 100644
index dd4ec48fc..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/AssetExtract.java
+++ /dev/null
@@ -1,115 +0,0 @@
-// This string is autogenerated by ChangeAppSettings.sh, do not change
-// spaces amount
-package org.renpy.android;
-
-import java.io.*;
-
-import android.app.Activity;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.File;
-
-import java.util.zip.GZIPInputStream;
-
-import android.content.res.AssetManager;
-
-import org.kamranzafar.jtar.*;
-
-class AssetExtract {
-
- private AssetManager mAssetManager = null;
- private Activity mActivity = null;
-
- AssetExtract(Activity act) {
- mActivity = act;
- mAssetManager = act.getAssets();
- }
-
- public boolean extractTar(String asset, String target) {
-
- byte buf[] = new byte[1024 * 1024];
-
- InputStream assetStream = null;
- TarInputStream tis = null;
-
- try {
- assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
- tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
- } catch (IOException e) {
- Log.e("python", "opening up extract tar", e);
- return false;
- }
-
- while (true) {
- TarEntry entry = null;
-
- try {
- entry = tis.getNextEntry();
- } catch ( java.io.IOException e ) {
- Log.e("python", "extracting tar", e);
- return false;
- }
-
- if ( entry == null ) {
- break;
- }
-
- Log.v("python", "extracting " + entry.getName());
-
- if (entry.isDirectory()) {
-
- try {
- new File(target +"/" + entry.getName()).mkdirs();
- } catch ( SecurityException e ) { };
-
- continue;
- }
-
- OutputStream out = null;
- String path = target + "/" + entry.getName();
-
- try {
- out = new BufferedOutputStream(new FileOutputStream(path), 8192);
- } catch ( FileNotFoundException e ) {
- } catch ( SecurityException e ) { };
-
- if ( out == null ) {
- Log.e("python", "could not open " + path);
- return false;
- }
-
- try {
- while (true) {
- int len = tis.read(buf);
-
- if (len == -1) {
- break;
- }
-
- out.write(buf, 0, len);
- }
-
- out.flush();
- out.close();
- } catch ( java.io.IOException e ) {
- Log.e("python", "extracting zip", e);
- return false;
- }
- }
-
- try {
- tis.close();
- assetStream.close();
- } catch (IOException e) {
- // pass
- }
-
- return true;
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Audio.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Audio.java
deleted file mode 100644
index 553a2b484..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Audio.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- Simple DirectMedia Layer
- Java source code (C) 2009-2011 Sergii Pylypenko
-
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
-
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
-
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
-
-package org.renpy.android;
-
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.MotionEvent;
-import android.view.KeyEvent;
-import android.view.Window;
-import android.view.WindowManager;
-import android.media.AudioTrack;
-import android.media.AudioManager;
-import android.media.AudioFormat;
-import java.io.*;
-import java.nio.ByteBuffer;
-import android.util.Log;
-import java.lang.Thread;
-
-
-class AudioThread {
-
- private PythonActivity mParent;
- private AudioTrack mAudio;
- private byte[] mAudioBuffer;
- private int mVirtualBufSize;
-
- public AudioThread(PythonActivity parent)
- {
- mParent = parent;
- mAudio = null;
- mAudioBuffer = null;
- nativeAudioInitJavaCallbacks();
- }
-
- public int fillBuffer()
- {
- if( mParent.isPaused() )
- {
- try{
- Thread.sleep(200);
- } catch (InterruptedException e) {}
- }
- else
- {
- //if( Globals.AudioBufferConfig == 0 ) // Gives too much spam to logcat, makes things worse
- // mAudio.flush();
-
- mAudio.write( mAudioBuffer, 0, mVirtualBufSize );
- }
-
- return 1;
- }
-
- public int initAudio(int rate, int channels, int encoding, int bufSize)
- {
- if( mAudio == null )
- {
- channels = ( channels == 1 ) ? AudioFormat.CHANNEL_CONFIGURATION_MONO :
- AudioFormat.CHANNEL_CONFIGURATION_STEREO;
- encoding = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT :
- AudioFormat.ENCODING_PCM_8BIT;
-
- mVirtualBufSize = bufSize;
-
- if( AudioTrack.getMinBufferSize( rate, channels, encoding ) > bufSize )
- bufSize = AudioTrack.getMinBufferSize( rate, channels, encoding );
-
- /**
- if(Globals.AudioBufferConfig != 0) { // application's choice - use minimal buffer
- bufSize = (int)((float)bufSize * (((float)(Globals.AudioBufferConfig - 1) * 2.5f) + 1.0f));
- mVirtualBufSize = bufSize;
- }
- **/
- mAudioBuffer = new byte[bufSize];
-
- mAudio = new AudioTrack(AudioManager.STREAM_MUSIC,
- rate,
- channels,
- encoding,
- bufSize,
- AudioTrack.MODE_STREAM );
- mAudio.play();
- }
- return mVirtualBufSize;
- }
-
- public byte[] getBuffer()
- {
- return mAudioBuffer;
- }
-
- public int deinitAudio()
- {
- if( mAudio != null )
- {
- mAudio.stop();
- mAudio.release();
- mAudio = null;
- }
- mAudioBuffer = null;
- return 1;
- }
-
- public int initAudioThread()
- {
- // Make audio thread priority higher so audio thread won't get underrun
- Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
- return 1;
- }
-
- public int pauseAudioPlayback()
- {
- if( mAudio != null )
- {
- mAudio.pause();
- return 1;
- }
- return 0;
- }
-
- public int resumeAudioPlayback()
- {
- if( mAudio != null )
- {
- mAudio.play();
- return 1;
- }
- return 0;
- }
-
- private native int nativeAudioInitJavaCallbacks();
-}
-
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Hardware.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Hardware.java
deleted file mode 100644
index 01ed712bc..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Hardware.java
+++ /dev/null
@@ -1,284 +0,0 @@
-package org.renpy.android;
-
-import android.content.Context;
-import android.os.Vibrator;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.util.DisplayMetrics;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.EditorInfo;
-import android.view.View;
-
-import java.util.List;
-import java.util.ArrayList;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-
-
-/**
- * Methods that are expected to be called via JNI, to access the
- * device's non-screen hardware. (For example, the vibration and
- * accelerometer.)
- */
-public class Hardware {
-
- // The context.
- static Context context;
- static View view;
-
- /**
- * Vibrate for s seconds.
- */
- public static void vibrate(double s) {
- Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- if (v != null) {
- v.vibrate((int) (1000 * s));
- }
- }
-
- /**
- * Get an Overview of all Hardware Sensors of an Android Device
- */
- public static String getHardwareSensors() {
- SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
- List<Sensor> allSensors = sm.getSensorList(Sensor.TYPE_ALL);
-
- if (allSensors != null) {
- String resultString = "";
- for (Sensor s : allSensors) {
- resultString += String.format("Name=" + s.getName());
- resultString += String.format(",Vendor=" + s.getVendor());
- resultString += String.format(",Version=" + s.getVersion());
- resultString += String.format(",MaximumRange=" + s.getMaximumRange());
- // XXX MinDelay is not in the 2.2
- //resultString += String.format(",MinDelay=" + s.getMinDelay());
- resultString += String.format(",Power=" + s.getPower());
- resultString += String.format(",Type=" + s.getType() + "\n");
- }
- return resultString;
- }
- return "";
- }
-
-
- /**
- * Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and Magnetic Field Sensors
- */
- public static class generic3AxisSensor implements SensorEventListener {
- private final SensorManager sSensorManager;
- private final Sensor sSensor;
- private final int sSensorType;
- SensorEvent sSensorEvent;
-
- public generic3AxisSensor(int sensorType) {
- sSensorType = sensorType;
- sSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- sSensor = sSensorManager.getDefaultSensor(sSensorType);
- }
-
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
-
- public void onSensorChanged(SensorEvent event) {
- sSensorEvent = event;
- }
-
- /**
- * Enable or disable the Sensor by registering/unregistering
- */
- public void changeStatus(boolean enable) {
- if (enable) {
- sSensorManager.registerListener(this, sSensor, SensorManager.SENSOR_DELAY_NORMAL);
- } else {
- sSensorManager.unregisterListener(this, sSensor);
- }
- }
-
- /**
- * Read the Sensor
- */
- public float[] readSensor() {
- if (sSensorEvent != null) {
- return sSensorEvent.values;
- } else {
- float rv[] = { 0f, 0f, 0f };
- return rv;
- }
- }
- }
-
- public static generic3AxisSensor accelerometerSensor = null;
- public static generic3AxisSensor orientationSensor = null;
- public static generic3AxisSensor magneticFieldSensor = null;
-
- /**
- * functions for backward compatibility reasons
- */
-
- public static void accelerometerEnable(boolean enable) {
- if ( accelerometerSensor == null )
- accelerometerSensor = new generic3AxisSensor(Sensor.TYPE_ACCELEROMETER);
- accelerometerSensor.changeStatus(enable);
- }
- public static float[] accelerometerReading() {
- float rv[] = { 0f, 0f, 0f };
- if ( accelerometerSensor == null )
- return rv;
- return (float[]) accelerometerSensor.readSensor();
- }
- public static void orientationSensorEnable(boolean enable) {
- if ( orientationSensor == null )
- orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION);
- orientationSensor.changeStatus(enable);
- }
- public static float[] orientationSensorReading() {
- float rv[] = { 0f, 0f, 0f };
- if ( orientationSensor == null )
- return rv;
- return (float[]) orientationSensor.readSensor();
- }
- public static void magneticFieldSensorEnable(boolean enable) {
- if ( magneticFieldSensor == null )
- magneticFieldSensor = new generic3AxisSensor(Sensor.TYPE_MAGNETIC_FIELD);
- magneticFieldSensor.changeStatus(enable);
- }
- public static float[] magneticFieldSensorReading() {
- float rv[] = { 0f, 0f, 0f };
- if ( magneticFieldSensor == null )
- return rv;
- return (float[]) magneticFieldSensor.readSensor();
- }
-
- static public DisplayMetrics metrics = new DisplayMetrics();
-
- /**
- * Get display DPI.
- */
- public static int getDPI() {
- return metrics.densityDpi;
- }
-
- /**
- * Show the soft keyboard.
- */
- public static void showKeyboard(int input_type) {
- //Log.i("python", "hardware.Java show_keyword " input_type);
-
- InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
-
- SDLSurfaceView vw = (SDLSurfaceView) view;
-
- int inputType = input_type;
-
- if (vw.inputType != inputType){
- vw.inputType = inputType;
- imm.restartInput(view);
- }
-
- imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
- }
-
- /**
- * Hide the soft keyboard.
- */
- public static void hideKeyboard() {
- InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
- }
-
- /**
- * Scan WiFi networks
- */
- static List<ScanResult> latestResult;
-
- public static void enableWifiScanner()
- {
- IntentFilter i = new IntentFilter();
- i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-
- context.registerReceiver(new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context c, Intent i) {
- // Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event occurs
- WifiManager w = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);
- latestResult = w.getScanResults(); // Returns a <list> of scanResults
- }
-
- }, i);
-
- }
-
- public static String scanWifi() {
-
- // Now you can call this and it should execute the broadcastReceiver's
- // onReceive()
- WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- boolean a = wm.startScan();
-
- if (latestResult != null){
-
- String latestResultString = "";
- for (ScanResult result : latestResult)
- {
- latestResultString += String.format("%s\t%s\t%d\n", result.SSID, result.BSSID, result.level);
- }
-
- return latestResultString;
- }
-
- return "";
- }
-
- /**
- * network state
- */
-
- public static boolean network_state = false;
-
- /**
- * Check network state directly
- *
- * (only one connection can be active at a given moment, detects all network type)
- *
- */
- public static boolean checkNetwork()
- {
- boolean state = false;
- final ConnectivityManager conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-
- final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();
- if (activeNetwork != null && activeNetwork.isConnected()) {
- state = true;
- } else {
- state = false;
- }
-
- return state;
- }
-
- /**
- * To recieve network state changes
- */
- public static void registerNetworkCheck()
- {
- IntentFilter i = new IntentFilter();
- i.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- context.registerReceiver(new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context c, Intent i) {
- network_state = checkNetwork();
- }
-
- }, i);
- }
-
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Project.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Project.java
deleted file mode 100644
index 03e06811f..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/Project.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package org.renpy.android;
-
-import java.io.UnsupportedEncodingException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.Properties;
-
-import android.util.Log;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
-
-/**
- * This represents a project we've scanned for.
- */
-public class Project {
-
- String dir = null;
- String title = null;
- String author = null;
- Bitmap icon = null;
- boolean landscape = false;
-
- static String decode(String s) {
- try {
- return new String(s.getBytes("ISO-8859-1"), "UTF-8");
- } catch (UnsupportedEncodingException e) {
- return s;
- }
- }
-
- /**
- * Scans directory for a project.txt file. If it finds one,
- * and it looks valid enough, then it creates a new Project,
- * and returns that. Otherwise, returns null.
- */
- public static Project scanDirectory(File dir) {
-
- // We might have a link file.
- if (dir.getAbsolutePath().endsWith(".link")) {
- try {
-
- // Scan the android.txt file.
- File propfile = new File(dir, "android.txt");
- FileInputStream in = new FileInputStream(propfile);
- Properties p = new Properties();
- p.load(in);
- in.close();
-
- String directory = p.getProperty("directory", null);
-
- if (directory == null) {
- return null;
- }
-
- dir = new File(directory);
-
- } catch (Exception e) {
- Log.i("Project", "Couldn't open link file " + dir, e);
- }
- }
-
- // Make sure we're dealing with a directory.
- if (! dir.isDirectory()) {
- return null;
- }
-
- try {
-
- // Scan the android.txt file.
- File propfile = new File(dir, "android.txt");
- FileInputStream in = new FileInputStream(propfile);
- Properties p = new Properties();
- p.load(in);
- in.close();
-
- // Get the various properties.
- String title = decode(p.getProperty("title", "Untitled"));
- String author = decode(p.getProperty("author", ""));
- boolean landscape = p.getProperty("orientation", "portrait").equals("landscape");
-
- // Create the project object.
- Project rv = new Project();
- rv.title = title;
- rv.author = author;
- rv.icon = BitmapFactory.decodeFile(new File(dir, "icon.png").getAbsolutePath());
- rv.landscape = landscape;
- rv.dir = dir.getAbsolutePath();
-
- return rv;
-
- } catch (Exception e) {
- Log.i("Project", "Couldn't open android.txt", e);
- }
-
- return null;
-
- }
-
-
-
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ProjectAdapter.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ProjectAdapter.java
deleted file mode 100644
index 566d46a8e..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ProjectAdapter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.renpy.android;
-
-import android.app.Activity;
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Gravity;
-import android.widget.ArrayAdapter;
-import android.widget.TextView;
-import android.widget.LinearLayout;
-import android.widget.ImageView;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.Log;
-
-public class ProjectAdapter extends ArrayAdapter<Project> {
-
- private Activity mContext;
- private ResourceManager resourceManager;
-
-
- public ProjectAdapter(Activity context) {
- super(context, 0);
-
- mContext = context;
- resourceManager = new ResourceManager(context);
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- Project p = getItem(position);
-
- View v = resourceManager.inflateView("chooser_item");
- TextView title = (TextView) resourceManager.getViewById(v, "title");
- TextView author = (TextView) resourceManager.getViewById(v, "author");
- ImageView icon = (ImageView) resourceManager.getViewById(v, "icon");
-
- title.setText(p.title);
- author.setText(p.author);
- icon.setImageBitmap(p.icon);
-
- return v;
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ProjectChooser.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ProjectChooser.java
deleted file mode 100644
index 49ff71ac8..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ProjectChooser.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package org.renpy.android;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import android.content.Intent;
-import android.content.res.Resources;
-import android.util.Log;
-import android.view.View;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.AdapterView;
-import android.os.Environment;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import android.net.Uri;
-
-public class ProjectChooser extends Activity implements AdapterView.OnItemClickListener {
-
- ResourceManager resourceManager;
-
- String urlScheme;
-
- @Override
- public void onStart()
- {
- super.onStart();
-
- resourceManager = new ResourceManager(this);
-
- urlScheme = resourceManager.getString("urlScheme");
-
- // Set the window title.
- setTitle(resourceManager.getString("appName"));
-
- // Scan the sdcard for files, and sort them.
- File dir = new File(Environment.getExternalStorageDirectory(), urlScheme);
-
- File entries[] = dir.listFiles();
-
- if (entries == null) {
- entries = new File[0];
- }
-
- Arrays.sort(entries);
-
- // Create a ProjectAdapter and fill it with projects.
- ProjectAdapter projectAdapter = new ProjectAdapter(this);
-
- // Populate it with the properties files.
- for (File d : entries) {
- Project p = Project.scanDirectory(d);
- if (p != null) {
- projectAdapter.add(p);
- }
- }
-
- if (projectAdapter.getCount() != 0) {
-
- View v = resourceManager.inflateView("project_chooser");
- ListView l = (ListView) resourceManager.getViewById(v, "projectList");
-
- l.setAdapter(projectAdapter);
- l.setOnItemClickListener(this);
-
- setContentView(v);
-
- } else {
-
- View v = resourceManager.inflateView("project_empty");
- TextView emptyText = (TextView) resourceManager.getViewById(v, "emptyText");
-
- emptyText.setText("No projects are available to launch. Please place a project into " + dir + " and restart this application. Press the back button to exit.");
-
- setContentView(v);
- }
- }
-
- public void onItemClick(AdapterView parent, View view, int position, long id) {
- Project p = (Project) parent.getItemAtPosition(position);
-
- Intent intent = new Intent(
- "org.renpy.LAUNCH",
- Uri.fromParts(urlScheme, p.dir, ""));
-
- intent.setClassName(getPackageName(), "org.renpy.android.PythonActivity");
- this.startActivity(intent);
- this.finish();
- }
-
-
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonActivity.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonActivity.java
deleted file mode 100644
index aed7e9ed8..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonActivity.java
+++ /dev/null
@@ -1,627 +0,0 @@
-package org.renpy.android;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.os.Bundle;
-import android.os.Environment;
-import android.view.KeyEvent;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.Toast;
-import android.util.Log;
-import android.content.pm.PackageManager;
-import android.content.pm.ApplicationInfo;
-
-import java.io.InputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Iterator;
-
-// Billing
-import org.renpy.android.Configuration;
-import org.renpy.android.billing.BillingService.RequestPurchase;
-import org.renpy.android.billing.BillingService.RestoreTransactions;
-import org.renpy.android.billing.Consts.PurchaseState;
-import org.renpy.android.billing.Consts.ResponseCode;
-import org.renpy.android.billing.PurchaseObserver;
-import org.renpy.android.billing.BillingService;
-import org.renpy.android.billing.PurchaseDatabase;
-import org.renpy.android.billing.Consts;
-import org.renpy.android.billing.ResponseHandler;
-import org.renpy.android.billing.Security;
-import android.os.Handler;
-import android.database.Cursor;
-import java.util.List;
-import java.util.ArrayList;
-import android.content.SharedPreferences;
-import android.content.Context;
-
-
-public class PythonActivity extends Activity implements Runnable {
- private static String TAG = "Python";
-
- // The audio thread for streaming audio...
- private static AudioThread mAudioThread = null;
-
- // The SDLSurfaceView we contain.
- public static SDLSurfaceView mView = null;
- public static PythonActivity mActivity = null;
- public static ApplicationInfo mInfo = null;
-
- // Did we launch our thread?
- private boolean mLaunchedThread = false;
-
- private ResourceManager resourceManager;
-
- // The path to the directory contaning our external storage.
- private File externalStorage;
-
- // The path to the directory containing the game.
- private File mPath = null;
-
- boolean _isPaused = false;
-
- private static final String DB_INITIALIZED = "db_initialized";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Hardware.context = this;
- Action.context = this;
- this.mActivity = this;
-
- getWindowManager().getDefaultDisplay().getMetrics(Hardware.metrics);
-
- resourceManager = new ResourceManager(this);
- externalStorage = new File(Environment.getExternalStorageDirectory(), getPackageName());
-
- // Figure out the directory where the game is. If the game was
- // given to us via an intent, then we use the scheme-specific
- // part of that intent to determine the file to launch. We
- // also use the android.txt file to determine the orientation.
- //
- // Otherwise, we use the public data, if we have it, or the
- // private data if we do not.
- if (getIntent() != null && getIntent().getAction() != null &&
- getIntent().getAction().equals("org.renpy.LAUNCH")) {
- mPath = new File(getIntent().getData().getSchemeSpecificPart());
-
- Project p = Project.scanDirectory(mPath);
-
- if (p != null) {
- if (p.landscape) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- } else {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- }
- }
-
- // Let old apps know they started.
- try {
- FileWriter f = new FileWriter(new File(mPath, ".launch"));
- f.write("started");
- f.close();
- } catch (IOException e) {
- // pass
- }
-
-
-
- } else if (resourceManager.getString("public_version") != null) {
- mPath = externalStorage;
- } else {
- mPath = getFilesDir();
- }
-
- requestWindowFeature(Window.FEATURE_NO_TITLE);
-
- // go to fullscreen mode if requested
- try {
- this.mInfo = this.getPackageManager().getApplicationInfo(
- this.getPackageName(), PackageManager.GET_META_DATA);
- Log.v("python", "metadata fullscreen is" + this.mInfo.metaData.get("fullscreen"));
- if ( (Integer)this.mInfo.metaData.get("fullscreen") == 1 ) {
- getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- }
- } catch (PackageManager.NameNotFoundException e) {
- }
-
- if ( Configuration.use_billing ) {
- mBillingHandler = new Handler();
- }
-
- // Start showing an SDLSurfaceView.
- mView = new SDLSurfaceView(
- this,
- mPath.getAbsolutePath());
-
- Hardware.view = mView;
- setContentView(mView);
-
- // Force the background window color if asked
- if ( this.mInfo.metaData.containsKey("android.background_color") ) {
- getWindow().getDecorView().setBackgroundColor(
- this.mInfo.metaData.getInt("android.background_color"));
- }
- }
-
- /**
- * Show an error using a toast. (Only makes sense from non-UI
- * threads.)
- */
- public void toastError(final String msg) {
-
- final Activity thisActivity = this;
-
- runOnUiThread(new Runnable () {
- public void run() {
- Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show();
- }
- });
-
- // Wait to show the error.
- synchronized (this) {
- try {
- this.wait(1000);
- } catch (InterruptedException e) {
- }
- }
- }
-
- public void recursiveDelete(File f) {
- if (f.isDirectory()) {
- for (File r : f.listFiles()) {
- recursiveDelete(r);
- }
- }
- f.delete();
- }
-
-
- /**
- * This determines if unpacking one the zip files included in
- * the .apk is necessary. If it is, the zip file is unpacked.
- */
- public void unpackData(final String resource, File target) {
-
- // The version of data in memory and on disk.
- String data_version = resourceManager.getString(resource + "_version");
- String disk_version = null;
-
- // If no version, no unpacking is necessary.
- if (data_version == null) {
- return;
- }
-
- // Check the current disk version, if any.
- String filesDir = target.getAbsolutePath();
- String disk_version_fn = filesDir + "/" + resource + ".version";
-
- try {
- byte buf[] = new byte[64];
- InputStream is = new FileInputStream(disk_version_fn);
- int len = is.read(buf);
- disk_version = new String(buf, 0, len);
- is.close();
- } catch (Exception e) {
- disk_version = "";
- }
-
- // If the disk data is out of date, extract it and write the
- // version file.
- if (! data_version.equals(disk_version)) {
- Log.v(TAG, "Extracting " + resource + " assets.");
-
- recursiveDelete(target);
- target.mkdirs();
-
- AssetExtract ae = new AssetExtract(this);
- if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) {
- toastError("Could not extract " + resource + " data.");
- }
-
- try {
- // Write .nomedia.
- new File(target, ".nomedia").createNewFile();
-
- // Write version file.
- FileOutputStream os = new FileOutputStream(disk_version_fn);
- os.write(data_version.getBytes());
- os.close();
- } catch (Exception e) {
- Log.w("python", e);
- }
- }
-
- }
-
- public void run() {
-
- unpackData("private", getFilesDir());
- unpackData("public", externalStorage);
-
- System.loadLibrary("sdl");
- System.loadLibrary("sdl_image");
- System.loadLibrary("sdl_ttf");
- System.loadLibrary("sdl_mixer");
- System.loadLibrary("python2.7");
- System.loadLibrary("application");
- System.loadLibrary("sdl_main");
-
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_io.so");
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/unicodedata.so");
-
- try {
- System.loadLibrary("sqlite3");
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_sqlite3.so");
- } catch(UnsatisfiedLinkError e) {
- }
-
- try {
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_imaging.so");
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_imagingft.so");
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_imagingmath.so");
- } catch(UnsatisfiedLinkError e) {
- }
-
- if ( mAudioThread == null ) {
- Log.i("python", "Starting audio thread");
- mAudioThread = new AudioThread(this);
- }
-
- runOnUiThread(new Runnable () {
- public void run() {
- mView.start();
- }
- });
- }
-
- @Override
- protected void onPause() {
- _isPaused = true;
- super.onPause();
-
- if (mView != null) {
- mView.onPause();
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- _isPaused = false;
-
- if (!mLaunchedThread) {
- mLaunchedThread = true;
- new Thread(this).start();
- }
-
- if (mView != null) {
- mView.onResume();
- }
- }
-
- public boolean isPaused() {
- return _isPaused;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, final KeyEvent event) {
- //Log.i("python", "key2 " + mView + " " + mView.mStarted);
- if (mView != null && mView.mStarted && SDLSurfaceView.nativeKey(keyCode, 1, event.getUnicodeChar())) {
- return true;
- } else {
- return super.onKeyDown(keyCode, event);
- }
- }
-
- @Override
- public boolean onKeyUp(int keyCode, final KeyEvent event) {
- //Log.i("python", "key up " + mView + " " + mView.mStarted);
- if (mView != null && mView.mStarted && SDLSurfaceView.nativeKey(keyCode, 0, event.getUnicodeChar())) {
- return true;
- } else {
- return super.onKeyUp(keyCode, event);
- }
- }
-
- protected void onDestroy() {
- mPurchaseDatabase.close();
- mBillingService.unbind();
-
- if (mView != null) {
- mView.onDestroy();
- }
-
- //Log.i(TAG, "on destroy (exit1)");
- System.exit(0);
- }
-
- public static void start_service(String serviceTitle, String serviceDescription,
- String pythonServiceArgument) {
- Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
- String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
- String filesDirectory = PythonActivity.mActivity.mPath.getAbsolutePath();
- serviceIntent.putExtra("androidPrivate", argument);
- serviceIntent.putExtra("androidArgument", filesDirectory);
- serviceIntent.putExtra("pythonHome", argument);
- serviceIntent.putExtra("pythonPath", argument + ":" + filesDirectory + "/lib");
- serviceIntent.putExtra("serviceTitle", serviceTitle);
- serviceIntent.putExtra("serviceDescription", serviceDescription);
- serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
- PythonActivity.mActivity.startService(serviceIntent);
- }
-
- public static void stop_service() {
- Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
- PythonActivity.mActivity.stopService(serviceIntent);
- }
-
- //----------------------------------------------------------------------------
- // Listener interface for onNewIntent
- //
-
- public interface NewIntentListener {
- void onNewIntent(Intent intent);
- }
-
- private List<NewIntentListener> newIntentListeners = null;
-
- public void registerNewIntentListener(NewIntentListener listener) {
- if ( this.newIntentListeners == null )
- this.newIntentListeners = Collections.synchronizedList(new ArrayList<NewIntentListener>());
- this.newIntentListeners.add(listener);
- }
-
- public void unregisterNewIntentListener(NewIntentListener listener) {
- if ( this.newIntentListeners == null )
- return;
- this.newIntentListeners.remove(listener);
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- if ( this.newIntentListeners == null )
- return;
- if ( this.mView != null )
- this.mView.onResume();
- synchronized ( this.newIntentListeners ) {
- Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();
- while ( iterator.hasNext() ) {
- (iterator.next()).onNewIntent(intent);
- }
- }
- }
-
- //----------------------------------------------------------------------------
- // Listener interface for onActivityResult
- //
-
- public interface ActivityResultListener {
- void onActivityResult(int requestCode, int resultCode, Intent data);
- }
-
- private List<ActivityResultListener> activityResultListeners = null;
-
- public void registerActivityResultListener(ActivityResultListener listener) {
- if ( this.activityResultListeners == null )
- this.activityResultListeners = Collections.synchronizedList(new ArrayList<ActivityResultListener>());
- this.activityResultListeners.add(listener);
- }
-
- public void unregisterActivityResultListener(ActivityResultListener listener) {
- if ( this.activityResultListeners == null )
- return;
- this.activityResultListeners.remove(listener);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
- if ( this.activityResultListeners == null )
- return;
- if ( this.mView != null )
- this.mView.onResume();
- synchronized ( this.activityResultListeners ) {
- Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();
- while ( iterator.hasNext() )
- (iterator.next()).onActivityResult(requestCode, resultCode, intent);
- }
- }
-
- //----------------------------------------------------------------------------
- // Billing
- //
- class PythonPurchaseObserver extends PurchaseObserver {
- public PythonPurchaseObserver(Handler handler) {
- super(PythonActivity.this, handler);
- }
-
- @Override
- public void onBillingSupported(boolean supported, String type) {
- if (Consts.DEBUG) {
- Log.i(TAG, "supported: " + supported);
- }
-
- String sup = "1";
- if ( !supported )
- sup = "0";
- if (type == null)
- type = Consts.ITEM_TYPE_INAPP;
-
- // add notification for python message queue
- mActivity.mBillingQueue.add("billingSupported|" + type + "|" + sup);
-
- // for managed items, restore the database
- if ( type == Consts.ITEM_TYPE_INAPP && supported ) {
- restoreDatabase();
- }
- }
-
- @Override
- public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
- int quantity, long purchaseTime, String developerPayload) {
- mActivity.mBillingQueue.add(
- "purchaseStateChange|" + itemId + "|" + purchaseState.toString());
- }
-
- @Override
- public void onRequestPurchaseResponse(RequestPurchase request,
- ResponseCode responseCode) {
- mActivity.mBillingQueue.add(
- "requestPurchaseResponse|" + request.mProductId + "|" + responseCode.toString());
- }
-
- @Override
- public void onRestoreTransactionsResponse(RestoreTransactions request,
- ResponseCode responseCode) {
- if (responseCode == ResponseCode.RESULT_OK) {
- mActivity.mBillingQueue.add("restoreTransaction|ok");
- if (Consts.DEBUG) {
- Log.d(TAG, "completed RestoreTransactions request");
- }
- // Update the shared preferences so that we don't perform
- // a RestoreTransactions again.
- SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
- SharedPreferences.Editor edit = prefs.edit();
- edit.putBoolean(DB_INITIALIZED, true);
- edit.commit();
- } else {
- if (Consts.DEBUG) {
- Log.d(TAG, "RestoreTransactions error: " + responseCode);
- }
-
- mActivity.mBillingQueue.add(
- "restoreTransaction|error|" + responseCode.toString());
- }
- }
- }
-
- /**
- * If the database has not been initialized, we send a
- * RESTORE_TRANSACTIONS request to Android Market to get the list of purchased items
- * for this user. This happens if the application has just been installed
- * or the user wiped data. We do not want to do this on every startup, rather, we want to do
- * only when the database needs to be initialized.
- */
- private void restoreDatabase() {
- SharedPreferences prefs = getPreferences(MODE_PRIVATE);
- boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
- if (!initialized) {
- mBillingService.restoreTransactions();
- }
- }
-
- /** An array of product list entries for the products that can be purchased. */
-
- private enum Managed { MANAGED, UNMANAGED, SUBSCRIPTION }
-
-
- private PythonPurchaseObserver mPythonPurchaseObserver;
- private Handler mBillingHandler;
- private BillingService mBillingService;
- private PurchaseDatabase mPurchaseDatabase;
- private String mPayloadContents;
- public List<String> mBillingQueue;
-
- public void billingServiceStart_() {
- mBillingQueue = new ArrayList<String>();
-
- // Start the billing part
- mPythonPurchaseObserver = new PythonPurchaseObserver(mBillingHandler);
- mBillingService = new BillingService();
- mBillingService.setContext(this);
- mPurchaseDatabase = new PurchaseDatabase(this);
-
- ResponseHandler.register(mPythonPurchaseObserver);
- if (!mBillingService.checkBillingSupported()) {
- //showDialog(DIALOG_CANNOT_CONNECT_ID);
- Log.w(TAG, "NO BILLING SUPPORTED");
- }
- if (!mBillingService.checkBillingSupported(Consts.ITEM_TYPE_SUBSCRIPTION)) {
- //showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
- Log.w(TAG, "NO SUBSCRIPTION SUPPORTED");
- }
- }
-
- public void billingServiceStop_() {
- }
-
- public void billingBuy_(String mSku) {
- Managed mManagedType = Managed.MANAGED;
- if (Consts.DEBUG) {
- Log.d(TAG, "buying sku: " + mSku);
- }
-
- if (mManagedType == Managed.MANAGED) {
- if (!mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_INAPP, mPayloadContents)) {
- Log.w(TAG, "ERROR IN BILLING REQUEST PURCHASE");
- }
- } else if (mManagedType == Managed.SUBSCRIPTION) {
- if (!mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_INAPP, mPayloadContents)) {
- Log.w(TAG, "ERROR IN BILLING REQUEST PURCHASE");
- }
- }
- }
-
- public String billingGetPurchasedItems_() {
- String ownedItems = "";
- Cursor cursor = mPurchaseDatabase.queryAllPurchasedItems();
- if (cursor == null)
- return "";
-
- try {
- int productIdCol = cursor.getColumnIndexOrThrow(
- PurchaseDatabase.PURCHASED_PRODUCT_ID_COL);
- int qtCol = cursor.getColumnIndexOrThrow(
- PurchaseDatabase.PURCHASED_QUANTITY_COL);
- while (cursor.moveToNext()) {
- String productId = cursor.getString(productIdCol);
- String qt = cursor.getString(qtCol);
-
- productId = Security.unobfuscate(this, Configuration.billing_salt, productId);
- if ( productId == null )
- continue;
-
- if ( ownedItems != "" )
- ownedItems += "\n";
- ownedItems += productId + "," + qt;
- }
- } finally {
- cursor.close();
- }
-
- return ownedItems;
- }
-
-
- static void billingServiceStart() {
- mActivity.billingServiceStart_();
- }
-
- static void billingServiceStop() {
- mActivity.billingServiceStop_();
- }
-
- static void billingBuy(String sku) {
- mActivity.billingBuy_(sku);
- }
-
- static String billingGetPurchasedItems() {
- return mActivity.billingGetPurchasedItems_();
- }
-
- static String billingGetPendingMessage() {
- if (mActivity.mBillingQueue.isEmpty())
- return null;
- return mActivity.mBillingQueue.remove(0);
- }
-
-}
-
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java
deleted file mode 100644
index 07f9c8865..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/PythonService.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package org.renpy.android;
-
-import android.os.Build;
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
-import android.app.Service;
-import android.os.IBinder;
-import android.os.Bundle;
-import android.content.Intent;
-import android.content.Context;
-import android.util.Log;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.os.Process;
-
-public class PythonService extends Service implements Runnable {
-
- // Thread for Python code
- private Thread pythonThread = null;
-
- // Python environment variables
- private String androidPrivate;
- private String androidArgument;
- private String pythonHome;
- private String pythonPath;
- // Argument to pass to Python code,
- private String pythonServiceArgument;
- public static Service mService = null;
-
- @Override
- public IBinder onBind(Intent arg0) {
- return null;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (pythonThread != null) {
- Log.v("python service", "service exists, do not start again");
- return START_NOT_STICKY;
- }
-
- Bundle extras = intent.getExtras();
- androidPrivate = extras.getString("androidPrivate");
- // service code is located in service subdir
- androidArgument = extras.getString("androidArgument") + "/service";
- pythonHome = extras.getString("pythonHome");
- pythonPath = extras.getString("pythonPath");
- pythonServiceArgument = extras.getString("pythonServiceArgument");
- String serviceTitle = extras.getString("serviceTitle");
- String serviceDescription = extras.getString("serviceDescription");
-
- pythonThread = new Thread(this);
- pythonThread.start();
-
- Notification notification;
- Context context = getApplicationContext();
- Intent contextIntent = new Intent(context, PythonActivity.class);
- PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
- notification = new Notification(
- context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis());
- try {
- // prevent using NotificationCompat, this saves 100kb on apk
- Method func = notification.getClass().getMethod(
- "setLatestEventInfo", Context.class, CharSequence.class,
- CharSequence.class, PendingIntent.class);
- func.invoke(notification, context, serviceTitle, serviceDescription, pIntent);
- } catch (NoSuchMethodException | IllegalAccessException |
- IllegalArgumentException | InvocationTargetException e) {
- }
- } else {
- Notification.Builder builder = new Notification.Builder(context);
- builder.setContentTitle(serviceTitle);
- builder.setContentText(serviceDescription);
- builder.setContentIntent(pIntent);
- builder.setSmallIcon(context.getApplicationInfo().icon);
- notification = builder.build();
- }
- startForeground(1, notification);
-
- return START_NOT_STICKY;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- pythonThread = null;
- Process.killProcess(Process.myPid());
- }
-
- @Override
- public void run(){
-
- // libraries loading, the same way PythonActivity.run() do
- System.loadLibrary("sdl");
- System.loadLibrary("sdl_image");
- System.loadLibrary("sdl_ttf");
- System.loadLibrary("sdl_mixer");
- System.loadLibrary("python2.7");
- System.loadLibrary("application");
- System.loadLibrary("sdl_main");
-
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_io.so");
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/unicodedata.so");
-
- try {
- System.loadLibrary("sqlite3");
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_sqlite3.so");
- } catch(UnsatisfiedLinkError e) {
- }
-
- try {
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_imaging.so");
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_imagingft.so");
- System.load(getFilesDir() + "/lib/python2.7/lib-dynload/_imagingmath.so");
- } catch(UnsatisfiedLinkError e) {
- }
-
- this.mService = this;
- nativeInitJavaEnv();
- nativeStart(androidPrivate, androidArgument, pythonHome, pythonPath,
- pythonServiceArgument);
- }
-
- // Native part
- public static native void nativeStart(String androidPrivate, String androidArgument,
- String pythonHome, String pythonPath,
- String pythonServiceArgument);
-
- public static native void nativeInitJavaEnv();
-
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/RenPySound.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/RenPySound.java
deleted file mode 100644
index c0d1ea59d..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/RenPySound.java
+++ /dev/null
@@ -1,337 +0,0 @@
-package org.renpy.android;
-
-import android.media.MediaPlayer;
-import android.util.Log;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.HashMap;
-
-public class RenPySound {
-
- private static class Channel implements MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener {
-
- // MediaPlayers for the currently playing and queued up
- // sounds.
- MediaPlayer player[];
-
- // Filenames for the currently playing and queued up sounds.
- String filename[];
-
- // Is the corresponding player prepareD?
- boolean prepared[];
-
- // The volume for the left and right channel.
- double volume;
- double secondary_volume;
- double left_volume;
- double right_volume;
-
- Channel() {
- player = new MediaPlayer[2];
- filename = new String[2];
- prepared = new boolean[2];
-
- player[0] = new MediaPlayer();
- player[1] = new MediaPlayer();
-
- volume = 1.0;
- secondary_volume = 1.0;
- left_volume = 1.0;
- right_volume = 1.0;
- }
-
- /**
- * Queue up a sound file.
- */
- synchronized void queue(String fn, String real_fn, long base, long length) {
- MediaPlayer mp = player[1];
-
- mp.reset();
-
- try {
- FileInputStream f = new FileInputStream(real_fn);
-
- if (length >= 0) {
- mp.setDataSource(f.getFD(), base, length);
- } else {
- mp.setDataSource(f.getFD());
- }
-
- mp.setOnCompletionListener(this);
- mp.setOnPreparedListener(this);
-
- mp.prepareAsync();
-
- f.close();
- } catch (IOException e) {
- Log.w("RenPySound", e);
- return;
- }
-
-
- filename[1] = fn;
-
- }
-
- /**
- * Play the queued-up sound.
- */
- synchronized void play() {
- MediaPlayer tmp;
-
- player[0].reset();
-
- tmp = player[0];
- player[0] = player[1];
- player[1] = tmp;
-
- filename[0] = filename[1];
- filename[1] = null;
-
- prepared[0] = prepared[1];
- prepared[1] = false;
-
- if (filename[0] != null) {
- updateVolume();
-
- if (prepared[0]) {
- player[0].start();
- }
- }
- }
-
- /**
- * Seek to the position specified on this channel
- */
-
- synchronized void seek(float position) {
- if (prepared[0]){
- player[0].seekTo((int)position*1000);
- }
- }
-
- /**
- * Stop playback on this channel.
- */
- synchronized void stop() {
- player[0].reset();
- player[1].reset();
-
- filename[0] = null;
- filename[1] = null;
-
- prepared[0] = false;
- prepared[1] = false;
- }
-
- /**
- * Dequeue the queued file on this channel.
- */
- synchronized void dequeue() {
- player[1].reset();
- filename[1] = null;
- prepared[1] = false;
- }
-
-
- /**
- * Updates the volume on the playing file.
- */
- synchronized void updateVolume() {
- player[0].setVolume((float) (volume * secondary_volume * left_volume),
- (float) (volume * secondary_volume * right_volume));
- }
-
-
- /**
- * Called to update the volume.
- */
- synchronized void set_volume(float v) {
- volume = v;
- updateVolume();
- }
-
- /**
- * Called to update the volume.
- */
- synchronized void set_secondary_volume(float v) {
- secondary_volume = v;
- updateVolume();
- }
-
- /**
- * Called to update the pan. (By setting up the fractional
- * volume.)
- */
- synchronized void set_pan(float pan) {
- if (pan < 0) {
- left_volume = 1.0;
- right_volume = 1.0 + pan;
- } else {
- left_volume = 1.0 - pan;
- right_volume = 1.0;
- }
-
- updateVolume();
- }
-
- synchronized void pause() {
- if (filename[0] != null) {
- player[0].pause();
- }
- }
-
- synchronized void unpause() {
- if (filename[0] != null) {
- player[0].start();
- }
- }
-
- synchronized int get_pos(){
- if (prepared[0]) {
- return player[0].getCurrentPosition();
- }
- return 0;
- }
-
- synchronized int get_length(){
- if (prepared[0]) {
- return player[0].getDuration();
- }
- return 1;
- }
-
- synchronized public void onPrepared(MediaPlayer mp) {
- if (mp == player[0]) {
- prepared[0] = true;
- player[0].start();
- }
-
- if (mp == player[1]) {
- prepared[1] = true;
- }
- }
-
- /**
- * Called on completion.
- */
- synchronized public void onCompletion(MediaPlayer mp) {
- if (mp == player[0]) {
- play();
- }
- }
-
-
- }
-
- // A map from channel number to channel object.
- static HashMap<Integer, Channel> channels = new HashMap<Integer, Channel>();
-
- /**
- * Gets the Channel object for the numbered channel, returning a
- * new channel object as necessary.
- */
- static Channel getChannel(int num) {
- Channel rv = channels.get(num);
-
- if (rv == null) {
- rv = new Channel();
- channels.put(num, rv);
- }
-
- return rv;
- }
-
- static void queue(int channel, String filename, String real_fn, long base, long length) {
- Channel c = getChannel(channel);
- c.queue(filename, real_fn, base, length);
- if (c.filename[0] == null) {
- c.play();
- }
-
- }
-
- static void play(int channel,
- String filename,
- String real_fn,
- long base,
- long length) {
-
- Channel c = getChannel(channel);
- c.queue(filename, real_fn, base, length);
- c.play();
- }
-
- static void seek(int channel, float position){
- Channel c = getChannel(channel);
- c.seek(position);
- }
-
- static void stop(int channel) {
- Channel c = getChannel(channel);
- c.stop();
- }
-
- static void dequeue(int channel) {
- Channel c = getChannel(channel);
- c.dequeue();
- }
-
-
- static String playing_name(int channel) {
- Channel c = getChannel(channel);
- if (c.filename[0] == null) {
- return "";
- }
-
- return c.filename[0];
- }
-
- static int queue_depth(int channel) {
- Channel c = getChannel(channel);
-
- if (c.filename[0] == null) return 0;
- if (c.filename[1] == null) return 1;
- return 2;
- }
-
- static void set_volume(int channel, float v) {
- Channel c = getChannel(channel);
- c.set_volume(v);
- }
-
- static void set_secondary_volume(int channel, float v) {
- Channel c = getChannel(channel);
- c.set_secondary_volume(v);
- }
-
- static void set_pan(int channel, float pan) {
- Channel c = getChannel(channel);
- c.set_pan(pan);
- }
-
- static void pause(int channel) {
- Channel c = getChannel(channel);
- c.pause();
- }
-
- static void unpause(int channel) {
- Channel c = getChannel(channel);
- c.unpause();
- }
-
- static int get_pos(int channel){
- Channel c = getChannel(channel);
- return c.get_pos();
- }
-
- static int get_length(int channel){
- Channel c = getChannel(channel);
- return c.get_length();
- }
-
- static {
- new MediaPlayer();
- }
-
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ResourceManager.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ResourceManager.java
deleted file mode 100644
index f80fe843e..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/ResourceManager.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * This class takes care of managing resources for us. In our code, we
- * can't use R, since the name of the package containing R will
- * change. (This same code is used in both org.renpy.android and
- * org.renpy.pygame.) So this is the next best thing.
- */
-
-package org.renpy.android;
-
-import android.app.Activity;
-import android.content.res.Resources;
-import android.view.View;
-
-public class ResourceManager {
-
- private Activity act;
- private Resources res;
-
- public ResourceManager(Activity activity) {
- act = activity;
- res = act.getResources();
- }
-
- public int getIdentifier(String name, String kind) {
- return res.getIdentifier(name, kind, act.getPackageName());
- }
-
- public String getString(String name) {
-
- try {
- return res.getString(getIdentifier(name, "string"));
- } catch (Exception e) {
- return null;
- }
- }
-
- public View inflateView(String name) {
- int id = getIdentifier(name, "layout");
- return act.getLayoutInflater().inflate(id, null);
- }
-
- public View getViewById(View v, String name) {
- int id = getIdentifier(name, "id");
- return v.findViewById(id);
- }
-
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java
deleted file mode 100644
index d88783b48..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/SDLSurfaceView.java
+++ /dev/null
@@ -1,1431 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// This string is autogenerated by ChangeAppSettings.sh, do not change spaces amount
-package org.renpy.android;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGL11;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-import javax.microedition.khronos.opengles.GL10;
-import android.opengl.GLES20;
-import android.opengl.Matrix;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.MotionEvent;
-import android.view.KeyEvent;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.CompletionInfo;
-//API 11 only
-//import android.view.inputmethod.CorrectionInfo;
-import android.opengl.GLSurfaceView;
-import android.net.Uri;
-import android.os.PowerManager;
-import android.os.Handler;
-import android.content.pm.PackageManager;
-import android.content.pm.ApplicationInfo;
-import android.graphics.PixelFormat;
-
-import java.io.IOException;
-import java.io.InputStream;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.opengl.GLUtils;
-import java.lang.Math;
-import java.nio.FloatBuffer;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import android.graphics.Color;
-import android.content.res.Resources;
-
-
-public class SDLSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
- static private final String TAG = "SDLSurface";
- static private final boolean DEBUG = false;
- static private final String mVertexShader =
- "uniform mat4 uMVPMatrix;\n" +
- "attribute vec4 aPosition;\n" +
- "attribute vec2 aTextureCoord;\n" +
- "varying vec2 vTextureCoord;\n" +
- "void main() {\n" +
- " gl_Position = uMVPMatrix * aPosition;\n" +
- " vTextureCoord = aTextureCoord;\n" +
- "}\n";
-
- static private final String mFragmentShader =
- "precision mediump float;\n" +
- "varying vec2 vTextureCoord;\n" +
- "uniform sampler2D sTexture;\n" +
- "void main() {\n" +
- " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
-
- "}\n";
- private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
-
- public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
- mRedSize = r;
- mGreenSize = g;
- mBlueSize = b;
- mAlphaSize = a;
- mDepthSize = depth;
- mStencilSize = stencil;
- }
-
- /* This EGL config specification is used to specify 2.0 rendering.
- * We use a minimum size of 4 bits for red/green/blue, but will
- * perform actual matching in chooseConfig() below.
- */
- private static int EGL_OPENGL_ES2_BIT = 4;
- private static int[] s_configAttribs2 =
- {
- EGL10.EGL_RED_SIZE, 4,
- EGL10.EGL_GREEN_SIZE, 4,
- EGL10.EGL_BLUE_SIZE, 4,
- EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL10.EGL_NONE
- };
-
- public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
-
- /* Get the number of minimally matching EGL configurations
- */
- int[] num_config = new int[1];
- egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
-
- int numConfigs = num_config[0];
-
- if (numConfigs <= 0) {
- throw new IllegalArgumentException("No configs match configSpec");
- }
-
- /* Allocate then read the array of minimally matching EGL configs
- */
- EGLConfig[] configs = new EGLConfig[numConfigs];
- egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
-
- /* Now return the "best" one
- */
- //printConfigs(egl, display, configs);
- return chooseConfig(egl, display, configs);
- }
-
- public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
- EGLConfig[] configs) {
- for(EGLConfig config : configs) {
- int d = findConfigAttrib(egl, display, config,
- EGL10.EGL_DEPTH_SIZE, 0);
- int s = findConfigAttrib(egl, display, config,
- EGL10.EGL_STENCIL_SIZE, 0);
-
- // We need at least mDepthSize and mStencilSize bits
- if (d < mDepthSize || s < mStencilSize)
- continue;
-
- // We want an *exact* match for red/green/blue/alpha
- int r = findConfigAttrib(egl, display, config,
- EGL10.EGL_RED_SIZE, 0);
- int g = findConfigAttrib(egl, display, config,
- EGL10.EGL_GREEN_SIZE, 0);
- int b = findConfigAttrib(egl, display, config,
- EGL10.EGL_BLUE_SIZE, 0);
- int a = findConfigAttrib(egl, display, config,
- EGL10.EGL_ALPHA_SIZE, 0);
-
- if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
- return config;
- }
- return null;
- }
-
- private int findConfigAttrib(EGL10 egl, EGLDisplay display,
- EGLConfig config, int attribute, int defaultValue) {
-
- if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
- return mValue[0];
- }
- return defaultValue;
- }
-
- private void printConfigs(EGL10 egl, EGLDisplay display,
- EGLConfig[] configs) {
- int numConfigs = configs.length;
- Log.w(TAG, String.format("%d configurations", numConfigs));
- for (int i = 0; i < numConfigs; i++) {
- Log.w(TAG, String.format("Configuration %d:\n", i));
- printConfig(egl, display, configs[i]);
- }
- }
-
- private void printConfig(EGL10 egl, EGLDisplay display,
- EGLConfig config) {
- int[] attributes = {
- EGL10.EGL_BUFFER_SIZE,
- EGL10.EGL_ALPHA_SIZE,
- EGL10.EGL_BLUE_SIZE,
- EGL10.EGL_GREEN_SIZE,
- EGL10.EGL_RED_SIZE,
- EGL10.EGL_DEPTH_SIZE,
- EGL10.EGL_STENCIL_SIZE,
- EGL10.EGL_CONFIG_CAVEAT,
- EGL10.EGL_CONFIG_ID,
- EGL10.EGL_LEVEL,
- EGL10.EGL_MAX_PBUFFER_HEIGHT,
- EGL10.EGL_MAX_PBUFFER_PIXELS,
- EGL10.EGL_MAX_PBUFFER_WIDTH,
- EGL10.EGL_NATIVE_RENDERABLE,
- EGL10.EGL_NATIVE_VISUAL_ID,
- EGL10.EGL_NATIVE_VISUAL_TYPE,
- 0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
- EGL10.EGL_SAMPLES,
- EGL10.EGL_SAMPLE_BUFFERS,
- EGL10.EGL_SURFACE_TYPE,
- EGL10.EGL_TRANSPARENT_TYPE,
- EGL10.EGL_TRANSPARENT_RED_VALUE,
- EGL10.EGL_TRANSPARENT_GREEN_VALUE,
- EGL10.EGL_TRANSPARENT_BLUE_VALUE,
- 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
- 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
- 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
- 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
- EGL10.EGL_LUMINANCE_SIZE,
- EGL10.EGL_ALPHA_MASK_SIZE,
- EGL10.EGL_COLOR_BUFFER_TYPE,
- EGL10.EGL_RENDERABLE_TYPE,
- 0x3042 // EGL10.EGL_CONFORMANT
- };
- String[] names = {
- "EGL_BUFFER_SIZE",
- "EGL_ALPHA_SIZE",
- "EGL_BLUE_SIZE",
- "EGL_GREEN_SIZE",
- "EGL_RED_SIZE",
- "EGL_DEPTH_SIZE",
- "EGL_STENCIL_SIZE",
- "EGL_CONFIG_CAVEAT",
- "EGL_CONFIG_ID",
- "EGL_LEVEL",
- "EGL_MAX_PBUFFER_HEIGHT",
- "EGL_MAX_PBUFFER_PIXELS",
- "EGL_MAX_PBUFFER_WIDTH",
- "EGL_NATIVE_RENDERABLE",
- "EGL_NATIVE_VISUAL_ID",
- "EGL_NATIVE_VISUAL_TYPE",
- "EGL_PRESERVED_RESOURCES",
- "EGL_SAMPLES",
- "EGL_SAMPLE_BUFFERS",
- "EGL_SURFACE_TYPE",
- "EGL_TRANSPARENT_TYPE",
- "EGL_TRANSPARENT_RED_VALUE",
- "EGL_TRANSPARENT_GREEN_VALUE",
- "EGL_TRANSPARENT_BLUE_VALUE",
- "EGL_BIND_TO_TEXTURE_RGB",
- "EGL_BIND_TO_TEXTURE_RGBA",
- "EGL_MIN_SWAP_INTERVAL",
- "EGL_MAX_SWAP_INTERVAL",
- "EGL_LUMINANCE_SIZE",
- "EGL_ALPHA_MASK_SIZE",
- "EGL_COLOR_BUFFER_TYPE",
- "EGL_RENDERABLE_TYPE",
- "EGL_CONFORMANT"
- };
- int[] value = new int[1];
- for (int i = 0; i < attributes.length; i++) {
- int attribute = attributes[i];
- String name = names[i];
- if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
- Log.w(TAG, String.format(" %s: %d\n", name, value[0]));
- } else {
- // Log.w(TAG, String.format(" %s: failed\n", name));
- while (egl.eglGetError() != EGL10.EGL_SUCCESS);
- }
- }
- }
-
- // Subclasses can adjust these values:
- protected int mRedSize;
- protected int mGreenSize;
- protected int mBlueSize;
- protected int mAlphaSize;
- protected int mDepthSize;
- protected int mStencilSize;
- private int[] mValue = new int[1];
- }
-
- public interface OnInterceptTouchListener {
- boolean onTouch(MotionEvent ev);
- }
-
- private OnInterceptTouchListener mOnInterceptTouchListener = null;
-
- // The activity we're a part of.
- private static PythonActivity mActivity;
-
- // Have we started yet?
- public boolean mStarted = false;
-
- // what is the textinput type while calling the keyboard
- public int inputType = EditorInfo.TYPE_CLASS_TEXT;
-
- // Is Python ready to receive input events?
- static boolean mInputActivated = false;
-
- // The number of times we should clear the screen after swap.
- private int mClears = 2;
-
- // Has the display been changed?
- private boolean mChanged = false;
-
- // Are we running yet?
- private boolean mRunning = false;
-
- // The EGL used by our thread.
- private EGL10 mEgl = null;
-
- // The EGL Display used.
- private EGLDisplay mEglDisplay = null;
-
- // The EGL Context used.
- private EGLContext mEglContext = null;
-
- // The EGL Surface used.
- private EGLSurface mEglSurface = null;
-
- // The EGL Config used.
- private EGLConfig mEglConfig = null;
-
- // The user program is not participating in the pause protocol.
- static int PAUSE_NOT_PARTICIPATING = 0;
-
- // A pause has not been requested by the OS.
- static int PAUSE_NONE = 1;
-
- // A pause has been requested by Android, but the user program has
- // not bothered responding yet.
- static int PAUSE_REQUEST = 2;
-
- // The user program is waiting in waitForResume.
- static int PAUSE_WAIT_FOR_RESUME = 3;
-
- static int PAUSE_STOP_REQUEST = 4;
- static int PAUSE_STOP_ACK = 5;
-
- // This stores the state of the pause system.
- static int mPause = PAUSE_NOT_PARTICIPATING;
-
- private PowerManager.WakeLock wakeLock;
-
- // This stores the length of the text in pridiction/swype buffer
- private int mDelLen = 0;
-
- // The width and height. (This should be set at startup time -
- // these values just prevent segfaults and divide by zero, etc.)
- int mWidth = 100;
- int mHeight = 100;
-
- // The name of the directory where the context stores its files.
- String mFilesDirectory = null;
-
- // The value of the argument passed in.
- String mArgument = null;
-
- // The resource manager we use.
- ResourceManager mResourceManager;
-
- // Access to our meta-data
- private ApplicationInfo ai;
-
- // Text before/after cursor
- static String mTbf = "";
- static String mTaf = "";
-
- public static void updateTextFromCursor(String bef, String aft){
- mTbf = bef;
- mTaf = aft;
- if (DEBUG) Log.d(TAG, String.format("mtbf: %s mtaf:%s <<<<<<<<<", mTbf, mTaf));
- }
-
- // Our own view
- static SDLSurfaceView instance = null;
-
- public SDLSurfaceView(PythonActivity act, String argument) {
- super(act);
- SDLSurfaceView.instance = this;
-
- mActivity = act;
- mResourceManager = new ResourceManager(act);
-
- SurfaceHolder holder = getHolder();
- holder.addCallback(this);
- holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
-
- mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
- mArgument = argument;
-
- PowerManager pm = (PowerManager) act.getSystemService(Context.POWER_SERVICE);
-
- wakeLock = null;
- try {
- ai = act.getPackageManager().getApplicationInfo(
- act.getPackageName(), PackageManager.GET_META_DATA);
- if ( (Integer)ai.metaData.get("wakelock") == 1 ) {
- wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
- }
- } catch (PackageManager.NameNotFoundException e) {
- }
-
- if ( ai.metaData.getInt("surface.transparent") != 0 ) {
- Log.d(TAG, "Surface will be transparent.");
- setZOrderOnTop(true);
- getHolder().setFormat(PixelFormat.TRANSPARENT);
- } else {
- Log.i(TAG, "Surface will NOT be transparent");
- }
-
- }
-
-
- /**
- * The user program should call this frequently to check if a
- * pause has been requested by android. If this ever returns
- * true, the user program should clean up and call waitForResume.
- */
- public int checkPause() {
- if (mPause == PAUSE_NOT_PARTICIPATING) {
- mPause = PAUSE_NONE;
- }
-
- if (mPause == PAUSE_REQUEST) {
- return 1;
- } else {
- return 0;
- }
- }
-
-
- /**
- * The user program should call this quickly after checkPause
- * returns true. This causes the android application to sleep,
- * waiting for resume. While sleeping, it should not have any
- * activity. (Notably, it should stop all timers.)
- *
- * While we're waiting in this method, android is allowed to
- * kill us to reclaim memory, without any further warning.
- */
- public void waitForResume() {
- synchronized (this) {
- mPause = PAUSE_WAIT_FOR_RESUME;
-
- // Notify any threads waiting in onPause.
- this.notifyAll();
-
- while (mPause == PAUSE_WAIT_FOR_RESUME) {
- try {
- this.wait();
- } catch (InterruptedException e) {
- }
- }
- }
- setOpenFile();
- }
-
- /**
- * if the activity was called with a file parameter, put it in the
- * 'PYTHON_OPENFILE' env var
- */
- public static void setOpenFile(){
- final android.content.Intent intent = mActivity.getIntent();
- if (intent != null) {
- final android.net.Uri data = intent.getData();
- if (data != null && data.getEncodedPath() != null){
- nativeSetEnv("PYTHON_OPENFILE", data.getEncodedPath());
- }
- }
- }
-
-
- public void closeSoftKeyboard(){
- // close the IME overlay(keyboard)
- Hardware.hideKeyboard();
- }
-
- /**
- * Inform the view that the activity is paused. The owner of this view must
- * call this method when the activity is paused. Calling this method will
- * pause the rendering thread.
- * Must not be called before a renderer has been set.
- */
- public void onPause() {
-
- this.closeSoftKeyboard();
- synchronized (this) {
- if (mPause == PAUSE_NONE) {
- mPause = PAUSE_REQUEST;
-
- while (mPause == PAUSE_REQUEST) {
- try {
- this.wait();
- } catch (InterruptedException e) {
- // pass
- }
- }
- }
- }
-
- if ( wakeLock != null )
- wakeLock.release();
-
- }
-
- /**
- * Inform the view that the activity is resumed. The owner of this view must
- * call this method when the activity is resumed. Calling this method will
- * recreate the OpenGL display and resume the rendering
- * thread.
- * Must not be called before a renderer has been set.
- */
- public void onResume() {
- synchronized (this) {
- if (mPause == PAUSE_WAIT_FOR_RESUME) {
- mPause = PAUSE_NONE;
- this.notifyAll();
- }
- }
- if ( wakeLock != null )
- wakeLock.acquire();
- }
-
- public void onDestroy() {
- Log.w(TAG, "onDestroy() called");
- this.closeSoftKeyboard();
- synchronized (this) {
- this.notifyAll();
-
- if ( mPause == PAUSE_STOP_ACK ) {
- Log.d(TAG, "onDestroy() app already leaved.");
- return;
- }
-
-
- // application didn't leave, give 10s before closing.
- // hopefully, this could be enough for launching the on_stop() trigger within the app.
- mPause = PAUSE_STOP_REQUEST;
- int i = 50;
-
- Log.d(TAG, "onDestroy() stop requested, wait for an event from the app");
- for (; i >= 0 && mPause == PAUSE_STOP_REQUEST; i--) {
- try {
- this.wait(200);
- } catch (InterruptedException e) {
- break;
- }
- }
- Log.d(TAG, "onDestroy() stop finished waiting.");
- }
- }
-
- static int checkStop() {
- if (mPause == PAUSE_STOP_REQUEST)
- return 1;
- return 0;
- }
-
- static void ackStop() {
- Log.d(TAG, "ackStop() notify");
- synchronized (instance) {
- mPause = PAUSE_STOP_ACK;
- instance.notifyAll();
- }
- }
-
-
- /**
- * This method is part of the SurfaceHolder.Callback interface, and is
- * not normally called or subclassed by clients of GLSurfaceView.
- */
- public void surfaceCreated(SurfaceHolder holder) {
- if (DEBUG) Log.d(TAG, "surfaceCreated()");
- synchronized (this) {
- if (!mStarted) {
- this.notifyAll();
- }
- }
- }
-
- /**
- * This method is part of the SurfaceHolder.Callback interface, and is
- * not normally called or subclassed by clients of GLSurfaceView.
- */
- public void surfaceDestroyed(SurfaceHolder holder) {
- if (DEBUG) Log.d(TAG, "surfaceDestroyed()");
- }
-
- /**
- * This method is part of the SurfaceHolder.Callback interface, and is
- * not normally called or subclassed by clients of GLSurfaceView.
- */
- public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
- if (DEBUG) Log.i(TAG, String.format("surfaceChanged() fmt=%d size=%dx%d", format, w, h));
-
- mWidth = w;
- mHeight = h;
-
- if (mActivity.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE &&
- mWidth < mHeight) {
- return;
- }
-
- if (mActivity.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT &&
- mWidth > mHeight) {
- return;
- }
-
- if (!mRunning) {
- mRunning = true;
- new Thread(this).start();
- } else {
- mChanged = true;
- if (mStarted) {
- nativeExpose();
- }
- }
- }
-
-
- public void run() {
- mEgl = (EGL10) EGLContext.getEGL();
-
- mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
-
- int[] version = new int[2];
- mEgl.eglInitialize(mEglDisplay, version);
-
- // Pick an appropriate config. We just take the first config
- // the system offers to us, because anything more complicated
- // than that stands a really good chance of not working.
- int[] configSpec = {
- // RENDERABLE_TYPE = OpenGL ES is the default.
- EGL10.EGL_NONE
- };
-
- EGLConfig[] configs = new EGLConfig[1];
- int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- int[] num_config = new int[1];
- int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
-
- // Create an opengl es 2.0 surface
- Log.i(TAG, "Choose egl configuration");
- int configToTest = 0;
- boolean configFound = false;
-
- while (true) {
- try {
- if (configToTest == 0) {
- Log.i(TAG, "Try to use graphics config R8G8B8A8S8");
- ConfigChooser chooser = new ConfigChooser(
- // rgba
- 8, 8, 8, 8,
- // depth
- ai.metaData.getInt("surface.depth", 0),
- // stencil
- ai.metaData.getInt("surface.stencil", 8));
- mEglConfig = chooser.chooseConfig(mEgl, mEglDisplay);
- } else if (configToTest == 1) {
- Log.i(TAG, "Try to use graphics config R5G6B5S8");
- ConfigChooser chooser = new ConfigChooser(
- // rgba
- 5, 6, 5, 0,
- // depth
- ai.metaData.getInt("surface.depth", 0),
- // stencil
- ai.metaData.getInt("surface.stencil", 8));
- mEglConfig = chooser.chooseConfig(mEgl, mEglDisplay);
- } else {
- Log.e(TAG, "Unable to find a correct surface for this device !");
- break;
- }
-
- } catch (IllegalArgumentException e) {
- configToTest++;
- continue;
- }
-
- if (DEBUG) Log.d(TAG, "Create egl context");
- mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
- if (mEglContext == null) {
- Log.w(TAG, "Unable to create egl context with this configuration, try the next one.");
- configToTest++;
- continue;
- }
-
- Log.w(TAG, "Create egl surface");
- if (!createSurface()) {
- Log.w(TAG, "Unable to create egl surface with this configuration, try the next one.");
- configToTest++;
- continue;
- }
-
- configFound = true;
- break;
- }
-
- if (!configFound) {
- System.exit(0);
- return;
- }
-
- if (DEBUG) Log.d(TAG, "Done egl");
- waitForStart();
-
- nativeResize(mWidth, mHeight);
- nativeInitJavaCallbacks();
- nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory);
- nativeSetEnv("ANDROID_UNPACK", mFilesDirectory);
- nativeSetEnv("ANDROID_ARGUMENT", mArgument);
- nativeSetEnv("ANDROID_APP_PATH", mArgument);
- nativeSetEnv("PYTHONOPTIMIZE", "2");
- nativeSetEnv("PYTHONHOME", mFilesDirectory);
- nativeSetEnv("PYTHONPATH", mArgument + ":" + mFilesDirectory + "/lib");
-
- // XXX Using SetOpenFile make a crash in nativeSetEnv. I don't
- // understand why, maybe because the method is static or something.
- // Anyway, if you remove that part of the code, ensure the Laucher
- // (ProjectChooser) is still working.
- final android.content.Intent intent = mActivity.getIntent();
- if (intent != null) {
- final android.net.Uri data = intent.getData();
- if (data != null && data.getEncodedPath() != null)
- nativeSetEnv("PYTHON_OPENFILE", data.getEncodedPath());
- }
-
- nativeSetMultitouchUsed();
- nativeInit();
-
- mPause = PAUSE_STOP_ACK;
-
- //Log.i(TAG, "End of native init, stop everything (exit0)");
- System.exit(0);
- }
-
- private void waitForStart() {
-
- int presplashId = mResourceManager.getIdentifier("presplash", "drawable");
- InputStream is = mActivity.getResources().openRawResource(presplashId);
-
- Bitmap bitmap = null;
- try {
- bitmap = BitmapFactory.decodeStream(is);
- bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false);
- } finally {
- try {
- is.close();
- } catch (IOException e) { }
- }
-
- mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length
- * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
- mTriangleVertices.put(mTriangleVerticesData).position(0);
-
- mProgram = createProgram(mVertexShader, mFragmentShader);
- if (mProgram == 0) {
- synchronized (this) {
- while (!mStarted) {
- try {
- this.wait(250);
- } catch (InterruptedException e) {
- continue;
- }
- }
- }
- return;
- }
-
- maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
- checkGlError("glGetAttribLocation aPosition");
- if (maPositionHandle == -1) {
- throw new RuntimeException("Could not get attrib location for aPosition");
- }
- maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
- checkGlError("glGetAttribLocation aTextureCoord");
- if (maTextureHandle == -1) {
- throw new RuntimeException("Could not get attrib location for aTextureCoord");
- }
-
- muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
- checkGlError("glGetUniformLocation uMVPMatrix");
- if (muMVPMatrixHandle == -1) {
- throw new RuntimeException("Could not get attrib location for uMVPMatrix");
- }
-
- // Create our texture. This has to be done each time the
- // surface is created.
-
- int[] textures = new int[1];
- GLES20.glGenTextures(1, textures, 0);
-
- mTextureID = textures[0];
- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);
-
- GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
- GLES20.GL_LINEAR);
- GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
- GLES20.GL_TEXTURE_MAG_FILTER,
- GLES20.GL_LINEAR);
-
- GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
- GLES20.GL_CLAMP_TO_EDGE);
- GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
- GLES20.GL_CLAMP_TO_EDGE);
-
- GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
-
- Matrix.setLookAtM(mVMatrix, 0, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
-
- GLES20.glViewport(0, 0, mWidth, mHeight);
-
- if (bitmap != null) {
- float mx = ((float)mWidth / bitmap.getWidth()) / 2.0f;
- float my = ((float)mHeight / bitmap.getHeight()) / 2.0f;
- Matrix.orthoM(mProjMatrix, 0, -mx, mx, my, -my, 0, 10);
- int value = bitmap.getPixel(0, 0);
- Color color = new Color();
- GLES20.glClearColor(
- (float)color.red(value) / 255.0f,
- (float)color.green(value) / 255.0f,
- (float)color.blue(value) / 255.0f,
- 0.0f);
- } else {
- Matrix.orthoM(mProjMatrix, 0, -1, 1, -1, 1, 0, 10);
- GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- }
-
- GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
- GLES20.glUseProgram(mProgram);
- checkGlError("glUseProgram");
-
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);
-
- mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
- GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
- checkGlError("glVertexAttribPointer maPosition");
- mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
- GLES20.glEnableVertexAttribArray(maPositionHandle);
- checkGlError("glEnableVertexAttribArray maPositionHandle");
- GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
- checkGlError("glVertexAttribPointer maTextureHandle");
- GLES20.glEnableVertexAttribArray(maTextureHandle);
- checkGlError("glEnableVertexAttribArray maTextureHandle");
-
- Matrix.setRotateM(mMMatrix, 0, 0, 0, 0, 1.0f);
- Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
- Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
-
- GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
-
- // Ensure that, even with double buffer, or if we lost one buffer (like
- // BufferQueue has been abandoned!), it will work.
- for ( int i = 0; i < 2; i++ ) {
- GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
- GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
- checkGlError("glDrawArrays");
- swapBuffers();
- }
-
- // Wait to be notified it's okay to start Python.
- synchronized (this) {
- while (!mStarted) {
- // Draw & Flip.
- GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
- GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
- swapBuffers();
-
- try {
- this.wait(250);
- } catch (InterruptedException e) {
- }
- }
- }
-
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
-
- // Delete texture.
- GLES20.glDeleteTextures(1, textures, 0);
- if (bitmap != null)
- bitmap.recycle();
-
- // Delete program
- GLES20.glDeleteProgram(mProgram);
- }
-
-
- public void start() {
- this.setFocusableInTouchMode(true);
- this.setFocusable(true);
- this.requestFocus();
-
- synchronized (this) {
- mStarted = true;
- this.notify();
- }
-
- }
-
- public boolean createSurface() {
- mChanged = false;
-
- // Destroy the old surface.
- if (mEglSurface != null) {
-
- /*
- * Unbind and destroy the old EGL surface, if
- * there is one.
- */
- mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
- EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
- mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
- }
-
- // Create a new surface.
- mEglSurface = mEgl.eglCreateWindowSurface(
- mEglDisplay, mEglConfig, getHolder(), null);
-
- // Make the new surface current.
- boolean rv = mEgl.eglMakeCurrent(
- mEglDisplay, mEglSurface, mEglSurface, mEglContext);
- if (!rv) {
- mEglSurface = null;
- return false;
- }
-
- if (mStarted) {
- if (mInputActivated)
- nativeResizeEvent(mWidth, mHeight);
- else
- nativeResize(mWidth, mHeight);
-
- // If the size didn't change, kivy might no rerender the scene. Force it.
- nativeExpose();
- }
-
- return true;
-
- }
-
- public int swapBuffers() {
- // If the display has been changed, then disregard all the
- // rendering we've done to it, and make a new surface.
- //
- // Otherwise, swap the buffers.
- if (mChanged) {
- createSurface();
- mClears = 2;
- return 0;
-
- } else {
- mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
- if (mClears-- > 0)
- GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
- return 1;
- }
-
- }
-
- private static final int INVALID_POINTER_ID = -1;
- private int mActivePointerId = INVALID_POINTER_ID;
-
- public void setInterceptTouchListener(OnInterceptTouchListener listener) {
- this.mOnInterceptTouchListener = listener;
- }
-
- @Override
- public boolean onTouchEvent(final MotionEvent event) {
-
- if (mInputActivated == false)
- return true;
-
- if (mOnInterceptTouchListener != null)
- if (mOnInterceptTouchListener.onTouch(event))
- return false;
-
- int action = event.getAction() & MotionEvent.ACTION_MASK;
- int sdlAction = -1;
- int pointerId = -1;
- int pointerIndex = -1;
-
- switch ( action ) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_POINTER_DOWN:
- sdlAction = 0;
- break;
- case MotionEvent.ACTION_MOVE:
- sdlAction = 2;
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_POINTER_UP:
- sdlAction = 1;
- break;
- }
-
- // http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
- switch ( action & MotionEvent.ACTION_MASK ) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- case MotionEvent.ACTION_UP:
- pointerIndex = event.findPointerIndex(mActivePointerId);
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_POINTER_UP:
- pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- if ( action == MotionEvent.ACTION_POINTER_UP ) {
- pointerId = event.getPointerId(pointerIndex);
- if ( pointerId == mActivePointerId )
- mActivePointerId = event.getPointerId(pointerIndex == 0 ? 1 : 0);
- }
- break;
- }
-
- if ( sdlAction >= 0 ) {
-
- for ( int i = 0; i < event.getPointerCount(); i++ ) {
-
- if ( pointerIndex == -1 || pointerIndex == i ) {
-
- /**
- Log.i("python", String.format("mouse id=%d action=%d x=%f y=%f",
- event.getPointerId(i),
- sdlAction,
- event.getX(i),
- event.getY(i)
- ));
- **/
- SDLSurfaceView.nativeMouse(
- (int)event.getX(i),
- (int)event.getY(i),
- sdlAction,
- event.getPointerId(i),
- (int)(event.getPressure(i) * 1000.0),
- (int)(event.getSize(i) * 1000.0));
- }
-
- }
-
- }
-
- return true;
- };
-
- @Override
- public boolean onKeyDown(int keyCode, final KeyEvent event) {
- if (DEBUG) Log.d(TAG, String.format("onKeyDown() keyCode=%d", keyCode));
- if (mDelLen > 0){
- mDelLen = 0;
- return true;
- }
- if (mInputActivated && nativeKey(keyCode, 1, event.getUnicodeChar())) {
- return true;
- } else {
- return super.onKeyDown(keyCode, event);
- }
- }
-
- @Override
- public boolean onKeyUp(int keyCode, final KeyEvent event) {
- if (DEBUG) Log.d(TAG, String.format("onKeyUp() keyCode=%d", keyCode));
- if (mDelLen > 0){
- mDelLen = 0;
- return true;
- }
- if (mInputActivated && nativeKey(keyCode, 0, event.getUnicodeChar())) {
- return true;
- } else {
- return super.onKeyUp(keyCode, event);
- }
- }
-
- @Override
- public boolean onKeyMultiple(int keyCode, int count, KeyEvent event){
- String keys = event.getCharacters();
- if (DEBUG)
- Log.d(TAG, String.format(
- "onKeyMultiple() keyCode=%d count=%d, keys=%s", keyCode, count, keys));
- char[] keysBuffer = new char[keys.length()];
- if (mDelLen > 0){
- mDelLen = 0;
- return true;
- }
- if (keyCode == 0){
- // FIXME: here is hardcoded value of "q" key
- // on hacker's keyboard. It is passed to
- // nativeKey function to get it worked if
- // we get 9 and some non-ascii characters
- // but it my cause some odd behaviour
- keyCode = 45;
- }
- if (mInputActivated && event.getAction() == KeyEvent.ACTION_MULTIPLE){
- String message = String.format("INSERTN:%s", keys);
- dispatchCommand(message);
- return true;
- }else {
- return super.onKeyMultiple(keyCode, count, event);
- }
- }
-
- @Override
- public boolean onKeyPreIme(int keyCode, final KeyEvent event){
- if (DEBUG) Log.d(TAG, String.format("onKeyPreIme() keyCode=%d", keyCode));
- if (mInputActivated){
- switch (event.getAction()) {
- case KeyEvent.ACTION_DOWN:
- return onKeyDown(keyCode, event);
- case KeyEvent.ACTION_UP:
- return onKeyUp(keyCode, event);
- case KeyEvent.ACTION_MULTIPLE:
- return onKeyMultiple(
- keyCode,
- event.getRepeatCount(),
- event);
- }
- return false;
- }
- return super.onKeyPreIme(keyCode, event);
- }
-
- public void delayed_message(String message, int delay){
- Handler handler = new Handler();
- final String msg = message;
- final int d = delay;
- handler.postDelayed(new Runnable(){
- @Override
- public void run(){
- dispatchCommand(msg);
- }
- }, d);
- }
-
- public void dispatchCommand(String message){
-
- int delay = 0;
- while (message.length() > 50){
- delayed_message(message.substring(0, 50), delay);
- delay += 100;
- message = String.format("INSERTN:%s", message.substring(50, message.length()));
- if (message.length() <= 50){
- delayed_message(message, delay+50);
- return;
- }
- }
-
- if (DEBUG) Log.d(TAG, String.format("dispatch :%s", message));
- int keyCode = 45;
- //send control sequence start
- nativeKey(keyCode, 1, 1);
- nativeKey(keyCode, 0, 1);
-
- for(int i=0; i < message.length(); i++){
- //Calls both up/down events to emulate key pressing
- nativeKey(keyCode, 1, (int) message.charAt(i));
- nativeKey(keyCode, 0, (int) message.charAt(i));
- }
-
- //send control sequence end \x02
- nativeKey(keyCode, 1, 2);
- nativeKey(keyCode, 0, 2);
-
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- outAttrs.inputType = inputType;
- // ask IME to avoid taking full screen on landscape mode
- outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI;
-
- // add a listener for the layout chnages to the IME view
- final android.view.View activityRootView = mActivity.getWindow().getDecorView();
- activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- //send control sequence start /x04 == kayboard layout changed
- nativeKey(45, 1, 4);
- nativeKey(45, 0, 4);
- }
- });
- return new BaseInputConnection(this, false){
-
- private void deleteLastText(){
- // send back space keys
- if (DEBUG) Log.i("Python:", String.format("delete text%s", mDelLen));
-
- if (mDelLen == 0){
- return;
- }
-
- String message = String.format("DEL:%s", mDelLen);
- dispatchCommand(message);
- }
-
- @Override
- public boolean endBatchEdit() {
- if (DEBUG) Log.i("Python:", "endBatchEdit");
- return super.endBatchEdit();
- }
-
- @Override
- public boolean beginBatchEdit() {
- if (DEBUG) Log.i("Python:", "beginBatchEdit");
- return super.beginBatchEdit();
- }
-
- @Override
- public boolean commitCompletion(CompletionInfo text){
- if (DEBUG) Log.i("Python:", String.format("Commit Completion %s", text));
- return super.commitCompletion(text);
- }
-
- /*API11 only
- @Override
- public boolean commitCorrection(CorrectionInfo correctionInfo){
- if (DEBUG) Log.i("Python:", String.format("Commit Correction"));
- return super.commitCorrection(correctionInfo);
- }*/
-
- @Override
- public boolean commitText(CharSequence text, int newCursorPosition) {
- // some code which takes the input and manipulates it and calls editText.getText().replace() afterwards
- this.deleteLastText();
- if (DEBUG) Log.i("Python:", String.format("Commit Text %s", text));
- mDelLen = 0;
- return super.commitText(text, newCursorPosition);
- }
-
- @Override
- public boolean sendKeyEvent(KeyEvent event){
- if (DEBUG) Log.d("Python:", String.format("sendKeyEvent %s", event.getKeyCode()));
- return super.sendKeyEvent(event);
- }
-
- @Override
- public boolean setComposingRegion(int start, int end){
- if (DEBUG) Log.d("Python:", String.format("Set Composing Region %s %s", start, end));
- finishComposingText();
- if (start < 0 || start > end)
- return true;
- //dispatchCommand(String.format("SEL:%s,%s,%s", mTbf.length(), start, end));
- return true;
- //return super.setComposingRegion(start, end);
- }
-
- @Override
- public boolean setComposingText(CharSequence text,
- int newCursorPosition){
- this.deleteLastText();
- if (DEBUG) Log.i("Python:", String.format("set Composing Text %s", text));
- // send text
- String message = String.format("INSERT:%s", text);
- dispatchCommand(message);
- // store len to be deleted for next time
- mDelLen = text.length();
- return super.setComposingText(text, newCursorPosition);
- }
-
- @Override
- public boolean finishComposingText(){
- if (DEBUG) Log.i("Python:", String.format("finish Composing Text"));
- return super.finishComposingText();
- }
-
- @Override
- public boolean deleteSurroundingText (int beforeLength, int afterLength){
- if (DEBUG) Log.d("Python:", String.format("delete surrounding Text %s %s", beforeLength, afterLength));
- // move cursor to place from where to start deleting
- // send right arrow keys
- for (int i = 0; i < afterLength; i++){
- nativeKey(45, 1, 39);
- nativeKey(45, 0, 39);
- }
- // remove text before cursor
- mDelLen = beforeLength + afterLength;
- this.deleteLastText();
- mDelLen = 0;
-
- return super.deleteSurroundingText(beforeLength, afterLength);
- }
-
- @Override
- public ExtractedText getExtractedText (ExtractedTextRequest request, int flags){
- if (DEBUG) Log.d("Python:", String.format("getExtractedText %s %s", request.describeContents(), flags));
- return super.getExtractedText(request, flags);
- }
-
- @Override
- public CharSequence getSelectedText(int flags){
- if (DEBUG) Log.d("Python:", String.format("getSelectedText %s", flags));
- return super.getSelectedText(flags);
- }
-
- @Override
- public CharSequence getTextBeforeCursor(int n, int flags){
- if (DEBUG) Log.d("Python:", String.format("getTextBeforeCursor %s %s", n, flags));
- /*int len = mTbf.length();
- int len_n = Math.min(len, n);
- int start = Math.max(len - n, 0);
- String tbf = mTbf.substring(start, start + len_n);
- return tbf;*/
- return super.getTextBeforeCursor(n, flags);
- }
-
- @Override
- public CharSequence getTextAfterCursor(int n, int flags){
- if (DEBUG) Log.d("Python:", String.format("getTextAfterCursor %s %s", n, flags));
- Log.d("Python:", String.format("TextAfterCursor %s", mTaf));
- //return mTaf.substring(0, Math.min(mTaf.length(), n));
- return super.getTextAfterCursor(n, flags);
- }
-
- @Override
- public boolean setSelection(int start, int end){
- if (DEBUG) Log.d("Python:", String.format("Set Selection %s %s", start, end));
- return super.setSelection(start, end);
- }
- };
- }
-
- static void activateInput() {
- mInputActivated = true;
- }
-
- static void openUrl(String url) {
- Log.i("python", "Opening URL: " + url);
-
- Intent i = new Intent(Intent.ACTION_VIEW);
- i.setData(Uri.parse(url));
- mActivity.startActivity(i);
- }
-
- // Taken from the "GLES20TriangleRenderer" in Android SDK
- private int loadShader(int shaderType, String source) {
- int shader = GLES20.glCreateShader(shaderType);
- if (shader != 0) {
- GLES20.glShaderSource(shader, source);
- GLES20.glCompileShader(shader);
- int[] compiled = new int[1];
- GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
- if (compiled[0] == 0) {
- Log.e(TAG, "Could not compile shader " + shaderType + ":");
- Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
- GLES20.glDeleteShader(shader);
- shader = 0;
- }
- }
- return shader;
- }
-
- private int createProgram(String vertexSource, String fragmentSource) {
- int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
- if (vertexShader == 0) {
- return 0;
- }
-
- int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
- if (pixelShader == 0) {
- return 0;
- }
-
- int program = GLES20.glCreateProgram();
- if (program != 0) {
- GLES20.glAttachShader(program, vertexShader);
- checkGlError("glAttachShader");
- GLES20.glAttachShader(program, pixelShader);
- checkGlError("glAttachShader");
- GLES20.glLinkProgram(program);
- int[] linkStatus = new int[1];
- GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
- if (linkStatus[0] != GLES20.GL_TRUE) {
- Log.e(TAG, "Could not link program: ");
- Log.e(TAG, GLES20.glGetProgramInfoLog(program));
- GLES20.glDeleteProgram(program);
- program = 0;
- }
- }
- return program;
- }
-
- private void checkGlError(String op) {
- int error;
- while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
- Log.e(TAG, op + ": glError " + error);
- //throw new RuntimeException(op + ": glError " + error);
- }
- }
- private static final int FLOAT_SIZE_BYTES = 4;
- private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
- private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
- private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
- private final float[] mTriangleVerticesData = {
- // X, Y, Z, U, V
- -0.5f, -0.5f, 0, 1.0f, 0.0f,
- 0.5f, -0.5f, 0, 0.0f, 0.0f,
- 0.5f, 0.5f, 0, 0.0f, 1.0f,
- -0.5f, -0.5f, 0, 1.0f, 0.0f,
- 0.5f, 0.5f, 0, 0.0f, 1.0f,
- -0.5f, 0.5f, 0, 1.0f, 1.0f,
- };
-
- private FloatBuffer mTriangleVertices;
-
- private float[] mMVPMatrix = new float[16];
- private float[] mProjMatrix = new float[16];
- private float[] mMMatrix = new float[16];
- private float[] mVMatrix = new float[16];
- private int mProgram;
- private int mTextureID;
- private int muMVPMatrixHandle;
- private int maPositionHandle;
- private int maTextureHandle;
-
- // Native part
-
- public static native void nativeSetEnv(String name, String value);
- public static native void nativeInit();
-
- public static native void nativeMouse( int x, int y, int action, int pointerId, int pressure, int radius );
- public static native boolean nativeKey(int keyCode, int down, int unicode);
- public static native void nativeSetMouseUsed();
- public static native void nativeSetMultitouchUsed();
-
- public native void nativeResize(int width, int height);
- public native void nativeResizeEvent(int width, int height);
- public native void nativeExpose();
- public native void nativeInitJavaCallbacks();
-
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/BillingReceiver.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/BillingReceiver.java
deleted file mode 100644
index 11e8cbf8a..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/BillingReceiver.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.renpy.android.billing;
-
-import org.renpy.android.billing.Consts.ResponseCode;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * This class implements the broadcast receiver for in-app billing. All asynchronous messages from
- * Android Market come to this app through this receiver. This class forwards all
- * messages to the {@link BillingService}, which can start background threads,
- * if necessary, to process the messages. This class runs on the UI thread and must not do any
- * network I/O, database updates, or any tasks that might take a long time to complete.
- * It also must not start a background thread because that may be killed as soon as
- * {@link #onReceive(Context, Intent)} returns.
- *
- * You should modify and obfuscate this code before using it.
- */
-public class BillingReceiver extends BroadcastReceiver {
- private static final String TAG = "BillingReceiver";
-
- /**
- * This is the entry point for all asynchronous messages sent from Android Market to
- * the application. This method forwards the messages on to the
- * {@link BillingService}, which handles the communication back to Android Market.
- * The {@link BillingService} also reports state changes back to the application through
- * the {@link ResponseHandler}.
- */
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
- String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
- String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
- purchaseStateChanged(context, signedData, signature);
- } else if (Consts.ACTION_NOTIFY.equals(action)) {
- String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
- if (Consts.DEBUG) {
- Log.i(TAG, "notifyId: " + notifyId);
- }
- notify(context, notifyId);
- } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
- long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
- int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
- ResponseCode.RESULT_ERROR.ordinal());
- checkResponseCode(context, requestId, responseCodeIndex);
- } else {
- Log.w(TAG, "unexpected action: " + action);
- }
- }
-
- /**
- * This is called when Android Market sends information about a purchase state
- * change. The signedData parameter is a plaintext JSON string that is
- * signed by the server with the developer's private key. The signature
- * for the signed data is passed in the signature parameter.
- * @param context the context
- * @param signedData the (unencrypted) JSON string
- * @param signature the signature for the signedData
- */
- private void purchaseStateChanged(Context context, String signedData, String signature) {
- Intent intent = new Intent(Consts.ACTION_PURCHASE_STATE_CHANGED);
- intent.setClass(context, BillingService.class);
- intent.putExtra(Consts.INAPP_SIGNED_DATA, signedData);
- intent.putExtra(Consts.INAPP_SIGNATURE, signature);
- context.startService(intent);
- }
-
- /**
- * This is called when Android Market sends a "notify" message indicating that transaction
- * information is available. The request includes a nonce (random number used once) that
- * we generate and Android Market signs and sends back to us with the purchase state and
- * other transaction details. This BroadcastReceiver cannot bind to the
- * MarketBillingService directly so it starts the {@link BillingService}, which does the
- * actual work of sending the message.
- *
- * @param context the context
- * @param notifyId the notification ID
- */
- private void notify(Context context, String notifyId) {
- Intent intent = new Intent(Consts.ACTION_GET_PURCHASE_INFORMATION);
- intent.setClass(context, BillingService.class);
- intent.putExtra(Consts.NOTIFICATION_ID, notifyId);
- context.startService(intent);
- }
-
- /**
- * This is called when Android Market sends a server response code. The BillingService can
- * then report the status of the response if desired.
- *
- * @param context the context
- * @param requestId the request ID that corresponds to a previous request
- * @param responseCodeIndex the ResponseCode ordinal value for the request
- */
- private void checkResponseCode(Context context, long requestId, int responseCodeIndex) {
- Intent intent = new Intent(Consts.ACTION_RESPONSE_CODE);
- intent.setClass(context, BillingService.class);
- intent.putExtra(Consts.INAPP_REQUEST_ID, requestId);
- intent.putExtra(Consts.INAPP_RESPONSE_CODE, responseCodeIndex);
- context.startService(intent);
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/BillingService.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/BillingService.java
deleted file mode 100644
index 473338821..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/BillingService.java
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.renpy.android.billing;
-
-import com.android.vending.billing.IMarketBillingService;
-
-import org.renpy.android.billing.Consts.PurchaseState;
-import org.renpy.android.billing.Consts.ResponseCode;
-import org.renpy.android.billing.Security.VerifiedPurchase;
-
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-
-
-/**
- * This class sends messages to Android Market on behalf of the application by
- * connecting (binding) to the MarketBillingService. The application
- * creates an instance of this class and invokes billing requests through this service.
- *
- * The {@link BillingReceiver} class starts this service to process commands
- * that it receives from Android Market.
- *
- * You should modify and obfuscate this code before using it.
- */
-public class BillingService extends Service implements ServiceConnection {
- private static final String TAG = "BillingService";
-
- /** The service connection to the remote MarketBillingService. */
- private static IMarketBillingService mService;
-
- /**
- * The list of requests that are pending while we are waiting for the
- * connection to the MarketBillingService to be established.
- */
- private static LinkedList<BillingRequest> mPendingRequests = new LinkedList<BillingRequest>();
-
- /**
- * The list of requests that we have sent to Android Market but for which we have
- * not yet received a response code. The HashMap is indexed by the
- * request Id that each request receives when it executes.
- */
- private static HashMap<Long, BillingRequest> mSentRequests =
- new HashMap<Long, BillingRequest>();
-
- /**
- * The base class for all requests that use the MarketBillingService.
- * Each derived class overrides the run() method to call the appropriate
- * service interface. If we are already connected to the MarketBillingService,
- * then we call the run() method directly. Otherwise, we bind
- * to the service and save the request on a queue to be run later when
- * the service is connected.
- */
- abstract class BillingRequest {
- private final int mStartId;
- protected long mRequestId;
-
- public BillingRequest(int startId) {
- mStartId = startId;
- }
-
- public int getStartId() {
- return mStartId;
- }
-
- /**
- * Run the request, starting the connection if necessary.
- * @return true if the request was executed or queued; false if there
- * was an error starting the connection
- */
- public boolean runRequest() {
- if (runIfConnected()) {
- return true;
- }
-
- if (bindToMarketBillingService()) {
- // Add a pending request to run when the service is connected.
- mPendingRequests.add(this);
- return true;
- }
- return false;
- }
-
- /**
- * Try running the request directly if the service is already connected.
- * @return true if the request ran successfully; false if the service
- * is not connected or there was an error when trying to use it
- */
- public boolean runIfConnected() {
- if (Consts.DEBUG) {
- Log.d(TAG, getClass().getSimpleName());
- }
- if (mService != null) {
- try {
- mRequestId = run();
- if (Consts.DEBUG) {
- Log.d(TAG, "request id: " + mRequestId);
- }
- if (mRequestId >= 0) {
- mSentRequests.put(mRequestId, this);
- }
- return true;
- } catch (RemoteException e) {
- onRemoteException(e);
- }
- }
- return false;
- }
-
- /**
- * Called when a remote exception occurs while trying to execute the
- * {@link #run()} method. The derived class can override this to
- * execute exception-handling code.
- * @param e the exception
- */
- protected void onRemoteException(RemoteException e) {
- Log.w(TAG, "remote billing service crashed");
- mService = null;
- }
-
- /**
- * The derived class must implement this method.
- * @throws RemoteException
- */
- abstract protected long run() throws RemoteException;
-
- /**
- * This is called when Android Market sends a response code for this
- * request.
- * @param responseCode the response code
- */
- protected void responseCodeReceived(ResponseCode responseCode) {
- }
-
- protected Bundle makeRequestBundle(String method) {
- Bundle request = new Bundle();
- request.putString(Consts.BILLING_REQUEST_METHOD, method);
- request.putInt(Consts.BILLING_REQUEST_API_VERSION, 2);
- request.putString(Consts.BILLING_REQUEST_PACKAGE_NAME, getPackageName());
- return request;
- }
-
- protected void logResponseCode(String method, Bundle response) {
- ResponseCode responseCode = ResponseCode.valueOf(
- response.getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE));
- if (Consts.DEBUG) {
- Log.e(TAG, method + " received " + responseCode.toString());
- }
- }
- }
-
- /**
- * Wrapper class that checks if in-app billing is supported.
- *
- * Note: Support for subscriptions implies support for one-time purchases. However, the opposite
- * is not true.
- *
- * Developers may want to perform two checks if both one-time and subscription products are
- * available.
- */
- class CheckBillingSupported extends BillingRequest {
- public String mProductType = null;
-
- /** Legacy contrustor
- *
- * This constructor is provided for legacy purposes. Assumes the calling application will
- * not be using any features not present in API v1, such as subscriptions.
- */
- @Deprecated
- public CheckBillingSupported() {
- // This object is never created as a side effect of starting this
- // service so we pass -1 as the startId to indicate that we should
- // not stop this service after executing this request.
- super(-1);
- }
-
- /** Constructor
- *
- * Note: Support for subscriptions implies support for one-time purchases. However, the
- * opposite is not true.
- *
- * Developers may want to perform two checks if both one-time and subscription products are
- * available.
- *
- * @pram itemType Either Consts.ITEM_TYPE_INAPP or Consts.ITEM_TYPE_SUBSCRIPTION, indicating
- * the type of item support is being checked for.
- */
- public CheckBillingSupported(String itemType) {
- super(-1);
- mProductType = itemType;
- }
-
- @Override
- protected long run() throws RemoteException {
- Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
- if (mProductType != null) {
- request.putString(Consts.BILLING_REQUEST_ITEM_TYPE, mProductType);
- }
- Bundle response = mService.sendBillingRequest(request);
- int responseCode = response.getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE);
- if (Consts.DEBUG) {
- Log.i(TAG, "CheckBillingSupported response code: " +
- ResponseCode.valueOf(responseCode));
- }
- boolean billingSupported = (responseCode == ResponseCode.RESULT_OK.ordinal());
- ResponseHandler.checkBillingSupportedResponse(billingSupported, mProductType);
- return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
- }
- }
-
- /**
- * Wrapper class that requests a purchase.
- */
- public class RequestPurchase extends BillingRequest {
- public final String mProductId;
- public final String mDeveloperPayload;
- public final String mProductType;
-
- /** Legacy constructor
- *
- * @param itemId The ID of the item to be purchased. Will be assumed to be a one-time
- * purchase.
- */
- @Deprecated
- public RequestPurchase(String itemId) {
- this(itemId, null, null);
- }
-
- /** Legacy constructor
- *
- * @param itemId The ID of the item to be purchased. Will be assumed to be a one-time
- * purchase.
- * @param developerPayload Optional data.
- */
- @Deprecated
- public RequestPurchase(String itemId, String developerPayload) {
- this(itemId, null, developerPayload);
- }
-
- /** Constructor
- *
- * @param itemId The ID of the item to be purchased. Will be assumed to be a one-time
- * purchase.
- * @param itemType Either Consts.ITEM_TYPE_INAPP or Consts.ITEM_TYPE_SUBSCRIPTION,
- * indicating the type of item type support is being checked for.
- * @param developerPayload Optional data.
- */
- public RequestPurchase(String itemId, String itemType, String developerPayload) {
- // This object is never created as a side effect of starting this
- // service so we pass -1 as the startId to indicate that we should
- // not stop this service after executing this request.
- super(-1);
- mProductId = itemId;
- mDeveloperPayload = developerPayload;
- mProductType = itemType;
- }
-
- @Override
- protected long run() throws RemoteException {
- Bundle request = makeRequestBundle("REQUEST_PURCHASE");
- request.putString(Consts.BILLING_REQUEST_ITEM_ID, mProductId);
- request.putString(Consts.BILLING_REQUEST_ITEM_TYPE, mProductType);
- // Note that the developer payload is optional.
- if (mDeveloperPayload != null) {
- request.putString(Consts.BILLING_REQUEST_DEVELOPER_PAYLOAD, mDeveloperPayload);
- }
- Bundle response = mService.sendBillingRequest(request);
- PendingIntent pendingIntent
- = response.getParcelable(Consts.BILLING_RESPONSE_PURCHASE_INTENT);
- if (pendingIntent == null) {
- Log.e(TAG, "Error with requestPurchase");
- return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
- }
-
- Intent intent = new Intent();
- ResponseHandler.buyPageIntentResponse(pendingIntent, intent);
- return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
- Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
- }
-
- @Override
- protected void responseCodeReceived(ResponseCode responseCode) {
- ResponseHandler.responseCodeReceived(BillingService.this, this, responseCode);
- }
- }
-
- /**
- * Wrapper class that confirms a list of notifications to the server.
- */
- public class ConfirmNotifications extends BillingRequest {
- final String[] mNotifyIds;
-
- public ConfirmNotifications(int startId, String[] notifyIds) {
- super(startId);
- mNotifyIds = notifyIds;
- }
-
- @Override
- protected long run() throws RemoteException {
- Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
- request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds);
- Bundle response = mService.sendBillingRequest(request);
- logResponseCode("confirmNotifications", response);
- return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
- Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
- }
- }
-
- /**
- * Wrapper class that sends a GET_PURCHASE_INFORMATION message to the server.
- */
- public class GetPurchaseInformation extends BillingRequest {
- long mNonce;
- final String[] mNotifyIds;
-
- public GetPurchaseInformation(int startId, String[] notifyIds) {
- super(startId);
- mNotifyIds = notifyIds;
- }
-
- @Override
- protected long run() throws RemoteException {
- mNonce = Security.generateNonce();
-
- Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
- request.putLong(Consts.BILLING_REQUEST_NONCE, mNonce);
- request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds);
- Bundle response = mService.sendBillingRequest(request);
- logResponseCode("getPurchaseInformation", response);
- return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
- Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
- }
-
- @Override
- protected void onRemoteException(RemoteException e) {
- super.onRemoteException(e);
- Security.removeNonce(mNonce);
- }
- }
-
- /**
- * Wrapper class that sends a RESTORE_TRANSACTIONS message to the server.
- */
- public class RestoreTransactions extends BillingRequest {
- long mNonce;
-
- public RestoreTransactions() {
- // This object is never created as a side effect of starting this
- // service so we pass -1 as the startId to indicate that we should
- // not stop this service after executing this request.
- super(-1);
- }
-
- @Override
- protected long run() throws RemoteException {
- mNonce = Security.generateNonce();
-
- Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
- request.putLong(Consts.BILLING_REQUEST_NONCE, mNonce);
- Bundle response = mService.sendBillingRequest(request);
- logResponseCode("restoreTransactions", response);
- return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
- Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
- }
-
- @Override
- protected void onRemoteException(RemoteException e) {
- super.onRemoteException(e);
- Security.removeNonce(mNonce);
- }
-
- @Override
- protected void responseCodeReceived(ResponseCode responseCode) {
- ResponseHandler.responseCodeReceived(BillingService.this, this, responseCode);
- }
- }
-
- public BillingService() {
- super();
- }
-
- public void setContext(Context context) {
- attachBaseContext(context);
- }
-
- /**
- * We don't support binding to this service, only starting the service.
- */
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- handleCommand(intent, startId);
- }
-
- /**
- * The {@link BillingReceiver} sends messages to this service using intents.
- * Each intent has an action and some extra arguments specific to that action.
- * @param intent the intent containing one of the supported actions
- * @param startId an identifier for the invocation instance of this service
- */
- public void handleCommand(Intent intent, int startId) {
- String action = intent.getAction();
- if (Consts.DEBUG) {
- Log.i(TAG, "handleCommand() action: " + action);
- }
- if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) {
- String[] notifyIds = intent.getStringArrayExtra(Consts.NOTIFICATION_ID);
- confirmNotifications(startId, notifyIds);
- } else if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) {
- String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
- getPurchaseInformation(startId, new String[] { notifyId });
- } else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
- String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
- String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
- purchaseStateChanged(startId, signedData, signature);
- } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
- long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
- int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
- ResponseCode.RESULT_ERROR.ordinal());
- ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex);
- checkResponseCode(requestId, responseCode);
- }
- }
-
- /**
- * Binds to the MarketBillingService and returns true if the bind
- * succeeded.
- * @return true if the bind succeeded; false otherwise
- */
- private boolean bindToMarketBillingService() {
- try {
- if (Consts.DEBUG) {
- Log.i(TAG, "binding to Market billing service");
- }
- boolean bindResult = bindService(
- new Intent(Consts.MARKET_BILLING_SERVICE_ACTION),
- this, // ServiceConnection.
- Context.BIND_AUTO_CREATE);
-
- if (bindResult) {
- return true;
- } else {
- Log.e(TAG, "Could not bind to service.");
- }
- } catch (SecurityException e) {
- Log.e(TAG, "Security exception: " + e);
- }
- return false;
- }
-
- /**
- * Checks if in-app billing is supported. Assumes this is a one-time purchase.
- *
- * @return true if supported; false otherwise
- */
- @Deprecated
- public boolean checkBillingSupported() {
- return new CheckBillingSupported().runRequest();
- }
-
- /**
- * Checks if in-app billing is supported.
- * @pram itemType Either Consts.ITEM_TYPE_INAPP or Consts.ITEM_TYPE_SUBSCRIPTION, indicating the
- * type of item support is being checked for.
- * @return true if supported; false otherwise
- */
- public boolean checkBillingSupported(String itemType) {
- return new CheckBillingSupported(itemType).runRequest();
- }
-
- /**
- * Requests that the given item be offered to the user for purchase. When
- * the purchase succeeds (or is canceled) the {@link BillingReceiver}
- * receives an intent with the action {@link Consts#ACTION_NOTIFY}.
- * Returns false if there was an error trying to connect to Android Market.
- * @param productId an identifier for the item being offered for purchase
- * @param itemType Either Consts.ITEM_TYPE_INAPP or Consts.ITEM_TYPE_SUBSCRIPTION, indicating
- * the type of item type support is being checked for.
- * @param developerPayload a payload that is associated with a given
- * purchase, if null, no payload is sent
- * @return false if there was an error connecting to Android Market
- */
- public boolean requestPurchase(String productId, String itemType, String developerPayload) {
- return new RequestPurchase(productId, itemType, developerPayload).runRequest();
- }
-
- /**
- * Requests transaction information for all managed items. Call this only when the
- * application is first installed or after a database wipe. Do NOT call this
- * every time the application starts up.
- * @return false if there was an error connecting to Android Market
- */
- public boolean restoreTransactions() {
- return new RestoreTransactions().runRequest();
- }
-
- /**
- * Confirms receipt of a purchase state change. Each {@code notifyId} is
- * an opaque identifier that came from the server. This method sends those
- * identifiers back to the MarketBillingService, which ACKs them to the
- * server. Returns false if there was an error trying to connect to the
- * MarketBillingService.
- * @param startId an identifier for the invocation instance of this service
- * @param notifyIds a list of opaque identifiers associated with purchase
- * state changes.
- * @return false if there was an error connecting to Market
- */
- private boolean confirmNotifications(int startId, String[] notifyIds) {
- return new ConfirmNotifications(startId, notifyIds).runRequest();
- }
-
- /**
- * Gets the purchase information. This message includes a list of
- * notification IDs sent to us by Android Market, which we include in
- * our request. The server responds with the purchase information,
- * encoded as a JSON string, and sends that to the {@link BillingReceiver}
- * in an intent with the action {@link Consts#ACTION_PURCHASE_STATE_CHANGED}.
- * Returns false if there was an error trying to connect to the MarketBillingService.
- *
- * @param startId an identifier for the invocation instance of this service
- * @param notifyIds a list of opaque identifiers associated with purchase
- * state changes
- * @return false if there was an error connecting to Android Market
- */
- private boolean getPurchaseInformation(int startId, String[] notifyIds) {
- return new GetPurchaseInformation(startId, notifyIds).runRequest();
- }
-
- /**
- * Verifies that the data was signed with the given signature, and calls
- * {@link ResponseHandler#purchaseResponse(Context, PurchaseState, String, String, long)}
- * for each verified purchase.
- * @param startId an identifier for the invocation instance of this service
- * @param signedData the signed JSON string (signed, not encrypted)
- * @param signature the signature for the data, signed with the private key
- */
- private void purchaseStateChanged(int startId, String signedData, String signature) {
- ArrayList<Security.VerifiedPurchase> purchases;
- purchases = Security.verifyPurchase(signedData, signature);
- if (purchases == null) {
- return;
- }
-
- if (Consts.DEBUG) {
- Log.d(TAG, "purchaseStateChanged: " + signedData);
- }
-
- ArrayList<String> notifyList = new ArrayList<String>();
- for (VerifiedPurchase vp : purchases) {
- if (vp.notificationId != null) {
- notifyList.add(vp.notificationId);
- }
- ResponseHandler.purchaseResponse(this, vp.purchaseState, vp.productId,
- vp.orderId, vp.purchaseTime, vp.developerPayload,
- vp.purchaseToken, vp.packageName);
- }
- if (!notifyList.isEmpty()) {
- String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
- confirmNotifications(startId, notifyIds);
- }
- }
-
- /**
- * This is called when we receive a response code from Android Market for a request
- * that we made. This is used for reporting various errors and for
- * acknowledging that an order was sent to the server. This is NOT used
- * for any purchase state changes. All purchase state changes are received
- * in the {@link BillingReceiver} and passed to this service, where they are
- * handled in {@link #purchaseStateChanged(int, String, String)}.
- * @param requestId a number that identifies a request, assigned at the
- * time the request was made to Android Market
- * @param responseCode a response code from Android Market to indicate the state
- * of the request
- */
- private void checkResponseCode(long requestId, ResponseCode responseCode) {
- BillingRequest request = mSentRequests.get(requestId);
- if (request != null) {
- if (Consts.DEBUG) {
- Log.d(TAG, request.getClass().getSimpleName() + ": " + responseCode);
- }
- request.responseCodeReceived(responseCode);
- }
- mSentRequests.remove(requestId);
- }
-
- /**
- * Runs any pending requests that are waiting for a connection to the
- * service to be established. This runs in the main UI thread.
- */
- private void runPendingRequests() {
- int maxStartId = -1;
- BillingRequest request;
- while ((request = mPendingRequests.peek()) != null) {
- if (request.runIfConnected()) {
- // Remove the request
- mPendingRequests.remove();
-
- // Remember the largest startId, which is the most recent
- // request to start this service.
- if (maxStartId < request.getStartId()) {
- maxStartId = request.getStartId();
- }
- } else {
- // The service crashed, so restart it. Note that this leaves
- // the current request on the queue.
- bindToMarketBillingService();
- return;
- }
- }
-
- // If we get here then all the requests ran successfully. If maxStartId
- // is not -1, then one of the requests started the service, so we can
- // stop it now.
- if (maxStartId >= 0) {
- if (Consts.DEBUG) {
- Log.i(TAG, "stopping service, startId: " + maxStartId);
- }
- stopSelf(maxStartId);
- }
- }
-
- /**
- * This is called when we are connected to the MarketBillingService.
- * This runs in the main UI thread.
- */
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (Consts.DEBUG)
- Log.d(TAG, "Billing service connected");
- mService = IMarketBillingService.Stub.asInterface(service);
- runPendingRequests();
- }
-
- /**
- * This is called when we are disconnected from the MarketBillingService.
- */
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (Consts.DEBUG)
- Log.w(TAG, "Billing service disconnected");
- mService = null;
- }
-
- /**
- * Unbinds from the MarketBillingService. Call this when the application
- * terminates to avoid leaking a ServiceConnection.
- */
- public void unbind() {
- try {
- unbindService(this);
- } catch (IllegalArgumentException e) {
- // This might happen if the service was disconnected
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/Consts.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/Consts.java
deleted file mode 100644
index b04b1a9a6..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/Consts.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.renpy.android.billing;
-
-/**
- * This class holds global constants that are used throughout the application
- * to support in-app billing.
- */
-public class Consts {
- // The response codes for a request, defined by Android Market.
- public enum ResponseCode {
- RESULT_OK,
- RESULT_USER_CANCELED,
- RESULT_SERVICE_UNAVAILABLE,
- RESULT_BILLING_UNAVAILABLE,
- RESULT_ITEM_UNAVAILABLE,
- RESULT_DEVELOPER_ERROR,
- RESULT_ERROR;
-
- // Converts from an ordinal value to the ResponseCode
- public static ResponseCode valueOf(int index) {
- ResponseCode[] values = ResponseCode.values();
- if (index < 0 || index >= values.length) {
- return RESULT_ERROR;
- }
- return values[index];
- }
- }
-
- // The possible states of an in-app purchase, as defined by Android Market.
- public enum PurchaseState {
- // Responses to requestPurchase or restoreTransactions.
- PURCHASED, // User was charged for the order.
- CANCELED, // The charge failed on the server.
- REFUNDED; // User received a refund for the order.
-
- // Converts from an ordinal value to the PurchaseState
- public static PurchaseState valueOf(int index) {
- PurchaseState[] values = PurchaseState.values();
- if (index < 0 || index >= values.length) {
- return CANCELED;
- }
- return values[index];
- }
- }
-
- /** This is the action we use to bind to the MarketBillingService. */
- public static final String MARKET_BILLING_SERVICE_ACTION =
- "com.android.vending.billing.MarketBillingService.BIND";
-
- // Intent actions that we send from the BillingReceiver to the
- // BillingService. Defined by this application.
- public static final String ACTION_CONFIRM_NOTIFICATION =
- "com.example.subscriptions.CONFIRM_NOTIFICATION";
- public static final String ACTION_GET_PURCHASE_INFORMATION =
- "com.example.subscriptions.GET_PURCHASE_INFORMATION";
- public static final String ACTION_RESTORE_TRANSACTIONS =
- "com.example.subscriptions.RESTORE_TRANSACTIONS";
-
- // Intent actions that we receive in the BillingReceiver from Market.
- // These are defined by Market and cannot be changed.
- public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY";
- public static final String ACTION_RESPONSE_CODE =
- "com.android.vending.billing.RESPONSE_CODE";
- public static final String ACTION_PURCHASE_STATE_CHANGED =
- "com.android.vending.billing.PURCHASE_STATE_CHANGED";
-
- // These are the names of the extras that are passed in an intent from
- // Market to this application and cannot be changed.
- public static final String NOTIFICATION_ID = "notification_id";
- public static final String INAPP_SIGNED_DATA = "inapp_signed_data";
- public static final String INAPP_SIGNATURE = "inapp_signature";
- public static final String INAPP_REQUEST_ID = "request_id";
- public static final String INAPP_RESPONSE_CODE = "response_code";
-
- // These are the names of the fields in the request bundle.
- public static final String BILLING_REQUEST_METHOD = "BILLING_REQUEST";
- public static final String BILLING_REQUEST_API_VERSION = "API_VERSION";
- public static final String BILLING_REQUEST_PACKAGE_NAME = "PACKAGE_NAME";
- public static final String BILLING_REQUEST_ITEM_ID = "ITEM_ID";
- public static final String BILLING_REQUEST_ITEM_TYPE = "ITEM_TYPE";
- public static final String BILLING_REQUEST_DEVELOPER_PAYLOAD = "DEVELOPER_PAYLOAD";
- public static final String BILLING_REQUEST_NOTIFY_IDS = "NOTIFY_IDS";
- public static final String BILLING_REQUEST_NONCE = "NONCE";
-
- public static final String BILLING_RESPONSE_RESPONSE_CODE = "RESPONSE_CODE";
- public static final String BILLING_RESPONSE_PURCHASE_INTENT = "PURCHASE_INTENT";
- public static final String BILLING_RESPONSE_REQUEST_ID = "REQUEST_ID";
- public static long BILLING_RESPONSE_INVALID_REQUEST_ID = -1;
-
- // These are the types supported in the IAB v2
- public static final String ITEM_TYPE_INAPP = "inapp";
- public static final String ITEM_TYPE_SUBSCRIPTION = "subs";
-
- public static final boolean DEBUG = false;
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/PurchaseDatabase.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/PurchaseDatabase.java
deleted file mode 100644
index 0b2796b85..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/PurchaseDatabase.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.renpy.android.billing;
-
-import org.renpy.android.billing.Consts.PurchaseState;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-/**
- * An example database that records the state of each purchase. You should use
- * an obfuscator before storing any information to persistent storage. The
- * obfuscator should use a key that is specific to the device and/or user.
- * Otherwise an attacker could copy a database full of valid purchases and
- * distribute it to others.
- */
-public class PurchaseDatabase {
- private static final String TAG = "PurchaseDatabase";
- private static final String DATABASE_NAME = "purchase.db";
- private static final int DATABASE_VERSION = 1;
- private static final String PURCHASE_HISTORY_TABLE_NAME = "history";
- private static final String PURCHASED_ITEMS_TABLE_NAME = "purchased";
-
- // These are the column names for the purchase history table. We need a
- // column named "_id" if we want to use a CursorAdapter. The primary key is
- // the orderId so that we can be robust against getting multiple messages
- // from the server for the same purchase.
- static final String HISTORY_ORDER_ID_COL = "_id";
- static final String HISTORY_STATE_COL = "state";
- static final String HISTORY_PRODUCT_ID_COL = "productId";
- static final String HISTORY_PURCHASE_TIME_COL = "purchaseTime";
- static final String HISTORY_DEVELOPER_PAYLOAD_COL = "developerPayload";
-
- private static final String[] HISTORY_COLUMNS = {
- HISTORY_ORDER_ID_COL, HISTORY_PRODUCT_ID_COL, HISTORY_STATE_COL,
- HISTORY_PURCHASE_TIME_COL, HISTORY_DEVELOPER_PAYLOAD_COL
- };
-
- // These are the column names for the "purchased items" table.
- static public final String PURCHASED_PRODUCT_ID_COL = "_id";
- static public final String PURCHASED_QUANTITY_COL = "quantity";
-
- private static final String[] PURCHASED_COLUMNS = {
- PURCHASED_PRODUCT_ID_COL, PURCHASED_QUANTITY_COL
- };
-
- private SQLiteDatabase mDb;
- private DatabaseHelper mDatabaseHelper;
-
- public PurchaseDatabase(Context context) {
- mDatabaseHelper = new DatabaseHelper(context);
- mDb = mDatabaseHelper.getWritableDatabase();
- }
-
- public void close() {
- mDatabaseHelper.close();
- }
-
- /**
- * Inserts a purchased product into the database. There may be multiple
- * rows in the table for the same product if it was purchased multiple times
- * or if it was refunded.
- * @param orderId the order ID (matches the value in the product list)
- * @param productId the product ID (sku)
- * @param state the state of the purchase
- * @param purchaseTime the purchase time (in milliseconds since the epoch)
- * @param developerPayload the developer provided "payload" associated with
- * the order.
- */
- private void insertOrder(String orderId, String productId, PurchaseState state,
- long purchaseTime, String developerPayload) {
- ContentValues values = new ContentValues();
- values.put(HISTORY_ORDER_ID_COL, orderId);
- values.put(HISTORY_PRODUCT_ID_COL, productId);
- values.put(HISTORY_STATE_COL, state.ordinal());
- values.put(HISTORY_PURCHASE_TIME_COL, purchaseTime);
- values.put(HISTORY_DEVELOPER_PAYLOAD_COL, developerPayload);
- mDb.replace(PURCHASE_HISTORY_TABLE_NAME, null /* nullColumnHack */, values);
- }
-
- /**
- * Updates the quantity of the given product to the given value. If the
- * given value is zero, then the product is removed from the table.
- * @param productId the product to update
- * @param quantity the number of times the product has been purchased
- */
- private void updatePurchasedItem(String productId, int quantity) {
- if (quantity == 0) {
- mDb.delete(PURCHASED_ITEMS_TABLE_NAME, PURCHASED_PRODUCT_ID_COL + "=?",
- new String[] { productId });
- return;
- }
- ContentValues values = new ContentValues();
- values.put(PURCHASED_PRODUCT_ID_COL, productId);
- values.put(PURCHASED_QUANTITY_COL, quantity);
- mDb.replace(PURCHASED_ITEMS_TABLE_NAME, null /* nullColumnHack */, values);
- }
-
- /**
- * Adds the given purchase information to the database and returns the total
- * number of times that the given product has been purchased.
- * @param orderId a string identifying the order
- * @param productId the product ID (sku)
- * @param purchaseState the purchase state of the product
- * @param purchaseTime the time the product was purchased, in milliseconds
- * since the epoch (Jan 1, 1970)
- * @param developerPayload the developer provided "payload" associated with
- * the order
- * @return the number of times the given product has been purchased.
- */
- public synchronized int updatePurchase(String orderId, String productId,
- PurchaseState purchaseState, long purchaseTime, String developerPayload) {
- insertOrder(orderId, productId, purchaseState, purchaseTime, developerPayload);
- Cursor cursor = mDb.query(PURCHASE_HISTORY_TABLE_NAME, HISTORY_COLUMNS,
- HISTORY_PRODUCT_ID_COL + "=?", new String[] { productId }, null, null, null, null);
- if (cursor == null) {
- return 0;
- }
- int quantity = 0;
- try {
- // Count the number of times the product was purchased
- while (cursor.moveToNext()) {
- int stateIndex = cursor.getInt(2);
- PurchaseState state = PurchaseState.valueOf(stateIndex);
- // Note that a refunded purchase is treated as a purchase. Such
- // a friendly refund policy is nice for the user.
- if (state == PurchaseState.PURCHASED || state == PurchaseState.REFUNDED) {
- quantity += 1;
- }
- }
-
- // Update the "purchased items" table
- updatePurchasedItem(productId, quantity);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return quantity;
- }
-
- /**
- * Returns a cursor that can be used to read all the rows and columns of
- * the "purchased items" table.
- */
- public Cursor queryAllPurchasedItems() {
- return mDb.query(PURCHASED_ITEMS_TABLE_NAME, PURCHASED_COLUMNS, null,
- null, null, null, null);
- }
-
- /**
- * This is a standard helper class for constructing the database.
- */
- private class DatabaseHelper extends SQLiteOpenHelper {
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- createPurchaseTable(db);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // Production-quality upgrade code should modify the tables when
- // the database version changes instead of dropping the tables and
- // re-creating them.
- if (newVersion != DATABASE_VERSION) {
- Log.w(TAG, "Database upgrade from old: " + oldVersion + " to: " +
- newVersion);
- db.execSQL("DROP TABLE IF EXISTS " + PURCHASE_HISTORY_TABLE_NAME);
- db.execSQL("DROP TABLE IF EXISTS " + PURCHASED_ITEMS_TABLE_NAME);
- createPurchaseTable(db);
- return;
- }
- }
-
- private void createPurchaseTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + PURCHASE_HISTORY_TABLE_NAME + "(" +
- HISTORY_ORDER_ID_COL + " TEXT PRIMARY KEY, " +
- HISTORY_STATE_COL + " INTEGER, " +
- HISTORY_PRODUCT_ID_COL + " TEXT, " +
- HISTORY_DEVELOPER_PAYLOAD_COL + " TEXT, " +
- HISTORY_PURCHASE_TIME_COL + " INTEGER)");
- db.execSQL("CREATE TABLE " + PURCHASED_ITEMS_TABLE_NAME + "(" +
- PURCHASED_PRODUCT_ID_COL + " TEXT PRIMARY KEY, " +
- PURCHASED_QUANTITY_COL + " INTEGER)");
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/PurchaseObserver.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/PurchaseObserver.java
deleted file mode 100644
index 3d299e197..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/PurchaseObserver.java
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2010 Google Inc. All Rights Reserved.
-
-package org.renpy.android.billing;
-
-import org.renpy.android.billing.BillingService.RequestPurchase;
-import org.renpy.android.billing.BillingService.RestoreTransactions;
-import org.renpy.android.billing.Consts.PurchaseState;
-import org.renpy.android.billing.Consts.ResponseCode;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Handler;
-import android.util.Log;
-
-import java.lang.reflect.Method;
-
-/**
- * An interface for observing changes related to purchases. The main application
- * extends this class and registers an instance of that derived class with
- * {@link ResponseHandler}. The main application implements the callbacks
- * {@link #onBillingSupported(boolean)} and
- * {@link #onPurchaseStateChange(PurchaseState, String, int, long)}. These methods
- * are used to update the UI.
- */
-public abstract class PurchaseObserver {
- private static final String TAG = "PurchaseObserver";
- private final Activity mActivity;
- private final Handler mHandler;
- private Method mStartIntentSender;
- private Object[] mStartIntentSenderArgs = new Object[5];
- private static final Class[] START_INTENT_SENDER_SIG = new Class[] {
- IntentSender.class, Intent.class, int.class, int.class, int.class
- };
-
- public PurchaseObserver(Activity activity, Handler handler) {
- mActivity = activity;
- mHandler = handler;
- initCompatibilityLayer();
- }
-
- /**
- * This is the callback that is invoked when Android Market responds to the
- * {@link BillingService#checkBillingSupported()} request.
- * @param supported true if in-app billing is supported.
- */
- public abstract void onBillingSupported(boolean supported, String type);
-
- /**
- * This is the callback that is invoked when an item is purchased,
- * refunded, or canceled. It is the callback invoked in response to
- * calling {@link BillingService#requestPurchase(String)}. It may also
- * be invoked asynchronously when a purchase is made on another device
- * (if the purchase was for a Market-managed item), or if the purchase
- * was refunded, or the charge was canceled. This handles the UI
- * update. The database update is handled in
- * {@link ResponseHandler#purchaseResponse(Context, PurchaseState,
- * String, String, long)}.
- * @param purchaseState the purchase state of the item
- * @param itemId a string identifying the item (the "SKU")
- * @param quantity the current quantity of this item after the purchase
- * @param purchaseTime the time the product was purchased, in
- * milliseconds since the epoch (Jan 1, 1970)
- */
- public abstract void onPurchaseStateChange(PurchaseState purchaseState,
- String itemId, int quantity, long purchaseTime, String developerPayload);
-
- /**
- * This is called when we receive a response code from Market for a
- * RequestPurchase request that we made. This is NOT used for any
- * purchase state changes. All purchase state changes are received in
- * {@link #onPurchaseStateChange(PurchaseState, String, int, long)}.
- * This is used for reporting various errors, or if the user backed out
- * and didn't purchase the item. The possible response codes are:
- * RESULT_OK means that the order was sent successfully to the server.
- * The onPurchaseStateChange() will be invoked later (with a
- * purchase state of PURCHASED or CANCELED) when the order is
- * charged or canceled. This response code can also happen if an
- * order for a Market-managed item was already sent to the server.
- * RESULT_USER_CANCELED means that the user didn't buy the item.
- * RESULT_SERVICE_UNAVAILABLE means that we couldn't connect to the
- * Android Market server (for example if the data connection is down).
- * RESULT_BILLING_UNAVAILABLE means that in-app billing is not
- * supported yet.
- * RESULT_ITEM_UNAVAILABLE means that the item this app offered for
- * sale does not exist (or is not published) in the server-side
- * catalog.
- * RESULT_ERROR is used for any other errors (such as a server error).
- */
- public abstract void onRequestPurchaseResponse(RequestPurchase request,
- ResponseCode responseCode);
-
- /**
- * This is called when we receive a response code from Android Market for a
- * RestoreTransactions request that we made. A response code of
- * RESULT_OK means that the request was successfully sent to the server.
- */
- public abstract void onRestoreTransactionsResponse(RestoreTransactions request,
- ResponseCode responseCode);
-
- private void initCompatibilityLayer() {
- try {
- mStartIntentSender = mActivity.getClass().getMethod("startIntentSender",
- START_INTENT_SENDER_SIG);
- } catch (SecurityException e) {
- mStartIntentSender = null;
- } catch (NoSuchMethodException e) {
- mStartIntentSender = null;
- }
- }
-
- void startBuyPageActivity(PendingIntent pendingIntent, Intent intent) {
- if (mStartIntentSender != null) {
- // This is on Android 2.0 and beyond. The in-app buy page activity
- // must be on the activity stack of the application.
- try {
- // This implements the method call:
- // mActivity.startIntentSender(pendingIntent.getIntentSender(),
- // intent, 0, 0, 0);
- mStartIntentSenderArgs[0] = pendingIntent.getIntentSender();
- mStartIntentSenderArgs[1] = intent;
- mStartIntentSenderArgs[2] = Integer.valueOf(0);
- mStartIntentSenderArgs[3] = Integer.valueOf(0);
- mStartIntentSenderArgs[4] = Integer.valueOf(0);
- mStartIntentSender.invoke(mActivity, mStartIntentSenderArgs);
- } catch (Exception e) {
- Log.e(TAG, "error starting activity", e);
- }
- } else {
- // This is on Android version 1.6. The in-app buy page activity must be on its
- // own separate activity stack instead of on the activity stack of
- // the application.
- try {
- pendingIntent.send(mActivity, 0 /* code */, intent);
- } catch (CanceledException e) {
- Log.e(TAG, "error starting activity", e);
- }
- }
- }
-
- /**
- * Updates the UI after the database has been updated. This method runs
- * in a background thread so it has to post a Runnable to run on the UI
- * thread.
- * @param purchaseState the purchase state of the item
- * @param itemId a string identifying the item
- * @param quantity the quantity of items in this purchase
- */
- void postPurchaseStateChange(final PurchaseState purchaseState, final String itemId,
- final int quantity, final long purchaseTime, final String developerPayload) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- onPurchaseStateChange(
- purchaseState, itemId, quantity, purchaseTime, developerPayload);
- }
- });
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/ResponseHandler.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/ResponseHandler.java
deleted file mode 100644
index a616f26e1..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/ResponseHandler.java
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2010 Google Inc. All Rights Reserved.
-
-package org.renpy.android.billing;
-
-import org.renpy.android.Configuration;
-import org.renpy.android.billing.BillingService.RequestPurchase;
-import org.renpy.android.billing.BillingService.RestoreTransactions;
-import org.renpy.android.billing.Consts.PurchaseState;
-import org.renpy.android.billing.Consts.ResponseCode;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * This class contains the methods that handle responses from Android Market. The
- * implementation of these methods is specific to a particular application.
- * The methods in this example update the database and, if the main application
- * has registered a {@llink PurchaseObserver}, will also update the UI. An
- * application might also want to forward some responses on to its own server,
- * and that could be done here (in a background thread) but this example does
- * not do that.
- *
- * You should modify and obfuscate this code before using it.
- */
-public class ResponseHandler {
- private static final String TAG = "ResponseHandler";
-
- /**
- * This is a static instance of {@link PurchaseObserver} that the
- * application creates and registers with this class. The PurchaseObserver
- * is used for updating the UI if the UI is visible.
- */
- private static PurchaseObserver sPurchaseObserver;
-
- /**
- * Registers an observer that updates the UI.
- * @param observer the observer to register
- */
- public static synchronized void register(PurchaseObserver observer) {
- Log.d(TAG, "@@@@@@@@@22 RESPONSE HANDLER CALLED REGISTER()");
- sPurchaseObserver = observer;
- }
-
- /**
- * Unregisters a previously registered observer.
- * @param observer the previously registered observer.
- */
- public static synchronized void unregister(PurchaseObserver observer) {
- Log.d(TAG, "@@@@@@@@@22 RESPONSE HANDLER CALLED UNREGISTER()");
- sPurchaseObserver = null;
- }
-
- /**
- * Notifies the application of the availability of the MarketBillingService.
- * This method is called in response to the application calling
- * {@link BillingService#checkBillingSupported()}.
- * @param supported true if in-app billing is supported.
- */
- public static void checkBillingSupportedResponse(boolean supported, String type) {
- Log.d(TAG, "checkBillingSupportedResponse()");
- if (sPurchaseObserver != null) {
- sPurchaseObserver.onBillingSupported(supported, type);
- }
- }
-
- /**
- * Starts a new activity for the user to buy an item for sale. This method
- * forwards the intent on to the PurchaseObserver (if it exists) because
- * we need to start the activity on the activity stack of the application.
- *
- * @param pendingIntent a PendingIntent that we received from Android Market that
- * will create the new buy page activity
- * @param intent an intent containing a request id in an extra field that
- * will be passed to the buy page activity when it is created
- */
- public static void buyPageIntentResponse(PendingIntent pendingIntent, Intent intent) {
- Log.d(TAG, "buyPageIntentResponse()");
- if (sPurchaseObserver == null) {
- if (Consts.DEBUG) {
- Log.d(TAG, "UI is not running");
- }
- return;
- }
- sPurchaseObserver.startBuyPageActivity(pendingIntent, intent);
- }
-
- /**
- * Notifies the application of purchase state changes. The application
- * can offer an item for sale to the user via
- * {@link BillingService#requestPurchase(String)}. The BillingService
- * calls this method after it gets the response. Another way this method
- * can be called is if the user bought something on another device running
- * this same app. Then Android Market notifies the other devices that
- * the user has purchased an item, in which case the BillingService will
- * also call this method. Finally, this method can be called if the item
- * was refunded.
- * @param purchaseState the state of the purchase request (PURCHASED,
- * CANCELED, or REFUNDED)
- * @param productId a string identifying a product for sale
- * @param orderId a string identifying the order
- * @param purchaseTime the time the product was purchased, in milliseconds
- * since the epoch (Jan 1, 1970)
- * @param developerPayload the developer provided "payload" associated with
- * the order
- */
- public static void purchaseResponse(
- final Context context, final PurchaseState purchaseState, final String productId,
- final String orderId, final long purchaseTime, final String developerPayload,
- final String purchaseToken, final String packageName) {
-
- // Update the database with the purchase state. We shouldn't do that
- // from the main thread so we do the work in a background thread.
- // We don't update the UI here. We will update the UI after we update
- // the database because we need to read and update the current quantity
- // first.
- new Thread(new Runnable() {
- @Override
- public void run() {
- PurchaseDatabase db = new PurchaseDatabase(context);
- int quantity = db.updatePurchase(
- Security.obfuscate(context, Configuration.billing_salt, orderId),
- Security.obfuscate(context, Configuration.billing_salt, productId),
- purchaseState,
- purchaseTime,
- Security.obfuscate(context, Configuration.billing_salt, developerPayload));
- db.close();
-
- // This needs to be synchronized because the UI thread can change the
- // value of sPurchaseObserver.
- synchronized(ResponseHandler.class) {
- if (sPurchaseObserver != null) {
- sPurchaseObserver.postPurchaseStateChange(
- purchaseState, productId, quantity, purchaseTime, developerPayload);
- }
- }
- }
- }).start();
- }
-
- /**
- * This is called when we receive a response code from Android Market for a
- * RequestPurchase request that we made. This is used for reporting various
- * errors and also for acknowledging that an order was sent successfully to
- * the server. This is NOT used for any purchase state changes. All
- * purchase state changes are received in the {@link BillingReceiver} and
- * are handled in {@link Security#verifyPurchase(String, String)}.
- * @param context the context
- * @param request the RequestPurchase request for which we received a
- * response code
- * @param responseCode a response code from Market to indicate the state
- * of the request
- */
- public static void responseCodeReceived(Context context, RequestPurchase request,
- ResponseCode responseCode) {
- if (sPurchaseObserver != null) {
- sPurchaseObserver.onRequestPurchaseResponse(request, responseCode);
- }
- }
-
- /**
- * This is called when we receive a response code from Android Market for a
- * RestoreTransactions request.
- * @param context the context
- * @param request the RestoreTransactions request for which we received a
- * response code
- * @param responseCode a response code from Market to indicate the state
- * of the request
- */
- public static void responseCodeReceived(Context context, RestoreTransactions request,
- ResponseCode responseCode) {
- if (sPurchaseObserver != null) {
- sPurchaseObserver.onRestoreTransactionsResponse(request, responseCode);
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/Security.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/Security.java
deleted file mode 100644
index 8f3fc07de..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/Security.java
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright 2010 Google Inc. All Rights Reserved.
-
-package org.renpy.android.billing;
-
-import org.renpy.android.billing.Consts.PurchaseState;
-import org.renpy.android.billing.util.Base64;
-import org.renpy.android.billing.util.Base64DecoderException;
-import org.renpy.android.PythonActivity;
-import org.renpy.android.Configuration;
-import org.renpy.android.billing.util.Installation;
-import org.renpy.android.billing.util.AESObfuscator;
-import android.content.Context;
-import android.provider.Settings;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.ArrayList;
-import java.util.HashSet;
-
-/**
- * Security-related methods. For a secure implementation, all of this code
- * should be implemented on a server that communicates with the
- * application on the device. For the sake of simplicity and clarity of this
- * example, this code is included here and is executed on the device. If you
- * must verify the purchases on the phone, you should obfuscate this code to
- * make it harder for an attacker to replace the code with stubs that treat all
- * purchases as verified.
- */
-public class Security {
- private static final String TAG = "Security";
-
- private static final String KEY_FACTORY_ALGORITHM = "RSA";
- private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
- private static final SecureRandom RANDOM = new SecureRandom();
-
- /**
- * This keeps track of the nonces that we generated and sent to the
- * server. We need to keep track of these until we get back the purchase
- * state and send a confirmation message back to Android Market. If we are
- * killed and lose this list of nonces, it is not fatal. Android Market will
- * send us a new "notify" message and we will re-generate a new nonce.
- * This has to be "static" so that the {@link BillingReceiver} can
- * check if a nonce exists.
- */
- private static HashSet<Long> sKnownNonces = new HashSet<Long>();
-
- /**
- * A class to hold the verified purchase information.
- */
- public static class VerifiedPurchase {
- public PurchaseState purchaseState;
- public String notificationId;
- public String productId;
- public String orderId;
- public long purchaseTime;
- public String developerPayload;
- public String purchaseToken;
- public String packageName;
-
- public VerifiedPurchase(PurchaseState purchaseState, String notificationId,
- String productId, String orderId, long purchaseTime, String developerPayload,
- String purchaseToken, String packageName) {
- this.purchaseState = purchaseState;
- this.notificationId = notificationId;
- this.productId = productId;
- this.orderId = orderId;
- this.purchaseTime = purchaseTime;
- this.developerPayload = developerPayload;
- this.purchaseToken = purchaseToken;
- this.packageName = packageName;
- }
- }
-
- /** Generates a nonce (a random number used once). */
- public static long generateNonce() {
- long nonce = RANDOM.nextLong();
- sKnownNonces.add(nonce);
- return nonce;
- }
-
- public static void removeNonce(long nonce) {
- sKnownNonces.remove(nonce);
- }
-
- public static boolean isNonceKnown(long nonce) {
- return sKnownNonces.contains(nonce);
- }
-
- /**
- * Verifies that the data was signed with the given signature, and returns
- * the list of verified purchases. The data is in JSON format and contains
- * a nonce (number used once) that we generated and that was signed
- * (as part of the whole data string) with a private key. The data also
- * contains the {@link PurchaseState} and product ID of the purchase.
- * In the general case, there can be an array of purchase transactions
- * because there may be delays in processing the purchase on the backend
- * and then several purchases can be batched together.
- * @param signedData the signed JSON string (signed, not encrypted)
- * @param signature the signature for the data, signed with the private key
- */
- public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData, String signature) {
- if (signedData == null) {
- Log.e(TAG, "data is null");
- return null;
- }
- if (Consts.DEBUG) {
- Log.i(TAG, "signedData: " + signedData);
- }
- boolean verified = false;
- if (!TextUtils.isEmpty(signature)) {
- /**
- * Compute your public key (that you got from the Android Market publisher site).
- *
- * Instead of just storing the entire literal string here embedded in the
- * program, construct the key at runtime from pieces or
- * use bit manipulation (for example, XOR with some other string) to hide
- * the actual key. The key itself is not secret information, but we don't
- * want to make it easy for an adversary to replace the public key with one
- * of their own and then fake messages from the server.
- *
- * Generally, encryption keys / passwords should only be kept in memory
- * long enough to perform the operation they need to perform.
- */
- String base64EncodedPublicKey = Configuration.billing_pubkey;
- PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
- verified = Security.verify(key, signedData, signature);
- if (!verified) {
- Log.w(TAG, "signature does not match data.");
- return null;
- }
- }
-
- JSONObject jObject;
- JSONArray jTransactionsArray = null;
- int numTransactions = 0;
- long nonce = 0L;
- try {
- jObject = new JSONObject(signedData);
-
- // The nonce might be null if the user backed out of the buy page.
- nonce = jObject.optLong("nonce");
- jTransactionsArray = jObject.optJSONArray("orders");
- if (jTransactionsArray != null) {
- numTransactions = jTransactionsArray.length();
- }
- } catch (JSONException e) {
- return null;
- }
-
- if (!Security.isNonceKnown(nonce)) {
- Log.w(TAG, "Nonce not found: " + nonce);
- return null;
- }
-
- ArrayList<VerifiedPurchase> purchases = new ArrayList<VerifiedPurchase>();
- try {
- for (int i = 0; i < numTransactions; i++) {
- JSONObject jElement = jTransactionsArray.getJSONObject(i);
- int response = jElement.getInt("purchaseState");
- PurchaseState purchaseState = PurchaseState.valueOf(response);
- String productId = jElement.getString("productId");
- String packageName = jElement.getString("packageName");
- long purchaseTime = jElement.getLong("purchaseTime");
- String orderId = jElement.optString("orderId", "");
- String notifyId = null;
- if (jElement.has("notificationId")) {
- notifyId = jElement.getString("notificationId");
- }
- String developerPayload = jElement.optString("developerPayload", null);
- String purchaseToken = jElement.optString("purchaseToken", null);
-
- // If the purchase state is PURCHASED, then we require a
- // verified nonce.
- if (purchaseState == PurchaseState.PURCHASED && !verified) {
- continue;
- }
- purchases.add(new VerifiedPurchase(purchaseState, notifyId, productId,
- orderId, purchaseTime, developerPayload, purchaseToken, packageName));
- }
- } catch (JSONException e) {
- Log.e(TAG, "JSON exception: ", e);
- return null;
- }
- removeNonce(nonce);
- return purchases;
- }
-
- /**
- * Generates a PublicKey instance from a string containing the
- * Base64-encoded public key.
- *
- * @param encodedPublicKey Base64-encoded public key
- * @throws IllegalArgumentException if encodedPublicKey is invalid
- */
- public static PublicKey generatePublicKey(String encodedPublicKey) {
- try {
- byte[] decodedKey = Base64.decode(encodedPublicKey);
- KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
- return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- } catch (InvalidKeySpecException e) {
- Log.e(TAG, "Invalid key specification.");
- throw new IllegalArgumentException(e);
- } catch (Base64DecoderException e) {
- Log.e(TAG, "Base64 decoding failed.");
- throw new IllegalArgumentException(e);
- }
- }
-
- /**
- * Verifies that the signature from the server matches the computed
- * signature on the data. Returns true if the data is correctly signed.
- *
- * @param publicKey public key associated with the developer account
- * @param signedData signed data from server
- * @param signature server signature
- * @return true if the data and signature match
- */
- public static boolean verify(PublicKey publicKey, String signedData, String signature) {
- if (Consts.DEBUG) {
- Log.i(TAG, "signature: " + signature);
- }
- Signature sig;
- try {
- sig = Signature.getInstance(SIGNATURE_ALGORITHM);
- sig.initVerify(publicKey);
- sig.update(signedData.getBytes());
- if (!sig.verify(Base64.decode(signature))) {
- Log.e(TAG, "Signature verification failed.");
- return false;
- }
- return true;
- } catch (NoSuchAlgorithmException e) {
- Log.e(TAG, "NoSuchAlgorithmException.");
- } catch (InvalidKeyException e) {
- Log.e(TAG, "Invalid key specification.");
- } catch (SignatureException e) {
- Log.e(TAG, "Signature exception.");
- } catch (Base64DecoderException e) {
- Log.e(TAG, "Base64 decoding failed.");
- }
- return false;
- }
-
- private static AESObfuscator _obfuscator = null;
-
- private static AESObfuscator getObfuscator(Context context, byte[] salt) {
- if (_obfuscator == null) {
- final String installationId = Installation.id(context);
- final String deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
- final String password = installationId + deviceId + context.getPackageName();
- _obfuscator = new AESObfuscator(salt, password);
- }
- return _obfuscator;
- }
-
- public static String unobfuscate(Context context, byte[] salt, String obfuscated) {
- final AESObfuscator obfuscator = getObfuscator(context, salt);
- try {
- return obfuscator.unobfuscate(obfuscated);
- } catch (AESObfuscator.ValidationException e) {
- Log.w(TAG, "Invalid obfuscated data or key");
- }
- return null;
- }
-
- public static String obfuscate(Context context, byte[] salt, String original) {
- final AESObfuscator obfuscator = getObfuscator(context, salt);
- return obfuscator.obfuscate(original);
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/AESObfuscator.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/AESObfuscator.java
deleted file mode 100644
index 7a7e419b8..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/AESObfuscator.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.renpy.android.billing.util;
-
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-import java.security.spec.KeySpec;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * An obfuscator that uses AES to encrypt data.
- */
-public class AESObfuscator {
- private static final String UTF8 = "UTF-8";
- private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC";
- private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
- private static final byte[] IV =
- { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 };
- private static final String header = "org.android.billing.util.AESObfuscator-1|";
-
- private Cipher mEncryptor;
- private Cipher mDecryptor;
-
- public AESObfuscator(byte[] salt, String password) {
- try {
- SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM);
- KeySpec keySpec =
- new PBEKeySpec(password.toCharArray(), salt, 1024, 256);
- SecretKey tmp = factory.generateSecret(keySpec);
- SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
- mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM);
- mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV));
- mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM);
- mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV));
- } catch (GeneralSecurityException e) {
- // This can't happen on a compatible Android device.
- throw new RuntimeException("Invalid environment", e);
- }
- }
-
- public String obfuscate(String original) {
- if (original == null) {
- return null;
- }
- try {
- // Header is appended as an integrity check
- return Base64.encode(mEncryptor.doFinal((header + original).getBytes(UTF8)));
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("Invalid environment", e);
- } catch (GeneralSecurityException e) {
- throw new RuntimeException("Invalid environment", e);
- }
- }
-
- public String unobfuscate(String obfuscated) throws ValidationException {
- if (obfuscated == null) {
- return null;
- }
- try {
- String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8);
- // Check for presence of header. This serves as a final integrity check, for cases
- // where the block size is correct during decryption.
- int headerIndex = result.indexOf(header);
- if (headerIndex != 0) {
- throw new ValidationException("Header not found (invalid data or key)" + ":" +
- obfuscated);
- }
- return result.substring(header.length(), result.length());
- } catch (Base64DecoderException e) {
- throw new ValidationException(e.getMessage() + ":" + obfuscated);
- } catch (IllegalBlockSizeException e) {
- throw new ValidationException(e.getMessage() + ":" + obfuscated);
- } catch (BadPaddingException e) {
- throw new ValidationException(e.getMessage() + ":" + obfuscated);
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("Invalid environment", e);
- }
- }
-
- public class ValidationException extends Exception {
- public ValidationException() {
- super();
- }
-
- public ValidationException(String s) {
- super(s);
- }
-
- private static final long serialVersionUID = 1L;
- }
-
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Base64.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Base64.java
deleted file mode 100644
index 6b4fdea84..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Base64.java
+++ /dev/null
@@ -1,570 +0,0 @@
-// Portions copyright 2002, Google, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.renpy.android.billing.util;
-
-// This code was converted from code at http://iharder.sourceforge.net/base64/
-// Lots of extraneous features were removed.
-/* The original code said:
- * <p>
- * I am placing this code in the Public Domain. Do with it as you will.
- * This software comes with no guarantees or warranties but with
- * plenty of well-wishing instead!
- * Please visit
- * <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
- * periodically to check for updates or to contribute improvements.
- * </p>
- *
- * @author Robert Harder
- * @author rharder@usa.net
- * @version 1.3
- */
-
-/**
- * Base64 converter class. This code is not a complete MIME encoder;
- * it simply converts binary data to base64 data and back.
- *
- * <p>Note {@link CharBase64} is a GWT-compatible implementation of this
- * class.
- */
-public class Base64 {
- /** Specify encoding (value is {@code true}). */
- public final static boolean ENCODE = true;
-
- /** Specify decoding (value is {@code false}). */
- public final static boolean DECODE = false;
-
- /** The equals sign (=) as a byte. */
- private final static byte EQUALS_SIGN = (byte) '=';
-
- /** The new line character (\n) as a byte. */
- private final static byte NEW_LINE = (byte) '\n';
-
- /**
- * The 64 valid Base64 values.
- */
- private final static byte[] ALPHABET =
- {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
- (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
- (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
- (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
- (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
- (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
- (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
- (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
- (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
- (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
- (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
- (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
- (byte) '9', (byte) '+', (byte) '/'};
-
- /**
- * The 64 valid web safe Base64 values.
- */
- private final static byte[] WEBSAFE_ALPHABET =
- {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
- (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
- (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
- (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
- (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
- (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
- (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
- (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
- (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
- (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
- (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
- (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
- (byte) '9', (byte) '-', (byte) '_'};
-
- /**
- * Translates a Base64 value to either its 6-bit reconstruction value
- * or a negative number indicating some other meaning.
- **/
- private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
- -5, -5, // Whitespace: Tab and Linefeed
- -9, -9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
- -9, -9, -9, -9, -9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
- 62, // Plus sign at decimal 43
- -9, -9, -9, // Decimal 44 - 46
- 63, // Slash at decimal 47
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
- -9, -9, -9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9, -9, -9, // Decimal 62 - 64
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
- 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
- -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
- 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
- 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
- -9, -9, -9, -9, -9 // Decimal 123 - 127
- /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
- };
-
- /** The web safe decodabet */
- private final static byte[] WEBSAFE_DECODABET =
- {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
- -5, -5, // Whitespace: Tab and Linefeed
- -9, -9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
- -9, -9, -9, -9, -9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
- 62, // Dash '-' sign at decimal 45
- -9, -9, // Decimal 46-47
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
- -9, -9, -9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9, -9, -9, // Decimal 62 - 64
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
- 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
- -9, -9, -9, -9, // Decimal 91-94
- 63, // Underscore '_' at decimal 95
- -9, // Decimal 96
- 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
- 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
- -9, -9, -9, -9, -9 // Decimal 123 - 127
- /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
- };
-
- // Indicates white space in encoding
- private final static byte WHITE_SPACE_ENC = -5;
- // Indicates equals sign in encoding
- private final static byte EQUALS_SIGN_ENC = -1;
-
- /** Defeats instantiation. */
- private Base64() {
- }
-
- /* ******** E N C O D I N G M E T H O D S ******** */
-
- /**
- * Encodes up to three bytes of the array <var>source</var>
- * and writes the resulting four Base64 bytes to <var>destination</var>.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * <var>srcOffset</var> and <var>destOffset</var>.
- * This method does not check to make sure your arrays
- * are large enough to accommodate <var>srcOffset</var> + 3 for
- * the <var>source</var> array or <var>destOffset</var> + 4 for
- * the <var>destination</var> array.
- * The actual number of significant bytes in your array is
- * given by <var>numSigBytes</var>.
- *
- * @param source the array to convert
- * @param srcOffset the index where conversion begins
- * @param numSigBytes the number of significant bytes in your array
- * @param destination the array to hold the conversion
- * @param destOffset the index where output will be put
- * @param alphabet is the encoding alphabet
- * @return the <var>destination</var> array
- * @since 1.3
- */
- private static byte[] encode3to4(byte[] source, int srcOffset,
- int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
- // 1 2 3
- // 01234567890123456789012345678901 Bit position
- // --------000000001111111122222222 Array position from threeBytes
- // --------| || || || | Six bit groups to index alphabet
- // >>18 >>12 >> 6 >> 0 Right shift necessary
- // 0x3f 0x3f 0x3f Additional AND
-
- // Create buffer with zero-padding if there are only one or two
- // significant bytes passed in the array.
- // We have to shift left 24 in order to flush out the 1's that appear
- // when Java treats a value as negative that is cast from a byte to an int.
- int inBuff =
- (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
- | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
- | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
-
- switch (numSigBytes) {
- case 3:
- destination[destOffset] = alphabet[(inBuff >>> 18)];
- destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
- destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
- destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
- return destination;
- case 2:
- destination[destOffset] = alphabet[(inBuff >>> 18)];
- destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
- destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
- destination[destOffset + 3] = EQUALS_SIGN;
- return destination;
- case 1:
- destination[destOffset] = alphabet[(inBuff >>> 18)];
- destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
- destination[destOffset + 2] = EQUALS_SIGN;
- destination[destOffset + 3] = EQUALS_SIGN;
- return destination;
- default:
- return destination;
- } // end switch
- } // end encode3to4
-
- /**
- * Encodes a byte array into Base64 notation.
- * Equivalent to calling
- * {@code encodeBytes(source, 0, source.length)}
- *
- * @param source The data to convert
- * @since 1.4
- */
- public static String encode(byte[] source) {
- return encode(source, 0, source.length, ALPHABET, true);
- }
-
- /**
- * Encodes a byte array into web safe Base64 notation.
- *
- * @param source The data to convert
- * @param doPadding is {@code true} to pad result with '=' chars
- * if it does not fall on 3 byte boundaries
- */
- public static String encodeWebSafe(byte[] source, boolean doPadding) {
- return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
- }
-
- /**
- * Encodes a byte array into Base64 notation.
- *
- * @param source the data to convert
- * @param off offset in array where conversion should begin
- * @param len length of data to convert
- * @param alphabet the encoding alphabet
- * @param doPadding is {@code true} to pad result with '=' chars
- * if it does not fall on 3 byte boundaries
- * @since 1.4
- */
- public static String encode(byte[] source, int off, int len, byte[] alphabet,
- boolean doPadding) {
- byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
- int outLen = outBuff.length;
-
- // If doPadding is false, set length to truncate '='
- // padding characters
- while (doPadding == false && outLen > 0) {
- if (outBuff[outLen - 1] != '=') {
- break;
- }
- outLen -= 1;
- }
-
- return new String(outBuff, 0, outLen);
- }
-
- /**
- * Encodes a byte array into Base64 notation.
- *
- * @param source the data to convert
- * @param off offset in array where conversion should begin
- * @param len length of data to convert
- * @param alphabet is the encoding alphabet
- * @param maxLineLength maximum length of one line.
- * @return the BASE64-encoded byte array
- */
- public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
- int maxLineLength) {
- int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
- int len43 = lenDiv3 * 4;
- byte[] outBuff = new byte[len43 // Main 4:3
- + (len43 / maxLineLength)]; // New lines
-
- int d = 0;
- int e = 0;
- int len2 = len - 2;
- int lineLength = 0;
- for (; d < len2; d += 3, e += 4) {
-
- // The following block of code is the same as
- // encode3to4( source, d + off, 3, outBuff, e, alphabet );
- // but inlined for faster encoding (~20% improvement)
- int inBuff =
- ((source[d + off] << 24) >>> 8)
- | ((source[d + 1 + off] << 24) >>> 16)
- | ((source[d + 2 + off] << 24) >>> 24);
- outBuff[e] = alphabet[(inBuff >>> 18)];
- outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
- outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
- outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
-
- lineLength += 4;
- if (lineLength == maxLineLength) {
- outBuff[e + 4] = NEW_LINE;
- e++;
- lineLength = 0;
- } // end if: end of line
- } // end for: each piece of array
-
- if (d < len) {
- encode3to4(source, d + off, len - d, outBuff, e, alphabet);
-
- lineLength += 4;
- if (lineLength == maxLineLength) {
- // Add a last newline
- outBuff[e + 4] = NEW_LINE;
- e++;
- }
- e += 4;
- }
-
- assert (e == outBuff.length);
- return outBuff;
- }
-
-
- /* ******** D E C O D I N G M E T H O D S ******** */
-
-
- /**
- * Decodes four bytes from array <var>source</var>
- * and writes the resulting bytes (up to three of them)
- * to <var>destination</var>.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * <var>srcOffset</var> and <var>destOffset</var>.
- * This method does not check to make sure your arrays
- * are large enough to accommodate <var>srcOffset</var> + 4 for
- * the <var>source</var> array or <var>destOffset</var> + 3 for
- * the <var>destination</var> array.
- * This method returns the actual number of bytes that
- * were converted from the Base64 encoding.
- *
- *
- * @param source the array to convert
- * @param srcOffset the index where conversion begins
- * @param destination the array to hold the conversion
- * @param destOffset the index where output will be put
- * @param decodabet the decodabet for decoding Base64 content
- * @return the number of decoded bytes converted
- * @since 1.3
- */
- private static int decode4to3(byte[] source, int srcOffset,
- byte[] destination, int destOffset, byte[] decodabet) {
- // Example: Dk==
- if (source[srcOffset + 2] == EQUALS_SIGN) {
- int outBuff =
- ((decodabet[source[srcOffset]] << 24) >>> 6)
- | ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
-
- destination[destOffset] = (byte) (outBuff >>> 16);
- return 1;
- } else if (source[srcOffset + 3] == EQUALS_SIGN) {
- // Example: DkL=
- int outBuff =
- ((decodabet[source[srcOffset]] << 24) >>> 6)
- | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
- | ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
-
- destination[destOffset] = (byte) (outBuff >>> 16);
- destination[destOffset + 1] = (byte) (outBuff >>> 8);
- return 2;
- } else {
- // Example: DkLE
- int outBuff =
- ((decodabet[source[srcOffset]] << 24) >>> 6)
- | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
- | ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
- | ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
-
- destination[destOffset] = (byte) (outBuff >> 16);
- destination[destOffset + 1] = (byte) (outBuff >> 8);
- destination[destOffset + 2] = (byte) (outBuff);
- return 3;
- }
- } // end decodeToBytes
-
-
- /**
- * Decodes data from Base64 notation.
- *
- * @param s the string to decode (decoded in default encoding)
- * @return the decoded data
- * @since 1.4
- */
- public static byte[] decode(String s) throws Base64DecoderException {
- byte[] bytes = s.getBytes();
- return decode(bytes, 0, bytes.length);
- }
-
- /**
- * Decodes data from web safe Base64 notation.
- * Web safe encoding uses '-' instead of '+', '_' instead of '/'
- *
- * @param s the string to decode (decoded in default encoding)
- * @return the decoded data
- */
- public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
- byte[] bytes = s.getBytes();
- return decodeWebSafe(bytes, 0, bytes.length);
- }
-
- /**
- * Decodes Base64 content in byte array format and returns
- * the decoded byte array.
- *
- * @param source The Base64 encoded data
- * @return decoded data
- * @since 1.3
- * @throws Base64DecoderException
- */
- public static byte[] decode(byte[] source) throws Base64DecoderException {
- return decode(source, 0, source.length);
- }
-
- /**
- * Decodes web safe Base64 content in byte array format and returns
- * the decoded data.
- * Web safe encoding uses '-' instead of '+', '_' instead of '/'
- *
- * @param source the string to decode (decoded in default encoding)
- * @return the decoded data
- */
- public static byte[] decodeWebSafe(byte[] source)
- throws Base64DecoderException {
- return decodeWebSafe(source, 0, source.length);
- }
-
- /**
- * Decodes Base64 content in byte array format and returns
- * the decoded byte array.
- *
- * @param source the Base64 encoded data
- * @param off the offset of where to begin decoding
- * @param len the length of characters to decode
- * @return decoded data
- * @since 1.3
- * @throws Base64DecoderException
- */
- public static byte[] decode(byte[] source, int off, int len)
- throws Base64DecoderException {
- return decode(source, off, len, DECODABET);
- }
-
- /**
- * Decodes web safe Base64 content in byte array format and returns
- * the decoded byte array.
- * Web safe encoding uses '-' instead of '+', '_' instead of '/'
- *
- * @param source the Base64 encoded data
- * @param off the offset of where to begin decoding
- * @param len the length of characters to decode
- * @return decoded data
- */
- public static byte[] decodeWebSafe(byte[] source, int off, int len)
- throws Base64DecoderException {
- return decode(source, off, len, WEBSAFE_DECODABET);
- }
-
- /**
- * Decodes Base64 content using the supplied decodabet and returns
- * the decoded byte array.
- *
- * @param source the Base64 encoded data
- * @param off the offset of where to begin decoding
- * @param len the length of characters to decode
- * @param decodabet the decodabet for decoding Base64 content
- * @return decoded data
- */
- public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
- throws Base64DecoderException {
- int len34 = len * 3 / 4;
- byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
- int outBuffPosn = 0;
-
- byte[] b4 = new byte[4];
- int b4Posn = 0;
- int i = 0;
- byte sbiCrop = 0;
- byte sbiDecode = 0;
- for (i = 0; i < len; i++) {
- sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
- sbiDecode = decodabet[sbiCrop];
-
- if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
- if (sbiDecode >= EQUALS_SIGN_ENC) {
- // An equals sign (for padding) must not occur at position 0 or 1
- // and must be the last byte[s] in the encoded value
- if (sbiCrop == EQUALS_SIGN) {
- int bytesLeft = len - i;
- byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
- if (b4Posn == 0 || b4Posn == 1) {
- throw new Base64DecoderException(
- "invalid padding byte '=' at byte offset " + i);
- } else if ((b4Posn == 3 && bytesLeft > 2)
- || (b4Posn == 4 && bytesLeft > 1)) {
- throw new Base64DecoderException(
- "padding byte '=' falsely signals end of encoded value "
- + "at offset " + i);
- } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
- throw new Base64DecoderException(
- "encoded value has invalid trailing byte");
- }
- break;
- }
-
- b4[b4Posn++] = sbiCrop;
- if (b4Posn == 4) {
- outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
- b4Posn = 0;
- }
- }
- } else {
- throw new Base64DecoderException("Bad Base64 input character at " + i
- + ": " + source[i + off] + "(decimal)");
- }
- }
-
- // Because web safe encoding allows non padding base64 encodes, we
- // need to pad the rest of the b4 buffer with equal signs when
- // b4Posn != 0. There can be at most 2 equal signs at the end of
- // four characters, so the b4 buffer must have two or three
- // characters. This also catches the case where the input is
- // padded with EQUALS_SIGN
- if (b4Posn != 0) {
- if (b4Posn == 1) {
- throw new Base64DecoderException("single trailing character at offset "
- + (len - 1));
- }
- b4[b4Posn++] = EQUALS_SIGN;
- outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
- }
-
- byte[] out = new byte[outBuffPosn];
- System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
- return out;
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Base64DecoderException.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Base64DecoderException.java
deleted file mode 100644
index 338404046..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Base64DecoderException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2002, Google, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.renpy.android.billing.util;
-
-/**
- * Exception thrown when encountering an invalid Base64 input character.
- *
- * @author nelson
- */
-public class Base64DecoderException extends Exception {
- public Base64DecoderException() {
- super();
- }
-
- public Base64DecoderException(String s) {
- super(s);
- }
-
- private static final long serialVersionUID = 1L;
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Installation.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Installation.java
deleted file mode 100644
index 029953d96..000000000
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/billing/util/Installation.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/* Copyright 2011 Robot Media SL (http://www.robotmedia.net)
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-package org.renpy.android.billing.util;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.UUID;
-
-import android.content.Context;
-
-public class Installation {
- private static final String INSTALLATION = "INSTALLATION";
- private static String sID = null;
-
- public synchronized static String id(Context context) {
- if (sID == null) {
- File installation = new File(context.getFilesDir(), INSTALLATION);
- try {
- if (!installation.exists()) {
- writeInstallationFile(installation);
- }
- sID = readInstallationFile(installation);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- return sID;
- }
-
- private static String readInstallationFile(File installation) throws IOException {
- RandomAccessFile f = new RandomAccessFile(installation, "r");
- byte[] bytes = new byte[(int) f.length()];
- f.readFully(bytes);
- f.close();
- return new String(bytes);
- }
-
- private static void writeInstallationFile(File installation) throws IOException {
- FileOutputStream out = new FileOutputStream(installation);
- String id = UUID.randomUUID().toString();
- out.write(id.getBytes());
- out.close();
- }
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/pygame/build/templates/AndroidManifest.tmpl.xml
deleted file mode 100644
index 9766d14db..000000000
--- a/pythonforandroid/bootstraps/pygame/build/templates/AndroidManifest.tmpl.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="{{args.package}}"
- android:versionCode="{{args.numeric_version}}"
- android:versionName="{{args.version}}"
- android:installLocation="{{ args.install_location }}"
- >
-
- <supports-screens
- android:smallScreens="true"
- android:normalScreens="true"
- android:largeScreens="true"
- android:anyDensity="true"
- {% if args.min_sdk_version >= 9 %}
- android:xlargeScreens="true"
- {% endif %}
- />
-
- <application android:label="@string/appName"
- android:icon="@drawable/icon"
- android:hardwareAccelerated="true"
- >
-
- {% for m in args.meta_data %}
- <meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
- <meta-data android:name="fullscreen" android:value="{% if args.window %}0{% else %}1{% endif %}"/>
- <meta-data android:name="wakelock" android:value="{% if args.wakelock %}1{% else %}0{% endif %}"/>
-
-
- <activity android:name="org.renpy.android.PythonActivity"
- android:label="@string/iconName"
- android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|uiMode{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
- android:launchMode="singleTask"
- android:process=":python"
- android:screenOrientation="{{ args.orientation }}"
- android:windowSoftInputMode="stateAlwaysHidden"
- >
-
- {% if args.launcher %}
- <intent-filter>
- <action android:name="org.renpy.LAUNCH" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:scheme="{{ url_scheme }}" />
- </intent-filter>
- {% else %}
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- {% endif %}
-
- {%if args.ouya_category %}
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- <category android:name="tv.ouya.intent.category.{{ args.ouya_category }}"/>
- </intent-filter>
-
- {% endif %}
-
- {%if args.intent_filters %}
- {{ intent_filters }}
- {% endif %}
- </activity>
-
- {% if args.launcher %}
- <activity android:name="org.renpy.android.ProjectChooser"
- android:label="@string/iconName">
-
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
-
- </activity>
- {% endif %}
-
- {% if service or args.launcher %}
- <service android:name="org.renpy.android.PythonService"
- android:process=":PythonService"/>
- {% endif %}
-
- {% if args.billing_pubkey %}
- <service android:name="org.renpy.android.billing.BillingService"
- android:process=":python" />
- <receiver
- android:name="org.renpy.android.billing.BillingReceiver"
- android:process=":python">
- <intent-filter>
- <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
- <action android:name="com.android.vending.billing.RESPONSE_CODE" />
- <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
- </intent-filter>
- </receiver>
- {% endif %}
-
- {% for a in args.add_activity %}
- <activity android:name="{{ a }}"></activity>
- {% endfor %}
-
- </application>
-
- <uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ args.sdk_version }}"/>
- {% if args.wakelock %}
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- {% endif %}
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
- {% for perm in args.permissions %}
- {% if '.' in perm %}
- <uses-permission android:name="{{ perm }}" />
- {% else %}
- <uses-permission android:name="android.permission.{{ perm }}" />
- {% endif %}
- {% endfor %}
-
- {% if args.billing_pubkey %}
- <uses-permission android:name="com.android.vending.BILLING" />
- {% endif %}
-
-
-{{ manifest_extra }}
-</manifest>
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/Configuration.tmpl.java b/pythonforandroid/bootstraps/pygame/build/templates/Configuration.tmpl.java
deleted file mode 100644
index a24ef09e2..000000000
--- a/pythonforandroid/bootstraps/pygame/build/templates/Configuration.tmpl.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// Autogenerated file by build.py, don't change!
-
-package org.renpy.android;
-
-public class Configuration {
- {% if args.billing_pubkey %}
- static public boolean use_billing = true;
- static public String billing_pubkey = "{{ args.billing_pubkey }}";
- {% else %}
- static public boolean use_billing = false;
- static public String billing_pubkey = null;
- {% endif %}
- static public byte billing_salt[] = new byte[] {41, -90, -116, -41, 66, -53, 122, -110, -127, -96, -88, 77, 127, 115, 1, 73, 57, 110, 48, -116};
-}
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/build.xml b/pythonforandroid/bootstraps/pygame/build/templates/build.xml
deleted file mode 100644
index 8f6b84649..000000000
--- a/pythonforandroid/bootstraps/pygame/build/templates/build.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="{{ versioned_name }}" default="help">
-
- <loadproperties srcFile="local.properties" />
- <property file="ant.properties" />
- <loadproperties srcFile="project.properties" />
-
- <property environment="env" />
- <property file="build.properties" />
-
- <fail
- message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'"
- unless="sdk.dir"
- />
- <target name="-pre-build">
- </target>
- <target name="-pre-compile">
- </target>
- <target name="-post-compile">
- </target>
- <import file="${sdk.dir}/tools/ant/build.xml" />
-
-</project>
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/kivy-icon.png b/pythonforandroid/bootstraps/pygame/build/templates/kivy-icon.png
deleted file mode 100644
index 59a00ba6f..000000000
Binary files a/pythonforandroid/bootstraps/pygame/build/templates/kivy-icon.png and /dev/null differ
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/kivy-ouya-icon.png b/pythonforandroid/bootstraps/pygame/build/templates/kivy-ouya-icon.png
deleted file mode 100644
index 1919040a2..000000000
Binary files a/pythonforandroid/bootstraps/pygame/build/templates/kivy-ouya-icon.png and /dev/null differ
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/kivy-presplash.jpg b/pythonforandroid/bootstraps/pygame/build/templates/kivy-presplash.jpg
deleted file mode 100644
index 161ebc092..000000000
Binary files a/pythonforandroid/bootstraps/pygame/build/templates/kivy-presplash.jpg and /dev/null differ
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/launcher-icon.png b/pythonforandroid/bootstraps/pygame/build/templates/launcher-icon.png
deleted file mode 100644
index 073314f28..000000000
Binary files a/pythonforandroid/bootstraps/pygame/build/templates/launcher-icon.png and /dev/null differ
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/launcher-presplash.jpg b/pythonforandroid/bootstraps/pygame/build/templates/launcher-presplash.jpg
deleted file mode 100644
index 5f4995c2c..000000000
Binary files a/pythonforandroid/bootstraps/pygame/build/templates/launcher-presplash.jpg and /dev/null differ
diff --git a/pythonforandroid/bootstraps/pygame/build/templates/strings.xml b/pythonforandroid/bootstraps/pygame/build/templates/strings.xml
deleted file mode 100644
index f5d30e721..000000000
--- a/pythonforandroid/bootstraps/pygame/build/templates/strings.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-<string name="appName">{{args.name}}</string>
-<string name="iconName">{{args.icon_name}}</string>
-{% if private_version %}
-<string name="private_version">{{ private_version }}</string>
-{% endif %}
-{% if public_version %}
-<string name="public_version">{{ public_version }}</string>
-{% endif %}
-<string name="urlScheme">{{ url_scheme }}</string>
-{% for m in args.resource %}
-<string name="{{ m.split('=', 1)[0] }}">{{ m.split('=', 1)[-1] }}</string>
-{% endfor %}
-
-</resources>
diff --git a/pythonforandroid/bootstraps/pygame/build/tools/biglink b/pythonforandroid/bootstraps/pygame/build/tools/biglink
deleted file mode 100755
index 1f82d18b4..000000000
--- a/pythonforandroid/bootstraps/pygame/build/tools/biglink
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-import os
-import sys
-import subprocess
-
-sofiles = [ ]
-
-for directory in sys.argv[2:]:
-
- for fn in os.listdir(directory):
- fn = os.path.join(directory, fn)
-
- if not fn.endswith(".so.o"):
- continue
- if not os.path.exists(fn[:-2] + ".libs"):
- continue
-
- sofiles.append(fn[:-2])
-
-# The raw argument list.
-args = [ ]
-
-for fn in sofiles:
- afn = fn + ".o"
- libsfn = fn + ".libs"
-
- args.append(afn)
- with open(libsfn) as fd:
- data = fd.read()
- args.extend(data.split(" "))
-
-unique_args = [ ]
-while args:
- a = args.pop()
- if a in ('-L', ):
- continue
- if a not in unique_args:
- unique_args.insert(0, a)
-
-
-print('Biglink create %s library' % sys.argv[1])
-print('Biglink arguments:')
-for arg in unique_args:
- print(' %s' % arg)
-
-args = os.environ['CC'].split() + \
- ['-shared', '-O3', '-o', sys.argv[1]] + \
- unique_args
-
-sys.exit(subprocess.call(args))
diff --git a/pythonforandroid/bootstraps/pygame/build/tools/biglink-jb b/pythonforandroid/bootstraps/pygame/build/tools/biglink-jb
deleted file mode 100755
index ef3004f5b..000000000
--- a/pythonforandroid/bootstraps/pygame/build/tools/biglink-jb
+++ /dev/null
@@ -1,111 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-import os
-import sys
-import subprocess
-import re
-
-re_needso = re.compile(r'^.*\(NEEDED\)\s+Shared library: \[lib(.*)\.so\]\s*$')
-
-blacklist_libs = (
- 'c',
- 'stdc++',
- 'dl',
- 'python2.7',
- 'sdl',
- 'sdl_image',
- 'sdl_ttf',
- 'z',
- 'm',
- 'GLESv2',
- 'jpeg',
- 'png',
- 'log',
-)
-
-found_libs = []
-sofiles = [ ]
-
-for directory in sys.argv[2:]:
-
- for fn in os.listdir(directory):
- fn = os.path.join(directory, fn)
-
- if not fn.endswith(".libs"):
- continue
-
- dirfn = fn[:-1] + 'dirs'
- if not os.path.exists(dirfn):
- continue
-
- with open(fn) as f:
- needed_libs = [lib for lib in {ln.strip() for ln in f} if lib not in blacklist_libs and lib not in found_libs]
-
- while needed_libs:
- print('need libs:\n\t' + '\n\t'.join(needed_libs))
-
- start_needed_libs = needed_libs[:]
- found_sofiles = []
-
- with open(dirfn) as f:
- for libdir in f:
- if not needed_libs:
- break
-
- libdir = libdir.strip()
- print('scanning %s' % libdir)
- for lib in needed_libs[:]:
- if lib in found_libs:
- continue
-
- if lib.endswith('.a'):
- needed_libs.remove(lib)
- found_libs.append(lib)
- continue
-
- lib_a = 'lib' + lib + '.a'
- libpath_a = os.path.join(libdir, lib_a)
- lib_so = 'lib' + lib + '.so'
- libpath_so = os.path.join(libdir, lib_so)
- plain_so = lib + '.so'
- plainpath_so = os.path.join(libdir, plain_so)
-
- sopath = None
- if os.path.exists(libpath_so):
- sopath = libpath_so
- elif os.path.exists(plainpath_so):
- sopath = plainpath_so
-
- if sopath:
- print('found %s in %s' % (lib, libdir))
- found_sofiles.append(sopath)
- needed_libs.remove(lib)
- found_libs.append(lib)
- continue
-
- if os.path.exists(libpath_a):
- print('found %s (static) in %s' % (lib, libdir))
- needed_libs.remove(lib)
- found_libs.append(lib)
- continue
-
- for sofile in found_sofiles:
- print('scanning dependencies for %s' % sofile)
- out = subprocess.check_output([os.environ['READELF'], '-d', sofile])
- for line in out.splitlines():
- needso = re_needso.match(line)
- if needso:
- lib = needso.group(1)
- if lib not in needed_libs and lib not in found_libs and lib not in blacklist_libs:
- needed_libs.append(needso.group(1))
-
- sofiles += found_sofiles
-
- if needed_libs == start_needed_libs:
- raise RuntimeError('Failed to locate needed libraries!\n\t' + '\n\t'.join(needed_libs))
-
-output = sys.argv[1]
-
-with open(output, 'w') as f:
- f.write('\n'.join(sofiles))
diff --git a/pythonforandroid/bootstraps/pygame/build/tools/liblink b/pythonforandroid/bootstraps/pygame/build/tools/liblink
deleted file mode 100755
index cf7f26d3b..000000000
--- a/pythonforandroid/bootstraps/pygame/build/tools/liblink
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-import sys
-import subprocess
-from os import environ
-from os.path import basename, join
-
-libs = [ ]
-objects = [ ]
-output = None
-
-
-i = 1
-while i < len(sys.argv):
- opt = sys.argv[i]
- i += 1
-
- if opt == "-o":
- output = sys.argv[i]
- i += 1
- continue
-
- if opt.startswith("-l") or opt.startswith("-L"):
- libs.append(opt)
- continue
-
- if opt in ("-r", "-pipe", "-no-cpp-precomp"):
- continue
-
- if opt in ("--sysroot", "-isysroot", "-framework", "-undefined",
- "-macosx_version_min"):
- i += 1
- continue
-
- if opt.startswith("-I"):
- continue
-
- if opt.startswith("-m"):
- continue
-
- if opt.startswith("-f"):
- continue
-
- if opt.startswith("-O"):
- continue
-
- if opt.startswith("-g"):
- continue
-
- if opt.startswith("-D"):
- continue
-
- if opt.startswith("-"):
- print(sys.argv)
- print("Unknown option: %s" % opt)
- sys.exit(1)
-
- if not opt.endswith('.o'):
- continue
-
- objects.append(opt)
-
-
-f = open(output, "w")
-f.close()
-
-output = join(environ.get('LIBLINK_PATH'), basename(output))
-
-f = open(output + ".libs", "w")
-f.write(" ".join(libs))
-f.close()
-
-sys.exit(subprocess.call([
- environ.get('LD'), '-r',
- '-o', output + '.o'
- #, '-arch', environ.get('ARCH')
- ] + objects))
-
diff --git a/pythonforandroid/bootstraps/pygame/build/tools/liblink-jb b/pythonforandroid/bootstraps/pygame/build/tools/liblink-jb
deleted file mode 100755
index 46dc1ca32..000000000
--- a/pythonforandroid/bootstraps/pygame/build/tools/liblink-jb
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-import sys
-import subprocess
-from os import environ
-from os.path import basename, join
-
-libs = [ ]
-libdirs = [ ]
-output = None
-
-
-i = 1
-while i < len(sys.argv):
- opt = sys.argv[i]
- i += 1
-
- if opt == "-o":
- output = sys.argv[i]
- i ++ 1
- continue
-
- if opt.startswith("-l"):
- libs.append(opt[2:])
- continue
-
- if opt.startswith("-L"):
- libdirs.append(opt[2:])
- continue
-
-output = join(environ.get('LIBLINK_PATH'), basename(output))
-
-with open(output + ".libs", "w") as f:
- f.write("\n".join(libs))
-
-with open(output + ".libdirs", "w") as f:
- f.write("\n".join(libdirs))
-
-
-libargs = ' '.join(["'%s'" % arg for arg in sys.argv[1:]])
-cmd = '%s -shared %s %s' % (environ['CC'], environ['LDFLAGS'], libargs)
-sys.exit(subprocess.call(cmd, shell=True))
diff --git a/pythonforandroid/bootstraps/qt/__init__.py b/pythonforandroid/bootstraps/qt/__init__.py
new file mode 100644
index 000000000..9a6e03f06
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/__init__.py
@@ -0,0 +1,53 @@
+import sh
+from os.path import join
+from pythonforandroid.toolchain import (
+ Bootstrap, current_directory, info, info_main, shprint)
+from pythonforandroid.util import ensure_dir, rmdir
+
+
+class QtBootstrap(Bootstrap):
+ name = 'qt'
+ recipe_depends = ['python3', 'genericndkbuild', 'PySide6', 'shiboken6']
+ # this is needed because the recipes PySide6 and shiboken6 resides in the PySide Qt repository
+ # - https://code.qt.io/cgit/pyside/pyside-setup.git/
+ # Without this some tests will error because it cannot find the recipes within pythonforandroid
+ # repository
+ can_be_chosen_automatically = False
+
+ def assemble_distribution(self):
+ info_main("# Creating Android project using Qt bootstrap")
+
+ rmdir(self.dist_dir)
+ info("Copying gradle build")
+ shprint(sh.cp, '-r', self.build_dir, self.dist_dir)
+
+ with current_directory(self.dist_dir):
+ with open('local.properties', 'w') as fileh:
+ fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))
+
+ arch = self.ctx.archs[0]
+ if len(self.ctx.archs) > 1:
+ raise ValueError("Trying to build for more than one arch. Qt bootstrap cannot handle that yet")
+
+ info(f"Bootstrap running with arch {arch}")
+
+ with current_directory(self.dist_dir):
+ info("Copying Python distribution")
+
+ self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
+ self.distribute_aars(arch)
+ self.distribute_javaclasses(self.ctx.javaclass_dir,
+ dest_dir=join("src", "main", "java"))
+
+ python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
+ ensure_dir(python_bundle_dir)
+ site_packages_dir = self.ctx.python_recipe.create_python_bundle(
+ join(self.dist_dir, python_bundle_dir), arch)
+
+ if not self.ctx.with_debug_symbols:
+ self.strip_libraries(arch)
+ self.fry_eggs(site_packages_dir)
+ super().assemble_distribution()
+
+
+bootstrap = QtBootstrap()
diff --git a/pythonforandroid/bootstraps/qt/build/.gitignore b/pythonforandroid/bootstraps/qt/build/.gitignore
new file mode 100644
index 000000000..a1fc39c07
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/.gitignore
@@ -0,0 +1,14 @@
+.gradle
+/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
diff --git a/pythonforandroid/bootstraps/pygame/build/blacklist.txt b/pythonforandroid/bootstraps/qt/build/blacklist.txt
similarity index 55%
rename from pythonforandroid/bootstraps/pygame/build/blacklist.txt
rename to pythonforandroid/bootstraps/qt/build/blacklist.txt
index 710280369..65f6e4df2 100644
--- a/pythonforandroid/bootstraps/pygame/build/blacklist.txt
+++ b/pythonforandroid/bootstraps/qt/build/blacklist.txt
@@ -1,3 +1,9 @@
+# prevent user to include invalid extensions
+*.apk
+*.aab
+*.apks
+*.pxd
+
# eggs
*.egg-info
@@ -7,33 +13,6 @@ unittest/*
# python config
config/makesetup
-# unused pygame files
-pygame/_camera_*
-pygame/camera.pyo
-pygame/*.html
-pygame/*.bmp
-pygame/*.svg
-pygame/cdrom.so
-pygame/pygame_icon.icns
-pygame/LGPL
-pygame/threads/Py25Queue.pyo
-pygame/*.ttf
-pygame/mac*
-pygame/_numpy*
-pygame/sndarray.pyo
-pygame/surfarray.pyo
-pygame/_arraysurfarray.pyo
-
-# unused kivy files (platform specific)
-kivy/input/providers/wm_*
-kivy/input/providers/mactouch*
-kivy/input/providers/probesysfs*
-kivy/input/providers/mtdev*
-kivy/input/providers/hidinput*
-kivy/core/camera/camera_videocapture*
-kivy/core/spelling/*osx*
-kivy/core/video/video_pyglet*
-
# unused encodings
lib-dynload/*codec*
encodings/cp*.pyo
@@ -78,20 +57,14 @@ multiprocessing/dummy*
lib-dynload/termios.so
lib-dynload/_lsprof.so
lib-dynload/*audioop.so
-lib-dynload/mmap.so
lib-dynload/_hotshot.so
-lib-dynload/_csv.so
lib-dynload/_heapq.so
+lib-dynload/_json.so
lib-dynload/grp.so
lib-dynload/resource.so
lib-dynload/pyexpat.so
+lib-dynload/_ctypes_test.so
+lib-dynload/_testcapi.so
# odd files
plat-linux3/regen
-
-#>sqlite3
-# conditionnal include depending if some recipes are included or not.
-sqlite3/*
-lib-dynload/_sqlite3.so
-#<sqlite3
-
diff --git a/pythonforandroid/bootstraps/qt/build/jni/Application.mk b/pythonforandroid/bootstraps/qt/build/jni/Application.mk
new file mode 100644
index 000000000..e3d23e5be
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/jni/Application.mk
@@ -0,0 +1,8 @@
+
+# Uncomment this if you're using STL in your project
+# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information
+# APP_STL := stlport_static
+
+# APP_ABI := armeabi armeabi-v7a x86
+APP_ABI := $(ARCH)
+APP_PLATFORM := $(NDK_API)
diff --git a/pythonforandroid/bootstraps/qt/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/qt/build/jni/application/src/Android.mk
new file mode 100644
index 000000000..aebe3f623
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/jni/application/src/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := main_$(PREFERRED_ABI)
+
+# Add your application source files here...
+LOCAL_SRC_FILES := start.c
+
+LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
+
+LOCAL_SHARED_LIBRARIES := python_shared
+
+LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
+
+LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/bootstraps/qt/build/jni/application/src/Android_static.mk b/pythonforandroid/bootstraps/qt/build/jni/application/src/Android_static.mk
new file mode 100644
index 000000000..1bb58cb76
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/jni/application/src/Android_static.mk
@@ -0,0 +1,9 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := main_$(PREFERRED_ABI)
+
+LOCAL_SRC_FILES := start.c
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/bootstraps/qt/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/qt/build/jni/application/src/bootstrap_name.h
new file mode 100644
index 000000000..8a4d8aa46
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/jni/application/src/bootstrap_name.h
@@ -0,0 +1,4 @@
+
+#define BOOTSTRAP_USES_NO_SDL_HEADERS
+
+const char bootstrap_name[] = "qt";
diff --git a/pythonforandroid/bootstraps/webview/build/res/drawable/.gitkeep b/pythonforandroid/bootstraps/qt/build/src/main/assets/.gitkeep
similarity index 100%
rename from pythonforandroid/bootstraps/webview/build/res/drawable/.gitkeep
rename to pythonforandroid/bootstraps/qt/build/src/main/assets/.gitkeep
diff --git a/doc/source/ext/sphinx_rtd_theme/demo_docs/source/test_py_module/__init__.py b/pythonforandroid/bootstraps/qt/build/src/main/java/.gitkeep
similarity index 100%
rename from doc/source/ext/sphinx_rtd_theme/demo_docs/source/test_py_module/__init__.py
rename to pythonforandroid/bootstraps/qt/build/src/main/java/.gitkeep
diff --git a/pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java
new file mode 100644
index 000000000..81cad0161
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -0,0 +1,245 @@
+package org.kivy.android;
+
+import android.os.SystemClock;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.view.KeyEvent;
+import android.util.Log;
+import android.widget.Toast;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import org.qtproject.qt.android.bindings.QtActivity;
+import org.qtproject.qt.android.QtNative;
+
+public class PythonActivity extends QtActivity {
+
+ private static final String TAG = "PythonActivity";
+
+ public static PythonActivity mActivity = null;
+
+ private Bundle mMetaData = null;
+ private PowerManager.WakeLock mWakeLock = null;
+
+ public String getAppRoot() {
+ String app_root = getFilesDir().getAbsolutePath() + "/app";
+ return app_root;
+ }
+
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List<String> entryPoints = new ArrayList<String>();
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ this.mActivity = this;
+ Log.v(TAG, "Ready to unpack");
+ File app_root_file = new File(getAppRoot());
+ PythonUtil.unpackAsset(mActivity, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
+
+ Log.v("Python", "Device: " + android.os.Build.DEVICE);
+ Log.v("Python", "Model: " + android.os.Build.MODEL);
+
+ // Set up the Python environment
+ String app_root_dir = getAppRoot();
+ String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
+ String entry_point = getEntryPoint(app_root_dir);
+
+ Log.v(TAG, "Setting env vars for start.c and Python to use");
+ QtNative.setEnvironmentVariable("ANDROID_ENTRYPOINT", entry_point);
+ QtNative.setEnvironmentVariable("ANDROID_ARGUMENT", app_root_dir);
+ QtNative.setEnvironmentVariable("ANDROID_APP_PATH", app_root_dir);
+ QtNative.setEnvironmentVariable("ANDROID_PRIVATE", mFilesDirectory);
+ QtNative.setEnvironmentVariable("ANDROID_UNPACK", app_root_dir);
+ QtNative.setEnvironmentVariable("PYTHONHOME", app_root_dir);
+ QtNative.setEnvironmentVariable("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ QtNative.setEnvironmentVariable("PYTHONOPTIMIZE", "2");
+
+ Log.v(TAG, "About to do super onCreate");
+ super.onCreate(savedInstanceState);
+ Log.v(TAG, "Did super onCreate");
+
+ this.mActivity = this;
+ try {
+ Log.v(TAG, "Access to our meta-data...");
+ mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo(
+ mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
+
+ PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
+ if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
+ mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ mActivity.mWakeLock.acquire();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i("Destroy", "end of app");
+ super.onDestroy();
+
+ // make sure all child threads (python_thread) are stopped
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+
+ long lastBackClick = SystemClock.elapsedRealtime();
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // If it wasn't the Back key or there's no web page history, bubble up to the default
+ // system behavior (probably exit the activity)
+ if (SystemClock.elapsedRealtime() - lastBackClick > 2000){
+ lastBackClick = SystemClock.elapsedRealtime();
+ Toast.makeText(this, "Click again to close the app",
+ Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ lastBackClick = SystemClock.elapsedRealtime();
+ return super.onKeyDown(keyCode, event);
+ }
+
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onNewIntent
+ //
+
+ public interface NewIntentListener {
+ void onNewIntent(Intent intent);
+ }
+
+ private List<NewIntentListener> newIntentListeners = null;
+
+ public void registerNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ this.newIntentListeners = Collections.synchronizedList(new ArrayList<NewIntentListener>());
+ this.newIntentListeners.add(listener);
+ }
+
+ public void unregisterNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.newIntentListeners.remove(listener);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.newIntentListeners ) {
+ Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();
+ while ( iterator.hasNext() ) {
+ (iterator.next()).onNewIntent(intent);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onActivityResult
+ //
+
+ public interface ActivityResultListener {
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+ }
+
+ private List<ActivityResultListener> activityResultListeners = null;
+
+ public void registerActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ this.activityResultListeners = Collections.synchronizedList(new ArrayList<ActivityResultListener>());
+ this.activityResultListeners.add(listener);
+ }
+
+ public void unregisterActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.activityResultListeners.remove(listener);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.activityResultListeners ) {
+ Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();
+ while ( iterator.hasNext() )
+ (iterator.next()).onActivityResult(requestCode, resultCode, intent);
+ }
+ }
+
+ public static void start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, true
+ );
+ }
+
+ public static void start_service_not_as_foreground(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, false
+ );
+ }
+
+ public static void _do_start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument,
+ boolean showForegroundNotification
+ ) {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
+ String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
+ serviceIntent.putExtra("androidPrivate", argument);
+ serviceIntent.putExtra("androidArgument", app_root_dir);
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
+ serviceIntent.putExtra("pythonName", "python");
+ serviceIntent.putExtra("pythonHome", app_root_dir);
+ serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
+ serviceIntent.putExtra("serviceStartAsForeground",
+ (showForegroundNotification ? "true" : "false")
+ );
+ serviceIntent.putExtra("serviceTitle", serviceTitle);
+ serviceIntent.putExtra("serviceDescription", serviceDescription);
+ serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ PythonActivity.mActivity.startService(serviceIntent);
+ }
+
+ public static void stop_service() {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ PythonActivity.mActivity.stopService(serviceIntent);
+ }
+
+}
diff --git a/doc/source/old_toolchain/_static/.empty b/pythonforandroid/bootstraps/qt/build/src/main/jniLibs/.gitkeep
similarity index 100%
rename from doc/source/old_toolchain/_static/.empty
rename to pythonforandroid/bootstraps/qt/build/src/main/jniLibs/.gitkeep
diff --git a/pythonforandroid/bootstraps/qt/build/src/main/libs/.gitkeep b/pythonforandroid/bootstraps/qt/build/src/main/libs/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/qt/build/src/main/res/drawable/.gitkeep b/pythonforandroid/bootstraps/qt/build/src/main/res/drawable/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/qt/build/src/main/res/mipmap/.gitkeep b/pythonforandroid/bootstraps/qt/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/qt/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/qt/build/templates/AndroidManifest.tmpl.xml
new file mode 100644
index 000000000..057794e4e
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/templates/AndroidManifest.tmpl.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="{{ args.numeric_version }}"
+ android:versionName="{{ args.version }}"
+ android:installLocation="auto">
+
+ <supports-screens
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:largeScreens="true"
+ android:anyDensity="true"
+ {% if args.min_sdk_version >= 9 %}
+ android:xlargeScreens="true"
+ {% endif %}
+ />
+
+ <uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ android_api }}" />
+ <uses-feature android:glEsVersion="0x00020000" />
+
+ <!-- Set permissions -->
+ {% for perm in args.permissions %}
+ <uses-permission android:name="{{ perm.name }}"{% if perm.maxSdkVersion %} android:maxSdkVersion="{{ perm.maxSdkVersion }}"{% endif %}{% if perm.usesPermissionFlags %} android:usesPermissionFlags="{{ perm.usesPermissionFlags }}"{% endif %} />
+ {% endfor %}
+
+ {% if args.wakelock %}
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ {% endif %}
+
+ {% if args.billing_pubkey %}
+ <uses-permission android:name="com.android.vending.BILLING" />
+ {% endif %}
+
+ {{ args.extra_manifest_xml }}
+
+ <application android:name="org.qtproject.qt.android.bindings.QtApplication"
+ android:label="@string/app_name"
+ {% if debug %}android:debuggable="true"{% endif %}
+ android:icon="@mipmap/icon"
+ android:allowBackup="{{ args.allow_backup }}"
+ android:fullBackupOnly="false"
+ {% if args.backup_rules %}android:fullBackupContent="@xml/{{ args.backup_rules }}"{% endif %}
+ {{ args.extra_manifest_application_arguments }}
+ android:theme="{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}"
+ android:hardwareAccelerated="true"
+ >
+ <!--
+ android:extractNativeLibs="true" = needed for smaller apk size
+ android:requestLegacyExternalStorage="true"
+ android:allowNativeHeapPointerTagging="false"
+ -->
+ {% for l in args.android_used_libs %}
+ <uses-library android:name="{{ l }}" />
+ {% endfor %}
+
+ {% for m in args.meta_data %}
+ <meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
+ <meta-data android:name="wakelock" android:value="{% if args.wakelock %}1{% else %}0{% endif %}"/>
+
+ <activity android:name="{{args.android_entrypoint}}"
+ android:label="@string/app_name"
+ 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 %}"
+ android:screenOrientation="{{ args.manifest_orientation }}"
+ android:exported="true"
+ {% if args.activity_launch_mode %}
+ android:launchMode="{{ args.activity_launch_mode }}"
+ {% endif %}
+ >
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ {%- if args.intent_filters -%}
+ {{- args.intent_filters -}}
+ {%- endif -%}
+
+ <!-- ToDo: Need more meta-data. Adapt accordingly -->
+ <!-- https://doc.qt.io/qt-6/android-manifest-file-configuration.html#qt-specific-meta-data -->
+ <meta-data
+ android:name="android.app.lib_name"
+ android:value="main"/>
+
+ <meta-data
+ android:name="android.app.extract_android_style"
+ android:value="minimal" />
+ </activity>
+
+ {% if service or args.launcher %}
+ <service android:name="{{ args.service_class_name }}"
+ android:process=":pythonservice" />
+ {% endif %}
+ {% for name in service_names %}
+ <service android:name="{{ args.package }}.Service{{ name|capitalize }}"
+ android:process=":service_{{ name }}" />
+ {% endfor %}
+ {% for name in native_services %}
+ <service android:name="{{ name }}" />
+ {% endfor %}
+
+ {% for a in args.add_activity %}
+ <activity android:name="{{ a }}"></activity>
+ {% endfor %}
+ </application>
+
+</manifest>
diff --git a/pythonforandroid/bootstraps/qt/build/templates/libs.tmpl.xml b/pythonforandroid/bootstraps/qt/build/templates/libs.tmpl.xml
new file mode 100644
index 000000000..d423f4152
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/templates/libs.tmpl.xml
@@ -0,0 +1,27 @@
+<?xml version='1.0' encoding='utf-8'?>
+<resources>
+ <array name="qt_libs">
+ <item>{{ arch }};c++_shared</item>
+ {%- for qt_lib in qt_libs %}
+ <item>{{ arch }};Qt6{{ qt_lib }}_{{ arch }}</item>
+ {%- endfor -%}
+ </array>
+
+ <array name="load_local_libs">
+ {%- for load_local_lib in load_local_libs %}
+ <item>{{ arch }};lib{{ load_local_lib }}_{{ arch }}.so</item>
+ {%- endfor -%}
+ <item>{{ arch }};libshiboken6.abi3.so</item>
+ <item>{{ arch }};libpyside6.abi3.so</item>
+ {%- for qt_lib in qt_libs %}
+ <item>{{ arch }};Qt{{ qt_lib }}.abi3.so</item>
+ {% if qt_lib == "Qml" -%}
+ <item>{{ arch }};libpyside6qml.abi3.so</item>
+ {% endif %}
+ {%- endfor -%}
+ </array>
+
+ <string name="static_init_classes">{{ init_classes }}</string>
+ <string name="use_local_qt_libs">1</string>
+ <string name="bundle_local_qt_libs">1</string>
+</resources>
diff --git a/pythonforandroid/bootstraps/qt/build/templates/strings.tmpl.xml b/pythonforandroid/bootstraps/qt/build/templates/strings.tmpl.xml
new file mode 100644
index 000000000..41c20ac66
--- /dev/null
+++ b/pythonforandroid/bootstraps/qt/build/templates/strings.tmpl.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">{{ args.name }}</string>
+ <string name="private_version">{{ private_version }}</string>
+ <string name="presplash_color">{{ args.presplash_color }}</string>
+</resources>
diff --git a/pythonforandroid/bootstraps/sdl2/__init__.py b/pythonforandroid/bootstraps/sdl2/__init__.py
index 95748a63d..9334724a3 100644
--- a/pythonforandroid/bootstraps/sdl2/__init__.py
+++ b/pythonforandroid/bootstraps/sdl2/__init__.py
@@ -1,36 +1,27 @@
-from pythonforandroid.toolchain import (
- Bootstrap, shprint, current_directory, info, info_main)
-from pythonforandroid.util import ensure_dir
-from os.path import join, exists, curdir, abspath
-from os import walk
-import glob
-import sh
+from os.path import join
+import sh
-EXCLUDE_EXTS = (".py", ".pyc", ".so.o", ".so.a", ".so.libs", ".pyx")
+from pythonforandroid.toolchain import (
+ Bootstrap, shprint, current_directory, info, info_main)
+from pythonforandroid.util import ensure_dir, rmdir
class SDL2GradleBootstrap(Bootstrap):
- name = 'sdl2_gradle'
+ name = 'sdl2'
- recipe_depends = ['sdl2', ('python2', 'python3crystax')]
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'sdl2'})
+ )
- def run_distribute(self):
+ def assemble_distribution(self):
info_main("# Creating Android project ({})".format(self.name))
- arch = self.ctx.archs[0]
- python_install_dir = self.ctx.get_python_install_dir()
- from_crystax = self.ctx.python_recipe.from_crystax
- crystax_python_dir = join("crystax_python", "crystax_python")
-
- if len(self.ctx.archs) > 1:
- raise ValueError("SDL2/gradle support only one arch")
-
- info("Copying SDL2/gradle build for {}".format(arch))
- shprint(sh.rm, "-rf", self.dist_dir)
+ rmdir(self.dist_dir)
+ info("Copying SDL2/gradle build")
shprint(sh.cp, "-r", self.build_dir, self.dist_dir)
- # either the build use environemnt variable (ANDROID_HOME)
+ # either the build use environment variable (ANDROID_HOME)
# or the local.properties if exists
with current_directory(self.dist_dir):
with open('local.properties', 'w') as fileh:
@@ -39,98 +30,25 @@ def run_distribute(self):
with current_directory(self.dist_dir):
info("Copying Python distribution")
- if not exists("private") and not from_crystax:
- ensure_dir("private")
- if not exists("crystax_python") and from_crystax:
- ensure_dir(crystax_python_dir)
-
- hostpython = sh.Command(self.ctx.hostpython)
- if not from_crystax:
- try:
- shprint(hostpython, '-OO', '-m', 'compileall',
- python_install_dir,
- _tail=10, _filterout="^Listing")
- except sh.ErrorReturnCode:
- pass
- if not exists('python-install'):
- shprint(
- sh.cp, '-a', python_install_dir, './python-install')
-
- self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
self.distribute_javaclasses(self.ctx.javaclass_dir,
dest_dir=join("src", "main", "java"))
- if not from_crystax:
- info("Filling private directory")
- if not exists(join("private", "lib")):
- info("private/lib does not exist, making")
- shprint(sh.cp, "-a",
- join("python-install", "lib"), "private")
- shprint(sh.mkdir, "-p",
- join("private", "include", "python2.7"))
-
- libpymodules_fn = join("libs", arch.arch, "libpymodules.so")
- if exists(libpymodules_fn):
- shprint(sh.mv, libpymodules_fn, 'private/')
- shprint(sh.cp,
- join('python-install', 'include',
- 'python2.7', 'pyconfig.h'),
- join('private', 'include', 'python2.7/'))
-
- info('Removing some unwanted files')
- shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so'))
- shprint(sh.rm, '-rf', join('private', 'lib', 'pkgconfig'))
-
- libdir = join(self.dist_dir, 'private', 'lib', 'python2.7')
- site_packages_dir = join(libdir, 'site-packages')
- with current_directory(libdir):
- removes = []
- for dirname, root, filenames in walk("."):
- for filename in filenames:
- for suffix in EXCLUDE_EXTS:
- if filename.endswith(suffix):
- removes.append(filename)
- shprint(sh.rm, '-f', *removes)
-
- info('Deleting some other stuff not used on android')
- # To quote the original distribute.sh, 'well...'
- shprint(sh.rm, '-rf', 'lib2to3')
- shprint(sh.rm, '-rf', 'idlelib')
- for filename in glob.glob('config/libpython*.a'):
- shprint(sh.rm, '-f', filename)
- shprint(sh.rm, '-rf', 'config/python.o')
-
- else: # Python *is* loaded from crystax
- ndk_dir = self.ctx.ndk_dir
- py_recipe = self.ctx.python_recipe
- python_dir = join(ndk_dir, 'sources', 'python',
- py_recipe.version, 'libs', arch.arch)
- shprint(sh.cp, '-r', join(python_dir,
- 'stdlib.zip'), crystax_python_dir)
- shprint(sh.cp, '-r', join(python_dir,
- 'modules'), crystax_python_dir)
- shprint(sh.cp, '-r', self.ctx.get_python_install_dir(),
- join(crystax_python_dir, 'site-packages'))
-
- info('Renaming .so files to reflect cross-compile')
- site_packages_dir = join(crystax_python_dir, "site-packages")
- find_ret = shprint(
- sh.find, site_packages_dir, '-iname', '*.so')
- filenames = find_ret.stdout.decode('utf-8').split('\n')[:-1]
- for filename in filenames:
- parts = filename.split('.')
- if len(parts) <= 2:
- continue
- shprint(sh.mv, filename, filename.split('.')[0] + '.so')
- site_packages_dir = join(abspath(curdir),
- site_packages_dir)
+ for arch in self.ctx.archs:
+ python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
+ ensure_dir(python_bundle_dir)
+
+ self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
+ site_packages_dir = self.ctx.python_recipe.create_python_bundle(
+ join(self.dist_dir, python_bundle_dir), arch)
+ if not self.ctx.with_debug_symbols:
+ self.strip_libraries(arch)
+ self.fry_eggs(site_packages_dir)
+
if 'sqlite3' not in self.ctx.recipe_build_order:
with open('blacklist.txt', 'a') as fileh:
fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n')
- self.strip_libraries(arch)
- self.fry_eggs(site_packages_dir)
- super(SDL2GradleBootstrap, self).run_distribute()
+ super().assemble_distribution()
bootstrap = SDL2GradleBootstrap()
diff --git a/pythonforandroid/bootstraps/sdl2/build/blacklist.txt b/pythonforandroid/bootstraps/sdl2/build/blacklist.txt
index 3d596e44c..d5e230c89 100644
--- a/pythonforandroid/bootstraps/sdl2/build/blacklist.txt
+++ b/pythonforandroid/bootstraps/sdl2/build/blacklist.txt
@@ -1,5 +1,7 @@
# prevent user to include invalid extensions
*.apk
+*.aab
+*.apks
*.pxd
# eggs
@@ -69,7 +71,6 @@ multiprocessing/dummy*
lib-dynload/termios.so
lib-dynload/_lsprof.so
lib-dynload/*audioop.so
-lib-dynload/mmap.so
lib-dynload/_hotshot.so
lib-dynload/_heapq.so
lib-dynload/_json.so
diff --git a/pythonforandroid/bootstraps/sdl2/build/build.py b/pythonforandroid/bootstraps/sdl2/build/build.py
deleted file mode 100755
index 85e4f6387..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/build.py
+++ /dev/null
@@ -1,579 +0,0 @@
-#!/usr/bin/env python2.7
-# coding: utf-8
-
-from __future__ import print_function
-from os.path import (
- dirname, join, isfile, realpath, relpath, split, exists, basename)
-from os import makedirs, remove, listdir
-import os
-import tarfile
-import time
-import subprocess
-import shutil
-from zipfile import ZipFile
-import sys
-from distutils.version import LooseVersion
-
-from fnmatch import fnmatch
-
-import jinja2
-
-curdir = dirname(__file__)
-
-# Try to find a host version of Python that matches our ARM version.
-PYTHON = join(curdir, 'python-install', 'bin', 'python.host')
-if not exists(PYTHON):
- print('Could not find hostpython, will not compile to .pyo (this is normal with python3)')
- PYTHON = None
-
-BLACKLIST_PATTERNS = [
- # code versionning
- '^*.hg/*',
- '^*.git/*',
- '^*.bzr/*',
- '^*.svn/*',
-
- # pyc/py
- '*.pyc',
-
- # temp files
- '~',
- '*.bak',
- '*.swp',
-]
-if PYTHON is not None:
- BLACKLIST_PATTERNS.append('*.py')
-
-WHITELIST_PATTERNS = ['pyconfig.h', ]
-
-python_files = []
-
-
-environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
- join(curdir, 'templates')))
-
-
-def try_unlink(fn):
- if exists(fn):
- os.unlink(fn)
-
-
-def ensure_dir(path):
- if not exists(path):
- makedirs(path)
-
-
-def render(template, dest, **kwargs):
- '''Using jinja2, render `template` to the filename `dest`, supplying the
-
- keyword arguments as template parameters.
- '''
-
- dest_dir = dirname(dest)
- if dest_dir and not exists(dest_dir):
- makedirs(dest_dir)
-
- template = environment.get_template(template)
- text = template.render(**kwargs)
-
- f = open(dest, 'wb')
- f.write(text.encode('utf-8'))
- f.close()
-
-
-def is_whitelist(name):
- return match_filename(WHITELIST_PATTERNS, name)
-
-
-def is_blacklist(name):
- if is_whitelist(name):
- return False
- return match_filename(BLACKLIST_PATTERNS, name)
-
-
-def match_filename(pattern_list, name):
- for pattern in pattern_list:
- if pattern.startswith('^'):
- pattern = pattern[1:]
- else:
- pattern = '*/' + pattern
- if fnmatch(name, pattern):
- return True
-
-
-def listfiles(d):
- basedir = d
- subdirlist = []
- for item in os.listdir(d):
- fn = join(d, item)
- if isfile(fn):
- yield fn
- else:
- subdirlist.append(join(basedir, item))
- for subdir in subdirlist:
- for fn in listfiles(subdir):
- yield fn
-
-
-def make_python_zip():
- '''
- Search for all the python related files, and construct the pythonXX.zip
- According to
- # http://randomsplat.com/id5-cross-compiling-python-for-embedded-linux.html
- site-packages, config and lib-dynload will be not included.
- '''
-
- if not exists('private'):
- print('No compiled python is present to zip, skipping.')
- print('this should only be the case if you are using the CrystaX python')
- return
-
- global python_files
- d = realpath(join('private', 'lib', 'python2.7'))
-
- def select(fn):
- if is_blacklist(fn):
- return False
- fn = realpath(fn)
- assert(fn.startswith(d))
- fn = fn[len(d):]
- if (fn.startswith('/site-packages/') or
- fn.startswith('/config/') or
- fn.startswith('/lib-dynload/') or
- fn.startswith('/libpymodules.so')):
- return False
- return fn
-
- # get a list of all python file
- python_files = [x for x in listfiles(d) if select(x)]
-
- # create the final zipfile
- zfn = join('private', 'lib', 'python27.zip')
- zf = ZipFile(zfn, 'w')
-
- # put all the python files in it
- for fn in python_files:
- afn = fn[len(d):]
- zf.write(fn, afn)
- zf.close()
-
-
-def make_tar(tfn, source_dirs, ignore_path=[]):
- '''
- Make a zip file `fn` from the contents of source_dis.
- '''
-
- # selector function
- def select(fn):
- rfn = realpath(fn)
- for p in ignore_path:
- if p.endswith('/'):
- p = p[:-1]
- if rfn.startswith(p):
- return False
- if rfn in python_files:
- return False
- return not is_blacklist(fn)
-
- # get the files and relpath file of all the directory we asked for
- files = []
- for sd in source_dirs:
- sd = realpath(sd)
- compile_dir(sd)
- files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd)
- if select(x)]
-
- # create tar.gz of thoses files
- tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT)
- dirs = []
- for fn, afn in files:
-# print('%s: %s' % (tfn, fn))
- dn = dirname(afn)
- if dn not in dirs:
- # create every dirs first if not exist yet
- d = ''
- for component in split(dn):
- d = join(d, component)
- if d.startswith('/'):
- d = d[1:]
- if d == '' or d in dirs:
- continue
- dirs.append(d)
- tinfo = tarfile.TarInfo(d)
- tinfo.type = tarfile.DIRTYPE
- tf.addfile(tinfo)
-
- # put the file
- tf.add(fn, afn)
- tf.close()
-
-
-def compile_dir(dfn):
- '''
- Compile *.py in directory `dfn` to *.pyo
- '''
- # -OO = strip docstrings
- if PYTHON is None:
- return
- subprocess.call([PYTHON, '-OO', '-m', 'compileall', '-f', dfn])
-
-
-def make_package(args):
- # Ignore warning if the launcher is in args
- if not args.launcher:
- if not (exists(join(realpath(args.private), 'main.py')) or
- exists(join(realpath(args.private), 'main.pyo'))):
- print('''BUILD FAILURE: No main.py(o) found in your app directory. This
-file must exist to act as the entry point for you app. If your app is
-started by a file with a different name, rename it to main.py or add a
-main.py that loads it.''')
- exit(1)
-
- # Delete the old assets.
- try_unlink('src/main/assets/public.mp3')
- try_unlink('src/main/assets/private.mp3')
-
- # In order to speedup import and initial depack,
- # construct a python27.zip
- make_python_zip()
-
- # Package up the private data (public not supported).
- tar_dirs = [args.private]
- if exists('private'):
- tar_dirs.append('private')
- if exists('crystax_python'):
- tar_dirs.append('crystax_python')
-
- if args.private:
- make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path)
- elif args.launcher:
- # clean 'None's as a result of main.py path absence
- tar_dirs = [tdir for tdir in tar_dirs if tdir]
- make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path)
-
- # folder name for launcher
- url_scheme = 'kivy'
-
- # Prepare some variables for templating process
- default_icon = 'templates/kivy-icon.png'
- default_presplash = 'templates/kivy-presplash.jpg'
- shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png')
- shutil.copy(args.presplash or default_presplash,
- 'src/main/res/drawable/presplash.jpg')
-
- jars = []
- # If extra Java jars were requested, copy them into the libs directory
- if args.add_jar:
- for jarname in args.add_jar:
- if not exists(jarname):
- print('Requested jar does not exist: {}'.format(jarname))
- sys.exit(-1)
- shutil.copy(jarname, 'src/main/libs')
- jars.append(basename(jarname))
- # if extra aar were requested, copy them into the libs directory
- aars = []
- if args.add_aar:
- ensure_dir("libs")
- for aarname in args.add_aar:
- if not exists(aarname):
- print('Requested aar does not exists: {}'.format(aarname))
- sys.exit(-1)
- shutil.copy(aarname, 'libs')
- aars.append(basename(aarname).rsplit('.', 1)[0])
-
- versioned_name = (args.name.replace(' ', '').replace('\'', '') +
- '-' + args.version)
-
- version_code = 0
- if not args.numeric_version:
- for i in args.version.split('.'):
- version_code *= 100
- version_code += int(i)
- args.numeric_version = str(version_code)
-
- if args.intent_filters:
- with open(args.intent_filters) as fd:
- args.intent_filters = fd.read()
-
- args.add_activity = args.add_activity or []
-
- if args.extra_source_dirs:
- esd = []
- for spec in args.extra_source_dirs:
- if ':' in spec:
- specdir, specincludes = spec.split(':')
- else:
- specdir = spec
- specincludes = '**'
- esd.append((realpath(specdir), specincludes))
- args.extra_source_dirs = esd
- else:
- args.extra_source_dirs = []
-
- service = False
- if args.private:
- service_main = join(realpath(args.private), 'service', 'main.py')
- if exists(service_main) or exists(service_main + 'o'):
- service = True
-
- service_names = []
- for sid, spec in enumerate(args.services):
- spec = spec.split(':')
- name = spec[0]
- entrypoint = spec[1]
- options = spec[2:]
-
- foreground = 'foreground' in options
- sticky = 'sticky' in options
-
- service_names.append(name)
- render(
- 'Service.tmpl.java',
- 'src/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
- name=name,
- entrypoint=entrypoint,
- args=args,
- foreground=foreground,
- sticky=sticky,
- service_id=sid + 1)
-
- # Find the SDK directory and target API
- with open('project.properties', 'r') as fileh:
- target = fileh.read().strip()
- android_api = target.split('-')[1]
- with open('local.properties', 'r') as fileh:
- sdk_dir = fileh.read().strip()
- sdk_dir = sdk_dir[8:]
-
- # Try to build with the newest available build tools
- build_tools_versions = listdir(join(sdk_dir, 'build-tools'))
- build_tools_versions = sorted(build_tools_versions,
- key=LooseVersion)
- build_tools_version = build_tools_versions[-1]
-
-
- render(
- 'AndroidManifest.tmpl.xml',
- 'src/main/AndroidManifest.xml',
- args=args,
- service=service,
- service_names=service_names,
- android_api=android_api,
- url_scheme=url_scheme)
-
- # Copy the AndroidManifest.xml to the dist root dir so that ant
- # can also use it
- if exists('AndroidManifest.xml'):
- remove('AndroidManifest.xml')
- shutil.copy(join('src', 'main', 'AndroidManifest.xml'),
- 'AndroidManifest.xml')
-
-
- render(
- 'strings.tmpl.xml',
- 'src/main/res/values/strings.xml',
- args=args,
- url_scheme=url_scheme,
- private_version=str(time.time()))
-
- ## gradle build templates
- render(
- 'build.tmpl.gradle',
- 'build.gradle',
- args=args,
- aars=aars,
- jars=jars,
- android_api=android_api,
- build_tools_version=build_tools_version)
-
- ## ant build templates
- render(
- 'build.tmpl.xml',
- 'build.xml',
- args=args,
- versioned_name=versioned_name)
-
- render(
- 'custom_rules.tmpl.xml',
- 'custom_rules.xml',
- args=args)
-
-
- if args.sign:
- render('build.properties', 'build.properties')
- else:
- if exists('build.properties'):
- os.remove('build.properties')
-
-def parse_args(args=None):
- global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
- default_android_api = 12
- import argparse
- ap = argparse.ArgumentParser(description='''\
-Package a Python application for Android.
-
-For this to work, Java and Ant need to be in your path, as does the
-tools directory of the Android SDK.
-''')
-
- ap.add_argument('--private', dest='private',
- help='the dir of user files')
- # , required=True) for launcher, crashes in make_package
- # if not mentioned (and the check is there anyway)
- ap.add_argument('--package', dest='package',
- help=('The name of the java package the project will be'
- ' packaged under.'),
- required=True)
- ap.add_argument('--name', dest='name',
- help=('The human-readable name of the project.'),
- required=True)
- ap.add_argument('--numeric-version', dest='numeric_version',
- help=('The numeric version number of the project. If not '
- 'given, this is automatically computed from the '
- 'version.'))
- ap.add_argument('--version', dest='version',
- help=('The version number of the project. This should '
- 'consist of numbers and dots, and should have the '
- 'same number of groups of numbers as previous '
- 'versions.'),
- required=True)
- ap.add_argument('--orientation', dest='orientation', default='portrait',
- help=('The orientation that the game will display in. '
- 'Usually one of "landscape", "portrait", '
- '"sensor", or "user" (the same as "sensor" but '
- 'obeying the user\'s Android rotation setting). '
- 'The full list of options is given under '
- 'android_screenOrientation at '
- 'https://developer.android.com/guide/topics/manifest/'
- 'activity-element.html'))
- ap.add_argument('--launcher', dest='launcher', action='store_true',
- help=('Provide this argument to build a multi-app '
- 'launcher, rather than a single app.'))
- ap.add_argument('--icon', dest='icon',
- help='A png file to use as the icon for the application.')
- ap.add_argument('--permission', dest='permissions', action='append',
- help='The permissions to give this app.', nargs='+')
- ap.add_argument('--meta-data', dest='meta_data', action='append',
- help='Custom key=value to add in application metadata')
- ap.add_argument('--presplash', dest='presplash',
- help=('A jpeg file to use as a screen while the '
- 'application is loading.'))
- ap.add_argument('--presplash-color', dest='presplash_color', default='#000000',
- help=('A string to set the loading screen background color. '
- 'Supported formats are: #RRGGBB #AARRGGBB or color names '
- 'like red, green, blue, etc.'))
- ap.add_argument('--wakelock', dest='wakelock', action='store_true',
- help=('Indicate if the application needs the device '
- 'to stay on'))
- ap.add_argument('--window', dest='window', action='store_true',
- help='Indicate if the application will be windowed')
- ap.add_argument('--blacklist', dest='blacklist',
- default=join(curdir, 'blacklist.txt'),
- help=('Use a blacklist file to match unwanted file in '
- 'the final APK'))
- ap.add_argument('--whitelist', dest='whitelist',
- default=join(curdir, 'whitelist.txt'),
- help=('Use a whitelist file to prevent blacklisting of '
- 'file in the final APK'))
- ap.add_argument('--add-jar', dest='add_jar', action='append',
- help=('Add a Java .jar to the libs, so you can access its '
- 'classes with pyjnius. You can specify this '
- 'argument more than once to include multiple jars'))
- ap.add_argument('--add-aar', dest='add_aar', action='append',
- help=('Add an aar dependency manually'))
- ap.add_argument('--depend', dest='depends', action='append',
- help=('Add a external dependency '
- '(eg: com.android.support:appcompat-v7:19.0.1)'))
- ## The --sdk option has been removed, it is ignored in favour of
- ## --android-api handled by toolchain.py
- ap.add_argument('--sdk', dest='sdk_version', default=-1,
- type=int, help=('Deprecated argument, does nothing'))
- ap.add_argument('--minsdk', dest='min_sdk_version',
- default=default_android_api, type=int,
- help=('Minimum Android SDK version to use. Default to '
- 'the value of ANDROIDAPI, or {} if not set'
- .format(default_android_api)))
- ap.add_argument('--intent-filters', dest='intent_filters',
- help=('Add intent-filters xml rules to the '
- 'AndroidManifest.xml file. The argument is a '
- 'filename containing xml. The filename should be '
- 'located relative to the python-for-android '
- 'directory'))
- ap.add_argument('--service', dest='services', action='append',
- help='Declare a new service entrypoint: '
- 'NAME:PATH_TO_PY[:foreground]')
- ap.add_argument('--add-source', dest='extra_source_dirs', action='append',
- help='Include additional source dirs in Java build')
- ap.add_argument('--try-system-python-compile', dest='try_system_python_compile',
- action='store_true',
- help='Use the system python during compileall if possible.')
- ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true',
- help='Do not optimise .py files to .pyo.')
- ap.add_argument('--sign', action='store_true',
- help=('Try to sign the APK with your credentials. You must set '
- 'the appropriate environment variables.'))
- ap.add_argument('--add-activity', dest='add_activity', action='append',
- help='Add this Java class as an Activity to the manifest.')
-
- if args is None:
- args = sys.argv[1:]
- args = ap.parse_args(args)
- args.ignore_path = []
-
- if args.name and args.name[0] == '"' and args.name[-1] == '"':
- args.name = args.name[1:-1]
-
- # if args.sdk_version == -1:
- # args.sdk_version = args.min_sdk_version
-
- if args.sdk_version != -1:
- print('WARNING: Received a --sdk argument, but this argument is '
- 'deprecated and does nothing.')
-
- if args.permissions is None:
- args.permissions = []
- elif args.permissions:
- if isinstance(args.permissions[0], list):
- args.permissions = [p for perm in args.permissions for p in perm]
-
- if args.meta_data is None:
- args.meta_data = []
-
- if args.services is None:
- args.services = []
-
- if args.try_system_python_compile:
- # Hardcoding python2.7 is okay for now, as python3 skips the
- # compilation anyway
- if not exists('crystax_python'):
- python_executable = 'python2.7'
- try:
- subprocess.call([python_executable, '--version'])
- except (OSError, subprocess.CalledProcessError):
- pass
- else:
- PYTHON = python_executable
-
- if args.no_compile_pyo:
- PYTHON = None
- BLACKLIST_PATTERNS.remove('*.py')
-
- if args.blacklist:
- with open(args.blacklist) as fd:
- patterns = [x.strip() for x in fd.read().splitlines()
- if x.strip() and not x.strip().startswith('#')]
- BLACKLIST_PATTERNS += patterns
-
- if args.whitelist:
- with open(args.whitelist) as fd:
- patterns = [x.strip() for x in fd.read().splitlines()
- if x.strip() and not x.strip().startswith('#')]
- WHITELIST_PATTERNS += patterns
-
- make_package(args)
-
- return args
-
-
-if __name__ == "__main__":
- parse_args()
diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/Application.mk b/pythonforandroid/bootstraps/sdl2/build/jni/Application.mk
index e79e378f9..15598537c 100644
--- a/pythonforandroid/bootstraps/sdl2/build/jni/Application.mk
+++ b/pythonforandroid/bootstraps/sdl2/build/jni/Application.mk
@@ -5,3 +5,4 @@
# APP_ABI := armeabi armeabi-v7a x86
APP_ABI := $(ARCH)
+APP_PLATFORM := $(NDK_API)
diff --git a/pythonforandroid/bootstraps/webview/build/jni/src/Android_static.mk b/pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android_static.mk
similarity index 84%
rename from pythonforandroid/bootstraps/webview/build/jni/src/Android_static.mk
rename to pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android_static.mk
index faed669c0..517660be2 100644
--- a/pythonforandroid/bootstraps/webview/build/jni/src/Android_static.mk
+++ b/pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android_static.mk
@@ -4,7 +4,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := main
-LOCAL_SRC_FILES := YourSourceHere.c
+LOCAL_SRC_FILES := start.c
LOCAL_STATIC_LIBRARIES := SDL2_static
diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/sdl2/build/jni/application/src/bootstrap_name.h
new file mode 100644
index 000000000..83dec517d
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/jni/application/src/bootstrap_name.h
@@ -0,0 +1,5 @@
+
+#define BOOTSTRAP_NAME_SDL2
+
+const char bootstrap_name[] = "SDL2"; // capitalized for historic reasons
+
diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/src/Android.mk b/pythonforandroid/bootstraps/sdl2/build/jni/src/Android.mk
deleted file mode 100644
index 41d689d68..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/jni/src/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := main
-
-SDL_PATH := ../SDL
-
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
-
-# Add your application source files here...
-LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
- start.c
-
-LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/include/python2.7 $(EXTRA_CFLAGS)
-
-LOCAL_SHARED_LIBRARIES := SDL2 python_shared
-
-LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS)
-
-LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/lib $(APPLICATION_ADDITIONAL_LDFLAGS)
-
-include $(BUILD_SHARED_LIBRARY)
-
-ifdef CRYSTAX_PYTHON_VERSION
- $(call import-module,python/$(CRYSTAX_PYTHON_VERSION))
-endif
diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c b/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c
deleted file mode 100644
index 121d925d9..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/jni/src/start.c
+++ /dev/null
@@ -1,328 +0,0 @@
-
-#define PY_SSIZE_T_CLEAN
-#include "Python.h"
-#ifndef Py_PYTHON_H
-#error Python headers needed to compile C extensions, please install development version of Python.
-#else
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <jni.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-
-#include "SDL.h"
-#include "android/log.h"
-#include "SDL_opengles2.h"
-
-#define ENTRYPOINT_MAXLEN 128
-#define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x))
-#define LOGP(x) LOG("python", (x))
-
-static PyObject *androidembed_log(PyObject *self, PyObject *args) {
- char *logstr = NULL;
- if (!PyArg_ParseTuple(args, "s", &logstr)) {
- return NULL;
- }
- LOG(getenv("PYTHON_NAME"), logstr);
- Py_RETURN_NONE;
-}
-
-static PyMethodDef AndroidEmbedMethods[] = {
- {"log", androidembed_log, METH_VARARGS, "Log on android platform"},
- {NULL, NULL, 0, NULL}};
-
-#if PY_MAJOR_VERSION >= 3
-static struct PyModuleDef androidembed = {PyModuleDef_HEAD_INIT, "androidembed",
- "", -1, AndroidEmbedMethods};
-
-PyMODINIT_FUNC initandroidembed(void) {
- return PyModule_Create(&androidembed);
-}
-#else
-PyMODINIT_FUNC initandroidembed(void) {
- (void)Py_InitModule("androidembed", AndroidEmbedMethods);
-}
-#endif
-
-int dir_exists(char *filename) {
- struct stat st;
- if (stat(filename, &st) == 0) {
- if (S_ISDIR(st.st_mode))
- return 1;
- }
- return 0;
-}
-
-int file_exists(const char *filename) {
- FILE *file;
- if (file = fopen(filename, "r")) {
- fclose(file);
- return 1;
- }
- return 0;
-}
-
-/* int main(int argc, char **argv) { */
-int main(int argc, char *argv[]) {
-
- char *env_argument = NULL;
- char *env_entrypoint = NULL;
- char *env_logname = NULL;
- char entrypoint[ENTRYPOINT_MAXLEN];
- int ret = 0;
- FILE *fd;
-
- /* AND: Several filepaths are hardcoded here, these must be made
- configurable */
- /* AND: P4A uses env vars...not sure what's best */
- LOGP("Initialize Python for Android");
- env_argument = getenv("ANDROID_ARGUMENT");
- setenv("ANDROID_APP_PATH", env_argument, 1);
- env_entrypoint = getenv("ANDROID_ENTRYPOINT");
- env_logname = getenv("PYTHON_NAME");
-
- if (!getenv("ANDROID_UNPACK")) {
- /* ANDROID_UNPACK currently isn't set in services */
- setenv("ANDROID_UNPACK", env_argument, 1);
- }
-
- if (env_logname == NULL) {
- env_logname = "python";
- setenv("PYTHON_NAME", "python", 1);
- }
-
- LOGP("Changing directory to the one provided by ANDROID_ARGUMENT");
- LOGP(env_argument);
- chdir(env_argument);
-
- Py_SetProgramName(L"android_python");
-
-#if PY_MAJOR_VERSION >= 3
- /* our logging module for android
- */
- PyImport_AppendInittab("androidembed", initandroidembed);
-#endif
-
- LOGP("Preparing to initialize python");
-
- char crystax_python_dir[256];
- snprintf(crystax_python_dir, 256,
- "%s/crystax_python", getenv("ANDROID_UNPACK"));
- if (dir_exists(crystax_python_dir)) {
- LOGP("crystax_python exists");
- char paths[256];
- snprintf(paths, 256,
- "%s/stdlib.zip:%s/modules",
- crystax_python_dir, crystax_python_dir);
- /* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument,
- * env_argument); */
- LOGP("calculated paths to be...");
- LOGP(paths);
-
-#if PY_MAJOR_VERSION >= 3
- wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
- Py_SetPath(wchar_paths);
-#else
- char *wchar_paths = paths;
- LOGP("Can't Py_SetPath in python2, so crystax python2 doesn't work yet");
- exit(1);
-#endif
-
- LOGP("set wchar paths...");
- } else {
- LOGP("crystax_python does not exist");
- }
-
- Py_Initialize();
-
-#if PY_MAJOR_VERSION < 3
- PySys_SetArgv(argc, argv);
-#endif
-
- LOGP("Initialized python");
-
- /* ensure threads will work.
- */
- LOGP("AND: Init threads");
- PyEval_InitThreads();
-
-#if PY_MAJOR_VERSION < 3
- initandroidembed();
-#endif
-
- PyRun_SimpleString("import androidembed\nandroidembed.log('testing python "
- "print redirection')");
-
- /* inject our bootstrap code to redirect python stdin/stdout
- * replace sys.path with our path
- */
- PyRun_SimpleString("import sys, posix\n");
- if (dir_exists("lib")) {
- /* If we built our own python, set up the paths correctly */
- LOGP("Setting up python from ANDROID_PRIVATE");
- PyRun_SimpleString("private = posix.environ['ANDROID_APP_PATH']\n"
- "argument = posix.environ['ANDROID_ARGUMENT']\n"
- "sys.path[:] = [ \n"
- " private + '/lib/python27.zip', \n"
- " private + '/lib/python2.7/', \n"
- " private + '/lib/python2.7/lib-dynload/', \n"
- " private + '/lib/python2.7/site-packages/', \n"
- " argument ]\n");
- }
-
- if (dir_exists(crystax_python_dir)) {
- char add_site_packages_dir[256];
- snprintf(add_site_packages_dir, 256,
- "sys.path.append('%s/site-packages')",
- crystax_python_dir);
-
- PyRun_SimpleString("import sys\n"
- "sys.argv = ['notaninterpreterreally']\n"
- "from os.path import realpath, join, dirname");
- PyRun_SimpleString(add_site_packages_dir);
- /* "sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))") */
- PyRun_SimpleString("sys.path = ['.'] + sys.path");
- }
-
- PyRun_SimpleString(
- "class LogFile(object):\n"
- " def __init__(self):\n"
- " self.buffer = ''\n"
- " def write(self, s):\n"
- " s = self.buffer + s\n"
- " lines = s.split(\"\\n\")\n"
- " for l in lines[:-1]:\n"
- " androidembed.log(l)\n"
- " self.buffer = lines[-1]\n"
- " def flush(self):\n"
- " return\n"
- "sys.stdout = sys.stderr = LogFile()\n"
- "print('Android path', sys.path)\n"
- "import os\n"
- "print('os.environ is', os.environ)\n"
- "print('Android kivy bootstrap done. __name__ is', __name__)");
-
-#if PY_MAJOR_VERSION < 3
- PyRun_SimpleString("import site; print site.getsitepackages()\n");
-#endif
-
- LOGP("AND: Ran string");
-
- /* run it !
- */
- LOGP("Run user program, change dir and execute entrypoint");
-
- /* Get the entrypoint, search the .pyo then .py
- */
- char *dot = strrchr(env_entrypoint, '.');
- if (dot <= 0) {
- LOGP("Invalid entrypoint, abort.");
- return -1;
- }
- if (strlen(env_entrypoint) > ENTRYPOINT_MAXLEN - 2) {
- LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN.");
- return -1;
- }
- if (!strcmp(dot, ".pyo")) {
- if (!file_exists(env_entrypoint)) {
- /* fallback on .py */
- strcpy(entrypoint, env_entrypoint);
- entrypoint[strlen(env_entrypoint) - 1] = '\0';
- LOGP(entrypoint);
- if (!file_exists(entrypoint)) {
- LOGP("Entrypoint not found (.pyo, fallback on .py), abort");
- return -1;
- }
- } else {
- strcpy(entrypoint, env_entrypoint);
- }
- } else if (!strcmp(dot, ".py")) {
- /* if .py is passed, check the pyo version first */
- strcpy(entrypoint, env_entrypoint);
- entrypoint[strlen(env_entrypoint) + 1] = '\0';
- entrypoint[strlen(env_entrypoint)] = 'o';
- if (!file_exists(entrypoint)) {
- /* fallback on pure python version */
- if (!file_exists(env_entrypoint)) {
- LOGP("Entrypoint not found (.py), abort.");
- return -1;
- }
- strcpy(entrypoint, env_entrypoint);
- }
- } else {
- LOGP("Entrypoint have an invalid extension (must be .py or .pyo), abort.");
- return -1;
- }
- // LOGP("Entrypoint is:");
- // LOGP(entrypoint);
- fd = fopen(entrypoint, "r");
- if (fd == NULL) {
- LOGP("Open the entrypoint failed");
- LOGP(entrypoint);
- return -1;
- }
-
- /* run python !
- */
- ret = PyRun_SimpleFile(fd, entrypoint);
-
- if (PyErr_Occurred() != NULL) {
- ret = 1;
- PyErr_Print(); /* This exits with the right code if SystemExit. */
- PyObject *f = PySys_GetObject("stdout");
- if (PyFile_WriteString(
- "\n", f)) /* python2 used Py_FlushLine, but this no longer exists */
- PyErr_Clear();
- }
-
- /* close everything
- */
- Py_Finalize();
- fclose(fd);
-
- LOGP("Python for android ended.");
- return ret;
-}
-
-JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
- JNIEnv *env, jobject thiz, jstring j_android_private,
- jstring j_android_argument, jstring j_service_entrypoint,
- jstring j_python_name, jstring j_python_home, jstring j_python_path,
- jstring j_arg) {
- jboolean iscopy;
- const char *android_private =
- (*env)->GetStringUTFChars(env, j_android_private, &iscopy);
- const char *android_argument =
- (*env)->GetStringUTFChars(env, j_android_argument, &iscopy);
- const char *service_entrypoint =
- (*env)->GetStringUTFChars(env, j_service_entrypoint, &iscopy);
- const char *python_name =
- (*env)->GetStringUTFChars(env, j_python_name, &iscopy);
- const char *python_home =
- (*env)->GetStringUTFChars(env, j_python_home, &iscopy);
- const char *python_path =
- (*env)->GetStringUTFChars(env, j_python_path, &iscopy);
- const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy);
-
- setenv("ANDROID_PRIVATE", android_private, 1);
- setenv("ANDROID_ARGUMENT", android_argument, 1);
- setenv("ANDROID_APP_PATH", android_argument, 1);
- setenv("ANDROID_ENTRYPOINT", service_entrypoint, 1);
- setenv("PYTHONOPTIMIZE", "2", 1);
- setenv("PYTHON_NAME", python_name, 1);
- setenv("PYTHONHOME", python_home, 1);
- setenv("PYTHONPATH", python_path, 1);
- setenv("PYTHON_SERVICE_ARGUMENT", arg, 1);
-
- char *argv[] = {"."};
- /* ANDROID_ARGUMENT points to service subdir,
- * so main() will run main.py from this dir
- */
- main(1, argv);
-}
-
-#endif
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java
deleted file mode 100755
index dd10624ea..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/Octal.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class Octal {
-
- /**
- * Parse an octal string from a header buffer. This is used for the file
- * permission mode value.
- *
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The long value of the octal string.
- */
- public static long parseOctal(byte[] header, int offset, int length) {
- long result = 0;
- boolean stillPadding = true;
-
- int end = offset + length;
- for (int i = offset; i < end; ++i) {
- if (header[i] == 0)
- break;
-
- if (header[i] == (byte) ' ' || header[i] == '0') {
- if (stillPadding)
- continue;
-
- if (header[i] == (byte) ' ')
- break;
- }
-
- stillPadding = false;
-
- result = ( result << 3 ) + ( header[i] - '0' );
- }
-
- return result;
- }
-
- /**
- * Parse an octal integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The integer value of the octal bytes.
- */
- public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
- int idx = length - 1;
-
- buf[offset + idx] = 0;
- --idx;
- buf[offset + idx] = (byte) ' ';
- --idx;
-
- if (value == 0) {
- buf[offset + idx] = (byte) '0';
- --idx;
- } else {
- for (long val = value; idx >= 0 && val > 0; --idx) {
- buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
- val = val >> 3;
- }
- }
-
- for (; idx >= 0; --idx) {
- buf[offset + idx] = (byte) ' ';
- }
-
- return offset + length;
- }
-
- /**
- * Parse the checksum octal integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The integer value of the entry's checksum.
- */
- public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
- getOctalBytes( value, buf, offset, length );
- buf[offset + length - 1] = (byte) ' ';
- buf[offset + length - 2] = 0;
- return offset + length;
- }
-
- /**
- * Parse an octal long integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The long value of the octal bytes.
- */
- public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
- byte[] temp = new byte[length + 1];
- getOctalBytes( value, temp, 0, length + 1 );
- System.arraycopy( temp, 0, buf, offset, length );
- return offset + length;
- }
-
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java
deleted file mode 100755
index 4611e20ea..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarConstants.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarConstants {
- public static final int EOF_BLOCK = 1024;
- public static final int DATA_BLOCK = 512;
- public static final int HEADER_BLOCK = 512;
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java
deleted file mode 100755
index fe01db463..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarEntry.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-import java.util.Date;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarEntry {
- protected File file;
- protected TarHeader header;
-
- private TarEntry() {
- this.file = null;
- header = new TarHeader();
- }
-
- public TarEntry(File file, String entryName) {
- this();
- this.file = file;
- this.extractTarHeader(entryName);
- }
-
- public TarEntry(byte[] headerBuf) {
- this();
- this.parseTarHeader(headerBuf);
- }
-
- /**
- * Constructor to create an entry from an existing TarHeader object.
- *
- * This method is useful to add new entries programmatically (e.g. for
- * adding files or directories that do not exist in the file system).
- *
- * @param header
- *
- */
- public TarEntry(TarHeader header) {
- this.file = null;
- this.header = header;
- }
-
- public boolean equals(TarEntry it) {
- return header.name.toString().equals(it.header.name.toString());
- }
-
- public boolean isDescendent(TarEntry desc) {
- return desc.header.name.toString().startsWith(header.name.toString());
- }
-
- public TarHeader getHeader() {
- return header;
- }
-
- public String getName() {
- String name = header.name.toString();
- if (header.namePrefix != null && !header.namePrefix.toString().equals("")) {
- name = header.namePrefix.toString() + "/" + name;
- }
-
- return name;
- }
-
- public void setName(String name) {
- header.name = new StringBuffer(name);
- }
-
- public int getUserId() {
- return header.userId;
- }
-
- public void setUserId(int userId) {
- header.userId = userId;
- }
-
- public int getGroupId() {
- return header.groupId;
- }
-
- public void setGroupId(int groupId) {
- header.groupId = groupId;
- }
-
- public String getUserName() {
- return header.userName.toString();
- }
-
- public void setUserName(String userName) {
- header.userName = new StringBuffer(userName);
- }
-
- public String getGroupName() {
- return header.groupName.toString();
- }
-
- public void setGroupName(String groupName) {
- header.groupName = new StringBuffer(groupName);
- }
-
- public void setIds(int userId, int groupId) {
- this.setUserId(userId);
- this.setGroupId(groupId);
- }
-
- public void setModTime(long time) {
- header.modTime = time / 1000;
- }
-
- public void setModTime(Date time) {
- header.modTime = time.getTime() / 1000;
- }
-
- public Date getModTime() {
- return new Date(header.modTime * 1000);
- }
-
- public File getFile() {
- return this.file;
- }
-
- public long getSize() {
- return header.size;
- }
-
- public void setSize(long size) {
- header.size = size;
- }
-
- /**
- * Checks if the org.kamrazafar.jtar entry is a directory
- *
- * @return
- */
- public boolean isDirectory() {
- if (this.file != null)
- return this.file.isDirectory();
-
- if (header != null) {
- if (header.linkFlag == TarHeader.LF_DIR)
- return true;
-
- if (header.name.toString().endsWith("/"))
- return true;
- }
-
- return false;
- }
-
- /**
- * Extract header from File
- *
- * @param entryName
- */
- public void extractTarHeader(String entryName) {
- header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory());
- }
-
- /**
- * Calculate checksum
- *
- * @param buf
- * @return
- */
- public long computeCheckSum(byte[] buf) {
- long sum = 0;
-
- for (int i = 0; i < buf.length; ++i) {
- sum += 255 & buf[i];
- }
-
- return sum;
- }
-
- /**
- * Writes the header to the byte buffer
- *
- * @param outbuf
- */
- public void writeEntryHeader(byte[] outbuf) {
- int offset = 0;
-
- offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN);
- offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN);
- offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN);
- offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN);
-
- long size = header.size;
-
- offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
- offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN);
-
- int csOffset = offset;
- for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
- outbuf[offset++] = (byte) ' ';
-
- outbuf[offset++] = header.linkFlag;
-
- offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN);
- offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN);
- offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN);
- offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN);
- offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN);
- offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN);
- offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX);
-
- for (; offset < outbuf.length;)
- outbuf[offset++] = 0;
-
- long checkSum = this.computeCheckSum(outbuf);
-
- Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN);
- }
-
- /**
- * Parses the tar header to the byte buffer
- *
- * @param header
- * @param bh
- */
- public void parseTarHeader(byte[] bh) {
- int offset = 0;
-
- header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
- offset += TarHeader.NAMELEN;
-
- header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
- offset += TarHeader.MODELEN;
-
- header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
- offset += TarHeader.UIDLEN;
-
- header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
- offset += TarHeader.GIDLEN;
-
- header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
- offset += TarHeader.SIZELEN;
-
- header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
- offset += TarHeader.MODTIMELEN;
-
- header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN);
- offset += TarHeader.CHKSUMLEN;
-
- header.linkFlag = bh[offset++];
-
- header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
- offset += TarHeader.NAMELEN;
-
- header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
- offset += TarHeader.USTAR_MAGICLEN;
-
- header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN);
- offset += TarHeader.USTAR_USER_NAMELEN;
-
- header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN);
- offset += TarHeader.USTAR_GROUP_NAMELEN;
-
- header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
- offset += TarHeader.USTAR_DEVLEN;
-
- header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
- offset += TarHeader.USTAR_DEVLEN;
-
- header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX);
- }
-}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java
deleted file mode 100755
index b9d3a86be..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarHeader.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-
-/**
- * Header
- *
- * <pre>
- * Offset Size Field
- * 0 100 File name
- * 100 8 File mode
- * 108 8 Owner's numeric user ID
- * 116 8 Group's numeric user ID
- * 124 12 File size in bytes
- * 136 12 Last modification time in numeric Unix time format
- * 148 8 Checksum for header block
- * 156 1 Link indicator (file type)
- * 157 100 Name of linked file
- * </pre>
- *
- *
- * File Types
- *
- * <pre>
- * Value Meaning
- * '0' Normal file
- * (ASCII NUL) Normal file (now obsolete)
- * '1' Hard link
- * '2' Symbolic link
- * '3' Character special
- * '4' Block special
- * '5' Directory
- * '6' FIFO
- * '7' Contigous
- * </pre>
- *
- *
- *
- * Ustar header
- *
- * <pre>
- * Offset Size Field
- * 257 6 UStar indicator "ustar"
- * 263 2 UStar version "00"
- * 265 32 Owner user name
- * 297 32 Owner group name
- * 329 8 Device major number
- * 337 8 Device minor number
- * 345 155 Filename prefix
- * </pre>
- */
-
-public class TarHeader {
-
- /*
- * Header
- */
- public static final int NAMELEN = 100;
- public static final int MODELEN = 8;
- public static final int UIDLEN = 8;
- public static final int GIDLEN = 8;
- public static final int SIZELEN = 12;
- public static final int MODTIMELEN = 12;
- public static final int CHKSUMLEN = 8;
- public static final byte LF_OLDNORM = 0;
-
- /*
- * File Types
- */
- public static final byte LF_NORMAL = (byte) '0';
- public static final byte LF_LINK = (byte) '1';
- public static final byte LF_SYMLINK = (byte) '2';
- public static final byte LF_CHR = (byte) '3';
- public static final byte LF_BLK = (byte) '4';
- public static final byte LF_DIR = (byte) '5';
- public static final byte LF_FIFO = (byte) '6';
- public static final byte LF_CONTIG = (byte) '7';
-
- /*
- * Ustar header
- */
-
- public static final String USTAR_MAGIC = "ustar"; // POSIX
-
- public static final int USTAR_MAGICLEN = 8;
- public static final int USTAR_USER_NAMELEN = 32;
- public static final int USTAR_GROUP_NAMELEN = 32;
- public static final int USTAR_DEVLEN = 8;
- public static final int USTAR_FILENAME_PREFIX = 155;
-
- // Header values
- public StringBuffer name;
- public int mode;
- public int userId;
- public int groupId;
- public long size;
- public long modTime;
- public int checkSum;
- public byte linkFlag;
- public StringBuffer linkName;
- public StringBuffer magic; // ustar indicator and version
- public StringBuffer userName;
- public StringBuffer groupName;
- public int devMajor;
- public int devMinor;
- public StringBuffer namePrefix;
-
- public TarHeader() {
- this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
-
- this.name = new StringBuffer();
- this.linkName = new StringBuffer();
-
- String user = System.getProperty("user.name", "");
-
- if (user.length() > 31)
- user = user.substring(0, 31);
-
- this.userId = 0;
- this.groupId = 0;
- this.userName = new StringBuffer(user);
- this.groupName = new StringBuffer("");
- this.namePrefix = new StringBuffer();
- }
-
- /**
- * Parse an entry name from a header buffer.
- *
- * @param name
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The header's entry name.
- */
- public static StringBuffer parseName(byte[] header, int offset, int length) {
- StringBuffer result = new StringBuffer(length);
-
- int end = offset + length;
- for (int i = offset; i < end; ++i) {
- if (header[i] == 0)
- break;
- result.append((char) header[i]);
- }
-
- return result;
- }
-
- /**
- * Determine the number of bytes in an entry name.
- *
- * @param name
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The number of bytes in a header's entry name.
- */
- public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
- int i;
-
- for (i = 0; i < length && i < name.length(); ++i) {
- buf[offset + i] = (byte) name.charAt(i);
- }
-
- for (; i < length; ++i) {
- buf[offset + i] = 0;
- }
-
- return offset + length;
- }
-
- /**
- * Creates a new header for a file/directory entry.
- *
- *
- * @param name
- * File name
- * @param size
- * File size in bytes
- * @param modTime
- * Last modification time in numeric Unix time format
- * @param dir
- * Is directory
- *
- * @return
- */
- public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
- String name = entryName;
- name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
-
- TarHeader header = new TarHeader();
- header.linkName = new StringBuffer("");
-
- if (name.length() > 100) {
- header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
- header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
- } else {
- header.name = new StringBuffer(name);
- }
-
- if (dir) {
- header.mode = 040755;
- header.linkFlag = TarHeader.LF_DIR;
- if (header.name.charAt(header.name.length() - 1) != '/') {
- header.name.append("/");
- }
- header.size = 0;
- } else {
- header.mode = 0100644;
- header.linkFlag = TarHeader.LF_NORMAL;
- header.size = size;
- }
-
- header.modTime = modTime;
- header.checkSum = 0;
- header.devMajor = 0;
- header.devMinor = 0;
-
- return header;
- }
-}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java
deleted file mode 100755
index ec50a1b68..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarInputStream extends FilterInputStream {
-
- private static final int SKIP_BUFFER_SIZE = 2048;
- private TarEntry currentEntry;
- private long currentFileSize;
- private long bytesRead;
- private boolean defaultSkip = false;
-
- public TarInputStream(InputStream in) {
- super(in);
- currentFileSize = 0;
- bytesRead = 0;
- }
-
- @Override
- public boolean markSupported() {
- return false;
- }
-
- /**
- * Not supported
- *
- */
- @Override
- public synchronized void mark(int readlimit) {
- }
-
- /**
- * Not supported
- *
- */
- @Override
- public synchronized void reset() throws IOException {
- throw new IOException("mark/reset not supported");
- }
-
- /**
- * Read a byte
- *
- * @see java.io.FilterInputStream#read()
- */
- @Override
- public int read() throws IOException {
- byte[] buf = new byte[1];
-
- int res = this.read(buf, 0, 1);
-
- if (res != -1) {
- return 0xFF & buf[0];
- }
-
- return res;
- }
-
- /**
- * Checks if the bytes being read exceed the entry size and adjusts the byte
- * array length. Updates the byte counters
- *
- *
- * @see java.io.FilterInputStream#read(byte[], int, int)
- */
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- if (currentEntry != null) {
- if (currentFileSize == currentEntry.getSize()) {
- return -1;
- } else if ((currentEntry.getSize() - currentFileSize) < len) {
- len = (int) (currentEntry.getSize() - currentFileSize);
- }
- }
-
- int br = super.read(b, off, len);
-
- if (br != -1) {
- if (currentEntry != null) {
- currentFileSize += br;
- }
-
- bytesRead += br;
- }
-
- return br;
- }
-
- /**
- * Returns the next entry in the tar file
- *
- * @return TarEntry
- * @throws IOException
- */
- public TarEntry getNextEntry() throws IOException {
- closeCurrentEntry();
-
- byte[] header = new byte[TarConstants.HEADER_BLOCK];
- byte[] theader = new byte[TarConstants.HEADER_BLOCK];
- int tr = 0;
-
- // Read full header
- while (tr < TarConstants.HEADER_BLOCK) {
- int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
-
- if (res < 0) {
- break;
- }
-
- System.arraycopy(theader, 0, header, tr, res);
- tr += res;
- }
-
- // Check if record is null
- boolean eof = true;
- for (byte b : header) {
- if (b != 0) {
- eof = false;
- break;
- }
- }
-
- if (!eof) {
- currentEntry = new TarEntry(header);
- }
-
- return currentEntry;
- }
-
- /**
- * Returns the current offset (in bytes) from the beginning of the stream.
- * This can be used to find out at which point in a tar file an entry's content begins, for instance.
- */
- public long getCurrentOffset() {
- return bytesRead;
- }
-
- /**
- * Closes the current tar entry
- *
- * @throws IOException
- */
- protected void closeCurrentEntry() throws IOException {
- if (currentEntry != null) {
- if (currentEntry.getSize() > currentFileSize) {
- // Not fully read, skip rest of the bytes
- long bs = 0;
- while (bs < currentEntry.getSize() - currentFileSize) {
- long res = skip(currentEntry.getSize() - currentFileSize - bs);
-
- if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
- // I suspect file corruption
- throw new IOException("Possible tar file corruption");
- }
-
- bs += res;
- }
- }
-
- currentEntry = null;
- currentFileSize = 0L;
- skipPad();
- }
- }
-
- /**
- * Skips the pad at the end of each tar entry file content
- *
- * @throws IOException
- */
- protected void skipPad() throws IOException {
- if (bytesRead > 0) {
- int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
-
- if (extra > 0) {
- long bs = 0;
- while (bs < TarConstants.DATA_BLOCK - extra) {
- long res = skip(TarConstants.DATA_BLOCK - extra - bs);
- bs += res;
- }
- }
- }
- }
-
- /**
- * Skips 'n' bytes on the InputStream<br>
- * Overrides default implementation of skip
- *
- */
- @Override
- public long skip(long n) throws IOException {
- if (defaultSkip) {
- // use skip method of parent stream
- // may not work if skip not implemented by parent
- long bs = super.skip(n);
- bytesRead += bs;
-
- return bs;
- }
-
- if (n <= 0) {
- return 0;
- }
-
- long left = n;
- byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
-
- while (left > 0) {
- int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
- if (res < 0) {
- break;
- }
- left -= res;
- }
-
- return n - left;
- }
-
- public boolean isDefaultSkip() {
- return defaultSkip;
- }
-
- public void setDefaultSkip(boolean defaultSkip) {
- this.defaultSkip = defaultSkip;
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java
deleted file mode 100755
index ffdfe8756..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarOutputStream extends OutputStream {
- private final OutputStream out;
- private long bytesWritten;
- private long currentFileSize;
- private TarEntry currentEntry;
-
- public TarOutputStream(OutputStream out) {
- this.out = out;
- bytesWritten = 0;
- currentFileSize = 0;
- }
-
- public TarOutputStream(final File fout) throws FileNotFoundException {
- this.out = new BufferedOutputStream(new FileOutputStream(fout));
- bytesWritten = 0;
- currentFileSize = 0;
- }
-
- /**
- * Opens a file for writing.
- */
- public TarOutputStream(final File fout, final boolean append) throws IOException {
- @SuppressWarnings("resource")
- RandomAccessFile raf = new RandomAccessFile(fout, "rw");
- final long fileSize = fout.length();
- if (append && fileSize > TarConstants.EOF_BLOCK) {
- raf.seek(fileSize - TarConstants.EOF_BLOCK);
- }
- out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
- }
-
- /**
- * Appends the EOF record and closes the stream
- *
- * @see java.io.FilterOutputStream#close()
- */
- @Override
- public void close() throws IOException {
- closeCurrentEntry();
- write( new byte[TarConstants.EOF_BLOCK] );
- out.close();
- }
- /**
- * Writes a byte to the stream and updates byte counters
- *
- * @see java.io.FilterOutputStream#write(int)
- */
- @Override
- public void write(int b) throws IOException {
- out.write( b );
- bytesWritten += 1;
-
- if (currentEntry != null) {
- currentFileSize += 1;
- }
- }
-
- /**
- * Checks if the bytes being written exceed the current entry size.
- *
- * @see java.io.FilterOutputStream#write(byte[], int, int)
- */
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- if (currentEntry != null && !currentEntry.isDirectory()) {
- if (currentEntry.getSize() < currentFileSize + len) {
- throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
- + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
- + "] being written." );
- }
- }
-
- out.write( b, off, len );
-
- bytesWritten += len;
-
- if (currentEntry != null) {
- currentFileSize += len;
- }
- }
-
- /**
- * Writes the next tar entry header on the stream
- *
- * @param entry
- * @throws IOException
- */
- public void putNextEntry(TarEntry entry) throws IOException {
- closeCurrentEntry();
-
- byte[] header = new byte[TarConstants.HEADER_BLOCK];
- entry.writeEntryHeader( header );
-
- write( header );
-
- currentEntry = entry;
- }
-
- /**
- * Closes the current tar entry
- *
- * @throws IOException
- */
- protected void closeCurrentEntry() throws IOException {
- if (currentEntry != null) {
- if (currentEntry.getSize() > currentFileSize) {
- throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
- + currentEntry.getSize() + "] has not been fully written." );
- }
-
- currentEntry = null;
- currentFileSize = 0;
-
- pad();
- }
- }
-
- /**
- * Pads the last content block
- *
- * @throws IOException
- */
- protected void pad() throws IOException {
- if (bytesWritten > 0) {
- int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
-
- if (extra > 0) {
- write( new byte[TarConstants.DATA_BLOCK - extra] );
- }
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java
deleted file mode 100755
index 50165765c..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kamranzafar/jtar/TarUtils.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-
-/**
- * @author Kamran
- *
- */
-public class TarUtils {
- /**
- * Determines the tar file size of the given folder/file path
- *
- * @param path
- * @return
- */
- public static long calculateTarSize(File path) {
- return tarSize(path) + TarConstants.EOF_BLOCK;
- }
-
- private static long tarSize(File dir) {
- long size = 0;
-
- if (dir.isFile()) {
- return entrySize(dir.length());
- } else {
- File[] subFiles = dir.listFiles();
-
- if (subFiles != null && subFiles.length > 0) {
- for (File file : subFiles) {
- if (file.isFile()) {
- size += entrySize(file.length());
- } else {
- size += tarSize(file);
- }
- }
- } else {
- // Empty folder header
- return TarConstants.HEADER_BLOCK;
- }
- }
-
- return size;
- }
-
- private static long entrySize(long fileSize) {
- long size = 0;
- size += TarConstants.HEADER_BLOCK; // Header
- size += fileSize; // File size
-
- long extra = size % TarConstants.DATA_BLOCK;
-
- if (extra > 0) {
- size += (TarConstants.DATA_BLOCK - extra); // pad
- }
-
- return size;
- }
-
- public static String trim(String s, char c) {
- StringBuffer tmp = new StringBuffer(s);
- for (int i = 0; i < tmp.length(); i++) {
- if (tmp.charAt(i) != c) {
- break;
- } else {
- tmp.deleteCharAt(i);
- }
- }
-
- for (int i = tmp.length() - 1; i >= 0; i--) {
- if (tmp.charAt(i) != c) {
- break;
- } else {
- tmp.deleteCharAt(i);
- }
- }
-
- return tmp.toString();
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
index 6a0c4d304..361975a4c 100644
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -1,46 +1,43 @@
-
package org.kivy.android;
import java.io.InputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import java.util.ArrayList;
+import java.util.Timer;
+import java.util.TimerTask;
-import android.view.ViewGroup;
-import android.view.SurfaceView;
import android.app.Activity;
-import android.content.Intent;
-import android.util.Log;
-import android.widget.Toast;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.PowerManager;
-import android.graphics.PixelFormat;
-import android.view.SurfaceHolder;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ApplicationInfo;
-import android.content.Intent;
-import android.widget.ImageView;
-import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+import android.content.res.Resources.NotFoundException;
import org.libsdl.app.SDLActivity;
-import org.kivy.android.PythonUtil;
import org.kivy.android.launcher.Project;
import org.renpy.android.ResourceManager;
-import org.renpy.android.AssetExtract;
public class PythonActivity extends SDLActivity {
@@ -57,10 +54,9 @@ public String getAppRoot() {
return app_root;
}
-
@Override
protected void onCreate(Bundle savedInstanceState) {
- Log.v(TAG, "My oncreate running");
+ Log.v(TAG, "PythonActivity onCreate running");
resourceManager = new ResourceManager(this);
Log.v(TAG, "About to do super onCreate");
@@ -68,7 +64,7 @@ protected void onCreate(Bundle savedInstanceState) {
Log.v(TAG, "Did super onCreate");
this.mActivity = this;
- this.showLoadingScreen();
+ this.showLoadingScreen(this.getLoadingScreen());
new UnpackFilesTask().execute(getAppRoot());
}
@@ -76,16 +72,8 @@ protected void onCreate(Bundle savedInstanceState) {
public void loadLibraries() {
String app_root = new String(getAppRoot());
File app_root_file = new File(app_root);
- PythonUtil.loadLibraries(app_root_file);
- }
-
- public void recursiveDelete(File f) {
- if (f.isDirectory()) {
- for (File r : f.listFiles()) {
- recursiveDelete(r);
- }
- }
- f.delete();
+ PythonUtil.loadLibraries(app_root_file,
+ new File(getApplicationInfo().nativeLibraryDir));
}
/**
@@ -116,7 +104,8 @@ private class UnpackFilesTask extends AsyncTask<String, Void, String> {
protected String doInBackground(String... params) {
File app_root_file = new File(params[0]);
Log.v(TAG, "Ready to unpack");
- unpackData("private", app_root_file);
+ PythonUtil.unpackAsset(mActivity, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
return null;
}
@@ -135,7 +124,7 @@ protected void onPostExecute(String result) {
// removed the loading screen. However, we still need it to
// show until the app is ready to render, so pop it back up
// on top of the SDL view.
- mActivity.showLoadingScreen();
+ mActivity.showLoadingScreen(getLoadingScreen());
String app_root_dir = getAppRoot();
if (getIntent() != null && getIntent().getAction() != null &&
@@ -143,9 +132,10 @@ protected void onPostExecute(String result) {
File path = new File(getIntent().getData().getSchemeSpecificPart());
Project p = Project.scanDirectory(path);
- SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", p.dir + "/main.py");
- SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", p.dir);
- SDLActivity.nativeSetEnv("ANDROID_APP_PATH", p.dir);
+ String entry_point = getEntryPoint(p.dir);
+ SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", p.dir + "/" + entry_point);
+ SDLActivity.nativeSetenv("ANDROID_ARGUMENT", p.dir);
+ SDLActivity.nativeSetenv("ANDROID_APP_PATH", p.dir);
if (p != null) {
if (p.landscape) {
@@ -164,18 +154,19 @@ protected void onPostExecute(String result) {
// pass
}
} else {
- SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo");
- SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", app_root_dir);
- SDLActivity.nativeSetEnv("ANDROID_APP_PATH", app_root_dir);
+ String entry_point = getEntryPoint(app_root_dir);
+ SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ SDLActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ SDLActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
}
String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
Log.v(TAG, "Setting env vars for start.c and Python to use");
- SDLActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory);
- SDLActivity.nativeSetEnv("ANDROID_UNPACK", app_root_dir);
- SDLActivity.nativeSetEnv("PYTHONHOME", app_root_dir);
- SDLActivity.nativeSetEnv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
- SDLActivity.nativeSetEnv("PYTHONOPTIMIZE", "2");
+ SDLActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ SDLActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ SDLActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ SDLActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ SDLActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
try {
Log.v(TAG, "Access to our meta-data...");
@@ -184,8 +175,8 @@ protected void onPostExecute(String result) {
PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
- mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
- mActivity.mWakeLock.acquire();
+ mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ mActivity.mWakeLock.acquire();
}
if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) {
Log.v(TAG, "Surface will be transparent.");
@@ -196,6 +187,20 @@ protected void onPostExecute(String result) {
}
} catch (PackageManager.NameNotFoundException e) {
}
+
+ // Launch app if that hasn't been done yet:
+ if (mActivity.mHasFocus && (
+ // never went into proper resume state:
+ mActivity.mCurrentNativeState == NativeState.INIT ||
+ (
+ // resumed earlier but wasn't ready yet
+ mActivity.mCurrentNativeState == NativeState.RESUMED &&
+ mActivity.mSDLThread == null
+ ))) {
+ // Because sometimes the app will get stuck here and never
+ // actually run, ensure that it gets launched if we're active:
+ mActivity.resumeNativeThread();
+ }
}
@Override
@@ -207,63 +212,6 @@ protected void onProgressUpdate(Void... values) {
}
}
- public void unpackData(final String resource, File target) {
-
- Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName());
-
- // The version of data in memory and on disk.
- String data_version = resourceManager.getString(resource + "_version");
- String disk_version = null;
-
- Log.v(TAG, "Data version is " + data_version);
-
- // If no version, no unpacking is necessary.
- if (data_version == null) {
- return;
- }
-
- // Check the current disk version, if any.
- String filesDir = target.getAbsolutePath();
- String disk_version_fn = filesDir + "/" + resource + ".version";
-
- try {
- byte buf[] = new byte[64];
- InputStream is = new FileInputStream(disk_version_fn);
- int len = is.read(buf);
- disk_version = new String(buf, 0, len);
- is.close();
- } catch (Exception e) {
- disk_version = "";
- }
-
- // If the disk data is out of date, extract it and write the
- // version file.
- // if (! data_version.equals(disk_version)) {
- if (! data_version.equals(disk_version)) {
- Log.v(TAG, "Extracting " + resource + " assets.");
-
- recursiveDelete(target);
- target.mkdirs();
-
- AssetExtract ae = new AssetExtract(this);
- if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) {
- toastError("Could not extract " + resource + " data.");
- }
-
- try {
- // Write .nomedia.
- new File(target, ".nomedia").createNewFile();
-
- // Write version file.
- FileOutputStream os = new FileOutputStream(disk_version_fn);
- os.write(data_version.getBytes());
- os.close();
- } catch (Exception e) {
- Log.w("python", e);
- }
- }
- }
-
public static ViewGroup getLayout() {
return mLayout;
}
@@ -341,17 +289,45 @@ protected void onActivityResult(int requestCode, int resultCode, Intent intent)
}
}
- public static void start_service(String serviceTitle, String serviceDescription,
- String pythonServiceArgument) {
+ public static void start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, true
+ );
+ }
+
+ public static void start_service_not_as_foreground(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, false
+ );
+ }
+
+ public static void _do_start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument,
+ boolean showForegroundNotification
+ ) {
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
- String filesDirectory = argument;
String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
serviceIntent.putExtra("androidPrivate", argument);
serviceIntent.putExtra("androidArgument", app_root_dir);
- serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo");
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
+ serviceIntent.putExtra("pythonName", "python");
serviceIntent.putExtra("pythonHome", app_root_dir);
serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
+ serviceIntent.putExtra("serviceStartAsForeground",
+ (showForegroundNotification ? "true" : "false")
+ );
serviceIntent.putExtra("serviceTitle", serviceTitle);
serviceIntent.putExtra("serviceDescription", serviceDescription);
serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
@@ -363,69 +339,118 @@ public static void stop_service() {
PythonActivity.mActivity.stopService(serviceIntent);
}
- /** Loading screen implementation
- * keepActive() is a method plugged in pollInputDevices in SDLActivity.
- * Once it's called twice, the loading screen will be removed.
- * The first call happen as soon as the window is created, but no image has been
- * displayed first. My tests showed that we can wait one more. This might delay
- * the real available of few hundred milliseconds.
- * The real deal is to know if a rendering has already happen. The previous
- * python-for-android and kivy was having something for that, but this new version
- * is not compatible, and would require a new kivy version.
- * In case of, the method PythonActivty.mActivity.removeLoadingScreen() can be called.
- */
+ /** Loading screen view **/
public static ImageView mImageView = null;
- int mLoadingCount = 2;
-
+ public static View mLottieView = null;
+ /** Whether main routine/actual app has started yet **/
+ protected boolean mAppConfirmedActive = false;
+ /** Timer for delayed loading screen removal. **/
+ protected Timer loadingScreenRemovalTimer = null;
+
+ // Overridden since it's called often, to check whether to remove the
+ // loading screen:
+ @Override
+ protected boolean sendCommand(int command, Object data) {
+ boolean result = super.sendCommand(command, data);
+ considerLoadingScreenRemoval();
+ return result;
+ }
+
+ /** Confirm that the app's main routine has been launched.
+ **/
@Override
- public void keepActive() {
- if (this.mLoadingCount > 0) {
- this.mLoadingCount -= 1;
- if (this.mLoadingCount == 0) {
- this.removeLoadingScreen();
+ public void appConfirmedActive() {
+ if (!mAppConfirmedActive) {
+ Log.v(TAG, "appConfirmedActive() -> preparing loading screen removal");
+ mAppConfirmedActive = true;
+ considerLoadingScreenRemoval();
}
- }
}
- public void removeLoadingScreen() {
- runOnUiThread(new Runnable() {
- public void run() {
- if (PythonActivity.mImageView != null &&
- PythonActivity.mImageView.getParent() != null) {
- ((ViewGroup)PythonActivity.mImageView.getParent()).removeView(
- PythonActivity.mImageView);
- PythonActivity.mImageView = null;
- }
- }
- });
+ /** This is called from various places to check whether the app's main
+ * routine has been launched already, and if it has, then the loading
+ * screen will be removed.
+ **/
+ public void considerLoadingScreenRemoval() {
+ if (loadingScreenRemovalTimer != null)
+ return;
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (((PythonActivity)PythonActivity.mSingleton).mAppConfirmedActive &&
+ loadingScreenRemovalTimer == null) {
+ // Remove loading screen but with a delay.
+ // (app can use p4a's android.loadingscreen module to
+ // do it quicker if it wants to)
+ // get a handler (call from main thread)
+ // this will run when timer elapses
+ TimerTask removalTask = new TimerTask() {
+ @Override
+ public void run() {
+ // post a runnable to the handler
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ PythonActivity activity =
+ ((PythonActivity)PythonActivity.mSingleton);
+ if (activity != null)
+ activity.removeLoadingScreen();
+ }
+ });
+ }
+ };
+ loadingScreenRemovalTimer = new Timer();
+ loadingScreenRemovalTimer.schedule(removalTask, 5000);
+ }
+ }
+ });
}
+ public void removeLoadingScreen() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ View view = mLottieView != null ? mLottieView : mImageView;
+ if (view != null && view.getParent() != null) {
+ ((ViewGroup)view.getParent()).removeView(view);
+ mLottieView = null;
+ mImageView = null;
+ }
+ }
+ });
+ }
- protected void showLoadingScreen() {
- // load the bitmap
- // 1. if the image is valid and we don't have layout yet, assign this bitmap
- // as main view.
- // 2. if we have a layout, just set it in the layout.
- // 3. If we have an mImageView already, then do nothing because it will have
- // already been made the content view or added to the layout.
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List<String> entryPoints = new ArrayList<String>();
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
- if (mImageView == null) {
- int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
- InputStream is = this.getResources().openRawResource(presplashId);
- Bitmap bitmap = null;
+ protected void showLoadingScreen(View view) {
try {
- bitmap = BitmapFactory.decodeStream(is);
- } finally {
- try {
- is.close();
- } catch (IOException e) {};
+ if (mLayout == null) {
+ setContentView(view);
+ } else if (view.getParent() == null) {
+ mLayout.addView(view);
+ }
+ } catch (IllegalStateException e) {
+ // The loading screen can be attempted to be applied twice if app
+ // is tabbed in/out, quickly.
+ // (Gives error "The specified child already has a parent.
+ // You must call removeView() on the child's parent first.")
}
+ }
- mImageView = new ImageView(this);
- mImageView.setImageBitmap(bitmap);
-
+ protected void setBackgroundColor(View view) {
/*
- * Set the presplash loading screen background color
+ * Set the presplash loading screen background color
* https://developer.android.com/reference/android/graphics/Color.html
* Parse the color string, and return the corresponding color-int.
* If the string cannot be parsed, throws an IllegalArgumentException exception.
@@ -436,45 +461,183 @@ protected void showLoadingScreen() {
*/
String backgroundColor = resourceManager.getString("presplash_color");
if (backgroundColor != null) {
- try {
- mImageView.setBackgroundColor(Color.parseColor(backgroundColor));
- } catch (IllegalArgumentException e) {}
- }
- mImageView.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT));
- mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
-
+ try {
+ view.setBackgroundColor(Color.parseColor(backgroundColor));
+ } catch (IllegalArgumentException e) {}
+ }
}
- if (mLayout == null) {
- setContentView(mImageView);
- } else if (PythonActivity.mImageView.getParent() == null){
- mLayout.addView(mImageView);
- }
+ protected View getLoadingScreen() {
+ // If we have an mLottieView or mImageView already, then do
+ // nothing because it will have already been made the content
+ // view or added to the layout.
+ if (mLottieView != null || mImageView != null) {
+ // we already have a splash screen
+ return mLottieView != null ? mLottieView : mImageView;
+ }
+ // first try to load the lottie one
+ try {
+ mLottieView = getLayoutInflater().inflate(
+ this.resourceManager.getIdentifier("lottie", "layout"),
+ mLayout,
+ false
+ );
+ try {
+ if (mLayout == null) {
+ setContentView(mLottieView);
+ } else if (PythonActivity.mLottieView.getParent() == null) {
+ mLayout.addView(mLottieView);
+ }
+ } catch (IllegalStateException e) {
+ // The loading screen can be attempted to be applied twice if app
+ // is tabbed in/out, quickly.
+ // (Gives error "The specified child already has a parent.
+ // You must call removeView() on the child's parent first.")
+ }
+ setBackgroundColor(mLottieView);
+ return mLottieView;
+ }
+ catch (NotFoundException e) {
+ Log.v("SDL", "couldn't find lottie layout or animation, trying static splash");
+ }
+
+ // no lottie asset, try to load the static image then
+ int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
+ InputStream is = this.getResources().openRawResource(presplashId);
+ Bitmap bitmap = null;
+ try {
+ bitmap = BitmapFactory.decodeStream(is);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {};
+ }
+
+ mImageView = new ImageView(this);
+ mImageView.setImageBitmap(bitmap);
+ setBackgroundColor(mImageView);
+
+ mImageView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
+ mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ return mImageView;
}
-
+
@Override
protected void onPause() {
- // fooabc
- if ( this.mWakeLock != null && mWakeLock.isHeld()){
- this.mWakeLock.release();
+ if (this.mWakeLock != null && mWakeLock.isHeld()) {
+ this.mWakeLock.release();
}
Log.v(TAG, "onPause()");
- super.onPause();
+ try {
+ super.onPause();
+ } catch (UnsatisfiedLinkError e) {
+ // Catch pause while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
}
@Override
protected void onResume() {
- if ( this.mWakeLock != null){
- this.mWakeLock.acquire();
- }
- Log.v(TAG, "onResume()");
- super.onResume();
+ if (this.mWakeLock != null) {
+ this.mWakeLock.acquire();
+ }
+ Log.v(TAG, "onResume()");
+ try {
+ super.onResume();
+ } catch (UnsatisfiedLinkError e) {
+ // Catch resume while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
+ considerLoadingScreenRemoval();
}
-
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ try {
+ super.onWindowFocusChanged(hasFocus);
+ } catch (UnsatisfiedLinkError e) {
+ // Catch window focus while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
+ considerLoadingScreenRemoval();
+ }
+
+ /**
+ * Used by android.permissions p4a module to register a call back after
+ * requesting runtime permissions
+ **/
+ public interface PermissionsCallback {
+ void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
+ }
+
+ private PermissionsCallback permissionCallback;
+ private boolean havePermissionsCallback = false;
+ public void addPermissionsCallback(PermissionsCallback callback) {
+ permissionCallback = callback;
+ havePermissionsCallback = true;
+ Log.v(TAG, "addPermissionsCallback(): Added callback for onRequestPermissionsResult");
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ Log.v(TAG, "onRequestPermissionsResult()");
+ if (havePermissionsCallback) {
+ Log.v(TAG, "onRequestPermissionsResult passed to callback");
+ permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ /**
+ * Used by android.permissions p4a module to check a permission
+ **/
+ public boolean checkCurrentPermission(String permission) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return true;
+
+ try {
+ java.lang.reflect.Method methodCheckPermission =
+ Activity.class.getMethod("checkSelfPermission", String.class);
+ Object resultObj = methodCheckPermission.invoke(this, permission);
+ int result = Integer.parseInt(resultObj.toString());
+ if (result == PackageManager.PERMISSION_GRANTED)
+ return true;
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Used by android.permissions p4a module to request runtime permissions
+ **/
+ public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return;
+ try {
+ java.lang.reflect.Method methodRequestPermission =
+ Activity.class.getMethod("requestPermissions",
+ String[].class, int.class);
+ methodRequestPermission.invoke(this, permissions, requestCode);
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ }
+
+ public void requestPermissions(String[] permissions) {
+ requestPermissionsWithRequestCode(permissions, 1);
+ }
+
+ public static void changeKeyboard(int inputType) {
+ if (SDLActivity.keyboardInputType != inputType){
+ SDLActivity.keyboardInputType = inputType;
+ InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.restartInput(mTextEdit);
+ }
+ }
}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java
deleted file mode 100644
index 6574dae78..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonUtil.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.kivy.android;
-
-import java.io.File;
-
-import android.util.Log;
-import java.util.ArrayList;
-import java.io.FilenameFilter;
-import java.util.regex.Pattern;
-
-public class PythonUtil {
- private static final String TAG = "pythonutil";
-
- protected static void addLibraryIfExists(ArrayList<String> libsList, String pattern, File libsDir) {
- // pattern should be the name of the lib file, without the
- // preceding "lib" or suffix ".so", for instance "ssl.*" will
- // match files of the form "libssl.*.so".
- File [] files = libsDir.listFiles();
-
- pattern = "lib" + pattern + "\\.so";
- Pattern p = Pattern.compile(pattern);
- for (int i = 0; i < files.length; ++i) {
- File file = files[i];
- String name = file.getName();
- Log.v(TAG, "Checking pattern " + pattern + " against " + name);
- if (p.matcher(name).matches()) {
- Log.v(TAG, "Pattern " + pattern + " matched file " + name);
- libsList.add(name.substring(3, name.length() - 3));
- }
- }
- }
-
- protected static ArrayList<String> getLibraries(File filesDir) {
-
- String libsDirPath = filesDir.getParentFile().getParentFile().getAbsolutePath() + "/lib/";
- File libsDir = new File(libsDirPath);
-
- ArrayList<String> libsList = new ArrayList<String>();
- addLibraryIfExists(libsList, "crystax", libsDir);
- addLibraryIfExists(libsList, "sqlite3", libsDir);
- libsList.add("SDL2");
- libsList.add("SDL2_image");
- libsList.add("SDL2_mixer");
- libsList.add("SDL2_ttf");
- addLibraryIfExists(libsList, "ssl.*", libsDir);
- addLibraryIfExists(libsList, "crypto.*", libsDir);
- libsList.add("python2.7");
- libsList.add("python3.5m");
- libsList.add("main");
- return libsList;
- }
-
- public static void loadLibraries(File filesDir) {
-
- String filesDirPath = filesDir.getAbsolutePath();
- boolean foundPython = false;
-
- for (String lib : getLibraries(filesDir)) {
- Log.v(TAG, "Loading library: " + lib);
- try {
- System.loadLibrary(lib);
- if (lib.startsWith("python")) {
- foundPython = true;
- }
- } catch(UnsatisfiedLinkError e) {
- // If this is the last possible libpython
- // load, and it has failed, give a more
- // general error
- Log.v(TAG, "Library loading error: " + e.getMessage());
- if (lib.startsWith("python3.6") && !foundPython) {
- throw new java.lang.RuntimeException("Could not load any libpythonXXX.so");
- } else if (lib.startsWith("python")) {
- continue;
- } else {
- Log.v(TAG, "An UnsatisfiedLinkError occurred loading " + lib);
- throw e;
- }
- }
- }
-
- try {
- System.load(filesDirPath + "/lib/python2.7/lib-dynload/_io.so");
- System.load(filesDirPath + "/lib/python2.7/lib-dynload/unicodedata.so");
- } catch(UnsatisfiedLinkError e) {
- Log.v(TAG, "Failed to load _io.so or unicodedata.so...but that's okay.");
- }
-
- try {
- // System.loadLibrary("ctypes");
- System.load(filesDirPath + "/lib/python2.7/lib-dynload/_ctypes.so");
- } catch(UnsatisfiedLinkError e) {
- Log.v(TAG, "Unsatisfied linker when loading ctypes");
- }
-
- Log.v(TAG, "Loaded everything!");
- }
-}
-
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java
deleted file mode 100644
index 9911356ba..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.kivy.android.concurrency;
-
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Created by ryan on 3/28/14.
- */
-public class PythonEvent {
- private final Lock lock = new ReentrantLock();
- private final Condition cond = lock.newCondition();
- private boolean flag = false;
-
- public void set() {
- lock.lock();
- try {
- flag = true;
- cond.signalAll();
- } finally {
- lock.unlock();
- }
- }
-
- public void wait_() throws InterruptedException {
- lock.lock();
- try {
- while (!flag) {
- cond.await();
- }
- } finally {
- lock.unlock();
- }
- }
-
- public void clear() {
- lock.lock();
- try {
- flag = false;
- cond.signalAll();
- } finally {
- lock.unlock();
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java
deleted file mode 100644
index 22f9d903e..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/concurrency/PythonLock.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.kivy.android.concurrency;
-
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Created by ryan on 3/28/14.
- */
-public class PythonLock {
- private final Lock lock = new ReentrantLock();
-
- public void acquire() {
- lock.lock();
- }
-
- public void release() {
- lock.unlock();
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java
index f66debfec..457f83f79 100644
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java
@@ -1,29 +1,20 @@
package org.kivy.android.launcher;
import android.app.Activity;
-import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Gravity;
import android.widget.ArrayAdapter;
import android.widget.TextView;
-import android.widget.LinearLayout;
import android.widget.ImageView;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.Log;
import org.renpy.android.ResourceManager;
public class ProjectAdapter extends ArrayAdapter<Project> {
- private Activity mContext;
private ResourceManager resourceManager;
public ProjectAdapter(Activity context) {
super(context, 0);
-
- mContext = context;
resourceManager = new ResourceManager(context);
}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java
index 17eec32f0..486f88bae 100644
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java
@@ -1,11 +1,8 @@
package org.kivy.android.launcher;
import android.app.Activity;
-import android.os.Bundle;
import android.content.Intent;
-import android.content.res.Resources;
-import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
@@ -13,7 +10,6 @@
import android.os.Environment;
import java.io.File;
-import java.util.ArrayList;
import java.util.Arrays;
import android.net.Uri;
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java
deleted file mode 100644
index e1dc08468..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java
+++ /dev/null
@@ -1,1579 +0,0 @@
-package org.libsdl.app;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.lang.reflect.Method;
-
-import android.app.*;
-import android.content.*;
-import android.view.*;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AbsoluteLayout;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.os.*;
-import android.util.Log;
-import android.util.SparseArray;
-import android.graphics.*;
-import android.graphics.drawable.Drawable;
-import android.media.*;
-import android.hardware.*;
-
-/**
- SDL Activity
-*/
-public class SDLActivity extends Activity {
- private static final String TAG = "SDL";
-
- // Keep track of the paused state
- public static boolean mIsPaused, mIsSurfaceReady, mHasFocus;
- public static boolean mExitCalledFromJava;
-
- /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
- public static boolean mBrokenLibraries;
-
- // If we want to separate mouse and touch events.
- // This is only toggled in native code when a hint is set!
- public static boolean mSeparateMouseAndTouch;
-
- // Main components
- protected static SDLActivity mSingleton;
- protected static SDLSurface mSurface;
- protected static View mTextEdit;
- protected static ViewGroup mLayout;
- protected static SDLJoystickHandler mJoystickHandler;
-
- // This is what SDL runs in. It invokes SDL_main(), eventually
- protected static Thread mSDLThread;
-
- // Audio
- protected static AudioTrack mAudioTrack;
-
- /**
- * This method is called by SDL before loading the native shared libraries.
- * It can be overridden to provide names of shared libraries to be loaded.
- * The default implementation returns the defaults. It never returns null.
- * An array returned by a new implementation must at least contain "SDL2".
- * Also keep in mind that the order the libraries are loaded may matter.
- * @return names of shared libraries to be loaded (e.g. "SDL2", "main").
- */
- protected String[] getLibraries() {
- return new String[] {
- "SDL2",
- // "SDL2_image",
- // "SDL2_mixer",
- // "SDL2_net",
- // "SDL2_ttf",
- "main"
- };
- }
-
- // Load the .so
- public void loadLibraries() {
- for (String lib : getLibraries()) {
- System.loadLibrary(lib);
- }
- }
-
- /**
- * This method is called by SDL before starting the native application thread.
- * It can be overridden to provide the arguments after the application name.
- * The default implementation returns an empty array. It never returns null.
- * @return arguments for the native application.
- */
- protected String[] getArguments() {
- return new String[0];
- }
-
- public static void initialize() {
- // The static nature of the singleton and Android quirkyness force us to initialize everything here
- // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
- mSingleton = null;
- mSurface = null;
- mTextEdit = null;
- mLayout = null;
- mJoystickHandler = null;
- mSDLThread = null;
- mAudioTrack = null;
- mExitCalledFromJava = false;
- mBrokenLibraries = false;
- mIsPaused = false;
- mIsSurfaceReady = false;
- mHasFocus = true;
- }
-
- // Setup
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- Log.v("SDL", "Device: " + android.os.Build.DEVICE);
- Log.v("SDL", "Model: " + android.os.Build.MODEL);
- Log.v("SDL", "onCreate():" + mSingleton);
- super.onCreate(savedInstanceState);
-
- SDLActivity.initialize();
- // So we can call stuff from static callbacks
- mSingleton = this;
- }
-
- // We don't do this in onCreate because we unpack and load the app data on a thread
- // and we can't run setup tasks until that thread completes.
- protected void finishLoad() {
- // Load shared libraries
- String errorMsgBrokenLib = "";
- try {
- loadLibraries();
- } catch(UnsatisfiedLinkError e) {
- System.err.println(e.getMessage());
- mBrokenLibraries = true;
- errorMsgBrokenLib = e.getMessage();
- } catch(Exception e) {
- System.err.println(e.getMessage());
- mBrokenLibraries = true;
- errorMsgBrokenLib = e.getMessage();
- }
-
- if (mBrokenLibraries)
- {
- AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
- dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
- + System.getProperty("line.separator")
- + System.getProperty("line.separator")
- + "Error: " + errorMsgBrokenLib);
- dlgAlert.setTitle("SDL Error");
- dlgAlert.setPositiveButton("Exit",
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,int id) {
- // if this button is clicked, close current activity
- SDLActivity.mSingleton.finish();
- }
- });
- dlgAlert.setCancelable(false);
- dlgAlert.create().show();
-
- return;
- }
-
- // Set up the surface
- mSurface = new SDLSurface(getApplication());
-
- if(Build.VERSION.SDK_INT >= 12) {
- mJoystickHandler = new SDLJoystickHandler_API12();
- }
- else {
- mJoystickHandler = new SDLJoystickHandler();
- }
-
- mLayout = new AbsoluteLayout(this);
- mLayout.addView(mSurface);
-
- setContentView(mLayout);
- }
-
- // Events
- @Override
- protected void onPause() {
- Log.v("SDL", "onPause()");
- super.onPause();
-
- if (SDLActivity.mBrokenLibraries) {
- return;
- }
-
- SDLActivity.handlePause();
- }
-
- @Override
- protected void onResume() {
- Log.v("SDL", "onResume()");
- super.onResume();
-
- if (SDLActivity.mBrokenLibraries) {
- return;
- }
-
- SDLActivity.handleResume();
- }
-
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- Log.v("SDL", "onWindowFocusChanged(): " + hasFocus);
-
- if (SDLActivity.mBrokenLibraries) {
- return;
- }
-
- SDLActivity.mHasFocus = hasFocus;
- if (hasFocus) {
- SDLActivity.handleResume();
- }
- }
-
- @Override
- public void onLowMemory() {
- Log.v("SDL", "onLowMemory()");
- super.onLowMemory();
-
- if (SDLActivity.mBrokenLibraries) {
- return;
- }
-
- SDLActivity.nativeLowMemory();
- }
-
- @Override
- protected void onDestroy() {
- Log.v("SDL", "onDestroy()");
-
- if (SDLActivity.mBrokenLibraries) {
- super.onDestroy();
- // Reset everything in case the user re opens the app
- SDLActivity.initialize();
- return;
- }
-
- // Send a quit message to the application
- SDLActivity.mExitCalledFromJava = true;
- SDLActivity.nativeQuit();
-
- // Now wait for the SDL thread to quit
- if (SDLActivity.mSDLThread != null) {
- try {
- SDLActivity.mSDLThread.join();
- } catch(Exception e) {
- Log.v("SDL", "Problem stopping thread: " + e);
- }
- SDLActivity.mSDLThread = null;
-
- //Log.v("SDL", "Finished waiting for SDL thread");
- }
-
- super.onDestroy();
- // Reset everything in case the user re opens the app
- SDLActivity.initialize();
-
- // Completely closes application.
- System.exit(0);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
-
- if (SDLActivity.mBrokenLibraries) {
- return false;
- }
-
- int keyCode = event.getKeyCode();
- // Ignore certain special keys so they're handled by Android
- if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
- keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
- keyCode == KeyEvent.KEYCODE_CAMERA ||
- keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */
- keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */
- ) {
- return false;
- }
- return super.dispatchKeyEvent(event);
- }
-
- /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed
- * is the first to be called, mIsSurfaceReady should still be set
- * to 'true' during the call to onPause (in a usual scenario).
- */
- public static void handlePause() {
- if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) {
- SDLActivity.mIsPaused = true;
- SDLActivity.nativePause();
- mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false);
- }
- }
-
- /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready.
- * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume
- * every time we get one of those events, only if it comes after surfaceDestroyed
- */
- public static void handleResume() {
- if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) {
- SDLActivity.mIsPaused = false;
- SDLActivity.nativeResume();
- mSurface.handleResume();
- }
- }
-
- /* The native thread has finished */
- public static void handleNativeExit() {
- SDLActivity.mSDLThread = null;
- mSingleton.finish();
- }
-
-
- // Messages from the SDLMain thread
- static final int COMMAND_CHANGE_TITLE = 1;
- static final int COMMAND_UNUSED = 2;
- static final int COMMAND_TEXTEDIT_HIDE = 3;
- static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
-
- protected static final int COMMAND_USER = 0x8000;
-
- /**
- * This method is called by SDL if SDL did not handle a message itself.
- * This happens if a received message contains an unsupported command.
- * Method can be overwritten to handle Messages in a different class.
- * @param command the command of the message.
- * @param param the parameter of the message. May be null.
- * @return if the message was handled in overridden method.
- */
- protected boolean onUnhandledMessage(int command, Object param) {
- return false;
- }
-
- /**
- * A Handler class for Messages from native SDL applications.
- * It uses current Activities as target (e.g. for the title).
- * static to prevent implicit references to enclosing object.
- */
- protected static class SDLCommandHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- Context context = getContext();
- if (context == null) {
- Log.e(TAG, "error handling message, getContext() returned null");
- return;
- }
- switch (msg.arg1) {
- case COMMAND_CHANGE_TITLE:
- if (context instanceof Activity) {
- ((Activity) context).setTitle((String)msg.obj);
- } else {
- Log.e(TAG, "error handling message, getContext() returned no Activity");
- }
- break;
- case COMMAND_TEXTEDIT_HIDE:
- if (mTextEdit != null) {
- mTextEdit.setVisibility(View.GONE);
-
- InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
- }
- break;
- case COMMAND_SET_KEEP_SCREEN_ON:
- {
- Window window = ((Activity) context).getWindow();
- if (window != null) {
- if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- } else {
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
- }
- break;
- }
- default:
- if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
- Log.e(TAG, "error handling message, command is " + msg.arg1);
- }
- }
- }
- }
-
- // Handler for the messages
- Handler commandHandler = new SDLCommandHandler();
-
- // Send a message from the SDLMain thread
- boolean sendCommand(int command, Object data) {
- Message msg = commandHandler.obtainMessage();
- msg.arg1 = command;
- msg.obj = data;
- return commandHandler.sendMessage(msg);
- }
-
- // C functions we call
- public static native int nativeInit(Object arguments);
- public static native void nativeLowMemory();
- public static native void nativeQuit();
- public static native void nativePause();
- public static native void nativeResume();
- public static native void onNativeResize(int x, int y, int format, float rate);
- public static native int onNativePadDown(int device_id, int keycode);
- public static native int onNativePadUp(int device_id, int keycode);
- public static native void onNativeJoy(int device_id, int axis,
- float value);
- public static native void onNativeHat(int device_id, int hat_id,
- int x, int y);
- public static native void nativeSetEnv(String j_name, String j_value);
- public static native void onNativeKeyDown(int keycode);
- public static native void onNativeKeyUp(int keycode);
- public static native void onNativeKeyboardFocusLost();
- public static native void onNativeMouse(int button, int action, float x, float y);
- public static native void onNativeTouch(int touchDevId, int pointerFingerId,
- int action, float x,
- float y, float p);
- public static native void onNativeAccel(float x, float y, float z);
- public static native void onNativeSurfaceChanged();
- public static native void onNativeSurfaceDestroyed();
- public static native void nativeFlipBuffers();
- public static native int nativeAddJoystick(int device_id, String name,
- int is_accelerometer, int nbuttons,
- int naxes, int nhats, int nballs);
- public static native int nativeRemoveJoystick(int device_id);
- public static native String nativeGetHint(String name);
-
- /**
- * This method is called by SDL using JNI.
- */
- public static void flipBuffers() {
- SDLActivity.nativeFlipBuffers();
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static boolean setActivityTitle(String title) {
- // Called from SDLMain() thread and can't directly affect the view
- return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static boolean sendMessage(int command, int param) {
- return mSingleton.sendCommand(command, Integer.valueOf(param));
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static Context getContext() {
- return mSingleton;
- }
-
- /**
- * This method is called by SDL using JNI.
- * @return result of getSystemService(name) but executed on UI thread.
- */
- public Object getSystemServiceFromUiThread(final String name) {
- final Object lock = new Object();
- final Object[] results = new Object[2]; // array for writable variables
- synchronized (lock) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- synchronized (lock) {
- results[0] = getSystemService(name);
- results[1] = Boolean.TRUE;
- lock.notify();
- }
- }
- });
- if (results[1] == null) {
- try {
- lock.wait();
- } catch (InterruptedException ex) {
- ex.printStackTrace();
- }
- }
- }
- return results[0];
- }
-
- static class ShowTextInputTask implements Runnable {
- /*
- * This is used to regulate the pan&scan method to have some offset from
- * the bottom edge of the input region and the top edge of an input
- * method (soft keyboard)
- */
- static final int HEIGHT_PADDING = 15;
-
- public int x, y, w, h;
-
- public ShowTextInputTask(int x, int y, int w, int h) {
- this.x = x;
- this.y = y;
- this.w = w;
- this.h = h;
- }
-
- @Override
- public void run() {
- AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams(
- w, h + HEIGHT_PADDING, x, y);
-
- if (mTextEdit == null) {
- mTextEdit = new DummyEdit(getContext());
-
- mLayout.addView(mTextEdit, params);
- } else {
- mTextEdit.setLayoutParams(params);
- }
-
- mTextEdit.setVisibility(View.VISIBLE);
- mTextEdit.requestFocus();
-
- InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(mTextEdit, 0);
- }
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static boolean showTextInput(int x, int y, int w, int h) {
- // Transfer the task to the main thread as a Runnable
- return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static Surface getNativeSurface() {
- return SDLActivity.mSurface.getNativeSurface();
- }
-
- // Audio
-
- /**
- * This method is called by SDL using JNI.
- */
- public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
- int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
- int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
- int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
-
- Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
- // Let the user pick a larger buffer if they really want -- but ye
- // gods they probably shouldn't, the minimums are horrifyingly high
- // latency already
- desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
-
- if (mAudioTrack == null) {
- mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
- channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
-
- // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
- // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
- // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
-
- if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
- Log.e("SDL", "Failed during initialization of Audio Track");
- mAudioTrack = null;
- return -1;
- }
-
- mAudioTrack.play();
- }
-
- Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
- return 0;
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static void audioWriteShortBuffer(short[] buffer) {
- for (int i = 0; i < buffer.length; ) {
- int result = mAudioTrack.write(buffer, i, buffer.length - i);
- if (result > 0) {
- i += result;
- } else if (result == 0) {
- try {
- Thread.sleep(1);
- } catch(InterruptedException e) {
- // Nom nom
- }
- } else {
- Log.w("SDL", "SDL audio: error return from write(short)");
- return;
- }
- }
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static void audioWriteByteBuffer(byte[] buffer) {
- for (int i = 0; i < buffer.length; ) {
- int result = mAudioTrack.write(buffer, i, buffer.length - i);
- if (result > 0) {
- i += result;
- } else if (result == 0) {
- try {
- Thread.sleep(1);
- } catch(InterruptedException e) {
- // Nom nom
- }
- } else {
- Log.w("SDL", "SDL audio: error return from write(byte)");
- return;
- }
- }
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static void audioQuit() {
- if (mAudioTrack != null) {
- mAudioTrack.stop();
- mAudioTrack = null;
- }
- }
-
- // Input
-
- /**
- * This method is called by SDL using JNI.
- * @return an array which may be empty but is never null.
- */
- public static int[] inputGetInputDeviceIds(int sources) {
- int[] ids = InputDevice.getDeviceIds();
- int[] filtered = new int[ids.length];
- int used = 0;
- for (int i = 0; i < ids.length; ++i) {
- InputDevice device = InputDevice.getDevice(ids[i]);
- if ((device != null) && ((device.getSources() & sources) != 0)) {
- filtered[used++] = device.getId();
- }
- }
- return Arrays.copyOf(filtered, used);
- }
-
- // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
- public static boolean handleJoystickMotionEvent(MotionEvent event) {
- return mJoystickHandler.handleMotionEvent(event);
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static void pollInputDevices() {
- if (SDLActivity.mSDLThread != null) {
- mJoystickHandler.pollInputDevices();
- SDLActivity.mSingleton.keepActive();
- }
- }
-
- /**
- * Trick needed for loading screen
- */
- public void keepActive() {
- }
-
- // APK extension files support
-
- /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
- private Object expansionFile;
-
- /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
- private Method expansionFileMethod;
-
- /**
- * This method is called by SDL using JNI.
- */
- public InputStream openAPKExtensionInputStream(String fileName) throws IOException {
- // Get a ZipResourceFile representing a merger of both the main and patch files
- if (expansionFile == null) {
- Integer mainVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"));
- Integer patchVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"));
-
- try {
- // To avoid direct dependency on Google APK extension library that is
- // not a part of Android SDK we access it using reflection
- expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
- .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
- .invoke(null, this, mainVersion, patchVersion);
-
- expansionFileMethod = expansionFile.getClass()
- .getMethod("getInputStream", String.class);
- } catch (Exception ex) {
- ex.printStackTrace();
- expansionFile = null;
- expansionFileMethod = null;
- }
- }
-
- // Get an input stream for a known file inside the expansion file ZIPs
- InputStream fileStream;
- try {
- fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
- } catch (Exception ex) {
- ex.printStackTrace();
- fileStream = null;
- }
-
- if (fileStream == null) {
- throw new IOException();
- }
-
- return fileStream;
- }
-
- // Messagebox
-
- /** Result of current messagebox. Also used for blocking the calling thread. */
- protected final int[] messageboxSelection = new int[1];
-
- /** Id of current dialog. */
- protected int dialogs = 0;
-
- /**
- * This method is called by SDL using JNI.
- * Shows the messagebox from UI thread and block calling thread.
- * buttonFlags, buttonIds and buttonTexts must have same length.
- * @param buttonFlags array containing flags for every button.
- * @param buttonIds array containing id for every button.
- * @param buttonTexts array containing text for every button.
- * @param colors null for default or array of length 5 containing colors.
- * @return button id or -1.
- */
- public int messageboxShowMessageBox(
- final int flags,
- final String title,
- final String message,
- final int[] buttonFlags,
- final int[] buttonIds,
- final String[] buttonTexts,
- final int[] colors) {
-
- messageboxSelection[0] = -1;
-
- // sanity checks
-
- if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
- return -1; // implementation broken
- }
-
- // collect arguments for Dialog
-
- final Bundle args = new Bundle();
- args.putInt("flags", flags);
- args.putString("title", title);
- args.putString("message", message);
- args.putIntArray("buttonFlags", buttonFlags);
- args.putIntArray("buttonIds", buttonIds);
- args.putStringArray("buttonTexts", buttonTexts);
- args.putIntArray("colors", colors);
-
- // trigger Dialog creation on UI thread
-
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- showDialog(dialogs++, args);
- }
- });
-
- // block the calling thread
-
- synchronized (messageboxSelection) {
- try {
- messageboxSelection.wait();
- } catch (InterruptedException ex) {
- ex.printStackTrace();
- return -1;
- }
- }
-
- // return selected value
-
- return messageboxSelection[0];
- }
-
- @Override
- protected Dialog onCreateDialog(int ignore, Bundle args) {
-
- // TODO set values from "flags" to messagebox dialog
-
- // get colors
-
- int[] colors = args.getIntArray("colors");
- int backgroundColor;
- int textColor;
- int buttonBorderColor;
- int buttonBackgroundColor;
- int buttonSelectedColor;
- if (colors != null) {
- int i = -1;
- backgroundColor = colors[++i];
- textColor = colors[++i];
- buttonBorderColor = colors[++i];
- buttonBackgroundColor = colors[++i];
- buttonSelectedColor = colors[++i];
- } else {
- backgroundColor = Color.TRANSPARENT;
- textColor = Color.TRANSPARENT;
- buttonBorderColor = Color.TRANSPARENT;
- buttonBackgroundColor = Color.TRANSPARENT;
- buttonSelectedColor = Color.TRANSPARENT;
- }
-
- // create dialog with title and a listener to wake up calling thread
-
- final Dialog dialog = new Dialog(this);
- dialog.setTitle(args.getString("title"));
- dialog.setCancelable(false);
- dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface unused) {
- synchronized (messageboxSelection) {
- messageboxSelection.notify();
- }
- }
- });
-
- // create text
-
- TextView message = new TextView(this);
- message.setGravity(Gravity.CENTER);
- message.setText(args.getString("message"));
- if (textColor != Color.TRANSPARENT) {
- message.setTextColor(textColor);
- }
-
- // create buttons
-
- int[] buttonFlags = args.getIntArray("buttonFlags");
- int[] buttonIds = args.getIntArray("buttonIds");
- String[] buttonTexts = args.getStringArray("buttonTexts");
-
- final SparseArray<Button> mapping = new SparseArray<Button>();
-
- LinearLayout buttons = new LinearLayout(this);
- buttons.setOrientation(LinearLayout.HORIZONTAL);
- buttons.setGravity(Gravity.CENTER);
- for (int i = 0; i < buttonTexts.length; ++i) {
- Button button = new Button(this);
- final int id = buttonIds[i];
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- messageboxSelection[0] = id;
- dialog.dismiss();
- }
- });
- if (buttonFlags[i] != 0) {
- // see SDL_messagebox.h
- if ((buttonFlags[i] & 0x00000001) != 0) {
- mapping.put(KeyEvent.KEYCODE_ENTER, button);
- }
- if ((buttonFlags[i] & 0x00000002) != 0) {
- mapping.put(111, button); /* API 11: KeyEvent.KEYCODE_ESCAPE */
- }
- }
- button.setText(buttonTexts[i]);
- if (textColor != Color.TRANSPARENT) {
- button.setTextColor(textColor);
- }
- if (buttonBorderColor != Color.TRANSPARENT) {
- // TODO set color for border of messagebox button
- }
- if (buttonBackgroundColor != Color.TRANSPARENT) {
- Drawable drawable = button.getBackground();
- if (drawable == null) {
- // setting the color this way removes the style
- button.setBackgroundColor(buttonBackgroundColor);
- } else {
- // setting the color this way keeps the style (gradient, padding, etc.)
- drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);
- }
- }
- if (buttonSelectedColor != Color.TRANSPARENT) {
- // TODO set color for selected messagebox button
- }
- buttons.addView(button);
- }
-
- // create content
-
- LinearLayout content = new LinearLayout(this);
- content.setOrientation(LinearLayout.VERTICAL);
- content.addView(message);
- content.addView(buttons);
- if (backgroundColor != Color.TRANSPARENT) {
- content.setBackgroundColor(backgroundColor);
- }
-
- // add content to dialog and return
-
- dialog.setContentView(content);
- dialog.setOnKeyListener(new Dialog.OnKeyListener() {
- @Override
- public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
- Button button = mapping.get(keyCode);
- if (button != null) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- button.performClick();
- }
- return true; // also for ignored actions
- }
- return false;
- }
- });
-
- return dialog;
- }
-}
-
-/**
- Simple nativeInit() runnable
-*/
-class SDLMain implements Runnable {
- @Override
- public void run() {
- // Runs SDL_main()
- SDLActivity.nativeInit(SDLActivity.mSingleton.getArguments());
-
- //Log.v("SDL", "SDL thread terminated");
- }
-}
-
-
-/**
- SDLSurface. This is what we draw on, so we need to know when it's created
- in order to do anything useful.
-
- Because of this, that's where we set up the SDL thread
-*/
-class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
- View.OnKeyListener, View.OnTouchListener, SensorEventListener {
-
- // Sensors
- protected static SensorManager mSensorManager;
- protected static Display mDisplay;
-
- // Keep track of the surface size to normalize touch events
- protected static float mWidth, mHeight;
-
- // Startup
- public SDLSurface(Context context) {
- super(context);
- getHolder().addCallback(this);
-
- setFocusable(true);
- setFocusableInTouchMode(true);
- requestFocus();
- setOnKeyListener(this);
- setOnTouchListener(this);
-
- mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
-
- if(Build.VERSION.SDK_INT >= 12) {
- setOnGenericMotionListener(new SDLGenericMotionListener_API12());
- }
-
- // Some arbitrary defaults to avoid a potential division by zero
- mWidth = 1.0f;
- mHeight = 1.0f;
- }
-
- public void handleResume() {
- setFocusable(true);
- setFocusableInTouchMode(true);
- requestFocus();
- setOnKeyListener(this);
- setOnTouchListener(this);
- enableSensor(Sensor.TYPE_ACCELEROMETER, true);
- }
-
- public Surface getNativeSurface() {
- return getHolder().getSurface();
- }
-
- // Called when we have a valid drawing surface
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- Log.v("SDL", "surfaceCreated()");
- holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
- }
-
- // Called when we lose the surface
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- Log.v("SDL", "surfaceDestroyed()");
- // Call this *before* setting mIsSurfaceReady to 'false'
- SDLActivity.handlePause();
- SDLActivity.mIsSurfaceReady = false;
- SDLActivity.onNativeSurfaceDestroyed();
- }
-
- // Called when the surface is resized
- @Override
- public void surfaceChanged(SurfaceHolder holder,
- int format, int width, int height) {
- Log.v("SDL", "surfaceChanged()");
-
- int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
- switch (format) {
- case PixelFormat.A_8:
- Log.v("SDL", "pixel format A_8");
- break;
- case PixelFormat.LA_88:
- Log.v("SDL", "pixel format LA_88");
- break;
- case PixelFormat.L_8:
- Log.v("SDL", "pixel format L_8");
- break;
- case PixelFormat.RGBA_4444:
- Log.v("SDL", "pixel format RGBA_4444");
- sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
- break;
- case PixelFormat.RGBA_5551:
- Log.v("SDL", "pixel format RGBA_5551");
- sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
- break;
- case PixelFormat.RGBA_8888:
- Log.v("SDL", "pixel format RGBA_8888");
- sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
- break;
- case PixelFormat.RGBX_8888:
- Log.v("SDL", "pixel format RGBX_8888");
- sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
- break;
- case PixelFormat.RGB_332:
- Log.v("SDL", "pixel format RGB_332");
- sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
- break;
- case PixelFormat.RGB_565:
- Log.v("SDL", "pixel format RGB_565");
- sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
- break;
- case PixelFormat.RGB_888:
- Log.v("SDL", "pixel format RGB_888");
- // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
- sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
- break;
- default:
- Log.v("SDL", "pixel format unknown " + format);
- break;
- }
-
- mWidth = width;
- mHeight = height;
- SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate());
- Log.v("SDL", "Window size:" + width + "x"+height);
-
- // Set mIsSurfaceReady to 'true' *before* making a call to handleResume
- SDLActivity.mIsSurfaceReady = true;
- SDLActivity.onNativeSurfaceChanged();
-
-
- if (SDLActivity.mSDLThread == null) {
- // This is the entry point to the C app.
- // Start up the C app thread and enable sensor input for the first time
-
- final Thread sdlThread = new Thread(new SDLMain(), "SDLThread");
- enableSensor(Sensor.TYPE_ACCELEROMETER, true);
- sdlThread.start();
-
- // Set up a listener thread to catch when the native thread ends
- SDLActivity.mSDLThread = new Thread(new Runnable(){
- @Override
- public void run(){
- try {
- sdlThread.join();
- }
- catch(Exception e){}
- finally{
- // Native thread has finished
- if (! SDLActivity.mExitCalledFromJava) {
- SDLActivity.handleNativeExit();
- }
- }
- }
- }, "SDLThreadListener");
- SDLActivity.mSDLThread.start();
- }
- }
-
- // unused
- @Override
- public void onDraw(Canvas canvas) {}
-
-
- // Key events
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- // Dispatch the different events depending on where they come from
- // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
- // So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD
-
- if ( (event.getSource() & InputDevice.SOURCE_GAMEPAD) != 0 ||
- (event.getSource() & InputDevice.SOURCE_DPAD) != 0 ) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
- return true;
- }
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
- return true;
- }
- }
- }
-
- if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- //Log.v("SDL", "key down: " + keyCode);
- SDLActivity.onNativeKeyDown(keyCode);
- return true;
- }
- else if (event.getAction() == KeyEvent.ACTION_UP) {
- //Log.v("SDL", "key up: " + keyCode);
- SDLActivity.onNativeKeyUp(keyCode);
- return true;
- }
- }
-
- return false;
- }
-
- // Touch events
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- /* Ref: http://developer.android.com/training/gestures/multi.html */
- final int touchDevId = event.getDeviceId();
- final int pointerCount = event.getPointerCount();
- int action = event.getActionMasked();
- int pointerFingerId;
- int mouseButton;
- int i = -1;
- float x,y,p;
-
- // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
- if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) {
- if (Build.VERSION.SDK_INT < 14) {
- mouseButton = 1; // For Android==12 all mouse buttons are the left button
- } else {
- try {
- mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
- } catch(Exception e) {
- mouseButton = 1; // oh well.
- }
- }
- SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.getY(0));
- } else {
- switch(action) {
- case MotionEvent.ACTION_MOVE:
- for (i = 0; i < pointerCount; i++) {
- pointerFingerId = event.getPointerId(i);
- x = event.getX(i) / mWidth;
- y = event.getY(i) / mHeight;
- p = event.getPressure(i);
- SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_DOWN:
- // Primary pointer up/down, the index is always zero
- i = 0;
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_POINTER_DOWN:
- // Non primary pointer up/down
- if (i == -1) {
- i = event.getActionIndex();
- }
-
- pointerFingerId = event.getPointerId(i);
- x = event.getX(i) / mWidth;
- y = event.getY(i) / mHeight;
- p = event.getPressure(i);
- SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
- break;
-
- case MotionEvent.ACTION_CANCEL:
- for (i = 0; i < pointerCount; i++) {
- pointerFingerId = event.getPointerId(i);
- x = event.getX(i) / mWidth;
- y = event.getY(i) / mHeight;
- p = event.getPressure(i);
- SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
- }
- break;
-
- default:
- break;
- }
- }
-
- return true;
- }
-
- // Sensor events
- public void enableSensor(int sensortype, boolean enabled) {
- // TODO: This uses getDefaultSensor - what if we have >1 accels?
- if (enabled) {
- mSensorManager.registerListener(this,
- mSensorManager.getDefaultSensor(sensortype),
- SensorManager.SENSOR_DELAY_GAME, null);
- } else {
- mSensorManager.unregisterListener(this,
- mSensorManager.getDefaultSensor(sensortype));
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // TODO
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
- float x, y;
- switch (mDisplay.getRotation()) {
- case Surface.ROTATION_90:
- x = -event.values[1];
- y = event.values[0];
- break;
- case Surface.ROTATION_270:
- x = event.values[1];
- y = -event.values[0];
- break;
- case Surface.ROTATION_180:
- x = -event.values[1];
- y = -event.values[0];
- break;
- default:
- x = event.values[0];
- y = event.values[1];
- break;
- }
- SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
- y / SensorManager.GRAVITY_EARTH,
- event.values[2] / SensorManager.GRAVITY_EARTH - 1);
- }
- }
-}
-
-/* This is a fake invisible editor view that receives the input and defines the
- * pan&scan region
- */
-class DummyEdit extends View implements View.OnKeyListener {
- InputConnection ic;
-
- public DummyEdit(Context context) {
- super(context);
- setFocusableInTouchMode(true);
- setFocusable(true);
- setOnKeyListener(this);
- }
-
- @Override
- public boolean onCheckIsTextEditor() {
- return true;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
-
- // This handles the hardware keyboard input
- if (event.isPrintingKey()) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
- }
- return true;
- }
-
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- SDLActivity.onNativeKeyDown(keyCode);
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- SDLActivity.onNativeKeyUp(keyCode);
- return true;
- }
-
- return false;
- }
-
- //
- @Override
- public boolean onKeyPreIme (int keyCode, KeyEvent event) {
- // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
- // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
- // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
- // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear
- // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
- // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
- if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
- SDLActivity.onNativeKeyboardFocusLost();
- }
- }
- return super.onKeyPreIme(keyCode, event);
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- ic = new SDLInputConnection(this, true);
-
- outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
- | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */;
-
- return ic;
- }
-}
-
-class SDLInputConnection extends BaseInputConnection {
-
- public SDLInputConnection(View targetView, boolean fullEditor) {
- super(targetView, fullEditor);
-
- }
-
- @Override
- public boolean sendKeyEvent(KeyEvent event) {
-
- /*
- * This handles the keycodes from soft keyboard (and IME-translated
- * input from hardkeyboard)
- */
- int keyCode = event.getKeyCode();
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (event.isPrintingKey()) {
- commitText(String.valueOf((char) event.getUnicodeChar()), 1);
- }
- SDLActivity.onNativeKeyDown(keyCode);
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
-
- SDLActivity.onNativeKeyUp(keyCode);
- return true;
- }
- return super.sendKeyEvent(event);
- }
-
- @Override
- public boolean commitText(CharSequence text, int newCursorPosition) {
-
- nativeCommitText(text.toString(), newCursorPosition);
-
- return super.commitText(text, newCursorPosition);
- }
-
- @Override
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
-
- nativeSetComposingText(text.toString(), newCursorPosition);
-
- return super.setComposingText(text, newCursorPosition);
- }
-
- public native void nativeCommitText(String text, int newCursorPosition);
-
- public native void nativeSetComposingText(String text, int newCursorPosition);
-
- @Override
- public boolean deleteSurroundingText(int beforeLength, int afterLength) {
- // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
- if (beforeLength == 1 && afterLength == 0) {
- // backspace
- return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
- && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
- }
-
- return super.deleteSurroundingText(beforeLength, afterLength);
- }
-}
-
-/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
-class SDLJoystickHandler {
-
- /**
- * Handles given MotionEvent.
- * @param event the event to be handled.
- * @return if given event was processed.
- */
- public boolean handleMotionEvent(MotionEvent event) {
- return false;
- }
-
- /**
- * Handles adding and removing of input devices.
- */
- public void pollInputDevices() {
- }
-}
-
-/* Actual joystick functionality available for API >= 12 devices */
-class SDLJoystickHandler_API12 extends SDLJoystickHandler {
-
- static class SDLJoystick {
- public int device_id;
- public String name;
- public ArrayList<InputDevice.MotionRange> axes;
- public ArrayList<InputDevice.MotionRange> hats;
- }
- static class RangeComparator implements Comparator<InputDevice.MotionRange> {
- @Override
- public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
- return arg0.getAxis() - arg1.getAxis();
- }
- }
-
- private ArrayList<SDLJoystick> mJoysticks;
-
- public SDLJoystickHandler_API12() {
-
- mJoysticks = new ArrayList<SDLJoystick>();
- }
-
- @Override
- public void pollInputDevices() {
- int[] deviceIds = InputDevice.getDeviceIds();
- // It helps processing the device ids in reverse order
- // For example, in the case of the XBox 360 wireless dongle,
- // so the first controller seen by SDL matches what the receiver
- // considers to be the first controller
-
- for(int i=deviceIds.length-1; i>-1; i--) {
- SDLJoystick joystick = getJoystick(deviceIds[i]);
- if (joystick == null) {
- joystick = new SDLJoystick();
- InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
- if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
- joystick.device_id = deviceIds[i];
- joystick.name = joystickDevice.getName();
- joystick.axes = new ArrayList<InputDevice.MotionRange>();
- joystick.hats = new ArrayList<InputDevice.MotionRange>();
-
- List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
- Collections.sort(ranges, new RangeComparator());
- for (InputDevice.MotionRange range : ranges ) {
- if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
- if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
- range.getAxis() == MotionEvent.AXIS_HAT_Y) {
- joystick.hats.add(range);
- }
- else {
- joystick.axes.add(range);
- }
- }
- }
-
- mJoysticks.add(joystick);
- SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1,
- joystick.axes.size(), joystick.hats.size()/2, 0);
- }
- }
- }
-
- /* Check removed devices */
- ArrayList<Integer> removedDevices = new ArrayList<Integer>();
- for(int i=0; i < mJoysticks.size(); i++) {
- int device_id = mJoysticks.get(i).device_id;
- int j;
- for (j=0; j < deviceIds.length; j++) {
- if (device_id == deviceIds[j]) break;
- }
- if (j == deviceIds.length) {
- removedDevices.add(Integer.valueOf(device_id));
- }
- }
-
- for(int i=0; i < removedDevices.size(); i++) {
- int device_id = removedDevices.get(i).intValue();
- SDLActivity.nativeRemoveJoystick(device_id);
- for (int j=0; j < mJoysticks.size(); j++) {
- if (mJoysticks.get(j).device_id == device_id) {
- mJoysticks.remove(j);
- break;
- }
- }
- }
- }
-
- protected SDLJoystick getJoystick(int device_id) {
- for(int i=0; i < mJoysticks.size(); i++) {
- if (mJoysticks.get(i).device_id == device_id) {
- return mJoysticks.get(i);
- }
- }
- return null;
- }
-
- @Override
- public boolean handleMotionEvent(MotionEvent event) {
- if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
- int actionPointerIndex = event.getActionIndex();
- int action = event.getActionMasked();
- switch(action) {
- case MotionEvent.ACTION_MOVE:
- SDLJoystick joystick = getJoystick(event.getDeviceId());
- if ( joystick != null ) {
- for (int i = 0; i < joystick.axes.size(); i++) {
- InputDevice.MotionRange range = joystick.axes.get(i);
- /* Normalize the value to -1...1 */
- float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
- SDLActivity.onNativeJoy(joystick.device_id, i, value );
- }
- for (int i = 0; i < joystick.hats.size(); i+=2) {
- int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
- int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
- SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY );
- }
- }
- break;
- default:
- break;
- }
- }
- return true;
- }
-}
-
-class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
- // Generic Motion (mouse hover, joystick...) events go here
- @Override
- public boolean onGenericMotion(View v, MotionEvent event) {
- float x, y;
- int mouseButton;
- int action;
-
- switch ( event.getSource() ) {
- case InputDevice.SOURCE_JOYSTICK:
- case InputDevice.SOURCE_GAMEPAD:
- case InputDevice.SOURCE_DPAD:
- SDLActivity.handleJoystickMotionEvent(event);
- return true;
-
- case InputDevice.SOURCE_MOUSE:
- action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_SCROLL:
- x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
- y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
- SDLActivity.onNativeMouse(0, action, x, y);
- return true;
-
- case MotionEvent.ACTION_HOVER_MOVE:
- x = event.getX(0);
- y = event.getY(0);
-
- SDLActivity.onNativeMouse(0, action, x, y);
- return true;
-
- default:
- break;
- }
-
- default:
- break;
- }
-
- // Event was not managed
- return false;
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/AssetExtract.java
deleted file mode 100644
index 52d6424e0..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/AssetExtract.java
+++ /dev/null
@@ -1,115 +0,0 @@
-// This string is autogenerated by ChangeAppSettings.sh, do not change
-// spaces amount
-package org.renpy.android;
-
-import java.io.*;
-
-import android.app.Activity;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.File;
-
-import java.util.zip.GZIPInputStream;
-
-import android.content.res.AssetManager;
-
-import org.kamranzafar.jtar.*;
-
-public class AssetExtract {
-
- private AssetManager mAssetManager = null;
- private Activity mActivity = null;
-
- public AssetExtract(Activity act) {
- mActivity = act;
- mAssetManager = act.getAssets();
- }
-
- public boolean extractTar(String asset, String target) {
-
- byte buf[] = new byte[1024 * 1024];
-
- InputStream assetStream = null;
- TarInputStream tis = null;
-
- try {
- assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
- tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
- } catch (IOException e) {
- Log.e("python", "opening up extract tar", e);
- return false;
- }
-
- while (true) {
- TarEntry entry = null;
-
- try {
- entry = tis.getNextEntry();
- } catch ( java.io.IOException e ) {
- Log.e("python", "extracting tar", e);
- return false;
- }
-
- if ( entry == null ) {
- break;
- }
-
- Log.v("python", "extracting " + entry.getName());
-
- if (entry.isDirectory()) {
-
- try {
- new File(target +"/" + entry.getName()).mkdirs();
- } catch ( SecurityException e ) { };
-
- continue;
- }
-
- OutputStream out = null;
- String path = target + "/" + entry.getName();
-
- try {
- out = new BufferedOutputStream(new FileOutputStream(path), 8192);
- } catch ( FileNotFoundException e ) {
- } catch ( SecurityException e ) { };
-
- if ( out == null ) {
- Log.e("python", "could not open " + path);
- return false;
- }
-
- try {
- while (true) {
- int len = tis.read(buf);
-
- if (len == -1) {
- break;
- }
-
- out.write(buf, 0, len);
- }
-
- out.flush();
- out.close();
- } catch ( java.io.IOException e ) {
- Log.e("python", "extracting zip", e);
- return false;
- }
- }
-
- try {
- tis.close();
- assetStream.close();
- } catch (IOException e) {
- // pass
- }
-
- return true;
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/Hardware.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/Hardware.java
deleted file mode 100644
index c50692d71..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/Hardware.java
+++ /dev/null
@@ -1,287 +0,0 @@
-package org.renpy.android;
-
-import android.content.Context;
-import android.os.Vibrator;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.util.DisplayMetrics;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.EditorInfo;
-import android.view.View;
-
-import java.util.List;
-import java.util.ArrayList;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-
-import org.kivy.android.PythonActivity;
-
-/**
- * Methods that are expected to be called via JNI, to access the
- * device's non-screen hardware. (For example, the vibration and
- * accelerometer.)
- */
-public class Hardware {
-
- // The context.
- static Context context;
- static View view;
-
- /**
- * Vibrate for s seconds.
- */
- public static void vibrate(double s) {
- Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- if (v != null) {
- v.vibrate((int) (1000 * s));
- }
- }
-
- /**
- * Get an Overview of all Hardware Sensors of an Android Device
- */
- public static String getHardwareSensors() {
- SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
- List<Sensor> allSensors = sm.getSensorList(Sensor.TYPE_ALL);
-
- if (allSensors != null) {
- String resultString = "";
- for (Sensor s : allSensors) {
- resultString += String.format("Name=" + s.getName());
- resultString += String.format(",Vendor=" + s.getVendor());
- resultString += String.format(",Version=" + s.getVersion());
- resultString += String.format(",MaximumRange=" + s.getMaximumRange());
- // XXX MinDelay is not in the 2.2
- //resultString += String.format(",MinDelay=" + s.getMinDelay());
- resultString += String.format(",Power=" + s.getPower());
- resultString += String.format(",Type=" + s.getType() + "\n");
- }
- return resultString;
- }
- return "";
- }
-
-
- /**
- * Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and Magnetic Field Sensors
- */
- public static class generic3AxisSensor implements SensorEventListener {
- private final SensorManager sSensorManager;
- private final Sensor sSensor;
- private final int sSensorType;
- SensorEvent sSensorEvent;
-
- public generic3AxisSensor(int sensorType) {
- sSensorType = sensorType;
- sSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- sSensor = sSensorManager.getDefaultSensor(sSensorType);
- }
-
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
-
- public void onSensorChanged(SensorEvent event) {
- sSensorEvent = event;
- }
-
- /**
- * Enable or disable the Sensor by registering/unregistering
- */
- public void changeStatus(boolean enable) {
- if (enable) {
- sSensorManager.registerListener(this, sSensor, SensorManager.SENSOR_DELAY_NORMAL);
- } else {
- sSensorManager.unregisterListener(this, sSensor);
- }
- }
-
- /**
- * Read the Sensor
- */
- public float[] readSensor() {
- if (sSensorEvent != null) {
- return sSensorEvent.values;
- } else {
- float rv[] = { 0f, 0f, 0f };
- return rv;
- }
- }
- }
-
- public static generic3AxisSensor accelerometerSensor = null;
- public static generic3AxisSensor orientationSensor = null;
- public static generic3AxisSensor magneticFieldSensor = null;
-
- /**
- * functions for backward compatibility reasons
- */
-
- public static void accelerometerEnable(boolean enable) {
- if ( accelerometerSensor == null )
- accelerometerSensor = new generic3AxisSensor(Sensor.TYPE_ACCELEROMETER);
- accelerometerSensor.changeStatus(enable);
- }
- public static float[] accelerometerReading() {
- float rv[] = { 0f, 0f, 0f };
- if ( accelerometerSensor == null )
- return rv;
- return (float[]) accelerometerSensor.readSensor();
- }
- public static void orientationSensorEnable(boolean enable) {
- if ( orientationSensor == null )
- orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION);
- orientationSensor.changeStatus(enable);
- }
- public static float[] orientationSensorReading() {
- float rv[] = { 0f, 0f, 0f };
- if ( orientationSensor == null )
- return rv;
- return (float[]) orientationSensor.readSensor();
- }
- public static void magneticFieldSensorEnable(boolean enable) {
- if ( magneticFieldSensor == null )
- magneticFieldSensor = new generic3AxisSensor(Sensor.TYPE_MAGNETIC_FIELD);
- magneticFieldSensor.changeStatus(enable);
- }
- public static float[] magneticFieldSensorReading() {
- float rv[] = { 0f, 0f, 0f };
- if ( magneticFieldSensor == null )
- return rv;
- return (float[]) magneticFieldSensor.readSensor();
- }
-
- static public DisplayMetrics metrics = new DisplayMetrics();
-
- /**
- * Get display DPI.
- */
- public static int getDPI() {
- // AND: Shouldn't have to get the metrics like this every time...
- PythonActivity.mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
- return metrics.densityDpi;
- }
-
- // /**
- // * Show the soft keyboard.
- // */
- // public static void showKeyboard(int input_type) {
- // //Log.i("python", "hardware.Java show_keyword " input_type);
-
- // InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
-
- // SDLSurfaceView vw = (SDLSurfaceView) view;
-
- // int inputType = input_type;
-
- // if (vw.inputType != inputType){
- // vw.inputType = inputType;
- // imm.restartInput(view);
- // }
-
- // imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
- // }
-
- /**
- * Hide the soft keyboard.
- */
- public static void hideKeyboard() {
- InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
- }
-
- /**
- * Scan WiFi networks
- */
- static List<ScanResult> latestResult;
-
- public static void enableWifiScanner()
- {
- IntentFilter i = new IntentFilter();
- i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-
- context.registerReceiver(new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context c, Intent i) {
- // Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event occurs
- WifiManager w = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);
- latestResult = w.getScanResults(); // Returns a <list> of scanResults
- }
-
- }, i);
-
- }
-
- public static String scanWifi() {
-
- // Now you can call this and it should execute the broadcastReceiver's
- // onReceive()
- WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- boolean a = wm.startScan();
-
- if (latestResult != null){
-
- String latestResultString = "";
- for (ScanResult result : latestResult)
- {
- latestResultString += String.format("%s\t%s\t%d\n", result.SSID, result.BSSID, result.level);
- }
-
- return latestResultString;
- }
-
- return "";
- }
-
- /**
- * network state
- */
-
- public static boolean network_state = false;
-
- /**
- * Check network state directly
- *
- * (only one connection can be active at a given moment, detects all network type)
- *
- */
- public static boolean checkNetwork()
- {
- boolean state = false;
- final ConnectivityManager conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-
- final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();
- if (activeNetwork != null && activeNetwork.isConnected()) {
- state = true;
- } else {
- state = false;
- }
-
- return state;
- }
-
- /**
- * To recieve network state changes
- */
- public static void registerNetworkCheck()
- {
- IntentFilter i = new IntentFilter();
- i.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- context.registerReceiver(new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context c, Intent i) {
- network_state = checkNetwork();
- }
-
- }, i);
- }
-
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/PythonActivity.java
deleted file mode 100644
index 0d34d31c9..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/PythonActivity.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.renpy.android;
-
-import android.util.Log;
-
-class PythonActivity extends org.kivy.android.PythonActivity {
- static {
- Log.w("PythonActivity", "Accessing org.renpy.android.PythonActivity "
- + "is deprecated and will be removed in a "
- + "future version. Please switch to "
- + "org.kivy.android.PythonActivity.");
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/PythonService.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/PythonService.java
deleted file mode 100644
index 73febed68..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/PythonService.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.renpy.android;
-
-import android.util.Log;
-
-class PythonService extends org.kivy.android.PythonService {
- static {
- Log.w("PythonService", "Accessing org.renpy.android.PythonService "
- + "is deprecated and will be removed in a "
- + "future version. Please switch to "
- + "org.kivy.android.PythonService.");
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/ResourceManager.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/ResourceManager.java
deleted file mode 100644
index 47455abb0..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/renpy/android/ResourceManager.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * This class takes care of managing resources for us. In our code, we
- * can't use R, since the name of the package containing R will
- * change. (This same code is used in both org.renpy.android and
- * org.renpy.pygame.) So this is the next best thing.
- */
-
-package org.renpy.android;
-
-import android.app.Activity;
-import android.content.res.Resources;
-import android.view.View;
-
-import android.util.Log;
-
-public class ResourceManager {
-
- private Activity act;
- private Resources res;
-
- public ResourceManager(Activity activity) {
- act = activity;
- res = act.getResources();
- }
-
- public int getIdentifier(String name, String kind) {
- Log.v("SDL", "getting identifier");
- Log.v("SDL", "kind is " + kind + " and name " + name);
- Log.v("SDL", "result is " + res.getIdentifier(name, kind, act.getPackageName()));
- return res.getIdentifier(name, kind, act.getPackageName());
- }
-
- public String getString(String name) {
-
- try {
- Log.v("SDL", "asked to get string " + name);
- return res.getString(getIdentifier(name, "string"));
- } catch (Exception e) {
- Log.v("SDL", "got exception looking for string!");
- return null;
- }
- }
-
- public View inflateView(String name) {
- int id = getIdentifier(name, "layout");
- return act.getLayoutInflater().inflate(id, null);
- }
-
- public View getViewById(View v, String name) {
- int id = getIdentifier(name, "id");
- return v.findViewById(id);
- }
-
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap-anydpi-v26/.gitkeep b/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap-anydpi-v26/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap/.gitkeep b/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch b/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch
new file mode 100644
index 000000000..434be4e8b
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch
@@ -0,0 +1,77 @@
+--- a/src/main/java/org/libsdl/app/SDLActivity.java
++++ b/src/main/java/org/libsdl/app/SDLActivity.java
+@@ -221,6 +221,8 @@
+
+ // This is what SDL runs in. It invokes SDL_main(), eventually
+ protected static Thread mSDLThread;
++
++ public static int keyboardInputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
+
+ protected static SDLGenericMotionListener_API12 getMotionListener() {
+ if (mMotionListener == null) {
+@@ -323,6 +325,15 @@
+ Log.v(TAG, "Model: " + Build.MODEL);
+ Log.v(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
++
++ SDLActivity.initialize();
++ // So we can call stuff from static callbacks
++ mSingleton = this;
++ }
++
++ // We don't do this in onCreate because we unpack and load the app data on a thread
++ // and we can't run setup tasks until that thread completes.
++ protected void finishLoad() {
+
+ try {
+ Thread.currentThread().setName("SDLActivity");
+@@ -837,7 +848,7 @@
+ Handler commandHandler = new SDLCommandHandler();
+
+ // Send a message from the SDLMain thread
+- boolean sendCommand(int command, Object data) {
++ protected boolean sendCommand(int command, Object data) {
+ Message msg = commandHandler.obtainMessage();
+ msg.arg1 = command;
+ msg.obj = data;
+@@ -1385,7 +1396,22 @@
+ return null;
+ }
+ return SDLActivity.mSurface.getNativeSurface();
++ }
++
++ /**
++ * Calls turnActive() on singleton to keep loading screen active
++ */
++ public static void triggerAppConfirmedActive() {
++ mSingleton.appConfirmedActive();
+ }
++
++ /**
++ * Trick needed for loading screen, overridden by PythonActivity
++ * to keep loading screen active
++ */
++ public void appConfirmedActive() {
++ }
++
+
+ // Input
+
+@@ -1881,6 +1907,7 @@
+
+ Log.v("SDL", "Running main function " + function + " from library " + library);
+
++ SDLActivity.mSingleton.appConfirmedActive();
+ SDLActivity.nativeRunMain(library, function, arguments);
+
+ Log.v("SDL", "Finished main function");
+@@ -1938,8 +1965,7 @@
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ ic = new SDLInputConnection(this, true);
+
+- outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
+- InputType.TYPE_TEXT_FLAG_MULTI_LINE;
++ outAttrs.inputType = SDLActivity.keyboardInputType;
+ outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |
+ EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
+
diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml
index b9b04ee5a..a887a53d5 100644
--- a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml
+++ b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml
@@ -3,7 +3,6 @@
com.gamemaker.game
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="{{ args.package }}"
android:versionCode="{{ args.numeric_version }}"
android:versionName="{{ args.version }}"
android:installLocation="auto">
@@ -24,14 +23,9 @@
<!-- OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" />
- <!-- Allow writing to external storage -->
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <!-- Set permissions -->
{% for perm in args.permissions %}
- {% if '.' in perm %}
- <uses-permission android:name="{{ perm }}" />
- {% else %}
- <uses-permission android:name="android.permission.{{ perm }}" />
- {% endif %}
+ <uses-permission android:name="{{ perm.name }}"{% if perm.maxSdkVersion %} android:maxSdkVersion="{{ perm.maxSdkVersion }}"{% endif %}{% if perm.usesPermissionFlags %} android:usesPermissionFlags="{{ perm.usesPermissionFlags }}"{% endif %} />
{% endfor %}
{% if args.wakelock %}
@@ -42,6 +36,9 @@
<uses-permission android:name="com.android.vending.BILLING" />
{% endif %}
+ {{ args.extra_manifest_xml }}
+
+
<!-- Create a Java class extending SDLActivity and place it in a
directory under src matching the package, e.g.
src/com/gamemaker/game/MyGame.java
@@ -52,34 +49,48 @@
An example Java class can be found in README-android.txt
-->
<application android:label="@string/app_name"
- android:icon="@drawable/icon"
- android:allowBackup="true"
- android:theme="@android:style/Theme.NoTitleBar{% if not args.window %}.Fullscreen{% endif %}"
- android:hardwareAccelerated="true" >
+ {% if debug %}android:debuggable="true"{% endif %}
+ android:icon="@mipmap/icon"
+ android:allowBackup="{{ args.allow_backup }}"
+ {% if args.backup_rules %}android:fullBackupContent="@xml/{{ args.backup_rules }}"{% endif %}
+ {{ args.extra_manifest_application_arguments }}
+ android:theme="{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}"
+ android:hardwareAccelerated="true"
+ android:extractNativeLibs="true" >
+ {% for l in args.android_used_libs %}
+ <uses-library android:name="{{ l }}" />
+ {% endfor %}
{% for m in args.meta_data %}
<meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
<meta-data android:name="wakelock" android:value="{% if args.wakelock %}1{% else %}0{% endif %}"/>
- <activity android:name="org.kivy.android.PythonActivity"
+ <activity android:name="{{args.android_entrypoint}}"
android:label="@string/app_name"
- android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
- android:screenOrientation="{{ args.orientation }}"
+ 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 %}"
+ android:screenOrientation="{{ args.manifest_orientation }}"
+ android:exported="true"
+ {% if args.activity_launch_mode %}
+ android:launchMode="{{ args.activity_launch_mode }}"
+ {% endif %}
>
- {% if args.launcher %}
<intent-filter>
+ {% if args.launcher %}
<action android:name="org.kivy.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="{{ url_scheme }}" />
- </intent-filter>
{% else %}
- <intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
{% endif %}
+ {% if args.home_app %}
+ <category android:name="android.intent.category.HOME" />
+ <category android:name="android.intent.category.DEFAULT" />
+ {% endif %}
+ </intent-filter>
+
{%- if args.intent_filters -%}
{{- args.intent_filters -}}
{%- endif -%}
@@ -87,8 +98,9 @@
{% if args.launcher %}
<activity android:name="org.kivy.android.launcher.ProjectChooser"
- android:icon="@drawable/icon"
- android:label="@string/app_name">
+ android:icon="@mipmap/icon"
+ android:label="@string/app_name"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -99,19 +111,23 @@
{% endif %}
{% if service or args.launcher %}
- <service android:name="org.kivy.android.PythonService"
+ <service android:name="{{ args.service_class_name }}"
android:process=":pythonservice" />
{% endif %}
{% for name in service_names %}
<service android:name="{{ args.package }}.Service{{ name|capitalize }}"
android:process=":service_{{ name }}" />
{% endfor %}
+ {% for name in native_services %}
+ <service android:name="{{ name }}" />
+ {% endfor %}
{% if args.billing_pubkey %}
<service android:name="org.kivy.android.billing.BillingReceiver"
android:process=":pythonbilling" />
<receiver android:name="org.kivy.android.billing.BillingReceiver"
- android:process=":pythonbillingreceiver">
+ android:process=":pythonbillingreceiver"
+ android:exported="false">
<intent-filter>
<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
<action android:name="com.android.vending.billing.RESPONSE_CODE" />
diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/sdl2/build/templates/Service.tmpl.java
deleted file mode 100644
index 3ed10c269..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/templates/Service.tmpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package {{ args.package }};
-
-import android.os.Build;
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
-import android.content.Intent;
-import android.content.Context;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.os.Bundle;
-import org.kivy.android.PythonService;
-import org.kivy.android.PythonActivity;
-
-
-public class Service{{ name|capitalize }} extends PythonService {
- {% if sticky %}
- @Override
- public int startType() {
- return START_STICKY;
- }
- {% endif %}
-
- {% if not foreground %}
- @Override
- public boolean canDisplayNotification() {
- return false;
- }
- {% endif %}
-
- @Override
- protected void doStartForeground(Bundle extras) {
- Notification notification;
- Context context = getApplicationContext();
- Intent contextIntent = new Intent(context, PythonActivity.class);
- PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
- notification = new Notification(
- context.getApplicationInfo().icon, "{{ args.name }}", System.currentTimeMillis());
- try {
- // prevent using NotificationCompat, this saves 100kb on apk
- Method func = notification.getClass().getMethod(
- "setLatestEventInfo", Context.class, CharSequence.class,
- CharSequence.class, PendingIntent.class);
- func.invoke(notification, context, "{{ args.name }}", "{{ name| capitalize }}", pIntent);
- } catch (NoSuchMethodException | IllegalAccessException |
- IllegalArgumentException | InvocationTargetException e) {
- }
- } else {
- Notification.Builder builder = new Notification.Builder(context);
- builder.setContentTitle("{{ args.name }}");
- builder.setContentText("{{ name| capitalize }}");
- builder.setContentIntent(pIntent);
- builder.setSmallIcon(context.getApplicationInfo().icon);
- notification = builder.build();
- }
- startForeground({{ service_id }}, notification);
- }
-
- static public void start(Context ctx, String pythonServiceArgument) {
- Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
- String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
- intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
- intent.putExtra("androidArgument", argument);
- intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
- intent.putExtra("pythonName", "{{ name }}");
- intent.putExtra("pythonHome", argument);
- intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
- intent.putExtra("pythonServiceArgument", pythonServiceArgument);
- ctx.startService(intent);
- }
-
- static public void stop(Context ctx) {
- Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
- ctx.stopService(intent);
- }
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/build.properties b/pythonforandroid/bootstraps/sdl2/build/templates/build.properties
deleted file mode 100644
index f12e25869..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/templates/build.properties
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file is used to override default values used by the Ant build system.
-#
-# This file must be checked in Version Control Systems, as it is
-# integral to the build system of your project.
-
-# This file is only used by the Ant script.
-
-# You can use this to override default values such as
-# 'source.dir' for the location of your java source folder and
-# 'out.dir' for the location of your output folder.
-
-# You can also use it define how the release builds are signed by declaring
-# the following properties:
-# 'key.store' for the location of your keystore and
-# 'key.alias' for the name of the key to use.
-# The password will be asked during the build when you use the 'release' target.
-
-key.store=${env.P4A_RELEASE_KEYSTORE}
-key.alias=${env.P4A_RELEASE_KEYALIAS}
-key.store.password=${env.P4A_RELEASE_KEYSTORE_PASSWD}
-key.alias.password=${env.P4A_RELEASE_KEYALIAS_PASSWD}
diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/build.tmpl.gradle b/pythonforandroid/bootstraps/sdl2/build/templates/build.tmpl.gradle
deleted file mode 100644
index f741f33b4..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/templates/build.tmpl.gradle
+++ /dev/null
@@ -1,78 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:2.3.1'
- }
-}
-
-allprojects {
- repositories {
- jcenter()
- flatDir {
- dirs 'libs'
- }
- }
-}
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion {{ android_api }}
- buildToolsVersion '{{ build_tools_version }}'
- defaultConfig {
- minSdkVersion {{ args.min_sdk_version }}
- targetSdkVersion {{ android_api }}
- versionCode {{ args.numeric_version }}
- versionName '{{ args.version }}'
- }
-
- {% if args.sign -%}
- signingConfigs {
- release {
- storeFile file(System.getenv("P4A_RELEASE_KEYSTORE"))
- keyAlias System.getenv("P4A_RELEASE_KEYALIAS")
- storePassword System.getenv("P4A_RELEASE_KEYSTORE_PASSWD")
- keyPassword System.getenv("P4A_RELEASE_KEYALIAS_PASSWD")
- }
- }
- {%- endif %}
-
- buildTypes {
- debug {
- }
- release {
- {% if args.sign -%}
- signingConfig signingConfigs.release
- {%- endif %}
- }
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
- }
-
- sourceSets {
- main {
- jniLibs.srcDir 'libs'
- }
- }
-
-}
-
-dependencies {
- {%- for aar in aars %}
- compile(name: '{{ aar }}', ext: 'aar')
- {%- endfor -%}
- {%- for jar in jars %}
- compile files('src/main/libs/{{ jar }}')
- {%- endfor -%}
- {%- if args.depends -%}
- {%- for depend in args.depends %}
- compile '{{ depend }}'
- {%- endfor %}
- {%- endif %}
-}
diff --git a/pythonforandroid/bootstraps/sdl2/build/whitelist.txt b/pythonforandroid/bootstraps/sdl2/build/whitelist.txt
deleted file mode 100644
index 41b06ee25..000000000
--- a/pythonforandroid/bootstraps/sdl2/build/whitelist.txt
+++ /dev/null
@@ -1 +0,0 @@
-# put files here that you need to un-blacklist
diff --git a/pythonforandroid/bootstraps/service_library/__init__.py b/pythonforandroid/bootstraps/service_library/__init__.py
new file mode 100644
index 000000000..0b41be87f
--- /dev/null
+++ b/pythonforandroid/bootstraps/service_library/__init__.py
@@ -0,0 +1,9 @@
+from pythonforandroid.bootstraps.service_only import ServiceOnlyBootstrap
+
+
+class ServiceLibraryBootstrap(ServiceOnlyBootstrap):
+
+ name = 'service_library'
+
+
+bootstrap = ServiceLibraryBootstrap()
diff --git a/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h
new file mode 100644
index 000000000..01fd12289
--- /dev/null
+++ b/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h
@@ -0,0 +1,6 @@
+
+#define BOOTSTRAP_NAME_LIBRARY
+#define BOOTSTRAP_USES_NO_SDL_HEADERS
+
+const char bootstrap_name[] = "service_library";
+
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/GenericBroadcastReceiver.java b/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
similarity index 100%
rename from pythonforandroid/bootstraps/webview/build/src/org/kivy/android/GenericBroadcastReceiver.java
rename to pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java b/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
similarity index 100%
rename from pythonforandroid/bootstraps/webview/build/src/org/kivy/android/GenericBroadcastReceiverCallback.java
rename to pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
diff --git a/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/PythonActivity.java
new file mode 100644
index 000000000..7be751da5
--- /dev/null
+++ b/pythonforandroid/bootstraps/service_library/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -0,0 +1,9 @@
+package org.kivy.android;
+
+import android.app.Activity;
+
+// Required by PythonService class
+public class PythonActivity extends Activity {
+ public static PythonActivity mActivity = null;
+}
+
diff --git a/pythonforandroid/bootstraps/service_library/build/src/main/res/mipmap/.gitkeep b/pythonforandroid/bootstraps/service_library/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/service_library/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/service_library/build/templates/AndroidManifest.tmpl.xml
new file mode 100644
index 000000000..017a1588e
--- /dev/null
+++ b/pythonforandroid/bootstraps/service_library/build/templates/AndroidManifest.tmpl.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="{{ args.numeric_version }}"
+ android:versionName="{{ args.version }}">
+
+ <!-- Android 2.3.3 -->
+ <uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ android_api }}" />
+
+ <application {% if debug %}android:debuggable="true"{% endif %} >
+ {% for name in service_names %}
+ <service android:name="{{ args.package }}.Service{{ name|capitalize }}"
+ android:process=":service_{{ name }}"
+ android:exported="true" />
+ {% endfor %}
+ </application>
+
+</manifest>
diff --git a/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java
new file mode 100644
index 000000000..ff889b462
--- /dev/null
+++ b/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java
@@ -0,0 +1,102 @@
+package {{ args.package }};
+
+import java.io.File;
+
+import android.os.Build;
+import android.content.Intent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+import org.kivy.android.PythonService;
+import org.kivy.android.PythonUtil;
+
+public class Service{{ name|capitalize }} extends PythonService {
+
+ private static final String TAG = "PythonService";
+
+ {% if sticky %}
+ @Override
+ public int startType() {
+ return START_STICKY;
+ }
+ {% endif %}
+
+ @Override
+ protected int getServiceId() {
+ return {{ service_id }};
+ }
+
+ public static void prepare(Context ctx) {
+ String appRoot = PythonUtil.getAppRoot(ctx);
+ Log.v(TAG, "Ready to unpack");
+ File app_root_file = new File(appRoot);
+ PythonUtil.unpackAsset(ctx, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(ctx, ctx.getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
+ }
+
+ static private void _start(Context ctx, String smallIconName,
+ String contentTitle,
+ String contentText,
+ String pythonServiceArgument) {
+ Intent intent = getDefaultIntent(ctx, smallIconName, contentTitle,
+ contentText, pythonServiceArgument);
+ //foreground: {{foreground}}
+ {% if foreground %}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ ctx.startForegroundService(intent);
+ } else {
+ ctx.startService(intent);
+ }
+ {% else %}
+ ctx.startService(intent);
+ {% endif %}
+ }
+
+ public static void start(Context ctx, String pythonServiceArgument) {
+ _start(ctx, "", "{{ args.name }}", "{{ name|capitalize }}", pythonServiceArgument);
+ }
+
+ static public void start(Context ctx, String smallIconName,
+ String contentTitle,
+ String contentText,
+ String pythonServiceArgument) {
+ _start(ctx, smallIconName, contentTitle, contentText, pythonServiceArgument);
+ }
+
+ static public Intent getDefaultIntent(Context ctx, String smallIconName,
+ String contentTitle,
+ String contentText,
+ String pythonServiceArgument) {
+ String appRoot = PythonUtil.getAppRoot(ctx);
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ intent.putExtra("androidPrivate", appRoot);
+ intent.putExtra("androidArgument", appRoot);
+ intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
+ intent.putExtra("serviceTitle", "{{ name|capitalize }}");
+ intent.putExtra("pythonName", "{{ name }}");
+ intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}");
+ intent.putExtra("pythonHome", appRoot);
+ intent.putExtra("androidUnpack", appRoot);
+ intent.putExtra("pythonPath", appRoot + ":" + appRoot + "/lib");
+ intent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ intent.putExtra("smallIconName", smallIconName);
+ intent.putExtra("contentTitle", contentTitle);
+ intent.putExtra("contentText", contentText);
+ return intent;
+ }
+
+ @Override
+ protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {
+ return Service{{ name|capitalize }}.getDefaultIntent(ctx, "", "", "",
+ pythonServiceArgument);
+ }
+
+
+
+ static public void stop(Context ctx) {
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ ctx.stopService(intent);
+ }
+
+}
diff --git a/pythonforandroid/bootstraps/service_only/__init__.py b/pythonforandroid/bootstraps/service_only/__init__.py
index a80c57894..4f0d6cf20 100644
--- a/pythonforandroid/bootstraps/service_only/__init__.py
+++ b/pythonforandroid/bootstraps/service_only/__init__.py
@@ -1,123 +1,52 @@
-import glob
-from os import walk
-from os.path import join, exists, curdir, abspath
import sh
-from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main
+from os.path import join
+from pythonforandroid.toolchain import (
+ Bootstrap, current_directory, info, info_main, shprint)
+from pythonforandroid.util import ensure_dir, rmdir
class ServiceOnlyBootstrap(Bootstrap):
name = 'service_only'
- recipe_depends = ['genericndkbuild', ('python2', 'python3crystax')]
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'genericndkbuild'})
+ )
- def run_distribute(self):
+ def assemble_distribution(self):
info_main('# Creating Android project from build and {} bootstrap'.format(
self.name))
info('This currently just copies the build stuff straight from the build dir.')
- shprint(sh.rm, '-rf', self.dist_dir)
+ rmdir(self.dist_dir)
shprint(sh.cp, '-r', self.build_dir, self.dist_dir)
with current_directory(self.dist_dir):
with open('local.properties', 'w') as fileh:
fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))
- fileh.write('ndk.dir={}'.format(self.ctx.ndk_dir))
-
- arch = self.ctx.archs[0]
- if len(self.ctx.archs) > 1:
- raise ValueError('built for more than one arch, but bootstrap cannot handle that yet')
- info('Bootstrap running with arch {}'.format(arch))
with current_directory(self.dist_dir):
info('Copying python distribution')
- if not exists('private') and not self.ctx.python_recipe.from_crystax:
- shprint(sh.mkdir, 'private')
- if not exists('crystax_python') and self.ctx.python_recipe.from_crystax:
- shprint(sh.mkdir, 'crystax_python')
- shprint(sh.mkdir, 'crystax_python/crystax_python')
- if not exists('assets'):
- shprint(sh.mkdir, 'assets')
-
- hostpython = sh.Command(self.ctx.hostpython)
- if not self.ctx.python_recipe.from_crystax:
- try:
- shprint(hostpython, '-OO', '-m', 'compileall',
- self.ctx.get_python_install_dir(),
- _tail=10, _filterout="^Listing")
- except sh.ErrorReturnCode:
- pass
- if not exists('python-install'):
- shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install')
-
- self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
- self.distribute_aars(arch)
- self.distribute_javaclasses(self.ctx.javaclass_dir)
-
- if not self.ctx.python_recipe.from_crystax:
- info('Filling private directory')
- if not exists(join('private', 'lib')):
- info('private/lib does not exist, making')
- shprint(sh.cp, '-a', join('python-install', 'lib'), 'private')
- shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7'))
-
- if exists(join('libs', arch.arch, 'libpymodules.so')):
- shprint(sh.mv, join('libs', arch.arch, 'libpymodules.so'), 'private/')
- shprint(sh.cp, join('python-install', 'include', 'python2.7', 'pyconfig.h'), join('private', 'include', 'python2.7/'))
-
- info('Removing some unwanted files')
- shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so'))
- shprint(sh.rm, '-rf', join('private', 'lib', 'pkgconfig'))
-
- libdir = join(self.dist_dir, 'private', 'lib', 'python2.7')
- site_packages_dir = join(libdir, 'site-packages')
- with current_directory(libdir):
- # shprint(sh.xargs, 'rm', sh.grep('-E', '*\.(py|pyx|so\.o|so\.a|so\.libs)$', sh.find('.')))
- removes = []
- for dirname, something, filens in walk('.'):
- for filename in filens:
- for suffix in ('py', 'pyc', 'so.o', 'so.a', 'so.libs'):
- if filename.endswith(suffix):
- removes.append(filename)
- shprint(sh.rm, '-f', *removes)
-
- info('Deleting some other stuff not used on android')
- # To quote the original distribute.sh, 'well...'
- # shprint(sh.rm, '-rf', 'ctypes')
- shprint(sh.rm, '-rf', 'lib2to3')
- shprint(sh.rm, '-rf', 'idlelib')
- for filename in glob.glob('config/libpython*.a'):
- shprint(sh.rm, '-f', filename)
- shprint(sh.rm, '-rf', 'config/python.o')
- # shprint(sh.rm, '-rf', 'lib-dynload/_ctypes_test.so')
- # shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so')
-
- else: # Python *is* loaded from crystax
- ndk_dir = self.ctx.ndk_dir
- py_recipe = self.ctx.python_recipe
- python_dir = join(ndk_dir, 'sources', 'python', py_recipe.version,
- 'libs', arch.arch)
+ self.distribute_javaclasses(self.ctx.javaclass_dir,
+ dest_dir=join("src", "main", "java"))
- shprint(sh.cp, '-r', join(python_dir, 'stdlib.zip'), 'crystax_python/crystax_python')
- shprint(sh.cp, '-r', join(python_dir, 'modules'), 'crystax_python/crystax_python')
- shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), 'crystax_python/crystax_python/site-packages')
+ for arch in self.ctx.archs:
+ self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
+ self.distribute_aars(arch)
- info('Renaming .so files to reflect cross-compile')
- site_packages_dir = 'crystax_python/crystax_python/site-packages'
- filens = shprint(sh.find, site_packages_dir, '-iname', '*.so').stdout.decode(
- 'utf-8').split('\n')[:-1]
- for filen in filens:
- parts = filen.split('.')
- if len(parts) <= 2:
- continue
- shprint(sh.mv, filen, filen.split('.')[0] + '.so')
- site_packages_dir = join(abspath(curdir),
- site_packages_dir)
+ python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
+ ensure_dir(python_bundle_dir)
+ site_packages_dir = self.ctx.python_recipe.create_python_bundle(
+ join(self.dist_dir, python_bundle_dir), arch)
+ if not self.ctx.with_debug_symbols:
+ self.strip_libraries(arch)
+ self.fry_eggs(site_packages_dir)
+ if 'sqlite3' not in self.ctx.recipe_build_order:
+ with open('blacklist.txt', 'a') as fileh:
+ fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n')
- self.strip_libraries(arch)
- self.fry_eggs(site_packages_dir)
- super(ServiceOnlyBootstrap, self).run_distribute()
+ super().assemble_distribution()
bootstrap = ServiceOnlyBootstrap()
diff --git a/pythonforandroid/bootstraps/service_only/build/blacklist.txt b/pythonforandroid/bootstraps/service_only/build/blacklist.txt
index d220d2a2a..53cc634b7 100644
--- a/pythonforandroid/bootstraps/service_only/build/blacklist.txt
+++ b/pythonforandroid/bootstraps/service_only/build/blacklist.txt
@@ -1,5 +1,7 @@
# prevent user to include invalid extensions
*.apk
+*.aab
+*.apks
*.pxd
# eggs
@@ -69,7 +71,6 @@ multiprocessing/dummy*
lib-dynload/termios.so
lib-dynload/_lsprof.so
lib-dynload/*audioop.so
-lib-dynload/mmap.so
lib-dynload/_hotshot.so
lib-dynload/_heapq.so
lib-dynload/_json.so
diff --git a/pythonforandroid/bootstraps/service_only/build/build.gradle b/pythonforandroid/bootstraps/service_only/build/build.gradle
deleted file mode 100644
index f56187ae3..000000000
--- a/pythonforandroid/bootstraps/service_only/build/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-allprojects {
- repositories {
- jcenter()
- }
-}
-buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath "com.android.tools.build:gradle-experimental:0.7.0"
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/service_only/build/build.py b/pythonforandroid/bootstraps/service_only/build/build.py
deleted file mode 100755
index e5a6e3a09..000000000
--- a/pythonforandroid/bootstraps/service_only/build/build.py
+++ /dev/null
@@ -1,509 +0,0 @@
-#!/usr/bin/env python2.7
-
-from __future__ import print_function
-
-from os.path import dirname, join, isfile, realpath, relpath, split, exists
-from os import makedirs
-import os
-import tarfile
-import time
-import subprocess
-import shutil
-from zipfile import ZipFile
-import sys
-import re
-import shlex
-
-from fnmatch import fnmatch
-
-import jinja2
-
-if os.name == 'nt':
- ANDROID = 'android.bat'
- ANT = 'ant.bat'
-else:
- ANDROID = 'android'
- ANT = 'ant'
-
-curdir = dirname(__file__)
-
-# Try to find a host version of Python that matches our ARM version.
-PYTHON = join(curdir, 'python-install', 'bin', 'python.host')
-
-BLACKLIST_PATTERNS = [
- # code versionning
- '^*.hg/*',
- '^*.git/*',
- '^*.bzr/*',
- '^*.svn/*',
-
- # pyc/py
- '*.pyc',
- # '*.py',
-
- # temp files
- '~',
- '*.bak',
- '*.swp',
-]
-
-WHITELIST_PATTERNS = []
-
-python_files = []
-
-
-environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
- join(curdir, 'templates')))
-
-def render(template, dest, **kwargs):
- '''Using jinja2, render `template` to the filename `dest`, supplying the
-
- keyword arguments as template parameters.
- '''
-
- dest_dir = dirname(dest)
- if dest_dir and not exists(dest_dir):
- makedirs(dest_dir)
-
- template = environment.get_template(template)
- text = template.render(**kwargs)
-
- f = open(dest, 'wb')
- f.write(text.encode('utf-8'))
- f.close()
-
-
-def is_whitelist(name):
- return match_filename(WHITELIST_PATTERNS, name)
-
-
-def is_blacklist(name):
- if is_whitelist(name):
- return False
- return match_filename(BLACKLIST_PATTERNS, name)
-
-
-def match_filename(pattern_list, name):
- for pattern in pattern_list:
- if pattern.startswith('^'):
- pattern = pattern[1:]
- else:
- pattern = '*/' + pattern
- if fnmatch(name, pattern):
- return True
-
-
-def listfiles(d):
- basedir = d
- subdirlist = []
- for item in os.listdir(d):
- fn = join(d, item)
- if isfile(fn):
- yield fn
- else:
- subdirlist.append(join(basedir, item))
- for subdir in subdirlist:
- for fn in listfiles(subdir):
- yield fn
-
-def make_python_zip():
- '''
- Search for all the python related files, and construct the pythonXX.zip
- According to
- # http://randomsplat.com/id5-cross-compiling-python-for-embedded-linux.html
- site-packages, config and lib-dynload will be not included.
- '''
-
- if not exists('private'):
- print('No compiled python is present to zip, skipping.')
- print('this should only be the case if you are using the CrystaX python')
- return
-
- global python_files
- d = realpath(join('private', 'lib', 'python2.7'))
-
-
- def select(fn):
- if is_blacklist(fn):
- return False
- fn = realpath(fn)
- assert(fn.startswith(d))
- fn = fn[len(d):]
- if (fn.startswith('/site-packages/') or
- fn.startswith('/config/') or
- fn.startswith('/lib-dynload/') or
- fn.startswith('/libpymodules.so')):
- return False
- return fn
-
- # get a list of all python file
- python_files = [x for x in listfiles(d) if select(x)]
-
- # create the final zipfile
- zfn = join('private', 'lib', 'python27.zip')
- zf = ZipFile(zfn, 'w')
-
- # put all the python files in it
- for fn in python_files:
- afn = fn[len(d):]
- zf.write(fn, afn)
- zf.close()
-
-def make_tar(tfn, source_dirs, ignore_path=[]):
- '''
- Make a zip file `fn` from the contents of source_dis.
- '''
-
- # selector function
- def select(fn):
- rfn = realpath(fn)
- for p in ignore_path:
- if p.endswith('/'):
- p = p[:-1]
- if rfn.startswith(p):
- return False
- if rfn in python_files:
- return False
- return not is_blacklist(fn)
-
- # get the files and relpath file of all the directory we asked for
- files = []
- for sd in source_dirs:
- sd = realpath(sd)
- compile_dir(sd)
- files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd)
- if select(x)]
-
- # create tar.gz of thoses files
- tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT)
- dirs = []
- for fn, afn in files:
-# print('%s: %s' % (tfn, fn))
- dn = dirname(afn)
- if dn not in dirs:
- # create every dirs first if not exist yet
- d = ''
- for component in split(dn):
- d = join(d, component)
- if d.startswith('/'):
- d = d[1:]
- if d == '' or d in dirs:
- continue
- dirs.append(d)
- tinfo = tarfile.TarInfo(d)
- tinfo.type = tarfile.DIRTYPE
- tf.addfile(tinfo)
-
- # put the file
- tf.add(fn, afn)
- tf.close()
-
-
-def compile_dir(dfn):
- '''
- Compile *.py in directory `dfn` to *.pyo
- '''
-
- return # Currently leaving out the compile to pyo step because it's somehow broken
- # -OO = strip docstrings
- subprocess.call([PYTHON, '-OO', '-m', 'compileall', '-f', dfn])
-
-
-def make_package(args):
- # # Update the project to a recent version.
- # try:
- # subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t',
- # 'android-{}'.format(args.sdk_version)])
- # except (OSError, IOError):
- # print('An error occured while calling', ANDROID, 'update')
- # print('Your PATH must include android tools.')
- # sys.exit(-1)
-
- # Delete the old assets.
- if exists('assets/public.mp3'):
- os.unlink('assets/public.mp3')
-
- if exists('assets/private.mp3'):
- os.unlink('assets/private.mp3')
-
- # In order to speedup import and initial depack,
- # construct a python27.zip
- make_python_zip()
-
- # Package up the private data (public not supported).
- tar_dirs = [args.private]
- if exists('private'):
- tar_dirs.append('private')
- if exists('crystax_python'):
- tar_dirs.append('crystax_python')
- if args.private:
- make_tar('assets/private.mp3', tar_dirs, args.ignore_path)
- # else:
- # make_tar('assets/private.mp3', ['private'])
-
- # if args.dir:
- # make_tar('assets/public.mp3', [args.dir], args.ignore_path)
-
-
- # # Build.
- # try:
- # for arg in args.command:
- # subprocess.check_call([ANT, arg])
- # except (OSError, IOError):
- # print 'An error occured while calling', ANT
- # print 'Did you install ant on your system ?'
- # sys.exit(-1)
-
-
- # Prepare some variables for templating process
-
-# default_icon = 'templates/kivy-icon.png'
-# shutil.copy(args.icon or default_icon, 'res/drawable/icon.png')
-
-# default_presplash = 'templates/kivy-presplash.jpg'
-# shutil.copy(args.presplash or default_presplash,
-# 'res/drawable/presplash.jpg')
-
- # If extra Java jars were requested, copy them into the libs directory
- if args.add_jar:
- for jarname in args.add_jar:
- if not exists(jarname):
- print('Requested jar does not exist: {}'.format(jarname))
- sys.exit(-1)
- shutil.copy(jarname, 'libs')
-
-# versioned_name = (args.name.replace(' ', '').replace('\'', '') +
-# '-' + args.version)
-
-# version_code = 0
-# if not args.numeric_version:
-# for i in args.version.split('.'):
-# version_code *= 100
-# version_code += int(i)
-# args.numeric_version = str(version_code)
-
-# if args.intent_filters:
-# with open(args.intent_filters) as fd:
-# args.intent_filters = fd.read()
-
- if args.extra_source_dirs:
- esd = []
- for spec in args.extra_source_dirs:
- if ':' in spec:
- specdir, specincludes = spec.split(':')
- else:
- specdir = spec
- specincludes = '**'
- esd.append((realpath(specdir), specincludes))
- args.extra_source_dirs = esd
- else:
- args.extra_source_dirs = []
-
- service = False
- service_main = join(realpath(args.private), 'service', 'main.py')
- if exists(service_main) or exists(service_main + 'o'):
- service = True
-
- service_names = []
- for sid, spec in enumerate(args.services):
- spec = spec.split(':')
- name = spec[0]
- entrypoint = spec[1]
- options = spec[2:]
-
- foreground = 'foreground' in options
- sticky = 'sticky' in options
-
- service_names.append(name)
- render(
- 'Service.tmpl.java',
- 'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
- name=name,
- entrypoint=entrypoint,
- args=args,
- foreground=foreground,
- sticky=sticky,
- service_id=sid + 1,
- )
-
- render(
- 'AndroidManifest.tmpl.xml',
- 'AndroidManifest.xml',
- args=args,
- service=service,
- service_names=service_names,
- )
-
- render(
- 'app.build.tmpl.gradle',
- 'app.build.gradle',
- args=args
- )
-
-# render(
-# 'build.tmpl.xml',
-# 'build.xml',
-# args=args,
-# versioned_name=versioned_name)
-
-# render(
-# 'strings.tmpl.xml',
-# 'res/values/strings.xml',
-# args=args)
-
-# render(
-# 'custom_rules.tmpl.xml',
-# 'custom_rules.xml',
-# args=args)
-
-# with open(join(dirname(__file__), 'res',
-# 'values', 'strings.xml')) as fileh:
-# lines = fileh.read()
-
-# with open(join(dirname(__file__), 'res',
-# 'values', 'strings.xml'), 'w') as fileh:
-# fileh.write(re.sub(r'"private_version">[0-9\.]*<',
-# '"private_version">{}<'.format(
-# str(time.time())), lines))
-
-
-def parse_args(args=None):
-
- global BLACKLIST_PATTERNS, WHITELIST_PATTERNS
- default_android_api = 12
- import argparse
- ap = argparse.ArgumentParser(description='''\
-Package a Python application for Android.
-
-For this to work, Java and Ant need to be in your path, as does the
-tools directory of the Android SDK.
-''')
-
- ap.add_argument('--private', dest='private',
- help='the dir of user files',
- required=True)
- ap.add_argument('--package', dest='package',
- help=('The name of the java package the project will be'
- ' packaged under.'),
- required=True)
-# ap.add_argument('--name', dest='name',
-# help=('The human-readable name of the project.'),
-# required=True)
-# ap.add_argument('--numeric-version', dest='numeric_version',
-# help=('The numeric version number of the project. If not '
-# 'given, this is automatically computed from the '
-# 'version.'))
-# ap.add_argument('--version', dest='version',
-# help=('The version number of the project. This should '
-# 'consist of numbers and dots, and should have the '
-# 'same number of groups of numbers as previous '
-# 'versions.'),
-# required=True)
-# ap.add_argument('--orientation', dest='orientation', default='portrait',
-# help=('The orientation that the game will display in. '
-# 'Usually one of "landscape", "portrait" or '
-# '"sensor"'))
-# ap.add_argument('--icon', dest='icon',
-# help='A png file to use as the icon for the application.')
-# ap.add_argument('--permission', dest='permissions', action='append',
-# help='The permissions to give this app.')
- ap.add_argument('--meta-data', dest='meta_data', action='append',
- help='Custom key=value to add in application metadata')
-# ap.add_argument('--presplash', dest='presplash',
-# help=('A jpeg file to use as a screen while the '
-# 'application is loading.'))
- ap.add_argument('--wakelock', dest='wakelock', action='store_true',
- help=('Indicate if the application needs the device '
- 'to stay on'))
- ap.add_argument('--window', dest='window', action='store_false',
- help='Indicate if the application will be windowed')
- ap.add_argument('--blacklist', dest='blacklist',
- default=join(curdir, 'blacklist.txt'),
- help=('Use a blacklist file to match unwanted file in '
- 'the final APK'))
- ap.add_argument('--whitelist', dest='whitelist',
- default=join(curdir, 'whitelist.txt'),
- help=('Use a whitelist file to prevent blacklisting of '
- 'file in the final APK'))
- ap.add_argument('--add-jar', dest='add_jar', action='append',
- help=('Add a Java .jar to the libs, so you can access its '
- 'classes with pyjnius. You can specify this '
- 'argument more than once to include multiple jars'))
- ap.add_argument('--sdk', dest='sdk_version', default=-1,
- type=int, help=('Android SDK version to use. Default to '
- 'the value of minsdk'))
- ap.add_argument('--minsdk', dest='min_sdk_version',
- default=default_android_api, type=int,
- help=('Minimum Android SDK version to use. Default to '
- 'the value of ANDROIDAPI, or {} if not set'
- .format(default_android_api)))
-# ap.add_argument('--intent-filters', dest='intent_filters',
-# help=('Add intent-filters xml rules to the '
-# 'AndroidManifest.xml file. The argument is a '
-# 'filename containing xml. The filename should be '
-# 'located relative to the python-for-android '
-# 'directory'))
-# ap.add_argument('--with-billing', dest='billing_pubkey',
-# help='If set, the billing service will be added (not implemented)')
- ap.add_argument('--service', dest='services', action='append',
- help='Declare a new service entrypoint: '
- 'NAME:PATH_TO_PY[:foreground]')
- ap.add_argument('--add-source', dest='extra_source_dirs', action='append',
- help='Include additional source dirs in Java build')
-
- def _read_configuration():
- # search for a .p4a configuration file in the current directory
- if not exists(".p4a"):
- return
- print("Reading .p4a configuration")
- with open(".p4a") as fd:
- lines = fd.readlines()
- lines = [shlex.split(line)
- for line in lines if not line.startswith("#")]
- for line in lines:
- for arg in line:
- sys.argv.append(arg)
-
- _read_configuration()
-
- if args is None:
- args = sys.argv[1:]
- args = ap.parse_args(args)
- args.ignore_path = []
-
-# if args.billing_pubkey:
-# print('Billing not yet supported in sdl2 bootstrap!')
-# exit(1)
-
- if args.sdk_version == -1:
- args.sdk_version = args.min_sdk_version
-
-# if args.permissions is None:
-# args.permissions = []
-
- if args.meta_data is None:
- args.meta_data = []
-
- if args.services is None:
- args.services = []
-
- if args.blacklist:
- with open(args.blacklist) as fd:
- patterns = [x.strip() for x in fd.read().splitlines()
- if x.strip() and not x.strip().startswith('#')]
- BLACKLIST_PATTERNS += patterns
-
- if args.whitelist:
- with open(args.whitelist) as fd:
- patterns = [x.strip() for x in fd.read().splitlines()
- if x.strip() and not x.strip().startswith('#')]
- WHITELIST_PATTERNS += patterns
-
- make_package(args)
-
- return args
-
-
-if __name__ == "__main__":
-
- parse_args()
diff --git a/pythonforandroid/bootstraps/service_only/build/gradle.properties b/pythonforandroid/bootstraps/service_only/build/gradle.properties
deleted file mode 100644
index 24a89fbbb..000000000
--- a/pythonforandroid/bootstraps/service_only/build/gradle.properties
+++ /dev/null
@@ -1,21 +0,0 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-# Default value: -Xmx10248m -XX:MaxPermSize=256m
-# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-
-android.useDeprecatedNdk=true
-org.gradle.jvmargs=-Xmx4096M
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/service_only/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/service_only/build/jni/application/src/Android.mk
new file mode 100644
index 000000000..dc351a331
--- /dev/null
+++ b/pythonforandroid/bootstraps/service_only/build/jni/application/src/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := main
+
+# Add your application source files here...
+LOCAL_SRC_FILES := start.c pyjniusjni.c
+
+LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
+
+LOCAL_SHARED_LIBRARIES := python_shared
+
+LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
+
+LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk b/pythonforandroid/bootstraps/service_only/build/jni/application/src/Android_static.mk
similarity index 100%
rename from pythonforandroid/bootstraps/service_only/build/jni/src/Android_static.mk
rename to pythonforandroid/bootstraps/service_only/build/jni/application/src/Android_static.mk
diff --git a/pythonforandroid/bootstraps/service_only/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/service_only/build/jni/application/src/bootstrap_name.h
new file mode 100644
index 000000000..b93a4ae6c
--- /dev/null
+++ b/pythonforandroid/bootstraps/service_only/build/jni/application/src/bootstrap_name.h
@@ -0,0 +1,6 @@
+
+#define BOOTSTRAP_NAME_SERVICEONLY
+#define BOOTSTRAP_USES_NO_SDL_HEADERS
+
+const char bootstrap_name[] = "service_only";
+
diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/pyjniusjni.c b/pythonforandroid/bootstraps/service_only/build/jni/application/src/pyjniusjni.c
similarity index 100%
rename from pythonforandroid/bootstraps/service_only/build/jni/src/pyjniusjni.c
rename to pythonforandroid/bootstraps/service_only/build/jni/application/src/pyjniusjni.c
diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk b/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk
deleted file mode 100644
index 018a7cadf..000000000
--- a/pythonforandroid/bootstraps/service_only/build/jni/src/Android.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := main
-
-# Add your application source files here...
-LOCAL_SRC_FILES := start.c pyjniusjni.c
-
-LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/include/python2.7 $(EXTRA_CFLAGS)
-
-LOCAL_SHARED_LIBRARIES := python_shared
-
-LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
-
-LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/lib $(APPLICATION_ADDITIONAL_LDFLAGS)
-
-include $(BUILD_SHARED_LIBRARY)
-
-ifdef CRYSTAX_PYTHON_VERSION
- $(call import-module,python/$(CRYSTAX_PYTHON_VERSION))
-endif
diff --git a/pythonforandroid/bootstraps/service_only/build/jni/src/start.c b/pythonforandroid/bootstraps/service_only/build/jni/src/start.c
deleted file mode 100644
index 7251cdd13..000000000
--- a/pythonforandroid/bootstraps/service_only/build/jni/src/start.c
+++ /dev/null
@@ -1,318 +0,0 @@
-
-#define PY_SSIZE_T_CLEAN
-#include "Python.h"
-#ifndef Py_PYTHON_H
-#error Python headers needed to compile C extensions, please install development version of Python.
-#else
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <jni.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-
-#include "android/log.h"
-
-#define ENTRYPOINT_MAXLEN 128
-#define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x))
-#define LOGP(x) LOG("python", (x))
-
-static PyObject *androidembed_log(PyObject *self, PyObject *args) {
- char *logstr = NULL;
- if (!PyArg_ParseTuple(args, "s", &logstr)) {
- return NULL;
- }
- LOG(getenv("PYTHON_NAME"), logstr);
- Py_RETURN_NONE;
-}
-
-static PyMethodDef AndroidEmbedMethods[] = {
- {"log", androidembed_log, METH_VARARGS, "Log on android platform"},
- {NULL, NULL, 0, NULL}};
-
-#if PY_MAJOR_VERSION >= 3
-static struct PyModuleDef androidembed = {PyModuleDef_HEAD_INIT, "androidembed",
- "", -1, AndroidEmbedMethods};
-
-PyMODINIT_FUNC initandroidembed(void) {
- return PyModule_Create(&androidembed);
-}
-#else
-PyMODINIT_FUNC initandroidembed(void) {
- (void)Py_InitModule("androidembed", AndroidEmbedMethods);
-}
-#endif
-
-int dir_exists(char *filename) {
- struct stat st;
- if (stat(filename, &st) == 0) {
- if (S_ISDIR(st.st_mode))
- return 1;
- }
- return 0;
-}
-
-int file_exists(const char *filename) {
- FILE *file;
- if (file = fopen(filename, "r")) {
- fclose(file);
- return 1;
- }
- return 0;
-}
-
-/* int main(int argc, char **argv) { */
-int main(int argc, char *argv[]) {
-
- char *env_argument = NULL;
- char *env_entrypoint = NULL;
- char *env_logname = NULL;
- char entrypoint[ENTRYPOINT_MAXLEN];
- int ret = 0;
- FILE *fd;
-
- /* AND: Several filepaths are hardcoded here, these must be made
- configurable */
- /* AND: P4A uses env vars...not sure what's best */
- LOGP("Initialize Python for Android");
- env_argument = getenv("ANDROID_ARGUMENT");
- setenv("ANDROID_APP_PATH", env_argument, 1);
- env_entrypoint = getenv("ANDROID_ENTRYPOINT");
- env_logname = getenv("PYTHON_NAME");
-
- if (env_logname == NULL) {
- env_logname = "python";
- setenv("PYTHON_NAME", "python", 1);
- }
-
- LOGP("Changing directory to the one provided by ANDROID_ARGUMENT");
- LOGP(env_argument);
- chdir(env_argument);
-
- Py_SetProgramName(L"android_python");
-
-#if PY_MAJOR_VERSION >= 3
- /* our logging module for android
- */
- PyImport_AppendInittab("androidembed", initandroidembed);
-#endif
-
- LOGP("Preparing to initialize python");
-
- if (dir_exists("crystax_python/")) {
- LOGP("crystax_python exists");
- char paths[256];
- snprintf(paths, 256,
- "%s/crystax_python/stdlib.zip:%s/crystax_python/modules",
- env_argument, env_argument);
- /* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument,
- * env_argument); */
- LOGP("calculated paths to be...");
- LOGP(paths);
-
-#if PY_MAJOR_VERSION >= 3
- wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
- Py_SetPath(wchar_paths);
-#else
- char *wchar_paths = paths;
- LOGP("Can't Py_SetPath in python2, so crystax python2 doesn't work yet");
- exit(1);
-#endif
-
- LOGP("set wchar paths...");
- } else {
- LOGP("crystax_python does not exist");
- }
-
- Py_Initialize();
-
-#if PY_MAJOR_VERSION < 3
- PySys_SetArgv(argc, argv);
-#endif
-
- LOGP("Initialized python");
-
- /* ensure threads will work.
- */
- LOGP("AND: Init threads");
- PyEval_InitThreads();
-
-#if PY_MAJOR_VERSION < 3
- initandroidembed();
-#endif
-
- PyRun_SimpleString("import androidembed\nandroidembed.log('testing python "
- "print redirection')");
-
- /* inject our bootstrap code to redirect python stdin/stdout
- * replace sys.path with our path
- */
- PyRun_SimpleString("import sys, posix\n");
- if (dir_exists("lib")) {
- /* If we built our own python, set up the paths correctly */
- LOGP("Setting up python from ANDROID_PRIVATE");
- PyRun_SimpleString("private = posix.environ['ANDROID_PRIVATE']\n"
- "argument = posix.environ['ANDROID_ARGUMENT']\n"
- "sys.path[:] = [ \n"
- " private + '/lib/python27.zip', \n"
- " private + '/lib/python2.7/', \n"
- " private + '/lib/python2.7/lib-dynload/', \n"
- " private + '/lib/python2.7/site-packages/', \n"
- " argument ]\n");
- }
-
- if (dir_exists("crystax_python")) {
- char add_site_packages_dir[256];
- snprintf(add_site_packages_dir, 256,
- "sys.path.append('%s/crystax_python/site-packages')",
- env_argument);
-
- PyRun_SimpleString("import sys\n"
- "sys.argv = ['notaninterpreterreally']\n"
- "from os.path import realpath, join, dirname");
- PyRun_SimpleString(add_site_packages_dir);
- /* "sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))") */
- PyRun_SimpleString("sys.path = ['.'] + sys.path");
- }
-
- PyRun_SimpleString(
- "class LogFile(object):\n"
- " def __init__(self):\n"
- " self.buffer = ''\n"
- " def write(self, s):\n"
- " s = self.buffer + s\n"
- " lines = s.split(\"\\n\")\n"
- " for l in lines[:-1]:\n"
- " androidembed.log(l)\n"
- " self.buffer = lines[-1]\n"
- " def flush(self):\n"
- " return\n"
- "sys.stdout = sys.stderr = LogFile()\n"
- "print('Android path', sys.path)\n"
- "import os\n"
- "print('os.environ is', os.environ)\n"
- "print('Android kivy bootstrap done. __name__ is', __name__)");
-
-#if PY_MAJOR_VERSION < 3
- PyRun_SimpleString("import site; print site.getsitepackages()\n");
-#endif
-
- LOGP("AND: Ran string");
-
- /* run it !
- */
- LOGP("Run user program, change dir and execute entrypoint");
-
- /* Get the entrypoint, search the .pyo then .py
- */
- char *dot = strrchr(env_entrypoint, '.');
- if (dot <= 0) {
- LOGP("Invalid entrypoint, abort.");
- return -1;
- }
- if (strlen(env_entrypoint) > ENTRYPOINT_MAXLEN - 2) {
- LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN.");
- return -1;
- }
- if (!strcmp(dot, ".pyo")) {
- if (!file_exists(env_entrypoint)) {
- /* fallback on .py */
- strcpy(entrypoint, env_entrypoint);
- entrypoint[strlen(env_entrypoint) - 1] = '\0';
- LOGP(entrypoint);
- if (!file_exists(entrypoint)) {
- LOGP("Entrypoint not found (.pyo, fallback on .py), abort");
- return -1;
- }
- } else {
- strcpy(entrypoint, env_entrypoint);
- }
- } else if (!strcmp(dot, ".py")) {
- /* if .py is passed, check the pyo version first */
- strcpy(entrypoint, env_entrypoint);
- entrypoint[strlen(env_entrypoint) + 1] = '\0';
- entrypoint[strlen(env_entrypoint)] = 'o';
- if (!file_exists(entrypoint)) {
- /* fallback on pure python version */
- if (!file_exists(env_entrypoint)) {
- LOGP("Entrypoint not found (.py), abort.");
- return -1;
- }
- strcpy(entrypoint, env_entrypoint);
- }
- } else {
- LOGP("Entrypoint have an invalid extension (must be .py or .pyo), abort.");
- return -1;
- }
- // LOGP("Entrypoint is:");
- // LOGP(entrypoint);
- fd = fopen(entrypoint, "r");
- if (fd == NULL) {
- LOGP("Open the entrypoint failed");
- LOGP(entrypoint);
- return -1;
- }
-
- /* run python !
- */
- ret = PyRun_SimpleFile(fd, entrypoint);
-
- if (PyErr_Occurred() != NULL) {
- ret = 1;
- PyErr_Print(); /* This exits with the right code if SystemExit. */
- PyObject *f = PySys_GetObject("stdout");
- if (PyFile_WriteString(
- "\n", f)) /* python2 used Py_FlushLine, but this no longer exists */
- PyErr_Clear();
- }
-
- /* close everything
- */
- Py_Finalize();
- fclose(fd);
-
- LOGP("Python for android ended.");
- return ret;
-}
-
-JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
- JNIEnv *env, jobject j_this, jstring j_android_private,
- jstring j_android_argument, jstring j_service_entrypoint,
- jstring j_python_name, jstring j_python_home, jstring j_python_path,
- jstring j_arg) {
- jboolean iscopy;
- const char *android_private =
- (*env)->GetStringUTFChars(env, j_android_private, &iscopy);
- const char *android_argument =
- (*env)->GetStringUTFChars(env, j_android_argument, &iscopy);
- const char *service_entrypoint =
- (*env)->GetStringUTFChars(env, j_service_entrypoint, &iscopy);
- const char *python_name =
- (*env)->GetStringUTFChars(env, j_python_name, &iscopy);
- const char *python_home =
- (*env)->GetStringUTFChars(env, j_python_home, &iscopy);
- const char *python_path =
- (*env)->GetStringUTFChars(env, j_python_path, &iscopy);
- const char *arg =
- (*env)->GetStringUTFChars(env, j_arg, &iscopy);
-
- setenv("ANDROID_PRIVATE", android_private, 1);
- setenv("ANDROID_ARGUMENT", android_argument, 1);
- setenv("ANDROID_ENTRYPOINT", service_entrypoint, 1);
- setenv("PYTHONOPTIMIZE", "2", 1);
- setenv("PYTHON_NAME", python_name, 1);
- setenv("PYTHONHOME", python_home, 1);
- setenv("PYTHONPATH", python_path, 1);
- setenv("PYTHON_SERVICE_ARGUMENT", arg, 1);
-
- char *argv[] = {"."};
- /* ANDROID_ARGUMENT points to service subdir,
- * so main() will run main.py from this dir
- */
- main(1, argv);
-}
-
-#endif
diff --git a/pythonforandroid/bootstraps/service_only/build/src/main/assets/.gitkeep b/pythonforandroid/bootstraps/service_only/build/src/main/assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java
similarity index 50%
rename from pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonActivity.java
rename to pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java
index 194bc90a6..57112dd55 100644
--- a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonActivity.java
+++ b/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -1,56 +1,26 @@
-
package org.kivy.android;
-import java.net.Socket;
-import java.net.InetSocketAddress;
-
import android.os.SystemClock;
-import java.io.InputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
import java.io.File;
-import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
-import android.app.*;
-import android.content.*;
-import android.view.*;
-import android.view.ViewGroup;
-import android.view.SurfaceView;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.view.KeyEvent;
import android.util.Log;
import android.widget.Toast;
import android.os.Bundle;
import android.os.PowerManager;
-import android.graphics.PixelFormat;
-import android.view.SurfaceHolder;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.ApplicationInfo;
-import android.content.Intent;
-import android.widget.ImageView;
-import java.io.InputStream;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
-import android.widget.AbsoluteLayout;
-import android.view.ViewGroup.LayoutParams;
-
-import android.webkit.WebViewClient;
-import android.webkit.WebView;
-
-import org.kivy.android.PythonUtil;
-
-import org.kivy.android.WebViewLoader;
import org.renpy.android.ResourceManager;
-import org.renpy.android.AssetExtract;
public class PythonActivity extends Activity {
// This activity is modified from a mixture of the SDLActivity and
@@ -61,23 +31,38 @@ public class PythonActivity extends Activity {
public static PythonActivity mActivity = null;
- /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
+ /** If shared libraries (e.g. the native application) could not be loaded. */
public static boolean mBrokenLibraries;
- protected static ViewGroup mLayout;
- protected static WebView mWebView;
-
protected static Thread mPythonThread;
private ResourceManager resourceManager = null;
private Bundle mMetaData = null;
private PowerManager.WakeLock mWakeLock = null;
+ public String getAppRoot() {
+ String app_root = getFilesDir().getAbsolutePath() + "/app";
+ return app_root;
+ }
+
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List<String> entryPoints = new ArrayList<String>();
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
+
public static void initialize() {
- // The static nature of the singleton and Android quirkyness force us to initialize everything here
+ // The static nature of the singleton and Android quirkiness force us to initialize everything here
// Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
- mWebView = null;
- mLayout = null;
mBrokenLibraries = false;
}
@@ -87,13 +72,21 @@ protected void onCreate(Bundle savedInstanceState) {
resourceManager = new ResourceManager(this);
Log.v(TAG, "Ready to unpack");
- unpackData("private", getFilesDir());
+ File app_root_file = new File(getAppRoot());
+ PythonUtil.unpackAsset(mActivity, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
- this.mActivity = this;
+ Log.v(TAG, "About to do super onCreate");
+ super.onCreate(savedInstanceState);
+ Log.v(TAG, "Did super onCreate");
+ this.mActivity = this;
+ //this.showLoadingScreen();
Log.v("Python", "Device: " + android.os.Build.DEVICE);
Log.v("Python", "Model: " + android.os.Build.MODEL);
- super.onCreate(savedInstanceState);
+
+ //Log.v(TAG, "Ready to unpack");
+ //new UnpackFilesTask().execute(getAppRoot());
PythonActivity.initialize();
@@ -133,44 +126,30 @@ public void onClick(DialogInterface dialog,int id) {
return;
}
- // Set up the webview
- mWebView = new WebView(this);
- mWebView.getSettings().setJavaScriptEnabled(true);
- mWebView.getSettings().setDomStorageEnabled(true);
- mWebView.loadUrl("file:///" + mActivity.getFilesDir().getAbsolutePath() + "/_load.html");
-
- mWebView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
- mWebView.setWebViewClient(new WebViewClient() {
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- view.loadUrl(url);
- return false;
- }
- });
-
- mLayout = new AbsoluteLayout(this);
- mLayout.addView(mWebView);
-
- setContentView(mLayout);
-
+ // Set up the Python environment
+ String app_root_dir = getAppRoot();
String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
+ String entry_point = getEntryPoint(app_root_dir);
+
Log.v(TAG, "Setting env vars for start.c and Python to use");
- PythonActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory);
- PythonActivity.nativeSetEnv("ANDROID_ARGUMENT", mFilesDirectory);
- PythonActivity.nativeSetEnv("ANDROID_APP_PATH", mFilesDirectory);
- PythonActivity.nativeSetEnv("ANDROID_UNPACK", mFilesDirectory);
- PythonActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo");
- PythonActivity.nativeSetEnv("PYTHONHOME", mFilesDirectory);
- PythonActivity.nativeSetEnv("PYTHONPATH", mFilesDirectory + ":" + mFilesDirectory + "/lib");
+ PythonActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ PythonActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ PythonActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ PythonActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
try {
Log.v(TAG, "Access to our meta-data...");
- this.mMetaData = this.mActivity.getPackageManager().getApplicationInfo(
- this.mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
+ mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo(
+ mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
- PowerManager pm = (PowerManager) this.mActivity.getSystemService(Context.POWER_SERVICE);
- if ( this.mMetaData.getInt("wakelock") == 1 ) {
- this.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
+ if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
+ mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ mActivity.mWakeLock.acquire();
}
} catch (PackageManager.NameNotFoundException e) {
}
@@ -179,151 +158,40 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
PythonActivity.mPythonThread = pythonThread;
pythonThread.start();
- final Thread wvThread = new Thread(new WebViewLoaderMain(), "WvThread");
- wvThread.start();
}
@Override
public void onDestroy() {
Log.i("Destroy", "end of app");
super.onDestroy();
-
+
// make sure all child threads (python_thread) are stopped
android.os.Process.killProcess(android.os.Process.myPid());
}
public void loadLibraries() {
- PythonUtil.loadLibraries(getFilesDir());
- }
-
- public void recursiveDelete(File f) {
- if (f.isDirectory()) {
- for (File r : f.listFiles()) {
- recursiveDelete(r);
- }
- }
- f.delete();
- }
-
- /**
- * Show an error using a toast. (Only makes sense from non-UI
- * threads.)
- */
- public void toastError(final String msg) {
-
- final Activity thisActivity = this;
-
- runOnUiThread(new Runnable () {
- public void run() {
- Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show();
- }
- });
-
- // Wait to show the error.
- synchronized (this) {
- try {
- this.wait(1000);
- } catch (InterruptedException e) {
- }
- }
+ String app_root = new String(getAppRoot());
+ File app_root_file = new File(app_root);
+ PythonUtil.loadLibraries(app_root_file,
+ new File(getApplicationInfo().nativeLibraryDir));
}
- public void unpackData(final String resource, File target) {
-
- Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName());
-
- // The version of data in memory and on disk.
- String data_version = resourceManager.getString(resource + "_version");
- String disk_version = null;
-
- Log.v(TAG, "Data version is " + data_version);
-
- // If no version, no unpacking is necessary.
- if (data_version == null) {
- return;
- }
-
- // Check the current disk version, if any.
- String filesDir = target.getAbsolutePath();
- String disk_version_fn = filesDir + "/" + resource + ".version";
-
- try {
- byte buf[] = new byte[64];
- InputStream is = new FileInputStream(disk_version_fn);
- int len = is.read(buf);
- disk_version = new String(buf, 0, len);
- is.close();
- } catch (Exception e) {
- disk_version = "";
- }
-
- // If the disk data is out of date, extract it and write the
- // version file.
- // if (! data_version.equals(disk_version)) {
- if (! data_version.equals(disk_version)) {
- Log.v(TAG, "Extracting " + resource + " assets.");
-
- recursiveDelete(target);
- target.mkdirs();
-
- AssetExtract ae = new AssetExtract(this);
- if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) {
- toastError("Could not extract " + resource + " data.");
- }
-
- try {
- // Write .nomedia.
- new File(target, ".nomedia").createNewFile();
-
- // Write version file.
- FileOutputStream os = new FileOutputStream(disk_version_fn);
- os.write(data_version.getBytes());
- os.close();
- } catch (Exception e) {
- Log.w("python", e);
- }
- }
- }
-
- public static void loadUrl(String url) {
- class LoadUrl implements Runnable {
- private String mUrl;
-
- public LoadUrl(String url) {
- mUrl = url;
- }
-
- public void run() {
- mWebView.loadUrl(mUrl);
- }
- }
-
- Log.i(TAG, "Opening URL: " + url);
- mActivity.runOnUiThread(new LoadUrl(url));
- }
-
- public static ViewGroup getLayout() {
- return mLayout;
- }
-
- long lastBackClick = SystemClock.elapsedRealtime();
+ long lastBackClick = 0;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Check if the key event was the Back button and if there's history
- if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
- mWebView.goBack();
- return true;
- }
- // If it wasn't the Back key or there's no web page history, bubble up to the default
- // system behavior (probably exit the activity)
- if (SystemClock.elapsedRealtime() - lastBackClick > 2000){
+ // Check if the key event was the Back button
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // If there's no web page history, bubble up to the default
+ // system behavior (probably exit the activity)
+ if (SystemClock.elapsedRealtime() - lastBackClick > 2000){
+ lastBackClick = SystemClock.elapsedRealtime();
+ Toast.makeText(this, "Tap again to close the app", Toast.LENGTH_LONG).show();
+ return true;
+ }
+
lastBackClick = SystemClock.elapsedRealtime();
- Toast.makeText(this, "Click again to close the app",
- Toast.LENGTH_LONG).show();
- return true;
}
- lastBackClick = SystemClock.elapsedRealtime();
return super.onKeyDown(keyCode, event);
}
@@ -397,16 +265,45 @@ protected void onActivityResult(int requestCode, int resultCode, Intent intent)
}
}
- public static void start_service(String serviceTitle, String serviceDescription,
- String pythonServiceArgument) {
+ public static void start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, true
+ );
+ }
+
+ public static void start_service_not_as_foreground(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, false
+ );
+ }
+
+ public static void _do_start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument,
+ boolean showForegroundNotification
+ ) {
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
- String filesDirectory = argument;
+ String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
serviceIntent.putExtra("androidPrivate", argument);
- serviceIntent.putExtra("androidArgument", argument);
- serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo");
- serviceIntent.putExtra("pythonHome", argument);
- serviceIntent.putExtra("pythonPath", argument + ":" + filesDirectory + "/lib");
+ serviceIntent.putExtra("androidArgument", app_root_dir);
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
+ serviceIntent.putExtra("pythonName", "python");
+ serviceIntent.putExtra("pythonHome", app_root_dir);
+ serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
+ serviceIntent.putExtra("serviceStartAsForeground",
+ (showForegroundNotification ? "true" : "false")
+ );
serviceIntent.putExtra("serviceTitle", serviceTitle);
serviceIntent.putExtra("serviceDescription", serviceDescription);
serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
@@ -419,7 +316,7 @@ public static void stop_service() {
}
- public static native void nativeSetEnv(String j_name, String j_value);
+ public static native void nativeSetenv(String name, String value);
public static native int nativeInit(Object arguments);
}
@@ -431,10 +328,3 @@ public void run() {
PythonActivity.nativeInit(new String[0]);
}
}
-
-class WebViewLoaderMain implements Runnable {
- @Override
- public void run() {
- WebViewLoader.testConnection();
- }
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/main/jniLibs/.gitkeep b/pythonforandroid/bootstraps/service_only/build/src/main/jniLibs/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/service_only/build/src/main/res/drawable/.gitkeep b/pythonforandroid/bootstraps/service_only/build/src/main/res/drawable/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/service_only/build/src/main/res/mipmap/.gitkeep b/pythonforandroid/bootstraps/service_only/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/Octal.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/Octal.java
deleted file mode 100755
index dd10624ea..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/Octal.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class Octal {
-
- /**
- * Parse an octal string from a header buffer. This is used for the file
- * permission mode value.
- *
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The long value of the octal string.
- */
- public static long parseOctal(byte[] header, int offset, int length) {
- long result = 0;
- boolean stillPadding = true;
-
- int end = offset + length;
- for (int i = offset; i < end; ++i) {
- if (header[i] == 0)
- break;
-
- if (header[i] == (byte) ' ' || header[i] == '0') {
- if (stillPadding)
- continue;
-
- if (header[i] == (byte) ' ')
- break;
- }
-
- stillPadding = false;
-
- result = ( result << 3 ) + ( header[i] - '0' );
- }
-
- return result;
- }
-
- /**
- * Parse an octal integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The integer value of the octal bytes.
- */
- public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
- int idx = length - 1;
-
- buf[offset + idx] = 0;
- --idx;
- buf[offset + idx] = (byte) ' ';
- --idx;
-
- if (value == 0) {
- buf[offset + idx] = (byte) '0';
- --idx;
- } else {
- for (long val = value; idx >= 0 && val > 0; --idx) {
- buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
- val = val >> 3;
- }
- }
-
- for (; idx >= 0; --idx) {
- buf[offset + idx] = (byte) ' ';
- }
-
- return offset + length;
- }
-
- /**
- * Parse the checksum octal integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The integer value of the entry's checksum.
- */
- public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
- getOctalBytes( value, buf, offset, length );
- buf[offset + length - 1] = (byte) ' ';
- buf[offset + length - 2] = 0;
- return offset + length;
- }
-
- /**
- * Parse an octal long integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The long value of the octal bytes.
- */
- public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
- byte[] temp = new byte[length + 1];
- getOctalBytes( value, temp, 0, length + 1 );
- System.arraycopy( temp, 0, buf, offset, length );
- return offset + length;
- }
-
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarConstants.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarConstants.java
deleted file mode 100755
index 4611e20ea..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarConstants.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarConstants {
- public static final int EOF_BLOCK = 1024;
- public static final int DATA_BLOCK = 512;
- public static final int HEADER_BLOCK = 512;
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarEntry.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarEntry.java
deleted file mode 100755
index fe01db463..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarEntry.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-import java.util.Date;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarEntry {
- protected File file;
- protected TarHeader header;
-
- private TarEntry() {
- this.file = null;
- header = new TarHeader();
- }
-
- public TarEntry(File file, String entryName) {
- this();
- this.file = file;
- this.extractTarHeader(entryName);
- }
-
- public TarEntry(byte[] headerBuf) {
- this();
- this.parseTarHeader(headerBuf);
- }
-
- /**
- * Constructor to create an entry from an existing TarHeader object.
- *
- * This method is useful to add new entries programmatically (e.g. for
- * adding files or directories that do not exist in the file system).
- *
- * @param header
- *
- */
- public TarEntry(TarHeader header) {
- this.file = null;
- this.header = header;
- }
-
- public boolean equals(TarEntry it) {
- return header.name.toString().equals(it.header.name.toString());
- }
-
- public boolean isDescendent(TarEntry desc) {
- return desc.header.name.toString().startsWith(header.name.toString());
- }
-
- public TarHeader getHeader() {
- return header;
- }
-
- public String getName() {
- String name = header.name.toString();
- if (header.namePrefix != null && !header.namePrefix.toString().equals("")) {
- name = header.namePrefix.toString() + "/" + name;
- }
-
- return name;
- }
-
- public void setName(String name) {
- header.name = new StringBuffer(name);
- }
-
- public int getUserId() {
- return header.userId;
- }
-
- public void setUserId(int userId) {
- header.userId = userId;
- }
-
- public int getGroupId() {
- return header.groupId;
- }
-
- public void setGroupId(int groupId) {
- header.groupId = groupId;
- }
-
- public String getUserName() {
- return header.userName.toString();
- }
-
- public void setUserName(String userName) {
- header.userName = new StringBuffer(userName);
- }
-
- public String getGroupName() {
- return header.groupName.toString();
- }
-
- public void setGroupName(String groupName) {
- header.groupName = new StringBuffer(groupName);
- }
-
- public void setIds(int userId, int groupId) {
- this.setUserId(userId);
- this.setGroupId(groupId);
- }
-
- public void setModTime(long time) {
- header.modTime = time / 1000;
- }
-
- public void setModTime(Date time) {
- header.modTime = time.getTime() / 1000;
- }
-
- public Date getModTime() {
- return new Date(header.modTime * 1000);
- }
-
- public File getFile() {
- return this.file;
- }
-
- public long getSize() {
- return header.size;
- }
-
- public void setSize(long size) {
- header.size = size;
- }
-
- /**
- * Checks if the org.kamrazafar.jtar entry is a directory
- *
- * @return
- */
- public boolean isDirectory() {
- if (this.file != null)
- return this.file.isDirectory();
-
- if (header != null) {
- if (header.linkFlag == TarHeader.LF_DIR)
- return true;
-
- if (header.name.toString().endsWith("/"))
- return true;
- }
-
- return false;
- }
-
- /**
- * Extract header from File
- *
- * @param entryName
- */
- public void extractTarHeader(String entryName) {
- header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory());
- }
-
- /**
- * Calculate checksum
- *
- * @param buf
- * @return
- */
- public long computeCheckSum(byte[] buf) {
- long sum = 0;
-
- for (int i = 0; i < buf.length; ++i) {
- sum += 255 & buf[i];
- }
-
- return sum;
- }
-
- /**
- * Writes the header to the byte buffer
- *
- * @param outbuf
- */
- public void writeEntryHeader(byte[] outbuf) {
- int offset = 0;
-
- offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN);
- offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN);
- offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN);
- offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN);
-
- long size = header.size;
-
- offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
- offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN);
-
- int csOffset = offset;
- for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
- outbuf[offset++] = (byte) ' ';
-
- outbuf[offset++] = header.linkFlag;
-
- offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN);
- offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN);
- offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN);
- offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN);
- offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN);
- offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN);
- offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX);
-
- for (; offset < outbuf.length;)
- outbuf[offset++] = 0;
-
- long checkSum = this.computeCheckSum(outbuf);
-
- Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN);
- }
-
- /**
- * Parses the tar header to the byte buffer
- *
- * @param header
- * @param bh
- */
- public void parseTarHeader(byte[] bh) {
- int offset = 0;
-
- header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
- offset += TarHeader.NAMELEN;
-
- header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
- offset += TarHeader.MODELEN;
-
- header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
- offset += TarHeader.UIDLEN;
-
- header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
- offset += TarHeader.GIDLEN;
-
- header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
- offset += TarHeader.SIZELEN;
-
- header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
- offset += TarHeader.MODTIMELEN;
-
- header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN);
- offset += TarHeader.CHKSUMLEN;
-
- header.linkFlag = bh[offset++];
-
- header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
- offset += TarHeader.NAMELEN;
-
- header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
- offset += TarHeader.USTAR_MAGICLEN;
-
- header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN);
- offset += TarHeader.USTAR_USER_NAMELEN;
-
- header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN);
- offset += TarHeader.USTAR_GROUP_NAMELEN;
-
- header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
- offset += TarHeader.USTAR_DEVLEN;
-
- header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
- offset += TarHeader.USTAR_DEVLEN;
-
- header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX);
- }
-}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarHeader.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarHeader.java
deleted file mode 100755
index b9d3a86be..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarHeader.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-
-/**
- * Header
- *
- * <pre>
- * Offset Size Field
- * 0 100 File name
- * 100 8 File mode
- * 108 8 Owner's numeric user ID
- * 116 8 Group's numeric user ID
- * 124 12 File size in bytes
- * 136 12 Last modification time in numeric Unix time format
- * 148 8 Checksum for header block
- * 156 1 Link indicator (file type)
- * 157 100 Name of linked file
- * </pre>
- *
- *
- * File Types
- *
- * <pre>
- * Value Meaning
- * '0' Normal file
- * (ASCII NUL) Normal file (now obsolete)
- * '1' Hard link
- * '2' Symbolic link
- * '3' Character special
- * '4' Block special
- * '5' Directory
- * '6' FIFO
- * '7' Contigous
- * </pre>
- *
- *
- *
- * Ustar header
- *
- * <pre>
- * Offset Size Field
- * 257 6 UStar indicator "ustar"
- * 263 2 UStar version "00"
- * 265 32 Owner user name
- * 297 32 Owner group name
- * 329 8 Device major number
- * 337 8 Device minor number
- * 345 155 Filename prefix
- * </pre>
- */
-
-public class TarHeader {
-
- /*
- * Header
- */
- public static final int NAMELEN = 100;
- public static final int MODELEN = 8;
- public static final int UIDLEN = 8;
- public static final int GIDLEN = 8;
- public static final int SIZELEN = 12;
- public static final int MODTIMELEN = 12;
- public static final int CHKSUMLEN = 8;
- public static final byte LF_OLDNORM = 0;
-
- /*
- * File Types
- */
- public static final byte LF_NORMAL = (byte) '0';
- public static final byte LF_LINK = (byte) '1';
- public static final byte LF_SYMLINK = (byte) '2';
- public static final byte LF_CHR = (byte) '3';
- public static final byte LF_BLK = (byte) '4';
- public static final byte LF_DIR = (byte) '5';
- public static final byte LF_FIFO = (byte) '6';
- public static final byte LF_CONTIG = (byte) '7';
-
- /*
- * Ustar header
- */
-
- public static final String USTAR_MAGIC = "ustar"; // POSIX
-
- public static final int USTAR_MAGICLEN = 8;
- public static final int USTAR_USER_NAMELEN = 32;
- public static final int USTAR_GROUP_NAMELEN = 32;
- public static final int USTAR_DEVLEN = 8;
- public static final int USTAR_FILENAME_PREFIX = 155;
-
- // Header values
- public StringBuffer name;
- public int mode;
- public int userId;
- public int groupId;
- public long size;
- public long modTime;
- public int checkSum;
- public byte linkFlag;
- public StringBuffer linkName;
- public StringBuffer magic; // ustar indicator and version
- public StringBuffer userName;
- public StringBuffer groupName;
- public int devMajor;
- public int devMinor;
- public StringBuffer namePrefix;
-
- public TarHeader() {
- this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
-
- this.name = new StringBuffer();
- this.linkName = new StringBuffer();
-
- String user = System.getProperty("user.name", "");
-
- if (user.length() > 31)
- user = user.substring(0, 31);
-
- this.userId = 0;
- this.groupId = 0;
- this.userName = new StringBuffer(user);
- this.groupName = new StringBuffer("");
- this.namePrefix = new StringBuffer();
- }
-
- /**
- * Parse an entry name from a header buffer.
- *
- * @param name
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The header's entry name.
- */
- public static StringBuffer parseName(byte[] header, int offset, int length) {
- StringBuffer result = new StringBuffer(length);
-
- int end = offset + length;
- for (int i = offset; i < end; ++i) {
- if (header[i] == 0)
- break;
- result.append((char) header[i]);
- }
-
- return result;
- }
-
- /**
- * Determine the number of bytes in an entry name.
- *
- * @param name
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The number of bytes in a header's entry name.
- */
- public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
- int i;
-
- for (i = 0; i < length && i < name.length(); ++i) {
- buf[offset + i] = (byte) name.charAt(i);
- }
-
- for (; i < length; ++i) {
- buf[offset + i] = 0;
- }
-
- return offset + length;
- }
-
- /**
- * Creates a new header for a file/directory entry.
- *
- *
- * @param name
- * File name
- * @param size
- * File size in bytes
- * @param modTime
- * Last modification time in numeric Unix time format
- * @param dir
- * Is directory
- *
- * @return
- */
- public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
- String name = entryName;
- name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
-
- TarHeader header = new TarHeader();
- header.linkName = new StringBuffer("");
-
- if (name.length() > 100) {
- header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
- header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
- } else {
- header.name = new StringBuffer(name);
- }
-
- if (dir) {
- header.mode = 040755;
- header.linkFlag = TarHeader.LF_DIR;
- if (header.name.charAt(header.name.length() - 1) != '/') {
- header.name.append("/");
- }
- header.size = 0;
- } else {
- header.mode = 0100644;
- header.linkFlag = TarHeader.LF_NORMAL;
- header.size = size;
- }
-
- header.modTime = modTime;
- header.checkSum = 0;
- header.devMajor = 0;
- header.devMinor = 0;
-
- return header;
- }
-}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarInputStream.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarInputStream.java
deleted file mode 100755
index ec50a1b68..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarInputStream.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarInputStream extends FilterInputStream {
-
- private static final int SKIP_BUFFER_SIZE = 2048;
- private TarEntry currentEntry;
- private long currentFileSize;
- private long bytesRead;
- private boolean defaultSkip = false;
-
- public TarInputStream(InputStream in) {
- super(in);
- currentFileSize = 0;
- bytesRead = 0;
- }
-
- @Override
- public boolean markSupported() {
- return false;
- }
-
- /**
- * Not supported
- *
- */
- @Override
- public synchronized void mark(int readlimit) {
- }
-
- /**
- * Not supported
- *
- */
- @Override
- public synchronized void reset() throws IOException {
- throw new IOException("mark/reset not supported");
- }
-
- /**
- * Read a byte
- *
- * @see java.io.FilterInputStream#read()
- */
- @Override
- public int read() throws IOException {
- byte[] buf = new byte[1];
-
- int res = this.read(buf, 0, 1);
-
- if (res != -1) {
- return 0xFF & buf[0];
- }
-
- return res;
- }
-
- /**
- * Checks if the bytes being read exceed the entry size and adjusts the byte
- * array length. Updates the byte counters
- *
- *
- * @see java.io.FilterInputStream#read(byte[], int, int)
- */
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- if (currentEntry != null) {
- if (currentFileSize == currentEntry.getSize()) {
- return -1;
- } else if ((currentEntry.getSize() - currentFileSize) < len) {
- len = (int) (currentEntry.getSize() - currentFileSize);
- }
- }
-
- int br = super.read(b, off, len);
-
- if (br != -1) {
- if (currentEntry != null) {
- currentFileSize += br;
- }
-
- bytesRead += br;
- }
-
- return br;
- }
-
- /**
- * Returns the next entry in the tar file
- *
- * @return TarEntry
- * @throws IOException
- */
- public TarEntry getNextEntry() throws IOException {
- closeCurrentEntry();
-
- byte[] header = new byte[TarConstants.HEADER_BLOCK];
- byte[] theader = new byte[TarConstants.HEADER_BLOCK];
- int tr = 0;
-
- // Read full header
- while (tr < TarConstants.HEADER_BLOCK) {
- int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
-
- if (res < 0) {
- break;
- }
-
- System.arraycopy(theader, 0, header, tr, res);
- tr += res;
- }
-
- // Check if record is null
- boolean eof = true;
- for (byte b : header) {
- if (b != 0) {
- eof = false;
- break;
- }
- }
-
- if (!eof) {
- currentEntry = new TarEntry(header);
- }
-
- return currentEntry;
- }
-
- /**
- * Returns the current offset (in bytes) from the beginning of the stream.
- * This can be used to find out at which point in a tar file an entry's content begins, for instance.
- */
- public long getCurrentOffset() {
- return bytesRead;
- }
-
- /**
- * Closes the current tar entry
- *
- * @throws IOException
- */
- protected void closeCurrentEntry() throws IOException {
- if (currentEntry != null) {
- if (currentEntry.getSize() > currentFileSize) {
- // Not fully read, skip rest of the bytes
- long bs = 0;
- while (bs < currentEntry.getSize() - currentFileSize) {
- long res = skip(currentEntry.getSize() - currentFileSize - bs);
-
- if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
- // I suspect file corruption
- throw new IOException("Possible tar file corruption");
- }
-
- bs += res;
- }
- }
-
- currentEntry = null;
- currentFileSize = 0L;
- skipPad();
- }
- }
-
- /**
- * Skips the pad at the end of each tar entry file content
- *
- * @throws IOException
- */
- protected void skipPad() throws IOException {
- if (bytesRead > 0) {
- int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
-
- if (extra > 0) {
- long bs = 0;
- while (bs < TarConstants.DATA_BLOCK - extra) {
- long res = skip(TarConstants.DATA_BLOCK - extra - bs);
- bs += res;
- }
- }
- }
- }
-
- /**
- * Skips 'n' bytes on the InputStream<br>
- * Overrides default implementation of skip
- *
- */
- @Override
- public long skip(long n) throws IOException {
- if (defaultSkip) {
- // use skip method of parent stream
- // may not work if skip not implemented by parent
- long bs = super.skip(n);
- bytesRead += bs;
-
- return bs;
- }
-
- if (n <= 0) {
- return 0;
- }
-
- long left = n;
- byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
-
- while (left > 0) {
- int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
- if (res < 0) {
- break;
- }
- left -= res;
- }
-
- return n - left;
- }
-
- public boolean isDefaultSkip() {
- return defaultSkip;
- }
-
- public void setDefaultSkip(boolean defaultSkip) {
- this.defaultSkip = defaultSkip;
- }
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarOutputStream.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarOutputStream.java
deleted file mode 100755
index ffdfe8756..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarOutputStream.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarOutputStream extends OutputStream {
- private final OutputStream out;
- private long bytesWritten;
- private long currentFileSize;
- private TarEntry currentEntry;
-
- public TarOutputStream(OutputStream out) {
- this.out = out;
- bytesWritten = 0;
- currentFileSize = 0;
- }
-
- public TarOutputStream(final File fout) throws FileNotFoundException {
- this.out = new BufferedOutputStream(new FileOutputStream(fout));
- bytesWritten = 0;
- currentFileSize = 0;
- }
-
- /**
- * Opens a file for writing.
- */
- public TarOutputStream(final File fout, final boolean append) throws IOException {
- @SuppressWarnings("resource")
- RandomAccessFile raf = new RandomAccessFile(fout, "rw");
- final long fileSize = fout.length();
- if (append && fileSize > TarConstants.EOF_BLOCK) {
- raf.seek(fileSize - TarConstants.EOF_BLOCK);
- }
- out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
- }
-
- /**
- * Appends the EOF record and closes the stream
- *
- * @see java.io.FilterOutputStream#close()
- */
- @Override
- public void close() throws IOException {
- closeCurrentEntry();
- write( new byte[TarConstants.EOF_BLOCK] );
- out.close();
- }
- /**
- * Writes a byte to the stream and updates byte counters
- *
- * @see java.io.FilterOutputStream#write(int)
- */
- @Override
- public void write(int b) throws IOException {
- out.write( b );
- bytesWritten += 1;
-
- if (currentEntry != null) {
- currentFileSize += 1;
- }
- }
-
- /**
- * Checks if the bytes being written exceed the current entry size.
- *
- * @see java.io.FilterOutputStream#write(byte[], int, int)
- */
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- if (currentEntry != null && !currentEntry.isDirectory()) {
- if (currentEntry.getSize() < currentFileSize + len) {
- throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
- + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
- + "] being written." );
- }
- }
-
- out.write( b, off, len );
-
- bytesWritten += len;
-
- if (currentEntry != null) {
- currentFileSize += len;
- }
- }
-
- /**
- * Writes the next tar entry header on the stream
- *
- * @param entry
- * @throws IOException
- */
- public void putNextEntry(TarEntry entry) throws IOException {
- closeCurrentEntry();
-
- byte[] header = new byte[TarConstants.HEADER_BLOCK];
- entry.writeEntryHeader( header );
-
- write( header );
-
- currentEntry = entry;
- }
-
- /**
- * Closes the current tar entry
- *
- * @throws IOException
- */
- protected void closeCurrentEntry() throws IOException {
- if (currentEntry != null) {
- if (currentEntry.getSize() > currentFileSize) {
- throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
- + currentEntry.getSize() + "] has not been fully written." );
- }
-
- currentEntry = null;
- currentFileSize = 0;
-
- pad();
- }
- }
-
- /**
- * Pads the last content block
- *
- * @throws IOException
- */
- protected void pad() throws IOException {
- if (bytesWritten > 0) {
- int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
-
- if (extra > 0) {
- write( new byte[TarConstants.DATA_BLOCK - extra] );
- }
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarUtils.java b/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarUtils.java
deleted file mode 100755
index 50165765c..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kamranzafar/jtar/TarUtils.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-
-/**
- * @author Kamran
- *
- */
-public class TarUtils {
- /**
- * Determines the tar file size of the given folder/file path
- *
- * @param path
- * @return
- */
- public static long calculateTarSize(File path) {
- return tarSize(path) + TarConstants.EOF_BLOCK;
- }
-
- private static long tarSize(File dir) {
- long size = 0;
-
- if (dir.isFile()) {
- return entrySize(dir.length());
- } else {
- File[] subFiles = dir.listFiles();
-
- if (subFiles != null && subFiles.length > 0) {
- for (File file : subFiles) {
- if (file.isFile()) {
- size += entrySize(file.length());
- } else {
- size += tarSize(file);
- }
- }
- } else {
- // Empty folder header
- return TarConstants.HEADER_BLOCK;
- }
- }
-
- return size;
- }
-
- private static long entrySize(long fileSize) {
- long size = 0;
- size += TarConstants.HEADER_BLOCK; // Header
- size += fileSize; // File size
-
- long extra = size % TarConstants.DATA_BLOCK;
-
- if (extra > 0) {
- size += (TarConstants.DATA_BLOCK - extra); // pad
- }
-
- return size;
- }
-
- public static String trim(String s, char c) {
- StringBuffer tmp = new StringBuffer(s);
- for (int i = 0; i < tmp.length(); i++) {
- if (tmp.charAt(i) != c) {
- break;
- } else {
- tmp.deleteCharAt(i);
- }
- }
-
- for (int i = tmp.length() - 1; i >= 0; i--) {
- if (tmp.charAt(i) != c) {
- break;
- } else {
- tmp.deleteCharAt(i);
- }
- }
-
- return tmp.toString();
- }
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/AssetExtract.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/AssetExtract.java
deleted file mode 100644
index 40d501c7d..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/AssetExtract.java
+++ /dev/null
@@ -1,186 +0,0 @@
-package org.kivy.android;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.res.AssetManager;
-import android.util.Log;
-
-import org.kamranzafar.jtar.TarEntry;
-import org.kamranzafar.jtar.TarInputStream;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.zip.GZIPInputStream;
-
-public class AssetExtract {
- private static String TAG = AssetExtract.class.getSimpleName();
-
- /**
- * @param parent File or directory to delete recursively
- */
- public static void recursiveDelete(File parent) {
- if (parent.isDirectory()) {
- for (File child : parent.listFiles()) {
- recursiveDelete(child);
- }
- }
- parent.delete();
- }
-
- public static void extractAsset(Context ctx, String assetName, File target) {
- Log.v(TAG, "Extract asset " + assetName + " to " + target.getAbsolutePath());
-
- // The version of data in memory and on disk.
- String packaged_version;
- String disk_version;
-
- try {
- PackageManager manager = ctx.getPackageManager();
- PackageInfo info = manager.getPackageInfo(ctx.getPackageName(), 0);
- packaged_version = info.versionName;
-
- Log.v(TAG, "Data version is " + packaged_version);
- } catch (PackageManager.NameNotFoundException e) {
- packaged_version = null;
- }
- // If no packaged data version, no unpacking is necessary.
- if (packaged_version == null) {
- Log.w(TAG, "Data version not found");
- return;
- }
-
- // Check the current disk version, if any.
- String filesDir = target.getAbsolutePath();
- String disk_version_fn = filesDir + "/" + assetName + ".version";
-
- try {
- byte buf[] = new byte[64];
- FileInputStream is = new FileInputStream(disk_version_fn);
- int len = is.read(buf);
- disk_version = new String(buf, 0, len);
- is.close();
- } catch (Exception e) {
- disk_version = "";
- }
-
- if (packaged_version.equals(disk_version)) {
- Log.v(TAG, "Disk data version equals packaged data version.");
- return;
- }
-
- recursiveDelete(target);
- target.mkdirs();
-
- if (!extractTar(ctx.getAssets(), assetName, target.getAbsolutePath())) {
- Log.e(TAG, "Could not extract " + assetName + " data.");
- }
-
- try {
- // Write .nomedia.
- new File(target, ".nomedia").createNewFile();
-
- // Write version file.
- FileOutputStream os = new FileOutputStream(disk_version_fn);
- os.write(packaged_version.getBytes());
- os.close();
- } catch (Exception ex) {
- Log.w(TAG, ex);
- }
- }
-
- public static boolean extractTar(AssetManager assets, String assetName, String target) {
- byte buf[] = new byte[1024 * 1024];
-
- InputStream assetStream = null;
- TarInputStream tis = null;
-
- try {
- assetStream = assets.open(assetName, AssetManager.ACCESS_STREAMING);
- tis = new TarInputStream(new BufferedInputStream(
- new GZIPInputStream(new BufferedInputStream(assetStream,
- 8192)), 8192));
- } catch (IOException e) {
- Log.e(TAG, "opening up extract tar", e);
- return false;
- }
-
- while (true) {
- TarEntry entry = null;
-
- try {
- entry = tis.getNextEntry();
- } catch (java.io.IOException e) {
- Log.e(TAG, "extracting tar", e);
- return false;
- }
-
- if (entry == null) {
- break;
- }
-
- Log.v(TAG, "extracting " + entry.getName());
-
- if (entry.isDirectory()) {
-
- try {
- new File(target + "/" + entry.getName()).mkdirs();
- } catch (SecurityException e) {
- Log.e(TAG, "extracting tar", e);
- }
-
- continue;
- }
-
- OutputStream out = null;
- String path = target + "/" + entry.getName();
-
- try {
- out = new BufferedOutputStream(new FileOutputStream(path), 8192);
- } catch (FileNotFoundException e) {
- Log.e(TAG, "extracting tar", e);
- } catch (SecurityException e) {
- Log.e(TAG, "extracting tar", e);
- }
-
- if (out == null) {
- Log.e(TAG, "could not open " + path);
- return false;
- }
-
- try {
- while (true) {
- int len = tis.read(buf);
-
- if (len == -1) {
- break;
- }
-
- out.write(buf, 0, len);
- }
-
- out.flush();
- out.close();
- } catch (java.io.IOException e) {
- Log.e(TAG, "extracting zip", e);
- return false;
- }
- }
-
- try {
- tis.close();
- assetStream.close();
- } catch (IOException e) {
- // pass
- }
-
- return true;
- }
-}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java
deleted file mode 100644
index 232c8553d..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonService.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package org.kivy.android;
-
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.os.Bundle;
-import android.os.Process;
-import android.support.v4.app.NotificationCompat;
-import android.util.Log;
-
-public abstract class PythonService extends Service implements Runnable {
- private static String TAG = PythonService.class.getSimpleName();
-
- /**
- * Intent that started the service
- */
- private Intent startIntent = null;
-
- private Thread pythonThread = null;
-
- // Python environment variables
- private String androidPrivate;
- private String androidArgument;
- private String pythonName;
- private String pythonHome;
- private String pythonPath;
- private String androidUnpack;
- private String serviceEntrypoint;
- private String pythonServiceArgument;
-
- public int getStartType() {
- return START_NOT_STICKY;
- }
-
- public boolean getStartForeground() {
- return false;
- }
-
- public boolean getAutoRestart() {
- return false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onCreate() {
- Log.v(TAG, "Device: " + android.os.Build.DEVICE);
- Log.v(TAG, "Model: " + android.os.Build.MODEL);
- AssetExtract.extractAsset(getApplicationContext(), "private.mp3", getFilesDir());
- super.onCreate();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (pythonThread != null) {
- Log.v(TAG, "Service exists, do not start again");
- return START_NOT_STICKY;
- }
- startIntent = intent;
-
- Bundle extras = intent.getExtras();
- androidPrivate = extras.getString("androidPrivate");
- androidArgument = extras.getString("androidArgument");
- serviceEntrypoint = extras.getString("serviceEntrypoint");
- pythonName = extras.getString("pythonName");
- pythonHome = extras.getString("pythonHome");
- pythonPath = extras.getString("pythonPath");
- androidUnpack = extras.getString("androidUnpack");
- pythonServiceArgument = extras.getString("pythonServiceArgument");
-
- Log.v(TAG, "Starting Python thread");
- pythonThread = new Thread(this);
- pythonThread.start();
-
- if (getStartForeground()) {
- doStartForeground(extras);
- }
-
- return getStartType();
- }
-
- protected void doStartForeground(Bundle extras) {
- Context appContext = getApplicationContext();
- ApplicationInfo appInfo = appContext.getApplicationInfo();
-
- String serviceTitle = extras.getString("serviceTitle", TAG);
- String serviceDescription = extras.getString("serviceDescription", "");
- int serviceIconId = extras.getInt("serviceIconId", appInfo.icon);
-
- NotificationCompat.Builder builder =
- new NotificationCompat.Builder(this)
- .setSmallIcon(serviceIconId)
- .setContentTitle(serviceTitle)
- .setContentText(serviceDescription);
-
- int NOTIFICATION_ID = 1;
-
- Intent targetIntent = new Intent(this, MainActivity.class);
- PendingIntent contentIntent = PendingIntent.getActivity(this, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- builder.setContentIntent(contentIntent);
-
- startForeground(NOTIFICATION_ID, builder.build());
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onDestroy() {
- super.onDestroy();
- pythonThread = null;
- if (getAutoRestart() && startIntent != null) {
- Log.v(TAG, "Service restart requested");
- startService(startIntent);
- }
- Process.killProcess(Process.myPid());
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void run() {
- PythonUtil.loadLibraries(getFilesDir());
- nativeStart(androidPrivate, androidArgument, serviceEntrypoint, pythonName, pythonHome,
- pythonPath, pythonServiceArgument, androidUnpack);
- stopSelf();
- }
-
- /**
- * @param androidPrivate Directory for private files
- * @param androidArgument Android path
- * @param serviceEntrypoint Python file to execute first
- * @param pythonName Python name
- * @param pythonHome Python home
- * @param pythonPath Python path
- * @param pythonServiceArgument Argument to pass to Python code
- */
- public static native void nativeStart(String androidPrivate, String androidArgument,
- String serviceEntrypoint, String pythonName,
- String pythonHome, String pythonPath,
- String pythonServiceArgument, String androidUnpack);
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java
deleted file mode 100644
index 692ee13b2..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/PythonUtil.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.kivy.android;
-
-import android.util.Log;
-
-import java.io.File;
-
-public class PythonUtil {
- private static String TAG = PythonUtil.class.getSimpleName();
-
- protected static String[] getLibraries() {
- return new String[]{
- "python2.7",
- "main",
- "/lib/python2.7/lib-dynload/_io.so",
- "/lib/python2.7/lib-dynload/unicodedata.so",
- "/lib/python2.7/lib-dynload/_ctypes.so",
- };
- }
-
- public static void loadLibraries(File filesDir) {
- String filesDirPath = filesDir.getAbsolutePath();
- Log.v(TAG, "Loading libraries from " + filesDirPath);
-
- for (String lib : getLibraries()) {
- if (lib.startsWith("/")) {
- System.load(filesDirPath + lib);
- } else {
- System.loadLibrary(lib);
- }
- }
-
- Log.v(TAG, "Loaded everything!");
- }
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonEvent.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonEvent.java
deleted file mode 100644
index 9911356ba..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonEvent.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.kivy.android.concurrency;
-
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Created by ryan on 3/28/14.
- */
-public class PythonEvent {
- private final Lock lock = new ReentrantLock();
- private final Condition cond = lock.newCondition();
- private boolean flag = false;
-
- public void set() {
- lock.lock();
- try {
- flag = true;
- cond.signalAll();
- } finally {
- lock.unlock();
- }
- }
-
- public void wait_() throws InterruptedException {
- lock.lock();
- try {
- while (!flag) {
- cond.await();
- }
- } finally {
- lock.unlock();
- }
- }
-
- public void clear() {
- lock.lock();
- try {
- flag = false;
- cond.signalAll();
- } finally {
- lock.unlock();
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonLock.java b/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonLock.java
deleted file mode 100644
index 22f9d903e..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/kivy/android/concurrency/PythonLock.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.kivy.android.concurrency;
-
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Created by ryan on 3/28/14.
- */
-public class PythonLock {
- private final Lock lock = new ReentrantLock();
-
- public void acquire() {
- lock.lock();
- }
-
- public void release() {
- lock.unlock();
- }
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java b/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java
deleted file mode 100644
index c56d7b1f7..000000000
--- a/pythonforandroid/bootstraps/service_only/build/src/org/renpy/android/Hardware.java
+++ /dev/null
@@ -1,257 +0,0 @@
-package org.renpy.android;
-
-import java.util.List;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.os.Vibrator;
-import android.view.View;
-
-/**
- * Methods that are expected to be called via JNI, to access the device's
- * non-screen hardware. (For example, the vibration and accelerometer.)
- */
-public class Hardware {
-
- // The context.
- static Context context;
- static View view;
-
- /**
- * Vibrate for s seconds.
- */
- public static void vibrate(double s) {
- Vibrator v = (Vibrator) context
- .getSystemService(Context.VIBRATOR_SERVICE);
- if (v != null) {
- v.vibrate((int) (1000 * s));
- }
- }
-
- /**
- * Get an Overview of all Hardware Sensors of an Android Device
- */
- public static String getHardwareSensors() {
- SensorManager sm = (SensorManager) context
- .getSystemService(Context.SENSOR_SERVICE);
- List<Sensor> allSensors = sm.getSensorList(Sensor.TYPE_ALL);
-
- if (allSensors != null) {
- String resultString = "";
- for (Sensor s : allSensors) {
- resultString += String.format("Name=" + s.getName());
- resultString += String.format(",Vendor=" + s.getVendor());
- resultString += String.format(",Version=" + s.getVersion());
- resultString += String.format(",MaximumRange="
- + s.getMaximumRange());
- // XXX MinDelay is not in the 2.2
- // resultString += String.format(",MinDelay=" +
- // s.getMinDelay());
- resultString += String.format(",Power=" + s.getPower());
- resultString += String.format(",Type=" + s.getType() + "\n");
- }
- return resultString;
- }
- return "";
- }
-
- /**
- * Get Access to 3 Axis Hardware Sensors Accelerometer, Orientation and
- * Magnetic Field Sensors
- */
- public static class generic3AxisSensor implements SensorEventListener {
- private final SensorManager sSensorManager;
- private final Sensor sSensor;
- private final int sSensorType;
- SensorEvent sSensorEvent;
-
- public generic3AxisSensor(int sensorType) {
- sSensorType = sensorType;
- sSensorManager = (SensorManager) context
- .getSystemService(Context.SENSOR_SERVICE);
- sSensor = sSensorManager.getDefaultSensor(sSensorType);
- }
-
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
-
- public void onSensorChanged(SensorEvent event) {
- sSensorEvent = event;
- }
-
- /**
- * Enable or disable the Sensor by registering/unregistering
- */
- public void changeStatus(boolean enable) {
- if (enable) {
- sSensorManager.registerListener(this, sSensor,
- SensorManager.SENSOR_DELAY_NORMAL);
- } else {
- sSensorManager.unregisterListener(this, sSensor);
- }
- }
-
- /**
- * Read the Sensor
- */
- public float[] readSensor() {
- if (sSensorEvent != null) {
- return sSensorEvent.values;
- } else {
- float rv[] = { 0f, 0f, 0f };
- return rv;
- }
- }
- }
-
- public static generic3AxisSensor accelerometerSensor = null;
- public static generic3AxisSensor orientationSensor = null;
- public static generic3AxisSensor magneticFieldSensor = null;
-
- /**
- * functions for backward compatibility reasons
- */
-
- public static void accelerometerEnable(boolean enable) {
- if (accelerometerSensor == null)
- accelerometerSensor = new generic3AxisSensor(
- Sensor.TYPE_ACCELEROMETER);
- accelerometerSensor.changeStatus(enable);
- }
-
- public static float[] accelerometerReading() {
- float rv[] = { 0f, 0f, 0f };
- if (accelerometerSensor == null)
- return rv;
- return (float[]) accelerometerSensor.readSensor();
- }
-
- public static void orientationSensorEnable(boolean enable) {
- if (orientationSensor == null)
- orientationSensor = new generic3AxisSensor(Sensor.TYPE_ORIENTATION);
- orientationSensor.changeStatus(enable);
- }
-
- public static float[] orientationSensorReading() {
- float rv[] = { 0f, 0f, 0f };
- if (orientationSensor == null)
- return rv;
- return (float[]) orientationSensor.readSensor();
- }
-
- public static void magneticFieldSensorEnable(boolean enable) {
- if (magneticFieldSensor == null)
- magneticFieldSensor = new generic3AxisSensor(
- Sensor.TYPE_MAGNETIC_FIELD);
- magneticFieldSensor.changeStatus(enable);
- }
-
- public static float[] magneticFieldSensorReading() {
- float rv[] = { 0f, 0f, 0f };
- if (magneticFieldSensor == null)
- return rv;
- return (float[]) magneticFieldSensor.readSensor();
- }
-
- /**
- * Scan WiFi networks
- */
- static List<ScanResult> latestResult;
-
- public static void enableWifiScanner() {
- IntentFilter i = new IntentFilter();
- i.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-
- context.registerReceiver(new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context c, Intent i) {
- // Code to execute when SCAN_RESULTS_AVAILABLE_ACTION event
- // occurs
- WifiManager w = (WifiManager) c
- .getSystemService(Context.WIFI_SERVICE);
- latestResult = w.getScanResults(); // Returns a <list> of
- // scanResults
- }
-
- }, i);
-
- }
-
- public static String scanWifi() {
-
- // Now you can call this and it should execute the broadcastReceiver's
- // onReceive()
- WifiManager wm = (WifiManager) context
- .getSystemService(Context.WIFI_SERVICE);
- boolean a = wm.startScan();
-
- if (latestResult != null) {
-
- String latestResultString = "";
- for (ScanResult result : latestResult) {
- latestResultString += String.format("%s\t%s\t%d\n",
- result.SSID, result.BSSID, result.level);
- }
-
- return latestResultString;
- }
-
- return "";
- }
-
- /**
- * network state
- */
-
- public static boolean network_state = false;
-
- /**
- * Check network state directly
- *
- * (only one connection can be active at a given moment, detects all network
- * type)
- *
- */
- public static boolean checkNetwork() {
- boolean state = false;
- final ConnectivityManager conMgr = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
-
- final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();
- if (activeNetwork != null && activeNetwork.isConnected()) {
- state = true;
- } else {
- state = false;
- }
-
- return state;
- }
-
- /**
- * To recieve network state changes
- */
- public static void registerNetworkCheck() {
- IntentFilter i = new IntentFilter();
- i.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- context.registerReceiver(new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context c, Intent i) {
- network_state = checkNetwork();
- }
-
- }, i);
- }
-
-}
diff --git a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml
index 0ac9581be..f0034d7e7 100644
--- a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml
+++ b/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml
@@ -1,8 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="{{ args.numeric_version }}"
+ android:versionName="{{ args.version }}"
+ android:installLocation="auto">
+
+ <supports-screens
+ android:smallScreens="true"
+ android:normalScreens="true"
+ android:largeScreens="true"
+ android:anyDensity="true"
+ {% if args.min_sdk_version >= 9 %}
+ android:xlargeScreens="true"
+ {% endif %}
+ />
+
+ <!-- Android 2.3.3 -->
+ <uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ android_api }}" />
+
+ <!-- Set permissions -->
+ {% for perm in args.permissions %}
+ <uses-permission android:name="{{ perm.name }}"{% if perm.maxSdkVersion %} android:maxSdkVersion="{{ perm.maxSdkVersion }}"{% endif %}{% if perm.usesPermissionFlags %} android:usesPermissionFlags="{{ perm.usesPermissionFlags }}"{% endif %} />
+ {% endfor %}
+
+ {% if args.wakelock %}
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ {% endif %}
+
+ {% if args.billing_pubkey %}
+ <uses-permission android:name="com.android.vending.BILLING" />
+ {% endif %}
+
+ <!-- Create a Java class extending SDLActivity and place it in a
+ directory under src matching the package, e.g.
+ src/com/gamemaker/game/MyGame.java
+
+ then replace "SDLActivity" with the name of your class (e.g. "MyGame")
+ in the XML below.
+
+ An example Java class can be found in README-android.txt
+ -->
+ <application android:label="@string/app_name"
+ {% if debug %}android:debuggable="true"{% endif %}
+ android:icon="@mipmap/icon"
+ android:allowBackup="{{ args.allow_backup }}"
+ {% if args.backup_rules %}android:fullBackupContent="@xml/{{ args.backup_rules }}"{% endif %}
+ android:theme="{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}"
+ android:hardwareAccelerated="true"
+ android:extractNativeLibs="true" >
+ {% for l in args.android_used_libs %}
+ <uses-library android:name="{{ l }}" />
+ {% endfor %}
+ {% for m in args.meta_data %}
+ <meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
+ <meta-data android:name="wakelock" android:value="{% if args.wakelock %}1{% else %}0{% endif %}"/>
+
+ <activity android:name="org.kivy.android.PythonActivity"
+ android:label="@string/app_name"
+ android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ {%- if args.intent_filters -%}
+ {{- args.intent_filters -}}
+ {%- endif -%}
+ </activity>
+
{% if service %}
<service android:name="org.kivy.android.PythonService"
- android:process=":pythonservice" />
+ android:process=":pythonservice"
+ android:exported="true"/>
{% endif %}
{% for name in service_names %}
<service android:name="{{ args.package }}.Service{{ name|capitalize }}"
- android:process=":service_{{ name }}" />
- {% endfor %}
\ No newline at end of file
+ android:process=":service_{{ name }}"
+ android:exported="true" />
+ {% endfor %}
+
+ {% if args.billing_pubkey %}
+ <service android:name="org.kivy.android.billing.BillingReceiver"
+ android:process=":pythonbilling" />
+ <receiver android:name="org.kivy.android.billing.BillingReceiver"
+ android:process=":pythonbillingreceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
+ <action android:name="com.android.vending.billing.RESPONSE_CODE" />
+ <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
+ </intent-filter>
+ </receiver>
+ {% endif %}
+ </application>
+
+</manifest>
diff --git a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java
index 137181a7f..eeda810be 100644
--- a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java
+++ b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java
@@ -1,5 +1,7 @@
package {{ args.package }};
+import android.os.Binder;
+import android.os.IBinder;
import android.content.Intent;
import android.content.Context;
import org.kivy.android.PythonService;
@@ -15,37 +17,57 @@ public class Service{{ name|capitalize }} extends PythonService {
* {@inheritDoc}
*/
@Override
- public int getStartType() {
+ public int startType() {
return START_STICKY;
}
{% endif %}
- {% if foreground %}
- /**
- * {@inheritDoc}
- */
@Override
- public boolean getStartForeground() {
- return true;
+ protected int getServiceId() {
+ return {{ service_id }};
}
- {% endif %}
public static void start(Context ctx, String pythonServiceArgument) {
- String argument = ctx.getFilesDir().getAbsolutePath();
+ String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
intent.putExtra("androidPrivate", argument);
intent.putExtra("androidArgument", argument);
intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
intent.putExtra("serviceTitle", "{{ name|capitalize }}");
- intent.putExtra("serviceDescription", "");
intent.putExtra("pythonName", "{{ name }}");
+ intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}");
intent.putExtra("pythonHome", argument);
intent.putExtra("androidUnpack", argument);
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ intent.putExtra("smallIconName", "");
+ intent.putExtra("contentTitle", "{{ name|capitalize }}");
+ intent.putExtra("contentText", "");
ctx.startService(intent);
}
-
+
+ public static void start(Context ctx, String smallIconName,
+ String contentTitle,
+ String contentText,
+ String pythonServiceArgument) {
+ String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ intent.putExtra("androidPrivate", argument);
+ intent.putExtra("androidArgument", argument);
+ intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
+ intent.putExtra("serviceTitle", "{{ name|capitalize }}");
+ intent.putExtra("pythonName", "{{ name }}");
+ intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}");
+ intent.putExtra("pythonHome", argument);
+ intent.putExtra("androidUnpack", argument);
+ intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
+ intent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ intent.putExtra("smallIconName", smallIconName);
+ intent.putExtra("contentTitle", contentTitle);
+ intent.putExtra("contentText", contentText);
+ ctx.startService(intent);
+ }
+
public static void stop(Context ctx) {
Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
ctx.stopService(intent);
@@ -58,7 +80,7 @@ public static void stop(Context ctx) {
public IBinder onBind(Intent intent) {
return mBinder;
}
-
+
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
diff --git a/pythonforandroid/bootstraps/service_only/build/templates/app.build.tmpl.gradle b/pythonforandroid/bootstraps/service_only/build/templates/app.build.tmpl.gradle
deleted file mode 100644
index 514ba0f81..000000000
--- a/pythonforandroid/bootstraps/service_only/build/templates/app.build.tmpl.gradle
+++ /dev/null
@@ -1,59 +0,0 @@
-apply plugin: 'com.android.model.application'
-
-model {
- android {
- compileSdkVersion {{ args.sdk_version }}
- buildToolsVersion "23.0.3"
-
- defaultConfig {
- applicationId "{{ args.package }}"
- minSdkVersion.apiLevel {{ args.min_sdk_version }}
- targetSdkVersion.apiLevel {{ args.sdk_version }}
- versionCode {{ args.numeric_version }}
- versionName "{{ args.version }}"
-
- buildConfigFields {
- create() {
- type "int"
- name "VALUE"
- value "1"
- }
- }
- }
- ndk {
- abiFilters.add("armeabi-v7a")
- moduleName = "main"
- toolchain = "gcc"
- toolchainVersion = "4.9"
- platformVersion = 16
- stl = "gnustl_shared"
- renderscriptNdkMode = false
- CFlags.add("-I" + file("src/main/jni/include/python2.7"))
- ldFlags.add("-L" + file("src/main/jni/lib"))
- ldLibs.addAll(["log", "python2.7"])
- }
- // Configures source set directory.
- sources {
- main {
- jniLibs {
- dependencies {
- library "gnustl_shared"
- // add pre-built libraries here and locate them below:
- }
- }
- }
- }
- }
- repositories {
- libs(PrebuiltLibraries) {
- gnustl_shared {
- binaries.withType(SharedLibraryBinary) {
- sharedLibraryFile = file("src/main/jniLibs/${targetPlatform.getName()}/libgnustl_shared.so")
- }
- }
- // more here
- }
- }
-}
-
-// normal project dependencies here
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml
new file mode 100644
index 000000000..22866570b
--- /dev/null
+++ b/pythonforandroid/bootstraps/service_only/build/templates/strings.tmpl.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">{{ args.name }}</string>
+ <string name="private_version">{{ private_version }}</string>
+</resources>
diff --git a/pythonforandroid/bootstraps/service_only/build/whitelist.txt b/pythonforandroid/bootstraps/service_only/build/whitelist.txt
deleted file mode 100644
index 41b06ee25..000000000
--- a/pythonforandroid/bootstraps/service_only/build/whitelist.txt
+++ /dev/null
@@ -1 +0,0 @@
-# put files here that you need to un-blacklist
diff --git a/pythonforandroid/bootstraps/webview/__init__.py b/pythonforandroid/bootstraps/webview/__init__.py
index 335fa9402..7604ed3b8 100644
--- a/pythonforandroid/bootstraps/webview/__init__.py
+++ b/pythonforandroid/bootstraps/webview/__init__.py
@@ -1,118 +1,51 @@
-from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, info_main
-from os.path import join, exists, curdir, abspath
-from os import walk
-import glob
+from os.path import join
+
import sh
+from pythonforandroid.toolchain import Bootstrap, current_directory, info, info_main, shprint
+from pythonforandroid.util import ensure_dir, rmdir
+
+
class WebViewBootstrap(Bootstrap):
name = 'webview'
- recipe_depends = ['genericndkbuild', ('python2', 'python3crystax')]
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'genericndkbuild'})
+ )
- def run_distribute(self):
+ def assemble_distribution(self):
info_main('# Creating Android project from build and {} bootstrap'.format(
self.name))
- shprint(sh.rm, '-rf', self.dist_dir)
+ rmdir(self.dist_dir)
shprint(sh.cp, '-r', self.build_dir, self.dist_dir)
with current_directory(self.dist_dir):
with open('local.properties', 'w') as fileh:
fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))
- arch = self.ctx.archs[0]
- if len(self.ctx.archs) > 1:
- raise ValueError('built for more than one arch, but bootstrap cannot handle that yet')
- info('Bootstrap running with arch {}'.format(arch))
-
with current_directory(self.dist_dir):
info('Copying python distribution')
- if not exists('private') and not self.ctx.python_recipe.from_crystax:
- shprint(sh.mkdir, 'private')
- if not exists('crystax_python') and self.ctx.python_recipe.from_crystax:
- shprint(sh.mkdir, 'crystax_python')
- shprint(sh.mkdir, 'crystax_python/crystax_python')
- if not exists('assets'):
- shprint(sh.mkdir, 'assets')
-
- hostpython = sh.Command(self.ctx.hostpython)
- if not self.ctx.python_recipe.from_crystax:
- try:
- shprint(hostpython, '-OO', '-m', 'compileall',
- self.ctx.get_python_install_dir(),
- _tail=10, _filterout="^Listing")
- except sh.ErrorReturnCode:
- pass
- if not exists('python-install'):
- shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install')
-
- self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
- self.distribute_aars(arch)
- self.distribute_javaclasses(self.ctx.javaclass_dir)
-
- if not self.ctx.python_recipe.from_crystax:
- info('Filling private directory')
- if not exists(join('private', 'lib')):
- info('private/lib does not exist, making')
- shprint(sh.cp, '-a', join('python-install', 'lib'), 'private')
- shprint(sh.mkdir, '-p', join('private', 'include', 'python2.7'))
-
- if exists(join('libs', arch.arch, 'libpymodules.so')):
- shprint(sh.mv, join('libs', arch.arch, 'libpymodules.so'), 'private/')
- shprint(sh.cp, join('python-install', 'include' , 'python2.7', 'pyconfig.h'), join('private', 'include', 'python2.7/'))
-
- info('Removing some unwanted files')
- shprint(sh.rm, '-f', join('private', 'lib', 'libpython2.7.so'))
- shprint(sh.rm, '-rf', join('private', 'lib', 'pkgconfig'))
-
- libdir = join(self.dist_dir, 'private', 'lib', 'python2.7')
- site_packages_dir = join(libdir, 'site-packages')
- with current_directory(libdir):
- # shprint(sh.xargs, 'rm', sh.grep('-E', '*\.(py|pyx|so\.o|so\.a|so\.libs)$', sh.find('.')))
- removes = []
- for dirname, something, filens in walk('.'):
- for filename in filens:
- for suffix in ('py', 'pyc', 'so.o', 'so.a', 'so.libs'):
- if filename.endswith(suffix):
- removes.append(filename)
- shprint(sh.rm, '-f', *removes)
-
- info('Deleting some other stuff not used on android')
- # To quote the original distribute.sh, 'well...'
- # shprint(sh.rm, '-rf', 'ctypes')
- shprint(sh.rm, '-rf', 'lib2to3')
- shprint(sh.rm, '-rf', 'idlelib')
- for filename in glob.glob('config/libpython*.a'):
- shprint(sh.rm, '-f', filename)
- shprint(sh.rm, '-rf', 'config/python.o')
- # shprint(sh.rm, '-rf', 'lib-dynload/_ctypes_test.so')
- # shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so')
+ self.distribute_javaclasses(self.ctx.javaclass_dir,
+ dest_dir=join("src", "main", "java"))
- else: # Python *is* loaded from crystax
- ndk_dir = self.ctx.ndk_dir
- py_recipe = self.ctx.python_recipe
- python_dir = join(ndk_dir, 'sources', 'python', py_recipe.version,
- 'libs', arch.arch)
+ for arch in self.ctx.archs:
+ self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
+ self.distribute_aars(arch)
- shprint(sh.cp, '-r', join(python_dir, 'stdlib.zip'), 'crystax_python/crystax_python')
- shprint(sh.cp, '-r', join(python_dir, 'modules'), 'crystax_python/crystax_python')
- shprint(sh.cp, '-r', self.ctx.get_python_install_dir(), 'crystax_python/crystax_python/site-packages')
+ python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
+ ensure_dir(python_bundle_dir)
+ site_packages_dir = self.ctx.python_recipe.create_python_bundle(
+ join(self.dist_dir, python_bundle_dir), arch)
+ if not self.ctx.with_debug_symbols:
+ self.strip_libraries(arch)
+ self.fry_eggs(site_packages_dir)
- info('Renaming .so files to reflect cross-compile')
- site_packages_dir = 'crystax_python/crystax_python/site-packages'
- filens = shprint(sh.find, site_packages_dir, '-iname', '*.so').stdout.decode(
- 'utf-8').split('\n')[:-1]
- for filen in filens:
- parts = filen.split('.')
- if len(parts) <= 2:
- continue
- shprint(sh.mv, filen, filen.split('.')[0] + '.so')
- site_packages_dir = join(abspath(curdir),
- site_packages_dir)
+ if 'sqlite3' not in self.ctx.recipe_build_order:
+ with open('blacklist.txt', 'a') as fileh:
+ fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n')
+ super().assemble_distribution()
- self.strip_libraries(arch)
- self.fry_eggs(site_packages_dir)
- super(WebViewBootstrap, self).run_distribute()
bootstrap = WebViewBootstrap()
diff --git a/pythonforandroid/bootstraps/webview/build/ant.properties b/pythonforandroid/bootstraps/webview/build/ant.properties
deleted file mode 100644
index f74e644b8..000000000
--- a/pythonforandroid/bootstraps/webview/build/ant.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-# This file is used to override default values used by the Ant build system.
-#
-# This file must be checked into Version Control Systems, as it is
-# integral to the build system of your project.
-
-# This file is only used by the Ant script.
-
-# You can use this to override default values such as
-# 'source.dir' for the location of your java source folder and
-# 'out.dir' for the location of your output folder.
-
-# You can also use it define how the release builds are signed by declaring
-# the following properties:
-# 'key.store' for the location of your keystore and
-# 'key.alias' for the name of the key to use.
-# The password will be asked during the build when you use the 'release' target.
-
-source.absolute.dir = tmp-src
diff --git a/pythonforandroid/bootstraps/webview/build/blacklist.txt b/pythonforandroid/bootstraps/webview/build/blacklist.txt
index d220d2a2a..53cc634b7 100644
--- a/pythonforandroid/bootstraps/webview/build/blacklist.txt
+++ b/pythonforandroid/bootstraps/webview/build/blacklist.txt
@@ -1,5 +1,7 @@
# prevent user to include invalid extensions
*.apk
+*.aab
+*.apks
*.pxd
# eggs
@@ -69,7 +71,6 @@ multiprocessing/dummy*
lib-dynload/termios.so
lib-dynload/_lsprof.so
lib-dynload/*audioop.so
-lib-dynload/mmap.so
lib-dynload/_hotshot.so
lib-dynload/_heapq.so
lib-dynload/_json.so
diff --git a/pythonforandroid/bootstraps/webview/build/build.properties b/pythonforandroid/bootstraps/webview/build/build.properties
deleted file mode 100644
index f12e25869..000000000
--- a/pythonforandroid/bootstraps/webview/build/build.properties
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file is used to override default values used by the Ant build system.
-#
-# This file must be checked in Version Control Systems, as it is
-# integral to the build system of your project.
-
-# This file is only used by the Ant script.
-
-# You can use this to override default values such as
-# 'source.dir' for the location of your java source folder and
-# 'out.dir' for the location of your output folder.
-
-# You can also use it define how the release builds are signed by declaring
-# the following properties:
-# 'key.store' for the location of your keystore and
-# 'key.alias' for the name of the key to use.
-# The password will be asked during the build when you use the 'release' target.
-
-key.store=${env.P4A_RELEASE_KEYSTORE}
-key.alias=${env.P4A_RELEASE_KEYALIAS}
-key.store.password=${env.P4A_RELEASE_KEYSTORE_PASSWD}
-key.alias.password=${env.P4A_RELEASE_KEYALIAS_PASSWD}
diff --git a/pythonforandroid/bootstraps/webview/build/build.py b/pythonforandroid/bootstraps/webview/build/build.py
deleted file mode 100755
index fa6f92b01..000000000
--- a/pythonforandroid/bootstraps/webview/build/build.py
+++ /dev/null
@@ -1,499 +0,0 @@
-#!/usr/bin/env python2.7
-
-from __future__ import print_function
-
-from os.path import dirname, join, isfile, realpath, relpath, split, exists
-from os import makedirs
-import os
-import tarfile
-import time
-import subprocess
-import shutil
-from zipfile import ZipFile
-import sys
-import re
-
-from fnmatch import fnmatch
-
-import jinja2
-
-if os.name == 'nt':
- ANDROID = 'android.bat'
- ANT = 'ant.bat'
-else:
- ANDROID = 'android'
- ANT = 'ant'
-
-curdir = dirname(__file__)
-
-# Try to find a host version of Python that matches our ARM version.
-PYTHON = join(curdir, 'python-install', 'bin', 'python.host')
-
-BLACKLIST_PATTERNS = [
- # code versionning
- '^*.hg/*',
- '^*.git/*',
- '^*.bzr/*',
- '^*.svn/*',
-
- # pyc/py
- '*.pyc',
- # '*.py',
-
- # temp files
- '~',
- '*.bak',
- '*.swp',
-]
-
-WHITELIST_PATTERNS = []
-
-python_files = []
-
-
-environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
- join(curdir, 'templates')))
-
-def render(template, dest, **kwargs):
- '''Using jinja2, render `template` to the filename `dest`, supplying the
-
- keyword arguments as template parameters.
- '''
-
- dest_dir = dirname(dest)
- if dest_dir and not exists(dest_dir):
- makedirs(dest_dir)
-
- template = environment.get_template(template)
- text = template.render(**kwargs)
-
- f = open(dest, 'wb')
- f.write(text.encode('utf-8'))
- f.close()
-
-
-def is_whitelist(name):
- return match_filename(WHITELIST_PATTERNS, name)
-
-
-def is_blacklist(name):
- if is_whitelist(name):
- return False
- return match_filename(BLACKLIST_PATTERNS, name)
-
-
-def match_filename(pattern_list, name):
- for pattern in pattern_list:
- if pattern.startswith('^'):
- pattern = pattern[1:]
- else:
- pattern = '*/' + pattern
- if fnmatch(name, pattern):
- return True
-
-
-def listfiles(d):
- basedir = d
- subdirlist = []
- for item in os.listdir(d):
- fn = join(d, item)
- if isfile(fn):
- yield fn
- else:
- subdirlist.append(join(basedir, item))
- for subdir in subdirlist:
- for fn in listfiles(subdir):
- yield fn
-
-def make_python_zip():
- '''
- Search for all the python related files, and construct the pythonXX.zip
- According to
- # http://randomsplat.com/id5-cross-compiling-python-for-embedded-linux.html
- site-packages, config and lib-dynload will be not included.
- '''
-
- if not exists('private'):
- print('No compiled python is present to zip, skipping.')
- print('this should only be the case if you are using the CrystaX python')
- return
-
- global python_files
- d = realpath(join('private', 'lib', 'python2.7'))
-
-
- def select(fn):
- if is_blacklist(fn):
- return False
- fn = realpath(fn)
- assert(fn.startswith(d))
- fn = fn[len(d):]
- if (fn.startswith('/site-packages/') or
- fn.startswith('/config/') or
- fn.startswith('/lib-dynload/') or
- fn.startswith('/libpymodules.so')):
- return False
- return fn
-
- # get a list of all python file
- python_files = [x for x in listfiles(d) if select(x)]
-
- # create the final zipfile
- zfn = join('private', 'lib', 'python27.zip')
- zf = ZipFile(zfn, 'w')
-
- # put all the python files in it
- for fn in python_files:
- afn = fn[len(d):]
- zf.write(fn, afn)
- zf.close()
-
-def make_tar(tfn, source_dirs, ignore_path=[]):
- '''
- Make a zip file `fn` from the contents of source_dis.
- '''
-
- # selector function
- def select(fn):
- rfn = realpath(fn)
- for p in ignore_path:
- if p.endswith('/'):
- p = p[:-1]
- if rfn.startswith(p):
- return False
- if rfn in python_files:
- return False
- return not is_blacklist(fn)
-
- # get the files and relpath file of all the directory we asked for
- files = []
- for sd in source_dirs:
- sd = realpath(sd)
- compile_dir(sd)
- files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd)
- if select(x)]
-
- # create tar.gz of thoses files
- tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT)
- dirs = []
- for fn, afn in files:
-# print('%s: %s' % (tfn, fn))
- dn = dirname(afn)
- if dn not in dirs:
- # create every dirs first if not exist yet
- d = ''
- for component in split(dn):
- d = join(d, component)
- if d.startswith('/'):
- d = d[1:]
- if d == '' or d in dirs:
- continue
- dirs.append(d)
- tinfo = tarfile.TarInfo(d)
- tinfo.type = tarfile.DIRTYPE
- tf.addfile(tinfo)
-
- # put the file
- tf.add(fn, afn)
- tf.close()
-
-
-def compile_dir(dfn):
- '''
- Compile *.py in directory `dfn` to *.pyo
- '''
-
- return # Currently leaving out the compile to pyo step because it's somehow broken
- # -OO = strip docstrings
- subprocess.call([PYTHON, '-OO', '-m', 'compileall', '-f', dfn])
-
-
-def make_package(args):
- # # Update the project to a recent version.
- # try:
- # subprocess.call([ANDROID, 'update', 'project', '-p', '.', '-t',
- # 'android-{}'.format(args.sdk_version)])
- # except (OSError, IOError):
- # print('An error occured while calling', ANDROID, 'update')
- # print('Your PATH must include android tools.')
- # sys.exit(-1)
-
- # Delete the old assets.
- if exists('assets/public.mp3'):
- os.unlink('assets/public.mp3')
-
- if exists('assets/private.mp3'):
- os.unlink('assets/private.mp3')
-
- # In order to speedup import and initial depack,
- # construct a python27.zip
- make_python_zip()
-
- # Package up the private data (public not supported).
- tar_dirs = [args.private]
- if exists('private'):
- tar_dirs.append('private')
- if exists('crystax_python'):
- tar_dirs.append('crystax_python')
- tar_dirs.append('webview_includes')
- if args.private:
- make_tar('assets/private.mp3', tar_dirs, args.ignore_path)
- # else:
- # make_tar('assets/private.mp3', ['private'])
-
- # if args.dir:
- # make_tar('assets/public.mp3', [args.dir], args.ignore_path)
-
-
- # # Build.
- # try:
- # for arg in args.command:
- # subprocess.check_call([ANT, arg])
- # except (OSError, IOError):
- # print 'An error occured while calling', ANT
- # print 'Did you install ant on your system ?'
- # sys.exit(-1)
-
-
- # Prepare some variables for templating process
-
- default_icon = 'templates/kivy-icon.png'
- shutil.copy(args.icon or default_icon, 'res/drawable/icon.png')
-
- default_presplash = 'templates/kivy-presplash.jpg'
- shutil.copy(args.presplash or default_presplash,
- 'res/drawable/presplash.jpg')
-
- # If extra Java jars were requested, copy them into the libs directory
- if args.add_jar:
- for jarname in args.add_jar:
- if not exists(jarname):
- print('Requested jar does not exist: {}'.format(jarname))
- sys.exit(-1)
- shutil.copy(jarname, 'libs')
-
- versioned_name = (args.name.replace(' ', '').replace('\'', '') +
- '-' + args.version)
-
- version_code = 0
- if not args.numeric_version:
- for i in args.version.split('.'):
- version_code *= 100
- version_code += int(i)
- args.numeric_version = str(version_code)
-
- if args.intent_filters:
- with open(args.intent_filters) as fd:
- args.intent_filters = fd.read()
-
- if args.extra_source_dirs:
- esd = []
- for spec in args.extra_source_dirs:
- if ':' in spec:
- specdir, specincludes = spec.split(':')
- else:
- specdir = spec
- specincludes = '**'
- esd.append((realpath(specdir), specincludes))
- args.extra_source_dirs = esd
- else:
- args.extra_source_dirs = []
-
- service = False
- service_main = join(realpath(args.private), 'service', 'main.py')
- if exists(service_main) or exists(service_main + 'o'):
- service = True
-
- service_names = []
- for sid, spec in enumerate(args.services):
- spec = spec.split(':')
- name = spec[0]
- entrypoint = spec[1]
- options = spec[2:]
-
- foreground = 'foreground' in options
- sticky = 'sticky' in options
-
- service_names.append(name)
- render(
- 'Service.tmpl.java',
- 'src/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
- name=name,
- entrypoint=entrypoint,
- args=args,
- foreground=foreground,
- sticky=sticky,
- service_id=sid + 1,
- )
-
- render(
- 'AndroidManifest.tmpl.xml',
- 'AndroidManifest.xml',
- args=args,
- service=service,
- service_names=service_names,
- )
-
- render(
- 'build.tmpl.xml',
- 'build.xml',
- args=args,
- versioned_name=versioned_name)
-
- render(
- 'strings.tmpl.xml',
- 'res/values/strings.xml',
- args=args)
-
- render(
- 'custom_rules.tmpl.xml',
- 'custom_rules.xml',
- args=args)
-
- render('WebViewLoader.tmpl.java',
- 'src/org/kivy/android/WebViewLoader.java',
- args=args)
-
- with open(join(dirname(__file__), 'res',
- 'values', 'strings.xml')) as fileh:
- lines = fileh.read()
-
- with open(join(dirname(__file__), 'res',
- 'values', 'strings.xml'), 'w') as fileh:
- fileh.write(re.sub(r'"private_version">[0-9\.]*<',
- '"private_version">{}<'.format(
- str(time.time())), lines))
-
-
-def parse_args(args=None):
- global BLACKLIST_PATTERNS, WHITELIST_PATTERNS
- default_android_api = 12
- import argparse
- ap = argparse.ArgumentParser(description='''\
-Package a Python application for Android.
-
-For this to work, Java and Ant need to be in your path, as does the
-tools directory of the Android SDK.
-''')
-
- ap.add_argument('--private', dest='private',
- help='the dir of user files',
- required=True)
- ap.add_argument('--package', dest='package',
- help=('The name of the java package the project will be'
- ' packaged under.'),
- required=True)
- ap.add_argument('--name', dest='name',
- help=('The human-readable name of the project.'),
- required=True)
- ap.add_argument('--numeric-version', dest='numeric_version',
- help=('The numeric version number of the project. If not '
- 'given, this is automatically computed from the '
- 'version.'))
- ap.add_argument('--version', dest='version',
- help=('The version number of the project. This should '
- 'consist of numbers and dots, and should have the '
- 'same number of groups of numbers as previous '
- 'versions.'),
- required=True)
- ap.add_argument('--orientation', dest='orientation', default='portrait',
- help=('The orientation that the game will display in. '
- 'Usually one of "landscape", "portrait" or '
- '"sensor"'))
- ap.add_argument('--icon', dest='icon',
- help='A png file to use as the icon for the application.')
- ap.add_argument('--permission', dest='permissions', action='append',
- help='The permissions to give this app.', nargs='+')
- ap.add_argument('--meta-data', dest='meta_data', action='append',
- help='Custom key=value to add in application metadata')
- ap.add_argument('--presplash', dest='presplash',
- help=('A jpeg file to use as a screen while the '
- 'application is loading.'))
- ap.add_argument('--wakelock', dest='wakelock', action='store_true',
- help=('Indicate if the application needs the device '
- 'to stay on'))
- ap.add_argument('--window', dest='window', action='store_false',
- help='Indicate if the application will be windowed')
- ap.add_argument('--blacklist', dest='blacklist',
- default=join(curdir, 'blacklist.txt'),
- help=('Use a blacklist file to match unwanted file in '
- 'the final APK'))
- ap.add_argument('--whitelist', dest='whitelist',
- default=join(curdir, 'whitelist.txt'),
- help=('Use a whitelist file to prevent blacklisting of '
- 'file in the final APK'))
- ap.add_argument('--add-jar', dest='add_jar', action='append',
- help=('Add a Java .jar to the libs, so you can access its '
- 'classes with pyjnius. You can specify this '
- 'argument more than once to include multiple jars'))
- ap.add_argument('--sdk', dest='sdk_version', default=-1,
- type=int, help=('Android SDK version to use. Default to '
- 'the value of minsdk'))
- ap.add_argument('--minsdk', dest='min_sdk_version',
- default=default_android_api, type=int,
- help=('Minimum Android SDK version to use. Default to '
- 'the value of ANDROIDAPI, or {} if not set'
- .format(default_android_api)))
- ap.add_argument('--intent-filters', dest='intent_filters',
- help=('Add intent-filters xml rules to the '
- 'AndroidManifest.xml file. The argument is a '
- 'filename containing xml. The filename should be '
- 'located relative to the python-for-android '
- 'directory'))
- ap.add_argument('--with-billing', dest='billing_pubkey',
- help='If set, the billing service will be added (not implemented)')
- ap.add_argument('--service', dest='services', action='append',
- help='Declare a new service entrypoint: '
- 'NAME:PATH_TO_PY[:foreground]')
- ap.add_argument('--add-source', dest='extra_source_dirs', action='append',
- help='Include additional source dirs in Java build')
- ap.add_argument('--port', help='The port on localhost that the WebView will access',
- default='5000')
-
- if args is None:
- args = sys.argv[1:]
- args = ap.parse_args(args)
- args.ignore_path = []
-
- if args.name and args.name[0] == '"' and args.name[-1] == '"':
- args.name = args.name[1:-1]
-
- if args.billing_pubkey:
- print('Billing not yet supported in sdl2 bootstrap!')
- exit(1)
-
- if args.sdk_version == -1:
- args.sdk_version = args.min_sdk_version
-
- if args.permissions is None:
- args.permissions = []
- elif args.permissions:
- if isinstance(args.permissions[0], list):
- args.permissions = [p for perm in args.permissions for p in perm]
-
- if args.meta_data is None:
- args.meta_data = []
-
- if args.services is None:
- args.services = []
-
- if args.blacklist:
- with open(args.blacklist) as fd:
- patterns = [x.strip() for x in fd.read().splitlines()
- if x.strip() and not x.strip().startswith('#')]
- BLACKLIST_PATTERNS += patterns
-
- if args.whitelist:
- with open(args.whitelist) as fd:
- patterns = [x.strip() for x in fd.read().splitlines()
- if x.strip() and not x.strip().startswith('#')]
- WHITELIST_PATTERNS += patterns
-
- make_package(args)
-
- return args
-
-
-if __name__ == "__main__":
-
- parse_args()
diff --git a/pythonforandroid/bootstraps/webview/build/build.xml b/pythonforandroid/bootstraps/webview/build/build.xml
deleted file mode 100644
index 9f19a077b..000000000
--- a/pythonforandroid/bootstraps/webview/build/build.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This should be changed to the name of your project -->
-<project name="SDLActivity" default="help">
-
- <!-- The local.properties file is created and updated by the 'android' tool.
- It contains the path to the SDK. It should *NOT* be checked into
- Version Control Systems. -->
- <property file="local.properties" />
-
- <!-- The ant.properties file can be created by you. It is only edited by the
- 'android' tool to add properties to it.
- This is the place to change some Ant specific build properties.
- Here are some properties you may want to change/update:
-
- source.dir
- The name of the source directory. Default is 'src'.
- out.dir
- The name of the output directory. Default is 'bin'.
-
- For other overridable properties, look at the beginning of the rules
- files in the SDK, at tools/ant/build.xml
-
- Properties related to the SDK location or the project target should
- be updated using the 'android' tool with the 'update' action.
-
- This file is an integral part of the build system for your
- application and should be checked into Version Control Systems.
-
- -->
- <property file="ant.properties" />
-
- <!-- if sdk.dir was not set from one of the property file, then
- get it from the ANDROID_HOME env var.
- This must be done before we load project.properties since
- the proguard config can use sdk.dir -->
- <property environment="env" />
- <condition property="sdk.dir" value="${env.ANDROID_HOME}">
- <isset property="env.ANDROID_HOME" />
- </condition>
-
- <!-- The project.properties file is created and updated by the 'android'
- tool, as well as ADT.
-
- This contains project specific properties such as project target, and library
- dependencies. Lower level build properties are stored in ant.properties
- (or in .classpath for Eclipse projects).
-
- This file is an integral part of the build system for your
- application and should be checked into Version Control Systems. -->
- <loadproperties srcFile="project.properties" />
-
- <!-- quick check on sdk.dir -->
- <fail
- 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."
- unless="sdk.dir"
- />
-
- <!--
- Import per project custom build rules if present at the root of the project.
- This is the place to put custom intermediary targets such as:
- -pre-build
- -pre-compile
- -post-compile (This is typically used for code obfuscation.
- Compiled code location: ${out.classes.absolute.dir}
- If this is not done in place, override ${out.dex.input.absolute.dir})
- -post-package
- -post-build
- -pre-clean
- -->
- <import file="custom_rules.xml" optional="true" />
-
- <!-- Import the actual build file.
-
- To customize existing targets, there are two options:
- - Customize only one target:
- - copy/paste the target into this file, *before* the
- <import> task.
- - customize it to your needs.
- - Customize the whole content of build.xml
- - copy/paste the content of the rules files (minus the top node)
- into this file, replacing the <import> task.
- - customize to your needs.
-
- ***********************
- ****** IMPORTANT ******
- ***********************
- In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
- in order to avoid having your file be overridden by tools such as "android update project"
- -->
- <!-- version-tag: 1 -->
- <import file="${sdk.dir}/tools/ant/build.xml" />
-
-</project>
diff --git a/pythonforandroid/bootstraps/webview/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/webview/build/jni/application/src/Android.mk
new file mode 100644
index 000000000..5fbc4cd36
--- /dev/null
+++ b/pythonforandroid/bootstraps/webview/build/jni/application/src/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := main
+
+# LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
+
+# Add your application source files here...
+LOCAL_SRC_FILES := start.c pyjniusjni.c
+
+LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
+
+LOCAL_SHARED_LIBRARIES := python_shared
+
+LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
+
+LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/src/Android_static.mk b/pythonforandroid/bootstraps/webview/build/jni/application/src/Android_static.mk
similarity index 100%
rename from pythonforandroid/bootstraps/sdl2/build/jni/src/Android_static.mk
rename to pythonforandroid/bootstraps/webview/build/jni/application/src/Android_static.mk
diff --git a/pythonforandroid/bootstraps/webview/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/webview/build/jni/application/src/bootstrap_name.h
new file mode 100644
index 000000000..11c7905df
--- /dev/null
+++ b/pythonforandroid/bootstraps/webview/build/jni/application/src/bootstrap_name.h
@@ -0,0 +1,6 @@
+
+#define BOOTSTRAP_NAME_WEBVIEW
+#define BOOTSTRAP_USES_NO_SDL_HEADERS
+
+const char bootstrap_name[] = "webview";
+
diff --git a/pythonforandroid/bootstraps/webview/build/jni/src/pyjniusjni.c b/pythonforandroid/bootstraps/webview/build/jni/application/src/pyjniusjni.c
similarity index 100%
rename from pythonforandroid/bootstraps/webview/build/jni/src/pyjniusjni.c
rename to pythonforandroid/bootstraps/webview/build/jni/application/src/pyjniusjni.c
diff --git a/pythonforandroid/bootstraps/webview/build/jni/src/Android.mk b/pythonforandroid/bootstraps/webview/build/jni/src/Android.mk
deleted file mode 100644
index b431059f1..000000000
--- a/pythonforandroid/bootstraps/webview/build/jni/src/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := main
-
-# LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
-
-# Add your application source files here...
-LOCAL_SRC_FILES := start.c pyjniusjni.c
-
-LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/include/python2.7 $(EXTRA_CFLAGS)
-
-LOCAL_SHARED_LIBRARIES := python_shared
-
-LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
-
-LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/lib $(APPLICATION_ADDITIONAL_LDFLAGS)
-
-include $(BUILD_SHARED_LIBRARY)
-
-ifdef CRYSTAX_PYTHON_VERSION
- $(call import-module,python/$(CRYSTAX_PYTHON_VERSION))
-endif
diff --git a/pythonforandroid/bootstraps/webview/build/res/drawable/icon.png b/pythonforandroid/bootstraps/webview/build/res/drawable/icon.png
deleted file mode 100644
index 59a00ba6f..000000000
Binary files a/pythonforandroid/bootstraps/webview/build/res/drawable/icon.png and /dev/null differ
diff --git a/pythonforandroid/bootstraps/webview/build/src/main/assets/.gitkeep b/pythonforandroid/bootstraps/webview/build/src/main/assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/GenericBroadcastReceiver.java b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
similarity index 94%
rename from pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/GenericBroadcastReceiver.java
rename to pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
index ec3ed901b..58a1c5edf 100644
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/GenericBroadcastReceiver.java
+++ b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
@@ -1,4 +1,4 @@
-package org.renpy.android;
+package org.kivy.android;
import android.content.BroadcastReceiver;
import android.content.Intent;
diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/GenericBroadcastReceiverCallback.java b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
similarity index 86%
rename from pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/GenericBroadcastReceiverCallback.java
rename to pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
index b934ca9fd..1a87c98b2 100644
--- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/GenericBroadcastReceiverCallback.java
+++ b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
@@ -1,4 +1,4 @@
-package org.renpy.android;
+package org.kivy.android;
import android.content.Intent;
import android.content.Context;
diff --git a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java
new file mode 100644
index 000000000..2f0afdc6f
--- /dev/null
+++ b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -0,0 +1,572 @@
+package org.kivy.android;
+
+import android.os.SystemClock;
+
+import java.io.InputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+import android.view.ViewGroup;
+import android.view.KeyEvent;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.Toast;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.widget.ImageView;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+
+import android.widget.AbsoluteLayout;
+import android.view.ViewGroup.LayoutParams;
+
+import android.webkit.WebBackForwardList;
+import android.webkit.WebViewClient;
+import android.webkit.WebView;
+import android.webkit.CookieManager;
+import android.net.Uri;
+
+import org.renpy.android.ResourceManager;
+
+public class PythonActivity extends Activity {
+ // This activity is modified from a mixture of the SDLActivity and
+ // PythonActivity in the SDL2 bootstrap, but removing all the SDL2
+ // specifics.
+
+ private static final String TAG = "PythonActivity";
+
+ public static PythonActivity mActivity = null;
+ public static boolean mOpenExternalLinksInBrowser = false;
+
+ /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
+ public static boolean mBrokenLibraries;
+
+ protected static ViewGroup mLayout;
+ protected static WebView mWebView;
+
+ protected static Thread mPythonThread;
+
+ private ResourceManager resourceManager = null;
+ private Bundle mMetaData = null;
+ private PowerManager.WakeLock mWakeLock = null;
+
+ public String getAppRoot() {
+ String app_root = getFilesDir().getAbsolutePath() + "/app";
+ return app_root;
+ }
+
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List<String> entryPoints = new ArrayList<String>();
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
+
+ public static void initialize() {
+ // The static nature of the singleton and Android quirkyness force us to initialize everything here
+ // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
+ mWebView = null;
+ mLayout = null;
+ mBrokenLibraries = false;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "My oncreate running");
+ resourceManager = new ResourceManager(this);
+ super.onCreate(savedInstanceState);
+
+ this.mActivity = this;
+ this.showLoadingScreen();
+ new UnpackFilesTask().execute(getAppRoot());
+ }
+
+ private class UnpackFilesTask extends AsyncTask<String, Void, String> {
+ @Override
+ protected String doInBackground(String... params) {
+ File app_root_file = new File(params[0]);
+ Log.v(TAG, "Ready to unpack");
+ PythonUtil.unpackAsset(mActivity, "private", app_root_file, true);
+ PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+ Log.v("Python", "Device: " + android.os.Build.DEVICE);
+ Log.v("Python", "Model: " + android.os.Build.MODEL);
+
+ PythonActivity.initialize();
+
+ // Load shared libraries
+ String errorMsgBrokenLib = "";
+ try {
+ loadLibraries();
+ } catch(UnsatisfiedLinkError e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ } catch(Exception e) {
+ System.err.println(e.getMessage());
+ mBrokenLibraries = true;
+ errorMsgBrokenLib = e.getMessage();
+ }
+
+ if (mBrokenLibraries)
+ {
+ AlertDialog.Builder dlgAlert = new AlertDialog.Builder(PythonActivity.mActivity);
+ dlgAlert.setMessage("An error occurred while trying to load the application libraries. Please try again and/or reinstall."
+ + System.getProperty("line.separator")
+ + System.getProperty("line.separator")
+ + "Error: " + errorMsgBrokenLib);
+ dlgAlert.setTitle("Python Error");
+ dlgAlert.setPositiveButton("Exit",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,int id) {
+ // if this button is clicked, close current activity
+ PythonActivity.mActivity.finish();
+ }
+ });
+ dlgAlert.setCancelable(false);
+ dlgAlert.create().show();
+
+ return;
+ }
+
+ // Set up the webview
+ String app_root_dir = getAppRoot();
+
+ mWebView = new WebView(PythonActivity.mActivity);
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.getSettings().setDomStorageEnabled(true);
+ mWebView.loadUrl("file:///android_asset/_load.html");
+
+ mWebView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+ mWebView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ Uri u = Uri.parse(url);
+ if (mOpenExternalLinksInBrowser) {
+ if (!(u.getScheme().equals("file") || u.getHost().equals("127.0.0.1"))) {
+ Intent i = new Intent(Intent.ACTION_VIEW, u);
+ startActivity(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ CookieManager.getInstance().flush();
+ }
+ });
+ mLayout = new AbsoluteLayout(PythonActivity.mActivity);
+ mLayout.addView(mWebView);
+
+ setContentView(mLayout);
+
+ String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
+ String entry_point = getEntryPoint(app_root_dir);
+
+ Log.v(TAG, "Setting env vars for start.c and Python to use");
+ PythonActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ PythonActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ PythonActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ PythonActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
+
+ try {
+ Log.v(TAG, "Access to our meta-data...");
+ mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo(
+ mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData;
+
+ PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
+ if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
+ mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ mActivity.mWakeLock.acquire();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ final Thread pythonThread = new Thread(new PythonMain(), "PythonThread");
+ PythonActivity.mPythonThread = pythonThread;
+ pythonThread.start();
+
+ final Thread wvThread = new Thread(new WebViewLoaderMain(), "WvThread");
+ wvThread.start();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i("Destroy", "end of app");
+ super.onDestroy();
+
+ // make sure all child threads (python_thread) are stopped
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+
+ public void loadLibraries() {
+ String app_root = new String(getAppRoot());
+ File app_root_file = new File(app_root);
+ PythonUtil.loadLibraries(app_root_file,
+ new File(getApplicationInfo().nativeLibraryDir));
+ }
+
+ public static void loadUrl(String url) {
+ class LoadUrl implements Runnable {
+ private String mUrl;
+
+ public LoadUrl(String url) {
+ mUrl = url;
+ }
+
+ public void run() {
+ mWebView.loadUrl(mUrl);
+ }
+ }
+
+ Log.i(TAG, "Opening URL: " + url);
+ mActivity.runOnUiThread(new LoadUrl(url));
+ }
+
+ public static void enableZoom() {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mWebView.getSettings().setBuiltInZoomControls(true);
+ mWebView.getSettings().setDisplayZoomControls(false);
+ }
+ });
+ }
+
+ public static ViewGroup getLayout() {
+ return mLayout;
+ }
+
+ long lastBackClick = 0;
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // Check if the key event was the Back button
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // Go back if there is web page history behind,
+ // but not to the start preloader
+ WebBackForwardList webViewBackForwardList = mWebView.copyBackForwardList();
+ if (webViewBackForwardList.getCurrentIndex() > 1) {
+ mWebView.goBack();
+ return true;
+ }
+
+ // If there's no web page history, bubble up to the default
+ // system behavior (probably exit the activity)
+ if (SystemClock.elapsedRealtime() - lastBackClick > 2000){
+ lastBackClick = SystemClock.elapsedRealtime();
+ Toast.makeText(this, "Tap again to close the app", Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ lastBackClick = SystemClock.elapsedRealtime();
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ // loading screen implementation
+ public static ImageView mImageView = null;
+ public void removeLoadingScreen() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (PythonActivity.mImageView != null &&
+ PythonActivity.mImageView.getParent() != null) {
+ ((ViewGroup)PythonActivity.mImageView.getParent()).removeView(
+ PythonActivity.mImageView);
+ PythonActivity.mImageView = null;
+ }
+ }
+ });
+ }
+
+ protected void showLoadingScreen() {
+ // load the bitmap
+ // 1. if the image is valid and we don't have layout yet, assign this bitmap
+ // as main view.
+ // 2. if we have a layout, just set it in the layout.
+ // 3. If we have an mImageView already, then do nothing because it will have
+ // already been made the content view or added to the layout.
+
+ if (mImageView == null) {
+ int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
+ InputStream is = this.getResources().openRawResource(presplashId);
+ Bitmap bitmap = null;
+ try {
+ bitmap = BitmapFactory.decodeStream(is);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {};
+ }
+
+ mImageView = new ImageView(this);
+ mImageView.setImageBitmap(bitmap);
+
+ /*
+ * Set the presplash loading screen background color
+ * https://developer.android.com/reference/android/graphics/Color.html
+ * Parse the color string, and return the corresponding color-int.
+ * If the string cannot be parsed, throws an IllegalArgumentException exception.
+ * Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
+ * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',
+ * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',
+ * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.
+ */
+ String backgroundColor = resourceManager.getString("presplash_color");
+ if (backgroundColor != null) {
+ try {
+ mImageView.setBackgroundColor(Color.parseColor(backgroundColor));
+ } catch (IllegalArgumentException e) {}
+ }
+ mImageView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
+ mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+
+ }
+
+ if (mLayout == null) {
+ setContentView(mImageView);
+ } else if (PythonActivity.mImageView.getParent() == null){
+ mLayout.addView(mImageView);
+ }
+ }
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onNewIntent
+ //
+
+ public interface NewIntentListener {
+ void onNewIntent(Intent intent);
+ }
+
+ private List<NewIntentListener> newIntentListeners = null;
+
+ public void registerNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ this.newIntentListeners = Collections.synchronizedList(new ArrayList<NewIntentListener>());
+ this.newIntentListeners.add(listener);
+ }
+
+ public void unregisterNewIntentListener(NewIntentListener listener) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.newIntentListeners.remove(listener);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ if ( this.newIntentListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.newIntentListeners ) {
+ Iterator<NewIntentListener> iterator = this.newIntentListeners.iterator();
+ while ( iterator.hasNext() ) {
+ (iterator.next()).onNewIntent(intent);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------------
+ // Listener interface for onActivityResult
+ //
+
+ public interface ActivityResultListener {
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+ }
+
+ private List<ActivityResultListener> activityResultListeners = null;
+
+ public void registerActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ this.activityResultListeners = Collections.synchronizedList(new ArrayList<ActivityResultListener>());
+ this.activityResultListeners.add(listener);
+ }
+
+ public void unregisterActivityResultListener(ActivityResultListener listener) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.activityResultListeners.remove(listener);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if ( this.activityResultListeners == null )
+ return;
+ this.onResume();
+ synchronized ( this.activityResultListeners ) {
+ Iterator<ActivityResultListener> iterator = this.activityResultListeners.iterator();
+ while ( iterator.hasNext() )
+ (iterator.next()).onActivityResult(requestCode, resultCode, intent);
+ }
+ }
+
+ public static void start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, true
+ );
+ }
+
+ public static void start_service_not_as_foreground(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument
+ ) {
+ _do_start_service(
+ serviceTitle, serviceDescription, pythonServiceArgument, false
+ );
+ }
+
+ public static void _do_start_service(
+ String serviceTitle,
+ String serviceDescription,
+ String pythonServiceArgument,
+ boolean showForegroundNotification
+ ) {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
+ String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
+ serviceIntent.putExtra("androidPrivate", argument);
+ serviceIntent.putExtra("androidArgument", app_root_dir);
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
+ serviceIntent.putExtra("pythonName", "python");
+ serviceIntent.putExtra("pythonHome", app_root_dir);
+ serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
+ serviceIntent.putExtra("serviceStartAsForeground",
+ (showForegroundNotification ? "true" : "false")
+ );
+ serviceIntent.putExtra("serviceTitle", serviceTitle);
+ serviceIntent.putExtra("serviceDescription", serviceDescription);
+ serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ PythonActivity.mActivity.startService(serviceIntent);
+ }
+
+ public static void stop_service() {
+ Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
+ PythonActivity.mActivity.stopService(serviceIntent);
+ }
+
+
+ public static native void nativeSetenv(String name, String value);
+ public static native int nativeInit(Object arguments);
+
+
+ /**
+ * Used by android.permissions p4a module to register a call back after
+ * requesting runtime permissions
+ **/
+ public interface PermissionsCallback {
+ void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
+ }
+
+ private PermissionsCallback permissionCallback;
+ private boolean havePermissionsCallback = false;
+
+ public void addPermissionsCallback(PermissionsCallback callback) {
+ permissionCallback = callback;
+ havePermissionsCallback = true;
+ Log.v(TAG, "addPermissionsCallback(): Added callback for onRequestPermissionsResult");
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ Log.v(TAG, "onRequestPermissionsResult()");
+ if (havePermissionsCallback) {
+ Log.v(TAG, "onRequestPermissionsResult passed to callback");
+ permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ /**
+ * Used by android.permissions p4a module to check a permission
+ **/
+ public boolean checkCurrentPermission(String permission) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return true;
+
+ try {
+ java.lang.reflect.Method methodCheckPermission =
+ Activity.class.getMethod("checkSelfPermission", String.class);
+ Object resultObj = methodCheckPermission.invoke(this, permission);
+ int result = Integer.parseInt(resultObj.toString());
+ if (result == PackageManager.PERMISSION_GRANTED)
+ return true;
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Used by android.permissions p4a module to request runtime permissions
+ **/
+ public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return;
+ try {
+ java.lang.reflect.Method methodRequestPermission =
+ Activity.class.getMethod("requestPermissions",
+ String[].class, int.class);
+ methodRequestPermission.invoke(this, permissions, requestCode);
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ }
+
+ public void requestPermissions(String[] permissions) {
+ requestPermissionsWithRequestCode(permissions, 1);
+ }
+}
+
+
+class PythonMain implements Runnable {
+ @Override
+ public void run() {
+ PythonActivity.nativeInit(new String[0]);
+ }
+}
+
+class WebViewLoaderMain implements Runnable {
+ @Override
+ public void run() {
+ WebViewLoader.testConnection();
+ }
+}
diff --git a/pythonforandroid/bootstraps/webview/build/src/main/jniLibs/.gitkeep b/pythonforandroid/bootstraps/webview/build/src/main/jniLibs/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/webview/build/src/main/res/drawable/.gitkeep b/pythonforandroid/bootstraps/webview/build/src/main/res/drawable/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/pygame/build/res/drawable/icon.png b/pythonforandroid/bootstraps/webview/build/src/main/res/drawable/icon.png
similarity index 100%
rename from pythonforandroid/bootstraps/pygame/build/res/drawable/icon.png
rename to pythonforandroid/bootstraps/webview/build/src/main/res/drawable/icon.png
diff --git a/pythonforandroid/bootstraps/webview/build/res/layout/main.xml b/pythonforandroid/bootstraps/webview/build/src/main/res/layout/main.xml
similarity index 100%
rename from pythonforandroid/bootstraps/webview/build/res/layout/main.xml
rename to pythonforandroid/bootstraps/webview/build/src/main/res/layout/main.xml
diff --git a/pythonforandroid/bootstraps/webview/build/src/main/res/mipmap-anydpi-v26/.gitkeep b/pythonforandroid/bootstraps/webview/build/src/main/res/mipmap-anydpi-v26/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/webview/build/src/main/res/mipmap/.gitkeep b/pythonforandroid/bootstraps/webview/build/src/main/res/mipmap/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/pythonforandroid/bootstraps/webview/build/res/values/strings.xml b/pythonforandroid/bootstraps/webview/build/src/main/res/values/strings.xml
similarity index 100%
rename from pythonforandroid/bootstraps/webview/build/res/values/strings.xml
rename to pythonforandroid/bootstraps/webview/build/src/main/res/values/strings.xml
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/Octal.java b/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/Octal.java
deleted file mode 100755
index dd10624ea..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/Octal.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class Octal {
-
- /**
- * Parse an octal string from a header buffer. This is used for the file
- * permission mode value.
- *
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The long value of the octal string.
- */
- public static long parseOctal(byte[] header, int offset, int length) {
- long result = 0;
- boolean stillPadding = true;
-
- int end = offset + length;
- for (int i = offset; i < end; ++i) {
- if (header[i] == 0)
- break;
-
- if (header[i] == (byte) ' ' || header[i] == '0') {
- if (stillPadding)
- continue;
-
- if (header[i] == (byte) ' ')
- break;
- }
-
- stillPadding = false;
-
- result = ( result << 3 ) + ( header[i] - '0' );
- }
-
- return result;
- }
-
- /**
- * Parse an octal integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The integer value of the octal bytes.
- */
- public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
- int idx = length - 1;
-
- buf[offset + idx] = 0;
- --idx;
- buf[offset + idx] = (byte) ' ';
- --idx;
-
- if (value == 0) {
- buf[offset + idx] = (byte) '0';
- --idx;
- } else {
- for (long val = value; idx >= 0 && val > 0; --idx) {
- buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
- val = val >> 3;
- }
- }
-
- for (; idx >= 0; --idx) {
- buf[offset + idx] = (byte) ' ';
- }
-
- return offset + length;
- }
-
- /**
- * Parse the checksum octal integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The integer value of the entry's checksum.
- */
- public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
- getOctalBytes( value, buf, offset, length );
- buf[offset + length - 1] = (byte) ' ';
- buf[offset + length - 2] = 0;
- return offset + length;
- }
-
- /**
- * Parse an octal long integer from a header buffer.
- *
- * @param value
- * @param buf
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- *
- * @return The long value of the octal bytes.
- */
- public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
- byte[] temp = new byte[length + 1];
- getOctalBytes( value, temp, 0, length + 1 );
- System.arraycopy( temp, 0, buf, offset, length );
- return offset + length;
- }
-
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarConstants.java b/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarConstants.java
deleted file mode 100755
index 4611e20ea..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarConstants.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarConstants {
- public static final int EOF_BLOCK = 1024;
- public static final int DATA_BLOCK = 512;
- public static final int HEADER_BLOCK = 512;
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarEntry.java b/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarEntry.java
deleted file mode 100755
index fe01db463..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarEntry.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-import java.util.Date;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarEntry {
- protected File file;
- protected TarHeader header;
-
- private TarEntry() {
- this.file = null;
- header = new TarHeader();
- }
-
- public TarEntry(File file, String entryName) {
- this();
- this.file = file;
- this.extractTarHeader(entryName);
- }
-
- public TarEntry(byte[] headerBuf) {
- this();
- this.parseTarHeader(headerBuf);
- }
-
- /**
- * Constructor to create an entry from an existing TarHeader object.
- *
- * This method is useful to add new entries programmatically (e.g. for
- * adding files or directories that do not exist in the file system).
- *
- * @param header
- *
- */
- public TarEntry(TarHeader header) {
- this.file = null;
- this.header = header;
- }
-
- public boolean equals(TarEntry it) {
- return header.name.toString().equals(it.header.name.toString());
- }
-
- public boolean isDescendent(TarEntry desc) {
- return desc.header.name.toString().startsWith(header.name.toString());
- }
-
- public TarHeader getHeader() {
- return header;
- }
-
- public String getName() {
- String name = header.name.toString();
- if (header.namePrefix != null && !header.namePrefix.toString().equals("")) {
- name = header.namePrefix.toString() + "/" + name;
- }
-
- return name;
- }
-
- public void setName(String name) {
- header.name = new StringBuffer(name);
- }
-
- public int getUserId() {
- return header.userId;
- }
-
- public void setUserId(int userId) {
- header.userId = userId;
- }
-
- public int getGroupId() {
- return header.groupId;
- }
-
- public void setGroupId(int groupId) {
- header.groupId = groupId;
- }
-
- public String getUserName() {
- return header.userName.toString();
- }
-
- public void setUserName(String userName) {
- header.userName = new StringBuffer(userName);
- }
-
- public String getGroupName() {
- return header.groupName.toString();
- }
-
- public void setGroupName(String groupName) {
- header.groupName = new StringBuffer(groupName);
- }
-
- public void setIds(int userId, int groupId) {
- this.setUserId(userId);
- this.setGroupId(groupId);
- }
-
- public void setModTime(long time) {
- header.modTime = time / 1000;
- }
-
- public void setModTime(Date time) {
- header.modTime = time.getTime() / 1000;
- }
-
- public Date getModTime() {
- return new Date(header.modTime * 1000);
- }
-
- public File getFile() {
- return this.file;
- }
-
- public long getSize() {
- return header.size;
- }
-
- public void setSize(long size) {
- header.size = size;
- }
-
- /**
- * Checks if the org.kamrazafar.jtar entry is a directory
- *
- * @return
- */
- public boolean isDirectory() {
- if (this.file != null)
- return this.file.isDirectory();
-
- if (header != null) {
- if (header.linkFlag == TarHeader.LF_DIR)
- return true;
-
- if (header.name.toString().endsWith("/"))
- return true;
- }
-
- return false;
- }
-
- /**
- * Extract header from File
- *
- * @param entryName
- */
- public void extractTarHeader(String entryName) {
- header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory());
- }
-
- /**
- * Calculate checksum
- *
- * @param buf
- * @return
- */
- public long computeCheckSum(byte[] buf) {
- long sum = 0;
-
- for (int i = 0; i < buf.length; ++i) {
- sum += 255 & buf[i];
- }
-
- return sum;
- }
-
- /**
- * Writes the header to the byte buffer
- *
- * @param outbuf
- */
- public void writeEntryHeader(byte[] outbuf) {
- int offset = 0;
-
- offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN);
- offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN);
- offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN);
- offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN);
-
- long size = header.size;
-
- offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
- offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN);
-
- int csOffset = offset;
- for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
- outbuf[offset++] = (byte) ' ';
-
- outbuf[offset++] = header.linkFlag;
-
- offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN);
- offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN);
- offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN);
- offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN);
- offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN);
- offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN);
- offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX);
-
- for (; offset < outbuf.length;)
- outbuf[offset++] = 0;
-
- long checkSum = this.computeCheckSum(outbuf);
-
- Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN);
- }
-
- /**
- * Parses the tar header to the byte buffer
- *
- * @param header
- * @param bh
- */
- public void parseTarHeader(byte[] bh) {
- int offset = 0;
-
- header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
- offset += TarHeader.NAMELEN;
-
- header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
- offset += TarHeader.MODELEN;
-
- header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
- offset += TarHeader.UIDLEN;
-
- header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
- offset += TarHeader.GIDLEN;
-
- header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
- offset += TarHeader.SIZELEN;
-
- header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
- offset += TarHeader.MODTIMELEN;
-
- header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN);
- offset += TarHeader.CHKSUMLEN;
-
- header.linkFlag = bh[offset++];
-
- header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
- offset += TarHeader.NAMELEN;
-
- header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
- offset += TarHeader.USTAR_MAGICLEN;
-
- header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN);
- offset += TarHeader.USTAR_USER_NAMELEN;
-
- header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN);
- offset += TarHeader.USTAR_GROUP_NAMELEN;
-
- header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
- offset += TarHeader.USTAR_DEVLEN;
-
- header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
- offset += TarHeader.USTAR_DEVLEN;
-
- header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX);
- }
-}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarHeader.java b/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarHeader.java
deleted file mode 100755
index b9d3a86be..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarHeader.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-
-/**
- * Header
- *
- * <pre>
- * Offset Size Field
- * 0 100 File name
- * 100 8 File mode
- * 108 8 Owner's numeric user ID
- * 116 8 Group's numeric user ID
- * 124 12 File size in bytes
- * 136 12 Last modification time in numeric Unix time format
- * 148 8 Checksum for header block
- * 156 1 Link indicator (file type)
- * 157 100 Name of linked file
- * </pre>
- *
- *
- * File Types
- *
- * <pre>
- * Value Meaning
- * '0' Normal file
- * (ASCII NUL) Normal file (now obsolete)
- * '1' Hard link
- * '2' Symbolic link
- * '3' Character special
- * '4' Block special
- * '5' Directory
- * '6' FIFO
- * '7' Contigous
- * </pre>
- *
- *
- *
- * Ustar header
- *
- * <pre>
- * Offset Size Field
- * 257 6 UStar indicator "ustar"
- * 263 2 UStar version "00"
- * 265 32 Owner user name
- * 297 32 Owner group name
- * 329 8 Device major number
- * 337 8 Device minor number
- * 345 155 Filename prefix
- * </pre>
- */
-
-public class TarHeader {
-
- /*
- * Header
- */
- public static final int NAMELEN = 100;
- public static final int MODELEN = 8;
- public static final int UIDLEN = 8;
- public static final int GIDLEN = 8;
- public static final int SIZELEN = 12;
- public static final int MODTIMELEN = 12;
- public static final int CHKSUMLEN = 8;
- public static final byte LF_OLDNORM = 0;
-
- /*
- * File Types
- */
- public static final byte LF_NORMAL = (byte) '0';
- public static final byte LF_LINK = (byte) '1';
- public static final byte LF_SYMLINK = (byte) '2';
- public static final byte LF_CHR = (byte) '3';
- public static final byte LF_BLK = (byte) '4';
- public static final byte LF_DIR = (byte) '5';
- public static final byte LF_FIFO = (byte) '6';
- public static final byte LF_CONTIG = (byte) '7';
-
- /*
- * Ustar header
- */
-
- public static final String USTAR_MAGIC = "ustar"; // POSIX
-
- public static final int USTAR_MAGICLEN = 8;
- public static final int USTAR_USER_NAMELEN = 32;
- public static final int USTAR_GROUP_NAMELEN = 32;
- public static final int USTAR_DEVLEN = 8;
- public static final int USTAR_FILENAME_PREFIX = 155;
-
- // Header values
- public StringBuffer name;
- public int mode;
- public int userId;
- public int groupId;
- public long size;
- public long modTime;
- public int checkSum;
- public byte linkFlag;
- public StringBuffer linkName;
- public StringBuffer magic; // ustar indicator and version
- public StringBuffer userName;
- public StringBuffer groupName;
- public int devMajor;
- public int devMinor;
- public StringBuffer namePrefix;
-
- public TarHeader() {
- this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
-
- this.name = new StringBuffer();
- this.linkName = new StringBuffer();
-
- String user = System.getProperty("user.name", "");
-
- if (user.length() > 31)
- user = user.substring(0, 31);
-
- this.userId = 0;
- this.groupId = 0;
- this.userName = new StringBuffer(user);
- this.groupName = new StringBuffer("");
- this.namePrefix = new StringBuffer();
- }
-
- /**
- * Parse an entry name from a header buffer.
- *
- * @param name
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The header's entry name.
- */
- public static StringBuffer parseName(byte[] header, int offset, int length) {
- StringBuffer result = new StringBuffer(length);
-
- int end = offset + length;
- for (int i = offset; i < end; ++i) {
- if (header[i] == 0)
- break;
- result.append((char) header[i]);
- }
-
- return result;
- }
-
- /**
- * Determine the number of bytes in an entry name.
- *
- * @param name
- * @param header
- * The header buffer from which to parse.
- * @param offset
- * The offset into the buffer from which to parse.
- * @param length
- * The number of header bytes to parse.
- * @return The number of bytes in a header's entry name.
- */
- public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
- int i;
-
- for (i = 0; i < length && i < name.length(); ++i) {
- buf[offset + i] = (byte) name.charAt(i);
- }
-
- for (; i < length; ++i) {
- buf[offset + i] = 0;
- }
-
- return offset + length;
- }
-
- /**
- * Creates a new header for a file/directory entry.
- *
- *
- * @param name
- * File name
- * @param size
- * File size in bytes
- * @param modTime
- * Last modification time in numeric Unix time format
- * @param dir
- * Is directory
- *
- * @return
- */
- public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
- String name = entryName;
- name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
-
- TarHeader header = new TarHeader();
- header.linkName = new StringBuffer("");
-
- if (name.length() > 100) {
- header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
- header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
- } else {
- header.name = new StringBuffer(name);
- }
-
- if (dir) {
- header.mode = 040755;
- header.linkFlag = TarHeader.LF_DIR;
- if (header.name.charAt(header.name.length() - 1) != '/') {
- header.name.append("/");
- }
- header.size = 0;
- } else {
- header.mode = 0100644;
- header.linkFlag = TarHeader.LF_NORMAL;
- header.size = size;
- }
-
- header.modTime = modTime;
- header.checkSum = 0;
- header.devMajor = 0;
- header.devMinor = 0;
-
- return header;
- }
-}
\ No newline at end of file
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarInputStream.java b/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarInputStream.java
deleted file mode 100755
index ec50a1b68..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarInputStream.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarInputStream extends FilterInputStream {
-
- private static final int SKIP_BUFFER_SIZE = 2048;
- private TarEntry currentEntry;
- private long currentFileSize;
- private long bytesRead;
- private boolean defaultSkip = false;
-
- public TarInputStream(InputStream in) {
- super(in);
- currentFileSize = 0;
- bytesRead = 0;
- }
-
- @Override
- public boolean markSupported() {
- return false;
- }
-
- /**
- * Not supported
- *
- */
- @Override
- public synchronized void mark(int readlimit) {
- }
-
- /**
- * Not supported
- *
- */
- @Override
- public synchronized void reset() throws IOException {
- throw new IOException("mark/reset not supported");
- }
-
- /**
- * Read a byte
- *
- * @see java.io.FilterInputStream#read()
- */
- @Override
- public int read() throws IOException {
- byte[] buf = new byte[1];
-
- int res = this.read(buf, 0, 1);
-
- if (res != -1) {
- return 0xFF & buf[0];
- }
-
- return res;
- }
-
- /**
- * Checks if the bytes being read exceed the entry size and adjusts the byte
- * array length. Updates the byte counters
- *
- *
- * @see java.io.FilterInputStream#read(byte[], int, int)
- */
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- if (currentEntry != null) {
- if (currentFileSize == currentEntry.getSize()) {
- return -1;
- } else if ((currentEntry.getSize() - currentFileSize) < len) {
- len = (int) (currentEntry.getSize() - currentFileSize);
- }
- }
-
- int br = super.read(b, off, len);
-
- if (br != -1) {
- if (currentEntry != null) {
- currentFileSize += br;
- }
-
- bytesRead += br;
- }
-
- return br;
- }
-
- /**
- * Returns the next entry in the tar file
- *
- * @return TarEntry
- * @throws IOException
- */
- public TarEntry getNextEntry() throws IOException {
- closeCurrentEntry();
-
- byte[] header = new byte[TarConstants.HEADER_BLOCK];
- byte[] theader = new byte[TarConstants.HEADER_BLOCK];
- int tr = 0;
-
- // Read full header
- while (tr < TarConstants.HEADER_BLOCK) {
- int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
-
- if (res < 0) {
- break;
- }
-
- System.arraycopy(theader, 0, header, tr, res);
- tr += res;
- }
-
- // Check if record is null
- boolean eof = true;
- for (byte b : header) {
- if (b != 0) {
- eof = false;
- break;
- }
- }
-
- if (!eof) {
- currentEntry = new TarEntry(header);
- }
-
- return currentEntry;
- }
-
- /**
- * Returns the current offset (in bytes) from the beginning of the stream.
- * This can be used to find out at which point in a tar file an entry's content begins, for instance.
- */
- public long getCurrentOffset() {
- return bytesRead;
- }
-
- /**
- * Closes the current tar entry
- *
- * @throws IOException
- */
- protected void closeCurrentEntry() throws IOException {
- if (currentEntry != null) {
- if (currentEntry.getSize() > currentFileSize) {
- // Not fully read, skip rest of the bytes
- long bs = 0;
- while (bs < currentEntry.getSize() - currentFileSize) {
- long res = skip(currentEntry.getSize() - currentFileSize - bs);
-
- if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
- // I suspect file corruption
- throw new IOException("Possible tar file corruption");
- }
-
- bs += res;
- }
- }
-
- currentEntry = null;
- currentFileSize = 0L;
- skipPad();
- }
- }
-
- /**
- * Skips the pad at the end of each tar entry file content
- *
- * @throws IOException
- */
- protected void skipPad() throws IOException {
- if (bytesRead > 0) {
- int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
-
- if (extra > 0) {
- long bs = 0;
- while (bs < TarConstants.DATA_BLOCK - extra) {
- long res = skip(TarConstants.DATA_BLOCK - extra - bs);
- bs += res;
- }
- }
- }
- }
-
- /**
- * Skips 'n' bytes on the InputStream<br>
- * Overrides default implementation of skip
- *
- */
- @Override
- public long skip(long n) throws IOException {
- if (defaultSkip) {
- // use skip method of parent stream
- // may not work if skip not implemented by parent
- long bs = super.skip(n);
- bytesRead += bs;
-
- return bs;
- }
-
- if (n <= 0) {
- return 0;
- }
-
- long left = n;
- byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
-
- while (left > 0) {
- int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
- if (res < 0) {
- break;
- }
- left -= res;
- }
-
- return n - left;
- }
-
- public boolean isDefaultSkip() {
- return defaultSkip;
- }
-
- public void setDefaultSkip(boolean defaultSkip) {
- this.defaultSkip = defaultSkip;
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarOutputStream.java b/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarOutputStream.java
deleted file mode 100755
index ffdfe8756..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarOutputStream.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-
-/**
- * @author Kamran Zafar
- *
- */
-public class TarOutputStream extends OutputStream {
- private final OutputStream out;
- private long bytesWritten;
- private long currentFileSize;
- private TarEntry currentEntry;
-
- public TarOutputStream(OutputStream out) {
- this.out = out;
- bytesWritten = 0;
- currentFileSize = 0;
- }
-
- public TarOutputStream(final File fout) throws FileNotFoundException {
- this.out = new BufferedOutputStream(new FileOutputStream(fout));
- bytesWritten = 0;
- currentFileSize = 0;
- }
-
- /**
- * Opens a file for writing.
- */
- public TarOutputStream(final File fout, final boolean append) throws IOException {
- @SuppressWarnings("resource")
- RandomAccessFile raf = new RandomAccessFile(fout, "rw");
- final long fileSize = fout.length();
- if (append && fileSize > TarConstants.EOF_BLOCK) {
- raf.seek(fileSize - TarConstants.EOF_BLOCK);
- }
- out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
- }
-
- /**
- * Appends the EOF record and closes the stream
- *
- * @see java.io.FilterOutputStream#close()
- */
- @Override
- public void close() throws IOException {
- closeCurrentEntry();
- write( new byte[TarConstants.EOF_BLOCK] );
- out.close();
- }
- /**
- * Writes a byte to the stream and updates byte counters
- *
- * @see java.io.FilterOutputStream#write(int)
- */
- @Override
- public void write(int b) throws IOException {
- out.write( b );
- bytesWritten += 1;
-
- if (currentEntry != null) {
- currentFileSize += 1;
- }
- }
-
- /**
- * Checks if the bytes being written exceed the current entry size.
- *
- * @see java.io.FilterOutputStream#write(byte[], int, int)
- */
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- if (currentEntry != null && !currentEntry.isDirectory()) {
- if (currentEntry.getSize() < currentFileSize + len) {
- throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
- + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
- + "] being written." );
- }
- }
-
- out.write( b, off, len );
-
- bytesWritten += len;
-
- if (currentEntry != null) {
- currentFileSize += len;
- }
- }
-
- /**
- * Writes the next tar entry header on the stream
- *
- * @param entry
- * @throws IOException
- */
- public void putNextEntry(TarEntry entry) throws IOException {
- closeCurrentEntry();
-
- byte[] header = new byte[TarConstants.HEADER_BLOCK];
- entry.writeEntryHeader( header );
-
- write( header );
-
- currentEntry = entry;
- }
-
- /**
- * Closes the current tar entry
- *
- * @throws IOException
- */
- protected void closeCurrentEntry() throws IOException {
- if (currentEntry != null) {
- if (currentEntry.getSize() > currentFileSize) {
- throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
- + currentEntry.getSize() + "] has not been fully written." );
- }
-
- currentEntry = null;
- currentFileSize = 0;
-
- pad();
- }
- }
-
- /**
- * Pads the last content block
- *
- * @throws IOException
- */
- protected void pad() throws IOException {
- if (bytesWritten > 0) {
- int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
-
- if (extra > 0) {
- write( new byte[TarConstants.DATA_BLOCK - extra] );
- }
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarUtils.java b/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarUtils.java
deleted file mode 100755
index 50165765c..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kamranzafar/jtar/TarUtils.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Copyright 2012 Kamran Zafar
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.kamranzafar.jtar;
-
-import java.io.File;
-
-/**
- * @author Kamran
- *
- */
-public class TarUtils {
- /**
- * Determines the tar file size of the given folder/file path
- *
- * @param path
- * @return
- */
- public static long calculateTarSize(File path) {
- return tarSize(path) + TarConstants.EOF_BLOCK;
- }
-
- private static long tarSize(File dir) {
- long size = 0;
-
- if (dir.isFile()) {
- return entrySize(dir.length());
- } else {
- File[] subFiles = dir.listFiles();
-
- if (subFiles != null && subFiles.length > 0) {
- for (File file : subFiles) {
- if (file.isFile()) {
- size += entrySize(file.length());
- } else {
- size += tarSize(file);
- }
- }
- } else {
- // Empty folder header
- return TarConstants.HEADER_BLOCK;
- }
- }
-
- return size;
- }
-
- private static long entrySize(long fileSize) {
- long size = 0;
- size += TarConstants.HEADER_BLOCK; // Header
- size += fileSize; // File size
-
- long extra = size % TarConstants.DATA_BLOCK;
-
- if (extra > 0) {
- size += (TarConstants.DATA_BLOCK - extra); // pad
- }
-
- return size;
- }
-
- public static String trim(String s, char c) {
- StringBuffer tmp = new StringBuffer(s);
- for (int i = 0; i < tmp.length(); i++) {
- if (tmp.charAt(i) != c) {
- break;
- } else {
- tmp.deleteCharAt(i);
- }
- }
-
- for (int i = tmp.length() - 1; i >= 0; i--) {
- if (tmp.charAt(i) != c) {
- break;
- } else {
- tmp.deleteCharAt(i);
- }
- }
-
- return tmp.toString();
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonService.java
deleted file mode 100644
index 9a2900219..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonService.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package org.kivy.android;
-
-import android.os.Build;
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
-import android.app.Service;
-import android.os.IBinder;
-import android.os.Bundle;
-import android.content.Intent;
-import android.content.Context;
-import android.util.Log;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.os.Process;
-
-import org.kivy.android.PythonUtil;
-
-import org.renpy.android.Hardware;
-
-
-public class PythonService extends Service implements Runnable {
-
- // Thread for Python code
- private Thread pythonThread = null;
-
- // Python environment variables
- private String androidPrivate;
- private String androidArgument;
- private String pythonName;
- private String pythonHome;
- private String pythonPath;
- private String serviceEntrypoint;
- // Argument to pass to Python code,
- private String pythonServiceArgument;
- public static PythonService mService = null;
- private Intent startIntent = null;
-
- private boolean autoRestartService = false;
-
- public void setAutoRestartService(boolean restart) {
- autoRestartService = restart;
- }
-
- public boolean canDisplayNotification() {
- return true;
- }
-
- public int startType() {
- return START_NOT_STICKY;
- }
-
- @Override
- public IBinder onBind(Intent arg0) {
- return null;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (pythonThread != null) {
- Log.v("python service", "service exists, do not start again");
- return START_NOT_STICKY;
- }
-
- startIntent = intent;
- Bundle extras = intent.getExtras();
- androidPrivate = extras.getString("androidPrivate");
- androidArgument = extras.getString("androidArgument");
- serviceEntrypoint = extras.getString("serviceEntrypoint");
- pythonName = extras.getString("pythonName");
- pythonHome = extras.getString("pythonHome");
- pythonPath = extras.getString("pythonPath");
- pythonServiceArgument = extras.getString("pythonServiceArgument");
-
- pythonThread = new Thread(this);
- pythonThread.start();
-
- if (canDisplayNotification()) {
- doStartForeground(extras);
- }
-
- return startType();
- }
-
- protected void doStartForeground(Bundle extras) {
- String serviceTitle = extras.getString("serviceTitle");
- String serviceDescription = extras.getString("serviceDescription");
-
- Notification notification;
- Context context = getApplicationContext();
- Intent contextIntent = new Intent(context, PythonActivity.class);
- PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
- notification = new Notification(
- context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis());
- try {
- // prevent using NotificationCompat, this saves 100kb on apk
- Method func = notification.getClass().getMethod(
- "setLatestEventInfo", Context.class, CharSequence.class,
- CharSequence.class, PendingIntent.class);
- func.invoke(notification, context, serviceTitle, serviceDescription, pIntent);
- } catch (NoSuchMethodException | IllegalAccessException |
- IllegalArgumentException | InvocationTargetException e) {
- }
- } else {
- Notification.Builder builder = new Notification.Builder(context);
- builder.setContentTitle(serviceTitle);
- builder.setContentText(serviceDescription);
- builder.setContentIntent(pIntent);
- builder.setSmallIcon(context.getApplicationInfo().icon);
- notification = builder.build();
- }
- startForeground(1, notification);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- pythonThread = null;
- if (autoRestartService && startIntent != null) {
- Log.v("python service", "service restart requested");
- startService(startIntent);
- }
- Process.killProcess(Process.myPid());
- }
-
- @Override
- public void run(){
- PythonUtil.loadLibraries(getFilesDir());
- this.mService = this;
- nativeStart(
- androidPrivate, androidArgument,
- serviceEntrypoint, pythonName,
- pythonHome, pythonPath,
- pythonServiceArgument);
- stopSelf();
- }
-
- // Native part
- public static native void nativeStart(
- String androidPrivate, String androidArgument,
- String serviceEntrypoint, String pythonName,
- String pythonHome, String pythonPath,
- String pythonServiceArgument);
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonUtil.java
deleted file mode 100644
index 9d532b613..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/PythonUtil.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.kivy.android;
-
-import java.io.File;
-
-import android.util.Log;
-
-
-public class PythonUtil {
- private static final String TAG = "PythonUtil";
-
- protected static String[] getLibraries() {
- return new String[] {
- // "SDL2",
- // "SDL2_image",
- // "SDL2_mixer",
- // "SDL2_ttf",
- "python2.7",
- "python3.5m",
- "main"
- };
- }
-
- public static void loadLibraries(File filesDir) {
-
- String filesDirPath = filesDir.getAbsolutePath();
- boolean skippedPython = false;
-
- for (String lib : getLibraries()) {
- try {
- System.loadLibrary(lib);
- } catch(UnsatisfiedLinkError e) {
- if (lib.startsWith("python") && !skippedPython) {
- skippedPython = true;
- continue;
- }
- throw e;
- }
- }
-
- try {
- System.load(filesDirPath + "/lib/python2.7/lib-dynload/_io.so");
- System.load(filesDirPath + "/lib/python2.7/lib-dynload/unicodedata.so");
- } catch(UnsatisfiedLinkError e) {
- Log.v(TAG, "Failed to load _io.so or unicodedata.so...but that's okay.");
- }
-
- try {
- // System.loadLibrary("ctypes");
- System.load(filesDirPath + "/lib/python2.7/lib-dynload/_ctypes.so");
- } catch(UnsatisfiedLinkError e) {
- Log.v(TAG, "Unsatisfied linker when loading ctypes");
- }
-
- Log.v(TAG, "Loaded everything!");
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/concurrency/PythonEvent.java b/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/concurrency/PythonEvent.java
deleted file mode 100644
index 9911356ba..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/concurrency/PythonEvent.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.kivy.android.concurrency;
-
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Created by ryan on 3/28/14.
- */
-public class PythonEvent {
- private final Lock lock = new ReentrantLock();
- private final Condition cond = lock.newCondition();
- private boolean flag = false;
-
- public void set() {
- lock.lock();
- try {
- flag = true;
- cond.signalAll();
- } finally {
- lock.unlock();
- }
- }
-
- public void wait_() throws InterruptedException {
- lock.lock();
- try {
- while (!flag) {
- cond.await();
- }
- } finally {
- lock.unlock();
- }
- }
-
- public void clear() {
- lock.lock();
- try {
- flag = false;
- cond.signalAll();
- } finally {
- lock.unlock();
- }
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/concurrency/PythonLock.java b/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/concurrency/PythonLock.java
deleted file mode 100644
index 22f9d903e..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/kivy/android/concurrency/PythonLock.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.kivy.android.concurrency;
-
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Created by ryan on 3/28/14.
- */
-public class PythonLock {
- private final Lock lock = new ReentrantLock();
-
- public void acquire() {
- lock.lock();
- }
-
- public void release() {
- lock.unlock();
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/PythonActivity.java b/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/PythonActivity.java
deleted file mode 100644
index 0d34d31c9..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/PythonActivity.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.renpy.android;
-
-import android.util.Log;
-
-class PythonActivity extends org.kivy.android.PythonActivity {
- static {
- Log.w("PythonActivity", "Accessing org.renpy.android.PythonActivity "
- + "is deprecated and will be removed in a "
- + "future version. Please switch to "
- + "org.kivy.android.PythonActivity.");
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/PythonService.java b/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/PythonService.java
deleted file mode 100644
index 73febed68..000000000
--- a/pythonforandroid/bootstraps/webview/build/src/org/renpy/android/PythonService.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.renpy.android;
-
-import android.util.Log;
-
-class PythonService extends org.kivy.android.PythonService {
- static {
- Log.w("PythonService", "Accessing org.renpy.android.PythonService "
- + "is deprecated and will be removed in a "
- + "future version. Please switch to "
- + "org.kivy.android.PythonService.");
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml
index 4976120c4..f9e4fa3c6 100644
--- a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml
+++ b/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="{{ args.package }}"
android:versionCode="{{ args.numeric_version }}"
android:versionName="{{ args.version }}"
android:installLocation="auto">
@@ -16,16 +15,12 @@
/>
<!-- Android 2.3.3 -->
- <uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ args.sdk_version }}" />
+ <uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ android_api }}" />
<!-- Allow writing to external storage -->
<uses-permission android:name="android.permission.INTERNET" />
{% for perm in args.permissions %}
- {% if '.' in perm %}
- <uses-permission android:name="{{ perm }}" />
- {% else %}
- <uses-permission android:name="android.permission.{{ perm }}" />
- {% endif %}
+ <uses-permission android:name="{{ perm.name }}"{% if perm.maxSdkVersion %} android:maxSdkVersion="{{ perm.maxSdkVersion }}"{% endif %}{% if perm.usesPermissionFlags %} android:usesPermissionFlags="{{ perm.usesPermissionFlags }}"{% endif %} />
{% endfor %}
{% if args.wakelock %}
@@ -46,19 +41,31 @@
An example Java class can be found in README-android.txt
-->
<application android:label="@string/app_name"
- android:icon="@drawable/icon"
- android:allowBackup="true"
- android:theme="@android:style/Theme.NoTitleBar{% if not args.window %}.Fullscreen{% endif %}"
- android:hardwareAccelerated="true" >
-
+ android:icon="@mipmap/icon"
+ android:allowBackup="{{ args.allow_backup }}"
+ {% if args.backup_rules %}android:fullBackupContent="@xml/{{ args.backup_rules }}"{% endif %}
+ android:theme="{{args.android_apptheme}}{% if not args.window %}.Fullscreen{% endif %}"
+ android:hardwareAccelerated="true"
+ android:usesCleartextTraffic="true"
+ android:extractNativeLibs="true"
+ {% if debug %}android:debuggable="true"{% endif %}
+ >
+ {% for l in args.android_used_libs %}
+ <uses-library android:name="{{ l }}" />
+ {% endfor %}
{% for m in args.meta_data %}
<meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
<meta-data android:name="wakelock" android:value="{% if args.wakelock %}1{% else %}0{% endif %}"/>
<activity android:name="org.kivy.android.PythonActivity"
android:label="@string/app_name"
- android:configChanges="keyboardHidden|orientation{% if args.min_sdk_version >= 13 %}|screenSize{% endif %}"
- android:screenOrientation="{{ args.orientation }}"
+ 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 %}"
+ android:screenOrientation="{{ args.manifest_orientation }}"
+ android:exported="true"
+ {% if args.activity_launch_mode %}
+ android:launchMode="{{ args.activity_launch_mode }}"
+ {% endif %}
+ android:windowSoftInputMode="adjustResize"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -82,7 +89,8 @@
<service android:name="org.kivy.android.billing.BillingReceiver"
android:process=":pythonbilling" />
<receiver android:name="org.kivy.android.billing.BillingReceiver"
- android:process=":pythonbillingreceiver">
+ android:process=":pythonbillingreceiver"
+ android:exported="false">
<intent-filter>
<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
<action android:name="com.android.vending.billing.RESPONSE_CODE" />
diff --git a/pythonforandroid/bootstraps/webview/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/webview/build/templates/Service.tmpl.java
deleted file mode 100644
index e835388ed..000000000
--- a/pythonforandroid/bootstraps/webview/build/templates/Service.tmpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package {{ args.package }};
-
-import android.os.Build;
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
-import android.content.Intent;
-import android.content.Context;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.os.Bundle;
-import org.kivy.android.PythonService;
-import org.kivy.android.PythonActivity;
-
-
-public class Service{{ name|capitalize }} extends PythonService {
- {% if sticky %}
- @Override
- public int startType() {
- return START_STICKY;
- }
- {% endif %}
-
- {% if not foreground %}
- @Override
- public boolean canDisplayNotification() {
- return false;
- }
- {% endif %}
-
- @Override
- protected void doStartForeground(Bundle extras) {
- Notification notification;
- Context context = getApplicationContext();
- Intent contextIntent = new Intent(context, PythonActivity.class);
- PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
- notification = new Notification(
- context.getApplicationInfo().icon, "{{ args.name }}", System.currentTimeMillis());
- try {
- // prevent using NotificationCompat, this saves 100kb on apk
- Method func = notification.getClass().getMethod(
- "setLatestEventInfo", Context.class, CharSequence.class,
- CharSequence.class, PendingIntent.class);
- func.invoke(notification, context, "{{ args.name }}", "{{ name| capitalize }}", pIntent);
- } catch (NoSuchMethodException | IllegalAccessException |
- IllegalArgumentException | InvocationTargetException e) {
- }
- } else {
- Notification.Builder builder = new Notification.Builder(context);
- builder.setContentTitle("{{ args.name }}");
- builder.setContentText("{{ name| capitalize }}");
- builder.setContentIntent(pIntent);
- builder.setSmallIcon(context.getApplicationInfo().icon);
- notification = builder.build();
- }
- startForeground({{ service_id }}, notification);
- }
-
- static public void start(Context ctx, String pythonServiceArgument) {
- Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
- String argument = ctx.getFilesDir().getAbsolutePath();
- intent.putExtra("androidPrivate", argument);
- intent.putExtra("androidArgument", argument);
- intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
- intent.putExtra("pythonName", "{{ name }}");
- intent.putExtra("pythonHome", argument);
- intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
- intent.putExtra("pythonServiceArgument", pythonServiceArgument);
- ctx.startService(intent);
- }
-
- static public void stop(Context ctx) {
- Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
- ctx.stopService(intent);
- }
-}
diff --git a/pythonforandroid/bootstraps/webview/build/templates/WebViewLoader.tmpl.java b/pythonforandroid/bootstraps/webview/build/templates/WebViewLoader.tmpl.java
index fb2583654..5482da847 100644
--- a/pythonforandroid/bootstraps/webview/build/templates/WebViewLoader.tmpl.java
+++ b/pythonforandroid/bootstraps/webview/build/templates/WebViewLoader.tmpl.java
@@ -13,7 +13,7 @@
import org.kivy.android.PythonActivity;
public class WebViewLoader {
- private static final String TAG = "WebViewLoader";
+ private static final String TAG = "WebViewLoader";
public static void testConnection() {
diff --git a/pythonforandroid/bootstraps/webview/build/templates/build.tmpl.xml b/pythonforandroid/bootstraps/webview/build/templates/build.tmpl.xml
deleted file mode 100644
index 9ab301ad9..000000000
--- a/pythonforandroid/bootstraps/webview/build/templates/build.tmpl.xml
+++ /dev/null
@@ -1,95 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This should be changed to the name of your project -->
-<project name="{{ versioned_name }}" default="help">
-
- <!-- The local.properties file is created and updated by the 'android' tool.
- It contains the path to the SDK. It should *NOT* be checked into
- Version Control Systems. -->
- <property file="local.properties" />
-
- <!-- The ant.properties file can be created by you. It is only edited by the
- 'android' tool to add properties to it.
- This is the place to change some Ant specific build properties.
- Here are some properties you may want to change/update:
-
- source.dir
- The name of the source directory. Default is 'src'.
- out.dir
- The name of the output directory. Default is 'bin'.
-
- For other overridable properties, look at the beginning of the rules
- files in the SDK, at tools/ant/build.xml
-
- Properties related to the SDK location or the project target should
- be updated using the 'android' tool with the 'update' action.
-
- This file is an integral part of the build system for your
- application and should be checked into Version Control Systems.
-
- -->
- <property file="ant.properties" />
-
- <!-- if sdk.dir was not set from one of the property file, then
- get it from the ANDROID_HOME env var.
- This must be done before we load project.properties since
- the proguard config can use sdk.dir -->
- <property environment="env" />
- <condition property="sdk.dir" value="${env.ANDROID_HOME}">
- <isset property="env.ANDROID_HOME" />
- </condition>
-
- <property file="build.properties" />
-
- <!-- The project.properties file is created and updated by the 'android'
- tool, as well as ADT.
-
- This contains project specific properties such as project target, and library
- dependencies. Lower level build properties are stored in ant.properties
- (or in .classpath for Eclipse projects).
-
- This file is an integral part of the build system for your
- application and should be checked into Version Control Systems. -->
- <loadproperties srcFile="project.properties" />
-
- <!-- quick check on sdk.dir -->
- <fail
- 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."
- unless="sdk.dir"
- />
-
- <!--
- Import per project custom build rules if present at the root of the project.
- This is the place to put custom intermediary targets such as:
- -pre-build
- -pre-compile
- -post-compile (This is typically used for code obfuscation.
- Compiled code location: ${out.classes.absolute.dir}
- If this is not done in place, override ${out.dex.input.absolute.dir})
- -post-package
- -post-build
- -pre-clean
- -->
- <import file="custom_rules.xml" optional="true" />
-
- <!-- Import the actual build file.
-
- To customize existing targets, there are two options:
- - Customize only one target:
- - copy/paste the target into this file, *before* the
- <import> task.
- - customize it to your needs.
- - Customize the whole content of build.xml
- - copy/paste the content of the rules files (minus the top node)
- into this file, replacing the <import> task.
- - customize to your needs.
-
- ***********************
- ****** IMPORTANT ******
- ***********************
- In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
- in order to avoid having your file be overridden by tools such as "android update project"
- -->
- <!-- version-tag: 1 -->
- <import file="${sdk.dir}/tools/ant/build.xml" />
-
-</project>
diff --git a/pythonforandroid/bootstraps/webview/build/templates/custom_rules.tmpl.xml b/pythonforandroid/bootstraps/webview/build/templates/custom_rules.tmpl.xml
deleted file mode 100644
index 8b2f60c7e..000000000
--- a/pythonforandroid/bootstraps/webview/build/templates/custom_rules.tmpl.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="CustomRules">
- <target name="-pre-build">
- <copy todir="tmp-src">
- <fileset dir="src" includes="**" />
- {% for dir, includes in args.extra_source_dirs %}
- <fileset dir="{{ dir }}" includes="{{ includes }}" />
- {% endfor %}
- </copy>
- </target>
- <target name="-post-build">
- <delete dir="tmp-src" />
- </target>
-</project>
diff --git a/pythonforandroid/bootstraps/webview/build/templates/kivy-icon.png b/pythonforandroid/bootstraps/webview/build/templates/kivy-icon.png
deleted file mode 100644
index 59a00ba6f..000000000
Binary files a/pythonforandroid/bootstraps/webview/build/templates/kivy-icon.png and /dev/null differ
diff --git a/pythonforandroid/bootstraps/webview/build/templates/kivy-presplash.jpg b/pythonforandroid/bootstraps/webview/build/templates/kivy-presplash.jpg
deleted file mode 100644
index 161ebc092..000000000
Binary files a/pythonforandroid/bootstraps/webview/build/templates/kivy-presplash.jpg and /dev/null differ
diff --git a/pythonforandroid/bootstraps/webview/build/templates/strings.tmpl.xml b/pythonforandroid/bootstraps/webview/build/templates/strings.tmpl.xml
index 0bbeb192f..41c20ac66 100644
--- a/pythonforandroid/bootstraps/webview/build/templates/strings.tmpl.xml
+++ b/pythonforandroid/bootstraps/webview/build/templates/strings.tmpl.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">{{ args.name }}</string>
- <string name="private_version">0.1</string>
+ <string name="private_version">{{ private_version }}</string>
+ <string name="presplash_color">{{ args.presplash_color }}</string>
</resources>
diff --git a/pythonforandroid/bootstraps/webview/build/webview_includes/_load.html b/pythonforandroid/bootstraps/webview/build/webview_includes/_load.html
index c3cba2303..1896d63ec 100644
--- a/pythonforandroid/bootstraps/webview/build/webview_includes/_load.html
+++ b/pythonforandroid/bootstraps/webview/build/webview_includes/_load.html
@@ -7,54 +7,10 @@
<title>
Python WebView loader
</title>
-
</head>
-
<body>
-
<div id="load" style="height:100%">
<div class="loader" >Loading...</div>
</div>
-
-
- <div id="result" >
- </div>
-
- <script>
- console.log('called!');
- var resultElem = document.getElementById('result');
-
- function queryFlask() {
-
- var request = new XMLHttpRequest();
- request.open('HEAD', 'http://127.0.0.1:5000/', true);
- request.send();
- request.onerror = function(e) {
- console.log('error!');
- console.log(e);
- resultElem.innerHTML = 'workederr';
- }
- request.onreadystatechange = function() {
- if (request.status == 500) {
- console.log('500!!!!!');
- }
- if (request.readyState == 4 & request.status == 200) {
- resultElem.innerHTML = 'worked';
- console.log('worked');
- } else {
- resultElem.innerHTML = 'failed';
- console.log(request.status);
- console.log('done');
- }
-
-
- }
- }
-
- /* window.setInterval(queryFlask, 500); */
- /* window.setTimeout(function() {location.replace('http://127.0.0.1:5000/')}, 1000) */
- </script>
-
</body>
-
</html>
diff --git a/pythonforandroid/bootstraps/webview/build/whitelist.txt b/pythonforandroid/bootstraps/webview/build/whitelist.txt
deleted file mode 100644
index 41b06ee25..000000000
--- a/pythonforandroid/bootstraps/webview/build/whitelist.txt
+++ /dev/null
@@ -1 +0,0 @@
-# put files here that you need to un-blacklist
diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py
index 9bdb69491..4777e2f93 100644
--- a/pythonforandroid/build.py
+++ b/pythonforandroid/build.py
@@ -1,51 +1,96 @@
-from __future__ import print_function
-
-from os.path import (join, realpath, dirname, expanduser, exists,
- split, isdir)
-from os import environ, listdir
-import os
+from contextlib import suppress
+import copy
import glob
-import sys
+import os
+from os import environ
+from os.path import (
+ abspath, join, realpath, dirname, expanduser, exists
+)
import re
+import shutil
+import subprocess
+
import sh
-from pythonforandroid.util import (ensure_dir, current_directory)
-from pythonforandroid.logger import (info, warning, error, info_notify,
- Err_Fore, Err_Style, info_main,
- shprint)
-from pythonforandroid.archs import ArchARM, ArchARMv7_a, Archx86, Archx86_64, ArchAarch_64
-from pythonforandroid.recipe import Recipe
+from pythonforandroid.androidndk import AndroidNDK
+from pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64
+from pythonforandroid.logger import (info, warning, info_notify, info_main, shprint)
+from pythonforandroid.pythonpackage import get_package_name
+from pythonforandroid.recipe import CythonRecipe, Recipe
+from pythonforandroid.recommendations import (
+ check_ndk_version, check_target_api, check_ndk_api,
+ RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
+from pythonforandroid.util import (
+ current_directory, ensure_dir,
+ BuildInterruptingException, rmdir
+)
+
+
+def get_targets(sdk_dir):
+ if exists(join(sdk_dir, 'cmdline-tools', 'latest', 'bin', 'avdmanager')):
+ avdmanager = sh.Command(join(sdk_dir, 'cmdline-tools', 'latest', 'bin', 'avdmanager'))
+ targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
+
+ elif exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
+ avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
+ targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
+ elif exists(join(sdk_dir, 'tools', 'android')):
+ android = sh.Command(join(sdk_dir, 'tools', 'android'))
+ targets = android('list').stdout.decode('utf-8').split('\n')
+ else:
+ raise BuildInterruptingException(
+ 'Could not find `android` or `sdkmanager` binaries in Android SDK',
+ instructions='Make sure the path to the Android SDK is correct')
+ return targets
+
-DEFAULT_ANDROID_API = 15
+def get_available_apis(sdk_dir):
+ targets = get_targets(sdk_dir)
+ apis = [s for s in targets if re.match(r'^ *API level: ', s)]
+ apis = [re.findall(r'[0-9]+', s) for s in apis]
+ apis = [int(s[0]) for s in apis if s]
+ return apis
-class Context(object):
+class Context:
'''A build context. If anything will be built, an instance this class
will be instantiated and used to hold all the build state.'''
+ # Whether to make a debug or release build
+ build_as_debuggable = False
+
+ # Whether to strip debug symbols in `.so` files
+ with_debug_symbols = False
+
env = environ.copy()
- root_dir = None # the filepath of toolchain.py
- storage_dir = None # the root dir where builds and dists will be stored
+ # the filepath of toolchain.py
+ root_dir = None
+ # the root dir where builds and dists will be stored
+ storage_dir = None
+
+ # in which bootstraps are copied for building
+ # and recipes are built
+ build_dir = None
+
+ distribution = None
+ """The Distribution object representing the current build target location."""
+
+ # the Android project folder where everything ends up
+ dist_dir = None
- build_dir = None # in which bootstraps are copied for building
- # and recipes are built
- dist_dir = None # the Android project folder where everything ends up
- libs_dir = None # where Android libs are cached after build but
- # before being placed in dists
- aars_dir = None
+ # Whether setup.py or similar should be used if present:
+ use_setup_py = False
ccache = None # whether to use ccache
- cython = None # the cython interpreter name
- ndk_platform = None # the ndk platform directory
+ ndk = None
- dist_name = None # should be deprecated in favour of self.dist.dist_name
bootstrap = None
bootstrap_build_dir = None
recipe_build_order = None # Will hold the list of all built recipes
- symlink_java_src = False # If True, will symlink instead of copying during build
+ symlink_bootstrap_files = False # If True, will symlink instead of copying during build
java_build_tool = 'auto'
@@ -60,35 +105,38 @@ def templates_dir(self):
@property
def libs_dir(self):
+ """
+ where Android libs are cached after build
+ but before being placed in dists
+ """
# Was previously hardcoded as self.build_dir/libs
- dir = join(self.build_dir, 'libs_collections',
- self.bootstrap.distribution.name)
- ensure_dir(dir)
- return dir
+ directory = join(self.build_dir, 'libs_collections',
+ self.bootstrap.distribution.name)
+ ensure_dir(directory)
+ return directory
@property
def javaclass_dir(self):
# Was previously hardcoded as self.build_dir/java
- dir = join(self.build_dir, 'javaclasses',
- self.bootstrap.distribution.name)
- ensure_dir(dir)
- return dir
+ directory = join(self.build_dir, 'javaclasses',
+ self.bootstrap.distribution.name)
+ ensure_dir(directory)
+ return directory
@property
def aars_dir(self):
- dir = join(self.build_dir, 'aars', self.bootstrap.distribution.name)
- ensure_dir(dir)
- return dir
+ directory = join(self.build_dir, 'aars', self.bootstrap.distribution.name)
+ ensure_dir(directory)
+ return directory
@property
def python_installs_dir(self):
- dir = join(self.build_dir, 'python-installs')
- ensure_dir(dir)
- return dir
+ directory = join(self.build_dir, 'python-installs')
+ ensure_dir(directory)
+ return directory
- def get_python_install_dir(self):
- dir = join(self.python_installs_dir, self.bootstrap.distribution.name)
- return dir
+ def get_python_install_dir(self, arch):
+ return join(self.python_installs_dir, self.bootstrap.distribution.name, arch)
def setup_dirs(self, storage_dir):
'''Calculates all the storage and build dirs, and makes sure
@@ -121,17 +169,17 @@ def android_api(self, value):
self._android_api = value
@property
- def ndk_ver(self):
- '''The version of the NDK being used for compilation.'''
- if self._ndk_ver is None:
- raise ValueError('Tried to access ndk_ver but it has not '
+ def ndk_api(self):
+ '''The API number compile against'''
+ if self._ndk_api is None:
+ raise ValueError('Tried to access ndk_api but it has not '
'been set - this should not happen, something '
'went wrong!')
- return self._ndk_ver
+ return self._ndk_api
- @ndk_ver.setter
- def ndk_ver(self, value):
- self._ndk_ver = value
+ @ndk_api.setter
+ def ndk_api(self, value):
+ self._ndk_api = value
@property
def sdk_dir(self):
@@ -159,8 +207,11 @@ def ndk_dir(self):
def ndk_dir(self, value):
self._ndk_dir = value
- def prepare_build_environment(self, user_sdk_dir, user_ndk_dir,
- user_android_api, user_ndk_ver):
+ def prepare_build_environment(self,
+ user_sdk_dir,
+ user_ndk_dir,
+ user_android_api,
+ user_ndk_api):
'''Checks that build dependencies exist and sets internal variables
for the Android SDK etc.
@@ -173,269 +224,141 @@ def prepare_build_environment(self, user_sdk_dir, user_ndk_dir,
if self._build_env_prepared:
return
- ok = True
-
# Work out where the Android SDK is
sdk_dir = None
if user_sdk_dir:
sdk_dir = user_sdk_dir
- if sdk_dir is None: # This is the old P4A-specific var
+ # This is the old P4A-specific var
+ if sdk_dir is None:
sdk_dir = environ.get('ANDROIDSDK', None)
- if sdk_dir is None: # This seems used more conventionally
+ # This seems used more conventionally
+ if sdk_dir is None:
sdk_dir = environ.get('ANDROID_HOME', None)
- if sdk_dir is None: # Checks in the buildozer SDK dir, useful
- # for debug tests of p4a
+ # Checks in the buildozer SDK dir, useful for debug tests of p4a
+ if sdk_dir is None:
possible_dirs = glob.glob(expanduser(join(
'~', '.buildozer', 'android', 'platform', 'android-sdk-*')))
possible_dirs = [d for d in possible_dirs if not
- (d.endswith('.bz2') or d.endswith('.gz'))]
+ d.endswith(('.bz2', '.gz'))]
if possible_dirs:
info('Found possible SDK dirs in buildozer dir: {}'.format(
- ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
+ ', '.join(d.split(os.sep)[-1] for d in possible_dirs)))
info('Will attempt to use SDK at {}'.format(possible_dirs[0]))
warning('This SDK lookup is intended for debug only, if you '
'use python-for-android much you should probably '
'maintain your own SDK download.')
sdk_dir = possible_dirs[0]
if sdk_dir is None:
- warning('Android SDK dir was not specified, exiting.')
- exit(1)
+ raise BuildInterruptingException('Android SDK dir was not specified, exiting.')
self.sdk_dir = realpath(sdk_dir)
# Check what Android API we're using
android_api = None
if user_android_api:
android_api = user_android_api
- if android_api is not None:
- info('Getting Android API version from user argument')
- if android_api is None:
- android_api = environ.get('ANDROIDAPI', None)
- if android_api is not None:
- info('Found Android API target in $ANDROIDAPI')
- if android_api is None:
+ info('Getting Android API version from user argument: {}'.format(android_api))
+ elif 'ANDROIDAPI' in environ:
+ android_api = environ['ANDROIDAPI']
+ info('Found Android API target in $ANDROIDAPI: {}'.format(android_api))
+ else:
info('Android API target was not set manually, using '
- 'the default of {}'.format(DEFAULT_ANDROID_API))
- android_api = DEFAULT_ANDROID_API
+ 'the default of {}'.format(RECOMMENDED_TARGET_API))
+ android_api = RECOMMENDED_TARGET_API
android_api = int(android_api)
self.android_api = android_api
- if self.android_api >= 21 and self.archs[0].arch == 'armeabi':
- error('Asked to build for armeabi architecture with API '
- '{}, but API 21 or greater does not support armeabi'.format(
- self.android_api))
- error('You probably want to build with --arch=armeabi-v7a instead')
- exit(1)
-
- if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
- avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
- targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
- elif exists(join(sdk_dir, 'tools', 'android')):
- android = sh.Command(join(sdk_dir, 'tools', 'android'))
- targets = android('list').stdout.decode('utf-8').split('\n')
- else:
- error('Could not find `android` or `sdkmanager` binaries in '
- 'Android SDK. Exiting.')
- exit(1)
- apis = [s for s in targets if re.match(r'^ *API level: ', s)]
- apis = [re.findall(r'[0-9]+', s) for s in apis]
- apis = [int(s[0]) for s in apis if s]
+ for arch in self.archs:
+ # Maybe We could remove this one in a near future (ARMv5 is definitely old)
+ check_target_api(android_api, arch)
+ apis = get_available_apis(self.sdk_dir)
info('Available Android APIs are ({})'.format(
', '.join(map(str, apis))))
if android_api in apis:
info(('Requested API target {} is available, '
'continuing.').format(android_api))
else:
- warning(('Requested API target {} is not available, install '
- 'it with the SDK android tool.').format(android_api))
- warning('Exiting.')
- exit(1)
+ raise BuildInterruptingException(
+ ('Requested API target {} is not available, install '
+ 'it with the SDK android tool.').format(android_api))
# Find the Android NDK
# Could also use ANDROID_NDK, but doesn't look like many tools use this
ndk_dir = None
if user_ndk_dir:
ndk_dir = user_ndk_dir
- if ndk_dir is not None:
- info('Getting NDK dir from from user argument')
+ info('Getting NDK dir from from user argument')
if ndk_dir is None: # The old P4A-specific dir
ndk_dir = environ.get('ANDROIDNDK', None)
if ndk_dir is not None:
- info('Found NDK dir in $ANDROIDNDK')
+ info('Found NDK dir in $ANDROIDNDK: {}'.format(ndk_dir))
if ndk_dir is None: # Apparently the most common convention
ndk_dir = environ.get('NDK_HOME', None)
if ndk_dir is not None:
- info('Found NDK dir in $NDK_HOME')
+ info('Found NDK dir in $NDK_HOME: {}'.format(ndk_dir))
if ndk_dir is None: # Another convention (with maven?)
ndk_dir = environ.get('ANDROID_NDK_HOME', None)
if ndk_dir is not None:
- info('Found NDK dir in $ANDROID_NDK_HOME')
+ info('Found NDK dir in $ANDROID_NDK_HOME: {}'.format(ndk_dir))
if ndk_dir is None: # Checks in the buildozer NDK dir, useful
# # for debug tests of p4a
possible_dirs = glob.glob(expanduser(join(
'~', '.buildozer', 'android', 'platform', 'android-ndk-r*')))
if possible_dirs:
info('Found possible NDK dirs in buildozer dir: {}'.format(
- ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
+ ', '.join(d.split(os.sep)[-1] for d in possible_dirs)))
info('Will attempt to use NDK at {}'.format(possible_dirs[0]))
warning('This NDK lookup is intended for debug only, if you '
'use python-for-android much you should probably '
'maintain your own NDK download.')
ndk_dir = possible_dirs[0]
if ndk_dir is None:
- warning('Android NDK dir was not specified, exiting.')
- exit(1)
+ raise BuildInterruptingException('Android NDK dir was not specified')
self.ndk_dir = realpath(ndk_dir)
+ check_ndk_version(ndk_dir)
+
+ ndk_api = None
+ if user_ndk_api:
+ ndk_api = user_ndk_api
+ info('Getting NDK API version (i.e. minimum supported API) from user argument')
+ elif 'NDKAPI' in environ:
+ ndk_api = environ.get('NDKAPI', None)
+ info('Found Android API target in $NDKAPI')
+ else:
+ ndk_api = min(self.android_api, RECOMMENDED_NDK_API)
+ warning('NDK API target was not set manually, using '
+ 'the default of {} = min(android-api={}, default ndk-api={})'.format(
+ ndk_api, self.android_api, RECOMMENDED_NDK_API))
+ ndk_api = int(ndk_api)
+ self.ndk_api = ndk_api
- # Find the NDK version, and check it against what the NDK dir
- # seems to report
- ndk_ver = None
- if user_ndk_ver:
- ndk_ver = user_ndk_ver
- if ndk_dir is not None:
- info('Got NDK version from from user argument')
- if ndk_ver is None:
- ndk_ver = environ.get('ANDROIDNDKVER', None)
- if ndk_dir is not None:
- info('Got NDK version from $ANDROIDNDKVER')
-
- self.ndk = 'google'
+ check_ndk_api(ndk_api, self.android_api)
- try:
- with open(join(ndk_dir, 'RELEASE.TXT')) as fileh:
- reported_ndk_ver = fileh.read().split(' ')[0].strip()
- except IOError:
- pass
- else:
- if reported_ndk_ver.startswith('crystax-ndk-'):
- reported_ndk_ver = reported_ndk_ver[12:]
- self.ndk = 'crystax'
- if ndk_ver is None:
- ndk_ver = reported_ndk_ver
- info(('Got Android NDK version from the NDK dir: '
- 'it is {}').format(ndk_ver))
- else:
- if ndk_ver != reported_ndk_ver:
- warning('NDK version was set as {}, but checking '
- 'the NDK dir claims it is {}.'.format(
- ndk_ver, reported_ndk_ver))
- warning('The build will try to continue, but it may '
- 'fail and you should check '
- 'that your setting is correct.')
- warning('If the NDK dir result is correct, you don\'t '
- 'need to manually set the NDK ver.')
- if ndk_ver is None:
- warning('Android NDK version could not be found. This probably'
- 'won\'t cause any problems, but if necessary you can'
- 'set it with `--ndk-version=...`.')
- self.ndk_ver = ndk_ver
-
- info('Using {} NDK {}'.format(self.ndk.capitalize(), self.ndk_ver))
-
- virtualenv = None
- if virtualenv is None:
- virtualenv = sh.which('virtualenv2')
- if virtualenv is None:
- virtualenv = sh.which('virtualenv-2.7')
- if virtualenv is None:
- virtualenv = sh.which('virtualenv')
- if virtualenv is None:
- raise IOError('Couldn\'t find a virtualenv executable, '
- 'you must install this to use p4a.')
- self.virtualenv = virtualenv
- info('Found virtualenv at {}'.format(virtualenv))
+ self.ndk = AndroidNDK(self.ndk_dir)
# path to some tools
- self.ccache = sh.which("ccache")
+ self.ccache = shutil.which("ccache")
if not self.ccache:
info('ccache is missing, the build will not be optimized in the '
'future.')
- for cython_fn in ("cython2", "cython-2.7", "cython"):
- cython = sh.which(cython_fn)
- if cython:
- self.cython = cython
- break
- else:
- error('No cython binary found. Exiting.')
- exit(1)
- if not self.cython:
- ok = False
- warning("Missing requirement: cython is not installed")
-
- # This would need to be changed if supporting multiarch APKs
- arch = self.archs[0]
- platform_dir = arch.platform_dir
- toolchain_prefix = arch.toolchain_prefix
- toolchain_version = None
- self.ndk_platform = join(
- self.ndk_dir,
- 'platforms',
- 'android-{}'.format(self.android_api),
- platform_dir)
- if not exists(self.ndk_platform):
- warning('ndk_platform doesn\'t exist: {}'.format(
- self.ndk_platform))
- ok = False
-
- py_platform = sys.platform
- if py_platform in ['linux2', 'linux3']:
- py_platform = 'linux'
-
- toolchain_versions = []
- toolchain_path = join(self.ndk_dir, 'toolchains')
- if os.path.isdir(toolchain_path):
- toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
- toolchain_prefix))
- toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
- for path in toolchain_contents]
- else:
- warning('Could not find toolchain subdirectory!')
- ok = False
- toolchain_versions.sort()
-
- toolchain_versions_gcc = []
- for toolchain_version in toolchain_versions:
- if toolchain_version[0].isdigit():
- # GCC toolchains begin with a number
- toolchain_versions_gcc.append(toolchain_version)
-
- if toolchain_versions:
- info('Found the following toolchain versions: {}'.format(
- toolchain_versions))
- info('Picking the latest gcc toolchain, here {}'.format(
- toolchain_versions_gcc[-1]))
- toolchain_version = toolchain_versions_gcc[-1]
- else:
- warning('Could not find any toolchain for {}!'.format(
- toolchain_prefix))
- ok = False
-
- self.toolchain_prefix = toolchain_prefix
- self.toolchain_version = toolchain_version
- # Modify the path so that sh finds modules appropriately
- environ['PATH'] = (
- '{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/'
- 'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/'
- '{toolchain_prefix}-{toolchain_version}/prebuilt/'
- '{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/'
- 'tools:{path}').format(
- sdk_dir=self.sdk_dir, ndk_dir=self.ndk_dir,
- toolchain_prefix=toolchain_prefix,
- toolchain_version=toolchain_version,
- py_platform=py_platform, path=environ.get('PATH'))
-
- for executable in ("pkg-config", "autoconf", "automake", "libtoolize",
- "tar", "bzip2", "unzip", "make", "gcc", "g++"):
- if not sh.which(executable):
- warning("Missing executable: {} is not installed".format(
- executable))
-
- if not ok:
- error('{}python-for-android cannot continue; aborting{}'.format(
- Err_Fore.RED, Err_Fore.RESET))
- sys.exit(1)
+ try:
+ subprocess.check_output([
+ "python3", "-m", "cython", "--help",
+ ])
+ except subprocess.CalledProcessError:
+ warning('Cython for python3 missing. If you are building for '
+ ' a python 3 target (which is the default)'
+ ' then THINGS WILL BREAK.')
+
+ self.env["PATH"] = ":".join(
+ [
+ self.ndk.llvm_bin_dir,
+ self.ndk_dir,
+ f"{self.sdk_dir}/tools",
+ environ.get("PATH"),
+ ]
+ )
def __init__(self):
- super(Context, self).__init__()
self.include_dirs = []
self._build_env_prepared = False
@@ -443,20 +366,21 @@ def __init__(self):
self._sdk_dir = None
self._ndk_dir = None
self._android_api = None
- self._ndk_ver = None
+ self._ndk_api = None
self.ndk = None
- self.toolchain_prefix = None
- self.toolchain_version = None
-
self.local_recipes = None
self.copy_libs = False
+ self.activity_class_name = u'org.kivy.android.PythonActivity'
+ self.service_class_name = u'org.kivy.android.PythonService'
+
# this list should contain all Archs, it is pruned later
self.archs = (
ArchARM(self),
ArchARMv7_a(self),
Archx86(self),
+ Archx86_64(self),
ArchAarch_64(self),
)
@@ -478,33 +402,26 @@ def set_archs(self, arch_names):
new_archs.add(match)
self.archs = list(new_archs)
if not self.archs:
- warning('Asked to compile for no Archs, so failing.')
- exit(1)
+ raise BuildInterruptingException('Asked to compile for no Archs, so failing.')
info('Will compile for the following archs: {}'.format(
- ', '.join([arch.arch for arch in self.archs])))
+ ', '.join(arch.arch for arch in self.archs)))
- def prepare_bootstrap(self, bs):
- bs.ctx = self
- self.bootstrap = bs
+ def prepare_bootstrap(self, bootstrap):
+ if not bootstrap:
+ raise TypeError("None is not allowed for bootstrap")
+ bootstrap.ctx = self
+ self.bootstrap = bootstrap
self.bootstrap.prepare_build_dir()
self.bootstrap_build_dir = self.bootstrap.build_dir
- def prepare_dist(self, name):
- self.dist_name = name
- self.bootstrap.prepare_dist_dir(self.dist_name)
+ def prepare_dist(self):
+ self.bootstrap.prepare_dist_dir()
- def get_site_packages_dir(self, arch=None):
+ def get_site_packages_dir(self, arch):
'''Returns the location of site-packages in the python-install build
dir.
'''
-
- # This needs to be replaced with something more general in
- # order to support multiple python versions and/or multiple
- # archs.
- if self.python_recipe.from_crystax:
- return self.get_python_install_dir()
- return join(self.get_python_install_dir(),
- 'lib', 'python2.7', 'site-packages')
+ return self.get_python_install_dir(arch.arch)
def get_libs_dir(self, arch):
'''The libs dir for a given arch.'''
@@ -515,9 +432,22 @@ def has_lib(self, arch, lib):
return exists(join(self.get_libs_dir(arch), lib))
def has_package(self, name, arch=None):
+ # If this is a file path, it'll need special handling:
+ if (name.find("/") >= 0 or name.find("\\") >= 0) and \
+ name.find("://") < 0: # (:// would indicate an url)
+ if not os.path.exists(name):
+ # Non-existing dir, cannot look this up.
+ return False
+ try:
+ name = get_package_name(os.path.abspath(name))
+ except ValueError:
+ # Failed to look up any meaningful name.
+ return False
+
+ # Try to look up recipe by name:
try:
recipe = Recipe.get_recipe(name, self)
- except IOError:
+ except ValueError:
pass
else:
name = getattr(recipe, 'site_packages_name', None) or name
@@ -526,7 +456,6 @@ def has_package(self, name, arch=None):
return (exists(join(site_packages_dir, name)) or
exists(join(site_packages_dir, name + '.py')) or
exists(join(site_packages_dir, name + '.pyc')) or
- exists(join(site_packages_dir, name + '.pyo')) or
exists(join(site_packages_dir, name + '.so')) or
glob.glob(join(site_packages_dir, name + '-*.egg')))
@@ -534,9 +463,10 @@ def not_has_package(self, name, arch=None):
return not self.has_package(name, arch)
-def build_recipes(build_order, python_modules, ctx):
+def build_recipes(build_order, python_modules, ctx, project_dir,
+ ignore_project_setup_py=False
+ ):
# Put recipes in correct build order
- bs = ctx.bootstrap
info_notify("Recipe build order is {}".format(build_order))
if python_modules:
python_modules = sorted(set(python_modules))
@@ -575,13 +505,17 @@ def build_recipes(build_order, python_modules, ctx):
else:
info('{} said it is already built, skipping'
.format(recipe.name))
+ recipe.install_libraries(arch)
# 4) biglink everything
info_main('# Biglinking object files')
- if not ctx.python_recipe or not ctx.python_recipe.from_crystax:
+ if not ctx.python_recipe:
biglink(ctx, arch)
else:
- info('NDK is crystax, skipping biglink (will this work?)')
+ warning(
+ "Context's python recipe found, "
+ "skipping biglink (will this work?)"
+ )
# 5) postbuild packages
info_main('# Postbuilding recipes')
@@ -590,48 +524,255 @@ def build_recipes(build_order, python_modules, ctx):
recipe.postbuild_arch(arch)
info_main('# Installing pure Python modules')
- run_pymodules_install(ctx, python_modules)
+ for arch in ctx.archs:
+ run_pymodules_install(
+ ctx, arch, python_modules, project_dir,
+ ignore_setup_py=ignore_project_setup_py
+ )
+
+
+def project_has_setup_py(project_dir):
+ return (project_dir is not None and
+ (exists(join(project_dir, "setup.py")) or
+ exists(join(project_dir, "pyproject.toml"))
+ ))
+
+
+def run_setuppy_install(ctx, project_dir, env=None, arch=None):
+ env = env or {}
+
+ with current_directory(project_dir):
+ info('got setup.py or similar, running project install. ' +
+ '(disable this behavior with --ignore-setup-py)')
+
+ # Compute & output the constraints we will use:
+ info('Contents that will be used for constraints.txt:')
+ constraints = subprocess.check_output([
+ join(
+ ctx.build_dir, "venv", "bin", "pip"
+ ),
+ "freeze"
+ ], env=copy.copy(env))
+ with suppress(AttributeError):
+ constraints = constraints.decode("utf-8", "replace")
+ info(constraints)
+
+ # Make sure all packages found are fixed in version
+ # by writing a constraint file, to avoid recipes being
+ # upgraded & reinstalled:
+ with open('._tmp_p4a_recipe_constraints.txt', 'wb') as fileh:
+ fileh.write(constraints.encode("utf-8", "replace"))
+ try:
- return
+ info('Populating venv\'s site-packages with '
+ 'ctx.get_site_packages_dir()...')
+
+ # Copy dist contents into site-packages for discovery.
+ # Why this is needed:
+ # --target is somewhat evil and messes with discovery of
+ # packages in PYTHONPATH if that also includes the target
+ # folder. So we need to use the regular virtualenv
+ # site-packages folder instead.
+ # Reference:
+ # https://github.com/pypa/pip/issues/6223
+ ctx_site_packages_dir = os.path.normpath(
+ os.path.abspath(ctx.get_site_packages_dir(arch))
+ )
+ venv_site_packages_dir = os.path.normpath(os.path.join(
+ ctx.build_dir, "venv", "lib", [
+ f for f in os.listdir(os.path.join(
+ ctx.build_dir, "venv", "lib"
+ )) if f.startswith("python")
+ ][0], "site-packages"
+ ))
+ copied_over_contents = []
+ for f in os.listdir(ctx_site_packages_dir):
+ full_path = os.path.join(ctx_site_packages_dir, f)
+ if not os.path.exists(os.path.join(
+ venv_site_packages_dir, f
+ )):
+ if os.path.isdir(full_path):
+ shutil.copytree(full_path, os.path.join(
+ venv_site_packages_dir, f
+ ))
+ else:
+ shutil.copy2(full_path, os.path.join(
+ venv_site_packages_dir, f
+ ))
+ copied_over_contents.append(f)
+
+ # Get listing of virtualenv's site-packages, to see the
+ # newly added things afterwards & copy them back into
+ # the distribution folder / build context site-packages:
+ previous_venv_contents = os.listdir(
+ venv_site_packages_dir
+ )
+ # Actually run setup.py:
+ info('Launching package install...')
+ shprint(sh.bash, '-c', (
+ "'" + join(
+ ctx.build_dir, "venv", "bin", "pip"
+ ).replace("'", "'\"'\"'") + "' " +
+ "install -c ._tmp_p4a_recipe_constraints.txt -v ."
+ ).format(ctx.get_site_packages_dir(arch).
+ replace("'", "'\"'\"'")),
+ _env=copy.copy(env))
+
+ # Go over all new additions and copy them back:
+ info('Copying additions resulting from setup.py back '
+ 'into ctx.get_site_packages_dir()...')
+ new_venv_additions = []
+ for f in (set(os.listdir(venv_site_packages_dir)) -
+ set(previous_venv_contents)):
+ new_venv_additions.append(f)
+ full_path = os.path.join(venv_site_packages_dir, f)
+ if os.path.isdir(full_path):
+ shutil.copytree(full_path, os.path.join(
+ ctx_site_packages_dir, f
+ ))
+ else:
+ shutil.copy2(full_path, os.path.join(
+ ctx_site_packages_dir, f
+ ))
+
+ # Undo all the changes we did to the venv-site packages:
+ info('Reverting additions to '
+ 'virtualenv\'s site-packages...')
+ for f in set(copied_over_contents + new_venv_additions):
+ full_path = os.path.join(venv_site_packages_dir, f)
+ if os.path.isdir(full_path):
+ rmdir(full_path)
+ else:
+ os.remove(full_path)
+ finally:
+ os.remove("._tmp_p4a_recipe_constraints.txt")
-def run_pymodules_install(ctx, modules):
- modules = list(filter(ctx.not_has_package, modules))
- if not modules:
- info('There are no Python modules to install, skipping')
- return
+def run_pymodules_install(ctx, arch, modules, project_dir=None,
+ ignore_setup_py=False):
+ """ This function will take care of all non-recipe things, by:
- info('The requirements ({}) don\'t have recipes, attempting to install '
- 'them with pip'.format(', '.join(modules)))
- info('If this fails, it may mean that the module has compiled '
- 'components and needs a recipe.')
+ 1. Processing them from --requirements (the modules argument)
+ and installing them
- venv = sh.Command(ctx.virtualenv)
+ 2. Installing the user project/app itself via setup.py if
+ ignore_setup_py=True
+
+ """
+
+ info('*** PYTHON PACKAGE / PROJECT INSTALL STAGE FOR ARCH: {} ***'.format(arch))
+
+ modules = [m for m in modules if ctx.not_has_package(m, arch)]
+
+ # We change current working directory later, so this has to be an absolute
+ # path or `None` in case that we didn't supply the `project_dir` via kwargs
+ project_dir = abspath(project_dir) if project_dir else None
+
+ # Bail out if no python deps and no setup.py to process:
+ if not modules and (
+ ignore_setup_py or
+ project_dir is None or
+ not project_has_setup_py(project_dir)
+ ):
+ info('No Python modules and no setup.py to process, skipping')
+ return
+
+ # Output messages about what we're going to do:
+ if modules:
+ info(
+ "The requirements ({}) don\'t have recipes, attempting to "
+ "install them with pip".format(', '.join(modules))
+ )
+ info(
+ "If this fails, it may mean that the module has compiled "
+ "components and needs a recipe."
+ )
+ if project_dir is not None and \
+ project_has_setup_py(project_dir) and not ignore_setup_py:
+ info(
+ "Will process project install, if it fails then the "
+ "project may not be compatible for Android install."
+ )
+
+ # Use our hostpython to create the virtualenv
+ host_python = sh.Command(ctx.hostpython)
with current_directory(join(ctx.build_dir)):
- shprint(venv, '--python=python2.7', 'venv')
-
- info('Creating a requirements.txt file for the Python modules')
- with open('requirements.txt', 'w') as fileh:
- for module in modules:
- key = 'VERSION_' + module
- if key in environ:
- line = '{}=={}\n'.format(module, environ[key])
- else:
- line = '{}\n'.format(module)
- fileh.write(line)
+ shprint(host_python, '-m', 'venv', 'venv')
- info('Installing Python modules with pip')
- info('If this fails with a message about /bin/false, this '
- 'probably means the package cannot be installed with '
- 'pip as it needs a compilation recipe.')
+ # Prepare base environment and upgrade pip:
+ base_env = dict(copy.copy(os.environ))
+ base_env["PYTHONPATH"] = ctx.get_site_packages_dir(arch)
+ info('Upgrade pip to latest version')
+ shprint(sh.bash, '-c', (
+ "source venv/bin/activate && pip install -U pip"
+ ), _env=copy.copy(base_env))
- # This bash method is what old-p4a used
- # It works but should be replaced with something better
+ # Install Cython in case modules need it to build:
+ info('Install Cython in case one of the modules needs it to build')
shprint(sh.bash, '-c', (
- "source venv/bin/activate && env CC=/bin/false CXX=/bin/false "
- "PYTHONPATH={0} pip install --target '{0}' --no-deps -r requirements.txt"
- ).format(ctx.get_site_packages_dir()))
+ "venv/bin/pip install Cython"
+ ), _env=copy.copy(base_env))
+
+ # Get environment variables for build (with CC/compiler set):
+ standard_recipe = CythonRecipe()
+ standard_recipe.ctx = ctx
+ # (note: following line enables explicit -lpython... linker options)
+ standard_recipe.call_hostpython_via_targetpython = False
+ recipe_env = standard_recipe.get_recipe_env(ctx.archs[0])
+ env = copy.copy(base_env)
+ env.update(recipe_env)
+
+ # Make sure our build package dir is available, and the virtualenv
+ # site packages come FIRST (so the proper pip version is used):
+ env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir(arch)
+ env["PYTHONPATH"] = os.path.abspath(join(
+ ctx.build_dir, "venv", "lib",
+ "python" + ctx.python_recipe.major_minor_version_string,
+ "site-packages")) + ":" + env["PYTHONPATH"]
+
+ # Install the manually specified requirements first:
+ if not modules:
+ info('There are no Python modules to install, skipping')
+ else:
+ info('Creating a requirements.txt file for the Python modules')
+ with open('requirements.txt', 'w') as fileh:
+ for module in modules:
+ key = 'VERSION_' + module
+ if key in environ:
+ line = '{}=={}\n'.format(module, environ[key])
+ else:
+ line = '{}\n'.format(module)
+ fileh.write(line)
+
+ info('Installing Python modules with pip')
+ info(
+ "IF THIS FAILS, THE MODULES MAY NEED A RECIPE. "
+ "A reason for this is often modules compiling "
+ "native code that is unaware of Android cross-compilation "
+ "and does not work without additional "
+ "changes / workarounds."
+ )
+
+ shprint(sh.bash, '-c', (
+ "venv/bin/pip " +
+ "install -v --target '{0}' --no-deps -r requirements.txt"
+ ).format(ctx.get_site_packages_dir(arch).replace("'", "'\"'\"'")),
+ _env=copy.copy(env))
+
+ # Afterwards, run setup.py if present:
+ if project_dir is not None and (
+ project_has_setup_py(project_dir) and not ignore_setup_py
+ ):
+ run_setuppy_install(ctx, project_dir, env, arch.arch)
+ elif not ignore_setup_py:
+ info("No setup.py found in project directory: " + str(project_dir))
+
+ # Strip object files after potential Cython or native code builds:
+ if not ctx.with_debug_symbols:
+ standard_recipe.strip_object_files(
+ arch, env, build_dir=ctx.build_dir
+ )
def biglink(ctx, arch):
@@ -670,7 +811,7 @@ def biglink(ctx, arch):
# Move to the directory containing crtstart_so.o and crtend_so.o
# This is necessary with newer NDKs? A gcc bug?
- with current_directory(join(ctx.ndk_platform, 'usr', 'lib')):
+ with current_directory(arch.ndk_lib_dir):
do_biglink(
join(ctx.get_libs_dir(arch.arch), 'libpymodules.so'),
obj_dir.split(' '),
@@ -680,7 +821,9 @@ def biglink(ctx, arch):
env=env)
-def biglink_function(soname, objs_paths, extra_link_dirs=[], env=None):
+def biglink_function(soname, objs_paths, extra_link_dirs=None, env=None):
+ if extra_link_dirs is None:
+ extra_link_dirs = []
print('objs_paths are', objs_paths)
sofiles = []
@@ -727,7 +870,9 @@ def biglink_function(soname, objs_paths, extra_link_dirs=[], env=None):
shprint(cc, '-shared', '-O3', '-o', soname, *unique_args, _env=env)
-def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None):
+def copylibs_function(soname, objs_paths, extra_link_dirs=None, env=None):
+ if extra_link_dirs is None:
+ extra_link_dirs = []
print('objs_paths are', objs_paths)
re_needso = re.compile(r'^.*\(NEEDED\)\s+Shared library: \[lib(.*)\.so\]\s*$')
@@ -759,7 +904,7 @@ def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None):
elif 'READELF' in os.environ:
readelf = os.environ['READELF']
else:
- readelf = sh.which('readelf').strip()
+ readelf = shutil.which('readelf').strip()
readelf = sh.Command(readelf).bake('-d')
dest = dirname(soname)
@@ -843,8 +988,8 @@ def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None):
if needso:
lib = needso.group(1)
if (lib not in needed_libs
- and lib not in found_libs
- and lib not in blacklist_libs):
+ and lib not in found_libs
+ and lib not in blacklist_libs):
needed_libs.append(needso.group(1))
sofiles += found_sofiles
@@ -855,5 +1000,4 @@ def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None):
'\n\t'.join(needed_libs))
print('Copying libraries')
- for lib in sofiles:
- shprint(sh.cp, lib, dest)
+ shprint(sh.cp, *sofiles, dest)
diff --git a/pythonforandroid/checkdependencies.py b/pythonforandroid/checkdependencies.py
new file mode 100644
index 000000000..c53115de7
--- /dev/null
+++ b/pythonforandroid/checkdependencies.py
@@ -0,0 +1,70 @@
+from importlib import import_module
+from os import environ
+import sys
+
+from packaging.version import Version
+
+from pythonforandroid.prerequisites import (
+ check_and_install_default_prerequisites,
+)
+
+
+def check_python_dependencies():
+ """
+ Check if the Python requirements are installed. This must appears
+ before other imports because otherwise they're imported elsewhere.
+
+ Using the ok check instead of failing immediately so that all
+ errors are printed at once.
+ """
+
+ ok = True
+
+ modules = [("colorama", "0.3.3"), "appdirs", ("sh", "1.10"), "jinja2"]
+
+ for module in modules:
+ if isinstance(module, tuple):
+ module, version = module
+ else:
+ version = None
+
+ try:
+ import_module(module)
+ except ImportError:
+ if version is None:
+ print(
+ "ERROR: The {} Python module could not be found, please "
+ "install it.".format(module)
+ )
+ ok = False
+ else:
+ print(
+ "ERROR: The {} Python module could not be found, "
+ "please install version {} or higher".format(
+ module, version
+ )
+ )
+ ok = False
+ else:
+ if version is None:
+ continue
+ try:
+ cur_ver = sys.modules[module].__version__
+ except AttributeError: # this is sometimes not available
+ continue
+ if Version(cur_ver) < Version(version):
+ print(
+ "ERROR: {} version is {}, but python-for-android needs "
+ "at least {}.".format(module, cur_ver, version)
+ )
+ ok = False
+
+ if not ok:
+ print("python-for-android is exiting due to the errors logged above")
+ exit(1)
+
+
+def check():
+ if not environ.get("SKIP_PREREQUISITES_CHECK", "0") == "1":
+ check_and_install_default_prerequisites()
+ check_python_dependencies()
diff --git a/pythonforandroid/distribution.py b/pythonforandroid/distribution.py
index 371e35e04..c878e0ea8 100644
--- a/pythonforandroid/distribution.py
+++ b/pythonforandroid/distribution.py
@@ -1,13 +1,14 @@
-from os.path import exists, join
-import glob
import json
+import glob
+from os.path import exists, join
-from pythonforandroid.logger import (info, info_notify, warning,
- Err_Style, Err_Fore)
-from pythonforandroid.util import current_directory
+from pythonforandroid.logger import (
+ debug, info, info_notify, warning, Err_Style, Err_Fore)
+from pythonforandroid.util import (
+ current_directory, BuildInterruptingException, rmdir)
-class Distribution(object):
+class Distribution:
'''State container for information about a distribution (i.e. an
Android project).
@@ -21,9 +22,10 @@ class Distribution(object):
needs_build = False # Whether the dist needs compiling
url = None
dist_dir = None # Where the dist dir ultimately is. Should not be None.
+ ndk_api = None
archs = []
- '''The arch targets that the dist is built for.'''
+ '''The names of the arch targets that the dist is built for.'''
recipes = []
@@ -41,10 +43,19 @@ def __repr__(self):
return str(self)
@classmethod
- def get_distribution(cls, ctx, name=None, recipes=[],
- force_build=False,
- extra_dist_dirs=[],
- require_perfect_match=False):
+ def get_distribution(
+ cls,
+ ctx,
+ *,
+ archs, # required keyword argument: there is no sensible default
+ name=None,
+ recipes=[],
+ ndk_api=None,
+ force_build=False,
+ extra_dist_dirs=[],
+ require_perfect_match=False,
+ allow_replace_dist=True
+ ):
'''Takes information about the distribution, and decides what kind of
distribution it will be.
@@ -57,6 +68,12 @@ def get_distribution(cls, ctx, name=None, recipes=[],
name : str
The name of the distribution. If a dist with this name already '
exists, it will be used.
+ ndk_api : int
+ The NDK API to compile against, included in the dist because it cannot
+ be changed later during APK packaging.
+ archs : list
+ The target architectures list to compile against, included in the dist because
+ it cannot be changed later during APK packaging.
recipes : list
The recipes that the distribution must contain.
force_download: bool
@@ -68,27 +85,51 @@ def get_distribution(cls, ctx, name=None, recipes=[],
require_perfect_match : bool
If True, will only match distributions with precisely the
correct set of recipes.
+ allow_replace_dist : bool
+ If True, will allow an existing dist with the specified
+ name but incompatible requirements to be overwritten by
+ a new one with the current requirements.
'''
- existing_dists = Distribution.get_distributions(ctx)
+ possible_dists = Distribution.get_distributions(ctx)
+ debug(f"All possible dists: {possible_dists}")
- needs_build = True # whether the dist needs building, will be returned
+ # Will hold dists that would be built in the same folder as an existing dist
+ folder_match_dist = None
- possible_dists = existing_dists
-
- # 0) Check if a dist with that name already exists
+ # 0) Check if a dist with that name and architecture already exists
if name is not None and name:
- possible_dists = [d for d in possible_dists if d.name == name]
+ possible_dists = [
+ d for d in possible_dists if
+ (d.name == name) and all(arch_name in d.archs for arch_name in archs)]
+ debug(f"Dist matching name and arch: {possible_dists}")
+
+ if possible_dists:
+ # There should only be one folder with a given dist name *and* arch.
+ # We could check that here, but for compatibility let's let it slide
+ # and just record the details of one of them. We only use this data to
+ # possibly fail the build later, so it doesn't really matter if there
+ # was more than one clash.
+ folder_match_dist = possible_dists[0]
# 1) Check if any existing dists meet the requirements
_possible_dists = []
for dist in possible_dists:
+ if (
+ ndk_api is not None and dist.ndk_api != ndk_api
+ ) or dist.ndk_api is None:
+ debug(
+ f"dist {dist} failed to match ndk_api, target api {ndk_api}, dist api {dist.ndk_api}"
+ )
+ continue
for recipe in recipes:
if recipe not in dist.recipes:
+ debug(f"dist {dist} missing recipe {recipe}")
break
else:
_possible_dists.append(dist)
possible_dists = _possible_dists
+ debug(f"Dist matching ndk_api and recipe: {possible_dists}")
if possible_dists:
info('Of the existing distributions, the following meet '
@@ -97,9 +138,16 @@ def get_distribution(cls, ctx, name=None, recipes=[],
else:
info('No existing dists meet the given requirements!')
- # If any dist has perfect recipes, return it
+ # If any dist has perfect recipes, arch and NDK API, return it
for dist in possible_dists:
if force_build:
+ debug("Skipping dist due to forced build")
+ continue
+ if ndk_api is not None and dist.ndk_api != ndk_api:
+ debug("Skipping dist due to ndk_api mismatch")
+ continue
+ if not all(arch_name in dist.archs for arch_name in archs):
+ debug("Skipping dist due to arch mismatch")
continue
if (set(dist.recipes) == set(recipes) or
(set(recipes).issubset(set(dist.recipes)) and
@@ -107,37 +155,28 @@ def get_distribution(cls, ctx, name=None, recipes=[],
info_notify('{} has compatible recipes, using this one'
.format(dist.name))
return dist
+ else:
+ debug(
+ f"Skipping dist due to recipes mismatch, expected {set(recipes)}, actual {set(dist.recipes)}"
+ )
+
+ # If there was a name match but we didn't already choose it,
+ # then the existing dist is incompatible with the requested
+ # configuration and the build cannot continue
+ if folder_match_dist is not None and not allow_replace_dist:
+ raise BuildInterruptingException(
+ 'Asked for dist with name {name} with recipes ({req_recipes}) and '
+ 'NDK API {req_ndk_api}, but a dist '
+ 'with this name already exists and has either incompatible recipes '
+ '({dist_recipes}) or NDK API {dist_ndk_api}'.format(
+ name=name,
+ req_ndk_api=ndk_api,
+ dist_ndk_api=folder_match_dist.ndk_api,
+ req_recipes=', '.join(recipes),
+ dist_recipes=', '.join(folder_match_dist.recipes)))
assert len(possible_dists) < 2
- if not name and possible_dists:
- info('Asked for dist with name {} with recipes ({}), but a dist '
- 'with this name already exists and has incompatible recipes '
- '({})'.format(name, ', '.join(recipes),
- ', '.join(possible_dists[0].recipes)))
- info('No compatible dist found, so exiting.')
- exit(1)
-
- # # 2) Check if any downloadable dists meet the requirements
-
- # online_dists = [('testsdl2', ['hostpython2', 'sdl2_image',
- # 'sdl2_mixer', 'sdl2_ttf',
- # 'python2', 'sdl2',
- # 'pyjniussdl2', 'kivysdl2'],
- # 'https://github.com/inclement/sdl2-example-dist/archive/master.zip'),
- # ]
- # _possible_dists = []
- # for dist_name, dist_recipes, dist_url in online_dists:
- # for recipe in recipes:
- # if recipe not in dist_recipes:
- # break
- # else:
- # dist = Distribution(ctx)
- # dist.name = dist_name
- # dist.url = dist_url
- # _possible_dists.append(dist)
- # # if _possible_dists
-
# If we got this far, we need to build a new dist
dist = Distribution(ctx)
dist.needs_build = True
@@ -150,18 +189,28 @@ def get_distribution(cls, ctx, name=None, recipes=[],
name = filen.format(i)
dist.name = name
- dist.dist_dir = join(ctx.dist_dir, dist.name)
+ dist.dist_dir = join(
+ ctx.dist_dir,
+ name)
dist.recipes = recipes
+ dist.ndk_api = ctx.ndk_api
+ dist.archs = archs
return dist
+ def folder_exists(self):
+ return exists(self.dist_dir)
+
+ def delete(self):
+ rmdir(self.dist_dir)
+
@classmethod
def get_distributions(cls, ctx, extra_dist_dirs=[]):
'''Returns all the distributions found locally.'''
if extra_dist_dirs:
- warning('extra_dist_dirs argument to get_distributions '
- 'is not yet implemented')
- exit(1)
+ raise BuildInterruptingException(
+ 'extra_dist_dirs argument to get_distributions '
+ 'is not yet implemented')
dist_dir = ctx.dist_dir
folders = glob.glob(join(dist_dir, '*'))
for dir in extra_dist_dirs:
@@ -173,46 +222,54 @@ def get_distributions(cls, ctx, extra_dist_dirs=[]):
with open(join(folder, 'dist_info.json')) as fileh:
dist_info = json.load(fileh)
dist = cls(ctx)
- dist.name = folder.split('/')[-1]
+ dist.name = dist_info['dist_name']
dist.dist_dir = folder
dist.needs_build = False
dist.recipes = dist_info['recipes']
if 'archs' in dist_info:
dist.archs = dist_info['archs']
+ if 'ndk_api' in dist_info:
+ dist.ndk_api = dist_info['ndk_api']
+ else:
+ dist.ndk_api = None
+ warning(
+ "Distribution {distname}: ({distdir}) has been "
+ "built with an unknown api target, ignoring it, "
+ "you might want to delete it".format(
+ distname=dist.name,
+ distdir=dist.dist_dir
+ )
+ )
dists.append(dist)
return dists
- def save_info(self):
+ def save_info(self, dirn):
'''
Save information about the distribution in its dist_dir.
'''
- with current_directory(self.dist_dir):
+ with current_directory(dirn):
info('Saving distribution info')
with open('dist_info.json', 'w') as fileh:
json.dump({'dist_name': self.name,
+ 'bootstrap': self.ctx.bootstrap.name,
'archs': [arch.arch for arch in self.ctx.archs],
- 'recipes': self.ctx.recipe_build_order},
+ 'ndk_api': self.ctx.ndk_api,
+ 'use_setup_py': self.ctx.use_setup_py,
+ 'recipes': self.ctx.recipe_build_order + self.ctx.python_modules,
+ 'hostpython': self.ctx.hostpython,
+ 'python_version': self.ctx.python_recipe.major_minor_version_string},
fileh)
- def load_info(self):
- '''Load information about the dist from the info file that p4a
- automatically creates.'''
- with current_directory(self.dist_dir):
- filen = 'dist_info.json'
- if not exists(filen):
- return None
- with open('dist_info.json', 'r') as fileh:
- dist_info = json.load(fileh)
- return dist_info
-
def pretty_log_dists(dists, log_func=info):
infos = []
for dist in dists:
- infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: '
+ ndk_api = 'unknown' if dist.ndk_api is None else dist.ndk_api
+ infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: min API {ndk_api}, '
'includes recipes ({Fore.GREEN}{recipes}'
'{Style.RESET_ALL}), built for archs ({Fore.BLUE}'
'{archs}{Style.RESET_ALL})'.format(
+ ndk_api=ndk_api,
name=dist.name, recipes=', '.join(dist.recipes),
archs=', '.join(dist.archs) if dist.archs else 'UNKNOWN',
Fore=Err_Fore, Style=Err_Style))
diff --git a/pythonforandroid/entrypoints.py b/pythonforandroid/entrypoints.py
new file mode 100644
index 000000000..1ba6a2601
--- /dev/null
+++ b/pythonforandroid/entrypoints.py
@@ -0,0 +1,20 @@
+from pythonforandroid.recommendations import check_python_version
+from pythonforandroid.util import BuildInterruptingException, handle_build_exception
+
+
+def main():
+ """
+ Main entrypoint for running python-for-android as a script.
+ """
+
+ try:
+ # Check the Python version before importing anything heavier than
+ # the util functions. This lets us provide a nice message about
+ # incompatibility rather than having the interpreter crash if it
+ # reaches unsupported syntax from a newer Python version.
+ check_python_version()
+
+ from pythonforandroid.toolchain import ToolchainCL
+ ToolchainCL()
+ except BuildInterruptingException as exc:
+ handle_build_exception(exc)
diff --git a/pythonforandroid/graph.py b/pythonforandroid/graph.py
index 203071859..4edb8f4c9 100644
--- a/pythonforandroid/graph.py
+++ b/pythonforandroid/graph.py
@@ -1,24 +1,37 @@
-
from copy import deepcopy
from itertools import product
-from sys import exit
-from pythonforandroid.logger import (info, warning, error)
+from pythonforandroid.logger import info
from pythonforandroid.recipe import Recipe
from pythonforandroid.bootstrap import Bootstrap
+from pythonforandroid.util import BuildInterruptingException
-class RecipeOrder(dict):
+def fix_deplist(deps):
+ """ Turn a dependency list into lowercase, and make sure all entries
+ that are just a string become a tuple of strings
+ """
+ deps = [
+ ((dep.lower(),)
+ if not isinstance(dep, (list, tuple))
+ else tuple([dep_entry.lower()
+ for dep_entry in dep
+ ]))
+ for dep in deps
+ ]
+ return deps
+
+class RecipeOrder(dict):
def __init__(self, ctx):
self.ctx = ctx
- def conflicts(self, name):
+ def conflicts(self):
for name in self.keys():
try:
recipe = Recipe.get_recipe(name, self.ctx)
- conflicts = recipe.conflicts
- except IOError:
+ conflicts = [dep.lower() for dep in recipe.conflicts]
+ except ValueError:
conflicts = []
if any([c in self for c in conflicts]):
@@ -26,26 +39,59 @@ def conflicts(self, name):
return False
-def recursively_collect_orders(name, ctx, orders=[]):
+def get_dependency_tuple_list_for_recipe(recipe, blacklist=None):
+ """ Get the dependencies of a recipe with filtered out blacklist, and
+ turned into tuples with fix_deplist()
+ """
+ if blacklist is None:
+ blacklist = set()
+ assert type(blacklist) is set
+ if recipe.depends is None:
+ dependencies = []
+ else:
+ # Turn all dependencies into tuples so that product will work
+ dependencies = fix_deplist(recipe.depends)
+
+ # Filter out blacklisted items and turn lowercase:
+ dependencies = [
+ tuple(set(deptuple) - blacklist)
+ for deptuple in dependencies
+ if tuple(set(deptuple) - blacklist)
+ ]
+ return dependencies
+
+
+def recursively_collect_orders(
+ name, ctx, all_inputs, orders=None, blacklist=None
+ ):
'''For each possible recipe ordering, try to add the new recipe name
to that order. Recursively do the same thing with all the
dependencies of each recipe.
'''
+ name = name.lower()
+ if orders is None:
+ orders = []
+ if blacklist is None:
+ blacklist = set()
try:
recipe = Recipe.get_recipe(name, ctx)
- if recipe.depends is None:
- dependencies = []
- else:
- # make all dependencies into lists so that product will work
- dependencies = [([dependency] if not isinstance(
- dependency, (list, tuple))
- else dependency) for dependency in recipe.depends]
+ dependencies = get_dependency_tuple_list_for_recipe(
+ recipe, blacklist=blacklist
+ )
+
+ # handle opt_depends: these impose requirements on the build
+ # order only if already present in the list of recipes to build
+ dependencies.extend(fix_deplist(
+ [[d] for d in recipe.get_opt_depends_in_list(all_inputs)
+ if d.lower() not in blacklist]
+ ))
+
if recipe.conflicts is None:
conflicts = []
else:
- conflicts = recipe.conflicts
- except IOError:
+ conflicts = [dep.lower() for dep in recipe.conflicts]
+ except ValueError:
# The recipe does not exist, so we assume it can be installed
# via pip with no extra dependencies
dependencies = []
@@ -57,7 +103,7 @@ def recursively_collect_orders(name, ctx, orders=[]):
if name in order:
new_orders.append(deepcopy(order))
continue
- if order.conflicts(name):
+ if order.conflicts():
continue
if any([conflict in order for conflict in conflicts]):
continue
@@ -69,7 +115,9 @@ def recursively_collect_orders(name, ctx, orders=[]):
dependency_new_orders = [new_order]
for dependency in dependency_set:
dependency_new_orders = recursively_collect_orders(
- dependency, ctx, dependency_new_orders)
+ dependency, ctx, all_inputs, dependency_new_orders,
+ blacklist=blacklist
+ )
new_orders.extend(dependency_new_orders)
@@ -82,7 +130,7 @@ def find_order(graph):
'''
while graph:
# Find all items without a parent
- leftmost = [l for l, s in graph.items() if not s]
+ leftmost = [name for name, dep in graph.items() if not dep]
if not leftmost:
raise ValueError('Dependency cycle detected! %s' % graph)
# If there is more than one, sort them for predictable order
@@ -95,22 +143,142 @@ def find_order(graph):
bset.discard(result)
-def get_recipe_order_and_bootstrap(ctx, names, bs=None):
- recipes_to_load = set(names)
+def obvious_conflict_checker(ctx, name_tuples, blacklist=None):
+ """ This is a pre-flight check function that will completely ignore
+ recipe order or choosing an actual value in any of the multiple
+ choice tuples/dependencies, and just do a very basic obvious
+ conflict check.
+ """
+ deps_were_added_by = dict()
+ deps = set()
+ if blacklist is None:
+ blacklist = set()
+
+ # Add dependencies for all recipes:
+ to_be_added = [(name_tuple, None) for name_tuple in name_tuples]
+ while len(to_be_added) > 0:
+ current_to_be_added = list(to_be_added)
+ to_be_added = []
+ for (added_tuple, adding_recipe) in current_to_be_added:
+ assert type(added_tuple) is tuple
+ if len(added_tuple) > 1:
+ # No obvious commitment in what to add, don't check it itself
+ # but throw it into deps for later comparing against
+ # (Remember this function only catches obvious issues)
+ deps.add(added_tuple)
+ continue
+
+ name = added_tuple[0]
+ recipe_conflicts = set()
+ recipe_dependencies = []
+ try:
+ # Get recipe to add and who's ultimately adding it:
+ recipe = Recipe.get_recipe(name, ctx)
+ recipe_conflicts = {c.lower() for c in recipe.conflicts}
+ recipe_dependencies = get_dependency_tuple_list_for_recipe(
+ recipe, blacklist=blacklist
+ )
+ except ValueError:
+ pass
+ adder_first_recipe_name = adding_recipe or name
+
+ # Collect the conflicts:
+ triggered_conflicts = []
+ for dep_tuple_list in deps:
+ # See if the new deps conflict with things added before:
+ if set(dep_tuple_list).intersection(
+ recipe_conflicts) == set(dep_tuple_list):
+ triggered_conflicts.append(dep_tuple_list)
+ continue
+
+ # See if what was added before conflicts with the new deps:
+ if len(dep_tuple_list) > 1:
+ # Not an obvious commitment to a specific recipe/dep
+ # to be added, so we won't check.
+ # (remember this function only catches obvious issues)
+ continue
+ try:
+ dep_recipe = Recipe.get_recipe(dep_tuple_list[0], ctx)
+ except ValueError:
+ continue
+ conflicts = [c.lower() for c in dep_recipe.conflicts]
+ if name in conflicts:
+ triggered_conflicts.append(dep_tuple_list)
+
+ # Throw error on conflict:
+ if triggered_conflicts:
+ # Get first conflict and see who added that one:
+ adder_second_recipe_name = "'||'".join(triggered_conflicts[0])
+ second_recipe_original_adder = deps_were_added_by.get(
+ (adder_second_recipe_name,), None
+ )
+ if second_recipe_original_adder:
+ adder_second_recipe_name = second_recipe_original_adder
+
+ # Prompt error:
+ raise BuildInterruptingException(
+ "Conflict detected: '{}'"
+ " inducing dependencies {}, and '{}'"
+ " inducing conflicting dependencies {}".format(
+ adder_first_recipe_name,
+ (recipe.name,),
+ adder_second_recipe_name,
+ triggered_conflicts[0]
+ ))
+
+ # Actually add it to our list:
+ deps.add(added_tuple)
+ deps_were_added_by[added_tuple] = adding_recipe
+
+ # Schedule dependencies to be added
+ to_be_added += [
+ (dep, adder_first_recipe_name or name)
+ for dep in recipe_dependencies
+ if dep not in deps
+ ]
+ # If we came here, then there were no obvious conflicts.
+ return None
+
+
+def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None):
+ # Get set of recipe/dependency names, clean up and add bootstrap deps:
+ names = set(names)
if bs is not None and bs.recipe_depends:
- recipes_to_load = recipes_to_load.union(set(bs.recipe_depends))
+ names = names.union(set(bs.recipe_depends))
+ names = fix_deplist([
+ ([name] if not isinstance(name, (list, tuple)) else name)
+ for name in names
+ ])
+ if blacklist is None:
+ blacklist = set()
+ blacklist = {bitem.lower() for bitem in blacklist}
- possible_orders = []
+ # Remove all values that are in the blacklist:
+ names_before_blacklist = list(names)
+ names = []
+ for name in names_before_blacklist:
+ cleaned_up_tuple = tuple([
+ item for item in name if item not in blacklist
+ ])
+ if cleaned_up_tuple:
+ names.append(cleaned_up_tuple)
+
+ # Do check for obvious conflicts (that would trigger in any order, and
+ # without comitting to any specific choice in a multi-choice tuple of
+ # dependencies):
+ obvious_conflict_checker(ctx, names, blacklist=blacklist)
+ # If we get here, no obvious conflicts!
# get all possible order graphs, as names may include tuples/lists
# of alternative dependencies
- names = [([name] if not isinstance(name, (list, tuple)) else name)
- for name in names]
+ possible_orders = []
for name_set in product(*names):
new_possible_orders = [RecipeOrder(ctx)]
for name in name_set:
new_possible_orders = recursively_collect_orders(
- name, ctx, orders=new_possible_orders)
+ name, ctx, name_set, orders=new_possible_orders,
+ blacklist=blacklist
+ )
possible_orders.extend(new_possible_orders)
# turn each order graph into a linear list if possible
@@ -122,23 +290,18 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None):
info('Circular dependency found in graph {}, skipping it.'.format(
possible_order))
continue
- except:
- warning('Failed to import recipe named {}; the recipe exists '
- 'but appears broken.'.format(name))
- warning('Exception was:')
- raise
orders.append(list(order))
- # prefer python2 and SDL2 if available
+ # prefer python3 and SDL2 if available
orders = sorted(orders,
- key=lambda order: -('python2' in order) - ('sdl2' in order))
+ key=lambda order: -('python3' in order) - ('sdl2' in order))
if not orders:
- error('Didn\'t find any valid dependency graphs.')
- error('This means that some of your requirements pull in '
- 'conflicting dependencies.')
- error('Exiting.')
- exit(1)
+ raise BuildInterruptingException(
+ 'Didn\'t find any valid dependency graphs. '
+ 'This means that some of your '
+ 'requirements pull in conflicting dependencies.')
+
# It would be better to check against possible orders other
# than the first one, but in practice clashes will be rare,
# and can be resolved by specifying more parameters
@@ -153,8 +316,14 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None):
if bs is None:
bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx)
+ if bs is None:
+ # Note: don't remove this without thought, causes infinite loop
+ raise BuildInterruptingException(
+ "Could not find any compatible bootstrap!"
+ )
recipes, python_modules, bs = get_recipe_order_and_bootstrap(
- ctx, chosen_order, bs=bs)
+ ctx, chosen_order, bs=bs, blacklist=blacklist
+ )
else:
# check if each requirement has a recipe
recipes = []
@@ -163,7 +332,7 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None):
try:
recipe = Recipe.get_recipe(name, ctx)
python_modules += recipe.python_depends
- except IOError:
+ except ValueError:
python_modules.append(name)
else:
recipes.append(name)
diff --git a/pythonforandroid/logger.py b/pythonforandroid/logger.py
index 75fee40d1..8bcf85c2e 100644
--- a/pythonforandroid/logger.py
+++ b/pythonforandroid/logger.py
@@ -1,4 +1,3 @@
-
import logging
import os
import re
@@ -7,18 +6,7 @@
from math import log10
from collections import defaultdict
from colorama import Style as Colo_Style, Fore as Colo_Fore
-import six
-
-# This codecs change fixes a bug with log output, but crashes under python3
-if not six.PY3:
- import codecs
- stdout = codecs.getwriter('utf8')(stdout)
- stderr = codecs.getwriter('utf8')(stderr)
-if six.PY2:
- unistr = unicode
-else:
- unistr = str
# monkey patch to show full output
sh.ErrorReturnCode.truncate_cap = 999999
@@ -41,12 +29,13 @@ def format(self, record):
record.msg = '{}{}[DEBUG]{}{}: '.format(
Err_Style.BRIGHT, Err_Fore.LIGHTBLACK_EX, Err_Fore.RESET,
Err_Style.RESET_ALL) + record.msg
- return super(LevelDifferentiatingFormatter, self).format(record)
+ return super().format(record)
+
logger = logging.getLogger('p4a')
-if not hasattr(logger, 'touched'): # Necessary as importlib reloads
- # this, which would add a second
- # handler and reset the level
+# Necessary as importlib reloads this,
+# which would add a second handler and reset the level
+if not hasattr(logger, 'touched'):
logger.setLevel(logging.INFO)
logger.touched = True
ch = logging.StreamHandler(stderr)
@@ -59,7 +48,7 @@ def format(self, record):
error = logger.error
-class colorama_shim(object):
+class colorama_shim:
def __init__(self, real):
self._dict = defaultdict(str)
@@ -72,6 +61,7 @@ def __getattr__(self, key):
def enable(self, enable):
self._enabled = enable
+
Out_Style = colorama_shim(Colo_Style)
Out_Fore = colorama_shim(Colo_Fore)
Err_Style = colorama_shim(Colo_Style)
@@ -111,12 +101,12 @@ def shorten_string(string, max_width):
return string
visible = max_width - 16 - int(log10(string_len))
# expected suffix len "...(and XXXXX more)"
- if not isinstance(string, unistr):
- visstring = unistr(string[:visible], errors='ignore')
+ if not isinstance(string, str):
+ visstring = str(string[:visible], errors='ignore')
else:
visstring = string[:visible]
return u''.join((visstring, u'...(and ',
- unistr(string_len - visible), u' more)'))
+ str(string_len - visible), u' more)'))
def get_console_width():
@@ -147,8 +137,10 @@ def shprint(command, *args, **kwargs):
kwargs["_bg"] = True
is_critical = kwargs.pop('_critical', False)
tail_n = kwargs.pop('_tail', None)
+ full_debug = False
if "P4A_FULL_DEBUG" in os.environ:
tail_n = 0
+ full_debug = True
filter_in = kwargs.pop('_filter', None)
filter_out = kwargs.pop('_filterout', None)
if len(logger.handlers) > 1:
@@ -176,16 +168,21 @@ def shprint(command, *args, **kwargs):
if isinstance(line, bytes):
line = line.decode('utf-8', errors='replace')
if logger.level > logging.DEBUG:
+ if full_debug:
+ stdout.write(line)
+ stdout.flush()
+ continue
msg = line.replace(
'\n', ' ').replace(
'\t', ' ').replace(
'\b', ' ').rstrip()
if msg:
- stdout.write(u'{}\r{}{:<{width}}'.format(
- Err_Style.RESET_ALL, msg_hdr,
- shorten_string(msg, msg_width), width=msg_width))
- stdout.flush()
- need_closing_newline = True
+ if "CI" not in os.environ:
+ stdout.write(u'{}\r{}{:<{width}}'.format(
+ Err_Style.RESET_ALL, msg_hdr,
+ shorten_string(msg, msg_width), width=msg_width))
+ stdout.flush()
+ need_closing_newline = True
else:
logger.debug(''.join(['\t', line.rstrip()]))
if need_closing_newline:
@@ -202,9 +199,9 @@ def printtail(out, name, forecolor, tail_n=0,
re_filter_in=None, re_filter_out=None):
lines = out.splitlines()
if re_filter_in is not None:
- lines = [l for l in lines if re_filter_in.search(l)]
+ lines = [line for line in lines if re_filter_in.search(line)]
if re_filter_out is not None:
- lines = [l for l in lines if not re_filter_out.search(l)]
+ lines = [line for line in lines if not re_filter_out.search(line)]
if tail_n == 0 or len(lines) <= tail_n:
info('{}:\n{}\t{}{}'.format(
name, forecolor, '\t\n'.join(lines), Out_Fore.RESET))
@@ -217,17 +214,18 @@ def printtail(out, name, forecolor, tail_n=0,
re.compile(filter_in) if filter_in else None,
re.compile(filter_out) if filter_out else None)
printtail(err.stderr.decode('utf-8'), 'STDERR', Err_Fore.RED)
- if is_critical:
- env = kwargs.get("env")
+ if is_critical or full_debug:
+ env = kwargs.get("_env")
if env is not None:
info("{}ENV:{}\n{}\n".format(
Err_Fore.YELLOW, Err_Fore.RESET, "\n".join(
- "set {}={}".format(n, v) for n, v in env.items())))
+ "export {}='{}'".format(n, v) for n, v in env.items())))
info("{}COMMAND:{}\ncd {} && {} {}\n".format(
Err_Fore.YELLOW, Err_Fore.RESET, os.getcwd(), command,
' '.join(args)))
warning("{}ERROR: {} failed!{}".format(
Err_Fore.RED, command, Err_Fore.RESET))
+ if is_critical:
exit(1)
else:
raise
diff --git a/pythonforandroid/patching.py b/pythonforandroid/patching.py
index 70d7e9c9c..1e143cef9 100644
--- a/pythonforandroid/patching.py
+++ b/pythonforandroid/patching.py
@@ -1,71 +1,178 @@
-from os import uname
+"""
+ Helper functions for recipes.
+ Recipes must supply a list of patches.
-def check_all(*callables):
- def check(**kwargs):
- return all(c(**kwargs) for c in callables)
- return check
+ Patches consist of a filename and an optional conditional, which is
+ any function of the form:
+ def patch_check(arch: string, recipe : Recipe) -> bool
+ This library provides some helpful conditionals and mechanisms to
+ join multiple conditionals.
+
+ Example:
+ patches = [
+ ("linux_or_darwin_only.patch",
+ check_any(is_linux, is_darwin),
+ ("recent_android_API.patch",
+ is_apt_gte(27)),
+ ]
+"""
+from platform import uname
+from packaging.version import Version
-def check_any(*callables):
- def check(**kwargs):
- return any(c(**kwargs) for c in callables)
- return check
+
+# Platform checks
def is_platform(platform):
- def is_x(**kwargs):
- return uname()[0] == platform
- return is_x
+ """
+ Returns true if the host platform matches the parameter given.
+ """
-is_linux = is_platform('Linux')
-is_darwin = is_platform('Darwin')
+ def check(arch, recipe):
+ return uname().system.lower() == platform.lower()
+
+ return check
+
+
+is_linux = is_platform("Linux")
+is_darwin = is_platform("Darwin")
+is_windows = is_platform("Windows")
def is_arch(xarch):
- def is_x(arch, **kwargs):
+ """
+ Returns true if the target architecture platform matches the parameter
+ given.
+ """
+
+ def check(arch):
return arch.arch == xarch
- return is_x
+ return check
+
+
+# Android API comparisons:
+# Return true if the Android API level being targeted
+# is equal (or >, >=, <, <= as appropriate) the given parameter
+
+
+def is_api(apiver: int):
+ def check(arch, recipe):
+ return recipe.ctx.android_api == apiver
+
+ return check
-def is_api_gt(apiver):
- def is_x(recipe, **kwargs):
+
+def is_api_gt(apiver: int):
+ def check(arch, recipe):
return recipe.ctx.android_api > apiver
- return is_x
+
+ return check
-def is_api_gte(apiver):
- def is_x(recipe, **kwargs):
+def is_api_gte(apiver: int):
+ def check(arch, recipe):
return recipe.ctx.android_api >= apiver
- return is_x
+
+ return check
-def is_api_lt(apiver):
- def is_x(recipe, **kwargs):
+def is_api_lt(apiver: int):
+ def check(arch, recipe):
return recipe.ctx.android_api < apiver
- return is_x
+
+ return check
-def is_api_lte(apiver):
- def is_x(recipe, **kwargs):
+def is_api_lte(apiver: int):
+ def check(arch, recipe):
return recipe.ctx.android_api <= apiver
- return is_x
+
+ return check
-def is_api(apiver):
- def is_x(recipe, **kwargs):
- return recipe.ctx.android_api == apiver
- return is_x
+# Android API comparisons:
+
+
+def is_ndk(ndk):
+ """
+ Return true if the Minimum Supported Android NDK level being targeted
+ is equal the given parameter (which should be an AndroidNDK instance)
+ """
+
+ def check(arch, recipe):
+ return recipe.ctx.ndk == ndk
+
+ return check
+
+
+# Recipe Version comparisons:
+# These compare the Recipe's version with the provided string (or
+# Packaging.Version).
+#
+# Warning: Both strings must conform to PEP 440 - e.g. "3.2.1" or "1.0rc1"
+
+
+def is_version_gt(version):
+ """Return true if the Recipe's version is greater"""
+
+ def check(arch, recipe):
+ return Version(recipe.version) > Version(version)
+
+ return check
+
+
+def is_version_lt(version):
+ """Return true if the Recipe's version is less than"""
+
+ def check(arch, recipe):
+ return Version(recipe.version) < Version(version)
+
+ return check
+
+
+def version_starts_with(version_prefix):
+ def check(arch, recipe):
+ return recipe.version.startswith(version_prefix)
+
+ return check
+
+
+# Will Build
def will_build(recipe_name):
- def will(recipe, **kwargs):
+ """Return true if the recipe with this name is planned to be included in
+ the distribution."""
+
+ def check(arch, recipe):
return recipe_name in recipe.ctx.recipe_build_order
- return will
+
+ return check
-def is_ndk(ndk):
- def is_x(recipe, **kwargs):
- return recipe.ctx.ndk == ndk
- return is_x
+# Conjunctions
+
+def check_all(*patch_checks):
+ """
+ Given a collection of patch_checks as params, return if all returned true.
+ """
+
+ def check(arch, recipe):
+ return all(patch_check(arch, recipe) for patch_check in patch_checks)
+
+ return check
+
+
+def check_any(*patch_checks):
+ """
+ Given a collection of patch_checks as params, return if any returned true.
+ """
+
+ def check(arch, recipe):
+ return any(patch_check(arch, recipe) for patch_check in patch_checks)
+
+ return check
diff --git a/pythonforandroid/prerequisites.py b/pythonforandroid/prerequisites.py
new file mode 100644
index 000000000..e85991948
--- /dev/null
+++ b/pythonforandroid/prerequisites.py
@@ -0,0 +1,408 @@
+#!/usr/bin/env python3
+
+import os
+import platform
+import shutil
+import subprocess
+import sys
+
+from pythonforandroid.logger import info, warning, error
+from pythonforandroid.util import ensure_dir
+
+
+class Prerequisite(object):
+ name = "Default"
+ homebrew_formula_name = ""
+ mandatory = dict(linux=False, darwin=False)
+ installer_is_supported = dict(linux=False, darwin=False)
+
+ def is_valid(self):
+ if self.checker():
+ info(f"Prerequisite {self.name} is met")
+ return (True, "")
+ elif not self.mandatory[sys.platform]:
+ warning(
+ f"Prerequisite {self.name} is not met, but is marked as non-mandatory"
+ )
+ else:
+ error(f"Prerequisite {self.name} is not met")
+
+ def checker(self):
+ if sys.platform == "darwin":
+ return self.darwin_checker()
+ elif sys.platform == "linux":
+ return self.linux_checker()
+ else:
+ raise Exception("Unsupported platform")
+
+ def ask_to_install(self):
+ if (
+ os.environ.get("PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE", "1")
+ == "1"
+ ):
+ res = input(
+ f"Do you want automatically install prerequisite {self.name}? [y/N] "
+ )
+ if res.lower() == "y":
+ return True
+ else:
+ return False
+ else:
+ info(
+ "Session is not interactive (usually this happens during a CI run), so let's consider it as a YES"
+ )
+ return True
+
+ def install(self):
+ info(f"python-for-android can automatically install prerequisite: {self.name}")
+ if self.ask_to_install():
+ if sys.platform == "darwin":
+ self.darwin_installer()
+ elif sys.platform == "linux":
+ self.linux_installer()
+ else:
+ raise Exception("Unsupported platform")
+ else:
+ info(
+ f"Skipping installation of prerequisite {self.name} as per user request"
+ )
+
+ def show_helper(self):
+ if sys.platform == "darwin":
+ self.darwin_helper()
+ elif sys.platform == "linux":
+ self.linux_helper()
+ else:
+ raise Exception("Unsupported platform")
+
+ def install_is_supported(self):
+ return self.installer_is_supported[sys.platform]
+
+ def linux_checker(self):
+ raise Exception(f"Unsupported prerequisite check on linux for {self.name}")
+
+ def darwin_checker(self):
+ raise Exception(f"Unsupported prerequisite check on macOS for {self.name}")
+
+ def linux_installer(self):
+ raise Exception(f"Unsupported prerequisite installer on linux for {self.name}")
+
+ def darwin_installer(self):
+ raise Exception(f"Unsupported prerequisite installer on macOS for {self.name}")
+
+ def darwin_helper(self):
+ info(f"No helper available for prerequisite: {self.name} on macOS")
+
+ def linux_helper(self):
+ info(f"No helper available for prerequisite: {self.name} on linux")
+
+ def _darwin_get_brew_formula_location_prefix(self, formula, installed=False):
+ opts = ["--installed"] if installed else []
+ p = subprocess.Popen(
+ ["brew", "--prefix", formula, *opts],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ _stdout_res, _stderr_res = p.communicate()
+
+ if p.returncode != 0:
+ error(_stderr_res.decode("utf-8").strip())
+ return None
+ else:
+ return _stdout_res.decode("utf-8").strip()
+
+ def darwin_pkg_config_location(self):
+ warning(
+ f"pkg-config location is not supported on macOS for prerequisite: {self.name}"
+ )
+ return ""
+
+ def linux_pkg_config_location(self):
+ warning(
+ f"pkg-config location is not supported on linux for prerequisite: {self.name}"
+ )
+ return ""
+
+ @property
+ def pkg_config_location(self):
+ if sys.platform == "darwin":
+ return self.darwin_pkg_config_location()
+ elif sys.platform == "linux":
+ return self.linux_pkg_config_location()
+
+
+class HomebrewPrerequisite(Prerequisite):
+ name = "homebrew"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=False)
+
+ def darwin_checker(self):
+ return shutil.which("brew") is not None
+
+ def darwin_helper(self):
+ info(
+ "Installer for homebrew is not yet supported on macOS,"
+ "the nice news is that the installation process is easy!"
+ "See: https://brew.sh for further instructions."
+ )
+
+
+class JDKPrerequisite(Prerequisite):
+ name = "JDK"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+ supported_version = 17
+
+ def darwin_checker(self):
+ if "JAVA_HOME" in os.environ:
+ info("Found JAVA_HOME environment variable, using it")
+ jdk_path = os.environ["JAVA_HOME"]
+ else:
+ jdk_path = self._darwin_get_libexec_jdk_path(version=None)
+ return self._darwin_jdk_is_supported(jdk_path)
+
+ def _darwin_get_libexec_jdk_path(self, version=None):
+ version_args = []
+ if version is not None:
+ version_args = ["-v", version]
+ return (
+ subprocess.run(
+ ["/usr/libexec/java_home", *version_args],
+ stdout=subprocess.PIPE,
+ )
+ .stdout.strip()
+ .decode()
+ )
+
+ def _darwin_jdk_is_supported(self, jdk_path):
+ if not jdk_path:
+ return False
+
+ javac_bin = os.path.join(jdk_path, "bin", "javac")
+ if not os.path.exists(javac_bin):
+ return False
+
+ p = subprocess.Popen(
+ [javac_bin, "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+ _stdout_res, _stderr_res = p.communicate()
+
+ if p.returncode != 0:
+ error("Failed to run javac to check JDK version")
+ return False
+
+ if not _stdout_res:
+ _stdout_res = _stderr_res
+
+ res = _stdout_res.strip().decode()
+
+ major_version = int(res.split(" ")[-1].split(".")[0])
+ if major_version == self.supported_version:
+ info(f"Found a valid JDK at {jdk_path}")
+ return True
+ else:
+ error(f"JDK version {major_version} is not supported")
+ return False
+
+ def darwin_helper(self):
+ info(
+ f"python-for-android requires a JDK {self.supported_version} to be installed on macOS,"
+ "but seems like you don't have one installed."
+ )
+ info(
+ "If you think that a valid JDK is already installed, please verify that "
+ f"you have a JDK {self.supported_version} installed and that `/usr/libexec/java_home` "
+ "shows the correct path."
+ )
+ info(
+ "If you have multiple JDK installations, please make sure that you have "
+ "`JAVA_HOME` environment variable set to the correct JDK installation."
+ )
+
+ def darwin_installer(self):
+ info(
+ f"Looking for a JDK {self.supported_version} installation which is not the default one ..."
+ )
+ jdk_path = self._darwin_get_libexec_jdk_path(version=str(self.supported_version))
+
+ if not self._darwin_jdk_is_supported(jdk_path):
+ info(f"We're unlucky, there's no JDK {self.supported_version} or higher installation available")
+
+ base_url = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/"
+ if platform.machine() == "arm64":
+ filename = "OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.2_8.tar.gz"
+ else:
+ filename = "OpenJDK17U-jdk_x64_mac_hotspot_17.0.2_8.tar.gz"
+
+ info(f"Downloading {filename} from {base_url}")
+ subprocess.check_output(
+ [
+ "curl",
+ "-L",
+ f"{base_url}{filename}",
+ "-o",
+ f"/tmp/{filename}",
+ ]
+ )
+
+ user_library_java_path = os.path.expanduser(
+ "~/Library/Java/JavaVirtualMachines"
+ )
+ info(f"Extracting {filename} to {user_library_java_path}")
+ ensure_dir(user_library_java_path)
+ subprocess.check_output(
+ ["tar", "xzf", f"/tmp/{filename}", "-C", user_library_java_path],
+ )
+
+ jdk_path = self._darwin_get_libexec_jdk_path(version="17.0.2+8")
+
+ info(f"Setting JAVA_HOME to {jdk_path}")
+ os.environ["JAVA_HOME"] = jdk_path
+
+
+class OpenSSLPrerequisite(Prerequisite):
+ name = "openssl"
+ homebrew_formula_name = "openssl@1.1"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix(
+ self.homebrew_formula_name, installed=True
+ )
+ is not None
+ )
+
+ def darwin_pkg_config_location(self):
+ return os.path.join(
+ self._darwin_get_brew_formula_location_prefix(self.homebrew_formula_name),
+ "lib/pkgconfig",
+ )
+
+ def darwin_installer(self):
+ info("Installing OpenSSL ...")
+ subprocess.check_output(["brew", "install", self.homebrew_formula_name])
+
+
+class AutoconfPrerequisite(Prerequisite):
+ name = "autoconf"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("autoconf", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing Autoconf ...")
+ subprocess.check_output(["brew", "install", "autoconf"])
+
+
+class AutomakePrerequisite(Prerequisite):
+ name = "automake"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("automake", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing Automake ...")
+ subprocess.check_output(["brew", "install", "automake"])
+
+
+class LibtoolPrerequisite(Prerequisite):
+ name = "libtool"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("libtool", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing Libtool ...")
+ subprocess.check_output(["brew", "install", "libtool"])
+
+
+class PkgConfigPrerequisite(Prerequisite):
+ name = "pkg-config"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("pkg-config", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing Pkg-Config ...")
+ subprocess.check_output(["brew", "install", "pkg-config"])
+
+
+class CmakePrerequisite(Prerequisite):
+ name = "cmake"
+ mandatory = dict(linux=False, darwin=True)
+ installer_is_supported = dict(linux=False, darwin=True)
+
+ def darwin_checker(self):
+ return (
+ self._darwin_get_brew_formula_location_prefix("cmake", installed=True)
+ is not None
+ )
+
+ def darwin_installer(self):
+ info("Installing cmake ...")
+ subprocess.check_output(["brew", "install", "cmake"])
+
+
+def get_required_prerequisites(platform="linux"):
+ return [
+ prerequisite_cls()
+ for prerequisite_cls in [
+ HomebrewPrerequisite,
+ AutoconfPrerequisite,
+ AutomakePrerequisite,
+ LibtoolPrerequisite,
+ PkgConfigPrerequisite,
+ CmakePrerequisite,
+ OpenSSLPrerequisite,
+ JDKPrerequisite,
+ ] if prerequisite_cls.mandatory.get(platform, False)
+ ]
+
+
+def check_and_install_default_prerequisites():
+
+ prerequisites_not_met = []
+
+ warning(
+ "prerequisites.py is experimental and does not support all prerequisites yet."
+ )
+ warning("Please report any issues to the python-for-android issue tracker.")
+
+ # Phase 1: Check if all prerequisites are met and add the ones
+ # which are not to `prerequisites_not_met`
+ for prerequisite in get_required_prerequisites(sys.platform):
+ if not prerequisite.is_valid():
+ prerequisites_not_met.append(prerequisite)
+
+ # Phase 2: Setup/Install all prerequisites that are not met
+ # (where possible), otherwise show an helper.
+ for prerequisite in prerequisites_not_met:
+ prerequisite.show_helper()
+ if prerequisite.install_is_supported():
+ prerequisite.install()
+
+
+if __name__ == "__main__":
+ check_and_install_default_prerequisites()
diff --git a/pythonforandroid/pythonpackage.py b/pythonforandroid/pythonpackage.py
new file mode 100644
index 000000000..9e4c29bd8
--- /dev/null
+++ b/pythonforandroid/pythonpackage.py
@@ -0,0 +1,720 @@
+""" This module offers highlevel functions to get package metadata
+ like the METADATA file, the name, or a list of dependencies.
+
+ Usage examples:
+
+ # Getting package name from pip reference:
+ from pythonforandroid.pythonpackage import get_package_name
+ print(get_package_name("pillow"))
+ # Outputs: "Pillow" (note the spelling!)
+
+ # Getting package dependencies:
+ from pythonforandroid.pythonpackage import get_package_dependencies
+ print(get_package_dependencies("pep517"))
+ # Outputs: "['pytoml']"
+
+ # Get package name from arbitrary package source:
+ from pythonforandroid.pythonpackage import get_package_name
+ print(get_package_name("/some/local/project/folder/"))
+ # Outputs package name
+
+ NOTE:
+
+ Yes, this module doesn't fit well into python-for-android, but this
+ functionality isn't available ANYWHERE ELSE, and upstream (pip, ...)
+ currently has no interest in taking this over, so it has no other place
+ to go.
+ (Unless someone reading this puts it into yet another packaging lib)
+
+ Reference discussion/upstream inclusion attempt:
+
+ https://github.com/pypa/packaging-problems/issues/247
+
+"""
+
+
+import functools
+from io import open # needed for python 2
+import os
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+import time
+from urllib.parse import unquote as urlunquote
+from urllib.parse import urlparse
+import zipfile
+
+import toml
+import build.util
+
+from pythonforandroid.util import rmdir, ensure_dir
+
+
+def transform_dep_for_pip(dependency):
+ if dependency.find("@") > 0 and (
+ dependency.find("@") < dependency.find("://") or
+ "://" not in dependency
+ ):
+ # WORKAROUND FOR UPSTREAM BUG:
+ # https://github.com/pypa/pip/issues/6097
+ # (Please REMOVE workaround once that is fixed & released upstream!)
+ #
+ # Basically, setup_requires() can contain a format pip won't install
+ # from a requirements.txt (PEP 508 URLs).
+ # To avoid this, translate to an #egg= reference:
+ if dependency.endswith("#"):
+ dependency = dependency[:-1]
+ url = (dependency.partition("@")[2].strip().partition("#egg")[0] +
+ "#egg=" +
+ dependency.partition("@")[0].strip()
+ )
+ return url
+ return dependency
+
+
+def extract_metainfo_files_from_package(
+ package,
+ output_folder,
+ debug=False
+ ):
+ """ Extracts metdata files from the given package to the given folder,
+ which may be referenced in any way that is permitted in
+ a requirements.txt file or install_requires=[] listing.
+
+ Current supported metadata files that will be extracted:
+
+ - pytoml.yml (only if package wasn't obtained as wheel)
+ - METADATA
+ """
+
+ if package is None:
+ raise ValueError("package cannot be None")
+
+ if not os.path.exists(output_folder) or os.path.isfile(output_folder):
+ raise ValueError("output folder needs to be existing folder")
+
+ if debug:
+ print("extract_metainfo_files_from_package: extracting for " +
+ "package: " + str(package))
+
+ # A temp folder for making a package copy in case it's a local folder,
+ # because extracting metadata might modify files
+ # (creating sdists/wheels...)
+ temp_folder = tempfile.mkdtemp(prefix="pythonpackage-package-copy-")
+ try:
+ # Package is indeed a folder! Get a temp copy to work on:
+ if is_filesystem_path(package):
+ shutil.copytree(
+ parse_as_folder_reference(package),
+ os.path.join(temp_folder, "package"),
+ ignore=shutil.ignore_patterns(".tox")
+ )
+ package = os.path.join(temp_folder, "package")
+
+ _extract_metainfo_files_from_package_unsafe(package, output_folder)
+ finally:
+ rmdir(temp_folder)
+
+
+def _get_system_python_executable():
+ """ Returns the path the system-wide python binary.
+ (In case we're running in a virtualenv or venv)
+ """
+ # This function is required by get_package_as_folder() to work
+ # inside a virtualenv, since venv creation will fail with
+ # the virtualenv's local python binary.
+ # (venv/virtualenv incompatibility)
+
+ # Abort if not in virtualenv or venv:
+ if not hasattr(sys, "real_prefix") and (
+ not hasattr(sys, "base_prefix") or
+ os.path.normpath(sys.base_prefix) ==
+ os.path.normpath(sys.prefix)):
+ return sys.executable
+
+ # Extract prefix we need to look in:
+ if hasattr(sys, "real_prefix"):
+ search_prefix = sys.real_prefix # virtualenv
+ else:
+ search_prefix = sys.base_prefix # venv
+
+ def python_binary_from_folder(path):
+ def binary_is_usable(python_bin):
+ """ Helper function to see if a given binary name refers
+ to a usable python interpreter binary
+ """
+
+ # Abort if path isn't present at all or a directory:
+ if not os.path.exists(
+ os.path.join(path, python_bin)
+ ) or os.path.isdir(os.path.join(path, python_bin)):
+ return
+ # We should check file not found anyway trying to run it,
+ # since it might be a dead symlink:
+ try:
+ filenotfounderror = FileNotFoundError
+ except NameError: # Python 2
+ filenotfounderror = OSError
+ try:
+ # Run it and see if version output works with no error:
+ subprocess.check_output([
+ os.path.join(path, python_bin), "--version"
+ ], stderr=subprocess.STDOUT)
+ return True
+ except (subprocess.CalledProcessError, filenotfounderror):
+ return False
+
+ python_name = "python" + sys.version
+ while (not binary_is_usable(python_name) and
+ python_name.find(".") > 0):
+ # Try less specific binary name:
+ python_name = python_name.rpartition(".")[0]
+ if binary_is_usable(python_name):
+ return os.path.join(path, python_name)
+ return None
+
+ # Return from sys.real_prefix if present:
+ result = python_binary_from_folder(search_prefix)
+ if result is not None:
+ return result
+
+ # Check out all paths in $PATH:
+ bad_candidates = []
+ good_candidates = []
+ ever_had_nonvenv_path = False
+ ever_had_path_starting_with_prefix = False
+ for p in os.environ.get("PATH", "").split(":"):
+ # Skip if not possibly the real system python:
+ if not os.path.normpath(p).startswith(
+ os.path.normpath(search_prefix)
+ ):
+ continue
+
+ ever_had_path_starting_with_prefix = True
+
+ # First folders might be virtualenv/venv we want to avoid:
+ if not ever_had_nonvenv_path:
+ sep = os.path.sep
+ if (
+ ("system32" not in p.lower() and
+ "usr" not in p and
+ not p.startswith("/opt/python")) or
+ {"home", ".tox"}.intersection(set(p.split(sep))) or
+ "users" in p.lower()
+ ):
+ # Doesn't look like bog-standard system path.
+ if (p.endswith(os.path.sep + "bin") or
+ p.endswith(os.path.sep + "bin" + os.path.sep)):
+ # Also ends in "bin" -> likely virtualenv/venv.
+ # Add as unfavorable / end of candidates:
+ bad_candidates.append(p)
+ continue
+ ever_had_nonvenv_path = True
+
+ good_candidates.append(p)
+
+ # If we have a bad env with PATH not containing any reference to our
+ # real python (travis, why would you do that to me?) then just guess
+ # based from the search prefix location itself:
+ if not ever_had_path_starting_with_prefix:
+ # ... and yes we're scanning all the folders for that, it's dumb
+ # but i'm not aware of a better way: (@JonasT)
+ for root, dirs, files in os.walk(search_prefix, topdown=True):
+ for name in dirs:
+ bad_candidates.append(os.path.join(root, name))
+
+ # Sort candidates by length (to prefer shorter ones):
+ def candidate_cmp(a, b):
+ return len(a) - len(b)
+ good_candidates = sorted(
+ good_candidates, key=functools.cmp_to_key(candidate_cmp)
+ )
+ bad_candidates = sorted(
+ bad_candidates, key=functools.cmp_to_key(candidate_cmp)
+ )
+
+ # See if we can now actually find the system python:
+ for p in good_candidates + bad_candidates:
+ result = python_binary_from_folder(p)
+ if result is not None:
+ return result
+
+ raise RuntimeError(
+ "failed to locate system python in: {}"
+ " - checked candidates were: {}, {}"
+ .format(sys.real_prefix, good_candidates, bad_candidates)
+ )
+
+
+def get_package_as_folder(dependency):
+ """ This function downloads the given package / dependency and extracts
+ the raw contents into a folder.
+
+ Afterwards, it returns a tuple with the type of distribution obtained,
+ and the temporary folder it extracted to. It is the caller's
+ responsibility to delete the returned temp folder after use.
+
+ Examples of returned values:
+
+ ("source", "/tmp/pythonpackage-venv-e84toiwjw")
+ ("wheel", "/tmp/pythonpackage-venv-85u78uj")
+
+ What the distribution type will be depends on what pip decides to
+ download.
+ """
+
+ venv_parent = tempfile.mkdtemp(
+ prefix="pythonpackage-venv-"
+ )
+ try:
+ # Create a venv to install into:
+ try:
+ if int(sys.version.partition(".")[0]) < 3:
+ # Python 2.x has no venv.
+ subprocess.check_output([
+ sys.executable, # no venv conflict possible,
+ # -> no need to use system python
+ "-m", "virtualenv",
+ "--python=" + _get_system_python_executable(),
+ os.path.join(venv_parent, 'venv')
+ ], cwd=venv_parent)
+ else:
+ # On modern Python 3, use venv.
+ subprocess.check_output([
+ _get_system_python_executable(), "-m", "venv",
+ os.path.join(venv_parent, 'venv')
+ ], cwd=venv_parent)
+ except subprocess.CalledProcessError as e:
+ output = e.output.decode('utf-8', 'replace')
+ raise ValueError(
+ 'venv creation unexpectedly ' +
+ 'failed. error output: ' + str(output)
+ )
+ venv_path = os.path.join(venv_parent, "venv")
+
+ # Update pip and wheel in venv for latest feature support:
+ try:
+ filenotfounderror = FileNotFoundError
+ except NameError: # Python 2.
+ filenotfounderror = OSError
+ try:
+ subprocess.check_output([
+ os.path.join(venv_path, "bin", "pip"),
+ "install", "-U", "pip", "wheel",
+ ])
+ except filenotfounderror:
+ raise RuntimeError(
+ "venv appears to be missing pip. "
+ "did we fail to use a proper system python??\n"
+ "system python path detected: {}\n"
+ "os.environ['PATH']: {}".format(
+ _get_system_python_executable(),
+ os.environ.get("PATH", "")
+ )
+ )
+
+ # Create download subfolder:
+ ensure_dir(os.path.join(venv_path, "download"))
+
+ # Write a requirements.txt with our package and download:
+ with open(os.path.join(venv_path, "requirements.txt"),
+ "w", encoding="utf-8"
+ ) as f:
+ def to_unicode(s): # Needed for Python 2.
+ try:
+ return s.decode("utf-8")
+ except AttributeError:
+ return s
+ f.write(to_unicode(transform_dep_for_pip(dependency)))
+ try:
+ subprocess.check_output(
+ [
+ os.path.join(venv_path, "bin", "pip"),
+ "download", "--no-deps", "-r", "../requirements.txt",
+ "-d", os.path.join(venv_path, "download")
+ ],
+ stderr=subprocess.STDOUT,
+ cwd=os.path.join(venv_path, "download")
+ )
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError("package download failed: " + str(e.output))
+
+ if len(os.listdir(os.path.join(venv_path, "download"))) == 0:
+ # No download. This can happen if the dependency has a condition
+ # which prohibits install in our environment.
+ # (the "package ; ... conditional ... " type of condition)
+ return (None, None)
+
+ # Get the result and make sure it's an extracted directory:
+ result_folder_or_file = os.path.join(
+ venv_path, "download",
+ os.listdir(os.path.join(venv_path, "download"))[0]
+ )
+ dl_type = "source"
+ if not os.path.isdir(result_folder_or_file):
+ # Must be an archive.
+ if result_folder_or_file.endswith((".zip", ".whl")):
+ if result_folder_or_file.endswith(".whl"):
+ dl_type = "wheel"
+ with zipfile.ZipFile(result_folder_or_file) as f:
+ f.extractall(os.path.join(venv_path,
+ "download", "extracted"
+ ))
+ result_folder_or_file = os.path.join(
+ venv_path, "download", "extracted"
+ )
+ elif result_folder_or_file.find(".tar.") > 0:
+ # Probably a tarball.
+ with tarfile.open(result_folder_or_file) as f:
+ f.extractall(os.path.join(venv_path,
+ "download", "extracted"
+ ))
+ result_folder_or_file = os.path.join(
+ venv_path, "download", "extracted"
+ )
+ else:
+ raise RuntimeError(
+ "unknown archive or download " +
+ "type: " + str(result_folder_or_file)
+ )
+
+ # If the result is hidden away in an additional subfolder,
+ # descend into it:
+ while os.path.isdir(result_folder_or_file) and \
+ len(os.listdir(result_folder_or_file)) == 1 and \
+ os.path.isdir(os.path.join(
+ result_folder_or_file,
+ os.listdir(result_folder_or_file)[0]
+ )):
+ result_folder_or_file = os.path.join(
+ result_folder_or_file,
+ os.listdir(result_folder_or_file)[0]
+ )
+
+ # Copy result to new dedicated folder so we can throw away
+ # our entire virtualenv nonsense after returning:
+ result_path = tempfile.mkdtemp()
+ rmdir(result_path)
+ shutil.copytree(result_folder_or_file, result_path)
+ return (dl_type, result_path)
+ finally:
+ rmdir(venv_parent)
+
+
+def _extract_metainfo_files_from_package_unsafe(
+ package,
+ output_path
+ ):
+ # This is the unwrapped function that will
+ # 1. make lots of stdout/stderr noise
+ # 2. possibly modify files (if the package source is a local folder)
+ # Use extract_metainfo_files_from_package_folder instead which avoids
+ # these issues.
+
+ clean_up_path = False
+ path_type = "source"
+ path = parse_as_folder_reference(package)
+ if path is None:
+ # This is not a path. Download it:
+ (path_type, path) = get_package_as_folder(package)
+ if path_type is None:
+ # Download failed.
+ raise ValueError(
+ "cannot get info for this package, " +
+ "pip says it has no downloads (conditional dependency?)"
+ )
+ clean_up_path = True
+
+ try:
+ metadata_path = None
+
+ if path_type != "wheel":
+ # Use a build helper function to fetch the metadata directly
+ metadata = build.util.project_wheel_metadata(path)
+ # And write it to a file
+ metadata_path = os.path.join(output_path, "built_metadata")
+ with open(metadata_path, 'w') as f:
+ for key in metadata.keys():
+ for value in metadata.get_all(key):
+ f.write("{}: {}\n".format(key, value))
+ else:
+ # This is a wheel, so metadata should be in *.dist-info folder:
+ metadata_path = os.path.join(
+ path,
+ [f for f in os.listdir(path) if f.endswith(".dist-info")][0],
+ "METADATA"
+ )
+
+ # Store type of metadata source. Can be "wheel", "source" for source
+ # distribution, and others get_package_as_folder() may support
+ # in the future.
+ with open(os.path.join(output_path, "metadata_source"), "w") as f:
+ try:
+ f.write(path_type)
+ except TypeError: # in python 2 path_type may be str/bytes:
+ f.write(path_type.decode("utf-8", "replace"))
+
+ # Copy the metadata file:
+ shutil.copyfile(metadata_path, os.path.join(output_path, "METADATA"))
+ finally:
+ if clean_up_path:
+ rmdir(path)
+
+
+def is_filesystem_path(dep):
+ """ Convenience function around parse_as_folder_reference() to
+ check if a dependency refers to a folder path or something remote.
+
+ Returns True if local, False if remote.
+ """
+ return (parse_as_folder_reference(dep) is not None)
+
+
+def parse_as_folder_reference(dep):
+ """ See if a dependency reference refers to a folder path.
+ If it does, return the folder path (which parses and
+ resolves file:// urls in the process).
+ If it doesn't, return None.
+ """
+ # Special case: pep508 urls
+ if dep.find("@") > 0 and (
+ (dep.find("@") < dep.find("/") or "/" not in dep) and
+ (dep.find("@") < dep.find(":") or ":" not in dep)
+ ):
+ # This should be a 'pkgname @ https://...' style path, or
+ # 'pkname @ /local/file/path'.
+ return parse_as_folder_reference(dep.partition("@")[2].lstrip())
+
+ # Check if this is either not an url, or a file URL:
+ if dep.startswith(("/", "file://")) or (
+ dep.find("/") > 0 and
+ dep.find("://") < 0) or (dep in ["", "."]):
+ if dep.startswith("file://"):
+ dep = urlunquote(urlparse(dep).path)
+ return dep
+ return None
+
+
+def _extract_info_from_package(dependency,
+ extract_type=None,
+ debug=False,
+ include_build_requirements=False
+ ):
+ """ Internal function to extract metainfo from a package.
+ Currently supported info types:
+
+ - name
+ - dependencies (a list of dependencies)
+ """
+ if debug:
+ print("_extract_info_from_package called with "
+ "extract_type={} include_build_requirements={}".format(
+ extract_type, include_build_requirements,
+ ))
+ output_folder = tempfile.mkdtemp(prefix="pythonpackage-metafolder-")
+ try:
+ extract_metainfo_files_from_package(
+ dependency, output_folder, debug=debug
+ )
+
+ # Extract the type of data source we used to get the metadata:
+ with open(os.path.join(output_folder,
+ "metadata_source"), "r") as f:
+ metadata_source_type = f.read().strip()
+
+ # Extract main METADATA file:
+ with open(os.path.join(output_folder, "METADATA"),
+ "r", encoding="utf-8"
+ ) as f:
+ # Get metadata and cut away description (is after 2 linebreaks)
+ metadata_entries = f.read().partition("\n\n")[0].splitlines()
+
+ if extract_type == "name":
+ name = None
+ for meta_entry in metadata_entries:
+ if meta_entry.lower().startswith("name:"):
+ return meta_entry.partition(":")[2].strip()
+ if name is None:
+ raise ValueError("failed to obtain package name")
+ return name
+ elif extract_type == "dependencies":
+ # First, make sure we don't attempt to return build requirements
+ # for wheels since they usually come without pyproject.toml
+ # and we haven't implemented another way to get them:
+ if include_build_requirements and \
+ metadata_source_type == "wheel":
+ if debug:
+ print("_extract_info_from_package: was called "
+ "with include_build_requirements=True on "
+ "package obtained as wheel, raising error...")
+ raise NotImplementedError(
+ "fetching build requirements for "
+ "wheels is not implemented"
+ )
+
+ # Get build requirements from pyproject.toml if requested:
+ requirements = []
+ if os.path.exists(os.path.join(output_folder,
+ 'pyproject.toml')
+ ) and include_build_requirements:
+ # Read build system from pyproject.toml file: (PEP518)
+ with open(os.path.join(output_folder, 'pyproject.toml')) as f:
+ build_sys = toml.load(f)['build-system']
+ if "requires" in build_sys:
+ requirements += build_sys["requires"]
+ elif include_build_requirements:
+ # For legacy packages with no pyproject.toml, we have to
+ # add setuptools as default build system.
+ requirements.append("setuptools")
+
+ # Add requirements from metadata:
+ requirements += [
+ entry.rpartition("Requires-Dist:")[2].strip()
+ for entry in metadata_entries
+ if entry.startswith("Requires-Dist")
+ ]
+
+ return list(set(requirements)) # remove duplicates
+ finally:
+ rmdir(output_folder)
+
+
+package_name_cache = dict()
+
+
+def get_package_name(dependency,
+ use_cache=True):
+ def timestamp():
+ try:
+ return time.monotonic()
+ except AttributeError:
+ return time.time() # Python 2.
+ try:
+ value = package_name_cache[dependency]
+ if value[0] + 600.0 > timestamp() and use_cache:
+ return value[1]
+ except KeyError:
+ pass
+ result = _extract_info_from_package(dependency, extract_type="name")
+ package_name_cache[dependency] = (timestamp(), result)
+ return result
+
+
+def get_package_dependencies(package,
+ recursive=False,
+ verbose=False,
+ include_build_requirements=False):
+ """ Obtain the dependencies from a package. Please note this
+ function is possibly SLOW, especially if you enable
+ the recursive mode.
+ """
+ packages_processed = set()
+ package_queue = [package]
+ reqs = set()
+ reqs_as_names = set()
+ while len(package_queue) > 0:
+ current_queue = package_queue
+ package_queue = []
+ for package_dep in current_queue:
+ new_reqs = set()
+ if verbose:
+ print("get_package_dependencies: resolving dependency "
+ f"to package name: {package_dep}")
+ package = get_package_name(package_dep)
+ if package.lower() in packages_processed:
+ continue
+ if verbose:
+ print("get_package_dependencies: "
+ "processing package: {}".format(package))
+ print("get_package_dependencies: "
+ "Packages seen so far: {}".format(
+ packages_processed
+ ))
+ packages_processed.add(package.lower())
+
+ # Use our regular folder processing to examine:
+ new_reqs = new_reqs.union(_extract_info_from_package(
+ package_dep, extract_type="dependencies",
+ debug=verbose,
+ include_build_requirements=include_build_requirements,
+ ))
+
+ # Process new requirements:
+ if verbose:
+ print('get_package_dependencies: collected '
+ "deps of '{}': {}".format(
+ package_dep, str(new_reqs),
+ ))
+ for new_req in new_reqs:
+ try:
+ req_name = get_package_name(new_req)
+ except ValueError as e:
+ if new_req.find(";") >= 0:
+ # Conditional dep where condition isn't met?
+ # --> ignore it
+ continue
+ if verbose:
+ print("get_package_dependencies: " +
+ "unexpected failure to get name " +
+ "of '" + str(new_req) + "': " +
+ str(e))
+ raise RuntimeError(
+ "failed to get " +
+ "name of dependency: " + str(e)
+ )
+ if req_name.lower() in reqs_as_names:
+ continue
+ if req_name.lower() not in packages_processed:
+ package_queue.append(new_req)
+ reqs.add(new_req)
+ reqs_as_names.add(req_name.lower())
+
+ # Bail out here if we're not scanning recursively:
+ if not recursive:
+ package_queue[:] = [] # wipe queue
+ break
+ if verbose:
+ print("get_package_dependencies: returning result: {}".format(reqs))
+ return reqs
+
+
+def get_dep_names_of_package(
+ package,
+ keep_version_pins=False,
+ recursive=False,
+ verbose=False,
+ include_build_requirements=False
+ ):
+ """ Gets the dependencies from the package in the given folder,
+ then attempts to deduce the actual package name resulting
+ from each dependency line, stripping away everything else.
+ """
+
+ # First, obtain the dependencies:
+ dependencies = get_package_dependencies(
+ package, recursive=recursive, verbose=verbose,
+ include_build_requirements=include_build_requirements,
+ )
+ if verbose:
+ print("get_dep_names_of_package_folder: " +
+ "processing dependency list to names: " +
+ str(dependencies))
+
+ # Transform dependencies to their stripped down names:
+ # (they can still have version pins/restrictions, conditionals, ...)
+ dependency_names = set()
+ for dep in dependencies:
+ # If we are supposed to keep exact version pins, extract first:
+ pin_to_append = ""
+ if keep_version_pins and "(==" in dep and dep.endswith(")"):
+ # This is a dependency of the format: 'pkg (==1.0)'
+ pin_to_append = "==" + dep.rpartition("==")[2][:-1]
+ elif keep_version_pins and "==" in dep and not dep.endswith(")"):
+ # This is a dependency of the format: 'pkg==1.0'
+ pin_to_append = "==" + dep.rpartition("==")[2]
+ # Now get true (and e.g. case-corrected) dependency name:
+ dep_name = get_package_name(dep) + pin_to_append
+ dependency_names.add(dep_name)
+ return dependency_names
diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py
index bf71ccf3d..bbd61e603 100644
--- a/pythonforandroid/recipe.py
+++ b/pythonforandroid/recipe.py
@@ -1,9 +1,5 @@
-from os.path import join, dirname, isdir, exists, isfile, split, realpath, basename
-import importlib
-import zipfile
+from os.path import basename, dirname, exists, isdir, isfile, join, realpath, split
import glob
-from shutil import rmtree
-from six import PY2, with_metaclass
import hashlib
from re import match
@@ -11,35 +7,29 @@
import sh
import shutil
import fnmatch
-from os import listdir, unlink, environ, mkdir, curdir, walk
+import urllib.request
+from urllib.request import urlretrieve
+from os import listdir, unlink, environ, curdir, walk
from sys import stdout
+import time
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
-from pythonforandroid.logger import (logger, info, warning, error, debug, shprint, info_main)
-from pythonforandroid.util import (urlretrieve, current_directory, ensure_dir)
-# this import is necessary to keep imp.load_source from complaining :)
-import pythonforandroid.recipes
+import packaging.version
+from pythonforandroid.logger import (
+ logger, info, warning, debug, shprint, info_main)
+from pythonforandroid.util import (
+ current_directory, ensure_dir, BuildInterruptingException, rmdir, move,
+ touch)
+from pythonforandroid.util import load_source as import_recipe
-if PY2:
- import imp
- import_recipe = imp.load_source
-else:
- import importlib.util
- if hasattr(importlib.util, 'module_from_spec'):
- def import_recipe(module, filename):
- spec = importlib.util.spec_from_file_location(module, filename)
- mod = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(mod)
- return mod
- else:
- from importlib.machinery import SourceFileLoader
- def import_recipe(module, filename):
- return SourceFileLoader(module, filename).load_module()
+url_opener = urllib.request.build_opener()
+url_orig_headers = url_opener.addheaders
+urllib.request.install_opener(url_opener)
class RecipeMeta(type):
@@ -50,10 +40,10 @@ def __new__(cls, name, bases, dct):
if 'version' in dct:
dct['_version'] = dct.pop('version')
- return super(RecipeMeta, cls).__new__(cls, name, bases, dct)
+ return super().__new__(cls, name, bases, dct)
-class Recipe(with_metaclass(RecipeMeta)):
+class Recipe(metaclass=RecipeMeta):
_url = None
'''The address from which the recipe may be downloaded. This is not
essential, it may be omitted if the source is available some other
@@ -78,6 +68,18 @@ class Recipe(with_metaclass(RecipeMeta)):
finished correctly.
'''
+ sha512sum = None
+ '''The sha512sum of the source from the :attr:`url`. Non-essential, but
+ you should try to include this, it is used to check that the download
+ finished correctly.
+ '''
+
+ blake2bsum = None
+ '''The blake2bsum of the source from the :attr:`url`. Non-essential, but
+ you should try to include this, it is used to check that the download
+ finished correctly.
+ '''
+
depends = []
'''A list containing the names of any recipes that this recipe depends on.
'''
@@ -104,6 +106,50 @@ class Recipe(with_metaclass(RecipeMeta)):
archs = ['armeabi'] # Not currently implemented properly
+ built_libraries = {}
+ """Each recipe that builds a system library (e.g.:libffi, openssl, etc...)
+ should contain a dict holding the relevant information of the library. The
+ keys should be the generated libraries and the values the relative path of
+ the library inside his build folder. This dict will be used to perform
+ different operations:
+ - copy the library into the right location, depending on if it's shared
+ or static)
+ - check if we have to rebuild the library
+
+ Here an example of how it would look like for `libffi` recipe:
+
+ - `built_libraries = {'libffi.so': '.libs'}`
+
+ .. note:: in case that the built library resides in recipe's build
+ directory, you can set the following values for the relative
+ path: `'.', None or ''`
+ """
+
+ need_stl_shared = False
+ '''Some libraries or python packages may need the c++_shared in APK.
+ We can automatically do this for any recipe if we set this property to
+ `True`'''
+
+ stl_lib_name = 'c++_shared'
+ '''
+ The default STL shared lib to use: `c++_shared`.
+
+ .. note:: Android NDK version > 17 only supports 'c++_shared', because
+ starting from NDK r18 the `gnustl_shared` lib has been deprecated.
+ '''
+
+ def get_stl_library(self, arch):
+ return join(
+ arch.ndk_lib_dir,
+ 'lib{name}.so'.format(name=self.stl_lib_name),
+ )
+
+ def install_stl_lib(self, arch):
+ if not self.ctx.has_lib(
+ arch.arch, 'lib{name}.so'.format(name=self.stl_lib_name)
+ ):
+ self.install_libs(arch, self.get_stl_library(arch))
+
@property
def version(self):
key = 'VERSION_' + self.name
@@ -142,55 +188,69 @@ def report_hook(index, blksize, size):
else:
progression = '{0:.2f}%'.format(
index * blksize * 100. / float(size))
- stdout.write('- Download {}\r'.format(progression))
- stdout.flush()
+ if "CI" not in environ:
+ stdout.write('- Download {}\r'.format(progression))
+ stdout.flush()
if exists(target):
unlink(target)
- urlretrieve(url, target, report_hook)
+ # Download item with multiple attempts (for bad connections):
+ attempts = 0
+ seconds = 1
+ while True:
+ try:
+ # jqueryui.com returns a 403 w/ the default user agent
+ # Mozilla/5.0 doesnt handle redirection for liblzma
+ url_opener.addheaders = [('User-agent', 'Wget/1.0')]
+ urlretrieve(url, target, report_hook)
+ except OSError as e:
+ attempts += 1
+ if attempts >= 5:
+ raise
+ stdout.write('Download failed: {}; retrying in {} second(s)...'.format(e, seconds))
+ time.sleep(seconds)
+ seconds *= 2
+ continue
+ finally:
+ url_opener.addheaders = url_orig_headers
+ break
return target
elif parsed_url.scheme in ('git', 'git+file', 'git+ssh', 'git+http', 'git+https'):
- if isdir(target):
- with current_directory(target):
- shprint(sh.git, 'fetch', '--tags')
- if self.version:
- shprint(sh.git, 'checkout', self.version)
- shprint(sh.git, 'pull')
- shprint(sh.git, 'pull', '--recurse-submodules')
- shprint(sh.git, 'submodule', 'update', '--recursive')
- else:
+ if not isdir(target):
if url.startswith('git+'):
url = url[4:]
- shprint(sh.git, 'clone', '--recursive', url, target)
+ # if 'version' is specified, do a shallow clone
if self.version:
+ ensure_dir(target)
with current_directory(target):
- shprint(sh.git, 'checkout', self.version)
- shprint(sh.git, 'submodule', 'update', '--recursive')
+ shprint(sh.git, 'init')
+ shprint(sh.git, 'remote', 'add', 'origin', url)
+ else:
+ shprint(sh.git, 'clone', '--recursive', url, target)
+ with current_directory(target):
+ if self.version:
+ shprint(sh.git, 'fetch', '--depth', '1', 'origin', self.version)
+ shprint(sh.git, 'checkout', self.version)
+ branch = sh.git('branch', '--show-current')
+ if branch:
+ shprint(sh.git, 'pull')
+ shprint(sh.git, 'pull', '--recurse-submodules')
+ shprint(sh.git, 'submodule', 'update', '--recursive', '--init', '--depth', '1')
return target
- # def get_archive_rootdir(self, filename):
- # if filename.endswith(".tgz") or filename.endswith(".tar.gz") or \
- # filename.endswith(".tbz2") or filename.endswith(".tar.bz2"):
- # archive = tarfile.open(filename)
- # root = archive.next().path.split("/")
- # return root[0]
- # elif filename.endswith(".zip"):
- # with zipfile.ZipFile(filename) as zf:
- # return dirname(zf.namelist()[0])
- # else:
- # print("Error: cannot detect root directory")
- # print("Unrecognized extension for {}".format(filename))
- # raise Exception()
-
- def apply_patch(self, filename, arch):
+ def apply_patch(self, filename, arch, build_dir=None):
"""
Apply a patch from the current recipe directory into the current
build directory.
+
+ .. versionchanged:: 0.6.0
+ Add ability to apply patch from any dir via kwarg `build_dir`'''
"""
info("Applying patch {}".format(filename))
+ build_dir = build_dir if build_dir else self.get_build_dir(arch)
filename = join(self.get_recipe_dir(), filename)
- shprint(sh.patch, "-t", "-d", self.get_build_dir(arch), "-p1",
+ shprint(sh.patch, "-t", "-d", build_dir, "-p1",
"-i", filename, _tail=10)
def copy_file(self, filename, dest):
@@ -208,42 +268,12 @@ def append_file(self, filename, dest):
with open(dest, "ab") as fd:
fd.write(data)
- # def has_marker(self, marker):
- # """
- # Return True if the current build directory has the marker set
- # """
- # return exists(join(self.build_dir, ".{}".format(marker)))
-
- # def set_marker(self, marker):
- # """
- # Set a marker info the current build directory
- # """
- # with open(join(self.build_dir, ".{}".format(marker)), "w") as fd:
- # fd.write("ok")
-
- # def delete_marker(self, marker):
- # """
- # Delete a specific marker
- # """
- # try:
- # unlink(join(self.build_dir, ".{}".format(marker)))
- # except:
- # pass
-
@property
def name(self):
'''The name of the recipe, the same as the folder containing it.'''
modname = self.__class__.__module__
return modname.split(".", 2)[-1]
- # @property
- # def archive_fn(self):
- # bfn = basename(self.url.format(version=self.version))
- # fn = "{}/{}-{}".format(
- # self.ctx.cache_dir,
- # self.name, bfn)
- # return fn
-
@property
def filtered_archs(self):
'''Return archs of self.ctx that are valid build archs
@@ -271,6 +301,12 @@ def check_recipe_choices(self):
recipes.append(recipe)
return sorted(recipes)
+ def get_opt_depends_in_list(self, recipes):
+ '''Given a list of recipe names, returns those that are also in
+ self.opt_depends.
+ '''
+ return [recipe for recipe in recipes if recipe in self.opt_depends]
+
def get_build_container_dir(self, arch):
'''Given the arch name, returns the directory where it will be
built.
@@ -279,7 +315,8 @@ def get_build_container_dir(self, arch):
alternative or optional dependencies are being built.
'''
dir_name = self.get_dir_name()
- return join(self.ctx.build_dir, 'other_builds', dir_name, arch)
+ return join(self.ctx.build_dir, 'other_builds',
+ dir_name, '{}__ndk_target_{}'.format(arch, self.ctx.ndk_api))
def get_dir_name(self):
choices = self.check_recipe_choices()
@@ -293,6 +330,14 @@ def get_build_dir(self, arch):
return join(self.get_build_container_dir(arch), self.name)
def get_recipe_dir(self):
+ """
+ Returns the local recipe directory or defaults to the core recipe
+ directory.
+ """
+ if self.ctx.local_recipes is not None:
+ local_recipe_dir = join(self.ctx.local_recipes, self.name)
+ if exists(local_recipe_dir):
+ return local_recipe_dir
return join(self.ctx.root_dir, 'recipes', self.name)
# Public Recipe API to be subclassed if needed
@@ -312,18 +357,21 @@ def download(self):
return
url = self.versioned_url
- ma = match(u'^(.+)#md5=([0-9a-f]{32})$', url)
- if ma: # fragmented URL?
- if self.md5sum:
- raise ValueError(
- ('Received md5sum from both the {} recipe '
- 'and its url').format(self.name))
- url = ma.group(1)
- expected_md5 = ma.group(2)
- else:
- expected_md5 = self.md5sum
-
- shprint(sh.mkdir, '-p', join(self.ctx.packages_path, self.name))
+ expected_digests = {}
+ for alg in set(hashlib.algorithms_guaranteed) | set(('md5', 'sha512', 'blake2b')):
+ expected_digest = getattr(self, alg + 'sum') if hasattr(self, alg + 'sum') else None
+ ma = match(u'^(.+)#' + alg + u'=([0-9a-f]{32,})$', url)
+ if ma: # fragmented URL?
+ if expected_digest:
+ raise ValueError(
+ ('Received {}sum from both the {} recipe '
+ 'and its url').format(alg, self.name))
+ url = ma.group(1)
+ expected_digest = ma.group(2)
+ if expected_digest:
+ expected_digests[alg] = expected_digest
+
+ ensure_dir(join(self.ctx.packages_path, self.name))
with current_directory(join(self.ctx.packages_path, self.name)):
filename = shprint(sh.basename, url).stdout[:-1].decode('utf-8')
@@ -333,16 +381,17 @@ def download(self):
if exists(filename) and isfile(filename):
if not exists(marker_filename):
shprint(sh.rm, filename)
- elif expected_md5:
- current_md5 = md5sum(filename)
- if current_md5 != expected_md5:
- debug('* Generated md5sum: {}'.format(current_md5))
- debug('* Expected md5sum: {}'.format(expected_md5))
- raise ValueError(
- ('Generated md5sum does not match expected md5sum '
- 'for {} recipe').format(self.name))
- do_download = False
else:
+ for alg, expected_digest in expected_digests.items():
+ current_digest = algsum(alg, filename)
+ if current_digest != expected_digest:
+ debug('* Generated {}sum: {}'.format(alg,
+ current_digest))
+ debug('* Expected {}sum: {}'.format(alg,
+ expected_digest))
+ raise ValueError(
+ ('Generated {0}sum does not match expected {0}sum '
+ 'for {1} recipe').format(alg, self.name))
do_download = False
# If we got this far, we will download
@@ -351,17 +400,19 @@ def download(self):
shprint(sh.rm, '-f', marker_filename)
self.download_file(self.versioned_url, filename)
- shprint(sh.touch, marker_filename)
-
- if exists(filename) and isfile(filename) and expected_md5:
- current_md5 = md5sum(filename)
- if expected_md5 is not None:
- if current_md5 != expected_md5:
- debug('* Generated md5sum: {}'.format(current_md5))
- debug('* Expected md5sum: {}'.format(expected_md5))
+ touch(marker_filename)
+
+ if exists(filename) and isfile(filename):
+ for alg, expected_digest in expected_digests.items():
+ current_digest = algsum(alg, filename)
+ if current_digest != expected_digest:
+ debug('* Generated {}sum: {}'.format(alg,
+ current_digest))
+ debug('* Expected {}sum: {}'.format(alg,
+ expected_digest))
raise ValueError(
- ('Generated md5sum does not match expected md5sum '
- 'for {} recipe').format(self.name))
+ ('Generated {0}sum does not match expected {0}sum '
+ 'for {1} recipe').format(alg, self.name))
else:
info('{} download already cached, skipping'.format(self.name))
@@ -376,9 +427,7 @@ def unpack(self, arch):
self.name.lower()))
if exists(self.get_build_dir(arch)):
return
- shprint(sh.rm, '-rf', build_dir)
- shprint(sh.mkdir, '-p', build_dir)
- shprint(sh.rmdir, build_dir)
+ rmdir(build_dir)
ensure_dir(build_dir)
shprint(sh.cp, '-a', user_dir, self.get_build_dir(arch))
return
@@ -389,7 +438,7 @@ def unpack(self, arch):
filename = shprint(
sh.basename, self.versioned_url).stdout[:-1].decode('utf-8')
- ma = match(u'^(.+)#md5=([0-9a-f]{32})$', filename)
+ ma = match(u'^(.+)#[a-z0-9_]{3,}=([0-9a-f]{32,})$', filename)
if ma: # fragmented URL?
filename = ma.group(1)
@@ -400,37 +449,33 @@ def unpack(self, arch):
extraction_filename = join(
self.ctx.packages_path, self.name, filename)
if isfile(extraction_filename):
- if extraction_filename.endswith('.zip'):
+ if extraction_filename.endswith(('.zip', '.whl')):
try:
sh.unzip(extraction_filename)
except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2):
- pass # return code 1 means unzipping had
- # warnings but did complete,
- # apparently happens sometimes with
- # github zips
+ # return code 1 means unzipping had
+ # warnings but did complete,
+ # apparently happens sometimes with
+ # github zips
+ pass
import zipfile
fileh = zipfile.ZipFile(extraction_filename, 'r')
root_directory = fileh.filelist[0].filename.split('/')[0]
if root_directory != basename(directory_name):
- shprint(sh.mv, root_directory, directory_name)
- elif (extraction_filename.endswith('.tar.gz') or
- extraction_filename.endswith('.tgz') or
- extraction_filename.endswith('.tar.bz2') or
- extraction_filename.endswith('.tbz2') or
- extraction_filename.endswith('.tar.xz') or
- extraction_filename.endswith('.txz')):
+ move(root_directory, directory_name)
+ elif extraction_filename.endswith(
+ ('.tar.gz', '.tgz', '.tar.bz2', '.tbz2', '.tar.xz', '.txz')):
sh.tar('xf', extraction_filename)
- root_directory = shprint(
- sh.tar, 'tf', extraction_filename).stdout.decode(
+ root_directory = sh.tar('tf', extraction_filename).stdout.decode(
'utf-8').split('\n')[0].split('/')[0]
- if root_directory != directory_name:
- shprint(sh.mv, root_directory, directory_name)
+ if root_directory != basename(directory_name):
+ move(root_directory, directory_name)
else:
raise Exception(
'Could not extract {} download, it must be .zip, '
'.tar.gz or .tar.bz2 or .tar.xz'.format(extraction_filename))
elif isdir(extraction_filename):
- mkdir(directory_name)
+ ensure_dir(directory_name)
for entry in listdir(extraction_filename):
if entry not in ('.git',):
shprint(sh.cp, '-Rv',
@@ -449,7 +494,8 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
"""
if arch is None:
arch = self.filtered_archs[0]
- return arch.get_env(with_flags_in_cc=with_flags_in_cc)
+ env = arch.get_env(with_flags_in_cc=with_flags_in_cc)
+ return env
def prebuild_arch(self, arch):
'''Run any pre-build tasks for the Recipe. By default, this checks if
@@ -465,8 +511,11 @@ def is_patched(self, arch):
build_dir = self.get_build_dir(arch.arch)
return exists(join(build_dir, '.patched'))
- def apply_patches(self, arch):
- '''Apply any patches for the Recipe.'''
+ def apply_patches(self, arch, build_dir=None):
+ '''Apply any patches for the Recipe.
+
+ .. versionchanged:: 0.6.0
+ Add ability to apply patches from any dir via kwarg `build_dir`'''
if self.patches:
info_main('Applying patches for {}[{}]'
.format(self.name, arch.arch))
@@ -475,6 +524,7 @@ def apply_patches(self, arch):
info_main('{} already patched, skipping'.format(self.name))
return
+ build_dir = build_dir if build_dir else self.get_build_dir(arch.arch)
for patch in self.patches:
if isinstance(patch, (tuple, list)):
patch, patch_check = patch
@@ -483,15 +533,20 @@ def apply_patches(self, arch):
self.apply_patch(
patch.format(version=self.version, arch=arch.arch),
- arch.arch)
+ arch.arch, build_dir=build_dir)
- shprint(sh.touch, join(self.get_build_dir(arch.arch), '.patched'))
+ touch(join(build_dir, '.patched'))
def should_build(self, arch):
'''Should perform any necessary test and return True only if it needs
- building again.
+ building again. Per default we implement a library test, in case that
+ we detect so.
'''
+ if self.built_libraries:
+ return not all(
+ exists(lib) for lib in self.get_libraries(arch.arch)
+ )
return True
def build_arch(self, arch):
@@ -502,6 +557,19 @@ def build_arch(self, arch):
if hasattr(self, build):
getattr(self, build)()
+ def install_libraries(self, arch):
+ '''This method is always called after `build_arch`. In case that we
+ detect a library recipe, defined by the class attribute
+ `built_libraries`, we will copy all defined libraries into the
+ right location.
+ '''
+ if not self.built_libraries:
+ return
+ shared_libs = [
+ lib for lib in self.get_libraries(arch) if lib.endswith(".so")
+ ]
+ self.install_libs(arch, *shared_libs)
+
def postbuild_arch(self, arch):
'''Run any post-build tasks for the Recipe. By default, this checks if
any postbuild_archname methods exist for the archname of the
@@ -511,6 +579,9 @@ def postbuild_arch(self, arch):
if hasattr(self, postbuild):
getattr(self, postbuild)()
+ if self.need_stl_shared:
+ self.install_stl_lib(arch)
+
def prepare_build_dir(self, arch):
'''Copies the recipe data into a build dir for the given arch. By
default, this unpacks a downloaded recipe. You should override
@@ -541,17 +612,15 @@ def clean_build(self, arch=None):
if exists(base_dir):
dirs.append(base_dir)
if not dirs:
- warning(('Attempted to clean build for {} but found no existing '
- 'build dirs').format(self.name))
+ warning('Attempted to clean build for {} but found no existing '
+ 'build dirs'.format(self.name))
for directory in dirs:
- if exists(directory):
- info('Deleting {}'.format(directory))
- shutil.rmtree(directory)
+ rmdir(directory)
# Delete any Python distributions to ensure the recipe build
# doesn't persist in site-packages
- shutil.rmtree(self.ctx.python_installs_dir)
+ rmdir(self.ctx.python_installs_dir)
def install_libs(self, arch, *libs):
libs_dir = self.ctx.get_libs_dir(arch.arch)
@@ -562,7 +631,28 @@ def install_libs(self, arch, *libs):
shprint(sh.cp, *args)
def has_libs(self, arch, *libs):
- return all(map(lambda l: self.ctx.has_lib(arch.arch, l), libs))
+ return all(map(lambda lib: self.ctx.has_lib(arch.arch, lib), libs))
+
+ def get_libraries(self, arch_name, in_context=False):
+ """Return the full path of the library depending on the architecture.
+ Per default, the build library path it will be returned, unless
+ `get_libraries` has been called with kwarg `in_context` set to
+ True.
+
+ .. note:: this method should be used for library recipes only
+ """
+ recipe_libs = set()
+ if not self.built_libraries:
+ return recipe_libs
+ for lib, rel_path in self.built_libraries.items():
+ if not in_context:
+ abs_path = join(self.get_build_dir(arch_name), rel_path, lib)
+ if rel_path in {".", "", None}:
+ abs_path = join(self.get_build_dir(arch_name), lib)
+ else:
+ abs_path = join(self.ctx.get_libs_dir(arch_name), lib)
+ recipe_libs.add(abs_path)
+ return recipe_libs
@classmethod
def recipe_dirs(cls, ctx):
@@ -589,6 +679,7 @@ def list_recipes(cls, ctx):
@classmethod
def get_recipe(cls, name, ctx):
'''Returns the Recipe with the given name, if it exists.'''
+ name = name.lower()
if not hasattr(cls, "recipes"):
cls.recipes = {}
if name in cls.recipes:
@@ -596,20 +687,28 @@ def get_recipe(cls, name, ctx):
recipe_file = None
for recipes_dir in cls.recipe_dirs(ctx):
- recipe_file = join(recipes_dir, name, '__init__.py')
- if exists(recipe_file):
+ if not exists(recipes_dir):
+ continue
+ # Find matching folder (may differ in case):
+ for subfolder in listdir(recipes_dir):
+ if subfolder.lower() == name:
+ recipe_file = join(recipes_dir, subfolder, '__init__.py')
+ if exists(recipe_file):
+ name = subfolder # adapt to actual spelling
+ break
+ recipe_file = None
+ if recipe_file is not None:
break
- recipe_file = None
- if not recipe_file:
- raise IOError('Recipe does not exist: {}'.format(name))
+ else:
+ raise ValueError('Recipe does not exist: {}'.format(name))
mod = import_recipe('pythonforandroid.recipes.{}'.format(name), recipe_file)
if len(logger.handlers) > 1:
logger.removeHandler(logger.handlers[1])
recipe = mod.recipe
recipe.ctx = ctx
- cls.recipes[name] = recipe
+ cls.recipes[name.lower()] = recipe
return recipe
@@ -620,9 +719,9 @@ class IncludedFilesBehaviour(object):
def prepare_build_dir(self, arch):
if self.src_filename is None:
- print('IncludedFilesBehaviour failed: no src_filename specified')
- exit(1)
- shprint(sh.rm, '-rf', self.get_build_dir(arch))
+ raise BuildInterruptingException(
+ 'IncludedFilesBehaviour failed: no src_filename specified')
+ rmdir(self.get_build_dir(arch))
shprint(sh.cp, '-a', join(self.get_recipe_dir(), self.src_filename),
self.get_build_dir(arch))
@@ -634,6 +733,9 @@ class BootstrapNDKRecipe(Recipe):
To build an NDK project which is not part of the bootstrap, see
:class:`~pythonforandroid.recipe.NDKRecipe`.
+
+ To link with python, call the method :meth:`get_recipe_env`
+ with the kwarg *with_python=True*.
'''
dir_name = None # The name of the recipe build folder in the jni dir
@@ -650,6 +752,17 @@ def get_build_dir(self, arch):
def get_jni_dir(self):
return join(self.ctx.bootstrap.build_dir, 'jni')
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=False):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+ if not with_python:
+ return env
+
+ env['PYTHON_INCLUDE_ROOT'] = self.ctx.python_recipe.include_root(arch.arch)
+ env['PYTHON_LINK_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)
+ env['EXTRA_LDLIBS'] = ' -lpython{}'.format(
+ self.ctx.python_recipe.link_version)
+ return env
+
class NDKRecipe(Recipe):
'''A recipe class for any NDK project not included in the bootstrap.'''
@@ -672,11 +785,18 @@ def get_jni_dir(self, arch):
return join(self.get_build_dir(arch.arch), 'jni')
def build_arch(self, arch, *extra_args):
- super(NDKRecipe, self).build_arch(arch)
+ super().build_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
- shprint(sh.ndk_build, 'V=1', 'APP_ABI=' + arch.arch, *extra_args, _env=env)
+ shprint(
+ sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
+ 'V=1',
+ 'NDK_DEBUG=' + ("1" if self.ctx.build_as_debuggable else "0"),
+ 'APP_PLATFORM=android-' + str(self.ctx.ndk_api),
+ 'APP_ABI=' + arch.arch,
+ *extra_args, _env=env
+ )
class PythonRecipe(Recipe):
@@ -703,10 +823,35 @@ class PythonRecipe(Recipe):
This is almost always what you want to do.'''
setup_extra_args = []
- '''List of extra arugments to pass to setup.py'''
+ '''List of extra arguments to pass to setup.py'''
+
+ depends = ['python3']
+ '''
+ .. note:: it's important to keep this depends as a class attribute outside
+ `__init__` because sometimes we only initialize the class, so the
+ `__init__` call won't be called and the deps would be missing
+ (which breaks the dependency graph computation)
+
+ .. warning:: don't forget to call `super().__init__()` in any recipe's
+ `__init__`, or otherwise it may not be ensured that it depends
+ on python2 or python3 which can break the dependency graph
+ '''
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ if 'python3' not in self.depends:
+ # We ensure here that the recipe depends on python even it overrode
+ # `depends`. We only do this if it doesn't already depend on any
+ # python, since some recipes intentionally don't depend on/work
+ # with all python variants
+ depends = self.depends
+ depends.append('python3')
+ depends = list(set(depends))
+ self.depends = depends
def clean_build(self, arch=None):
- super(PythonRecipe, self).clean_build(arch=arch)
+ super().clean_build(arch=arch)
name = self.folder_name
python_install_dirs = glob.glob(join(self.ctx.python_installs_dir, '*'))
for python_install in python_install_dirs:
@@ -716,14 +861,14 @@ def clean_build(self, arch=None):
build_dir = join(site_packages_dir[0], name)
if exists(build_dir):
info('Deleted {}'.format(build_dir))
- rmtree(build_dir)
+ rmdir(build_dir)
@property
def real_hostpython_location(self):
- if 'hostpython2' in self.ctx.recipe_build_order:
- return join(
- Recipe.get_recipe('hostpython2', self.ctx).get_build_dir(),
- 'hostpython')
+ host_name = 'host{}'.format(self.ctx.python_recipe.name)
+ if host_name == 'hostpython3':
+ python_recipe = Recipe.get_recipe(host_name, self.ctx)
+ return python_recipe.python_exe
else:
python_recipe = self.ctx.python_recipe
return 'python{}'.format(python_recipe.version)
@@ -743,26 +888,40 @@ def folder_name(self):
return name
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(PythonRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
env['PYTHONNOUSERSITE'] = '1'
+ # Set the LANG, this isn't usually important but is a better default
+ # as it occasionally matters how Python e.g. reads files
+ env['LANG'] = "en_GB.UTF-8"
+
if not self.call_hostpython_via_targetpython:
+ env['CFLAGS'] += ' -I{}'.format(
+ self.ctx.python_recipe.include_root(arch.arch)
+ )
+ env['LDFLAGS'] += ' -L{} -lpython{}'.format(
+ self.ctx.python_recipe.link_root(arch.arch),
+ self.ctx.python_recipe.link_version,
+ )
+
hppath = []
hppath.append(join(dirname(self.hostpython_location), 'Lib'))
hppath.append(join(hppath[0], 'site-packages'))
builddir = join(dirname(self.hostpython_location), 'build')
- hppath += [join(builddir, d) for d in listdir(builddir)
- if isdir(join(builddir, d))]
- if 'PYTHONPATH' in env:
- env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']])
- else:
- env['PYTHONPATH'] = ':'.join(hppath)
+ if exists(builddir):
+ hppath += [join(builddir, d) for d in listdir(builddir)
+ if isdir(join(builddir, d))]
+ if len(hppath) > 0:
+ if 'PYTHONPATH' in env:
+ env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']])
+ else:
+ env['PYTHONPATH'] = ':'.join(hppath)
return env
def should_build(self, arch):
name = self.folder_name
- if self.ctx.has_package(name):
+ if self.ctx.has_package(name, arch):
info('Python package already exists in site-packages')
return False
info('{} apparently isn\'t already in site-packages'.format(name))
@@ -771,7 +930,7 @@ def should_build(self, arch):
def build_arch(self, arch):
'''Install the Python module by calling setup.py install with
the target Python dir.'''
- super(PythonRecipe, self).build_arch(arch)
+ super().build_arch(arch)
self.install_python_package(arch)
def install_python_package(self, arch, name=None, env=None, is_dir=True):
@@ -785,32 +944,13 @@ def install_python_package(self, arch, name=None, env=None, is_dir=True):
info('Installing {} into site-packages'.format(self.name))
+ hostpython = sh.Command(self.hostpython_location)
+ hpenv = env.copy()
with current_directory(self.get_build_dir(arch.arch)):
- hostpython = sh.Command(self.hostpython_location)
-
-
- if self.ctx.python_recipe.from_crystax:
- hpenv = env.copy()
- shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
- '--install-lib=.',
- _env=hpenv, *self.setup_extra_args)
- elif self.call_hostpython_via_targetpython:
- shprint(hostpython, 'setup.py', 'install', '-O2', _env=env,
- *self.setup_extra_args)
- else:
- hppath = join(dirname(self.hostpython_location), 'Lib',
- 'site-packages')
- hpenv = env.copy()
- if 'PYTHONPATH' in hpenv:
- hpenv['PYTHONPATH'] = ':'.join([hppath] +
- hpenv['PYTHONPATH'].split(':'))
- else:
- hpenv['PYTHONPATH'] = hppath
- shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
- '--install-lib=lib/python2.7/site-packages',
- _env=hpenv, *self.setup_extra_args)
+ shprint(hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),
+ '--install-lib=.',
+ _env=hpenv, *self.setup_extra_args)
# If asked, also install in the hostpython build dir
if self.install_in_hostpython:
@@ -847,8 +987,8 @@ def build_compiled_components(self, arch):
info('Building compiled components in {}'.format(self.name))
env = self.get_recipe_env(arch)
+ hostpython = sh.Command(self.hostpython_location)
with current_directory(self.get_build_dir(arch.arch)):
- hostpython = sh.Command(self.hostpython_location)
if self.install_in_hostpython:
shprint(hostpython, 'setup.py', 'clean', '--all', _env=env)
shprint(hostpython, 'setup.py', self.build_cmd, '-v',
@@ -860,7 +1000,7 @@ def build_compiled_components(self, arch):
def install_hostpython_package(self, arch):
env = self.get_hostrecipe_env(arch)
self.rebuild_compiled_components(arch, env)
- super(CompiledComponentsPythonRecipe, self).install_hostpython_package(arch)
+ super().install_hostpython_package(arch)
def rebuild_compiled_components(self, arch, env):
info('Rebuilding compiled components in {}'.format(self.name))
@@ -870,55 +1010,18 @@ def rebuild_compiled_components(self, arch, env):
shprint(hostpython, 'setup.py', self.build_cmd, '-v', _env=env,
*self.setup_extra_args)
+
class CppCompiledComponentsPythonRecipe(CompiledComponentsPythonRecipe):
""" Extensions that require the cxx-stl """
call_hostpython_via_targetpython = False
-
- def get_recipe_env(self, arch):
- env = super(CppCompiledComponentsPythonRecipe, self).get_recipe_env(arch)
- keys = dict(
- ctx=self.ctx,
- arch=arch,
- arch_noeabi=arch.arch.replace('eabi', ''),
- pyroot=self.ctx.get_python_install_dir()
- )
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['CFLAGS'] += " -I{pyroot}/include/python2.7 " \
- " -I{ctx.ndk_dir}/platforms/android-{ctx.android_api}/arch-{arch_noeabi}/usr/include" \
- " -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/include" \
- " -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/include".format(**keys)
- env['CXXFLAGS'] = env['CFLAGS'] + ' -frtti -fexceptions'
- env['LDFLAGS'] += " -L{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}" \
- " -lpython2.7" \
- " -lgnustl_shared".format(**keys)
-
-
- return env
-
- def build_compiled_components(self,arch):
- super(CppCompiledComponentsPythonRecipe, self).build_compiled_components(arch)
-
- # Copy libgnustl_shared.so
- with current_directory(self.get_build_dir(arch.arch)):
- sh.cp(
- "{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/libgnustl_shared.so".format(ctx=self.ctx,arch=arch),
- self.ctx.get_libs_dir(arch.arch)
- )
-
-
+ need_stl_shared = True
class CythonRecipe(PythonRecipe):
pre_build_ext = False
cythonize = True
cython_args = []
-
- def __init__(self, *args, **kwargs):
- super(CythonRecipe, self).__init__(*args, **kwargs)
- depends = self.depends
- depends.append(('python2', 'python3crystax'))
- depends = list(set(depends))
- self.depends = depends
+ call_hostpython_via_targetpython = False
def build_arch(self, arch):
'''Build any cython components, then install the Python module by
@@ -933,20 +1036,10 @@ def build_cython_components(self, arch):
env = self.get_recipe_env(arch)
- if self.ctx.python_recipe.from_crystax:
- command = sh.Command('python{}'.format(self.ctx.python_recipe.version))
- site_packages_dirs = command(
- '-c', 'import site; print("\\n".join(site.getsitepackages()))')
- site_packages_dirs = site_packages_dirs.stdout.decode('utf-8').split('\n')
- if 'PYTHONPATH' in env:
- env['PYTHONPATH'] = env['PYTHONPATH'] + ':{}'.format(':'.join(site_packages_dirs))
- else:
- env['PYTHONPATH'] = ':'.join(site_packages_dirs)
-
with current_directory(self.get_build_dir(arch.arch)):
hostpython = sh.Command(self.ctx.hostpython)
shprint(hostpython, '-c', 'import sys; print(sys.path)', _env=env)
- print('cwd is', realpath(curdir))
+ debug('cwd is {}'.format(realpath(curdir)))
info('Trying first build of {} to get cython files: this is '
'expected to fail'.format(self.name))
@@ -967,20 +1060,20 @@ def build_cython_components(self, arch):
info('First build appeared to complete correctly, skipping manual'
'cythonising.')
- if 'python2' in self.ctx.recipe_build_order:
- info('Stripping object files')
- build_lib = glob.glob('./build/lib*')
- shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
- env['STRIP'], '{}', ';', _env=env)
-
- if 'python3crystax' in self.ctx.recipe_build_order:
- info('Stripping object files')
- shprint(sh.find, '.', '-iname', '*.so', '-exec',
- '/usr/bin/echo', '{}', ';', _env=env)
- shprint(sh.find, '.', '-iname', '*.so', '-exec',
- env['STRIP'].split(' ')[0], '--strip-unneeded',
- # '/usr/bin/strip', '--strip-unneeded',
- '{}', ';', _env=env)
+ if not self.ctx.with_debug_symbols:
+ self.strip_object_files(arch, env)
+
+ def strip_object_files(self, arch, env, build_dir=None):
+ if build_dir is None:
+ build_dir = self.get_build_dir(arch.arch)
+ with current_directory(build_dir):
+ info('Stripping object files')
+ shprint(sh.find, '.', '-iname', '*.so', '-exec',
+ '/usr/bin/echo', '{}', ';', _env=env)
+ shprint(sh.find, '.', '-iname', '*.so', '-exec',
+ env['STRIP'].split(' ')[0], '--strip-unneeded',
+ # '/usr/bin/strip', '--strip-unneeded',
+ '{}', ';', _env=env)
def cythonize_file(self, env, build_dir, filename):
short_filename = filename
@@ -994,9 +1087,12 @@ def cythonize_file(self, env, build_dir, filename):
del cyenv['PYTHONPATH']
if 'PYTHONNOUSERSITE' in cyenv:
cyenv.pop('PYTHONNOUSERSITE')
- cython = 'cython' if self.ctx.python_recipe.from_crystax else self.ctx.cython
- cython_command = sh.Command(cython)
- shprint(cython_command, filename, *self.cython_args, _env=cyenv)
+ python_command = sh.Command("python{}".format(
+ self.ctx.python_recipe.major_minor_version_string.split(".")[0]
+ ))
+ shprint(python_command, "-c"
+ "import sys; from Cython.Compiler.Main import setuptools_main; sys.exit(setuptools_main());",
+ filename, *self.cython_args, _env=cyenv)
def cythonize_build(self, env, build_dir="."):
if not self.cythonize:
@@ -1008,23 +1104,16 @@ def cythonize_build(self, env, build_dir="."):
self.cythonize_file(env, build_dir, join(root, filename))
def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(CythonRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
env['LDFLAGS'] = env['LDFLAGS'] + ' -L{} '.format(
self.ctx.get_libs_dir(arch.arch) +
' -L{} '.format(self.ctx.libs_dir) +
' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'obj', 'local',
arch.arch)))
- if self.ctx.python_recipe.from_crystax:
- env['LDFLAGS'] = (env['LDFLAGS'] +
- ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)))
- # ' -L/home/asandy/.local/share/python-for-android/build/bootstrap_builds/sdl2/libs/armeabi '
- if self.ctx.python_recipe.from_crystax:
- env['LDSHARED'] = env['CC'] + ' -shared'
- else:
- env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink.sh')
+
+ env['LDSHARED'] = env['CC'] + ' -shared'
# shprint(sh.whereis, env['LDSHARED'], _env=env)
env['LIBLINK'] = 'NOTNONE'
- env['NDKPLATFORM'] = self.ctx.ndk_platform
if self.ctx.copy_libs:
env['COPYLIBS'] = '1'
@@ -1035,21 +1124,6 @@ def get_recipe_env(self, arch, with_flags_in_cc=True):
env['LIBLINK_PATH'] = liblink_path
ensure_dir(liblink_path)
- if self.ctx.python_recipe.from_crystax:
- env['CFLAGS'] = '-I{} '.format(
- join(self.ctx.ndk_dir, 'sources', 'python',
- self.ctx.python_recipe.version, 'include',
- 'python')) + env['CFLAGS']
-
- # Temporarily hardcode the -lpython3.x as this does not
- # get applied automatically in some environments. This
- # will need generalising, along with the other hardcoded
- # py3.5 references, to support other python3 or crystax
- # python versions.
- python3_version = self.ctx.python_recipe.version
- python3_version = '.'.join(python3_version.split('.')[:2])
- env['LDFLAGS'] = env['LDFLAGS'] + ' -lpython{}m'.format(python3_version)
-
return env
@@ -1057,36 +1131,56 @@ class TargetPythonRecipe(Recipe):
'''Class for target python recipes. Sets ctx.python_recipe to point to
itself, so as to know later what kind of Python was built or used.'''
- from_crystax = False
- '''True if the python is used from CrystaX, False otherwise (i.e. if
- it is built by p4a).'''
-
def __init__(self, *args, **kwargs):
self._ctx = None
- super(TargetPythonRecipe, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def prebuild_arch(self, arch):
- super(TargetPythonRecipe, self).prebuild_arch(arch)
- if self.from_crystax and self.ctx.ndk != 'crystax':
- error('The {} recipe can only be built when '
- 'using the CrystaX NDK. Exiting.'.format(self.name))
- exit(1)
+ super().prebuild_arch(arch)
self.ctx.python_recipe = self
- # @property
- # def ctx(self):
- # return self._ctx
+ def include_root(self, arch):
+ '''The root directory from which to include headers.'''
+ raise NotImplementedError('Not implemented in TargetPythonRecipe')
+
+ def link_root(self):
+ raise NotImplementedError('Not implemented in TargetPythonRecipe')
- # @ctx.setter
- # def ctx(self, ctx):
- # self._ctx = ctx
- # ctx.python_recipe = self
+ @property
+ def major_minor_version_string(self):
+ parsed_version = packaging.version.parse(self.version)
+ return f"{parsed_version.major}.{parsed_version.minor}"
+ def create_python_bundle(self, dirn, arch):
+ """
+ Create a packaged python bundle in the target directory, by
+ copying all the modules and standard library to the right
+ place.
+ """
+ raise NotImplementedError('{} does not implement create_python_bundle'.format(self))
-def md5sum(filen):
- '''Calculate the md5sum of a file.
+ def reduce_object_file_names(self, dirn):
+ """Recursively renames all files named XXX.cpython-...-linux-gnu.so"
+ to "XXX.so", i.e. removing the erroneous architecture name
+ coming from the local system.
+ """
+ py_so_files = shprint(sh.find, dirn, '-iname', '*.so')
+ filens = py_so_files.stdout.decode('utf-8').split('\n')[:-1]
+ for filen in filens:
+ file_dirname, file_basename = split(filen)
+ parts = file_basename.split('.')
+ if len(parts) <= 2:
+ continue
+ # PySide6 libraries end with .abi3.so
+ if parts[1] == "abi3":
+ continue
+ move(filen, join(file_dirname, parts[0] + '.so'))
+
+
+def algsum(alg, filen):
+ '''Calculate the digest of a file.
'''
with open(filen, 'rb') as fileh:
- md5 = hashlib.md5(fileh.read())
+ digest = getattr(hashlib, alg)(fileh.read())
- return md5.hexdigest()
+ return digest.hexdigest()
diff --git a/pythonforandroid/recipes/Pillow/__init__.py b/pythonforandroid/recipes/Pillow/__init__.py
new file mode 100644
index 000000000..f8f6929db
--- /dev/null
+++ b/pythonforandroid/recipes/Pillow/__init__.py
@@ -0,0 +1,90 @@
+from os.path import join
+
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
+
+class PillowRecipe(CompiledComponentsPythonRecipe):
+ """
+ A recipe for Pillow (previously known as Pil).
+
+ This recipe allow us to build the Pillow recipe with support for different
+ types of images and fonts. But you should be aware, that in order to use
+ some of the features of Pillow, we must build some libraries. By default
+ we automatically trigger the build of below libraries::
+
+ - freetype: rendering fonts support.
+ - harfbuzz: a text shaping library.
+ - jpeg: reading and writing JPEG image files.
+ - png: support for PNG images.
+
+ But you also could enable the build of some extra image types by requesting
+ the build of some libraries via argument `requirements`::
+
+ - libwebp: library to encode and decode images in WebP format.
+ """
+
+ version = '8.4.0'
+ url = 'https://github.com/python-pillow/Pillow/archive/{version}.tar.gz'
+ site_packages_name = 'Pillow'
+ depends = ['png', 'jpeg', 'freetype', 'setuptools']
+ opt_depends = ['libwebp']
+ patches = [join('patches', 'fix-setup.patch')]
+
+ call_hostpython_via_targetpython = False
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+
+ png = self.get_recipe('png', self.ctx)
+ png_lib_dir = join(png.get_build_dir(arch.arch), '.libs')
+ png_inc_dir = png.get_build_dir(arch)
+
+ jpeg = self.get_recipe('jpeg', self.ctx)
+ jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch)
+
+ freetype = self.get_recipe('freetype', self.ctx)
+ free_lib_dir = join(freetype.get_build_dir(arch.arch), 'objs', '.libs')
+ free_inc_dir = join(freetype.get_build_dir(arch.arch), 'include')
+
+ # harfbuzz is a direct dependency of freetype and we need the proper
+ # flags to successfully build the Pillow recipe, so we add them here.
+ harfbuzz = self.get_recipe('harfbuzz', self.ctx)
+ harf_lib_dir = join(harfbuzz.get_build_dir(arch.arch), 'src', '.libs')
+ harf_inc_dir = harfbuzz.get_build_dir(arch.arch)
+
+ # libwebp is an optional dependency, so we add the
+ # flags if we have it in our `ctx.recipe_build_order`
+ build_with_webp_support = 'libwebp' in self.ctx.recipe_build_order
+ if build_with_webp_support:
+ webp = self.get_recipe('libwebp', self.ctx)
+ webp_install = join(
+ webp.get_build_dir(arch.arch), 'installation'
+ )
+
+ # Add libraries includes to CFLAGS
+ cflags = f' -I{png_inc_dir}'
+ cflags += f' -I{harf_inc_dir} -I{join(harf_inc_dir, "src")}'
+ cflags += f' -I{free_inc_dir}'
+ cflags += f' -I{jpeg_inc_dir}'
+ if build_with_webp_support:
+ cflags += f' -I{join(webp_install, "include")}'
+ cflags += f' -I{self.ctx.ndk.sysroot_include_dir}'
+
+ # Link the basic Pillow libraries...no need to add webp's libraries
+ # since it seems that the linkage is properly made without it :)
+ env['LIBS'] = ' -lpng -lfreetype -lharfbuzz -ljpeg -lturbojpeg -lm'
+
+ # Add libraries locations to LDFLAGS
+ env['LDFLAGS'] += f' -L{png_lib_dir}'
+ env['LDFLAGS'] += f' -L{free_lib_dir}'
+ env['LDFLAGS'] += f' -L{harf_lib_dir}'
+ env['LDFLAGS'] += f' -L{jpeg_lib_dir}'
+ if build_with_webp_support:
+ env['LDFLAGS'] += f' -L{join(webp_install, "lib")}'
+ env['LDFLAGS'] += f' -L{arch.ndk_lib_dir_versioned}'
+ if cflags not in env['CFLAGS']:
+ env['CFLAGS'] += cflags + " -lm"
+ return env
+
+
+recipe = PillowRecipe()
diff --git a/pythonforandroid/recipes/Pillow/patches/fix-setup.patch b/pythonforandroid/recipes/Pillow/patches/fix-setup.patch
new file mode 100644
index 000000000..5c5a3d053
--- /dev/null
+++ b/pythonforandroid/recipes/Pillow/patches/fix-setup.patch
@@ -0,0 +1,196 @@
+--- Pillow.orig/setup.py 2021-11-01 14:50:48.000000000 +0100
++++ Pillow/setup.py 2021-11-01 14:51:31.000000000 +0100
+@@ -125,7 +125,7 @@
+ "codec_fd",
+ )
+
+-DEBUG = False
++DEBUG = True # So we can easely triage user issues.
+
+
+ class DependencyException(Exception):
+@@ -411,46 +411,6 @@
+ include_dirs = []
+
+ pkg_config = None
+- if _cmd_exists(os.environ.get("PKG_CONFIG", "pkg-config")):
+- pkg_config = _pkg_config
+-
+- #
+- # add configured kits
+- for root_name, lib_name in dict(
+- JPEG_ROOT="libjpeg",
+- JPEG2K_ROOT="libopenjp2",
+- TIFF_ROOT=("libtiff-5", "libtiff-4"),
+- ZLIB_ROOT="zlib",
+- FREETYPE_ROOT="freetype2",
+- HARFBUZZ_ROOT="harfbuzz",
+- FRIBIDI_ROOT="fribidi",
+- LCMS_ROOT="lcms2",
+- IMAGEQUANT_ROOT="libimagequant",
+- ).items():
+- root = globals()[root_name]
+-
+- if root is None and root_name in os.environ:
+- prefix = os.environ[root_name]
+- root = (os.path.join(prefix, "lib"), os.path.join(prefix, "include"))
+-
+- if root is None and pkg_config:
+- if isinstance(lib_name, tuple):
+- for lib_name2 in lib_name:
+- _dbg(f"Looking for `{lib_name2}` using pkg-config.")
+- root = pkg_config(lib_name2)
+- if root:
+- break
+- else:
+- _dbg(f"Looking for `{lib_name}` using pkg-config.")
+- root = pkg_config(lib_name)
+-
+- if isinstance(root, tuple):
+- lib_root, include_root = root
+- else:
+- lib_root = include_root = root
+-
+- _add_directory(library_dirs, lib_root)
+- _add_directory(include_dirs, include_root)
+
+ # respect CFLAGS/CPPFLAGS/LDFLAGS
+ for k in ("CFLAGS", "CPPFLAGS", "LDFLAGS"):
+@@ -471,137 +431,6 @@
+ for d in os.environ[k].split(os.path.pathsep):
+ _add_directory(library_dirs, d)
+
+- _add_directory(library_dirs, os.path.join(sys.prefix, "lib"))
+- _add_directory(include_dirs, os.path.join(sys.prefix, "include"))
+-
+- #
+- # add platform directories
+-
+- if self.disable_platform_guessing:
+- pass
+-
+- elif sys.platform == "cygwin":
+- # pythonX.Y.dll.a is in the /usr/lib/pythonX.Y/config directory
+- _add_directory(
+- library_dirs,
+- os.path.join(
+- "/usr/lib", "python{}.{}".format(*sys.version_info), "config"
+- ),
+- )
+-
+- elif sys.platform == "darwin":
+- # attempt to make sure we pick freetype2 over other versions
+- _add_directory(include_dirs, "/sw/include/freetype2")
+- _add_directory(include_dirs, "/sw/lib/freetype2/include")
+- # fink installation directories
+- _add_directory(library_dirs, "/sw/lib")
+- _add_directory(include_dirs, "/sw/include")
+- # darwin ports installation directories
+- _add_directory(library_dirs, "/opt/local/lib")
+- _add_directory(include_dirs, "/opt/local/include")
+-
+- # if Homebrew is installed, use its lib and include directories
+- try:
+- prefix = (
+- subprocess.check_output(["brew", "--prefix"])
+- .strip()
+- .decode("latin1")
+- )
+- except Exception:
+- # Homebrew not installed
+- prefix = None
+-
+- ft_prefix = None
+-
+- if prefix:
+- # add Homebrew's include and lib directories
+- _add_directory(library_dirs, os.path.join(prefix, "lib"))
+- _add_directory(include_dirs, os.path.join(prefix, "include"))
+- _add_directory(
+- include_dirs, os.path.join(prefix, "opt", "zlib", "include")
+- )
+- ft_prefix = os.path.join(prefix, "opt", "freetype")
+-
+- if ft_prefix and os.path.isdir(ft_prefix):
+- # freetype might not be linked into Homebrew's prefix
+- _add_directory(library_dirs, os.path.join(ft_prefix, "lib"))
+- _add_directory(include_dirs, os.path.join(ft_prefix, "include"))
+- else:
+- # fall back to freetype from XQuartz if
+- # Homebrew's freetype is missing
+- _add_directory(library_dirs, "/usr/X11/lib")
+- _add_directory(include_dirs, "/usr/X11/include")
+-
+- # SDK install path
+- sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
+- if not os.path.exists(sdk_path):
+- try:
+- sdk_path = (
+- subprocess.check_output(["xcrun", "--show-sdk-path"])
+- .strip()
+- .decode("latin1")
+- )
+- except Exception:
+- sdk_path = None
+- if sdk_path:
+- _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib"))
+- _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include"))
+- elif (
+- sys.platform.startswith("linux")
+- or sys.platform.startswith("gnu")
+- or sys.platform.startswith("freebsd")
+- ):
+- for dirname in _find_library_dirs_ldconfig():
+- _add_directory(library_dirs, dirname)
+- if sys.platform.startswith("linux") and os.environ.get(
+- "ANDROID_ROOT", None
+- ):
+- # termux support for android.
+- # system libraries (zlib) are installed in /system/lib
+- # headers are at $PREFIX/include
+- # user libs are at $PREFIX/lib
+- _add_directory(
+- library_dirs, os.path.join(os.environ["ANDROID_ROOT"], "lib")
+- )
+-
+- elif sys.platform.startswith("netbsd"):
+- _add_directory(library_dirs, "/usr/pkg/lib")
+- _add_directory(include_dirs, "/usr/pkg/include")
+-
+- elif sys.platform.startswith("sunos5"):
+- _add_directory(library_dirs, "/opt/local/lib")
+- _add_directory(include_dirs, "/opt/local/include")
+-
+- # FIXME: check /opt/stuff directories here?
+-
+- # standard locations
+- if not self.disable_platform_guessing:
+- _add_directory(library_dirs, "/usr/local/lib")
+- _add_directory(include_dirs, "/usr/local/include")
+-
+- _add_directory(library_dirs, "/usr/lib")
+- _add_directory(include_dirs, "/usr/include")
+- # alpine, at least
+- _add_directory(library_dirs, "/lib")
+-
+- if sys.platform == "win32":
+- # on Windows, look for the OpenJPEG libraries in the location that
+- # the official installer puts them
+- program_files = os.environ.get("ProgramFiles", "")
+- best_version = (0, 0)
+- best_path = None
+- for name in os.listdir(program_files):
+- if name.startswith("OpenJPEG "):
+- version = tuple(int(x) for x in name[9:].strip().split("."))
+- if version > best_version:
+- best_version = version
+- best_path = os.path.join(program_files, name)
+-
+- if best_path:
+- _dbg("Adding %s to search list", best_path)
+- _add_directory(library_dirs, os.path.join(best_path, "lib"))
+- _add_directory(include_dirs, os.path.join(best_path, "include"))
+-
+ #
+ # insert new dirs *before* default libs, to avoid conflicts
+ # between Python PYD stub libs and real libraries
\ No newline at end of file
diff --git a/pythonforandroid/recipes/aiohttp/__init__.py b/pythonforandroid/recipes/aiohttp/__init__.py
new file mode 100644
index 000000000..f32c653fc
--- /dev/null
+++ b/pythonforandroid/recipes/aiohttp/__init__.py
@@ -0,0 +1,20 @@
+"""Build AIOHTTP"""
+from typing import List
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
+
+class AIOHTTPRecipe(CppCompiledComponentsPythonRecipe): # type: ignore # pylint: disable=R0903
+ version = "3.8.3"
+ url = "https://pypi.python.org/packages/source/a/aiohttp/aiohttp-{version}.tar.gz"
+ name = "aiohttp"
+ depends: List[str] = ["setuptools"]
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['LDFLAGS'] += ' -lc++_shared'
+ return env
+
+
+recipe = AIOHTTPRecipe()
diff --git a/pythonforandroid/recipes/android/__init__.py b/pythonforandroid/recipes/android/__init__.py
index b8b10e7d0..608d9ee73 100644
--- a/pythonforandroid/recipes/android/__init__.py
+++ b/pythonforandroid/recipes/android/__init__.py
@@ -1,6 +1,5 @@
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
from pythonforandroid.util import current_directory
-from pythonforandroid.patching import will_build
from pythonforandroid import logger
from os.path import join
@@ -13,64 +12,77 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
src_filename = 'src'
- depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')]
+ depends = [('sdl2', 'genericndkbuild'), 'pyjnius']
config_env = {}
def get_recipe_env(self, arch):
- env = super(AndroidRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env.update(self.config_env)
return env
def prebuild_arch(self, arch):
- super(AndroidRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
+ ctx_bootstrap = self.ctx.bootstrap.name
+ # define macros for Cython, C, Python
tpxi = 'DEF {} = {}\n'
th = '#define {} {}\n'
tpy = '{} = {}\n'
- bootstrap = bootstrap_name = self.ctx.bootstrap.name
- is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3', 'sdl2_gradle')
- is_pygame = bootstrap_name in ('pygame',)
- is_webview = bootstrap_name in ('webview',)
-
- if is_sdl2 or is_webview:
- if is_sdl2:
- bootstrap = 'sdl2'
- java_ns = 'org.kivy.android'
- jni_ns = 'org/kivy/android'
- elif is_pygame:
- java_ns = 'org.renpy.android'
- jni_ns = 'org/renpy/android'
+ # make sure bootstrap name is in unicode
+ if isinstance(ctx_bootstrap, bytes):
+ ctx_bootstrap = ctx_bootstrap.decode('utf-8')
+ bootstrap = bootstrap_name = ctx_bootstrap
+ is_sdl2 = (bootstrap_name == "sdl2")
+ if bootstrap_name in ["sdl2", "webview", "service_only", "service_library", "qt"]:
+ java_ns = u'org.kivy.android'
+ jni_ns = u'org/kivy/android'
else:
- logger.error('unsupported bootstrap for android recipe: {}'.format(bootstrap_name))
+ logger.error((
+ 'unsupported bootstrap for android recipe: {}'
+ ''.format(bootstrap_name)
+ ))
exit(1)
config = {
'BOOTSTRAP': bootstrap,
'IS_SDL2': int(is_sdl2),
- 'IS_PYGAME': int(is_pygame),
- 'PY2': int(will_build('python2')(self)),
+ 'PY2': 0,
'JAVA_NAMESPACE': java_ns,
'JNI_NAMESPACE': jni_ns,
+ 'ACTIVITY_CLASS_NAME': self.ctx.activity_class_name,
+ 'ACTIVITY_CLASS_NAMESPACE': self.ctx.activity_class_name.replace('.', '/'),
+ 'SERVICE_CLASS_NAME': self.ctx.service_class_name,
}
- with current_directory(self.get_build_dir(arch.arch)):
- with open(join('android', 'config.pxi'), 'w') as fpxi:
- with open(join('android', 'config.h'), 'w') as fh:
- with open(join('android', 'config.py'), 'w') as fpy:
- for key, value in config.items():
- fpxi.write(tpxi.format(key, repr(value)))
- fpy.write(tpy.format(key, repr(value)))
- fh.write(th.format(key, value if isinstance(value, int)
- else '"{}"'.format(value)))
- self.config_env[key] = str(value)
-
- if is_sdl2:
- fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
- fh.write('#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n')
- elif is_pygame:
- fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
+ # create config files for Cython, C and Python
+ with (
+ current_directory(self.get_build_dir(arch.arch))), (
+ open(join('android', 'config.pxi'), 'w')) as fpxi, (
+ open(join('android', 'config.h'), 'w')) as fh, (
+ open(join('android', 'config.py'), 'w')) as fpy:
+
+ for key, value in config.items():
+ fpxi.write(tpxi.format(key, repr(value)))
+ fpy.write(tpy.format(key, repr(value)))
+
+ fh.write(th.format(
+ key,
+ value if isinstance(value, int) else '"{}"'.format(value)
+ ))
+ self.config_env[key] = str(value)
+
+ if is_sdl2:
+ fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
+ fh.write(
+ '#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n'
+ )
+ else:
+ fh.write('JNIEnv *WebView_AndroidGetJNIEnv(void);\n')
+ fh.write(
+ '#define SDL_ANDROID_GetJNIEnv WebView_AndroidGetJNIEnv\n'
+ )
recipe = AndroidRecipe()
diff --git a/pythonforandroid/recipes/android/src/android/__init__.py b/pythonforandroid/recipes/android/src/android/__init__.py
index c50c76135..cb95734cc 100644
--- a/pythonforandroid/recipes/android/src/android/__init__.py
+++ b/pythonforandroid/recipes/android/src/android/__init__.py
@@ -5,4 +5,4 @@
'''
# legacy import
-from android._android import *
+from android._android import * # noqa: F401, F403
diff --git a/pythonforandroid/recipes/android/src/android/_android.pyx b/pythonforandroid/recipes/android/src/android/_android.pyx
index f4f37d8ab..6708b846a 100644
--- a/pythonforandroid/recipes/android/src/android/_android.pyx
+++ b/pythonforandroid/recipes/android/src/android/_android.pyx
@@ -2,22 +2,6 @@
include "config.pxi"
-IF BOOTSTRAP == 'pygame':
- cdef extern int SDL_ANDROID_CheckPause()
- cdef extern void SDL_ANDROID_WaitForResume() nogil
- cdef extern void SDL_ANDROID_MapKey(int scancode, int keysym)
-
- def check_pause():
- return SDL_ANDROID_CheckPause()
-
- def wait_for_resume():
- android_accelerometer_enable(False)
- SDL_ANDROID_WaitForResume()
- android_accelerometer_enable(accelerometer_enabled)
-
- def map_key(scancode, keysym):
- SDL_ANDROID_MapKey(scancode, keysym)
-
# Android keycodes.
KEYCODE_UNKNOWN = 0
KEYCODE_SOFT_LEFT = 1
@@ -175,13 +159,11 @@ api_version = autoclass('android.os.Build$VERSION').SDK_INT
version_codes = autoclass('android.os.Build$VERSION_CODES')
-python_act = autoclass(JAVA_NAMESPACE + '.PythonActivity')
-Rect = autoclass('android.graphics.Rect')
+python_act = autoclass(ACTIVITY_CLASS_NAME)
+Rect = autoclass(u'android.graphics.Rect')
mActivity = python_act.mActivity
if mActivity:
- # PyGame backend already has the listener so adding
- # one here leads to a crash/too much cpu usage.
- # SDL2 now does noe need the listener so there is
+ # SDL2 now does not need the listener so there is
# no point adding a processor intensive layout listenere here.
height = 0
def get_keyboard_height():
@@ -274,42 +256,6 @@ def get_buildinfo():
binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE
return binfo
-IF IS_PYGAME:
- # Activate input - required to receive input events.
- cdef extern void android_activate_input()
-
- def init():
- android_activate_input()
-
- # Action send
- cdef extern void android_action_send(char*, char*, char*, char*, char*)
- def action_send(mimetype, filename=None, subject=None, text=None,
- chooser_title=None):
- cdef char *j_mimetype = <bytes>mimetype
- cdef char *j_filename = NULL
- cdef char *j_subject = NULL
- cdef char *j_text = NULL
- cdef char *j_chooser_title = NULL
- if filename is not None:
- j_filename = <bytes>filename
- if subject is not None:
- j_subject = <bytes>subject
- if text is not None:
- j_text = <bytes>text
- if chooser_title is not None:
- j_chooser_title = <bytes>chooser_title
- android_action_send(j_mimetype, j_filename, j_subject, j_text,
- j_chooser_title)
-
- cdef extern int android_checkstop()
- cdef extern void android_ackstop()
-
- def check_stop():
- return android_checkstop()
-
- def ack_stop():
- android_ackstop()
-
# -------------------------------------------------------------------
# URL Opening.
def open_url(url):
@@ -332,19 +278,31 @@ class AndroidBrowser(object):
return open_url(url)
import webbrowser
-webbrowser.register('android', AndroidBrowser, None, -1)
-
-cdef extern void android_start_service(char *, char *, char *)
-def start_service(title=None, description=None, arg=None):
- cdef char *j_title = NULL
- cdef char *j_description = NULL
- if title is not None:
- j_title = <bytes>title
- if description is not None:
- j_description = <bytes>description
- if arg is not None:
- j_arg = <bytes>arg
- android_start_service(j_title, j_description, j_arg)
+webbrowser.register('android', AndroidBrowser)
+
+
+def start_service(title="Background Service",
+ description="", arg="",
+ as_foreground=True):
+ # Legacy None value support (for old function signature style):
+ if title is None:
+ title = "Background Service"
+ if description is None:
+ description = ""
+ if arg is None:
+ arg = ""
+
+ # Start service:
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ if as_foreground:
+ mActivity.start_service(
+ title, description, arg
+ )
+ else:
+ mActivity.start_service_not_as_foreground(
+ title, description, arg
+ )
+
cdef extern void android_stop_service()
def stop_service():
diff --git a/pythonforandroid/recipes/android/src/android/_android_billing.pyx b/pythonforandroid/recipes/android/src/android/_android_billing.pyx
index bd6bb2ef7..d5ed2a00d 100644
--- a/pythonforandroid/recipes/android/src/android/_android_billing.pyx
+++ b/pythonforandroid/recipes/android/src/android/_android_billing.pyx
@@ -15,7 +15,7 @@ class BillingService(object):
BILLING_TYPE_SUBSCRIPTION = 'subs'
def __init__(self, callback):
- super(BillingService, self).__init__()
+ super().__init__()
self.callback = callback
self.purchased_items = None
android_billing_service_start()
diff --git a/pythonforandroid/recipes/android/src/android/_android_jni.c b/pythonforandroid/recipes/android/src/android/_android_jni.c
index 8eee77073..cf1b1bf50 100644
--- a/pythonforandroid/recipes/android/src/android/_android_jni.c
+++ b/pythonforandroid/recipes/android/src/android/_android_jni.c
@@ -201,146 +201,6 @@ void android_get_buildinfo() {
}
}
-#if IS_PYGAME
-void android_activate_input(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "activateInput", "()V");
- aassert(mid);
- }
-
- (*env)->CallStaticVoidMethod(env, cls, mid);
-}
-
-int android_checkstop(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "checkStop", "()I");
- aassert(mid);
- }
-
- return (*env)->CallStaticIntMethod(env, cls, mid);
-}
-
-void android_ackstop(void) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "ackStop", "()I");
- aassert(mid);
- }
-
- (*env)->CallStaticIntMethod(env, cls, mid);
-}
-
-void android_action_send(char *mimeType, char *filename, char *subject, char *text, char *chooser_title) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/Action");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "send",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
- aassert(mid);
- }
-
- jstring j_mimeType = (*env)->NewStringUTF(env, mimeType);
- jstring j_filename = NULL;
- jstring j_subject = NULL;
- jstring j_text = NULL;
- jstring j_chooser_title = NULL;
- if ( filename != NULL )
- j_filename = (*env)->NewStringUTF(env, filename);
- if ( subject != NULL )
- j_subject = (*env)->NewStringUTF(env, subject);
- if ( text != NULL )
- j_text = (*env)->NewStringUTF(env, text);
- if ( chooser_title != NULL )
- j_chooser_title = (*env)->NewStringUTF(env, text);
-
- (*env)->CallStaticVoidMethod(
- env, cls, mid,
- j_mimeType, j_filename, j_subject, j_text,
- j_chooser_title);
-}
-
-void android_open_url(char *url) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "openUrl", "(Ljava/lang/String;)V");
- aassert(mid);
- }
-
- PUSH_FRAME;
-
- (*env)->CallStaticVoidMethod(
- env, cls, mid,
- (*env)->NewStringUTF(env, url)
- );
-
- POP_FRAME;
-}
-#endif // IS_PYGAME
-
-void android_start_service(char *title, char *description, char *arg) {
- static JNIEnv *env = NULL;
- static jclass *cls = NULL;
- static jmethodID mid = NULL;
-
- if (env == NULL) {
- env = SDL_ANDROID_GetJNIEnv();
- aassert(env);
- cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
- aassert(cls);
- mid = (*env)->GetStaticMethodID(env, cls, "start_service",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
- aassert(mid);
- }
-
- jstring j_title = NULL;
- jstring j_description = NULL;
- jstring j_arg = NULL;
- if ( title != 0 )
- j_title = (*env)->NewStringUTF(env, title);
- if ( description != 0 )
- j_description = (*env)->NewStringUTF(env, description);
- if ( arg != 0 )
- j_arg = (*env)->NewStringUTF(env, arg);
-
- (*env)->CallStaticVoidMethod(env, cls, mid, j_title, j_description, j_arg);
-}
-
void android_stop_service() {
static JNIEnv *env = NULL;
static jclass *cls = NULL;
diff --git a/pythonforandroid/recipes/android/src/android/_ctypes_library_finder.py b/pythonforandroid/recipes/android/src/android/_ctypes_library_finder.py
new file mode 100644
index 000000000..a03512ef5
--- /dev/null
+++ b/pythonforandroid/recipes/android/src/android/_ctypes_library_finder.py
@@ -0,0 +1,67 @@
+
+import sys
+import os
+
+
+def get_activity_lib_dir(activity_name):
+ from jnius import autoclass
+
+ # Get the actual activity instance:
+ activity_class = autoclass(activity_name)
+ if activity_class is None:
+ return None
+ activity = None
+ if hasattr(activity_class, "mActivity") and \
+ activity_class.mActivity is not None:
+ activity = activity_class.mActivity
+ elif hasattr(activity_class, "mService") and \
+ activity_class.mService is not None:
+ activity = activity_class.mService
+ if activity is None:
+ return None
+
+ # Extract the native lib dir from the activity instance:
+ package_name = activity.getApplicationContext().getPackageName()
+ manager = activity.getApplicationContext().getPackageManager()
+ manager_class = autoclass("android.content.pm.PackageManager")
+ native_lib_dir = manager.getApplicationInfo(
+ package_name, manager_class.GET_SHARED_LIBRARY_FILES
+ ).nativeLibraryDir
+ return native_lib_dir
+
+
+def does_libname_match_filename(search_name, file_path):
+ # Filter file names so given search_name="mymodule" we match one of:
+ # mymodule.so (direct name + .so)
+ # libmymodule.so (added lib prefix)
+ # mymodule.arm64.so (added dot-separated middle parts)
+ # mymodule.so.1.3.4 (added dot-separated version tail)
+ # and all above (all possible combinations)
+ import re
+ file_name = os.path.basename(file_path)
+ return (re.match(r"^(lib)?" + re.escape(search_name) +
+ r"\.(.*\.)?so(\.[0-9]+)*$", file_name) is not None)
+
+
+def find_library(name):
+ # Obtain all places for native libraries:
+ if sys.maxsize > 2**32: # 64bit-build
+ lib_search_dirs = ["/system/lib64", "/system/lib"]
+ else:
+ lib_search_dirs = ["/system/lib"]
+ lib_dir_1 = get_activity_lib_dir("org.kivy.android.PythonActivity")
+ if lib_dir_1 is not None:
+ lib_search_dirs.insert(0, lib_dir_1)
+ lib_dir_2 = get_activity_lib_dir("org.kivy.android.PythonService")
+ if lib_dir_2 is not None and lib_dir_2 not in lib_search_dirs:
+ lib_search_dirs.insert(0, lib_dir_2)
+
+ # Now scan the lib dirs:
+ for lib_dir in [ldir for ldir in lib_search_dirs if os.path.exists(ldir)]:
+ filelist = [
+ f for f in os.listdir(lib_dir)
+ if does_libname_match_filename(name, f)
+ ]
+ if len(filelist) > 0:
+ return os.path.join(lib_dir, filelist[0])
+ return None
diff --git a/pythonforandroid/recipes/android/src/android/activity.py b/pythonforandroid/recipes/android/src/android/activity.py
index 94e08e71a..78d068c9f 100644
--- a/pythonforandroid/recipes/android/src/android/activity.py
+++ b/pythonforandroid/recipes/android/src/android/activity.py
@@ -1,18 +1,20 @@
-from jnius import PythonJavaClass, java_method, autoclass, cast
-from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
+from jnius import PythonJavaClass, autoclass, java_method
+from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
-_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity
+_activity = autoclass(ACTIVITY_CLASS_NAME).mActivity
_callbacks = {
'on_new_intent': [],
- 'on_activity_result': [] }
+ 'on_activity_result': [],
+}
+
class NewIntentListener(PythonJavaClass):
- __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener']
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$NewIntentListener']
__javacontext__ = 'app'
def __init__(self, callback, **kwargs):
- super(NewIntentListener, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.callback = callback
@java_method('(Landroid/content/Intent;)V')
@@ -21,11 +23,11 @@ def onNewIntent(self, intent):
class ActivityResultListener(PythonJavaClass):
- __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$ActivityResultListener']
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$ActivityResultListener']
__javacontext__ = 'app'
def __init__(self, callback):
- super(ActivityResultListener, self).__init__()
+ super().__init__()
self.callback = callback
@java_method('(IILandroid/content/Intent;)V')
@@ -46,6 +48,7 @@ def bind(**kwargs):
_activity.registerActivityResultListener(listener)
_callbacks[event].append(listener)
+
def unbind(**kwargs):
for event, callback in kwargs.items():
if event not in _callbacks:
@@ -59,3 +62,153 @@ def unbind(**kwargs):
elif event == 'on_activity_result':
_activity.unregisterActivityResultListener(listener)
+
+# Keep a reference to all the registered classes so that python doesn't
+# garbage collect them.
+_lifecycle_callbacks = set()
+
+
+class ActivityLifecycleCallbacks(PythonJavaClass):
+ """Callback class for handling PythonActivity lifecycle transitions"""
+
+ __javainterfaces__ = ['android/app/Application$ActivityLifecycleCallbacks']
+
+ def __init__(self, callbacks):
+ super().__init__()
+
+ # It would be nice to use keyword arguments, but PythonJavaClass
+ # doesn't allow that in its __cinit__ method.
+ if not isinstance(callbacks, dict):
+ raise ValueError('callbacks must be a dict instance')
+ self.callbacks = callbacks
+
+ def _callback(self, name, *args):
+ func = self.callbacks.get(name)
+ if func:
+ return func(*args)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityCreated(self, activity, savedInstanceState):
+ self._callback('onActivityCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityDestroyed(self, activity):
+ self._callback('onActivityDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPaused(self, activity):
+ self._callback('onActivityPaused', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPostCreated(self, activity, savedInstanceState):
+ self._callback('onActivityPostCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostDestroyed(self, activity):
+ self._callback('onActivityPostDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostPaused(self, activity):
+ self._callback('onActivityPostPaused', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostResumed(self, activity):
+ self._callback('onActivityPostResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPostSaveInstanceState(self, activity, outState):
+ self._callback('onActivityPostSaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostStarted(self, activity):
+ self._callback('onActivityPostStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPostStopped(self, activity):
+ self._callback('onActivityPostStopped', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPreCreated(self, activity, savedInstanceState):
+ self._callback('onActivityPreCreated', activity, savedInstanceState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreDestroyed(self, activity):
+ self._callback('onActivityPreDestroyed', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPrePaused(self, activity):
+ self._callback('onActivityPrePaused', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreResumed(self, activity):
+ self._callback('onActivityPreResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivityPreSaveInstanceState(self, activity, outState):
+ self._callback('onActivityPreSaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreStarted(self, activity):
+ self._callback('onActivityPreStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityPreStopped(self, activity):
+ self._callback('onActivityPreStopped', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityResumed(self, activity):
+ self._callback('onActivityResumed', activity)
+
+ @java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
+ def onActivitySaveInstanceState(self, activity, outState):
+ self._callback('onActivitySaveInstanceState', activity, outState)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityStarted(self, activity):
+ self._callback('onActivityStarted', activity)
+
+ @java_method('(Landroid/app/Activity;)V')
+ def onActivityStopped(self, activity):
+ self._callback('onActivityStopped', activity)
+
+
+def register_activity_lifecycle_callbacks(**callbacks):
+ """Register ActivityLifecycleCallbacks instance
+
+ The callbacks are supplied as keyword arguments corresponding to the
+ Application.ActivityLifecycleCallbacks methods such as
+ onActivityStarted. See the ActivityLifecycleCallbacks documentation
+ for the signature of each method.
+
+ The ActivityLifecycleCallbacks instance is returned so it can be
+ supplied to unregister_activity_lifecycle_callbacks if needed.
+ """
+ instance = ActivityLifecycleCallbacks(callbacks)
+ _lifecycle_callbacks.add(instance)
+
+ # Use the registerActivityLifecycleCallbacks method from the
+ # Activity class if it's available (API 29) since it guarantees the
+ # callbacks will only be run for that activity. Otherwise, fallback
+ # to the method on the Application class (API 14). In practice there
+ # should be no difference since p4a applications only have a single
+ # activity.
+ if hasattr(_activity, 'registerActivityLifecycleCallbacks'):
+ _activity.registerActivityLifecycleCallbacks(instance)
+ else:
+ app = _activity.getApplication()
+ app.registerActivityLifecycleCallbacks(instance)
+ return instance
+
+
+def unregister_activity_lifecycle_callbacks(instance):
+ """Unregister ActivityLifecycleCallbacks instance"""
+ if hasattr(_activity, 'unregisterActivityLifecycleCallbacks'):
+ _activity.unregisterActivityLifecycleCallbacks(instance)
+ else:
+ app = _activity.getApplication()
+ app.unregisterActivityLifecycleCallbacks(instance)
+
+ try:
+ _lifecycle_callbacks.remove(instance)
+ except KeyError:
+ pass
diff --git a/pythonforandroid/recipes/android/src/android/billing.py b/pythonforandroid/recipes/android/src/android/billing.py
index 46715dc94..0ea10083c 100644
--- a/pythonforandroid/recipes/android/src/android/billing.py
+++ b/pythonforandroid/recipes/android/src/android/billing.py
@@ -3,5 +3,3 @@
===================
'''
-
-from android._android_billing import *
diff --git a/pythonforandroid/recipes/android/src/android/broadcast.py b/pythonforandroid/recipes/android/src/android/broadcast.py
index ba3dfc976..3232d83bb 100644
--- a/pythonforandroid/recipes/android/src/android/broadcast.py
+++ b/pythonforandroid/recipes/android/src/android/broadcast.py
@@ -2,7 +2,7 @@
# Broadcast receiver bridge
from jnius import autoclass, PythonJavaClass, java_method
-from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
+from android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
class BroadcastReceiver(object):
@@ -20,7 +20,7 @@ def onReceive(self, context, intent):
self.callback(context, intent)
def __init__(self, callback, actions=None, categories=None):
- super(BroadcastReceiver, self).__init__()
+ super().__init__()
self.callback = callback
if not actions and not categories:
@@ -28,7 +28,7 @@ def __init__(self, callback, actions=None, categories=None):
def _expand_partial_name(partial_name):
if '.' in partial_name:
- return partial_name # Its actually a full dotted name
+ return partial_name # Its actually a full dotted name
else:
name = 'ACTION_{}'.format(partial_name.upper())
if not hasattr(Intent, name):
@@ -61,8 +61,8 @@ def start(self):
Handler = autoclass('android.os.Handler')
self.handlerthread.start()
self.handler = Handler(self.handlerthread.getLooper())
- self.context.registerReceiver(self.receiver, self.receiver_filter, None,
- self.handler)
+ self.context.registerReceiver(
+ self.receiver, self.receiver_filter, None, self.handler)
def stop(self):
self.context.unregisterReceiver(self.receiver)
@@ -72,8 +72,7 @@ def stop(self):
def context(self):
from os import environ
if 'PYTHON_SERVICE_ARGUMENT' in environ:
- PythonService = autoclass(JAVA_NAMESPACE + '.PythonService')
+ PythonService = autoclass(SERVICE_CLASS_NAME)
return PythonService.mService
- PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
+ PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
return PythonActivity.mActivity
-
diff --git a/pythonforandroid/recipes/android/src/android/loadingscreen.py b/pythonforandroid/recipes/android/src/android/loadingscreen.py
new file mode 100644
index 000000000..a18162e06
--- /dev/null
+++ b/pythonforandroid/recipes/android/src/android/loadingscreen.py
@@ -0,0 +1,9 @@
+
+from jnius import autoclass
+
+from android.config import ACTIVITY_CLASS_NAME
+
+
+def hide_loading_screen():
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ mActivity.removeLoadingScreen()
diff --git a/pythonforandroid/recipes/android/src/android/mixer.py b/pythonforandroid/recipes/android/src/android/mixer.py
index 4ac224a79..303a9530e 100644
--- a/pythonforandroid/recipes/android/src/android/mixer.py
+++ b/pythonforandroid/recipes/android/src/android/mixer.py
@@ -8,36 +8,45 @@
condition = threading.Condition()
+
def periodic():
for i in range(0, num_channels):
if i in channels:
channels[i].periodic()
+
num_channels = 8
reserved_channels = 0
+
def init(frequency=22050, size=-16, channels=2, buffer=4096):
return None
+
def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096):
return None
+
def quit():
stop()
return None
+
def stop():
for i in range(0, num_channels):
sound.stop(i)
+
def pause():
for i in range(0, num_channels):
sound.pause(i)
+
def unpause():
for i in range(0, num_channels):
sound.unpause(i)
+
def get_busy():
for i in range(0, num_channels):
if sound.busy(i):
@@ -45,28 +54,33 @@ def get_busy():
return False
+
def fadeout(time):
# Fadeout doesn't work - it just immediately stops playback.
stop()
# A map from channel number to Channel object.
-channels = { }
+channels = {}
+
def set_num_channels(count):
global num_channels
num_channels = count
+
def get_num_channels(count):
return num_channels
+
def set_reserved(count):
global reserved_channels
reserved_channels = count
+
def find_channel(force=False):
- busy = [ ]
+ busy = []
for i in range(reserved_channels, num_channels):
c = Channel(i)
@@ -79,10 +93,11 @@ def find_channel(force=False):
if not force:
return None
- busy.sort(key=lambda x : x.play_time)
+ busy.sort(key=lambda x: x.play_time)
return busy[0]
+
class ChannelImpl(object):
def __init__(self, id):
@@ -101,7 +116,6 @@ def periodic(self):
if self.loop is not None and sound.queue_depth(self.id) < 2:
self.queue(self.loop, loops=1)
-
def play(self, s, loops=0, maxtime=0, fade_ms=0):
if loops:
self.loop = s
@@ -181,7 +195,8 @@ def Channel(n):
sound_serial = 0
-sounds = { }
+sounds = {}
+
class Sound(object):
@@ -196,10 +211,10 @@ def __init__(self, what):
self.serial = str(sound_serial)
sound_serial += 1
- if isinstance(what, file):
+ if isinstance(what, file): # noqa F821
self.file = what
else:
- self.file = file(os.path.abspath(what), "rb")
+ self.file = file(os.path.abspath(what), "rb") # noqa F821
sounds[self.serial] = self
@@ -214,7 +229,6 @@ def play(self, loops=0, maxtime=0, fade_ms=0):
channel.play(self, loops=loops)
return channel
-
def stop(self):
for i in range(0, num_channels):
if Channel(i).get_sound() is self:
@@ -244,9 +258,11 @@ def get_num_channels(self):
def get_length(self):
return 1.0
+
music_channel = Channel(256)
music_sound = None
+
class music(object):
@staticmethod
@@ -306,6 +322,3 @@ def get_pos():
@staticmethod
def queue(filename):
return music_channel.queue(Sound(filename))
-
-
-
diff --git a/pythonforandroid/recipes/android/src/android/permissions.py b/pythonforandroid/recipes/android/src/android/permissions.py
new file mode 100644
index 000000000..0ce568fbe
--- /dev/null
+++ b/pythonforandroid/recipes/android/src/android/permissions.py
@@ -0,0 +1,618 @@
+import threading
+
+try:
+ from jnius import autoclass, PythonJavaClass, java_method
+except ImportError:
+ # To allow importing by build/manifest-creating code without
+ # pyjnius being present:
+ def autoclass(item):
+ raise RuntimeError("pyjnius not available")
+
+
+from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
+
+
+class Permission:
+ ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
+ ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION"
+ ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
+ ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"
+ ACCESS_LOCATION_EXTRA_COMMANDS = (
+ "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"
+ )
+ ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE"
+ ACCESS_NOTIFICATION_POLICY = (
+ "android.permission.ACCESS_NOTIFICATION_POLICY"
+ )
+ ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE"
+ ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"
+ ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"
+ BATTERY_STATS = "android.permission.BATTERY_STATS"
+ BIND_ACCESSIBILITY_SERVICE = (
+ "android.permission.BIND_ACCESSIBILITY_SERVICE"
+ )
+ BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE"
+ BIND_CARRIER_MESSAGING_SERVICE = ( # note: deprecated in api 23+
+ "android.permission.BIND_CARRIER_MESSAGING_SERVICE"
+ )
+ BIND_CARRIER_SERVICES = ( # replaces BIND_CARRIER_MESSAGING_SERVICE
+ "android.permission.BIND_CARRIER_SERVICES"
+ )
+ BIND_CHOOSER_TARGET_SERVICE = (
+ "android.permission.BIND_CHOOSER_TARGET_SERVICE"
+ )
+ BIND_CONDITION_PROVIDER_SERVICE = (
+ "android.permission.BIND_CONDITION_PROVIDER_SERVICE"
+ )
+ BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN"
+ BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE"
+ BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE"
+ BIND_INPUT_METHOD = (
+ "android.permission.BIND_INPUT_METHOD"
+ )
+ BIND_MIDI_DEVICE_SERVICE = (
+ "android.permission.BIND_MIDI_DEVICE_SERVICE"
+ )
+ BIND_NFC_SERVICE = (
+ "android.permission.BIND_NFC_SERVICE"
+ )
+ BIND_NOTIFICATION_LISTENER_SERVICE = (
+ "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+ )
+ BIND_PRINT_SERVICE = (
+ "android.permission.BIND_PRINT_SERVICE"
+ )
+ BIND_QUICK_SETTINGS_TILE = (
+ "android.permission.BIND_QUICK_SETTINGS_TILE"
+ )
+ BIND_REMOTEVIEWS = (
+ "android.permission.BIND_REMOTEVIEWS"
+ )
+ BIND_SCREENING_SERVICE = (
+ "android.permission.BIND_SCREENING_SERVICE"
+ )
+ BIND_TELECOM_CONNECTION_SERVICE = (
+ "android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+ )
+ BIND_TEXT_SERVICE = (
+ "android.permission.BIND_TEXT_SERVICE"
+ )
+ BIND_TV_INPUT = (
+ "android.permission.BIND_TV_INPUT"
+ )
+ BIND_VISUAL_VOICEMAIL_SERVICE = (
+ "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
+ )
+ BIND_VOICE_INTERACTION = (
+ "android.permission.BIND_VOICE_INTERACTION"
+ )
+ BIND_VPN_SERVICE = (
+ "android.permission.BIND_VPN_SERVICE"
+ )
+ BIND_VR_LISTENER_SERVICE = (
+ "android.permission.BIND_VR_LISTENER_SERVICE"
+ )
+ BIND_WALLPAPER = (
+ "android.permission.BIND_WALLPAPER"
+ )
+ BLUETOOTH = (
+ "android.permission.BLUETOOTH"
+ )
+ BLUETOOTH_ADVERTISE = (
+ "android.permission.BLUETOOTH_ADVERTISE"
+ )
+ BLUETOOTH_CONNECT = (
+ "android.permission.BLUETOOTH_CONNECT"
+ )
+ BLUETOOTH_SCAN = (
+ "android.permission.BLUETOOTH_SCAN"
+ )
+ BLUETOOTH_ADMIN = (
+ "android.permission.BLUETOOTH_ADMIN"
+ )
+ BODY_SENSORS = (
+ "android.permission.BODY_SENSORS"
+ )
+ BROADCAST_PACKAGE_REMOVED = (
+ "android.permission.BROADCAST_PACKAGE_REMOVED"
+ )
+ BROADCAST_STICKY = (
+ "android.permission.BROADCAST_STICKY"
+ )
+ CALL_PHONE = (
+ "android.permission.CALL_PHONE"
+ )
+ CALL_PRIVILEGED = (
+ "android.permission.CALL_PRIVILEGED"
+ )
+ CAMERA = (
+ "android.permission.CAMERA"
+ )
+ CAPTURE_AUDIO_OUTPUT = (
+ "android.permission.CAPTURE_AUDIO_OUTPUT"
+ )
+ CAPTURE_SECURE_VIDEO_OUTPUT = (
+ "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
+ )
+ CAPTURE_VIDEO_OUTPUT = (
+ "android.permission.CAPTURE_VIDEO_OUTPUT"
+ )
+ CHANGE_COMPONENT_ENABLED_STATE = (
+ "android.permission.CHANGE_COMPONENT_ENABLED_STATE"
+ )
+ CHANGE_CONFIGURATION = (
+ "android.permission.CHANGE_CONFIGURATION"
+ )
+ CHANGE_NETWORK_STATE = (
+ "android.permission.CHANGE_NETWORK_STATE"
+ )
+ CHANGE_WIFI_MULTICAST_STATE = (
+ "android.permission.CHANGE_WIFI_MULTICAST_STATE"
+ )
+ CHANGE_WIFI_STATE = (
+ "android.permission.CHANGE_WIFI_STATE"
+ )
+ CLEAR_APP_CACHE = (
+ "android.permission.CLEAR_APP_CACHE"
+ )
+ CONTROL_LOCATION_UPDATES = (
+ "android.permission.CONTROL_LOCATION_UPDATES"
+ )
+ DELETE_CACHE_FILES = (
+ "android.permission.DELETE_CACHE_FILES"
+ )
+ DELETE_PACKAGES = (
+ "android.permission.DELETE_PACKAGES"
+ )
+ DIAGNOSTIC = (
+ "android.permission.DIAGNOSTIC"
+ )
+ DISABLE_KEYGUARD = (
+ "android.permission.DISABLE_KEYGUARD"
+ )
+ DUMP = (
+ "android.permission.DUMP"
+ )
+ EXPAND_STATUS_BAR = (
+ "android.permission.EXPAND_STATUS_BAR"
+ )
+ FACTORY_TEST = (
+ "android.permission.FACTORY_TEST"
+ )
+ FOREGROUND_SERVICE = (
+ "android.permission.FOREGROUND_SERVICE"
+ )
+ GET_ACCOUNTS = (
+ "android.permission.GET_ACCOUNTS"
+ )
+ GET_ACCOUNTS_PRIVILEGED = (
+ "android.permission.GET_ACCOUNTS_PRIVILEGED"
+ )
+ GET_PACKAGE_SIZE = (
+ "android.permission.GET_PACKAGE_SIZE"
+ )
+ GET_TASKS = (
+ "android.permission.GET_TASKS"
+ )
+ GLOBAL_SEARCH = (
+ "android.permission.GLOBAL_SEARCH"
+ )
+ INSTALL_LOCATION_PROVIDER = (
+ "android.permission.INSTALL_LOCATION_PROVIDER"
+ )
+ INSTALL_PACKAGES = (
+ "android.permission.INSTALL_PACKAGES"
+ )
+ INSTALL_SHORTCUT = (
+ "com.android.launcher.permission.INSTALL_SHORTCUT"
+ )
+ INSTANT_APP_FOREGROUND_SERVICE = (
+ "android.permission.INSTANT_APP_FOREGROUND_SERVICE"
+ )
+ INTERNET = (
+ "android.permission.INTERNET"
+ )
+ KILL_BACKGROUND_PROCESSES = (
+ "android.permission.KILL_BACKGROUND_PROCESSES"
+ )
+ LOCATION_HARDWARE = (
+ "android.permission.LOCATION_HARDWARE"
+ )
+ MANAGE_DOCUMENTS = (
+ "android.permission.MANAGE_DOCUMENTS"
+ )
+ MANAGE_OWN_CALLS = (
+ "android.permission.MANAGE_OWN_CALLS"
+ )
+ MASTER_CLEAR = (
+ "android.permission.MASTER_CLEAR"
+ )
+ MEDIA_CONTENT_CONTROL = (
+ "android.permission.MEDIA_CONTENT_CONTROL"
+ )
+ MODIFY_AUDIO_SETTINGS = (
+ "android.permission.MODIFY_AUDIO_SETTINGS"
+ )
+ MODIFY_PHONE_STATE = (
+ "android.permission.MODIFY_PHONE_STATE"
+ )
+ MOUNT_FORMAT_FILESYSTEMS = (
+ "android.permission.MOUNT_FORMAT_FILESYSTEMS"
+ )
+ MOUNT_UNMOUNT_FILESYSTEMS = (
+ "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+ )
+ NEARBY_WIFI_DEVICES = (
+ "android.permission.NEARBY_WIFI_DEVICES"
+ )
+ NFC = (
+ "android.permission.NFC"
+ )
+ NFC_TRANSACTION_EVENT = (
+ "android.permission.NFC_TRANSACTION_EVENT"
+ )
+ PACKAGE_USAGE_STATS = (
+ "android.permission.PACKAGE_USAGE_STATS"
+ )
+ PERSISTENT_ACTIVITY = (
+ "android.permission.PERSISTENT_ACTIVITY"
+ )
+ POST_NOTIFICATIONS = (
+ "android.permission.POST_NOTIFICATIONS"
+ )
+ PROCESS_OUTGOING_CALLS = (
+ "android.permission.PROCESS_OUTGOING_CALLS"
+ )
+ READ_CALENDAR = (
+ "android.permission.READ_CALENDAR"
+ )
+ READ_CALL_LOG = (
+ "android.permission.READ_CALL_LOG"
+ )
+ READ_CONTACTS = (
+ "android.permission.READ_CONTACTS"
+ )
+ READ_EXTERNAL_STORAGE = (
+ "android.permission.READ_EXTERNAL_STORAGE"
+ )
+ READ_FRAME_BUFFER = (
+ "android.permission.READ_FRAME_BUFFER"
+ )
+ READ_INPUT_STATE = (
+ "android.permission.READ_INPUT_STATE"
+ )
+ READ_LOGS = (
+ "android.permission.READ_LOGS"
+ )
+ READ_MEDIA_AUDIO = (
+ "android.permission.READ_MEDIA_AUDIO"
+ )
+ READ_MEDIA_IMAGES = (
+ "android.permission.READ_MEDIA_IMAGES"
+ )
+ READ_MEDIA_VIDEO = (
+ "android.permission.READ_MEDIA_VIDEO"
+ )
+ READ_PHONE_NUMBERS = (
+ "android.permission.READ_PHONE_NUMBERS"
+ )
+ READ_PHONE_STATE = (
+ "android.permission.READ_PHONE_STATE"
+ )
+ READ_SMS = (
+ "android.permission.READ_SMS"
+ )
+ READ_SYNC_SETTINGS = (
+ "android.permission.READ_SYNC_SETTINGS"
+ )
+ READ_SYNC_STATS = (
+ "android.permission.READ_SYNC_STATS"
+ )
+ READ_VOICEMAIL = (
+ "com.android.voicemail.permission.READ_VOICEMAIL"
+ )
+ REBOOT = (
+ "android.permission.REBOOT"
+ )
+ RECEIVE_BOOT_COMPLETED = (
+ "android.permission.RECEIVE_BOOT_COMPLETED"
+ )
+ RECEIVE_MMS = (
+ "android.permission.RECEIVE_MMS"
+ )
+ RECEIVE_SMS = (
+ "android.permission.RECEIVE_SMS"
+ )
+ RECEIVE_WAP_PUSH = (
+ "android.permission.RECEIVE_WAP_PUSH"
+ )
+ RECORD_AUDIO = (
+ "android.permission.RECORD_AUDIO"
+ )
+ REORDER_TASKS = (
+ "android.permission.REORDER_TASKS"
+ )
+ REQUEST_COMPANION_RUN_IN_BACKGROUND = (
+ "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"
+ )
+ REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = (
+ "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"
+ )
+ REQUEST_DELETE_PACKAGES = (
+ "android.permission.REQUEST_DELETE_PACKAGES"
+ )
+ REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = (
+ "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
+ )
+ REQUEST_INSTALL_PACKAGES = (
+ "android.permission.REQUEST_INSTALL_PACKAGES"
+ )
+ RESTART_PACKAGES = (
+ "android.permission.RESTART_PACKAGES"
+ )
+ SEND_RESPOND_VIA_MESSAGE = (
+ "android.permission.SEND_RESPOND_VIA_MESSAGE"
+ )
+ SEND_SMS = (
+ "android.permission.SEND_SMS"
+ )
+ SET_ALARM = (
+ "com.android.alarm.permission.SET_ALARM"
+ )
+ SET_ALWAYS_FINISH = (
+ "android.permission.SET_ALWAYS_FINISH"
+ )
+ SET_ANIMATION_SCALE = (
+ "android.permission.SET_ANIMATION_SCALE"
+ )
+ SET_DEBUG_APP = (
+ "android.permission.SET_DEBUG_APP"
+ )
+ SET_PREFERRED_APPLICATIONS = (
+ "android.permission.SET_PREFERRED_APPLICATIONS"
+ )
+ SET_PROCESS_LIMIT = (
+ "android.permission.SET_PROCESS_LIMIT"
+ )
+ SET_TIME = (
+ "android.permission.SET_TIME"
+ )
+ SET_TIME_ZONE = (
+ "android.permission.SET_TIME_ZONE"
+ )
+ SET_WALLPAPER = (
+ "android.permission.SET_WALLPAPER"
+ )
+ SET_WALLPAPER_HINTS = (
+ "android.permission.SET_WALLPAPER_HINTS"
+ )
+ SIGNAL_PERSISTENT_PROCESSES = (
+ "android.permission.SIGNAL_PERSISTENT_PROCESSES"
+ )
+ STATUS_BAR = (
+ "android.permission.STATUS_BAR"
+ )
+ SYSTEM_ALERT_WINDOW = (
+ "android.permission.SYSTEM_ALERT_WINDOW"
+ )
+ TRANSMIT_IR = (
+ "android.permission.TRANSMIT_IR"
+ )
+ UNINSTALL_SHORTCUT = (
+ "com.android.launcher.permission.UNINSTALL_SHORTCUT"
+ )
+ UPDATE_DEVICE_STATS = (
+ "android.permission.UPDATE_DEVICE_STATS"
+ )
+ USE_BIOMETRIC = (
+ "android.permission.USE_BIOMETRIC"
+ )
+ USE_FINGERPRINT = (
+ "android.permission.USE_FINGERPRINT"
+ )
+ USE_SIP = (
+ "android.permission.USE_SIP"
+ )
+ VIBRATE = (
+ "android.permission.VIBRATE"
+ )
+ WAKE_LOCK = (
+ "android.permission.WAKE_LOCK"
+ )
+ WRITE_APN_SETTINGS = (
+ "android.permission.WRITE_APN_SETTINGS"
+ )
+ WRITE_CALENDAR = (
+ "android.permission.WRITE_CALENDAR"
+ )
+ WRITE_CALL_LOG = (
+ "android.permission.WRITE_CALL_LOG"
+ )
+ WRITE_CONTACTS = (
+ "android.permission.WRITE_CONTACTS"
+ )
+ WRITE_EXTERNAL_STORAGE = (
+ "android.permission.WRITE_EXTERNAL_STORAGE"
+ )
+ WRITE_GSERVICES = (
+ "android.permission.WRITE_GSERVICES"
+ )
+ WRITE_SECURE_SETTINGS = (
+ "android.permission.WRITE_SECURE_SETTINGS"
+ )
+ WRITE_SETTINGS = (
+ "android.permission.WRITE_SETTINGS"
+ )
+ WRITE_SYNC_SETTINGS = (
+ "android.permission.WRITE_SYNC_SETTINGS"
+ )
+ WRITE_VOICEMAIL = (
+ "com.android.voicemail.permission.WRITE_VOICEMAIL"
+ )
+
+
+PERMISSION_GRANTED = 0
+PERMISSION_DENIED = -1
+
+
+class _onRequestPermissionsCallback(PythonJavaClass):
+ """Callback class for registering a Python callback from
+ onRequestPermissionsResult in PythonActivity.
+ """
+ __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$PermissionsCallback']
+ __javacontext__ = 'app'
+
+ def __init__(self, func):
+ self.func = func
+ super().__init__()
+
+ @java_method('(I[Ljava/lang/String;[I)V')
+ def onRequestPermissionsResult(self, requestCode,
+ permissions, grantResults):
+ self.func(requestCode, permissions, grantResults)
+
+
+class _RequestPermissionsManager:
+ """Internal class for requesting Android permissions.
+
+ Permissions are requested through the method 'request_permissions' which
+ accepts a list of permissions and an optional callback.
+
+ Any callback will asynchronously receive arguments from
+ onRequestPermissionsResult on PythonActivity after requestPermissions is
+ called.
+
+ The callback supplied must accept two arguments: 'permissions' and
+ 'grantResults' (as supplied to onPermissionsCallbackResult).
+
+ Note that for SDK_INT < 23, run-time permissions are not required, and so
+ the callback will be called immediately.
+
+ The attribute '_java_callback' is initially None, but is set when the first
+ permissions request is made. It is set to an instance of
+ onRequestPermissionsCallback, which allows the Java callback to be
+ propagated to the class method 'python_callback'. This is then, in turn,
+ used to call an application callback if provided to request_permissions.
+
+ The attribute '_callback_id' is incremented with each call to
+ request_permissions which has a callback (the value '1' is used for any
+ call which does not pass a callback). This is passed to requestCode in
+ the Java call, and used to identify (via the _callbacks dictionary)
+ the matching call.
+ """
+ _SDK_INT = None
+ _java_callback = None
+ _callbacks = {1: None}
+ _callback_id = 1
+ # Lock to prevent multiple calls to request_permissions being handled
+ # simultaneously (as incrementing _callback_id is not atomic)
+ _lock = threading.Lock()
+
+ @classmethod
+ def register_callback(cls):
+ """Register Java callback for requestPermissions."""
+ cls._java_callback = _onRequestPermissionsCallback(cls.python_callback)
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ mActivity.addPermissionsCallback(cls._java_callback)
+
+ @classmethod
+ def request_permissions(cls, permissions, callback=None):
+ """Requests Android permissions from PythonActivity.
+ If 'callback' is supplied, the request is made with a new requestCode
+ and the callback is stored in the _callbacks dict. When a Java callback
+ with the matching requestCode is received, callback will be called
+ with arguments of 'permissions' and 'grant_results'.
+ """
+ if not cls._SDK_INT:
+ # Get the Android build version and store it
+ VERSION = autoclass('android.os.Build$VERSION')
+ cls.SDK_INT = VERSION.SDK_INT
+ if cls.SDK_INT < 23:
+ # No run-time permissions needed, return immediately.
+ if callback:
+ callback(permissions, [True for x in permissions])
+ return
+ # Request permissions
+ with cls._lock:
+ if not cls._java_callback:
+ cls.register_callback()
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ if not callback:
+ mActivity.requestPermissions(permissions)
+ else:
+ cls._callback_id += 1
+ mActivity.requestPermissionsWithRequestCode(
+ permissions, cls._callback_id)
+ cls._callbacks[cls._callback_id] = callback
+
+ @classmethod
+ def python_callback(cls, requestCode, permissions, grantResults):
+ """Calls the relevant callback with arguments of 'permissions'
+ and 'grantResults'."""
+ # Convert from Android codes to True/False
+ grant_results = [x == PERMISSION_GRANTED for x in grantResults]
+ if cls._callbacks.get(requestCode):
+ cls._callbacks[requestCode](permissions, grant_results)
+
+
+# Public API methods for requesting permissions
+
+def request_permissions(permissions, callback=None):
+ """Requests Android permissions.
+
+ Args:
+ permissions (str): A list of permissions to requests (str)
+ callback (callable, optional): A function to call when the request
+ is completed (callable)
+
+ Returns:
+ None
+
+ Notes:
+
+ Permission strings can be imported from the 'Permission' class in this
+ module. For example:
+
+ from android import Permission
+ permissions_list = [Permission.CAMERA,
+ Permission.WRITE_EXTERNAL_STORAGE]
+
+ See the p4a source file 'permissions.py' for a list of valid permission
+ strings (pythonforandroid/recipes/android/src/android/permissions.py).
+
+ Any callback supplied must accept two arguments:
+ permissions (list of str): A list of permission strings
+ grant_results (list of bool): A list of bools indicating whether the
+ respective permission was granted.
+ See Android documentation for onPermissionsCallbackResult for
+ further information.
+
+ Note that if the request is interupted the callback may contain an empty
+ list of permissions, without permissions being granted; the App should
+ check that each permission requested has been granted.
+
+ Also note that when calling request_permission on SDK_INT < 23, the
+ callback will be returned immediately as requesting permissions is not
+ required.
+ """
+ _RequestPermissionsManager.request_permissions(permissions, callback)
+
+
+def request_permission(permission, callback=None):
+ request_permissions([permission], callback)
+
+
+def check_permission(permission):
+ """Checks if an app holds the passed permission.
+
+ Args:
+ - permission An Android permission (str)
+
+ Returns:
+ bool: True if the app holds the permission given, False otherwise.
+ """
+ mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
+ result = bool(mActivity.checkCurrentPermission(
+ permission + ""
+ ))
+ return result
diff --git a/pythonforandroid/recipes/android/src/android/runnable.py b/pythonforandroid/recipes/android/src/android/runnable.py
index 564d83b03..b20f6cc3f 100644
--- a/pythonforandroid/recipes/android/src/android/runnable.py
+++ b/pythonforandroid/recipes/android/src/android/runnable.py
@@ -1,14 +1,17 @@
'''
Runnable
========
-
'''
from jnius import PythonJavaClass, java_method, autoclass
-from android.config import JAVA_NAMESPACE
+from android.config import ACTIVITY_CLASS_NAME
+
+# Reference to the activity
+_PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
-# reference to the activity
-_PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
+# Cache of functions table. In older Android versions the number of JNI references
+# is limited, so by caching them we avoid running out.
+__functionstable__ = {}
class Runnable(PythonJavaClass):
@@ -20,7 +23,7 @@ class Runnable(PythonJavaClass):
__runnables__ = []
def __init__(self, func):
- super(Runnable, self).__init__()
+ super().__init__()
self.func = func
def __call__(self, *args, **kwargs):
@@ -33,16 +36,23 @@ def __call__(self, *args, **kwargs):
def run(self):
try:
self.func(*self.args, **self.kwargs)
- except:
+ except: # noqa E722
import traceback
traceback.print_exc()
Runnable.__runnables__.remove(self)
+
def run_on_ui_thread(f):
'''Decorator to create automatically a :class:`Runnable` object with the
function. The function will be delayed and call into the Activity thread.
'''
+ if f not in __functionstable__:
+ rfunction = Runnable(f) # store the runnable function
+ __functionstable__[f] = {"rfunction": rfunction}
+ rfunction = __functionstable__[f]["rfunction"]
+
def f2(*args, **kwargs):
- Runnable(f)(*args, **kwargs)
+ rfunction(*args, **kwargs)
+
return f2
diff --git a/pythonforandroid/recipes/android/src/android/storage.py b/pythonforandroid/recipes/android/src/android/storage.py
new file mode 100644
index 000000000..aa6d781f2
--- /dev/null
+++ b/pythonforandroid/recipes/android/src/android/storage.py
@@ -0,0 +1,117 @@
+from jnius import autoclass, cast
+import os
+
+from android.config import ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
+
+
+Environment = autoclass('android.os.Environment')
+File = autoclass('java.io.File')
+
+
+def _android_has_is_removable_func():
+ VERSION = autoclass('android.os.Build$VERSION')
+ return (VERSION.SDK_INT >= 24)
+
+
+def _get_sdcard_path():
+ """ Internal function to return getExternalStorageDirectory()
+ path. This is internal because it may either return the internal,
+ or an external sd card, depending on the device.
+ Use primary_external_storage_path()
+ or secondary_external_storage_path() instead which try to
+ distinguish this properly.
+ """
+ return (
+ Environment.getExternalStorageDirectory().getAbsolutePath()
+ )
+
+
+def _get_activity():
+ """
+ Retrieves the activity from `PythonActivity` fallback to `PythonService`.
+ """
+ PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
+ activity = PythonActivity.mActivity
+ if activity is None:
+ # assume we're running from the background service
+ PythonService = autoclass(SERVICE_CLASS_NAME)
+ activity = PythonService.mService
+ return activity
+
+
+def app_storage_path():
+ """ Locate the built-in device storage used for this app only.
+
+ This storage is APP-SPECIFIC, and not visible to other apps.
+ It will be wiped when your app is uninstalled.
+
+ Returns directory path to storage.
+ """
+ activity = _get_activity()
+ currentActivity = cast('android.app.Activity', activity)
+ context = cast('android.content.ContextWrapper',
+ currentActivity.getApplicationContext())
+ file_p = cast('java.io.File', context.getFilesDir())
+ return os.path.normpath(os.path.abspath(
+ file_p.getAbsolutePath().replace("/", os.path.sep)))
+
+
+def primary_external_storage_path():
+ """ Locate the built-in device storage that user can see via file browser.
+ Often found at: /sdcard/
+
+ This is storage is SHARED, and visible to other apps and the user.
+ It will remain untouched when your app is uninstalled.
+
+ Returns directory path to storage.
+
+ WARNING: You need storage permissions to access this storage.
+ """
+ if _android_has_is_removable_func():
+ sdpath = _get_sdcard_path()
+ # Apparently this can both return primary (built-in) or
+ # secondary (removable) external storage depending on the device,
+ # therefore check that we got what we wanted:
+ if not Environment.isExternalStorageRemovable(File(sdpath)):
+ return sdpath
+ if "EXTERNAL_STORAGE" in os.environ:
+ return os.environ["EXTERNAL_STORAGE"]
+ raise RuntimeError(
+ "unexpectedly failed to determine " +
+ "primary external storage path"
+ )
+
+
+def secondary_external_storage_path():
+ """ Locate the external SD Card storage, which may not be present.
+ Often found at: /sdcard/External_SD/
+
+ This storage is SHARED, visible to other apps, and may not be
+ be available if the user didn't put in an external SD card.
+ It will remain untouched when your app is uninstalled.
+
+ Returns None if not found, otherwise path to storage.
+
+ WARNING: You need storage permissions to access this storage.
+ If it is not writable and presents as empty even with
+ permissions, then the external sd card may not be present.
+ """
+ if _android_has_is_removable_func:
+ # See if getExternalStorageDirectory() returns secondary ext storage:
+ sdpath = _get_sdcard_path()
+ # Apparently this can both return primary (built-in) or
+ # secondary (removable) external storage depending on the device,
+ # therefore check that we got what we wanted:
+ if Environment.isExternalStorageRemovable(File(sdpath)):
+ if os.path.exists(sdpath):
+ return sdpath
+
+ # See if we can take a guess based on environment variables:
+ p = None
+ if "SECONDARY_STORAGE" in os.environ:
+ p = os.environ["SECONDARY_STORAGE"]
+ elif "EXTERNAL_SDCARD_STORAGE" in os.environ:
+ p = os.environ["EXTERNAL_SDCARD_STORAGE"]
+ if p is not None and os.path.exists(p):
+ return p
+ return None
diff --git a/pythonforandroid/recipes/android/src/setup.py b/pythonforandroid/recipes/android/src/setup.py
index 8a276fe2e..bcd411f46 100755
--- a/pythonforandroid/recipes/android/src/setup.py
+++ b/pythonforandroid/recipes/android/src/setup.py
@@ -3,15 +3,9 @@
library_dirs = ['libs/' + os.environ['ARCH']]
lib_dict = {
- 'pygame': ['sdl'],
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
}
-sdl_libs = lib_dict[os.environ['BOOTSTRAP']]
-
-renpy_sound = Extension('android._android_sound',
- ['android/_android_sound.c', 'android/_android_sound_jni.c', ],
- libraries=sdl_libs + ['log'],
- library_dirs=library_dirs)
+sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], ['main'])
modules = [Extension('android._android',
['android/_android.c', 'android/_android_jni.c'],
@@ -22,10 +16,6 @@
libraries=['log'],
library_dirs=library_dirs)]
-if int(os.environ['IS_PYGAME']):
- modules.append(renpy_sound)
-
-
setup(name='android',
version='1.0',
packages=['android'],
diff --git a/pythonforandroid/recipes/apsw/__init__.py b/pythonforandroid/recipes/apsw/__init__.py
index 92bdd7df8..42ad3ba33 100644
--- a/pythonforandroid/recipes/apsw/__init__.py
+++ b/pythonforandroid/recipes/apsw/__init__.py
@@ -1,11 +1,12 @@
-from pythonforandroid.toolchain import PythonRecipe, shprint, shutil, current_directory
-from os.path import join, exists
+from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.toolchain import current_directory, shprint
import sh
+
class ApswRecipe(PythonRecipe):
version = '3.15.0-r1'
url = 'https://github.com/rogerbinns/apsw/archive/{version}.tar.gz'
- depends = ['sqlite3', 'hostpython2', 'python2', 'setuptools']
+ depends = ['sqlite3', 'setuptools']
call_hostpython_via_targetpython = False
site_packages_name = 'apsw'
@@ -17,21 +18,17 @@ def build_arch(self, arch):
shprint(hostpython,
'setup.py',
'build_ext',
- '--enable=fts4'
- , _env=env)
+ '--enable=fts4', _env=env)
# Install python bindings
- super(ApswRecipe, self).build_arch(arch)
+ super().build_arch(arch)
def get_recipe_env(self, arch):
- env = super(ApswRecipe, self).get_recipe_env(arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \
- ' -I' + self.get_recipe('sqlite3', self.ctx).get_build_dir(arch.arch)
- # Set linker to use the correct gcc
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
- ' -lpython2.7' + \
- ' -lsqlite3'
+ env = super().get_recipe_env(arch)
+ sqlite_recipe = self.get_recipe('sqlite3', self.ctx)
+ env['CFLAGS'] += ' -I' + sqlite_recipe.get_build_dir(arch.arch)
+ env['LDFLAGS'] += ' -L' + sqlite_recipe.get_lib_dir(arch)
+ env['LIBS'] = env.get('LIBS', '') + ' -lsqlite3'
return env
+
recipe = ApswRecipe()
diff --git a/pythonforandroid/recipes/argon2-cffi/__init__.py b/pythonforandroid/recipes/argon2-cffi/__init__.py
new file mode 100644
index 000000000..0450d789f
--- /dev/null
+++ b/pythonforandroid/recipes/argon2-cffi/__init__.py
@@ -0,0 +1,17 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
+
+class Argon2Recipe(CompiledComponentsPythonRecipe):
+ version = '20.1.0'
+ url = 'git+https://github.com/hynek/argon2-cffi'
+ depends = ['setuptools', 'cffi']
+ call_hostpython_via_targetpython = False
+ build_cmd = 'build'
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['ARGON2_CFFI_USE_SSE2'] = '0'
+ return env
+
+
+recipe = Argon2Recipe()
diff --git a/pythonforandroid/recipes/atom/__init__.py b/pythonforandroid/recipes/atom/__init__.py
index 57d363bec..51923d548 100644
--- a/pythonforandroid/recipes/atom/__init__.py
+++ b/pythonforandroid/recipes/atom/__init__.py
@@ -1,9 +1,11 @@
from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
class AtomRecipe(CppCompiledComponentsPythonRecipe):
site_packages_name = 'atom'
version = '0.3.10'
url = 'https://github.com/nucleic/atom/archive/master.zip'
- depends = ['python2','setuptools']
-
+ depends = ['setuptools']
+
+
recipe = AtomRecipe()
diff --git a/pythonforandroid/recipes/audiostream/__init__.py b/pythonforandroid/recipes/audiostream/__init__.py
index 5629a97fb..00c92b386 100644
--- a/pythonforandroid/recipes/audiostream/__init__.py
+++ b/pythonforandroid/recipes/audiostream/__init__.py
@@ -1,33 +1,51 @@
-from pythonforandroid.toolchain import CythonRecipe, shprint, current_directory, info
+from pythonforandroid.recipe import CythonRecipe
+from pythonforandroid.toolchain import shprint, current_directory, info
import sh
-import glob
-from os.path import join, exists
+from os.path import join
class AudiostreamRecipe(CythonRecipe):
- version = 'master'
+ # audiostream has no tagged versions; this is the latest commit to master 2020-12-22
+ # it includes a fix for the dyload issue on android that was preventing use
+ version = '69f6b100f1ea4e3982a1acf6bbb0804e31a2cd50'
url = 'https://github.com/kivy/audiostream/archive/{version}.zip'
+ sha256sum = '4d415c91706fd76865d0d22f1945f87900dc42125ff5a6c8d77898ccdf613c21'
name = 'audiostream'
- depends = ['python2', ('sdl', 'sdl2'), 'pyjnius']
+ depends = ['python3', 'sdl2', 'pyjnius']
def get_recipe_env(self, arch):
- env = super(AudiostreamRecipe, self).get_recipe_env(arch)
- if 'sdl' in self.ctx.recipe_build_order:
- sdl_include = 'sdl'
- sdl_mixer_include = 'sdl_mixer'
- elif 'sdl2' in self.ctx.recipe_build_order:
- sdl_include = 'SDL2'
- sdl_mixer_include = 'SDL2_mixer'
- env['USE_SDL2'] = 'True'
- env['SDL2_INCLUDE_DIR'] = '/home/kivy/.buildozer/android/platform/android-ndk-r9c/sources/android/support/include'
-
- env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include -I{jni_path}/{sdl_mixer_include}'.format(
- jni_path = join(self.ctx.bootstrap.build_dir, 'jni'),
- sdl_include = sdl_include,
- sdl_mixer_include = sdl_mixer_include)
+ env = super().get_recipe_env(arch)
+ sdl_include = 'SDL2'
+
+ env['USE_SDL2'] = 'True'
+ env['SDL2_INCLUDE_DIR'] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include')
+
+ env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include'.format(
+ jni_path=join(self.ctx.bootstrap.build_dir, 'jni'),
+ sdl_include=sdl_include)
+
+ sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)
+ for include_dir in sdl2_mixer_recipe.get_include_dirs(arch):
+ env['CFLAGS'] += ' -I{include_dir}'.format(include_dir=include_dir)
+
+ # NDKPLATFORM is our switch for detecting Android platform, so can't be None
+ env['NDKPLATFORM'] = "NOTNONE"
+ env['LIBLINK'] = 'NOTNONE' # Hacky fix. Needed by audiostream setup.py
return env
-
+
+ def postbuild_arch(self, arch):
+ # TODO: This code was copied from pyjnius, but judging by the
+ # audiostream history, it looks like this step might have
+ # happened automatically in the past.
+ # Given the goal of migrating off of recipes, it would
+ # be good to repair or build infrastructure for doing this
+ # automatically, for when including a java class is
+ # the best solution to a problem.
+ super().postbuild_arch(arch)
+ info('Copying audiostream java files to classes build dir')
+ with current_directory(self.get_build_dir(arch.arch)):
+ shprint(sh.cp, '-a', join('audiostream', 'platform', 'android'), self.ctx.javaclass_dir)
recipe = AudiostreamRecipe()
diff --git a/pythonforandroid/recipes/av/__init__.py b/pythonforandroid/recipes/av/__init__.py
new file mode 100644
index 000000000..816f27e35
--- /dev/null
+++ b/pythonforandroid/recipes/av/__init__.py
@@ -0,0 +1,25 @@
+from pythonforandroid.toolchain import Recipe
+from pythonforandroid.recipe import CythonRecipe
+
+
+class PyAVRecipe(CythonRecipe):
+
+ name = "av"
+ version = "10.0.0"
+ url = "https://github.com/PyAV-Org/PyAV/archive/v{version}.zip"
+
+ depends = ["python3", "cython", "ffmpeg", "av_codecs"]
+ opt_depends = ["openssl"]
+
+ def get_recipe_env(self, arch, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch)
+
+ build_dir = Recipe.get_recipe("ffmpeg", self.ctx).get_build_dir(
+ arch.arch
+ )
+ self.setup_extra_args = ["--ffmpeg-dir={}".format(build_dir)]
+
+ return env
+
+
+recipe = PyAVRecipe()
diff --git a/pythonforandroid/recipes/av_codecs/__init__.py b/pythonforandroid/recipes/av_codecs/__init__.py
new file mode 100644
index 000000000..9952f9ea4
--- /dev/null
+++ b/pythonforandroid/recipes/av_codecs/__init__.py
@@ -0,0 +1,11 @@
+from pythonforandroid.toolchain import Recipe
+
+
+class PyAVCodecsRecipe(Recipe):
+ depends = ["libx264", "libshine", "libvpx"]
+
+ def build_arch(self, arch):
+ pass
+
+
+recipe = PyAVCodecsRecipe()
diff --git a/pythonforandroid/recipes/babel/__init__.py b/pythonforandroid/recipes/babel/__init__.py
deleted file mode 100644
index 818c973f8..000000000
--- a/pythonforandroid/recipes/babel/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class BabelRecipe(PythonRecipe):
- name = 'babel'
- version = '2.1.1'
- url = 'https://pypi.python.org/packages/source/B/Babel/Babel-{version}.tar.gz'
-
- depends = [('python2', 'python3crystax'), 'setuptools', 'pytz']
-
- call_hostpython_via_targetpython = False
- install_in_hostpython = True
-
-
-recipe = BabelRecipe()
diff --git a/pythonforandroid/recipes/bcrypt/__init__.py b/pythonforandroid/recipes/bcrypt/__init__.py
new file mode 100644
index 000000000..da220ff30
--- /dev/null
+++ b/pythonforandroid/recipes/bcrypt/__init__.py
@@ -0,0 +1,22 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
+
+
+class BCryptRecipe(CompiledComponentsPythonRecipe):
+ name = 'bcrypt'
+ version = '3.1.7'
+ url = 'https://github.com/pyca/bcrypt/archive/{version}.tar.gz'
+ depends = ['openssl', 'cffi']
+ call_hostpython_via_targetpython = False
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+
+ openssl_recipe = Recipe.get_recipe('openssl', self.ctx)
+ env['CFLAGS'] += openssl_recipe.include_flags(arch)
+ env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
+ env['LIBS'] = openssl_recipe.link_libs_flags()
+
+ return env
+
+
+recipe = BCryptRecipe()
diff --git a/pythonforandroid/recipes/boost/__init__.py b/pythonforandroid/recipes/boost/__init__.py
index 26afc2a07..aa386c9bd 100644
--- a/pythonforandroid/recipes/boost/__init__.py
+++ b/pythonforandroid/recipes/boost/__init__.py
@@ -1,67 +1,104 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
+from pythonforandroid.util import current_directory
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
from os.path import join, exists
+from os import environ
+import shutil
import sh
"""
-This recipe creates a custom toolchain and bootstraps Boost from source to build Boost.Build
+This recipe bootstraps Boost from source to build Boost.Build
including python bindings
"""
+
+
class BoostRecipe(Recipe):
- version = '1.60.0'
- # Don't forget to change the URL when changing the version
- url = 'http://downloads.sourceforge.net/project/boost/boost/{version}/boost_1_60_0.tar.bz2'
- depends = ['python2']
- patches = ['disable-so-version.patch', 'use-android-libs.patch']
+ # Todo: make recipe compatible with all p4a architectures
+ '''
+ .. note:: This recipe can be built only against API 21+ and an android
+ ndk >= r19
+
+ .. versionchanged:: 0.6.0
+ Rewrote recipe to support clang's build. The following changes has
+ been made:
+
+ - Bumped version number to 1.68.0
+ - Better version handling for url
+ - Added python 3 compatibility
+ - Default compiler for ndk's toolchain set to clang
+ - Python version will be detected via user-config.jam
+ - Changed stl's lib from ``gnustl_shared`` to ``c++_shared``
+
+ .. versionchanged:: 2019.08.09.1.dev0
+
+ - Bumped version number to 1.68.0
+ - Adapted to work with ndk-r19+
+ '''
+ version = '1.69.0'
+ url = (
+ 'https://downloads.sourceforge.net/project/boost/'
+ 'boost/{version}/boost_{version_underscore}.tar.bz2'
+ )
+ depends = ['python3']
+ patches = [
+ 'disable-so-version.patch',
+ 'use-android-libs.patch',
+ 'fix-android-issues.patch',
+ ]
+ need_stl_shared = True
+
+ @property
+ def versioned_url(self):
+ if self.url is None:
+ return None
+ return self.url.format(
+ version=self.version,
+ version_underscore=self.version.replace('.', '_'),
+ )
def should_build(self, arch):
return not exists(join(self.get_build_dir(arch.arch), 'b2'))
def prebuild_arch(self, arch):
- super(BoostRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
- if not exists(env['CROSSHOME']):
- # Make custom toolchain
- bash = sh.Command('bash')
- shprint(bash, join(self.ctx.ndk_dir, 'build/tools/make-standalone-toolchain.sh'),
- '--arch=' + env['ARCH'],
- '--platform=android-' + str(self.ctx.android_api),
- '--toolchain=' + env['CROSSHOST'] + '-' + env['TOOLCHAIN_VERSION'],
- '--install-dir=' + env['CROSSHOME']
- )
# Set custom configuration
- shutil.copyfile(join(self.get_recipe_dir(), 'user-config.jam'),
- join(env['BOOST_BUILD_PATH'], 'user-config.jam'))
+ shutil.copyfile(
+ join(self.get_recipe_dir(), 'user-config.jam'),
+ join(env['BOOST_BUILD_PATH'], 'user-config.jam'),
+ )
def build_arch(self, arch):
- super(BoostRecipe, self).build_arch(arch)
+ super().build_arch(arch)
env = self.get_recipe_env(arch)
+ env['PYTHON_HOST'] = self.ctx.hostpython
with current_directory(self.get_build_dir(arch.arch)):
- # Compile Boost.Build engine with this custom toolchain
- bash = sh.Command('bash')
- shprint(bash, 'bootstrap.sh',
- '--with-python=' + join(env['PYTHON_ROOT'], 'bin/python.host'),
- '--with-python-version=2.7',
- '--with-python-root=' + env['PYTHON_ROOT']
- ) # Do not pass env
- # Install app stl
- shutil.copyfile(join(env['CROSSHOME'], env['CROSSHOST'], 'lib/libgnustl_shared.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libgnustl_shared.so'))
-
- def select_build_arch(self, arch):
- return arch.arch.replace('eabi-v7a', '').replace('eabi', '')
+ if not exists('b2'):
+ # Compile Boost.Build engine with this custom toolchain
+ bash = sh.Command('bash')
+ shprint(bash, 'bootstrap.sh') # Do not pass env
def get_recipe_env(self, arch):
- env = super(BoostRecipe, self).get_recipe_env(arch)
- env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch) # find user-config.jam
- env['BOOST_ROOT'] = env['BOOST_BUILD_PATH'] # find boost source
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['ARCH'] = self.select_build_arch(arch)
- env['ANDROIDAPI'] = str(self.ctx.android_api)
- env['CROSSHOST'] = env['ARCH'] + '-linux-androideabi'
- env['CROSSHOME'] = join(env['BOOST_ROOT'], 'standalone-' + env['ARCH'] + '-toolchain')
- env['TOOLCHAIN_PREFIX'] = join(env['CROSSHOME'], 'bin', env['CROSSHOST'])
+ # We don't use the normal env because we
+ # are building with a standalone toolchain
+ env = environ.copy()
+
+ # find user-config.jam
+ env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch)
+ # find boost source
+ env['BOOST_ROOT'] = env['BOOST_BUILD_PATH']
+
+ env['PYTHON_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)
+ env['PYTHON_INCLUDE'] = self.ctx.python_recipe.include_root(arch.arch)
+ env['PYTHON_MAJOR_MINOR'] = self.ctx.python_recipe.version[:3]
+ env['PYTHON_LINK_VERSION'] = self.ctx.python_recipe.link_version
+
+ env['ARCH'] = arch.arch.replace('-', '')
+ env['TARGET_TRIPLET'] = arch.target
+ env['CROSSHOST'] = arch.command_prefix
+ env['CROSSHOME'] = self.ctx.ndk.llvm_prebuilt_dir
return env
-recipe = BoostRecipe()
\ No newline at end of file
+recipe = BoostRecipe()
diff --git a/pythonforandroid/recipes/boost/fix-android-issues.patch b/pythonforandroid/recipes/boost/fix-android-issues.patch
new file mode 100644
index 000000000..40bdea42d
--- /dev/null
+++ b/pythonforandroid/recipes/boost/fix-android-issues.patch
@@ -0,0 +1,86 @@
+diff -u -r boost_1_69_0.orig/boost/asio/detail/config.hpp boost_1_69_0/boost/asio/detail/config.hpp
+--- boost_1_69_0.orig/boost/asio/detail/config.hpp 2018-12-05 20:58:15.000000000 +0100
++++ boost_1_69_0/boost/asio/detail/config.hpp 2018-12-13 14:52:06.000000000 +0100
+@@ -815,7 +815,11 @@
+ # if (_LIBCPP_VERSION < 7000)
+ # if (__cplusplus >= 201402)
+ # if __has_include(<experimental/string_view>)
+-# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1
++# if __clang_major__ >= 7
++# undef BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW
++# else
++# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1
++# endif // __clang_major__ >= 7
+ # endif // __has_include(<experimental/string_view>)
+ # endif // (__cplusplus >= 201402)
+ # endif // (_LIBCPP_VERSION < 7000)
+diff -u -r boost_1_69_0.orig/boost/config/user.hpp boost_1_69_0/boost/config/user.hpp
+--- boost_1_69_0.orig/boost/config/user.hpp 2018-12-05 20:58:16.000000000 +0100
++++ boost_1_69_0/boost/config/user.hpp 2018-12-13 14:35:29.000000000 +0100
+@@ -13,6 +13,12 @@
+ // configuration policy:
+ //
+
++// Android defines
++// There is problem with std::atomic on android (and some other platforms).
++// See this link for more info:
++// https://code.google.com/p/android/issues/detail?id=42735#makechanges
++#define BOOST_ASIO_DISABLE_STD_ATOMIC 1
++
+ // define this to locate a compiler config file:
+ // #define BOOST_COMPILER_CONFIG <myheader>
+
+diff -u -r boost_1_69_0.orig/boost/system/error_code.hpp boost_1_69_0/boost/system/error_code.hpp
+--- boost_1_69_0.orig/boost/system/error_code.hpp 2018-12-05 20:58:23.000000000 +0100
++++ boost_1_69_0/boost/system/error_code.hpp 2018-12-13 14:53:33.000000000 +0100
+@@ -14,6 +14,7 @@
+ #include <boost/system/detail/config.hpp>
+ #include <boost/cstdint.hpp>
+ #include <boost/config.hpp>
++#include <stdio.h>
+ #include <ostream>
+ #include <string>
+ #include <functional>
+diff -u -r boost_1_69_0.orig/libs/filesystem/src/operations.cpp boost_1_69_0/libs/filesystem/src/operations.cpp
+--- boost_1_69_0.orig/libs/filesystem/src/operations.cpp 2018-12-05 20:58:17.000000000 +0100
++++ boost_1_69_0/libs/filesystem/src/operations.cpp 2018-12-13 14:55:41.000000000 +0100
+@@ -232,6 +232,21 @@
+
+ # if defined(BOOST_POSIX_API)
+
++# if defined(__ANDROID__)
++# define truncate libboost_truncate_wrapper
++// truncate() is present in Android libc only starting from ABI 21, so here's a simple wrapper
++static int libboost_truncate_wrapper(const char *path, off_t length)
++{
++ int fd = open(path, O_WRONLY);
++ if (fd == -1) {
++ return -1;
++ }
++ int status = ftruncate(fd, length);
++ close(fd);
++ return status;
++}
++# endif
++
+ typedef int err_t;
+
+ // POSIX uses a 0 return to indicate success
+diff -u -r boost_1_69_0.orig/tools/build/src/tools/common.jam boost_1_69_0/tools/build/src/tools/common.jam
+--- boost_1_69_0.orig/tools/build/src/tools/common.jam 2019-01-25 23:18:34.544755629 +0200
++++ boost_1_69_0/tools/build/src/tools/common.jam 2019-01-25 23:20:42.309047754 +0200
+@@ -976,10 +976,10 @@
+ }
+
+ # Ditto, from Clang 4
+- if $(tag) in clang clangw && [ numbers.less 3 $(version[1]) ]
+- {
+- version = $(version[1]) ;
+- }
++ #if $(tag) in clang clangw && [ numbers.less 3 $(version[1]) ]
++ #{
++ # version = $(version[1]) ;
++ #}
+
+ # On intel, version is not added, because it does not matter and it is the
+ # version of vc used as backend that matters. Ideally, we should encode the
diff --git a/pythonforandroid/recipes/boost/user-config.jam b/pythonforandroid/recipes/boost/user-config.jam
index 72643d8a0..fa1eef133 100644
--- a/pythonforandroid/recipes/boost/user-config.jam
+++ b/pythonforandroid/recipes/boost/user-config.jam
@@ -1,28 +1,42 @@
import os ;
-local ANDROIDNDK = [ os.environ ANDROIDNDK ] ;
-local ANDROIDAPI = [ os.environ ANDROIDAPI ] ;
-local TOOLCHAIN_VERSION = [ os.environ TOOLCHAIN_VERSION ] ;
-local TOOLCHAIN_PREFIX = [ os.environ TOOLCHAIN_PREFIX ] ;
local ARCH = [ os.environ ARCH ] ;
+local TARGET_TRIPLET = [ os.environ TARGET_TRIPLET ] ;
+local CROSSHOME = [ os.environ CROSSHOME ] ;
+local PYTHON_HOST = [ os.environ PYTHON_HOST ] ;
local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;
+local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ;
+local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ;
+local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ;
-using gcc : $(ARCH) : $(TOOLCHAIN_PREFIX)-g++ :
-<architecture>$(ARCH)
-<archiver>$(TOOLCHAIN_PREFIX)-ar
-<compileflags>-DBOOST_SP_USE_PTHREADS
-<compileflags>-DBOOST_AC_USE_PTHREADS
-<cxxflags>-DBOOST_SP_USE_PTHREADS
-<cxxflags>-DBOOST_AC_USE_PTHREADS
-<cxxflags>-frtti
-<cxxflags>-fexceptions
-<compileflags>-I$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)/usr/include
-<compileflags>-I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/include
-<compileflags>-I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)/include
-<compileflags>-I$(PYTHON_ROOT)/include/python2.7
-<linkflags>--sysroot=$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)
-<linkflags>-L$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)
-<linkflags>-L$(PYTHON_ROOT)/lib
-<linkflags>-lgnustl_shared
-<linkflags>-lpython2.7
+using clang : $(ARCH) : $(CROSSHOME)/bin/$(TARGET_TRIPLET)-clang++ :
+<archiver>$(CROSSHOME)/bin/llvm-ar
+<compileflags>-fPIC
+<compileflags>-ffunction-sections
+<compileflags>-fdata-sections
+<compileflags>-funwind-tables
+<compileflags>-fstack-protector-strong
+<compileflags>-no-canonical-prefixes
+<compileflags>-Wformat
+<compileflags>-Werror=format-security
+<compileflags>-frtti
+<compileflags>-fexceptions
+<compileflags>-DNDEBUG
+<compileflags>-g
+<compileflags>-Oz
+<compileflags>-mthumb
+<linkflags>-Wl,-z,relro
+<linkflags>-Wl,-z,now
+<linkflags>-lc++_shared
+<linkflags>-L$(PYTHON_ROOT)
+<linkflags>-lpython$(PYTHON_LINK_VERSION)
+<linkflags>-Wl,-O1
+<linkflags>-Wl,-Bsymbolic-functions
;
+
+using python : $(PYTHON_MAJOR_MINOR)
+ : $(PYTHON_host)
+ : $(PYTHON_ROOT) $(PYTHON_INCLUDE)
+ : $(PYTHON_ROOT)/libpython$(PYTHON_LINK_VERSION).so
+ : #<define>BOOST_ALL_DYN_LINK
+;
\ No newline at end of file
diff --git a/pythonforandroid/recipes/brokenrecipe/__init__.py b/pythonforandroid/recipes/brokenrecipe/__init__.py
index b617074b3..48e266b3a 100644
--- a/pythonforandroid/recipes/brokenrecipe/__init__.py
+++ b/pythonforandroid/recipes/brokenrecipe/__init__.py
@@ -1,5 +1,6 @@
from pythonforandroid.toolchain import Recipe
+
class BrokenRecipe(Recipe):
def __init__(self):
print('This is a broken recipe, not a real one!')
diff --git a/pythonforandroid/recipes/cdecimal/__init__.py b/pythonforandroid/recipes/cdecimal/__init__.py
deleted file mode 100644
index e08185943..000000000
--- a/pythonforandroid/recipes/cdecimal/__init__.py
+++ /dev/null
@@ -1,26 +0,0 @@
-
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe
-from pythonforandroid.patching import is_darwin
-
-
-class CdecimalRecipe(CompiledComponentsPythonRecipe):
- name = 'cdecimal'
- version = '2.3'
- url = 'http://www.bytereef.org/software/mpdecimal/releases/cdecimal-{version}.tar.gz'
-
- depends = ['python2']
-
- patches = ['locale.patch',
- 'cross-compile.patch']
-
- def prebuild_arch(self, arch):
- super(CdecimalRecipe, self).prebuild_arch(arch)
- if not is_darwin():
- if '64' in arch.arch:
- machine = 'ansi64'
- else:
- machine = 'ansi32'
- self.setup_extra_args = ['--with-machine=' + machine]
-
-
-recipe = CdecimalRecipe()
diff --git a/pythonforandroid/recipes/cdecimal/cross-compile.patch b/pythonforandroid/recipes/cdecimal/cross-compile.patch
deleted file mode 100644
index cc15f33ba..000000000
--- a/pythonforandroid/recipes/cdecimal/cross-compile.patch
+++ /dev/null
@@ -1,12 +0,0 @@
-diff -Naur cdecimal/setup.py b/setup.py
---- cdecimal/setup.py 2015-12-14 13:48:23.085997956 -0600
-+++ b/setup.py 2015-12-14 13:48:11.413805121 -0600
-@@ -229,7 +229,7 @@
- def configure(machine, cc, py_size_t):
- os.chmod("./configure", 0x1ed) # pip removes execute permissions.
- if machine: # string has been validated.
-- os.system("./configure MACHINE=%s" % machine)
-+ os.system("./configure --host=%s MACHINE=%s" % (os.environ['TOOLCHAIN_PREFIX'], machine))
- elif 'sunos' in SYSTEM and py_size_t == 8:
- # cc is from sysconfig.
- os.system("./configure CC='%s -m64'" % cc)
diff --git a/pythonforandroid/recipes/cdecimal/locale.patch b/pythonforandroid/recipes/cdecimal/locale.patch
deleted file mode 100644
index 4b8df6b37..000000000
--- a/pythonforandroid/recipes/cdecimal/locale.patch
+++ /dev/null
@@ -1,172 +0,0 @@
-diff -Naur a/io.c b/io.c
---- a/io.c 2012-02-01 14:29:49.000000000 -0600
-+++ b/io.c 2015-12-09 17:04:00.060579230 -0600
-@@ -34,7 +34,7 @@
- #include <limits.h>
- #include <assert.h>
- #include <errno.h>
--#include <locale.h>
-+#include "locale.h"
- #include "bits.h"
- #include "constants.h"
- #include "memory.h"
-@@ -792,15 +792,14 @@
- }
- else if (*cp == 'N' || *cp == 'n') {
- /* locale specific conversion */
-- struct lconv *lc;
- spec->type = *cp++;
- /* separator has already been specified */
- if (*spec->sep) return 0;
- spec->type = (spec->type == 'N') ? 'G' : 'g';
-- lc = localeconv();
-- spec->dot = lc->decimal_point;
-- spec->sep = lc->thousands_sep;
-- spec->grouping = lc->grouping;
-+ /* TODO: Android does not have localeconv(); we'll just use C locale values for now */
-+ spec->dot = ".";
-+ spec->sep = "";
-+ spec->grouping = "";
- }
-
- /* check correctness */
-diff -Naur a/locale.h b/locale.h
---- a/locale.h 1969-12-31 18:00:00.000000000 -0600
-+++ b/locale.h 2015-12-09 17:04:11.128762784 -0600
-@@ -0,0 +1,136 @@
-+/*
-+ * Copyright (C) 2008 The Android Open Source Project
-+ * All rights reserved.
-+ *
-+ * Redistribution and use in source and binary forms, with or without
-+ * modification, are permitted provided that the following conditions
-+ * are met:
-+ * * Redistributions of source code must retain the above copyright
-+ * notice, this list of conditions and the following disclaimer.
-+ * * Redistributions in binary form must reproduce the above copyright
-+ * notice, this list of conditions and the following disclaimer in
-+ * the documentation and/or other materials provided with the
-+ * distribution.
-+ *
-+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-+ * SUCH DAMAGE.
-+ */
-+#ifndef _LOCALE_H_
-+#define _LOCALE_H_
-+
-+#include <sys/cdefs.h>
-+
-+__BEGIN_DECLS
-+
-+enum {
-+ LC_CTYPE = 0,
-+ LC_NUMERIC = 1,
-+ LC_TIME = 2,
-+ LC_COLLATE = 3,
-+ LC_MONETARY = 4,
-+ LC_MESSAGES = 5,
-+ LC_ALL = 6,
-+ LC_PAPER = 7,
-+ LC_NAME = 8,
-+ LC_ADDRESS = 9,
-+
-+ LC_TELEPHONE = 10,
-+ LC_MEASUREMENT = 11,
-+ LC_IDENTIFICATION = 12
-+};
-+
-+extern char *setlocale(int category, const char *locale);
-+
-+#if 1 /* MISSING FROM BIONIC - DEFINED TO MAKE libstdc++-v3 happy */
-+/*struct lconv { };*/
-+
-+__BEGIN_NAMESPACE_STD;
-+
-+/* Structure giving information about numeric and monetary notation. */
-+struct lconv
-+{
-+ /* Numeric (non-monetary) information. */
-+
-+ char *decimal_point; /* Decimal point character. */
-+ char *thousands_sep; /* Thousands separator. */
-+ /* Each element is the number of digits in each group;
-+ elements with higher indices are farther left.
-+ An element with value CHAR_MAX means that no further grouping is done.
-+ An element with value 0 means that the previous element is used
-+ for all groups farther left. */
-+ char *grouping;
-+
-+ /* Monetary information. */
-+
-+ /* First three chars are a currency symbol from ISO 4217.
-+ Fourth char is the separator. Fifth char is '\0'. */
-+ char *int_curr_symbol;
-+ char *currency_symbol; /* Local currency symbol. */
-+ char *mon_decimal_point; /* Decimal point character. */
-+ char *mon_thousands_sep; /* Thousands separator. */
-+ char *mon_grouping; /* Like `grouping' element (above). */
-+ char *positive_sign; /* Sign for positive values. */
-+ char *negative_sign; /* Sign for negative values. */
-+ char int_frac_digits; /* Int'l fractional digits. */
-+ char frac_digits; /* Local fractional digits. */
-+ /* 1 if currency_symbol precedes a positive value, 0 if succeeds. */
-+ char p_cs_precedes;
-+ /* 1 iff a space separates currency_symbol from a positive value. */
-+ char p_sep_by_space;
-+ /* 1 if currency_symbol precedes a negative value, 0 if succeeds. */
-+ char n_cs_precedes;
-+ /* 1 iff a space separates currency_symbol from a negative value. */
-+ char n_sep_by_space;
-+ /* Positive and negative sign positions:
-+ 0 Parentheses surround the quantity and currency_symbol.
-+ 1 The sign string precedes the quantity and currency_symbol.
-+ 2 The sign string follows the quantity and currency_symbol.
-+ 3 The sign string immediately precedes the currency_symbol.
-+ 4 The sign string immediately follows the currency_symbol. */
-+ char p_sign_posn;
-+ char n_sign_posn;
-+#ifdef __USE_ISOC99
-+ /* 1 if int_curr_symbol precedes a positive value, 0 if succeeds. */
-+ char int_p_cs_precedes;
-+ /* 1 iff a space separates int_curr_symbol from a positive value. */
-+ char int_p_sep_by_space;
-+ /* 1 if int_curr_symbol precedes a negative value, 0 if succeeds. */
-+ char int_n_cs_precedes;
-+ /* 1 iff a space separates int_curr_symbol from a negative value. */
-+ char int_n_sep_by_space;
-+ /* Positive and negative sign positions:
-+ 0 Parentheses surround the quantity and int_curr_symbol.
-+ 1 The sign string precedes the quantity and int_curr_symbol.
-+ 2 The sign string follows the quantity and int_curr_symbol.
-+ 3 The sign string immediately precedes the int_curr_symbol.
-+ 4 The sign string immediately follows the int_curr_symbol. */
-+ char int_p_sign_posn;
-+ char int_n_sign_posn;
-+#else
-+ char __int_p_cs_precedes;
-+ char __int_p_sep_by_space;
-+ char __int_n_cs_precedes;
-+ char __int_n_sep_by_space;
-+ char __int_p_sign_posn;
-+ char __int_n_sign_posn;
-+#endif
-+};
-+
-+__END_NAMESPACE_STD;
-+
-+struct lconv *localeconv(void);
-+#endif /* MISSING */
-+
-+__END_DECLS
-+
-+#endif /* _LOCALE_H_ */
diff --git a/pythonforandroid/recipes/cffi/__init__.py b/pythonforandroid/recipes/cffi/__init__.py
index 450c32a52..f0c25a92c 100644
--- a/pythonforandroid/recipes/cffi/__init__.py
+++ b/pythonforandroid/recipes/cffi/__init__.py
@@ -1,30 +1,48 @@
+import os
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
class CffiRecipe(CompiledComponentsPythonRecipe):
- name = 'cffi'
- version = '1.4.2'
- url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz'
-
- depends = [('python2', 'python3crystax'), 'setuptools', 'pycparser', 'libffi']
-
- patches = ['disable-pkg-config.patch']
-
- # call_hostpython_via_targetpython = False
- install_in_hostpython = True
-
- def get_recipe_env(self, arch=None):
- env = super(CffiRecipe, self).get_recipe_env(arch)
- libffi = self.get_recipe('libffi', self.ctx)
- includes = libffi.get_include_dirs(arch)
- env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes)
- env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' +
- self.ctx.get_libs_dir(arch.arch))
- env['PYTHONPATH'] = ':'.join([
- self.ctx.get_site_packages_dir(),
- env['BUILDLIB_PATH'],
- ])
- return env
+ """
+ Extra system dependencies: autoconf, automake and libtool.
+ """
+ name = 'cffi'
+ version = '1.15.1'
+ url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz'
+
+ depends = ['setuptools', 'pycparser', 'libffi']
+
+ patches = ['disable-pkg-config.patch']
+
+ # call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+ def get_hostrecipe_env(self, arch=None):
+ # fixes missing ffi.h on some host systems (e.g. gentoo)
+ env = super().get_hostrecipe_env(arch)
+ libffi = self.get_recipe('libffi', self.ctx)
+ includes = libffi.get_include_dirs(arch)
+ env['FFI_INC'] = ",".join(includes)
+ return env
+
+ def get_recipe_env(self, arch=None):
+ env = super().get_recipe_env(arch)
+ libffi = self.get_recipe('libffi', self.ctx)
+ includes = libffi.get_include_dirs(arch)
+ env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes)
+ env['CFLAGS'] += ' -I{}'.format(self.ctx.python_recipe.include_root(arch.arch))
+ env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' +
+ self.ctx.get_libs_dir(arch.arch))
+ env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))
+ # required for libc and libdl
+ env['LDFLAGS'] += ' -L{}'.format(arch.ndk_lib_dir_versioned)
+ env['PYTHONPATH'] = ':'.join([
+ self.ctx.get_site_packages_dir(arch),
+ env['BUILDLIB_PATH'],
+ ])
+ env['LDFLAGS'] += ' -L{}'.format(self.ctx.python_recipe.link_root(arch.arch))
+ env['LDFLAGS'] += ' -lpython{}'.format(self.ctx.python_recipe.link_version)
+ return env
recipe = CffiRecipe()
diff --git a/pythonforandroid/recipes/cffi/disable-pkg-config.patch b/pythonforandroid/recipes/cffi/disable-pkg-config.patch
index 56346bb7c..b1a5ff9b4 100644
--- a/pythonforandroid/recipes/cffi/disable-pkg-config.patch
+++ b/pythonforandroid/recipes/cffi/disable-pkg-config.patch
@@ -1,18 +1,19 @@
-diff -Naur cffi-1.4.2/setup.py b/setup.py
---- cffi-1.4.2/setup.py 2015-12-21 12:09:47.000000000 -0600
-+++ b/setup.py 2015-12-23 10:20:40.590622524 -0600
-@@ -5,8 +5,7 @@
+diff --git a/setup.py b/setup copy.py
+index 4ce0007..9be4a6d 100644
+--- a/setup.py
++++ b/setup
+@@ -9,8 +9,7 @@ if sys.platform == "win32":
sources = ['c/_cffi_backend.c']
libraries = ['ffi']
-include_dirs = ['/usr/include/ffi',
- '/usr/include/libffi'] # may be changed by pkg-config
-+include_dirs = []
- define_macros = []
++include_dirs = os.environ['FFI_INC'].split(',') if 'FFI_INC' in os.environ else []
+ define_macros = [('FFI_BUILDING', '1')] # for linking with libffi static library
library_dirs = []
extra_compile_args = []
-@@ -67,14 +66,7 @@
- sys.stderr.write("The above error message can be safely ignored\n")
+@@ -105,14 +104,7 @@ def uses_msvc():
+ return config.try_compile('#ifndef _MSC_VER\n#error "not MSVC"\n#endif')
def use_pkg_config():
- if sys.platform == 'darwin' and os.path.exists('/usr/local/bin/brew'):
@@ -24,6 +25,4 @@ diff -Naur cffi-1.4.2/setup.py b/setup.py
- _ask_pkg_config(extra_link_args, '--libs-only-other')
- _ask_pkg_config(libraries, '--libs-only-l', '-l')
+ pass
-
- def use_homebrew_for_libffi():
- # We can build by setting:
+
diff --git a/pythonforandroid/recipes/cherrypy/__init__.py b/pythonforandroid/recipes/cherrypy/__init__.py
deleted file mode 100644
index 74ed3dbb9..000000000
--- a/pythonforandroid/recipes/cherrypy/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from pythonforandroid.toolchain import PythonRecipe
-
-class CherryPyRecipe(PythonRecipe):
- version = '5.1.0'
- url = 'https://bitbucket.org/cherrypy/cherrypy/get/{version}.tar.gz'
- depends = ['hostpython2', 'setuptools']
- site_packages_name = 'cherrypy'
- call_hostpython_via_targetpython = False
-
-recipe = CherryPyRecipe()
diff --git a/pythonforandroid/recipes/coverage/__init__.py b/pythonforandroid/recipes/coverage/__init__.py
index a37358be9..2ee2d059c 100644
--- a/pythonforandroid/recipes/coverage/__init__.py
+++ b/pythonforandroid/recipes/coverage/__init__.py
@@ -1,4 +1,4 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
class CoverageRecipe(PythonRecipe):
@@ -7,7 +7,7 @@ class CoverageRecipe(PythonRecipe):
url = 'https://pypi.python.org/packages/2d/10/6136c8e10644c16906edf4d9f7c782c0f2e7ed47ff2f41f067384e432088/coverage-{version}.tar.gz'
- depends = ['hostpython2', 'setuptools']
+ depends = ['hostpython3', 'setuptools']
patches = ['fallback-utf8.patch']
diff --git a/pythonforandroid/recipes/cppy/__init__.py b/pythonforandroid/recipes/cppy/__init__.py
new file mode 100644
index 000000000..f61e2c251
--- /dev/null
+++ b/pythonforandroid/recipes/cppy/__init__.py
@@ -0,0 +1,14 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class CppyRecipe(PythonRecipe):
+ site_packages_name = 'cppy'
+ version = '1.1.0'
+ url = 'https://github.com/nucleic/cppy/archive/{version}.zip'
+ call_hostpython_via_targetpython = False
+ # to be detected by the matplotlib install script
+ install_in_hostpython = True
+ depends = ['setuptools']
+
+
+recipe = CppyRecipe()
diff --git a/pythonforandroid/recipes/cryptography/__init__.py b/pythonforandroid/recipes/cryptography/__init__.py
index 8dceb2886..182c74599 100644
--- a/pythonforandroid/recipes/cryptography/__init__.py
+++ b/pythonforandroid/recipes/cryptography/__init__.py
@@ -1,27 +1,22 @@
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-from os.path import dirname, join
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
+
class CryptographyRecipe(CompiledComponentsPythonRecipe):
name = 'cryptography'
- version = '1.3'
+ version = '2.8'
url = 'https://github.com/pyca/cryptography/archive/{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'openssl', 'idna', 'pyasn1', 'six', 'setuptools', 'enum34', 'ipaddress', 'cffi']
+ depends = ['openssl', 'six', 'setuptools', 'cffi']
call_hostpython_via_targetpython = False
def get_recipe_env(self, arch):
- env = super(CryptographyRecipe, self).get_recipe_env(arch)
- r = self.get_recipe('openssl', self.ctx)
- openssl_dir = r.get_build_dir(arch.arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \
- ' -I' + join(openssl_dir, 'include')
- # Set linker to use the correct gcc
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
- ' -L' + openssl_dir + \
- ' -lpython2.7' + \
- ' -lssl' + r.version + \
- ' -lcrypto' + r.version
+ env = super().get_recipe_env(arch)
+
+ openssl_recipe = Recipe.get_recipe('openssl', self.ctx)
+ env['CFLAGS'] += openssl_recipe.include_flags(arch)
+ env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
+ env['LIBS'] = openssl_recipe.link_libs_flags()
+
return env
+
recipe = CryptographyRecipe()
diff --git a/pythonforandroid/recipes/cymunk/__init__.py b/pythonforandroid/recipes/cymunk/__init__.py
index c9733e3e7..272c18f9e 100644
--- a/pythonforandroid/recipes/cymunk/__init__.py
+++ b/pythonforandroid/recipes/cymunk/__init__.py
@@ -1,4 +1,4 @@
-from pythonforandroid.toolchain import CythonRecipe
+from pythonforandroid.recipe import CythonRecipe
class CymunkRecipe(CythonRecipe):
@@ -6,7 +6,5 @@ class CymunkRecipe(CythonRecipe):
url = 'https://github.com/tito/cymunk/archive/{version}.zip'
name = 'cymunk'
- depends = [('python2', 'python3crystax')]
-
recipe = CymunkRecipe()
diff --git a/pythonforandroid/recipes/cython/__init__.py b/pythonforandroid/recipes/cython/__init__.py
new file mode 100644
index 000000000..b8bac0ae1
--- /dev/null
+++ b/pythonforandroid/recipes/cython/__init__.py
@@ -0,0 +1,14 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
+
+class CythonRecipe(CompiledComponentsPythonRecipe):
+
+ version = '0.29.36'
+ url = 'https://github.com/cython/cython/archive/{version}.tar.gz'
+ site_packages_name = 'cython'
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+
+recipe = CythonRecipe()
diff --git a/pythonforandroid/recipes/dateutil/__init__.py b/pythonforandroid/recipes/dateutil/__init__.py
deleted file mode 100644
index 18e65047e..000000000
--- a/pythonforandroid/recipes/dateutil/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class DateutilRecipe(PythonRecipe):
- name = 'dateutil'
- version = '2.6.0'
- url = 'https://pypi.python.org/packages/51/fc/39a3fbde6864942e8bb24c93663734b74e281b984d1b8c4f95d64b0c21f6/python-dateutil-2.6.0.tar.gz'
-
- depends = ['python2', "setuptools"]
- call_hostpython_via_targetpython = False
- install_in_hostpython = True
-
-
-recipe = DateutilRecipe()
diff --git a/pythonforandroid/recipes/decorator/__init__.py b/pythonforandroid/recipes/decorator/__init__.py
index 6a20b31eb..e1001dd6f 100644
--- a/pythonforandroid/recipes/decorator/__init__.py
+++ b/pythonforandroid/recipes/decorator/__init__.py
@@ -1,10 +1,13 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
+
class DecoratorPyRecipe(PythonRecipe):
- version = '4.0.9'
+ version = '4.2.1'
url = 'https://pypi.python.org/packages/source/d/decorator/decorator-{version}.tar.gz'
- depends = ['hostpython2', 'setuptools']
+ url = 'https://github.com/micheles/decorator/archive/{version}.tar.gz'
+ depends = ['setuptools']
site_packages_name = 'decorator'
call_hostpython_via_targetpython = False
+
recipe = DecoratorPyRecipe()
diff --git a/pythonforandroid/recipes/enaml/__init__.py b/pythonforandroid/recipes/enaml/__init__.py
index 89c070081..d2335206c 100644
--- a/pythonforandroid/recipes/enaml/__init__.py
+++ b/pythonforandroid/recipes/enaml/__init__.py
@@ -1,10 +1,12 @@
from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
class EnamlRecipe(CppCompiledComponentsPythonRecipe):
site_packages_name = 'enaml'
version = '0.9.8'
- url = 'https://github.com/nucleic/enaml/archive/master.zip'
- patches = ['0001-Update-setup.py.patch'] # Remove PyQt dependency
- depends = ['python2','setuptools','atom','kiwisolver']
+ url = 'https://github.com/nucleic/enaml/archive/{version}.zip'
+ patches = ['0001-Update-setup.py.patch'] # Remove PyQt dependency
+ depends = ['setuptools', 'atom', 'kiwisolver']
+
recipe = EnamlRecipe()
diff --git a/pythonforandroid/recipes/enum34/__init__.py b/pythonforandroid/recipes/enum34/__init__.py
deleted file mode 100644
index ba9acfc75..000000000
--- a/pythonforandroid/recipes/enum34/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from pythonforandroid.toolchain import PythonRecipe
-
-class Enum34Recipe(PythonRecipe):
- version = '1.1.3'
- url = 'https://pypi.python.org/packages/source/e/enum34/enum34-{version}.tar.gz'
- depends = ['python2', 'setuptools']
- site_packages_name = 'enum'
- call_hostpython_via_targetpython = False
-
-recipe = Enum34Recipe()
diff --git a/pythonforandroid/recipes/ethash/__init__.py b/pythonforandroid/recipes/ethash/__init__.py
index 403513d89..b65e10ad3 100644
--- a/pythonforandroid/recipes/ethash/__init__.py
+++ b/pythonforandroid/recipes/ethash/__init__.py
@@ -5,7 +5,7 @@ class EthashRecipe(PythonRecipe):
url = 'https://github.com/ethereum/ethash/archive/master.zip'
- depends = ['python2', 'setuptools']
+ depends = ['setuptools']
recipe = EthashRecipe()
diff --git a/pythonforandroid/recipes/evdev/__init__.py b/pythonforandroid/recipes/evdev/__init__.py
index b4921dd76..b69169d3c 100644
--- a/pythonforandroid/recipes/evdev/__init__.py
+++ b/pythonforandroid/recipes/evdev/__init__.py
@@ -5,8 +5,9 @@ class EvdevRecipe(CompiledComponentsPythonRecipe):
name = 'evdev'
version = 'v0.4.7'
url = 'https://github.com/gvalkov/python-evdev/archive/{version}.zip'
+ call_hostpython_via_targetpython = False
- depends = [('python2', 'python3crystax')]
+ depends = []
build_cmd = 'build'
@@ -17,8 +18,8 @@ class EvdevRecipe(CompiledComponentsPythonRecipe):
'evdev-permissions.patch']
def get_recipe_env(self, arch=None):
- env = super(EvdevRecipe, self).get_recipe_env(arch)
- env['NDKPLATFORM'] = self.ctx.ndk_platform
+ env = super().get_recipe_env(arch)
+ env['SYSROOT'] = self.ctx.ndk.sysroot
return env
diff --git a/pythonforandroid/recipes/evdev/include-dir.patch b/pythonforandroid/recipes/evdev/include-dir.patch
index d6a7c813d..a1c41e740 100644
--- a/pythonforandroid/recipes/evdev/include-dir.patch
+++ b/pythonforandroid/recipes/evdev/include-dir.patch
@@ -6,7 +6,7 @@ diff -Naur orig/setup.py v0.4.7/setup.py
#-----------------------------------------------------------------------------
def create_ecodes():
- header = '/usr/include/linux/input.h'
-+ header = os.environ['NDKPLATFORM'] + '/usr/include/linux/input.h'
++ header = os.environ['SYSROOT'] + '/usr/include/linux/input.h'
if not os.path.isfile(header):
msg = '''\
diff --git a/pythonforandroid/recipes/feedparser/__init__.py b/pythonforandroid/recipes/feedparser/__init__.py
index d030494aa..cce88b9ee 100644
--- a/pythonforandroid/recipes/feedparser/__init__.py
+++ b/pythonforandroid/recipes/feedparser/__init__.py
@@ -1,10 +1,12 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
+
class FeedparserPyRecipe(PythonRecipe):
version = '5.2.1'
url = 'https://github.com/kurtmckee/feedparser/archive/{version}.tar.gz'
- depends = ['hostpython2', 'setuptools']
+ depends = ['setuptools']
site_packages_name = 'feedparser'
call_hostpython_via_targetpython = False
+
recipe = FeedparserPyRecipe()
diff --git a/pythonforandroid/recipes/ffmpeg/__init__.py b/pythonforandroid/recipes/ffmpeg/__init__.py
index cf2999fdf..9414552f0 100644
--- a/pythonforandroid/recipes/ffmpeg/__init__.py
+++ b/pythonforandroid/recipes/ffmpeg/__init__.py
@@ -1,15 +1,12 @@
-from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM
+from pythonforandroid.toolchain import Recipe, current_directory, shprint
from os.path import exists, join, realpath
-from os import uname
-import glob
import sh
-import os
-import shutil
class FFMpegRecipe(Recipe):
- version = '3.4.1'
- url = 'http://ffmpeg.org/releases/ffmpeg-{version}.tar.bz2'
+ version = 'n4.3.1'
+ # Moved to github.com instead of ffmpeg.org to improve download speed
+ url = 'https://github.com/FFmpeg/FFmpeg/archive/{version}.zip'
depends = ['sdl2'] # Need this to build correct recipe order
opts_depends = ['openssl', 'ffpyplayer_codecs']
patches = ['patches/configure.patch']
@@ -22,7 +19,7 @@ def prebuild_arch(self, arch):
self.apply_patches(arch)
def get_recipe_env(self, arch):
- env = super(FFMpegRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['NDK'] = self.ctx.ndk_dir
return env
@@ -36,18 +33,24 @@ def build_arch(self, arch):
if 'openssl' in self.ctx.recipe_build_order:
flags += [
- '--enable-openssl',
+ '--enable-openssl',
'--enable-nonfree',
'--enable-protocol=https,tls_openssl',
]
- build_dir = Recipe.get_recipe('openssl', self.ctx).get_build_dir(arch.arch)
- cflags += ['-I' + build_dir + '/include/']
+ build_dir = Recipe.get_recipe(
+ 'openssl', self.ctx).get_build_dir(arch.arch)
+ cflags += ['-I' + build_dir + '/include/',
+ '-DOPENSSL_API_COMPAT=0x10002000L']
ldflags += ['-L' + build_dir]
- if 'ffpyplayer_codecs' in self.ctx.recipe_build_order:
+ if 'ffpyplayer_codecs' in self.ctx.recipe_build_order:
+ # Enable GPL
+ flags += ['--enable-gpl']
+
# libx264
flags += ['--enable-libx264']
- build_dir = Recipe.get_recipe('libx264', self.ctx).get_build_dir(arch.arch)
+ build_dir = Recipe.get_recipe(
+ 'libx264', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/']
ldflags += ['-lx264', '-L' + build_dir + '/lib/']
@@ -56,6 +59,14 @@ def build_arch(self, arch):
build_dir = Recipe.get_recipe('libshine', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/']
ldflags += ['-lshine', '-L' + build_dir + '/lib/']
+ ldflags += ['-lm']
+
+ # libvpx
+ flags += ['--enable-libvpx']
+ build_dir = Recipe.get_recipe(
+ 'libvpx', self.ctx).get_build_dir(arch.arch)
+ cflags += ['-I' + build_dir + '/include/']
+ ldflags += ['-lvpx', '-L' + build_dir + '/lib/']
# Enable all codecs:
flags += [
@@ -71,7 +82,7 @@ def build_arch(self, arch):
'--enable-parser=aac,ac3,h261,h264,mpegaudio,mpeg4video,mpegvideo,vc1',
'--enable-decoder=aac,h264,mpeg4,mpegvideo',
'--enable-muxer=h264,mov,mp4,mpeg2video',
- '--enable-demuxer=aac,h264,m4v,mov,mpegvideo,vc1',
+ '--enable-demuxer=aac,h264,m4v,mov,mpegvideo,vc1,rtsp',
]
# needed to prevent _ffmpeg.so: version node not found for symbol av_init_packet@LIBAVFORMAT_52
@@ -82,39 +93,48 @@ def build_arch(self, arch):
# disable binaries / doc
flags += [
- '--disable-ffmpeg',
- '--disable-ffplay',
- '--disable-ffprobe',
- '--disable-ffserver',
+ '--disable-programs',
'--disable-doc',
]
# other flags:
flags += [
- '--enable-filter=aresample,resample,crop,adelay,volume',
- '--enable-protocol=file,http',
+ '--enable-filter=aresample,resample,crop,adelay,volume,scale',
+ '--enable-protocol=file,http,hls,udp,tcp',
'--enable-small',
'--enable-hwaccels',
- '--enable-gpl',
'--enable-pic',
'--disable-static',
+ '--disable-debug',
'--enable-shared',
]
+ if 'arm64' in arch.arch:
+ arch_flag = 'aarch64'
+ elif 'x86' in arch.arch:
+ arch_flag = 'x86'
+ flags += ['--disable-asm']
+ else:
+ arch_flag = 'arm'
+
# android:
flags += [
- '--target-os=android',
- '--cross-prefix=arm-linux-androideabi-',
- '--arch=arm',
- '--sysroot=' + self.ctx.ndk_platform,
+ '--target-os=android',
+ '--enable-cross-compile',
+ '--cross-prefix={}-'.format(arch.target),
+ '--arch={}'.format(arch_flag),
+ '--strip={}'.format(self.ctx.ndk.llvm_strip),
+ '--sysroot={}'.format(self.ctx.ndk.sysroot),
'--enable-neon',
'--prefix={}'.format(realpath('.')),
]
- cflags += [
- '-mfpu=vfpv3-d16',
- '-mfloat-abi=softfp',
- '-fPIC',
- ]
+
+ if arch_flag == 'arm':
+ cflags += [
+ '-mfpu=vfpv3-d16',
+ '-mfloat-abi=softfp',
+ '-fPIC',
+ ]
env['CFLAGS'] += ' ' + ' '.join(cflags)
env['LDFLAGS'] += ' ' + ' '.join(ldflags)
@@ -124,6 +144,8 @@ def build_arch(self, arch):
shprint(sh.make, '-j4', _env=env)
shprint(sh.make, 'install', _env=env)
# copy libs:
- sh.cp('-a', sh.glob('./lib/lib*.so'), self.ctx.get_libs_dir(arch.arch))
+ sh.cp('-a', sh.glob('./lib/lib*.so'),
+ self.ctx.get_libs_dir(arch.arch))
+
recipe = FFMpegRecipe()
diff --git a/pythonforandroid/recipes/ffmpeg/patches/configure.patch b/pythonforandroid/recipes/ffmpeg/patches/configure.patch
index 1610a5678..cacf0294e 100644
--- a/pythonforandroid/recipes/ffmpeg/patches/configure.patch
+++ b/pythonforandroid/recipes/ffmpeg/patches/configure.patch
@@ -1,21 +1,11 @@
---- ./configure.orig 2017-12-11 00:35:18.000000000 +0300
-+++ ./configure 2017-12-19 09:47:54.104914600 +0300
-@@ -4841,9 +4841,6 @@
- add_cflags -std=c11 ||
- check_cflags -std=c99
-
--check_cppflags -D_FILE_OFFSET_BITS=64
--check_cppflags -D_LARGEFILE_SOURCE
--
- add_host_cppflags -D_ISOC99_SOURCE
- check_host_cflags -std=c99
- check_host_cflags -Wall
-@@ -5979,7 +5976,7 @@
+--- ./configure 2020-10-11 19:12:16.759760904 +0200
++++ ./configure.patch 2020-10-11 19:15:49.059533563 +0200
+@@ -6361,7 +6361,7 @@
enabled librsvg && require_pkg_config librsvg librsvg-2.0 librsvg-2.0/librsvg/rsvg.h rsvg_handle_render_cairo
enabled librtmp && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket
- enabled librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new
+ enabled librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs "-lstdc++"
-enabled libshine && require_pkg_config libshine shine shine/layer3.h shine_encode_buffer
-+enabled libshine && require "shine" shine/layer3.h shine_encode_buffer -lshine
- enabled libsmbclient && { use_pkg_config libsmbclient smbclient libsmbclient.h smbc_init ||
- require smbclient libsmbclient.h smbc_init -lsmbclient; }
- enabled libsnappy && require libsnappy snappy-c.h snappy_compress -lsnappy
++enabled libshine && require "shine" shine/layer3.h shine_encode_buffer -lshine -lm
+ enabled libsmbclient && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init ||
+ require libsmbclient libsmbclient.h smbc_init -lsmbclient; }
+ enabled libsnappy && require libsnappy snappy-c.h snappy_compress -lsnappy -lstdc++
\ No newline at end of file
diff --git a/pythonforandroid/recipes/ffpyplayer/__init__.py b/pythonforandroid/recipes/ffpyplayer/__init__.py
index b8c3280f2..6260037a7 100644
--- a/pythonforandroid/recipes/ffpyplayer/__init__.py
+++ b/pythonforandroid/recipes/ffpyplayer/__init__.py
@@ -1,19 +1,16 @@
-from pythonforandroid.toolchain import Recipe, CythonRecipe, shprint, current_directory, ArchARM
-from os.path import exists, join, realpath
-from os import uname
-import glob
-import sh
-import os
+from pythonforandroid.recipe import CythonRecipe
+from pythonforandroid.toolchain import Recipe
+from os.path import join
class FFPyPlayerRecipe(CythonRecipe):
- version = '6f7568b498715c2da88f061ebad082a042514923'
+ version = 'v4.3.2'
url = 'https://github.com/matham/ffpyplayer/archive/{version}.zip'
- depends = [('python2', 'python3crystax'), 'sdl2', 'ffmpeg']
+ depends = ['python3', 'sdl2', 'ffmpeg']
opt_depends = ['openssl', 'ffpyplayer_codecs']
def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(FFPyPlayerRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
build_dir = Recipe.get_recipe('ffmpeg', self.ctx).get_build_dir(arch.arch)
env["FFMPEG_INCLUDE_DIR"] = join(build_dir, "include")
@@ -23,8 +20,23 @@ def get_recipe_env(self, arch, with_flags_in_cc=True):
env["SDL_LIB_DIR"] = join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)
env["USE_SDL2_MIXER"] = '1'
- env["SDL2_MIXER_INCLUDE_DIR"] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_mixer')
+
+ # ffpyplayer does not allow to pass more than one include dir for sdl2_mixer (and ATM is
+ # not needed), so we only pass the first one.
+ sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)
+ env["SDL2_MIXER_INCLUDE_DIR"] = sdl2_mixer_recipe.get_include_dirs(arch)[0]
+
+ # NDKPLATFORM and LIBLINK are our switches for detecting Android platform, so can't be empty
+ # FIXME: We may want to introduce a cleaner approach to this?
+ env['NDKPLATFORM'] = "NOTNONE"
+ env['LIBLINK'] = 'NOTNONE'
+
+ # ffmpeg recipe enables GPL components only if ffpyplayer_codecs recipe used.
+ # Therefor we need to disable libpostproc if skipped.
+ if 'ffpyplayer_codecs' not in self.ctx.recipe_build_order:
+ env["CONFIG_POSTPROC"] = '0'
return env
+
recipe = FFPyPlayerRecipe()
diff --git a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py b/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py
index ee70a43f6..eedb1269e 100644
--- a/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py
+++ b/pythonforandroid/recipes/ffpyplayer_codecs/__init__.py
@@ -2,9 +2,10 @@
class FFPyPlayerCodecsRecipe(Recipe):
- depends = ['libshine', 'libx264']
+ depends = ['libx264', 'libshine', 'libvpx']
def build_arch(self, arch):
pass
+
recipe = FFPyPlayerCodecsRecipe()
diff --git a/pythonforandroid/recipes/flask/__init__.py b/pythonforandroid/recipes/flask/__init__.py
index c21b9d912..b2729420d 100644
--- a/pythonforandroid/recipes/flask/__init__.py
+++ b/pythonforandroid/recipes/flask/__init__.py
@@ -1,15 +1,12 @@
-from pythonforandroid.toolchain import PythonRecipe, shprint
-import sh
+from pythonforandroid.recipe import PythonRecipe
class FlaskRecipe(PythonRecipe):
- version = '0.10.1' # The webserver of 'master' seems to fail
- # after a little while on Android, so use
- # 0.10.1 at least for now
+ version = '2.0.3'
url = 'https://github.com/pallets/flask/archive/{version}.zip'
- depends = [('python2', 'python3crystax'), 'setuptools', 'genericndkbuild']
+ depends = ['setuptools']
python_depends = ['jinja2', 'werkzeug', 'markupsafe', 'itsdangerous', 'click']
diff --git a/pythonforandroid/recipes/fontconfig/__init__.py b/pythonforandroid/recipes/fontconfig/__init__.py
index f91232e77..ad959f638 100644
--- a/pythonforandroid/recipes/fontconfig/__init__.py
+++ b/pythonforandroid/recipes/fontconfig/__init__.py
@@ -1,11 +1,10 @@
+from os.path import join
-from pythonforandroid.toolchain import BootstrapNDKRecipe, shprint, current_directory, info_main
-from os.path import exists, join
+from pythonforandroid.recipe import BootstrapNDKRecipe
+from pythonforandroid.toolchain import current_directory, shprint
import sh
-
-
class FontconfigRecipe(BootstrapNDKRecipe):
version = "really_old"
url = 'https://github.com/vault/fontconfig/archive/androidbuild.zip'
@@ -16,7 +15,13 @@ def build_arch(self, arch):
env = self.get_recipe_env(arch)
with current_directory(self.get_jni_dir()):
- shprint(sh.ndk_build, "V=1", 'fontconfig', _env=env)
+ shprint(
+ sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
+ "V=1",
+ "APP_ALLOW_MISSING_DEPS=true",
+ "fontconfig",
+ _env=env,
+ )
recipe = FontconfigRecipe()
diff --git a/pythonforandroid/recipes/freetype-py/__init__.py b/pythonforandroid/recipes/freetype-py/__init__.py
new file mode 100644
index 000000000..7be2f2e10
--- /dev/null
+++ b/pythonforandroid/recipes/freetype-py/__init__.py
@@ -0,0 +1,12 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class FreetypePyRecipe(PythonRecipe):
+ version = '2.2.0'
+ url = 'https://github.com/rougier/freetype-py/archive/refs/tags/v{version}.tar.gz'
+ depends = ['freetype']
+ patches = ['fall-back-to-distutils.patch']
+ site_packages_name = 'freetype'
+
+
+recipe = FreetypePyRecipe()
diff --git a/pythonforandroid/recipes/freetype-py/fall-back-to-distutils.patch b/pythonforandroid/recipes/freetype-py/fall-back-to-distutils.patch
new file mode 100644
index 000000000..0f06f1854
--- /dev/null
+++ b/pythonforandroid/recipes/freetype-py/fall-back-to-distutils.patch
@@ -0,0 +1,15 @@
+diff -ruN freetype-py.orig/setup.py freetype-py/setup.py
+--- freetype-py.orig/setup.py 2020-07-09 20:58:51.000000000 +0700
++++ freetype-py/setup.py 2022-03-02 19:28:17.948831134 +0700
+@@ -12,7 +12,10 @@
+ from io import open
+ from os import path
+
+-from setuptools import setup
++try:
++ from setuptools import setup
++except ImportError:
++ from distutils.core import setup
+
+ if os.environ.get("FREETYPEPY_BUNDLE_FT"):
+ print("# Will build and bundle FreeType.")
diff --git a/pythonforandroid/recipes/freetype/__init__.py b/pythonforandroid/recipes/freetype/__init__.py
index 7217dff9a..e5ddfe142 100644
--- a/pythonforandroid/recipes/freetype/__init__.py
+++ b/pythonforandroid/recipes/freetype/__init__.py
@@ -1,39 +1,132 @@
-
-from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM
-from os.path import exists, join, realpath
-from os import uname
-import glob
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint, info
+from pythonforandroid.util import current_directory
+from os.path import join, exists
+from multiprocessing import cpu_count
import sh
+
class FreetypeRecipe(Recipe):
+ """The freetype library it's special, because has cyclic dependencies with
+ harfbuzz library, so freetype can be build with harfbuzz support, and
+ harfbuzz can be build with freetype support. This complicates the build of
+ both recipes because in order to get the full set we need to compile those
+ recipes several times:
+ - build freetype without harfbuzz
+ - build harfbuzz with freetype
+ - build freetype with harfbuzz support
+
+ .. note::
+ To build freetype with harfbuzz support you must add `harfbuzz` to your
+ requirements, otherwise freetype will be build without harfbuzz
+
+ .. seealso::
+ https://sourceforge.net/projects/freetype/files/freetype2/2.5.3/
+ """
+
+ version = '2.10.1'
+ url = 'https://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.gz' # noqa
+ built_libraries = {'libfreetype.so': 'objs/.libs'}
- version = '2.5.5'
- url = 'http://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.gz'
+ def get_recipe_env(self, arch=None, with_harfbuzz=False):
+ env = super().get_recipe_env(arch)
+ if with_harfbuzz:
+ harfbuzz_build = self.get_recipe(
+ 'harfbuzz', self.ctx
+ ).get_build_dir(arch.arch)
+ freetype_install = join(self.get_build_dir(arch.arch), 'install')
- depends = ['harfbuzz']
+ env['HARFBUZZ_CFLAGS'] = '-I{harfbuzz} -I{harfbuzz}/src'.format(
+ harfbuzz=harfbuzz_build
+ )
+ env['HARFBUZZ_LIBS'] = (
+ '-L{freetype}/lib -lfreetype '
+ '-L{harfbuzz}/src/.libs -lharfbuzz'.format(
+ freetype=freetype_install, harfbuzz=harfbuzz_build
+ )
+ )
- def should_build(self, arch):
- if exists(join(self.get_build_dir(arch.arch), 'objs', '.libs', 'libfreetype.so')):
- return False
- return True
+ # android's zlib support
+ zlib_lib_path = arch.ndk_lib_dir_versioned
+ zlib_includes = self.ctx.ndk.sysroot_include_dir
- def build_arch(self, arch):
- env = self.get_recipe_env(arch)
+ def add_flag_if_not_added(flag, env_key):
+ if flag not in env[env_key]:
+ env[env_key] += flag
- harfbuzz_recipe = Recipe.get_recipe('harfbuzz', self.ctx)
- env['LDFLAGS'] = ' '.join(
- [env['LDFLAGS'],
- '-L{}'.format(join(harfbuzz_recipe.get_build_dir(arch.arch), 'src', '.libs'))])
+ add_flag_if_not_added(' -I' + zlib_includes, 'CFLAGS')
+ add_flag_if_not_added(' -L' + zlib_lib_path, 'LDFLAGS')
+ add_flag_if_not_added(' -lz', 'LDLIBS')
+ return env
+
+ def build_arch(self, arch, with_harfbuzz=False):
+ env = self.get_recipe_env(arch, with_harfbuzz=with_harfbuzz)
+
+ harfbuzz_in_recipes = 'harfbuzz' in self.ctx.recipe_build_order
+ prefix_path = self.get_build_dir(arch.arch)
+ if harfbuzz_in_recipes and not with_harfbuzz:
+ # This is the first time we build freetype and we modify `prefix`,
+ # because we will install the compiled library so later we can
+ # build harfbuzz (with freetype support) using this freetype
+ # installation
+ prefix_path = join(prefix_path, 'install')
+
+ # Configure freetype library
+ config_args = {
+ '--host={}'.format(arch.command_prefix),
+ '--prefix={}'.format(prefix_path),
+ '--without-bzip2',
+ '--with-png=no',
+ }
+ if not harfbuzz_in_recipes:
+ info('Build freetype (without harfbuzz)')
+ config_args = config_args.union(
+ {'--disable-static',
+ '--enable-shared',
+ '--with-harfbuzz=no',
+ '--with-zlib=yes',
+ }
+ )
+ elif not with_harfbuzz:
+ info('Build freetype for First time (without harfbuzz)')
+ # This time we will build our freetype library as static because we
+ # want that the harfbuzz library to have the necessary freetype
+ # symbols/functions, so we avoid to have two freetype shared
+ # libraries which will be confusing and harder to link with them
+ config_args = config_args.union(
+ {'--disable-shared', '--with-harfbuzz=no', '--with-zlib=no'}
+ )
+ else:
+ info('Build freetype for Second time (with harfbuzz)')
+ config_args = config_args.union(
+ {'--disable-static',
+ '--enable-shared',
+ '--with-harfbuzz=yes',
+ '--with-zlib=yes',
+ }
+ )
+ info('Configure args are:\n\t-{}'.format('\n\t-'.join(config_args)))
+
+ # Build freetype library
with current_directory(self.get_build_dir(arch.arch)):
configure = sh.Command('./configure')
- shprint(configure, '--host=arm-linux-androideabi',
- '--prefix={}'.format(realpath('.')),
- '--without-zlib', '--with-png=no', '--enable-shared',
- _env=env)
- shprint(sh.make, '-j5', _env=env)
+ shprint(configure, *config_args, _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
+
+ if not with_harfbuzz and harfbuzz_in_recipes:
+ info('Installing freetype (first time build without harfbuzz)')
+ # First build, install the compiled lib, and clean build env
+ shprint(sh.make, 'install', _env=env)
+ shprint(sh.make, 'distclean', _env=env)
- shprint(sh.cp, 'objs/.libs/libfreetype.so', self.ctx.libs_dir)
+ def install_libraries(self, arch):
+ # This library it's special because the first time we built it may not
+ # generate the expected library, because it can depend on harfbuzz, so
+ # we will make sure to only install it when the library exists
+ if not exists(list(self.get_libraries(arch))[0]):
+ return
+ self.install_libs(arch, *self.get_libraries(arch))
recipe = FreetypeRecipe()
diff --git a/pythonforandroid/recipes/genericndkbuild/__init__.py b/pythonforandroid/recipes/genericndkbuild/__init__.py
index ead8d9bd9..8b2a9c26a 100644
--- a/pythonforandroid/recipes/genericndkbuild/__init__.py
+++ b/pythonforandroid/recipes/genericndkbuild/__init__.py
@@ -1,5 +1,7 @@
-from pythonforandroid.toolchain import BootstrapNDKRecipe, shprint, current_directory, info
-from os.path import exists, join
+from os.path import join
+
+from pythonforandroid.recipe import BootstrapNDKRecipe
+from pythonforandroid.toolchain import current_directory, shprint
import sh
@@ -7,27 +9,27 @@ class GenericNDKBuildRecipe(BootstrapNDKRecipe):
version = None
url = None
- depends = [('python2', 'python3crystax')]
- conflicts = ['sdl2', 'pygame', 'sdl']
+ depends = ['python3']
+ conflicts = ['sdl2']
def should_build(self, arch):
return True
- def get_recipe_env(self, arch=None):
- env = super(GenericNDKBuildRecipe, self).get_recipe_env(arch)
- py2 = self.get_recipe('python2', arch.ctx)
- env['PYTHON2_NAME'] = py2.get_dir_name()
- if 'python2' in self.ctx.recipe_build_order:
- env['EXTRA_LDLIBS'] = ' -lpython2.7'
-
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=True):
+ env = super().get_recipe_env(
+ arch=arch, with_flags_in_cc=with_flags_in_cc,
+ with_python=with_python,
+ )
env['APP_ALLOW_MISSING_DEPS'] = 'true'
+ # required for Qt bootstrap
+ env['PREFERRED_ABI'] = arch.arch
return env
def build_arch(self, arch):
env = self.get_recipe_env(arch)
with current_directory(self.get_jni_dir()):
- shprint(sh.ndk_build, "V=1", _env=env)
+ shprint(sh.Command(join(self.ctx.ndk_dir, "ndk-build")), "V=1", _env=env)
recipe = GenericNDKBuildRecipe()
diff --git a/pythonforandroid/recipes/gevent-websocket/__init__.py b/pythonforandroid/recipes/gevent-websocket/__init__.py
deleted file mode 100644
index a0bab4227..000000000
--- a/pythonforandroid/recipes/gevent-websocket/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from pythonforandroid.toolchain import PythonRecipe
-
-
-class GeventWebsocketRecipe(PythonRecipe):
- version = '0.9.5'
- url = 'https://pypi.python.org/packages/source/g/gevent-websocket/gevent-websocket-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools']
- site_packages_name = 'geventwebsocket'
- call_hostpython_via_targetpython = False
-
-recipe = GeventWebsocketRecipe()
diff --git a/pythonforandroid/recipes/gevent/__init__.py b/pythonforandroid/recipes/gevent/__init__.py
index c3a9957a3..7958a5480 100644
--- a/pythonforandroid/recipes/gevent/__init__.py
+++ b/pythonforandroid/recipes/gevent/__init__.py
@@ -1,10 +1,34 @@
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe
+import re
+from pythonforandroid.logger import info
+from pythonforandroid.recipe import CythonRecipe
-class GeventRecipe(CompiledComponentsPythonRecipe):
- version = '1.1.1'
+class GeventRecipe(CythonRecipe):
+ version = '1.4.0'
url = 'https://pypi.python.org/packages/source/g/gevent/gevent-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'greenlet']
- patches = ["gevent.patch"]
+ depends = ['librt', 'setuptools']
+ patches = ["cross_compiling.patch"]
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ """
+ - Moves all -I<inc> -D<macro> from CFLAGS to CPPFLAGS environment.
+ - Moves all -l<lib> from LDFLAGS to LIBS environment.
+ - Copies all -l<lib> from LDLIBS to LIBS environment.
+ - Fixes linker name (use cross compiler) and flags (appends LIBS)
+ """
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+ # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS
+ regex = re.compile(r'(?:\s|^)-[DI][\S]+')
+ env['CPPFLAGS'] = ''.join(re.findall(regex, env['CFLAGS'])).strip()
+ env['CFLAGS'] = re.sub(regex, '', env['CFLAGS'])
+ info('Moved "{}" from CFLAGS to CPPFLAGS.'.format(env['CPPFLAGS']))
+ # LDFLAGS may only be used to specify linker flags, for libraries use LIBS
+ regex = re.compile(r'(?:\s|^)-l[\w\.]+')
+ env['LIBS'] = ''.join(re.findall(regex, env['LDFLAGS'])).strip()
+ env['LIBS'] += ' {}'.format(''.join(re.findall(regex, env['LDLIBS'])).strip())
+ env['LDFLAGS'] = re.sub(regex, '', env['LDFLAGS'])
+ info('Moved "{}" from LDFLAGS to LIBS.'.format(env['LIBS']))
+ return env
+
recipe = GeventRecipe()
diff --git a/pythonforandroid/recipes/gevent/cross_compiling.patch b/pythonforandroid/recipes/gevent/cross_compiling.patch
new file mode 100644
index 000000000..01e55d8c0
--- /dev/null
+++ b/pythonforandroid/recipes/gevent/cross_compiling.patch
@@ -0,0 +1,26 @@
+diff --git a/_setupares.py b/_setupares.py
+index dd184de6..bb16bebe 100644
+--- a/_setupares.py
++++ b/_setupares.py
+@@ -43,7 +43,7 @@ else:
+ ares_configure_command = ' '.join([
+ "(cd ", quoted_dep_abspath('c-ares'),
+ " && if [ -r ares_build.h ]; then cp ares_build.h ares_build.h.orig; fi ",
+- " && sh ./configure --disable-dependency-tracking " + _m32 + "CONFIG_COMMANDS= ",
++ " && sh ./configure --host={} --disable-dependency-tracking ".format(os.environ['TOOLCHAIN_PREFIX']) + _m32 + "CONFIG_COMMANDS= ",
+ " && cp ares_config.h ares_build.h \"$OLDPWD\" ",
+ " && cat ares_build.h ",
+ " && if [ -r ares_build.h.orig ]; then mv ares_build.h.orig ares_build.h; fi)",
+diff --git a/_setuplibev.py b/_setuplibev.py
+index 2a5841bf..b6433c94 100644
+--- a/_setuplibev.py
++++ b/_setuplibev.py
+@@ -31,7 +31,7 @@ LIBEV_EMBED = should_embed('libev')
+ # and the PyPy branch will clean it up.
+ libev_configure_command = ' '.join([
+ "(cd ", quoted_dep_abspath('libev'),
+- " && sh ./configure ",
++ " && sh ./configure --host={} ".format(os.environ['TOOLCHAIN_PREFIX']),
+ " && cp config.h \"$OLDPWD\"",
+ ")",
+ '> configure-output.txt'
diff --git a/pythonforandroid/recipes/gevent/gevent.patch b/pythonforandroid/recipes/gevent/gevent.patch
deleted file mode 100644
index 4b4b673fc..000000000
--- a/pythonforandroid/recipes/gevent/gevent.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-diff -Naur gevent-1.1.1/setup.py gevent-1.1.1_diff/setup.py
---- gevent-1.1.1/setup.py 2016-04-04 17:27:33.000000000 +0200
-+++ gevent-1.1.1_diff/setup.py 2016-05-10 10:10:39.145881610 +0200
-@@ -96,7 +96,7 @@
- # and the PyPy branch will clean it up.
- libev_configure_command = ' '.join([
- "(cd ", _quoted_abspath('libev/'),
-- " && /bin/sh ./configure ",
-+ " && /bin/sh ./configure --host={}".format(os.environ['TOOLCHAIN_PREFIX']),
- " && cp config.h \"$OLDPWD\"",
- ")",
- '> configure-output.txt'
-@@ -112,7 +112,7 @@
- # Use -r, not -e, for support of old solaris. See https://github.com/gevent/gevent/issues/777
- ares_configure_command = ' '.join(["(cd ", _quoted_abspath('c-ares/'),
- " && if [ -r ares_build.h ]; then cp ares_build.h ares_build.h.orig; fi ",
-- " && /bin/sh ./configure " + _m32 + "CONFIG_COMMANDS= CONFIG_FILES= ",
-+ " && /bin/sh ./configure --host={} ".format(os.environ['TOOLCHAIN_PREFIX']) + "CFLAGS= LDFLAGS= CONFIG_COMMANDS= CONFIG_FILES= ",
- " && cp ares_config.h ares_build.h \"$OLDPWD\" ",
- " && mv ares_build.h.orig ares_build.h)",
- "> configure-output.txt"])
diff --git a/pythonforandroid/recipes/greenlet/__init__.py b/pythonforandroid/recipes/greenlet/__init__.py
index a12758142..3f2043d57 100644
--- a/pythonforandroid/recipes/greenlet/__init__.py
+++ b/pythonforandroid/recipes/greenlet/__init__.py
@@ -1,9 +1,11 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-class GreenletRecipe(PythonRecipe):
- version = '0.4.9'
+class GreenletRecipe(CompiledComponentsPythonRecipe):
+ version = '0.4.15'
url = 'https://pypi.python.org/packages/source/g/greenlet/greenlet-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
+
recipe = GreenletRecipe()
diff --git a/pythonforandroid/recipes/groestlcoin_hash/__init__.py b/pythonforandroid/recipes/groestlcoin_hash/__init__.py
new file mode 100644
index 000000000..873ca6157
--- /dev/null
+++ b/pythonforandroid/recipes/groestlcoin_hash/__init__.py
@@ -0,0 +1,11 @@
+from pythonforandroid.recipe import CythonRecipe
+
+
+class GroestlcoinHashRecipe(CythonRecipe):
+ version = '1.0.3'
+ url = 'https://github.com/Groestlcoin/groestlcoin-hash-python/archive/{version}.tar.gz'
+ depends = ['setuptools']
+ cythonize = False
+
+
+recipe = GroestlcoinHashRecipe()
diff --git a/pythonforandroid/recipes/harfbuzz/__init__.py b/pythonforandroid/recipes/harfbuzz/__init__.py
index 1df851fbf..fd1dbe9de 100644
--- a/pythonforandroid/recipes/harfbuzz/__init__.py
+++ b/pythonforandroid/recipes/harfbuzz/__init__.py
@@ -1,33 +1,75 @@
-
-from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM
-from os.path import exists, join, realpath
-from os import uname
-import glob
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from multiprocessing import cpu_count
+from os.path import join
import sh
class HarfbuzzRecipe(Recipe):
- version = '0.9.40'
- url = 'http://www.freedesktop.org/software/harfbuzz/release/harfbuzz-{version}.tar.bz2'
+ """The harfbuzz library it's special, because has cyclic dependencies with
+ freetype library, so freetype can be build with harfbuzz support, and
+ harfbuzz can be build with freetype support. This complicates the build of
+ both recipes because in order to get the full set we need to compile those
+ recipes several times:
+ - build freetype without harfbuzz
+ - build harfbuzz with freetype
+ - build freetype with harfbuzz support
+
+ .. seealso::
+ https://sourceforge.net/projects/freetype/files/freetype2/2.5.3/
+ """
+
+ version = '2.6.4'
+ url = 'http://www.freedesktop.org/software/harfbuzz/release/harfbuzz-{version}.tar.xz' # noqa
+ opt_depends = ['freetype']
+ built_libraries = {'libharfbuzz.so': 'src/.libs'}
- def should_build(self, arch):
- if exists(join(self.get_build_dir(arch.arch), 'src', '.libs', 'libharfbuzz.so')):
- return False
- return True
+ def get_recipe_env(self, arch=None):
+ env = super().get_recipe_env(arch)
+ if 'freetype' in self.ctx.recipe_build_order:
+ freetype = self.get_recipe('freetype', self.ctx)
+ freetype_install = join(
+ freetype.get_build_dir(arch.arch), 'install'
+ )
+ # Explicitly tell harfbuzz's configure script that we want to
+ # use our freetype library or it won't be correctly detected
+ env['FREETYPE_CFLAGS'] = '-I{}/include/freetype2'.format(
+ freetype_install
+ )
+ env['FREETYPE_LIBS'] = ' '.join(
+ ['-L{}/lib'.format(freetype_install), '-lfreetype']
+ )
+ return env
def build_arch(self, arch):
env = self.get_recipe_env(arch)
- env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
- self.ctx.get_libs_dir(arch.arch) +
- '-L{}'.format(self.ctx.libs_dir))
+
with current_directory(self.get_build_dir(arch.arch)):
configure = sh.Command('./configure')
- shprint(configure, '--without-icu', '--host=arm-linux=androideabi',
- '--prefix={}'.format(join(self.ctx.build_dir, 'python-install')),
- '--without-freetype', '--without-glib', _env=env)
- shprint(sh.make, '-j5', _env=env)
+ shprint(
+ configure,
+ '--host={}'.format(arch.command_prefix),
+ '--prefix={}'.format(self.get_build_dir(arch.arch)),
+ '--with-freetype={}'.format(
+ 'yes'
+ if 'freetype' in self.ctx.recipe_build_order
+ else 'no'
+ ),
+ '--with-icu=no',
+ '--with-cairo=no',
+ '--with-fontconfig=no',
+ '--with-glib=no',
+ _env=env,
+ )
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
+
+ if 'freetype' in self.ctx.recipe_build_order:
+ # Rebuild/install freetype with harfbuzz support
+ freetype = self.get_recipe('freetype', self.ctx)
+ freetype.build_arch(arch, with_harfbuzz=True)
+ freetype.install_libraries(arch)
- shprint(sh.cp, '-L', join('src', '.libs', 'libharfbuzz.so'), self.ctx.libs_dir)
recipe = HarfbuzzRecipe()
diff --git a/pythonforandroid/recipes/hostpython2/Setup b/pythonforandroid/recipes/hostpython2/Setup
deleted file mode 100644
index d21c89365..000000000
--- a/pythonforandroid/recipes/hostpython2/Setup
+++ /dev/null
@@ -1,495 +0,0 @@
-# -*- makefile -*-
-# The file Setup is used by the makesetup script to construct the files
-# Makefile and config.c, from Makefile.pre and config.c.in,
-# respectively. The file Setup itself is initially copied from
-# Setup.dist; once it exists it will not be overwritten, so you can edit
-# Setup to your heart's content. Note that Makefile.pre is created
-# from Makefile.pre.in by the toplevel configure script.
-
-# (VPATH notes: Setup and Makefile.pre are in the build directory, as
-# are Makefile and config.c; the *.in and *.dist files are in the source
-# directory.)
-
-# Each line in this file describes one or more optional modules.
-# Modules enabled here will not be compiled by the setup.py script,
-# so the file can be used to override setup.py's behavior.
-
-# Lines have the following structure:
-#
-# <module> ... [<sourcefile> ...] [<cpparg> ...] [<library> ...]
-#
-# <sourcefile> is anything ending in .c (.C, .cc, .c++ are C++ files)
-# <cpparg> is anything starting with -I, -D, -U or -C
-# <library> is anything ending in .a or beginning with -l or -L
-# <module> is anything else but should be a valid Python
-# identifier (letters, digits, underscores, beginning with non-digit)
-#
-# (As the makesetup script changes, it may recognize some other
-# arguments as well, e.g. *.so and *.sl as libraries. See the big
-# case statement in the makesetup script.)
-#
-# Lines can also have the form
-#
-# <name> = <value>
-#
-# which defines a Make variable definition inserted into Makefile.in
-#
-# Finally, if a line contains just the word "*shared*" (without the
-# quotes but with the stars), then the following modules will not be
-# built statically. The build process works like this:
-#
-# 1. Build all modules that are declared as static in Modules/Setup,
-# combine them into libpythonxy.a, combine that into python.
-# 2. Build all modules that are listed as shared in Modules/Setup.
-# 3. Invoke setup.py. That builds all modules that
-# a) are not builtin, and
-# b) are not listed in Modules/Setup, and
-# c) can be build on the target
-#
-# Therefore, modules declared to be shared will not be
-# included in the config.c file, nor in the list of objects to be
-# added to the library archive, and their linker options won't be
-# added to the linker options. Rules to create their .o files and
-# their shared libraries will still be added to the Makefile, and
-# their names will be collected in the Make variable SHAREDMODS. This
-# is used to build modules as shared libraries. (They can be
-# installed using "make sharedinstall", which is implied by the
-# toplevel "make install" target.) (For compatibility,
-# *noconfig* has the same effect as *shared*.)
-#
-# In addition, *static* explicitly declares the following modules to
-# be static. Lines containing "*static*" and "*shared*" may thus
-# alternate throughout this file.
-
-# NOTE: As a standard policy, as many modules as can be supported by a
-# platform should be present. The distribution comes with all modules
-# enabled that are supported by most platforms and don't require you
-# to ftp sources from elsewhere.
-
-
-# Some special rules to define PYTHONPATH.
-# Edit the definitions below to indicate which options you are using.
-# Don't add any whitespace or comments!
-
-# Directories where library files get installed.
-# DESTLIB is for Python modules; MACHDESTLIB for shared libraries.
-DESTLIB=$(LIBDEST)
-MACHDESTLIB=$(BINLIBDEST)
-
-# NOTE: all the paths are now relative to the prefix that is computed
-# at run time!
-
-# Standard path -- don't edit.
-# No leading colon since this is the first entry.
-# Empty since this is now just the runtime prefix.
-DESTPATH=
-
-# Site specific path components -- should begin with : if non-empty
-SITEPATH=
-
-# Standard path components for test modules
-TESTPATH=
-
-# Path components for machine- or system-dependent modules and shared libraries
-MACHDEPPATH=:plat-$(MACHDEP)
-EXTRAMACHDEPPATH=
-
-# Path component for the Tkinter-related modules
-# The TKPATH variable is always enabled, to save you the effort.
-TKPATH=:lib-tk
-
-# Path component for old modules.
-OLDPATH=:lib-old
-
-COREPYTHONPATH=$(DESTPATH)$(SITEPATH)$(TESTPATH)$(MACHDEPPATH)$(EXTRAMACHDEPPATH)$(TKPATH)$(OLDPATH)
-PYTHONPATH=$(COREPYTHONPATH)
-
-
-# The modules listed here can't be built as shared libraries for
-# various reasons; therefore they are listed here instead of in the
-# normal order.
-
-# This only contains the minimal set of modules required to run the
-# setup.py script in the root of the Python source tree.
-
-posix posixmodule.c # posix (UNIX) system calls
-errno errnomodule.c # posix (UNIX) errno values
-pwd pwdmodule.c # this is needed to find out the user's home dir
- # if $HOME is not set
-_sre _sre.c # Fredrik Lundh's new regular expressions
-_codecs _codecsmodule.c # access to the builtin codecs and codec registry
-
-# The zipimport module is always imported at startup. Having it as a
-# builtin module avoids some bootstrapping problems and reduces overhead.
-zipimport zipimport.c
-
-# The rest of the modules listed in this file are all commented out by
-# default. Usually they can be detected and built as dynamically
-# loaded modules by the new setup.py script added in Python 2.1. If
-# you're on a platform that doesn't support dynamic loading, want to
-# compile modules statically into the Python binary, or need to
-# specify some odd set of compiler switches, you can uncomment the
-# appropriate lines below.
-
-# ======================================================================
-
-# The Python symtable module depends on .h files that setup.py doesn't track
-_symtable symtablemodule.c
-
-# The SGI specific GL module:
-
-GLHACK=-Dclear=__GLclear
-#gl glmodule.c cgensupport.c -I$(srcdir) $(GLHACK) -lgl -lX11
-
-# Pure module. Cannot be linked dynamically.
-# -DWITH_QUANTIFY, -DWITH_PURIFY, or -DWITH_ALL_PURE
-#WHICH_PURE_PRODUCTS=-DWITH_ALL_PURE
-#PURE_INCLS=-I/usr/local/include
-#PURE_STUBLIBS=-L/usr/local/lib -lpurify_stubs -lquantify_stubs
-#pure puremodule.c $(WHICH_PURE_PRODUCTS) $(PURE_INCLS) $(PURE_STUBLIBS)
-
-# Uncommenting the following line tells makesetup that all following
-# modules are to be built as shared libraries (see above for more
-# detail; also note that *static* reverses this effect):
-
-#*shared*
-
-# GNU readline. Unlike previous Python incarnations, GNU readline is
-# now incorporated in an optional module, configured in the Setup file
-# instead of by a configure script switch. You may have to insert a
-# -L option pointing to the directory where libreadline.* lives,
-# and you may have to change -ltermcap to -ltermlib or perhaps remove
-# it, depending on your system -- see the GNU readline instructions.
-# It's okay for this to be a shared library, too.
-
-#readline readline.c -lreadline -ltermcap
-
-
-# Modules that should always be present (non UNIX dependent):
-
-array arraymodule.c # array objects
-cmath cmathmodule.c # -lm # complex math library functions
-math mathmodule.c # -lm # math library functions, e.g. sin()
-_struct _struct.c # binary structure packing/unpacking
-time timemodule.c # -lm # time operations and variables
-operator operator.c # operator.add() and similar goodies
-_weakref _weakref.c # basic weak reference support
-#_testcapi _testcapimodule.c # Python C API test module
-_random _randommodule.c # Random number generator
-_collections _collectionsmodule.c # Container types
-itertools itertoolsmodule.c # Functions creating iterators for efficient looping
-strop stropmodule.c # String manipulations
-_functools _functoolsmodule.c # Tools for working with functions and callable objects
-_elementtree -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI _elementtree.c # elementtree accelerator
-#_pickle _pickle.c # pickle accelerator
-datetime datetimemodule.c # date/time type
-_bisect _bisectmodule.c # Bisection algorithms
-
-#unicodedata unicodedata.c # static Unicode character database
-
-# access to ISO C locale support
-#_locale _localemodule.c # -lintl
-
-
-# Modules with some UNIX dependencies -- on by default:
-# (If you have a really backward UNIX, select and socket may not be
-# supported...)
-
-fcntl fcntlmodule.c # fcntl(2) and ioctl(2)
-#spwd spwdmodule.c # spwd(3)
-#grp grpmodule.c # grp(3)
-select selectmodule.c # select(2); not on ancient System V
-
-# Memory-mapped files (also works on Win32).
-#mmap mmapmodule.c
-
-# CSV file helper
-#_csv _csv.c
-
-# Socket module helper for socket(2)
-_socket socketmodule.c
-
-# Socket module helper for SSL support; you must comment out the other
-# socket line above, and possibly edit the SSL variable:
-#SSL=/usr/local/ssl
-#_ssl _ssl.c \
-# -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
-# -L$(SSL)/lib -lssl -lcrypto
-
-# The crypt module is now disabled by default because it breaks builds
-# on many systems (where -lcrypt is needed), e.g. Linux (I believe).
-#
-# First, look at Setup.config; configure may have set this for you.
-
-#crypt cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems
-
-
-# Some more UNIX dependent modules -- off by default, since these
-# are not supported by all UNIX systems:
-
-#nis nismodule.c -lnsl # Sun yellow pages -- not everywhere
-#termios termios.c # Steen Lumholt's termios module
-#resource resource.c # Jeremy Hylton's rlimit interface
-
-
-# Multimedia modules -- off by default.
-# These don't work for 64-bit platforms!!!
-# #993173 says audioop works on 64-bit platforms, though.
-# These represent audio samples or images as strings:
-
-#audioop audioop.c # Operations on audio samples
-#imageop imageop.c # Operations on images
-
-
-# Note that the _md5 and _sha modules are normally only built if the
-# system does not have the OpenSSL libs containing an optimized version.
-
-# The _md5 module implements the RSA Data Security, Inc. MD5
-# Message-Digest Algorithm, described in RFC 1321. The necessary files
-# md5.c and md5.h are included here.
-
-_md5 md5module.c md5.c
-
-
-# The _sha module implements the SHA checksum algorithms.
-# (NIST's Secure Hash Algorithms.)
-_sha shamodule.c
-_sha256 sha256module.c
-_sha512 sha512module.c
-
-
-# SGI IRIX specific modules -- off by default.
-
-# These module work on any SGI machine:
-
-# *** gl must be enabled higher up in this file ***
-#fm fmmodule.c $(GLHACK) -lfm -lgl # Font Manager
-#sgi sgimodule.c # sgi.nap() and a few more
-
-# This module requires the header file
-# /usr/people/4Dgifts/iristools/include/izoom.h:
-#imgfile imgfile.c -limage -lgutil -lgl -lm # Image Processing Utilities
-
-
-# These modules require the Multimedia Development Option (I think):
-
-#al almodule.c -laudio # Audio Library
-#cd cdmodule.c -lcdaudio -lds -lmediad # CD Audio Library
-#cl clmodule.c -lcl -lawareaudio # Compression Library
-#sv svmodule.c yuvconvert.c -lsvideo -lXext -lX11 # Starter Video
-
-
-# The FORMS library, by Mark Overmars, implements user interface
-# components such as dialogs and buttons using SGI's GL and FM
-# libraries. You must ftp the FORMS library separately from
-# ftp://ftp.cs.ruu.nl/pub/SGI/FORMS. It was tested with FORMS 2.2a.
-# NOTE: if you want to be able to use FORMS and curses simultaneously
-# (or both link them statically into the same binary), you must
-# compile all of FORMS with the cc option "-Dclear=__GLclear".
-
-# The FORMS variable must point to the FORMS subdirectory of the forms
-# toplevel directory:
-
-#FORMS=/ufs/guido/src/forms/FORMS
-#fl flmodule.c -I$(FORMS) $(GLHACK) $(FORMS)/libforms.a -lfm -lgl
-
-
-# SunOS specific modules -- off by default:
-
-#sunaudiodev sunaudiodev.c
-
-
-# A Linux specific module -- off by default; this may also work on
-# some *BSDs.
-
-#linuxaudiodev linuxaudiodev.c
-
-
-# George Neville-Neil's timing module:
-
-#timing timingmodule.c
-
-
-# The _tkinter module.
-#
-# The command for _tkinter is long and site specific. Please
-# uncomment and/or edit those parts as indicated. If you don't have a
-# specific extension (e.g. Tix or BLT), leave the corresponding line
-# commented out. (Leave the trailing backslashes in! If you
-# experience strange errors, you may want to join all uncommented
-# lines and remove the backslashes -- the backslash interpretation is
-# done by the shell's "read" command and it may not be implemented on
-# every system.
-
-# *** Always uncomment this (leave the leading underscore in!):
-# _tkinter _tkinter.c tkappinit.c -DWITH_APPINIT \
-# *** Uncomment and edit to reflect where your Tcl/Tk libraries are:
-# -L/usr/local/lib \
-# *** Uncomment and edit to reflect where your Tcl/Tk headers are:
-# -I/usr/local/include \
-# *** Uncomment and edit to reflect where your X11 header files are:
-# -I/usr/X11R6/include \
-# *** Or uncomment this for Solaris:
-# -I/usr/openwin/include \
-# *** Uncomment and edit for Tix extension only:
-# -DWITH_TIX -ltix8.1.8.2 \
-# *** Uncomment and edit for BLT extension only:
-# -DWITH_BLT -I/usr/local/blt/blt8.0-unoff/include -lBLT8.0 \
-# *** Uncomment and edit for PIL (TkImaging) extension only:
-# (See http://www.pythonware.com/products/pil/ for more info)
-# -DWITH_PIL -I../Extensions/Imaging/libImaging tkImaging.c \
-# *** Uncomment and edit for TOGL extension only:
-# -DWITH_TOGL togl.c \
-# *** Uncomment and edit to reflect your Tcl/Tk versions:
-# -ltk8.2 -ltcl8.2 \
-# *** Uncomment and edit to reflect where your X11 libraries are:
-# -L/usr/X11R6/lib \
-# *** Or uncomment this for Solaris:
-# -L/usr/openwin/lib \
-# *** Uncomment these for TOGL extension only:
-# -lGL -lGLU -lXext -lXmu \
-# *** Uncomment for AIX:
-# -lld \
-# *** Always uncomment this; X11 libraries to link with:
-# -lX11
-
-# Lance Ellinghaus's syslog module
-#syslog syslogmodule.c # syslog daemon interface
-
-
-# Curses support, requring the System V version of curses, often
-# provided by the ncurses library. e.g. on Linux, link with -lncurses
-# instead of -lcurses).
-#
-# First, look at Setup.config; configure may have set this for you.
-
-#_curses _cursesmodule.c -lcurses -ltermcap
-# Wrapper for the panel library that's part of ncurses and SYSV curses.
-#_curses_panel _curses_panel.c -lpanel -lncurses
-
-
-# Generic (SunOS / SVR4) dynamic loading module.
-# This is not needed for dynamic loading of Python modules --
-# it is a highly experimental and dangerous device for calling
-# *arbitrary* C functions in *arbitrary* shared libraries:
-
-#dl dlmodule.c
-
-
-# Modules that provide persistent dictionary-like semantics. You will
-# probably want to arrange for at least one of them to be available on
-# your machine, though none are defined by default because of library
-# dependencies. The Python module anydbm.py provides an
-# implementation independent wrapper for these; dumbdbm.py provides
-# similar functionality (but slower of course) implemented in Python.
-
-# The standard Unix dbm module has been moved to Setup.config so that
-# it will be compiled as a shared library by default. Compiling it as
-# a built-in module causes conflicts with the pybsddb3 module since it
-# creates a static dependency on an out-of-date version of db.so.
-#
-# First, look at Setup.config; configure may have set this for you.
-
-#dbm dbmmodule.c # dbm(3) may require -lndbm or similar
-
-# Anthony Baxter's gdbm module. GNU dbm(3) will require -lgdbm:
-#
-# First, look at Setup.config; configure may have set this for you.
-
-#gdbm gdbmmodule.c -I/usr/local/include -L/usr/local/lib -lgdbm
-
-
-# Sleepycat Berkeley DB interface.
-#
-# This requires the Sleepycat DB code, see http://www.sleepycat.com/
-# The earliest supported version of that library is 3.0, the latest
-# supported version is 4.0 (4.1 is specifically not supported, as that
-# changes the semantics of transactional databases). A list of available
-# releases can be found at
-#
-# http://www.sleepycat.com/update/index.html
-#
-# Edit the variables DB and DBLIBVERto point to the db top directory
-# and the subdirectory of PORT where you built it.
-#DB=/usr/local/BerkeleyDB.4.0
-#DBLIBVER=4.0
-#DBINC=$(DB)/include
-#DBLIB=$(DB)/lib
-#_bsddb _bsddb.c -I$(DBINC) -L$(DBLIB) -ldb-$(DBLIBVER)
-
-# Historical Berkeley DB 1.85
-#
-# This module is deprecated; the 1.85 version of the Berkeley DB library has
-# bugs that can cause data corruption. If you can, use later versions of the
-# library instead, available from <http://www.sleepycat.com/>.
-
-#DB=/depot/sundry/src/berkeley-db/db.1.85
-#DBPORT=$(DB)/PORT/irix.5.3
-#bsddb185 bsddbmodule.c -I$(DBPORT)/include -I$(DBPORT) $(DBPORT)/libdb.a
-
-
-
-# Helper module for various ascii-encoders
-binascii binascii.c
-
-# Fred Drake's interface to the Python parser
-parser parsermodule.c
-
-# cStringIO and cPickle
-cStringIO cStringIO.c
-cPickle cPickle.c
-
-
-# Lee Busby's SIGFPE modules.
-# The library to link fpectl with is platform specific.
-# Choose *one* of the options below for fpectl:
-
-# For SGI IRIX (tested on 5.3):
-#fpectl fpectlmodule.c -lfpe
-
-# For Solaris with SunPro compiler (tested on Solaris 2.5 with SunPro C 4.2):
-# (Without the compiler you don't have -lsunmath.)
-#fpectl fpectlmodule.c -R/opt/SUNWspro/lib -lsunmath -lm
-
-# For other systems: see instructions in fpectlmodule.c.
-#fpectl fpectlmodule.c ...
-
-# Test module for fpectl. No extra libraries needed.
-#fpetest fpetestmodule.c
-
-# Andrew Kuchling's zlib module.
-# This require zlib 1.1.3 (or later).
-# See http://www.gzip.org/zlib/
-zlib zlibmodule.c -I$(prefix)/include -L$(exec_prefix)/lib -lz
-
-# Interface to the Expat XML parser
-#
-# Expat was written by James Clark and is now maintained by a group of
-# developers on SourceForge; see www.libexpat.org for more
-# information. The pyexpat module was written by Paul Prescod after a
-# prototype by Jack Jansen. Source of Expat 1.95.2 is included in
-# Modules/expat/. Usage of a system shared libexpat.so/expat.dll is
-# not advised.
-#
-# More information on Expat can be found at www.libexpat.org.
-#
-pyexpat expat/xmlparse.c expat/xmlrole.c expat/xmltok.c pyexpat.c -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI
-
-
-# Hye-Shik Chang's CJKCodecs
-
-# multibytecodec is required for all the other CJK codec modules
-#_multibytecodec cjkcodecs/multibytecodec.c
-
-#_codecs_cn cjkcodecs/_codecs_cn.c
-#_codecs_hk cjkcodecs/_codecs_hk.c
-#_codecs_iso2022 cjkcodecs/_codecs_iso2022.c
-#_codecs_jp cjkcodecs/_codecs_jp.c
-#_codecs_kr cjkcodecs/_codecs_kr.c
-#_codecs_tw cjkcodecs/_codecs_tw.c
-
-# Example -- included for reference only:
-# xx xxmodule.c
-
-# Another example -- the 'xxsubtype' module shows C-level subtyping in action
-xxsubtype xxsubtype.c
diff --git a/pythonforandroid/recipes/hostpython2/__init__.py b/pythonforandroid/recipes/hostpython2/__init__.py
deleted file mode 100644
index 5a5b362f5..000000000
--- a/pythonforandroid/recipes/hostpython2/__init__.py
+++ /dev/null
@@ -1,62 +0,0 @@
-
-from pythonforandroid.toolchain import Recipe, shprint, current_directory, info, warning
-from os.path import join, exists
-from os import chdir
-import os
-import sh
-
-
-class Hostpython2Recipe(Recipe):
- version = '2.7.2'
- url = 'https://python.org/ftp/python/{version}/Python-{version}.tar.bz2'
- name = 'hostpython2'
-
- conflicts = ['hostpython3']
-
- def get_build_container_dir(self, arch=None):
- choices = self.check_recipe_choices()
- dir_name = '-'.join([self.name] + choices)
- return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop')
-
- def get_build_dir(self, arch=None):
- return join(self.get_build_container_dir(), self.name)
-
- def prebuild_arch(self, arch):
- # Override hostpython Setup?
- shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'),
- join(self.get_build_dir(), 'Modules', 'Setup'))
-
- def build_arch(self, arch):
- with current_directory(self.get_build_dir()):
-
- if exists('hostpython'):
- info('hostpython already exists, skipping build')
- self.ctx.hostpython = join(self.get_build_dir(),
- 'hostpython')
- self.ctx.hostpgen = join(self.get_build_dir(),
- 'hostpgen')
- return
-
- if 'LIBS' in os.environ:
- os.environ.pop('LIBS')
- configure = sh.Command('./configure')
-
- shprint(configure)
- shprint(sh.make, '-j5')
-
- shprint(sh.mv, join('Parser', 'pgen'), 'hostpgen')
-
- if exists('python.exe'):
- shprint(sh.mv, 'python.exe', 'hostpython')
- elif exists('python'):
- shprint(sh.mv, 'python', 'hostpython')
- else:
- warning('Unable to find the python executable after '
- 'hostpython build! Exiting.')
- exit(1)
-
- self.ctx.hostpython = join(self.get_build_dir(), 'hostpython')
- self.ctx.hostpgen = join(self.get_build_dir(), 'hostpgen')
-
-
-recipe = Hostpython2Recipe()
diff --git a/pythonforandroid/recipes/hostpython3/__init__.py b/pythonforandroid/recipes/hostpython3/__init__.py
new file mode 100644
index 000000000..9ba458001
--- /dev/null
+++ b/pythonforandroid/recipes/hostpython3/__init__.py
@@ -0,0 +1,144 @@
+import sh
+import os
+
+from multiprocessing import cpu_count
+from pathlib import Path
+from os.path import join
+
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import (
+ BuildInterruptingException,
+ current_directory,
+ ensure_dir,
+)
+from pythonforandroid.prerequisites import OpenSSLPrerequisite
+
+HOSTPYTHON_VERSION_UNSET_MESSAGE = (
+ 'The hostpython recipe must have set version'
+)
+
+SETUP_DIST_NOT_FIND_MESSAGE = (
+ 'Could not find Setup.dist or Setup in Python build'
+)
+
+
+class HostPython3Recipe(Recipe):
+ '''
+ The hostpython3's recipe.
+
+ .. versionchanged:: 2019.10.06.post0
+ Refactored from deleted class ``python.HostPythonRecipe`` into here.
+
+ .. versionchanged:: 0.6.0
+ Refactored into the new class
+ :class:`~pythonforandroid.python.HostPythonRecipe`
+ '''
+
+ version = '3.11.5'
+ name = 'hostpython3'
+
+ build_subdir = 'native-build'
+ '''Specify the sub build directory for the hostpython3 recipe. Defaults
+ to ``native-build``.'''
+
+ url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz'
+ '''The default url to download our host python recipe. This url will
+ change depending on the python version set in attribute :attr:`version`.'''
+
+ patches = ['patches/pyconfig_detection.patch']
+
+ @property
+ def _exe_name(self):
+ '''
+ Returns the name of the python executable depending on the version.
+ '''
+ if not self.version:
+ raise BuildInterruptingException(HOSTPYTHON_VERSION_UNSET_MESSAGE)
+ return f'python{self.version.split(".")[0]}'
+
+ @property
+ def python_exe(self):
+ '''Returns the full path of the hostpython executable.'''
+ return join(self.get_path_to_python(), self._exe_name)
+
+ def get_recipe_env(self, arch=None):
+ env = os.environ.copy()
+ openssl_prereq = OpenSSLPrerequisite()
+ if env.get("PKG_CONFIG_PATH", ""):
+ env["PKG_CONFIG_PATH"] = os.pathsep.join(
+ [openssl_prereq.pkg_config_location, env["PKG_CONFIG_PATH"]]
+ )
+ else:
+ env["PKG_CONFIG_PATH"] = openssl_prereq.pkg_config_location
+ return env
+
+ def should_build(self, arch):
+ if Path(self.python_exe).exists():
+ # no need to build, but we must set hostpython for our Context
+ self.ctx.hostpython = self.python_exe
+ return False
+ return True
+
+ def get_build_container_dir(self, arch=None):
+ choices = self.check_recipe_choices()
+ dir_name = '-'.join([self.name] + choices)
+ return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop')
+
+ def get_build_dir(self, arch=None):
+ '''
+ .. note:: Unlike other recipes, the hostpython build dir doesn't
+ depend on the target arch
+ '''
+ return join(self.get_build_container_dir(), self.name)
+
+ def get_path_to_python(self):
+ return join(self.get_build_dir(), self.build_subdir)
+
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+
+ recipe_build_dir = self.get_build_dir(arch.arch)
+
+ # Create a subdirectory to actually perform the build
+ build_dir = join(recipe_build_dir, self.build_subdir)
+ ensure_dir(build_dir)
+
+ # Configure the build
+ with current_directory(build_dir):
+ if not Path('config.status').exists():
+ shprint(sh.Command(join(recipe_build_dir, 'configure')), _env=env)
+
+ with current_directory(recipe_build_dir):
+ # Create the Setup file. This copying from Setup.dist is
+ # the normal and expected procedure before Python 3.8, but
+ # after this the file with default options is already named "Setup"
+ setup_dist_location = join('Modules', 'Setup.dist')
+ if Path(setup_dist_location).exists():
+ shprint(sh.cp, setup_dist_location,
+ join(build_dir, 'Modules', 'Setup'))
+ else:
+ # Check the expected file does exist
+ setup_location = join('Modules', 'Setup')
+ if not Path(setup_location).exists():
+ raise BuildInterruptingException(
+ SETUP_DIST_NOT_FIND_MESSAGE
+ )
+
+ shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir, _env=env)
+
+ # make a copy of the python executable giving it the name we want,
+ # because we got different python's executable names depending on
+ # the fs being case-insensitive (Mac OS X, Cygwin...) or
+ # case-sensitive (linux)...so this way we will have an unique name
+ # for our hostpython, regarding the used fs
+ for exe_name in ['python.exe', 'python']:
+ exe = join(self.get_path_to_python(), exe_name)
+ if Path(exe).is_file():
+ shprint(sh.cp, exe, self.python_exe)
+ break
+
+ self.ctx.hostpython = self.python_exe
+
+
+recipe = HostPython3Recipe()
diff --git a/pythonforandroid/recipes/hostpython3/patches/pyconfig_detection.patch b/pythonforandroid/recipes/hostpython3/patches/pyconfig_detection.patch
new file mode 100644
index 000000000..7f78b664e
--- /dev/null
+++ b/pythonforandroid/recipes/hostpython3/patches/pyconfig_detection.patch
@@ -0,0 +1,13 @@
+diff -Nru Python-3.8.2/Lib/site.py Python-3.8.2-new/Lib/site.py
+--- Python-3.8.2/Lib/site.py 2020-04-28 12:48:38.000000000 -0700
++++ Python-3.8.2-new/Lib/site.py 2020-04-28 12:52:46.000000000 -0700
+@@ -487,7 +487,8 @@
+ if key == 'include-system-site-packages':
+ system_site = value.lower()
+ elif key == 'home':
+- sys._home = value
++ # this is breaking pyconfig.h path detection with venv
++ print('Ignoring "sys._home = value" override', file=sys.stderr)
+
+ sys.prefix = sys.exec_prefix = site_prefix
+
diff --git a/pythonforandroid/recipes/hostpython3crystax/__init__.py b/pythonforandroid/recipes/hostpython3crystax/__init__.py
deleted file mode 100644
index a629a8847..000000000
--- a/pythonforandroid/recipes/hostpython3crystax/__init__.py
+++ /dev/null
@@ -1,26 +0,0 @@
-
-from pythonforandroid.toolchain import Recipe, shprint, current_directory, info, warning
-from os.path import join, exists
-from os import chdir
-import sh
-
-
-class Hostpython3Recipe(Recipe):
- version = '3.5'
- # url = 'http://python.org/ftp/python/{version}/Python-{version}.tgz'
- # url = 'https://github.com/crystax/android-vendor-python-3-5/archive/master.zip'
- name = 'hostpython3crystax'
-
- conflicts = ['hostpython2']
-
- # def prebuild_armeabi(self):
- # # Override hostpython Setup?
- # shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'),
- # join(self.get_build_dir('armeabi'), 'Modules', 'Setup'))
-
- def build_arch(self, arch):
- self.ctx.hostpython = '/usr/bin/false'
- self.ctx.hostpgen = '/usr/bin/false'
-
-
-recipe = Hostpython3Recipe()
diff --git a/pythonforandroid/recipes/icu/__init__.py b/pythonforandroid/recipes/icu/__init__.py
index a3a5bfea5..232939ba9 100644
--- a/pythonforandroid/recipes/icu/__init__.py
+++ b/pythonforandroid/recipes/icu/__init__.py
@@ -1,33 +1,57 @@
import sh
import os
-from os.path import join, isdir
-from pythonforandroid.recipe import NDKRecipe
-from pythonforandroid.toolchain import shprint, info
+import platform
+from os.path import join, isdir, exists
+from multiprocessing import cpu_count
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.toolchain import shprint
from pythonforandroid.util import current_directory, ensure_dir
-class ICURecipe(NDKRecipe):
+class ICURecipe(Recipe):
name = 'icu4c'
version = '57.1'
- url = 'http://download.icu-project.org/files/icu4c/57.1/icu4c-57_1-src.tgz'
-
- depends = [('python2', 'python3crystax'), 'hostpython2'] # installs in python
- generated_libraries = [
- 'libicui18n.so', 'libicuuc.so', 'libicudata.so', 'libicule.so']
-
- def get_lib_dir(self, arch):
- lib_dir = join(self.ctx.get_python_install_dir(), "lib")
- ensure_dir(lib_dir)
- return lib_dir
-
- def prepare_build_dir(self, arch):
- if self.ctx.android_api > 19:
- # greater versions do not have /usr/include/sys/exec_elf.h
- raise RuntimeError("icu needs an android api <= 19")
-
- super(ICURecipe, self).prepare_build_dir(arch)
-
- def build_arch(self, arch, *extra_args):
+ major_version = version.split('.')[0]
+ url = (
+ "https://github.com/unicode-org/icu/releases/download/"
+ "release-{version_hyphen}/icu4c-{version_underscore}-src.tgz"
+ )
+
+ depends = ['hostpython3'] # installs in python
+ patches = ['disable-libs-version.patch']
+
+ built_libraries = {
+ 'libicui18n{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicuuc{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicudata{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicule{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicuio{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libicutu{}.so'.format(major_version): 'build_icu_android/lib',
+ 'libiculx{}.so'.format(major_version): 'build_icu_android/lib',
+ }
+
+ @property
+ def versioned_url(self):
+ if self.url is None:
+ return None
+ return self.url.format(
+ version=self.version,
+ version_underscore=self.version.replace('.', '_'),
+ version_hyphen=self.version.replace('.', '-'))
+
+ def get_recipe_dir(self):
+ """
+ .. note:: We need to overwrite `Recipe.get_recipe_dir` due to the
+ mismatch name between the recipe's folder (icu) and the value
+ of `ICURecipe.name` (icu4c).
+ """
+ if self.ctx.local_recipes is not None:
+ local_recipe_dir = join(self.ctx.local_recipes, 'icu')
+ if exists(local_recipe_dir):
+ return local_recipe_dir
+ return join(self.ctx.root_dir, 'recipes', 'icu')
+
+ def build_arch(self, arch):
env = self.get_recipe_env(arch).copy()
build_root = self.get_build_dir(arch.arch)
@@ -40,7 +64,7 @@ def make_build_dest(dest):
return build_dest, True
icu_build = join(build_root, "icu_build")
- build_linux, exists = make_build_dest("build_icu_linux")
+ build_host, exists = make_build_dest("build_icu_host")
host_env = os.environ.copy()
# reduce the function set
@@ -51,102 +75,53 @@ def make_build_dest(dest):
"-DUCONFIG_NO_TRANSLITERATION=0 ")
if not exists:
+ icu4c_host_platform = platform.system()
+ if icu4c_host_platform == "Darwin":
+ icu4c_host_platform = "MacOSX"
configure = sh.Command(
join(build_root, "source", "runConfigureICU"))
- with current_directory(build_linux):
+ with current_directory(build_host):
shprint(
configure,
- "Linux",
+ icu4c_host_platform,
"--prefix="+icu_build,
"--enable-extras=no",
"--enable-strict=no",
- "--enable-static",
+ "--enable-static=no",
"--enable-tests=no",
"--enable-samples=no",
_env=host_env)
- shprint(sh.make, "-j5", _env=host_env)
+ shprint(sh.make, "-j", str(cpu_count()), _env=host_env)
shprint(sh.make, "install", _env=host_env)
-
build_android, exists = make_build_dest("build_icu_android")
if not exists:
-
configure = sh.Command(join(build_root, "source", "configure"))
- include = (
- " -I{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/include/"
- " -I{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/"
- "{arch}/include")
- include = include.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- env["CPPFLAGS"] = env["CXXFLAGS"] + " "
- env["CPPFLAGS"] += host_env["CPPFLAGS"]
- env["CPPFLAGS"] += include
-
- lib = "{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/{arch}"
- lib = lib.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- env["LDFLAGS"] += " -lgnustl_shared -L"+lib
-
- env.pop("CFLAGS", None)
- env.pop("CXXFLAGS", None)
-
with current_directory(build_android):
shprint(
configure,
- "--with-cross-build="+build_linux,
+ "--with-cross-build="+build_host,
"--enable-extras=no",
"--enable-strict=no",
- "--enable-static",
+ "--enable-static=no",
"--enable-tests=no",
"--enable-samples=no",
- "--host="+env["TOOLCHAIN_PREFIX"],
+ "--host="+arch.command_prefix,
"--prefix="+icu_build,
_env=env)
- shprint(sh.make, "-j5", _env=env)
+ shprint(sh.make, "-j", str(cpu_count()), _env=env)
shprint(sh.make, "install", _env=env)
- self.copy_files(arch)
-
- def copy_files(self, arch):
- env = self.get_recipe_env(arch)
-
- lib = "{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/{arch}"
- lib = lib.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- stl_lib = join(lib, "libgnustl_shared.so")
- dst_dir = join(self.ctx.get_site_packages_dir(), "..", "lib-dynload")
- shprint(sh.cp, stl_lib, dst_dir)
-
- src_lib = join(self.get_build_dir(arch.arch), "icu_build", "lib")
- dst_lib = self.get_lib_dir(arch)
-
- src_suffix = "." + self.version
- dst_suffix = "." + self.version.split(".")[0] # main version
- for lib in self.generated_libraries:
- shprint(sh.cp, join(src_lib, lib+src_suffix),
- join(dst_lib, lib+dst_suffix))
+ def install_libraries(self, arch):
+ super().install_libraries(arch)
src_include = join(
self.get_build_dir(arch.arch), "icu_build", "include")
dst_include = join(
- self.ctx.get_python_install_dir(), "include", "icu")
+ self.ctx.get_python_install_dir(arch.arch), "include", "icu")
ensure_dir(dst_include)
shprint(sh.cp, "-r", join(src_include, "layout"), dst_include)
shprint(sh.cp, "-r", join(src_include, "unicode"), dst_include)
- # copy stl library
- lib = "{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/{arch}"
- lib = lib.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- stl_lib = join(lib, "libgnustl_shared.so")
-
- dst_dir = join(self.ctx.get_python_install_dir(), "lib")
- ensure_dir(dst_dir)
- shprint(sh.cp, stl_lib, dst_dir)
-
recipe = ICURecipe()
diff --git a/pythonforandroid/recipes/icu/disable-libs-version.patch b/pythonforandroid/recipes/icu/disable-libs-version.patch
new file mode 100644
index 000000000..872abe01e
--- /dev/null
+++ b/pythonforandroid/recipes/icu/disable-libs-version.patch
@@ -0,0 +1,66 @@
+diff -aur icu4c-org/source/config/Makefile.inc.in icu4c/source/config/Makefile.inc.in
+--- icu/source/config/Makefile.inc.in.orig 2016-03-23 21:50:50.000000000 +0100
++++ icu/source/config/Makefile.inc.in 2019-02-15 17:59:28.331749766 +0100
+@@ -142,8 +142,8 @@
+ LDLIBRARYPATH_ENVVAR = LD_LIBRARY_PATH
+
+ # Versioned target for a shared library
+-FINAL_SO_TARGET = $(SO_TARGET).$(SO_TARGET_VERSION)
+-MIDDLE_SO_TARGET = $(SO_TARGET).$(SO_TARGET_VERSION_MAJOR)
++FINAL_SO_TARGET = $(SO_TARGET).$(SO_TARGET_VERSION)
++MIDDLE_SO_TARGET = $(SO_TARGET)
+
+ # Access to important ICU tools.
+ # Use as follows: $(INVOKE) $(GENRB) arguments ..
+diff -aur icu4c-org/source/config/mh-linux icu4c/source/config/mh-linux
+--- icu4c-org/source/config/mh-linux 2017-07-05 13:23:06.000000000 +0200
++++ icu4c/source/config/mh-linux 2017-07-06 14:02:52.275016858 +0200
+@@ -24,9 +24,17 @@
+
+ ## Compiler switch to embed a library name
+ # The initial tab in the next line is to prevent icu-config from reading it.
+- LD_SONAME = -Wl,-soname -Wl,$(notdir $(MIDDLE_SO_TARGET))
++ LD_SONAME = -Wl,-soname -Wl,$(notdir $(SO_TARGET))
++ DATA_STUBNAME = data$(SO_TARGET_VERSION_MAJOR)
++ COMMON_STUBNAME = uc$(SO_TARGET_VERSION_MAJOR)
++ I18N_STUBNAME = i18n$(SO_TARGET_VERSION_MAJOR)
++ LAYOUT_STUBNAME = le$(SO_TARGET_VERSION_MAJOR)
++ LAYOUTEX_STUBNAME = lx$(SO_TARGET_VERSION_MAJOR)
++ IO_STUBNAME = io$(SO_TARGET_VERSION_MAJOR)
++ TOOLUTIL_STUBNAME = tu$(SO_TARGET_VERSION_MAJOR)
++ CTESTFW_STUBNAME = test$(SO_TARGET_VERSION_MAJOR)
+ #SH# # We can't depend on MIDDLE_SO_TARGET being set.
+-#SH# LD_SONAME=
++#SH# LD_SONAME=$(SO_TARGET)
+
+ ## Shared library options
+ LD_SOOPTIONS= -Wl,-Bsymbolic
+@@ -64,10 +64,10 @@
+
+ ## Versioned libraries rules
+
+-%.$(SO).$(SO_TARGET_VERSION_MAJOR): %.$(SO).$(SO_TARGET_VERSION)
+- $(RM) $@ && ln -s ${<F} $@
+-%.$(SO): %.$(SO).$(SO_TARGET_VERSION_MAJOR)
+- $(RM) $@ && ln -s ${*F}.$(SO).$(SO_TARGET_VERSION) $@
++%.$(SO).$(SO_TARGET_VERSION_MAJOR): %.$(SO).$(SO_TARGET_VERSION)
++ $(RM) $@ && ln -s ${<F} $@
++%.$(SO): %.$(SO).$(SO_TARGET_VERSION_MAJOR)
++ $(RM) $@ && ln -s ${*F}.$(SO).$(SO_TARGET_VERSION) $@
+
+ ## Bind internal references
+
+diff -aur icu4c-org/source/data/Makefile.in icu4c/source/data/Makefile.in
+--- icu4c-org/source/data/Makefile.in 2017-07-05 13:23:06.000000000 +0200
++++ icu4c/source/data/Makefile.in 2017-07-06 14:05:31.607995855 +0200
+@@ -24,9 +24,9 @@
+ ifeq ($(PKGDATA_OPTS),)
+ PKGDATA_OPTS = -O $(top_builddir)/data/icupkg.inc
+ endif
+-ifeq ($(PKGDATA_VERSIONING),)
+-PKGDATA_VERSIONING = -r $(SO_TARGET_VERSION)
+-endif
++#ifeq ($(PKGDATA_VERSIONING),)
++#PKGDATA_VERSIONING = -r $(SO_TARGET_VERSION)
++#endif
+
diff --git a/pythonforandroid/recipes/idna/__init__.py b/pythonforandroid/recipes/idna/__init__.py
deleted file mode 100644
index 7d534f4f4..000000000
--- a/pythonforandroid/recipes/idna/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class IdnaRecipe(PythonRecipe):
- name = 'idna'
- version = '2.0'
- url = 'https://pypi.python.org/packages/source/i/idna/idna-{version}.tar.gz'
-
- depends = [('python2', 'python3crystax'), 'setuptools']
-
- call_hostpython_via_targetpython = False
-
-
-recipe = IdnaRecipe()
diff --git a/pythonforandroid/recipes/ifaddr/__init__.py b/pythonforandroid/recipes/ifaddr/__init__.py
new file mode 100644
index 000000000..9409e76d1
--- /dev/null
+++ b/pythonforandroid/recipes/ifaddr/__init__.py
@@ -0,0 +1,13 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class IfaddrRecipe(PythonRecipe):
+ name = 'ifaddr'
+ version = '0.1.7'
+ url = 'https://pypi.python.org/packages/source/i/ifaddr/ifaddr-{version}.tar.gz'
+ depends = ['setuptools', 'ifaddrs', 'ipaddress;python_version<"3.3"']
+ call_hostpython_via_targetpython = False
+ patches = ["getifaddrs.patch"]
+
+
+recipe = IfaddrRecipe()
diff --git a/pythonforandroid/recipes/ifaddr/getifaddrs.patch b/pythonforandroid/recipes/ifaddr/getifaddrs.patch
new file mode 100644
index 000000000..8b0be9d85
--- /dev/null
+++ b/pythonforandroid/recipes/ifaddr/getifaddrs.patch
@@ -0,0 +1,15 @@
+diff --git a/ifaddr/_posix.py b/ifaddr/_posix.py
+index 2903ee7..546e3ce 100644
+--- a/ifaddr/_posix.py
++++ b/ifaddr/_posix.py
+@@ -39,6 +39,10 @@ ifaddrs._fields_ = [('ifa_next', ctypes.POINTER(ifaddrs)),
+
+ libc = ctypes.CDLL(ctypes.util.find_library("socket" if os.uname()[0] == "SunOS" else "c"), use_errno=True)
+
++# On old Androids getifaddrs is not available in libc => use libifaddrs instead
++if not hasattr(libc, 'getifaddrs'):
++ libc = ctypes.CDLL(ctypes.util.find_library('ifaddrs'), use_errno=True)
++
+ def get_adapters():
+
+ addr0 = addr = ctypes.POINTER(ifaddrs)()
diff --git a/pythonforandroid/recipes/ifaddrs/__init__.py b/pythonforandroid/recipes/ifaddrs/__init__.py
index d58fd604c..1317dc255 100644
--- a/pythonforandroid/recipes/ifaddrs/__init__.py
+++ b/pythonforandroid/recipes/ifaddrs/__init__.py
@@ -1,68 +1,53 @@
""" ifaddrs for Android
"""
-from os.path import join, exists
+from os.path import join
+
import sh
-from pythonforandroid.logger import info, shprint
-from pythonforandroid.toolchain import (CompiledComponentsPythonRecipe,
- current_directory)
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.toolchain import current_directory
+from pythonforandroid.util import ensure_dir
+
class IFAddrRecipe(CompiledComponentsPythonRecipe):
- version = 'master'
- url = 'git+https://github.com/morristech/android-ifaddrs.git'
- depends = [('hostpython2', 'hostpython3'), ('python2', 'python3crystax')]
+ version = '8f9a87c'
+ url = 'https://github.com/morristech/android-ifaddrs/archive/{version}.zip'
+ depends = ['hostpython3']
call_hostpython_via_targetpython = False
site_packages_name = 'ifaddrs'
generated_libraries = ['libifaddrs.so']
- def should_build(self, arch):
- """It's faster to build than check"""
- return not (exists(join(self.ctx.libs_dir, arch.arch, 'libifaddrs.so'))
- and exists(join(self.ctx.get_python_install_dir(), 'lib'
- "libifaddrs.so"))
- )
-
def prebuild_arch(self, arch):
"""Make the build and target directories"""
path = self.get_build_dir(arch.arch)
- if not exists(path):
- info("creating {}".format(path))
- shprint(sh.mkdir, '-p', path)
+ ensure_dir(path)
def build_arch(self, arch):
"""simple shared compile"""
env = self.get_recipe_env(arch, with_flags_in_cc=False)
- for path in (self.get_build_dir(arch.arch),
- join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Lib'),
- join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include'),
- ):
- if not exists(path):
- info("creating {}".format(path))
- shprint(sh.mkdir, '-p', path)
- cli = env['CC'].split()
- cc = sh.Command(cli[0])
+ for path in (
+ self.get_build_dir(arch.arch),
+ join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Lib'),
+ join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')):
+ ensure_dir(path)
+ cli = env['CC'].split()[0]
+ # makes sure first CC command is the compiler rather than ccache, refs:
+ # https://github.com/kivy/python-for-android/issues/1398
+ if 'ccache' in cli:
+ cli = env['CC'].split()[1]
+ cc = sh.Command(cli)
with current_directory(self.get_build_dir(arch.arch)):
cflags = env['CFLAGS'].split()
cflags.extend(['-I.', '-c', '-l.', 'ifaddrs.c', '-I.'])
shprint(cc, *cflags, _env=env)
-
cflags = env['CFLAGS'].split()
cflags.extend(['-shared', '-I.', 'ifaddrs.o', '-o', 'libifaddrs.so'])
cflags.extend(env['LDFLAGS'].split())
shprint(cc, *cflags, _env=env)
-
shprint(sh.cp, 'libifaddrs.so', self.ctx.get_libs_dir(arch.arch))
- shprint(sh.cp, "libifaddrs.so", join(self.ctx.get_python_install_dir(), 'lib'))
- # drop header in to the Python include directory
- shprint(sh.cp, "ifaddrs.h", join(self.ctx.get_python_install_dir(),
- 'include/python{}'.format(
- self.ctx.python_recipe.version[0:3]
- )
- )
- )
- include_path = join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')
- shprint(sh.cp, "ifaddrs.h", include_path)
+
recipe = IFAddrRecipe()
diff --git a/pythonforandroid/recipes/ipaddress/__init__.py b/pythonforandroid/recipes/ipaddress/__init__.py
deleted file mode 100644
index 16a468cdc..000000000
--- a/pythonforandroid/recipes/ipaddress/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class IpaddressRecipe(PythonRecipe):
- name = 'ipaddress'
- version = '1.0.16'
- url = 'https://pypi.python.org/packages/source/i/ipaddress/ipaddress-{version}.tar.gz'
-
- depends = ['python2']
-
-
-recipe = IpaddressRecipe()
diff --git a/pythonforandroid/recipes/jedi/__init__.py b/pythonforandroid/recipes/jedi/__init__.py
index ea076837e..17168e85a 100644
--- a/pythonforandroid/recipes/jedi/__init__.py
+++ b/pythonforandroid/recipes/jedi/__init__.py
@@ -1,14 +1,10 @@
-
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
class JediRecipe(PythonRecipe):
- # version = 'master'
version = 'v0.9.0'
url = 'https://github.com/davidhalter/jedi/archive/{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
-
patches = ['fix_MergedNamesDict_get.patch']
# This apparently should be fixed in jedi 0.10 (not released to
# pypi yet), but it still occurs on Android, I could not reproduce
diff --git a/pythonforandroid/recipes/jpeg/Application.mk b/pythonforandroid/recipes/jpeg/Application.mk
index 608b6db88..5942a0344 100644
--- a/pythonforandroid/recipes/jpeg/Application.mk
+++ b/pythonforandroid/recipes/jpeg/Application.mk
@@ -1,3 +1,4 @@
APP_OPTIM := release
APP_ABI := all # or armeabi
APP_MODULES := libjpeg
+APP_ALLOW_MISSING_DEPS := true
diff --git a/pythonforandroid/recipes/jpeg/__init__.py b/pythonforandroid/recipes/jpeg/__init__.py
index 052a735f3..a81b82555 100644
--- a/pythonforandroid/recipes/jpeg/__init__.py
+++ b/pythonforandroid/recipes/jpeg/__init__.py
@@ -1,35 +1,55 @@
-from pythonforandroid.recipe import NDKRecipe
+from pythonforandroid.recipe import Recipe
from pythonforandroid.logger import shprint
from pythonforandroid.util import current_directory
-from os.path import join, exists
+from os.path import join
import sh
-class JpegRecipe(NDKRecipe):
- name = 'jpeg'
- version = 'linaro-android'
- url = 'git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git'
-
- patches = ['build-static.patch']
-
- generated_libraries = ['libjpeg.a']
-
- def prebuild_arch(self, arch):
- super(JpegRecipe, self).prebuild_arch(arch)
-
- build_dir = self.get_build_dir(arch.arch)
- app_mk = join(build_dir, 'Application.mk')
- if not exists(app_mk):
- shprint(sh.cp, join(self.get_recipe_dir(), 'Application.mk'), app_mk)
- jni_ln = join(build_dir, 'jni')
- if not exists(jni_ln):
- shprint(sh.ln, '-s', build_dir, jni_ln)
-
- def build_arch(self, arch):
- super(JpegRecipe, self).build_arch(arch)
- with current_directory(self.get_lib_dir(arch)):
- shprint(sh.mv, 'libjpeg.a', 'libjpeg-orig.a')
- shprint(sh.ar, '-rcT', 'libjpeg.a', 'libjpeg-orig.a', 'libsimd.a')
+class JpegRecipe(Recipe):
+ '''
+ .. versionchanged:: 0.6.0
+ rewrote recipe to be build with clang and updated libraries to latest
+ version of the official git repo.
+ '''
+ name = 'jpeg'
+ version = '2.0.1'
+ url = 'https://github.com/libjpeg-turbo/libjpeg-turbo/archive/{version}.tar.gz' # noqa
+ built_libraries = {'libjpeg.a': '.', 'libturbojpeg.a': '.'}
+ # we will require this below patch to build the shared library
+ # patches = ['remove-version.patch']
+
+ def build_arch(self, arch):
+ build_dir = self.get_build_dir(arch.arch)
+
+ # TODO: Fix simd/neon
+ with current_directory(build_dir):
+ env = self.get_recipe_env(arch)
+ toolchain_file = join(self.ctx.ndk_dir,
+ 'build/cmake/android.toolchain.cmake')
+
+ shprint(sh.rm, '-f', 'CMakeCache.txt', 'CMakeFiles/')
+ shprint(sh.cmake, '-G', 'Unix Makefiles',
+ '-DCMAKE_SYSTEM_NAME=Android',
+ '-DCMAKE_POSITION_INDEPENDENT_CODE=1',
+ '-DCMAKE_ANDROID_ARCH_ABI={arch}'.format(arch=arch.arch),
+ '-DCMAKE_ANDROID_NDK=' + self.ctx.ndk_dir,
+ '-DCMAKE_C_COMPILER={cc}'.format(cc=arch.get_clang_exe()),
+ '-DCMAKE_CXX_COMPILER={cc_plus}'.format(
+ cc_plus=arch.get_clang_exe(plus_plus=True)),
+ '-DCMAKE_BUILD_TYPE=Release',
+ '-DCMAKE_INSTALL_PREFIX=./install',
+ '-DCMAKE_TOOLCHAIN_FILE=' + toolchain_file,
+
+ '-DANDROID_ABI={arch}'.format(arch=arch.arch),
+ '-DANDROID_ARM_NEON=ON',
+ '-DENABLE_NEON=ON',
+ # '-DREQUIRE_SIMD=1',
+
+ # Force disable shared, with the static ones is enough
+ '-DENABLE_SHARED=0',
+ '-DENABLE_STATIC=1',
+ _env=env)
+ shprint(sh.make, _env=env)
recipe = JpegRecipe()
diff --git a/pythonforandroid/recipes/jpeg/remove-version.patch b/pythonforandroid/recipes/jpeg/remove-version.patch
new file mode 100644
index 000000000..311aa33bc
--- /dev/null
+++ b/pythonforandroid/recipes/jpeg/remove-version.patch
@@ -0,0 +1,12 @@
+--- jpeg/CMakeLists.txt.orig 2018-11-12 20:20:28.000000000 +0100
++++ jpeg/CMakeLists.txt 2018-12-14 12:43:45.338704504 +0100
+@@ -573,6 +573,9 @@
+ add_library(turbojpeg SHARED ${TURBOJPEG_SOURCES})
+ set_property(TARGET turbojpeg PROPERTY COMPILE_FLAGS
+ "-DBMP_SUPPORTED -DPPM_SUPPORTED")
++ set_property(TARGET jpeg PROPERTY NO_SONAME 1)
++ set_property(TARGET turbojpeg PROPERTY NO_SONAME 1)
++ set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "")
+ if(WIN32)
+ set_target_properties(turbojpeg PROPERTIES DEFINE_SYMBOL DLLDEFINE)
+ endif()
diff --git a/pythonforandroid/recipes/kivent_core/__init__.py b/pythonforandroid/recipes/kivent_core/__init__.py
deleted file mode 100644
index 044aca0e1..000000000
--- a/pythonforandroid/recipes/kivent_core/__init__.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from pythonforandroid.toolchain import CythonRecipe
-from os.path import join
-
-
-class KiventCoreRecipe(CythonRecipe):
- version = 'master'
- url = 'https://github.com/kivy/kivent/archive/{version}.zip'
- name = 'kivent_core'
-
- depends = ['kivy']
-
- subbuilddir = False
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(KiventCoreRecipe, self).get_recipe_env(
- arch, with_flags_in_cc=with_flags_in_cc)
- env['CYTHONPATH'] = self.get_recipe(
- 'kivy', self.ctx).get_build_dir(arch.arch)
- return env
-
- def get_build_dir(self, arch, sub=False):
- builddir = super(KiventCoreRecipe, self).get_build_dir(arch)
- if sub or self.subbuilddir:
- return join(builddir, 'modules', 'core')
- else:
- return builddir
-
- def build_arch(self, arch):
- self.subbuilddir = True
- super(KiventCoreRecipe, self).build_arch(arch)
- self.subbuilddir = False
-
-
-recipe = KiventCoreRecipe()
diff --git a/pythonforandroid/recipes/kivent_cymunk/__init__.py b/pythonforandroid/recipes/kivent_cymunk/__init__.py
deleted file mode 100644
index 38132546d..000000000
--- a/pythonforandroid/recipes/kivent_cymunk/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from pythonforandroid.toolchain import CythonRecipe
-from os.path import join
-
-
-class KiventCymunkRecipe(CythonRecipe):
- name = 'kivent_cymunk'
-
- depends = ['kivent_core', 'cymunk']
-
- subbuilddir = False
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(KiventCymunkRecipe, self).get_recipe_env(
- arch, with_flags_in_cc=with_flags_in_cc)
- cymunk = self.get_recipe('cymunk', self.ctx).get_build_dir(arch.arch)
- env['PYTHONPATH'] = join(cymunk, 'cymunk', 'python')
- kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch)
- kivent = self.get_recipe('kivent_core',
- self.ctx).get_build_dir(arch.arch, sub=True)
- env['CYTHONPATH'] = ':'.join((kivy, cymunk, kivent))
- return env
-
- def prepare_build_dir(self, arch):
- '''No need to prepare, we'll use kivent_core'''
- return
-
- def get_build_dir(self, arch):
- builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch)
- return join(builddir, 'modules', 'cymunk')
-
-
-recipe = KiventCymunkRecipe()
diff --git a/pythonforandroid/recipes/kivent_particles/__init__.py b/pythonforandroid/recipes/kivent_particles/__init__.py
deleted file mode 100644
index ce82ea0c5..000000000
--- a/pythonforandroid/recipes/kivent_particles/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from pythonforandroid.toolchain import CythonRecipe
-from os.path import join
-
-
-class KiventParticlesRecipe(CythonRecipe):
- name = 'kivent_particles'
-
- depends = ['kivent_core']
-
- subbuilddir = False
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(KiventParticlesRecipe, self).get_recipe_env(
- arch, with_flags_in_cc=with_flags_in_cc)
- kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch)
- kivent = self.get_recipe('kivent_core',
- self.ctx).get_build_dir(arch.arch, sub=True)
- env['CYTHONPATH'] = ':'.join((kivy, kivent))
- return env
-
- def prepare_build_dir(self, arch):
- '''No need to prepare, we'll use kivent_core'''
- return
-
- def get_build_dir(self, arch):
- builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch)
- return join(builddir, 'modules', 'particles')
-
-
-recipe = KiventParticlesRecipe()
diff --git a/pythonforandroid/recipes/kivent_polygen/__init__.py b/pythonforandroid/recipes/kivent_polygen/__init__.py
deleted file mode 100644
index 9cbd19814..000000000
--- a/pythonforandroid/recipes/kivent_polygen/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from pythonforandroid.toolchain import CythonRecipe
-from os.path import join
-
-
-class KiventPolygenRecipe(CythonRecipe):
- name = 'kivent_polygen'
-
- depends = ['kivent_core']
-
- subbuilddir = False
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(KiventPolygenRecipe, self).get_recipe_env(
- arch, with_flags_in_cc=with_flags_in_cc)
- kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch)
- kivent = self.get_recipe('kivent_core',
- self.ctx).get_build_dir(arch.arch, sub=True)
- env['CYTHONPATH'] = ':'.join((kivy, kivent))
- return env
-
- def prepare_build_dir(self, arch):
- '''No need to prepare, we'll use kivent_core'''
- return
-
- def get_build_dir(self, arch):
- builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch)
- return join(builddir, 'modules', 'polygen')
-
-
-recipe = KiventPolygenRecipe()
diff --git a/pythonforandroid/recipes/kivy/__init__.py b/pythonforandroid/recipes/kivy/__init__.py
index 53e3877a4..5cb56611e 100644
--- a/pythonforandroid/recipes/kivy/__init__.py
+++ b/pythonforandroid/recipes/kivy/__init__.py
@@ -1,21 +1,41 @@
+import glob
+from os.path import basename, exists, join
+import sys
+import packaging.version
-from pythonforandroid.toolchain import CythonRecipe, shprint, current_directory, ArchARM
-from os.path import exists, join, basename
import sh
-import glob
+from pythonforandroid.recipe import CythonRecipe
+from pythonforandroid.toolchain import current_directory, shprint
+
+
+def is_kivy_affected_by_deadlock_issue(recipe=None, arch=None):
+ with current_directory(join(recipe.get_build_dir(arch.arch), "kivy")):
+ kivy_version = shprint(
+ sh.Command(sys.executable),
+ "-c",
+ "import _version; print(_version.__version__)",
+ )
+
+ return packaging.version.parse(
+ str(kivy_version)
+ ) < packaging.version.Version("2.2.0.dev0")
class KivyRecipe(CythonRecipe):
- version = '1.10.0'
+ version = '2.3.0'
url = 'https://github.com/kivy/kivy/archive/{version}.zip'
name = 'kivy'
- depends = [('sdl2', 'pygame'), 'pyjnius']
+ depends = ['sdl2', 'pyjnius', 'setuptools']
+ python_depends = ['certifi', 'chardet', 'idna', 'requests', 'urllib3']
- # patches = ['setargv.patch']
+ # sdl-gl-swapwindow-nogil.patch is needed to avoid a deadlock.
+ # See: https://github.com/kivy/kivy/pull/8025
+ # WARNING: Remove this patch when a new Kivy version is released.
+ patches = [("sdl-gl-swapwindow-nogil.patch", is_kivy_affected_by_deadlock_issue)]
def cythonize_build(self, env, build_dir='.'):
- super(KivyRecipe, self).cythonize_build(env, build_dir=build_dir)
+ super().cythonize_build(env, build_dir=build_dir)
if not exists(join(build_dir, 'kivy', 'include')):
return
@@ -36,20 +56,25 @@ def cythonize_file(self, env, build_dir, filename):
do_not_cythonize = ['window_x11.pyx', ]
if basename(filename) in do_not_cythonize:
return
- super(KivyRecipe, self).cythonize_file(env, build_dir, filename)
+ super().cythonize_file(env, build_dir, filename)
def get_recipe_env(self, arch):
- env = super(KivyRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
+ # NDKPLATFORM is our switch for detecting Android platform, so can't be None
+ env['NDKPLATFORM'] = "NOTNONE"
if 'sdl2' in self.ctx.recipe_build_order:
env['USE_SDL2'] = '1'
env['KIVY_SPLIT_EXAMPLES'] = '1'
+ sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)
+ sdl2_image_recipe = self.get_recipe('sdl2_image', self.ctx)
env['KIVY_SDL2_PATH'] = ':'.join([
join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include'),
- join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_image'),
- join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_mixer'),
+ *sdl2_image_recipe.get_include_dirs(arch),
+ *sdl2_mixer_recipe.get_include_dirs(arch),
join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'),
- ])
+ ])
return env
+
recipe = KivyRecipe()
diff --git a/pythonforandroid/recipes/kivy/sdl-gl-swapwindow-nogil.patch b/pythonforandroid/recipes/kivy/sdl-gl-swapwindow-nogil.patch
new file mode 100644
index 000000000..8a7c33a8b
--- /dev/null
+++ b/pythonforandroid/recipes/kivy/sdl-gl-swapwindow-nogil.patch
@@ -0,0 +1,32 @@
+diff --git a/kivy/core/window/_window_sdl2.pyx b/kivy/core/window/_window_sdl2.pyx
+index 46e15ec63..5002cd0f9 100644
+--- a/kivy/core/window/_window_sdl2.pyx
++++ b/kivy/core/window/_window_sdl2.pyx
+@@ -746,7 +746,13 @@ cdef class _WindowSDL2Storage:
+ pass
+
+ def flip(self):
+- SDL_GL_SwapWindow(self.win)
++ # On Android (and potentially other platforms), SDL_GL_SwapWindow may
++ # lock the thread waiting for a mutex from another thread to be
++ # released. Calling SDL_GL_SwapWindow with the GIL released allow the
++ # other thread to run (e.g. to process the event filter callback) and
++ # release the mutex SDL_GL_SwapWindow is waiting for.
++ with nogil:
++ SDL_GL_SwapWindow(self.win)
+
+ def save_bytes_in_png(self, filename, data, int width, int height):
+ cdef SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(
+diff --git a/kivy/lib/sdl2.pxi b/kivy/lib/sdl2.pxi
+index 6a539de6d..3a5a69d23 100644
+--- a/kivy/lib/sdl2.pxi
++++ b/kivy/lib/sdl2.pxi
+@@ -627,7 +627,7 @@ cdef extern from "SDL.h":
+ cdef SDL_GLContext SDL_GL_GetCurrentContext()
+ cdef int SDL_GL_SetSwapInterval(int interval)
+ cdef int SDL_GL_GetSwapInterval()
+- cdef void SDL_GL_SwapWindow(SDL_Window * window)
++ cdef void SDL_GL_SwapWindow(SDL_Window * window) nogil
+ cdef void SDL_GL_DeleteContext(SDL_GLContext context)
+
+ cdef int SDL_NumJoysticks()
diff --git a/pythonforandroid/recipes/kivy3/__init__.py b/pythonforandroid/recipes/kivy3/__init__.py
new file mode 100644
index 000000000..6f27f62cc
--- /dev/null
+++ b/pythonforandroid/recipes/kivy3/__init__.py
@@ -0,0 +1,21 @@
+from pythonforandroid.recipe import PythonRecipe
+import shutil
+
+
+class Kivy3Recipe(PythonRecipe):
+ version = 'master'
+ url = 'https://github.com/kivy/kivy3/archive/{version}.zip'
+
+ depends = ['kivy']
+ site_packages_name = 'kivy3'
+
+ '''Due to setuptools.'''
+ call_hostpython_via_targetpython = False
+
+ def build_arch(self, arch):
+ super().build_arch(arch)
+ suffix = '/kivy3/default.glsl'
+ shutil.copyfile(self.get_build_dir(arch.arch) + suffix, self.ctx.get_python_install_dir(arch.arch) + suffix)
+
+
+recipe = Kivy3Recipe()
diff --git a/pythonforandroid/recipes/kiwisolver/__init__.py b/pythonforandroid/recipes/kiwisolver/__init__.py
index d7b5e2753..587c2b9a4 100644
--- a/pythonforandroid/recipes/kiwisolver/__init__.py
+++ b/pythonforandroid/recipes/kiwisolver/__init__.py
@@ -1,9 +1,11 @@
from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
class KiwiSolverRecipe(CppCompiledComponentsPythonRecipe):
site_packages_name = 'kiwisolver'
- version = '0.1.3'
- url = 'https://github.com/nucleic/kiwi/archive/master.zip'
- depends = ['python2', 'setuptools']
-
+ version = '1.3.2'
+ url = 'https://github.com/nucleic/kiwi/archive/{version}.zip'
+ depends = ['cppy']
+
+
recipe = KiwiSolverRecipe()
diff --git a/pythonforandroid/recipes/lapack/__init__.py b/pythonforandroid/recipes/lapack/__init__.py
new file mode 100644
index 000000000..b6124dc28
--- /dev/null
+++ b/pythonforandroid/recipes/lapack/__init__.py
@@ -0,0 +1,80 @@
+'''
+known to build with cmake version 3.23.2 and NDK r21e.
+See https://gitlab.kitware.com/cmake/cmake/-/issues/18739
+'''
+
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory, ensure_dir, BuildInterruptingException
+from multiprocessing import cpu_count
+from os.path import join
+import sh
+import shutil
+from os import environ
+from pythonforandroid.util import build_platform, rmdir
+
+arch_to_sysroot = {'armeabi': 'arm', 'armeabi-v7a': 'arm', 'arm64-v8a': 'arm64'}
+
+
+def arch_to_toolchain(arch):
+ if 'arm' in arch.arch:
+ return arch.command_prefix
+ return arch.arch
+
+
+class LapackRecipe(Recipe):
+
+ name = 'lapack'
+ version = 'v3.10.1'
+ url = 'https://github.com/Reference-LAPACK/lapack/archive/{version}.tar.gz'
+ libdir = 'build/install/lib'
+ built_libraries = {'libblas.so': libdir, 'liblapack.so': libdir, 'libcblas.so': libdir}
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+
+ ndk_dir = environ.get("LEGACY_NDK")
+ if ndk_dir is None:
+ raise BuildInterruptingException("Please set the environment variable 'LEGACY_NDK' to point to a NDK location with gcc/gfortran support (supported NDK version: 'r21e')")
+
+ GCC_VER = '4.9'
+ HOST = build_platform
+
+ sysroot_suffix = arch_to_sysroot.get(arch.arch, arch.arch)
+ sysroot = f"{ndk_dir}/platforms/{env['NDK_API']}/arch-{sysroot_suffix}"
+ FC = f"{ndk_dir}/toolchains/{arch_to_toolchain(arch)}-{GCC_VER}/prebuilt/{HOST}/bin/{arch.command_prefix}-gfortran"
+ env['FC'] = f'{FC} --sysroot={sysroot}'
+ if shutil.which(FC) is None:
+ raise BuildInterruptingException(f"{FC} not found. See https://github.com/mzakharo/android-gfortran")
+ return env
+
+ def build_arch(self, arch):
+ source_dir = self.get_build_dir(arch.arch)
+ build_target = join(source_dir, 'build')
+ install_target = join(build_target, 'install')
+
+ ensure_dir(build_target)
+ with current_directory(build_target):
+ env = self.get_recipe_env(arch)
+ ndk_dir = environ["LEGACY_NDK"]
+ rmdir('CMakeFiles')
+ shprint(sh.rm, '-f', 'CMakeCache.txt', _env=env)
+ opts = [
+ '-DCMAKE_SYSTEM_NAME=Android',
+ '-DCMAKE_POSITION_INDEPENDENT_CODE=1',
+ '-DCMAKE_ANDROID_ARCH_ABI={arch}'.format(arch=arch.arch),
+ '-DCMAKE_ANDROID_NDK=' + ndk_dir,
+ '-DCMAKE_ANDROID_API={api}'.format(api=self.ctx.ndk_api),
+ '-DCMAKE_BUILD_TYPE=Release',
+ '-DCMAKE_INSTALL_PREFIX={}'.format(install_target),
+ '-DCBLAS=ON',
+ '-DBUILD_SHARED_LIBS=ON',
+ ]
+ if arch.arch == 'armeabi-v7a':
+ opts.append('-DCMAKE_ANDROID_ARM_NEON=ON')
+ shprint(sh.cmake, source_dir, *opts, _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), _env=env)
+ shprint(sh.make, 'install', _env=env)
+
+
+recipe = LapackRecipe()
diff --git a/pythonforandroid/recipes/leveldb/__init__.py b/pythonforandroid/recipes/leveldb/__init__.py
index 92dc4fdce..7f65a55a4 100644
--- a/pythonforandroid/recipes/leveldb/__init__.py
+++ b/pythonforandroid/recipes/leveldb/__init__.py
@@ -1,45 +1,46 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import join, exists
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
+from pythonforandroid.recipe import Recipe
+from multiprocessing import cpu_count
+from os.path import join
import sh
-class LevelDBRecipe(Recipe):
- version = '1.18'
- url = 'https://github.com/google/leveldb/archive/v{version}.tar.gz'
- opt_depends = ['snappy']
- patches = ['disable-so-version.patch', 'find-snappy.patch']
- def should_build(self, arch):
- return not self.has_libs(arch, 'libleveldb.so', 'libgnustl_shared.so')
+class LevelDBRecipe(Recipe):
+ version = '1.22'
+ url = 'https://github.com/google/leveldb/archive/{version}.tar.gz'
+ depends = ['snappy']
+ built_libraries = {'libleveldb.so': '.'}
+ need_stl_shared = True
def build_arch(self, arch):
- super(LevelDBRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
- with current_directory(self.get_build_dir(arch.arch)):
- if 'snappy' in recipe.ctx.recipe_build_order:
- # Copy source from snappy recipe
- sh.cp('-rf', self.get_recipe('snappy', self.ctx).get_build_dir(arch.arch), 'snappy')
- # Build
- shprint(sh.make, _env=env)
- # Copy the shared library
- shutil.copyfile('libleveldb.so', join(self.ctx.get_libs_dir(arch.arch), 'libleveldb.so'))
- # Copy stl
- shutil.copyfile(self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + '/libgnustl_shared.so',
- join(self.ctx.get_libs_dir(arch.arch), 'libgnustl_shared.so'))
-
- def get_recipe_env(self, arch):
- env = super(LevelDBRecipe, self).get_recipe_env(arch)
- env['TARGET_OS'] = 'OS_ANDROID_CROSSCOMPILE'
- if 'snappy' in recipe.ctx.recipe_build_order:
- env['CFLAGS'] += ' -DSNAPPY' + \
- ' -I./snappy'
- env['CFLAGS'] += ' -I' + self.ctx.ndk_dir + '/platforms/android-' + str(self.ctx.android_api) + '/arch-' + arch.arch.replace('eabi', '') + '/usr/include' + \
- ' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/include' + \
- ' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + '/include'
- env['CXXFLAGS'] = env['CFLAGS']
- env['CXXFLAGS'] += ' -frtti'
- env['CXXFLAGS'] += ' -fexceptions'
- env['LDFLAGS'] += ' -L' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + \
- ' -lgnustl_shared'
- return env
+ source_dir = self.get_build_dir(arch.arch)
+ with current_directory(source_dir):
+ snappy_recipe = self.get_recipe('snappy', self.ctx)
+ snappy_build = snappy_recipe.get_build_dir(arch.arch)
+
+ shprint(sh.cmake, source_dir,
+ '-DANDROID_ABI={}'.format(arch.arch),
+ '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),
+ '-DANDROID_STL=' + self.stl_lib_name,
+
+ '-DCMAKE_TOOLCHAIN_FILE={}'.format(
+ join(self.ctx.ndk_dir, 'build', 'cmake',
+ 'android.toolchain.cmake')),
+ '-DCMAKE_BUILD_TYPE=Release',
+
+ '-DBUILD_SHARED_LIBS=1',
+
+ '-DHAVE_SNAPPY=1',
+ '-DCMAKE_CXX_FLAGS=-I{path}'.format(path=snappy_build),
+ '-DCMAKE_SHARED_LINKER_FLAGS=-L{path} -lsnappy'.format(
+ path=snappy_build),
+ '-DCMAKE_EXE_LINKER_FLAGS=-L{path} -lsnappy'.format(
+ path=snappy_build),
+
+ _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), _env=env)
+
recipe = LevelDBRecipe()
diff --git a/pythonforandroid/recipes/leveldb/disable-so-version.patch b/pythonforandroid/recipes/leveldb/disable-so-version.patch
deleted file mode 100644
index 0f6a7e728..000000000
--- a/pythonforandroid/recipes/leveldb/disable-so-version.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- leveldb/build_detect_platform 2014-09-16 23:19:52.000000000 +0200
-+++ leveldb-patch/build_detect_platform 2016-03-01 20:25:04.074484399 +0100
-@@ -124,6 +124,7 @@
- ;;
- OS_ANDROID_CROSSCOMPILE)
- PLATFORM=OS_ANDROID
-+ PLATFORM_SHARED_VERSIONED=
- COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_ANDROID -DLEVELDB_PLATFORM_POSIX"
- PLATFORM_LDFLAGS="" # All pthread features are in the Android C library
- PORT_FILE=port/port_posix.cc
diff --git a/pythonforandroid/recipes/leveldb/find-snappy.patch b/pythonforandroid/recipes/leveldb/find-snappy.patch
deleted file mode 100644
index ae978d7b0..000000000
--- a/pythonforandroid/recipes/leveldb/find-snappy.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- leveldb/build_detect_platform 2014-09-16 23:19:52.000000000 +0200
-+++ leveldb-patch/build_detect_platform 2016-03-01 21:56:04.926650079 +0100
-@@ -156,7 +157,7 @@
- # except for the test and benchmark files. By default, find will output a list
- # of all files matching either rule, so we need to append -print to make the
- # prune take effect.
--DIRS="$PREFIX/db $PREFIX/util $PREFIX/table"
-+DIRS="$PREFIX/snappy $PREFIX/db $PREFIX/util $PREFIX/table"
-
- set -f # temporarily disable globbing so that our patterns aren't expanded
- PRUNE_TEST="-name *test*.cc -prune"
diff --git a/pythonforandroid/recipes/libbz2/__init__.py b/pythonforandroid/recipes/libbz2/__init__.py
new file mode 100644
index 000000000..01d5146b6
--- /dev/null
+++ b/pythonforandroid/recipes/libbz2/__init__.py
@@ -0,0 +1,57 @@
+import sh
+
+from multiprocessing import cpu_count
+
+from pythonforandroid.archs import Arch
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+
+
+class LibBz2Recipe(Recipe):
+
+ version = "1.0.8"
+ url = "https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz"
+ built_libraries = {"libbz2.so": ""}
+ patches = ["lib_android.patch"]
+
+ def build_arch(self, arch: Arch) -> None:
+ env = self.get_recipe_env(arch)
+ with current_directory(self.get_build_dir(arch.arch)):
+ shprint(
+ sh.make,
+ "-j",
+ str(cpu_count()),
+ f'CC={env["CC"]}',
+ "-f",
+ "Makefile-libbz2_so",
+ _env=env,
+ )
+
+ def get_library_includes(self, arch: Arch) -> str:
+ """
+ Returns a string with the appropriate `-I<lib directory>` to link
+ with the bz2 lib. This string is usually added to the environment
+ variable `CPPFLAGS`.
+ """
+ return " -I" + self.get_build_dir(arch.arch)
+
+ def get_library_ldflags(self, arch: Arch) -> str:
+ """
+ Returns a string with the appropriate `-L<lib directory>` to link
+ with the bz2 lib. This string is usually added to the environment
+ variable `LDFLAGS`.
+ """
+ return " -L" + self.get_build_dir(arch.arch)
+
+ @staticmethod
+ def get_library_libs_flag() -> str:
+ """
+ Returns a string with the appropriate `-l<lib>` flags to link with
+ the bz2 lib. This string is usually added to the environment
+ variable `LIBS`.
+ """
+ return " -lbz2"
+
+
+recipe = LibBz2Recipe()
diff --git a/pythonforandroid/recipes/libbz2/lib_android.patch b/pythonforandroid/recipes/libbz2/lib_android.patch
new file mode 100644
index 000000000..b208896ba
--- /dev/null
+++ b/pythonforandroid/recipes/libbz2/lib_android.patch
@@ -0,0 +1,29 @@
+Set default compiler to `clang` and disable versioned shared library
+--- bzip2-1.0.8/Makefile-libbz2_so.orig 2019-07-13 19:50:05.000000000 +0200
++++ bzip2-1.0.8/Makefile-libbz2_so 2020-03-13 20:10:32.336990786 +0100
+@@ -22,7 +22,7 @@
+
+
+ SHELL=/bin/sh
+-CC=gcc
++CC=clang
+ BIGFILES=-D_FILE_OFFSET_BITS=64
+ CFLAGS=-fpic -fPIC -Wall -Winline -O2 -g $(BIGFILES)
+
+@@ -35,13 +35,11 @@ OBJS= blocksort.o \
+ bzlib.o
+
+ all: $(OBJS)
+- $(CC) -shared -Wl,-soname -Wl,libbz2.so.1.0 -o libbz2.so.1.0.8 $(OBJS)
+- $(CC) $(CFLAGS) -o bzip2-shared bzip2.c libbz2.so.1.0.8
+- rm -f libbz2.so.1.0
+- ln -s libbz2.so.1.0.8 libbz2.so.1.0
++ $(CC) -shared -Wl,-soname=libbz2.so -o libbz2.so $(OBJS)
++ $(CC) $(CFLAGS) -o bzip2-shared bzip2.c libbz2.so
+
+ clean:
+- rm -f $(OBJS) bzip2.o libbz2.so.1.0.8 libbz2.so.1.0 bzip2-shared
++ rm -f $(OBJS) bzip2.o libbz2.so bzip2-shared
+
+ blocksort.o: blocksort.c
+ $(CC) $(CFLAGS) -c blocksort.c
diff --git a/pythonforandroid/recipes/libcurl/__init__.py b/pythonforandroid/recipes/libcurl/__init__.py
index 6f87dbbd3..2971532fb 100644
--- a/pythonforandroid/recipes/libcurl/__init__.py
+++ b/pythonforandroid/recipes/libcurl/__init__.py
@@ -1,40 +1,37 @@
import sh
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from pythonforandroid.util import ensure_dir
-from os.path import exists, join, abspath
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from os.path import join
from multiprocessing import cpu_count
class LibcurlRecipe(Recipe):
version = '7.55.1'
url = 'https://curl.haxx.se/download/curl-7.55.1.tar.gz'
+ built_libraries = {'libcurl.so': 'dist/lib'}
depends = ['openssl']
- def should_build(self, arch):
- super(LibcurlRecipe, self).should_build(arch)
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libcurl.so'))
-
def build_arch(self, arch):
- super(LibcurlRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
- r = self.get_recipe('openssl', self.ctx)
- openssl_dir = r.get_build_dir(arch.arch)
+ openssl_recipe = self.get_recipe('openssl', self.ctx)
+ openssl_dir = openssl_recipe.get_build_dir(arch.arch)
+
+ env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
+ env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags()
with current_directory(self.get_build_dir(arch.arch)):
dst_dir = join(self.get_build_dir(arch.arch), 'dist')
shprint(
sh.Command('./configure'),
- '--host=arm-linux-androideabi',
+ '--host={}'.format(arch.command_prefix),
'--enable-shared',
'--with-ssl={}'.format(openssl_dir),
'--prefix={}'.format(dst_dir),
_env=env)
shprint(sh.make, '-j', str(cpu_count()), _env=env)
shprint(sh.make, 'install', _env=env)
- shutil.copyfile('{}/lib/libcurl.so'.format(dst_dir),
- join(
- self.ctx.get_libs_dir(arch.arch),
- 'libcurl.so'))
+
recipe = LibcurlRecipe()
diff --git a/pythonforandroid/recipes/libexpat/__init__.py b/pythonforandroid/recipes/libexpat/__init__.py
index f0e03acb7..614b0df0f 100644
--- a/pythonforandroid/recipes/libexpat/__init__.py
+++ b/pythonforandroid/recipes/libexpat/__init__.py
@@ -1,38 +1,32 @@
-
+
import sh
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import exists, join
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from os.path import join
from multiprocessing import cpu_count
class LibexpatRecipe(Recipe):
version = 'master'
url = 'https://github.com/libexpat/libexpat/archive/{version}.zip'
+ built_libraries = {'libexpat.so': 'dist/lib'}
depends = []
- def should_build(self, arch):
- super(LibexpatRecipe, self).should_build(arch)
- return not exists(
- join(self.ctx.get_libs_dir(arch.arch), 'libexpat.so'))
-
def build_arch(self, arch):
- super(LibexpatRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(join(self.get_build_dir(arch.arch), 'expat')):
dst_dir = join(self.get_build_dir(arch.arch), 'dist')
shprint(sh.Command('./buildconf.sh'), _env=env)
shprint(
sh.Command('./configure'),
- '--host=arm-linux-androideabi',
+ '--host={}'.format(arch.command_prefix),
'--enable-shared',
'--without-xmlwf',
'--prefix={}'.format(dst_dir),
_env=env)
shprint(sh.make, '-j', str(cpu_count()), _env=env)
shprint(sh.make, 'install', _env=env)
- shutil.copyfile(
- '{}/lib/libexpat.so'.format(dst_dir),
- join(self.ctx.get_libs_dir(arch.arch), 'libexpat.so'))
recipe = LibexpatRecipe()
diff --git a/pythonforandroid/recipes/libffi/__init__.py b/pythonforandroid/recipes/libffi/__init__.py
index 6d8084076..767881b79 100644
--- a/pythonforandroid/recipes/libffi/__init__.py
+++ b/pythonforandroid/recipes/libffi/__init__.py
@@ -1,34 +1,25 @@
from os.path import exists, join
+from multiprocessing import cpu_count
from pythonforandroid.recipe import Recipe
-from pythonforandroid.logger import info, shprint
+from pythonforandroid.logger import shprint
from pythonforandroid.util import current_directory
import sh
class LibffiRecipe(Recipe):
+ """
+ Requires additional system dependencies on Ubuntu:
+ - `automake` for the `aclocal` binary
+ - `autoconf` for the `autoreconf` binary
+ - `libltdl-dev` which defines the `LT_SYS_SYMBOL_USCORE` macro
+ """
name = 'libffi'
- version = 'v3.2.1'
- url = 'https://github.com/atgreen/libffi/archive/{version}.zip'
+ version = 'v3.4.2'
+ url = 'https://github.com/libffi/libffi/archive/{version}.tar.gz'
patches = ['remove-version-info.patch']
- def get_host(self, arch):
- with current_directory(self.get_build_dir(arch.arch)):
- host = None
- with open('Makefile') as f:
- for line in f:
- if line.startswith('host = '):
- host = line.strip()[7:]
- break
-
- if not host or not exists(host):
- raise RuntimeError('failed to find build output! ({})'
- .format(host))
-
- return host
-
- def should_build(self, arch):
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libffi.so'))
+ built_libraries = {'libffi.so': '.libs'}
def build_arch(self, arch):
env = self.get_recipe_env(arch)
@@ -37,47 +28,14 @@ def build_arch(self, arch):
shprint(sh.Command('./autogen.sh'), _env=env)
shprint(sh.Command('autoreconf'), '-vif', _env=env)
shprint(sh.Command('./configure'),
- '--host=' + arch.toolchain_prefix,
- '--prefix=' + self.ctx.get_python_install_dir(),
+ '--host=' + arch.command_prefix,
+ '--prefix=' + self.get_build_dir(arch.arch),
+ '--disable-builddir',
'--enable-shared', _env=env)
- #'--with-sysroot={}'.format(self.ctx.ndk_platform),
- #'--target={}'.format(arch.toolchain_prefix),
-
- # ndk 15 introduces unified headers required --sysroot and
- # -isysroot for libraries and headers. libtool's head explodes
- # trying to weave them into it's own magic. The result is a link
- # failure tryng to link libc. We call make to compile the bits
- # and manually link...
-
- try:
- shprint(sh.make, '-j5', 'libffi.la', _env=env)
- except sh.ErrorReturnCode_2:
- info("make libffi.la failed as expected")
- cc = sh.Command(env['CC'].split()[0])
- cflags = env['CC'].split()[1:]
-
- cflags.extend(['-march=armv7-a', '-mfloat-abi=softfp', '-mfpu=vfp',
- '-mthumb', '-shared', '-fPIC', '-DPIC',
- 'src/.libs/prep_cif.o', 'src/.libs/types.o',
- 'src/.libs/raw_api.o', 'src/.libs/java_raw_api.o',
- 'src/.libs/closures.o', 'src/arm/.libs/sysv.o',
- 'src/arm/.libs/ffi.o', ]
- )
-
- ldflags = env['LDFLAGS'].split()
- cflags.extend(ldflags)
- cflags.extend(['-Wl,-soname', '-Wl,libffi.so', '-o',
- '.libs/libffi.so'])
-
- with current_directory(self.get_host(arch)):
- shprint(cc, *cflags, _env=env)
-
- shprint(sh.cp, '-t', self.ctx.get_libs_dir(arch.arch),
- join(self.get_host(arch), '.libs', 'libffi.so'))
+ shprint(sh.make, '-j', str(cpu_count()), 'libffi.la', _env=env)
def get_include_dirs(self, arch):
- return [join(self.get_build_dir(arch.arch), self.get_host(arch),
- 'include')]
+ return [join(self.get_build_dir(arch.arch), 'include')]
recipe = LibffiRecipe()
diff --git a/pythonforandroid/recipes/libffi/remove-version-info.patch b/pythonforandroid/recipes/libffi/remove-version-info.patch
index 7bdc11a64..0a32b7e61 100644
--- a/pythonforandroid/recipes/libffi/remove-version-info.patch
+++ b/pythonforandroid/recipes/libffi/remove-version-info.patch
@@ -1,12 +1,11 @@
-diff -Naur libffi/Makefile.am b/Makefile.am
---- libffi/Makefile.am 2014-11-12 06:00:59.000000000 -0600
-+++ b/Makefile.am 2015-12-23 15:57:10.363148806 -0600
-@@ -249,7 +249,7 @@
- AM_CFLAGS += -DFFI_DEBUG
- endif
-
--libffi_la_LDFLAGS = -no-undefined -version-info `grep -v '^\#' $(srcdir)/libtool-version` $(LTLDFLAGS) $(AM_LTLDFLAGS)
+--- libffi/Makefile.am.orig 2018-12-21 16:11:26.159181262 +0100
++++ libffi/Makefile.am 2018-12-21 16:14:44.075179374 +0100
+@@ -156,7 +156,7 @@
+ libffi.map: $(top_srcdir)/libffi.map.in
+ $(COMPILE) -D$(TARGET) -E -x assembler-with-cpp -o $@ $<
+
+-libffi_la_LDFLAGS = -no-undefined $(libffi_version_info) $(libffi_version_script) $(LTLDFLAGS) $(AM_LTLDFLAGS)
+libffi_la_LDFLAGS = -no-undefined -avoid-version $(LTLDFLAGS) $(AM_LTLDFLAGS)
-
+ libffi_la_DEPENDENCIES = $(libffi_la_LIBADD) $(libffi_version_dep)
+
AM_CPPFLAGS = -I. -I$(top_srcdir)/include -Iinclude -I$(top_srcdir)/src
- AM_CCASFLAGS = $(AM_CPPFLAGS)
diff --git a/pythonforandroid/recipes/libgeos/__init__.py b/pythonforandroid/recipes/libgeos/__init__.py
index 20f353978..cff9fe0f5 100644
--- a/pythonforandroid/recipes/libgeos/__init__.py
+++ b/pythonforandroid/recipes/libgeos/__init__.py
@@ -1,44 +1,52 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from pythonforandroid.util import ensure_dir
-from os.path import exists, join, abspath
-import sh
+from pythonforandroid.util import current_directory, ensure_dir
+from pythonforandroid.toolchain import shprint
+from pythonforandroid.recipe import Recipe
from multiprocessing import cpu_count
+from os.path import join
+import sh
-class LibgeosRecipe(Recipe):
- version = '3.5'
- #url = 'http://download.osgeo.org/geos/geos-{version}.tar.bz2'
- url = 'https://github.com/libgeos/libgeos/archive/svn-{version}.zip'
- depends = ['python2']
- def should_build(self, arch):
- super(LibgeosRecipe, self).should_build(arch)
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libgeos_c.so'))
+class LibgeosRecipe(Recipe):
+ version = '3.7.1'
+ url = 'https://github.com/libgeos/libgeos/archive/{version}.zip'
+ depends = []
+ built_libraries = {
+ 'libgeos.so': 'install_target/lib',
+ 'libgeos_c.so': 'install_target/lib'
+ }
+ need_stl_shared = True
def build_arch(self, arch):
- super(LibgeosRecipe, self).build_arch(arch)
- env = self.get_recipe_env(arch)
-
- with current_directory(self.get_build_dir(arch.arch)):
- dst_dir = join(self.get_build_dir(arch.arch),'dist')
- bash = sh.Command('bash')
- print("If this fails make sure you have autoconf and libtool installed")
- shprint(bash,'autogen.sh') # Requires autoconf and libtool
- shprint(bash, 'configure', '--host=arm-linux-androideabi', '--enable-shared','--prefix={}'.format(dst_dir), _env=env)
- shprint(sh.make,'-j',str(cpu_count()),_env=env)
- shprint(sh.make,'install',_env=env)
- shutil.copyfile('{}/lib/libgeos_c.so'.format(dst_dir), join(self.ctx.get_libs_dir(arch.arch), 'libgeos_c.so'))
-
- def get_recipe_env(self, arch):
- env = super(LibgeosRecipe, self).get_recipe_env(arch)
- env['CXXFLAGS'] += ' -I{}/sources/cxx-stl/gnu-libstdc++/4.8/include'.format(self.ctx.ndk_dir)
- env['CXXFLAGS'] += ' -I{}/sources/cxx-stl/gnu-libstdc++/4.8/libs/{}/include'.format(
- self.ctx.ndk_dir, arch)
- env['CXXFLAGS'] += ' -L{}/sources/cxx-stl/gnu-libstdc++/4.8/libs/{}'.format(
- self.ctx.ndk_dir, arch)
- env['CXXFLAGS'] += ' -lgnustl_shared'
- env['LDFLAGS'] += ' -L{}/sources/cxx-stl/gnu-libstdc++/4.8/libs/{}'.format(
- self.ctx.ndk_dir, arch)
- return env
+ source_dir = self.get_build_dir(arch.arch)
+ build_target = join(source_dir, 'build_target')
+ install_target = join(source_dir, 'install_target')
-recipe = LibgeosRecipe()
+ ensure_dir(build_target)
+ with current_directory(build_target):
+ env = self.get_recipe_env(arch)
+ shprint(sh.cmake, source_dir,
+ '-DANDROID_ABI={}'.format(arch.arch),
+ '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),
+ '-DANDROID_STL=' + self.stl_lib_name,
+
+ '-DCMAKE_TOOLCHAIN_FILE={}'.format(
+ join(self.ctx.ndk_dir, 'build', 'cmake',
+ 'android.toolchain.cmake')),
+ '-DCMAKE_INSTALL_PREFIX={}'.format(install_target),
+ '-DCMAKE_BUILD_TYPE=Release',
+ '-DGEOS_ENABLE_TESTS=OFF',
+
+ '-DBUILD_SHARED_LIBS=1',
+
+ _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), _env=env)
+
+ # We make the install because this way we will have all the
+ # includes in one place (mostly we are interested in `geos_c.h`,
+ # which is not in the include folder, so this way we make easier to
+ # link with this library...case of shapely's recipe)
+ shprint(sh.make, 'install', _env=env)
+
+
+recipe = LibgeosRecipe()
diff --git a/pythonforandroid/recipes/libglob/__init__.py b/pythonforandroid/recipes/libglob/__init__.py
index eca17f9bd..39a68b7ee 100644
--- a/pythonforandroid/recipes/libglob/__init__.py
+++ b/pythonforandroid/recipes/libglob/__init__.py
@@ -2,25 +2,30 @@
android libglob
available via '-lglob' LDFLAG
"""
-from os.path import exists, join, dirname
-from pythonforandroid.toolchain import (CompiledComponentsPythonRecipe,
- current_directory)
-from pythonforandroid.logger import shprint, info, warning, info_main
+from os.path import join
+
import sh
-class LibGlobRecipe(CompiledComponentsPythonRecipe):
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.toolchain import current_directory
+from pythonforandroid.util import ensure_dir
+
+
+class LibGlobRecipe(Recipe):
"""Make a glob.h and glob.so for the python_install_dir()"""
version = '0.0.1'
url = None
- #
+ #
# glob.h and glob.c extracted from
# https://github.com/white-gecko/TokyoCabinet, e.g.:
# https://raw.githubusercontent.com/white-gecko/TokyoCabinet/master/glob.h
# https://raw.githubusercontent.com/white-gecko/TokyoCabinet/master/glob.c
# and pushed in via patch
name = 'libglob'
+ built_libraries = {'libglob.so': '.'}
- depends = [('hostpython2', 'hostpython3'), ('python2', 'python3crystax')]
+ depends = ['hostpython3']
patches = ['glob.patch']
def should_build(self, arch):
@@ -30,45 +35,31 @@ def should_build(self, arch):
def prebuild_arch(self, arch):
"""Make the build and target directories"""
path = self.get_build_dir(arch.arch)
- if not exists(path):
- info("creating {}".format(path))
- shprint(sh.mkdir, '-p', path)
+ ensure_dir(path)
def build_arch(self, arch):
"""simple shared compile"""
env = self.get_recipe_env(arch, with_flags_in_cc=False)
- for path in (self.get_build_dir(arch.arch),
- join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Lib'),
- join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include'),
- ):
- if not exists(path):
- info("creating {}".format(path))
- shprint(sh.mkdir, '-p', path)
- cli = env['CC'].split()
- cc = sh.Command(cli[0])
+ for path in (
+ self.get_build_dir(arch.arch),
+ join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Lib'),
+ join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')):
+ ensure_dir(path)
+ cli = env['CC'].split()[0]
+ # makes sure first CC command is the compiler rather than ccache, refs:
+ # https://github.com/kivy/python-for-android/issues/1399
+ if 'ccache' in cli:
+ cli = env['CC'].split()[1]
+ cc = sh.Command(cli)
with current_directory(self.get_build_dir(arch.arch)):
cflags = env['CFLAGS'].split()
- cflags.extend(['-I.', '-c', '-l.', 'glob.c', '-I.']) # , '-o', 'glob.o'])
+ cflags.extend(['-I.', '-c', '-l.', 'glob.c', '-I.'])
shprint(cc, *cflags, _env=env)
-
cflags = env['CFLAGS'].split()
- srindex = cflags.index('--sysroot')
- if srindex:
- cflags[srindex+1] = self.ctx.ndk_platform
cflags.extend(['-shared', '-I.', 'glob.o', '-o', 'libglob.so'])
+ cflags.extend(env['LDFLAGS'].split())
shprint(cc, *cflags, _env=env)
- shprint(sh.cp, 'libglob.so', join(self.ctx.libs_dir, arch.arch))
- shprint(sh.cp, "libglob.so", join(self.ctx.get_python_install_dir(), 'lib'))
- # drop header in to the Python include directory
- shprint(sh.cp, "glob.h", join(self.ctx.get_python_install_dir(),
- 'include/python{}'.format(
- self.ctx.python_recipe.version[0:3]
- )
- )
- )
- include_path = join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')
- shprint(sh.cp, "glob.h", include_path)
recipe = LibGlobRecipe()
diff --git a/pythonforandroid/recipes/libglob/glob.patch b/pythonforandroid/recipes/libglob/glob.patch
index c7fe81738..ee71719a1 100644
--- a/pythonforandroid/recipes/libglob/glob.patch
+++ b/pythonforandroid/recipes/libglob/glob.patch
@@ -911,7 +911,7 @@ diff -Nur /tmp/x/glob.c libglob/glob.c
diff -Nur /tmp/x/glob.h libglob/glob.h
--- /tmp/x/glob.h 1969-12-31 19:00:00.000000000 -0500
+++ libglob/glob.h 2017-08-19 15:22:18.367109399 -0400
-@@ -0,0 +1,102 @@
+@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
@@ -952,10 +952,12 @@ diff -Nur /tmp/x/glob.h libglob/glob.h
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
++#ifndef ARG_MAX
+#define ARG_MAX 6553
++#endif
+
+#ifndef _SIZE_T_DECLARED
-+typedef __size_t size_t;
++#include <stddef.h>
+#define _SIZE_T_DECLARED
+#endif
+
diff --git a/pythonforandroid/recipes/libiconv/__init__.py b/pythonforandroid/recipes/libiconv/__init__.py
index 4a6466920..1cdcb9179 100644
--- a/pythonforandroid/recipes/libiconv/__init__.py
+++ b/pythonforandroid/recipes/libiconv/__init__.py
@@ -1,5 +1,5 @@
-import os
-from pythonforandroid.toolchain import shprint, current_directory
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
from pythonforandroid.recipe import Recipe
from multiprocessing import cpu_count
import sh
@@ -7,28 +7,21 @@
class LibIconvRecipe(Recipe):
- version = '1.15'
+ version = '1.16'
url = 'https://ftp.gnu.org/pub/gnu/libiconv/libiconv-{version}.tar.gz'
- patches = ['libiconv-1.15-no-gets.patch']
-
- def should_build(self, arch):
- return not os.path.exists(
- os.path.join(self.ctx.get_libs_dir(arch.arch), 'libiconv.so'))
+ built_libraries = {'libiconv.so': 'lib/.libs'}
def build_arch(self, arch):
- super(LibIconvRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
shprint(
sh.Command('./configure'),
- '--host=' + arch.toolchain_prefix,
- '--prefix=' + self.ctx.get_python_install_dir(),
+ '--host=' + arch.command_prefix,
+ '--prefix=' + self.ctx.get_python_install_dir(arch.arch),
_env=env)
shprint(sh.make, '-j' + str(cpu_count()), _env=env)
- libs = ['lib/.libs/libiconv.so']
- self.install_libs(arch, *libs)
recipe = LibIconvRecipe()
diff --git a/pythonforandroid/recipes/libiconv/libiconv-1.15-no-gets.patch b/pythonforandroid/recipes/libiconv/libiconv-1.15-no-gets.patch
deleted file mode 100644
index 5bc20b377..000000000
--- a/pythonforandroid/recipes/libiconv/libiconv-1.15-no-gets.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-hack until gzip pulls a newer gnulib version
-
-From 66712c23388e93e5c518ebc8515140fa0c807348 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Thu, 29 Mar 2012 13:30:41 -0600
-Subject: [PATCH] stdio: don't assume gets any more
-
-Gnulib intentionally does not have a gets module, and now that C11
-and glibc have dropped it, we should be more proactive about warning
-any user on a platform that still has a declaration of this dangerous
-interface.
-
---- a/srclib/stdio.in.h
-+++ b/srclib/stdio.in.h
-@@ -744,7 +744,6 @@ _GL_WARN_ON_USE (getline, "getline is un
- removed it. */
- #undef gets
- #if HAVE_RAW_DECL_GETS && !defined __cplusplus
--_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
- #endif
-
- #if @GNULIB_OBSTACK_PRINTF@ || @GNULIB_OBSTACK_PRINTF_POSIX@
diff --git a/pythonforandroid/recipes/liblzma/__init__.py b/pythonforandroid/recipes/liblzma/__init__.py
new file mode 100644
index 000000000..0b880bc48
--- /dev/null
+++ b/pythonforandroid/recipes/liblzma/__init__.py
@@ -0,0 +1,77 @@
+import sh
+
+from multiprocessing import cpu_count
+from os.path import exists, join
+
+from pythonforandroid.archs import Arch
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+
+
+class LibLzmaRecipe(Recipe):
+
+ version = '5.2.4'
+ url = 'https://tukaani.org/xz/xz-{version}.tar.gz'
+ built_libraries = {'liblzma.so': 'p4a_install/lib'}
+
+ def build_arch(self, arch: Arch) -> None:
+ env = self.get_recipe_env(arch)
+ install_dir = join(self.get_build_dir(arch.arch), 'p4a_install')
+ with current_directory(self.get_build_dir(arch.arch)):
+ if not exists('configure'):
+ shprint(sh.Command('./autogen.sh'), _env=env)
+ shprint(sh.Command('autoreconf'), '-vif', _env=env)
+ shprint(sh.Command('./configure'),
+ '--host=' + arch.command_prefix,
+ '--prefix=' + install_dir,
+ '--disable-builddir',
+ '--disable-static',
+ '--enable-shared',
+
+ '--disable-xz',
+ '--disable-xzdec',
+ '--disable-lzmadec',
+ '--disable-lzmainfo',
+ '--disable-scripts',
+ '--disable-doc',
+
+ _env=env)
+ shprint(
+ sh.make, '-j', str(cpu_count()),
+ _env=env
+ )
+
+ shprint(sh.make, 'install', _env=env)
+
+ def get_library_includes(self, arch: Arch) -> str:
+ """
+ Returns a string with the appropriate `-I<lib directory>` to link
+ with the lzma lib. This string is usually added to the environment
+ variable `CPPFLAGS`.
+ """
+ return " -I" + join(
+ self.get_build_dir(arch.arch), 'p4a_install', 'include',
+ )
+
+ def get_library_ldflags(self, arch: Arch) -> str:
+ """
+ Returns a string with the appropriate `-L<lib directory>` to link
+ with the lzma lib. This string is usually added to the environment
+ variable `LDFLAGS`.
+ """
+ return " -L" + join(
+ self.get_build_dir(arch.arch), self.built_libraries['liblzma.so'],
+ )
+
+ @staticmethod
+ def get_library_libs_flag() -> str:
+ """
+ Returns a string with the appropriate `-l<lib>` flags to link with
+ the lzma lib. This string is usually added to the environment
+ variable `LIBS`.
+ """
+ return " -llzma"
+
+
+recipe = LibLzmaRecipe()
diff --git a/pythonforandroid/recipes/libmysqlclient/__init__.py b/pythonforandroid/recipes/libmysqlclient/__init__.py
index 0f070ffb3..84fd8d30a 100644
--- a/pythonforandroid/recipes/libmysqlclient/__init__.py
+++ b/pythonforandroid/recipes/libmysqlclient/__init__.py
@@ -6,62 +6,62 @@
class LibmysqlclientRecipe(Recipe):
- name = 'libmysqlclient'
- version = 'master'
- url = 'https://github.com/0x-ff/libmysql-android/archive/{version}.zip'
- # version = '5.5.47'
- # url = 'http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-{version}.tar.gz'
- #
- # depends = ['ncurses']
- #
+ name = 'libmysqlclient'
+ version = 'master'
+ url = 'https://github.com/0x-ff/libmysql-android/archive/{version}.zip'
+ # version = '5.5.47'
+ # url = 'http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-{version}.tar.gz'
+ #
+ # depends = ['ncurses']
+ #
- # patches = ['add-custom-platform.patch']
+ # patches = ['add-custom-platform.patch']
- patches = ['disable-soversion.patch']
+ patches = ['disable-soversion.patch']
- def should_build(self, arch):
- return not self.has_libs(arch, 'libmysql.so')
+ def should_build(self, arch):
+ return not self.has_libs(arch, 'libmysql.so')
- def build_arch(self, arch):
- env = self.get_recipe_env(arch)
- with current_directory(join(self.get_build_dir(arch.arch), 'libmysqlclient')):
- shprint(sh.cp, '-t', '.', join(self.get_recipe_dir(), 'p4a.cmake'))
- # shprint(sh.mkdir, 'Platform')
- # shprint(sh.cp, '-t', 'Platform', join(self.get_recipe_dir(), 'Linux.cmake'))
- shprint(sh.rm, '-f', 'CMakeCache.txt')
- shprint(sh.cmake, '-G', 'Unix Makefiles',
- # '-DCMAKE_MODULE_PATH=' + join(self.get_build_dir(arch.arch), 'libmysqlclient'),
- '-DCMAKE_INSTALL_PREFIX=./install',
- '-DCMAKE_TOOLCHAIN_FILE=p4a.cmake', _env=env)
- shprint(sh.make, _env=env)
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+ with current_directory(join(self.get_build_dir(arch.arch), 'libmysqlclient')):
+ shprint(sh.cp, '-t', '.', join(self.get_recipe_dir(), 'p4a.cmake'))
+ # ensure_dir('Platform')
+ # shprint(sh.cp, '-t', 'Platform', join(self.get_recipe_dir(), 'Linux.cmake'))
+ shprint(sh.rm, '-f', 'CMakeCache.txt')
+ shprint(sh.cmake, '-G', 'Unix Makefiles',
+ # '-DCMAKE_MODULE_PATH=' + join(self.get_build_dir(arch.arch), 'libmysqlclient'),
+ '-DCMAKE_INSTALL_PREFIX=./install',
+ '-DCMAKE_TOOLCHAIN_FILE=p4a.cmake', _env=env)
+ shprint(sh.make, _env=env)
- self.install_libs(arch, join('libmysql', 'libmysql.so'))
+ self.install_libs(arch, join('libmysql', 'libmysql.so'))
- # def get_recipe_env(self, arch=None):
- # env = super(LibmysqlclientRecipe, self).get_recipe_env(arch)
- # env['WITHOUT_SERVER'] = 'ON'
- # ncurses = self.get_recipe('ncurses', self)
- # # env['CFLAGS'] += ' -I' + join(ncurses.get_build_dir(arch.arch),
- # # 'include')
- # env['CURSES_LIBRARY'] = join(self.ctx.get_libs_dir(arch.arch), 'libncurses.so')
- # env['CURSES_INCLUDE_PATH'] = join(ncurses.get_build_dir(arch.arch),
- # 'include')
- # return env
- #
- # def build_arch(self, arch):
- # env = self.get_recipe_env(arch)
- # with current_directory(self.get_build_dir(arch.arch)):
- # # configure = sh.Command('./configure')
- # # TODO: should add openssl as an optional dep and compile support
- # # shprint(configure, '--enable-shared', '--enable-assembler',
- # # '--enable-thread-safe-client', '--with-innodb',
- # # '--without-server', _env=env)
- # # shprint(sh.make, _env=env)
- # shprint(sh.cmake, '.', '-DCURSES_LIBRARY=' + env['CURSES_LIBRARY'],
- # '-DCURSES_INCLUDE_PATH=' + env['CURSES_INCLUDE_PATH'], _env=env)
- # shprint(sh.make, _env=env)
- #
- # self.install_libs(arch, 'libmysqlclient.so')
+ # def get_recipe_env(self, arch=None):
+ # env = super().get_recipe_env(arch)
+ # env['WITHOUT_SERVER'] = 'ON'
+ # ncurses = self.get_recipe('ncurses', self)
+ # # env['CFLAGS'] += ' -I' + join(ncurses.get_build_dir(arch.arch),
+ # # 'include')
+ # env['CURSES_LIBRARY'] = join(self.ctx.get_libs_dir(arch.arch), 'libncurses.so')
+ # env['CURSES_INCLUDE_PATH'] = join(ncurses.get_build_dir(arch.arch),
+ # 'include')
+ # return env
+ #
+ # def build_arch(self, arch):
+ # env = self.get_recipe_env(arch)
+ # with current_directory(self.get_build_dir(arch.arch)):
+ # # configure = sh.Command('./configure')
+ # # TODO: should add openssl as an optional dep and compile support
+ # # shprint(configure, '--enable-shared', '--enable-assembler',
+ # # '--enable-thread-safe-client', '--with-innodb',
+ # # '--without-server', _env=env)
+ # # shprint(sh.make, _env=env)
+ # shprint(sh.cmake, '.', '-DCURSES_LIBRARY=' + env['CURSES_LIBRARY'],
+ # '-DCURSES_INCLUDE_PATH=' + env['CURSES_INCLUDE_PATH'], _env=env)
+ # shprint(sh.make, _env=env)
+ #
+ # self.install_libs(arch, 'libmysqlclient.so')
recipe = LibmysqlclientRecipe()
diff --git a/pythonforandroid/recipes/libnacl/__init__.py b/pythonforandroid/recipes/libnacl/__init__.py
deleted file mode 100644
index 62fc7bee2..000000000
--- a/pythonforandroid/recipes/libnacl/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from pythonforandroid.toolchain import PythonRecipe
-
-class LibNaClRecipe(PythonRecipe):
- version = '1.4.4'
- url = 'https://github.com/saltstack/libnacl/archive/v{version}.tar.gz'
- depends = ['hostpython2', 'setuptools']
- site_packages_name = 'libnacl'
- call_hostpython_via_targetpython = False
-
-recipe = LibNaClRecipe()
diff --git a/pythonforandroid/recipes/libogg/__init__.py b/pythonforandroid/recipes/libogg/__init__.py
new file mode 100644
index 000000000..875dd7f7a
--- /dev/null
+++ b/pythonforandroid/recipes/libogg/__init__.py
@@ -0,0 +1,22 @@
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.toolchain import current_directory, shprint
+import sh
+
+
+class OggRecipe(Recipe):
+ version = '1.3.3'
+ url = 'http://downloads.xiph.org/releases/ogg/libogg-{version}.tar.gz'
+ built_libraries = {'libogg.so': 'src/.libs'}
+
+ def build_arch(self, arch):
+ with current_directory(self.get_build_dir(arch.arch)):
+ env = self.get_recipe_env(arch)
+ flags = [
+ '--host=' + arch.command_prefix,
+ ]
+ configure = sh.Command('./configure')
+ shprint(configure, *flags, _env=env)
+ shprint(sh.make, _env=env)
+
+
+recipe = OggRecipe()
diff --git a/pythonforandroid/recipes/libpcre/__init__.py b/pythonforandroid/recipes/libpcre/__init__.py
new file mode 100644
index 000000000..ddf005e03
--- /dev/null
+++ b/pythonforandroid/recipes/libpcre/__init__.py
@@ -0,0 +1,31 @@
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+import sh
+from multiprocessing import cpu_count
+from os.path import join
+
+
+class LibpcreRecipe(Recipe):
+ version = '8.44'
+ url = 'https://ftp.pcre.org/pub/pcre/pcre-{version}.tar.bz2'
+
+ built_libraries = {'libpcre.so': '.libs'}
+
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+
+ with current_directory(self.get_build_dir(arch.arch)):
+ shprint(
+ sh.Command('./configure'),
+ *'''--host=arm-linux-androideabi
+ --disable-cpp --enable-jit --enable-utf8
+ --enable-unicode-properties'''.split(),
+ _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
+
+ def get_lib_dir(self, arch):
+ return join(self.get_build_dir(arch), '.libs')
+
+
+recipe = LibpcreRecipe()
diff --git a/pythonforandroid/recipes/libpq/__init__.py b/pythonforandroid/recipes/libpq/__init__.py
index ee018c9eb..1faed7c59 100644
--- a/pythonforandroid/recipes/libpq/__init__.py
+++ b/pythonforandroid/recipes/libpq/__init__.py
@@ -4,9 +4,15 @@
class LibpqRecipe(Recipe):
- version = '9.5.3'
+ version = '10.12'
url = 'http://ftp.postgresql.org/pub/source/v{version}/postgresql-{version}.tar.bz2'
- depends = [('python2', 'python3crystax')]
+ depends = []
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['USE_DEV_URANDOM'] = '1'
+
+ return env
def should_build(self, arch):
return not os.path.isfile('{}/libpq.a'.format(self.ctx.get_libs_dir(arch.arch)))
@@ -22,4 +28,5 @@ def build_arch(self, arch):
shprint(sh.cp, '-a', 'src/interfaces/libpq/libpq.a',
self.ctx.get_libs_dir(arch.arch))
+
recipe = LibpqRecipe()
diff --git a/pythonforandroid/recipes/librt/__init__.py b/pythonforandroid/recipes/librt/__init__.py
new file mode 100644
index 000000000..6c42490e6
--- /dev/null
+++ b/pythonforandroid/recipes/librt/__init__.py
@@ -0,0 +1,52 @@
+from os import makedirs, remove
+from os.path import exists, join
+import sh
+
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+
+
+class LibRt(Recipe):
+ '''
+ This is a dumb recipe. We may need this because some recipes inserted some
+ flags `-lrt` without our control, case of:
+
+ - :class:`~pythonforandroid.recipes.gevent.GeventRecipe`
+ - :class:`~pythonforandroid.recipes.lxml.LXMLRecipe`
+
+ .. note:: the librt doesn't exist in android but it is integrated into
+ libc, so we create a symbolic link which we will remove when our build
+ finishes'''
+
+ def build_arch(self, arch):
+ libc_path = join(arch.ndk_lib_dir_versioned, 'libc')
+ # Create a temporary folder to add to link path with a fake librt.so:
+ fake_librt_temp_folder = join(
+ self.get_build_dir(arch.arch),
+ "p4a-librt-recipe-tempdir"
+ )
+ if not exists(fake_librt_temp_folder):
+ makedirs(fake_librt_temp_folder)
+
+ # Set symlinks, and make sure to update them on every build run:
+ if exists(join(fake_librt_temp_folder, "librt.so")):
+ remove(join(fake_librt_temp_folder, "librt.so"))
+ shprint(sh.ln, '-sf',
+ libc_path + '.so',
+ join(fake_librt_temp_folder, "librt.so"),
+ )
+ if exists(join(fake_librt_temp_folder, "librt.a")):
+ remove(join(fake_librt_temp_folder, "librt.a"))
+ shprint(sh.ln, '-sf',
+ libc_path + '.a',
+ join(fake_librt_temp_folder, "librt.a"),
+ )
+
+ # Add folder as -L link option for all recipes if not done yet:
+ if fake_librt_temp_folder not in arch.extra_global_link_paths:
+ arch.extra_global_link_paths.append(
+ fake_librt_temp_folder
+ )
+
+
+recipe = LibRt()
diff --git a/pythonforandroid/recipes/libsecp256k1/__init__.py b/pythonforandroid/recipes/libsecp256k1/__init__.py
index a8552577e..f3a2772cf 100644
--- a/pythonforandroid/recipes/libsecp256k1/__init__.py
+++ b/pythonforandroid/recipes/libsecp256k1/__init__.py
@@ -1,4 +1,5 @@
-from pythonforandroid.toolchain import shprint, current_directory
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
from pythonforandroid.recipe import Recipe
from multiprocessing import cpu_count
from os.path import exists
@@ -7,26 +8,25 @@
class LibSecp256k1Recipe(Recipe):
+ built_libraries = {'libsecp256k1.so': '.libs'}
+
url = 'https://github.com/bitcoin-core/secp256k1/archive/master.zip'
def build_arch(self, arch):
- super(LibSecp256k1Recipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
if not exists('configure'):
shprint(sh.Command('./autogen.sh'), _env=env)
shprint(
sh.Command('./configure'),
- '--host=' + arch.toolchain_prefix,
- '--prefix=' + self.ctx.get_python_install_dir(),
+ '--host=' + arch.command_prefix,
+ '--prefix=' + self.ctx.get_python_install_dir(arch.arch),
'--enable-shared',
'--enable-module-recovery',
'--enable-experimental',
'--enable-module-ecdh',
_env=env)
shprint(sh.make, '-j' + str(cpu_count()), _env=env)
- libs = ['.libs/libsecp256k1.so']
- self.install_libs(arch, *libs)
recipe = LibSecp256k1Recipe()
diff --git a/pythonforandroid/recipes/libshine/__init__.py b/pythonforandroid/recipes/libshine/__init__.py
index 280b067a0..32fa9e175 100644
--- a/pythonforandroid/recipes/libshine/__init__.py
+++ b/pythonforandroid/recipes/libshine/__init__.py
@@ -1,7 +1,8 @@
-from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM
-from os.path import exists, join, realpath
-from os import uname
-import glob
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from multiprocessing import cpu_count
+from os.path import realpath
import sh
@@ -9,9 +10,15 @@ class LibShineRecipe(Recipe):
version = 'c72aba9031bde18a0995e7c01c9b53f2e08a0e46'
url = 'https://github.com/toots/shine/archive/{version}.zip'
- def should_build(self, arch):
- build_dir = self.get_build_dir(arch.arch)
- return not exists(join(build_dir, 'lib', 'libshine.a'))
+ built_libraries = {'libshine.so': 'lib'}
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+ # technically, libraries should go to `LDLIBS`, but it seems
+ # that libshine doesn't like so, and it will fail on linking stage
+ env['LDLIBS'] = env['LDLIBS'].replace(' -lm', '')
+ env['LDFLAGS'] += ' -lm'
+ return env
def build_arch(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
@@ -19,13 +26,14 @@ def build_arch(self, arch):
shprint(sh.Command('./bootstrap'))
configure = sh.Command('./configure')
shprint(configure,
- '--host=arm-linux',
+ f'--host={arch.command_prefix}',
'--enable-pic',
- '--disable-shared',
- '--enable-static',
- '--prefix={}'.format(realpath('.')),
+ '--disable-static',
+ '--enable-shared',
+ f'--prefix={realpath(".")}',
_env=env)
- shprint(sh.make, '-j4', _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
shprint(sh.make, 'install', _env=env)
+
recipe = LibShineRecipe()
diff --git a/pythonforandroid/recipes/libsodium/__init__.py b/pythonforandroid/recipes/libsodium/__init__.py
index 95b2d2191..f66fc18e7 100644
--- a/pythonforandroid/recipes/libsodium/__init__.py
+++ b/pythonforandroid/recipes/libsodium/__init__.py
@@ -1,28 +1,35 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import exists, join
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from multiprocessing import cpu_count
import sh
+
class LibsodiumRecipe(Recipe):
- version = '1.0.8'
+ version = '1.0.16'
url = 'https://github.com/jedisct1/libsodium/releases/download/{version}/libsodium-{version}.tar.gz'
- depends = ['python2']
-
- def should_build(self, arch):
- super(LibsodiumRecipe, self).should_build(arch)
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libsodium.so'))
+ depends = []
+ patches = ['size_max_fix.patch']
+ built_libraries = {'libsodium.so': 'src/libsodium/.libs'}
def build_arch(self, arch):
- super(LibsodiumRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
bash = sh.Command('bash')
- shprint(bash, 'configure', '--enable-minimal', '--disable-soname-versions', '--host=arm-linux-androideabi', '--enable-shared', _env=env)
- shprint(sh.make, _env=env)
- shutil.copyfile('src/libsodium/.libs/libsodium.so', join(self.ctx.get_libs_dir(arch.arch), 'libsodium.so'))
+ shprint(
+ bash,
+ 'configure',
+ '--disable-soname-versions',
+ '--host={}'.format(arch.command_prefix),
+ '--enable-shared',
+ _env=env,
+ )
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
def get_recipe_env(self, arch):
- env = super(LibsodiumRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['CFLAGS'] += ' -Os'
return env
+
recipe = LibsodiumRecipe()
diff --git a/pythonforandroid/recipes/libsodium/size_max_fix.patch b/pythonforandroid/recipes/libsodium/size_max_fix.patch
new file mode 100644
index 000000000..c05477c77
--- /dev/null
+++ b/pythonforandroid/recipes/libsodium/size_max_fix.patch
@@ -0,0 +1,12 @@
+diff -urN libsodium-1.0.16.ori/src/libsodium/include/sodium/export.h libsodium-1.0.16/src/libsodium/include/sodium/export.h
+--- libsodium-1.0.16.ori/src/libsodium/include/sodium/export.h 2017-12-12 00:03:07.000000000 +0100
++++ libsodium-1.0.16/src/libsodium/include/sodium/export.h 2018-10-31 09:46:06.051189444 +0100
+@@ -47,6 +47,8 @@
+ # endif
+ #endif
+
++#include <limits.h>
++
+ #define SODIUM_MIN(A, B) ((A) < (B) ? (A) : (B))
+ #define SODIUM_SIZE_MAX SODIUM_MIN(UINT64_MAX, SIZE_MAX)
+
diff --git a/pythonforandroid/recipes/libtorrent/__init__.py b/pythonforandroid/recipes/libtorrent/__init__.py
index 53621f8a4..1086e00fc 100644
--- a/pythonforandroid/recipes/libtorrent/__init__.py
+++ b/pythonforandroid/recipes/libtorrent/__init__.py
@@ -1,73 +1,151 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import join, exists
+from multiprocessing import cpu_count
+from os import listdir, walk
+from os.path import join, basename
+import shutil
+
import sh
+from pythonforandroid.toolchain import Recipe, shprint, current_directory
+
# This recipe builds libtorrent with Python bindings
-# It depends on Boost.Build and the source of several Boost libraries present in BOOST_ROOT,
-# which is all provided by the boost recipe
+# It depends on Boost.Build and the source of several Boost libraries present
+# in BOOST_ROOT, which is all provided by the boost recipe
+
+
+def get_lib_from(search_directory, lib_extension='.so'):
+ '''Scan directories recursively until find any file with the given
+ extension. The default extension to search is ``.so``.'''
+ for root, dirs, files in walk(search_directory):
+ for file in files:
+ if file.endswith(lib_extension):
+ print('get_lib_from: {}\n\t- {}'.format(
+ search_directory, join(root, file)))
+ return join(root, file)
+ return None
+
+
class LibtorrentRecipe(Recipe):
- version = '1.0.9'
- # Don't forget to change the URL when changing the version
- url = 'https://github.com/arvidn/libtorrent/archive/libtorrent-1_0_9.tar.gz'
- depends = ['boost', 'python2']
+ # Todo: make recipe compatible with all p4a architectures
+ '''
+ .. note:: This recipe can be built only against API 21+ and an android
+ ndk >= r19
+
+ .. versionchanged:: 0.6.0
+ Rewrote recipe to support clang's build and boost 1.68. The following
+ changes has been made:
+
+ - Bumped version number to 1.2.0
+ - added python 3 compatibility
+ - new system to detect/copy generated libraries
+
+ .. versionchanged:: 2019.08.09.1.dev0
+
+ - Bumped version number to 1.2.1
+ - Adapted to work with ndk-r19+
+ '''
+ version = '1_2_1'
+ url = 'https://github.com/arvidn/libtorrent/archive/libtorrent-{version}.tar.gz'
+
+ depends = ['boost']
opt_depends = ['openssl']
- patches = ['disable-so-version.patch', 'use-soname-python.patch', 'setup-lib-name.patch']
+ patches = ['disable-so-version.patch',
+ 'use-soname-python.patch',
+ 'setup-lib-name.patch']
+
+ # libtorrent.so is not included because is not a system library
+ generated_libraries = [
+ 'boost_system', 'boost_python{py_version}', 'torrent_rasterbar']
def should_build(self, arch):
- return not ( self.has_libs(arch, 'libboost_python.so', 'libboost_system.so', 'libtorrent_rasterbar.so')
- and self.ctx.has_package('libtorrent', arch.arch) )
+ python_version = self.ctx.python_recipe.version[:3].replace('.', '')
+ libs = ['lib' + lib_name.format(py_version=python_version) +
+ '.so' for lib_name in self.generated_libraries]
+ return not (self.has_libs(arch, *libs) and
+ self.ctx.has_package('libtorrent', arch.arch))
def prebuild_arch(self, arch):
- super(LibtorrentRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
if 'openssl' in recipe.ctx.recipe_build_order:
# Patch boost user-config.jam to use openssl
- self.get_recipe('boost', self.ctx).apply_patch(join(self.get_recipe_dir(), 'user-config-openssl.patch'), arch.arch)
+ self.get_recipe('boost', self.ctx).apply_patch(
+ join(self.get_recipe_dir(), 'user-config-openssl.patch'), arch.arch)
def build_arch(self, arch):
- super(LibtorrentRecipe, self).build_arch(arch)
+ super().build_arch(arch)
env = self.get_recipe_env(arch)
- with current_directory(join(self.get_build_dir(arch.arch), 'bindings/python')):
- # Compile libtorrent with boost libraries and python bindings
+ env['PYTHON_HOST'] = self.ctx.hostpython
+
+ # Define build variables
+ build_dir = self.get_build_dir(arch.arch)
+ ctx_libs_dir = self.ctx.get_libs_dir(arch.arch)
+ encryption = 'openssl' if 'openssl' in recipe.ctx.recipe_build_order else 'built-in'
+ build_args = [
+ '-q',
+ # '-a', # force build, useful to debug the build
+ '-j' + str(cpu_count()),
+ '--debug-configuration', # so we know if our python is detected
+ # '--deprecated-functions=off',
+ 'toolset=clang-{arch}'.format(arch=env['ARCH']),
+ 'abi=aapcs',
+ 'binary-format=elf',
+ 'cxxflags=-std=c++11',
+ 'target-os=android',
+ 'threading=multi',
+ 'link=shared',
+ 'boost-link=shared',
+ 'libtorrent-link=shared',
+ 'runtime-link=shared',
+ 'encryption={}'.format('on' if encryption == 'openssl' else 'off'),
+ 'crypto=' + encryption
+ ]
+ crypto_folder = 'encryption-off'
+ if encryption == 'openssl':
+ crypto_folder = 'crypto-openssl'
+ build_args.extend(['openssl-lib=' + env['OPENSSL_BUILD_PATH'],
+ 'openssl-include=' + env['OPENSSL_INCLUDE']
+ ])
+ build_args.append('release')
+
+ # Compile libtorrent with boost libraries and python bindings
+ with current_directory(join(build_dir, 'bindings/python')):
b2 = sh.Command(join(env['BOOST_ROOT'], 'b2'))
- shprint(b2,
- '-q',
- '-j5',
- 'toolset=gcc-' + env['ARCH'],
- 'target-os=android',
- 'threading=multi',
- 'link=shared',
- 'boost-link=shared',
- 'boost=source',
- 'encryption=openssl' if 'openssl' in recipe.ctx.recipe_build_order else '',
- '--prefix=' + env['CROSSHOME'],
- 'release'
- , _env=env)
- # Common build directories
- build_subdirs = 'gcc-arm/release/boost-link-shared/boost-source'
- if 'openssl' in recipe.ctx.recipe_build_order:
- build_subdirs += '/encryption-openssl'
- build_subdirs += '/libtorrent-python-pic-on/target-os-android/threading-multi/visibility-hidden'
- # Copy the shared libraries into the libs folder
- shutil.copyfile(join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/python/build', build_subdirs, 'libboost_python.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libboost_python.so'))
- shutil.copyfile(join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/system/build', build_subdirs, 'libboost_system.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libboost_system.so'))
- if 'openssl' in recipe.ctx.recipe_build_order:
- shutil.copyfile(join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/date_time/build', build_subdirs, 'libboost_date_time.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libboost_date_time.so'))
- shutil.copyfile(join(self.get_build_dir(arch.arch), 'bin', build_subdirs, 'libtorrent_rasterbar.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libtorrent_rasterbar.so'))
- shutil.copyfile(join(self.get_build_dir(arch.arch), 'bindings/python/bin', build_subdirs, 'libtorrent.so'),
- join(self.ctx.get_site_packages_dir(arch.arch), 'libtorrent.so'))
+ shprint(b2, *build_args, _env=env)
+
+ # Copy only the boost shared libraries into the libs folder. Because
+ # boost build two boost_python libraries, we force to search the lib
+ # into the corresponding build path.
+ b2_build_dir = (
+ 'build/clang-linux-{arch}/release/{encryption}/'
+ 'lt-visibility-hidden/'.format(
+ arch=env['ARCH'], encryption=crypto_folder
+ )
+ )
+ boost_libs_dir = join(env['BOOST_BUILD_PATH'], 'bin.v2/libs')
+ for boost_lib in listdir(boost_libs_dir):
+ lib_path = get_lib_from(join(boost_libs_dir, boost_lib, b2_build_dir))
+ if lib_path:
+ lib_name = basename(lib_path)
+ shutil.copyfile(lib_path, join(ctx_libs_dir, lib_name))
+
+ # Copy libtorrent shared libraries into the right places
+ system_libtorrent = get_lib_from(join(build_dir, 'bin'))
+ if system_libtorrent:
+ shutil.copyfile(system_libtorrent,
+ join(ctx_libs_dir, 'libtorrent_rasterbar.so'))
+
+ python_libtorrent = get_lib_from(join(build_dir, 'bindings/python/bin'))
+ shutil.copyfile(python_libtorrent,
+ join(self.ctx.get_site_packages_dir(arch), 'libtorrent.so'))
def get_recipe_env(self, arch):
- env = super(LibtorrentRecipe, self).get_recipe_env(arch)
- # Copy environment from boost recipe
- env.update(self.get_recipe('boost', self.ctx).get_recipe_env(arch))
+ # Use environment from boost recipe, cause we use b2 tool from boost
+ env = self.get_recipe('boost', self.ctx).get_recipe_env(arch)
if 'openssl' in recipe.ctx.recipe_build_order:
r = self.get_recipe('openssl', self.ctx)
env['OPENSSL_BUILD_PATH'] = r.get_build_dir(arch.arch)
+ env['OPENSSL_INCLUDE'] = join(r.get_build_dir(arch.arch), 'include')
env['OPENSSL_VERSION'] = r.version
return env
+
recipe = LibtorrentRecipe()
diff --git a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch b/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
index ec3985af1..4b688be35 100644
--- a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
+++ b/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
@@ -1,20 +1,20 @@
---- libtorrent/bindings/python/setup.py 2016-02-28 08:28:49.000000000 +0100
-+++ patch/bindings/python/setup.py 2016-07-12 12:03:05.256455888 +0200
-@@ -97,7 +97,7 @@
- source_list = os.listdir(os.path.join(os.path.dirname(__file__), "src"))
- source_list = [os.path.join("src", s) for s in source_list if s.endswith(".cpp")]
-
-- ext = [Extension('libtorrent',
-+ ext = [Extension('libtorrent_rasterbar',
- sources = source_list,
- language='c++',
- include_dirs = parse_cmd(extra_cmd, '-I'),
-@@ -107,7 +107,7 @@
- + target_specific(),
- libraries = ['torrent-rasterbar'] + parse_cmd(extra_cmd, '-l'))]
-
--setup(name = 'python-libtorrent',
-+setup(name = 'libtorrent',
- version = '1.0.9',
- author = 'Arvid Norberg',
- author_email = 'arvid@libtorrent.org',
+--- libtorrent/bindings/python/setup.py.orig 2018-11-26 22:21:48.772142135 +0100
++++ libtorrent/bindings/python/setup.py 2018-11-26 22:23:23.092141235 +0100
+@@ -167,7 +167,7 @@
+ extra_compile = flags.parse(extra_cmd)
+
+ ext = [Extension(
+- 'libtorrent',
++ 'libtorrent_rasterbar',
+ sources=sorted(source_list),
+ language='c++',
+ include_dirs=flags.include_dirs,
+@@ -178,7 +178,7 @@
+ ]
+
+ setup(
+- name='python-libtorrent',
++ name='libtorrent',
+ version='1.2.1',
+ author='Arvid Norberg',
+ author_email='arvid@libtorrent.org',
diff --git a/pythonforandroid/recipes/libtorrent/use-soname-python.patch b/pythonforandroid/recipes/libtorrent/use-soname-python.patch
index f78553d26..145622071 100644
--- a/pythonforandroid/recipes/libtorrent/use-soname-python.patch
+++ b/pythonforandroid/recipes/libtorrent/use-soname-python.patch
@@ -1,11 +1,11 @@
---- libtorrent/bindings/python/Jamfile 2016-01-17 23:52:45.000000000 +0100
-+++ libtorrent-patch/bindings/python/Jamfile 2016-02-09 17:11:44.261578000 +0100
-@@ -35,7 +35,7 @@
-
- if ( <toolset>gcc in $(properties) )
- {
-- result += <linkflags>-Wl,-Bsymbolic ;
-+ result += <linkflags>-Wl,-soname=libtorrent.so,-Bsymbolic ;
- }
- }
+--- libtorrent/bindings/python/Jamfile.orig 2018-12-07 16:46:50.851838981 +0100
++++ libtorrent/bindings/python/Jamfile 2018-12-07 16:49:09.099837663 +0100
+@@ -113,7 +113,7 @@
+
+ if ( <toolset>gcc in $(properties) )
+ {
+- result += <linkflags>-Wl,-Bsymbolic ;
++ result += <linkflags>-Wl,-soname=libtorrent.so,-Bsymbolic ;
+ }
+ }
diff --git a/pythonforandroid/recipes/libtorrent/user-config-openssl.patch b/pythonforandroid/recipes/libtorrent/user-config-openssl.patch
index eea9b3f64..6a54071f4 100644
--- a/pythonforandroid/recipes/libtorrent/user-config-openssl.patch
+++ b/pythonforandroid/recipes/libtorrent/user-config-openssl.patch
@@ -1,25 +1,21 @@
---- boost/user-config.jam 2016-03-02 14:31:41.280414820 +0100
-+++ boost-patch/user-config.jam 2016-03-02 14:32:08.904384741 +0100
-@@ -6,6 +6,7 @@
- local TOOLCHAIN_PREFIX = [ os.environ TOOLCHAIN_PREFIX ] ;
- local ARCH = [ os.environ ARCH ] ;
- local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;
+--- boost/user-config.jam.orig 2018-12-07 14:16:45.911924859 +0100
++++ boost/user-config.jam 2018-12-07 14:20:16.243922853 +0100
+@@ -9,6 +9,8 @@
+ local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ;
+ local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ;
+ local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ;
+local OPENSSL_BUILD_PATH = [ os.environ OPENSSL_BUILD_PATH ] ;
++local OPENSSL_VERSION = [ os.environ OPENSSL_VERSION ] ;
- using gcc : $(ARCH) : $(TOOLCHAIN_PREFIX)-g++ :
- <architecture>$(ARCH)
-@@ -20,9 +21,14 @@
- <compileflags>-I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/include
- <compileflags>-I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)/include
- <compileflags>-I$(PYTHON_ROOT)/include/python2.7
-+<compileflags>-I$(OPENSSL_BUILD_PATH)/include
-+<compileflags>-I$(OPENSSL_BUILD_PATH)/include/openssl
- <linkflags>--sysroot=$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)
- <linkflags>-L$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)
- <linkflags>-L$(PYTHON_ROOT)/lib
+ #using clang : $(ARCH) : $(ANDROID_BINARIES_PATH)/clang++ :
+ #<archiver>$(ANDROID_BINARIES_PATH)/llvm-ar
+@@ -56,6 +58,9 @@
+ <linkflags>-Wl,-z,relro
+ <linkflags>-Wl,-z,now
+ <linkflags>-lc++_shared
+<linkflags>-L$(OPENSSL_BUILD_PATH)
- <linkflags>-lgnustl_shared
- <linkflags>-lpython2.7
+<linkflags>-lcrypto$(OPENSSL_VERSION)
+<linkflags>-lssl$(OPENSSL_VERSION)
- ;
+ <linkflags>-L$(PYTHON_ROOT)
+ <linkflags>-lpython$(PYTHON_LINK_VERSION)
+ <linkflags>-Wl,-O1
diff --git a/pythonforandroid/recipes/libtribler/__init__.py b/pythonforandroid/recipes/libtribler/__init__.py
index 856aea3a2..134ed9e33 100644
--- a/pythonforandroid/recipes/libtribler/__init__.py
+++ b/pythonforandroid/recipes/libtribler/__init__.py
@@ -1,10 +1,12 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
"""
Privacy with BitTorrent and resilient to shut down
http://www.tribler.org
"""
+
+
class LibTriblerRecipe(PythonRecipe):
version = 'devel'
@@ -12,9 +14,11 @@ class LibTriblerRecipe(PythonRecipe):
url = 'git+https://github.com/Tribler/tribler.git'
depends = ['apsw', 'cryptography', 'ffmpeg', 'libsodium', 'libtorrent', 'm2crypto',
- 'netifaces', 'openssl', 'pil', 'pycrypto', 'pyleveldb', 'python2', 'twisted',
+ 'netifaces', 'openssl', 'pil', 'pycrypto', 'pyleveldb', 'twisted',
]
+ conflicts = ['python3']
+
python_depends = ['chardet', 'cherrypy', 'configobj', 'decorator', 'feedparser',
'libnacl', 'pyasn1', 'requests', 'six',
]
@@ -22,4 +26,4 @@ class LibTriblerRecipe(PythonRecipe):
site_packages_name = 'Tribler'
-recipe = LibTriblerRecipe()
\ No newline at end of file
+recipe = LibTriblerRecipe()
diff --git a/pythonforandroid/recipes/libvorbis/__init__.py b/pythonforandroid/recipes/libvorbis/__init__.py
new file mode 100644
index 000000000..bbbca6f34
--- /dev/null
+++ b/pythonforandroid/recipes/libvorbis/__init__.py
@@ -0,0 +1,36 @@
+from pythonforandroid.recipe import NDKRecipe
+from pythonforandroid.toolchain import current_directory, shprint
+from os.path import join
+import sh
+
+
+class VorbisRecipe(NDKRecipe):
+ version = '1.3.6'
+ url = 'http://downloads.xiph.org/releases/vorbis/libvorbis-{version}.tar.gz'
+ opt_depends = ['libogg']
+
+ generated_libraries = ['libvorbis.so', 'libvorbisfile.so', 'libvorbisenc.so']
+
+ def get_recipe_env(self, arch=None):
+ env = super().get_recipe_env(arch)
+ ogg = self.get_recipe('libogg', self.ctx)
+ env['CFLAGS'] += ' -I{}'.format(join(ogg.get_build_dir(arch.arch), 'include'))
+ return env
+
+ def build_arch(self, arch):
+ with current_directory(self.get_build_dir(arch.arch)):
+ env = self.get_recipe_env(arch)
+ flags = [
+ '--host=' + arch.command_prefix,
+ ]
+ configure = sh.Command('./configure')
+ shprint(configure, *flags, _env=env)
+ shprint(sh.make, _env=env)
+ self.install_libs(
+ arch,
+ join('lib', '.libs', 'libvorbis.so'),
+ join('lib', '.libs', 'libvorbisfile.so'),
+ join('lib', '.libs', 'libvorbisenc.so'))
+
+
+recipe = VorbisRecipe()
diff --git a/pythonforandroid/recipes/libvpx/__init__.py b/pythonforandroid/recipes/libvpx/__init__.py
new file mode 100644
index 000000000..0173e366d
--- /dev/null
+++ b/pythonforandroid/recipes/libvpx/__init__.py
@@ -0,0 +1,59 @@
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.toolchain import current_directory, shprint
+from os.path import join, realpath
+from multiprocessing import cpu_count
+import sh
+
+
+TARGETS = {
+ 'armeabi-v7a': 'armv7-android-gcc',
+ 'arm64-v8a': 'arm64-android-gcc',
+ 'x86': 'x86-android-gcc',
+ 'x86_64': 'x86_64-android-gcc',
+}
+
+
+class VPXRecipe(Recipe):
+ version = '1.11.0'
+ url = 'https://github.com/webmproject/libvpx/archive/v{version}.tar.gz'
+
+ patches = [
+ # See https://git.io/Jq50q
+ join('patches', '0001-android-force-neon-runtime.patch'),
+ ]
+
+ def get_recipe_env(self, arch=None):
+ env = super().get_recipe_env(arch)
+ env['CXXFLAGS'] += f' -I{self.ctx.ndk.libcxx_include_dir}'
+ return env
+
+ def build_arch(self, arch):
+ with current_directory(self.get_build_dir(arch.arch)):
+ env = self.get_recipe_env(arch)
+ flags = [
+ '--target=' + TARGETS[arch.arch],
+ '--enable-pic',
+ '--enable-vp8',
+ '--enable-vp9',
+ '--enable-static',
+ '--enable-small',
+ '--disable-shared',
+ '--disable-examples',
+ '--disable-unit-tests',
+ '--disable-tools',
+ '--disable-docs',
+ '--disable-install-docs',
+ '--disable-realtime-only',
+ f'--prefix={realpath(".")}',
+ ]
+
+ if arch.arch == 'armeabi-v7a':
+ flags.append('--disable-neon-asm')
+
+ configure = sh.Command('./configure')
+ shprint(configure, *flags, _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
+ shprint(sh.make, 'install', _env=env)
+
+
+recipe = VPXRecipe()
diff --git a/pythonforandroid/recipes/libvpx/patches/0001-android-force-neon-runtime.patch b/pythonforandroid/recipes/libvpx/patches/0001-android-force-neon-runtime.patch
new file mode 100644
index 000000000..220800d77
--- /dev/null
+++ b/pythonforandroid/recipes/libvpx/patches/0001-android-force-neon-runtime.patch
@@ -0,0 +1,25 @@
+diff -u -r ../libvpx-1.6.1/vpx_ports/arm_cpudetect.c ./vpx_ports/arm_cpudetect.c
+--- ../libvpx-1.6.1/vpx_ports/arm_cpudetect.c 2017-01-12 21:27:27.000000000 +0100
++++ ./vpx_ports/arm_cpudetect.c 2017-01-29 23:55:05.399283897 +0100
+@@ -92,20 +92,17 @@
+ }
+
+ #elif defined(__ANDROID__) /* end _MSC_VER */
+-#include <cpu-features.h>
+
+ int arm_cpu_caps(void) {
+ int flags;
+ int mask;
+- uint64_t features;
+ if (!arm_cpu_env_flags(&flags)) {
+ return flags;
+ }
+ mask = arm_cpu_env_mask();
+- features = android_getCpuFeatures();
+
+ #if HAVE_NEON || HAVE_NEON_ASM
+- if (features & ANDROID_CPU_ARM_FEATURE_NEON) flags |= HAS_NEON;
++ flags |= HAS_NEON;
+ #endif /* HAVE_NEON || HAVE_NEON_ASM */
+ return flags & mask;
+ }
diff --git a/pythonforandroid/recipes/libwebp/__init__.py b/pythonforandroid/recipes/libwebp/__init__.py
new file mode 100644
index 000000000..aacd48588
--- /dev/null
+++ b/pythonforandroid/recipes/libwebp/__init__.py
@@ -0,0 +1,50 @@
+from multiprocessing import cpu_count
+from os.path import join
+
+import sh
+
+from pythonforandroid.util import current_directory, ensure_dir
+from pythonforandroid.toolchain import shprint
+from pythonforandroid.recipe import Recipe
+
+
+class LibwebpRecipe(Recipe):
+ version = '1.1.0'
+ url = 'https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-{version}.tar.gz' # noqa
+ depends = []
+ built_libraries = {
+ 'libwebp.so': 'installation/lib',
+ 'libwebpdecoder.so': 'installation/lib',
+ 'libwebpdemux.so': 'installation/lib',
+ 'libwebpmux.so': 'installation/lib',
+ }
+
+ def build_arch(self, arch):
+ source_dir = self.get_build_dir(arch.arch)
+ build_dir = join(source_dir, 'build')
+ install_dir = join(source_dir, 'installation')
+ toolchain_file = join(
+ self.ctx.ndk_dir, 'build', 'cmake', 'android.toolchain.cmake',
+ )
+
+ ensure_dir(build_dir)
+ with current_directory(build_dir):
+ env = self.get_recipe_env(arch)
+ shprint(sh.cmake, source_dir,
+ f'-DANDROID_ABI={arch.arch}',
+ f'-DANDROID_NATIVE_API_LEVEL={self.ctx.ndk_api}',
+
+ f'-DCMAKE_TOOLCHAIN_FILE={toolchain_file}',
+ f'-DCMAKE_INSTALL_PREFIX={install_dir}',
+ '-DCMAKE_BUILD_TYPE=Release',
+
+ '-DBUILD_SHARED_LIBS=1',
+
+ _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), _env=env)
+ # We make the install because this way we will have
+ # all the includes and libraries in one place
+ shprint(sh.make, 'install', _env=env)
+
+
+recipe = LibwebpRecipe()
diff --git a/pythonforandroid/recipes/libx264/__init__.py b/pythonforandroid/recipes/libx264/__init__.py
index 2bab2b43e..63413096f 100644
--- a/pythonforandroid/recipes/libx264/__init__.py
+++ b/pythonforandroid/recipes/libx264/__init__.py
@@ -1,33 +1,30 @@
-from pythonforandroid.toolchain import Recipe, shprint, current_directory, ArchARM
-from os.path import exists, join, realpath
-from os import uname
-import glob
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from multiprocessing import cpu_count
+from os.path import realpath
import sh
class LibX264Recipe(Recipe):
- version = 'x264-snapshot-20171218-2245-stable' # using mirror url since can't use ftp
- url = 'http://mirror.yandex.ru/mirrors/ftp.videolan.org/x264/snapshots/{version}.tar.bz2'
-
- def should_build(self, arch):
- build_dir = self.get_build_dir(arch.arch)
- return not exists(join(build_dir, 'lib', 'libx264.a'))
+ version = '5db6aa6cab1b146e07b60cc1736a01f21da01154' # commit of latest known stable version
+ url = 'https://code.videolan.org/videolan/x264/-/archive/{version}/x264-{version}.zip'
+ built_libraries = {'libx264.a': 'lib'}
def build_arch(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
env = self.get_recipe_env(arch)
configure = sh.Command('./configure')
shprint(configure,
- '--cross-prefix=arm-linux-androideabi-',
- '--host=arm-linux',
+ f'--host={arch.command_prefix}',
'--disable-asm',
'--disable-cli',
'--enable-pic',
- '--disable-shared',
'--enable-static',
'--prefix={}'.format(realpath('.')),
_env=env)
- shprint(sh.make, '-j4', _env=env)
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
shprint(sh.make, 'install', _env=env)
+
recipe = LibX264Recipe()
diff --git a/pythonforandroid/recipes/libxml2/__init__.py b/pythonforandroid/recipes/libxml2/__init__.py
new file mode 100644
index 000000000..100c528dc
--- /dev/null
+++ b/pythonforandroid/recipes/libxml2/__init__.py
@@ -0,0 +1,53 @@
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from os.path import exists
+import sh
+
+
+class Libxml2Recipe(Recipe):
+ version = '2.9.12'
+ url = 'http://xmlsoft.org/sources/libxml2-{version}.tar.gz'
+ depends = []
+ patches = ['add-glob.c.patch']
+ built_libraries = {'libxml2.a': '.libs'}
+
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+ with current_directory(self.get_build_dir(arch.arch)):
+
+ if not exists('configure'):
+ shprint(sh.Command('./autogen.sh'), _env=env)
+ shprint(sh.Command('autoreconf'), '-vif', _env=env)
+ build_arch = shprint(
+ sh.gcc, '-dumpmachine').stdout.decode('utf-8').split('\n')[0]
+ shprint(sh.Command('./configure'),
+ '--build=' + build_arch,
+ '--host=' + arch.command_prefix,
+ '--target=' + arch.command_prefix,
+ '--without-modules',
+ '--without-legacy',
+ '--without-history',
+ '--without-debug',
+ '--without-docbook',
+ '--without-python',
+ '--without-threads',
+ '--without-iconv',
+ '--without-lzma',
+ '--disable-shared',
+ '--enable-static',
+ _env=env)
+
+ # Ensure we only build libxml2.la as if we do everything
+ # we'll need the glob dependency which is a big headache
+ shprint(sh.make, "libxml2.la", _env=env)
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['CONFIG_SHELL'] = '/bin/bash'
+ env['SHELL'] = '/bin/bash'
+ env['CC'] += ' -I' + self.get_build_dir(arch.arch)
+ return env
+
+
+recipe = Libxml2Recipe()
diff --git a/pythonforandroid/recipes/libxml2/add-glob.c.patch b/pythonforandroid/recipes/libxml2/add-glob.c.patch
new file mode 100644
index 000000000..776c0c4d5
--- /dev/null
+++ b/pythonforandroid/recipes/libxml2/add-glob.c.patch
@@ -0,0 +1,1038 @@
+From c97da18834aa41637e3e550bccb70bd2dd0ca3b9 Mon Sep 17 00:00:00 2001
+From: Zachary Goldberg <zach@zachgoldberg.com>
+Date: Wed, 20 Apr 2016 21:21:52 -0700
+Subject: [PATCH] Add glob
+
+---
+ glob.c | 906 ++++++++++++++++++++++++++++++++
+ glob.h | 105 ++++
+ 2 files changed, 1011 insertions(+)
+ create mode 100644 glob.c
+ create mode 100644 glob.h
+
+diff --git a/glob.c b/glob.c
+new file mode 100644
+index 0000000..cec80ed
+--- /dev/null
++++ b/glob.c
+@@ -0,0 +1,906 @@
++/*
++ * Natanael Arndt, 2011: removed collate.h dependencies
++ * (my changes are trivial)
++ *
++ * Copyright (c) 1989, 1993
++ * The Regents of the University of California. All rights reserved.
++ *
++ * This code is derived from software contributed to Berkeley by
++ * Guido van Rossum.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ * 4. Neither the name of the University nor the names of its contributors
++ * may be used to endorse or promote products derived from this software
++ * without specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
++ * SUCH DAMAGE.
++ */
++
++#if defined(LIBC_SCCS) && !defined(lint)
++static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93";
++#endif /* LIBC_SCCS and not lint */
++#include <sys/cdefs.h>
++__FBSDID("$FreeBSD$");
++
++/*
++ * glob(3) -- a superset of the one defined in POSIX 1003.2.
++ *
++ * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
++ *
++ * Optional extra services, controlled by flags not defined by POSIX:
++ *
++ * GLOB_QUOTE:
++ * Escaping convention: \ inhibits any special meaning the following
++ * character might have (except \ at end of string is retained).
++ * GLOB_MAGCHAR:
++ * Set in gl_flags if pattern contained a globbing character.
++ * GLOB_NOMAGIC:
++ * Same as GLOB_NOCHECK, but it will only append pattern if it did
++ * not contain any magic characters. [Used in csh style globbing]
++ * GLOB_ALTDIRFUNC:
++ * Use alternately specified directory access functions.
++ * GLOB_TILDE:
++ * expand ~user/foo to the /home/dir/of/user/foo
++ * GLOB_BRACE:
++ * expand {1,2}{a,b} to 1a 1b 2a 2b
++ * gl_matchc:
++ * Number of matches in the current invocation of glob.
++ */
++
++/*
++ * Some notes on multibyte character support:
++ * 1. Patterns with illegal byte sequences match nothing - even if
++ * GLOB_NOCHECK is specified.
++ * 2. Illegal byte sequences in filenames are handled by treating them as
++ * single-byte characters with a value of the first byte of the sequence
++ * cast to wchar_t.
++ * 3. State-dependent encodings are not currently supported.
++ */
++
++#include <sys/param.h>
++#include <sys/stat.h>
++
++#include <ctype.h>
++#include <dirent.h>
++#include <errno.h>
++#include <glob.h>
++#include <limits.h>
++#include <pwd.h>
++#include <stdint.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++#include <wchar.h>
++
++#define DOLLAR '$'
++#define DOT '.'
++#define EOS '\0'
++#define LBRACKET '['
++#define NOT '!'
++#define QUESTION '?'
++#define QUOTE '\\'
++#define RANGE '-'
++#define RBRACKET ']'
++#define SEP '/'
++#define STAR '*'
++#define TILDE '~'
++#define UNDERSCORE '_'
++#define LBRACE '{'
++#define RBRACE '}'
++#define SLASH '/'
++#define COMMA ','
++
++#ifndef DEBUG
++
++#define M_QUOTE 0x8000000000ULL
++#define M_PROTECT 0x4000000000ULL
++#define M_MASK 0xffffffffffULL
++#define M_CHAR 0x00ffffffffULL
++
++typedef uint_fast64_t Char;
++
++#else
++
++#define M_QUOTE 0x80
++#define M_PROTECT 0x40
++#define M_MASK 0xff
++#define M_CHAR 0x7f
++
++typedef char Char;
++
++#endif
++
++
++#define CHAR(c) ((Char)((c)&M_CHAR))
++#define META(c) ((Char)((c)|M_QUOTE))
++#define M_ALL META('*')
++#define M_END META(']')
++#define M_NOT META('!')
++#define M_ONE META('?')
++#define M_RNG META('-')
++#define M_SET META('[')
++#define ismeta(c) (((c)&M_QUOTE) != 0)
++
++
++static int compare(const void *, const void *);
++static int g_Ctoc(const Char *, char *, size_t);
++static int g_lstat(Char *, struct stat *, glob_t *);
++static DIR *g_opendir(Char *, glob_t *);
++static const Char *g_strchr(const Char *, wchar_t);
++#ifdef notdef
++static Char *g_strcat(Char *, const Char *);
++#endif
++static int g_stat(Char *, struct stat *, glob_t *);
++static int glob0(const Char *, glob_t *, size_t *);
++static int glob1(Char *, glob_t *, size_t *);
++static int glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *);
++static int glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *);
++static int globextend(const Char *, glob_t *, size_t *);
++static const Char *
++ globtilde(const Char *, Char *, size_t, glob_t *);
++static int globexp1(const Char *, glob_t *, size_t *);
++static int globexp2(const Char *, const Char *, glob_t *, int *, size_t *);
++static int match(Char *, Char *, Char *);
++#ifdef DEBUG
++static void qprintf(const char *, Char *);
++#endif
++
++int
++glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob)
++{
++ const char *patnext;
++ size_t limit;
++ Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot;
++ mbstate_t mbs;
++ wchar_t wc;
++ size_t clen;
++
++ patnext = pattern;
++ if (!(flags & GLOB_APPEND)) {
++ pglob->gl_pathc = 0;
++ pglob->gl_pathv = NULL;
++ if (!(flags & GLOB_DOOFFS))
++ pglob->gl_offs = 0;
++ }
++ if (flags & GLOB_LIMIT) {
++ limit = pglob->gl_matchc;
++ if (limit == 0)
++ limit = ARG_MAX;
++ } else
++ limit = 0;
++ pglob->gl_flags = flags & ~GLOB_MAGCHAR;
++ pglob->gl_errfunc = errfunc;
++ pglob->gl_matchc = 0;
++
++ bufnext = patbuf;
++ bufend = bufnext + MAXPATHLEN - 1;
++ if (flags & GLOB_NOESCAPE) {
++ memset(&mbs, 0, sizeof(mbs));
++ while (bufend - bufnext >= MB_CUR_MAX) {
++ clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
++ if (clen == (size_t)-1 || clen == (size_t)-2)
++ return (GLOB_NOMATCH);
++ else if (clen == 0)
++ break;
++ *bufnext++ = wc;
++ patnext += clen;
++ }
++ } else {
++ /* Protect the quoted characters. */
++ memset(&mbs, 0, sizeof(mbs));
++ while (bufend - bufnext >= MB_CUR_MAX) {
++ if (*patnext == QUOTE) {
++ if (*++patnext == EOS) {
++ *bufnext++ = QUOTE | M_PROTECT;
++ continue;
++ }
++ prot = M_PROTECT;
++ } else
++ prot = 0;
++ clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
++ if (clen == (size_t)-1 || clen == (size_t)-2)
++ return (GLOB_NOMATCH);
++ else if (clen == 0)
++ break;
++ *bufnext++ = wc | prot;
++ patnext += clen;
++ }
++ }
++ *bufnext = EOS;
++
++ if (flags & GLOB_BRACE)
++ return globexp1(patbuf, pglob, &limit);
++ else
++ return glob0(patbuf, pglob, &limit);
++}
++
++/*
++ * Expand recursively a glob {} pattern. When there is no more expansion
++ * invoke the standard globbing routine to glob the rest of the magic
++ * characters
++ */
++static int
++globexp1(const Char *pattern, glob_t *pglob, size_t *limit)
++{
++ const Char* ptr = pattern;
++ int rv;
++
++ /* Protect a single {}, for find(1), like csh */
++ if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
++ return glob0(pattern, pglob, limit);
++
++ while ((ptr = g_strchr(ptr, LBRACE)) != NULL)
++ if (!globexp2(ptr, pattern, pglob, &rv, limit))
++ return rv;
++
++ return glob0(pattern, pglob, limit);
++}
++
++
++/*
++ * Recursive brace globbing helper. Tries to expand a single brace.
++ * If it succeeds then it invokes globexp1 with the new pattern.
++ * If it fails then it tries to glob the rest of the pattern and returns.
++ */
++static int
++globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit)
++{
++ int i;
++ Char *lm, *ls;
++ const Char *pe, *pm, *pm1, *pl;
++ Char patbuf[MAXPATHLEN];
++
++ /* copy part up to the brace */
++ for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
++ continue;
++ *lm = EOS;
++ ls = lm;
++
++ /* Find the balanced brace */
++ for (i = 0, pe = ++ptr; *pe; pe++)
++ if (*pe == LBRACKET) {
++ /* Ignore everything between [] */
++ for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
++ continue;
++ if (*pe == EOS) {
++ /*
++ * We could not find a matching RBRACKET.
++ * Ignore and just look for RBRACE
++ */
++ pe = pm;
++ }
++ }
++ else if (*pe == LBRACE)
++ i++;
++ else if (*pe == RBRACE) {
++ if (i == 0)
++ break;
++ i--;
++ }
++
++ /* Non matching braces; just glob the pattern */
++ if (i != 0 || *pe == EOS) {
++ *rv = glob0(patbuf, pglob, limit);
++ return 0;
++ }
++
++ for (i = 0, pl = pm = ptr; pm <= pe; pm++)
++ switch (*pm) {
++ case LBRACKET:
++ /* Ignore everything between [] */
++ for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++)
++ continue;
++ if (*pm == EOS) {
++ /*
++ * We could not find a matching RBRACKET.
++ * Ignore and just look for RBRACE
++ */
++ pm = pm1;
++ }
++ break;
++
++ case LBRACE:
++ i++;
++ break;
++
++ case RBRACE:
++ if (i) {
++ i--;
++ break;
++ }
++ /* FALLTHROUGH */
++ case COMMA:
++ if (i && *pm == COMMA)
++ break;
++ else {
++ /* Append the current string */
++ for (lm = ls; (pl < pm); *lm++ = *pl++)
++ continue;
++ /*
++ * Append the rest of the pattern after the
++ * closing brace
++ */
++ for (pl = pe + 1; (*lm++ = *pl++) != EOS;)
++ continue;
++
++ /* Expand the current pattern */
++#ifdef DEBUG
++ qprintf("globexp2:", patbuf);
++#endif
++ *rv = globexp1(patbuf, pglob, limit);
++
++ /* move after the comma, to the next string */
++ pl = pm + 1;
++ }
++ break;
++
++ default:
++ break;
++ }
++ *rv = 0;
++ return 0;
++}
++
++
++
++/*
++ * expand tilde from the passwd file.
++ */
++static const Char *
++globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)
++{
++ struct passwd *pwd;
++ char *h;
++ const Char *p;
++ Char *b, *eb;
++
++ if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))
++ return pattern;
++
++ /*
++ * Copy up to the end of the string or /
++ */
++ eb = &patbuf[patbuf_len - 1];
++ for (p = pattern + 1, h = (char *) patbuf;
++ h < (char *)eb && *p && *p != SLASH; *h++ = *p++)
++ continue;
++
++ *h = EOS;
++
++ if (((char *) patbuf)[0] == EOS) {
++ /*
++ * handle a plain ~ or ~/ by expanding $HOME first (iff
++ * we're not running setuid or setgid) and then trying
++ * the password file
++ */
++ if (issetugid() != 0 ||
++ (h = getenv("HOME")) == NULL) {
++ if (((h = getlogin()) != NULL &&
++ (pwd = getpwnam(h)) != NULL) ||
++ (pwd = getpwuid(getuid())) != NULL)
++ h = pwd->pw_dir;
++ else
++ return pattern;
++ }
++ }
++ else {
++ /*
++ * Expand a ~user
++ */
++ if ((pwd = getpwnam((char*) patbuf)) == NULL)
++ return pattern;
++ else
++ h = pwd->pw_dir;
++ }
++
++ /* Copy the home directory */
++ for (b = patbuf; b < eb && *h; *b++ = *h++)
++ continue;
++
++ /* Append the rest of the pattern */
++ while (b < eb && (*b++ = *p++) != EOS)
++ continue;
++ *b = EOS;
++
++ return patbuf;
++}
++
++
++/*
++ * The main glob() routine: compiles the pattern (optionally processing
++ * quotes), calls glob1() to do the real pattern matching, and finally
++ * sorts the list (unless unsorted operation is requested). Returns 0
++ * if things went well, nonzero if errors occurred.
++ */
++static int
++glob0(const Char *pattern, glob_t *pglob, size_t *limit)
++{
++ const Char *qpatnext;
++ int err;
++ size_t oldpathc;
++ Char *bufnext, c, patbuf[MAXPATHLEN];
++
++ qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob);
++ oldpathc = pglob->gl_pathc;
++ bufnext = patbuf;
++
++ /* We don't need to check for buffer overflow any more. */
++ while ((c = *qpatnext++) != EOS) {
++ switch (c) {
++ case LBRACKET:
++ c = *qpatnext;
++ if (c == NOT)
++ ++qpatnext;
++ if (*qpatnext == EOS ||
++ g_strchr(qpatnext+1, RBRACKET) == NULL) {
++ *bufnext++ = LBRACKET;
++ if (c == NOT)
++ --qpatnext;
++ break;
++ }
++ *bufnext++ = M_SET;
++ if (c == NOT)
++ *bufnext++ = M_NOT;
++ c = *qpatnext++;
++ do {
++ *bufnext++ = CHAR(c);
++ if (*qpatnext == RANGE &&
++ (c = qpatnext[1]) != RBRACKET) {
++ *bufnext++ = M_RNG;
++ *bufnext++ = CHAR(c);
++ qpatnext += 2;
++ }
++ } while ((c = *qpatnext++) != RBRACKET);
++ pglob->gl_flags |= GLOB_MAGCHAR;
++ *bufnext++ = M_END;
++ break;
++ case QUESTION:
++ pglob->gl_flags |= GLOB_MAGCHAR;
++ *bufnext++ = M_ONE;
++ break;
++ case STAR:
++ pglob->gl_flags |= GLOB_MAGCHAR;
++ /* collapse adjacent stars to one,
++ * to avoid exponential behavior
++ */
++ if (bufnext == patbuf || bufnext[-1] != M_ALL)
++ *bufnext++ = M_ALL;
++ break;
++ default:
++ *bufnext++ = CHAR(c);
++ break;
++ }
++ }
++ *bufnext = EOS;
++#ifdef DEBUG
++ qprintf("glob0:", patbuf);
++#endif
++
++ if ((err = glob1(patbuf, pglob, limit)) != 0)
++ return(err);
++
++ /*
++ * If there was no match we are going to append the pattern
++ * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified
++ * and the pattern did not contain any magic characters
++ * GLOB_NOMAGIC is there just for compatibility with csh.
++ */
++ if (pglob->gl_pathc == oldpathc) {
++ if (((pglob->gl_flags & GLOB_NOCHECK) ||
++ ((pglob->gl_flags & GLOB_NOMAGIC) &&
++ !(pglob->gl_flags & GLOB_MAGCHAR))))
++ return(globextend(pattern, pglob, limit));
++ else
++ return(GLOB_NOMATCH);
++ }
++ if (!(pglob->gl_flags & GLOB_NOSORT))
++ qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
++ pglob->gl_pathc - oldpathc, sizeof(char *), compare);
++ return(0);
++}
++
++static int
++compare(const void *p, const void *q)
++{
++ return(strcmp(*(char **)p, *(char **)q));
++}
++
++static int
++glob1(Char *pattern, glob_t *pglob, size_t *limit)
++{
++ Char pathbuf[MAXPATHLEN];
++
++ /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
++ if (*pattern == EOS)
++ return(0);
++ return(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1,
++ pattern, pglob, limit));
++}
++
++/*
++ * The functions glob2 and glob3 are mutually recursive; there is one level
++ * of recursion for each segment in the pattern that contains one or more
++ * meta characters.
++ */
++static int
++glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern,
++ glob_t *pglob, size_t *limit)
++{
++ struct stat sb;
++ Char *p, *q;
++ int anymeta;
++
++ /*
++ * Loop over pattern segments until end of pattern or until
++ * segment with meta character found.
++ */
++ for (anymeta = 0;;) {
++ if (*pattern == EOS) { /* End of pattern? */
++ *pathend = EOS;
++ if (g_lstat(pathbuf, &sb, pglob))
++ return(0);
++
++ if (((pglob->gl_flags & GLOB_MARK) &&
++ pathend[-1] != SEP) && (S_ISDIR(sb.st_mode)
++ || (S_ISLNK(sb.st_mode) &&
++ (g_stat(pathbuf, &sb, pglob) == 0) &&
++ S_ISDIR(sb.st_mode)))) {
++ if (pathend + 1 > pathend_last)
++ return (GLOB_ABORTED);
++ *pathend++ = SEP;
++ *pathend = EOS;
++ }
++ ++pglob->gl_matchc;
++ return(globextend(pathbuf, pglob, limit));
++ }
++
++ /* Find end of next segment, copy tentatively to pathend. */
++ q = pathend;
++ p = pattern;
++ while (*p != EOS && *p != SEP) {
++ if (ismeta(*p))
++ anymeta = 1;
++ if (q + 1 > pathend_last)
++ return (GLOB_ABORTED);
++ *q++ = *p++;
++ }
++
++ if (!anymeta) { /* No expansion, do next segment. */
++ pathend = q;
++ pattern = p;
++ while (*pattern == SEP) {
++ if (pathend + 1 > pathend_last)
++ return (GLOB_ABORTED);
++ *pathend++ = *pattern++;
++ }
++ } else /* Need expansion, recurse. */
++ return(glob3(pathbuf, pathend, pathend_last, pattern, p,
++ pglob, limit));
++ }
++ /* NOTREACHED */
++}
++
++static int
++glob3(Char *pathbuf, Char *pathend, Char *pathend_last,
++ Char *pattern, Char *restpattern,
++ glob_t *pglob, size_t *limit)
++{
++ struct dirent *dp;
++ DIR *dirp;
++ int err;
++ char buf[MAXPATHLEN];
++
++ /*
++ * The readdirfunc declaration can't be prototyped, because it is
++ * assigned, below, to two functions which are prototyped in glob.h
++ * and dirent.h as taking pointers to differently typed opaque
++ * structures.
++ */
++ struct dirent *(*readdirfunc)();
++
++ if (pathend > pathend_last)
++ return (GLOB_ABORTED);
++ *pathend = EOS;
++ errno = 0;
++
++ if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
++ /* TODO: don't call for ENOENT or ENOTDIR? */
++ if (pglob->gl_errfunc) {
++ if (g_Ctoc(pathbuf, buf, sizeof(buf)))
++ return (GLOB_ABORTED);
++ if (pglob->gl_errfunc(buf, errno) ||
++ pglob->gl_flags & GLOB_ERR)
++ return (GLOB_ABORTED);
++ }
++ return(0);
++ }
++
++ err = 0;
++
++ /* Search directory for matching names. */
++ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
++ readdirfunc = pglob->gl_readdir;
++ else
++ readdirfunc = readdir;
++ while ((dp = (*readdirfunc)(dirp))) {
++ char *sc;
++ Char *dc;
++ wchar_t wc;
++ size_t clen;
++ mbstate_t mbs;
++
++ /* Initial DOT must be matched literally. */
++ if (dp->d_name[0] == DOT && *pattern != DOT)
++ continue;
++ memset(&mbs, 0, sizeof(mbs));
++ dc = pathend;
++ sc = dp->d_name;
++ while (dc < pathend_last) {
++ clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs);
++ if (clen == (size_t)-1 || clen == (size_t)-2) {
++ wc = *sc;
++ clen = 1;
++ memset(&mbs, 0, sizeof(mbs));
++ }
++ if ((*dc++ = wc) == EOS)
++ break;
++ sc += clen;
++ }
++ if (!match(pathend, pattern, restpattern)) {
++ *pathend = EOS;
++ continue;
++ }
++ err = glob2(pathbuf, --dc, pathend_last, restpattern,
++ pglob, limit);
++ if (err)
++ break;
++ }
++
++ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
++ (*pglob->gl_closedir)(dirp);
++ else
++ closedir(dirp);
++ return(err);
++}
++
++
++/*
++ * Extend the gl_pathv member of a glob_t structure to accomodate a new item,
++ * add the new item, and update gl_pathc.
++ *
++ * This assumes the BSD realloc, which only copies the block when its size
++ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
++ * behavior.
++ *
++ * Return 0 if new item added, error code if memory couldn't be allocated.
++ *
++ * Invariant of the glob_t structure:
++ * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
++ * gl_pathv points to (gl_offs + gl_pathc + 1) items.
++ */
++static int
++globextend(const Char *path, glob_t *pglob, size_t *limit)
++{
++ char **pathv;
++ size_t i, newsize, len;
++ char *copy;
++ const Char *p;
++
++ if (*limit && pglob->gl_pathc > *limit) {
++ errno = 0;
++ return (GLOB_NOSPACE);
++ }
++
++ newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);
++ pathv = pglob->gl_pathv ?
++ realloc((char *)pglob->gl_pathv, newsize) :
++ malloc(newsize);
++ if (pathv == NULL) {
++ if (pglob->gl_pathv) {
++ free(pglob->gl_pathv);
++ pglob->gl_pathv = NULL;
++ }
++ return(GLOB_NOSPACE);
++ }
++
++ if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
++ /* first time around -- clear initial gl_offs items */
++ pathv += pglob->gl_offs;
++ for (i = pglob->gl_offs + 1; --i > 0; )
++ *--pathv = NULL;
++ }
++ pglob->gl_pathv = pathv;
++
++ for (p = path; *p++;)
++ continue;
++ len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */
++ if ((copy = malloc(len)) != NULL) {
++ if (g_Ctoc(path, copy, len)) {
++ free(copy);
++ return (GLOB_NOSPACE);
++ }
++ pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
++ }
++ pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
++ return(copy == NULL ? GLOB_NOSPACE : 0);
++}
++
++/*
++ * pattern matching function for filenames. Each occurrence of the *
++ * pattern causes a recursion level.
++ */
++static int
++match(Char *name, Char *pat, Char *patend)
++{
++ int ok, negate_range;
++ Char c, k;
++
++ while (pat < patend) {
++ c = *pat++;
++ switch (c & M_MASK) {
++ case M_ALL:
++ if (pat == patend)
++ return(1);
++ do
++ if (match(name, pat, patend))
++ return(1);
++ while (*name++ != EOS);
++ return(0);
++ case M_ONE:
++ if (*name++ == EOS)
++ return(0);
++ break;
++ case M_SET:
++ ok = 0;
++ if ((k = *name++) == EOS)
++ return(0);
++ if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
++ ++pat;
++ while (((c = *pat++) & M_MASK) != M_END)
++ if ((*pat & M_MASK) == M_RNG) {
++ if (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1;
++ pat += 2;
++ } else if (c == k)
++ ok = 1;
++ if (ok == negate_range)
++ return(0);
++ break;
++ default:
++ if (*name++ != c)
++ return(0);
++ break;
++ }
++ }
++ return(*name == EOS);
++}
++
++/* Free allocated data belonging to a glob_t structure. */
++void
++globfree(glob_t *pglob)
++{
++ size_t i;
++ char **pp;
++
++ if (pglob->gl_pathv != NULL) {
++ pp = pglob->gl_pathv + pglob->gl_offs;
++ for (i = pglob->gl_pathc; i--; ++pp)
++ if (*pp)
++ free(*pp);
++ free(pglob->gl_pathv);
++ pglob->gl_pathv = NULL;
++ }
++}
++
++static DIR *
++g_opendir(Char *str, glob_t *pglob)
++{
++ char buf[MAXPATHLEN];
++
++ if (!*str)
++ strcpy(buf, ".");
++ else {
++ if (g_Ctoc(str, buf, sizeof(buf)))
++ return (NULL);
++ }
++
++ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
++ return((*pglob->gl_opendir)(buf));
++
++ return(opendir(buf));
++}
++
++static int
++g_lstat(Char *fn, struct stat *sb, glob_t *pglob)
++{
++ char buf[MAXPATHLEN];
++
++ if (g_Ctoc(fn, buf, sizeof(buf))) {
++ errno = ENAMETOOLONG;
++ return (-1);
++ }
++ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
++ return((*pglob->gl_lstat)(buf, sb));
++ return(lstat(buf, sb));
++}
++
++static int
++g_stat(Char *fn, struct stat *sb, glob_t *pglob)
++{
++ char buf[MAXPATHLEN];
++
++ if (g_Ctoc(fn, buf, sizeof(buf))) {
++ errno = ENAMETOOLONG;
++ return (-1);
++ }
++ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
++ return((*pglob->gl_stat)(buf, sb));
++ return(stat(buf, sb));
++}
++
++static const Char *
++g_strchr(const Char *str, wchar_t ch)
++{
++
++ do {
++ if (*str == ch)
++ return (str);
++ } while (*str++);
++ return (NULL);
++}
++
++static int
++g_Ctoc(const Char *str, char *buf, size_t len)
++{
++ mbstate_t mbs;
++ size_t clen;
++
++ memset(&mbs, 0, sizeof(mbs));
++ while (len >= MB_CUR_MAX) {
++ clen = wcrtomb(buf, *str, &mbs);
++ if (clen == (size_t)-1)
++ return (1);
++ if (*str == L'\0')
++ return (0);
++ str++;
++ buf += clen;
++ len -= clen;
++ }
++ return (1);
++}
++
++#ifdef DEBUG
++static void
++qprintf(const char *str, Char *s)
++{
++ Char *p;
++
++ (void)printf("%s:\n", str);
++ for (p = s; *p; p++)
++ (void)printf("%c", CHAR(*p));
++ (void)printf("\n");
++ for (p = s; *p; p++)
++ (void)printf("%c", *p & M_PROTECT ? '"' : ' ');
++ (void)printf("\n");
++ for (p = s; *p; p++)
++ (void)printf("%c", ismeta(*p) ? '_' : ' ');
++ (void)printf("\n");
++}
++#endif
+diff --git a/glob.h b/glob.h
+new file mode 100644
+index 0000000..351b6c4
+--- /dev/null
++++ b/glob.h
+@@ -0,0 +1,105 @@
++/*
++ * Copyright (c) 1989, 1993
++ * The Regents of the University of California. All rights reserved.
++ *
++ * This code is derived from software contributed to Berkeley by
++ * Guido van Rossum.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ * 3. Neither the name of the University nor the names of its contributors
++ * may be used to endorse or promote products derived from this software
++ * without specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
++ * SUCH DAMAGE.
++ *
++ * @(#)glob.h 8.1 (Berkeley) 6/2/93
++ * $FreeBSD$
++ */
++
++#ifndef _GLOB_H_
++#define _GLOB_H_
++
++#include <sys/cdefs.h>
++#include <sys/_types.h>
++
++#ifndef _SIZE_T_DECLARED
++typedef __size_t size_t;
++#define _SIZE_T_DECLARED
++#endif
++
++struct stat;
++typedef struct {
++ size_t gl_pathc; /* Count of total paths so far. */
++ size_t gl_matchc; /* Count of paths matching pattern. */
++ size_t gl_offs; /* Reserved at beginning of gl_pathv. */
++ int gl_flags; /* Copy of flags parameter to glob. */
++ char **gl_pathv; /* List of paths matching pattern. */
++ /* Copy of errfunc parameter to glob. */
++ int (*gl_errfunc)(const char *, int);
++
++ /*
++ * Alternate filesystem access methods for glob; replacement
++ * versions of closedir(3), readdir(3), opendir(3), stat(2)
++ * and lstat(2).
++ */
++ void (*gl_closedir)(void *);
++ struct dirent *(*gl_readdir)(void *);
++ void *(*gl_opendir)(const char *);
++ int (*gl_lstat)(const char *, struct stat *);
++ int (*gl_stat)(const char *, struct stat *);
++} glob_t;
++
++#if __POSIX_VISIBLE >= 199209
++/* Believed to have been introduced in 1003.2-1992 */
++#define GLOB_APPEND 0x0001 /* Append to output from previous call. */
++#define GLOB_DOOFFS 0x0002 /* Use gl_offs. */
++#define GLOB_ERR 0x0004 /* Return on error. */
++#define GLOB_MARK 0x0008 /* Append / to matching directories. */
++#define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */
++#define GLOB_NOSORT 0x0020 /* Don't sort. */
++#define GLOB_NOESCAPE 0x2000 /* Disable backslash escaping. */
++
++/* Error values returned by glob(3) */
++#define GLOB_NOSPACE (-1) /* Malloc call failed. */
++#define GLOB_ABORTED (-2) /* Unignored error. */
++#define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK was not set. */
++#define GLOB_NOSYS (-4) /* Obsolete: source comptability only. */
++#endif /* __POSIX_VISIBLE >= 199209 */
++
++#if __BSD_VISIBLE
++#define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */
++#define GLOB_BRACE 0x0080 /* Expand braces ala csh. */
++#define GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */
++#define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */
++#define GLOB_QUOTE 0x0400 /* Quote special chars with \. */
++#define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */
++#define GLOB_LIMIT 0x1000 /* limit number of returned paths */
++
++/* source compatibility, these are the old names */
++#define GLOB_MAXPATH GLOB_LIMIT
++#define GLOB_ABEND GLOB_ABORTED
++#endif /* __BSD_VISIBLE */
++
++__BEGIN_DECLS
++int glob(const char *, int, int (*)(const char *, int), glob_t *);
++void globfree(glob_t *);
++__END_DECLS
++
++#endif /* !_GLOB_H_ */
+--
+1.9.1
+
diff --git a/pythonforandroid/recipes/libxml2/glob.c b/pythonforandroid/recipes/libxml2/glob.c
new file mode 100644
index 000000000..cec80ed7c
--- /dev/null
+++ b/pythonforandroid/recipes/libxml2/glob.c
@@ -0,0 +1,906 @@
+/*
+ * Natanael Arndt, 2011: removed collate.h dependencies
+ * (my changes are trivial)
+ *
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93";
+#endif /* LIBC_SCCS and not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * glob(3) -- a superset of the one defined in POSIX 1003.2.
+ *
+ * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
+ *
+ * Optional extra services, controlled by flags not defined by POSIX:
+ *
+ * GLOB_QUOTE:
+ * Escaping convention: \ inhibits any special meaning the following
+ * character might have (except \ at end of string is retained).
+ * GLOB_MAGCHAR:
+ * Set in gl_flags if pattern contained a globbing character.
+ * GLOB_NOMAGIC:
+ * Same as GLOB_NOCHECK, but it will only append pattern if it did
+ * not contain any magic characters. [Used in csh style globbing]
+ * GLOB_ALTDIRFUNC:
+ * Use alternately specified directory access functions.
+ * GLOB_TILDE:
+ * expand ~user/foo to the /home/dir/of/user/foo
+ * GLOB_BRACE:
+ * expand {1,2}{a,b} to 1a 1b 2a 2b
+ * gl_matchc:
+ * Number of matches in the current invocation of glob.
+ */
+
+/*
+ * Some notes on multibyte character support:
+ * 1. Patterns with illegal byte sequences match nothing - even if
+ * GLOB_NOCHECK is specified.
+ * 2. Illegal byte sequences in filenames are handled by treating them as
+ * single-byte characters with a value of the first byte of the sequence
+ * cast to wchar_t.
+ * 3. State-dependent encodings are not currently supported.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <glob.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#define DOLLAR '$'
+#define DOT '.'
+#define EOS '\0'
+#define LBRACKET '['
+#define NOT '!'
+#define QUESTION '?'
+#define QUOTE '\\'
+#define RANGE '-'
+#define RBRACKET ']'
+#define SEP '/'
+#define STAR '*'
+#define TILDE '~'
+#define UNDERSCORE '_'
+#define LBRACE '{'
+#define RBRACE '}'
+#define SLASH '/'
+#define COMMA ','
+
+#ifndef DEBUG
+
+#define M_QUOTE 0x8000000000ULL
+#define M_PROTECT 0x4000000000ULL
+#define M_MASK 0xffffffffffULL
+#define M_CHAR 0x00ffffffffULL
+
+typedef uint_fast64_t Char;
+
+#else
+
+#define M_QUOTE 0x80
+#define M_PROTECT 0x40
+#define M_MASK 0xff
+#define M_CHAR 0x7f
+
+typedef char Char;
+
+#endif
+
+
+#define CHAR(c) ((Char)((c)&M_CHAR))
+#define META(c) ((Char)((c)|M_QUOTE))
+#define M_ALL META('*')
+#define M_END META(']')
+#define M_NOT META('!')
+#define M_ONE META('?')
+#define M_RNG META('-')
+#define M_SET META('[')
+#define ismeta(c) (((c)&M_QUOTE) != 0)
+
+
+static int compare(const void *, const void *);
+static int g_Ctoc(const Char *, char *, size_t);
+static int g_lstat(Char *, struct stat *, glob_t *);
+static DIR *g_opendir(Char *, glob_t *);
+static const Char *g_strchr(const Char *, wchar_t);
+#ifdef notdef
+static Char *g_strcat(Char *, const Char *);
+#endif
+static int g_stat(Char *, struct stat *, glob_t *);
+static int glob0(const Char *, glob_t *, size_t *);
+static int glob1(Char *, glob_t *, size_t *);
+static int glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *);
+static int glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *);
+static int globextend(const Char *, glob_t *, size_t *);
+static const Char *
+ globtilde(const Char *, Char *, size_t, glob_t *);
+static int globexp1(const Char *, glob_t *, size_t *);
+static int globexp2(const Char *, const Char *, glob_t *, int *, size_t *);
+static int match(Char *, Char *, Char *);
+#ifdef DEBUG
+static void qprintf(const char *, Char *);
+#endif
+
+int
+glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob)
+{
+ const char *patnext;
+ size_t limit;
+ Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot;
+ mbstate_t mbs;
+ wchar_t wc;
+ size_t clen;
+
+ patnext = pattern;
+ if (!(flags & GLOB_APPEND)) {
+ pglob->gl_pathc = 0;
+ pglob->gl_pathv = NULL;
+ if (!(flags & GLOB_DOOFFS))
+ pglob->gl_offs = 0;
+ }
+ if (flags & GLOB_LIMIT) {
+ limit = pglob->gl_matchc;
+ if (limit == 0)
+ limit = ARG_MAX;
+ } else
+ limit = 0;
+ pglob->gl_flags = flags & ~GLOB_MAGCHAR;
+ pglob->gl_errfunc = errfunc;
+ pglob->gl_matchc = 0;
+
+ bufnext = patbuf;
+ bufend = bufnext + MAXPATHLEN - 1;
+ if (flags & GLOB_NOESCAPE) {
+ memset(&mbs, 0, sizeof(mbs));
+ while (bufend - bufnext >= MB_CUR_MAX) {
+ clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
+ if (clen == (size_t)-1 || clen == (size_t)-2)
+ return (GLOB_NOMATCH);
+ else if (clen == 0)
+ break;
+ *bufnext++ = wc;
+ patnext += clen;
+ }
+ } else {
+ /* Protect the quoted characters. */
+ memset(&mbs, 0, sizeof(mbs));
+ while (bufend - bufnext >= MB_CUR_MAX) {
+ if (*patnext == QUOTE) {
+ if (*++patnext == EOS) {
+ *bufnext++ = QUOTE | M_PROTECT;
+ continue;
+ }
+ prot = M_PROTECT;
+ } else
+ prot = 0;
+ clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
+ if (clen == (size_t)-1 || clen == (size_t)-2)
+ return (GLOB_NOMATCH);
+ else if (clen == 0)
+ break;
+ *bufnext++ = wc | prot;
+ patnext += clen;
+ }
+ }
+ *bufnext = EOS;
+
+ if (flags & GLOB_BRACE)
+ return globexp1(patbuf, pglob, &limit);
+ else
+ return glob0(patbuf, pglob, &limit);
+}
+
+/*
+ * Expand recursively a glob {} pattern. When there is no more expansion
+ * invoke the standard globbing routine to glob the rest of the magic
+ * characters
+ */
+static int
+globexp1(const Char *pattern, glob_t *pglob, size_t *limit)
+{
+ const Char* ptr = pattern;
+ int rv;
+
+ /* Protect a single {}, for find(1), like csh */
+ if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
+ return glob0(pattern, pglob, limit);
+
+ while ((ptr = g_strchr(ptr, LBRACE)) != NULL)
+ if (!globexp2(ptr, pattern, pglob, &rv, limit))
+ return rv;
+
+ return glob0(pattern, pglob, limit);
+}
+
+
+/*
+ * Recursive brace globbing helper. Tries to expand a single brace.
+ * If it succeeds then it invokes globexp1 with the new pattern.
+ * If it fails then it tries to glob the rest of the pattern and returns.
+ */
+static int
+globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit)
+{
+ int i;
+ Char *lm, *ls;
+ const Char *pe, *pm, *pm1, *pl;
+ Char patbuf[MAXPATHLEN];
+
+ /* copy part up to the brace */
+ for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
+ continue;
+ *lm = EOS;
+ ls = lm;
+
+ /* Find the balanced brace */
+ for (i = 0, pe = ++ptr; *pe; pe++)
+ if (*pe == LBRACKET) {
+ /* Ignore everything between [] */
+ for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
+ continue;
+ if (*pe == EOS) {
+ /*
+ * We could not find a matching RBRACKET.
+ * Ignore and just look for RBRACE
+ */
+ pe = pm;
+ }
+ }
+ else if (*pe == LBRACE)
+ i++;
+ else if (*pe == RBRACE) {
+ if (i == 0)
+ break;
+ i--;
+ }
+
+ /* Non matching braces; just glob the pattern */
+ if (i != 0 || *pe == EOS) {
+ *rv = glob0(patbuf, pglob, limit);
+ return 0;
+ }
+
+ for (i = 0, pl = pm = ptr; pm <= pe; pm++)
+ switch (*pm) {
+ case LBRACKET:
+ /* Ignore everything between [] */
+ for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++)
+ continue;
+ if (*pm == EOS) {
+ /*
+ * We could not find a matching RBRACKET.
+ * Ignore and just look for RBRACE
+ */
+ pm = pm1;
+ }
+ break;
+
+ case LBRACE:
+ i++;
+ break;
+
+ case RBRACE:
+ if (i) {
+ i--;
+ break;
+ }
+ /* FALLTHROUGH */
+ case COMMA:
+ if (i && *pm == COMMA)
+ break;
+ else {
+ /* Append the current string */
+ for (lm = ls; (pl < pm); *lm++ = *pl++)
+ continue;
+ /*
+ * Append the rest of the pattern after the
+ * closing brace
+ */
+ for (pl = pe + 1; (*lm++ = *pl++) != EOS;)
+ continue;
+
+ /* Expand the current pattern */
+#ifdef DEBUG
+ qprintf("globexp2:", patbuf);
+#endif
+ *rv = globexp1(patbuf, pglob, limit);
+
+ /* move after the comma, to the next string */
+ pl = pm + 1;
+ }
+ break;
+
+ default:
+ break;
+ }
+ *rv = 0;
+ return 0;
+}
+
+
+
+/*
+ * expand tilde from the passwd file.
+ */
+static const Char *
+globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)
+{
+ struct passwd *pwd;
+ char *h;
+ const Char *p;
+ Char *b, *eb;
+
+ if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))
+ return pattern;
+
+ /*
+ * Copy up to the end of the string or /
+ */
+ eb = &patbuf[patbuf_len - 1];
+ for (p = pattern + 1, h = (char *) patbuf;
+ h < (char *)eb && *p && *p != SLASH; *h++ = *p++)
+ continue;
+
+ *h = EOS;
+
+ if (((char *) patbuf)[0] == EOS) {
+ /*
+ * handle a plain ~ or ~/ by expanding $HOME first (iff
+ * we're not running setuid or setgid) and then trying
+ * the password file
+ */
+ if (issetugid() != 0 ||
+ (h = getenv("HOME")) == NULL) {
+ if (((h = getlogin()) != NULL &&
+ (pwd = getpwnam(h)) != NULL) ||
+ (pwd = getpwuid(getuid())) != NULL)
+ h = pwd->pw_dir;
+ else
+ return pattern;
+ }
+ }
+ else {
+ /*
+ * Expand a ~user
+ */
+ if ((pwd = getpwnam((char*) patbuf)) == NULL)
+ return pattern;
+ else
+ h = pwd->pw_dir;
+ }
+
+ /* Copy the home directory */
+ for (b = patbuf; b < eb && *h; *b++ = *h++)
+ continue;
+
+ /* Append the rest of the pattern */
+ while (b < eb && (*b++ = *p++) != EOS)
+ continue;
+ *b = EOS;
+
+ return patbuf;
+}
+
+
+/*
+ * The main glob() routine: compiles the pattern (optionally processing
+ * quotes), calls glob1() to do the real pattern matching, and finally
+ * sorts the list (unless unsorted operation is requested). Returns 0
+ * if things went well, nonzero if errors occurred.
+ */
+static int
+glob0(const Char *pattern, glob_t *pglob, size_t *limit)
+{
+ const Char *qpatnext;
+ int err;
+ size_t oldpathc;
+ Char *bufnext, c, patbuf[MAXPATHLEN];
+
+ qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob);
+ oldpathc = pglob->gl_pathc;
+ bufnext = patbuf;
+
+ /* We don't need to check for buffer overflow any more. */
+ while ((c = *qpatnext++) != EOS) {
+ switch (c) {
+ case LBRACKET:
+ c = *qpatnext;
+ if (c == NOT)
+ ++qpatnext;
+ if (*qpatnext == EOS ||
+ g_strchr(qpatnext+1, RBRACKET) == NULL) {
+ *bufnext++ = LBRACKET;
+ if (c == NOT)
+ --qpatnext;
+ break;
+ }
+ *bufnext++ = M_SET;
+ if (c == NOT)
+ *bufnext++ = M_NOT;
+ c = *qpatnext++;
+ do {
+ *bufnext++ = CHAR(c);
+ if (*qpatnext == RANGE &&
+ (c = qpatnext[1]) != RBRACKET) {
+ *bufnext++ = M_RNG;
+ *bufnext++ = CHAR(c);
+ qpatnext += 2;
+ }
+ } while ((c = *qpatnext++) != RBRACKET);
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ *bufnext++ = M_END;
+ break;
+ case QUESTION:
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ *bufnext++ = M_ONE;
+ break;
+ case STAR:
+ pglob->gl_flags |= GLOB_MAGCHAR;
+ /* collapse adjacent stars to one,
+ * to avoid exponential behavior
+ */
+ if (bufnext == patbuf || bufnext[-1] != M_ALL)
+ *bufnext++ = M_ALL;
+ break;
+ default:
+ *bufnext++ = CHAR(c);
+ break;
+ }
+ }
+ *bufnext = EOS;
+#ifdef DEBUG
+ qprintf("glob0:", patbuf);
+#endif
+
+ if ((err = glob1(patbuf, pglob, limit)) != 0)
+ return(err);
+
+ /*
+ * If there was no match we are going to append the pattern
+ * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified
+ * and the pattern did not contain any magic characters
+ * GLOB_NOMAGIC is there just for compatibility with csh.
+ */
+ if (pglob->gl_pathc == oldpathc) {
+ if (((pglob->gl_flags & GLOB_NOCHECK) ||
+ ((pglob->gl_flags & GLOB_NOMAGIC) &&
+ !(pglob->gl_flags & GLOB_MAGCHAR))))
+ return(globextend(pattern, pglob, limit));
+ else
+ return(GLOB_NOMATCH);
+ }
+ if (!(pglob->gl_flags & GLOB_NOSORT))
+ qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
+ pglob->gl_pathc - oldpathc, sizeof(char *), compare);
+ return(0);
+}
+
+static int
+compare(const void *p, const void *q)
+{
+ return(strcmp(*(char **)p, *(char **)q));
+}
+
+static int
+glob1(Char *pattern, glob_t *pglob, size_t *limit)
+{
+ Char pathbuf[MAXPATHLEN];
+
+ /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
+ if (*pattern == EOS)
+ return(0);
+ return(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1,
+ pattern, pglob, limit));
+}
+
+/*
+ * The functions glob2 and glob3 are mutually recursive; there is one level
+ * of recursion for each segment in the pattern that contains one or more
+ * meta characters.
+ */
+static int
+glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern,
+ glob_t *pglob, size_t *limit)
+{
+ struct stat sb;
+ Char *p, *q;
+ int anymeta;
+
+ /*
+ * Loop over pattern segments until end of pattern or until
+ * segment with meta character found.
+ */
+ for (anymeta = 0;;) {
+ if (*pattern == EOS) { /* End of pattern? */
+ *pathend = EOS;
+ if (g_lstat(pathbuf, &sb, pglob))
+ return(0);
+
+ if (((pglob->gl_flags & GLOB_MARK) &&
+ pathend[-1] != SEP) && (S_ISDIR(sb.st_mode)
+ || (S_ISLNK(sb.st_mode) &&
+ (g_stat(pathbuf, &sb, pglob) == 0) &&
+ S_ISDIR(sb.st_mode)))) {
+ if (pathend + 1 > pathend_last)
+ return (GLOB_ABORTED);
+ *pathend++ = SEP;
+ *pathend = EOS;
+ }
+ ++pglob->gl_matchc;
+ return(globextend(pathbuf, pglob, limit));
+ }
+
+ /* Find end of next segment, copy tentatively to pathend. */
+ q = pathend;
+ p = pattern;
+ while (*p != EOS && *p != SEP) {
+ if (ismeta(*p))
+ anymeta = 1;
+ if (q + 1 > pathend_last)
+ return (GLOB_ABORTED);
+ *q++ = *p++;
+ }
+
+ if (!anymeta) { /* No expansion, do next segment. */
+ pathend = q;
+ pattern = p;
+ while (*pattern == SEP) {
+ if (pathend + 1 > pathend_last)
+ return (GLOB_ABORTED);
+ *pathend++ = *pattern++;
+ }
+ } else /* Need expansion, recurse. */
+ return(glob3(pathbuf, pathend, pathend_last, pattern, p,
+ pglob, limit));
+ }
+ /* NOTREACHED */
+}
+
+static int
+glob3(Char *pathbuf, Char *pathend, Char *pathend_last,
+ Char *pattern, Char *restpattern,
+ glob_t *pglob, size_t *limit)
+{
+ struct dirent *dp;
+ DIR *dirp;
+ int err;
+ char buf[MAXPATHLEN];
+
+ /*
+ * The readdirfunc declaration can't be prototyped, because it is
+ * assigned, below, to two functions which are prototyped in glob.h
+ * and dirent.h as taking pointers to differently typed opaque
+ * structures.
+ */
+ struct dirent *(*readdirfunc)();
+
+ if (pathend > pathend_last)
+ return (GLOB_ABORTED);
+ *pathend = EOS;
+ errno = 0;
+
+ if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
+ /* TODO: don't call for ENOENT or ENOTDIR? */
+ if (pglob->gl_errfunc) {
+ if (g_Ctoc(pathbuf, buf, sizeof(buf)))
+ return (GLOB_ABORTED);
+ if (pglob->gl_errfunc(buf, errno) ||
+ pglob->gl_flags & GLOB_ERR)
+ return (GLOB_ABORTED);
+ }
+ return(0);
+ }
+
+ err = 0;
+
+ /* Search directory for matching names. */
+ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+ readdirfunc = pglob->gl_readdir;
+ else
+ readdirfunc = readdir;
+ while ((dp = (*readdirfunc)(dirp))) {
+ char *sc;
+ Char *dc;
+ wchar_t wc;
+ size_t clen;
+ mbstate_t mbs;
+
+ /* Initial DOT must be matched literally. */
+ if (dp->d_name[0] == DOT && *pattern != DOT)
+ continue;
+ memset(&mbs, 0, sizeof(mbs));
+ dc = pathend;
+ sc = dp->d_name;
+ while (dc < pathend_last) {
+ clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs);
+ if (clen == (size_t)-1 || clen == (size_t)-2) {
+ wc = *sc;
+ clen = 1;
+ memset(&mbs, 0, sizeof(mbs));
+ }
+ if ((*dc++ = wc) == EOS)
+ break;
+ sc += clen;
+ }
+ if (!match(pathend, pattern, restpattern)) {
+ *pathend = EOS;
+ continue;
+ }
+ err = glob2(pathbuf, --dc, pathend_last, restpattern,
+ pglob, limit);
+ if (err)
+ break;
+ }
+
+ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+ (*pglob->gl_closedir)(dirp);
+ else
+ closedir(dirp);
+ return(err);
+}
+
+
+/*
+ * Extend the gl_pathv member of a glob_t structure to accomodate a new item,
+ * add the new item, and update gl_pathc.
+ *
+ * This assumes the BSD realloc, which only copies the block when its size
+ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
+ * behavior.
+ *
+ * Return 0 if new item added, error code if memory couldn't be allocated.
+ *
+ * Invariant of the glob_t structure:
+ * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
+ * gl_pathv points to (gl_offs + gl_pathc + 1) items.
+ */
+static int
+globextend(const Char *path, glob_t *pglob, size_t *limit)
+{
+ char **pathv;
+ size_t i, newsize, len;
+ char *copy;
+ const Char *p;
+
+ if (*limit && pglob->gl_pathc > *limit) {
+ errno = 0;
+ return (GLOB_NOSPACE);
+ }
+
+ newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);
+ pathv = pglob->gl_pathv ?
+ realloc((char *)pglob->gl_pathv, newsize) :
+ malloc(newsize);
+ if (pathv == NULL) {
+ if (pglob->gl_pathv) {
+ free(pglob->gl_pathv);
+ pglob->gl_pathv = NULL;
+ }
+ return(GLOB_NOSPACE);
+ }
+
+ if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
+ /* first time around -- clear initial gl_offs items */
+ pathv += pglob->gl_offs;
+ for (i = pglob->gl_offs + 1; --i > 0; )
+ *--pathv = NULL;
+ }
+ pglob->gl_pathv = pathv;
+
+ for (p = path; *p++;)
+ continue;
+ len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */
+ if ((copy = malloc(len)) != NULL) {
+ if (g_Ctoc(path, copy, len)) {
+ free(copy);
+ return (GLOB_NOSPACE);
+ }
+ pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
+ }
+ pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
+ return(copy == NULL ? GLOB_NOSPACE : 0);
+}
+
+/*
+ * pattern matching function for filenames. Each occurrence of the *
+ * pattern causes a recursion level.
+ */
+static int
+match(Char *name, Char *pat, Char *patend)
+{
+ int ok, negate_range;
+ Char c, k;
+
+ while (pat < patend) {
+ c = *pat++;
+ switch (c & M_MASK) {
+ case M_ALL:
+ if (pat == patend)
+ return(1);
+ do
+ if (match(name, pat, patend))
+ return(1);
+ while (*name++ != EOS);
+ return(0);
+ case M_ONE:
+ if (*name++ == EOS)
+ return(0);
+ break;
+ case M_SET:
+ ok = 0;
+ if ((k = *name++) == EOS)
+ return(0);
+ if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
+ ++pat;
+ while (((c = *pat++) & M_MASK) != M_END)
+ if ((*pat & M_MASK) == M_RNG) {
+ if (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1;
+ pat += 2;
+ } else if (c == k)
+ ok = 1;
+ if (ok == negate_range)
+ return(0);
+ break;
+ default:
+ if (*name++ != c)
+ return(0);
+ break;
+ }
+ }
+ return(*name == EOS);
+}
+
+/* Free allocated data belonging to a glob_t structure. */
+void
+globfree(glob_t *pglob)
+{
+ size_t i;
+ char **pp;
+
+ if (pglob->gl_pathv != NULL) {
+ pp = pglob->gl_pathv + pglob->gl_offs;
+ for (i = pglob->gl_pathc; i--; ++pp)
+ if (*pp)
+ free(*pp);
+ free(pglob->gl_pathv);
+ pglob->gl_pathv = NULL;
+ }
+}
+
+static DIR *
+g_opendir(Char *str, glob_t *pglob)
+{
+ char buf[MAXPATHLEN];
+
+ if (!*str)
+ strcpy(buf, ".");
+ else {
+ if (g_Ctoc(str, buf, sizeof(buf)))
+ return (NULL);
+ }
+
+ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+ return((*pglob->gl_opendir)(buf));
+
+ return(opendir(buf));
+}
+
+static int
+g_lstat(Char *fn, struct stat *sb, glob_t *pglob)
+{
+ char buf[MAXPATHLEN];
+
+ if (g_Ctoc(fn, buf, sizeof(buf))) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+ return((*pglob->gl_lstat)(buf, sb));
+ return(lstat(buf, sb));
+}
+
+static int
+g_stat(Char *fn, struct stat *sb, glob_t *pglob)
+{
+ char buf[MAXPATHLEN];
+
+ if (g_Ctoc(fn, buf, sizeof(buf))) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+ return((*pglob->gl_stat)(buf, sb));
+ return(stat(buf, sb));
+}
+
+static const Char *
+g_strchr(const Char *str, wchar_t ch)
+{
+
+ do {
+ if (*str == ch)
+ return (str);
+ } while (*str++);
+ return (NULL);
+}
+
+static int
+g_Ctoc(const Char *str, char *buf, size_t len)
+{
+ mbstate_t mbs;
+ size_t clen;
+
+ memset(&mbs, 0, sizeof(mbs));
+ while (len >= MB_CUR_MAX) {
+ clen = wcrtomb(buf, *str, &mbs);
+ if (clen == (size_t)-1)
+ return (1);
+ if (*str == L'\0')
+ return (0);
+ str++;
+ buf += clen;
+ len -= clen;
+ }
+ return (1);
+}
+
+#ifdef DEBUG
+static void
+qprintf(const char *str, Char *s)
+{
+ Char *p;
+
+ (void)printf("%s:\n", str);
+ for (p = s; *p; p++)
+ (void)printf("%c", CHAR(*p));
+ (void)printf("\n");
+ for (p = s; *p; p++)
+ (void)printf("%c", *p & M_PROTECT ? '"' : ' ');
+ (void)printf("\n");
+ for (p = s; *p; p++)
+ (void)printf("%c", ismeta(*p) ? '_' : ' ');
+ (void)printf("\n");
+}
+#endif
diff --git a/pythonforandroid/recipes/libxml2/glob.h b/pythonforandroid/recipes/libxml2/glob.h
new file mode 100644
index 000000000..351b6c46b
--- /dev/null
+++ b/pythonforandroid/recipes/libxml2/glob.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)glob.h 8.1 (Berkeley) 6/2/93
+ * $FreeBSD$
+ */
+
+#ifndef _GLOB_H_
+#define _GLOB_H_
+
+#include <sys/cdefs.h>
+#include <sys/_types.h>
+
+#ifndef _SIZE_T_DECLARED
+typedef __size_t size_t;
+#define _SIZE_T_DECLARED
+#endif
+
+struct stat;
+typedef struct {
+ size_t gl_pathc; /* Count of total paths so far. */
+ size_t gl_matchc; /* Count of paths matching pattern. */
+ size_t gl_offs; /* Reserved at beginning of gl_pathv. */
+ int gl_flags; /* Copy of flags parameter to glob. */
+ char **gl_pathv; /* List of paths matching pattern. */
+ /* Copy of errfunc parameter to glob. */
+ int (*gl_errfunc)(const char *, int);
+
+ /*
+ * Alternate filesystem access methods for glob; replacement
+ * versions of closedir(3), readdir(3), opendir(3), stat(2)
+ * and lstat(2).
+ */
+ void (*gl_closedir)(void *);
+ struct dirent *(*gl_readdir)(void *);
+ void *(*gl_opendir)(const char *);
+ int (*gl_lstat)(const char *, struct stat *);
+ int (*gl_stat)(const char *, struct stat *);
+} glob_t;
+
+#if __POSIX_VISIBLE >= 199209
+/* Believed to have been introduced in 1003.2-1992 */
+#define GLOB_APPEND 0x0001 /* Append to output from previous call. */
+#define GLOB_DOOFFS 0x0002 /* Use gl_offs. */
+#define GLOB_ERR 0x0004 /* Return on error. */
+#define GLOB_MARK 0x0008 /* Append / to matching directories. */
+#define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */
+#define GLOB_NOSORT 0x0020 /* Don't sort. */
+#define GLOB_NOESCAPE 0x2000 /* Disable backslash escaping. */
+
+/* Error values returned by glob(3) */
+#define GLOB_NOSPACE (-1) /* Malloc call failed. */
+#define GLOB_ABORTED (-2) /* Unignored error. */
+#define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK was not set. */
+#define GLOB_NOSYS (-4) /* Obsolete: source comptability only. */
+#endif /* __POSIX_VISIBLE >= 199209 */
+
+#if __BSD_VISIBLE
+#define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */
+#define GLOB_BRACE 0x0080 /* Expand braces ala csh. */
+#define GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */
+#define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */
+#define GLOB_QUOTE 0x0400 /* Quote special chars with \. */
+#define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */
+#define GLOB_LIMIT 0x1000 /* limit number of returned paths */
+
+/* source compatibility, these are the old names */
+#define GLOB_MAXPATH GLOB_LIMIT
+#define GLOB_ABEND GLOB_ABORTED
+#endif /* __BSD_VISIBLE */
+
+__BEGIN_DECLS
+int glob(const char *, int, int (*)(const char *, int), glob_t *);
+void globfree(glob_t *);
+__END_DECLS
+
+#endif /* !_GLOB_H_ */
diff --git a/pythonforandroid/recipes/libxslt/__init__.py b/pythonforandroid/recipes/libxslt/__init__.py
new file mode 100644
index 000000000..d9127cfa5
--- /dev/null
+++ b/pythonforandroid/recipes/libxslt/__init__.py
@@ -0,0 +1,70 @@
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
+from os.path import exists, join
+import sh
+
+
+class LibxsltRecipe(Recipe):
+ version = '1.1.34'
+ url = 'http://xmlsoft.org/sources/libxslt-{version}.tar.gz'
+ depends = ['libxml2']
+ patches = ['fix-dlopen.patch']
+ built_libraries = {
+ 'libxslt.a': 'libxslt/.libs',
+ 'libexslt.a': 'libexslt/.libs'
+ }
+
+ call_hostpython_via_targetpython = False
+
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+ build_dir = self.get_build_dir(arch.arch)
+ with current_directory(build_dir):
+ # If the build is done with /bin/sh things blow up,
+ # try really hard to use bash
+ libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx)
+ libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch)
+ build_arch = shprint(sh.gcc, '-dumpmachine').stdout.decode(
+ 'utf-8').split('\n')[0]
+
+ if not exists('configure'):
+ shprint(sh.Command('./autogen.sh'), _env=env)
+ shprint(sh.Command('autoreconf'), '-vif', _env=env)
+ shprint(sh.Command('./configure'),
+ '--build=' + build_arch,
+ '--host=' + arch.command_prefix,
+ '--target=' + arch.command_prefix,
+ '--without-plugins',
+ '--without-debug',
+ '--without-python',
+ '--without-crypto',
+ '--with-libxml-src=' + libxml2_build_dir,
+ '--disable-shared',
+ _env=env)
+ shprint(sh.make, "V=1", _env=env)
+
+ shprint(sh.Command('chmod'), '+x', 'xslt-config')
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['CONFIG_SHELL'] = '/bin/bash'
+ env['SHELL'] = '/bin/bash'
+
+ libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx)
+ libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch)
+ libxml2_libs_dir = join(libxml2_build_dir, '.libs')
+
+ env['CFLAGS'] = ' '.join([
+ env['CFLAGS'],
+ '-I' + libxml2_build_dir,
+ '-I' + join(libxml2_build_dir, 'include', 'libxml'),
+ '-I' + self.get_build_dir(arch.arch),
+ ])
+ env['LDFLAGS'] += ' -L' + libxml2_libs_dir
+ env['LIBS'] = '-lxml2 -lz -lm'
+
+ return env
+
+
+recipe = LibxsltRecipe()
diff --git a/pythonforandroid/recipes/libxslt/fix-dlopen.patch b/pythonforandroid/recipes/libxslt/fix-dlopen.patch
new file mode 100644
index 000000000..34d56b6e0
--- /dev/null
+++ b/pythonforandroid/recipes/libxslt/fix-dlopen.patch
@@ -0,0 +1,11 @@
+--- libxslt-1.1.27.orig/python/libxsl.py 2012-09-04 16:26:23.000000000 +0200
++++ libxslt-1.1.27/python/libxsl.py 2013-07-29 15:11:04.182227378 +0200
+@@ -4,7 +4,7 @@
+ # loader to work in that mode if feasible
+ #
+ import sys
+-if not hasattr(sys,'getdlopenflags'):
++if True:
+ import libxml2mod
+ import libxsltmod
+ import libxml2
diff --git a/pythonforandroid/recipes/libzbar/__init__.py b/pythonforandroid/recipes/libzbar/__init__.py
index b9de0c828..4e26ca4d7 100644
--- a/pythonforandroid/recipes/libzbar/__init__.py
+++ b/pythonforandroid/recipes/libzbar/__init__.py
@@ -1,6 +1,7 @@
import os
-from pythonforandroid.toolchain import shprint, current_directory
from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
from multiprocessing import cpu_count
import sh
@@ -11,34 +12,29 @@ class LibZBarRecipe(Recipe):
url = 'https://github.com/ZBar/ZBar/archive/{version}.zip'
- depends = ['hostpython2', 'python2', 'libiconv']
+ depends = ['libiconv']
patches = ["werror.patch"]
- def should_build(self, arch):
- return not os.path.exists(
- os.path.join(self.ctx.get_libs_dir(arch.arch), 'libzbar.so'))
+ built_libraries = {'libzbar.so': 'zbar/.libs'}
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(LibZBarRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
libiconv = self.get_recipe('libiconv', self.ctx)
libiconv_dir = libiconv.get_build_dir(arch.arch)
env['CFLAGS'] += ' -I' + os.path.join(libiconv_dir, 'include')
- env['LDSHARED'] = env['CC'] + \
- ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += " -landroid -liconv"
+ env['LIBS'] = env.get('LIBS', '') + ' -landroid -liconv'
return env
def build_arch(self, arch):
- super(LibZBarRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
shprint(sh.Command('autoreconf'), '-vif', _env=env)
shprint(
sh.Command('./configure'),
- '--host=' + arch.toolchain_prefix,
- '--target=' + arch.toolchain_prefix,
- '--prefix=' + self.ctx.get_python_install_dir(),
+ '--host=' + arch.command_prefix,
+ '--target=' + arch.command_prefix,
+ '--prefix=' + self.ctx.get_python_install_dir(arch.arch),
# Python bindings are compiled in a separated recipe
'--with-python=no',
'--with-gtk=no',
@@ -52,8 +48,6 @@ def build_arch(self, arch):
'--enable-static=no',
_env=env)
shprint(sh.make, '-j' + str(cpu_count()), _env=env)
- libs = ['zbar/.libs/libzbar.so']
- self.install_libs(arch, *libs)
recipe = LibZBarRecipe()
diff --git a/pythonforandroid/recipes/libzmq/__init__.py b/pythonforandroid/recipes/libzmq/__init__.py
index 1ebebf7b6..243517bc9 100644
--- a/pythonforandroid/recipes/libzmq/__init__.py
+++ b/pythonforandroid/recipes/libzmq/__init__.py
@@ -1,21 +1,18 @@
-from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from pythonforandroid.util import ensure_dir
-from os.path import exists, join
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
+from os.path import join
import sh
class LibZMQRecipe(Recipe):
- version = '4.1.4'
- url = 'http://download.zeromq.org/zeromq-{version}.tar.gz'
- depends = ['python2']
-
- def should_build(self, arch):
- super(LibZMQRecipe, self).should_build(arch)
- return True
- return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libzmq.so'))
+ version = '4.3.2'
+ url = 'https://github.com/zeromq/libzmq/releases/download/v{version}/zeromq-{version}.zip'
+ depends = []
+ built_libraries = {'libzmq.so': 'src/.libs'}
+ need_stl_shared = True
def build_arch(self, arch):
- super(LibZMQRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
#
# libsodium_recipe = Recipe.get_recipe('libsodium', self.ctx)
@@ -27,49 +24,19 @@ def build_arch(self, arch):
curdir = self.get_build_dir(arch.arch)
prefix = join(curdir, "install")
+
with current_directory(curdir):
bash = sh.Command('sh')
shprint(
bash, './configure',
- '--host=arm-linux-androideabi',
+ '--host={}'.format(arch.command_prefix),
'--without-documentation',
'--prefix={}'.format(prefix),
'--with-libsodium=no',
+ '--disable-libunwind',
_env=env)
shprint(sh.make, _env=env)
shprint(sh.make, 'install', _env=env)
- shutil.copyfile('.libs/libzmq.so', join(
- self.ctx.get_libs_dir(arch.arch), 'libzmq.so'))
-
- bootstrap_obj_dir = join(self.ctx.bootstrap.build_dir, 'obj', 'local', arch.arch)
- ensure_dir(bootstrap_obj_dir)
- shutil.copyfile(
- '{}/sources/cxx-stl/gnu-libstdc++/{}/libs/{}/libgnustl_shared.so'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version, arch),
- join(bootstrap_obj_dir, 'libgnustl_shared.so'))
-
- # Copy libgnustl_shared.so
- with current_directory(self.get_build_dir(arch.arch)):
- sh.cp(
- "{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/libgnustl_shared.so".format(ctx=self.ctx,arch=arch),
- self.ctx.get_libs_dir(arch.arch)
- )
-
- def get_recipe_env(self, arch):
- # XXX should stl be configuration for the toolchain itself?
- env = super(LibZMQRecipe, self).get_recipe_env(arch)
- env['CFLAGS'] += ' -Os'
- env['CXXFLAGS'] += ' -Os -fPIC -fvisibility=default'
- env['CXXFLAGS'] += ' -I{}/sources/cxx-stl/gnu-libstdc++/{}/include'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version)
- env['CXXFLAGS'] += ' -I{}/sources/cxx-stl/gnu-libstdc++/{}/libs/{}/include'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version, arch)
- env['CXXFLAGS'] += ' -L{}/sources/cxx-stl/gnu-libstdc++/{}/libs/{}'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version, arch)
- env['CXXFLAGS'] += ' -lgnustl_shared'
- env['LDFLAGS'] += ' -L{}/sources/cxx-stl/gnu-libstdc++/{}/libs/{}'.format(
- self.ctx.ndk_dir, self.ctx.toolchain_version, arch)
- return env
recipe = LibZMQRecipe()
diff --git a/pythonforandroid/recipes/lxml/__init__.py b/pythonforandroid/recipes/lxml/__init__.py
new file mode 100644
index 000000000..4910caf7f
--- /dev/null
+++ b/pythonforandroid/recipes/lxml/__init__.py
@@ -0,0 +1,65 @@
+from pythonforandroid.recipe import Recipe, CompiledComponentsPythonRecipe
+from os.path import exists, join
+from os import uname
+
+
+class LXMLRecipe(CompiledComponentsPythonRecipe):
+ version = '4.8.0'
+ url = 'https://pypi.python.org/packages/source/l/lxml/lxml-{version}.tar.gz' # noqa
+ depends = ['librt', 'libxml2', 'libxslt', 'setuptools']
+ name = 'lxml'
+
+ call_hostpython_via_targetpython = False # Due to setuptools
+
+ def should_build(self, arch):
+ super().should_build(arch)
+
+ py_ver = self.ctx.python_recipe.major_minor_version_string
+ build_platform = "{system}-{machine}".format(
+ system=uname()[0], machine=uname()[-1]
+ ).lower()
+ build_dir = join(
+ self.get_build_dir(arch.arch),
+ "build",
+ "lib." + build_platform + "-" + py_ver,
+ "lxml",
+ )
+ py_libs = ["_elementpath.so", "builder.so", "etree.so", "objectify.so"]
+
+ return not all([exists(join(build_dir, lib)) for lib in py_libs])
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+
+ # libxslt flags
+ libxslt_recipe = Recipe.get_recipe('libxslt', self.ctx)
+ libxslt_build_dir = libxslt_recipe.get_build_dir(arch.arch)
+
+ # libxml2 flags
+ libxml2_recipe = Recipe.get_recipe('libxml2', self.ctx)
+ libxml2_build_dir = libxml2_recipe.get_build_dir(arch.arch)
+
+ env["STATIC"] = "true"
+
+ env["LXML_STATIC_INCLUDE_DIRS"] = "{}:{}".format(
+ join(libxml2_build_dir, "include"), join(libxslt_build_dir)
+ )
+ env["LXML_STATIC_LIBRARY_DIRS"] = "{}:{}:{}".format(
+ join(libxml2_build_dir, ".libs"),
+ join(libxslt_build_dir, "libxslt", ".libs"),
+ join(libxslt_build_dir, "libexslt", ".libs"),
+ )
+
+ env["WITH_XML2_CONFIG"] = join(libxml2_build_dir, "xml2-config")
+ env["WITH_XSLT_CONFIG"] = join(libxslt_build_dir, "xslt-config")
+
+ env["LXML_STATIC_BINARIES"] = "{}:{}:{}".format(
+ join(libxml2_build_dir, ".libs", "libxml2.a"),
+ join(libxslt_build_dir, "libxslt", ".libs", "libxslt.a"),
+ join(libxslt_build_dir, "libexslt", ".libs", "libexslt.a"),
+ )
+
+ return env
+
+
+recipe = LXMLRecipe()
diff --git a/pythonforandroid/recipes/m2crypto/__init__.py b/pythonforandroid/recipes/m2crypto/__init__.py
index 04754a631..57860493e 100644
--- a/pythonforandroid/recipes/m2crypto/__init__.py
+++ b/pythonforandroid/recipes/m2crypto/__init__.py
@@ -1,46 +1,40 @@
-from pythonforandroid.toolchain import PythonRecipe, shprint, shutil, current_directory
-from os.path import join, exists
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.toolchain import current_directory
+from pythonforandroid.logger import shprint, info
+import glob
import sh
-class M2CryptoRecipe(PythonRecipe):
- version = '0.24.0'
+
+class M2CryptoRecipe(CompiledComponentsPythonRecipe):
+ version = '0.30.1'
url = 'https://pypi.python.org/packages/source/M/M2Crypto/M2Crypto-{version}.tar.gz'
- #md5sum = '89557730e245294a6cab06de8ad4fb42'
- depends = ['openssl', 'hostpython2', 'python2', 'setuptools']
+ depends = ['openssl', 'setuptools']
site_packages_name = 'M2Crypto'
call_hostpython_via_targetpython = False
- def build_arch(self, arch):
+ def build_compiled_components(self, arch):
+ info('Building compiled components in {}'.format(self.name))
+
env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
# Build M2Crypto
hostpython = sh.Command(self.hostpython_location)
- r = self.get_recipe('openssl', self.ctx)
- openssl_dir = r.get_build_dir(arch.arch)
- shprint(hostpython,
- 'setup.py',
- 'build_ext',
+ if self.install_in_hostpython:
+ shprint(hostpython, 'setup.py', 'clean', '--all', _env=env)
+ shprint(hostpython, 'setup.py', self.build_cmd,
'-p' + arch.arch,
'-c' + 'unix',
- '--openssl=' + openssl_dir
- , _env=env)
- # Install M2Crypto
- super(M2CryptoRecipe, self).build_arch(arch)
+ '-o' + env['OPENSSL_BUILD_PATH'],
+ '-L' + env['OPENSSL_BUILD_PATH'],
+ _env=env, *self.setup_extra_args)
+ build_dir = glob.glob('build/lib.*')[0]
+ shprint(sh.find, build_dir, '-name', '"*.o"', '-exec',
+ env['STRIP'], '{}', ';', _env=env)
def get_recipe_env(self, arch):
- env = super(M2CryptoRecipe, self).get_recipe_env(arch)
- r = self.get_recipe('openssl', self.ctx)
- openssl_dir = r.get_build_dir(arch.arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \
- ' -I' + join(openssl_dir, 'include')
- # Set linker to use the correct gcc
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
- ' -L' + openssl_dir + \
- ' -lpython2.7' + \
- ' -lssl' + r.version + \
- ' -lcrypto' + r.version
+ env = super().get_recipe_env(arch)
+ env['OPENSSL_BUILD_PATH'] = self.get_recipe('openssl', self.ctx).get_build_dir(arch.arch)
return env
+
recipe = M2CryptoRecipe()
diff --git a/pythonforandroid/recipes/matplotlib/__init__.py b/pythonforandroid/recipes/matplotlib/__init__.py
new file mode 100644
index 000000000..f79cde348
--- /dev/null
+++ b/pythonforandroid/recipes/matplotlib/__init__.py
@@ -0,0 +1,94 @@
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+from pythonforandroid.util import ensure_dir
+
+from os.path import join
+import shutil
+
+
+class MatplotlibRecipe(CppCompiledComponentsPythonRecipe):
+
+ version = '3.5.2'
+ url = 'https://github.com/matplotlib/matplotlib/archive/v{version}.zip'
+
+ depends = ['kiwisolver', 'numpy', 'pillow', 'setuptools', 'freetype']
+
+ python_depends = ['cycler', 'fonttools', 'packaging', 'pyparsing', 'python-dateutil']
+
+ def generate_libraries_pc_files(self, arch):
+ """
+ Create *.pc files for libraries that `matplotib` depends on.
+
+ Because, for unix platforms, the mpl install script uses `pkg-config`
+ to detect libraries installed in non standard locations (our case...
+ well...we don't even install the libraries...so we must trick a little
+ the mlp install).
+ """
+ pkg_config_path = self.get_recipe_env(arch)['PKG_CONFIG_PATH']
+ ensure_dir(pkg_config_path)
+
+ lib_to_pc_file = {
+ # `pkg-config` search for version freetype2.pc, our current
+ # version for freetype, but we have our recipe named without
+ # the version...so we add it in here for our pc file
+ 'freetype': 'freetype2.pc',
+ }
+
+ for lib_name in {'freetype'}:
+ pc_template_file = join(
+ self.get_recipe_dir(),
+ f'lib{lib_name}.pc.template'
+ )
+ # read template file into buffer
+ with open(pc_template_file) as template_file:
+ text_buffer = template_file.read()
+ # set the library absolute path and library version
+ lib_recipe = self.get_recipe(lib_name, self.ctx)
+ text_buffer = text_buffer.replace(
+ 'path_to_built', lib_recipe.get_build_dir(arch.arch),
+ )
+ text_buffer = text_buffer.replace(
+ 'library_version', lib_recipe.version,
+ )
+
+ # write the library pc file into our defined dir `PKG_CONFIG_PATH`
+ pc_dest_file = join(pkg_config_path, lib_to_pc_file[lib_name])
+ with open(pc_dest_file, 'w') as pc_file:
+ pc_file.write(text_buffer)
+
+ def prebuild_arch(self, arch):
+ shutil.copyfile(
+ join(self.get_recipe_dir(), "setup.cfg.template"),
+ join(self.get_build_dir(arch), "mplsetup.cfg"),
+ )
+ self.generate_libraries_pc_files(arch)
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+
+ # we make use of the same directory than `XDG_CACHE_HOME`, for our
+ # custom library pc files, so we have all the install files that we
+ # generate at the same place
+ env['XDG_CACHE_HOME'] = join(self.get_build_dir(arch), 'p4a_files')
+ env['PKG_CONFIG_PATH'] = env['XDG_CACHE_HOME']
+
+ # creating proper *.pc files for our libraries does not seem enough to
+ # success with our build (without depending on system development
+ # libraries), but if we tell the compiler where to find our libraries
+ # and includes, then the install success :)
+ freetype = self.get_recipe('freetype', self.ctx)
+ free_lib_dir = join(freetype.get_build_dir(arch.arch), 'objs', '.libs')
+ free_inc_dir = join(freetype.get_build_dir(arch.arch), 'include')
+ env['CFLAGS'] += f' -I{free_inc_dir}'
+ env['LDFLAGS'] += f' -L{free_lib_dir}'
+
+ # `freetype` could be built with `harfbuzz` support,
+ # so we also include the necessary flags...just to be sure
+ if 'harfbuzz' in self.ctx.recipe_build_order:
+ harfbuzz = self.get_recipe('harfbuzz', self.ctx)
+ harf_build = harfbuzz.get_build_dir(arch.arch)
+ env['CFLAGS'] += f' -I{harf_build} -I{join(harf_build, "src")}'
+ env['LDFLAGS'] += f' -L{join(harf_build, "src", ".libs")}'
+ return env
+
+
+recipe = MatplotlibRecipe()
diff --git a/pythonforandroid/recipes/matplotlib/libfreetype.pc.template b/pythonforandroid/recipes/matplotlib/libfreetype.pc.template
new file mode 100644
index 000000000..df5ef288d
--- /dev/null
+++ b/pythonforandroid/recipes/matplotlib/libfreetype.pc.template
@@ -0,0 +1,10 @@
+prefix=path_to_built
+exec_prefix=${prefix}
+includedir=${prefix}/include
+libdir=${exec_prefix}/objs/.libs
+
+Name: freetype2
+Description: The freetype2 library
+Version: library_version
+Cflags: -I${includedir}
+Libs: -L${libdir} -lfreetype
diff --git a/pythonforandroid/recipes/matplotlib/setup.cfg.template b/pythonforandroid/recipes/matplotlib/setup.cfg.template
new file mode 100644
index 000000000..96ef80d4d
--- /dev/null
+++ b/pythonforandroid/recipes/matplotlib/setup.cfg.template
@@ -0,0 +1,38 @@
+# Rename this file to mplsetup.cfg to modify Matplotlib's build options.
+
+[libs]
+# By default, Matplotlib builds with LTO, which may be slow if you re-compile
+# often, and don't need the space saving/speedup.
+enable_lto = False
+# By default, Matplotlib downloads and builds its own copies of FreeType and of
+# Qhull. You may set the following to True to instead link against a system
+# FreeType/Qhull. As an exception, Matplotlib defaults to the system version
+# of FreeType on AIX.
+system_freetype = True
+#system_qhull = False
+
+[packages]
+# There are a number of data subpackages from Matplotlib that are
+# considered optional. All except 'tests' data (meaning the baseline
+# image files) are installed by default, but that can be changed here.
+#tests = False
+
+[gui_support]
+# Matplotlib supports multiple GUI toolkits, known as backends.
+# The MacOSX backend requires the Cocoa headers included with XCode.
+# You can select whether to build it by uncommenting the following line.
+# It is never built on Linux or Windows, regardless of the config value.
+#
+macosx = False
+
+[rc_options]
+# User-configurable options
+#
+# Default backend, one of: Agg, Cairo, GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo,
+# MacOSX, Pdf, Ps, QtAgg, QtCairo, SVG, TkAgg, WX, WXAgg.
+#
+# The Agg, Ps, Pdf and SVG backends do not require external dependencies. Do
+# not choose MacOSX if you have disabled the relevant extension modules. The
+# default is determined by fallback.
+#
+#backend = Agg
\ No newline at end of file
diff --git a/pythonforandroid/recipes/msgpack-python/__init__.py b/pythonforandroid/recipes/msgpack-python/__init__.py
index 393e6b40b..cdd024b92 100644
--- a/pythonforandroid/recipes/msgpack-python/__init__.py
+++ b/pythonforandroid/recipes/msgpack-python/__init__.py
@@ -1,12 +1,11 @@
-import os
-import sh
from pythonforandroid.recipe import CythonRecipe
class MsgPackRecipe(CythonRecipe):
version = '0.4.7'
url = 'https://pypi.python.org/packages/source/m/msgpack-python/msgpack-python-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), "setuptools"]
+ depends = ["setuptools"]
call_hostpython_via_targetpython = False
+
recipe = MsgPackRecipe()
diff --git a/pythonforandroid/recipes/mysqldb/__init__.py b/pythonforandroid/recipes/mysqldb/__init__.py
deleted file mode 100644
index b217bbb64..000000000
--- a/pythonforandroid/recipes/mysqldb/__init__.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-from os.path import join
-
-
-class MysqldbRecipe(CompiledComponentsPythonRecipe):
- name = 'mysqldb'
- version = '1.2.5'
- url = 'https://pypi.python.org/packages/source/M/MySQL-python/MySQL-python-{version}.zip'
- site_packages_name = 'MySQLdb'
-
- depends = ['python2', 'setuptools', 'libmysqlclient']
-
- patches = ['override-mysql-config.patch',
- 'disable-zip.patch']
-
- # call_hostpython_via_targetpython = False
-
- def convert_newlines(self, filename):
- print('converting newlines in {}'.format(filename))
- with open(filename, 'rb') as f:
- data = f.read()
- with open(filename, 'wb') as f:
- f.write(data.replace(b'\r\n', b'\n').replace(b'\r', b'\n'))
-
- def prebuild_arch(self, arch):
- super(MysqldbRecipe, self).prebuild_arch(arch)
- setupbase = join(self.get_build_dir(arch.arch), 'setup')
- self.convert_newlines(setupbase + '.py')
- self.convert_newlines(setupbase + '_posix.py')
-
- def get_recipe_env(self, arch=None):
- env = super(MysqldbRecipe, self).get_recipe_env(arch)
-
- hostpython = self.get_recipe('hostpython2', self.ctx)
- # TODO: fix hardcoded path
- env['PYTHONPATH'] = (join(hostpython.get_build_dir(arch.arch),
- 'build', 'lib.linux-x86_64-2.7') +
- ':' + env.get('PYTHONPATH', ''))
-
- libmysql = self.get_recipe('libmysqlclient', self.ctx)
- mydir = join(libmysql.get_build_dir(arch.arch), 'libmysqlclient')
- # env['CFLAGS'] += ' -I' + join(mydir, 'include')
- # env['LDFLAGS'] += ' -L' + join(mydir)
- libdir = self.ctx.get_libs_dir(arch.arch)
- env['MYSQL_libs'] = env['MYSQL_libs_r'] = '-L' + libdir + ' -lmysql'
- env['MYSQL_cflags'] = env['MYSQL_include'] = '-I' + join(mydir,
- 'include')
-
- return env
-
-
-recipe = MysqldbRecipe()
diff --git a/pythonforandroid/recipes/mysqldb/disable-zip.patch b/pythonforandroid/recipes/mysqldb/disable-zip.patch
deleted file mode 100644
index 51f804ea2..000000000
--- a/pythonforandroid/recipes/mysqldb/disable-zip.patch
+++ /dev/null
@@ -1,8 +0,0 @@
---- mysqldb/setup.py 2014-01-02 13:52:50.000000000 -0600
-+++ b/setup.py 2016-01-13 15:48:36.781216443 -0600
-@@ -18,4 +18,5 @@
- metadata['ext_modules'] = [
- setuptools.Extension(sources=['_mysql.c'], **options)]
- metadata['long_description'] = metadata['long_description'].replace(r'\n', '')
-+metadata['zip_safe'] = False
- setuptools.setup(**metadata)
diff --git a/pythonforandroid/recipes/mysqldb/override-mysql-config.patch b/pythonforandroid/recipes/mysqldb/override-mysql-config.patch
deleted file mode 100644
index 195ebdacb..000000000
--- a/pythonforandroid/recipes/mysqldb/override-mysql-config.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- mysqldb/setup_posix.py 2014-01-02 13:52:50.000000000 -0600
-+++ b/setup_posix.py 2016-01-13 15:48:18.732883429 -0600
-@@ -13,17 +13,7 @@
- return "-%s" % f
-
- def mysql_config(what):
-- from os import popen
--
-- f = popen("%s --%s" % (mysql_config.path, what))
-- data = f.read().strip().split()
-- ret = f.close()
-- if ret:
-- if ret/256:
-- data = []
-- if ret/256 > 1:
-- raise EnvironmentError("%s not found" % (mysql_config.path,))
-- return data
-+ return os.environ['MYSQL_' + what.replace('-', '_')].strip().split()
- mysql_config.path = "mysql_config"
-
- def get_config():
diff --git a/pythonforandroid/recipes/ndghttpsclient b/pythonforandroid/recipes/ndghttpsclient
index bd971fcdc..bfe581a11 100644
--- a/pythonforandroid/recipes/ndghttpsclient
+++ b/pythonforandroid/recipes/ndghttpsclient
@@ -1,9 +1,9 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
class NdgHttpsClientRecipe(PythonRecipe):
- version = '0.4.0'
+ version = '0.5.1'
url = 'https://pypi.python.org/packages/source/n/ndg-httpsclient/ndg_httpsclient-{version}.tar.gz'
- depends = ['python2', 'pyopenssl', 'cryptography']
+ depends = ['python3', 'pyopenssl', 'cryptography']
call_hostpython_via_targetpython = False
recipe = NdgHttpsClientRecipe()
diff --git a/pythonforandroid/recipes/netifaces/__init__.py b/pythonforandroid/recipes/netifaces/__init__.py
index 74273fc5f..8ad138202 100644
--- a/pythonforandroid/recipes/netifaces/__init__.py
+++ b/pythonforandroid/recipes/netifaces/__init__.py
@@ -3,25 +3,17 @@
class NetifacesRecipe(CompiledComponentsPythonRecipe):
- version = '0.10.4'
+ version = '0.10.9'
- url = 'https://pypi.python.org/packages/18/fa/dd13d4910aea339c0bb87d2b3838d8fd923c11869b1f6e741dbd0ff3bc00/netifaces-{version}.tar.gz'
+ url = 'https://files.pythonhosted.org/packages/source/n/netifaces/netifaces-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools']
+ depends = ['setuptools']
+
+ patches = ['fix-build.patch']
site_packages_name = 'netifaces'
call_hostpython_via_targetpython = False
- def get_recipe_env(self, arch):
- env = super(NetifacesRecipe, self).get_recipe_env(arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
- # Set linker to use the correct gcc
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
- ' -lpython2.7'
- return env
-
recipe = NetifacesRecipe()
diff --git a/pythonforandroid/recipes/netifaces/fix-build.patch b/pythonforandroid/recipes/netifaces/fix-build.patch
new file mode 100644
index 000000000..3404c4fe6
--- /dev/null
+++ b/pythonforandroid/recipes/netifaces/fix-build.patch
@@ -0,0 +1,11 @@
+--- netifaces/setup.py.orig 2018-05-02 09:45:09.000000000 +0200
++++ netifaces/setup.py 2018-12-11 14:12:02.785808692 +0100
+@@ -55,7 +55,7 @@
+ self.check_requirements()
+ build_ext.build_extensions(self)
+
+- def test_build(self, contents, link=True, execute=False, libraries=None,
++ def test_build(self, contents, link=False, execute=False, libraries=None,
+ include_dirs=None, library_dirs=None):
+ name = os.path.join(self.build_temp, 'conftest-%s.c' % self.conftestidx)
+ self.conftestidx += 1
diff --git a/pythonforandroid/recipes/numpy/__init__.py b/pythonforandroid/recipes/numpy/__init__.py
index 9c2ce3305..55a027977 100644
--- a/pythonforandroid/recipes/numpy/__init__.py
+++ b/pythonforandroid/recipes/numpy/__init__.py
@@ -1,55 +1,75 @@
-
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe, warning
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.logger import shprint, info
+from pythonforandroid.util import current_directory
+from multiprocessing import cpu_count
+from os.path import join
+import glob
+import sh
+import shutil
class NumpyRecipe(CompiledComponentsPythonRecipe):
-
- version = '1.9.2'
- url = 'https://pypi.python.org/packages/source/n/numpy/numpy-{version}.tar.gz'
- site_packages_name= 'numpy'
-
- depends = ['python2']
-
- patches = ['patches/fix-numpy.patch',
- 'patches/prevent_libs_check.patch',
- 'patches/ar.patch',
- 'patches/lib.patch']
-
- def get_recipe_env(self, arch):
- """ looks like numpy has no proper -L flags. Code copied and adapted from
- https://github.com/frmdstryr/p4a-numpy/
- """
-
- env = super(NumpyRecipe, self).get_recipe_env(arch)
- #: Hack add path L to crystax as a CFLAG
-
- py_ver = '3.5'
- if {'python2crystax', 'python2'} & set(self.ctx.recipe_build_order):
- py_ver = '2.7'
-
- py_so = '2.7' if py_ver == '2.7' else '3.5m'
-
- api_ver = self.ctx.android_api
-
- platform = 'arm' if 'arm' in arch.arch else arch.arch
- #: Not sure why but we have to inject these into the CC and LD env's for it to
- #: use the correct arguments.
- flags = " -L{ctx.ndk_dir}/platforms/android-{api_ver}/arch-{platform}/usr/lib/" \
- " --sysroot={ctx.ndk_dir}/platforms/android-{api_ver}/arch-{platform}" \
- .format(ctx=self.ctx, arch=arch, platform=platform, api_ver=api_ver,
- py_so=py_so, py_ver=py_ver)
- if flags not in env['CC']:
- env['CC'] += flags
- if flags not in env['LD']:
- env['LD'] += flags + ' -shared'
+
+ version = '1.22.3'
+ url = 'https://pypi.python.org/packages/source/n/numpy/numpy-{version}.zip'
+ site_packages_name = 'numpy'
+ depends = ['setuptools', 'cython']
+ install_in_hostpython = True
+ call_hostpython_via_targetpython = False
+
+ patches = [
+ join("patches", "remove-default-paths.patch"),
+ join("patches", "add_libm_explicitly_to_build.patch"),
+ join("patches", "ranlib.patch"),
+ ]
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+
+ # _PYTHON_HOST_PLATFORM declares that we're cross-compiling
+ # and avoids issues when building on macOS for Android targets.
+ env["_PYTHON_HOST_PLATFORM"] = arch.command_prefix
+
+ # NPY_DISABLE_SVML=1 allows numpy to build for non-AVX512 CPUs
+ # See: https://github.com/numpy/numpy/issues/21196
+ env["NPY_DISABLE_SVML"] = "1"
return env
- def prebuild_arch(self, arch):
- super(NumpyRecipe, self).prebuild_arch(arch)
+ def _build_compiled_components(self, arch):
+ info('Building compiled components in {}'.format(self.name))
- warning('Numpy is built assuming the archiver name is '
- 'arm-linux-androideabi-ar, which may not always be true!')
+ env = self.get_recipe_env(arch)
+ with current_directory(self.get_build_dir(arch.arch)):
+ hostpython = sh.Command(self.hostpython_location)
+ shprint(hostpython, 'setup.py', self.build_cmd, '-v',
+ _env=env, *self.setup_extra_args)
+ build_dir = glob.glob('build/lib.*')[0]
+ shprint(sh.find, build_dir, '-name', '"*.o"', '-exec',
+ env['STRIP'], '{}', ';', _env=env)
+
+ def _rebuild_compiled_components(self, arch, env):
+ info('Rebuilding compiled components in {}'.format(self.name))
+
+ hostpython = sh.Command(self.real_hostpython_location)
+ shprint(hostpython, 'setup.py', 'clean', '--all', '--force', _env=env)
+ shprint(hostpython, 'setup.py', self.build_cmd, '-v', _env=env,
+ *self.setup_extra_args)
+
+ def build_compiled_components(self, arch):
+ self.setup_extra_args = ['-j', str(cpu_count())]
+ self._build_compiled_components(arch)
+ self.setup_extra_args = []
+
+ def rebuild_compiled_components(self, arch, env):
+ self.setup_extra_args = ['-j', str(cpu_count())]
+ self._rebuild_compiled_components(arch, env)
+ self.setup_extra_args = []
+
+ def get_hostrecipe_env(self, arch):
+ env = super().get_hostrecipe_env(arch)
+ env['RANLIB'] = shutil.which('ranlib')
+ return env
recipe = NumpyRecipe()
diff --git a/pythonforandroid/recipes/numpy/patches/add_libm_explicitly_to_build.patch b/pythonforandroid/recipes/numpy/patches/add_libm_explicitly_to_build.patch
new file mode 100644
index 000000000..f9ba9e924
--- /dev/null
+++ b/pythonforandroid/recipes/numpy/patches/add_libm_explicitly_to_build.patch
@@ -0,0 +1,20 @@
+diff --git a/numpy/linalg/setup.py b/numpy/linalg/setup.py
+index 66c07c9..d34bd93 100644
+--- a/numpy/linalg/setup.py
++++ b/numpy/linalg/setup.py
+@@ -46,6 +46,7 @@ def configuration(parent_package='', top_path=None):
+ sources=['lapack_litemodule.c', get_lapack_lite_sources],
+ depends=['lapack_lite/f2c.h'],
+ extra_info=lapack_info,
++ libraries=['m'],
+ )
+
+ # umath_linalg module
+@@ -54,7 +54,7 @@ def configuration(parent_package='', top_path=None):
+ sources=['umath_linalg.c.src', get_lapack_lite_sources],
+ depends=['lapack_lite/f2c.h'],
+ extra_info=lapack_info,
+- libraries=['npymath'],
++ libraries=['npymath', 'm'],
+ )
+ return config
diff --git a/pythonforandroid/recipes/numpy/patches/ar.patch b/pythonforandroid/recipes/numpy/patches/ar.patch
deleted file mode 100644
index c579d5e91..000000000
--- a/pythonforandroid/recipes/numpy/patches/ar.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/numpy/distutils/unixccompiler.py 2015-02-01 17:38:21.000000000 +0100
-+++ b/numpy/distutils/unixccompiler.py 2015-07-08 17:21:05.742468485 +0200
-@@ -82,6 +82,8 @@
- pass
- self.mkpath(os.path.dirname(output_filename))
- tmp_objects = objects + self.objects
-+ from os import environ
-+ self.archiver[0] = 'arm-linux-androideabi-ar'
- while tmp_objects:
- objects = tmp_objects[:50]
- tmp_objects = tmp_objects[50:]
diff --git a/pythonforandroid/recipes/numpy/patches/fix-numpy.patch b/pythonforandroid/recipes/numpy/patches/fix-numpy.patch
deleted file mode 100644
index 52d447f5d..000000000
--- a/pythonforandroid/recipes/numpy/patches/fix-numpy.patch
+++ /dev/null
@@ -1,66 +0,0 @@
-diff --git a/numpy/core/src/multiarray/numpyos.c b/numpy/core/src/multiarray/numpyos.c
-index 44b32f4..378e199 100644
---- a/numpy/core/src/multiarray/numpyos.c
-+++ b/numpy/core/src/multiarray/numpyos.c
-@@ -165,8 +165,7 @@ ensure_decimal_point(char* buffer, size_t buf_size)
- static void
- change_decimal_from_locale_to_dot(char* buffer)
- {
-- struct lconv *locale_data = localeconv();
-- const char *decimal_point = locale_data->decimal_point;
-+ const char *decimal_point = ".";
-
- if (decimal_point[0] != '.' || decimal_point[1] != 0) {
- size_t decimal_point_len = strlen(decimal_point);
-@@ -448,8 +447,7 @@ NumPyOS_ascii_strtod_plain(const char *s, char** endptr)
- NPY_NO_EXPORT double
- NumPyOS_ascii_strtod(const char *s, char** endptr)
- {
-- struct lconv *locale_data = localeconv();
-- const char *decimal_point = locale_data->decimal_point;
-+ const char *decimal_point = ".";
- size_t decimal_point_len = strlen(decimal_point);
-
- char buffer[FLOAT_FORMATBUFLEN+1];
-diff --git a/numpy/core/src/private/npy_config.h b/numpy/core/src/private/npy_config.h
-index f768c90..4e5d168 100644
---- a/numpy/core/src/private/npy_config.h
-+++ b/numpy/core/src/private/npy_config.h
-@@ -41,4 +41,10 @@
- #undef HAVE_ATAN2
- #endif
-
-+/* Android only */
-+#ifdef ANDROID
-+#undef HAVE_LDEXPL
-+#undef HAVE_FREXPL
-+#endif
-+
- #endif
-diff --git a/numpy/testing/__init__.py b/numpy/testing/__init__.py
-index 258cbe9..ce4e0eb 100644
---- a/numpy/testing/__init__.py
-+++ b/numpy/testing/__init__.py
-@@ -1,16 +1,7 @@
--"""Common test support for all numpy test scripts.
--
--This single module should provide all the common functionality for numpy tests
--in a single location, so that test scripts can just import it and work right
--away.
--
--"""
--from __future__ import division, absolute_import, print_function
--
--from unittest import TestCase
--
--from . import decorators as dec
--from .utils import *
--from .nosetester import NoseTester as Tester
--from .nosetester import run_module_suite
-+# fake tester, android don't have unittest
-+class Tester(object):
-+ def test(self, *args, **kwargs):
-+ pass
-+ def bench(self, *args, **kwargs):
-+ pass
- test = Tester().test
diff --git a/pythonforandroid/recipes/numpy/patches/lib.patch b/pythonforandroid/recipes/numpy/patches/lib.patch
deleted file mode 100644
index 3087eb451..000000000
--- a/pythonforandroid/recipes/numpy/patches/lib.patch
+++ /dev/null
@@ -1,44 +0,0 @@
---- a/numpy/linalg/setup.py 2015-07-09 14:15:59.850853336 +0200
-+++ b/numpy/linalg/setup.py 2015-07-09 14:21:59.403889000 +0200
-@@ -37,7 +37,8 @@
- config.add_extension('lapack_lite',
- sources = [get_lapack_lite_sources],
- depends = ['lapack_litemodule.c'] + lapack_lite_src,
-- extra_info = lapack_info
-+ extra_info = lapack_info,
-+ libraries = ['m'],
- )
-
- # umath_linalg module
-@@ -46,7 +47,7 @@
- sources = [get_lapack_lite_sources],
- depends = ['umath_linalg.c.src'] + lapack_lite_src,
- extra_info = lapack_info,
-- libraries = ['npymath'],
-+ libraries = ['npymath','m'],
- )
-
- return config
---- a/numpy/fft/setup.py 2015-07-09 14:35:22.299888028 +0200
-+++ b/numpy/fft/setup.py 2015-07-09 14:33:54.858392578 +0200
-@@ -9,7 +9,8 @@
-
- # Configure fftpack_lite
- config.add_extension('fftpack_lite',
-- sources=['fftpack_litemodule.c', 'fftpack.c']
-+ sources=['fftpack_litemodule.c', 'fftpack.c'],
-+ libraries = ['m']
- )
-
-
---- a/numpy/random/setup.orig.py 2015-07-09 14:44:41.105174826 +0200
-+++ b/numpy/random/setup.py 2015-07-09 14:46:08.592679877 +0200
-@@ -38,7 +38,7 @@
- if needs_mingw_ftime_workaround():
- defs.append(("NPY_NEEDS_MINGW_TIME_WORKAROUND", None))
-
-- libs = []
-+ libs = ['m']
- # Configure mtrand
- config.add_extension('mtrand',
- sources=[join('mtrand', x) for x in
diff --git a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch b/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch
deleted file mode 100644
index 73f2a9275..000000000
--- a/pythonforandroid/recipes/numpy/patches/prevent_libs_check.patch
+++ /dev/null
@@ -1,12 +0,0 @@
-diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py
-index a050430..471e958 100644
---- a/numpy/distutils/system_info.py
-+++ b/numpy/distutils/system_info.py
-@@ -610,6 +610,7 @@ class system_info:
- return self.get_paths(self.section, key)
-
- def get_libs(self, key, default):
-+ return []
- try:
- libs = self.cp.get(self.section, key)
- except NoOptionError:
diff --git a/pythonforandroid/recipes/numpy/patches/ranlib.patch b/pythonforandroid/recipes/numpy/patches/ranlib.patch
new file mode 100644
index 000000000..c0b5dad6b
--- /dev/null
+++ b/pythonforandroid/recipes/numpy/patches/ranlib.patch
@@ -0,0 +1,11 @@
+diff -Naur numpy.orig/numpy/distutils/unixccompiler.py numpy/numpy/distutils/unixccompiler.py
+--- numpy.orig/numpy/distutils/unixccompiler.py 2022-05-28 10:22:10.000000000 +0200
++++ numpy/numpy/distutils/unixccompiler.py 2022-05-28 10:22:24.000000000 +0200
+@@ -124,6 +124,7 @@
+ # platform intelligence here to skip ranlib if it's not
+ # needed -- or maybe Python's configure script took care of
+ # it for us, hence the check for leading colon.
++ self.ranlib = [os.environ.get('RANLIB')]
+ if self.ranlib:
+ display = '%s:@ %s' % (os.path.basename(self.ranlib[0]),
+ output_filename)
diff --git a/pythonforandroid/recipes/numpy/patches/remove-default-paths.patch b/pythonforandroid/recipes/numpy/patches/remove-default-paths.patch
new file mode 100644
index 000000000..3581f0f9e
--- /dev/null
+++ b/pythonforandroid/recipes/numpy/patches/remove-default-paths.patch
@@ -0,0 +1,28 @@
+diff --git a/numpy/distutils/system_info.py b/numpy/distutils/system_info.py
+index fc7018a..7b514bc 100644
+--- a/numpy/distutils/system_info.py
++++ b/numpy/distutils/system_info.py
+@@ -340,10 +340,10 @@ if os.path.join(sys.prefix, 'lib') not in default_lib_dirs:
+ default_include_dirs.append(os.path.join(sys.prefix, 'include'))
+ default_src_dirs.append(os.path.join(sys.prefix, 'src'))
+
+-default_lib_dirs = [_m for _m in default_lib_dirs if os.path.isdir(_m)]
+-default_runtime_dirs = [_m for _m in default_runtime_dirs if os.path.isdir(_m)]
+-default_include_dirs = [_m for _m in default_include_dirs if os.path.isdir(_m)]
+-default_src_dirs = [_m for _m in default_src_dirs if os.path.isdir(_m)]
++default_lib_dirs = [] #[_m for _m in default_lib_dirs if os.path.isdir(_m)]
++default_runtime_dirs =[] # [_m for _m in default_runtime_dirs if os.path.isdir(_m)]
++default_include_dirs =[] # [_m for _m in default_include_dirs if os.path.isdir(_m)]
++default_src_dirs =[] # [_m for _m in default_src_dirs if os.path.isdir(_m)]
+
+ so_ext = get_shared_lib_extension()
+
+@@ -814,7 +814,7 @@ class system_info(object):
+ path = self.get_paths(self.section, key)
+ if path == ['']:
+ path = []
+- return path
++ return []
+
+ def get_include_dirs(self, key='include_dirs'):
+ return self.get_paths(self.section, key)
diff --git a/pythonforandroid/recipes/omemo-backend-signal/__init__.py b/pythonforandroid/recipes/omemo-backend-signal/__init__.py
new file mode 100644
index 000000000..8efa81592
--- /dev/null
+++ b/pythonforandroid/recipes/omemo-backend-signal/__init__.py
@@ -0,0 +1,21 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class OmemoBackendSignalRecipe(PythonRecipe):
+ name = 'omemo-backend-signal'
+ version = '0.2.5'
+ url = 'https://pypi.python.org/packages/source/o/omemo-backend-signal/omemo-backend-signal-{version}.tar.gz'
+ site_packages_name = 'omemo-backend-signal'
+ depends = [
+ 'setuptools',
+ 'protobuf_cpp',
+ 'x3dh',
+ 'DoubleRatchet',
+ 'hkdf==0.0.3',
+ 'cryptography',
+ 'omemo',
+ ]
+ call_hostpython_via_targetpython = False
+
+
+recipe = OmemoBackendSignalRecipe()
diff --git a/pythonforandroid/recipes/omemo/__init__.py b/pythonforandroid/recipes/omemo/__init__.py
new file mode 100644
index 000000000..7ea3d6898
--- /dev/null
+++ b/pythonforandroid/recipes/omemo/__init__.py
@@ -0,0 +1,17 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class OmemoRecipe(PythonRecipe):
+ name = 'omemo'
+ version = '0.11.0'
+ url = 'https://pypi.python.org/packages/source/O/OMEMO/OMEMO-{version}.tar.gz'
+ site_packages_name = 'omemo'
+ depends = [
+ 'setuptools',
+ 'x3dh',
+ 'cryptography',
+ ]
+ call_hostpython_via_targetpython = False
+
+
+recipe = OmemoRecipe()
diff --git a/pythonforandroid/recipes/openal/__init__.py b/pythonforandroid/recipes/openal/__init__.py
new file mode 100644
index 000000000..f5b7d015d
--- /dev/null
+++ b/pythonforandroid/recipes/openal/__init__.py
@@ -0,0 +1,31 @@
+from pythonforandroid.recipe import NDKRecipe
+from pythonforandroid.toolchain import current_directory, shprint
+from os.path import join
+import sh
+
+
+class OpenALRecipe(NDKRecipe):
+ version = '1.21.1'
+ url = 'https://github.com/kcat/openal-soft/archive/refs/tags/{version}.tar.gz'
+
+ generated_libraries = ['libopenal.so']
+
+ def build_arch(self, arch):
+ with current_directory(self.get_build_dir(arch.arch)):
+ env = self.get_recipe_env(arch)
+ cmake_args = [
+ "-DANDROID_STL=" + self.stl_lib_name,
+ "-DCMAKE_TOOLCHAIN_FILE={}".format(
+ join(self.ctx.ndk_dir, "build", "cmake", "android.toolchain.cmake")
+ ),
+ ]
+ shprint(
+ sh.cmake, '.',
+ *cmake_args,
+ _env=env
+ )
+ shprint(sh.make, _env=env)
+ self.install_libs(arch, 'libopenal.so')
+
+
+recipe = OpenALRecipe()
diff --git a/pythonforandroid/recipes/opencv/__init__.py b/pythonforandroid/recipes/opencv/__init__.py
index 7e70162ea..650c77e50 100644
--- a/pythonforandroid/recipes/opencv/__init__.py
+++ b/pythonforandroid/recipes/opencv/__init__.py
@@ -1,53 +1,147 @@
-import os
-import sh
-from pythonforandroid.toolchain import (
- NDKRecipe,
- Recipe,
- current_directory,
- info,
- shprint,
-)
from multiprocessing import cpu_count
+from os.path import join
+
+import sh
+
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import NDKRecipe
+from pythonforandroid.util import current_directory, ensure_dir
class OpenCVRecipe(NDKRecipe):
- version = '2.4.10.1'
- url = 'https://github.com/Itseez/opencv/archive/{version}.zip'
- #md5sum = '2ddfa98e867e6611254040df841186dc'
+ '''
+ .. versionchanged:: 0.7.1
+ rewrote recipe to support the python bindings (cv2.so) and enable the
+ build of most of the libraries of the opencv's package, so we can
+ process images, videos, objects, photos...
+ '''
+ version = '4.5.1'
+ url = 'https://github.com/opencv/opencv/archive/{version}.zip'
depends = ['numpy']
- patches = ['patches/p4a_build-2.4.10.1.patch']
- generated_libraries = ['cv2.so']
-
- def prebuild_arch(self, arch):
- self.apply_patches(arch)
-
- def get_recipe_env(self,arch):
- env = super(OpenCVRecipe, self).get_recipe_env(arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
+ patches = ['patches/p4a_build.patch']
+ generated_libraries = [
+ 'libopencv_features2d.so',
+ 'libopencv_imgproc.so',
+ 'libopencv_stitching.so',
+ 'libopencv_calib3d.so',
+ 'libopencv_flann.so',
+ 'libopencv_ml.so',
+ 'libopencv_videoio.so',
+ 'libopencv_core.so',
+ 'libopencv_highgui.so',
+ 'libopencv_objdetect.so',
+ 'libopencv_video.so',
+ 'libopencv_dnn.so',
+ 'libopencv_imgcodecs.so',
+ 'libopencv_photo.so',
+ ]
+
+ def get_lib_dir(self, arch):
+ return join(self.get_build_dir(arch.arch), 'build', 'lib', arch.arch)
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
env['ANDROID_NDK'] = self.ctx.ndk_dir
env['ANDROID_SDK'] = self.ctx.sdk_dir
- env['SITEPACKAGES_PATH'] = self.ctx.get_site_packages_dir()
return env
def build_arch(self, arch):
- with current_directory(self.get_build_dir(arch.arch)):
+ build_dir = join(self.get_build_dir(arch.arch), 'build')
+ ensure_dir(build_dir)
+
+ opencv_extras = []
+ if 'opencv_extras' in self.ctx.recipe_build_order:
+ opencv_extras_dir = self.get_recipe(
+ 'opencv_extras', self.ctx).get_build_dir(arch.arch)
+ opencv_extras = [
+ f'-DOPENCV_EXTRA_MODULES_PATH={opencv_extras_dir}/modules',
+ '-DBUILD_opencv_legacy=OFF',
+ ]
+
+ with current_directory(build_dir):
env = self.get_recipe_env(arch)
- cvsrc = self.get_build_dir(arch.arch)
- lib_dir = os.path.join(self.ctx.get_python_install_dir(), "lib")
-
+
+ python_major = self.ctx.python_recipe.version[0]
+ python_include_root = self.ctx.python_recipe.include_root(arch.arch)
+ python_site_packages = self.ctx.get_site_packages_dir(arch)
+ python_link_root = self.ctx.python_recipe.link_root(arch.arch)
+ python_link_version = self.ctx.python_recipe.link_version
+ python_library = join(python_link_root,
+ 'libpython{}.so'.format(python_link_version))
+ python_include_numpy = join(python_site_packages,
+ 'numpy', 'core', 'include')
+
shprint(sh.cmake,
- '-DP4A=ON','-DANDROID_ABI={}'.format(arch.arch),
- '-DCMAKE_TOOLCHAIN_FILE={}/platforms/android/android.toolchain.cmake'.format(cvsrc),
- '-DPYTHON_INCLUDE_PATH={}/include/python2.7'.format(env['PYTHON_ROOT']),
- '-DPYTHON_LIBRARY={}/lib/libpython2.7.so'.format(env['PYTHON_ROOT']),
- '-DPYTHON_NUMPY_INCLUDE_DIR={}/numpy/core/include'.format(env['SITEPACKAGES_PATH']),
- '-DANDROID_EXECUTABLE={}/tools/android'.format(env['ANDROID_SDK']),
- '-DBUILD_TESTS=OFF', '-DBUILD_PERF_TESTS=OFF', '-DBUILD_EXAMPLES=OFF', '-DBUILD_ANDROID_EXAMPLES=OFF',
- '-DPYTHON_PACKAGES_PATH={}'.format(env['SITEPACKAGES_PATH']),
- cvsrc,
- _env=env)
- shprint(sh.make,'-j',str(cpu_count()),'opencv_python')
- shprint(sh.cmake,'-DCOMPONENT=python','-P','./cmake_install.cmake')
- sh.cp('-a',sh.glob('./lib/{}/lib*.so'.format(arch.arch)),lib_dir)
+ '-DP4A=ON',
+ '-DANDROID_ABI={}'.format(arch.arch),
+ '-DANDROID_STANDALONE_TOOLCHAIN={}'.format(self.ctx.ndk_dir),
+ '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),
+ '-DANDROID_EXECUTABLE={}/tools/android'.format(env['ANDROID_SDK']),
+ '-DANDROID_SDK_TOOLS_VERSION=6514223',
+ '-DANDROID_PROJECTS_SUPPORT_GRADLE=ON',
+
+ '-DCMAKE_TOOLCHAIN_FILE={}'.format(
+ join(self.ctx.ndk_dir, 'build', 'cmake',
+ 'android.toolchain.cmake')),
+ # Make the linkage with our python library, otherwise we
+ # will get dlopen error when trying to import cv2's module.
+ '-DCMAKE_SHARED_LINKER_FLAGS=-L{path} -lpython{version}'.format(
+ path=python_link_root,
+ version=python_link_version),
+
+ '-DBUILD_WITH_STANDALONE_TOOLCHAIN=ON',
+ # Force to build as shared libraries the cv2's dependant
+ # libs or we will not be able to link with our python
+ '-DBUILD_SHARED_LIBS=ON',
+ '-DBUILD_STATIC_LIBS=OFF',
+
+ # Disable some opencv's features
+ '-DBUILD_opencv_java=OFF',
+ '-DBUILD_opencv_java_bindings_generator=OFF',
+ # '-DBUILD_opencv_highgui=OFF',
+ # '-DBUILD_opencv_imgproc=OFF',
+ # '-DBUILD_opencv_flann=OFF',
+ '-DBUILD_TESTS=OFF',
+ '-DBUILD_PERF_TESTS=OFF',
+ '-DENABLE_TESTING=OFF',
+ '-DBUILD_EXAMPLES=OFF',
+ '-DBUILD_ANDROID_EXAMPLES=OFF',
+
+ # Force to only build our version of python
+ '-DBUILD_OPENCV_PYTHON{major}=ON'.format(major=python_major),
+ '-DBUILD_OPENCV_PYTHON{major}=OFF'.format(
+ major='2' if python_major == '3' else '3'),
+
+ # Force to install the `cv2.so` library directly into
+ # python's site packages (otherwise the cv2's loader fails
+ # on finding the cv2.so library)
+ '-DOPENCV_SKIP_PYTHON_LOADER=ON',
+ '-DOPENCV_PYTHON{major}_INSTALL_PATH={site_packages}'.format(
+ major=python_major, site_packages=python_site_packages),
+
+ # Define python's paths for: exe, lib, includes, numpy...
+ '-DPYTHON_DEFAULT_EXECUTABLE={}'.format(self.ctx.hostpython),
+ '-DPYTHON{major}_EXECUTABLE={host_python}'.format(
+ major=python_major, host_python=self.ctx.hostpython),
+ '-DPYTHON{major}_INCLUDE_PATH={include_path}'.format(
+ major=python_major, include_path=python_include_root),
+ '-DPYTHON{major}_LIBRARIES={python_lib}'.format(
+ major=python_major, python_lib=python_library),
+ '-DPYTHON{major}_NUMPY_INCLUDE_DIRS={numpy_include}'.format(
+ major=python_major, numpy_include=python_include_numpy),
+ '-DPYTHON{major}_PACKAGES_PATH={site_packages}'.format(
+ major=python_major, site_packages=python_site_packages),
+
+ *opencv_extras,
+
+ self.get_build_dir(arch.arch),
+ _env=env)
+ shprint(sh.make, '-j' + str(cpu_count()), 'opencv_python' + python_major)
+ # Install python bindings (cv2.so)
+ shprint(sh.cmake, '-DCOMPONENT=python', '-P', './cmake_install.cmake')
+ # Copy third party shared libs that we need in our final apk
+ sh.cp('-a', sh.glob('./lib/{}/lib*.so'.format(arch.arch)),
+ self.ctx.get_libs_dir(arch.arch))
+
recipe = OpenCVRecipe()
diff --git a/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch b/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch
deleted file mode 100644
index a7a60aa3b..000000000
--- a/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch
+++ /dev/null
@@ -1,66 +0,0 @@
-diff --git a/cmake/OpenCVDetectPython.cmake b/cmake/OpenCVDetectPython.cmake
-index 31c2c1e..c890917 100644
---- a/cmake/OpenCVDetectPython.cmake
-+++ b/cmake/OpenCVDetectPython.cmake
-@@ -36,7 +36,7 @@ if(PYTHON_EXECUTABLE)
- unset(PYTHON_VERSION_FULL)
- endif()
-
-- if(NOT ANDROID AND NOT IOS)
-+ if(P4A OR NOT ANDROID AND NOT IOS)
- ocv_check_environment_variables(PYTHON_LIBRARY PYTHON_INCLUDE_DIR)
- if(CMAKE_CROSSCOMPILING)
- find_host_package(PythonLibs ${PYTHON_VERSION_MAJOR_MINOR})
-@@ -51,7 +51,7 @@ if(PYTHON_EXECUTABLE)
- endif()
- endif()
-
-- if(NOT ANDROID AND NOT IOS)
-+ if(P4A OR NOT ANDROID AND NOT IOS)
- if(CMAKE_HOST_UNIX)
- execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import *; print get_python_lib()"
- RESULT_VARIABLE PYTHON_CVPY_PROCESS
-@@ -117,7 +117,7 @@ if(PYTHON_EXECUTABLE)
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- endif()
- endif()
-- endif(NOT ANDROID AND NOT IOS)
-+ endif(P4A OR NOT ANDROID AND NOT IOS)
-
- if(BUILD_DOCS)
- find_host_program(SPHINX_BUILD sphinx-build)
-diff --git a/modules/python/CMakeLists.txt b/modules/python/CMakeLists.txt
-index 3c0f2fd..7ba234a 100644
---- a/modules/python/CMakeLists.txt
-+++ b/modules/python/CMakeLists.txt
-@@ -5,7 +5,7 @@
- if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
- ocv_module_disable(python)
- endif()
--if(ANDROID OR IOS OR NOT PYTHONLIBS_FOUND OR NOT PYTHON_USE_NUMPY)
-+if(ANDROID AND NOT P4A OR IOS OR NOT PYTHONLIBS_FOUND OR NOT PYTHON_USE_NUMPY)
- ocv_module_disable(python)
- endif()
-
-diff --git a/modules/androidcamera/src/camera_activity.cpp b/modules/androidcamera/src/camera_activity.cpp
-index 84db3e1..4222526 100644
---- a/modules/androidcamera/src/camera_activity.cpp
-+++ b/modules/androidcamera/src/camera_activity.cpp
-@@ -7,6 +7,7 @@
- #include <string>
- #include <vector>
- #include <algorithm>
-+#include <stdlib.h>
- #include <opencv2/core/version.hpp>
- #include "camera_activity.hpp"
- #include "camera_wrapper.h"
-@@ -342,6 +343,8 @@ std::string CameraWrapperConnector::getPathLibFolder()
-
- char* pathEnd = strrchr(pathBegin, '/');
- pathEnd[1] = 0;
-+ pathBegin = realpath((std::string(pathBegin)+"../../../../lib").c_str(), lineBuf);
-+ pathBegin = strcat(pathBegin, "/");
-
- LOGD("Libraries folder found: %s", pathBegin);
-
-
diff --git a/pythonforandroid/recipes/opencv/patches/p4a_build.patch b/pythonforandroid/recipes/opencv/patches/p4a_build.patch
new file mode 100644
index 000000000..fd60c01d3
--- /dev/null
+++ b/pythonforandroid/recipes/opencv/patches/p4a_build.patch
@@ -0,0 +1,33 @@
+This patch allow that the opencv's build command correctly detects our version
+of python, so we can successfully build the python bindings (cv2.so)
+--- opencv-4.0.1/cmake/OpenCVDetectPython.cmake.orig 2018-12-22 08:03:30.000000000 +0100
++++ opencv-4.0.1/cmake/OpenCVDetectPython.cmake 2019-01-31 11:33:10.896502978 +0100
+@@ -175,7 +175,7 @@ if(NOT ${found})
+ endif()
+ endif()
+
+- if(NOT ANDROID AND NOT IOS)
++ if(P4A OR NOT ANDROID AND NOT IOS)
+ if(CMAKE_HOST_UNIX)
+ execute_process(COMMAND ${_executable} -c "from distutils.sysconfig import *; print(get_python_lib())"
+ RESULT_VARIABLE _cvpy_process
+@@ -244,7 +244,7 @@ if(NOT ${found})
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ endif()
+ endif()
+- endif(NOT ANDROID AND NOT IOS)
++ endif(P4A OR NOT ANDROID AND NOT IOS)
+ endif()
+
+ # Export return values
+--- opencv-4.0.1/modules/python/CMakeLists.txt.orig 2018-12-22 08:03:30.000000000 +0100
++++ opencv-4.0.1/modules/python/CMakeLists.txt 2019-01-31 11:47:17.100494908 +0100
+@@ -3,7 +3,7 @@
+ # ----------------------------------------------------------------------------
+ if(DEFINED OPENCV_INITIAL_PASS) # OpenCV build
+
+-if(ANDROID OR APPLE_FRAMEWORK OR WINRT)
++if(ANDROID AND NOT P4A OR APPLE_FRAMEWORK OR WINRT)
+ ocv_module_disable_(python2)
+ ocv_module_disable_(python3)
+ return()
diff --git a/pythonforandroid/recipes/opencv_extras/__init__.py b/pythonforandroid/recipes/opencv_extras/__init__.py
new file mode 100644
index 000000000..693c3655d
--- /dev/null
+++ b/pythonforandroid/recipes/opencv_extras/__init__.py
@@ -0,0 +1,23 @@
+from pythonforandroid.recipe import Recipe
+
+
+class OpenCVExtrasRecipe(Recipe):
+ """
+ OpenCV extras recipe allows us to build extra modules from the
+ `opencv_contrib` repository. It depends on opencv recipe and all the build
+ of the modules will be performed inside opencv recipe build directory.
+
+ .. note:: the version of this recipe should be the same than opencv recipe.
+
+ .. warning:: Be aware that these modules are experimental, some of them
+ maybe included in opencv future releases and removed from extras.
+
+ .. seealso:: https://github.com/opencv/opencv_contrib
+
+ """
+ version = '4.5.1'
+ url = 'https://github.com/opencv/opencv_contrib/archive/{version}.zip'
+ depends = ['opencv']
+
+
+recipe = OpenCVExtrasRecipe()
diff --git a/pythonforandroid/recipes/openssl/__init__.py b/pythonforandroid/recipes/openssl/__init__.py
index 195fd3a2b..766c10e36 100644
--- a/pythonforandroid/recipes/openssl/__init__.py
+++ b/pythonforandroid/recipes/openssl/__init__.py
@@ -1,42 +1,115 @@
-from functools import partial
+from os.path import join
-from pythonforandroid.toolchain import Recipe, shprint, current_directory
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import current_directory
+from pythonforandroid.logger import shprint
import sh
class OpenSSLRecipe(Recipe):
- version = '1.0.2h'
- url = 'https://www.openssl.org/source/openssl-{version}.tar.gz'
-
- def should_build(self, arch):
- return not self.has_libs(arch, 'libssl' + self.version + '.so',
- 'libcrypto' + self.version + '.so')
-
- def check_symbol(self, env, sofile, symbol):
- nm = env.get('NM', 'nm')
- syms = sh.sh('-c', "{} -gp {} | cut -d' ' -f3".format(
- nm, sofile), _env=env).splitlines()
- if symbol in syms:
- return True
- print('{} missing symbol {}; rebuilding'.format(sofile, symbol))
- return False
+ '''
+ The OpenSSL libraries for python-for-android. This recipe will generate the
+ following libraries as shared libraries (*.so):
+
+ - crypto
+ - ssl
+
+ The generated openssl libraries are versioned, where the version is the
+ recipe attribute :attr:`version` e.g.: ``libcrypto1.1.so``,
+ ``libssl1.1.so``...so...to link your recipe with the openssl libs,
+ remember to add the version at the end, e.g.:
+ ``-lcrypto1.1 -lssl1.1``. Or better, you could do it dynamically
+ using the methods: :meth:`include_flags`, :meth:`link_dirs_flags` and
+ :meth:`link_libs_flags`.
+
+ .. warning:: This recipe is very sensitive because is used for our core
+ recipes, the python recipes. The used API should match with the one
+ used in our python build, otherwise we will be unable to build the
+ _ssl.so python module.
+
+ .. versionchanged:: 0.6.0
+
+ - The gcc compiler has been deprecated in favour of clang and libraries
+ updated to version 1.1.1 (LTS - supported until 11th September 2023)
+ - Added two new methods to make easier to link with openssl:
+ :meth:`include_flags` and :meth:`link_flags`
+ - subclassed versioned_url
+ - Adapted method :meth:`select_build_arch` to API 21+
+ - Add ability to build a legacy version of the openssl libs when using
+ python2legacy or python3crystax.
+
+ .. versionchanged:: 2019.06.06.1.dev0
+
+ - Removed legacy version of openssl libraries
+
+ '''
+
+ version = '1.1'
+ '''the major minor version used to link our recipes'''
+
+ url_version = '1.1.1w'
+ '''the version used to download our libraries'''
+
+ url = 'https://www.openssl.org/source/openssl-{url_version}.tar.gz'
+
+ built_libraries = {
+ 'libcrypto{version}.so'.format(version=version): '.',
+ 'libssl{version}.so'.format(version=version): '.',
+ }
+
+ @property
+ def versioned_url(self):
+ if self.url is None:
+ return None
+ return self.url.format(url_version=self.url_version)
+
+ def get_build_dir(self, arch):
+ return join(
+ self.get_build_container_dir(arch), self.name + self.version
+ )
+
+ def include_flags(self, arch):
+ '''Returns a string with the include folders'''
+ openssl_includes = join(self.get_build_dir(arch.arch), 'include')
+ return (' -I' + openssl_includes +
+ ' -I' + join(openssl_includes, 'internal') +
+ ' -I' + join(openssl_includes, 'openssl'))
+
+ def link_dirs_flags(self, arch):
+ '''Returns a string with the appropriate `-L<lib directory>` to link
+ with the openssl libs. This string is usually added to the environment
+ variable `LDFLAGS`'''
+ return ' -L' + self.get_build_dir(arch.arch)
+
+ def link_libs_flags(self):
+ '''Returns a string with the appropriate `-l<lib>` flags to link with
+ the openssl libs. This string is usually added to the environment
+ variable `LIBS`'''
+ return ' -lcrypto{version} -lssl{version}'.format(version=self.version)
+
+ def link_flags(self, arch):
+ '''Returns a string with the flags to link with the openssl libraries
+ in the format: `-L<lib directory> -l<lib>`'''
+ return self.link_dirs_flags(arch) + self.link_libs_flags()
def get_recipe_env(self, arch=None):
- env = super(OpenSSLRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['OPENSSL_VERSION'] = self.version
- env['CFLAGS'] += ' ' + env['LDFLAGS']
- env['CC'] += ' ' + env['LDFLAGS']
env['MAKE'] = 'make' # This removes the '-j5', which isn't safe
+ env['CC'] = 'clang'
+ env['ANDROID_NDK_HOME'] = self.ctx.ndk_dir
return env
def select_build_arch(self, arch):
aname = arch.arch
if 'arm64' in aname:
- return 'linux-aarch64'
+ return 'android-arm64'
if 'v7a' in aname:
- return 'android-armv7'
+ return 'android-arm'
if 'arm' in aname:
return 'android'
+ if 'x86_64' in aname:
+ return 'android-x86_64'
if 'x86' in aname:
return 'android-x86'
return 'linux-armv4'
@@ -48,19 +121,17 @@ def build_arch(self, arch):
# so instead we manually run perl passing in Configure
perl = sh.Command('perl')
buildarch = self.select_build_arch(arch)
- shprint(perl, 'Configure', 'shared', 'no-dso', 'no-krb5', buildarch, _env=env)
+ config_args = [
+ 'shared',
+ 'no-dso',
+ 'no-asm',
+ buildarch,
+ '-D__ANDROID_API__={}'.format(self.ctx.ndk_api),
+ ]
+ shprint(perl, 'Configure', *config_args, _env=env)
self.apply_patch('disable-sover.patch', arch.arch)
- self.apply_patch('rename-shared-lib.patch', arch.arch)
-
- # check_ssl = partial(self.check_symbol, env, 'libssl' + self.version + '.so')
- check_crypto = partial(self.check_symbol, env, 'libcrypto' + self.version + '.so')
- while True:
- shprint(sh.make, 'build_libs', _env=env)
- if all(map(check_crypto, ('SSLeay', 'MD5_Transform', 'MD4_Init'))):
- break
- shprint(sh.make, 'clean', _env=env)
-
- self.install_libs(arch, 'libssl' + self.version + '.so',
- 'libcrypto' + self.version + '.so')
+
+ shprint(sh.make, 'build_libs', _env=env)
+
recipe = OpenSSLRecipe()
diff --git a/pythonforandroid/recipes/openssl/disable-sover.patch b/pythonforandroid/recipes/openssl/disable-sover.patch
index 6099fadce..d944483cd 100644
--- a/pythonforandroid/recipes/openssl/disable-sover.patch
+++ b/pythonforandroid/recipes/openssl/disable-sover.patch
@@ -1,20 +1,11 @@
---- openssl/Makefile 2016-01-28 17:26:49.159522273 +0100
-+++ b/Makefile 2016-01-28 17:26:54.358438402 +0100
-@@ -342,7 +342,7 @@
- link-shared:
- @ set -e; for i in $(SHLIBDIRS); do \
- $(MAKE) -f $(HERE)/Makefile.shared -e $(BUILDENV) \
-- LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \
-+ LIBNAME=$$i LIBVERSION= \
- LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \
- symlink.$(SHLIB_TARGET); \
- libs="$$libs -l$$i"; \
-@@ -356,7 +356,7 @@
- libs="$(LIBKRB5) $$libs"; \
- fi; \
- $(CLEARENV) && $(MAKE) -f Makefile.shared -e $(BUILDENV) \
-- LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \
-+ LIBNAME=$$i LIBVERSION= \
- LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \
- LIBDEPS="$$libs $(EX_LIBS)" \
- link_a.$(SHLIB_TARGET); \
+--- openssl/Makefile.orig 2018-10-20 22:49:40.418310423 +0200
++++ openssl/Makefile 2018-10-20 22:50:23.347322403 +0200
+@@ -19,7 +19,7 @@
+ SHLIB_MAJOR=1
+ SHLIB_MINOR=1
+ SHLIB_TARGET=linux-shared
+-SHLIB_EXT=.so.$(SHLIB_VERSION_NUMBER)
++SHLIB_EXT=$(SHLIB_VERSION_NUMBER).so
+ SHLIB_EXT_SIMPLE=.so
+ SHLIB_EXT_IMPORT=
+
diff --git a/pythonforandroid/recipes/openssl/rename-shared-lib.patch b/pythonforandroid/recipes/openssl/rename-shared-lib.patch
deleted file mode 100644
index 30c0f796d..000000000
--- a/pythonforandroid/recipes/openssl/rename-shared-lib.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- openssl/Makefile.shared 2016-05-03 15:44:42.000000000 +0200
-+++ patch/Makefile.shared 2016-07-14 00:08:37.268792948 +0200
-@@ -147,11 +147,11 @@
- DETECT_GNU_LD=($(CC) -Wl,-V /dev/null 2>&1 | grep '^GNU ld' )>/dev/null
-
- DO_GNU_SO=$(CALC_VERSIONS); \
-- SHLIB=lib$(LIBNAME).so; \
-+ SHLIB=lib$(LIBNAME)$(OPENSSL_VERSION).so; \
- SHLIB_SUFFIX=; \
- ALLSYMSFLAGS='-Wl,--whole-archive'; \
- NOALLSYMSFLAGS='-Wl,--no-whole-archive'; \
-- SHAREDFLAGS="$(CFLAGS) $(SHARED_LDFLAGS) -shared -Wl,-Bsymbolic -Wl,-soname=$$SHLIB$$SHLIB_SOVER$$SHLIB_SUFFIX"
-+ SHAREDFLAGS="$(CFLAGS) $(SHARED_LDFLAGS) -shared -Wl,-Bsymbolic -Wl,-soname=$$SHLIB"
-
- DO_GNU_APP=LDFLAGS="$(CFLAGS) -Wl,-rpath,$(LIBRPATH)"
-
diff --git a/pythonforandroid/recipes/pandas/__init__.py b/pythonforandroid/recipes/pandas/__init__.py
new file mode 100644
index 000000000..a43209a33
--- /dev/null
+++ b/pythonforandroid/recipes/pandas/__init__.py
@@ -0,0 +1,35 @@
+from os.path import join
+
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
+
+class PandasRecipe(CppCompiledComponentsPythonRecipe):
+ version = '1.0.3'
+ url = 'https://github.com/pandas-dev/pandas/releases/download/v{version}/pandas-{version}.tar.gz' # noqa
+
+ depends = ['cython', 'numpy', 'libbz2', 'liblzma']
+
+ python_depends = ['python-dateutil', 'pytz']
+ patches = ['fix_numpy_includes.patch']
+
+ call_hostpython_via_targetpython = False
+ need_stl_shared = True
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ # we need the includes from our installed numpy at site packages
+ # because we need some includes generated at numpy's compile time
+ env['NUMPY_INCLUDES'] = join(
+ self.ctx.get_python_install_dir(arch.arch), "numpy/core/include",
+ )
+
+ # this flag below is to fix a runtime error:
+ # ImportError: dlopen failed: cannot locate symbol
+ # "_ZTVSt12length_error" referenced by
+ # "/data/data/org.test.matplotlib_testapp/files/app/_python_bundle
+ # /site-packages/pandas/_libs/window/aggregations.so"...
+ env['LDFLAGS'] += f' -landroid -l{self.stl_lib_name}'
+ return env
+
+
+recipe = PandasRecipe()
diff --git a/pythonforandroid/recipes/pandas/fix_numpy_includes.patch b/pythonforandroid/recipes/pandas/fix_numpy_includes.patch
new file mode 100644
index 000000000..ef1643b9b
--- /dev/null
+++ b/pythonforandroid/recipes/pandas/fix_numpy_includes.patch
@@ -0,0 +1,31 @@
+--- pandas-1.0.1/setup.py.orig 2020-02-05 17:15:24.000000000 +0100
++++ pandas-1.0.1/setup.py 2020-03-15 13:47:57.612237225 +0100
+@@ -37,11 +37,12 @@ min_cython_ver = "0.29.13" # note: sync
+
+ setuptools_kwargs = {
+ "install_requires": [
+- "python-dateutil >= 2.6.1",
+- "pytz >= 2017.2",
+- f"numpy >= {min_numpy_ver}",
++ # dependencies managed via p4a's recipe
++ # "python-dateutil >= 2.6.1",
++ # "pytz >= 2017.2",
++ # f"numpy >= {min_numpy_ver}",
+ ],
+- "setup_requires": [f"numpy >= {min_numpy_ver}"],
++ "setup_requires": [],
+ "zip_safe": False,
+ }
+
+@@ -514,7 +515,10 @@ def maybe_cythonize(extensions, *args, *
+ )
+ raise RuntimeError("Cannot cythonize without Cython installed.")
+
+- numpy_incl = pkg_resources.resource_filename("numpy", "core/include")
++ if 'NUMPY_INCLUDES' in os.environ:
++ numpy_incl = os.environ['NUMPY_INCLUDES']
++ else:
++ numpy_incl = pkg_resources.resource_filename("numpy", "core/include")
+ # TODO: Is this really necessary here?
+ for ext in extensions:
+ if hasattr(ext, "include_dirs") and numpy_incl not in ext.include_dirs:
diff --git a/pythonforandroid/recipes/pbkdf2/__init__.py b/pythonforandroid/recipes/pbkdf2/__init__.py
deleted file mode 100644
index d1d5d7b44..000000000
--- a/pythonforandroid/recipes/pbkdf2/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class Pbkdf2Recipe(PythonRecipe):
-
- # TODO: version
- url = 'https://github.com/dlitz/python-pbkdf2/archive/master.zip'
-
- depends = ['setuptools']
-
-
-recipe = Pbkdf2Recipe()
diff --git a/pythonforandroid/recipes/pil/__init__.py b/pythonforandroid/recipes/pil/__init__.py
index bd027457e..46bace12b 100644
--- a/pythonforandroid/recipes/pil/__init__.py
+++ b/pythonforandroid/recipes/pil/__init__.py
@@ -1,41 +1,23 @@
-from os.path import join
+from pythonforandroid.recipes.Pillow import PillowRecipe
+from pythonforandroid.logger import warning
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+class PilRecipe(PillowRecipe):
+ """A transparent wrapper around the Pillow recipe, it should build
+ Pillow automatically as if "pillow" were specified in the
+ requirements.
+ """
-class PILRecipe(CompiledComponentsPythonRecipe):
- name = 'pil'
- version = '1.1.7'
- url = 'http://effbot.org/downloads/Imaging-{version}.tar.gz'
- depends = ['python2', 'png', 'jpeg']
- site_packages_name = 'PIL'
+ name = 'Pillow' # ensures the Pillow recipe directory is used where necessary
- patches = ['disable-tk.patch',
- 'fix-directories.patch']
+ conflicts = ['pillow']
- def get_recipe_env(self, arch=None):
- env = super(PILRecipe, self).get_recipe_env(arch)
+ def build_arch(self, arch):
+ warning('PIL is no longer supported, building Pillow instead. '
+ 'This should be a drop-in replacement.')
+ warning('It is recommended to change "pil" to "pillow" in your requirements, '
+ 'to ensure future compatibility')
+ super().build_arch(arch)
- png = self.get_recipe('png', self.ctx)
- png_lib_dir = png.get_lib_dir(arch)
- png_jni_dir = png.get_jni_dir(arch)
- jpeg = self.get_recipe('jpeg', self.ctx)
- jpeg_lib_dir = jpeg.get_lib_dir(arch)
- jpeg_jni_dir = jpeg.get_jni_dir(arch)
- env['JPEG_ROOT'] = '{}|{}'.format(jpeg_lib_dir, jpeg_jni_dir)
- cflags = ' -I{} -L{} -I{} -L{}'.format(png_jni_dir, png_lib_dir, jpeg_jni_dir, jpeg_lib_dir)
- env['CFLAGS'] += cflags
- env['CXXFLAGS'] += cflags
- env['CC'] += cflags
- env['CXX'] += cflags
-
- ndk_dir = self.ctx.ndk_platform
- ndk_lib_dir = join(ndk_dir, 'usr', 'lib')
- ndk_include_dir = join(ndk_dir, 'usr', 'include')
- env['ZLIB_ROOT'] = '{}|{}'.format(ndk_lib_dir, ndk_include_dir)
-
- return env
-
-
-recipe = PILRecipe()
+recipe = PilRecipe()
diff --git a/pythonforandroid/recipes/pil/disable-tk.patch b/pythonforandroid/recipes/pil/disable-tk.patch
deleted file mode 100644
index c6934c9fd..000000000
--- a/pythonforandroid/recipes/pil/disable-tk.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- Imaging-1.1.7/setup.py.orig 2012-08-31 12:52:25.000000000 +0200
-+++ Imaging-1.1.7/setup.py 2012-08-31 12:53:04.000000000 +0200
-@@ -322,7 +322,7 @@
- "_imagingcms", ["_imagingcms.c"], libraries=["lcms"] + extra
- ))
-
-- if sys.platform == "darwin":
-+ if False: #sys.platform == "darwin":
- # locate Tcl/Tk frameworks
- frameworks = []
- framework_roots = [
-
diff --git a/pythonforandroid/recipes/pil/fix-directories.patch b/pythonforandroid/recipes/pil/fix-directories.patch
deleted file mode 100644
index d54cd0cb5..000000000
--- a/pythonforandroid/recipes/pil/fix-directories.patch
+++ /dev/null
@@ -1,79 +0,0 @@
-diff -Naur pil/setup.py b/setup.py
---- pil/setup.py 2015-12-11 16:42:40.817701332 -0600
-+++ b/setup.py 2015-12-11 17:07:34.778477132 -0600
-@@ -34,10 +34,10 @@
- # TIFF_ROOT = libinclude("/opt/tiff")
-
- TCL_ROOT = None
--JPEG_ROOT = None
--ZLIB_ROOT = None
-+JPEG_ROOT = tuple(os.environ['JPEG_ROOT'].split('|')) if 'JPEG_ROOT' in os.environ else None
-+ZLIB_ROOT = tuple(os.environ['ZLIB_ROOT'].split('|')) if 'ZLIB_ROOT' in os.environ else None
- TIFF_ROOT = None
--FREETYPE_ROOT = None
-+FREETYPE_ROOT = tuple(os.environ['FREETYPE_ROOT'].split('|')) if 'FREETYPE_ROOT' in os.environ else None
- LCMS_ROOT = None
-
- # FIXME: add mechanism to explicitly *disable* the use of a library
-@@ -127,29 +127,6 @@
-
- add_directory(include_dirs, "libImaging")
-
-- #
-- # add platform directories
--
-- if sys.platform == "cygwin":
-- # pythonX.Y.dll.a is in the /usr/lib/pythonX.Y/config directory
-- add_directory(library_dirs, os.path.join(
-- "/usr/lib", "python%s" % sys.version[:3], "config"
-- ))
--
-- elif sys.platform == "darwin":
-- # attempt to make sure we pick freetype2 over other versions
-- add_directory(include_dirs, "/sw/include/freetype2")
-- add_directory(include_dirs, "/sw/lib/freetype2/include")
-- # fink installation directories
-- add_directory(library_dirs, "/sw/lib")
-- add_directory(include_dirs, "/sw/include")
-- # darwin ports installation directories
-- add_directory(library_dirs, "/opt/local/lib")
-- add_directory(include_dirs, "/opt/local/include")
--
-- add_directory(library_dirs, "/usr/local/lib")
-- # FIXME: check /opt/stuff directories here?
--
- prefix = sysconfig.get_config_var("prefix")
- if prefix:
- add_directory(library_dirs, os.path.join(prefix, "lib"))
-@@ -199,22 +176,6 @@
- add_directory(include_dirs, include_root)
-
- #
-- # add standard directories
--
-- # look for tcl specific subdirectory (e.g debian)
-- if _tkinter:
-- tcl_dir = "/usr/include/tcl" + TCL_VERSION
-- if os.path.isfile(os.path.join(tcl_dir, "tk.h")):
-- add_directory(include_dirs, tcl_dir)
--
-- # standard locations
-- add_directory(library_dirs, "/usr/local/lib")
-- add_directory(include_dirs, "/usr/local/include")
--
-- add_directory(library_dirs, "/usr/lib")
-- add_directory(include_dirs, "/usr/include")
--
-- #
- # insert new dirs *before* default libs, to avoid conflicts
- # between Python PYD stub libs and real libraries
-
-@@ -299,8 +260,6 @@
- defs.append(("HAVE_LIBZ", None))
- if sys.platform == "win32":
- libs.extend(["kernel32", "user32", "gdi32"])
-- if struct.unpack("h", "\0\1")[0] == 1:
-- defs.append(("WORDS_BIGENDIAN", None))
-
- exts = [(Extension(
- "_imaging", files, libraries=libs, define_macros=defs
diff --git a/pythonforandroid/recipes/png/__init__.py b/pythonforandroid/recipes/png/__init__.py
index d912c669e..613819590 100644
--- a/pythonforandroid/recipes/png/__init__.py
+++ b/pythonforandroid/recipes/png/__init__.py
@@ -1,13 +1,30 @@
-from pythonforandroid.recipe import NDKRecipe
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
+from multiprocessing import cpu_count
+import sh
-class PngRecipe(NDKRecipe):
- name = 'png'
- version = '1.6.15'
- url = 'https://github.com/julienr/libpng-android/archive/{version}.zip'
+class PngRecipe(Recipe):
+ name = 'png'
+ version = '1.6.37'
+ url = 'https://github.com/glennrp/libpng/archive/v{version}.zip'
+ built_libraries = {'libpng16.so': '.libs'}
- generated_libraries = ['libpng.a']
+ def build_arch(self, arch):
+ build_dir = self.get_build_dir(arch.arch)
+ with current_directory(build_dir):
+ env = self.get_recipe_env(arch)
+ shprint(
+ sh.Command('./configure'),
+ '--host=' + arch.command_prefix,
+ '--target=' + arch.command_prefix,
+ '--disable-static',
+ '--enable-shared',
+ '--prefix={}/install'.format(self.get_build_dir(arch.arch)),
+ _env=env,
+ )
+ shprint(sh.make, '-j', str(cpu_count()), _env=env)
recipe = PngRecipe()
-
diff --git a/pythonforandroid/recipes/png/build_shared_library.patch b/pythonforandroid/recipes/png/build_shared_library.patch
new file mode 100644
index 000000000..01e3080f6
--- /dev/null
+++ b/pythonforandroid/recipes/png/build_shared_library.patch
@@ -0,0 +1,17 @@
+diff --git a/jni/Android.mk b/jni/Android.mk
+index df2ff1a..2f70985 100644
+--- a/jni/Android.mk
++++ b/jni/Android.mk
+@@ -26,8 +26,9 @@ LOCAL_SRC_FILES :=\
+ arm/filter_neon_intrinsics.c
+
+ #LOCAL_SHARED_LIBRARIES := -lz
+-LOCAL_EXPORT_LDLIBS := -lz
++# LOCAL_EXPORT_LDLIBS := -lz
++LOCAL_LDLIBS := -lz
+ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.
+
+-#include $(BUILD_SHARED_LIBRARY)
+-include $(BUILD_STATIC_LIBRARY)
++include $(BUILD_SHARED_LIBRARY)
++# include $(BUILD_STATIC_LIBRARY)
diff --git a/pythonforandroid/recipes/preppy/__init__.py b/pythonforandroid/recipes/preppy/__init__.py
index 2c7505ade..40afd681b 100644
--- a/pythonforandroid/recipes/preppy/__init__.py
+++ b/pythonforandroid/recipes/preppy/__init__.py
@@ -1,4 +1,6 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
+
+
class PreppyRecipe(PythonRecipe):
version = '27b7085'
url = 'https://bitbucket.org/rptlab/preppy/get/{version}.tar.gz'
@@ -6,4 +8,5 @@ class PreppyRecipe(PythonRecipe):
patches = ['fix-setup.patch']
call_hostpython_via_targetpython = False
+
recipe = PreppyRecipe()
diff --git a/pythonforandroid/recipes/protobuf_cpp/__init__.py b/pythonforandroid/recipes/protobuf_cpp/__init__.py
index c135c3d68..7209e0909 100644
--- a/pythonforandroid/recipes/protobuf_cpp/__init__.py
+++ b/pythonforandroid/recipes/protobuf_cpp/__init__.py
@@ -1,59 +1,110 @@
-from pythonforandroid.recipe import PythonRecipe
-from pythonforandroid.logger import shprint
-from pythonforandroid.util import current_directory, shutil
-from pythonforandroid.util import ensure_dir
-from os.path import exists, join, dirname
-import sh
from multiprocessing import cpu_count
-
-
+import os
+from os.path import exists, join
from pythonforandroid.toolchain import info
+import sh
+import sys
+
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+from pythonforandroid.logger import shprint, info_notify
+from pythonforandroid.util import current_directory, touch
-class ProtobufCppRecipe(PythonRecipe):
+class ProtobufCppRecipe(CppCompiledComponentsPythonRecipe):
+ """This is a two-in-one recipe:
+ - build labraru `libprotobuf.so`
+ - build and install python binding for protobuf_cpp
+ """
name = 'protobuf_cpp'
- version = '3.1.0'
+ version = '3.6.1'
url = 'https://github.com/google/protobuf/releases/download/v{version}/protobuf-python-{version}.tar.gz'
call_hostpython_via_targetpython = False
depends = ['cffi', 'setuptools']
site_packages_name = 'google/protobuf/pyext'
+ setup_extra_args = ['--cpp_implementation']
+ built_libraries = {'libprotobuf.so': 'src/.libs'}
+ protoc_dir = None
+
+ def prebuild_arch(self, arch):
+ super().prebuild_arch(arch)
+
+ patch_mark = join(self.get_build_dir(arch.arch), '.protobuf-patched')
+ if self.ctx.python_recipe.name == 'python3' and not exists(patch_mark):
+ self.apply_patch('fix-python3-compatibility.patch', arch.arch)
+ touch(patch_mark)
+
+ # During building, host needs to transpile .proto files to .py
+ # ideally with the same version as protobuf runtime, or with an older one.
+ # Because protoc is compiled for target (i.e. Android), we need an other binary
+ # which can be run by host.
+ # To make it easier, we download prebuild protoc binary adapted to the platform
+
+ info_notify("Downloading protoc compiler for your platform")
+ url_prefix = "https://github.com/protocolbuffers/protobuf/releases/download/v{version}".format(version=self.version)
+ if sys.platform.startswith('linux'):
+ info_notify("GNU/Linux detected")
+ filename = "protoc-{version}-linux-x86_64.zip".format(version=self.version)
+ elif sys.platform.startswith('darwin'):
+ info_notify("Mac OS X detected")
+ filename = "protoc-{version}-osx-x86_64.zip".format(version=self.version)
+ else:
+ info_notify("Your platform is not supported, but recipe can still "
+ "be built if you have a valid protoc (<={version}) in "
+ "your path".format(version=self.version))
+ return
+
+ protoc_url = join(url_prefix, filename)
+ self.protoc_dir = join(self.ctx.build_dir, "tools", "protoc")
+ if os.path.exists(join(self.protoc_dir, "bin", "protoc")):
+ info_notify("protoc found, no download needed")
+ return
+ try:
+ os.makedirs(self.protoc_dir)
+ except OSError as e:
+ # if dir already exists (errno 17), we ignore the error
+ if e.errno != 17:
+ raise e
+ info_notify("Will download into {dest_dir}".format(dest_dir=self.protoc_dir))
+ self.download_file(protoc_url, join(self.protoc_dir, filename))
+ with current_directory(self.protoc_dir):
+ shprint(sh.unzip, join(self.protoc_dir, filename))
def build_arch(self, arch):
env = self.get_recipe_env(arch)
- # Build libproto.a
+ # Build libproto.so
with current_directory(self.get_build_dir(arch.arch)):
- env['HOSTARCH'] = 'arm-eabi'
- env['BUILDARCH'] = shprint(sh.gcc, '-dumpmachine').stdout.decode('utf-8').split('\n')[0]
+ build_arch = (
+ shprint(sh.gcc, '-dumpmachine')
+ .stdout.decode('utf-8')
+ .split('\n')[0]
+ )
if not exists('configure'):
shprint(sh.Command('./autogen.sh'), _env=env)
shprint(sh.Command('./configure'),
- '--host={}'.format(env['HOSTARCH']),
- '--enable-shared',
- _env=env)
+ '--build={}'.format(build_arch),
+ '--host={}'.format(arch.command_prefix),
+ '--target={}'.format(arch.command_prefix),
+ '--disable-static',
+ '--enable-shared',
+ _env=env)
with current_directory(join(self.get_build_dir(arch.arch), 'src')):
shprint(sh.make, 'libprotobuf.la', '-j'+str(cpu_count()), _env=env)
- shprint(sh.cp, '.libs/libprotobuf.a', join(self.ctx.get_libs_dir(arch.arch), 'libprotobuf.a'))
- # Copy stl library
- shutil.copyfile(self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + '/libgnustl_shared.so',
- join(self.ctx.get_libs_dir(arch.arch), 'libgnustl_shared.so'))
+ self.install_python_package(arch)
+ def build_compiled_components(self, arch):
# Build python bindings and _message.so
+ env = self.get_recipe_env(arch)
with current_directory(join(self.get_build_dir(arch.arch), 'python')):
hostpython = sh.Command(self.hostpython_location)
shprint(hostpython,
'setup.py',
'build_ext',
- '--cpp_implementation'
- , _env=env)
-
- # Install python bindings
- self.install_python_package(arch)
-
+ _env=env, *self.setup_extra_args)
def install_python_package(self, arch):
env = self.get_recipe_env(arch)
@@ -63,46 +114,29 @@ def install_python_package(self, arch):
with current_directory(join(self.get_build_dir(arch.arch), 'python')):
hostpython = sh.Command(self.hostpython_location)
- if self.ctx.python_recipe.from_crystax:
- hpenv = env.copy()
- shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
- '--install-lib=.',
- '--cpp_implementation',
- _env=hpenv, *self.setup_extra_args)
- else:
- hppath = join(dirname(self.hostpython_location), 'Lib',
- 'site-packages')
- hpenv = env.copy()
- if 'PYTHONPATH' in hpenv:
- hpenv['PYTHONPATH'] = ':'.join([hppath] +
- hpenv['PYTHONPATH'].split(':'))
- else:
- hpenv['PYTHONPATH'] = hppath
- shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
- '--install-lib=lib/python2.7/site-packages',
- '--cpp_implementation',
- _env=hpenv, *self.setup_extra_args)
-
+ hpenv = env.copy()
+ shprint(hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),
+ '--install-lib=.',
+ _env=hpenv, *self.setup_extra_args)
+
+ # Create __init__.py which is missing, see also:
+ # - https://github.com/protocolbuffers/protobuf/issues/1296
+ # - https://stackoverflow.com/questions/13862562/
+ # google-protocol-buffers-not-found-when-trying-to-freeze-python-app
+ open(
+ join(self.ctx.get_site_packages_dir(arch), 'google', '__init__.py'),
+ 'a',
+ ).close()
def get_recipe_env(self, arch):
- env = super(ProtobufCppRecipe, self).get_recipe_env(arch)
- env['PROTOC'] = '/home/fipo/soft/protobuf-3.1.0/src/protoc'
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
+ env = super().get_recipe_env(arch)
+ if self.protoc_dir is not None:
+ # we need protoc with binary for host platform
+ env['PROTOC'] = join(self.protoc_dir, 'bin', 'protoc')
env['TARGET_OS'] = 'OS_ANDROID_CROSSCOMPILE'
- env['CFLAGS'] += ' -I' + self.ctx.ndk_dir + '/platforms/android-' + str(
- self.ctx.android_api) + '/arch-' + arch.arch.replace('eabi', '') + '/usr/include' + \
- ' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/include' + \
- ' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + '/include' + \
- ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
- env['CXXFLAGS'] = env['CFLAGS']
- env['CXXFLAGS'] += ' -frtti'
- env['CXXFLAGS'] += ' -fexceptions'
- env['LDFLAGS'] += ' -L' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version + '/libs/' + arch.arch + \
- ' -lgnustl_shared -lpython2.7'
-
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
+ env['CXXFLAGS'] += ' -std=c++11'
+ env['LDFLAGS'] += ' -lm -landroid -llog'
return env
diff --git a/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch b/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch
new file mode 100644
index 000000000..e77debaa6
--- /dev/null
+++ b/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch
@@ -0,0 +1,91 @@
+From 539bc017a62f91bdf7c547b58948cb5a2f59d918 Mon Sep 17 00:00:00 2001
+From: Ben Webb <ben@salilab.org>
+Date: Thu, 12 Jul 2018 10:58:10 -0700
+Subject: [PATCH] Add Python 3.7 compatibility (#4862)
+
+Compilation of Python wrappers fails with Python 3.7 because
+the Python folks changed their C API such that
+PyUnicode_AsUTF8AndSize() now returns a const char* rather
+than a char*. Add a patch to work around. Relates #4086.
+---
+ python/google/protobuf/pyext/descriptor.cc | 2 +-
+ python/google/protobuf/pyext/descriptor_containers.cc | 2 +-
+ python/google/protobuf/pyext/descriptor_pool.cc | 2 +-
+ python/google/protobuf/pyext/extension_dict.cc | 2 +-
+ python/google/protobuf/pyext/message.cc | 4 ++--
+ 5 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/python/google/protobuf/pyext/descriptor.cc b/python/google/protobuf/pyext/descriptor.cc
+index 8af0cb1289..19a1c38a62 100644
+--- a/python/google/protobuf/pyext/descriptor.cc
++++ b/python/google/protobuf/pyext/descriptor.cc
+@@ -56,7 +56,7 @@
+ #endif
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+
+diff --git a/python/google/protobuf/pyext/descriptor_containers.cc b/python/google/protobuf/pyext/descriptor_containers.cc
+index bc007f7efa..0153664f50 100644
+--- a/python/google/protobuf/pyext/descriptor_containers.cc
++++ b/python/google/protobuf/pyext/descriptor_containers.cc
+@@ -66,7 +66,7 @@
+ #endif
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+
+diff --git a/python/google/protobuf/pyext/descriptor_pool.cc b/python/google/protobuf/pyext/descriptor_pool.cc
+index 95882aeb35..962accc6e9 100644
+--- a/python/google/protobuf/pyext/descriptor_pool.cc
++++ b/python/google/protobuf/pyext/descriptor_pool.cc
+@@ -48,7 +48,7 @@
+ #endif
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+
+diff --git a/python/google/protobuf/pyext/extension_dict.cc b/python/google/protobuf/pyext/extension_dict.cc
+index 018b5c2c49..174c5470c2 100644
+--- a/python/google/protobuf/pyext/extension_dict.cc
++++ b/python/google/protobuf/pyext/extension_dict.cc
+@@ -53,7 +53,7 @@
+ #endif
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+
+diff --git a/python/google/protobuf/pyext/message.cc b/python/google/protobuf/pyext/message.cc
+index 5893533adf..31094b7e10 100644
+--- a/python/google/protobuf/pyext/message.cc
++++ b/python/google/protobuf/pyext/message.cc
+@@ -79,7 +79,7 @@
+ (PyUnicode_Check(ob)? PyUnicode_AsUTF8(ob): PyBytes_AsString(ob))
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast<char*>(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+ #endif
+@@ -1529,7 +1529,7 @@ PyObject* HasField(CMessage* self, PyObject* arg) {
+ return NULL;
+ }
+ #else
+- field_name = PyUnicode_AsUTF8AndSize(arg, &size);
++ field_name = const_cast<char*>(PyUnicode_AsUTF8AndSize(arg, &size));
+ if (!field_name) {
+ return NULL;
+ }
diff --git a/pythonforandroid/recipes/psycopg2/__init__.py b/pythonforandroid/recipes/psycopg2/__init__.py
index 1c9d227b2..1d946e7d4 100644
--- a/pythonforandroid/recipes/psycopg2/__init__.py
+++ b/pythonforandroid/recipes/psycopg2/__init__.py
@@ -1,12 +1,20 @@
-from pythonforandroid.toolchain import PythonRecipe, current_directory, shprint
+from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.toolchain import current_directory, shprint
import sh
class Psycopg2Recipe(PythonRecipe):
- version = 'latest'
- url = 'http://initd.org/psycopg/tarballs/psycopg2-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'libpq']
+ """
+ Requires `libpq-dev` system dependency e.g. for `pg_config` binary.
+ If you get `nl_langinfo` symbol runtime error, make sure you're running on
+ `ANDROID_API` (`ndk-api`) >= 26, see:
+ https://github.com/kivy/python-for-android/issues/1711#issuecomment-465747557
+ """
+ version = '2.8.5'
+ url = 'https://pypi.python.org/packages/source/p/psycopg2/psycopg2-{version}.tar.gz'
+ depends = ['libpq', 'setuptools']
site_packages_name = 'psycopg2'
+ call_hostpython_via_targetpython = False
def prebuild_arch(self, arch):
libdir = self.ctx.get_libs_dir(arch.arch)
@@ -18,7 +26,7 @@ def prebuild_arch(self, arch):
'setup.py')
def get_recipe_env(self, arch):
- env = super(Psycopg2Recipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['LDFLAGS'] = "{} -L{}".format(env['LDFLAGS'], self.ctx.get_libs_dir(arch.arch))
env['EXTRA_CFLAGS'] = "--host linux-armv"
return env
@@ -35,7 +43,8 @@ def install_python_package(self, arch, name=None, env=None, is_dir=True):
shprint(hostpython, 'setup.py', 'build_ext', '--static-libpq',
_env=env)
shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
- '--install-lib=lib/python2.7/site-packages', _env=env)
+ '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),
+ '--install-lib=.', _env=env)
+
recipe = Psycopg2Recipe()
diff --git a/pythonforandroid/recipes/py3dns/__init__.py b/pythonforandroid/recipes/py3dns/__init__.py
new file mode 100644
index 000000000..bccb39fc7
--- /dev/null
+++ b/pythonforandroid/recipes/py3dns/__init__.py
@@ -0,0 +1,13 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class Py3DNSRecipe(PythonRecipe):
+ site_packages_name = 'DNS'
+ version = '3.2.1'
+ url = 'https://launchpad.net/py3dns/trunk/{version}/+download/py3dns-{version}.tar.gz'
+ depends = ['setuptools']
+ patches = ['patches/android.patch']
+ call_hostpython_via_targetpython = False
+
+
+recipe = Py3DNSRecipe()
diff --git a/pythonforandroid/recipes/py3dns/patches/android.patch b/pythonforandroid/recipes/py3dns/patches/android.patch
new file mode 100644
index 000000000..f9ab78f07
--- /dev/null
+++ b/pythonforandroid/recipes/py3dns/patches/android.patch
@@ -0,0 +1,27 @@
+diff --git a/DNS/Base.py b/DNS/Base.py
+index 34a6da7..a558889 100644
+--- a/DNS/Base.py
++++ b/DNS/Base.py
+@@ -15,6 +15,7 @@ import socket, string, types, time, select
+ import errno
+ from . import Type,Class,Opcode
+ import asyncore
++import os
+ #
+ # This random generator is used for transaction ids and port selection. This
+ # is important to prevent spurious results from lost packets, and malicious
+@@ -50,8 +51,12 @@ defaults= { 'protocol':'udp', 'port':53, 'opcode':Opcode.QUERY,
+
+ def ParseResolvConf(resolv_path="/etc/resolv.conf"):
+ "parses the /etc/resolv.conf file and sets defaults for name servers"
+- with open(resolv_path, 'r') as stream:
+- return ParseResolvConfFromIterable(stream)
++ if os.path.exists(resolv_path):
++ with open(resolv_path, 'r') as stream:
++ return ParseResolvConfFromIterable(stream)
++ else:
++ defaults['server'].append('127.0.0.1')
++ return
+
+ def ParseResolvConfFromIterable(lines):
+ "parses a resolv.conf formatted stream and sets defaults for name servers"
diff --git a/pythonforandroid/recipes/pyaml/__init__.py b/pythonforandroid/recipes/pyaml/__init__.py
index d3d1eb910..844017570 100644
--- a/pythonforandroid/recipes/pyaml/__init__.py
+++ b/pythonforandroid/recipes/pyaml/__init__.py
@@ -1,11 +1,12 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
class PyamlRecipe(PythonRecipe):
version = "15.8.2"
url = 'https://pypi.python.org/packages/source/p/pyaml/pyaml-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), "setuptools"]
+ depends = ["setuptools"]
site_packages_name = 'yaml'
call_hostpython_via_targetpython = False
+
recipe = PyamlRecipe()
diff --git a/pythonforandroid/recipes/pyasn1/__init__.py b/pythonforandroid/recipes/pyasn1/__init__.py
deleted file mode 100644
index 29670cabd..000000000
--- a/pythonforandroid/recipes/pyasn1/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-
-from pythonforandroid.toolchain import PythonRecipe
-
-
-class PyASN1Recipe(PythonRecipe):
- version = '0.1.8'
- url = 'https://pypi.python.org/packages/source/p/pyasn1/pyasn1-{version}.tar.gz'
- depends = ['python2']
-
-recipe = PyASN1Recipe()
diff --git a/pythonforandroid/recipes/pybind11/__init__.py b/pythonforandroid/recipes/pybind11/__init__.py
new file mode 100644
index 000000000..3eb8871ff
--- /dev/null
+++ b/pythonforandroid/recipes/pybind11/__init__.py
@@ -0,0 +1,17 @@
+from pythonforandroid.recipe import PythonRecipe
+from os.path import join
+
+
+class Pybind11Recipe(PythonRecipe):
+
+ version = '2.11.1'
+ url = 'https://github.com/pybind/pybind11/archive/refs/tags/v{version}.zip'
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+ def get_include_dir(self, arch):
+ return join(self.get_build_dir(arch.arch), 'include')
+
+
+recipe = Pybind11Recipe()
diff --git a/pythonforandroid/recipes/pycparser/__init__.py b/pythonforandroid/recipes/pycparser/__init__.py
index 0f879f48a..6c82cf8a6 100644
--- a/pythonforandroid/recipes/pycparser/__init__.py
+++ b/pythonforandroid/recipes/pycparser/__init__.py
@@ -2,15 +2,15 @@
class PycparserRecipe(PythonRecipe):
- name = 'pycparser'
- version = '2.14'
- url = 'https://pypi.python.org/packages/source/p/pycparser/pycparser-{version}.tar.gz'
+ name = 'pycparser'
+ version = '2.14'
+ url = 'https://pypi.python.org/packages/source/p/pycparser/pycparser-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools']
+ depends = ['setuptools']
- call_hostpython_via_targetpython = False
+ call_hostpython_via_targetpython = False
- install_in_hostpython = True
+ install_in_hostpython = True
recipe = PycparserRecipe()
diff --git a/pythonforandroid/recipes/pycrypto/__init__.py b/pythonforandroid/recipes/pycrypto/__init__.py
index 038643379..f142d3776 100644
--- a/pythonforandroid/recipes/pycrypto/__init__.py
+++ b/pythonforandroid/recipes/pycrypto/__init__.py
@@ -1,31 +1,30 @@
-
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
from pythonforandroid.toolchain import (
- CompiledComponentsPythonRecipe,
- Recipe,
current_directory,
info,
shprint,
)
-from os.path import join
import sh
class PyCryptoRecipe(CompiledComponentsPythonRecipe):
- version = '2.6.1'
- url = 'https://pypi.python.org/packages/source/p/pycrypto/pycrypto-{version}.tar.gz'
- depends = ['openssl', 'python2']
+ version = '2.7a1'
+ url = 'https://github.com/dlitz/pycrypto/archive/v{version}.zip'
+ depends = ['openssl', 'python3']
site_packages_name = 'Crypto'
-
+ call_hostpython_via_targetpython = False
patches = ['add_length.patch']
def get_recipe_env(self, arch=None):
- env = super(PyCryptoRecipe, self).get_recipe_env(arch)
- openssl_build_dir = Recipe.get_recipe('openssl', self.ctx).get_build_dir(arch.arch)
- env['CC'] = '%s -I%s' % (env['CC'], join(openssl_build_dir, 'include'))
- env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
- self.ctx.get_libs_dir(arch.arch) +
- '-L{}'.format(self.ctx.libs_dir)) + ' -L{}'.format(
- openssl_build_dir)
+ env = super().get_recipe_env(arch)
+ openssl_recipe = Recipe.get_recipe('openssl', self.ctx)
+ env['CC'] = env['CC'] + openssl_recipe.include_flags(arch)
+
+ env['LDFLAGS'] += ' -L{}'.format(self.ctx.get_libs_dir(arch.arch))
+ env['LDFLAGS'] += ' -L{}'.format(self.ctx.libs_dir)
+ env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
+ env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags()
+
env['EXTRA_CFLAGS'] = '--host linux-armv'
env['ac_cv_func_malloc_0_nonnull'] = 'yes'
return env
@@ -37,8 +36,9 @@ def build_compiled_components(self, arch):
with current_directory(self.get_build_dir(arch.arch)):
configure = sh.Command('./configure')
shprint(configure, '--host=arm-eabi',
- '--prefix={}'.format(self.ctx.get_python_install_dir()),
+ '--prefix={}'.format(self.ctx.get_python_install_dir(arch.arch)),
'--enable-shared', _env=env)
- super(PyCryptoRecipe, self).build_compiled_components(arch)
+ super().build_compiled_components(arch)
+
recipe = PyCryptoRecipe()
diff --git a/pythonforandroid/recipes/pycryptodome/__init__.py b/pythonforandroid/recipes/pycryptodome/__init__.py
index 3fa007be6..9418600a2 100644
--- a/pythonforandroid/recipes/pycryptodome/__init__.py
+++ b/pythonforandroid/recipes/pycryptodome/__init__.py
@@ -2,10 +2,9 @@
class PycryptodomeRecipe(PythonRecipe):
- version = 'v3.4.6'
- url = 'https://github.com/Legrandin/pycryptodome/archive/{version}.tar.gz'
-
- depends = ['python2', 'setuptools', 'cffi']
+ version = '3.6.3'
+ url = 'https://github.com/Legrandin/pycryptodome/archive/v{version}.tar.gz'
+ depends = ['setuptools', 'cffi']
recipe = PycryptodomeRecipe()
diff --git a/pythonforandroid/recipes/pydantic/__init__.py b/pythonforandroid/recipes/pydantic/__init__.py
new file mode 100644
index 000000000..16e61e1b6
--- /dev/null
+++ b/pythonforandroid/recipes/pydantic/__init__.py
@@ -0,0 +1,12 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class PydanticRecipe(PythonRecipe):
+ version = '1.10.4'
+ url = 'https://github.com/pydantic/pydantic/archive/refs/tags/v{version}.zip'
+ depends = ['setuptools']
+ python_depends = ['Cython', 'devtools', 'email-validator', 'typing-extensions', 'python-dotenv']
+ call_hostpython_via_targetpython = False
+
+
+recipe = PydanticRecipe()
diff --git a/pythonforandroid/recipes/pyethereum/__init__.py b/pythonforandroid/recipes/pyethereum/__init__.py
deleted file mode 100644
index f08c07330..000000000
--- a/pythonforandroid/recipes/pyethereum/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class PyethereumRecipe(PythonRecipe):
- version = 'v1.6.1'
- url = 'https://github.com/ethereum/pyethereum/archive/{version}.tar.gz'
-
- depends = [
- 'python2', 'setuptools', 'pycryptodome', 'pysha3', 'ethash', 'scrypt'
- ]
-
- call_hostpython_via_targetpython = False
-
-
-recipe = PyethereumRecipe()
diff --git a/pythonforandroid/recipes/pygame/Setup b/pythonforandroid/recipes/pygame/Setup
deleted file mode 100644
index 601802e77..000000000
--- a/pythonforandroid/recipes/pygame/Setup
+++ /dev/null
@@ -1,72 +0,0 @@
-#This Setup file is used by the setup.py script to configure the
-#python extensions. You will likely use the "config.py" which will
-#build a correct Setup file for you based on your system settings.
-#If not, the format is simple enough to edit by hand. First change
-#the needed commandline flags for each dependency, then comment out
-#any unavailable optional modules in the first optional section.
-
-
-#--StartConfig
-SDL = -D_REENTRANT -lsdl -lm
-FONT = -lsdl_ttf
-IMAGE = -lsdl_image
-MIXER = -lsdl_mixer
-SMPEG = -lsmpeg
-PNG = -lpng -lz
-JPEG = -ljpeg
-SCRAP = -lX11
-PORTMIDI = -lportmidi
-PORTTIME = -lporttime
-#--EndConfig
-
-#DEBUG = -C-W -C-Wall
-DEBUG =
-
-#the following modules are optional. you will want to compile
-#everything you can, but you can ignore ones you don't have
-#dependencies for, just comment them out
-
-imageext src/imageext.c $(SDL) $(IMAGE) $(PNG) $(JPEG) $(DEBUG)
-font src/font.c $(SDL) $(FONT) $(DEBUG)
-# mixer src/mixer.c $(SDL) $(MIXER) $(DEBUG)
-# mixer_music src/music.c $(SDL) $(MIXER) $(DEBUG)
-# _numericsurfarray src/_numericsurfarray.c $(SDL) $(DEBUG)
-# _numericsndarray src/_numericsndarray.c $(SDL) $(MIXER) $(DEBUG)
-# movie src/movie.c $(SDL) $(SMPEG) $(DEBUG)
-# scrap src/scrap.c $(SDL) $(SCRAP) $(DEBUG)
-# _camera src/_camera.c src/camera_v4l2.c src/camera_v4l.c $(SDL) $(DEBUG)
-# pypm src/pypm.c $(SDL) $(PORTMIDI) $(PORTTIME) $(DEBUG)
-
-GFX = src/SDL_gfx/SDL_gfxPrimitives.c
-#GFX = src/SDL_gfx/SDL_gfxBlitFunc.c src/SDL_gfx/SDL_gfxPrimitives.c
-gfxdraw src/gfxdraw.c $(SDL) $(GFX) $(DEBUG)
-
-
-
-#these modules are required for pygame to run. they only require
-#SDL as a dependency. these should not be altered
-
-base src/base.c $(SDL) $(DEBUG)
-cdrom src/cdrom.c $(SDL) $(DEBUG)
-color src/color.c $(SDL) $(DEBUG)
-constants src/constants.c $(SDL) $(DEBUG)
-display src/display.c $(SDL) $(DEBUG)
-event src/event.c $(SDL) $(DEBUG)
-fastevent src/fastevent.c src/fastevents.c $(SDL) $(DEBUG)
-key src/key.c $(SDL) $(DEBUG)
-mouse src/mouse.c $(SDL) $(DEBUG)
-rect src/rect.c $(SDL) $(DEBUG)
-rwobject src/rwobject.c $(SDL) $(DEBUG)
-surface src/surface.c src/alphablit.c src/surface_fill.c $(SDL) $(DEBUG)
-surflock src/surflock.c $(SDL) $(DEBUG)
-time src/time.c $(SDL) $(DEBUG)
-joystick src/joystick.c $(SDL) $(DEBUG)
-draw src/draw.c $(SDL) $(DEBUG)
-image src/image.c $(SDL) $(DEBUG)
-overlay src/overlay.c $(SDL) $(DEBUG)
-transform src/transform.c src/rotozoom.c src/scale2x.c src/scale_mmx.c $(SDL) $(DEBUG) -D_NO_MMX_FOR_X86_64
-mask src/mask.c src/bitmask.c $(SDL) $(DEBUG)
-bufferproxy src/bufferproxy.c $(SDL) $(DEBUG)
-pixelarray src/pixelarray.c $(SDL) $(DEBUG)
-_arraysurfarray src/_arraysurfarray.c $(SDL) $(DEBUG)
-
diff --git a/pythonforandroid/recipes/pygame/__init__.py b/pythonforandroid/recipes/pygame/__init__.py
index 5e3b2de98..b77240e1b 100644
--- a/pythonforandroid/recipes/pygame/__init__.py
+++ b/pythonforandroid/recipes/pygame/__init__.py
@@ -1,75 +1,72 @@
+from os.path import join
-from pythonforandroid.recipe import Recipe
-from pythonforandroid.util import current_directory, ensure_dir
-from pythonforandroid.logger import debug, shprint, info, warning
-from os.path import exists, join
-import sh
-import glob
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.toolchain import current_directory
-class PygameRecipe(Recipe):
+
+class Pygame2Recipe(CompiledComponentsPythonRecipe):
+ """
+ Recipe to build apps based on SDL2-based pygame.
+
+ .. warning:: Some pygame functionality is still untested, and some
+ dependencies like freetype, postmidi and libjpeg are currently
+ not part of the build. It's usable, but not complete.
+ """
+
+ version = '2.1.0'
+ url = 'https://github.com/pygame/pygame/archive/{version}.tar.gz'
+
+ site_packages_name = 'pygame'
name = 'pygame'
- version = '1.9.1'
- url = 'http://pygame.org/ftp/pygame-{version}release.tar.gz'
-
- depends = ['python2', 'sdl']
- conflicts = ['sdl2']
-
- patches = ['patches/fix-surface-access.patch',
- 'patches/fix-array-surface.patch',
- 'patches/fix-sdl-spam-log.patch']
-
- def get_recipe_env(self, arch=None):
- env = super(PygameRecipe, self).get_recipe_env(arch)
- env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
- self.ctx.get_libs_dir(arch.arch))
- env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
- env['LIBLINK'] = 'NOTNONE'
- env['NDKPLATFORM'] = self.ctx.ndk_platform
-
- # Every recipe uses its own liblink path, object files are collected and biglinked later
- liblink_path = join(self.get_build_container_dir(arch.arch), 'objects_{}'.format(self.name))
- env['LIBLINK_PATH'] = liblink_path
- ensure_dir(liblink_path)
- return env
- def prebuild_arch(self, arch):
- if self.is_patched(arch):
- return
- shprint(sh.cp, join(self.get_recipe_dir(), 'Setup'),
- join(self.get_build_dir(arch.arch), 'Setup'))
-
- def build_arch(self, arch):
- env = self.get_recipe_env(arch)
-
- env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/png -I{jni_path}/jpeg'.format(
- jni_path=join(self.ctx.bootstrap.build_dir, 'jni'))
- env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/sdl/include -I{jni_path}/sdl_mixer'.format(
- jni_path=join(self.ctx.bootstrap.build_dir, 'jni'))
- env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/sdl_ttf -I{jni_path}/sdl_image'.format(
- jni_path=join(self.ctx.bootstrap.build_dir, 'jni'))
- debug('pygame cflags', env['CFLAGS'])
-
-
- env['LDFLAGS'] = env['LDFLAGS'] + ' -L{libs_path} -L{src_path}/obj/local/{arch} -lm -lz'.format(
- libs_path=self.ctx.libs_dir, src_path=self.ctx.bootstrap.build_dir, arch=env['ARCH'])
-
- env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
+ depends = ['sdl2', 'sdl2_image', 'sdl2_mixer', 'sdl2_ttf', 'setuptools', 'jpeg', 'png']
+ call_hostpython_via_targetpython = False # Due to setuptools
+ install_in_hostpython = False
+ def prebuild_arch(self, arch):
+ super().prebuild_arch(arch)
with current_directory(self.get_build_dir(arch.arch)):
- info('hostpython is ' + self.ctx.hostpython)
- hostpython = sh.Command(self.ctx.hostpython)
- shprint(hostpython, 'setup.py', 'install', '-O2', _env=env,
- _tail=10, _critical=True)
+ setup_template = open(join("buildconfig", "Setup.Android.SDL2.in")).read()
+ env = self.get_recipe_env(arch)
+ env['ANDROID_ROOT'] = join(self.ctx.ndk.sysroot, 'usr')
+
+ png = self.get_recipe('png', self.ctx)
+ png_lib_dir = join(png.get_build_dir(arch.arch), '.libs')
+ png_inc_dir = png.get_build_dir(arch)
+
+ jpeg = self.get_recipe('jpeg', self.ctx)
+ jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch)
- info('strip is ' + env['STRIP'])
- build_lib = glob.glob('./build/lib*')
- assert len(build_lib) == 1
- print('stripping pygame')
- shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
- env['STRIP'], '{}', ';')
+ sdl_mixer_includes = ""
+ sdl2_mixer_recipe = self.get_recipe('sdl2_mixer', self.ctx)
+ for include_dir in sdl2_mixer_recipe.get_include_dirs(arch):
+ sdl_mixer_includes += f"-I{include_dir} "
- python_install_path = join(self.ctx.build_dir, 'python-install')
- warning('Should remove pygame tests etc. here, but skipping for now')
+ sdl2_image_includes = ""
+ sdl2_image_recipe = self.get_recipe('sdl2_image', self.ctx)
+ for include_dir in sdl2_image_recipe.get_include_dirs(arch):
+ sdl2_image_includes += f"-I{include_dir} "
+
+ setup_file = setup_template.format(
+ sdl_includes=(
+ " -I" + join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include') +
+ " -L" + join(self.ctx.bootstrap.build_dir, "libs", str(arch)) +
+ " -L" + png_lib_dir + " -L" + jpeg_lib_dir + " -L" + arch.ndk_lib_dir_versioned),
+ sdl_ttf_includes="-I"+join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'),
+ sdl_image_includes=sdl2_image_includes,
+ sdl_mixer_includes=sdl_mixer_includes,
+ jpeg_includes="-I"+jpeg_inc_dir,
+ png_includes="-I"+png_inc_dir,
+ freetype_includes=""
+ )
+ open("Setup", "w").write(setup_file)
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['USE_SDL2'] = '1'
+ env["PYGAME_CROSS_COMPILE"] = "TRUE"
+ env["PYGAME_ANDROID"] = "TRUE"
+ return env
-recipe = PygameRecipe()
+recipe = Pygame2Recipe()
diff --git a/pythonforandroid/recipes/pygame/patches/fix-array-surface.patch b/pythonforandroid/recipes/pygame/patches/fix-array-surface.patch
deleted file mode 100644
index ab74d3eb5..000000000
--- a/pythonforandroid/recipes/pygame/patches/fix-array-surface.patch
+++ /dev/null
@@ -1,62 +0,0 @@
---- pygame-1.9.1release/src/_arraysurfarray.c.orig 2009-05-26 23:15:24.000000000 +0200
-+++ pygame-1.9.1release/src/_arraysurfarray.c 2012-01-06 15:10:08.273825849 +0100
-@@ -193,9 +193,6 @@
- case sizeof (Uint32):
- COPYMACRO_2D(Uint8, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_2D(Uint8, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -223,9 +220,6 @@
- case sizeof (Uint32):
- COPYMACRO_2D(Uint16, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_2D(Uint16, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -250,9 +244,6 @@
- case sizeof (Uint32):
- COPYMACRO_3D(Uint16, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_3D(Uint16, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -316,9 +307,6 @@
- case sizeof (Uint32):
- COPYMACRO_3D_24(Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_3D_24(Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -335,9 +323,6 @@
- case sizeof (Uint32):
- COPYMACRO_2D(Uint32, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_2D(Uint32, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
-@@ -362,9 +347,6 @@
- case sizeof (Uint32):
- COPYMACRO_3D(Uint32, Uint32);
- break;
-- case sizeof (Uint64):
-- COPYMACRO_3D(Uint32, Uint64);
-- break;
- default:
- Py_DECREF(cobj);
- if (!PySurface_UnlockBy(surfobj, (PyObject *) arrayobj)) {
diff --git a/pythonforandroid/recipes/pygame/patches/fix-sdl-spam-log.patch b/pythonforandroid/recipes/pygame/patches/fix-sdl-spam-log.patch
deleted file mode 100644
index d78b5b5a7..000000000
--- a/pythonforandroid/recipes/pygame/patches/fix-sdl-spam-log.patch
+++ /dev/null
@@ -1,47 +0,0 @@
---- pygame-1.9.1release/src/joystick.c.orig
-+++ pygame-1.9.1release/src/joystick.c
-@@ -201,7 +201,7 @@ joy_get_axis (PyObject* self, PyObject* args)
- }
-
- value = SDL_JoystickGetAxis (joy, axis);
-- printf("SDL_JoystickGetAxis value:%d:\n", value);
-+/* printf("SDL_JoystickGetAxis value:%d:\n", value); */
-
-
- return PyFloat_FromDouble (value / 32768.0);
-@@ -241,7 +241,7 @@ joy_get_button (PyObject* self, PyObject* args)
- }
-
- value = SDL_JoystickGetButton (joy, _index);
-- printf("SDL_JoystickGetButton value:%d:\n", value);
-+/* printf("SDL_JoystickGetButton value:%d:\n", value); */
-
- return PyInt_FromLong (value);
- }
-@@ -277,7 +277,7 @@ joy_get_ball (PyObject* self, PyObject* args)
- return RAISE (PyExc_SDLError, "Joystick not initialized");
- }
- value = SDL_JoystickNumBalls (joy);
-- printf("SDL_JoystickNumBalls value:%d:\n", value);
-+/* printf("SDL_JoystickNumBalls value:%d:\n", value); */
-
- if (_index < 0 || _index >= value) {
- return RAISE (PyExc_SDLError, "Invalid joystick trackball");
-@@ -300,7 +300,7 @@ joy_get_numhats (PyObject* self)
- }
-
- value = SDL_JoystickNumHats (joy);
-- printf("SDL_JoystickNumHats value:%d:\n", value);
-+/* printf("SDL_JoystickNumHats value:%d:\n", value); */
-
- return PyInt_FromLong (value);
- }
-@@ -327,7 +327,7 @@ joy_get_hat (PyObject* self, PyObject* args)
-
- px = py = 0;
- value = SDL_JoystickGetHat (joy, _index);
-- printf("SDL_JoystickGetHat value:%d:\n", value);
-+/* printf("SDL_JoystickGetHat value:%d:\n", value); */
-
- if (value & SDL_HAT_UP) {
- py = 1;
diff --git a/pythonforandroid/recipes/pygame/patches/fix-surface-access.patch b/pythonforandroid/recipes/pygame/patches/fix-surface-access.patch
deleted file mode 100644
index 66f7c2755..000000000
--- a/pythonforandroid/recipes/pygame/patches/fix-surface-access.patch
+++ /dev/null
@@ -1,37 +0,0 @@
---- pygame-1.9.1release/src/surface.c.orig 2012-01-06 15:05:14.457829356 +0100
-+++ pygame-1.9.1release/src/surface.c 2012-01-06 15:05:26.009829217 +0100
-@@ -1722,7 +1722,7 @@
- {
- SDL_Surface *surf = PySurface_AsSurface (self);
- /* Need to use 64bit vars so this works on 64 bit pythons. */
-- Uint64 r, g, b, a;
-+ unsigned long r, g, b, a;
-
- if (!PyArg_ParseTuple (args, "(kkkk)", &r, &g, &b, &a))
- return NULL;
-@@ -1734,10 +1734,12 @@
- printf("what are: %d, %d, %d, %d\n", surf->format->Rmask, surf->format->Gmask, surf->format->Bmask, surf->format->Amask);
- */
-
-- surf->format->Rmask = (Uint32)r;
-- surf->format->Gmask = (Uint32)g;
-- surf->format->Bmask = (Uint32)b;
-- surf->format->Amask = (Uint32)a;
-+ SDL_PixelFormat *spf = surf->format;
-+
-+ spf->Rmask = (Uint32)r;
-+ spf->Gmask = (Uint32)g;
-+ spf->Bmask = (Uint32)b;
-+ spf->Amask = (Uint32)a;
-
- Py_RETURN_NONE;
- }
-@@ -1762,7 +1764,7 @@
- surf_set_shifts (PyObject *self, PyObject *args)
- {
- SDL_Surface *surf = PySurface_AsSurface (self);
-- Uint64 r, g, b, a;
-+ unsigned long r, g, b, a;
-
- if (!PyArg_ParseTuple (args, "(kkkk)", &r, &g, &b, &a))
- return NULL;
diff --git a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py b/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py
deleted file mode 100644
index af7ec6b11..000000000
--- a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from pythonforandroid.toolchain import BootstrapNDKRecipe, current_directory, shprint, info
-from os.path import exists, join
-import sh
-import glob
-
-class PygameJNIComponentsRecipe(BootstrapNDKRecipe):
- version = 'master'
- url = 'https://github.com/kivy/p4a-pygame-bootstrap-components/archive/{version}.zip'
- dir_name = 'bootstrap_components'
-
- def prebuild_arch(self, arch):
- super(PygameJNIComponentsRecipe, self).postbuild_arch(arch)
-
- info('Unpacking pygame bootstrap JNI dir components')
- with current_directory(self.get_build_container_dir(arch)):
- if exists('sdl'):
- info('sdl dir exists, so it looks like the JNI components' +
- 'are already unpacked. Skipping.')
- return
- for dirn in glob.glob(join(self.get_build_dir(arch),
- 'pygame_bootstrap_jni', '*')):
- shprint(sh.mv, dirn, './')
- info('Unpacking was successful, deleting original container dir')
- shprint(sh.rm, '-rf', self.get_build_dir(arch))
-
-
-recipe = PygameJNIComponentsRecipe()
diff --git a/pythonforandroid/recipes/pyicu/__init__.py b/pythonforandroid/recipes/pyicu/__init__.py
index 3e6627e17..d1e3749fb 100644
--- a/pythonforandroid/recipes/pyicu/__init__.py
+++ b/pythonforandroid/recipes/pyicu/__init__.py
@@ -1,59 +1,29 @@
-import os
-import sh
from os.path import join
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-from pythonforandroid.util import current_directory
-from pythonforandroid.toolchain import shprint, info
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
-class PyICURecipe(CompiledComponentsPythonRecipe):
+class PyICURecipe(CppCompiledComponentsPythonRecipe):
version = '1.9.2'
- url = 'https://pypi.python.org/packages/source/P/PyICU/PyICU-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), "icu"]
- patches = ['locale.patch', 'icu.patch']
+ url = ('https://pypi.python.org/packages/source/P/PyICU/'
+ 'PyICU-{version}.tar.gz')
+ depends = ["icu"]
+ patches = ['locale.patch']
def get_recipe_env(self, arch):
- env = super(PyICURecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
icu_include = join(
- self.ctx.get_python_install_dir(), "include", "icu")
-
- env["CC"] += " -I"+icu_include
-
- include = (
- " -I{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/include/"
- " -I{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/"
- "{arch}/include")
- include = include.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- env["CC"] += include
-
- lib = "{ndk}/sources/cxx-stl/gnu-libstdc++/{version}/libs/{arch}"
- lib = lib.format(ndk=self.ctx.ndk_dir,
- version=env["TOOLCHAIN_VERSION"],
- arch=arch.arch)
- env["LDFLAGS"] += " -lgnustl_shared -L"+lib
-
- build_dir = self.get_build_dir(arch.arch)
- env["LDFLAGS"] += " -L"+build_dir
- return env
-
- def build_arch(self, arch):
- build_dir = self.get_build_dir(arch.arch)
+ self.ctx.get_python_install_dir(arch.arch), "include", "icu")
- info("create links to icu libs")
- lib_dir = join(self.ctx.get_python_install_dir(), "lib")
- icu_libs = [f for f in os.listdir(lib_dir) if f.startswith("libicu")]
+ icu_recipe = self.get_recipe('icu', self.ctx)
+ icu_link_libs = icu_recipe.built_libraries.keys()
+ env["PYICU_LIBRARIES"] = ":".join(lib[3:-3] for lib in icu_link_libs)
+ env["CPPFLAGS"] += " -I" + icu_include
+ env["LDFLAGS"] += " -L" + join(
+ icu_recipe.get_build_dir(arch.arch), "icu_build", "lib"
+ )
- for l in icu_libs:
- raw = l.rsplit(".", 1)[0]
- try:
- shprint(sh.ln, "-s", join(lib_dir, l), join(build_dir, raw))
- except Exception:
- pass
-
- super(PyICURecipe, self).build_arch(arch)
+ return env
recipe = PyICURecipe()
diff --git a/pythonforandroid/recipes/pyicu/icu.patch b/pythonforandroid/recipes/pyicu/icu.patch
deleted file mode 100644
index e0a42fc4e..000000000
--- a/pythonforandroid/recipes/pyicu/icu.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-diff -Naur icu.py icu1.py
---- pyicu/icu.py 2012-11-23 21:28:55.000000000 +0100
-+++ icu1.py 2016-05-14 14:45:44.160023949 +0200
-@@ -34,4 +34,15 @@
- class InvalidArgsError(Exception):
- pass
-
-+import ctypes
-+import os
-+root = os.environ["ANDROID_APP_PATH"]
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libgnustl_shared.so"))
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libicudata.so.57"))
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libicuuc.so.57"))
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libicui18n.so.57"))
-+ctypes.cdll.LoadLibrary(os.path.join(root, "lib", "libicule.so.57"))
-+del root
-+del os
-+
- from docs import *
diff --git a/pythonforandroid/recipes/pyjnius/__init__.py b/pythonforandroid/recipes/pyjnius/__init__.py
index 81fc323d7..0bcb74d39 100644
--- a/pythonforandroid/recipes/pyjnius/__init__.py
+++ b/pythonforandroid/recipes/pyjnius/__init__.py
@@ -1,22 +1,27 @@
-
-from pythonforandroid.toolchain import CythonRecipe, shprint, current_directory, info
-from pythonforandroid.patching import will_build, check_any
+from pythonforandroid.recipe import CythonRecipe
+from pythonforandroid.toolchain import shprint, current_directory, info
+from pythonforandroid.patching import will_build
import sh
from os.path import join
class PyjniusRecipe(CythonRecipe):
- version = 'master'
+ version = '1.6.1'
url = 'https://github.com/kivy/pyjnius/archive/{version}.zip'
name = 'pyjnius'
- depends = [('python2', 'python3crystax'), ('genericndkbuild', 'sdl2', 'sdl'), 'six']
+ depends = [('genericndkbuild', 'sdl2'), 'six']
site_packages_name = 'jnius'
- patches = [('sdl2_jnienv_getter.patch', will_build('sdl2')),
- ('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild'))]
+ patches = [('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild'))]
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ # NDKPLATFORM is our switch for detecting Android platform, so can't be None
+ env['NDKPLATFORM'] = "NOTNONE"
+ return env
def postbuild_arch(self, arch):
- super(PyjniusRecipe, self).postbuild_arch(arch)
+ super().postbuild_arch(arch)
info('Copying pyjnius java class to classes build dir')
with current_directory(self.get_build_dir(arch.arch)):
shprint(sh.cp, '-a', join('jnius', 'src', 'org'), self.ctx.javaclass_dir)
diff --git a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch b/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch
index 50c62cb39..fcd538711 100644
--- a/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch
+++ b/pythonforandroid/recipes/pyjnius/genericndkbuild_jnienv_getter.patch
@@ -1,25 +1,24 @@
-diff --git a/jnius/jnius_jvm_android.pxi b/jnius/jnius_jvm_android.pxi
-index ac89fec..71daa43 100644
---- a/jnius/jnius_jvm_android.pxi
-+++ b/jnius/jnius_jvm_android.pxi
-@@ -1,5 +1,5 @@
+diff -Naur pyjnius.orig/jnius/env.py pyjnius/jnius/env.py
+--- pyjnius.orig/jnius/env.py 2022-05-28 11:16:02.000000000 +0200
++++ pyjnius/jnius/env.py 2022-05-28 11:18:30.000000000 +0200
+@@ -268,7 +268,7 @@
+
+ class AndroidJavaLocation(UnixJavaLocation):
+ def get_libraries(self):
+- return ['SDL2', 'log']
++ return ['main', 'log']
+
+ def get_include_dirs(self):
+ # When cross-compiling for Android, we should not use the include dirs
+diff -Naur pyjnius.orig/jnius/jnius_jvm_android.pxi pyjnius/jnius/jnius_jvm_android.pxi
+--- pyjnius.orig/jnius/jnius_jvm_android.pxi 2022-05-28 11:16:02.000000000 +0200
++++ pyjnius/jnius/jnius_jvm_android.pxi 2022-05-28 11:17:17.000000000 +0200
+@@ -1,6 +1,6 @@
# on android, rely on SDL to get the JNI env
--cdef extern JNIEnv *SDL_ANDROID_GetJNIEnv()
+-cdef extern JNIEnv *SDL_AndroidGetJNIEnv()
+cdef extern JNIEnv *WebView_AndroidGetJNIEnv()
- cdef JNIEnv *get_platform_jnienv():
-- return SDL_ANDROID_GetJNIEnv()
-+ return <JNIEnv*>WebView_AndroidGetJNIEnv()
-diff --git a/setup.py b/setup.py
-index 740510f..0c8e55f 100644
---- a/setup.py
-+++ b/setup.py
-@@ -53,7 +53,7 @@ except ImportError:
- if platform == 'android':
- # for android, we use SDL...
-- libraries = ['sdl', 'log']
-+ libraries = ['main', 'log']
- library_dirs = ['libs/' + getenv('ARCH')]
- elif platform == 'darwin':
- import subprocess
+ cdef JNIEnv *get_platform_jnienv() except NULL:
+- return <JNIEnv*>SDL_AndroidGetJNIEnv()
++ return <JNIEnv*>WebView_AndroidGetJNIEnv()
diff --git a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch b/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch
deleted file mode 100644
index a36613892..000000000
--- a/pythonforandroid/recipes/pyjnius/sdl2_jnienv_getter.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-diff --git a/jnius/jnius_jvm_android.pxi b/jnius/jnius_jvm_android.pxi
-index ac89fec..71daa43 100644
---- a/jnius/jnius_jvm_android.pxi
-+++ b/jnius/jnius_jvm_android.pxi
-@@ -1,5 +1,5 @@
- # on android, rely on SDL to get the JNI env
--cdef extern JNIEnv *SDL_ANDROID_GetJNIEnv()
-+cdef extern JNIEnv *SDL_AndroidGetJNIEnv()
-
- cdef JNIEnv *get_platform_jnienv():
-- return SDL_ANDROID_GetJNIEnv()
-+ return <JNIEnv*>SDL_AndroidGetJNIEnv()
-diff --git a/setup.py b/setup.py
-index 740510f..0c8e55f 100644
---- a/setup.py
-+++ b/setup.py
-@@ -53,7 +53,7 @@ except ImportError:
-
- if platform == 'android':
- # for android, we use SDL...
-- libraries = ['sdl', 'log']
-+ libraries = ['SDL2', 'log']
- library_dirs = ['libs/' + getenv('ARCH')]
- elif platform == 'darwin':
- import subprocess
diff --git a/pythonforandroid/recipes/pyleveldb/__init__.py b/pythonforandroid/recipes/pyleveldb/__init__.py
index c3305b07d..54dfb6465 100644
--- a/pythonforandroid/recipes/pyleveldb/__init__.py
+++ b/pythonforandroid/recipes/pyleveldb/__init__.py
@@ -1,35 +1,27 @@
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe, shprint, shutil, current_directory
-from os.path import join, exists
-import sh
-
-class PyLevelDBRecipe(CompiledComponentsPythonRecipe):
- version = '0.193'
- url = 'https://pypi.python.org/packages/source/l/leveldb/leveldb-{version}.tar.gz'
- depends = ['snappy', 'leveldb', 'hostpython2', 'python2', 'setuptools']
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
+
+class PyLevelDBRecipe(CppCompiledComponentsPythonRecipe):
+ version = '0.194'
+ url = ('https://pypi.python.org/packages/source/l/leveldb/'
+ 'leveldb-{version}.tar.gz')
+ depends = ['snappy', 'leveldb', 'setuptools']
patches = ['bindings-only.patch']
- call_hostpython_via_targetpython = False # Due to setuptools
site_packages_name = 'leveldb'
- def build_arch(self, arch):
- env = self.get_recipe_env(arch)
- with current_directory(self.get_build_dir(arch.arch)):
- # Remove source in this pypi package
- sh.rm('-rf', 'leveldb', 'leveldb.egg-info', 'snappy')
- # Use source from leveldb recipe
- sh.ln('-s', self.get_recipe('leveldb', self.ctx).get_build_dir(arch.arch), 'leveldb')
- # Build and install python bindings
- super(PyLevelDBRecipe, self).build_arch(arch)
-
def get_recipe_env(self, arch):
- env = super(PyLevelDBRecipe, self).get_recipe_env(arch)
- # Copy environment from leveldb recipe
- env.update(self.get_recipe('leveldb', self.ctx).get_recipe_env(arch))
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
- # Set linker to use the correct gcc
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += ' -lpython2.7' + \
- ' -lleveldb'
+ env = super().get_recipe_env(arch)
+
+ snappy_recipe = self.get_recipe('snappy', self.ctx)
+ leveldb_recipe = self.get_recipe('leveldb', self.ctx)
+
+ env["LDFLAGS"] += " -L" + snappy_recipe.get_build_dir(arch.arch)
+ env["LDFLAGS"] += " -L" + leveldb_recipe.get_build_dir(arch.arch)
+
+ env["SNAPPY_BUILD_PATH"] = snappy_recipe.get_build_dir(arch.arch)
+ env["LEVELDB_BUILD_PATH"] = leveldb_recipe.get_build_dir(arch.arch)
+
return env
+
recipe = PyLevelDBRecipe()
diff --git a/pythonforandroid/recipes/pyleveldb/bindings-only.patch b/pythonforandroid/recipes/pyleveldb/bindings-only.patch
index 2899f4efa..9f7027abb 100644
--- a/pythonforandroid/recipes/pyleveldb/bindings-only.patch
+++ b/pythonforandroid/recipes/pyleveldb/bindings-only.patch
@@ -1,103 +1,119 @@
---- pyleveldb/setup.py 2014-03-28 02:51:24.000000000 +0100
-+++ pyleveldb-patch/setup.py 2016-03-02 11:52:13.780678586 +0100
-@@ -7,41 +7,22 @@
- #
- # See LICENSE for details.
-
--import glob
--import platform
--import sys
--
+This patch force to only build the python bindings, and to do so, we modify
+the setup.py file in oder that finds our compiled libraries (libleveldb.so and
+libsnappy.so)
+--- leveldb-0.194/setup.py.orig 2016-09-17 02:05:55.000000000 +0200
++++ leveldb-0.194/setup.py 2019-02-26 16:57:40.997435911 +0100
+@@ -11,44 +11,25 @@ import platform
+ import sys
+
from setuptools import setup, Extension
-
--system,node,release,version,machine,processor = platform.uname()
++from os import environ
+
+ system, node, release, version, machine, processor = platform.uname()
-common_flags = [
+- '-I./leveldb/include',
+- '-I./leveldb',
+- '-I./snappy',
+extra_compile_args = [
- '-I./leveldb/include',
- '-I./leveldb',
-- '-I./snappy',
-+ '-I./leveldb/snappy',
- '-I.',
-- '-fno-builtin-memcmp',
- '-O2',
- '-fPIC',
- '-DNDEBUG',
- '-DSNAPPY',
--]
--
++ '-I{}/include'.format(environ.get('LEVELDB_BUILD_PATH')),
++ '-I{}'.format(environ.get('LEVELDB_BUILD_PATH')),
++ '-I{}'.format(environ.get('SNAPPY_BUILD_PATH')),
++ '-I.',
+ '-I.',
+- '-fno-builtin-memcmp',
+ '-O2',
+ '-fPIC',
+ '-DNDEBUG',
+ '-DSNAPPY',
++ '-pthread',
++ '-Wall',
++ '-D_REENTRANT',
++ '-DOS_ANDROID',
+ ]
+
-if system == 'Darwin':
-- extra_compile_args = common_flags + [
-- '-DOS_MACOSX',
-+ '-Wall',
- '-DLEVELDB_PLATFORM_POSIX',
-- '-Wno-error=unused-command-line-argument-hard-error-in-future',
-- ]
+- extra_compile_args = common_flags + [
+- '-DOS_MACOSX',
+- '-DLEVELDB_PLATFORM_POSIX',
+- '-Wno-error=unused-command-line-argument-hard-error-in-future',
+- ]
-elif system == 'Linux':
+- extra_compile_args = common_flags + [
+- '-pthread',
+- '-Wall',
+- '-DOS_LINUX',
+- '-DLEVELDB_PLATFORM_POSIX',
+- ]
+-elif system == 'SunOS':
- extra_compile_args = common_flags + [
- '-pthread',
-- '-Wall',
-- '-DOS_LINUX',
+- '-Wall',
+- '-DOS_SOLARIS',
- '-DLEVELDB_PLATFORM_POSIX',
- ]
-else:
-- print >>sys.stderr, "Don't know how to compile leveldb for %s!" % system
-- sys.exit(0)
-+ '-D_REENTRANT',
-+ '-DOS_ANDROID',
-+]
-
+- sys.stderr.write("Don't know how to compile leveldb for %s!\n" % system)
+- sys.exit(1)
+-
setup(
- name = 'leveldb',
-@@ -75,52 +56,6 @@
- ext_modules = [
- Extension('leveldb',
- sources = [
-- # snappy
-- './snappy/snappy.cc',
-- './snappy/snappy-stubs-internal.cc',
-- './snappy/snappy-sinksource.cc',
-- './snappy/snappy-c.cc',
+ name = 'leveldb',
+ version = '0.194',
+@@ -81,57 +62,11 @@ setup(
+ ext_modules = [
+ Extension('leveldb',
+ sources = [
+- # snappy
+- './snappy/snappy.cc',
+- './snappy/snappy-stubs-internal.cc',
+- './snappy/snappy-sinksource.cc',
+- './snappy/snappy-c.cc',
-
-- #leveldb
-- 'leveldb/db/builder.cc',
-- 'leveldb/db/c.cc',
-- 'leveldb/db/db_impl.cc',
-- 'leveldb/db/db_iter.cc',
-- 'leveldb/db/dbformat.cc',
-- 'leveldb/db/filename.cc',
-- 'leveldb/db/log_reader.cc',
-- 'leveldb/db/log_writer.cc',
-- 'leveldb/db/memtable.cc',
-- 'leveldb/db/repair.cc',
-- 'leveldb/db/table_cache.cc',
-- 'leveldb/db/version_edit.cc',
-- 'leveldb/db/version_set.cc',
-- 'leveldb/db/write_batch.cc',
-- 'leveldb/table/block.cc',
-- 'leveldb/table/block_builder.cc',
-- 'leveldb/table/filter_block.cc',
-- 'leveldb/table/format.cc',
-- 'leveldb/table/iterator.cc',
-- 'leveldb/table/merger.cc',
-- 'leveldb/table/table.cc',
-- 'leveldb/table/table_builder.cc',
-- 'leveldb/table/two_level_iterator.cc',
-- 'leveldb/util/arena.cc',
-- 'leveldb/util/bloom.cc',
-- 'leveldb/util/cache.cc',
-- 'leveldb/util/coding.cc',
-- 'leveldb/util/comparator.cc',
-- 'leveldb/util/crc32c.cc',
-- 'leveldb/util/env.cc',
-- 'leveldb/util/env_posix.cc',
-- 'leveldb/util/filter_policy.cc',
-- 'leveldb/util/hash.cc',
-- 'leveldb/util/histogram.cc',
-- 'leveldb/util/logging.cc',
-- 'leveldb/util/options.cc',
-- 'leveldb/util/status.cc',
-- 'leveldb/port/port_posix.cc',
+- #leveldb
+- 'leveldb/db/builder.cc',
+- 'leveldb/db/c.cc',
+- 'leveldb/db/db_impl.cc',
+- 'leveldb/db/db_iter.cc',
+- 'leveldb/db/dbformat.cc',
+- 'leveldb/db/filename.cc',
+- 'leveldb/db/log_reader.cc',
+- 'leveldb/db/log_writer.cc',
+- 'leveldb/db/memtable.cc',
+- 'leveldb/db/repair.cc',
+- 'leveldb/db/table_cache.cc',
+- 'leveldb/db/version_edit.cc',
+- 'leveldb/db/version_set.cc',
+- 'leveldb/db/write_batch.cc',
+- 'leveldb/table/block.cc',
+- 'leveldb/table/block_builder.cc',
+- 'leveldb/table/filter_block.cc',
+- 'leveldb/table/format.cc',
+- 'leveldb/table/iterator.cc',
+- 'leveldb/table/merger.cc',
+- 'leveldb/table/table.cc',
+- 'leveldb/table/table_builder.cc',
+- 'leveldb/table/two_level_iterator.cc',
+- 'leveldb/util/arena.cc',
+- 'leveldb/util/bloom.cc',
+- 'leveldb/util/cache.cc',
+- 'leveldb/util/coding.cc',
+- 'leveldb/util/comparator.cc',
+- 'leveldb/util/crc32c.cc',
+- 'leveldb/util/env.cc',
+- 'leveldb/util/env_posix.cc',
+- 'leveldb/util/filter_policy.cc',
+- 'leveldb/util/hash.cc',
+- 'leveldb/util/histogram.cc',
+- 'leveldb/util/logging.cc',
+- 'leveldb/util/options.cc',
+- 'leveldb/util/status.cc',
+- 'leveldb/port/port_posix.cc',
-
- # python stuff
- 'leveldb_ext.cc',
- 'leveldb_object.cc',
+ # python stuff
+ 'leveldb_ext.cc',
+ 'leveldb_object.cc',
+ ],
+- libraries = ['stdc++'],
++ libraries = ['snappy', 'leveldb', 'stdc++', 'c++_shared'],
+ extra_compile_args = extra_compile_args,
+ )
+ ]
diff --git a/pythonforandroid/recipes/pymunk/__init__.py b/pythonforandroid/recipes/pymunk/__init__.py
index 73e4b7865..a982098f2 100644
--- a/pythonforandroid/recipes/pymunk/__init__.py
+++ b/pythonforandroid/recipes/pymunk/__init__.py
@@ -1,25 +1,18 @@
-from pythonforandroid.toolchain import PythonRecipe
-from pythonforandroid.toolchain import CythonRecipe
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-from pythonforandroid.logger import info
-import os.path
class PymunkRecipe(CompiledComponentsPythonRecipe):
name = "pymunk"
- version = '5.2.0'
- url = 'https://pypi.python.org/packages/5e/bd/e67edcffdee3d0a1e3ebf0050bb9746a61d616f5502ceedddf0f7fd0a896/pymunk-5.2.0.zip'
- depends = [('python2', 'python3crystax'), 'cffi', 'setuptools']
+ version = "6.0.0"
+ url = "https://pypi.python.org/packages/source/p/pymunk/pymunk-{version}.zip"
+ depends = ["cffi", "setuptools"]
call_hostpython_via_targetpython = False
def get_recipe_env(self, arch):
- env = super(PymunkRecipe, self).get_recipe_env(arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- arch_noeabi = arch.arch.replace('eabi', '')
- env['LDFLAGS'] += " -shared -llog"
- env['LDFLAGS'] += " -landroid -lpython2.7"
- env['LDFLAGS'] += " --sysroot={ctx.ndk_dir}/platforms/android-{ctx.android_api}/arch-{arch_noeabi}".format(
- ctx=self.ctx, arch_noeabi=arch_noeabi)
+ env = super().get_recipe_env(arch)
+ env["LDFLAGS"] += " -llog" # Used by Chipmunk cpMessage
+ env["LDFLAGS"] += " -lm" # For older versions of Android
return env
+
recipe = PymunkRecipe()
diff --git a/pythonforandroid/recipes/pynacl/__init__.py b/pythonforandroid/recipes/pynacl/__init__.py
new file mode 100644
index 000000000..0ab9352ee
--- /dev/null
+++ b/pythonforandroid/recipes/pynacl/__init__.py
@@ -0,0 +1,29 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+import os
+
+
+class PyNaCLRecipe(CompiledComponentsPythonRecipe):
+ name = 'pynacl'
+ version = '1.3.0'
+ url = 'https://pypi.python.org/packages/source/P/PyNaCl/PyNaCl-{version}.tar.gz'
+
+ depends = ['hostpython3', 'six', 'setuptools', 'cffi', 'libsodium']
+ call_hostpython_via_targetpython = False
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ env['SODIUM_INSTALL'] = 'system'
+
+ libsodium_build_dir = self.get_recipe(
+ 'libsodium', self.ctx).get_build_dir(arch.arch)
+ env['CFLAGS'] += ' -I{}'.format(os.path.join(libsodium_build_dir,
+ 'src/libsodium/include'))
+ env['LDFLAGS'] += ' -L{}'.format(
+ self.ctx.get_libs_dir(arch.arch) +
+ '-L{}'.format(self.ctx.libs_dir)) + ' -L{}'.format(
+ libsodium_build_dir)
+
+ return env
+
+
+recipe = PyNaCLRecipe()
diff --git a/pythonforandroid/recipes/pyogg/__init__.py b/pythonforandroid/recipes/pyogg/__init__.py
new file mode 100644
index 000000000..70ea435c7
--- /dev/null
+++ b/pythonforandroid/recipes/pyogg/__init__.py
@@ -0,0 +1,14 @@
+from pythonforandroid.recipe import PythonRecipe
+from os.path import join
+
+
+class PyOggRecipe(PythonRecipe):
+ version = '0.6.4a1'
+ url = 'https://files.pythonhosted.org/packages/source/p/pyogg/PyOgg-{version}.tar.gz'
+ depends = ['libogg', 'libvorbis', 'setuptools']
+ patches = [join('patches', 'fix-find-lib.patch')]
+
+ call_hostpython_via_targetpython = False
+
+
+recipe = PyOggRecipe()
diff --git a/pythonforandroid/recipes/pyogg/patches/fix-find-lib.patch b/pythonforandroid/recipes/pyogg/patches/fix-find-lib.patch
new file mode 100644
index 000000000..0db7bfd16
--- /dev/null
+++ b/pythonforandroid/recipes/pyogg/patches/fix-find-lib.patch
@@ -0,0 +1,13 @@
+diff --git a/pyogg/library_loader.py b/pyogg/library_loader.py
+index c2ba36c..383331a 100644
+--- a/pyogg/library_loader.py
++++ b/pyogg/library_loader.py
+@@ -54,7 +54,7 @@ def load_other(name, paths = None):
+ except:
+ pass
+ else:
+- for path in [os.getcwd(), _here]:
++ for path in [os.path.join(os.environ['ANDROID_PRIVATE'], '..', 'lib')]:
+ for style in other_styles:
+ candidate = os.path.join(path, style.format(name))
+ if os.path.exists(candidate):
diff --git a/pythonforandroid/recipes/pyopenal/__init__.py b/pythonforandroid/recipes/pyopenal/__init__.py
new file mode 100644
index 000000000..c42cd0965
--- /dev/null
+++ b/pythonforandroid/recipes/pyopenal/__init__.py
@@ -0,0 +1,14 @@
+from pythonforandroid.recipe import PythonRecipe
+from os.path import join
+
+
+class PyOpenALRecipe(PythonRecipe):
+ version = '0.7.3a1'
+ url = 'https://files.pythonhosted.org/packages/source/p/pyopenal/PyOpenAL-{version}.tar.gz'
+ depends = ['openal', 'numpy', 'setuptools']
+ patches = [join('patches', 'fix-find-lib.patch')]
+
+ call_hostpython_via_targetpython = False
+
+
+recipe = PyOpenALRecipe()
diff --git a/pythonforandroid/recipes/pyopenal/patches/fix-find-lib.patch b/pythonforandroid/recipes/pyopenal/patches/fix-find-lib.patch
new file mode 100644
index 000000000..e798bd12f
--- /dev/null
+++ b/pythonforandroid/recipes/pyopenal/patches/fix-find-lib.patch
@@ -0,0 +1,13 @@
+diff --git a/openal/library_loader.py b/openal/library_loader.py
+index be2485c..e8c6cd2 100644
+--- a/openal/library_loader.py
++++ b/openal/library_loader.py
+@@ -56,7 +56,7 @@ class ExternalLibrary:
+ except:
+ pass
+ else:
+- for path in [os.getcwd(), _here]:
++ for path in [os.path.join(os.environ['ANDROID_PRIVATE'], '..', 'lib')]:
+ for style in ExternalLibrary.other_styles:
+ candidate = os.path.join(path, style.format(name))
+ if os.path.exists(candidate) and os.path.isfile(candidate):
diff --git a/pythonforandroid/recipes/pyopenssl/__init__.py b/pythonforandroid/recipes/pyopenssl/__init__.py
index dde2e1edf..092a31059 100644
--- a/pythonforandroid/recipes/pyopenssl/__init__.py
+++ b/pythonforandroid/recipes/pyopenssl/__init__.py
@@ -1,11 +1,11 @@
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
class PyOpenSSLRecipe(PythonRecipe):
- version = '0.14'
+ version = '19.0.0'
url = 'https://pypi.python.org/packages/source/p/pyOpenSSL/pyOpenSSL-{version}.tar.gz'
- depends = ['openssl', 'python2', 'setuptools']
+ depends = ['openssl', 'setuptools']
site_packages_name = 'OpenSSL'
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/pyproj/__init__.py b/pythonforandroid/recipes/pyproj/__init__.py
index 3caec1dac..0c47b2951 100644
--- a/pythonforandroid/recipes/pyproj/__init__.py
+++ b/pythonforandroid/recipes/pyproj/__init__.py
@@ -2,10 +2,10 @@
class PyProjRecipe(CythonRecipe):
- version = '1.9.5.1'
- url = 'https://github.com/jswhit/pyproj/archive/master.zip'
- depends = ['python2', 'setuptools']
+ version = '1.9.6'
+ url = 'https://github.com/pyproj4/pyproj/archive/v{version}rel.zip'
+ depends = ['setuptools']
call_hostpython_via_targetpython = False
-
+
recipe = PyProjRecipe()
diff --git a/pythonforandroid/recipes/pyrxp/__init__.py b/pythonforandroid/recipes/pyrxp/__init__.py
index 2361f1e5f..09b1804a8 100644
--- a/pythonforandroid/recipes/pyrxp/__init__.py
+++ b/pythonforandroid/recipes/pyrxp/__init__.py
@@ -1,8 +1,11 @@
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe, warning
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
+
class PyRXPURecipe(CompiledComponentsPythonRecipe):
version = '2a02cecc87b9'
url = 'https://bitbucket.org/rptlab/pyrxp/get/{version}.tar.gz'
- depends = ['python2']
+ depends = []
patches = []
+
recipe = PyRXPURecipe()
diff --git a/pythonforandroid/recipes/pysdl2/__init__.py b/pythonforandroid/recipes/pysdl2/__init__.py
index cd22b639e..b1dc9cbd3 100644
--- a/pythonforandroid/recipes/pysdl2/__init__.py
+++ b/pythonforandroid/recipes/pysdl2/__init__.py
@@ -1,12 +1,9 @@
+from pythonforandroid.recipe import PythonRecipe
-from pythonforandroid.toolchain import PythonRecipe, shprint, current_directory, ArchARM
-from os.path import exists, join
-import sh
-import glob
class PySDL2Recipe(PythonRecipe):
- version = '0.9.3'
- url = 'https://bitbucket.org/marcusva/py-sdl2/downloads/PySDL2-{version}.tar.gz'
+ version = '0.9.6'
+ url = 'https://files.pythonhosted.org/packages/source/P/PySDL2/PySDL2-{version}.tar.gz'
depends = ['sdl2']
diff --git a/pythonforandroid/recipes/pysha3/__init__.py b/pythonforandroid/recipes/pysha3/__init__.py
index 3b98d5933..af389460f 100644
--- a/pythonforandroid/recipes/pysha3/__init__.py
+++ b/pythonforandroid/recipes/pysha3/__init__.py
@@ -1,11 +1,25 @@
+import os
from pythonforandroid.recipe import PythonRecipe
+# TODO: CompiledComponentsPythonRecipe
class Pysha3Recipe(PythonRecipe):
version = '1.0.2'
url = 'https://github.com/tiran/pysha3/archive/{version}.tar.gz'
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
- depends = ['python2', 'setuptools']
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+ # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS
+ env['CPPFLAGS'] = env['CFLAGS']
+ env['CFLAGS'] = ''
+ # LDFLAGS may only be used to specify linker flags, for libraries use LIBS
+ env['LDFLAGS'] = env['LDFLAGS'].replace('-lm', '')
+ env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))
+ env['LIBS'] = ' -lm'
+ env['LDSHARED'] += env['LIBS']
+ return env
recipe = Pysha3Recipe()
diff --git a/pythonforandroid/recipes/python2/Setup.local-ssl b/pythonforandroid/recipes/python2/Setup.local-ssl
deleted file mode 100644
index eadc6eae9..000000000
--- a/pythonforandroid/recipes/python2/Setup.local-ssl
+++ /dev/null
@@ -1,4 +0,0 @@
-SSL=
-_ssl _ssl.c \
- -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
- -L$(SSL) -lssl$(OPENSSL_VERSION) -lcrypto$(OPENSSL_VERSION)
diff --git a/pythonforandroid/recipes/python2/__init__.py b/pythonforandroid/recipes/python2/__init__.py
deleted file mode 100644
index ee4feb27e..000000000
--- a/pythonforandroid/recipes/python2/__init__.py
+++ /dev/null
@@ -1,181 +0,0 @@
-
-from pythonforandroid.recipe import TargetPythonRecipe, Recipe
-from pythonforandroid.toolchain import shprint, current_directory, info
-from pythonforandroid.patching import (is_linux, is_darwin, is_api_gt,
- check_all, is_api_lt, is_ndk)
-from os.path import exists, join, realpath
-import sh
-
-
-class Python2Recipe(TargetPythonRecipe):
- version = "2.7.2"
- url = 'https://python.org/ftp/python/{version}/Python-{version}.tar.bz2'
- name = 'python2'
-
- depends = ['hostpython2']
- conflicts = ['python3crystax', 'python3']
- opt_depends = ['openssl','sqlite3']
-
- patches = ['patches/Python-{version}-xcompile.patch',
- 'patches/Python-{version}-ctypes-disable-wchar.patch',
- 'patches/disable-modules.patch',
- 'patches/fix-locale.patch',
- 'patches/fix-gethostbyaddr.patch',
- 'patches/fix-setup-flags.patch',
- 'patches/fix-filesystemdefaultencoding.patch',
- 'patches/fix-termios.patch',
- 'patches/custom-loader.patch',
- 'patches/verbose-compilation.patch',
- 'patches/fix-remove-corefoundation.patch',
- 'patches/fix-dynamic-lookup.patch',
- 'patches/fix-dlfcn.patch',
- 'patches/parsetuple.patch',
- 'patches/ctypes-find-library-updated.patch',
- ('patches/fix-configure-darwin.patch', is_darwin),
- ('patches/fix-distutils-darwin.patch', is_darwin),
- ('patches/fix-ftime-removal.patch', is_api_gt(19)),
- ('patches/disable-openpty.patch', check_all(is_api_lt(21), is_ndk('crystax')))]
-
- from_crystax = False
-
- def build_arch(self, arch):
-
- if not exists(join(self.get_build_dir(arch.arch), 'libpython2.7.so')):
- self.do_python_build(arch)
-
- if not exists(self.ctx.get_python_install_dir()):
- shprint(sh.cp, '-a', join(self.get_build_dir(arch.arch), 'python-install'),
- self.ctx.get_python_install_dir())
-
- # This should be safe to run every time
- info('Copying hostpython binary to targetpython folder')
- shprint(sh.cp, self.ctx.hostpython,
- join(self.ctx.get_python_install_dir(), 'bin', 'python.host'))
- self.ctx.hostpython = join(self.ctx.get_python_install_dir(), 'bin', 'python.host')
-
- if not exists(join(self.ctx.get_libs_dir(arch.arch), 'libpython2.7.so')):
- shprint(sh.cp, join(self.get_build_dir(arch.arch), 'libpython2.7.so'), self.ctx.get_libs_dir(arch.arch))
-
-
- # # if exists(join(self.get_build_dir(arch.arch), 'libpython2.7.so')):
- # if exists(join(self.ctx.libs_dir, 'libpython2.7.so')):
- # info('libpython2.7.so already exists, skipping python build.')
- # if not exists(join(self.ctx.get_python_install_dir(), 'libpython2.7.so')):
- # info('Copying python-install to dist-dependent location')
- # shprint(sh.cp, '-a', 'python-install', self.ctx.get_python_install_dir())
- # self.ctx.hostpython = join(self.ctx.get_python_install_dir(), 'bin', 'python.host')
-
- # return
-
- def do_python_build(self, arch):
-
- hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)
- shprint(sh.cp, self.ctx.hostpython, self.get_build_dir(arch.arch))
- shprint(sh.cp, self.ctx.hostpgen, self.get_build_dir(arch.arch))
- hostpython = join(self.get_build_dir(arch.arch), 'hostpython')
- hostpgen = join(self.get_build_dir(arch.arch), 'hostpython')
-
- with current_directory(self.get_build_dir(arch.arch)):
-
-
- hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)
- shprint(sh.cp, join(hostpython_recipe.get_recipe_dir(), 'Setup'), 'Modules')
-
- env = arch.get_env()
-
- env['HOSTARCH'] = 'arm-eabi'
- env['BUILDARCH'] = shprint(sh.gcc, '-dumpmachine').stdout.decode('utf-8').split('\n')[0]
- env['CFLAGS'] = ' '.join([env['CFLAGS'], '-DNO_MALLINFO'])
-
- # TODO need to add a should_build that checks if optional
- # dependencies have changed (possibly in a generic way)
- if 'openssl' in self.ctx.recipe_build_order:
- r = Recipe.get_recipe('openssl', self.ctx)
- openssl_build_dir = r.get_build_dir(arch.arch)
- setuplocal = join('Modules', 'Setup.local')
- shprint(sh.cp, join(self.get_recipe_dir(), 'Setup.local-ssl'), setuplocal)
- shprint(sh.sed, '-i.backup', 's#^SSL=.*#SSL={}#'.format(openssl_build_dir), setuplocal)
- env['OPENSSL_VERSION'] = r.version
-
- if 'sqlite3' in self.ctx.recipe_build_order:
- # Include sqlite3 in python2 build
- r = Recipe.get_recipe('sqlite3', self.ctx)
- i = ' -I' + r.get_build_dir(arch.arch)
- l = ' -L' + r.get_lib_dir(arch) + ' -lsqlite3'
- # Insert or append to env
- f = 'CPPFLAGS'
- env[f] = env[f] + i if f in env else i
- f = 'LDFLAGS'
- env[f] = env[f] + l if f in env else l
-
- # NDK has langinfo.h but doesn't define nl_langinfo()
- env['ac_cv_header_langinfo_h'] = 'no'
- configure = sh.Command('./configure')
- shprint(configure,
- '--host={}'.format(env['HOSTARCH']),
- '--build={}'.format(env['BUILDARCH']),
- # 'OPT={}'.format(env['OFLAG']),
- '--prefix={}'.format(realpath('./python-install')),
- '--enable-shared',
- '--disable-toolbox-glue',
- '--disable-framework',
- _env=env)
-
- # tito left this comment in the original source. It's still true!
- # FIXME, the first time, we got a error at:
- # python$EXE ../../Tools/scripts/h2py.py -i '(u_long)' /usr/include/netinet/in.h
- # /home/tito/code/python-for-android/build/python/Python-2.7.2/python: 1: Syntax error: word unexpected (expecting ")")
- # because at this time, python is arm, not x86. even that, why /usr/include/netinet/in.h is used ?
- # check if we can avoid this part.
-
- make = sh.Command(env['MAKE'].split(' ')[0])
- print('First install (expected to fail...')
- try:
- shprint(make, '-j5', 'install', 'HOSTPYTHON={}'.format(hostpython),
- 'HOSTPGEN={}'.format(hostpgen),
- 'CROSS_COMPILE_TARGET=yes',
- 'INSTSONAME=libpython2.7.so',
- _env=env)
- except sh.ErrorReturnCode_2:
- print('First python2 make failed. This is expected, trying again.')
-
-
- print('Second install (expected to work)')
- shprint(sh.touch, 'python.exe', 'python')
- shprint(make, '-j5', 'install', 'HOSTPYTHON={}'.format(hostpython),
- 'HOSTPGEN={}'.format(hostpgen),
- 'CROSS_COMPILE_TARGET=yes',
- 'INSTSONAME=libpython2.7.so',
- _env=env)
-
- if is_darwin():
- shprint(sh.cp, join(self.get_recipe_dir(), 'patches', '_scproxy.py'),
- join('python-install', 'Lib'))
- shprint(sh.cp, join(self.get_recipe_dir(), 'patches', '_scproxy.py'),
- join('python-install', 'lib', 'python2.7'))
-
- # reduce python
- for dir_name in ('test', join('json', 'tests'), 'lib-tk',
- join('sqlite3', 'test'), join('unittest, test'),
- join('lib2to3', 'tests'), join('bsddb', 'tests'),
- join('distutils', 'tests'), join('email', 'test'),
- 'curses'):
- shprint(sh.rm, '-rf', join('python-install',
- 'lib', 'python2.7', dir_name))
-
-
- # info('Copying python-install to dist-dependent location')
- # shprint(sh.cp, '-a', 'python-install', self.ctx.get_python_install_dir())
-
- # print('Copying hostpython binary to targetpython folder')
- # shprint(sh.cp, self.ctx.hostpython,
- # join(self.ctx.get_python_install_dir(), 'bin', 'python.host'))
- # self.ctx.hostpython = join(self.ctx.get_python_install_dir(), 'bin', 'python.host')
-
-
-
- # print('python2 build done, exiting for debug')
- # exit(1)
-
-
-recipe = Python2Recipe()
diff --git a/pythonforandroid/recipes/python2/patches/Python-2.7.2-ctypes-disable-wchar.patch b/pythonforandroid/recipes/python2/patches/Python-2.7.2-ctypes-disable-wchar.patch
deleted file mode 100644
index 19d8915ea..000000000
--- a/pythonforandroid/recipes/python2/patches/Python-2.7.2-ctypes-disable-wchar.patch
+++ /dev/null
@@ -1,116 +0,0 @@
-diff -ur '--exclude=*~' Python-2.7.2.orig/Lib/ctypes/__init__.py Python-2.7.2/Lib/ctypes/__init__.py
---- Python-2.7.2.orig/Lib/ctypes/__init__.py 2011-06-11 16:46:24.000000000 +0100
-+++ Python-2.7.2/Lib/ctypes/__init__.py 2015-03-19 12:32:45.747723687 +0000
-@@ -272,31 +272,34 @@
- else:
- set_conversion_mode("ascii", "strict")
-
-- class c_wchar_p(_SimpleCData):
-- _type_ = "Z"
--
-- class c_wchar(_SimpleCData):
-- _type_ = "u"
--
-- POINTER(c_wchar).from_param = c_wchar_p.from_param #_SimpleCData.c_wchar_p_from_param
--
-- def create_unicode_buffer(init, size=None):
-- """create_unicode_buffer(aString) -> character array
-- create_unicode_buffer(anInteger) -> character array
-- create_unicode_buffer(aString, anInteger) -> character array
-- """
-- if isinstance(init, (str, unicode)):
-- if size is None:
-- size = len(init)+1
-- buftype = c_wchar * size
-- buf = buftype()
-- buf.value = init
-- return buf
-- elif isinstance(init, (int, long)):
-- buftype = c_wchar * init
-- buf = buftype()
-- return buf
-- raise TypeError(init)
-+# The wchar stuff causes a crash on Android (the bionic C library doesn't
-+# implement wchar_t anyway)
-+#
-+# class c_wchar_p(_SimpleCData):
-+# _type_ = "Z"
-+#
-+# class c_wchar(_SimpleCData):
-+# _type_ = "u"
-+#
-+# POINTER(c_wchar).from_param = c_wchar_p.from_param #_SimpleCData.c_wchar_p_from_param
-+#
-+# def create_unicode_buffer(init, size=None):
-+# """create_unicode_buffer(aString) -> character array
-+# create_unicode_buffer(anInteger) -> character array
-+# create_unicode_buffer(aString, anInteger) -> character array
-+# """
-+# if isinstance(init, (str, unicode)):
-+# if size is None:
-+# size = len(init)+1
-+# buftype = c_wchar * size
-+# buf = buftype()
-+# buf.value = init
-+# return buf
-+# elif isinstance(init, (int, long)):
-+# buftype = c_wchar * init
-+# buf = buftype()
-+# return buf
-+# raise TypeError(init)
-
- POINTER(c_char).from_param = c_char_p.from_param #_SimpleCData.c_char_p_from_param
-
-diff -ur '--exclude=*~' Python-2.7.2.orig/Modules/_ctypes/callproc.c Python-2.7.2/Modules/_ctypes/callproc.c
---- Python-2.7.2.orig/Modules/_ctypes/callproc.c 2015-03-19 11:56:40.668159317 +0000
-+++ Python-2.7.2/Modules/_ctypes/callproc.c 2015-03-19 11:45:45.898288000 +0000
-@@ -1423,7 +1423,7 @@
- mode |= RTLD_NOW;
- handle = ctypes_dlopen(name, mode);
- if (!handle) {
-- char *errmsg = ctypes_dlerror();
-+ const char *errmsg = ctypes_dlerror();
- if (!errmsg)
- errmsg = "dlopen() error";
- PyErr_SetString(PyExc_OSError,
-diff -ur '--exclude=*~' Python-2.7.2.orig/Modules/_ctypes/libffi/src/dlmalloc.c Python-2.7.2/Modules/_ctypes/libffi/src/dlmalloc.c
---- Python-2.7.2.orig/Modules/_ctypes/libffi/src/dlmalloc.c 2015-03-19 13:26:58.928438829 +0000
-+++ Python-2.7.2/Modules/_ctypes/libffi/src/dlmalloc.c 2015-03-19 15:32:19.042396376 +0000
-@@ -614,18 +614,6 @@
- #include "/usr/include/malloc.h"
- #else /* HAVE_USR_INCLUDE_MALLOC_H */
-
--struct mallinfo {
-- MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */
-- MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */
-- MALLINFO_FIELD_TYPE smblks; /* always 0 */
-- MALLINFO_FIELD_TYPE hblks; /* always 0 */
-- MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */
-- MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */
-- MALLINFO_FIELD_TYPE fsmblks; /* always 0 */
-- MALLINFO_FIELD_TYPE uordblks; /* total allocated space */
-- MALLINFO_FIELD_TYPE fordblks; /* total free space */
-- MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */
--};
-
- #endif /* HAVE_USR_INCLUDE_MALLOC_H */
- #endif /* NO_MALLINFO */
-@@ -966,7 +954,7 @@
- p = malloc(n);
- assert(malloc_usable_size(p) >= 256);
- */
--size_t dlmalloc_usable_size(void*);
-+size_t dlmalloc_usable_size(const void*);
-
- /*
- malloc_stats();
-@@ -4384,7 +4372,7 @@
- internal_malloc_stats(gm);
- }
-
--size_t dlmalloc_usable_size(void* mem) {
-+size_t dlmalloc_usable_size(const void* mem) {
- if (mem != 0) {
- mchunkptr p = mem2chunk(mem);
- if (cinuse(p))
-
diff --git a/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch b/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch
deleted file mode 100644
index 9883cb544..000000000
--- a/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch
+++ /dev/null
@@ -1,205 +0,0 @@
-diff -urN Python-2.7.2/configure ltib/rpm/BUILD/Python-2.7.2/configure
---- Python-2.7.2/configure 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/configure 2011-11-14 12:10:41.011373524 -0500
-@@ -13673,7 +13673,7 @@
- $as_echo_n "(cached) " >&6
- else
- if test "$cross_compiling" = yes; then :
-- ac_cv_have_long_long_format=no
-+ ac_cv_have_long_long_format="cross -- assuming yes"
- else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
- /* end confdefs.h. */
-@@ -13725,7 +13725,7 @@
- $as_echo "$ac_cv_have_long_long_format" >&6; }
- fi
-
--if test "$ac_cv_have_long_long_format" = yes
-+if test "$ac_cv_have_long_long_format" != no
- then
-
- $as_echo "#define PY_FORMAT_LONG_LONG \"ll\"" >>confdefs.h
-diff -urN Python-2.7.2/Makefile.pre.in ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in
---- Python-2.7.2/Makefile.pre.in 2011-06-11 11:46:26.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in 2011-11-14 12:10:41.013373444 -0500
-@@ -182,6 +182,7 @@
-
- PYTHON= python$(EXE)
- BUILDPYTHON= python$(BUILDEXE)
-+HOSTPYTHON= ./$(BUILDPYTHON)
-
- # The task to run while instrument when building the profile-opt target
- PROFILE_TASK= $(srcdir)/Tools/pybench/pybench.py -n 2 --with-gc --with-syscheck
-@@ -215,6 +216,8 @@
- # Parser
- PGEN= Parser/pgen$(EXE)
-
-+HOSTPGEN= $(PGEN)
-+
- POBJS= \
- Parser/acceler.o \
- Parser/grammar1.o \
-@@ -407,8 +410,8 @@
- # Build the shared modules
- sharedmods: $(BUILDPYTHON)
- @case $$MAKEFLAGS in \
-- *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py -q build;; \
-- *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py build;; \
-+ *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \
-+ *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \
- esac
-
- # Build static library
-@@ -542,7 +545,7 @@
- $(GRAMMAR_H) $(GRAMMAR_C): Parser/pgen.stamp
- Parser/pgen.stamp: $(PGEN) $(GRAMMAR_INPUT)
- -@$(INSTALL) -d Include
-- $(PGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
-+ -$(HOSTPGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
- -touch Parser/pgen.stamp
-
- $(PGEN): $(PGENOBJS)
-@@ -925,26 +928,26 @@
- done; \
- done
- $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-+ $(HOSTPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-
- # Create the PLATDIR source directory, if one wasn't distributed..
- $(srcdir)/Lib/$(PLATDIR):
-@@ -1049,7 +1052,9 @@
- # Install the dynamically loadable modules
- # This goes into $(exec_prefix)
- sharedinstall: sharedmods
-- $(RUNSHARED) ./$(BUILDPYTHON) -E $(srcdir)/setup.py install \
-+ CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' \
-+ $(RUNSHARED) $(HOSTPYTHON) -E $(srcdir)/setup.py install \
-+ --skip-build \
- --prefix=$(prefix) \
- --install-scripts=$(BINDIR) \
- --install-platlib=$(DESTSHARED) \
-diff -urN Python-2.7.2/setup.py ltib/rpm/BUILD/Python-2.7.2/setup.py
---- Python-2.7.2/setup.py 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/setup.py 2011-11-14 12:13:02.175758583 -0500
-@@ -145,6 +145,7 @@
- def __init__(self, dist):
- build_ext.__init__(self, dist)
- self.failed = []
-+ self.cross_compile = os.environ.get('CROSS_COMPILE_TARGET') == 'yes'
-
- def build_extensions(self):
-
-@@ -278,6 +279,14 @@
- (ext.name, sys.exc_info()[1]))
- self.failed.append(ext.name)
- return
-+
-+ # Import check will not work when cross-compiling.
-+ if os.environ.has_key('PYTHONXCPREFIX'):
-+ self.announce(
-+ 'WARNING: skipping import check for cross-compiled: "%s"' %
-+ ext.name)
-+ return
-+
- # Workaround for Mac OS X: The Carbon-based modules cannot be
- # reliably imported into a command-line Python
- if 'Carbon' in ext.extra_link_args:
-@@ -369,9 +378,10 @@
-
- def detect_modules(self):
- # Ensure that /usr/local is always used
-- add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-- add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-- self.add_multiarch_paths()
-+ if not self.cross_compile:
-+ add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-+ add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-+ self.add_multiarch_paths()
-
- # Add paths specified in the environment variables LDFLAGS and
- # CPPFLAGS for header and library files.
-@@ -408,7 +418,8 @@
- add_dir_to_list(dir_list, directory)
-
- if os.path.normpath(sys.prefix) != '/usr' \
-- and not sysconfig.get_config_var('PYTHONFRAMEWORK'):
-+ and not sysconfig.get_config_var('PYTHONFRAMEWORK') \
-+ and not self.cross_compile:
- # OSX note: Don't add LIBDIR and INCLUDEDIR to building a framework
- # (PYTHONFRAMEWORK is set) to avoid # linking problems when
- # building a framework with different architectures than
-@@ -426,11 +437,14 @@
- # lib_dirs and inc_dirs are used to search for files;
- # if a file is found in one of those directories, it can
- # be assumed that no additional -I,-L directives are needed.
-- lib_dirs = self.compiler.library_dirs + [
-- '/lib64', '/usr/lib64',
-- '/lib', '/usr/lib',
-- ]
-- inc_dirs = self.compiler.include_dirs + ['/usr/include']
-+ lib_dirs = self.compiler.library_dirs
-+ inc_dirs = self.compiler.include_dirs
-+ if not self.cross_compile:
-+ lib_dirs += [
-+ '/lib64', '/usr/lib64',
-+ '/lib', '/usr/lib',
-+ ]
-+ inc_dirs += ['/usr/include']
- exts = []
- missing = []
-
-@@ -1864,8 +1878,15 @@
-
- # Pass empty CFLAGS because we'll just append the resulting
- # CFLAGS to Python's; -g or -O2 is to be avoided.
-- cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-- % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-+ if self.cross_compile:
-+ cmd = "cd %s && env CFLAGS='' %s/configure --host=%s --build=%s %s" \
-+ % (ffi_builddir, ffi_srcdir,
-+ os.environ.get('HOSTARCH'),
-+ os.environ.get('BUILDARCH'),
-+ " ".join(config_args))
-+ else:
-+ cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-+ % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-
- res = os.system(cmd)
- if res or not os.path.exists(ffi_configfile):
---- Python-2.6.6.orig//Lib/plat-linux3/regen 1970-01-01 01:00:00.000000000 +0100
-+++ Python-2.6.6/Lib/plat-linux3/regen 2001-08-09 14:48:17.000000000 +0200
-@@ -0,0 +1,8 @@
-+#! /bin/sh
-+case `uname` in
-+Linux*) ;;
-+*) echo Probably not on a Linux system 1>&2
-+ exit 1;;
-+esac
-+set -v
-+h2py -i '(u_long)' /usr/include/sys/types.h /usr/include/netinet/in.h /usr/include/dlfcn.h
diff --git a/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch-backup b/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch-backup
deleted file mode 100644
index 3fdb85d2d..000000000
--- a/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch-backup
+++ /dev/null
@@ -1,194 +0,0 @@
-diff -urN Python-2.7.2/configure ltib/rpm/BUILD/Python-2.7.2/configure
---- Python-2.7.2/configure 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/configure 2011-11-14 12:10:41.011373524 -0500
-@@ -13673,7 +13673,7 @@
- $as_echo_n "(cached) " >&6
- else
- if test "$cross_compiling" = yes; then :
-- ac_cv_have_long_long_format=no
-+ ac_cv_have_long_long_format="cross -- assuming yes"
- else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
- /* end confdefs.h. */
-@@ -13725,7 +13725,7 @@
- $as_echo "$ac_cv_have_long_long_format" >&6; }
- fi
-
--if test "$ac_cv_have_long_long_format" = yes
-+if test "$ac_cv_have_long_long_format" != no
- then
-
- $as_echo "#define PY_FORMAT_LONG_LONG \"ll\"" >>confdefs.h
-diff -urN Python-2.7.2/Makefile.pre.in ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in
---- Python-2.7.2/Makefile.pre.in 2011-06-11 11:46:26.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in 2011-11-14 12:10:41.013373444 -0500
-@@ -182,6 +182,7 @@
-
- PYTHON= python$(EXE)
- BUILDPYTHON= python$(BUILDEXE)
-+HOSTPYTHON= ./$(BUILDPYTHON)
-
- # The task to run while instrument when building the profile-opt target
- PROFILE_TASK= $(srcdir)/Tools/pybench/pybench.py -n 2 --with-gc --with-syscheck
-@@ -215,6 +216,8 @@
- # Parser
- PGEN= Parser/pgen$(EXE)
-
-+HOSTPGEN= $(PGEN)
-+
- POBJS= \
- Parser/acceler.o \
- Parser/grammar1.o \
-@@ -407,8 +410,8 @@
- # Build the shared modules
- sharedmods: $(BUILDPYTHON)
- @case $$MAKEFLAGS in \
-- *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py -q build;; \
-- *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py build;; \
-+ *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \
-+ *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \
- esac
-
- # Build static library
-@@ -542,7 +545,7 @@
- $(GRAMMAR_H) $(GRAMMAR_C): Parser/pgen.stamp
- Parser/pgen.stamp: $(PGEN) $(GRAMMAR_INPUT)
- -@$(INSTALL) -d Include
-- $(PGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
-+ -$(HOSTPGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
- -touch Parser/pgen.stamp
-
- $(PGEN): $(PGENOBJS)
-@@ -925,26 +928,26 @@
- done; \
- done
- $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-+ $(HOSTPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-
- # Create the PLATDIR source directory, if one wasn't distributed..
- $(srcdir)/Lib/$(PLATDIR):
-@@ -1049,7 +1052,9 @@
- # Install the dynamically loadable modules
- # This goes into $(exec_prefix)
- sharedinstall: sharedmods
-- $(RUNSHARED) ./$(BUILDPYTHON) -E $(srcdir)/setup.py install \
-+ CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' \
-+ $(RUNSHARED) $(HOSTPYTHON) -E $(srcdir)/setup.py install \
-+ --skip-build \
- --prefix=$(prefix) \
- --install-scripts=$(BINDIR) \
- --install-platlib=$(DESTSHARED) \
-diff -urN Python-2.7.2/setup.py ltib/rpm/BUILD/Python-2.7.2/setup.py
---- Python-2.7.2/setup.py 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/setup.py 2011-11-14 12:13:02.175758583 -0500
-@@ -145,6 +145,7 @@
- def __init__(self, dist):
- build_ext.__init__(self, dist)
- self.failed = []
-+ self.cross_compile = os.environ.get('CROSS_COMPILE_TARGET') == 'yes'
-
- def build_extensions(self):
-
-@@ -278,6 +279,14 @@
- (ext.name, sys.exc_info()[1]))
- self.failed.append(ext.name)
- return
-+
-+ # Import check will not work when cross-compiling.
-+ if os.environ.has_key('PYTHONXCPREFIX'):
-+ self.announce(
-+ 'WARNING: skipping import check for cross-compiled: "%s"' %
-+ ext.name)
-+ return
-+
- # Workaround for Mac OS X: The Carbon-based modules cannot be
- # reliably imported into a command-line Python
- if 'Carbon' in ext.extra_link_args:
-@@ -369,9 +378,10 @@
-
- def detect_modules(self):
- # Ensure that /usr/local is always used
-- add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-- add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-- self.add_multiarch_paths()
-+ if not self.cross_compile:
-+ add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-+ add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-+ self.add_multiarch_paths()
-
- # Add paths specified in the environment variables LDFLAGS and
- # CPPFLAGS for header and library files.
-@@ -408,7 +418,8 @@
- add_dir_to_list(dir_list, directory)
-
- if os.path.normpath(sys.prefix) != '/usr' \
-- and not sysconfig.get_config_var('PYTHONFRAMEWORK'):
-+ and not sysconfig.get_config_var('PYTHONFRAMEWORK') \
-+ and not self.cross_compile:
- # OSX note: Don't add LIBDIR and INCLUDEDIR to building a framework
- # (PYTHONFRAMEWORK is set) to avoid # linking problems when
- # building a framework with different architectures than
-@@ -426,11 +437,14 @@
- # lib_dirs and inc_dirs are used to search for files;
- # if a file is found in one of those directories, it can
- # be assumed that no additional -I,-L directives are needed.
-- lib_dirs = self.compiler.library_dirs + [
-- '/lib64', '/usr/lib64',
-- '/lib', '/usr/lib',
-- ]
-- inc_dirs = self.compiler.include_dirs + ['/usr/include']
-+ lib_dirs = self.compiler.library_dirs
-+ inc_dirs = self.compiler.include_dirs
-+ if not self.cross_compile:
-+ lib_dirs += [
-+ '/lib64', '/usr/lib64',
-+ '/lib', '/usr/lib',
-+ ]
-+ inc_dirs += ['/usr/include']
- exts = []
- missing = []
-
-@@ -1864,8 +1878,15 @@
-
- # Pass empty CFLAGS because we'll just append the resulting
- # CFLAGS to Python's; -g or -O2 is to be avoided.
-- cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-- % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-+ if self.cross_compile:
-+ cmd = "cd %s && env CFLAGS='' %s/configure --host=%s --build=%s %s" \
-+ % (ffi_builddir, ffi_srcdir,
-+ os.environ.get('HOSTARCH'),
-+ os.environ.get('BUILDARCH'),
-+ " ".join(config_args))
-+ else:
-+ cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-+ % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-
- res = os.system(cmd)
- if res or not os.path.exists(ffi_configfile):
diff --git a/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch-new b/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch-new
deleted file mode 100644
index 9883cb544..000000000
--- a/pythonforandroid/recipes/python2/patches/Python-2.7.2-xcompile.patch-new
+++ /dev/null
@@ -1,205 +0,0 @@
-diff -urN Python-2.7.2/configure ltib/rpm/BUILD/Python-2.7.2/configure
---- Python-2.7.2/configure 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/configure 2011-11-14 12:10:41.011373524 -0500
-@@ -13673,7 +13673,7 @@
- $as_echo_n "(cached) " >&6
- else
- if test "$cross_compiling" = yes; then :
-- ac_cv_have_long_long_format=no
-+ ac_cv_have_long_long_format="cross -- assuming yes"
- else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
- /* end confdefs.h. */
-@@ -13725,7 +13725,7 @@
- $as_echo "$ac_cv_have_long_long_format" >&6; }
- fi
-
--if test "$ac_cv_have_long_long_format" = yes
-+if test "$ac_cv_have_long_long_format" != no
- then
-
- $as_echo "#define PY_FORMAT_LONG_LONG \"ll\"" >>confdefs.h
-diff -urN Python-2.7.2/Makefile.pre.in ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in
---- Python-2.7.2/Makefile.pre.in 2011-06-11 11:46:26.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/Makefile.pre.in 2011-11-14 12:10:41.013373444 -0500
-@@ -182,6 +182,7 @@
-
- PYTHON= python$(EXE)
- BUILDPYTHON= python$(BUILDEXE)
-+HOSTPYTHON= ./$(BUILDPYTHON)
-
- # The task to run while instrument when building the profile-opt target
- PROFILE_TASK= $(srcdir)/Tools/pybench/pybench.py -n 2 --with-gc --with-syscheck
-@@ -215,6 +216,8 @@
- # Parser
- PGEN= Parser/pgen$(EXE)
-
-+HOSTPGEN= $(PGEN)
-+
- POBJS= \
- Parser/acceler.o \
- Parser/grammar1.o \
-@@ -407,8 +410,8 @@
- # Build the shared modules
- sharedmods: $(BUILDPYTHON)
- @case $$MAKEFLAGS in \
-- *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py -q build;; \
-- *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' ./$(BUILDPYTHON) -E $(srcdir)/setup.py build;; \
-+ *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \
-+ *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \
- esac
-
- # Build static library
-@@ -542,7 +545,7 @@
- $(GRAMMAR_H) $(GRAMMAR_C): Parser/pgen.stamp
- Parser/pgen.stamp: $(PGEN) $(GRAMMAR_INPUT)
- -@$(INSTALL) -d Include
-- $(PGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
-+ -$(HOSTPGEN) $(GRAMMAR_INPUT) $(GRAMMAR_H) $(GRAMMAR_C)
- -touch Parser/pgen.stamp
-
- $(PGEN): $(PGENOBJS)
-@@ -925,26 +928,26 @@
- done; \
- done
- $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
-- PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-+ $(HOSTPYTHON) -Wi -tt -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST) -f \
- -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
- $(DESTDIR)$(LIBDEST)
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
-+ $(HOSTPYTHON) -Wi -t -O $(DESTDIR)$(LIBDEST)/compileall.py \
- -d $(LIBDEST)/site-packages -f \
- -x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
- -PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
-- ./$(BUILDPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-+ $(HOSTPYTHON) -Wi -t -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"
-
- # Create the PLATDIR source directory, if one wasn't distributed..
- $(srcdir)/Lib/$(PLATDIR):
-@@ -1049,7 +1052,9 @@
- # Install the dynamically loadable modules
- # This goes into $(exec_prefix)
- sharedinstall: sharedmods
-- $(RUNSHARED) ./$(BUILDPYTHON) -E $(srcdir)/setup.py install \
-+ CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' \
-+ $(RUNSHARED) $(HOSTPYTHON) -E $(srcdir)/setup.py install \
-+ --skip-build \
- --prefix=$(prefix) \
- --install-scripts=$(BINDIR) \
- --install-platlib=$(DESTSHARED) \
-diff -urN Python-2.7.2/setup.py ltib/rpm/BUILD/Python-2.7.2/setup.py
---- Python-2.7.2/setup.py 2011-06-11 11:46:28.000000000 -0400
-+++ ltib/rpm/BUILD/Python-2.7.2/setup.py 2011-11-14 12:13:02.175758583 -0500
-@@ -145,6 +145,7 @@
- def __init__(self, dist):
- build_ext.__init__(self, dist)
- self.failed = []
-+ self.cross_compile = os.environ.get('CROSS_COMPILE_TARGET') == 'yes'
-
- def build_extensions(self):
-
-@@ -278,6 +279,14 @@
- (ext.name, sys.exc_info()[1]))
- self.failed.append(ext.name)
- return
-+
-+ # Import check will not work when cross-compiling.
-+ if os.environ.has_key('PYTHONXCPREFIX'):
-+ self.announce(
-+ 'WARNING: skipping import check for cross-compiled: "%s"' %
-+ ext.name)
-+ return
-+
- # Workaround for Mac OS X: The Carbon-based modules cannot be
- # reliably imported into a command-line Python
- if 'Carbon' in ext.extra_link_args:
-@@ -369,9 +378,10 @@
-
- def detect_modules(self):
- # Ensure that /usr/local is always used
-- add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-- add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-- self.add_multiarch_paths()
-+ if not self.cross_compile:
-+ add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
-+ add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
-+ self.add_multiarch_paths()
-
- # Add paths specified in the environment variables LDFLAGS and
- # CPPFLAGS for header and library files.
-@@ -408,7 +418,8 @@
- add_dir_to_list(dir_list, directory)
-
- if os.path.normpath(sys.prefix) != '/usr' \
-- and not sysconfig.get_config_var('PYTHONFRAMEWORK'):
-+ and not sysconfig.get_config_var('PYTHONFRAMEWORK') \
-+ and not self.cross_compile:
- # OSX note: Don't add LIBDIR and INCLUDEDIR to building a framework
- # (PYTHONFRAMEWORK is set) to avoid # linking problems when
- # building a framework with different architectures than
-@@ -426,11 +437,14 @@
- # lib_dirs and inc_dirs are used to search for files;
- # if a file is found in one of those directories, it can
- # be assumed that no additional -I,-L directives are needed.
-- lib_dirs = self.compiler.library_dirs + [
-- '/lib64', '/usr/lib64',
-- '/lib', '/usr/lib',
-- ]
-- inc_dirs = self.compiler.include_dirs + ['/usr/include']
-+ lib_dirs = self.compiler.library_dirs
-+ inc_dirs = self.compiler.include_dirs
-+ if not self.cross_compile:
-+ lib_dirs += [
-+ '/lib64', '/usr/lib64',
-+ '/lib', '/usr/lib',
-+ ]
-+ inc_dirs += ['/usr/include']
- exts = []
- missing = []
-
-@@ -1864,8 +1878,15 @@
-
- # Pass empty CFLAGS because we'll just append the resulting
- # CFLAGS to Python's; -g or -O2 is to be avoided.
-- cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-- % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-+ if self.cross_compile:
-+ cmd = "cd %s && env CFLAGS='' %s/configure --host=%s --build=%s %s" \
-+ % (ffi_builddir, ffi_srcdir,
-+ os.environ.get('HOSTARCH'),
-+ os.environ.get('BUILDARCH'),
-+ " ".join(config_args))
-+ else:
-+ cmd = "cd %s && env CFLAGS='' '%s/configure' %s" \
-+ % (ffi_builddir, ffi_srcdir, " ".join(config_args))
-
- res = os.system(cmd)
- if res or not os.path.exists(ffi_configfile):
---- Python-2.6.6.orig//Lib/plat-linux3/regen 1970-01-01 01:00:00.000000000 +0100
-+++ Python-2.6.6/Lib/plat-linux3/regen 2001-08-09 14:48:17.000000000 +0200
-@@ -0,0 +1,8 @@
-+#! /bin/sh
-+case `uname` in
-+Linux*) ;;
-+*) echo Probably not on a Linux system 1>&2
-+ exit 1;;
-+esac
-+set -v
-+h2py -i '(u_long)' /usr/include/sys/types.h /usr/include/netinet/in.h /usr/include/dlfcn.h
diff --git a/pythonforandroid/recipes/python2/patches/_scproxy.py b/pythonforandroid/recipes/python2/patches/_scproxy.py
deleted file mode 100644
index 24239409e..000000000
--- a/pythonforandroid/recipes/python2/patches/_scproxy.py
+++ /dev/null
@@ -1,10 +0,0 @@
-'''
-Stub functions for _scproxy on iOS
-No proxy is supported yet.
-'''
-
-def _get_proxy_settings():
- return {'exclude_simple': 1}
-
-def _get_proxies():
- return {}
diff --git a/pythonforandroid/recipes/python2/patches/ctypes-find-library-updated.patch b/pythonforandroid/recipes/python2/patches/ctypes-find-library-updated.patch
deleted file mode 100644
index b12d7a8a4..000000000
--- a/pythonforandroid/recipes/python2/patches/ctypes-find-library-updated.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
-index 52b3520..01b13a9 100644
---- a/Lib/ctypes/util.py
-+++ b/Lib/ctypes/util.py
-@@ -71,7 +71,23 @@ if os.name == "ce":
- def find_library(name):
- return name
-
--if os.name == "posix" and sys.platform == "darwin":
-+# This patch overrides the find_library to look in the right places on
-+# Android
-+if True:
-+ def find_library(name):
-+ # Check the user app lib dir
-+ app_root = os.path.abspath('./').split(os.path.sep)[0:4]
-+ lib_search = os.path.sep.join(app_root) + os.path.sep + 'lib'
-+ for filename in os.listdir(lib_search):
-+ if filename.endswith('.so') and name in filename:
-+ return lib_search + os.path.sep + filename
-+ # Check the normal Android system libraries
-+ for filename in os.listdir('/system/lib'):
-+ if filename.endswith('.so') and name in filename:
-+ return lib_search + os.path.sep + filename
-+ return None
-+
-+elif os.name == "posix" and sys.platform == "darwin":
- from ctypes.macholib.dyld import dyld_find as _dyld_find
- def find_library(name):
- possible = ['lib%s.dylib' % name,
diff --git a/pythonforandroid/recipes/python2/patches/ctypes-find-library.patch b/pythonforandroid/recipes/python2/patches/ctypes-find-library.patch
deleted file mode 100644
index 68fa93f52..000000000
--- a/pythonforandroid/recipes/python2/patches/ctypes-find-library.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-diff -ur Python-2.7.2.orig/Lib/ctypes/util.py Python-2.7.2/Lib/ctypes/util.py
---- Python-2.7.2.orig/Lib/ctypes/util.py 2011-06-11 16:46:24.000000000 +0100
-+++ Python-2.7.2/Lib/ctypes/util.py 2015-05-10 15:50:18.906203529 +0100
-@@ -71,7 +71,21 @@
- def find_library(name):
- return name
-
--if os.name == "posix" and sys.platform == "darwin":
-+# this test is for android specifically shoudl match here and ignore any
-+# of the other platform tests below
-+if os.name == "posix":
-+ def find_library(name):
-+ """ hack to find librarys for kivy and android
-+ split the path and get the first parts which will give us
-+ the app path something like /data/data/org.app.foo/"""
-+ app_root = os.path.abspath('./').split(os.path.sep)[0:4]
-+ lib_search = os.path.sep.join(app_root) + os.path.sep + 'lib'
-+ for filename in os.listdir(lib_search):
-+ if filename.endswith('.so') and name in filename:
-+ return lib_search + os.path.sep + filename
-+ return None
-+
-+elif os.name == "posix" and sys.platform == "darwin":
- from ctypes.macholib.dyld import dyld_find as _dyld_find
- def find_library(name):
- possible = ['lib%s.dylib' % name,
-Only in Python-2.7.2/Lib/ctypes: util.py.save
-Only in Python-2.7.2/Lib/ctypes: util.py.save.1
diff --git a/pythonforandroid/recipes/python2/patches/custom-loader.patch b/pythonforandroid/recipes/python2/patches/custom-loader.patch
deleted file mode 100644
index f4408a0a0..000000000
--- a/pythonforandroid/recipes/python2/patches/custom-loader.patch
+++ /dev/null
@@ -1,67 +0,0 @@
---- Python-2.7.2.orig/Python/dynload_shlib.c 2010-05-09 16:46:46.000000000 +0200
-+++ Python-2.7.2/Python/dynload_shlib.c 2011-04-20 17:52:12.000000000 +0200
-@@ -6,6 +6,7 @@
-
- #include <sys/types.h>
- #include <sys/stat.h>
-+#include <stdlib.h>
-
- #if defined(__NetBSD__)
- #include <sys/param.h>
-@@ -75,6 +76,21 @@
- char pathbuf[260];
- int dlopenflags=0;
-
-+ static void *libpymodules = -1;
-+ void *rv = NULL;
-+
-+ /* Ensure we have access to libpymodules. */
-+ if (libpymodules == -1) {
-+ printf("ANDROID_UNPACK = %s\n", getenv("ANDROID_UNPACK"));
-+ PyOS_snprintf(pathbuf, sizeof(pathbuf), "%s/libpymodules.so", getenv("ANDROID_UNPACK"));
-+ libpymodules = dlopen(pathbuf, RTLD_NOW);
-+
-+ if (libpymodules == NULL) {
-+ //abort();
-+ }
-+ }
-+
-+
- if (strchr(pathname, '/') == NULL) {
- /* Prefix bare filename with "./" */
- PyOS_snprintf(pathbuf, sizeof(pathbuf), "./%-.255s", pathname);
-@@ -84,6 +100,17 @@
- PyOS_snprintf(funcname, sizeof(funcname),
- LEAD_UNDERSCORE "init%.200s", shortname);
-
-+
-+ /* Read symbols that have been linked into the main binary. */
-+
-+ if (libpymodules) {
-+ rv = dlsym(libpymodules, funcname);
-+ if (rv != NULL) {
-+ return rv;
-+ }
-+ }
-+
-+
- if (fp != NULL) {
- int i;
- struct stat statb;
---- Python-2.7.2.orig/Python/pythonrun.c 2010-10-29 05:45:34.000000000 +0200
-+++ Python-2.7.2/Python/pythonrun.c 2011-04-20 17:52:12.000000000 +0200
-@@ -254,9 +254,13 @@
- _PyGILState_Init(interp, tstate);
- #endif /* WITH_THREAD */
-
-+ /* For PGS4A, we don't want to call initsite, as we won't have the
-+ library path set up until start.pyx finishes running. */
-+#if 0
- if (!Py_NoSiteFlag)
- initsite(); /* Module site */
--
-+#endif
-+
- if ((p = Py_GETENV("PYTHONIOENCODING")) && *p != '\0') {
- p = icodeset = codeset = strdup(p);
- free_codeset = 1;
diff --git a/pythonforandroid/recipes/python2/patches/disable-modules.patch b/pythonforandroid/recipes/python2/patches/disable-modules.patch
deleted file mode 100644
index 3841488c4..000000000
--- a/pythonforandroid/recipes/python2/patches/disable-modules.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- Python-2.7.2.orig/setup.py 2010-10-31 17:40:21.000000000 +0100
-+++ Python-2.7.2/setup.py 2011-11-27 16:49:36.840204364 +0100
-@@ -21,7 +21,7 @@
- COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount')
-
- # This global variable is used to hold the list of modules to be disabled.
--disabled_module_list = []
-+disabled_module_list = ['spwd','bz2','ossaudiodev','_curses','_curses_panel','readline','_locale','_bsddb','gdbm','dbm','nis','linuxaudiodev','crypt','_multiprocessing']
-
- def add_dir_to_list(dirlist, dir):
- """Add the directory 'dir' to the list 'dirlist' (at the front) if
diff --git a/pythonforandroid/recipes/python2/patches/disable-openpty.patch b/pythonforandroid/recipes/python2/patches/disable-openpty.patch
deleted file mode 100644
index e6f971730..000000000
--- a/pythonforandroid/recipes/python2/patches/disable-openpty.patch
+++ /dev/null
@@ -1,59 +0,0 @@
---- python2/Modules/posixmodule.c 2016-01-05 15:54:39.504651722 -0600
-+++ b/Modules/posixmodule.c 2016-01-05 16:01:48.072559330 -0600
-@@ -3743,54 +3743,8 @@
- static PyObject *
- posix_openpty(PyObject *self, PyObject *noargs)
- {
-- int master_fd, slave_fd;
--#ifndef HAVE_OPENPTY
-- char * slave_name;
--#endif
--#if defined(HAVE_DEV_PTMX) && !defined(HAVE_OPENPTY) && !defined(HAVE__GETPTY)
-- PyOS_sighandler_t sig_saved;
--#ifdef sun
-- extern char *ptsname(int fildes);
--#endif
--#endif
--
--#ifdef HAVE_OPENPTY
-- if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) != 0)
-- return posix_error();
--#elif defined(HAVE__GETPTY)
-- slave_name = _getpty(&master_fd, O_RDWR, 0666, 0);
-- if (slave_name == NULL)
-- return posix_error();
--
-- slave_fd = open(slave_name, O_RDWR);
-- if (slave_fd < 0)
-- return posix_error();
--#else
-- master_fd = open(DEV_PTY_FILE, O_RDWR | O_NOCTTY); /* open master */
-- if (master_fd < 0)
-- return posix_error();
-- sig_saved = PyOS_setsig(SIGCHLD, SIG_DFL);
-- /* change permission of slave */
-- if (grantpt(master_fd) < 0) {
-- PyOS_setsig(SIGCHLD, sig_saved);
-- return posix_error();
-- }
-- /* unlock slave */
-- if (unlockpt(master_fd) < 0) {
-- PyOS_setsig(SIGCHLD, sig_saved);
-- return posix_error();
-- }
-- PyOS_setsig(SIGCHLD, sig_saved);
-- slave_name = ptsname(master_fd); /* get name of slave */
-- if (slave_name == NULL)
-- return posix_error();
-- slave_fd = open(slave_name, O_RDWR | O_NOCTTY); /* open slave */
-- if (slave_fd < 0)
-- return posix_error();
--#endif /* HAVE_OPENPTY */
--
-- return Py_BuildValue("(ii)", master_fd, slave_fd);
--
-+ PyErr_SetString(PyExc_NotImplementedError, "openpty not implemented for this build");
-+ return NULL;
- }
- #endif /* defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX) */
-
diff --git a/pythonforandroid/recipes/python2/patches/enable-ssl.patch b/pythonforandroid/recipes/python2/patches/enable-ssl.patch
deleted file mode 100644
index b7a249e43..000000000
--- a/pythonforandroid/recipes/python2/patches/enable-ssl.patch
+++ /dev/null
@@ -1,17 +0,0 @@
---- python2/Modules/Setup.dist 2011-06-11 10:46:26.000000000 -0500
-+++ b/Modules/Setup.dist 2015-12-28 16:18:13.329648940 -0600
-@@ -211,10 +211,10 @@
-
- # Socket module helper for SSL support; you must comment out the other
- # socket line above, and possibly edit the SSL variable:
--#SSL=/usr/local/ssl
--#_ssl _ssl.c \
--# -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
--# -L$(SSL)/lib -lssl -lcrypto
-+SSL=/p4a/path/to/openssl
-+_ssl _ssl.c \
-+ -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
-+ -L$(SSL) -lssl -lcrypto
-
- # The crypt module is now disabled by default because it breaks builds
- # on many systems (where -lcrypt is needed), e.g. Linux (I believe).
diff --git a/pythonforandroid/recipes/python2/patches/fix-configure-darwin.patch b/pythonforandroid/recipes/python2/patches/fix-configure-darwin.patch
deleted file mode 100644
index 93a1fe3db..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-configure-darwin.patch
+++ /dev/null
@@ -1,40 +0,0 @@
---- Python-2.7.2.orig/configure 2012-07-09 23:48:02.000000000 +0200
-+++ Python-2.7.2/configure 2012-07-09 23:47:34.000000000 +0200
-@@ -4927,7 +4927,7 @@
- RUNSHARED=LD_LIBRARY_PATH=`pwd`:${LD_LIBRARY_PATH}
- INSTSONAME="$LDLIBRARY".$SOVERSION
- ;;
-- Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*)
-+ Linux*|GNU*|NetBSD*|FreeBSD*|DragonFly*|Darwin*)
- LDLIBRARY='libpython$(VERSION).so'
- BLDLIBRARY='-L. -lpython$(VERSION)'
- RUNSHARED=LD_LIBRARY_PATH=`pwd`:${LD_LIBRARY_PATH}
-@@ -4960,7 +4960,7 @@
- BLDLIBRARY='-L. -lpython$(VERSION)'
- RUNSHARED=DLL_PATH=`pwd`:${DLL_PATH:-/atheos/sys/libs:/atheos/autolnk/lib}
- ;;
-- Darwin*)
-+ DDarwin*)
- LDLIBRARY='libpython$(VERSION).dylib'
- BLDLIBRARY='-L. -lpython$(VERSION)'
- RUNSHARED='DYLD_LIBRARY_PATH=`pwd`:${DYLD_LIBRARY_PATH}'
-@@ -7625,6 +7625,9 @@
- LDSHARED='ld -b'
- fi ;;
- OSF*) LDSHARED="ld -shared -expect_unresolved \"*\"";;
-+ Darwin*|Linux*|GNU*|QNX*)
-+ LDSHARED='$(CC) -shared'
-+ LDCXXSHARED='$(CXX) -shared';;
- Darwin/1.3*)
- LDSHARED='$(CC) -bundle'
- LDCXXSHARED='$(CXX) -bundle'
-@@ -7680,9 +7683,6 @@
- fi
- fi
- ;;
-- Linux*|GNU*|QNX*)
-- LDSHARED='$(CC) -shared'
-- LDCXXSHARED='$(CXX) -shared';;
- BSD/OS*/4*)
- LDSHARED="gcc -shared"
- LDCXXSHARED="g++ -shared";;
diff --git a/pythonforandroid/recipes/python2/patches/fix-distutils-darwin.patch b/pythonforandroid/recipes/python2/patches/fix-distutils-darwin.patch
deleted file mode 100644
index 408363487..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-distutils-darwin.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- Python-2.7.2.orig/Lib/distutils/command/build_ext.py 2011-06-11 17:46:24.000000000 +0200
-+++ Python-2.7.2/Lib/distutils/command/build_ext.py 2012-08-01 18:32:13.000000000 +0200
-@@ -236,7 +236,7 @@
- # Python's library directory must be appended to library_dirs
- sysconfig.get_config_var('Py_ENABLE_SHARED')
- if ((sys.platform.startswith('linux') or sys.platform.startswith('gnu')
-- or sys.platform.startswith('sunos'))
-+ or sys.platform.startswith('sunos') or sys.platform.startswith('darwin'))
- and sysconfig.get_config_var('Py_ENABLE_SHARED')):
- if sys.executable.startswith(os.path.join(sys.exec_prefix, "bin")):
- # building third party extensions
-@@ -750,9 +750,9 @@
- # extensions, it is a reference to the original list
- return ext.libraries + [pythonlib, "m"] + extra
-
-- elif sys.platform == 'darwin':
-- # Don't use the default code below
-- return ext.libraries
-+ #elif sys.platform == 'darwin':
-+ # # Don't use the default code below
-+ # return ext.libraries
- elif sys.platform[:3] == 'aix':
- # Don't use the default code below
- return ext.libraries
diff --git a/pythonforandroid/recipes/python2/patches/fix-dlfcn.patch b/pythonforandroid/recipes/python2/patches/fix-dlfcn.patch
deleted file mode 100644
index 7a685cadc..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-dlfcn.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-diff -Naur Python-2.7.2.orig/Lib/plat-linux2/DLFCN.py Python-2.7.2/Lib/plat-linux2/DLFCN.py
---- Python-2.7.2.orig/Lib/plat-linux2/DLFCN.py 2011-06-11 17:46:24.000000000 +0200
-+++ Python-2.7.2/Lib/plat-linux2/DLFCN.py 2013-07-29 16:34:45.318131844 +0200
-@@ -74,10 +74,17 @@
- # Included from gnu/stubs.h
-
- # Included from bits/dlfcn.h
-+# PATCHED FOR ANDROID (the only supported symbols are):
-+# enum {
-+# RTLD_NOW = 0,
-+# RTLD_LAZY = 1,
-+# RTLD_LOCAL = 0,
-+# RTLD_GLOBAL = 2,
-+# };
- RTLD_LAZY = 0x00001
--RTLD_NOW = 0x00002
--RTLD_BINDING_MASK = 0x3
--RTLD_NOLOAD = 0x00004
--RTLD_GLOBAL = 0x00100
-+RTLD_NOW = 0x00000
-+RTLD_BINDING_MASK = 0x0
-+RTLD_NOLOAD = 0x00000
-+RTLD_GLOBAL = 0x00002
- RTLD_LOCAL = 0
--RTLD_NODELETE = 0x01000
-+RTLD_NODELETE = 0x00000
diff --git a/pythonforandroid/recipes/python2/patches/fix-dynamic-lookup.patch b/pythonforandroid/recipes/python2/patches/fix-dynamic-lookup.patch
deleted file mode 100644
index 982cb30b4..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-dynamic-lookup.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- Python-2.7.2/Makefile.pre.in.orig 2012-07-05 17:09:45.000000000 +0200
-+++ Python-2.7.2/Makefile.pre.in 2012-07-05 17:10:00.000000000 +0200
-@@ -435,7 +435,7 @@
- fi
-
- libpython$(VERSION).dylib: $(LIBRARY_OBJS)
-- $(CC) -dynamiclib -Wl,-single_module $(LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(VERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(SHLIBS) $(LIBC) $(LIBM) $(LDLAST); \
-+ $(CC) -dynamiclib -Wl,-single_module $(LDFLAGS) -Wl,-install_name,$(prefix)/lib/libpython$(VERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(SHLIBS) $(LIBC) $(LIBM) $(LDLAST); \
-
-
- libpython$(VERSION).sl: $(LIBRARY_OBJS)
diff --git a/pythonforandroid/recipes/python2/patches/fix-filesystemdefaultencoding.patch b/pythonforandroid/recipes/python2/patches/fix-filesystemdefaultencoding.patch
deleted file mode 100644
index c77998e46..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-filesystemdefaultencoding.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- Python-2.7.2.orig/Python/bltinmodule.c 2012-03-30 01:44:57.018079845 +0200
-+++ Python-2.7.2/Python/bltinmodule.c 2012-03-30 01:45:02.650079649 +0200
-@@ -22,7 +22,7 @@
- #elif defined(__APPLE__)
- const char *Py_FileSystemDefaultEncoding = "utf-8";
- #else
--const char *Py_FileSystemDefaultEncoding = NULL; /* use default */
-+const char *Py_FileSystemDefaultEncoding = "utf-8"; /* use default */
- #endif
-
- /* Forward */
diff --git a/pythonforandroid/recipes/python2/patches/fix-ftime-removal.patch b/pythonforandroid/recipes/python2/patches/fix-ftime-removal.patch
deleted file mode 100644
index 5d79a1dc1..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-ftime-removal.patch
+++ /dev/null
@@ -1,11 +0,0 @@
-diff -u -r Python-2.7.2.orig/Modules/timemodule.c Python-2.7.2/Modules/timemodule.c
---- Python-2.7.2.orig/Modules/timemodule.c 2011-06-11 15:46:27.000000000 +0000
-+++ Python-2.7.2/Modules/timemodule.c 2015-09-11 10:37:36.708661691 +0000
-@@ -27,6 +27,7 @@
- #include <io.h>
- #endif
-
-+#undef HAVE_FTIME
- #ifdef HAVE_FTIME
- #include <sys/timeb.h>
- #if !defined(MS_WINDOWS) && !defined(PYOS_OS2)
diff --git a/pythonforandroid/recipes/python2/patches/fix-gethostbyaddr.patch b/pythonforandroid/recipes/python2/patches/fix-gethostbyaddr.patch
deleted file mode 100644
index 7b04250a3..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-gethostbyaddr.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- Python-2.7.2/Modules/socketmodule.c.orig 2012-01-06 01:40:09.915694810 +0100
-+++ Python-2.7.2/Modules/socketmodule.c 2012-01-06 01:40:36.967694486 +0100
-@@ -146,6 +146,9 @@
- On the other hand, not all Linux versions agree, so there the settings
- computed by the configure script are needed! */
-
-+/* Android hack, same reason are what is described above */
-+#undef HAVE_GETHOSTBYNAME_R
-+
- #ifndef linux
- # undef HAVE_GETHOSTBYNAME_R_3_ARG
- # undef HAVE_GETHOSTBYNAME_R_5_ARG
diff --git a/pythonforandroid/recipes/python2/patches/fix-locale.patch b/pythonforandroid/recipes/python2/patches/fix-locale.patch
deleted file mode 100644
index bd4fcbf0f..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-locale.patch
+++ /dev/null
@@ -1,91 +0,0 @@
---- Python-2.7.2.orig/Modules/pwdmodule.c 2010-08-16 22:30:26.000000000 +0200
-+++ Python-2.7.2/Modules/pwdmodule.c 2011-04-20 17:52:12.000000000 +0200
-@@ -75,11 +75,7 @@
- #endif
- SETI(setIndex++, p->pw_uid);
- SETI(setIndex++, p->pw_gid);
--#ifdef __VMS
- SETS(setIndex++, "");
--#else
-- SETS(setIndex++, p->pw_gecos);
--#endif
- SETS(setIndex++, p->pw_dir);
- SETS(setIndex++, p->pw_shell);
-
---- Python-2.7.2.orig/Modules/posixmodule.c 2010-11-26 18:35:50.000000000 +0100
-+++ Python-2.7.2/Modules/posixmodule.c 2011-04-20 17:52:12.000000000 +0200
-@@ -3775,13 +3775,6 @@
- slave_fd = open(slave_name, O_RDWR | O_NOCTTY); /* open slave */
- if (slave_fd < 0)
- return posix_error();
--#if !defined(__CYGWIN__) && !defined(HAVE_DEV_PTC)
-- ioctl(slave_fd, I_PUSH, "ptem"); /* push ptem */
-- ioctl(slave_fd, I_PUSH, "ldterm"); /* push ldterm */
--#ifndef __hpux
-- ioctl(slave_fd, I_PUSH, "ttcompat"); /* push ttcompat */
--#endif /* __hpux */
--#endif /* HAVE_CYGWIN */
- #endif /* HAVE_OPENPTY */
-
- return Py_BuildValue("(ii)", master_fd, slave_fd);
---- Python-2.7.2.orig/Objects/stringlib/formatter.h 2010-08-01 12:45:15.000000000 +0200
-+++ Python-2.7.2/Objects/stringlib/formatter.h 2011-04-20 17:52:12.000000000 +0200
-@@ -639,13 +639,7 @@
- get_locale_info(int type, LocaleInfo *locale_info)
- {
- switch (type) {
-- case LT_CURRENT_LOCALE: {
-- struct lconv *locale_data = localeconv();
-- locale_info->decimal_point = locale_data->decimal_point;
-- locale_info->thousands_sep = locale_data->thousands_sep;
-- locale_info->grouping = locale_data->grouping;
-- break;
-- }
-+ case LT_CURRENT_LOCALE:
- case LT_DEFAULT_LOCALE:
- locale_info->decimal_point = ".";
- locale_info->thousands_sep = ",";
---- Python-2.7.2.orig/Objects/stringlib/localeutil.h 2009-04-22 15:29:05.000000000 +0200
-+++ Python-2.7.2/Objects/stringlib/localeutil.h 2011-04-20 17:52:12.000000000 +0200
-@@ -202,9 +202,8 @@
- Py_ssize_t n_digits,
- Py_ssize_t min_width)
- {
-- struct lconv *locale_data = localeconv();
-- const char *grouping = locale_data->grouping;
-- const char *thousands_sep = locale_data->thousands_sep;
-+ const char *grouping = "\3\0";
-+ const char *thousands_sep = ",";
-
- return _Py_InsertThousandsGrouping(buffer, n_buffer, digits, n_digits,
- min_width, grouping, thousands_sep);
---- Python-2.7.2.orig/Python/pystrtod.c 2010-05-09 16:46:46.000000000 +0200
-+++ Python-2.7.2/Python/pystrtod.c 2011-04-20 17:52:12.000000000 +0200
-@@ -126,7 +126,6 @@
- {
- char *fail_pos;
- double val = -1.0;
-- struct lconv *locale_data;
- const char *decimal_point;
- size_t decimal_point_len;
- const char *p, *decimal_point_pos;
-@@ -138,8 +137,7 @@
-
- fail_pos = NULL;
-
-- locale_data = localeconv();
-- decimal_point = locale_data->decimal_point;
-+ decimal_point = ".";
- decimal_point_len = strlen(decimal_point);
-
- assert(decimal_point_len != 0);
-@@ -375,8 +373,7 @@
- Py_LOCAL_INLINE(void)
- change_decimal_from_locale_to_dot(char* buffer)
- {
-- struct lconv *locale_data = localeconv();
-- const char *decimal_point = locale_data->decimal_point;
-+ const char *decimal_point = ".";
-
- if (decimal_point[0] != '.' || decimal_point[1] != 0) {
- size_t decimal_point_len = strlen(decimal_point);
diff --git a/pythonforandroid/recipes/python2/patches/fix-remove-corefoundation.patch b/pythonforandroid/recipes/python2/patches/fix-remove-corefoundation.patch
deleted file mode 100644
index 02a472e1d..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-remove-corefoundation.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- Python-2.7.2/configure.orig 2012-07-05 16:44:36.000000000 +0200
-+++ Python-2.7.2/configure 2012-07-05 16:44:44.000000000 +0200
-@@ -13732,10 +13732,6 @@
-
- fi
-
--if test $ac_sys_system = Darwin
--then
-- LIBS="$LIBS -framework CoreFoundation"
--fi
-
-
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for %zd printf() format support" >&5
diff --git a/pythonforandroid/recipes/python2/patches/fix-setup-flags.patch b/pythonforandroid/recipes/python2/patches/fix-setup-flags.patch
deleted file mode 100644
index b1f640cc7..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-setup-flags.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- Python-2.7.2/setup.py.orig 2012-01-08 15:10:39.867332119 +0100
-+++ Python-2.7.2/setup.py 2012-01-08 15:10:45.723331911 +0100
-@@ -445,6 +445,13 @@
- '/lib', '/usr/lib',
- ]
- inc_dirs += ['/usr/include']
-+ else:
-+ cflags = os.environ.get('CFLAGS')
-+ if cflags:
-+ inc_dirs += [x[2:] for x in cflags.split() if x.startswith('-I')]
-+ ldflags = os.environ.get('LDFLAGS')
-+ if ldflags:
-+ lib_dirs += [x[2:] for x in ldflags.split() if x.startswith('-L')]
- exts = []
- missing = []
-
diff --git a/pythonforandroid/recipes/python2/patches/fix-termios.patch b/pythonforandroid/recipes/python2/patches/fix-termios.patch
deleted file mode 100644
index a114e276d..000000000
--- a/pythonforandroid/recipes/python2/patches/fix-termios.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- Python-2.7.2.orig/Modules/termios.c 2012-06-12 02:49:39.780162534 +0200
-+++ Python-2.7.2/Modules/termios.c 2012-06-12 02:51:52.092157828 +0200
-@@ -227,6 +227,7 @@
- return Py_None;
- }
-
-+#if 0 // No tcdrain defined for Android.
- PyDoc_STRVAR(termios_tcdrain__doc__,
- "tcdrain(fd) -> None\n\
- \n\
-@@ -246,6 +247,7 @@
- Py_INCREF(Py_None);
- return Py_None;
- }
-+#endif
-
- PyDoc_STRVAR(termios_tcflush__doc__,
- "tcflush(fd, queue) -> None\n\
-@@ -301,8 +303,10 @@
- METH_VARARGS, termios_tcsetattr__doc__},
- {"tcsendbreak", termios_tcsendbreak,
- METH_VARARGS, termios_tcsendbreak__doc__},
-+#if 0 // No tcdrain defined for Android.
- {"tcdrain", termios_tcdrain,
- METH_VARARGS, termios_tcdrain__doc__},
-+#endif
- {"tcflush", termios_tcflush,
- METH_VARARGS, termios_tcflush__doc__},
- {"tcflow", termios_tcflow,
diff --git a/pythonforandroid/recipes/python2/patches/parsetuple.patch b/pythonforandroid/recipes/python2/patches/parsetuple.patch
deleted file mode 100644
index 150c75e49..000000000
--- a/pythonforandroid/recipes/python2/patches/parsetuple.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- Python-2.7.2/configure.orig 2015-06-24 17:47:39.181473779 +0200
-+++ Python-2.7.2/configure 2015-06-24 17:48:31.646173137 +0200
-@@ -5731,7 +5731,7 @@
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether gcc supports ParseTuple __format__" >&5
- $as_echo_n "checking whether gcc supports ParseTuple __format__... " >&6; }
- save_CFLAGS=$CFLAGS
-- CFLAGS="$CFLAGS -Werror"
-+ CFLAGS="$CFLAGS -Werror -Wformat"
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
- /* end confdefs.h. */
-
diff --git a/pythonforandroid/recipes/python2/patches/t.htm b/pythonforandroid/recipes/python2/patches/t.htm
deleted file mode 100644
index ddb028acf..000000000
--- a/pythonforandroid/recipes/python2/patches/t.htm
+++ /dev/null
@@ -1,151 +0,0 @@
-<!DOCTYPE html>
-<html><head>
-<meta http-equiv="content-type" content="text/html; charset=UTF-8">
-<link rel="stylesheet" type="text/css" href="t_files/b0dcca.css" title="Default">
-<title>xkcd: Legal Hacks</title>
-<meta http-equiv="X-UA-Compatible" content="IE=edge">
-<link rel="shortcut icon" href="https://xkcd.com/s/919f27.ico" type="image/x-icon">
-<link rel="icon" href="https://xkcd.com/s/919f27.ico" type="image/x-icon">
-<link rel="alternate" type="application/atom+xml" title="Atom 1.0" href="https://xkcd.com/atom.xml">
-<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="https://xkcd.com/rss.xml">
-<script src="t_files/analytics.js" async=""></script><script>
-(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
-ga('create', 'UA-25700708-7', 'auto');
-ga('send', 'pageview');
-</script>
-<script type="text/javascript" src="t_files/jquery.js"></script>
-<script type="text/javascript" src="t_files/json2.js"></script>
-
-</head>
-<body>
-<div id="topContainer">
-<div id="topLeft">
-<ul>
-<li><a href="https://xkcd.com/archive">Archive</a></li>
-<li><a href="http://what-if.xkcd.com/">What If?</a></li>
-<li><a href="http://blag.xkcd.com/">Blag</a></li>
-<li><a href="http://store.xkcd.com/">Store</a></li>
-<li><a rel="author" href="https://xkcd.com/about">About</a></li>
-</ul>
-</div>
-<div id="topRight">
-<div id="masthead">
-<span><a href="https://xkcd.com/"><img src="t_files/terrible_small_logo.png" alt="xkcd.com logo" height="83" width="185"></a></span>
-<span id="slogan">A webcomic of romance,<br> sarcasm, math, and language.</span>
-</div>
-<div id="news">
-<a href="http://blog.xkcd.com/?p=805"><img src="t_files/te-news.png" border="0"></a><br>
-Preorder: <a title="Thing Explainer Amazon purchase link" href="http://amzn.to/1GCXMJ5">Amazon</a>, <a title="Thing Explainer Barnes and Noble purchase link" href="http://www.barnesandnoble.com/w/thing-explainer-randall-munroe/1121864432?ean=9780544668256">Barnes & Noble</a>, <a title="Thing Explainer Indie Bound purchase link" href="http://www.indiebound.org/book/9780544668256">Indie Bound</a>, <a title="Thing Explainer Hudson purchase link" href="http://www.hudsonbooksellers.com/thingexplainer">Hudson</a><br>
-In other news, <a href="https://www.youtube.com/watch?v=ygrdAvmr-MA">Space Weird Thing</a> is delightful, and I feel surprisingly invested in <a href="https://twitter.com/xkcdbracket">@xkcdbracket</a>'s results.
-
-</div>
-</div>
-<div id="bgLeft" class="bg box"></div>
-<div id="bgRight" class="bg box"></div>
-</div>
-<div id="middleContainer" class="box">
-
-<div id="ctitle">Legal Hacks</div>
-<ul class="comicNav">
-<li><a href="https://xkcd.com/1/">|<</a></li>
-<li><a rel="prev" href="https://xkcd.com/503/" accesskey="p">< Prev</a></li>
-<li><a href="https://c.xkcd.com/random/comic/">Random</a></li>
-<li><a rel="next" href="https://xkcd.com/505/" accesskey="n">Next ></a></li>
-<li><a href="https://xkcd.com/">>|</a></li>
-</ul>
-<div id="comic">
-<img src="t_files/legal_hacks.png" title="It's totally a reasonable modern analogue. Jefferson would have been all about crypto." alt="Legal Hacks">
-</div>
-<ul class="comicNav">
-<li><a href="https://xkcd.com/1/">|<</a></li>
-<li><a rel="prev" href="https://xkcd.com/503/" accesskey="p">< Prev</a></li>
-<li><a href="https://c.xkcd.com/random/comic/">Random</a></li>
-<li><a rel="next" href="https://xkcd.com/505/" accesskey="n">Next ></a></li>
-<li><a href="https://xkcd.com/">>|</a></li>
-</ul>
-<br>
-Permanent link to this comic: http://xkcd.com/504/<br>
-Image URL (for hotlinking/embedding): http://imgs.xkcd.com/comics/legal_hacks.png
-<div id="transcript" style="display: none">[[A woman sits at her
-computer, a man standing behind her. The woman speaks first.]]
-Woman: Another ISP's filtering content.
-Man: Thank God for Crypto.
-[[The man stands alone; the woman is presumably off-panel left.]]
-Man: It wasn't that long ago that RSA was illegal to export. Classified a
- munition.
-[[The woman, sitting in her chair, is looking back towards the man,
-presumably off-panel right.]]
-Woman: You know, I think the crypto community took the wrong side in
-that fight. We should've lobbied to keep it counted as a weapon.
-Man: Why?
-[[She is now turned around in the chair looking at the man, who is
-in-panel again.]]
-Woman: Once they get complacent, we break out the second amendment.
-[[The man has his hand on his chin, contemplatively.]]
-Man: ...Damn.
-{{Title text: It's totally a reasonable modern analogue. Jefferson
-would have been all about crypto.}}</div>
-</div>
-<div id="bottom" class="box">
-<img src="t_files/a899e84.jpg" alt="Selected Comics" usemap="#comicmap" height="100" width="520">
-<map id="comicmap" name="comicmap">
-<area shape="rect" coords="0,0,100,100" href="https://xkcd.com/150/" alt="Grownups">
-<area shape="rect" coords="104,0,204,100" href="https://xkcd.com/730/" alt="Circuit Diagram">
-<area shape="rect" coords="208,0,308,100" href="https://xkcd.com/162/" alt="Angular Momentum">
-<area shape="rect" coords="312,0,412,100" href="https://xkcd.com/688/" alt="Self-Description">
-<area shape="rect" coords="416,0,520,100" href="https://xkcd.com/556/" alt="Alternative Energy Revolution">
-</map>
-<div>
-<!--
-Search comic titles and transcripts:
-<script type="text/javascript" src="//www.google.com/jsapi"></script>
-<script type="text/javascript">google.load('search', '1');google.setOnLoadCallback(function() {google.search.CustomSearchControl.attachAutoCompletion('012652707207066138651:zudjtuwe28q',document.getElementById('q'),'cse-search-box');});</script>
-<form action="//www.google.com/cse" id="cse-search-box">
-<div>
-<input type="hidden" name="cx" value="012652707207066138651:zudjtuwe28q"/>
-<input type="hidden" name="ie" value="UTF-8"/>
-<input type="text" name="q" id="q" size="31"/>
-<input type="submit" name="sa" value="Search"/>
-</div>
-</form>
-<script type="text/javascript" src="//www.google.com/cse/brand?form=cse-search-box&lang=en"></script>
--->
-<a href="https://xkcd.com/rss.xml">RSS Feed</a> - <a href="https://xkcd.com/atom.xml">Atom Feed</a>
-</div>
-<br>
-<div id="comicLinks">
-Comics I enjoy:<br>
- <a href="http://threewordphrase.com/">Three Word Phrase</a>,
- <a href="http://www.smbc-comics.com/">SMBC</a>,
- <a href="http://www.qwantz.com/">Dinosaur Comics</a>,
- <a href="http://oglaf.com/">Oglaf</a> (nsfw),
- <a href="http://www.asofterworld.com/">A Softer World</a>,
- <a href="http://buttersafe.com/">Buttersafe</a>,
- <a href="http://pbfcomics.com/">Perry Bible Fellowship</a>,
- <a href="http://questionablecontent.net/">Questionable Content</a>,
- <a href="http://www.buttercupfestival.com/">Buttercup Festival</a>,
- <a href="http://www.mspaintadventures.com/?s=6&p=001901">Homestuck</a>,
- <a href="http://www.jspowerhour.com/">Junior Scientist Power Hour</a>
-</div>
-<p>Warning: this comic occasionally contains strong language (which may
-be unsuitable for children), unusual humor (which may be unsuitable for
-adults), and advanced mathematics (which may be unsuitable for
-liberal-arts majors).</p>
-<div id="footnote">BTC 1FhCLQK2ZXtCUQDtG98p6fVH7S6mxAsEey<br>We did not invent the algorithm. The algorithm consistently finds Jesus. The algorithm killed Jeeves. <br>The algorithm is banned in China. The algorithm is from Jersey. The algorithm constantly finds Jesus.<br>This is not the algorithm. This is close.</div>
-<div id="licenseText">
-<p>
-This work is licensed under a
-<a href="http://creativecommons.org/licenses/by-nc/2.5/">Creative Commons Attribution-NonCommercial 2.5 License</a>.
-</p><p>
-This means you're free to copy and share these comics (but not to sell them). <a rel="license" href="https://xkcd.com/license.html">More details</a>.</p>
-</div>
-</div>
-
-
-
-
-</body><!-- Layout by Ian Clasbey, davean, and chromakode --></html>
\ No newline at end of file
diff --git a/pythonforandroid/recipes/python2/patches/t_files/a899e84.jpg b/pythonforandroid/recipes/python2/patches/t_files/a899e84.jpg
deleted file mode 100644
index 5f2f1f775..000000000
Binary files a/pythonforandroid/recipes/python2/patches/t_files/a899e84.jpg and /dev/null differ
diff --git a/pythonforandroid/recipes/python2/patches/t_files/analytics.js b/pythonforandroid/recipes/python2/patches/t_files/analytics.js
deleted file mode 100644
index 4cea34a1b..000000000
--- a/pythonforandroid/recipes/python2/patches/t_files/analytics.js
+++ /dev/null
@@ -1,42 +0,0 @@
-(function(){var aa=encodeURIComponent,f=window,n=Math;function Pc(a,b){return a.href=b}
-var Qc="replace",q="data",m="match",ja="port",u="createElement",id="setAttribute",da="getTime",A="split",B="location",ra="hasOwnProperty",ma="hostname",ga="search",E="protocol",Ab="href",kd="action",G="apply",p="push",h="hash",pa="test",ha="slice",r="cookie",t="indexOf",ia="defaultValue",v="name",y="length",Ga="sendBeacon",z="prototype",la="clientWidth",jd="target",C="call",na="clientHeight",F="substring",oa="navigator",H="join",I="toLowerCase";var $c=function(a){this.w=a||[]};$c[z].set=function(a){this.w[a]=!0};$c[z].encode=function(){for(var a=[],b=0;b<this.w[y];b++)this.w[b]&&(a[n.floor(b/6)]=a[n.floor(b/6)]^1<<b%6);for(b=0;b<a[y];b++)a[b]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".charAt(a[b]||0);return a[H]("")+"~"};var vd=new $c;function J(a){vd.set(a)}var Nd=function(a,b){var c=new $c(Dd(a));c.set(b);a.set(Gd,c.w)},Td=function(a){a=Dd(a);a=new $c(a);for(var b=vd.w[ha](),c=0;c<a.w[y];c++)b[c]=b[c]||a.w[c];return(new $c(b)).encode()},Dd=function(a){a=a.get(Gd);ka(a)||(a=[]);return a};var ea=function(a){return"function"==typeof a},ka=function(a){return"[object Array]"==Object[z].toString[C](Object(a))},qa=function(a){return void 0!=a&&-1<(a.constructor+"")[t]("String")},D=function(a,b){return 0==a[t](b)},sa=function(a){return a?a[Qc](/^[\s\xa0]+|[\s\xa0]+$/g,""):""},ta=function(a){var b=M[u]("img");b.width=1;b.height=1;b.src=a;return b},ua=function(){},K=function(a){if(aa instanceof Function)return aa(a);J(28);return a},L=function(a,b,c,d){try{a.addEventListener?a.addEventListener(b,
-c,!!d):a.attachEvent&&a.attachEvent("on"+b,c)}catch(e){J(27)}},wa=function(a,b){if(a){var c=M[u]("script");c.type="text/javascript";c.async=!0;c.src=a;b&&(c.id=b);var d=M.getElementsByTagName("script")[0];d.parentNode.insertBefore(c,d)}},Ud=function(){return"https:"==M[B][E]},xa=function(){var a=""+M[B][ma];return 0==a[t]("www.")?a[F](4):a},ya=function(a){var b=M.referrer;if(/^https?:\/\//i[pa](b)){if(a)return b;a="//"+M[B][ma];var c=b[t](a);if(5==c||6==c)if(a=b.charAt(c+a[y]),"/"==a||"?"==a||""==
-a||":"==a)return;return b}},za=function(a,b){if(1==b[y]&&null!=b[0]&&"object"===typeof b[0])return b[0];for(var c={},d=n.min(a[y]+1,b[y]),e=0;e<d;e++)if("object"===typeof b[e]){for(var g in b[e])b[e][ra](g)&&(c[g]=b[e][g]);break}else e<a[y]&&(c[a[e]]=b[e]);return c};var ee=function(){this.keys=[];this.values={};this.m={}};ee[z].set=function(a,b,c){this.keys[p](a);c?this.m[":"+a]=b:this.values[":"+a]=b};ee[z].get=function(a){return this.m[ra](":"+a)?this.m[":"+a]:this.values[":"+a]};ee[z].map=function(a){for(var b=0;b<this.keys[y];b++){var c=this.keys[b],d=this.get(c);d&&a(c,d)}};var O=f,M=document,Mc=function(){for(var a=O[oa].userAgent+(M[r]?M[r]:"")+(M.referrer?M.referrer:""),b=a[y],c=O.history[y];0<c;)a+=c--^b++;return La(a)};var Aa=function(a){var b=O._gaUserPrefs;if(b&&b.ioo&&b.ioo()||a&&!0===O["ga-disable-"+a])return!0;try{var c=O.external;if(c&&c._gaUserPrefs&&"oo"==c._gaUserPrefs)return!0}catch(d){}return!1};var Ca=function(a){var b=[],c=M[r][A](";");a=new RegExp("^\\s*"+a+"=\\s*(.*?)\\s*$");for(var d=0;d<c[y];d++){var e=c[d][m](a);e&&b[p](e[1])}return b},zc=function(a,b,c,d,e,g){e=Aa(e)?!1:eb[pa](M[B][ma])||"/"==c&&vc[pa](d)?!1:!0;if(!e)return!1;b&&1200<b[y]&&(b=b[F](0,1200),J(24));c=a+"="+b+"; path="+c+"; ";g&&(c+="expires="+(new Date((new Date)[da]()+g)).toGMTString()+"; ");d&&"none"!=d&&(c+="domain="+d+";");d=M[r];M.cookie=c;if(!(d=d!=M[r]))a:{a=Ca(a);for(d=0;d<a[y];d++)if(b==a[d]){d=!0;break a}d=
-!1}return d},Cc=function(a){return K(a)[Qc](/\(/g,"%28")[Qc](/\)/g,"%29")},vc=/^(www\.)?google(\.com?)?(\.[a-z]{2})?$/,eb=/(^|\.)doubleclick\.net$/i;var oc=function(){return(Ba||Ud()?"https:":"http:")+"//www.google-analytics.com"},Da=function(a){this.name="len";this.message=a+"-8192"},ba=function(a,b,c){c=c||ua;if(2036>=b[y])wc(a,b,c);else if(8192>=b[y])x(a,b,c)||wd(a,b,c)||wc(a,b,c);else throw ge("len",b[y]),new Da(b[y]);},wc=function(a,b,c){var d=ta(a+"?"+b);d.onload=d.onerror=function(){d.onload=null;d.onerror=null;c()}},wd=function(a,b,c){var d=O.XMLHttpRequest;if(!d)return!1;var e=new d;if(!("withCredentials"in e))return!1;e.open("POST",
-a,!0);e.withCredentials=!0;e.setRequestHeader("Content-Type","text/plain");e.onreadystatechange=function(){4==e.readyState&&(c(),e=null)};e.send(b);return!0},x=function(a,b,c){return O[oa][Ga]?O[oa][Ga](a,b)?(c(),!0):!1:!1},ge=function(a,b,c){1<=100*n.random()||Aa("?")||(a=["t=error","_e="+a,"_v=j37","sr=1"],b&&a[p]("_f="+b),c&&a[p]("_m="+K(c[F](0,100))),a[p]("aip=1"),a[p]("z="+fe()),wc(oc()+"/collect",a[H]("&"),ua))};var Ha=function(){this.M=[]};Ha[z].add=function(a){this.M[p](a)};Ha[z].D=function(a){try{for(var b=0;b<this.M[y];b++){var c=a.get(this.M[b]);c&&ea(c)&&c[C](O,a)}}catch(d){}b=a.get(Ia);b!=ua&&ea(b)&&(a.set(Ia,ua,!0),setTimeout(b,10))};function Ja(a){if(100!=a.get(Ka)&&La(P(a,Q))%1E4>=100*R(a,Ka))throw"abort";}function Ma(a){if(Aa(P(a,Na)))throw"abort";}function Oa(){var a=M[B][E];if("http:"!=a&&"https:"!=a)throw"abort";}
-function Pa(a){try{O[oa][Ga]?J(42):O.XMLHttpRequest&&"withCredentials"in new O.XMLHttpRequest&&J(40)}catch(b){}a.set(ld,Td(a),!0);a.set(Ac,R(a,Ac)+1);var c=[];Qa.map(function(b,e){if(e.F){var g=a.get(b);void 0!=g&&g!=e[ia]&&("boolean"==typeof g&&(g*=1),c[p](e.F+"="+K(""+g)))}});c[p]("z="+Bd());a.set(Ra,c[H]("&"),!0)}
-function Sa(a){var b=P(a,gd)||oc()+"/collect",c=P(a,fa);!c&&a.get(Vd)&&(c="beacon");if(c){var d=P(a,Ra),e=a.get(Ia),e=e||ua;"image"==c?wc(b,d,e):"xhr"==c&&wd(b,d,e)||"beacon"==c&&x(b,d,e)||ba(b,d,e)}else ba(b,P(a,Ra),a.get(Ia));a.set(Ia,ua,!0)}function Hc(a){var b=O.gaData;b&&(b.expId&&a.set(Nc,b.expId),b.expVar&&a.set(Oc,b.expVar))}function cd(){if(O[oa]&&"preview"==O[oa].loadPurpose)throw"abort";}function yd(a){var b=O.gaDevIds;ka(b)&&0!=b[y]&&a.set("&did",b[H](","),!0)}
-function vb(a){if(!a.get(Na))throw"abort";};var hd=function(){return n.round(2147483647*n.random())},Bd=function(){try{var a=new Uint32Array(1);O.crypto.getRandomValues(a);return a[0]&2147483647}catch(b){return hd()}},fe=hd;function Ta(a){var b=R(a,Ua);500<=b&&J(15);var c=P(a,Va);if("transaction"!=c&&"item"!=c){var c=R(a,Wa),d=(new Date)[da](),e=R(a,Xa);0==e&&a.set(Xa,d);e=n.round(2*(d-e)/1E3);0<e&&(c=n.min(c+e,20),a.set(Xa,d));if(0>=c)throw"abort";a.set(Wa,--c)}a.set(Ua,++b)};var Ya=function(){this.data=new ee},Qa=new ee,Za=[];Ya[z].get=function(a){var b=$a(a),c=this[q].get(a);b&&void 0==c&&(c=ea(b[ia])?b[ia]():b[ia]);return b&&b.Z?b.Z(this,a,c):c};var P=function(a,b){var c=a.get(b);return void 0==c?"":""+c},R=function(a,b){var c=a.get(b);return void 0==c||""===c?0:1*c};Ya[z].set=function(a,b,c){if(a)if("object"==typeof a)for(var d in a)a[ra](d)&&ab(this,d,a[d],c);else ab(this,a,b,c)};
-var ab=function(a,b,c,d){if(void 0!=c)switch(b){case Na:wb[pa](c)}var e=$a(b);e&&e.o?e.o(a,b,c,d):a[q].set(b,c,d)},bb=function(a,b,c,d,e){this.name=a;this.F=b;this.Z=d;this.o=e;this.defaultValue=c},$a=function(a){var b=Qa.get(a);if(!b)for(var c=0;c<Za[y];c++){var d=Za[c],e=d[0].exec(a);if(e){b=d[1](e);Qa.set(b[v],b);break}}return b},yc=function(a){var b;Qa.map(function(c,d){d.F==a&&(b=d)});return b&&b[v]},S=function(a,b,c,d,e){a=new bb(a,b,c,d,e);Qa.set(a[v],a);return a[v]},cb=function(a,b){Za[p]([new RegExp("^"+
-a+"$"),b])},T=function(a,b,c){return S(a,b,c,void 0,db)},db=function(){};var gb=qa(f.GoogleAnalyticsObject)&&sa(f.GoogleAnalyticsObject)||"ga",Ba=!1,he=S("_br"),hb=T("apiVersion","v"),ib=T("clientVersion","_v");S("anonymizeIp","aip");var jb=S("adSenseId","a"),Va=S("hitType","t"),Ia=S("hitCallback"),Ra=S("hitPayload");S("nonInteraction","ni");S("currencyCode","cu");S("dataSource","ds");var Vd=S("useBeacon",void 0,!1),fa=S("transport");S("sessionControl","sc","");S("sessionGroup","sg");S("queueTime","qt");var Ac=S("_s","_s");S("screenName","cd");
-var kb=S("location","dl",""),lb=S("referrer","dr"),mb=S("page","dp","");S("hostname","dh");var nb=S("language","ul"),ob=S("encoding","de");S("title","dt",function(){return M.title||void 0});cb("contentGroup([0-9]+)",function(a){return new bb(a[0],"cg"+a[1])});var pb=S("screenColors","sd"),qb=S("screenResolution","sr"),rb=S("viewportSize","vp"),sb=S("javaEnabled","je"),tb=S("flashVersion","fl");S("campaignId","ci");S("campaignName","cn");S("campaignSource","cs");S("campaignMedium","cm");
-S("campaignKeyword","ck");S("campaignContent","cc");var ub=S("eventCategory","ec"),xb=S("eventAction","ea"),yb=S("eventLabel","el"),zb=S("eventValue","ev"),Bb=S("socialNetwork","sn"),Cb=S("socialAction","sa"),Db=S("socialTarget","st"),Eb=S("l1","plt"),Fb=S("l2","pdt"),Gb=S("l3","dns"),Hb=S("l4","rrt"),Ib=S("l5","srt"),Jb=S("l6","tcp"),Kb=S("l7","dit"),Lb=S("l8","clt"),Mb=S("timingCategory","utc"),Nb=S("timingVar","utv"),Ob=S("timingLabel","utl"),Pb=S("timingValue","utt");S("appName","an");
-S("appVersion","av","");S("appId","aid","");S("appInstallerId","aiid","");S("exDescription","exd");S("exFatal","exf");var Nc=S("expId","xid"),Oc=S("expVar","xvar"),Rc=S("_utma","_utma"),Sc=S("_utmz","_utmz"),Tc=S("_utmht","_utmht"),Ua=S("_hc",void 0,0),Xa=S("_ti",void 0,0),Wa=S("_to",void 0,20);cb("dimension([0-9]+)",function(a){return new bb(a[0],"cd"+a[1])});cb("metric([0-9]+)",function(a){return new bb(a[0],"cm"+a[1])});S("linkerParam",void 0,void 0,Bc,db);var ld=S("usage","_u"),Gd=S("_um");
-S("forceSSL",void 0,void 0,function(){return Ba},function(a,b,c){J(34);Ba=!!c});var ed=S("_j1","jid");cb("\\&(.*)",function(a){var b=new bb(a[0],a[1]),c=yc(a[0][F](1));c&&(b.Z=function(a){return a.get(c)},b.o=function(a,b,g,ca){a.set(c,g,ca)},b.F=void 0);return b});
-var Qb=T("_oot"),dd=S("previewTask"),Rb=S("checkProtocolTask"),md=S("validationTask"),Sb=S("checkStorageTask"),Uc=S("historyImportTask"),Tb=S("samplerTask"),Vb=S("_rlt"),Wb=S("buildHitTask"),Xb=S("sendHitTask"),Vc=S("ceTask"),zd=S("devIdTask"),Cd=S("timingTask"),Ld=S("displayFeaturesTask"),V=T("name"),Q=T("clientId","cid"),Ad=S("userId","uid"),Na=T("trackingId","tid"),U=T("cookieName",void 0,"_ga"),W=T("cookieDomain"),Yb=T("cookiePath",void 0,"/"),Zb=T("cookieExpires",void 0,63072E3),$b=T("legacyCookieDomain"),
-Wc=T("legacyHistoryImport",void 0,!0),ac=T("storage",void 0,"cookie"),bc=T("allowLinker",void 0,!1),cc=T("allowAnchor",void 0,!0),Ka=T("sampleRate","sf",100),dc=T("siteSpeedSampleRate",void 0,1),ec=T("alwaysSendReferrer",void 0,!1),gd=S("transportUrl"),Md=S("_r","_r");function X(a,b,c,d){b[a]=function(){try{return d&&J(d),c[G](this,arguments)}catch(b){throw ge("exc",a,b&&b[v]),b;}}};var Od=function(a,b,c){this.V=1E4;this.fa=a;this.$=!1;this.B=b;this.ea=c||1},Ed=function(a,b){var c;if(a.fa&&a.$)return 0;a.$=!0;if(b){if(a.B&&R(b,a.B))return R(b,a.B);if(0==b.get(dc))return 0}if(0==a.V)return 0;void 0===c&&(c=Bd());return 0==c%a.V?n.floor(c/a.V)%a.ea+1:0};var ie=new Od(!0,he,7),je=function(a){if(!Ud()&&!Ba){var b=Ed(ie,a);if(b&&!(!O[oa][Ga]&&4<=b&&6>=b)){var c=(new Date).getHours(),d=[Bd(),Bd(),Bd()][H](".");a=(3==b||5==b?"https:":"http:")+"//www.google-analytics.com/collect?z=br.";a+=[b,"A",c,d][H](".");var e=1!=b%3?"https:":"http:",e=e+"//www.google-analytics.com/collect?z=br.",e=e+[b,"B",c,d][H](".");7==b&&(e=e[Qc]("//www.","//ssl."));c=function(){4<=b&&6>=b?O[oa][Ga](e,""):ta(e)};Bd()%2?(ta(a),c()):(c(),ta(a))}}};function fc(){var a,b,c;if((c=(c=O[oa])?c.plugins:null)&&c[y])for(var d=0;d<c[y]&&!b;d++){var e=c[d];-1<e[v][t]("Shockwave Flash")&&(b=e.description)}if(!b)try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"),b=a.GetVariable("$version")}catch(g){}if(!b)try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"),b="WIN 6,0,21,0",a.AllowScriptAccess="always",b=a.GetVariable("$version")}catch(ca){}if(!b)try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash"),b=a.GetVariable("$version")}catch(l){}b&&
-(a=b[m](/[\d]+/g))&&3<=a[y]&&(b=a[0]+"."+a[1]+" r"+a[2]);return b||void 0};var gc=function(a,b){var c=n.min(R(a,dc),100);if(!(La(P(a,Q))%100>=c)&&(c={},Ec(c)||Fc(c))){var d=c[Eb];void 0==d||Infinity==d||isNaN(d)||(0<d?(Y(c,Gb),Y(c,Jb),Y(c,Ib),Y(c,Fb),Y(c,Hb),Y(c,Kb),Y(c,Lb),b(c)):L(O,"load",function(){gc(a,b)},!1))}},Ec=function(a){var b=O.performance||O.webkitPerformance,b=b&&b.timing;if(!b)return!1;var c=b.navigationStart;if(0==c)return!1;a[Eb]=b.loadEventStart-c;a[Gb]=b.domainLookupEnd-b.domainLookupStart;a[Jb]=b.connectEnd-b.connectStart;a[Ib]=b.responseStart-b.requestStart;
-a[Fb]=b.responseEnd-b.responseStart;a[Hb]=b.fetchStart-c;a[Kb]=b.domInteractive-c;a[Lb]=b.domContentLoadedEventStart-c;return!0},Fc=function(a){if(O.top!=O)return!1;var b=O.external,c=b&&b.onloadT;b&&!b.isValidLoadTime&&(c=void 0);2147483648<c&&(c=void 0);0<c&&b.setPageReadyTime();if(void 0==c)return!1;a[Eb]=c;return!0},Y=function(a,b){var c=a[b];if(isNaN(c)||Infinity==c||0>c)a[b]=void 0},Fd=function(a){return function(b){"pageview"!=b.get(Va)||a.I||(a.I=!0,gc(b,function(b){a.send("timing",b)}))}};var hc=!1,mc=function(a){if("cookie"==P(a,ac)){var b=P(a,U),c=nd(a),d=kc(P(a,Yb)),e=lc(P(a,W)),g=1E3*R(a,Zb),ca=P(a,Na);if("auto"!=e)zc(b,c,d,e,ca,g)&&(hc=!0);else{J(32);var l;a:{c=[];e=xa()[A](".");if(4==e[y]&&(l=e[e[y]-1],parseInt(l,10)==l)){l=["none"];break a}for(l=e[y]-2;0<=l;l--)c[p](e[ha](l)[H]("."));c[p]("none");l=c}for(var k=0;k<l[y];k++)if(e=l[k],a[q].set(W,e),c=nd(a),zc(b,c,d,e,ca,g)){hc=!0;return}a[q].set(W,"auto")}}},nc=function(a){if("cookie"==P(a,ac)&&!hc&&(mc(a),!hc))throw"abort";},
-Yc=function(a){if(a.get(Wc)){var b=P(a,W),c=P(a,$b)||xa(),d=Xc("__utma",c,b);d&&(J(19),a.set(Tc,(new Date)[da](),!0),a.set(Rc,d.R),(b=Xc("__utmz",c,b))&&d[h]==b[h]&&a.set(Sc,b.R))}},nd=function(a){var b=Cc(P(a,Q)),c=ic(P(a,W));a=jc(P(a,Yb));1<a&&(c+="-"+a);return["GA1",c,b][H](".")},Gc=function(a,b,c){for(var d=[],e=[],g,ca=0;ca<a[y];ca++){var l=a[ca];if(l.H[c]==b)d[p](l);else void 0==g||l.H[c]<g?(e=[l],g=l.H[c]):l.H[c]==g&&e[p](l)}return 0<d[y]?d:e},lc=function(a){return 0==a[t](".")?a.substr(1):
-a},ic=function(a){return lc(a)[A](".")[y]},kc=function(a){if(!a)return"/";1<a[y]&&a.lastIndexOf("/")==a[y]-1&&(a=a.substr(0,a[y]-1));0!=a[t]("/")&&(a="/"+a);return a},jc=function(a){a=kc(a);return"/"==a?1:a[A]("/")[y]};function Xc(a,b,c){"none"==b&&(b="");var d=[],e=Ca(a);a="__utma"==a?6:2;for(var g=0;g<e[y];g++){var ca=(""+e[g])[A](".");ca[y]>=a&&d[p]({hash:ca[0],R:e[g],O:ca})}return 0==d[y]?void 0:1==d[y]?d[0]:Zc(b,d)||Zc(c,d)||Zc(null,d)||d[0]}function Zc(a,b){var c,d;null==a?c=d=1:(c=La(a),d=La(D(a,".")?a[F](1):"."+a));for(var e=0;e<b[y];e++)if(b[e][h]==c||b[e][h]==d)return b[e]};var od=new RegExp(/^https?:\/\/([^\/:]+)/),pd=/(.*)([?&#])(?:_ga=[^&#]*)(?:&?)(.*)/;function Bc(a){a=a.get(Q);var b=Ic(a,0);return"_ga=1."+K(b+"."+a)}function Ic(a,b){for(var c=new Date,d=O[oa],e=d.plugins||[],c=[a,d.userAgent,c.getTimezoneOffset(),c.getYear(),c.getDate(),c.getHours(),c.getMinutes()+b],d=0;d<e[y];++d)c[p](e[d].description);return La(c[H]("."))}var Dc=function(a){J(48);this.target=a;this.T=!1};
-Dc[z].ca=function(a,b){if(a.tagName){if("a"==a.tagName[I]()){a[Ab]&&Pc(a,qd(this,a[Ab],b));return}if("form"==a.tagName[I]())return rd(this,a)}if("string"==typeof a)return qd(this,a,b)};
-var qd=function(a,b,c){var d=pd.exec(b);d&&3<=d[y]&&(b=d[1]+(d[3]?d[2]+d[3]:""));a=a[jd].get("linkerParam");var e=b[t]("?"),d=b[t]("#");c?b+=(-1==d?"#":"&")+a:(c=-1==e?"?":"&",b=-1==d?b+(c+a):b[F](0,d)+c+a+b[F](d));return b},rd=function(a,b){if(b&&b[kd]){var c=a[jd].get("linkerParam")[A]("=")[1];if("get"==b.method[I]()){for(var d=b.childNodes||[],e=0;e<d[y];e++)if("_ga"==d[e][v]){d[e][id]("value",c);return}d=M[u]("input");d[id]("type","hidden");d[id]("name","_ga");d[id]("value",c);b.appendChild(d)}else"post"==
-b.method[I]()&&(b.action=qd(a,b[kd]))}};
-Dc[z].S=function(a,b,c){function d(c){try{c=c||O.event;var d;a:{var g=c[jd]||c.srcElement;for(c=100;g&&0<c;){if(g[Ab]&&g.nodeName[m](/^a(?:rea)?$/i)){d=g;break a}g=g.parentNode;c--}d={}}("http:"==d[E]||"https:"==d[E])&&sd(a,d[ma]||"")&&d[Ab]&&Pc(d,qd(e,d[Ab],b))}catch(w){J(26)}}var e=this;this.T||(this.T=!0,L(M,"mousedown",d,!1),L(M,"keyup",d,!1));if(c){c=function(b){b=b||O.event;if((b=b[jd]||b.srcElement)&&b[kd]){var c=b[kd][m](od);c&&sd(a,c[1])&&rd(e,b)}};for(var g=0;g<M.forms[y];g++)L(M.forms[g],
-"submit",c)}};function sd(a,b){if(b==M[B][ma])return!1;for(var c=0;c<a[y];c++)if(a[c]instanceof RegExp){if(a[c][pa](b))return!0}else if(0<=b[t](a[c]))return!0;return!1};var Jd=function(a,b,c){this.U=ed;this.aa=b;(b=c)||(b=(b=P(a,V))&&"t0"!=b?Wd[pa](b)?"_gat_"+Cc(P(a,Na)):"_gat_"+Cc(b):"_gat");this.Y=b},Rd=function(a,b){var c=b.get(Wb);b.set(Wb,function(b){Pd(a,b);var d=c(b);Qd(a,b);return d});var d=b.get(Xb);b.set(Xb,function(b){var c=d(b);Id(a,b);return c})},Pd=function(a,b){b.get(a.U)||("1"==Ca(a.Y)[0]?b.set(a.U,"",!0):b.set(a.U,""+fe(),!0))},Qd=function(a,b){b.get(a.U)&&zc(a.Y,"1",b.get(Yb),b.get(W),b.get(Na),6E5)},Id=function(a,b){if(b.get(a.U)){var c=new ee,
-d=function(a){$a(a).F&&c.set($a(a).F,b.get(a))};d(hb);d(ib);d(Na);d(Q);d(a.U);c.set($a(ld).F,Td(b));var e=a.aa;c.map(function(a,b){e+=K(a)+"=";e+=K(""+b)+"&"});e+="z="+fe();ta(e);b.set(a.U,"",!0)}},Wd=/^gtm\d+$/;var fd=function(a,b){var c=a.b;if(!c.get("dcLoaded")){Nd(c,29);b=b||{};var d;b[U]&&(d=Cc(b[U]));d=new Jd(c,"https://stats.g.doubleclick.net/r/collect?t=dc&aip=1&_r=3&",d);Rd(d,c);c.set("dcLoaded",!0)}};var Sd=function(a){var b;b=a.get("dcLoaded")?!1:"cookie"!=a.get(ac)?!1:!0;b&&(Nd(a,51),b=new Jd(a),Pd(b,a),Qd(b,a),a.get(b.U)&&(a.set(Md,1,!0),a.set(gd,oc()+"/r/collect",!0)))};var Lc=function(){var a=O.gaGlobal=O.gaGlobal||{};return a.hid=a.hid||fe()};var ad,bd=function(a,b,c){if(!ad){var d;d=M[B][h];var e=O[v],g=/^#?gaso=([^&]*)/;if(e=(d=(d=d&&d[m](g)||e&&e[m](g))?d[1]:Ca("GASO")[0]||"")&&d[m](/^(?:!([-0-9a-z.]{1,40})!)?([-.\w]{10,1200})$/i))zc("GASO",""+d,c,b,a,0),f._udo||(f._udo=b),f._utcp||(f._utcp=c),a=e[1],wa("https://www.google.com/analytics/web/inpage/pub/inpage.js?"+(a?"prefix="+a+"&":"")+fe(),"_gasojs");ad=!0}};var wb=/^(UA|YT|MO|GP)-(\d+)-(\d+)$/,pc=function(a){function b(a,b){d.b[q].set(a,b)}function c(a,c){b(a,c);d.filters.add(a)}var d=this;this.b=new Ya;this.filters=new Ha;b(V,a[V]);b(Na,sa(a[Na]));b(U,a[U]);b(W,a[W]||xa());b(Yb,a[Yb]);b(Zb,a[Zb]);b($b,a[$b]);b(Wc,a[Wc]);b(bc,a[bc]);b(cc,a[cc]);b(Ka,a[Ka]);b(dc,a[dc]);b(ec,a[ec]);b(ac,a[ac]);b(Ad,a[Ad]);b(hb,1);b(ib,"j37");c(Qb,Ma);c(dd,cd);c(Rb,Oa);c(md,vb);c(Sb,nc);c(Uc,Yc);c(Tb,Ja);c(Vb,Ta);c(Vc,Hc);c(zd,yd);c(Ld,Sd);c(Wb,Pa);c(Xb,Sa);c(Cd,Fd(this));
-Jc(this.b,a[Q]);Kc(this.b);this.b.set(jb,Lc());bd(this.b.get(Na),this.b.get(W),this.b.get(Yb))},Jc=function(a,b){if("cookie"==P(a,ac)){hc=!1;var c;b:{var d=Ca(P(a,U));if(d&&!(1>d[y])){c=[];for(var e=0;e<d[y];e++){var g;g=d[e][A](".");var ca=g.shift();("GA1"==ca||"1"==ca)&&1<g[y]?(ca=g.shift()[A]("-"),1==ca[y]&&(ca[1]="1"),ca[0]*=1,ca[1]*=1,g={H:ca,s:g[H](".")}):g=void 0;g&&c[p](g)}if(1==c[y]){J(13);c=c[0].s;break b}if(0==c[y])J(12);else{J(14);d=ic(P(a,W));c=Gc(c,d,0);if(1==c[y]){c=c[0].s;break b}d=
-jc(P(a,Yb));c=Gc(c,d,1);c=c[0]&&c[0].s;break b}}c=void 0}c||(c=P(a,W),d=P(a,$b)||xa(),c=Xc("__utma",d,c),void 0!=c?(J(10),c=c.O[1]+"."+c.O[2]):c=void 0);c&&(a[q].set(Q,c),hc=!0)}c=a.get(cc);if(e=(c=M[B][c?"href":"search"][m]("(?:&|#|\\?)"+K("_ga")[Qc](/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1")+"=([^&#]*)"))&&2==c[y]?c[1]:"")a.get(bc)?(c=e[t]("."),-1==c?J(22):(d=e[F](c+1),"1"!=e[F](0,c)?J(22):(c=d[t]("."),-1==c?J(22):(e=d[F](0,c),c=d[F](c+1),e!=Ic(c,0)&&e!=Ic(c,-1)&&e!=Ic(c,-2)?J(23):(J(11),a[q].set(Q,
-c)))))):J(21);b&&(J(9),a[q].set(Q,K(b)));a.get(Q)||((c=(c=O.gaGlobal&&O.gaGlobal.vid)&&-1!=c[ga](/^(?:utma\.)?\d+\.\d+$/)?c:void 0)?(J(17),a[q].set(Q,c)):(J(8),a[q].set(Q,[fe()^Mc()&2147483647,n.round((new Date)[da]()/1E3)][H]("."))));mc(a)},Kc=function(a){var b=O[oa],c=O.screen,d=M[B];a.set(lb,ya(a.get(ec)));if(d){var e=d.pathname||"";"/"!=e.charAt(0)&&(J(31),e="/"+e);a.set(kb,d[E]+"//"+d[ma]+e+d[ga])}c&&a.set(qb,c.width+"x"+c.height);c&&a.set(pb,c.colorDepth+"-bit");var c=M.documentElement,g=(e=
-M.body)&&e[la]&&e[na],ca=[];c&&c[la]&&c[na]&&("CSS1Compat"===M.compatMode||!g)?ca=[c[la],c[na]]:g&&(ca=[e[la],e[na]]);c=0>=ca[0]||0>=ca[1]?"":ca[H]("x");a.set(rb,c);a.set(tb,fc());a.set(ob,M.characterSet||M.charset);a.set(sb,b&&"function"===typeof b.javaEnabled&&b.javaEnabled()||!1);a.set(nb,(b&&(b.language||b.browserLanguage)||"")[I]());if(d&&a.get(cc)&&(b=M[B][h])){b=b[A](/[?&#]+/);d=[];for(c=0;c<b[y];++c)(D(b[c],"utm_id")||D(b[c],"utm_campaign")||D(b[c],"utm_source")||D(b[c],"utm_medium")||D(b[c],
-"utm_term")||D(b[c],"utm_content")||D(b[c],"gclid")||D(b[c],"dclid")||D(b[c],"gclsrc"))&&d[p](b[c]);0<d[y]&&(b="#"+d[H]("&"),a.set(kb,a.get(kb)+b))}};pc[z].get=function(a){return this.b.get(a)};pc[z].set=function(a,b){this.b.set(a,b)};var qc={pageview:[mb],event:[ub,xb,yb,zb],social:[Bb,Cb,Db],timing:[Mb,Nb,Pb,Ob]};
-pc[z].send=function(a){if(!(1>arguments[y])){var b,c;"string"===typeof arguments[0]?(b=arguments[0],c=[][ha][C](arguments,1)):(b=arguments[0]&&arguments[0][Va],c=arguments);b&&(c=za(qc[b]||[],c),c[Va]=b,this.b.set(c,void 0,!0),this.filters.D(this.b),this.b[q].m={},je(this.b))}};var rc=function(a){if("prerender"==M.visibilityState)return!1;a();return!0};var td=/^(?:(\w+)\.)?(?:(\w+):)?(\w+)$/,sc=function(a){if(ea(a[0]))this.u=a[0];else{var b=td.exec(a[0]);null!=b&&4==b[y]&&(this.c=b[1]||"t0",this.K=b[2]||"",this.C=b[3],this.a=[][ha][C](a,1),this.K||(this.A="create"==this.C,this.i="require"==this.C,this.g="provide"==this.C,this.ba="remove"==this.C),this.i&&(3<=this.a[y]?(this.X=this.a[1],this.W=this.a[2]):this.a[1]&&(qa(this.a[1])?this.X=this.a[1]:this.W=this.a[1])));b=a[1];a=a[2];if(!this.C)throw"abort";if(this.i&&(!qa(b)||""==b))throw"abort";if(this.g&&
-(!qa(b)||""==b||!ea(a)))throw"abort";if(ud(this.c)||ud(this.K))throw"abort";if(this.g&&"t0"!=this.c)throw"abort";}};function ud(a){return 0<=a[t](".")||0<=a[t](":")};var Yd,Zd,$d;Yd=new ee;$d=new ee;Zd={ec:45,ecommerce:46,linkid:47};
-var ae=function(a){function b(a){var b=(a[ma]||"")[A](":")[0][I](),c=(a[E]||"")[I](),c=1*a[ja]||("http:"==c?80:"https:"==c?443:"");a=a.pathname||"";D(a,"/")||(a="/"+a);return[b,""+c,a]}var c=M[u]("a");Pc(c,M[B][Ab]);var d=(c[E]||"")[I](),e=b(c),g=c[ga]||"",ca=d+"//"+e[0]+(e[1]?":"+e[1]:"");D(a,"//")?a=d+a:D(a,"/")?a=ca+a:!a||D(a,"?")?a=ca+e[2]+(a||g):0>a[A]("/")[0][t](":")&&(a=ca+e[2][F](0,e[2].lastIndexOf("/"))+"/"+a);Pc(c,a);d=b(c);return{protocol:(c[E]||"")[I](),host:d[0],port:d[1],path:d[2],G:c[ga]||
-"",url:a||""}};var Z={ga:function(){Z.f=[]}};Z.ga();Z.D=function(a){var b=Z.J[G](Z,arguments),b=Z.f.concat(b);for(Z.f=[];0<b[y]&&!Z.v(b[0])&&!(b.shift(),0<Z.f[y]););Z.f=Z.f.concat(b)};
-Z.J=function(a){for(var b=[],c=0;c<arguments[y];c++)try{var d=new sc(arguments[c]);if(d.g)Yd.set(d.a[0],d.a[1]);else{if(d.i){var e=d,g=e.a[0];if(!ea(Yd.get(g))&&!$d.get(g)){Zd[ra](g)&&J(Zd[g]);var ca=e.X;!ca&&Zd[ra](g)?(J(39),ca=g+".js"):J(43);if(ca){ca&&0<=ca[t]("/")||(ca=(Ba||Ud()?"https:":"http:")+"//www.google-analytics.com/plugins/ua/"+ca);var l=ae(ca),e=void 0;var k=l[E],w=M[B][E],e="https:"==k||k==w?!0:"http:"!=k?!1:"http:"==w;var Xd;if(Xd=e){var e=l,be=ae(M[B][Ab]);if(e.G||0<=e.url[t]("?")||
-0<=e.path[t]("://"))Xd=!1;else if(e.host==be.host&&e[ja]==be[ja])Xd=!0;else{var ce="http:"==e[E]?80:443;Xd="www.google-analytics.com"==e.host&&(e[ja]||ce)==ce&&D(e.path,"/plugins/")?!0:!1}}Xd&&(wa(l.url),$d.set(g,!0))}}}b[p](d)}}catch(de){}return b};
-Z.v=function(a){try{if(a.u)a.u[C](O,N.j("t0"));else{var b=a.c==gb?N:N.j(a.c);if(a.A)"t0"==a.c&&N.create[G](N,a.a);else if(a.ba)N.remove(a.c);else if(b)if(a.i){var c;var d=a.a[0],e=a.W;b==N||b.get(V);var g=Yd.get(d);ea(g)?(b.plugins_=b.plugins_||new ee,b.plugins_.get(d)||b.plugins_.set(d,new g(b,e||{})),c=!0):c=!1;if(!c)return!0}else if(a.K){var ca=a.C,l=a.a,k=b.plugins_.get(a.K);k[ca][G](k,l)}else b[a.C][G](b,a.a)}}catch(w){}};var N=function(a){J(1);Z.D[G](Z,[arguments])};N.h={};N.P=[];N.L=0;N.answer=42;var uc=[Na,W,V];N.create=function(a){var b=za(uc,[][ha][C](arguments));b[V]||(b[V]="t0");var c=""+b[V];if(N.h[c])return N.h[c];b=new pc(b);N.h[c]=b;N.P[p](b);return b};N.remove=function(a){for(var b=0;b<N.P[y];b++)if(N.P[b].get(V)==a){N.P.splice(b,1);N.h[a]=null;break}};N.j=function(a){return N.h[a]};N.getAll=function(){return N.P[ha](0)};
-N.N=function(){"ga"!=gb&&J(49);var a=O[gb];if(!a||42!=a.answer){N.L=a&&a.l;N.loaded=!0;var b=O[gb]=N;X("create",b,b.create);X("remove",b,b.remove);X("getByName",b,b.j,5);X("getAll",b,b.getAll,6);b=pc[z];X("get",b,b.get,7);X("set",b,b.set,4);X("send",b,b.send);b=Ya[z];X("get",b,b.get);X("set",b,b.set);if(!Ud()&&!Ba){a:{for(var b=M.getElementsByTagName("script"),c=0;c<b[y]&&100>c;c++){var d=b[c].src;if(d&&0==d[t]("https://www.google-analytics.com/analytics")){J(33);b=!0;break a}}b=!1}b&&(Ba=!0)}Ud()||
-Ba||!Ed(new Od)||(J(36),Ba=!0);(O.gaplugins=O.gaplugins||{}).Linker=Dc;b=Dc[z];Yd.set("linker",Dc);X("decorate",b,b.ca,20);X("autoLink",b,b.S,25);Yd.set("displayfeatures",fd);Yd.set("adfeatures",fd);a=a&&a.q;ka(a)?Z.D[G](N,a):J(50)}};N.da=function(){for(var a=N.getAll(),b=0;b<a[y];b++)a[b].get(V)};
-(function(){var a=N.N;if(!rc(a)){J(16);var b=!1,c=function(){if(!b&&rc(a)){b=!0;var d=c,e=M;e.removeEventListener?e.removeEventListener("visibilitychange",d,!1):e.detachEvent&&e.detachEvent("onvisibilitychange",d)}};L(M,"visibilitychange",c)}})();function La(a){var b=1,c=0,d;if(a)for(b=0,d=a[y]-1;0<=d;d--)c=a.charCodeAt(d),b=(b<<6&268435455)+c+(c<<14),c=b&266338304,b=0!=c?b^c>>21:b;return b};})(window);
diff --git a/pythonforandroid/recipes/python2/patches/t_files/b0dcca.css b/pythonforandroid/recipes/python2/patches/t_files/b0dcca.css
deleted file mode 100644
index 381a42837..000000000
--- a/pythonforandroid/recipes/python2/patches/t_files/b0dcca.css
+++ /dev/null
@@ -1,191 +0,0 @@
-/* START GENERAL FORMAT */
-body{
- background-color:#96A8C8;
- text-align:center;
- font-size:16px;
- font-variant:small-caps;
- font-family:Lucida,Helvetica,sans-serif;
- font-weight:500;
- text-decoration: none;
- position: absolute;
- left: 50%;
- width: 780px;
- margin-left: -390px;
-}
-a{
- color:#96A8C8;
- text-decoration:none;
- font-weight:800
-}
-a:hover{
- text-decoration:underline
-}
-img{
- border:0
-}
-.box { /*any of the box layouts & white backgrounds*/
- background:white;
- border-style:solid;
- border-width:1.5px;
- border-color:#071419;
- border-radius: 12px;
- -moz-border-radius: 12px;
-}
-/* END GENERAL FORMAT */
-/* START UPPER LAYOUT */
-#topContainer{
- width:780px;
- position:relative;
- overflow:hidden;
-}
-#topLeft{
- width:166px;
- float:left;
- position:relative;
- text-align:left;
- padding: 17px;
-}
-#topLeft ul {
- margin: 0;
- list-style-type: none;
-}
-#topLeft a {
- color: #282B30;
- font-size: 21px;
- font-weight: 800;
-}
-#topLeft a:hover {
- text-decoration: underline;
-}
-#bgLeft {
- float: left;
- left:0;
- width: 200px;
- bottom:0;
- top: 0px;
-}
-#topRight {
- width:560px;
- padding-top:15px;
- padding-bottom:15px;
- padding-left:15px;
- float:right;
- position:relative;
- text-align:left;
- line-height: 150%;
-}
-#masthead {
- display: block;
-}
-#slogan {
- padding: 20px;
- display: inline-block;
- font-size: 20px;
- font-style: italic;
- font-weight: 800;
- line-height: 120%;
- vertical-align: top;
-}
-#bgRight {
- right: 0;
- float: right;
- width: 572px;
- bottom:0;
- top: 0px;
-}
-.bg { /* necessary for positioning box layouts for bg */
- position:absolute;
- z-index:-1;
-}
-/* END UPPER LAYOUT */
-
-/*START MIDDLE */
-#middleContainer {
- width:780px;
- margin: 5px auto;
- padding: 10px 0;
-}
-
-#ctitle {
- margin: 10px;
- font-size: 21px;
- font-weight: 800;
-}
-
-ul.comicNav {
- padding:0;
- list-style-type:none;
-}
-ul.comicNav li {
- display: inline;
-}
-
-ul.comicNav li a {
- /*background-color: #6E6E6E;*/
- background-color:#6E7B91;
- color: #FFF;
- border: 1.5px solid #333;
- font-size: 16px;
- font-weight: 600;
- padding: 1.5px 12px;
- margin: 0 4px;
- text-decoration: none;
- border-radius: 3px;
- -moz-border-radius: 3px;
- -webkit-border-radius: 3px;
- box-shadow: 0 0 5px 0 gray;
- -moz-box-shadow: 0 0 5px 0 gray;
- -webkit-box-shadow: 0 0 5px 0 gray;
-}
-
-
-ul.comicNav a:hover, ul.comicNav a:focus {
- background-color: #FFF;
- color: #6E7B91;
- box-shadow: none;
- -moz-box-shadow: none;
- -webkit-box-shadow: none;
-}
-
-.comicInfo {
- font-size:12px;
- font-style:italic;
- font-weight:800;
-}
-#bottom {
- margin-top:5px;
- padding:25px 15px;
- width:750px;
-}
-#comicLinks {
- display: block;
- margin: auto;
- width: 300px;
-}
-#footnote {
- clear: both;
- font-size: 6px;
- font-style: italic;
- font-variant: small-caps;
- font-weight: 800;
- margin: 0;
- padding: 0;
-}
-#licenseText {
- display: block;
- margin: auto;
- width: 410px;
-}
-
-#transcript {display: none;}
-
-#middleContainer { position:relative; left:50%; margin-left:-390px; }
-#comic .comic { position:absolute; }
-#comic .panel, #comic .cover, #comic .panel img { position:absolute; }
-#comic .cover { z-index:10; }
-#comic table { margin: auto; }
-
-@font-face {
- font-family: 'xkcd-Regular';
- src: url('//xkcd.com/fonts/xkcd-Regular.eot?') format('eot'), url('//xkcd.com/fonts/xkcd-Regular.otf') format('opentype');
-}
diff --git a/pythonforandroid/recipes/python2/patches/t_files/jquery.js b/pythonforandroid/recipes/python2/patches/t_files/jquery.js
deleted file mode 100644
index 73f33fb3a..000000000
--- a/pythonforandroid/recipes/python2/patches/t_files/jquery.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
-!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="<select t=''><option selected=''></option></select>",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=jb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=kb(b);function nb(){}nb.prototype=d.filters=d.pseudos,d.setFilters=new nb;function ob(a,b){var c,e,f,g,h,i,j,k=x[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=Q.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?db.error(a):x(a,i).slice(0)}function pb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f
-}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=n._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var T=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,U=["Top","Right","Bottom","Left"],V=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},W=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a>",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav></:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=$.test(e)?this.mouseHooks:Z.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||z),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||z,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==db()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===db()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return n.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=z.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===L&&(a[d]=null),a.detachEvent(d,c))},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&(a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault())?bb:cb):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:cb,isPropagationStopped:cb,isImmediatePropagationStopped:cb,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=bb,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=bb,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),l.submitBubbles||(n.event.special.submit={setup:function(){return n.nodeName(this,"form")?!1:void n.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=n.nodeName(b,"input")||n.nodeName(b,"button")?b.form:void 0;c&&!n._data(c,"submitBubbles")&&(n.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),n._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&n.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return n.nodeName(this,"form")?!1:void n.event.remove(this,"._submit")}}),l.changeBubbles||(n.event.special.change={setup:function(){return Y.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(n.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),n.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),n.event.simulate("change",this,a,!0)})),!1):void n.event.add(this,"beforeactivate._change",function(a){var b=a.target;Y.test(b.nodeName)&&!n._data(b,"changeBubbles")&&(n.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||n.event.simulate("change",this.parentNode,a,!0)}),n._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return n.event.remove(this,"._change"),!Y.test(this.nodeName)}}),l.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=n._data(d,b);e||d.addEventListener(a,c,!0),n._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=n._data(d,b)-1;e?n._data(d,b,e):(d.removeEventListener(a,c,!0),n._removeData(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=cb;else if(!d)return this;return 1===e&&(g=d,d=function(a){return n().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=cb),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});function eb(a){var b=fb.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var fb="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gb=/ jQuery\d+="(?:null|\d+)"/g,hb=new RegExp("<(?:"+fb+")[\\s/>]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/<tbody/i,mb=/<|&#?\w+;/,nb=/<(?:script|style|link)/i,ob=/checked\s*(?:[^=]|=\s*.checked.)/i,pb=/^$|\/(?:java|ecma)script/i,qb=/^true\/(.*)/,rb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,sb={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:l.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1></$2>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?"<table>"!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Db[0].contentWindow||Db[0].contentDocument).document,b.write(),b.close(),c=Fb(a,b),Db.detach()),Eb[a]=c),c}!function(){var a,b,c=z.createElement("div"),d="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;padding:0;margin:0;border:0";c.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",a=c.getElementsByTagName("a")[0],a.style.cssText="float:left;opacity:.5",l.opacity=/^0.5/.test(a.style.opacity),l.cssFloat=!!a.style.cssFloat,c.style.backgroundClip="content-box",c.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===c.style.backgroundClip,a=c=null,l.shrinkWrapBlocks=function(){var a,c,e,f;if(null==b){if(a=z.getElementsByTagName("body")[0],!a)return;f="border:0;width:0;height:0;position:absolute;top:0;left:-9999px",c=z.createElement("div"),e=z.createElement("div"),a.appendChild(c).appendChild(e),b=!1,typeof e.style.zoom!==L&&(e.style.cssText=d+";width:1px;padding:1px;zoom:1",e.innerHTML="<div></div>",e.firstChild.style.width="5px",b=3!==e.offsetWidth),a.removeChild(c),a=c=e=null}return b}}();var Hb=/^margin/,Ib=new RegExp("^("+T+")(?!px)[a-z%]+$","i"),Jb,Kb,Lb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Jb=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)},Kb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Jb(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),Ib.test(g)&&Hb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):z.documentElement.currentStyle&&(Jb=function(a){return a.currentStyle},Kb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Jb(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Ib.test(g)&&!Lb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Mb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h=z.createElement("div"),i="border:0;width:0;height:0;position:absolute;top:0;left:-9999px",j="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;padding:0;margin:0;border:0";h.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",b=h.getElementsByTagName("a")[0],b.style.cssText="float:left;opacity:.5",l.opacity=/^0.5/.test(b.style.opacity),l.cssFloat=!!b.style.cssFloat,h.style.backgroundClip="content-box",h.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===h.style.backgroundClip,b=h=null,n.extend(l,{reliableHiddenOffsets:function(){if(null!=c)return c;var a,b,d,e=z.createElement("div"),f=z.getElementsByTagName("body")[0];if(f)return e.setAttribute("className","t"),e.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",a=z.createElement("div"),a.style.cssText=i,f.appendChild(a).appendChild(e),e.innerHTML="<table><tr><td></td><td>t</td></tr></table>",b=e.getElementsByTagName("td"),b[0].style.cssText="padding:0;margin:0;border:0;display:none",d=0===b[0].offsetHeight,b[0].style.display="",b[1].style.display="none",c=d&&0===b[0].offsetHeight,f.removeChild(a),e=f=null,c},boxSizing:function(){return null==d&&k(),d},boxSizingReliable:function(){return null==e&&k(),e},pixelPosition:function(){return null==f&&k(),f},reliableMarginRight:function(){var b,c,d,e;if(null==g&&a.getComputedStyle){if(b=z.getElementsByTagName("body")[0],!b)return;c=z.createElement("div"),d=z.createElement("div"),c.style.cssText=i,b.appendChild(c).appendChild(d),e=d.appendChild(z.createElement("div")),e.style.cssText=d.style.cssText=j,e.style.marginRight=e.style.width="0",d.style.width="1px",g=!parseFloat((a.getComputedStyle(e,null)||{}).marginRight),b.removeChild(c)}return g}});function k(){var b,c,h=z.getElementsByTagName("body")[0];h&&(b=z.createElement("div"),c=z.createElement("div"),b.style.cssText=i,h.appendChild(b).appendChild(c),c.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;display:block;padding:1px;border:1px;width:4px;margin-top:1%;top:1%",n.swap(h,null!=h.style.zoom?{zoom:1}:{},function(){d=4===c.offsetWidth}),e=!0,f=!1,g=!0,a.getComputedStyle&&(f="1%"!==(a.getComputedStyle(c,null)||{}).top,e="4px"===(a.getComputedStyle(c,null)||{width:"4px"}).width),h.removeChild(b),c=h=null)}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Nb=/alpha\([^)]*\)/i,Ob=/opacity\s*=\s*([^)]*)/,Pb=/^(none|table(?!-c[ea]).+)/,Qb=new RegExp("^("+T+")(.*)$","i"),Rb=new RegExp("^([+-])=("+T+")","i"),Sb={position:"absolute",visibility:"hidden",display:"block"},Tb={letterSpacing:0,fontWeight:400},Ub=["Webkit","O","Moz","ms"];function Vb(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Ub.length;while(e--)if(b=Ub[e]+c,b in a)return b;return d}function Wb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=n._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&V(d)&&(f[g]=n._data(d,"olddisplay",Gb(d.nodeName)))):f[g]||(e=V(d),(c&&"none"!==c||!e)&&n._data(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Xb(a,b,c){var d=Qb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Yb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+U[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+U[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+U[f]+"Width",!0,e))):(g+=n.css(a,"padding"+U[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+U[f]+"Width",!0,e)));return g}function Zb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Jb(a),g=l.boxSizing()&&"border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Kb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Ib.test(e))return e;d=g&&(l.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Yb(a,b,c||(g?"border":"content"),d,f)+"px"}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Kb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":l.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;if(b=n.cssProps[h]||(n.cssProps[h]=Vb(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Rb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]="",i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Vb(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Kb(a,b,d)),"normal"===f&&b in Tb&&(f=Tb[b]),""===c||c?(e=parseFloat(f),c===!0||n.isNumeric(e)?e||0:f):f}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?0===a.offsetWidth&&Pb.test(n.css(a,"display"))?n.swap(a,Sb,function(){return Zb(a,b,d)}):Zb(a,b,d):void 0},set:function(a,c,d){var e=d&&Jb(a);return Xb(a,c,d?Yb(a,b,d,l.boxSizing()&&"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),l.opacity||(n.cssHooks.opacity={get:function(a,b){return Ob.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=n.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===n.trim(f.replace(Nb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Nb.test(f)?f.replace(Nb,e):f+" "+e)}}),n.cssHooks.marginRight=Mb(l.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},Kb,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+U[d]+b]=f[d]||f[d-2]||f[0];return e}},Hb.test(a)||(n.cssHooks[a+b].set=Xb)}),n.fn.extend({css:function(a,b){return W(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Jb(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)
-},a,b,arguments.length>1)},show:function(){return Wb(this,!0)},hide:function(){return Wb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){V(this)?n(this).show():n(this).hide()})}});function $b(a,b,c,d,e){return new $b.prototype.init(a,b,c,d,e)}n.Tween=$b,$b.prototype={constructor:$b,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=$b.propHooks[this.prop];return a&&a.get?a.get(this):$b.propHooks._default.get(this)},run:function(a){var b,c=$b.propHooks[this.prop];return this.pos=b=this.options.duration?n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):$b.propHooks._default.set(this),this}},$b.prototype.init.prototype=$b.prototype,$b.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},$b.propHooks.scrollTop=$b.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=$b.prototype.init,n.fx.step={};var _b,ac,bc=/^(?:toggle|show|hide)$/,cc=new RegExp("^(?:([+-])=|)("+T+")([a-z%]*)$","i"),dc=/queueHooks$/,ec=[jc],fc={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=cc.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&cc.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function gc(){return setTimeout(function(){_b=void 0}),_b=n.now()}function hc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=U[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function ic(a,b,c){for(var d,e=(fc[b]||[]).concat(fc["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function jc(a,b,c){var d,e,f,g,h,i,j,k,m=this,o={},p=a.style,q=a.nodeType&&V(a),r=n._data(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,m.always(function(){m.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=n.css(a,"display"),k=Gb(a.nodeName),"none"===j&&(j=k),"inline"===j&&"none"===n.css(a,"float")&&(l.inlineBlockNeedsLayout&&"inline"!==k?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",l.shrinkWrapBlocks()||m.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],bc.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||n.style(a,d)}if(!n.isEmptyObject(o)){r?"hidden"in r&&(q=r.hidden):r=n._data(a,"fxshow",{}),f&&(r.hidden=!q),q?n(a).show():m.done(function(){n(a).hide()}),m.done(function(){var b;n._removeData(a,"fxshow");for(b in o)n.style(a,b,o[b])});for(d in o)g=ic(q?r[d]:0,d,m),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function kc(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function lc(a,b,c){var d,e,f=0,g=ec.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=_b||gc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:_b||gc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(kc(k,j.opts.specialEasing);g>f;f++)if(d=ec[f].call(j,a,k,j.opts))return d;return n.map(k,ic,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(lc,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],fc[c]=fc[c]||[],fc[c].unshift(b)},prefilter:function(a,b){b?ec.unshift(a):ec.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(V).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=lc(this,n.extend({},a),f);(e||n._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=n._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&dc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=n._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(hc(b,!0),a,d,e)}}),n.each({slideDown:hc("show"),slideUp:hc("hide"),slideToggle:hc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=n.timers,c=0;for(_b=n.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||n.fx.stop(),_b=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){ac||(ac=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(ac),ac=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e=z.createElement("div");e.setAttribute("className","t"),e.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",a=e.getElementsByTagName("a")[0],c=z.createElement("select"),d=c.appendChild(z.createElement("option")),b=e.getElementsByTagName("input")[0],a.style.cssText="top:1px",l.getSetAttribute="t"!==e.className,l.style=/top/.test(a.getAttribute("style")),l.hrefNormalized="/a"===a.getAttribute("href"),l.checkOn=!!b.value,l.optSelected=d.selected,l.enctype=!!z.createElement("form").enctype,c.disabled=!0,l.optDisabled=!d.disabled,b=z.createElement("input"),b.setAttribute("value",""),l.input=""===b.getAttribute("value"),b.value="t",b.setAttribute("type","radio"),l.radioValue="t"===b.value,a=b=c=d=e=null}();var mc=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(mc,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.text(a)}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(l.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)if(d=e[g],n.inArray(n.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var nc,oc,pc=n.expr.attrHandle,qc=/^(?:checked|selected)$/i,rc=l.getSetAttribute,sc=l.input;n.fn.extend({attr:function(a,b){return W(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===L?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?oc:nc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(F);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)?sc&&rc||!qc.test(c)?a[d]=!1:a[n.camelCase("default-"+c)]=a[d]=!1:n.attr(a,c,""),a.removeAttribute(rc?c:d)},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),oc={set:function(a,b,c){return b===!1?n.removeAttr(a,c):sc&&rc||!qc.test(c)?a.setAttribute(!rc&&n.propFix[c]||c,c):a[n.camelCase("default-"+c)]=a[c]=!0,c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=pc[b]||n.find.attr;pc[b]=sc&&rc||!qc.test(b)?function(a,b,d){var e,f;return d||(f=pc[b],pc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,pc[b]=f),e}:function(a,b,c){return c?void 0:a[n.camelCase("default-"+b)]?b.toLowerCase():null}}),sc&&rc||(n.attrHooks.value={set:function(a,b,c){return n.nodeName(a,"input")?void(a.defaultValue=b):nc&&nc.set(a,b,c)}}),rc||(nc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},pc.id=pc.name=pc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},n.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:nc.set},n.attrHooks.contenteditable={set:function(a,b,c){nc.set(a,""===b?!1:b,c)}},n.each(["width","height"],function(a,b){n.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),l.style||(n.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var tc=/^(?:input|select|textarea|button|object)$/i,uc=/^(?:a|area)$/i;n.fn.extend({prop:function(a,b){return W(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return a=n.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):tc.test(a.nodeName)||uc.test(a.nodeName)&&a.href?0:-1}}}}),l.hrefNormalized||n.each(["href","src"],function(a,b){n.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this}),l.enctype||(n.propFix.enctype="encoding");var vc=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(F)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(vc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(F)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(vc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(F)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===L||"boolean"===c)&&(this.className&&n._data(this,"__className__",this.className),this.className=this.className||a===!1?"":n._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(vc," ").indexOf(b)>=0)return!0;return!1}}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var wc=n.now(),xc=/\?/,yc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;n.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=n.trim(b+"");return e&&!n.trim(e.replace(yc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():n.error("Invalid JSON: "+b)},n.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||n.error("Invalid XML: "+b),c};var zc,Ac,Bc=/#.*$/,Cc=/([?&])_=[^&]*/,Dc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Ec=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Fc=/^(?:GET|HEAD)$/,Gc=/^\/\//,Hc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Ic={},Jc={},Kc="*/".concat("*");try{Ac=location.href}catch(Lc){Ac=z.createElement("a"),Ac.href="",Ac=Ac.href}zc=Hc.exec(Ac.toLowerCase())||[];function Mc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(F)||[];if(n.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nc(a,b,c,d){var e={},f=a===Jc;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Oc(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&n.extend(!0,a,c),a}function Pc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Qc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ac,type:"GET",isLocal:Ec.test(zc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Oc(Oc(a,n.ajaxSettings),b):Oc(n.ajaxSettings,a)},ajaxPrefilter:Mc(Ic),ajaxTransport:Mc(Jc),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Dc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||Ac)+"").replace(Bc,"").replace(Gc,zc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(F)||[""],null==k.crossDomain&&(c=Hc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===zc[1]&&c[2]===zc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(zc[3]||("http:"===zc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),Nc(Ic,k,b,v),2===t)return v;h=k.global,h&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Fc.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(xc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Cc.test(e)?e.replace(Cc,"$1_="+wc++):e+(xc.test(e)?"&":"?")+"_="+wc++)),k.ifModified&&(n.lastModified[e]&&v.setRequestHeader("If-Modified-Since",n.lastModified[e]),n.etag[e]&&v.setRequestHeader("If-None-Match",n.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Kc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Nc(Jc,k,b,v)){v.readyState=1,h&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Pc(k,v,c)),u=Qc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(n.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){if(n.isFunction(a))return this.each(function(b){n(this).wrapAll(a.call(this,b))});if(this[0]){var b=n(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!l.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||n.css(a,"display"))},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var Rc=/%20/g,Sc=/\[\]$/,Tc=/\r?\n/g,Uc=/^(?:submit|button|image|reset|file)$/i,Vc=/^(?:input|select|textarea|keygen)/i;function Wc(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||Sc.test(a)?d(a,e):Wc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Wc(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Wc(c,a[c],b,e);return d.join("&").replace(Rc,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Vc.test(this.nodeName)&&!Uc.test(a)&&(this.checked||!X.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(Tc,"\r\n")}}):{name:b.name,value:c.replace(Tc,"\r\n")}}).get()}}),n.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&$c()||_c()}:$c;var Xc=0,Yc={},Zc=n.ajaxSettings.xhr();a.ActiveXObject&&n(a).on("unload",function(){for(var a in Yc)Yc[a](void 0,!0)}),l.cors=!!Zc&&"withCredentials"in Zc,Zc=l.ajax=!!Zc,Zc&&n.ajaxTransport(function(a){if(!a.crossDomain||l.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Xc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Yc[g],b=void 0,f.onreadystatechange=n.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Yc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function $c(){try{return new a.XMLHttpRequest}catch(b){}}function _c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=z.head||n("head")[0]||z.documentElement;return{send:function(d,e){b=z.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var ad=[],bd=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=ad.pop()||n.expando+"_"+wc++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(bd.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&bd.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(bd,"$1"+e):b.jsonp!==!1&&(b.url+=(xc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,ad.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||z;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var cd=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&cd)return cd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=a.slice(h,a.length),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&n.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var dd=a.document.documentElement;function ed(a){return n.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&n.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,n.contains(b,e)?(typeof e.getBoundingClientRect!==L&&(d=e.getBoundingClientRect()),c=ed(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===n.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(c=a.offset()),c.top+=n.css(a[0],"borderTopWidth",!0),c.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-n.css(d,"marginTop",!0),left:b.left-c.left-n.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||dd;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||dd})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);n.fn[a]=function(d){return W(this,function(a,d,e){var f=ed(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?n(f).scrollLeft():e,c?e:n(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=Mb(l.pixelPosition,function(a,c){return c?(c=Kb(a,b),Ib.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return W(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var fd=a.jQuery,gd=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=gd),b&&a.jQuery===n&&(a.jQuery=fd),n},typeof b===L&&(a.jQuery=a.$=n),n});
diff --git a/pythonforandroid/recipes/python2/patches/t_files/json2.js b/pythonforandroid/recipes/python2/patches/t_files/json2.js
deleted file mode 100644
index 8ede40a67..000000000
--- a/pythonforandroid/recipes/python2/patches/t_files/json2.js
+++ /dev/null
@@ -1 +0,0 @@
-typeof JSON!="object"&&(JSON={}),function(){"use strict";function f(e){return e<10?"0"+e:e}function quote(e){return escapable.lastIndex=0,escapable.test(e)?'"'+e.replace(escapable,function(e){var t=meta[e];return typeof t=="string"?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+e+'"'}function str(e,t){var n,r,i,s,o=gap,u,a=t[e];a&&typeof a=="object"&&typeof a.toJSON=="function"&&(a=a.toJSON(e)),typeof rep=="function"&&(a=rep.call(t,e,a));switch(typeof a){case"string":return quote(a);case"number":return isFinite(a)?String(a):"null";case"boolean":case"null":return String(a);case"object":if(!a)return"null";gap+=indent,u=[];if(Object.prototype.toString.apply(a)==="[object Array]"){s=a.length;for(n=0;n<s;n+=1)u[n]=str(n,a)||"null";return i=u.length===0?"[]":gap?"[\n"+gap+u.join(",\n"+gap)+"\n"+o+"]":"["+u.join(",")+"]",gap=o,i}if(rep&&typeof rep=="object"){s=rep.length;for(n=0;n<s;n+=1)typeof rep[n]=="string"&&(r=rep[n],i=str(r,a),i&&u.push(quote(r)+(gap?": ":":")+i))}else for(r in a)Object.prototype.hasOwnProperty.call(a,r)&&(i=str(r,a),i&&u.push(quote(r)+(gap?": ":":")+i));return i=u.length===0?"{}":gap?"{\n"+gap+u.join(",\n"+gap)+"\n"+o+"}":"{"+u.join(",")+"}",gap=o,i}}typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;typeof JSON.stringify!="function"&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(e,t,n){var r;gap="",indent="";if(typeof n=="number")for(r=0;r<n;r+=1)indent+=" ";else typeof n=="string"&&(indent=n);rep=t;if(!t||typeof t=="function"||typeof t=="object"&&typeof t.length=="number")return str("",{"":e});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(e,t){var n,r,i=e[t];if(i&&typeof i=="object")for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(r=walk(i,n),r!==undefined?i[n]=r:delete i[n]);return reviver.call(e,t,i)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(e){return"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),typeof reviver=="function"?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}();
\ No newline at end of file
diff --git a/pythonforandroid/recipes/python2/patches/t_files/legal_hacks.png b/pythonforandroid/recipes/python2/patches/t_files/legal_hacks.png
deleted file mode 100644
index e6aa89b05..000000000
Binary files a/pythonforandroid/recipes/python2/patches/t_files/legal_hacks.png and /dev/null differ
diff --git a/pythonforandroid/recipes/python2/patches/t_files/te-news.png b/pythonforandroid/recipes/python2/patches/t_files/te-news.png
deleted file mode 100644
index a7d10f0ef..000000000
Binary files a/pythonforandroid/recipes/python2/patches/t_files/te-news.png and /dev/null differ
diff --git a/pythonforandroid/recipes/python2/patches/t_files/terrible_small_logo.png b/pythonforandroid/recipes/python2/patches/t_files/terrible_small_logo.png
deleted file mode 100644
index e7f52a27c..000000000
Binary files a/pythonforandroid/recipes/python2/patches/t_files/terrible_small_logo.png and /dev/null differ
diff --git a/pythonforandroid/recipes/python2/patches/verbose-compilation.patch b/pythonforandroid/recipes/python2/patches/verbose-compilation.patch
deleted file mode 100644
index 00c89f906..000000000
--- a/pythonforandroid/recipes/python2/patches/verbose-compilation.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- Python-2.7.2/Makefile.pre.in.orig 2012-01-07 18:25:42.097075564 +0100
-+++ Python-2.7.2/Makefile.pre.in 2012-01-07 18:26:03.289074810 +0100
-@@ -410,8 +410,8 @@
- # Build the shared modules
- sharedmods: $(BUILDPYTHON)
- @case $$MAKEFLAGS in \
-- *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py -q build;; \
-- *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build;; \
-+ *s*) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build -v;; \
-+ *) $(RUNSHARED) CC='$(CC)' LDSHARED='$(BLDSHARED)' OPT='$(OPT)' PYTHONXCPREFIX='$(DESTDIR)$(prefix)' $(HOSTPYTHON) -E $(srcdir)/setup.py build -v;; \
- esac
-
- # Build static library
diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py
new file mode 100644
index 000000000..3a9575148
--- /dev/null
+++ b/pythonforandroid/recipes/python3/__init__.py
@@ -0,0 +1,445 @@
+import glob
+import sh
+import subprocess
+
+from os import environ, utime
+from os.path import dirname, exists, join
+from pathlib import Path
+import shutil
+
+from pythonforandroid.logger import info, warning, shprint
+from pythonforandroid.patching import version_starts_with
+from pythonforandroid.recipe import Recipe, TargetPythonRecipe
+from pythonforandroid.util import (
+ current_directory,
+ ensure_dir,
+ walk_valid_filens,
+ BuildInterruptingException,
+)
+
+NDK_API_LOWER_THAN_SUPPORTED_MESSAGE = (
+ 'Target ndk-api is {ndk_api}, '
+ 'but the python3 recipe supports only {min_ndk_api}+'
+)
+
+
+class Python3Recipe(TargetPythonRecipe):
+ '''
+ The python3's recipe
+ ^^^^^^^^^^^^^^^^^^^^
+
+ The python 3 recipe can be built with some extra python modules, but to do
+ so, we need some libraries. By default, we ship the python3 recipe with
+ some common libraries, defined in ``depends``. We also support some optional
+ libraries, which are less common that the ones defined in ``depends``, so
+ we added them as optional dependencies (``opt_depends``).
+
+ Below you have a relationship between the python modules and the recipe
+ libraries::
+
+ - _ctypes: you must add the recipe for ``libffi``.
+ - _sqlite3: you must add the recipe for ``sqlite3``.
+ - _ssl: you must add the recipe for ``openssl``.
+ - _bz2: you must add the recipe for ``libbz2`` (optional).
+ - _lzma: you must add the recipe for ``liblzma`` (optional).
+
+ .. note:: This recipe can be built only against API 21+.
+
+ .. versionchanged:: 2019.10.06.post0
+ - Refactored from deleted class ``python.GuestPythonRecipe`` into here
+ - Added optional dependencies: :mod:`~pythonforandroid.recipes.libbz2`
+ and :mod:`~pythonforandroid.recipes.liblzma`
+
+ .. versionchanged:: 0.6.0
+ Refactored into class
+ :class:`~pythonforandroid.python.GuestPythonRecipe`
+ '''
+
+ version = '3.11.5'
+ url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz'
+ name = 'python3'
+
+ patches = [
+ 'patches/pyconfig_detection.patch',
+ 'patches/reproducible-buildinfo.diff',
+
+ # Python 3.7.1
+ ('patches/py3.7.1_fix-ctypes-util-find-library.patch', version_starts_with("3.7")),
+ ('patches/py3.7.1_fix-zlib-version.patch', version_starts_with("3.7")),
+
+ # Python 3.8.1 & 3.9.X
+ ('patches/py3.8.1.patch', version_starts_with("3.8")),
+ ('patches/py3.8.1.patch', version_starts_with("3.9")),
+ ('patches/py3.8.1.patch', version_starts_with("3.10")),
+ ('patches/cpython-311-ctypes-find-library.patch', version_starts_with("3.11")),
+ ]
+
+ if shutil.which('lld') is not None:
+ patches += [
+ ("patches/py3.7.1_fix_cortex_a8.patch", version_starts_with("3.7")),
+ ("patches/py3.8.1_fix_cortex_a8.patch", version_starts_with("3.8")),
+ ("patches/py3.8.1_fix_cortex_a8.patch", version_starts_with("3.9")),
+ ("patches/py3.8.1_fix_cortex_a8.patch", version_starts_with("3.10")),
+ ("patches/py3.8.1_fix_cortex_a8.patch", version_starts_with("3.11")),
+ ]
+
+ depends = ['hostpython3', 'sqlite3', 'openssl', 'libffi']
+ # those optional depends allow us to build python compression modules:
+ # - _bz2.so
+ # - _lzma.so
+ opt_depends = ['libbz2', 'liblzma']
+ '''The optional libraries which we would like to get our python linked'''
+
+ configure_args = (
+ '--host={android_host}',
+ '--build={android_build}',
+ '--enable-shared',
+ '--enable-ipv6',
+ 'ac_cv_file__dev_ptmx=yes',
+ 'ac_cv_file__dev_ptc=no',
+ '--without-ensurepip',
+ 'ac_cv_little_endian_double=yes',
+ 'ac_cv_header_sys_eventfd_h=no',
+ '--prefix={prefix}',
+ '--exec-prefix={exec_prefix}',
+ '--enable-loadable-sqlite-extensions'
+ )
+
+ if version_starts_with("3.11"):
+ configure_args += ('--with-build-python={python_host_bin}',)
+
+ '''The configure arguments needed to build the python recipe. Those are
+ used in method :meth:`build_arch` (if not overwritten like python3's
+ recipe does).
+ '''
+
+ MIN_NDK_API = 21
+ '''Sets the minimal ndk api number needed to use the recipe.
+
+ .. warning:: This recipe can be built only against API 21+, so it means
+ that any class which inherits from class:`GuestPythonRecipe` will have
+ this limitation.
+ '''
+
+ stdlib_dir_blacklist = {
+ '__pycache__',
+ 'test',
+ 'tests',
+ 'lib2to3',
+ 'ensurepip',
+ 'idlelib',
+ 'tkinter',
+ }
+ '''The directories that we want to omit for our python bundle'''
+
+ stdlib_filen_blacklist = [
+ '*.py',
+ '*.exe',
+ '*.whl',
+ ]
+ '''The file extensions that we want to blacklist for our python bundle'''
+
+ site_packages_dir_blacklist = {
+ '__pycache__',
+ 'tests'
+ }
+ '''The directories from site packages dir that we don't want to be included
+ in our python bundle.'''
+
+ site_packages_filen_blacklist = [
+ '*.py'
+ ]
+ '''The file extensions from site packages dir that we don't want to be
+ included in our python bundle.'''
+
+ compiled_extension = '.pyc'
+ '''the default extension for compiled python files.
+
+ .. note:: the default extension for compiled python files has been .pyo for
+ python 2.x-3.4 but as of Python 3.5, the .pyo filename extension is no
+ longer used and has been removed in favour of extension .pyc
+ '''
+
+ def __init__(self, *args, **kwargs):
+ self._ctx = None
+ super().__init__(*args, **kwargs)
+
+ @property
+ def _libpython(self):
+ '''return the python's library name (with extension)'''
+ return 'libpython{link_version}.so'.format(
+ link_version=self.link_version
+ )
+
+ @property
+ def link_version(self):
+ '''return the python's library link version e.g. 3.7m, 3.8'''
+ major, minor = self.major_minor_version_string.split('.')
+ flags = ''
+ if major == '3' and int(minor) < 8:
+ flags += 'm'
+ return '{major}.{minor}{flags}'.format(
+ major=major,
+ minor=minor,
+ flags=flags
+ )
+
+ def include_root(self, arch_name):
+ return join(self.get_build_dir(arch_name), 'Include')
+
+ def link_root(self, arch_name):
+ return join(self.get_build_dir(arch_name), 'android-build')
+
+ def should_build(self, arch):
+ return not Path(self.link_root(arch.arch), self._libpython).is_file()
+
+ def prebuild_arch(self, arch):
+ super().prebuild_arch(arch)
+ self.ctx.python_recipe = self
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch)
+ env['HOSTARCH'] = arch.command_prefix
+
+ env['CC'] = arch.get_clang_exe(with_target=True)
+
+ env['PATH'] = (
+ '{hostpython_dir}:{old_path}').format(
+ hostpython_dir=self.get_recipe(
+ 'host' + self.name, self.ctx).get_path_to_python(),
+ old_path=env['PATH'])
+
+ env['CFLAGS'] = ' '.join(
+ [
+ '-fPIC',
+ '-DANDROID'
+ ]
+ )
+
+ env['LDFLAGS'] = env.get('LDFLAGS', '')
+ if shutil.which('lld') is not None:
+ # Note: The -L. is to fix a bug in python 3.7.
+ # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234409
+ env['LDFLAGS'] += ' -L. -fuse-ld=lld'
+ else:
+ warning('lld not found, linking without it. '
+ 'Consider installing lld if linker errors occur.')
+
+ return env
+
+ def set_libs_flags(self, env, arch):
+ '''Takes care to properly link libraries with python depending on our
+ requirements and the attribute :attr:`opt_depends`.
+ '''
+ def add_flags(include_flags, link_dirs, link_libs):
+ env['CPPFLAGS'] = env.get('CPPFLAGS', '') + include_flags
+ env['LDFLAGS'] = env.get('LDFLAGS', '') + link_dirs
+ env['LIBS'] = env.get('LIBS', '') + link_libs
+
+ if 'sqlite3' in self.ctx.recipe_build_order:
+ info('Activating flags for sqlite3')
+ recipe = Recipe.get_recipe('sqlite3', self.ctx)
+ add_flags(' -I' + recipe.get_build_dir(arch.arch),
+ ' -L' + recipe.get_lib_dir(arch), ' -lsqlite3')
+
+ if 'libffi' in self.ctx.recipe_build_order:
+ info('Activating flags for libffi')
+ recipe = Recipe.get_recipe('libffi', self.ctx)
+ # In order to force the correct linkage for our libffi library, we
+ # set the following variable to point where is our libffi.pc file,
+ # because the python build system uses pkg-config to configure it.
+ env['PKG_CONFIG_PATH'] = recipe.get_build_dir(arch.arch)
+ add_flags(' -I' + ' -I'.join(recipe.get_include_dirs(arch)),
+ ' -L' + join(recipe.get_build_dir(arch.arch), '.libs'),
+ ' -lffi')
+
+ if 'openssl' in self.ctx.recipe_build_order:
+ info('Activating flags for openssl')
+ recipe = Recipe.get_recipe('openssl', self.ctx)
+ self.configure_args += \
+ ('--with-openssl=' + recipe.get_build_dir(arch.arch),)
+ add_flags(recipe.include_flags(arch),
+ recipe.link_dirs_flags(arch), recipe.link_libs_flags())
+
+ for library_name in {'libbz2', 'liblzma'}:
+ if library_name in self.ctx.recipe_build_order:
+ info(f'Activating flags for {library_name}')
+ recipe = Recipe.get_recipe(library_name, self.ctx)
+ add_flags(recipe.get_library_includes(arch),
+ recipe.get_library_ldflags(arch),
+ recipe.get_library_libs_flag())
+
+ # python build system contains hardcoded zlib version which prevents
+ # the build of zlib module, here we search for android's zlib version
+ # and sets the right flags, so python can be build with android's zlib
+ info("Activating flags for android's zlib")
+ zlib_lib_path = arch.ndk_lib_dir_versioned
+ zlib_includes = self.ctx.ndk.sysroot_include_dir
+ zlib_h = join(zlib_includes, 'zlib.h')
+ try:
+ with open(zlib_h) as fileh:
+ zlib_data = fileh.read()
+ except IOError:
+ raise BuildInterruptingException(
+ "Could not determine android's zlib version, no zlib.h ({}) in"
+ " the NDK dir includes".format(zlib_h)
+ )
+ for line in zlib_data.split('\n'):
+ if line.startswith('#define ZLIB_VERSION '):
+ break
+ else:
+ raise BuildInterruptingException(
+ 'Could not parse zlib.h...so we cannot find zlib version,'
+ 'required by python build,'
+ )
+ env['ZLIB_VERSION'] = line.replace('#define ZLIB_VERSION ', '')
+ add_flags(' -I' + zlib_includes, ' -L' + zlib_lib_path, ' -lz')
+
+ return env
+
+ def build_arch(self, arch):
+ if self.ctx.ndk_api < self.MIN_NDK_API:
+ raise BuildInterruptingException(
+ NDK_API_LOWER_THAN_SUPPORTED_MESSAGE.format(
+ ndk_api=self.ctx.ndk_api, min_ndk_api=self.MIN_NDK_API
+ ),
+ )
+
+ recipe_build_dir = self.get_build_dir(arch.arch)
+
+ # Create a subdirectory to actually perform the build
+ build_dir = join(recipe_build_dir, 'android-build')
+ ensure_dir(build_dir)
+
+ # TODO: Get these dynamically, like bpo-30386 does
+ sys_prefix = '/usr/local'
+ sys_exec_prefix = '/usr/local'
+
+ env = self.get_recipe_env(arch)
+ env = self.set_libs_flags(env, arch)
+
+ android_build = sh.Command(
+ join(recipe_build_dir,
+ 'config.guess'))().stdout.strip().decode('utf-8')
+
+ with current_directory(build_dir):
+ if not exists('config.status'):
+ shprint(
+ sh.Command(join(recipe_build_dir, 'configure')),
+ *(' '.join(self.configure_args).format(
+ android_host=env['HOSTARCH'],
+ android_build=android_build,
+ python_host_bin=join(self.get_recipe(
+ 'host' + self.name, self.ctx
+ ).get_path_to_python(), "python3"),
+ prefix=sys_prefix,
+ exec_prefix=sys_exec_prefix)).split(' '),
+ _env=env)
+
+ # Python build does not seem to play well with make -j option from Python 3.11 and onwards
+ # Before losing some time, please check issue
+ # https://github.com/python/cpython/issues/101295 , as the root cause looks similar
+ shprint(
+ sh.make,
+ 'all',
+ 'INSTSONAME={lib_name}'.format(lib_name=self._libpython),
+ _env=env
+ )
+
+ # TODO: Look into passing the path to pyconfig.h in a
+ # better way, although this is probably acceptable
+ sh.cp('pyconfig.h', join(recipe_build_dir, 'Include'))
+
+ def compile_python_files(self, dir):
+ '''
+ Compile the python files (recursively) for the python files inside
+ a given folder.
+
+ .. note:: python2 compiles the files into extension .pyo, but in
+ python3, and as of Python 3.5, the .pyo filename extension is no
+ longer used...uses .pyc (https://www.python.org/dev/peps/pep-0488)
+ '''
+ args = [self.ctx.hostpython]
+ args += ['-OO', '-m', 'compileall', '-b', '-f', dir]
+ subprocess.call(args)
+
+ def create_python_bundle(self, dirn, arch):
+ """
+ Create a packaged python bundle in the target directory, by
+ copying all the modules and standard library to the right
+ place.
+ """
+ # Todo: find a better way to find the build libs folder
+ modules_build_dir = join(
+ self.get_build_dir(arch.arch),
+ 'android-build',
+ 'build',
+ 'lib.linux{}-{}-{}'.format(
+ '2' if self.version[0] == '2' else '',
+ arch.command_prefix.split('-')[0],
+ self.major_minor_version_string
+ ))
+
+ # Compile to *.pyc the python modules
+ self.compile_python_files(modules_build_dir)
+ # Compile to *.pyc the standard python library
+ self.compile_python_files(join(self.get_build_dir(arch.arch), 'Lib'))
+ # Compile to *.pyc the other python packages (site-packages)
+ self.compile_python_files(self.ctx.get_python_install_dir(arch.arch))
+
+ # Bundle compiled python modules to a folder
+ modules_dir = join(dirn, 'modules')
+ c_ext = self.compiled_extension
+ ensure_dir(modules_dir)
+ module_filens = (glob.glob(join(modules_build_dir, '*.so')) +
+ glob.glob(join(modules_build_dir, '*' + c_ext)))
+ info("Copy {} files into the bundle".format(len(module_filens)))
+ for filen in module_filens:
+ info(" - copy {}".format(filen))
+ shutil.copy2(filen, modules_dir)
+
+ # zip up the standard library
+ stdlib_zip = join(dirn, 'stdlib.zip')
+ with current_directory(join(self.get_build_dir(arch.arch), 'Lib')):
+ stdlib_filens = list(walk_valid_filens(
+ '.', self.stdlib_dir_blacklist, self.stdlib_filen_blacklist))
+ if 'SOURCE_DATE_EPOCH' in environ:
+ # for reproducible builds
+ stdlib_filens.sort()
+ timestamp = int(environ['SOURCE_DATE_EPOCH'])
+ for filen in stdlib_filens:
+ utime(filen, (timestamp, timestamp))
+ info("Zip {} files into the bundle".format(len(stdlib_filens)))
+ shprint(sh.zip, '-X', stdlib_zip, *stdlib_filens)
+
+ # copy the site-packages into place
+ ensure_dir(join(dirn, 'site-packages'))
+ ensure_dir(self.ctx.get_python_install_dir(arch.arch))
+ # TODO: Improve the API around walking and copying the files
+ with current_directory(self.ctx.get_python_install_dir(arch.arch)):
+ filens = list(walk_valid_filens(
+ '.', self.site_packages_dir_blacklist,
+ self.site_packages_filen_blacklist))
+ info("Copy {} files into the site-packages".format(len(filens)))
+ for filen in filens:
+ info(" - copy {}".format(filen))
+ ensure_dir(join(dirn, 'site-packages', dirname(filen)))
+ shutil.copy2(filen, join(dirn, 'site-packages', filen))
+
+ # copy the python .so files into place
+ python_build_dir = join(self.get_build_dir(arch.arch),
+ 'android-build')
+ python_lib_name = 'libpython' + self.link_version
+ shprint(
+ sh.cp,
+ join(python_build_dir, python_lib_name + '.so'),
+ join(self.ctx.bootstrap.dist_dir, 'libs', arch.arch)
+ )
+
+ info('Renaming .so files to reflect cross-compile')
+ self.reduce_object_file_names(join(dirn, 'site-packages'))
+
+ return join(dirn, 'site-packages')
+
+
+recipe = Python3Recipe()
diff --git a/pythonforandroid/recipes/python3/patches/cpython-311-ctypes-find-library.patch b/pythonforandroid/recipes/python3/patches/cpython-311-ctypes-find-library.patch
new file mode 100644
index 000000000..7864d57ac
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/cpython-311-ctypes-find-library.patch
@@ -0,0 +1,19 @@
+--- Python-3.11.5/Lib/ctypes/util.py 2023-08-24 17:39:18.000000000 +0530
++++ Python-3.11.5.mod/Lib/ctypes/util.py 2023-11-18 22:12:17.356160615 +0530
+@@ -4,7 +4,15 @@
+ import sys
+
+ # find_library(name) returns the pathname of a library, or None.
+-if os.name == "nt":
++
++# This patch overrides the find_library to look in the right places on
++# Android
++if True:
++ from android._ctypes_library_finder import find_library as _find_lib
++ def find_library(name):
++ return _find_lib(name)
++
++elif os.name == "nt":
+
+ def _get_build_version():
+ """Return the version of MSVC that was used to build Python.
diff --git a/pythonforandroid/recipes/python3/patches/py3.7.1_fix-ctypes-util-find-library.patch b/pythonforandroid/recipes/python3/patches/py3.7.1_fix-ctypes-util-find-library.patch
new file mode 100644
index 000000000..494270d2c
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/py3.7.1_fix-ctypes-util-find-library.patch
@@ -0,0 +1,15 @@
+diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
+--- a/Lib/ctypes/util.py
++++ b/Lib/ctypes/util.py
+@@ -67,4 +67,11 @@
+ return fname
+ return None
+
++# This patch overrides the find_library to look in the right places on
++# Android
++if True:
++ from android._ctypes_library_finder import find_library as _find_lib
++ def find_library(name):
++ return _find_lib(name)
++
+ elif os.name == "posix" and sys.platform == "darwin":
diff --git a/pythonforandroid/recipes/python3/patches/py3.7.1_fix-zlib-version.patch b/pythonforandroid/recipes/python3/patches/py3.7.1_fix-zlib-version.patch
new file mode 100644
index 000000000..0dbffae24
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/py3.7.1_fix-zlib-version.patch
@@ -0,0 +1,12 @@
+--- Python-3.7.1/setup.py.orig 2018-10-20 08:04:19.000000000 +0200
++++ Python-3.7.1/setup.py 2019-02-17 00:24:30.715904412 +0100
+@@ -1410,7 +1410,8 @@ class PyBuildExt(build_ext):
+ if zlib_inc is not None:
+ zlib_h = zlib_inc[0] + '/zlib.h'
+ version = '"0.0.0"'
+- version_req = '"1.1.3"'
++ version_req = '"{}"'.format(
++ os.environ.get('ZLIB_VERSION', '1.1.3'))
+ if host_platform == 'darwin' and is_macosx_sdk_path(zlib_h):
+ zlib_h = os.path.join(macosx_sdk_root(), zlib_h[1:])
+ with open(zlib_h) as fp:
diff --git a/pythonforandroid/recipes/python3/patches/py3.7.1_fix_cortex_a8.patch b/pythonforandroid/recipes/python3/patches/py3.7.1_fix_cortex_a8.patch
new file mode 100644
index 000000000..5ddc3c432
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/py3.7.1_fix_cortex_a8.patch
@@ -0,0 +1,14 @@
+This patch removes --fix-cortex-a8 from the linker flags in order to support linking
+with lld, as lld does not support this flag (https://github.com/android-ndk/ndk/issues/766).
+diff --git a/configure b/configure
+--- a/configure
++++ b/configure
+@@ -5671,7 +5671,7 @@ $as_echo_n "checking for the Android arm ABI... " >&6; }
+ $as_echo "$_arm_arch" >&6; }
+ if test "$_arm_arch" = 7; then
+ BASECFLAGS="${BASECFLAGS} -mfloat-abi=softfp -mfpu=vfpv3-d16"
+- LDFLAGS="${LDFLAGS} -march=armv7-a -Wl,--fix-cortex-a8"
++ LDFLAGS="${LDFLAGS} -march=armv7-a"
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not Android" >&5
\ No newline at end of file
diff --git a/pythonforandroid/recipes/python3/patches/py3.8.1.patch b/pythonforandroid/recipes/python3/patches/py3.8.1.patch
new file mode 100644
index 000000000..60188057a
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/py3.8.1.patch
@@ -0,0 +1,42 @@
+diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
+index 97973bc..053c231 100644
+--- a/Lib/ctypes/util.py
++++ b/Lib/ctypes/util.py
+@@ -67,6 +67,13 @@ if os.name == "nt":
+ return fname
+ return None
+
++# This patch overrides the find_library to look in the right places on
++# Android
++if True:
++ from android._ctypes_library_finder import find_library as _find_lib
++ def find_library(name):
++ return _find_lib(name)
++
+ elif os.name == "posix" and sys.platform == "darwin":
+ from ctypes.macholib.dyld import dyld_find as _dyld_find
+ def find_library(name):
+diff --git a/configure b/configure
+index 0914e24..dd00812 100755
+--- a/configure
++++ b/configure
+@@ -18673,4 +18673,3 @@ if test "$Py_OPT" = 'false' -a "$Py_DEBUG" != 'true'; then
+ echo "" >&6
+ echo "" >&6
+ fi
+-
+diff --git a/setup.py b/setup.py
+index 20d7f35..af15cc2 100644
+--- a/setup.py
++++ b/setup.py
+@@ -1501,7 +1501,9 @@ class PyBuildExt(build_ext):
+ if zlib_inc is not None:
+ zlib_h = zlib_inc[0] + '/zlib.h'
+ version = '"0.0.0"'
+- version_req = '"1.1.3"'
++ # version_req = '"1.1.3"'
++ version_req = '"{}"'.format(
++ os.environ.get('ZLIB_VERSION', '1.1.3'))
+ if MACOS and is_macosx_sdk_path(zlib_h):
+ zlib_h = os.path.join(macosx_sdk_root(), zlib_h[1:])
+ with open(zlib_h) as fp:
diff --git a/pythonforandroid/recipes/python3/patches/py3.8.1_fix_cortex_a8.patch b/pythonforandroid/recipes/python3/patches/py3.8.1_fix_cortex_a8.patch
new file mode 100644
index 000000000..92a41b507
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/py3.8.1_fix_cortex_a8.patch
@@ -0,0 +1,15 @@
+This patch removes --fix-cortex-a8 from the linker flags in order to support linking
+with lld, as lld does not support this flag (https://github.com/android-ndk/ndk/issues/766).
+diff --git a/configure b/configure
+index 0914e24..7517168 100755
+--- a/configure
++++ b/configure
+@@ -5642,7 +5642,7 @@ $as_echo_n "checking for the Android arm ABI... " >&6; }
+ $as_echo "$_arm_arch" >&6; }
+ if test "$_arm_arch" = 7; then
+ BASECFLAGS="${BASECFLAGS} -mfloat-abi=softfp -mfpu=vfpv3-d16"
+- LDFLAGS="${LDFLAGS} -march=armv7-a -Wl,--fix-cortex-a8"
++ LDFLAGS="${LDFLAGS} -march=armv7-a"
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: not Android" >&5
diff --git a/pythonforandroid/recipes/python3/patches/pyconfig_detection.patch b/pythonforandroid/recipes/python3/patches/pyconfig_detection.patch
new file mode 100644
index 000000000..087ab586a
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/pyconfig_detection.patch
@@ -0,0 +1,13 @@
+diff -Nru Python-3.8.2/Lib/site.py Python-3.8.2-new/Lib/site.py
+--- Python-3.8.2/Lib/site.py 2020-04-28 12:48:38.000000000 -0700
++++ Python-3.8.2-new/Lib/site.py 2020-04-28 12:52:46.000000000 -0700
+@@ -487,7 +487,8 @@
+ if key == 'include-system-site-packages':
+ system_site = value.lower()
+ elif key == 'home':
+- sys._home = value
++ # this is breaking pyconfig.h path detection with venv
++ print('Ignoring "sys._home = value" override')
+
+ sys.prefix = sys.exec_prefix = site_prefix
+
diff --git a/pythonforandroid/recipes/python3/patches/reproducible-buildinfo.diff b/pythonforandroid/recipes/python3/patches/reproducible-buildinfo.diff
new file mode 100644
index 000000000..807d180a6
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/reproducible-buildinfo.diff
@@ -0,0 +1,13 @@
+# DP: Build getbuildinfo.o with DATE/TIME values when defined
+
+--- a/Makefile.pre.in
++++ b/Makefile.pre.in
+@@ -785,6 +785,8 @@ Modules/getbuildinfo.o: $(PARSER_OBJS) \
+ -DGITVERSION="\"`LC_ALL=C $(GITVERSION)`\"" \
+ -DGITTAG="\"`LC_ALL=C $(GITTAG)`\"" \
+ -DGITBRANCH="\"`LC_ALL=C $(GITBRANCH)`\"" \
++ $(if $(BUILD_DATE),-DDATE='"$(BUILD_DATE)"') \
++ $(if $(BUILD_TIME),-DTIME='"$(BUILD_TIME)"') \
+ -o $@ $(srcdir)/Modules/getbuildinfo.c
+
+ Modules/getpath.o: $(srcdir)/Modules/getpath.c Makefile
diff --git a/pythonforandroid/recipes/python3crystax/__init__.py b/pythonforandroid/recipes/python3crystax/__init__.py
deleted file mode 100644
index 60da4904f..000000000
--- a/pythonforandroid/recipes/python3crystax/__init__.py
+++ /dev/null
@@ -1,76 +0,0 @@
-
-from pythonforandroid.recipe import TargetPythonRecipe
-from pythonforandroid.toolchain import shprint, current_directory, ArchARM
-from pythonforandroid.logger import info, error
-from pythonforandroid.util import ensure_dir, temp_directory
-from os.path import exists, join
-import glob
-import sh
-
-prebuilt_download_locations = {
- '3.6': ('https://github.com/inclement/crystax_python_builds/'
- 'releases/download/0.1/crystax_python_3.6_armeabi_armeabi-v7a.tar.gz')}
-
-class Python3Recipe(TargetPythonRecipe):
- version = '3.5'
- url = ''
- name = 'python3crystax'
-
- depends = ['hostpython3crystax']
- conflicts = ['python2', 'python3']
-
- from_crystax = True
-
- def get_dir_name(self):
- name = super(Python3Recipe, self).get_dir_name()
- name += '-version{}'.format(self.version)
- return name
-
- def build_arch(self, arch):
- # We don't have to actually build anything as CrystaX comes
- # with the necessary modules. They are included by modifying
- # the Android.mk in the jni folder.
-
- # If the Python version to be used is not prebuilt with the CrystaX
- # NDK, we do have to download it.
-
- crystax_python_dir = join(self.ctx.ndk_dir, 'sources', 'python')
- if not exists(join(crystax_python_dir, self.version)):
- info(('The NDK does not have a prebuilt Python {}, trying '
- 'to obtain one.').format(self.version))
-
- if self.version not in prebuilt_download_locations:
- error(('No prebuilt version for Python {} could be found, '
- 'the built cannot continue.'))
- exit(1)
-
- with temp_directory() as td:
- self.download_file(prebuilt_download_locations[self.version],
- join(td, 'downloaded_python'))
- shprint(sh.tar, 'xf', join(td, 'downloaded_python'),
- '--directory', crystax_python_dir)
-
- if not exists(join(crystax_python_dir, self.version)):
- error(('Something went wrong, the directory at {} should '
- 'have been created but does not exist.').format(
- join(crystax_python_dir, self.version)))
-
- if not exists(join(
- crystax_python_dir, self.version, 'libs', arch.arch)):
- error(('The prebuilt Python for version {} does not contain '
- 'binaries for your chosen architecture "{}".').format(
- self.version, arch.arch))
- exit(1)
-
- # TODO: We should have an option to build a new Python. This
- # would also allow linking to openssl and sqlite from CrystaX.
-
- dirn = self.ctx.get_python_install_dir()
- ensure_dir(dirn)
-
- # Instead of using a locally built hostpython, we use the
- # user's Python for now. They must have the right version
- # available. Using e.g. pyenv makes this easy.
- self.ctx.hostpython = 'python{}'.format(self.version)
-
-recipe = Python3Recipe()
diff --git a/pythonforandroid/recipes/pytz/__init__.py b/pythonforandroid/recipes/pytz/__init__.py
deleted file mode 100644
index 774153959..000000000
--- a/pythonforandroid/recipes/pytz/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pythonforandroid.recipe import PythonRecipe
-
-
-class PytzRecipe(PythonRecipe):
- name = 'pytz'
- version = '2015.7'
- url = 'https://pypi.python.org/packages/source/p/pytz/pytz-{version}.tar.bz2'
-
- depends = [('python2', 'python3crystax')]
-
- call_hostpython_via_targetpython = False
- install_in_hostpython = True
-
-
-recipe = PytzRecipe()
diff --git a/pythonforandroid/recipes/pyusb/__init__.py b/pythonforandroid/recipes/pyusb/__init__.py
index eff882a6d..0a0fbc72b 100644
--- a/pythonforandroid/recipes/pyusb/__init__.py
+++ b/pythonforandroid/recipes/pyusb/__init__.py
@@ -5,9 +5,9 @@ class PyusbRecipe(PythonRecipe):
name = 'pyusb'
version = '1.0.0b1'
url = 'https://pypi.python.org/packages/source/p/pyusb/pyusb-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = []
site_packages_name = 'usb'
-
+
patches = ['fix-android.patch']
diff --git a/pythonforandroid/recipes/pyyaml/__init__.py b/pythonforandroid/recipes/pyyaml/__init__.py
deleted file mode 100644
index 188397ad0..000000000
--- a/pythonforandroid/recipes/pyyaml/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from pythonforandroid.toolchain import PythonRecipe
-
-
-class PyYamlRecipe(PythonRecipe):
- version = "3.11"
- url = 'http://pyyaml.org/download/pyyaml/PyYAML-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), "setuptools"]
- site_packages_name = 'pyyaml'
- call_hostpython_via_targetpython = False
-
-recipe = PyYamlRecipe()
diff --git a/pythonforandroid/recipes/pyzbar/__init__.py b/pythonforandroid/recipes/pyzbar/__init__.py
new file mode 100644
index 000000000..cf78a558c
--- /dev/null
+++ b/pythonforandroid/recipes/pyzbar/__init__.py
@@ -0,0 +1,26 @@
+from os.path import join
+from pythonforandroid.recipe import PythonRecipe
+
+
+class PyZBarRecipe(PythonRecipe):
+
+ version = '0.1.7'
+
+ url = 'https://github.com/NaturalHistoryMuseum/pyzbar/archive/v{version}.tar.gz' # noqa
+
+ call_hostpython_via_targetpython = False
+
+ depends = ['setuptools', 'libzbar']
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+ libzbar = self.get_recipe('libzbar', self.ctx)
+ libzbar_dir = libzbar.get_build_dir(arch.arch)
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)
+ env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
+ env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
+ env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
+ return env
+
+
+recipe = PyZBarRecipe()
diff --git a/pythonforandroid/recipes/pyzmq/__init__.py b/pythonforandroid/recipes/pyzmq/__init__.py
index a078ecf5d..41addc88d 100644
--- a/pythonforandroid/recipes/pyzmq/__init__.py
+++ b/pythonforandroid/recipes/pyzmq/__init__.py
@@ -10,16 +10,16 @@
class PyZMQRecipe(CythonRecipe):
name = 'pyzmq'
- version = 'master'
- url = 'https://github.com/zeromq/pyzmq/archive/{version}.zip'
+ version = '20.0.0'
+ url = 'https://github.com/zeromq/pyzmq/archive/v{version}.zip'
site_packages_name = 'zmq'
- depends = ['python2', 'libzmq']
+ depends = ['setuptools', 'libzmq']
cython_args = ['-Izmq/utils',
'-Izmq/backend/cython',
'-Izmq/devices']
def get_recipe_env(self, arch=None):
- env = super(PyZMQRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
# TODO: fix hardcoded path
# This is required to prevent issue with _io.so import.
# hostpython = self.get_recipe('hostpython2', self.ctx)
@@ -43,9 +43,9 @@ def build_cython_components(self, arch):
[global]
zmq_prefix = {}
skip_check_zmq = True
-""".format(libzmq_prefix))
+""".format(libzmq_prefix).encode())
- return super(PyZMQRecipe, self).build_cython_components(arch)
+ return super().build_cython_components(arch)
with current_directory(self.get_build_dir(arch.arch)):
hostpython = sh.Command(self.hostpython_location)
@@ -55,4 +55,5 @@ def build_cython_components(self, arch):
shprint(sh.find, build_dir, '-name', '"*.o"', '-exec',
env['STRIP'], '{}', ';', _env=env)
+
recipe = PyZMQRecipe()
diff --git a/pythonforandroid/recipes/regex/__init__.py b/pythonforandroid/recipes/regex/__init__.py
index f2c36c76d..6ac914845 100644
--- a/pythonforandroid/recipes/regex/__init__.py
+++ b/pythonforandroid/recipes/regex/__init__.py
@@ -1,12 +1,13 @@
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
class RegexRecipe(CompiledComponentsPythonRecipe):
name = 'regex'
- version = '2017.07.28'
- url = 'https://pypi.python.org/packages/d1/23/5fa829706ee1d4452552eb32e0bfc1039553e01f50a8754c6f7152e85c1b/regex-{version}.tar.gz'
+ version = '2019.06.08'
+ url = 'https://pypi.python.org/packages/source/r/regex/regex-{version}.tar.gz' # noqa
- depends = ['python2', 'setuptools']
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
recipe = RegexRecipe()
diff --git a/pythonforandroid/recipes/reportlab/__init__.py b/pythonforandroid/recipes/reportlab/__init__.py
index d50d7ac56..ee5de3821 100644
--- a/pythonforandroid/recipes/reportlab/__init__.py
+++ b/pythonforandroid/recipes/reportlab/__init__.py
@@ -1,39 +1,56 @@
-import os, sh
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe, warning
-from pythonforandroid.util import (urlretrieve, current_directory, ensure_dir)
-from pythonforandroid.logger import (logger, info, warning, error, debug, shprint, info_main)
+import os
+import sh
+
+from pythonforandroid.logger import info
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.util import current_directory, ensure_dir, touch
+
+
class ReportLabRecipe(CompiledComponentsPythonRecipe):
- version = 'c088826211ca'
- url = 'https://bitbucket.org/rptlab/reportlab/get/{version}.tar.gz'
- depends = ['python2','freetype']
+ version = 'fe660f227cac'
+ url = 'https://hg.reportlab.com/hg-public/reportlab/archive/{version}.tar.gz'
+ depends = ['freetype']
+ call_hostpython_via_targetpython = False
def prebuild_arch(self, arch):
if not self.is_patched(arch):
- super(ReportLabRecipe, self).prebuild_arch(arch)
- self.apply_patch('patches/fix-setup.patch',arch.arch)
+ super().prebuild_arch(arch)
recipe_dir = self.get_build_dir(arch.arch)
- shprint(sh.touch, os.path.join(recipe_dir, '.patched'))
- ft = self.get_recipe('freetype',self.ctx)
+
+ # Some versions of reportlab ship with a GPL-licensed font.
+ # Remove it, since this is problematic in .apks unless the
+ # entire app is GPL:
+ font_dir = os.path.join(recipe_dir,
+ "src", "reportlab", "fonts")
+ if os.path.exists(font_dir):
+ for file in os.listdir(font_dir):
+ if file.lower().startswith('darkgarden'):
+ os.remove(os.path.join(font_dir, file))
+
+ # Apply patches:
+ self.apply_patch('patches/fix-setup.patch', arch.arch)
+ touch(os.path.join(recipe_dir, '.patched'))
+ ft = self.get_recipe('freetype', self.ctx)
ft_dir = ft.get_build_dir(arch.arch)
- ft_lib_dir = os.environ.get('_FT_LIB_',os.path.join(ft_dir,'objs','.libs'))
- ft_inc_dir = os.environ.get('_FT_INC_',os.path.join(ft_dir,'include'))
- tmp_dir = os.path.normpath(os.path.join(recipe_dir,"..","..","tmp"))
+ ft_lib_dir = os.environ.get('_FT_LIB_', os.path.join(ft_dir, 'objs', '.libs'))
+ ft_inc_dir = os.environ.get('_FT_INC_', os.path.join(ft_dir, 'include'))
+ tmp_dir = os.path.normpath(os.path.join(recipe_dir, "..", "..", "tmp"))
info('reportlab recipe: recipe_dir={}'.format(recipe_dir))
info('reportlab recipe: tmp_dir={}'.format(tmp_dir))
info('reportlab recipe: ft_dir={}'.format(ft_dir))
info('reportlab recipe: ft_lib_dir={}'.format(ft_lib_dir))
info('reportlab recipe: ft_inc_dir={}'.format(ft_inc_dir))
with current_directory(recipe_dir):
- sh.ls('-lathr')
ensure_dir(tmp_dir)
- pfbfile = os.path.join(tmp_dir,"pfbfer-20070710.zip")
+ pfbfile = os.path.join(tmp_dir, "pfbfer-20070710.zip")
if not os.path.isfile(pfbfile):
sh.wget("http://www.reportlab.com/ftp/pfbfer-20070710.zip", "-O", pfbfile)
sh.unzip("-u", "-d", os.path.join(recipe_dir, "src", "reportlab", "fonts"), pfbfile)
if os.path.isfile("setup.py"):
- with open('setup.py','rb') as f:
- text = f.read().replace('_FT_LIB_',ft_lib_dir).replace('_FT_INC_',ft_inc_dir)
- with open('setup.py','wb') as f:
+ with open('setup.py', 'r') as f:
+ text = f.read().replace('_FT_LIB_', ft_lib_dir).replace('_FT_INC_', ft_inc_dir)
+ with open('setup.py', 'w') as f:
f.write(text)
+
recipe = ReportLabRecipe()
diff --git a/pythonforandroid/recipes/requests/__init__.py b/pythonforandroid/recipes/requests/__init__.py
deleted file mode 100644
index c2a349af1..000000000
--- a/pythonforandroid/recipes/requests/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from pythonforandroid.toolchain import PythonRecipe
-
-class RequestsRecipe(PythonRecipe):
- version = '2.13.0'
- url = 'https://github.com/kennethreitz/requests/archive/v{version}.tar.gz'
- depends = ['hostpython2', 'setuptools']
- site_packages_name = 'requests'
- call_hostpython_via_targetpython = False
-
-recipe = RequestsRecipe()
diff --git a/pythonforandroid/recipes/ruamel.yaml/__init__.py b/pythonforandroid/recipes/ruamel.yaml/__init__.py
new file mode 100644
index 000000000..5965afa35
--- /dev/null
+++ b/pythonforandroid/recipes/ruamel.yaml/__init__.py
@@ -0,0 +1,13 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class RuamelYamlRecipe(PythonRecipe):
+ version = '0.15.77'
+ url = 'https://pypi.python.org/packages/source/r/ruamel.yaml/ruamel.yaml-{version}.tar.gz'
+ depends = ['setuptools']
+ site_packages_name = 'ruamel'
+ call_hostpython_via_targetpython = False
+ patches = ['disable-pip-req.patch']
+
+
+recipe = RuamelYamlRecipe()
diff --git a/pythonforandroid/recipes/ruamel.yaml/disable-pip-req.patch b/pythonforandroid/recipes/ruamel.yaml/disable-pip-req.patch
new file mode 100644
index 000000000..b033774c4
--- /dev/null
+++ b/pythonforandroid/recipes/ruamel.yaml/disable-pip-req.patch
@@ -0,0 +1,11 @@
+--- setup.py 2018-11-11 18:27:31.936424140 +0100
++++ b/setup.py 2018-11-11 18:28:19.873507071 +0100
+@@ -396,7 +396,7 @@
+ sys.exit(0)
+ if not os.environ.get('RUAMEL_NO_PIP_INSTALL_CHECK', False):
+ print('error: you have to install with "pip install ."')
+- sys.exit(1)
++ # sys.exit(1)
+ # If you only support an extension module on Linux, Windows thinks it
+ # is pure. That way you would get pure python .whl files that take
+ # precedence for downloading on Linux over source with compilable C code
diff --git a/pythonforandroid/recipes/scipy/__init__.py b/pythonforandroid/recipes/scipy/__init__.py
new file mode 100644
index 000000000..bde9758d8
--- /dev/null
+++ b/pythonforandroid/recipes/scipy/__init__.py
@@ -0,0 +1,91 @@
+from multiprocessing import cpu_count
+from os.path import join
+from os import environ
+import sh
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
+from pythonforandroid.util import build_platform, current_directory
+
+
+def arch_to_toolchain(arch):
+ if 'arm' in arch.arch:
+ return arch.command_prefix
+ return arch.arch
+
+
+class ScipyRecipe(CompiledComponentsPythonRecipe):
+
+ version = 'maintenance/1.11.x'
+ url = 'git+https://github.com/scipy/scipy.git'
+ git_commit = 'b430bf54b5064465983813e2cfef3fcb86c3df07' # version 1.11.3
+ site_packages_name = 'scipy'
+ depends = ['setuptools', 'cython', 'numpy', 'lapack', 'pybind11']
+ call_hostpython_via_targetpython = False
+ need_stl_shared = True
+ patches = ["setup.py.patch"]
+
+ def build_compiled_components(self, arch):
+ self.setup_extra_args = ['-j', str(cpu_count())]
+ super().build_compiled_components(arch)
+ self.setup_extra_args = []
+
+ def rebuild_compiled_components(self, arch, env):
+ self.setup_extra_args = ['-j', str(cpu_count())]
+ super().rebuild_compiled_components(arch, env)
+ self.setup_extra_args = []
+
+ def download_file(self, url, target, cwd=None):
+ super().download_file(url, target, cwd=cwd)
+ with current_directory(target):
+ shprint(sh.git, 'fetch', '--unshallow')
+ shprint(sh.git, 'checkout', self.git_commit)
+
+ def get_recipe_env(self, arch):
+ env = super().get_recipe_env(arch)
+ arch_env = arch.get_env()
+
+ env['LDFLAGS'] = arch_env['LDFLAGS']
+ env['LDFLAGS'] += ' -L{} -lpython{}'.format(
+ self.ctx.python_recipe.link_root(arch.arch),
+ self.ctx.python_recipe.link_version,
+ )
+
+ ndk_dir = environ["LEGACY_NDK"]
+ GCC_VER = '4.9'
+ HOST = build_platform
+ suffix = '64' if '64' in arch.arch else ''
+
+ prefix = arch.command_prefix
+ CLANG_BIN = f'{ndk_dir}/toolchains/llvm/prebuilt/{HOST}/bin/'
+ GCC = f'{ndk_dir}/toolchains/{arch_to_toolchain(arch)}-{GCC_VER}/prebuilt/{HOST}'
+ libgfortran = f'{GCC}/{prefix}/lib{suffix}'
+ numpylib = self.ctx.get_python_install_dir(arch.arch) + '/numpy'
+ arch_cflags = ' '.join(arch.arch_cflags)
+ LDSHARED_opts = f'-target {arch.target} {arch_cflags} ' + ' '.join(arch.common_ldshared)
+
+ # TODO: add pythran support
+ env['SCIPY_USE_PYTHRAN'] = '0'
+
+ lapack_dir = join(Recipe.get_recipe('lapack', self.ctx).get_build_dir(arch.arch), 'build', 'install')
+ env['LAPACK'] = f'{lapack_dir}/lib'
+ env['BLAS'] = env['LAPACK']
+
+ # compilers
+ env['F77'] = f'{GCC}/bin/{prefix}-gfortran'
+ env['F90'] = f'{GCC}/bin/{prefix}-gfortran'
+ env['CC'] = f'{CLANG_BIN}clang -target {arch.target} {arch_cflags}'
+ env['CXX'] = f'{CLANG_BIN}clang++ -target {arch.target} {arch_cflags}'
+
+ # scipy expects ldshared to be a single executable without options
+ env['LDSHARED'] = f'{CLANG_BIN}/clang'
+
+ # erase the default NDK C++ include options
+ env['CPPFLAGS'] = '-DANDROID'
+
+ # configure linker
+ env['LDFLAGS'] += f' {LDSHARED_opts} -L{libgfortran} -L{numpylib}/core/lib -L{numpylib}/random/lib'
+ env['LDFLAGS'] += f' -l{self.stl_lib_name}'
+ return env
+
+
+recipe = ScipyRecipe()
diff --git a/pythonforandroid/recipes/scipy/setup.py.patch b/pythonforandroid/recipes/scipy/setup.py.patch
new file mode 100644
index 000000000..9fbc0ab5f
--- /dev/null
+++ b/pythonforandroid/recipes/scipy/setup.py.patch
@@ -0,0 +1,1098 @@
+diff '--color=auto' -uNr scipy/_setup.py scipy.mod/_setup.py
+--- scipy/_setup.py 2023-10-30 19:20:36.545524745 +0530
++++ scipy.mod/_setup.py 1970-01-01 05:30:00.000000000 +0530
+@@ -1,545 +0,0 @@
+-#!/usr/bin/env python
+-"""SciPy: Scientific Library for Python
+-
+-SciPy (pronounced "Sigh Pie") is open-source software for mathematics,
+-science, and engineering. The SciPy library
+-depends on NumPy, which provides convenient and fast N-dimensional
+-array manipulation. The SciPy library is built to work with NumPy
+-arrays, and provides many user-friendly and efficient numerical
+-routines such as routines for numerical integration and optimization.
+-Together, they run on all popular operating systems, are quick to
+-install, and are free of charge. NumPy and SciPy are easy to use,
+-but powerful enough to be depended upon by some of the world's
+-leading scientists and engineers. If you need to manipulate
+-numbers on a computer and display or publish the results,
+-give SciPy a try!
+-
+-"""
+-
+-
+-# IMPORTANT:
+-#
+-# THIS FILE IS INTENTIONALLY RENAMED FROM setup.py TO _setup.py
+-# IT IS ONLY KEPT IN THE REPO BECAUSE conda-forge STILL NEEDS IT
+-# FOR BUILDING SCIPY ON WINDOWS. IT SHOULD NOT BE USED BY ANYONE
+-# ELSE. USE `pip install .` OR ANOTHER INSTALL COMMAND USING A
+-# BUILD FRONTEND LIKE pip OR pypa/build TO INSTALL SCIPY FROM SOURCE.
+-#
+-# SEE http://scipy.github.io/devdocs/building/index.html FOR BUILD
+-# INSTRUCTIONS.
+-
+-
+-DOCLINES = (__doc__ or '').split("\n")
+-
+-import os
+-import sys
+-import subprocess
+-import textwrap
+-import warnings
+-import sysconfig
+-from tools.version_utils import write_version_py, get_version_info
+-from tools.version_utils import IS_RELEASE_BRANCH
+-import importlib
+-
+-
+-if sys.version_info[:2] < (3, 9):
+- raise RuntimeError("Python version >= 3.9 required.")
+-
+-import builtins
+-
+-
+-CLASSIFIERS = """\
+-Development Status :: 5 - Production/Stable
+-Intended Audience :: Science/Research
+-Intended Audience :: Developers
+-License :: OSI Approved :: BSD License
+-Programming Language :: C
+-Programming Language :: Python
+-Programming Language :: Python :: 3
+-Programming Language :: Python :: 3.9
+-Programming Language :: Python :: 3.10
+-Programming Language :: Python :: 3.11
+-Topic :: Software Development :: Libraries
+-Topic :: Scientific/Engineering
+-Operating System :: Microsoft :: Windows
+-Operating System :: POSIX :: Linux
+-Operating System :: POSIX
+-Operating System :: Unix
+-Operating System :: MacOS
+-
+-"""
+-
+-
+-# BEFORE importing setuptools, remove MANIFEST. Otherwise it may not be
+-# properly updated when the contents of directories change (true for distutils,
+-# not sure about setuptools).
+-if os.path.exists('MANIFEST'):
+- os.remove('MANIFEST')
+-
+-# This is a bit hackish: we are setting a global variable so that the main
+-# scipy __init__ can detect if it is being loaded by the setup routine, to
+-# avoid attempting to load components that aren't built yet. While ugly, it's
+-# a lot more robust than what was previously being used.
+-builtins.__SCIPY_SETUP__ = True
+-
+-
+-def check_submodules():
+- """ verify that the submodules are checked out and clean
+- use `git submodule update --init`; on failure
+- """
+- if not os.path.exists('.git'):
+- return
+- with open('.gitmodules') as f:
+- for l in f:
+- if 'path' in l:
+- p = l.split('=')[-1].strip()
+- if not os.path.exists(p):
+- raise ValueError('Submodule %s missing' % p)
+-
+-
+- proc = subprocess.Popen(['git', 'submodule', 'status'],
+- stdout=subprocess.PIPE)
+- status, _ = proc.communicate()
+- status = status.decode("ascii", "replace")
+- for line in status.splitlines():
+- if line.startswith('-') or line.startswith('+'):
+- raise ValueError('Submodule not clean: %s' % line)
+-
+-
+-class concat_license_files():
+- """Merge LICENSE.txt and LICENSES_bundled.txt for sdist creation
+-
+- Done this way to keep LICENSE.txt in repo as exact BSD 3-clause (see
+- NumPy gh-13447). This makes GitHub state correctly how SciPy is licensed.
+- """
+- def __init__(self):
+- self.f1 = 'LICENSE.txt'
+- self.f2 = 'LICENSES_bundled.txt'
+-
+- def __enter__(self):
+- """Concatenate files and remove LICENSES_bundled.txt"""
+- with open(self.f1, 'r') as f1:
+- self.bsd_text = f1.read()
+-
+- with open(self.f1, 'a') as f1:
+- with open(self.f2, 'r') as f2:
+- self.bundled_text = f2.read()
+- f1.write('\n\n')
+- f1.write(self.bundled_text)
+-
+- def __exit__(self, exception_type, exception_value, traceback):
+- """Restore content of both files"""
+- with open(self.f1, 'w') as f:
+- f.write(self.bsd_text)
+-
+-
+-from distutils.command.sdist import sdist
+-class sdist_checked(sdist):
+- """ check submodules on sdist to prevent incomplete tarballs """
+- def run(self):
+- check_submodules()
+- with concat_license_files():
+- sdist.run(self)
+-
+-
+-def get_build_ext_override():
+- """
+- Custom build_ext command to tweak extension building.
+- """
+- from numpy.distutils.command.build_ext import build_ext as npy_build_ext
+- if int(os.environ.get('SCIPY_USE_PYTHRAN', 1)):
+- try:
+- import pythran
+- from pythran.dist import PythranBuildExt
+- except ImportError:
+- BaseBuildExt = npy_build_ext
+- else:
+- BaseBuildExt = PythranBuildExt[npy_build_ext]
+- _pep440 = importlib.import_module('scipy._lib._pep440')
+- if _pep440.parse(pythran.__version__) < _pep440.Version('0.11.0'):
+- raise RuntimeError("The installed `pythran` is too old, >= "
+- "0.11.0 is needed, {} detected. Please "
+- "upgrade Pythran, or use `export "
+- "SCIPY_USE_PYTHRAN=0`.".format(
+- pythran.__version__))
+- else:
+- BaseBuildExt = npy_build_ext
+-
+- class build_ext(BaseBuildExt):
+- def finalize_options(self):
+- super().finalize_options()
+-
+- # Disable distutils parallel build, due to race conditions
+- # in numpy.distutils (Numpy issue gh-15957)
+- if self.parallel:
+- print("NOTE: -j build option not supported. Set NPY_NUM_BUILD_JOBS=4 "
+- "for parallel build.")
+- self.parallel = None
+-
+- def build_extension(self, ext):
+- # When compiling with GNU compilers, use a version script to
+- # hide symbols during linking.
+- if self.__is_using_gnu_linker(ext):
+- export_symbols = self.get_export_symbols(ext)
+- text = '{global: %s; local: *; };' % (';'.join(export_symbols),)
+-
+- script_fn = os.path.join(self.build_temp, 'link-version-{}.map'.format(ext.name))
+- with open(script_fn, 'w') as f:
+- f.write(text)
+- # line below fixes gh-8680
+- ext.extra_link_args = [arg for arg in ext.extra_link_args if not "version-script" in arg]
+- ext.extra_link_args.append('-Wl,--version-script=' + script_fn)
+-
+- # Allow late configuration
+- hooks = getattr(ext, '_pre_build_hook', ())
+- _run_pre_build_hooks(hooks, (self, ext))
+-
+- super().build_extension(ext)
+-
+- def __is_using_gnu_linker(self, ext):
+- if not sys.platform.startswith('linux'):
+- return False
+-
+- # Fortran compilation with gfortran uses it also for
+- # linking. For the C compiler, we detect gcc in a similar
+- # way as distutils does it in
+- # UnixCCompiler.runtime_library_dir_option
+- if ext.language == 'f90':
+- is_gcc = (self._f90_compiler.compiler_type in ('gnu', 'gnu95'))
+- elif ext.language == 'f77':
+- is_gcc = (self._f77_compiler.compiler_type in ('gnu', 'gnu95'))
+- else:
+- is_gcc = False
+- if self.compiler.compiler_type == 'unix':
+- cc = sysconfig.get_config_var("CC")
+- if not cc:
+- cc = ""
+- compiler_name = os.path.basename(cc.split(" ")[0])
+- is_gcc = "gcc" in compiler_name or "g++" in compiler_name
+- return is_gcc and sysconfig.get_config_var('GNULD') == 'yes'
+-
+- return build_ext
+-
+-
+-def get_build_clib_override():
+- """
+- Custom build_clib command to tweak library building.
+- """
+- from numpy.distutils.command.build_clib import build_clib as old_build_clib
+-
+- class build_clib(old_build_clib):
+- def finalize_options(self):
+- super().finalize_options()
+-
+- # Disable parallelization (see build_ext above)
+- self.parallel = None
+-
+- def build_a_library(self, build_info, lib_name, libraries):
+- # Allow late configuration
+- hooks = build_info.get('_pre_build_hook', ())
+- _run_pre_build_hooks(hooks, (self, build_info))
+- old_build_clib.build_a_library(self, build_info, lib_name, libraries)
+-
+- return build_clib
+-
+-
+-def _run_pre_build_hooks(hooks, args):
+- """Call a sequence of pre-build hooks, if any"""
+- if hooks is None:
+- hooks = ()
+- elif not hasattr(hooks, '__iter__'):
+- hooks = (hooks,)
+- for hook in hooks:
+- hook(*args)
+-
+-
+-def generate_cython():
+- cwd = os.path.abspath(os.path.dirname(__file__))
+- print("Cythonizing sources")
+- p = subprocess.call([sys.executable,
+- os.path.join(cwd, 'tools', 'cythonize.py'),
+- 'scipy'],
+- cwd=cwd)
+- if p != 0:
+- # Could be due to a too old pip version and build isolation, check that
+- try:
+- # Note, pip may not be installed or not have been used
+- import pip
+- except (ImportError, ModuleNotFoundError):
+- raise RuntimeError("Running cythonize failed!")
+- else:
+- _pep440 = importlib.import_module('scipy._lib._pep440')
+- if _pep440.parse(pip.__version__) < _pep440.Version('18.0.0'):
+- raise RuntimeError("Cython not found or too old. Possibly due "
+- "to `pip` being too old, found version {}, "
+- "needed is >= 18.0.0.".format(
+- pip.__version__))
+- else:
+- raise RuntimeError("Running cythonize failed!")
+-
+-
+-def parse_setuppy_commands():
+- """Check the commands and respond appropriately. Disable broken commands.
+-
+- Return a boolean value for whether or not to run the build or not (avoid
+- parsing Cython and template files if False).
+- """
+- args = sys.argv[1:]
+-
+- if not args:
+- # User forgot to give an argument probably, let setuptools handle that.
+- return True
+-
+- info_commands = ['--help-commands', '--name', '--version', '-V',
+- '--fullname', '--author', '--author-email',
+- '--maintainer', '--maintainer-email', '--contact',
+- '--contact-email', '--url', '--license', '--description',
+- '--long-description', '--platforms', '--classifiers',
+- '--keywords', '--provides', '--requires', '--obsoletes']
+-
+- for command in info_commands:
+- if command in args:
+- return False
+-
+- # Note that 'alias', 'saveopts' and 'setopt' commands also seem to work
+- # fine as they are, but are usually used together with one of the commands
+- # below and not standalone. Hence they're not added to good_commands.
+- good_commands = ('develop', 'sdist', 'build', 'build_ext', 'build_py',
+- 'build_clib', 'build_scripts', 'bdist_wheel', 'bdist_rpm',
+- 'bdist_wininst', 'bdist_msi', 'bdist_mpkg')
+-
+- for command in good_commands:
+- if command in args:
+- return True
+-
+- # The following commands are supported, but we need to show more
+- # useful messages to the user
+- if 'install' in args:
+- print(textwrap.dedent("""
+- Note: for reliable uninstall behaviour and dependency installation
+- and uninstallation, please use pip instead of using
+- `setup.py install`:
+-
+- - `pip install .` (from a git repo or downloaded source
+- release)
+- - `pip install scipy` (last SciPy release on PyPI)
+-
+- """))
+- return True
+-
+- if '--help' in args or '-h' in sys.argv[1]:
+- print(textwrap.dedent("""
+- SciPy-specific help
+- -------------------
+-
+- To install SciPy from here with reliable uninstall, we recommend
+- that you use `pip install .`. To install the latest SciPy release
+- from PyPI, use `pip install scipy`.
+-
+- For help with build/installation issues, please ask on the
+- scipy-user mailing list. If you are sure that you have run
+- into a bug, please report it at https://github.com/scipy/scipy/issues.
+-
+- Setuptools commands help
+- ------------------------
+- """))
+- return False
+-
+-
+- # The following commands aren't supported. They can only be executed when
+- # the user explicitly adds a --force command-line argument.
+- bad_commands = dict(
+- test="""
+- `setup.py test` is not supported. Use one of the following
+- instead:
+-
+- - `python runtests.py` (to build and test)
+- - `python runtests.py --no-build` (to test installed scipy)
+- - `>>> scipy.test()` (run tests for installed scipy
+- from within an interpreter)
+- """,
+- upload="""
+- `setup.py upload` is not supported, because it's insecure.
+- Instead, build what you want to upload and upload those files
+- with `twine upload -s <filenames>` instead.
+- """,
+- upload_docs="`setup.py upload_docs` is not supported",
+- easy_install="`setup.py easy_install` is not supported",
+- clean="""
+- `setup.py clean` is not supported, use one of the following instead:
+-
+- - `git clean -xdf` (cleans all files)
+- - `git clean -Xdf` (cleans all versioned files, doesn't touch
+- files that aren't checked into the git repo)
+- """,
+- check="`setup.py check` is not supported",
+- register="`setup.py register` is not supported",
+- bdist_dumb="`setup.py bdist_dumb` is not supported",
+- bdist="`setup.py bdist` is not supported",
+- flake8="`setup.py flake8` is not supported, use flake8 standalone",
+- build_sphinx="`setup.py build_sphinx` is not supported, see doc/README.md",
+- )
+- bad_commands['nosetests'] = bad_commands['test']
+- for command in ('upload_docs', 'easy_install', 'bdist', 'bdist_dumb',
+- 'register', 'check', 'install_data', 'install_headers',
+- 'install_lib', 'install_scripts', ):
+- bad_commands[command] = "`setup.py %s` is not supported" % command
+-
+- for command in bad_commands.keys():
+- if command in args:
+- print(textwrap.dedent(bad_commands[command]) +
+- "\nAdd `--force` to your command to use it anyway if you "
+- "must (unsupported).\n")
+- sys.exit(1)
+-
+- # Commands that do more than print info, but also don't need Cython and
+- # template parsing.
+- other_commands = ['egg_info', 'install_egg_info', 'rotate']
+- for command in other_commands:
+- if command in args:
+- return False
+-
+- # If we got here, we didn't detect what setup.py command was given
+- warnings.warn("Unrecognized setuptools command ('{}'), proceeding with "
+- "generating Cython sources and expanding templates".format(
+- ' '.join(sys.argv[1:])))
+- return True
+-
+-def check_setuppy_command():
+- run_build = parse_setuppy_commands()
+- if run_build:
+- try:
+- pkgname = 'numpy'
+- import numpy
+- pkgname = 'pybind11'
+- import pybind11
+- except ImportError as exc: # We do not have our build deps installed
+- print(textwrap.dedent(
+- """Error: '%s' must be installed before running the build.
+- """
+- % (pkgname,)))
+- sys.exit(1)
+-
+- return run_build
+-
+-def configuration(parent_package='', top_path=None):
+- from numpy.distutils.system_info import get_info, NotFoundError
+- from numpy.distutils.misc_util import Configuration
+-
+- lapack_opt = get_info('lapack_opt')
+-
+- if not lapack_opt:
+- if sys.platform == "darwin":
+- msg = ('No BLAS/LAPACK libraries found. '
+- 'Note: Accelerate is no longer supported.')
+- else:
+- msg = 'No BLAS/LAPACK libraries found.'
+- msg += ("\n"
+- "To build Scipy from sources, BLAS & LAPACK libraries "
+- "need to be installed.\n"
+- "See site.cfg.example in the Scipy source directory and\n"
+- "https://docs.scipy.org/doc/scipy/dev/contributor/building.html "
+- "for details.")
+- raise NotFoundError(msg)
+-
+- config = Configuration(None, parent_package, top_path)
+- config.set_options(ignore_setup_xxx_py=True,
+- assume_default_configuration=True,
+- delegate_options_to_subpackages=True,
+- quiet=True)
+-
+- config.add_subpackage('scipy')
+- config.add_data_files(('scipy', '*.txt'))
+-
+- config.get_version('scipy/version.py')
+-
+- return config
+-
+-
+-def setup_package():
+- # In maintenance branch, change np_maxversion to N+3 if numpy is at N
+- # Update here, in pyproject.toml, and in scipy/__init__.py
+- # Rationale: SciPy builds without deprecation warnings with N; deprecations
+- # in N+1 will turn into errors in N+3
+- # For Python versions, if releases is (e.g.) <=3.9.x, set bound to 3.10
+- np_minversion = '1.21.6'
+- np_maxversion = '1.28.0'
+- python_minversion = '3.9'
+- python_maxversion = '3.13'
+- if IS_RELEASE_BRANCH:
+- req_np = 'numpy>={},<{}'.format(np_minversion, np_maxversion)
+- req_py = '>={},<{}'.format(python_minversion, python_maxversion)
+- else:
+- req_np = 'numpy>={}'.format(np_minversion)
+- req_py = '>={}'.format(python_minversion)
+-
+- # Rewrite the version file every time
+- write_version_py('.')
+-
+- cmdclass = {'sdist': sdist_checked}
+-
+- metadata = dict(
+- name='scipy',
+- maintainer="SciPy Developers",
+- maintainer_email="scipy-dev@python.org",
+- description=DOCLINES[0],
+- long_description="\n".join(DOCLINES[2:]),
+- url="https://www.scipy.org",
+- download_url="https://github.com/scipy/scipy/releases",
+- project_urls={
+- "Bug Tracker": "https://github.com/scipy/scipy/issues",
+- "Documentation": "https://docs.scipy.org/doc/scipy/reference/",
+- "Source Code": "https://github.com/scipy/scipy",
+- },
+- license='BSD',
+- cmdclass=cmdclass,
+- classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f],
+- platforms=["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"],
+- install_requires=[req_np],
+- python_requires=req_py,
+- zip_safe=False,
+- )
+-
+- if "--force" in sys.argv:
+- run_build = True
+- sys.argv.remove('--force')
+- else:
+- # Raise errors for unsupported commands, improve help output, etc.
+- run_build = check_setuppy_command()
+-
+- # Disable OSX Accelerate, it has too old LAPACK
+- os.environ['ACCELERATE'] = 'None'
+-
+- # This import is here because it needs to be done before importing setup()
+- # from numpy.distutils, but after the MANIFEST removing and sdist import
+- # higher up in this file.
+- from setuptools import setup
+-
+- if run_build:
+- from numpy.distutils.core import setup
+-
+- # Customize extension building
+- cmdclass['build_ext'] = get_build_ext_override()
+- cmdclass['build_clib'] = get_build_clib_override()
+-
+- if not 'sdist' in sys.argv:
+- # Generate Cython sources, unless we're creating an sdist
+- # Cython is a build dependency, and shipping generated .c files
+- # can cause problems (see gh-14199)
+- generate_cython()
+-
+- metadata['configuration'] = configuration
+- else:
+- # Don't import numpy here - non-build actions are required to succeed
+- # without NumPy for example when pip is used to install Scipy when
+- # NumPy is not yet present in the system.
+-
+- # Version number is added to metadata inside configuration() if build
+- # is run.
+- metadata['version'] = get_version_info('.')[0]
+-
+- setup(**metadata)
+-
+-
+-if __name__ == '__main__':
+- setup_package()
+diff '--color=auto' -uNr scipy/setup.py scipy.mod/setup.py
+--- scipy/setup.py 1970-01-01 05:30:00.000000000 +0530
++++ scipy.mod/setup.py 2023-10-30 19:22:02.921729484 +0530
+@@ -0,0 +1,545 @@
++#!/usr/bin/env python
++"""SciPy: Scientific Library for Python
++
++SciPy (pronounced "Sigh Pie") is open-source software for mathematics,
++science, and engineering. The SciPy library
++depends on NumPy, which provides convenient and fast N-dimensional
++array manipulation. The SciPy library is built to work with NumPy
++arrays, and provides many user-friendly and efficient numerical
++routines such as routines for numerical integration and optimization.
++Together, they run on all popular operating systems, are quick to
++install, and are free of charge. NumPy and SciPy are easy to use,
++but powerful enough to be depended upon by some of the world's
++leading scientists and engineers. If you need to manipulate
++numbers on a computer and display or publish the results,
++give SciPy a try!
++
++"""
++
++
++# IMPORTANT:
++#
++# THIS FILE IS INTENTIONALLY RENAMED FROM setup.py TO _setup.py
++# IT IS ONLY KEPT IN THE REPO BECAUSE conda-forge STILL NEEDS IT
++# FOR BUILDING SCIPY ON WINDOWS. IT SHOULD NOT BE USED BY ANYONE
++# ELSE. USE `pip install .` OR ANOTHER INSTALL COMMAND USING A
++# BUILD FRONTEND LIKE pip OR pypa/build TO INSTALL SCIPY FROM SOURCE.
++#
++# SEE http://scipy.github.io/devdocs/building/index.html FOR BUILD
++# INSTRUCTIONS.
++
++
++DOCLINES = (__doc__ or '').split("\n")
++
++import os
++import sys
++import subprocess
++import textwrap
++import warnings
++import sysconfig
++from tools.version_utils import write_version_py, get_version_info
++from tools.version_utils import IS_RELEASE_BRANCH
++import importlib
++
++
++if sys.version_info[:2] < (3, 9):
++ raise RuntimeError("Python version >= 3.9 required.")
++
++import builtins
++
++
++CLASSIFIERS = """\
++Development Status :: 5 - Production/Stable
++Intended Audience :: Science/Research
++Intended Audience :: Developers
++License :: OSI Approved :: BSD License
++Programming Language :: C
++Programming Language :: Python
++Programming Language :: Python :: 3
++Programming Language :: Python :: 3.9
++Programming Language :: Python :: 3.10
++Programming Language :: Python :: 3.11
++Topic :: Software Development :: Libraries
++Topic :: Scientific/Engineering
++Operating System :: Microsoft :: Windows
++Operating System :: POSIX :: Linux
++Operating System :: POSIX
++Operating System :: Unix
++Operating System :: MacOS
++
++"""
++
++
++# BEFORE importing setuptools, remove MANIFEST. Otherwise it may not be
++# properly updated when the contents of directories change (true for distutils,
++# not sure about setuptools).
++if os.path.exists('MANIFEST'):
++ os.remove('MANIFEST')
++
++# This is a bit hackish: we are setting a global variable so that the main
++# scipy __init__ can detect if it is being loaded by the setup routine, to
++# avoid attempting to load components that aren't built yet. While ugly, it's
++# a lot more robust than what was previously being used.
++builtins.__SCIPY_SETUP__ = True
++
++
++def check_submodules():
++ """ verify that the submodules are checked out and clean
++ use `git submodule update --init`; on failure
++ """
++ if not os.path.exists('.git'):
++ return
++ with open('.gitmodules') as f:
++ for l in f:
++ if 'path' in l:
++ p = l.split('=')[-1].strip()
++ if not os.path.exists(p):
++ raise ValueError('Submodule %s missing' % p)
++
++
++ proc = subprocess.Popen(['git', 'submodule', 'status'],
++ stdout=subprocess.PIPE)
++ status, _ = proc.communicate()
++ status = status.decode("ascii", "replace")
++ for line in status.splitlines():
++ if line.startswith('-') or line.startswith('+'):
++ raise ValueError('Submodule not clean: %s' % line)
++
++
++class concat_license_files():
++ """Merge LICENSE.txt and LICENSES_bundled.txt for sdist creation
++
++ Done this way to keep LICENSE.txt in repo as exact BSD 3-clause (see
++ NumPy gh-13447). This makes GitHub state correctly how SciPy is licensed.
++ """
++ def __init__(self):
++ self.f1 = 'LICENSE.txt'
++ self.f2 = 'LICENSES_bundled.txt'
++
++ def __enter__(self):
++ """Concatenate files and remove LICENSES_bundled.txt"""
++ with open(self.f1, 'r') as f1:
++ self.bsd_text = f1.read()
++
++ with open(self.f1, 'a') as f1:
++ with open(self.f2, 'r') as f2:
++ self.bundled_text = f2.read()
++ f1.write('\n\n')
++ f1.write(self.bundled_text)
++
++ def __exit__(self, exception_type, exception_value, traceback):
++ """Restore content of both files"""
++ with open(self.f1, 'w') as f:
++ f.write(self.bsd_text)
++
++
++from distutils.command.sdist import sdist
++class sdist_checked(sdist):
++ """ check submodules on sdist to prevent incomplete tarballs """
++ def run(self):
++ check_submodules()
++ with concat_license_files():
++ sdist.run(self)
++
++
++def get_build_ext_override():
++ """
++ Custom build_ext command to tweak extension building.
++ """
++ from numpy.distutils.command.build_ext import build_ext as npy_build_ext
++ if int(os.environ.get('SCIPY_USE_PYTHRAN', 1)):
++ try:
++ import pythran
++ from pythran.dist import PythranBuildExt
++ except ImportError:
++ BaseBuildExt = npy_build_ext
++ else:
++ BaseBuildExt = PythranBuildExt[npy_build_ext]
++ _pep440 = importlib.import_module('scipy._lib._pep440')
++ if _pep440.parse(pythran.__version__) < _pep440.Version('0.11.0'):
++ raise RuntimeError("The installed `pythran` is too old, >= "
++ "0.11.0 is needed, {} detected. Please "
++ "upgrade Pythran, or use `export "
++ "SCIPY_USE_PYTHRAN=0`.".format(
++ pythran.__version__))
++ else:
++ BaseBuildExt = npy_build_ext
++
++ class build_ext(BaseBuildExt):
++ def finalize_options(self):
++ super().finalize_options()
++
++ # Disable distutils parallel build, due to race conditions
++ # in numpy.distutils (Numpy issue gh-15957)
++ if self.parallel:
++ print("NOTE: -j build option not supported. Set NPY_NUM_BUILD_JOBS=4 "
++ "for parallel build.")
++ self.parallel = None
++
++ def build_extension(self, ext):
++ # When compiling with GNU compilers, use a version script to
++ # hide symbols during linking.
++ if self.__is_using_gnu_linker(ext):
++ export_symbols = self.get_export_symbols(ext)
++ text = '{global: %s; local: *; };' % (';'.join(export_symbols),)
++
++ script_fn = os.path.join(self.build_temp, 'link-version-{}.map'.format(ext.name))
++ with open(script_fn, 'w') as f:
++ f.write(text)
++ # line below fixes gh-8680
++ ext.extra_link_args = [arg for arg in ext.extra_link_args if not "version-script" in arg]
++ ext.extra_link_args.append('-Wl,--version-script=' + script_fn)
++
++ # Allow late configuration
++ hooks = getattr(ext, '_pre_build_hook', ())
++ _run_pre_build_hooks(hooks, (self, ext))
++
++ super().build_extension(ext)
++
++ def __is_using_gnu_linker(self, ext):
++ if not sys.platform.startswith('linux'):
++ return False
++
++ # Fortran compilation with gfortran uses it also for
++ # linking. For the C compiler, we detect gcc in a similar
++ # way as distutils does it in
++ # UnixCCompiler.runtime_library_dir_option
++ if ext.language == 'f90':
++ is_gcc = (self._f90_compiler.compiler_type in ('gnu', 'gnu95'))
++ elif ext.language == 'f77':
++ is_gcc = (self._f77_compiler.compiler_type in ('gnu', 'gnu95'))
++ else:
++ is_gcc = False
++ if self.compiler.compiler_type == 'unix':
++ cc = sysconfig.get_config_var("CC")
++ if not cc:
++ cc = ""
++ compiler_name = os.path.basename(cc.split(" ")[0])
++ is_gcc = "gcc" in compiler_name or "g++" in compiler_name
++ return is_gcc and sysconfig.get_config_var('GNULD') == 'yes'
++
++ return build_ext
++
++
++def get_build_clib_override():
++ """
++ Custom build_clib command to tweak library building.
++ """
++ from numpy.distutils.command.build_clib import build_clib as old_build_clib
++
++ class build_clib(old_build_clib):
++ def finalize_options(self):
++ super().finalize_options()
++
++ # Disable parallelization (see build_ext above)
++ self.parallel = None
++
++ def build_a_library(self, build_info, lib_name, libraries):
++ # Allow late configuration
++ hooks = build_info.get('_pre_build_hook', ())
++ _run_pre_build_hooks(hooks, (self, build_info))
++ old_build_clib.build_a_library(self, build_info, lib_name, libraries)
++
++ return build_clib
++
++
++def _run_pre_build_hooks(hooks, args):
++ """Call a sequence of pre-build hooks, if any"""
++ if hooks is None:
++ hooks = ()
++ elif not hasattr(hooks, '__iter__'):
++ hooks = (hooks,)
++ for hook in hooks:
++ hook(*args)
++
++
++def generate_cython():
++ cwd = os.path.abspath(os.path.dirname(__file__))
++ print("Cythonizing sources")
++ p = subprocess.call([sys.executable,
++ os.path.join(cwd, 'tools', 'cythonize.py'),
++ 'scipy'],
++ cwd=cwd)
++ if p != 0:
++ # Could be due to a too old pip version and build isolation, check that
++ try:
++ # Note, pip may not be installed or not have been used
++ import pip
++ except (ImportError, ModuleNotFoundError):
++ raise RuntimeError("Running cythonize failed!")
++ else:
++ _pep440 = importlib.import_module('scipy._lib._pep440')
++ if _pep440.parse(pip.__version__) < _pep440.Version('18.0.0'):
++ raise RuntimeError("Cython not found or too old. Possibly due "
++ "to `pip` being too old, found version {}, "
++ "needed is >= 18.0.0.".format(
++ pip.__version__))
++ else:
++ raise RuntimeError("Running cythonize failed!")
++
++
++def parse_setuppy_commands():
++ """Check the commands and respond appropriately. Disable broken commands.
++
++ Return a boolean value for whether or not to run the build or not (avoid
++ parsing Cython and template files if False).
++ """
++ args = sys.argv[1:]
++
++ if not args:
++ # User forgot to give an argument probably, let setuptools handle that.
++ return True
++
++ info_commands = ['--help-commands', '--name', '--version', '-V',
++ '--fullname', '--author', '--author-email',
++ '--maintainer', '--maintainer-email', '--contact',
++ '--contact-email', '--url', '--license', '--description',
++ '--long-description', '--platforms', '--classifiers',
++ '--keywords', '--provides', '--requires', '--obsoletes']
++
++ for command in info_commands:
++ if command in args:
++ return False
++
++ # Note that 'alias', 'saveopts' and 'setopt' commands also seem to work
++ # fine as they are, but are usually used together with one of the commands
++ # below and not standalone. Hence they're not added to good_commands.
++ good_commands = ('develop', 'sdist', 'build', 'build_ext', 'build_py',
++ 'build_clib', 'build_scripts', 'bdist_wheel', 'bdist_rpm',
++ 'bdist_wininst', 'bdist_msi', 'bdist_mpkg')
++
++ for command in good_commands:
++ if command in args:
++ return True
++
++ # The following commands are supported, but we need to show more
++ # useful messages to the user
++ if 'install' in args:
++ print(textwrap.dedent("""
++ Note: for reliable uninstall behaviour and dependency installation
++ and uninstallation, please use pip instead of using
++ `setup.py install`:
++
++ - `pip install .` (from a git repo or downloaded source
++ release)
++ - `pip install scipy` (last SciPy release on PyPI)
++
++ """))
++ return True
++
++ if '--help' in args or '-h' in sys.argv[1]:
++ print(textwrap.dedent("""
++ SciPy-specific help
++ -------------------
++
++ To install SciPy from here with reliable uninstall, we recommend
++ that you use `pip install .`. To install the latest SciPy release
++ from PyPI, use `pip install scipy`.
++
++ For help with build/installation issues, please ask on the
++ scipy-user mailing list. If you are sure that you have run
++ into a bug, please report it at https://github.com/scipy/scipy/issues.
++
++ Setuptools commands help
++ ------------------------
++ """))
++ return False
++
++
++ # The following commands aren't supported. They can only be executed when
++ # the user explicitly adds a --force command-line argument.
++ bad_commands = dict(
++ test="""
++ `setup.py test` is not supported. Use one of the following
++ instead:
++
++ - `python runtests.py` (to build and test)
++ - `python runtests.py --no-build` (to test installed scipy)
++ - `>>> scipy.test()` (run tests for installed scipy
++ from within an interpreter)
++ """,
++ upload="""
++ `setup.py upload` is not supported, because it's insecure.
++ Instead, build what you want to upload and upload those files
++ with `twine upload -s <filenames>` instead.
++ """,
++ upload_docs="`setup.py upload_docs` is not supported",
++ easy_install="`setup.py easy_install` is not supported",
++ clean="""
++ `setup.py clean` is not supported, use one of the following instead:
++
++ - `git clean -xdf` (cleans all files)
++ - `git clean -Xdf` (cleans all versioned files, doesn't touch
++ files that aren't checked into the git repo)
++ """,
++ check="`setup.py check` is not supported",
++ register="`setup.py register` is not supported",
++ bdist_dumb="`setup.py bdist_dumb` is not supported",
++ bdist="`setup.py bdist` is not supported",
++ flake8="`setup.py flake8` is not supported, use flake8 standalone",
++ build_sphinx="`setup.py build_sphinx` is not supported, see doc/README.md",
++ )
++ bad_commands['nosetests'] = bad_commands['test']
++ for command in ('upload_docs', 'easy_install', 'bdist', 'bdist_dumb',
++ 'register', 'check', 'install_data', 'install_headers',
++ 'install_lib', 'install_scripts', ):
++ bad_commands[command] = "`setup.py %s` is not supported" % command
++
++ for command in bad_commands.keys():
++ if command in args:
++ print(textwrap.dedent(bad_commands[command]) +
++ "\nAdd `--force` to your command to use it anyway if you "
++ "must (unsupported).\n")
++ sys.exit(1)
++
++ # Commands that do more than print info, but also don't need Cython and
++ # template parsing.
++ other_commands = ['egg_info', 'install_egg_info', 'rotate']
++ for command in other_commands:
++ if command in args:
++ return False
++
++ # If we got here, we didn't detect what setup.py command was given
++ warnings.warn("Unrecognized setuptools command ('{}'), proceeding with "
++ "generating Cython sources and expanding templates".format(
++ ' '.join(sys.argv[1:])))
++ return True
++
++def check_setuppy_command():
++ run_build = parse_setuppy_commands()
++ if run_build:
++ try:
++ pkgname = 'numpy'
++ import numpy
++ pkgname = 'pybind11'
++ import pybind11
++ except ImportError as exc: # We do not have our build deps installed
++ print(textwrap.dedent(
++ """Error: '%s' must be installed before running the build.
++ """
++ % (pkgname,)))
++ sys.exit(1)
++
++ return run_build
++
++def configuration(parent_package='', top_path=None):
++ from numpy.distutils.system_info import get_info, NotFoundError
++ from numpy.distutils.misc_util import Configuration
++
++ lapack_opt = get_info('lapack_opt')
++
++ if not lapack_opt:
++ if sys.platform == "darwin":
++ msg = ('No BLAS/LAPACK libraries found. '
++ 'Note: Accelerate is no longer supported.')
++ else:
++ msg = 'No BLAS/LAPACK libraries found.'
++ msg += ("\n"
++ "To build Scipy from sources, BLAS & LAPACK libraries "
++ "need to be installed.\n"
++ "See site.cfg.example in the Scipy source directory and\n"
++ "https://docs.scipy.org/doc/scipy/dev/contributor/building.html "
++ "for details.")
++ raise NotFoundError(msg)
++
++ config = Configuration(None, parent_package, top_path)
++ config.set_options(ignore_setup_xxx_py=True,
++ assume_default_configuration=True,
++ delegate_options_to_subpackages=True,
++ quiet=True)
++
++ config.add_subpackage('scipy')
++ config.add_data_files(('scipy', '*.txt'))
++
++ config.get_version('scipy/version.py')
++
++ return config
++
++
++def setup_package():
++ # In maintenance branch, change np_maxversion to N+3 if numpy is at N
++ # Update here, in pyproject.toml, and in scipy/__init__.py
++ # Rationale: SciPy builds without deprecation warnings with N; deprecations
++ # in N+1 will turn into errors in N+3
++ # For Python versions, if releases is (e.g.) <=3.9.x, set bound to 3.10
++ np_minversion = '1.21.6'
++ np_maxversion = '1.28.0'
++ python_minversion = '3.9'
++ python_maxversion = '3.13'
++ if IS_RELEASE_BRANCH:
++ req_np = 'numpy>={},<{}'.format(np_minversion, np_maxversion)
++ req_py = '>={},<{}'.format(python_minversion, python_maxversion)
++ else:
++ req_np = 'numpy>={}'.format(np_minversion)
++ req_py = '>={}'.format(python_minversion)
++
++ # Rewrite the version file every time
++ write_version_py('.')
++
++ cmdclass = {'sdist': sdist_checked}
++
++ metadata = dict(
++ name='scipy',
++ maintainer="SciPy Developers",
++ maintainer_email="scipy-dev@python.org",
++ description=DOCLINES[0],
++ long_description="\n".join(DOCLINES[2:]),
++ url="https://www.scipy.org",
++ download_url="https://github.com/scipy/scipy/releases",
++ project_urls={
++ "Bug Tracker": "https://github.com/scipy/scipy/issues",
++ "Documentation": "https://docs.scipy.org/doc/scipy/reference/",
++ "Source Code": "https://github.com/scipy/scipy",
++ },
++ license='BSD',
++ cmdclass=cmdclass,
++ classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f],
++ platforms=["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"],
++ install_requires=[req_np],
++ python_requires=req_py,
++ zip_safe=False,
++ )
++
++ if "--force" in sys.argv:
++ run_build = True
++ sys.argv.remove('--force')
++ else:
++ # Raise errors for unsupported commands, improve help output, etc.
++ run_build = check_setuppy_command()
++
++ # Disable OSX Accelerate, it has too old LAPACK
++ os.environ['ACCELERATE'] = 'None'
++
++ # This import is here because it needs to be done before importing setup()
++ # from numpy.distutils, but after the MANIFEST removing and sdist import
++ # higher up in this file.
++ from setuptools import setup
++
++ if run_build:
++ from numpy.distutils.core import setup
++
++ # Customize extension building
++ cmdclass['build_ext'] = get_build_ext_override()
++ cmdclass['build_clib'] = get_build_clib_override()
++
++ if not 'sdist' in sys.argv:
++ # Generate Cython sources, unless we're creating an sdist
++ # Cython is a build dependency, and shipping generated .c files
++ # can cause problems (see gh-14199)
++ generate_cython()
++
++ metadata['configuration'] = configuration
++ else:
++ # Don't import numpy here - non-build actions are required to succeed
++ # without NumPy for example when pip is used to install Scipy when
++ # NumPy is not yet present in the system.
++
++ # Version number is added to metadata inside configuration() if build
++ # is run.
++ metadata['version'] = get_version_info('.')[0]
++
++ setup(**metadata)
++
++
++if __name__ == '__main__':
++ setup_package()
diff --git a/pythonforandroid/recipes/scrypt/__init__.py b/pythonforandroid/recipes/scrypt/__init__.py
index 035b19abb..e41ba5905 100644
--- a/pythonforandroid/recipes/scrypt/__init__.py
+++ b/pythonforandroid/recipes/scrypt/__init__.py
@@ -1,30 +1,25 @@
-from pythonforandroid.toolchain import CythonRecipe
-from os.path import join
+from pythonforandroid.recipe import CythonRecipe
class ScryptRecipe(CythonRecipe):
- url = 'https://bitbucket.org/mhallin/py-scrypt/get/default.zip'
-
- depends = ['python2', 'setuptools', 'openssl']
-
+ version = '0.8.20'
+ url = 'https://github.com/holgern/py-scrypt/archive/refs/tags/v{version}.zip'
+ depends = ['setuptools', 'openssl']
call_hostpython_via_targetpython = False
-
patches = ["remove_librt.patch"]
def get_recipe_env(self, arch, with_flags_in_cc=True):
"""
Adds openssl recipe to include and library path.
"""
- env = super(ScryptRecipe, self).get_recipe_env(arch, with_flags_in_cc)
- openssl_build_dir = self.get_recipe(
- 'openssl', self.ctx).get_build_dir(arch.arch)
- print("openssl_build_dir:", openssl_build_dir)
- env['CC'] = '%s -I%s' % (env['CC'], join(openssl_build_dir, 'include'))
- env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
- self.ctx.get_libs_dir(arch.arch) +
- '-L{}'.format(self.ctx.libs_dir)) + ' -L{}'.format(
- openssl_build_dir)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+ openssl_recipe = self.get_recipe('openssl', self.ctx)
+ env['CFLAGS'] += openssl_recipe.include_flags(arch)
+ env['LDFLAGS'] += ' -L{}'.format(self.ctx.get_libs_dir(arch.arch))
+ env['LDFLAGS'] += ' -L{}'.format(self.ctx.libs_dir)
+ env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
+ env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags()
return env
diff --git a/pythonforandroid/recipes/scrypt/remove_librt.patch b/pythonforandroid/recipes/scrypt/remove_librt.patch
index 37d56a635..270bab2b1 100644
--- a/pythonforandroid/recipes/scrypt/remove_librt.patch
+++ b/pythonforandroid/recipes/scrypt/remove_librt.patch
@@ -1,7 +1,6 @@
-diff -r 91d194b6a6bd setup.py
---- a/setup.py Sat Sep 17 15:29:49 2016 +0200
-+++ b/setup.py Mon May 29 07:30:24 2017 +0000
-@@ -13,7 +13,6 @@
+--- a/setup.py 2018-05-06 23:25:08.757522119 +0200
++++ b/setup.py 2018-05-06 23:25:30.269797365 +0200
+@@ -15,7 +15,6 @@
if sys.platform.startswith('linux'):
define_macros = [('HAVE_CLOCK_GETTIME', '1'),
@@ -9,11 +8,12 @@ diff -r 91d194b6a6bd setup.py
('HAVE_POSIX_MEMALIGN', '1'),
('HAVE_STRUCT_SYSINFO', '1'),
('HAVE_STRUCT_SYSINFO_MEM_UNIT', '1'),
-@@ -21,7 +20,7 @@
+@@ -23,8 +22,7 @@
('HAVE_SYSINFO', '1'),
('HAVE_SYS_SYSINFO_H', '1'),
('_FILE_OFFSET_BITS', '64')]
- libraries = ['crypto', 'rt']
+- includes = ['/usr/local/include', '/usr/include']
+ libraries = ['crypto']
CFLAGS.append('-O2')
elif sys.platform.startswith('win32'):
diff --git a/pythonforandroid/recipes/sdl/__init__.py b/pythonforandroid/recipes/sdl/__init__.py
deleted file mode 100644
index be678f052..000000000
--- a/pythonforandroid/recipes/sdl/__init__.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from pythonforandroid.toolchain import BootstrapNDKRecipe, shprint, ArchARM, current_directory, info
-from os.path import exists, join
-import sh
-
-class LibSDLRecipe(BootstrapNDKRecipe):
- version = "1.2.14"
- url = None
- name = 'sdl'
- depends = ['python2', 'pygame_bootstrap_components']
- conflicts = ['sdl2']
-
- def build_arch(self, arch):
-
- if exists(join(self.ctx.libs_dir, 'libsdl.so')):
- info('libsdl.so already exists, skipping sdl build.')
- return
-
- env = self.get_recipe_env(arch)
-
- with current_directory(self.get_jni_dir()):
- shprint(sh.ndk_build, 'V=1', _env=env, _tail=20, _critical=True)
-
- libs_dir = join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)
- import os
- contents = list(os.walk(libs_dir))[0][-1]
- for content in contents:
- shprint(sh.cp, '-a', join(self.ctx.bootstrap.build_dir, 'libs', arch.arch, content),
- self.ctx.libs_dir)
-
- def get_recipe_env(self, arch=None):
- env = super(LibSDLRecipe, self).get_recipe_env(arch)
- py2 = self.get_recipe('python2', arch.ctx)
- env['PYTHON2_NAME'] = py2.get_dir_name()
- if 'python2' in self.ctx.recipe_build_order:
- env['EXTRA_LDLIBS'] = ' -lpython2.7'
- return env
-
-
-recipe = LibSDLRecipe()
diff --git a/pythonforandroid/recipes/sdl2/__init__.py b/pythonforandroid/recipes/sdl2/__init__.py
index 7c2a5729f..8d5fbc2dc 100644
--- a/pythonforandroid/recipes/sdl2/__init__.py
+++ b/pythonforandroid/recipes/sdl2/__init__.py
@@ -1,36 +1,40 @@
-from pythonforandroid.toolchain import BootstrapNDKRecipe, shprint, current_directory, info
from os.path import exists, join
+
+from pythonforandroid.recipe import BootstrapNDKRecipe
+from pythonforandroid.toolchain import current_directory, shprint
import sh
class LibSDL2Recipe(BootstrapNDKRecipe):
- version = "2.0.4"
- url = "https://www.libsdl.org/release/SDL2-{version}.tar.gz"
- md5sum = '44fc4a023349933e7f5d7a582f7b886e'
+ version = "2.28.5"
+ url = "https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL2-{version}.tar.gz"
+ md5sum = 'a344eb827a03045c9b399e99af4af13d'
dir_name = 'SDL'
- depends = [('python2', 'python3crystax'), 'sdl2_image', 'sdl2_mixer', 'sdl2_ttf']
- conflicts = ['sdl', 'pygame', 'pygame_bootstrap_components']
-
- patches = ['add_nativeSetEnv.patch']
-
- def get_recipe_env(self, arch=None):
- env = super(LibSDL2Recipe, self).get_recipe_env(arch)
- py2 = self.get_recipe('python2', arch.ctx)
- env['PYTHON2_NAME'] = py2.get_dir_name()
- if 'python2' in self.ctx.recipe_build_order:
- env['EXTRA_LDLIBS'] = ' -lpython2.7'
+ depends = ['sdl2_image', 'sdl2_mixer', 'sdl2_ttf']
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=True):
+ env = super().get_recipe_env(
+ arch=arch, with_flags_in_cc=with_flags_in_cc, with_python=with_python)
env['APP_ALLOW_MISSING_DEPS'] = 'true'
return env
+ def should_build(self, arch):
+ libdir = join(self.get_build_dir(arch.arch), "../..", "libs", arch.arch)
+ libs = ['libmain.so', 'libSDL2.so', 'libSDL2_image.so', 'libSDL2_mixer.so', 'libSDL2_ttf.so']
+ return not all(exists(join(libdir, x)) for x in libs)
+
def build_arch(self, arch):
env = self.get_recipe_env(arch)
with current_directory(self.get_jni_dir()):
- shprint(sh.ndk_build, "V=1", _env=env)
+ shprint(
+ sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
+ "V=1",
+ "NDK_DEBUG=" + ("1" if self.ctx.build_as_debuggable else "0"),
+ _env=env
+ )
recipe = LibSDL2Recipe()
-
diff --git a/pythonforandroid/recipes/sdl2/add_nativeSetEnv.patch b/pythonforandroid/recipes/sdl2/add_nativeSetEnv.patch
deleted file mode 100644
index 2262f1690..000000000
--- a/pythonforandroid/recipes/sdl2/add_nativeSetEnv.patch
+++ /dev/null
@@ -1,22 +0,0 @@
---- orig/src/core/android/SDL_android.c 2016-01-02 20:56:31.000000000 +0100
-+++ patch/src/core/android/SDL_android.c 2016-04-15 22:21:13.985708267 +0200
-@@ -188,6 +188,19 @@
- Android_OnHat(device_id, hat_id, x, y);
- }
-
-+/* Patched in env var setter for python-for-android */
-+JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeSetEnv(
-+ JNIEnv* env, jclass jcls,
-+ jstring j_name, jstring j_value)
-+{
-+ jboolean iscopy;
-+ const char *name = (*env)->GetStringUTFChars(env, j_name, &iscopy);
-+ const char *value = (*env)->GetStringUTFChars(env, j_value, &iscopy);
-+ setenv(name, value, 1);
-+ (*env)->ReleaseStringUTFChars(env, j_name, name);
-+ (*env)->ReleaseStringUTFChars(env, j_value, value);
-+}
-+
-
- JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_nativeAddJoystick(
- JNIEnv* env, jclass jcls,
diff --git a/pythonforandroid/recipes/sdl2_image/__init__.py b/pythonforandroid/recipes/sdl2_image/__init__.py
index fbddff5cd..b3ac504fb 100644
--- a/pythonforandroid/recipes/sdl2_image/__init__.py
+++ b/pythonforandroid/recipes/sdl2_image/__init__.py
@@ -1,15 +1,30 @@
-from pythonforandroid.toolchain import BootstrapNDKRecipe
-from pythonforandroid.patching import is_arch
+import os
+import sh
+from pythonforandroid.logger import shprint
+from pythonforandroid.recipe import BootstrapNDKRecipe
+from pythonforandroid.util import current_directory
class LibSDL2Image(BootstrapNDKRecipe):
- version = '2.0.1'
- url = 'https://www.libsdl.org/projects/SDL_image/release/SDL2_image-{version}.tar.gz'
+ version = '2.8.0'
+ url = 'https://github.com/libsdl-org/SDL_image/releases/download/release-{version}/SDL2_image-{version}.tar.gz'
dir_name = 'SDL2_image'
- patches = ['toggle_jpg_png_webp.patch',
- ('disable_jpg.patch', is_arch('x86')),
- 'extra_cflags.patch',
- 'fix_with_ndk_15_plus.patch']
+ patches = ['enable-webp.patch']
+
+ def get_include_dirs(self, arch):
+ return [
+ os.path.join(self.ctx.bootstrap.build_dir, "jni", "SDL2_image", "include")
+ ]
+
+ def prebuild_arch(self, arch):
+ # We do not have a folder for each arch on BootstrapNDKRecipe, so we
+ # need to skip the external deps download if we already have done it.
+ external_deps_dir = os.path.join(self.get_build_dir(arch.arch), "external")
+ if not os.path.exists(os.path.join(external_deps_dir, "libwebp")):
+ with current_directory(external_deps_dir):
+ shprint(sh.Command("./download.sh"))
+ super().prebuild_arch(arch)
+
recipe = LibSDL2Image()
diff --git a/pythonforandroid/recipes/sdl2_image/add_ndk_platform_include_dir.patch b/pythonforandroid/recipes/sdl2_image/add_ndk_platform_include_dir.patch
deleted file mode 100644
index 123e5c573..000000000
--- a/pythonforandroid/recipes/sdl2_image/add_ndk_platform_include_dir.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/Android.mk b/Android.mk
-index d48111b..3108b2c 100644
---- a/Android.mk
-+++ b/Android.mk
-@@ -22,7 +22,7 @@ SUPPORT_WEBP := false
- WEBP_LIBRARY_PATH := external/libwebp-0.3.0
-
-
--LOCAL_C_INCLUDES := $(LOCAL_PATH)
-+LOCAL_C_INCLUDES := $(LOCAL_PATH) $(NDK_PLATFORM_INCLUDE_DIR)
- LOCAL_CFLAGS := -DLOAD_BMP -DLOAD_GIF -DLOAD_LBM -DLOAD_PCX -DLOAD_PNM \
- -DLOAD_TGA -DLOAD_XCF -DLOAD_XPM -DLOAD_XV
- LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays $(EXTRA_CFLAGS)
diff --git a/pythonforandroid/recipes/sdl2_image/disable_jpg.patch b/pythonforandroid/recipes/sdl2_image/disable_jpg.patch
deleted file mode 100644
index 9e328ddab..000000000
--- a/pythonforandroid/recipes/sdl2_image/disable_jpg.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- orig/Android.mk 2016-04-15 21:15:41.578603933 +0200
-+++ patch/Android.mk 2016-04-15 21:15:29.214617537 +0200
-@@ -6,7 +6,7 @@
-
- # Enable this if you want to support loading JPEG images
- # The library path should be a relative path to this directory.
--SUPPORT_JPG := true
-+SUPPORT_JPG := false
- JPG_LIBRARY_PATH := external/jpeg-9
-
- # Enable this if you want to support loading PNG images
diff --git a/pythonforandroid/recipes/sdl2_image/enable-webp.patch b/pythonforandroid/recipes/sdl2_image/enable-webp.patch
new file mode 100644
index 000000000..98d72f201
--- /dev/null
+++ b/pythonforandroid/recipes/sdl2_image/enable-webp.patch
@@ -0,0 +1,12 @@
+diff -Naur SDL2_image.orig/Android.mk SDL2_image/Android.mk
+--- SDL2_image.orig/Android.mk 2022-10-03 20:51:52.000000000 +0200
++++ SDL2_image/Android.mk 2022-10-03 20:52:48.000000000 +0200
+@@ -32,7 +32,7 @@
+
+ # Enable this if you want to support loading WebP images
+ # The library path should be a relative path to this directory.
+-SUPPORT_WEBP ?= false
++SUPPORT_WEBP := true
+ WEBP_LIBRARY_PATH := external/libwebp
+
+
diff --git a/pythonforandroid/recipes/sdl2_image/extra_cflags.patch b/pythonforandroid/recipes/sdl2_image/extra_cflags.patch
deleted file mode 100644
index f8f26b73e..000000000
--- a/pythonforandroid/recipes/sdl2_image/extra_cflags.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- orig/Android.mk 2016-01-03 06:52:28.000000000 +0100
-+++ patch/Android.mk 2016-04-15 21:03:18.547379710 +0200
-@@ -25,7 +25,7 @@
- LOCAL_C_INCLUDES := $(LOCAL_PATH)
- LOCAL_CFLAGS := -DLOAD_BMP -DLOAD_GIF -DLOAD_LBM -DLOAD_PCX -DLOAD_PNM \
- -DLOAD_TGA -DLOAD_XCF -DLOAD_XPM -DLOAD_XV
--LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays
-+LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays $(EXTRA_CFLAGS)
-
- LOCAL_SRC_FILES := $(notdir $(filter-out %/showimage.c, $(wildcard $(LOCAL_PATH)/*.c)))
-
diff --git a/pythonforandroid/recipes/sdl2_image/fix_with_ndk_15_plus.patch b/pythonforandroid/recipes/sdl2_image/fix_with_ndk_15_plus.patch
deleted file mode 100644
index a6d42b8dc..000000000
--- a/pythonforandroid/recipes/sdl2_image/fix_with_ndk_15_plus.patch
+++ /dev/null
@@ -1,50 +0,0 @@
-diff --git a/Android.mk b/Android.mk
-index 97a96c7..2e724c0 100644
---- a/Android.mk
-+++ b/Android.mk
-@@ -79,6 +79,7 @@ ifeq ($(SUPPORT_JPG),true)
- $(JPG_LIBRARY_PATH)/jfdctfst.c \
- $(JPG_LIBRARY_PATH)/jfdctint.c \
- $(JPG_LIBRARY_PATH)/jidctflt.c \
-+ $(JPG_LIBRARY_PATH)/jidctfst.c \
- $(JPG_LIBRARY_PATH)/jidctint.c \
- $(JPG_LIBRARY_PATH)/jquant1.c \
- $(JPG_LIBRARY_PATH)/jquant2.c \
-@@ -86,12 +87,6 @@ ifeq ($(SUPPORT_JPG),true)
- $(JPG_LIBRARY_PATH)/jmemmgr.c \
- $(JPG_LIBRARY_PATH)/jmem-android.c
-
-- # assembler support is available for arm
-- ifeq ($(TARGET_ARCH),arm)
-- LOCAL_SRC_FILES += $(JPG_LIBRARY_PATH)/jidctfst.S
-- else
-- LOCAL_SRC_FILES += $(JPG_LIBRARY_PATH)/jidctfst.c
-- endif
- endif
-
- ifeq ($(SUPPORT_PNG),true)
-diff --git a/external/jpeg-9/Android.mk b/external/jpeg-9/Android.mk
-index a5edbde..77f139c 100644
---- a/external/jpeg-9/Android.mk
-+++ b/external/jpeg-9/Android.mk
-@@ -14,20 +14,6 @@ LOCAL_SRC_FILES := \
- jquant2.c jutils.c jmemmgr.c \
- jmem-android.c
-
--# the assembler is only for the ARM version, don't break the Linux sim
--ifneq ($(TARGET_ARCH),arm)
--ANDROID_JPEG_NO_ASSEMBLER := true
--endif
--
--# temp fix until we understand why this broke cnn.com
--#ANDROID_JPEG_NO_ASSEMBLER := true
--
--ifeq ($(strip $(ANDROID_JPEG_NO_ASSEMBLER)),true)
--LOCAL_SRC_FILES += jidctint.c jidctfst.c
--else
--LOCAL_SRC_FILES += jidctint.c jidctfst.S
--endif
--
- LOCAL_CFLAGS += -DAVOID_TABLES
- LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays
- #LOCAL_CFLAGS += -march=armv6j
diff --git a/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch b/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch
deleted file mode 100644
index 320d1abf0..000000000
--- a/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch
+++ /dev/null
@@ -1,25 +0,0 @@
---- orig/Android.mk 2016-01-03 06:52:28.000000000 +0100
-+++ patch/Android.mk 2016-04-15 21:14:23.906688966 +0200
-@@ -6,19 +6,19 @@
-
- # Enable this if you want to support loading JPEG images
- # The library path should be a relative path to this directory.
--SUPPORT_JPG ?= true
-+SUPPORT_JPG := true
- JPG_LIBRARY_PATH := external/jpeg-9
-
- # Enable this if you want to support loading PNG images
- # The library path should be a relative path to this directory.
--SUPPORT_PNG ?= true
-+SUPPORT_PNG := true
- PNG_LIBRARY_PATH := external/libpng-1.6.2
-
- # Enable this if you want to support loading WebP images
- # The library path should be a relative path to this directory.
- #
- # IMPORTANT: In order to enable this must have a symlink in your jni directory to external/libwebp-0.3.0.
--SUPPORT_WEBP ?= false
-+SUPPORT_WEBP := false
- WEBP_LIBRARY_PATH := external/libwebp-0.3.0
-
-
diff --git a/pythonforandroid/recipes/sdl2_mixer/__init__.py b/pythonforandroid/recipes/sdl2_mixer/__init__.py
index af4cb86bd..a00c267d4 100644
--- a/pythonforandroid/recipes/sdl2_mixer/__init__.py
+++ b/pythonforandroid/recipes/sdl2_mixer/__init__.py
@@ -1,11 +1,17 @@
-from pythonforandroid.toolchain import BootstrapNDKRecipe
+import os
+
+from pythonforandroid.recipe import BootstrapNDKRecipe
class LibSDL2Mixer(BootstrapNDKRecipe):
- version = '2.0.1'
- url = 'https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-{version}.tar.gz'
+ version = '2.6.3'
+ url = 'https://github.com/libsdl-org/SDL_mixer/releases/download/release-{version}/SDL2_mixer-{version}.tar.gz'
dir_name = 'SDL2_mixer'
- patches = ['toggle_modplug_mikmod_smpeg_ogg.patch']
+ def get_include_dirs(self, arch):
+ return [
+ os.path.join(self.ctx.bootstrap.build_dir, "jni", "SDL2_mixer", "include")
+ ]
+
recipe = LibSDL2Mixer()
diff --git a/pythonforandroid/recipes/sdl2_mixer/toggle_modplug_mikmod_smpeg_ogg.patch b/pythonforandroid/recipes/sdl2_mixer/toggle_modplug_mikmod_smpeg_ogg.patch
deleted file mode 100644
index 0d7b55459..000000000
--- a/pythonforandroid/recipes/sdl2_mixer/toggle_modplug_mikmod_smpeg_ogg.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- orig/Android.mk 2016-01-03 07:15:57.000000000 +0100
-+++ patch/Android.mk 2016-04-15 21:28:55.169697882 +0200
-@@ -6,22 +6,22 @@
-
- # Enable this if you want to support loading MOD music via modplug
- # The library path should be a relative path to this directory.
--SUPPORT_MOD_MODPLUG ?= true
-+SUPPORT_MOD_MODPLUG := false
- MODPLUG_LIBRARY_PATH := external/libmodplug-0.8.8.4
-
- # Enable this if you want to support loading MOD music via mikmod
- # The library path should be a relative path to this directory.
--SUPPORT_MOD_MIKMOD ?= true
-+SUPPORT_MOD_MIKMOD := false
- MIKMOD_LIBRARY_PATH := external/libmikmod-3.1.12
-
- # Enable this if you want to support loading MP3 music via SMPEG
- # The library path should be a relative path to this directory.
--SUPPORT_MP3_SMPEG ?= true
-+SUPPORT_MP3_SMPEG := false
- SMPEG_LIBRARY_PATH := external/smpeg2-2.0.0
-
- # Enable this if you want to support loading OGG Vorbis music via Tremor
- # The library path should be a relative path to this directory.
--SUPPORT_OGG ?= true
-+SUPPORT_OGG := true
- OGG_LIBRARY_PATH := external/libogg-1.3.1
- VORBIS_LIBRARY_PATH := external/libvorbisidec-1.2.1
-
diff --git a/pythonforandroid/recipes/sdl2_ttf/__init__.py b/pythonforandroid/recipes/sdl2_ttf/__init__.py
index 9f2d1e01b..9f97ae441 100644
--- a/pythonforandroid/recipes/sdl2_ttf/__init__.py
+++ b/pythonforandroid/recipes/sdl2_ttf/__init__.py
@@ -1,9 +1,10 @@
-from pythonforandroid.toolchain import BootstrapNDKRecipe
-from os.path import exists
+from pythonforandroid.recipe import BootstrapNDKRecipe
+
class LibSDL2TTF(BootstrapNDKRecipe):
- version = '2.0.14'
- url = 'https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-{version}.tar.gz'
+ version = '2.20.2'
+ url = 'https://github.com/libsdl-org/SDL_ttf/releases/download/release-{version}/SDL2_ttf-{version}.tar.gz'
dir_name = 'SDL2_ttf'
+
recipe = LibSDL2TTF()
diff --git a/pythonforandroid/recipes/secp256k1/__init__.py b/pythonforandroid/recipes/secp256k1/__init__.py
index a4ef6211a..1b3064231 100644
--- a/pythonforandroid/recipes/secp256k1/__init__.py
+++ b/pythonforandroid/recipes/secp256k1/__init__.py
@@ -1,33 +1,34 @@
-from os.path import join
-from pythonforandroid.recipe import PythonRecipe
+import os
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
-class Secp256k1Recipe(PythonRecipe):
+class Secp256k1Recipe(CppCompiledComponentsPythonRecipe):
- url = 'https://github.com/ludbb/secp256k1-py/archive/master.zip'
+ version = '0.13.2.4'
+ url = 'https://github.com/ludbb/secp256k1-py/archive/{version}.tar.gz'
call_hostpython_via_targetpython = False
depends = [
- 'openssl', 'hostpython2', 'python2', 'setuptools',
- 'libffi', 'cffi', 'libffi', 'libsecp256k1']
+ 'openssl',
+ 'hostpython3',
+ 'python3',
+ 'setuptools',
+ 'libffi',
+ 'cffi',
+ 'libsecp256k1'
+ ]
patches = [
"cross_compile.patch", "drop_setup_requires.patch",
"pkg-config.patch", "find_lib.patch", "no-download.patch"]
def get_recipe_env(self, arch=None):
- env = super(Secp256k1Recipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
libsecp256k1 = self.get_recipe('libsecp256k1', self.ctx)
libsecp256k1_dir = libsecp256k1.get_build_dir(arch.arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] = ' -I' + join(libsecp256k1_dir, 'include')
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
- env['LDSHARED'] = env['CC'] + \
- ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += ' -L{}'.format(libsecp256k1_dir)
- # TODO: hardcoded Python version
- env['LDFLAGS'] += " -landroid -lpython2.7 -lsecp256k1"
+ env['CFLAGS'] += ' -I' + os.path.join(libsecp256k1_dir, 'include')
+ env['LDFLAGS'] += ' -L{} -lsecp256k1'.format(libsecp256k1_dir)
return env
diff --git a/pythonforandroid/recipes/secp256k1/cross_compile.patch b/pythonforandroid/recipes/secp256k1/cross_compile.patch
index bfef22819..bcff1955f 100644
--- a/pythonforandroid/recipes/secp256k1/cross_compile.patch
+++ b/pythonforandroid/recipes/secp256k1/cross_compile.patch
@@ -6,7 +6,7 @@ index bba4bce..b86b369 100644
"--disable-dependency-tracking",
"--with-pic",
"--enable-module-recovery",
-+ "--host=%s" % os.environ['TOOLCHAIN_PREFIX'],
++ "--host=" + arch.command_prefix,
"--prefix",
os.path.abspath(self.build_clib),
]
diff --git a/pythonforandroid/recipes/setuptools/__init__.py b/pythonforandroid/recipes/setuptools/__init__.py
index 3ca2f9a85..8190f8efd 100644
--- a/pythonforandroid/recipes/setuptools/__init__.py
+++ b/pythonforandroid/recipes/setuptools/__init__.py
@@ -1,21 +1,9 @@
-
-from pythonforandroid.toolchain import (
- PythonRecipe,
- Recipe,
- current_directory,
- info,
- shprint,
-)
-from os.path import join
-import sh
+from pythonforandroid.recipe import PythonRecipe
class SetuptoolsRecipe(PythonRecipe):
- version = '18.3.1'
+ version = '51.3.3'
url = 'https://pypi.python.org/packages/source/s/setuptools/setuptools-{version}.tar.gz'
-
- depends = ['python2']
-
call_hostpython_via_targetpython = False
install_in_hostpython = True
diff --git a/pythonforandroid/recipes/shapely/__init__.py b/pythonforandroid/recipes/shapely/__init__.py
index 02b234dc5..fb3da7caa 100644
--- a/pythonforandroid/recipes/shapely/__init__.py
+++ b/pythonforandroid/recipes/shapely/__init__.py
@@ -1,24 +1,34 @@
-from pythonforandroid.recipe import Recipe,CythonRecipe
+from pythonforandroid.recipe import CythonRecipe
+from os.path import join
class ShapelyRecipe(CythonRecipe):
- version = '1.5'
- url = 'https://github.com/Toblerity/Shapely/archive/master.zip'
- depends = ['python2', 'setuptools', 'libgeos']
+ version = '1.7a1'
+ url = 'https://github.com/Toblerity/Shapely/archive/{version}.tar.gz'
+ depends = ['setuptools', 'libgeos']
+
call_hostpython_via_targetpython = False
-
- patches = ['setup.patch'] # Patch to force setup to fail when C extention fails to build
-
- # setup_extra_args = ['sdist'] # DontForce Cython
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- """ Add libgeos headers to path """
- env = super(ShapelyRecipe, self).get_recipe_env(arch,with_flags_in_cc)
- libgeos_dir = Recipe.get_recipe('libgeos', self.ctx).get_build_dir(arch.arch)
- env['CFLAGS'] += " -I{}/dist/include".format(libgeos_dir)
+
+ # Patch to avoid libgeos check (because it fails), insert environment
+ # variables for our libgeos build (includes, lib paths...) and force
+ # the cython's compilation to raise an error in case that it fails
+ patches = ['setup.patch']
+
+ # Don't Force Cython
+ # setup_extra_args = ['sdist']
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch)
+
+ libgeos_install = join(self.get_recipe(
+ 'libgeos', self.ctx).get_build_dir(arch.arch), 'install_target')
+ # All this `GEOS_X` variables should be string types, separated
+ # by commas in case that we need to pass more than one value
+ env['GEOS_INCLUDE_DIRS'] = join(libgeos_install, 'include')
+ env['GEOS_LIBRARY_DIRS'] = join(libgeos_install, 'lib')
+ env['GEOS_LIBRARIES'] = 'geos_c,geos'
+
return env
-
-
-
+
+
recipe = ShapelyRecipe()
-
\ No newline at end of file
diff --git a/pythonforandroid/recipes/shapely/setup.patch b/pythonforandroid/recipes/shapely/setup.patch
index 9523f357b..7fd1ca914 100644
--- a/pythonforandroid/recipes/shapely/setup.patch
+++ b/pythonforandroid/recipes/shapely/setup.patch
@@ -1,12 +1,44 @@
-*** shapely/setup.py 2016-06-29 11:29:49.000000000 -0400
---- b/setup.py 2016-07-09 01:51:37.759670990 -0400
-***************
-*** 359,364 ****
---- 359,365 ----
- construct_build_ext(existing_build_ext)
- setup(ext_modules=ext_modules, **setup_args)
- except BuildFailed as ex:
-+ raise # Force python only build to fail
- BUILD_EXT_WARNING = "The C extension could not be compiled, " \
- "speedups are not enabled."
- log.warn(ex)
+This patch does three things:
+ - disable the libgeos check, because, even setting the proper env variables,
+ it fails to load our libgeos library, so we skip that because it's not
+ mandatory for the cythonizing.
+ - sets some environment variables into the setup.py file, so we can pass
+ our libgeos information (includes, lib path and libraries)
+ - force to raise an error when cython file to compile (our current build
+ system relies on this failure to do the proper `cythonizing`, if we don't
+ raise the error, we will end up with the package installed without the
+ speed optimizations.
+--- Shapely-1.7a1/setup.py.orig 2018-07-29 22:53:13.000000000 +0200
++++ Shapely-1.7a1/setup.py 2019-02-24 14:26:19.178610660 +0100
+@@ -82,8 +82,8 @@ if not (py_version == (2, 7) or py_versi
+
+ # Get geos_version from GEOS dynamic library, which depends on
+ # GEOS_LIBRARY_PATH and/or GEOS_CONFIG environment variables
+-from shapely._buildcfg import geos_version_string, geos_version, \
+- geos_config, get_geos_config
++# from shapely._buildcfg import geos_version_string, geos_version, \
++# geos_config, get_geos_config
+
+ logging.basicConfig()
+ log = logging.getLogger(__file__)
+@@ -248,9 +248,9 @@ if sys.platform == 'win32':
+ setup_args['package_data']['shapely'].append('shapely/DLLs/*.dll')
+
+ # Prepare build opts and args for the speedups extension module.
+-include_dirs = []
+-library_dirs = []
+-libraries = []
++include_dirs = os.environ.get('GEOS_INCLUDE_DIRS', '').split(',')
++library_dirs = os.environ.get('GEOS_LIBRARY_DIRS', '').split(',')
++libraries = os.environ.get('GEOS_LIBRARIES', '').split(',')
+ extra_link_args = []
+
+ # If NO_GEOS_CONFIG is set in the environment, geos-config will not
+@@ -375,6 +375,7 @@ try:
+ construct_build_ext(existing_build_ext)
+ setup(ext_modules=ext_modules, **setup_args)
+ except BuildFailed as ex:
++ raise # Force python only build to fail
+ BUILD_EXT_WARNING = "The C extension could not be compiled, " \
+ "speedups are not enabled."
+ log.warn(ex)
diff --git a/pythonforandroid/recipes/simple-crypt/__init__.py b/pythonforandroid/recipes/simple-crypt/__init__.py
deleted file mode 100644
index c57c5136f..000000000
--- a/pythonforandroid/recipes/simple-crypt/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from pythonforandroid.toolchain import PythonRecipe
-
-
-class SimpleCryptRecipe(PythonRecipe):
- version = '4.1.7'
- url = 'https://pypi.python.org/packages/source/s/simple-crypt/simple-crypt-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'pycrypto']
- site_packages_name = 'simplecrypt'
-
-recipe = SimpleCryptRecipe()
diff --git a/pythonforandroid/recipes/six/__init__.py b/pythonforandroid/recipes/six/__init__.py
index 92b266f4e..3be8ce757 100644
--- a/pythonforandroid/recipes/six/__init__.py
+++ b/pythonforandroid/recipes/six/__init__.py
@@ -1,10 +1,10 @@
-
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
class SixRecipe(PythonRecipe):
- version = '1.9.0'
+ version = '1.15.0'
url = 'https://pypi.python.org/packages/source/s/six/six-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = ['setuptools']
+
recipe = SixRecipe()
diff --git a/pythonforandroid/recipes/snappy/__init__.py b/pythonforandroid/recipes/snappy/__init__.py
index ca429b20b..c57f797af 100644
--- a/pythonforandroid/recipes/snappy/__init__.py
+++ b/pythonforandroid/recipes/snappy/__init__.py
@@ -1,11 +1,28 @@
-from pythonforandroid.toolchain import Recipe
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+from pythonforandroid.util import current_directory
+from os.path import join
+import sh
+
class SnappyRecipe(Recipe):
- version = '1.1.3'
- url = 'https://github.com/google/snappy/releases/download/{version}/snappy-{version}.tar.gz'
+ version = '1.1.7'
+ url = 'https://github.com/google/snappy/archive/{version}.tar.gz'
+ built_libraries = {'libsnappy.so': '.'}
+
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+ source_dir = self.get_build_dir(arch.arch)
+ with current_directory(source_dir):
+ shprint(sh.cmake, source_dir,
+ '-DANDROID_ABI={}'.format(arch.arch),
+ '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),
+ '-DCMAKE_TOOLCHAIN_FILE={}'.format(
+ join(self.ctx.ndk_dir, 'build', 'cmake',
+ 'android.toolchain.cmake')),
+ '-DBUILD_SHARED_LIBS=1',
+ _env=env)
+ shprint(sh.make, _env=env)
- def should_build(self, arch):
- # Only download to use in leveldb recipe
- return False
recipe = SnappyRecipe()
diff --git a/pythonforandroid/recipes/spine/__init__.py b/pythonforandroid/recipes/spine/__init__.py
index fdca6cfa0..009a919b1 100644
--- a/pythonforandroid/recipes/spine/__init__.py
+++ b/pythonforandroid/recipes/spine/__init__.py
@@ -1,4 +1,4 @@
-from pythonforandroid.toolchain import CythonRecipe
+from pythonforandroid.recipe import CythonRecipe
class SpineCython(CythonRecipe):
diff --git a/pythonforandroid/recipes/sqlalchemy/__init__.py b/pythonforandroid/recipes/sqlalchemy/__init__.py
index 2632c116f..9837a59d0 100644
--- a/pythonforandroid/recipes/sqlalchemy/__init__.py
+++ b/pythonforandroid/recipes/sqlalchemy/__init__.py
@@ -1,14 +1,14 @@
-
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
class SQLAlchemyRecipe(CompiledComponentsPythonRecipe):
name = 'sqlalchemy'
- version = '1.0.9'
+ version = '1.3.3'
url = 'https://pypi.python.org/packages/source/S/SQLAlchemy/SQLAlchemy-{version}.tar.gz'
-
- depends = [('python2', 'python3crystax'), 'setuptools']
-
+ call_hostpython_via_targetpython = False
+
+ depends = ['setuptools']
+
patches = ['zipsafe.patch']
diff --git a/pythonforandroid/recipes/sqlalchemy/zipsafe.patch b/pythonforandroid/recipes/sqlalchemy/zipsafe.patch
index 1820d0961..46bdf6010 100644
--- a/pythonforandroid/recipes/sqlalchemy/zipsafe.patch
+++ b/pythonforandroid/recipes/sqlalchemy/zipsafe.patch
@@ -1,12 +1,10 @@
-diff --git a/setup.py b/setup.py
-index 09b524c..1e65772 100644
---- a/setup.py
-+++ b/setup.py
-@@ -125,6 +125,7 @@ def run_setup(with_cext):
- setup(name="SQLAlchemy",
- version=VERSION,
- description="Database Abstraction Library",
-+ zip_safe=False,
- author="Mike Bayer",
- author_email="mike_mp@zzzcomputing.com",
- url="http://www.sqlalchemy.org",
+--- a/setup.py 2019-04-15 17:45:03.000000000 +0200
++++ b/setup.py 2019-04-16 20:12:19.056710749 +0200
+@@ -145,6 +145,7 @@
+ name="SQLAlchemy",
+ version=VERSION,
+ description="Database Abstraction Library",
++ zip_safe=False,
+ author="Mike Bayer",
+ author_email="mike_mp@zzzcomputing.com",
+ url="http://www.sqlalchemy.org",
diff --git a/pythonforandroid/recipes/sqlite3/Android.mk b/pythonforandroid/recipes/sqlite3/Android.mk
index f52bc46f9..57bc81573 100644
--- a/pythonforandroid/recipes/sqlite3/Android.mk
+++ b/pythonforandroid/recipes/sqlite3/Android.mk
@@ -6,6 +6,6 @@ LOCAL_SRC_FILES := sqlite3.c
LOCAL_MODULE := sqlite3
-LOCAL_CFLAGS := -DSQLITE_ENABLE_FTS4 -D_FILE_OFFSET_BITS=32
+LOCAL_CFLAGS := -DSQLITE_ENABLE_FTS4 -D_FILE_OFFSET_BITS=32 -DSQLITE_ENABLE_JSON1
include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/recipes/sqlite3/__init__.py b/pythonforandroid/recipes/sqlite3/__init__.py
index 6397b2905..1f4292c1e 100644
--- a/pythonforandroid/recipes/sqlite3/__init__.py
+++ b/pythonforandroid/recipes/sqlite3/__init__.py
@@ -1,32 +1,36 @@
-from pythonforandroid.toolchain import NDKRecipe, shprint, shutil, current_directory
-from os.path import join, exists
-import sh
+from os.path import join
+import shutil
+
+from pythonforandroid.recipe import NDKRecipe
+from pythonforandroid.util import ensure_dir
+
class Sqlite3Recipe(NDKRecipe):
- version = '3.15.1'
+ version = '3.35.5'
# Don't forget to change the URL when changing the version
- url = 'https://www.sqlite.org/2016/sqlite-amalgamation-3150100.zip'
+ url = 'https://www.sqlite.org/2021/sqlite-amalgamation-3350500.zip'
generated_libraries = ['sqlite3']
def should_build(self, arch):
return not self.has_libs(arch, 'libsqlite3.so')
def prebuild_arch(self, arch):
- super(Sqlite3Recipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
# Copy the Android make file
- sh.mkdir('-p', join(self.get_build_dir(arch.arch), 'jni'))
+ ensure_dir(join(self.get_build_dir(arch.arch), 'jni'))
shutil.copyfile(join(self.get_recipe_dir(), 'Android.mk'),
join(self.get_build_dir(arch.arch), 'jni/Android.mk'))
def build_arch(self, arch, *extra_args):
- super(Sqlite3Recipe, self).build_arch(arch)
+ super().build_arch(arch)
# Copy the shared library
shutil.copyfile(join(self.get_build_dir(arch.arch), 'libs', arch.arch, 'libsqlite3.so'),
join(self.ctx.get_libs_dir(arch.arch), 'libsqlite3.so'))
def get_recipe_env(self, arch):
- env = super(Sqlite3Recipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
env['NDK_PROJECT_PATH'] = self.get_build_dir(arch.arch)
return env
+
recipe = Sqlite3Recipe()
diff --git a/pythonforandroid/recipes/storm/__init__.py b/pythonforandroid/recipes/storm/__init__.py
index 7b0ed6cdf..6b6446599 100644
--- a/pythonforandroid/recipes/storm/__init__.py
+++ b/pythonforandroid/recipes/storm/__init__.py
@@ -1,11 +1,11 @@
-from pythonforandroid.toolchain import PythonRecipe, current_directory, shprint
+from pythonforandroid.recipe import PythonRecipe, current_directory, shprint
import sh
class StormRecipe(PythonRecipe):
version = '0.20'
url = 'https://launchpad.net/storm/trunk/{version}/+download/storm-{version}.tar.bz2'
- depends = [('python2', 'python3crystax')]
+ depends = []
site_packages_name = 'storm'
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/sympy/__init__.py b/pythonforandroid/recipes/sympy/__init__.py
new file mode 100644
index 000000000..8684a95e0
--- /dev/null
+++ b/pythonforandroid/recipes/sympy/__init__.py
@@ -0,0 +1,16 @@
+
+from pythonforandroid.recipe import PythonRecipe
+
+
+class SympyRecipe(PythonRecipe):
+ version = '1.1.1'
+ url = 'https://github.com/sympy/sympy/releases/download/sympy-{version}/sympy-{version}.tar.gz'
+
+ depends = ['mpmath']
+
+ call_hostpython_via_targetpython = True
+
+ patches = ['fix_timeutils.patch', 'fix_pretty_print.patch']
+
+
+recipe = SympyRecipe()
diff --git a/pythonforandroid/recipes/sympy/fix_android_detection.patch b/pythonforandroid/recipes/sympy/fix_android_detection.patch
new file mode 100644
index 000000000..964c3db66
--- /dev/null
+++ b/pythonforandroid/recipes/sympy/fix_android_detection.patch
@@ -0,0 +1,47 @@
+diff --git a/pip/download.py b/pip/download.py
+index 54d3131..1aab70f 100644
+--- a/pip/download.py
++++ b/pip/download.py
+@@ -89,23 +89,25 @@ def user_agent():
+ # Complete Guess
+ data["implementation"]["version"] = platform.python_version()
+
+- if sys.platform.startswith("linux"):
+- from pip._vendor import distro
+- distro_infos = dict(filter(
+- lambda x: x[1],
+- zip(["name", "version", "id"], distro.linux_distribution()),
+- ))
+- libc = dict(filter(
+- lambda x: x[1],
+- zip(["lib", "version"], libc_ver()),
+- ))
+- if libc:
+- distro_infos["libc"] = libc
+- if distro_infos:
+- data["distro"] = distro_infos
+-
+- if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
+- data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
++ # if sys.platform.startswith("linux"):
++ # from pip._vendor import distro
++ # distro_infos = dict(filter(
++ # lambda x: x[1],
++ # zip(["name", "version", "id"], distro.linux_distribution()),
++ # ))
++ # libc = dict(filter(
++ # lambda x: x[1],
++ # zip(["lib", "version"], libc_ver()),
++ # ))
++ # if libc:
++ # distro_infos["libc"] = libc
++ # if distro_infos:
++ # data["distro"] = distro_infos
++
++ # if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
++ # data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
++
++ data['distro'] = {'name': 'Android'}
+
+ if platform.system():
+ data.setdefault("system", {})["name"] = platform.system()
diff --git a/pythonforandroid/recipes/sympy/fix_pretty_print.patch b/pythonforandroid/recipes/sympy/fix_pretty_print.patch
new file mode 100644
index 000000000..f94cb2245
--- /dev/null
+++ b/pythonforandroid/recipes/sympy/fix_pretty_print.patch
@@ -0,0 +1,223 @@
+diff --git a/sympy/printing/pretty/pretty.py b/sympy/printing/pretty/pretty.py
+index 604e97c..ddd3eb2 100644
+--- a/sympy/printing/pretty/pretty.py
++++ b/sympy/printing/pretty/pretty.py
+@@ -166,14 +166,14 @@ class PrettyPrinter(Printer):
+ arg = e.args[0]
+ pform = self._print(arg)
+ if isinstance(arg, Equivalent):
+- return self._print_Equivalent(arg, altchar=u"\N{NOT IDENTICAL TO}")
++ return self._print_Equivalent(arg, altchar=u"NOT IDENTICAL TO")
+ if isinstance(arg, Implies):
+- return self._print_Implies(arg, altchar=u"\N{RIGHTWARDS ARROW WITH STROKE}")
++ return self._print_Implies(arg, altchar=u"RIGHTWARDS ARROW WITH STROKE")
+
+ if arg.is_Boolean and not arg.is_Not:
+ pform = prettyForm(*pform.parens())
+
+- return prettyForm(*pform.left(u"\N{NOT SIGN}"))
++ return prettyForm(*pform.left(u"NOT SIGN"))
+ else:
+ return self._print_Function(e)
+
+@@ -200,43 +200,43 @@ class PrettyPrinter(Printer):
+
+ def _print_And(self, e):
+ if self._use_unicode:
+- return self.__print_Boolean(e, u"\N{LOGICAL AND}")
++ return self.__print_Boolean(e, u"LOGICAL AND")
+ else:
+ return self._print_Function(e, sort=True)
+
+ def _print_Or(self, e):
+ if self._use_unicode:
+- return self.__print_Boolean(e, u"\N{LOGICAL OR}")
++ return self.__print_Boolean(e, u"LOGICAL OR")
+ else:
+ return self._print_Function(e, sort=True)
+
+ def _print_Xor(self, e):
+ if self._use_unicode:
+- return self.__print_Boolean(e, u"\N{XOR}")
++ return self.__print_Boolean(e, u"XOR")
+ else:
+ return self._print_Function(e, sort=True)
+
+ def _print_Nand(self, e):
+ if self._use_unicode:
+- return self.__print_Boolean(e, u"\N{NAND}")
++ return self.__print_Boolean(e, u"NAND")
+ else:
+ return self._print_Function(e, sort=True)
+
+ def _print_Nor(self, e):
+ if self._use_unicode:
+- return self.__print_Boolean(e, u"\N{NOR}")
++ return self.__print_Boolean(e, u"NOR")
+ else:
+ return self._print_Function(e, sort=True)
+
+ def _print_Implies(self, e, altchar=None):
+ if self._use_unicode:
+- return self.__print_Boolean(e, altchar or u"\N{RIGHTWARDS ARROW}", sort=False)
++ return self.__print_Boolean(e, altchar or u"RIGHTWARDS ARROW", sort=False)
+ else:
+ return self._print_Function(e)
+
+ def _print_Equivalent(self, e, altchar=None):
+ if self._use_unicode:
+- return self.__print_Boolean(e, altchar or u"\N{IDENTICAL TO}")
++ return self.__print_Boolean(e, altchar or u"IDENTICAL TO")
+ else:
+ return self._print_Function(e, sort=True)
+
+@@ -425,7 +425,7 @@ class PrettyPrinter(Printer):
+ if self._use_unicode:
+ # use unicode corners
+ horizontal_chr = xobj('-', 1)
+- corner_chr = u'\N{BOX DRAWINGS LIGHT DOWN AND HORIZONTAL}'
++ corner_chr = u'BOX DRAWINGS LIGHT DOWN AND HORIZONTAL'
+
+ func_height = pretty_func.height()
+
+@@ -580,7 +580,7 @@ class PrettyPrinter(Printer):
+
+ LimArg = self._print(z)
+ if self._use_unicode:
+- LimArg = prettyForm(*LimArg.right(u'\N{BOX DRAWINGS LIGHT HORIZONTAL}\N{RIGHTWARDS ARROW}'))
++ LimArg = prettyForm(*LimArg.right(u'BOX DRAWINGS LIGHT HORIZONTALRIGHTWARDS ARROW'))
+ else:
+ LimArg = prettyForm(*LimArg.right('->'))
+ LimArg = prettyForm(*LimArg.right(self._print(z0)))
+@@ -589,7 +589,7 @@ class PrettyPrinter(Printer):
+ dir = ""
+ else:
+ if self._use_unicode:
+- dir = u'\N{SUPERSCRIPT PLUS SIGN}' if str(dir) == "+" else u'\N{SUPERSCRIPT MINUS}'
++ dir = u'SUPERSCRIPT PLUS SIGN' if str(dir) == "+" else u'SUPERSCRIPT MINUS'
+
+ LimArg = prettyForm(*LimArg.right(self._print(dir)))
+
+@@ -740,7 +740,7 @@ class PrettyPrinter(Printer):
+ def _print_Adjoint(self, expr):
+ pform = self._print(expr.arg)
+ if self._use_unicode:
+- dag = prettyForm(u'\N{DAGGER}')
++ dag = prettyForm(u'DAGGER')
+ else:
+ dag = prettyForm('+')
+ from sympy.matrices import MatrixSymbol
+@@ -850,8 +850,8 @@ class PrettyPrinter(Printer):
+ if '\n' in partstr:
+ tempstr = partstr
+ tempstr = tempstr.replace(vectstrs[i], '')
+- tempstr = tempstr.replace(u'\N{RIGHT PARENTHESIS UPPER HOOK}',
+- u'\N{RIGHT PARENTHESIS UPPER HOOK}'
++ tempstr = tempstr.replace(u'RIGHT PARENTHESIS UPPER HOOK',
++ u'RIGHT PARENTHESIS UPPER HOOK'
+ + ' ' + vectstrs[i])
+ o1[i] = tempstr
+ o1 = [x.split('\n') for x in o1]
+@@ -1153,7 +1153,7 @@ class PrettyPrinter(Printer):
+ def _print_Lambda(self, e):
+ vars, expr = e.args
+ if self._use_unicode:
+- arrow = u" \N{RIGHTWARDS ARROW FROM BAR} "
++ arrow = u" RIGHTWARDS ARROW FROM BAR "
+ else:
+ arrow = " -> "
+ if len(vars) == 1:
+@@ -1173,7 +1173,7 @@ class PrettyPrinter(Printer):
+ elif len(expr.variables):
+ pform = prettyForm(*pform.right(self._print(expr.variables[0])))
+ if self._use_unicode:
+- pform = prettyForm(*pform.right(u" \N{RIGHTWARDS ARROW} "))
++ pform = prettyForm(*pform.right(u" RIGHTWARDS ARROW "))
+ else:
+ pform = prettyForm(*pform.right(" -> "))
+ if len(expr.point) > 1:
+@@ -1462,7 +1462,7 @@ class PrettyPrinter(Printer):
+ and expt is S.Half and bpretty.height() == 1
+ and (bpretty.width() == 1
+ or (base.is_Integer and base.is_nonnegative))):
+- return prettyForm(*bpretty.left(u'\N{SQUARE ROOT}'))
++ return prettyForm(*bpretty.left(u'SQUARE ROOT'))
+
+ # Construct root sign, start with the \/ shape
+ _zZ = xobj('/', 1)
+@@ -1558,7 +1558,7 @@ class PrettyPrinter(Printer):
+ from sympy import Pow
+ return self._print(Pow(p.sets[0], len(p.sets), evaluate=False))
+ else:
+- prod_char = u"\N{MULTIPLICATION SIGN}" if self._use_unicode else 'x'
++ prod_char = u"MULTIPLICATION SIGN" if self._use_unicode else 'x'
+ return self._print_seq(p.sets, None, None, ' %s ' % prod_char,
+ parenthesize=lambda set: set.is_Union or
+ set.is_Intersection or set.is_ProductSet)
+@@ -1570,7 +1570,7 @@ class PrettyPrinter(Printer):
+ def _print_Range(self, s):
+
+ if self._use_unicode:
+- dots = u"\N{HORIZONTAL ELLIPSIS}"
++ dots = u"HORIZONTAL ELLIPSIS"
+ else:
+ dots = '...'
+
+@@ -1641,7 +1641,7 @@ class PrettyPrinter(Printer):
+
+ def _print_ImageSet(self, ts):
+ if self._use_unicode:
+- inn = u"\N{SMALL ELEMENT OF}"
++ inn = u"SMALL ELEMENT OF"
+ else:
+ inn = 'in'
+ variables = self._print_seq(ts.lamda.variables)
+@@ -1653,10 +1653,10 @@ class PrettyPrinter(Printer):
+
+ def _print_ConditionSet(self, ts):
+ if self._use_unicode:
+- inn = u"\N{SMALL ELEMENT OF}"
++ inn = u"SMALL ELEMENT OF"
+ # using _and because and is a keyword and it is bad practice to
+ # overwrite them
+- _and = u"\N{LOGICAL AND}"
++ _and = u"LOGICAL AND"
+ else:
+ inn = 'in'
+ _and = 'and'
+@@ -1677,7 +1677,7 @@ class PrettyPrinter(Printer):
+
+ def _print_ComplexRegion(self, ts):
+ if self._use_unicode:
+- inn = u"\N{SMALL ELEMENT OF}"
++ inn = u"SMALL ELEMENT OF"
+ else:
+ inn = 'in'
+ variables = self._print_seq(ts.variables)
+@@ -1690,7 +1690,7 @@ class PrettyPrinter(Printer):
+ def _print_Contains(self, e):
+ var, set = e.args
+ if self._use_unicode:
+- el = u" \N{ELEMENT OF} "
++ el = u" ELEMENT OF "
+ return prettyForm(*stringPict.next(self._print(var),
+ el, self._print(set)), binding=8)
+ else:
+@@ -1698,7 +1698,7 @@ class PrettyPrinter(Printer):
+
+ def _print_FourierSeries(self, s):
+ if self._use_unicode:
+- dots = u"\N{HORIZONTAL ELLIPSIS}"
++ dots = u"HORIZONTAL ELLIPSIS"
+ else:
+ dots = '...'
+ return self._print_Add(s.truncate()) + self._print(dots)
+@@ -1708,7 +1708,7 @@ class PrettyPrinter(Printer):
+
+ def _print_SeqFormula(self, s):
+ if self._use_unicode:
+- dots = u"\N{HORIZONTAL ELLIPSIS}"
++ dots = u"HORIZONTAL ELLIPSIS"
+ else:
+ dots = '...'
+
diff --git a/pythonforandroid/recipes/sympy/fix_timeutils.patch b/pythonforandroid/recipes/sympy/fix_timeutils.patch
new file mode 100644
index 000000000..c8424eaa2
--- /dev/null
+++ b/pythonforandroid/recipes/sympy/fix_timeutils.patch
@@ -0,0 +1,13 @@
+diff --git a/sympy/utilities/timeutils.py b/sympy/utilities/timeutils.py
+index 3770d85..c53594e 100644
+--- a/sympy/utilities/timeutils.py
++++ b/sympy/utilities/timeutils.py
+@@ -8,7 +8,7 @@ import math
+ from sympy.core.compatibility import range
+
+ _scales = [1e0, 1e3, 1e6, 1e9]
+-_units = [u's', u'ms', u'\N{GREEK SMALL LETTER MU}s', u'ns']
++_units = [u's', u'ms', u'mus', u'ns']
+
+
+ def timed(func, setup="pass", limit=None):
diff --git a/pythonforandroid/recipes/tflite-runtime/CMakeLists.patch b/pythonforandroid/recipes/tflite-runtime/CMakeLists.patch
new file mode 100644
index 000000000..f39d9b329
--- /dev/null
+++ b/pythonforandroid/recipes/tflite-runtime/CMakeLists.patch
@@ -0,0 +1,28 @@
+--- tflite-runtime/tensorflow/lite/CMakeLists.txt 2022-01-27 17:29:49.460000000 -1000
++++ CMakeLists.txt 2022-02-21 15:03:09.568367300 -1000
+@@ -220,6 +220,9 @@
+ if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "iOS")
+ list(FILTER TFLITE_SRCS EXCLUDE REGEX ".*minimal_logging_ios\\.cc$")
+ endif()
++if("${CMAKE_SYSTEM_NAME}" STREQUAL "Android")
++ list(FILTER TFLITE_SRCS EXCLUDE REGEX ".*minimal_logging_default\\.cc$")
++endif()
+ populate_tflite_source_vars("core" TFLITE_CORE_SRCS)
+ populate_tflite_source_vars("core/api" TFLITE_CORE_API_SRCS)
+ populate_tflite_source_vars("c" TFLITE_C_SRCS)
+@@ -505,6 +508,7 @@
+ ruy
+ ${CMAKE_DL_LIBS}
+ ${TFLITE_TARGET_DEPENDENCIES}
++ ${ANDROID_LOG_LIB}
+ )
+
+ if (NOT BUILD_SHARED_LIBS)
+@@ -550,6 +554,7 @@
+ tensorflow-lite
+ ${CMAKE_DL_LIBS}
+ )
++
+ target_compile_options(_pywrap_tensorflow_interpreter_wrapper
+ PUBLIC ${TFLITE_TARGET_PUBLIC_OPTIONS}
+ PRIVATE ${TFLITE_TARGET_PRIVATE_OPTIONS}
diff --git a/pythonforandroid/recipes/tflite-runtime/__init__.py b/pythonforandroid/recipes/tflite-runtime/__init__.py
new file mode 100644
index 000000000..1d208866c
--- /dev/null
+++ b/pythonforandroid/recipes/tflite-runtime/__init__.py
@@ -0,0 +1,108 @@
+from pythonforandroid.recipe import PythonRecipe, current_directory, \
+ shprint, info_main, warning
+from pythonforandroid.logger import error
+from os.path import join
+import sh
+
+
+class TFLiteRuntimeRecipe(PythonRecipe):
+ ###############################################################
+ #
+ # tflite-runtime README:
+ # https://github.com/Android-for-Python/c4k_tflite_example/blob/main/README.md
+ #
+ # Recipe build references:
+ # https://developer.android.com/ndk/guides/cmake
+ # https://developer.android.com/ndk/guides/cpu-arm-neon#cmake
+ # https://www.tensorflow.org/lite/guide/build_cmake
+ # https://www.tensorflow.org/lite/guide/build_cmake_arm
+ #
+ # Tested using cmake 3.16.3 probably requires cmake >= 3.13
+ #
+ # THIS RECIPE DOES NOT BUILD x86_64, USE X86 FOR AN EMULATOR
+ #
+ ###############################################################
+
+ version = '2.8.0'
+ url = 'https://github.com/tensorflow/tensorflow/archive/refs/tags/v{version}.zip'
+ depends = ['pybind11', 'numpy']
+ patches = ['CMakeLists.patch', 'build_with_cmake.patch']
+ site_packages_name = 'tflite-runtime'
+ call_hostpython_via_targetpython = False
+
+ def should_build(self, arch):
+ name = self.folder_name.replace('-', '_')
+
+ if self.ctx.has_package(name, arch):
+ info_main('Python package already exists in site-packages')
+ return False
+ info_main('{} apparently isn\'t already in site-packages'.format(name))
+ return True
+
+ def build_arch(self, arch):
+ if arch.arch == 'x86_64':
+ warning("******** tflite-runtime x86_64 will not be built *******")
+ warning("Expect one of these app run time error messages:")
+ warning("ModuleNotFoundError: No module named 'tensorflow'")
+ warning("ModuleNotFoundError: No module named 'tflite_runtime'")
+ warning("Use x86 not x86_64")
+ return
+
+ env = self.get_recipe_env(arch)
+
+ # Directories
+ root_dir = self.get_build_dir(arch.arch)
+ script_dir = join(root_dir,
+ 'tensorflow', 'lite', 'tools', 'pip_package')
+ build_dir = join(script_dir, 'gen', 'tflite_pip', 'python3')
+
+ # Includes
+ python_include_dir = self.ctx.python_recipe.include_root(arch.arch)
+ pybind11_recipe = self.get_recipe('pybind11', self.ctx)
+ pybind11_include_dir = pybind11_recipe.get_include_dir(arch)
+ numpy_include_dir = join(self.ctx.get_site_packages_dir(arch),
+ 'numpy', 'core', 'include')
+ includes = ' -I' + python_include_dir + \
+ ' -I' + numpy_include_dir + \
+ ' -I' + pybind11_include_dir
+
+ # Scripts
+ build_script = join(script_dir, 'build_pip_package_with_cmake.sh')
+ toolchain = join(self.ctx.ndk_dir,
+ 'build', 'cmake', 'android.toolchain.cmake')
+
+ # Build
+ ########
+ with current_directory(root_dir):
+ env.update({
+ 'TENSORFLOW_TARGET': 'android',
+ 'CMAKE_TOOLCHAIN_FILE': toolchain,
+ 'ANDROID_PLATFORM': str(self.ctx.ndk_api),
+ 'ANDROID_ABI': arch.arch,
+ 'WRAPPER_INCLUDES': includes,
+ 'CMAKE_SHARED_LINKER_FLAGS': env['LDFLAGS'],
+ })
+
+ try:
+ info_main('tflite-runtime is building...')
+ info_main('Expect this to take at least 5 minutes...')
+ cmd = sh.Command(build_script)
+ cmd(_env=env)
+ except sh.ErrorReturnCode as e:
+ error(str(e.stderr))
+ exit(1)
+
+ # Install
+ ##########
+ info_main('Installing tflite-runtime into site-packages')
+ with current_directory(build_dir):
+ hostpython = sh.Command(self.hostpython_location)
+ install_dir = self.ctx.get_python_install_dir(arch.arch)
+ env['PACKAGE_VERSION'] = self.version
+ shprint(hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(install_dir),
+ '--install-lib=.',
+ _env=env)
+
+
+recipe = TFLiteRuntimeRecipe()
diff --git a/pythonforandroid/recipes/tflite-runtime/build_with_cmake.patch b/pythonforandroid/recipes/tflite-runtime/build_with_cmake.patch
new file mode 100644
index 000000000..9670e1865
--- /dev/null
+++ b/pythonforandroid/recipes/tflite-runtime/build_with_cmake.patch
@@ -0,0 +1,48 @@
+--- tflite-runtime/tensorflow/lite/tools/pip_package/build_pip_package_with_cmake.sh 2022-01-22 08:57:16.000000000 -1000
++++ build_pip_package_with_cmake.sh 2022-03-02 18:19:05.185550500 -1000
+@@ -28,7 +28,7 @@
+ export TENSORFLOW_TARGET="armhf"
+ fi
+ PYTHON_INCLUDE=$(${PYTHON} -c "from sysconfig import get_paths as gp; print(gp()['include'])")
+-PYBIND11_INCLUDE=$(${PYTHON} -c "import pybind11; print (pybind11.get_include())")
++# PYBIND11_INCLUDE=$(${PYTHON} -c "import pybind11; print (pybind11.get_include())")
+ export CROSSTOOL_PYTHON_INCLUDE_PATH=${PYTHON_INCLUDE}
+
+ # Fix container image for cross build.
+@@ -58,7 +58,7 @@
+ "${TENSORFLOW_LITE_DIR}/python/metrics/metrics_portable.py" \
+ "${BUILD_DIR}/tflite_runtime"
+ echo "__version__ = '${PACKAGE_VERSION}'" >> "${BUILD_DIR}/tflite_runtime/__init__.py"
+-echo "__git_version__ = '$(git -C "${TENSORFLOW_DIR}" describe)'" >> "${BUILD_DIR}/tflite_runtime/__init__.py"
++echo "__git_version__ = '${PACKAGE_VERSION}'" >> "${BUILD_DIR}/tflite_runtime/__init__.py"
+
+ # Build python interpreter_wrapper.
+ mkdir -p "${BUILD_DIR}/cmake_build"
+@@ -111,6 +111,18 @@
+ -DCMAKE_CXX_FLAGS="${BUILD_FLAGS}" \
+ "${TENSORFLOW_LITE_DIR}"
+ ;;
++ android)
++ BUILD_FLAGS=${BUILD_FLAGS:-"${WRAPPER_INCLUDES}"}
++ cmake \
++ -DCMAKE_SYSTEM_NAME=Android \
++ -DANDROID_ARM_NEON=ON \
++ -DCMAKE_CXX_FLAGS="${BUILD_FLAGS}" \
++ -DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}" \
++ -DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}" \
++ -DANDROID_PLATFORM="${ANDROID_PLATFORM}" \
++ -DANDROID_ABI="${ANDROID_ABI}" \
++ "${TENSORFLOW_LITE_DIR}"
++ ;;
+ *)
+ BUILD_FLAGS=${BUILD_FLAGS:-"-I${PYTHON_INCLUDE} -I${PYBIND11_INCLUDE}"}
+ cmake \
+@@ -162,7 +174,7 @@
+ ${PYTHON} setup.py bdist --plat-name=${WHEEL_PLATFORM_NAME} \
+ bdist_wheel --plat-name=${WHEEL_PLATFORM_NAME}
+ else
+- ${PYTHON} setup.py bdist bdist_wheel
++ ${PYTHON} setup.py bdist
+ fi
+ ;;
+ esac
diff --git a/pythonforandroid/recipes/twisted/__init__.py b/pythonforandroid/recipes/twisted/__init__.py
index bb9624340..30a7af4bb 100644
--- a/pythonforandroid/recipes/twisted/__init__.py
+++ b/pythonforandroid/recipes/twisted/__init__.py
@@ -1,37 +1,38 @@
+import os
-import glob
-from pythonforandroid.toolchain import (
- CythonRecipe,
- Recipe,
- current_directory,
- info,
- shprint,
-)
-from os.path import join
-import sh
+from pythonforandroid.recipe import CythonRecipe
+from pythonforandroid.util import rmdir
class TwistedRecipe(CythonRecipe):
- version = '17.9.0'
+ version = '20.3.0'
url = 'https://github.com/twisted/twisted/archive/twisted-{version}.tar.gz'
depends = ['setuptools', 'zope_interface', 'incremental', 'constantly']
+ patches = ['incremental.patch', 'remove_tests.patch']
call_hostpython_via_targetpython = False
- install_in_hostpython = True
+ install_in_hostpython = False
def prebuild_arch(self, arch):
- super(TwistedRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
# TODO Need to whitelist tty.pyo and termios.so here
- print('Should remove twisted tests etc. here, but skipping for now')
+
+ # remove the unit test dirs
+ source_dir = os.path.join(self.get_build_dir(arch.arch), 'src/twisted')
+ for item in os.walk(source_dir):
+ if os.path.basename(item[0]) == 'test':
+ full_path = os.path.join(source_dir, item[0])
+ rmdir(full_path, ignore_errors=True)
def get_recipe_env(self, arch):
- env = super(TwistedRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
# We add BUILDLIB_PATH to PYTHONPATH so twisted can find _io.so
env['PYTHONPATH'] = ':'.join([
- self.ctx.get_site_packages_dir(),
+ self.ctx.get_site_packages_dir(arch),
env['BUILDLIB_PATH'],
])
return env
+
recipe = TwistedRecipe()
diff --git a/pythonforandroid/recipes/twisted/incremental.patch b/pythonforandroid/recipes/twisted/incremental.patch
new file mode 100644
index 000000000..61656fc22
--- /dev/null
+++ b/pythonforandroid/recipes/twisted/incremental.patch
@@ -0,0 +1,20 @@
+diff -Naur twisted-twisted-19.7.0/src/twisted/python/_setup.py twisted-twisted-19.7.0_patched/src/twisted/python/_setup.py
+--- twisted-twisted-19.7.0/src/twisted/python/_setup.py 2019-07-28 11:17:29.000000000 +0200
++++ twisted-twisted-19.7.0_patched/src/twisted/python/_setup.py 2019-10-21 22:10:03.643068863 +0200
+@@ -282,7 +282,6 @@
+ requirements = [
+ "zope.interface >= 4.4.2",
+ "constantly >= 15.1",
+- "incremental >= 16.10.1",
+ "Automat >= 0.3.0",
+ "hyperlink >= 17.1.1",
+ "PyHamcrest >= 1.9.0",
+@@ -291,8 +290,6 @@
+
+ arguments.update(dict(
+ packages=find_packages("src"),
+- use_incremental=True,
+- setup_requires=["incremental >= 16.10.1"],
+ install_requires=requirements,
+ entry_points={
+ 'console_scripts': _CONSOLE_SCRIPTS
diff --git a/pythonforandroid/recipes/twisted/remove_tests.patch b/pythonforandroid/recipes/twisted/remove_tests.patch
new file mode 100644
index 000000000..492062b98
--- /dev/null
+++ b/pythonforandroid/recipes/twisted/remove_tests.patch
@@ -0,0 +1,16 @@
+diff --git a/src/twisted/python/_setup.py b/src/twisted/python/_setup.py
+index 32cb096c7..a607fef07 100644
+--- a/src/twisted/python/_setup.py
++++ b/src/twisted/python/_setup.py
+@@ -160,11 +160,6 @@ class ConditionalExtension(Extension, object):
+
+ # The C extensions used for Twisted.
+ _EXTENSIONS = [
+- ConditionalExtension(
+- "twisted.test.raiser",
+- sources=["src/twisted/test/raiser.c"],
+- condition=lambda _: _isCPython),
+-
+ ConditionalExtension(
+ "twisted.internet.iocpreactor.iocpsupport",
+ sources=[
diff --git a/pythonforandroid/recipes/ujson/__init__.py b/pythonforandroid/recipes/ujson/__init__.py
index 4cccc094e..421e4d927 100644
--- a/pythonforandroid/recipes/ujson/__init__.py
+++ b/pythonforandroid/recipes/ujson/__init__.py
@@ -1,9 +1,10 @@
-from pythonforandroid.toolchain import CompiledComponentsPythonRecipe
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
class UJsonRecipe(CompiledComponentsPythonRecipe):
version = '1.35'
url = 'https://pypi.python.org/packages/source/u/ujson/ujson-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
-
+ depends = []
+
+
recipe = UJsonRecipe()
diff --git a/pythonforandroid/recipes/vispy/__init__.py b/pythonforandroid/recipes/vispy/__init__.py
index 1b4068f46..7ea046b3b 100644
--- a/pythonforandroid/recipes/vispy/__init__.py
+++ b/pythonforandroid/recipes/vispy/__init__.py
@@ -1,18 +1,10 @@
-
-from pythonforandroid.toolchain import PythonRecipe
+from pythonforandroid.recipe import PythonRecipe
class VispyRecipe(PythonRecipe):
- # version = 'v0.4.0'
- version = 'master'
- url = 'https://github.com/vispy/vispy/archive/{version}.tar.gz'
- # version = 'campagnola-scenegraph-update'
- # url = 'https://github.com/campagnola/vispy/archive/scenegraph-update.zip'
- # version = '???'
- # url = 'https://github.com/inclement/vispy/archive/Eric89GXL-arcball.zip'
-
- depends = ['python2', 'numpy', 'pysdl2']
-
+ version = '0.4.0'
+ url = 'https://github.com/vispy/vispy/archive/v{version}.tar.gz'
+ depends = ['numpy', 'pysdl2']
patches = ['disable_freetype.patch',
'disable_font_triage.patch',
'use_es2.patch',
diff --git a/pythonforandroid/recipes/vlc/__init__.py b/pythonforandroid/recipes/vlc/__init__.py
index a4f71f612..0995576f5 100644
--- a/pythonforandroid/recipes/vlc/__init__.py
+++ b/pythonforandroid/recipes/vlc/__init__.py
@@ -3,10 +3,10 @@
from os.path import join, isdir, isfile
from os import environ
import sh
-from colorama import Fore, Style
+
class VlcRecipe(Recipe):
- version = '3.0.0'
+ version = '3.0.18'
url = None
name = 'vlc'
@@ -15,10 +15,10 @@ class VlcRecipe(Recipe):
port_git = 'http://git.videolan.org/git/vlc-ports/android.git'
# vlc_git = 'http://git.videolan.org/git/vlc.git'
ENV_LIBVLC_AAR = 'LIBVLC_AAR'
- aars = {} # for future use of multiple arch
+ aars = {} # for future use of multiple arch
def prebuild_arch(self, arch):
- super(VlcRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
build_dir = self.get_build_dir(arch.arch)
port_dir = join(build_dir, 'vlc-port-android')
if self.ENV_LIBVLC_AAR in environ:
@@ -33,15 +33,15 @@ def prebuild_arch(self, arch):
else:
aar_path = join(port_dir, 'libvlc', 'build', 'outputs', 'aar')
self.aars[arch] = aar = join(aar_path, 'libvlc-{}.aar'.format(self.version))
- warning("HINT: set path to precompiled libvlc-<ver>.aar bundle " \
- "in {} environment!".format(self.ENV_LIBVLC_AAR))
- info("libvlc-<ver>.aar should build " \
- "from sources at {}".format(port_dir))
+ warning("HINT: set path to precompiled libvlc-<ver>.aar bundle "
+ "in {} environment!".format(self.ENV_LIBVLC_AAR))
+ info("libvlc-<ver>.aar should build "
+ "from sources at {}".format(port_dir))
if not isfile(join(port_dir, 'compile.sh')):
info("clone vlc port for android sources from {}".format(
self.port_git))
shprint(sh.git, 'clone', self.port_git, port_dir,
- _tail=20, _critical=True)
+ _tail=20, _critical=True)
# now "git clone ..." is a part of compile.sh
# vlc_dir = join(port_dir, 'vlc')
# if not isfile(join(vlc_dir, 'Makefile.am')):
@@ -50,9 +50,9 @@ def prebuild_arch(self, arch):
# _tail=20, _critical=True)
def build_arch(self, arch):
- super(VlcRecipe, self).build_arch(arch)
+ super().build_arch(arch)
build_dir = self.get_build_dir(arch.arch)
- port_dir = join(build_dir, 'vlc-port-android')
+ port_dir = join(build_dir, 'vlc-port-android', 'buildsystem')
aar = self.aars[arch]
if not isfile(aar):
with current_directory(port_dir):
@@ -65,10 +65,11 @@ def build_arch(self, arch):
info("compiling vlc from sources")
debug("environment: {}".format(env))
if not isfile(join('bin', 'VLC-debug.apk')):
- shprint(sh.Command('./compile.sh'), _env=env,
- _tail=50, _critical=True)
- shprint(sh.Command('./compile-libvlc.sh'), _env=env,
+ shprint(sh.Command('./compile.sh'), _env=env,
_tail=50, _critical=True)
+ shprint(sh.Command('./compile-medialibrary.sh'), _env=env,
+ _tail=50, _critical=True)
shprint(sh.cp, '-a', aar, self.ctx.aars_dir)
+
recipe = VlcRecipe()
diff --git a/pythonforandroid/recipes/websocket-client/__init__.py b/pythonforandroid/recipes/websocket-client/__init__.py
deleted file mode 100644
index b0de3d005..000000000
--- a/pythonforandroid/recipes/websocket-client/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from pythonforandroid.toolchain import Recipe
-
-# if android app crashes on start with "ImportError: No module named websocket"
-#
-# copy the 'websocket' directory into your app directory to force inclusion.
-#
-# see my example at https://github.com/debauchery1st/example_kivy_websocket-recipe
-#
-# If you see errors relating to 'SSL not available' ensure you have the package backports.ssl-match-hostname
-# in the buildozer requirements, since Kivy targets python 2.7.x
-#
-# You may also need sslopt={"cert_reqs": ssl.CERT_NONE} as a parameter to ws.run_forever() if you get an error relating to
-# host verification
-
-
-class WebSocketClient(Recipe):
-
- url = 'https://github.com/debauchery1st/websocket-client/raw/master/websocket_client-0.40.0.tar.gz'
-
- version = '0.40.0'
- # md5sum = 'f1cf4cc7869ef97a98e5f4be25c30986'
-
- # patches = ['websocket.patch'] # Paths relative to the recipe dir
-
- depends = ['kivy', 'python2', 'android', 'pyjnius',
- 'cryptography', 'pyasn1', 'pyopenssl']
-
-recipe = WebSocketClient()
diff --git a/pythonforandroid/recipes/websocket-client/websocket.patch b/pythonforandroid/recipes/websocket-client/websocket.patch
deleted file mode 100644
index 694bcb653..000000000
--- a/pythonforandroid/recipes/websocket-client/websocket.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-diff --git a/websocket/_logging.py b/websocket/_logging.py
-index 8a5f4a5..cebc23b 100644
---- a/websocket/_logging.py
-+++ b/websocket/_logging.py
-@@ -19,9 +19,8 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
- Boston, MA 02110-1335 USA
-
- """
--import logging
--
--_logger = logging.getLogger('websocket')
-+from kivy.logger import Logger
-+_logger = Logger
- _traceEnabled = False
-
- __all__ = ["enableTrace", "dump", "error", "debug", "trace",
-@@ -67,8 +66,9 @@ def trace(msg):
-
-
- def isEnabledForError():
-- return _logger.isEnabledFor(logging.ERROR)
-+ return True
-
-
- def isEnabledForDebug():
-- return _logger.isEnabledFor(logging.DEBUG)
-+ return True
-+
diff --git a/pythonforandroid/recipes/wsaccel/__init__.py b/pythonforandroid/recipes/wsaccel/__init__.py
index ad257b8e6..7bfc3465d 100644
--- a/pythonforandroid/recipes/wsaccel/__init__.py
+++ b/pythonforandroid/recipes/wsaccel/__init__.py
@@ -1,12 +1,11 @@
-import os
-import sh
from pythonforandroid.recipe import CythonRecipe
class WSAccellRecipe(CythonRecipe):
version = '0.6.2'
url = 'https://pypi.python.org/packages/source/w/wsaccel/wsaccel-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = []
call_hostpython_via_targetpython = False
+
recipe = WSAccellRecipe()
diff --git a/pythonforandroid/recipes/x3dh/__init__.py b/pythonforandroid/recipes/x3dh/__init__.py
new file mode 100644
index 000000000..134bf2991
--- /dev/null
+++ b/pythonforandroid/recipes/x3dh/__init__.py
@@ -0,0 +1,18 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class X3DHRecipe(PythonRecipe):
+ name = 'x3dh'
+ version = '0.5.3'
+ url = 'https://pypi.python.org/packages/source/X/X3DH/X3DH-{version}.tar.gz'
+ site_packages_name = 'x3dh'
+ depends = [
+ 'setuptools',
+ 'cryptography',
+ 'xeddsa',
+ ]
+ patches = ['requires_fix.patch']
+ call_hostpython_via_targetpython = False
+
+
+recipe = X3DHRecipe()
diff --git a/pythonforandroid/recipes/x3dh/requires_fix.patch b/pythonforandroid/recipes/x3dh/requires_fix.patch
new file mode 100644
index 000000000..250df058b
--- /dev/null
+++ b/pythonforandroid/recipes/x3dh/requires_fix.patch
@@ -0,0 +1,12 @@
+diff -urN X3DH-0.5.3.ori/setup.py X3DH-0.5.3/setup.py
+--- X3DH-0.5.3.ori/setup.py 2018-10-28 19:15:16.444766623 +0100
++++ X3DH-0.5.3/setup.py 2018-10-28 19:15:38.028060948 +0100
+@@ -24,7 +24,7 @@
+ author_email = "tim@cifg.io",
+ license = "MIT",
+ packages = find_packages(),
+- install_requires = [ "cryptography>=1.7.1", "XEdDSA>=0.4.2" ],
++ install_requires = [],
+ python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4",
+ zip_safe = True,
+ classifiers = [
diff --git a/pythonforandroid/recipes/xeddsa/__init__.py b/pythonforandroid/recipes/xeddsa/__init__.py
new file mode 100644
index 000000000..d386f921c
--- /dev/null
+++ b/pythonforandroid/recipes/xeddsa/__init__.py
@@ -0,0 +1,33 @@
+from pythonforandroid.recipe import CythonRecipe
+from pythonforandroid.toolchain import current_directory, shprint
+from os.path import join
+import sh
+
+
+class XedDSARecipe(CythonRecipe):
+ name = 'xeddsa'
+ version = '0.4.4'
+ url = 'https://pypi.python.org/packages/source/X/XEdDSA/XEdDSA-{version}.tar.gz'
+ depends = [
+ 'setuptools',
+ 'cffi',
+ 'pynacl',
+ ]
+ patches = ['remove_dependencies.patch']
+ call_hostpython_via_targetpython = False
+
+ def build_arch(self, arch):
+ with current_directory(join(self.get_build_dir(arch.arch))):
+ env = self.get_recipe_env(arch)
+ hostpython = sh.Command(self.ctx.hostpython)
+ shprint(
+ hostpython, 'ref10/build.py',
+ _env=env
+ )
+ # the library could be `_crypto_sign.cpython-37m-x86_64-linux-gnu.so`
+ # or simply `_crypto_sign.so` depending on the platform/distribution
+ sh.cp('-a', sh.glob('_crypto_sign*.so'), self.ctx.get_site_packages_dir(arch))
+ self.install_python_package(arch)
+
+
+recipe = XedDSARecipe()
diff --git a/pythonforandroid/recipes/xeddsa/remove_dependencies.patch b/pythonforandroid/recipes/xeddsa/remove_dependencies.patch
new file mode 100644
index 000000000..8bd762fb6
--- /dev/null
+++ b/pythonforandroid/recipes/xeddsa/remove_dependencies.patch
@@ -0,0 +1,15 @@
+diff -urN XEdDSA-0.4.4.ori/setup.py XEdDSA-0.4.4/setup.py
+--- XEdDSA-0.4.4.ori/setup.py 2018-09-23 16:08:35.000000000 +0200
++++ XEdDSA-0.4.4/setup.py 2018-10-30 08:21:23.338790184 +0100
+@@ -22,9 +22,8 @@
+ author_email = "tim@cifg.io",
+ license = "MIT",
+ packages = find_packages(),
+- install_requires = [ "cffi>=1.9.1", "pynacl>=1.0.1" ],
+- setup_requires = [ "cffi>=1.9.1" ],
+- cffi_modules = [ os.path.join("ref10", "build.py") + ":ffibuilder" ],
++ install_requires = ["pynacl>=1.0.1" ],
++ setup_requires = [],
+ python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4",
+ include_package_data = True,
+ zip_safe = False,
diff --git a/pythonforandroid/recipes/zbar/__init__.py b/pythonforandroid/recipes/zbar/__init__.py
index 6f604e375..c24971e21 100644
--- a/pythonforandroid/recipes/zbar/__init__.py
+++ b/pythonforandroid/recipes/zbar/__init__.py
@@ -1,4 +1,4 @@
-import os
+from os.path import join
from pythonforandroid.recipe import PythonRecipe
@@ -15,22 +15,18 @@ class ZBarRecipe(PythonRecipe):
call_hostpython_via_targetpython = False
- depends = ['hostpython2', 'python2', 'setuptools', 'libzbar']
+ depends = ['setuptools', 'libzbar']
patches = ["zbar-0.10-python-crash.patch"]
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(ZBarRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env = super().get_recipe_env(arch, with_flags_in_cc)
libzbar = self.get_recipe('libzbar', self.ctx)
libzbar_dir = libzbar.get_build_dir(arch.arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + os.path.join(libzbar_dir, 'include')
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
- # TODO
- env['LDSHARED'] = env['CC'] + \
- ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- # TODO: hardcoded Python version
- env['LDFLAGS'] += " -landroid -lpython2.7 -lzbar"
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)
+ env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
+ env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
+ env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
return env
diff --git a/pythonforandroid/recipes/zbarlight/__init__.py b/pythonforandroid/recipes/zbarlight/__init__.py
new file mode 100644
index 000000000..36365cd03
--- /dev/null
+++ b/pythonforandroid/recipes/zbarlight/__init__.py
@@ -0,0 +1,26 @@
+from os.path import join
+from pythonforandroid.recipe import PythonRecipe
+
+
+class ZBarLightRecipe(PythonRecipe):
+
+ version = '2.1'
+
+ url = 'https://github.com/Polyconseil/zbarlight/archive/{version}.tar.gz' # noqa
+
+ call_hostpython_via_targetpython = False
+
+ depends = ['setuptools', 'libzbar']
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super().get_recipe_env(arch, with_flags_in_cc)
+ libzbar = self.get_recipe('libzbar', self.ctx)
+ libzbar_dir = libzbar.get_build_dir(arch.arch)
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch)
+ env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
+ env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
+ env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
+ return env
+
+
+recipe = ZBarLightRecipe()
diff --git a/pythonforandroid/recipes/zeroconf/__init__.py b/pythonforandroid/recipes/zeroconf/__init__.py
index 6ee14a19f..a23bd6e25 100644
--- a/pythonforandroid/recipes/zeroconf/__init__.py
+++ b/pythonforandroid/recipes/zeroconf/__init__.py
@@ -1,24 +1,12 @@
from pythonforandroid.recipe import PythonRecipe
-from os.path import join
class ZeroconfRecipe(PythonRecipe):
name = 'zeroconf'
- version = '0.17.4'
+ version = '0.24.5'
url = 'https://pypi.python.org/packages/source/z/zeroconf/zeroconf-{version}.tar.gz'
- depends = ['python2', 'netifaces', 'enum34', 'six']
-
- def get_recipe_env(self, arch=None):
- env = super(ZeroconfRecipe, self).get_recipe_env(arch)
-
- # TODO: fix hardcoded path
- # This is required to prevent issue with _io.so import.
- hostpython = self.get_recipe('hostpython2', self.ctx)
- env['PYTHONPATH'] = (
- join(hostpython.get_build_dir(arch.arch), 'build',
- 'lib.linux-x86_64-2.7') + ':' + env.get('PYTHONPATH', '')
- )
- return env
+ depends = ['setuptools', 'ifaddr', 'typing;python_version<"3.5"']
+ call_hostpython_via_targetpython = False
recipe = ZeroconfRecipe()
diff --git a/pythonforandroid/recipes/zope/__init__.py b/pythonforandroid/recipes/zope/__init__.py
index 399b041d9..9c5ab7bf9 100644
--- a/pythonforandroid/recipes/zope/__init__.py
+++ b/pythonforandroid/recipes/zope/__init__.py
@@ -1,27 +1,30 @@
-from pythonforandroid.toolchain import PythonRecipe, shprint, current_directory
-from os.path import exists, join
-import sh
-import glob
+from pythonforandroid.recipe import PythonRecipe
+from os.path import join
+
class ZopeRecipe(PythonRecipe):
name = 'zope'
version = '4.1.3'
- url = 'http://pypi.python.org/packages/source/z/zope.interface/zope.interface-{version}.tar.gz'
+ url = 'https://pypi.python.org/packages/source/z/zope.interface/zope.interface-{version}.tar.gz'
+
+ depends = []
- depends = ['python2']
-
def get_recipe_env(self, arch):
- env = super(ZopeRecipe, self).get_recipe_env(arch)
+ env = super().get_recipe_env(arch)
# These are in the old zope recipe but seem like they shouldn't actually be necessary
env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
self.ctx.get_libs_dir(arch.arch))
env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
+ return env
def postbuild_arch(self, arch):
- super(ZopeRecipe, self).postbuild_arch(arch)
+ super().postbuild_arch(arch)
# Should do some deleting here
+
recipe = ZopeRecipe()
+
+# FIXME: @mirko liblink & LD
diff --git a/pythonforandroid/recipes/zope_interface/__init__.py b/pythonforandroid/recipes/zope_interface/__init__.py
index 3c8afc249..7e7fecaea 100644
--- a/pythonforandroid/recipes/zope_interface/__init__.py
+++ b/pythonforandroid/recipes/zope_interface/__init__.py
@@ -1,5 +1,8 @@
-from pythonforandroid.toolchain import PythonRecipe, current_directory
-import sh
+from os.path import join
+
+from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.toolchain import current_directory
+from pythonforandroid.util import rmdir
class ZopeInterfaceRecipe(PythonRecipe):
@@ -8,14 +11,23 @@ class ZopeInterfaceRecipe(PythonRecipe):
version = '4.1.3'
url = 'https://pypi.python.org/packages/source/z/zope.interface/zope.interface-{version}.tar.gz'
site_packages_name = 'zope.interface'
-
- depends = ['python2']
+ depends = ['setuptools']
patches = ['no_tests.patch']
+ def build_arch(self, arch):
+ super().build_arch(arch)
+ # The zope.interface module lacks of the __init__.py file in one of his
+ # folders (once is installed), that leads into an ImportError.
+ # Here we intentionally apply a patch to solve that, so, in case that
+ # this is solved in the future an error will be triggered
+ zope_install = join(self.ctx.get_site_packages_dir(arch), 'zope')
+ self.apply_patch('fix-init.patch', arch.arch, build_dir=zope_install)
+
def prebuild_arch(self, arch):
- super(ZopeInterfaceRecipe, self).prebuild_arch(arch)
+ super().prebuild_arch(arch)
with current_directory(self.get_build_dir(arch.arch)):
- sh.rm('-rf', 'src/zope/interface/tests', 'src/zope/interface/common/tests')
+ rmdir('src/zope/interface/tests')
+ rmdir('src/zope/interface/common/tests')
recipe = ZopeInterfaceRecipe()
diff --git a/pythonforandroid/recipes/zope_interface/fix-init.patch b/pythonforandroid/recipes/zope_interface/fix-init.patch
new file mode 100644
index 000000000..b618eb531
--- /dev/null
+++ b/pythonforandroid/recipes/zope_interface/fix-init.patch
@@ -0,0 +1,9 @@
+The zope.interface module lacks of the __init__.py file in `zope` folder
+(once is installed), this patch creates that missing file. This seems to be
+caused during the installation process because that file exists in source
+files.
+diff -Naurp zope.orig/__init__.py zope/__init__.py
+--- zope.orig/__init__.py 1970-01-01 01:00:00.000000000 +0100
++++ zope/__init__.py 2019-02-05 11:29:22.666757227 +0100
+@@ -0,0 +1 @@
++
diff --git a/pythonforandroid/recommendations.py b/pythonforandroid/recommendations.py
new file mode 100644
index 000000000..cbcfdd2b6
--- /dev/null
+++ b/pythonforandroid/recommendations.py
@@ -0,0 +1,230 @@
+"""Simple functions for checking dependency versions."""
+
+import sys
+from os.path import join
+
+import packaging.version
+
+from pythonforandroid.logger import info, warning
+from pythonforandroid.util import BuildInterruptingException
+
+# We only check the NDK major version
+MIN_NDK_VERSION = 25
+MAX_NDK_VERSION = 25
+
+# DO NOT CHANGE LINE FORMAT: buildozer parses the existence of a RECOMMENDED_NDK_VERSION
+RECOMMENDED_NDK_VERSION = "25b"
+
+NDK_DOWNLOAD_URL = "https://developer.android.com/ndk/downloads/"
+
+# Important log messages
+NEW_NDK_MESSAGE = 'Newer NDKs may not be fully supported by p4a.'
+UNKNOWN_NDK_MESSAGE = (
+ 'Could not determine NDK version, no source.properties in the NDK dir.'
+)
+PARSE_ERROR_NDK_MESSAGE = (
+ 'Could not parse $NDK_DIR/source.properties, not checking NDK version.'
+)
+READ_ERROR_NDK_MESSAGE = (
+ 'Unable to read the NDK version from the given directory {ndk_dir}.'
+)
+ENSURE_RIGHT_NDK_MESSAGE = (
+ 'Make sure your NDK version is greater than {min_supported}. If you get '
+ 'build errors, download the recommended NDK {rec_version} from {ndk_url}.'
+)
+NDK_LOWER_THAN_SUPPORTED_MESSAGE = (
+ 'The minimum supported NDK version is {min_supported}. '
+ 'You can download it from {ndk_url}.'
+)
+UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE = (
+ 'Asked to build for armeabi architecture with API '
+ '{req_ndk_api}, but API {max_ndk_api} or greater does not support armeabi.'
+)
+CURRENT_NDK_VERSION_MESSAGE = (
+ 'Found NDK version {ndk_version}'
+)
+RECOMMENDED_NDK_VERSION_MESSAGE = (
+ 'Maximum recommended NDK version is {recommended_ndk_version}, but newer versions may work.'
+)
+
+
+def check_ndk_version(ndk_dir):
+ """
+ Check the NDK version against what is currently recommended and raise an
+ exception of :class:`~pythonforandroid.util.BuildInterruptingException` in
+ case that the user tries to use an NDK lower than minimum supported,
+ specified via attribute `MIN_NDK_VERSION`.
+
+ .. versionchanged:: 2019.06.06.1.dev0
+ Added the ability to get android's NDK `letter version` and also
+ rewrote to raise an exception in case that an NDK version lower than
+ the minimum supported is detected.
+ """
+ ndk_version = read_ndk_version(ndk_dir)
+
+ if ndk_version is None:
+ warning(READ_ERROR_NDK_MESSAGE.format(ndk_dir=ndk_dir))
+ warning(
+ ENSURE_RIGHT_NDK_MESSAGE.format(
+ min_supported=MIN_NDK_VERSION,
+ rec_version=RECOMMENDED_NDK_VERSION,
+ ndk_url=NDK_DOWNLOAD_URL,
+ )
+ )
+ return
+
+ # create a dictionary which will describe the relationship of the android's
+ # NDK minor version with the `human readable` letter version, egs:
+ # Pkg.Revision = 17.1.4828580 => ndk-17b
+ # Pkg.Revision = 17.2.4988734 => ndk-17c
+ # Pkg.Revision = 19.0.5232133 => ndk-19 (No letter)
+ minor_to_letter = {0: ''}
+ minor_to_letter.update(
+ {n + 1: chr(i) for n, i in enumerate(range(ord('b'), ord('b') + 25))}
+ )
+ string_version = f"{ndk_version.major}{minor_to_letter[ndk_version.minor]}"
+
+ info(CURRENT_NDK_VERSION_MESSAGE.format(ndk_version=string_version))
+
+ if ndk_version.major < MIN_NDK_VERSION:
+ raise BuildInterruptingException(
+ NDK_LOWER_THAN_SUPPORTED_MESSAGE.format(
+ min_supported=MIN_NDK_VERSION, ndk_url=NDK_DOWNLOAD_URL
+ ),
+ instructions=(
+ 'Please, go to the android NDK page ({ndk_url}) and download a'
+ ' supported version.\n*** The currently recommended NDK'
+ ' version is {rec_version} ***'.format(
+ ndk_url=NDK_DOWNLOAD_URL,
+ rec_version=RECOMMENDED_NDK_VERSION,
+ )
+ ),
+ )
+ elif ndk_version.major > MAX_NDK_VERSION:
+ warning(
+ RECOMMENDED_NDK_VERSION_MESSAGE.format(
+ recommended_ndk_version=RECOMMENDED_NDK_VERSION
+ )
+ )
+ warning(NEW_NDK_MESSAGE)
+
+
+def read_ndk_version(ndk_dir):
+ """Read the NDK version from the NDK dir, if possible"""
+ try:
+ with open(join(ndk_dir, 'source.properties')) as fileh:
+ ndk_data = fileh.read()
+ except IOError:
+ info(UNKNOWN_NDK_MESSAGE)
+ return
+
+ for line in ndk_data.split('\n'):
+ if line.startswith('Pkg.Revision'):
+ break
+ else:
+ info(PARSE_ERROR_NDK_MESSAGE)
+ return
+
+ # Line should have the form "Pkg.Revision = ..."
+ unparsed_ndk_version = line.split('=')[-1].strip()
+
+ return packaging.version.parse(unparsed_ndk_version)
+
+
+MIN_TARGET_API = 30
+
+# highest version tested to work fine with SDL2
+# should be a good default for other bootstraps too
+RECOMMENDED_TARGET_API = 33
+
+ARMEABI_MAX_TARGET_API = 21
+OLD_API_MESSAGE = (
+ 'Target APIs lower than 30 are no longer supported on Google Play, '
+ 'and are not recommended. Note that the Target API can be higher than '
+ 'your device Android version, and should usually be as high as possible.')
+
+
+def check_target_api(api, arch):
+ """Warn if the user's target API is less than the current minimum
+ recommendation
+ """
+
+ # FIXME: Should We remove support for armeabi (ARMv5)?
+ if api >= ARMEABI_MAX_TARGET_API and arch == 'armeabi':
+ raise BuildInterruptingException(
+ UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE.format(
+ req_ndk_api=api, max_ndk_api=ARMEABI_MAX_TARGET_API
+ ),
+ instructions='You probably want to build with --arch=armeabi-v7a instead')
+
+ if api < MIN_TARGET_API:
+ warning('Target API {} < {}'.format(api, MIN_TARGET_API))
+ warning(OLD_API_MESSAGE)
+
+
+MIN_NDK_API = 21
+RECOMMENDED_NDK_API = 21
+OLD_NDK_API_MESSAGE = ('NDK API less than {} is not supported'.format(MIN_NDK_API))
+TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE = (
+ 'Target NDK API is {ndk_api}, '
+ 'higher than the target Android API {android_api}.'
+)
+
+
+def check_ndk_api(ndk_api, android_api):
+ """Warn if the user's NDK is too high or low."""
+ if ndk_api > android_api:
+ raise BuildInterruptingException(
+ TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE.format(
+ ndk_api=ndk_api, android_api=android_api
+ ),
+ instructions=('The NDK API is a minimum supported API number and must be lower '
+ 'than the target Android API'))
+
+ if ndk_api < MIN_NDK_API:
+ warning(OLD_NDK_API_MESSAGE)
+
+
+MIN_PYTHON_MAJOR_VERSION = 3
+MIN_PYTHON_MINOR_VERSION = 6
+MIN_PYTHON_VERSION = packaging.version.Version(
+ f"{MIN_PYTHON_MAJOR_VERSION}.{MIN_PYTHON_MINOR_VERSION}"
+)
+PY2_ERROR_TEXT = (
+ 'python-for-android no longer supports running under Python 2. Either upgrade to '
+ 'Python {min_version} or higher (recommended), or revert to python-for-android 2019.07.08.'
+).format(min_version=MIN_PYTHON_VERSION)
+
+PY_VERSION_ERROR_TEXT = (
+ 'Your Python version {user_major}.{user_minor} is not supported by python-for-android, '
+ 'please upgrade to {min_version} or higher.'
+ ).format(
+ user_major=sys.version_info.major,
+ user_minor=sys.version_info.minor,
+ min_version=MIN_PYTHON_VERSION)
+
+
+def check_python_version():
+ # Python 2 special cased because it's a major transition. In the
+ # future the major or minor versions can increment more quietly.
+ if sys.version_info.major == 2:
+ raise BuildInterruptingException(PY2_ERROR_TEXT)
+
+ if (
+ sys.version_info.major < MIN_PYTHON_MAJOR_VERSION or
+ sys.version_info.minor < MIN_PYTHON_MINOR_VERSION
+ ):
+
+ raise BuildInterruptingException(PY_VERSION_ERROR_TEXT)
+
+
+def print_recommendations():
+ """
+ Print the main recommended dependency versions as simple key-value pairs.
+ """
+ print('Min supported NDK version: {}'.format(MIN_NDK_VERSION))
+ print('Recommended NDK version: {}'.format(RECOMMENDED_NDK_VERSION))
+ print('Min target API: {}'.format(MIN_TARGET_API))
+ print('Recommended target API: {}'.format(RECOMMENDED_TARGET_API))
+ print('Min NDK API: {}'.format(MIN_NDK_API))
+ print('Recommended NDK API: {}'.format(RECOMMENDED_NDK_API))
diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py
index 028c540d1..1347038b8 100644
--- a/pythonforandroid/toolchain.py
+++ b/pythonforandroid/toolchain.py
@@ -6,93 +6,47 @@
This module defines the entry point for command line and programmatic use.
"""
-from __future__ import print_function
-from pythonforandroid import __version__
-
-def check_python_dependencies():
- # Check if the Python requirements are installed. This appears
- # before the imports because otherwise they're imported elsewhere.
-
- # Using the ok check instead of failing immediately so that all
- # errors are printed at once
-
- from distutils.version import LooseVersion
- from importlib import import_module
- import sys
-
- ok = True
-
- modules = [('colorama', '0.3.3'), 'appdirs', ('sh', '1.10'), 'jinja2',
- 'six']
-
- for module in modules:
- if isinstance(module, tuple):
- module, version = module
- else:
- version = None
-
- try:
- import_module(module)
- except ImportError:
- if version is None:
- print('ERROR: The {} Python module could not be found, please '
- 'install it.'.format(module))
- ok = False
- else:
- print('ERROR: The {} Python module could not be found, '
- 'please install version {} or higher'.format(
- module, version))
- ok = False
- else:
- if version is None:
- continue
- try:
- cur_ver = sys.modules[module].__version__
- except AttributeError: # this is sometimes not available
- continue
- if LooseVersion(cur_ver) < LooseVersion(version):
- print('ERROR: {} version is {}, but python-for-android needs '
- 'at least {}.'.format(module, cur_ver, version))
- ok = False
-
- if not ok:
- print('python-for-android is exiting due to the errors above.')
- exit(1)
-
-
-check_python_dependencies()
-
-
-import sys
-from sys import platform
-from os.path import (join, dirname, realpath, exists, expanduser, basename)
-import os
+from appdirs import user_data_dir
+import argparse
+from functools import wraps
import glob
-import shutil
+import logging
+import os
+from os import environ
+from os.path import (join, dirname, realpath, exists, expanduser, basename)
import re
-import imp
import shlex
-from functools import wraps
+import sys
+from sys import platform
-import argparse
+# This must be imported and run before other third-party or p4a
+# packages.
+from pythonforandroid.checkdependencies import check
+check()
+
+from packaging.version import Version
import sh
-import imp
-from appdirs import user_data_dir
-import logging
-from distutils.version import LooseVersion
-from pythonforandroid.recipe import (Recipe, PythonRecipe, CythonRecipe,
- CompiledComponentsPythonRecipe,
- BootstrapNDKRecipe, NDKRecipe)
-from pythonforandroid.archs import (ArchARM, ArchARMv7_a, Archx86)
-from pythonforandroid.logger import (logger, info, warning, setup_color,
- Out_Style, Out_Fore, Err_Style, Err_Fore,
- info_notify, info_main, shprint, error)
-from pythonforandroid.util import current_directory, ensure_dir
+from pythonforandroid import __version__
from pythonforandroid.bootstrap import Bootstrap
+from pythonforandroid.build import Context, build_recipes
from pythonforandroid.distribution import Distribution, pretty_log_dists
+from pythonforandroid.entrypoints import main
from pythonforandroid.graph import get_recipe_order_and_bootstrap
-from pythonforandroid.build import Context, build_recipes
+from pythonforandroid.logger import (logger, info, warning, setup_color,
+ Out_Style, Out_Fore,
+ info_notify, info_main, shprint)
+from pythonforandroid.pythonpackage import get_dep_names_of_package
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.recommendations import (
+ RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API, print_recommendations)
+from pythonforandroid.util import (
+ current_directory,
+ BuildInterruptingException,
+ load_source,
+ rmdir,
+ max_build_tool_version,
+)
user_dir = dirname(realpath(os.path.curdir))
toolchain_dir = dirname(__file__)
@@ -127,122 +81,144 @@ def add_no(x):
def require_prebuilt_dist(func):
- '''Decorator for ToolchainCL methods. If present, the method will
+ """Decorator for ToolchainCL methods. If present, the method will
automatically make sure a dist has been built before continuing
or, if no dists are present or can be obtained, will raise an
error.
-
- '''
+ """
@wraps(func)
- def wrapper_func(self, args):
+ def wrapper_func(self, args, **kw):
ctx = self.ctx
ctx.set_archs(self._archs)
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
user_ndk_dir=self.ndk_dir,
user_android_api=self.android_api,
- user_ndk_ver=self.ndk_version)
+ user_ndk_api=self.ndk_api)
dist = self._dist
if dist.needs_build:
+ if dist.folder_exists(): # possible if the dist is being replaced
+ dist.delete()
info_notify('No dist exists that meets your requirements, '
'so one will be built.')
build_dist_from_args(ctx, dist, args)
- func(self, args)
+ func(self, args, **kw)
return wrapper_func
def dist_from_args(ctx, args):
- '''Parses out any distribution-related arguments, and uses them to
+ """Parses out any distribution-related arguments, and uses them to
obtain a Distribution class instance for the build.
- '''
+ """
return Distribution.get_distribution(
ctx,
name=args.dist_name,
recipes=split_argument_list(args.requirements),
- require_perfect_match=args.require_perfect_match)
+ archs=args.arch,
+ ndk_api=args.ndk_api,
+ force_build=args.force_build,
+ require_perfect_match=args.require_perfect_match,
+ allow_replace_dist=args.allow_replace_dist)
def build_dist_from_args(ctx, dist, args):
- '''Parses out any bootstrap related arguments, and uses them to build
- a dist.'''
+ """Parses out any bootstrap related arguments, and uses them to build
+ a dist."""
bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)
- build_order, python_modules, bs \
- = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs)
+ blacklist = getattr(args, "blacklist_requirements", "").split(",")
+ if len(blacklist) == 1 and blacklist[0] == "":
+ blacklist = []
+ build_order, python_modules, bs = (
+ get_recipe_order_and_bootstrap(
+ ctx, dist.recipes, bs,
+ blacklist=blacklist
+ ))
+ assert set(build_order).intersection(set(python_modules)) == set()
ctx.recipe_build_order = build_order
ctx.python_modules = python_modules
- if python_modules and hasattr(sys, 'real_prefix'):
- error('virtualenv is needed to install pure-Python modules, but')
- error('virtualenv does not support nesting, and you are running')
- error('python-for-android in one. Please run p4a outside of a')
- error('virtualenv instead.')
- exit(1)
-
info('The selected bootstrap is {}'.format(bs.name))
info_main('# Creating dist with {} bootstrap'.format(bs.name))
bs.distribution = dist
- info_notify('Dist will have name {} and recipes ({})'.format(
+ info_notify('Dist will have name {} and requirements ({})'.format(
dist.name, ', '.join(dist.recipes)))
+ info('Dist contains the following requirements as recipes: {}'.format(
+ ctx.recipe_build_order))
info('Dist will also contain modules ({}) installed from pip'.format(
', '.join(ctx.python_modules)))
-
- ctx.dist_name = bs.distribution.name
+ info(
+ 'Dist will be build in mode {build_mode}{with_debug_symbols}'.format(
+ build_mode='debug' if ctx.build_as_debuggable else 'release',
+ with_debug_symbols=' (with debug symbols)'
+ if ctx.with_debug_symbols
+ else '',
+ )
+ )
+
+ ctx.distribution = dist
ctx.prepare_bootstrap(bs)
- ctx.prepare_dist(ctx.dist_name)
+ if dist.needs_build:
+ ctx.prepare_dist()
- build_recipes(build_order, python_modules, ctx)
+ build_recipes(build_order, python_modules, ctx,
+ getattr(args, "private", None),
+ ignore_project_setup_py=getattr(
+ args, "ignore_setup_py", False
+ ),
+ )
- ctx.bootstrap.run_distribute()
+ ctx.bootstrap.assemble_distribution()
info_main('# Your distribution was created successfully, exiting.')
info('Dist can be found at (for now) {}'
- .format(join(ctx.dist_dir, ctx.dist_name)))
+ .format(join(ctx.dist_dir, ctx.distribution.dist_dir)))
-def split_argument_list(l):
- if not len(l):
+def split_argument_list(arg_list):
+ if not len(arg_list):
return []
- return re.split(r'[ ,]+', l)
+ return re.split(r'[ ,]+', arg_list)
+
class NoAbbrevParser(argparse.ArgumentParser):
- '''We want to disable argument abbreviation so as not to interfere
+ """We want to disable argument abbreviation so as not to interfere
with passing through arguments to build.py, but in python2 argparse
doesn't have this option.
This subclass alternative is follows the suggestion at
https://bugs.python.org/issue14910.
- '''
+ """
def _get_option_tuples(self, option_string):
return []
-class ToolchainCL(object):
+
+class ToolchainCL:
def __init__(self):
argv = sys.argv
+ self.warn_on_carriage_return_args(argv)
# Buildozer used to pass these arguments in a now-invalid order
# If that happens, apply this fix
# This fix will be removed once a fixed buildozer is released
- if (len(argv) > 2 and
- argv[1].startswith('--color') and
- argv[2].startswith('--storage-dir')):
+ if (len(argv) > 2
+ and argv[1].startswith('--color')
+ and argv[2].startswith('--storage-dir')):
argv.append(argv.pop(1)) # the --color arg
argv.append(argv.pop(1)) # the --storage-dir arg
parser = NoAbbrevParser(
- description=('A packaging tool for turning Python scripts and apps '
- 'into Android APKs'))
+ description='A packaging tool for turning Python scripts and apps '
+ 'into Android APKs')
generic_parser = argparse.ArgumentParser(
add_help=False,
- description=('Generic arguments applied to all commands'))
- dist_parser = argparse.ArgumentParser(
- add_help=False,
- description=('Arguments for dist building'))
+ description='Generic arguments applied to all commands')
+ argparse.ArgumentParser(
+ add_help=False, description='Arguments for dist building')
generic_parser.add_argument(
- '--debug', dest='debug', action='store_true',
- default=False,
+ '--debug', dest='debug', action='store_true', default=False,
help='Display debug output and all build info')
generic_parser.add_argument(
'--color', dest='color', choices=['always', 'never', 'auto'],
@@ -254,61 +230,84 @@ def __init__(self):
'--ndk-dir', '--ndk_dir', dest='ndk_dir', default='',
help='The filepath where the Android NDK is installed')
generic_parser.add_argument(
- '--android-api', '--android_api', dest='android_api', default=0, type=int,
- help='The Android API level to build against.')
+ '--android-api',
+ '--android_api',
+ dest='android_api',
+ default=0,
+ type=int,
+ help=('The Android API level to build against defaults to {} if '
+ 'not specified.').format(RECOMMENDED_TARGET_API))
+ generic_parser.add_argument(
+ '--ndk-version', '--ndk_version', dest='ndk_version', default=None,
+ help=('DEPRECATED: the NDK version is now found automatically or '
+ 'not at all.'))
generic_parser.add_argument(
- '--ndk-version', '--ndk_version', dest='ndk_version', default='',
- help=('The version of the Android NDK. This is optional, '
- 'we try to work it out automatically from the ndk_dir.'))
+ '--ndk-api', type=int, default=None,
+ help=('The Android API level to compile against. This should be your '
+ '*minimal supported* API, not normally the same as your --android-api. '
+ 'Defaults to min(ANDROID_API, {}) if not specified.').format(RECOMMENDED_NDK_API))
generic_parser.add_argument(
- '--symlink-java-src', '--symlink_java_src',
+ '--symlink-bootstrap-files', '--ssymlink_bootstrap_files',
action='store_true',
- dest='symlink_java_src',
+ dest='symlink_bootstrap_files',
default=False,
- help=('If True, symlinks the java src folder during build and dist '
- 'creation. This is useful for development only, it could also '
- 'cause weird problems.'))
+ help=('If True, symlinks the bootstrap files '
+ 'creation. This is useful for development only, it could also'
+ ' cause weird problems.'))
default_storage_dir = user_data_dir('python-for-android')
if ' ' in default_storage_dir:
default_storage_dir = '~/.python-for-android'
generic_parser.add_argument(
- '--storage-dir', dest='storage_dir',
- default=default_storage_dir,
+ '--storage-dir', dest='storage_dir', default=default_storage_dir,
help=('Primary storage directory for downloads and builds '
'(default: {})'.format(default_storage_dir)))
generic_parser.add_argument(
- '--arch',
- help='The archs to build for, separated by commas.',
- default='armeabi')
+ '--arch', help='The archs to build for.',
+ action='append', default=[])
# Options for specifying the Distribution
generic_parser.add_argument(
'--dist-name', '--dist_name',
- help='The name of the distribution to use or create',
- default='')
+ help='The name of the distribution to use or create', default='')
generic_parser.add_argument(
'--requirements',
help=('Dependencies of your app, should be recipe names or '
- 'Python modules'),
+ 'Python modules. NOT NECESSARY if you are using '
+ 'Python 3 with --use-setup-py'),
+ default='')
+
+ generic_parser.add_argument(
+ '--recipe-blacklist',
+ help=('Blacklist an internal recipe from use. Allows '
+ 'disabling Python 3 core modules to save size'),
+ dest="recipe_blacklist",
+ default='')
+
+ generic_parser.add_argument(
+ '--blacklist-requirements',
+ help=('Blacklist an internal recipe from use. Allows '
+ 'disabling Python 3 core modules to save size'),
+ dest="blacklist_requirements",
default='')
generic_parser.add_argument(
'--bootstrap',
- help='The bootstrap to build with. Leave unset to choose automatically.',
+ help='The bootstrap to build with. Leave unset to choose '
+ 'automatically.',
default=None)
generic_parser.add_argument(
'--hook',
- help='Filename to a module that contain python-for-android hooks',
+ help='Filename to a module that contains python-for-android hooks',
default=None)
add_boolean_option(
generic_parser, ["force-build"],
default=False,
- description='Whether to force compilation of a new distribution:')
+ description='Whether to force compilation of a new distribution')
add_boolean_option(
generic_parser, ["require-perfect-match"],
@@ -316,11 +315,27 @@ def __init__(self):
description=('Whether the dist recipes must perfectly match '
'those requested'))
+ add_boolean_option(
+ generic_parser, ["allow-replace-dist"],
+ default=True,
+ description='Whether existing dist names can be automatically replaced'
+ )
+
generic_parser.add_argument(
'--local-recipes', '--local_recipes',
dest='local_recipes', default='./p4a-recipes',
help='Directory to look for local recipes')
+ generic_parser.add_argument(
+ '--activity-class-name',
+ dest='activity_class_name', default='org.kivy.android.PythonActivity',
+ help='The full java class name of the main activity')
+
+ generic_parser.add_argument(
+ '--service-class-name',
+ dest='service_class_name', default='org.kivy.android.PythonService',
+ help='Full java package name of the PythonService class')
+
generic_parser.add_argument(
'--java-build-tool',
dest='java_build_tool', default='auto',
@@ -331,7 +346,8 @@ def __init__(self):
add_boolean_option(
generic_parser, ['copy-libs'],
default=False,
- description='Copy libraries instead of using biglink (Android 4.3+)')
+ description='Copy libraries instead of using biglink (Android 4.3+)'
+ )
self._read_configuration()
@@ -339,137 +355,234 @@ def __init__(self):
help='The command to run')
def add_parser(subparsers, *args, **kwargs):
- '''
+ """
argparse in python2 doesn't support the aliases option,
so we just don't provide the aliases there.
- '''
+ """
if 'aliases' in kwargs and sys.version_info.major < 3:
kwargs.pop('aliases')
return subparsers.add_parser(*args, **kwargs)
- parser_recipes = add_parser(subparsers,
+ add_parser(
+ subparsers,
+ 'recommendations',
+ parents=[generic_parser],
+ help='List recommended p4a dependencies')
+ parser_recipes = add_parser(
+ subparsers,
'recipes',
parents=[generic_parser],
help='List the available recipes')
parser_recipes.add_argument(
- "--compact", action="store_true", default=False,
- help="Produce a compact list suitable for scripting")
-
- parser_bootstraps = add_parser(
+ "--compact",
+ action="store_true", default=False,
+ help="Produce a compact list suitable for scripting")
+ add_parser(
subparsers, 'bootstraps',
help='List the available bootstraps',
parents=[generic_parser])
- parser_clean_all = add_parser(
+ add_parser(
subparsers, 'clean_all',
aliases=['clean-all'],
help='Delete all builds, dists and caches',
parents=[generic_parser])
- parser_clean_dists = add_parser(
- subparsers,
- 'clean_dists', aliases=['clean-dists'],
+ add_parser(
+ subparsers, 'clean_dists',
+ aliases=['clean-dists'],
help='Delete all dists',
parents=[generic_parser])
- parser_clean_bootstrap_builds = add_parser(
- subparsers,
- 'clean_bootstrap_builds', aliases=['clean-bootstrap-builds'],
+ add_parser(
+ subparsers, 'clean_bootstrap_builds',
+ aliases=['clean-bootstrap-builds'],
help='Delete all bootstrap builds',
parents=[generic_parser])
- parser_clean_builds = add_parser(
- subparsers,
- 'clean_builds', aliases=['clean-builds'],
+ add_parser(
+ subparsers, 'clean_builds',
+ aliases=['clean-builds'],
help='Delete all builds',
parents=[generic_parser])
- parser_clean = add_parser(subparsers, 'clean',
- help='Delete build components.',
- parents=[generic_parser])
+ parser_clean = add_parser(
+ subparsers, 'clean',
+ help='Delete build components.',
+ parents=[generic_parser])
parser_clean.add_argument(
'component', nargs='+',
help=('The build component(s) to delete. You can pass any '
'number of arguments from "all", "builds", "dists", '
'"distributions", "bootstrap_builds", "downloads".'))
- parser_clean_recipe_build = add_parser(subparsers,
+ parser_clean_recipe_build = add_parser(
+ subparsers,
'clean_recipe_build', aliases=['clean-recipe-build'],
help=('Delete the build components of the given recipe. '
'By default this will also delete built dists'),
parents=[generic_parser])
- parser_clean_recipe_build.add_argument('recipe', help='The recipe name')
- parser_clean_recipe_build.add_argument('--no-clean-dists', default=False,
- dest='no_clean_dists',
- action='store_true',
- help='If passed, do not delete existing dists')
+ parser_clean_recipe_build.add_argument(
+ 'recipe', help='The recipe name')
+ parser_clean_recipe_build.add_argument(
+ '--no-clean-dists', default=False,
+ dest='no_clean_dists',
+ action='store_true',
+ help='If passed, do not delete existing dists')
- parser_clean_download_cache= add_parser(subparsers,
+ parser_clean_download_cache = add_parser(
+ subparsers,
'clean_download_cache', aliases=['clean-download-cache'],
help='Delete cached downloads for requirement builds',
parents=[generic_parser])
parser_clean_download_cache.add_argument(
- 'recipes', nargs='*',
- help=('The recipes to clean (space-separated). If no recipe name is '
- 'provided, the entire cache is cleared.'))
+ 'recipes',
+ nargs='*',
+ help='The recipes to clean (space-separated). If no recipe name is'
+ ' provided, the entire cache is cleared.')
- parser_export_dist = add_parser(subparsers,
+ parser_export_dist = add_parser(
+ subparsers,
'export_dist', aliases=['export-dist'],
help='Copy the named dist to the given path',
parents=[generic_parser])
- parser_export_dist.add_argument('output_dir', help=('The output dir to copy to'))
- parser_export_dist.add_argument('--symlink', action='store_true',
- help=('Symlink the dist instead of copying'))
+ parser_export_dist.add_argument('output_dir',
+ help='The output dir to copy to')
+ parser_export_dist.add_argument(
+ '--symlink',
+ action='store_true',
+ help='Symlink the dist instead of copying')
+
+ parser_packaging = argparse.ArgumentParser(
+ parents=[generic_parser],
+ add_help=False,
+ description='common options for packaging (apk, aar)')
+
+ # This is actually an internal argument of the build.py
+ # (see pythonforandroid/bootstraps/common/build/build.py).
+ # However, it is also needed before the distribution is finally
+ # assembled for locating the setup.py / other build systems, which
+ # is why we also add it here:
+ parser_packaging.add_argument(
+ '--add-asset', dest='assets',
+ action="append", default=[],
+ help='Put this in the assets folder in the apk.')
+ parser_packaging.add_argument(
+ '--add-resource', dest='resources',
+ action="append", default=[],
+ help='Put this in the res folder in the apk.')
+ parser_packaging.add_argument(
+ '--private', dest='private',
+ help='the directory with the app source code files' +
+ ' (containing your main.py entrypoint)',
+ required=False, default=None)
+ parser_packaging.add_argument(
+ '--use-setup-py', dest="use_setup_py",
+ action='store_true', default=False,
+ help="Process the setup.py of a project if present. " +
+ "(Experimental!")
+ parser_packaging.add_argument(
+ '--ignore-setup-py', dest="ignore_setup_py",
+ action='store_true', default=False,
+ help="Don't run the setup.py of a project if present. " +
+ "This may be required if the setup.py is not " +
+ "designed to work inside p4a (e.g. by installing " +
+ "dependencies that won't work or aren't desired " +
+ "on Android")
+ parser_packaging.add_argument(
+ '--release', dest='build_mode', action='store_const',
+ const='release', default='debug',
+ help='Build your app as a non-debug release build. '
+ '(Disables gdb debugging among other things)')
+ parser_packaging.add_argument(
+ '--with-debug-symbols', dest='with_debug_symbols',
+ action='store_const', const=True, default=False,
+ help='Will keep debug symbols from `.so` files.')
+ parser_packaging.add_argument(
+ '--keystore', dest='keystore', action='store', default=None,
+ help=('Keystore for JAR signing key, will use jarsigner '
+ 'default if not specified (release build only)'))
+ parser_packaging.add_argument(
+ '--signkey', dest='signkey', action='store', default=None,
+ help='Key alias to sign PARSER_APK. with (release build only)')
+ parser_packaging.add_argument(
+ '--keystorepw', dest='keystorepw', action='store', default=None,
+ help='Password for keystore')
+ parser_packaging.add_argument(
+ '--signkeypw', dest='signkeypw', action='store', default=None,
+ help='Password for key alias')
+
+ add_parser(
+ subparsers,
+ 'aar', help='Build an AAR',
+ parents=[parser_packaging])
- parser_apk = add_parser(subparsers,
+ add_parser(
+ subparsers,
'apk', help='Build an APK',
- parents=[generic_parser])
- parser_apk.add_argument('--release', dest='build_mode', action='store_const',
- const='release', default='debug',
- help='Build the PARSER_APK. in Release mode')
- parser_apk.add_argument('--keystore', dest='keystore', action='store', default=None,
- help=('Keystore for JAR signing key, will use jarsigner '
- 'default if not specified (release build only)'))
- parser_apk.add_argument('--signkey', dest='signkey', action='store', default=None,
- help='Key alias to sign PARSER_APK. with (release build only)')
- parser_apk.add_argument('--keystorepw', dest='keystorepw', action='store', default=None,
- help='Password for keystore')
- parser_apk.add_argument('--signkeypw', dest='signkeypw', action='store', default=None,
- help='Password for key alias')
-
- parser_create = add_parser(subparsers,
+ parents=[parser_packaging])
+
+ add_parser(
+ subparsers,
+ 'aab', help='Build an AAB',
+ parents=[parser_packaging])
+
+ add_parser(
+ subparsers,
'create', help='Compile a set of requirements into a dist',
parents=[generic_parser])
- parser_archs = add_parser(subparsers,
+ add_parser(
+ subparsers,
'archs', help='List the available target architectures',
parents=[generic_parser])
- parser_distributions = add_parser(subparsers,
+ add_parser(
+ subparsers,
'distributions', aliases=['dists'],
help='List the currently available (compiled) dists',
parents=[generic_parser])
- parser_delete_dist = add_parser(subparsers,
+ add_parser(
+ subparsers,
'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',
parents=[generic_parser])
- parser_sdk_tools = add_parser(subparsers,
+ parser_sdk_tools = add_parser(
+ subparsers,
'sdk_tools', aliases=['sdk-tools'],
help='Run the given binary from the SDK tools dis',
parents=[generic_parser])
parser_sdk_tools.add_argument(
- 'tool', help=('The tool binary name to run'))
+ 'tool', help='The binary tool name to run')
- parser_adb = add_parser(subparsers,
+ add_parser(
+ subparsers,
'adb', help='Run adb from the given SDK',
parents=[generic_parser])
- parser_logcat = add_parser(subparsers,
+ add_parser(
+ subparsers,
'logcat', help='Run logcat from the given SDK',
parents=[generic_parser])
- parser_build_status = add_parser(subparsers,
+ add_parser(
+ subparsers,
'build_status', aliases=['build-status'],
help='Print some debug information about current built components',
parents=[generic_parser])
- parser.add_argument('-v', '--version', action='version', version=__version__)
+ parser.add_argument('-v', '--version', action='version',
+ version=__version__)
args, unknown = parser.parse_known_args(sys.argv[1:])
args.unknown_args = unknown
+ if hasattr(args, "private") and args.private is not None:
+ # Pass this value on to the internal bootstrap build.py:
+ args.unknown_args += ["--private", args.private]
+ if hasattr(args, "build_mode") and args.build_mode == "release":
+ args.unknown_args += ["--release"]
+ if hasattr(args, "with_debug_symbols") and args.with_debug_symbols:
+ args.unknown_args += ["--with-debug-symbols"]
+ if hasattr(args, "ignore_setup_py") and args.ignore_setup_py:
+ args.use_setup_py = False
+ if hasattr(args, "activity_class_name") and args.activity_class_name != 'org.kivy.android.PythonActivity':
+ args.unknown_args += ["--activity-class-name", args.activity_class_name]
+ if hasattr(args, "service_class_name") and args.service_class_name != 'org.kivy.android.PythonService':
+ args.unknown_args += ["--service-class-name", args.service_class_name]
+
self.args = args
if args.subparser_name is None:
@@ -481,9 +594,64 @@ def add_parser(subparsers, *args, **kwargs):
if args.debug:
logger.setLevel(logging.DEBUG)
- # strip version from requirements, and put them in environ
+ self.ctx = Context()
+ self.ctx.use_setup_py = getattr(args, "use_setup_py", True)
+ self.ctx.build_as_debuggable = getattr(
+ args, "build_mode", "debug"
+ ) == "debug"
+ self.ctx.with_debug_symbols = getattr(
+ args, "with_debug_symbols", False
+ )
+
+ have_setup_py_or_similar = False
+ if getattr(args, "private", None) is not None:
+ project_dir = getattr(args, "private")
+ if (os.path.exists(os.path.join(project_dir, "setup.py")) or
+ os.path.exists(os.path.join(project_dir,
+ "pyproject.toml"))):
+ have_setup_py_or_similar = True
+
+ # Process requirements and put version in environ
if hasattr(args, 'requirements'):
requirements = []
+
+ # Add dependencies from setup.py, but only if they are recipes
+ # (because otherwise, setup.py itself will install them later)
+ if (have_setup_py_or_similar and
+ getattr(args, "use_setup_py", False)):
+ try:
+ info("Analyzing package dependencies. MAY TAKE A WHILE.")
+ # Get all the dependencies corresponding to a recipe:
+ dependencies = [
+ dep.lower() for dep in
+ get_dep_names_of_package(
+ args.private,
+ keep_version_pins=True,
+ recursive=True,
+ verbose=True,
+ )
+ ]
+ info("Dependencies obtained: " + str(dependencies))
+ all_recipes = [
+ recipe.lower() for recipe in
+ set(Recipe.list_recipes(self.ctx))
+ ]
+ dependencies = set(dependencies).intersection(
+ set(all_recipes)
+ )
+ # Add dependencies to argument list:
+ if len(dependencies) > 0:
+ if len(args.requirements) > 0:
+ args.requirements += u","
+ args.requirements += u",".join(dependencies)
+ except ValueError:
+ # Not a python package, apparently.
+ warning(
+ "Processing failed, is this project a valid "
+ "package? Will continue WITHOUT setup.py deps."
+ )
+
+ # Parse --requirements argument list:
for requirement in split_argument_list(args.requirements):
if "==" in requirement:
requirement, version = requirement.split(u"==", 1)
@@ -493,30 +661,75 @@ def add_parser(subparsers, *args, **kwargs):
requirements.append(requirement)
args.requirements = u",".join(requirements)
- self.ctx = Context()
+ self.warn_on_deprecated_args(args)
+
self.storage_dir = args.storage_dir
self.ctx.setup_dirs(self.storage_dir)
self.sdk_dir = args.sdk_dir
self.ndk_dir = args.ndk_dir
self.android_api = args.android_api
- self.ndk_version = args.ndk_version
- self.ctx.symlink_java_src = args.symlink_java_src
+ self.ndk_api = args.ndk_api
+ self.ctx.symlink_bootstrap_files = args.symlink_bootstrap_files
self.ctx.java_build_tool = args.java_build_tool
- self._archs = split_argument_list(args.arch)
+ self._archs = args.arch
- self.ctx.local_recipes = args.local_recipes
+ self.ctx.local_recipes = realpath(args.local_recipes)
self.ctx.copy_libs = args.copy_libs
+ self.ctx.activity_class_name = args.activity_class_name
+ self.ctx.service_class_name = args.service_class_name
+
# Each subparser corresponds to a method
- getattr(self, args.subparser_name.replace('-', '_'))(args)
+ command = args.subparser_name.replace('-', '_')
+ getattr(self, command)(args)
+
+ @staticmethod
+ def warn_on_carriage_return_args(args):
+ for check_arg in args:
+ if '\r' in check_arg:
+ warning("Argument '{}' contains a carriage return (\\r).".format(str(check_arg.replace('\r', ''))))
+ warning("Invoking this program via scripts which use CRLF instead of LF line endings will have undefined behaviour.")
+
+ def warn_on_deprecated_args(self, args):
+ """
+ Print warning messages for any deprecated arguments that were passed.
+ """
+
+ # Output warning if setup.py is present and neither --ignore-setup-py
+ # nor --use-setup-py was specified.
+ if getattr(args, "private", None) is not None and \
+ (os.path.exists(os.path.join(args.private, "setup.py")) or
+ os.path.exists(os.path.join(args.private, "pyproject.toml"))
+ ):
+ if not getattr(args, "use_setup_py", False) and \
+ not getattr(args, "ignore_setup_py", False):
+ warning(" **** FUTURE BEHAVIOR CHANGE WARNING ****")
+ warning("Your project appears to contain a setup.py file.")
+ warning("Currently, these are ignored by default.")
+ warning("This will CHANGE in an upcoming version!")
+ warning("")
+ warning("To ensure your setup.py is ignored, please specify:")
+ warning(" --ignore-setup-py")
+ warning("")
+ warning("To enable what will some day be the default, specify:")
+ warning(" --use-setup-py")
+
+ # NDK version is now determined automatically
+ if args.ndk_version is not None:
+ warning('--ndk-version is deprecated and no longer necessary, '
+ 'the value you passed is ignored')
+ if 'ANDROIDNDKVER' in environ:
+ warning('$ANDROIDNDKVER is deprecated and no longer necessary, '
+ 'the value you set is ignored')
def hook(self, name):
if not self.args.hook:
return
if not hasattr(self, "hook_module"):
# first time, try to load the hook module
- self.hook_module = imp.load_source("pythonforandroid.hook", self.args.hook)
+ self.hook_module = load_source(
+ "pythonforandroid.hook", self.args.hook)
if hasattr(self.hook_module, name):
info("Hook: execute {}".format(name))
getattr(self.hook_module, name)(self)
@@ -530,7 +743,8 @@ def default_storage_dir(self):
udd = '~/.python-for-android'
return udd
- def _read_configuration(self):
+ @staticmethod
+ def _read_configuration():
# search for a .p4a configuration file in the current directory
if not exists(".p4a"):
return
@@ -544,6 +758,14 @@ def _read_configuration(self):
sys.argv.append(arg)
def recipes(self, args):
+ """
+ Prints recipes basic info, e.g.
+ .. code-block:: bash
+ python3 3.7.1
+ depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi']
+ conflicts: []
+ optional depends: ['sqlite3', 'libffi', 'openssl']
+ """
ctx = self.ctx
if args.compact:
print(" ".join(set(Recipe.list_recipes(ctx))))
@@ -551,7 +773,7 @@ def recipes(self, args):
for name in sorted(Recipe.list_recipes(ctx)):
try:
recipe = Recipe.get_recipe(name, ctx)
- except IOError:
+ except (IOError, ValueError):
warning('Recipe "{}" could not be loaded'.format(name))
except SyntaxError:
import traceback
@@ -562,8 +784,8 @@ def recipes(self, args):
print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} '
'{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}'
'{version:<8}{Style.RESET_ALL}'.format(
- recipe=recipe, Fore=Out_Fore, Style=Out_Style,
- version=version))
+ recipe=recipe, Fore=Out_Fore, Style=Out_Style,
+ version=version))
print(' {Fore.GREEN}depends: {recipe.depends}'
'{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore))
if recipe.conflicts:
@@ -575,9 +797,9 @@ def recipes(self, args):
'{recipe.opt_depends}{Fore.RESET}'
.format(recipe=recipe, Fore=Out_Fore))
- def bootstraps(self, args):
- '''List all the bootstraps available to build with.'''
- for bs in Bootstrap.list_bootstraps():
+ def bootstraps(self, _args):
+ """List all the bootstraps available to build with."""
+ for bs in Bootstrap.all_bootstraps():
bs = Bootstrap.get_bootstrap(bs, self.ctx)
print('{Fore.BLUE}{Style.BRIGHT}{bs.name}{Style.RESET_ALL}'
.format(bs=bs, Fore=Out_Fore, Style=Out_Style))
@@ -587,73 +809,66 @@ def bootstraps(self, args):
def clean(self, args):
components = args.component
- component_clean_methods = {'all': self.clean_all,
- 'dists': self.clean_dists,
- 'distributions': self.clean_dists,
- 'builds': self.clean_builds,
- 'bootstrap_builds': self.clean_bootstrap_builds,
- 'downloads': self.clean_download_cache}
+ component_clean_methods = {
+ 'all': self.clean_all,
+ 'dists': self.clean_dists,
+ 'distributions': self.clean_dists,
+ 'builds': self.clean_builds,
+ 'bootstrap_builds': self.clean_bootstrap_builds,
+ 'downloads': self.clean_download_cache}
for component in components:
if component not in component_clean_methods:
- raise ValueError((
+ raise BuildInterruptingException((
'Asked to clean "{}" but this argument is not '
'recognised'.format(component)))
component_clean_methods[component](args)
-
def clean_all(self, args):
- '''Delete all build components; the package cache, package builds,
- bootstrap builds and distributions.'''
+ """Delete all build components; the package cache, package builds,
+ bootstrap builds and distributions."""
self.clean_dists(args)
self.clean_builds(args)
self.clean_download_cache(args)
- def clean_dists(self, args):
- '''Delete all compiled distributions in the internal distribution
- directory.'''
+ def clean_dists(self, _args):
+ """Delete all compiled distributions in the internal distribution
+ directory."""
ctx = self.ctx
- if exists(ctx.dist_dir):
- shutil.rmtree(ctx.dist_dir)
-
- def clean_bootstrap_builds(self, args):
- '''Delete all the bootstrap builds.'''
- if exists(join(self.ctx.build_dir, 'bootstrap_builds')):
- shutil.rmtree(join(self.ctx.build_dir, 'bootstrap_builds'))
- # for bs in Bootstrap.list_bootstraps():
+ rmdir(ctx.dist_dir)
+
+ def clean_bootstrap_builds(self, _args):
+ """Delete all the bootstrap builds."""
+ rmdir(join(self.ctx.build_dir, 'bootstrap_builds'))
+ # for bs in Bootstrap.all_bootstraps():
# bs = Bootstrap.get_bootstrap(bs, self.ctx)
# if bs.build_dir and exists(bs.build_dir):
# info('Cleaning build for {} bootstrap.'.format(bs.name))
- # shutil.rmtree(bs.build_dir)
+ # rmdir(bs.build_dir)
- def clean_builds(self, args):
- '''Delete all build caches for each recipe, python-install, java code
+ def clean_builds(self, _args):
+ """Delete all build caches for each recipe, python-install, java code
and compiled libs collection.
This does *not* delete the package download cache or the final
distributions. You can also use clean_recipe_build to delete the build
of a specific recipe.
- '''
+ """
ctx = self.ctx
- # if exists(ctx.dist_dir):
- # shutil.rmtree(ctx.dist_dir)
- if exists(ctx.build_dir):
- shutil.rmtree(ctx.build_dir)
- if exists(ctx.python_installs_dir):
- shutil.rmtree(ctx.python_installs_dir)
+ rmdir(ctx.build_dir)
+ rmdir(ctx.python_installs_dir)
libs_dir = join(self.ctx.build_dir, 'libs_collections')
- if exists(libs_dir):
- shutil.rmtree(libs_dir)
+ rmdir(libs_dir)
def clean_recipe_build(self, args):
- '''Deletes the build files of the given recipe.
+ """Deletes the build files of the given recipe.
- This is intended for debug purposes, you may experience
- strange behaviour or problems with some recipes (if their
- build has done unexpected state changes). If this happens, run
+ This is intended for debug purposes. You may experience
+ strange behaviour or problems with some recipes if their
+ build has made unexpected state changes. If this happens, run
clean_builds, or attempt to clean other recipes until things
work again.
- '''
+ """
recipe = Recipe.get_recipe(args.recipe, self.ctx)
info('Cleaning build for {} recipe.'.format(recipe.name))
recipe.clean_build()
@@ -661,45 +876,45 @@ def clean_recipe_build(self, args):
self.clean_dists(args)
def clean_download_cache(self, args):
- '''
- Deletes a download cache for recipes stated as arguments. If no
- argument is passed, it'll delete *all* downloaded cache. ::
+ """ Deletes a download cache for recipes passed as arguments. If no
+ argument is passed, it'll delete *all* downloaded caches. ::
p4a clean_download_cache kivy,pyjnius
This does *not* delete the build caches or final distributions.
- '''
+ """
ctx = self.ctx
if hasattr(args, 'recipes') and args.recipes:
for package in args.recipes:
remove_path = join(ctx.packages_path, package)
if exists(remove_path):
- shutil.rmtree(remove_path)
+ rmdir(remove_path)
info('Download cache removed for: "{}"'.format(package))
else:
- warning('No download cache found for "{}", skipping'.format(package))
+ warning('No download cache found for "{}", skipping'.format(
+ package))
else:
if exists(ctx.packages_path):
- shutil.rmtree(ctx.packages_path)
+ rmdir(ctx.packages_path)
info('Download cache removed.')
else:
print('No cache found at "{}"'.format(ctx.packages_path))
@require_prebuilt_dist
def export_dist(self, args):
- '''Copies a created dist to an output dir.
+ """Copies a created dist to an output dir.
This makes it easy to navigate to the dist to investigate it
or call build.py, though you do not in general need to do this
and can use the apk command instead.
- '''
+ """
ctx = self.ctx
dist = dist_from_args(ctx, args)
if dist.needs_build:
- info('You asked to export a dist, but there is no dist '
- 'with suitable recipes available. For now, you must '
- ' create one first with the create argument.')
- exit(1)
+ raise BuildInterruptingException(
+ 'You asked to export a dist, but there is no dist '
+ 'with suitable recipes available. For now, you must '
+ ' create one first with the create argument.')
if args.symlink:
shprint(sh.ln, '-s', dist.dist_dir, args.output_dir)
else:
@@ -709,31 +924,55 @@ def export_dist(self, args):
def _dist(self):
ctx = self.ctx
dist = dist_from_args(ctx, self.args)
+ ctx.distribution = dist
return dist
- @require_prebuilt_dist
- def apk(self, args):
- '''Create an APK using the given distribution.'''
-
- ctx = self.ctx
- dist = self._dist
+ @staticmethod
+ def _fix_args(args):
+ """
+ Manually fixing these arguments at the string stage is
+ unsatisfactory and should probably be changed somehow, but
+ we can't leave it until later as the build.py scripts assume
+ they are in the current directory.
+ works in-place
+ :param args: parser args
+ """
- # Manually fixing these arguments at the string stage is
- # unsatisfactory and should probably be changed somehow, but
- # we can't leave it until later as the build.py scripts assume
- # they are in the current directory.
fix_args = ('--dir', '--private', '--add-jar', '--add-source',
- '--whitelist', '--blacklist', '--presplash', '--icon')
+ '--whitelist', '--blacklist', '--presplash', '--icon',
+ '--icon-bg', '--icon-fg')
unknown_args = args.unknown_args
- for i, arg in enumerate(unknown_args[:-1]):
+
+ for asset in args.assets:
+ if ":" in asset:
+ asset_src, asset_dest = asset.split(":")
+ else:
+ asset_src = asset_dest = asset
+ # take abspath now, because build.py will be run in bootstrap dir
+ unknown_args += ["--asset", os.path.abspath(asset_src)+":"+asset_dest]
+ for resource in args.resources:
+ if ":" in resource:
+ resource_src, resource_dest = resource.split(":")
+ else:
+ resource_src = resource
+ resource_dest = ""
+ # take abspath now, because build.py will be run in bootstrap dir
+ unknown_args += ["--resource", os.path.abspath(resource_src)+":"+resource_dest]
+ for i, arg in enumerate(unknown_args):
argx = arg.split('=')
if argx[0] in fix_args:
if len(argx) > 1:
- unknown_args[i] = '='.join((argx[0],
- realpath(expanduser(argx[1]))))
- else:
+ unknown_args[i] = '='.join(
+ (argx[0], realpath(expanduser(argx[1]))))
+ elif i + 1 < len(unknown_args):
unknown_args[i+1] = realpath(expanduser(unknown_args[i+1]))
+ @staticmethod
+ def _prepare_release_env(args):
+ """
+ prepares envitonment dict with the necessary flags for signing an apk
+ :param args: parser args
+ """
env = os.environ.copy()
if args.build_mode == 'release':
if args.keystore:
@@ -747,164 +986,203 @@ def apk(self, args):
elif args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env:
env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.keystorepw
- build = imp.load_source('build', join(dist.dist_dir, 'build.py'))
+ return env
+
+ def _build_package(self, args, package_type):
+ """
+ Creates an android package using gradle
+ :param args: parser args
+ :param package_type: one of 'apk', 'aar', 'aab'
+ :return (gradle output, build_args)
+ """
+ ctx = self.ctx
+ dist = self._dist
+ bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)
+ ctx.prepare_bootstrap(bs)
+ self._fix_args(args)
+ env = self._prepare_release_env(args)
+
with current_directory(dist.dist_dir):
self.hook("before_apk_build")
os.environ["ANDROID_API"] = str(self.ctx.android_api)
- build_args = build.parse_args(args.unknown_args)
+ build = load_source('build', join(dist.dist_dir, 'build.py'))
+ build_args = build.parse_args_and_make_package(
+ args.unknown_args
+ )
+
self.hook("after_apk_build")
self.hook("before_apk_assemble")
-
- build_type = ctx.java_build_tool
- if build_type == 'auto':
- info('Selecting java build tool:')
-
- build_tools_versions = os.listdir(join(ctx.sdk_dir, 'build-tools'))
- build_tools_versions = sorted(build_tools_versions,
- key=LooseVersion)
- build_tools_version = build_tools_versions[-1]
- info(('Detected highest available build tools '
- 'version to be {}').format(build_tools_version))
-
- if build_tools_version >= '25.0' and exists('gradlew'):
- build_type = 'gradle'
- info(' Building with gradle, as gradle executable is present')
- else:
- build_type = 'ant'
- if build_tools_version < '25.0':
- info((' Building with ant, as the highest '
- 'build-tools-version is only {}').format(build_tools_version))
- else:
- info(' Building with ant, as no gradle executable detected')
-
- if build_type == 'gradle':
- # gradle-based build
- env["ANDROID_NDK_HOME"] = self.ctx.ndk_dir
- env["ANDROID_HOME"] = self.ctx.sdk_dir
-
- gradlew = sh.Command('./gradlew')
- if args.build_mode == "debug":
- gradle_task = "assembleDebug"
- elif args.build_mode == "release":
+ build_tools_versions = os.listdir(join(ctx.sdk_dir,
+ 'build-tools'))
+ build_tools_version = max_build_tool_version(build_tools_versions)
+ info(('Detected highest available build tools '
+ 'version to be {}').format(build_tools_version))
+
+ if Version(build_tools_version.replace(" ", "")) < Version('25.0'):
+ raise BuildInterruptingException(
+ 'build_tools >= 25 is required, but %s is installed' % build_tools_version)
+ if not exists("gradlew"):
+ raise BuildInterruptingException("gradlew file is missing")
+
+ env["ANDROID_NDK_HOME"] = self.ctx.ndk_dir
+ env["ANDROID_HOME"] = self.ctx.sdk_dir
+
+ gradlew = sh.Command('./gradlew')
+
+ if exists('/usr/bin/dos2unix'):
+ # .../dists/bdisttest_python3/gradlew
+ # .../build/bootstrap_builds/sdl2-python3/gradlew
+ # if docker on windows, gradle contains CRLF
+ output = shprint(
+ sh.Command('dos2unix'), gradlew._path.decode('utf8'),
+ _tail=20, _critical=True, _env=env
+ )
+ if args.build_mode == "debug":
+ if package_type == "aab":
+ raise BuildInterruptingException(
+ "aab is meant only for distribution and is not available in debug mode. "
+ "Instead, you can use apk while building for debugging purposes."
+ )
+ gradle_task = "assembleDebug"
+ elif args.build_mode == "release":
+ if package_type in ["apk", "aar"]:
gradle_task = "assembleRelease"
- else:
- error("Unknown build mode {} for apk()".format(
- args.build_mode))
- exit(1)
- output = shprint(gradlew, gradle_task, _tail=20,
- _critical=True, _env=env)
-
- # gradle output apks somewhere else
- # and don't have version in file
- apk_dir = join(dist.dist_dir, "build", "outputs", "apk")
- apk_glob = "*-{}.apk"
- apk_add_version = True
-
+ elif package_type == "aab":
+ gradle_task = "bundleRelease"
else:
- # ant-based build
- try:
- ant = sh.Command('ant')
- except sh.CommandNotFound:
- error('Could not find ant binary, please install it '
- 'and make sure it is in your $PATH.')
- exit(1)
- output = shprint(ant, args.build_mode, _tail=20,
- _critical=True, _env=env)
- apk_dir = join(dist.dist_dir, "bin")
- apk_glob = "*-*-{}.apk"
- apk_add_version = False
-
- self.hook("after_apk_assemble")
-
- info_main('# Copying APK to current directory')
-
- apk_re = re.compile(r'.*Package: (.*\.apk)$')
- apk_file = None
+ raise BuildInterruptingException(
+ "Unknown build mode {} for apk()".format(args.build_mode))
+
+ # WARNING: We should make sure to clean the build directory before building.
+ # See PR: kivy/python-for-android#2705
+ output = shprint(gradlew, "clean", gradle_task, _tail=20,
+ _critical=True, _env=env)
+ return output, build_args
+
+ def _finish_package(self, args, output, build_args, package_type, output_dir):
+ """
+ Finishes the package after the gradle script run
+ :param args: the parser args
+ :param output: RunningCommand output
+ :param build_args: build args as returned by build.parse_args
+ :param package_type: one of 'apk', 'aar', 'aab'
+ :param output_dir: where to put the package file
+ """
+
+ package_glob = "*-{}.%s" % package_type
+ package_add_version = True
+
+ self.hook("after_apk_assemble")
+
+ info_main('# Copying android package to current directory')
+
+ package_re = re.compile(r'.*Package: (.*\.apk)$')
+ package_file = None
for line in reversed(output.splitlines()):
- m = apk_re.match(line)
+ m = package_re.match(line)
if m:
- apk_file = m.groups()[0]
+ package_file = m.groups()[0]
break
-
- if not apk_file:
- info_main('# APK filename not found in build output, trying to guess')
+ if not package_file:
+ info_main('# Android package filename not found in build output. Guessing...')
if args.build_mode == "release":
suffixes = ("release", "release-unsigned")
else:
suffixes = ("debug", )
for suffix in suffixes:
- apks = glob.glob(join(apk_dir, apk_glob.format(suffix)))
- if apks:
- if len(apks) > 1:
+
+ package_files = glob.glob(join(output_dir, package_glob.format(suffix)))
+ if package_files:
+ if len(package_files) > 1:
info('More than one built APK found... guessing you '
- 'just built {}'.format(apks[-1]))
- apk_file = apks[-1]
+ 'just built {}'.format(package_files[-1]))
+ package_file = package_files[-1]
break
else:
- raise ValueError('Couldn\'t find the built APK')
-
- info_main('# Found APK file: {}'.format(apk_file))
- if apk_add_version:
- info('# Add version number to APK')
- apk_name, apk_suffix = basename(apk_file).split("-", 1)
- apk_file_dest = "{}-{}-{}".format(
- apk_name, build_args.version, apk_suffix)
- info('# APK renamed to {}'.format(apk_file_dest))
- shprint(sh.cp, apk_file, apk_file_dest)
+ raise BuildInterruptingException('Couldn\'t find the built APK')
+
+ info_main('# Found android package file: {}'.format(package_file))
+ package_extension = f".{package_type}"
+ if package_add_version:
+ info('# Add version number to android package')
+ package_name = basename(package_file)[:-len(package_extension)]
+ package_file_dest = "{}-{}{}".format(
+ package_name, build_args.version, package_extension)
+ info('# Android package renamed to {}'.format(package_file_dest))
+ shprint(sh.cp, package_file, package_file_dest)
else:
- shprint(sh.cp, apk_file, './')
+ shprint(sh.cp, package_file, './')
+
+ @require_prebuilt_dist
+ def apk(self, args):
+ output, build_args = self._build_package(args, package_type='apk')
+ output_dir = join(self._dist.dist_dir, "build", "outputs", 'apk', args.build_mode)
+ self._finish_package(args, output, build_args, 'apk', output_dir)
+
+ @require_prebuilt_dist
+ def aar(self, args):
+ output, build_args = self._build_package(args, package_type='aar')
+ output_dir = join(self._dist.dist_dir, "build", "outputs", 'aar')
+ self._finish_package(args, output, build_args, 'aar', output_dir)
+
+ @require_prebuilt_dist
+ def aab(self, args):
+ output, build_args = self._build_package(args, package_type='aab')
+ output_dir = join(self._dist.dist_dir, "build", "outputs", 'bundle', args.build_mode)
+ self._finish_package(args, output, build_args, 'aab', output_dir)
@require_prebuilt_dist
def create(self, args):
- '''Create a distribution directory if it doesn't already exist, run
+ """Create a distribution directory if it doesn't already exist, run
any recipes if necessary, and build the apk.
- '''
+ """
pass # The decorator does everything
- def archs(self, args):
- '''List the target architectures available to be built for.'''
+ def archs(self, _args):
+ """List the target architectures available to be built for."""
print('{Style.BRIGHT}Available target architectures are:'
'{Style.RESET_ALL}'.format(Style=Out_Style))
for arch in self.ctx.archs:
print(' {}'.format(arch.arch))
def dists(self, args):
- '''The same as :meth:`distributions`.'''
+ """The same as :meth:`distributions`."""
self.distributions(args)
- def distributions(self, args):
- '''Lists all distributions currently available (i.e. that have already
- been built).'''
+ def distributions(self, _args):
+ """Lists all distributions currently available (i.e. that have already
+ been built)."""
ctx = self.ctx
dists = Distribution.get_distributions(ctx)
if dists:
print('{Style.BRIGHT}Distributions currently installed are:'
- '{Style.RESET_ALL}'.format(Style=Out_Style, Fore=Out_Fore))
+ '{Style.RESET_ALL}'.format(Style=Out_Style))
pretty_log_dists(dists, print)
else:
print('{Style.BRIGHT}There are no dists currently built.'
'{Style.RESET_ALL}'.format(Style=Out_Style))
- def delete_dist(self, args):
+ def delete_dist(self, _args):
dist = self._dist
- if dist.needs_build:
+ if not dist.folder_exists():
info('No dist exists that matches your specifications, '
'exiting without deleting.')
- shutil.rmtree(dist.dist_dir)
+ return
+ dist.delete()
def sdk_tools(self, args):
- '''Runs the android binary from the detected SDK directory, passing
+ """Runs the android binary from the detected SDK directory, passing
all arguments straight to it. This binary is used to install
e.g. platform-tools for different API level targets. This is
intended as a convenience function if android is not in your
$PATH.
- '''
+ """
ctx = self.ctx
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
user_ndk_dir=self.ndk_dir,
user_android_api=self.android_api,
- user_ndk_ver=self.ndk_version)
+ user_ndk_api=self.ndk_api)
android = sh.Command(join(ctx.sdk_dir, 'tools', args.tool))
output = android(
*args.unknown_args, _iter=True, _out_bufsize=1, _err_to_out=True)
@@ -913,25 +1191,25 @@ def sdk_tools(self, args):
sys.stdout.flush()
def adb(self, args):
- '''Runs the adb binary from the detected SDK directory, passing all
+ """Runs the adb binary from the detected SDK directory, passing all
arguments straight to it. This is intended as a convenience
function if adb is not in your $PATH.
- '''
+ """
self._adb(args.unknown_args)
def logcat(self, args):
- '''Runs ``adb logcat`` using the adb binary from the detected SDK
- directory. All extra args are passed as arguments to logcat.'''
+ """Runs ``adb logcat`` using the adb binary from the detected SDK
+ directory. All extra args are passed as arguments to logcat."""
self._adb(['logcat'] + args.unknown_args)
def _adb(self, commands):
- '''Call the adb executable from the SDK, passing the given commands as
- arguments.'''
+ """Call the adb executable from the SDK, passing the given commands as
+ arguments."""
ctx = self.ctx
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
user_ndk_dir=self.ndk_dir,
user_android_api=self.android_api,
- user_ndk_ver=self.ndk_version)
+ user_ndk_api=self.ndk_api)
if platform in ('win32', 'cygwin'):
adb = sh.Command(join(ctx.sdk_dir, 'platform-tools', 'adb.exe'))
else:
@@ -942,8 +1220,11 @@ def _adb(self, commands):
sys.stdout.write(line)
sys.stdout.flush()
+ def recommendations(self, args):
+ print_recommendations()
- def build_status(self, args):
+ def build_status(self, _args):
+ """Print the status of the specified build. """
print('{Style.BRIGHT}Bootstraps whose core components are probably '
'already built:{Style.RESET_ALL}'.format(Style=Out_Style))
@@ -971,8 +1252,5 @@ def build_status(self, args):
print(recipe_str)
-def main():
- ToolchainCL()
-
if __name__ == "__main__":
main()
diff --git a/pythonforandroid/tools/biglink b/pythonforandroid/tools/biglink
index 6b86dbf84..8a8e561fb 100755
--- a/pythonforandroid/tools/biglink
+++ b/pythonforandroid/tools/biglink
@@ -1,11 +1,10 @@
#!/usr/bin/env python
-from __future__ import print_function
import os
import sys
import subprocess
-sofiles = [ ]
+sofiles = []
for directory in sys.argv[2:]:
@@ -20,7 +19,7 @@ for directory in sys.argv[2:]:
sofiles.append(fn[:-2])
# The raw argument list.
-args = [ ]
+args = []
for fn in sofiles:
afn = fn + ".o"
@@ -31,7 +30,7 @@ for fn in sofiles:
data = fd.read()
args.extend(data.split(" "))
-unique_args = [ ]
+unique_args = []
while args:
a = args.pop()
if a in ('-L', ):
diff --git a/pythonforandroid/tools/liblink b/pythonforandroid/tools/liblink
index 69f8ef23a..27e4cfee1 100755
--- a/pythonforandroid/tools/liblink
+++ b/pythonforandroid/tools/liblink
@@ -1,6 +1,5 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
-from __future__ import print_function
import sys
import subprocess
from os import environ
@@ -22,7 +21,7 @@ while i < len(sys.argv):
i += 1
continue
- if opt.startswith("-l") or opt.startswith("-L"):
+ if opt.startswith(("-l", "-L")):
libs.append(opt)
continue
@@ -34,26 +33,8 @@ while i < len(sys.argv):
i += 1
continue
- if opt.startswith("-I") or opt.startswith('-isystem'):
- continue
-
- if opt.startswith("-m"):
- continue
-
- if opt.startswith("-f"):
- continue
-
- if opt.startswith("-O"):
- continue
-
- if opt.startswith("-g"):
- continue
-
- if opt.startswith("-D"):
- continue
-
- if opt.startswith("-R"):
- # for -rpath, not implemented yet.
+ if opt.startswith(
+ ("-I", "-isystem", "-m", "-f", "-O", "-g", "-D", "-R")):
continue
if opt.startswith("-"):
diff --git a/pythonforandroid/util.py b/pythonforandroid/util.py
index cc9d94a61..2738d5999 100644
--- a/pythonforandroid/util.py
+++ b/pythonforandroid/util.py
@@ -1,30 +1,25 @@
import contextlib
-from os.path import exists
-from os import getcwd, chdir, makedirs
-import io
-import json
+from fnmatch import fnmatch
+import logging
+from os.path import exists, join
+from os import getcwd, chdir, makedirs, walk
+from pathlib import Path
+from platform import uname
import shutil
-import sys
from tempfile import mkdtemp
-try:
- from urllib.request import FancyURLopener
-except ImportError:
- from urllib import FancyURLopener
-from pythonforandroid.logger import (logger, Err_Fore)
+import packaging.version
-IS_PY3 = sys.version_info[0] >= 3
+from pythonforandroid.logger import (logger, Err_Fore, error, info)
-if IS_PY3:
- unistr = str
-else:
- unistr = unicode
+LOGGER = logging.getLogger("p4a.util")
-
-class WgetDownloader(FancyURLopener):
- version = ('Wget/1.17.1')
-
-urlretrieve = WgetDownloader().retrieve
+build_platform = "{system}-{machine}".format(
+ system=uname().system, machine=uname().machine
+).lower()
+"""the build platform in the format `system-machine`. We use
+this string to define the right build system when compiling some recipes or
+to get the right path for clang compiler"""
@contextlib.contextmanager
@@ -52,96 +47,119 @@ def temp_directory():
temp_dir, Err_Fore.RESET)))
-def ensure_dir(filename):
- if not exists(filename):
- makedirs(filename)
+def walk_valid_filens(base_dir, invalid_dir_names, invalid_file_patterns):
+ """Recursively walks all the files and directories in ``dirn``,
+ ignoring directories that match any pattern in ``invalid_dirns``
+ and files that patch any pattern in ``invalid_filens``.
+ ``invalid_dirns`` and ``invalid_filens`` should both be lists of
+ strings to match. ``invalid_dir_patterns`` expects a list of
+ invalid directory names, while ``invalid_file_patterns`` expects a
+ list of glob patterns compared against the full filepath.
+
+ File and directory paths are evaluated as full paths relative to ``dirn``.
-class JsonStore(object):
- """Replacement of shelve using json, needed for support python 2 and 3.
"""
- def __init__(self, filename):
- super(JsonStore, self).__init__()
- self.filename = filename
- self.data = {}
- if exists(filename):
- try:
- with io.open(filename, encoding='utf-8') as fd:
- self.data = json.load(fd)
- except ValueError:
- print("Unable to read the state.db, content will be replaced.")
-
- def __getitem__(self, key):
- return self.data[key]
-
- def __setitem__(self, key, value):
- self.data[key] = value
- self.sync()
-
- def __delitem__(self, key):
- del self.data[key]
- self.sync()
-
- def __contains__(self, item):
- return item in self.data
-
- def get(self, item, default=None):
- return self.data.get(item, default)
-
- def keys(self):
- return self.data.keys()
-
- def remove_all(self, prefix):
- for key in self.data.keys()[:]:
- if not key.startswith(prefix):
- continue
- del self.data[key]
- self.sync()
-
- def sync(self):
- # http://stackoverflow.com/questions/12309269/write-json-data-to-file-in-python/14870531#14870531
- if IS_PY3:
- with open(self.filename, 'w') as fd:
- json.dump(self.data, fd, ensure_ascii=False)
- else:
- with io.open(self.filename, 'w', encoding='utf-8') as fd:
- fd.write(unicode(json.dumps(self.data, ensure_ascii=False)))
-
-
-def which(program, path_env):
- '''Locate an executable in the system.'''
- import os
-
- def is_exe(fpath):
- return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
-
- fpath, fname = os.path.split(program)
- if fpath:
- if is_exe(program):
- return program
+ for dirn, subdirs, filens in walk(base_dir):
+
+ # Remove invalid subdirs so that they will not be walked
+ for i in reversed(range(len(subdirs))):
+ subdir = subdirs[i]
+ if subdir in invalid_dir_names:
+ subdirs.pop(i)
+
+ for filen in filens:
+ for pattern in invalid_file_patterns:
+ if fnmatch(filen, pattern):
+ break
+ else:
+ yield join(dirn, filen)
+
+
+def load_source(module, filename):
+ # Python 3.5+
+ import importlib.util
+ if hasattr(importlib.util, 'module_from_spec'):
+ spec = importlib.util.spec_from_file_location(module, filename)
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+ return mod
else:
- for path in path_env.split(os.pathsep):
- path = path.strip('"')
- exe_file = os.path.join(path, program)
- if is_exe(exe_file):
- return exe_file
-
- return None
-
-
-def get_directory(filename):
- '''If the filename ends with a recognised file extension, return the
- filename without this extension.'''
- if filename.endswith('.tar.gz'):
- return basename(filename[:-7])
- elif filename.endswith('.tgz'):
- return basename(filename[:-4])
- elif filename.endswith('.tar.bz2'):
- return basename(filename[:-8])
- elif filename.endswith('.tbz2'):
- return basename(filename[:-5])
- elif filename.endswith('.zip'):
- return basename(filename[:-4])
- info('Unknown file extension for {}'.format(filename))
+ # Python 3.3 and 3.4:
+ from importlib.machinery import SourceFileLoader
+ return SourceFileLoader(module, filename).load_module()
+
+
+class BuildInterruptingException(Exception):
+ def __init__(self, message, instructions=None):
+ super().__init__(message, instructions)
+ self.message = message
+ self.instructions = instructions
+
+
+def handle_build_exception(exception):
+ """
+ Handles a raised BuildInterruptingException by printing its error
+ message and associated instructions, if any, then exiting.
+ """
+ error('Build failed: {}'.format(exception.message))
+ if exception.instructions is not None:
+ info('Instructions: {}'.format(exception.instructions))
exit(1)
+
+
+def rmdir(dn, ignore_errors=False):
+ if not exists(dn):
+ return
+ LOGGER.debug("Remove directory and subdirectory {}".format(dn))
+ shutil.rmtree(dn, ignore_errors)
+
+
+def ensure_dir(dn):
+ if exists(dn):
+ return
+ LOGGER.debug("Create directory {0}".format(dn))
+ makedirs(dn)
+
+
+def move(source, destination):
+ LOGGER.debug("Moving {} to {}".format(source, destination))
+ shutil.move(source, destination)
+
+
+def touch(filename):
+ Path(filename).touch()
+
+
+def build_tools_version_sort_key(
+ version_string: str,
+) -> packaging.version.Version:
+ """
+ Returns a packaging.version.Version object for comparison purposes.
+ It includes canonicalization of the version string to allow for
+ comparison of versions with spaces in them (historically, RC candidates)
+
+ If the version string is invalid, it returns a version object with
+ version 0, which will be sorted at worst position.
+ """
+
+ try:
+ # Historically, Android build release candidates have had
+ # spaces in the version number.
+ return packaging.version.Version(version_string.replace(" ", ""))
+ except packaging.version.InvalidVersion:
+ # Put badly named versions at worst position.
+ return packaging.version.Version("0")
+
+
+def max_build_tool_version(
+ build_tools_versions: list,
+) -> str:
+ """
+ Returns the maximum build tools version from a list of build tools
+ versions. It uses the :meth:`build_tools_version_sort_key` function to
+ canonicalize the version strings and then returns the maximum version.
+ """
+
+ return max(build_tools_versions, key=build_tools_version_sort_key)
diff --git a/setup.py b/setup.py
index f091a3d3a..badce08e8 100644
--- a/setup.py
+++ b/setup.py
@@ -1,29 +1,31 @@
-from setuptools import setup, find_packages
+import glob
+from io import open # for open(..,encoding=...) parameter in python 2
from os import walk
from os.path import join, dirname, sep
-import os
-import glob
import re
+from setuptools import setup, find_packages
# NOTE: All package data should also be set in MANIFEST.in
packages = find_packages()
package_data = {'': ['*.tmpl',
- '*.patch', ], }
+ '*.patch',
+ '*.diff', ], }
data_files = []
-if os.name == 'nt':
- install_reqs = ['appdirs', 'colorama>=0.3.3', 'jinja2',
- 'six']
-else:
- # don't use sh after 1.12.5, we have performance issues
- # https://github.com/amoffat/sh/issues/378
- install_reqs = ['appdirs', 'colorama>=0.3.3', 'sh>=1.10,<1.12.5', 'jinja2',
- 'six']
+# must be a single statement since buildozer is currently parsing it, refs:
+# https://github.com/kivy/buildozer/issues/722
+install_reqs = [
+ 'appdirs', 'colorama>=0.3.3', 'jinja2',
+ 'sh>=1.10, <2.0; sys_platform!="win32"',
+ 'build', 'toml', 'packaging', 'setuptools'
+]
+# (build and toml are used by pythonpackage.py)
+
# By specifying every file manually, package_data will be able to
# include them in binary distributions. Note that we have to add
@@ -32,7 +34,8 @@
def recursively_include(results, directory, patterns):
for root, subfolders, files in walk(directory):
for fn in files:
- if not any([glob.fnmatch.fnmatch(fn, pattern) for pattern in patterns]):
+ if not any(
+ glob.fnmatch.fnmatch(fn, pattern) for pattern in patterns):
continue
filename = join(root, fn)
directory = 'pythonforandroid'
@@ -40,13 +43,17 @@ def recursively_include(results, directory, patterns):
results[directory] = []
results[directory].append(join(*filename.split(sep)[1:]))
+
recursively_include(package_data, 'pythonforandroid/recipes',
['*.patch', 'Setup*', '*.pyx', '*.py', '*.c', '*.h',
- '*.mk', '*.jam', ])
+ '*.mk', '*.jam', '*.diff', ])
recursively_include(package_data, 'pythonforandroid/bootstraps',
- ['*.properties', '*.xml', '*.java', '*.tmpl', '*.txt', '*.png',
- '*.mk', '*.c', '*.h', '*.py', '*.sh', '*.jpg', '*.aidl',
- '*.gradle', '.gitkeep', 'gradlew*', '*.jar', ])
+ [
+ '*.properties', '*.xml', '*.java', '*.tmpl', '*.txt',
+ '*.png', '*.mk', '*.c', '*.h', '*.py', '*.sh', '*.jpg',
+ '*.aidl', '*.gradle', '.gitkeep', 'gradlew*', '*.jar',
+ '*.patch',
+ ])
recursively_include(package_data, 'pythonforandroid/bootstraps',
['sdl-config', ])
recursively_include(package_data, 'pythonforandroid/bootstraps/webview',
@@ -54,13 +61,17 @@ def recursively_include(results, directory, patterns):
recursively_include(package_data, 'pythonforandroid',
['liblink', 'biglink', 'liblink.sh'])
-with open(join(dirname(__file__), 'README.rst')) as fileh:
+with open(join(dirname(__file__), 'README.md'),
+ encoding="utf-8",
+ errors="replace", ) as fileh:
long_description = fileh.read()
init_filen = join(dirname(__file__), 'pythonforandroid', '__init__.py')
version = None
try:
- with open(init_filen) as fileh:
+ with open(init_filen,
+ encoding="utf-8",
+ errors="replace") as fileh:
lines = fileh.readlines()
except IOError:
pass
@@ -73,28 +84,36 @@ def recursively_include(results, directory, patterns):
version = matches[0].strip("'").strip('"')
break
if version is None:
- raise Exception('Error: version could not be loaded from {}'.format(init_filen))
+ raise Exception(
+ 'Error: version could not be loaded from {}'.format(init_filen))
setup(name='python-for-android',
version=version,
- description='Android APK packager for Python scripts and apps',
+ description=(
+ 'A development tool that packages Python apps into '
+ 'binaries that can run on Android devices.'
+ ),
long_description=long_description,
- author='The Kivy team',
+ long_description_content_type='text/markdown',
+ python_requires=">=3.7.0",
+ author='Kivy Team and other contributors',
author_email='kivy-dev@googlegroups.com',
- url='https://github.com/kivy/python-for-android',
- license='MIT',
+ url='https://github.com/kivy/python-for-android',
+ license='MIT',
install_requires=install_reqs,
entry_points={
'console_scripts': [
- 'python-for-android = pythonforandroid.toolchain:main',
- 'p4a = pythonforandroid.toolchain:main',
+ 'python-for-android = pythonforandroid.entrypoints:main',
+ 'p4a = pythonforandroid.entrypoints:main',
],
'distutils.commands': [
'apk = pythonforandroid.bdistapk:BdistAPK',
+ 'aar = pythonforandroid.bdistapk:BdistAAR',
+ 'aab = pythonforandroid.bdistapk:BdistAAB',
],
},
- classifiers = [
- 'Development Status :: 4 - Beta',
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: Microsoft :: Windows',
@@ -103,11 +122,21 @@ def recursively_include(results, directory, patterns):
'Operating System :: MacOS :: MacOS X',
'Operating System :: Android',
'Programming Language :: C',
- 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
'Topic :: Software Development',
'Topic :: Utilities',
],
packages=packages,
package_data=package_data,
+ project_urls={
+ 'Documentation': "https://python-for-android.readthedocs.io",
+ 'Source': "https://github.com/kivy/python-for-android",
+ 'Bug Reports': "https://github.com/kivy/python-for-android/issues",
+ },
+
)
diff --git a/test_builds/test.sh b/test_builds/test.sh
deleted file mode 100755
index cd5e5bf5a..000000000
--- a/test_builds/test.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env sh
-
-py.test -s
diff --git a/test_builds/tests/test_apk.py b/test_builds/tests/test_apk.py
deleted file mode 100644
index 96162629b..000000000
--- a/test_builds/tests/test_apk.py
+++ /dev/null
@@ -1,69 +0,0 @@
-
-from pythonforandroid.toolchain import main
-from pythonforandroid.recipe import Recipe
-
-from os import path
-import sys
-
-import pytest
-
-# Set these values manually before testing (for now)
-ndk_dir = '/home/asandy/android/crystax-ndk-10.3.2'
-ndk_version='crystax-ndk-10.3.2'
-
-cur_dir = path.dirname(path.abspath(__file__))
-testapps_dir = path.join(path.split(path.split(cur_dir)[0])[0], 'testapps')
-
-orig_argv = sys.argv[:]
-
-def set_argv(argv):
- while sys.argv:
- sys.argv.pop()
- sys.argv.append(orig_argv[0])
- for item in argv:
- sys.argv.append(item)
- for item in orig_argv[1:]:
- if item == '-s':
- continue
- sys.argv.append(item)
-
-
-argument_combinations = [{'app_dir': path.join(testapps_dir, 'testapp'),
- 'requirements': 'python2,pyjnius,kivy',
- 'packagename': 'p4a_test_sdl2',
- 'bootstrap': 'sdl2',
- 'ndk_dir': ndk_dir,
- 'ndk_version': ndk_version},
- {'app_dir': path.join(testapps_dir, 'testapp'),
- 'requirements': 'python2,pyjnius,kivy',
- 'packagename': 'p4a_test_pygame',
- 'bootstrap': 'pygame',
- 'ndk_dir': ndk_dir,
- 'ndk_version': ndk_version},
- {'app_dir': path.join(testapps_dir, 'testapp_flask'),
- 'requirements': 'python2,flask,pyjnius',
- 'packagename': 'p4a_test_flask',
- 'bootstrap': 'webview',
- 'ndk_dir': ndk_dir,
- 'ndk_version': ndk_version},
- ]
-
-
-@pytest.mark.parametrize('args', argument_combinations)
-def test_build_sdl2(args):
-
- Recipe.recipes = {}
-
- set_argv(('apk --requirements={requirements} --private '
- '{app_dir} --package=net.p4a.{packagename} --name={packagename} '
- '--version=0.1 --bootstrap={bootstrap} --android_api=19 '
- '--ndk_dir={ndk_dir} --ndk_version={ndk_version} --debug '
- '--permission VIBRATE '
- '--symlink-java-src '
- '--orientation portrait --dist_name=test-{packagename}').format(
- **args).split(' '))
-
- print('argv are', sys.argv)
-
- main()
-
diff --git a/testapps/on_device_unit_tests/README.rst b/testapps/on_device_unit_tests/README.rst
new file mode 100644
index 000000000..6c0eb9626
--- /dev/null
+++ b/testapps/on_device_unit_tests/README.rst
@@ -0,0 +1,51 @@
+On device unit tests
+====================
+
+This test app runs a set of unit tests, to help confirm that the
+python-for-android build is actually working properly.
+
+Also it's dynamic, because it will run one app or another depending on the
+supplied recipes at build time.
+
+It currently supports three app `modes`:
+ - `kivy app` (with sdl2 bootstrap): if kivy in recipes
+ - `flask app` (with webview bootstrap): if flask in recipes
+ - `no gui`: if neither of above cases is taken
+
+The main tests are for the recipes built in the apk. Each module (or
+other tool) is at least imported and subject to some basic check.
+
+This test app can be build via `setup.py` or via buildozer. In both
+cases it will build a basic kivy app with a set of tests defined via the
+`requirements` keyword (specified at build time).
+
+In case that you build the `test app with no-gui`, the unittests results must
+be checked via command `adb logcat` or some logging apk (you may need root
+permissions in your device to use such app).
+
+Building the app with python-for-android
+========================================
+
+You can use the provided file `setup.py`. Check our `Makefile
+<https://github.com/kivy/python-for-android/blob/develop/Makefile>`__ to guess
+how to build the test app, or also you can look at `testing pull requests documentation
+<https://github.com/kivy/python-for-android/blob/develop/doc/source/testing_pull_requests.rst>`__,
+which describes some of the methods that you can use to build the test app.
+
+Building the app with buildozer
+===============================
+
+This app can be built using buildozer, which it also serves as a
+test for::
+
+ $ buildozer android debug
+
+Install on an Android device::
+
+ $ adb install -r adb install -r bin/p4aunittests-0.1-debug.apk
+ # or
+ $ buildozer android deploy
+
+Run the app and check in logcat that all the tests pass::
+
+ $ adb logcat | grep python # or look up the adb syntax for this
diff --git a/testapps/on_device_unit_tests/buildozer.spec b/testapps/on_device_unit_tests/buildozer.spec
new file mode 100644
index 000000000..b372d5faa
--- /dev/null
+++ b/testapps/on_device_unit_tests/buildozer.spec
@@ -0,0 +1,282 @@
+[app]
+
+# (str) Title of your application
+title = p4a unit tests
+
+# (str) Package name
+package.name = p4aunittests
+
+# (str) Package domain (needed for android/ios packaging)
+package.domain = org.kivy
+
+# (str) Source code where the main.py live
+source.dir = test_app
+
+# (list) Source files to include (let empty to include all the files)
+source.include_exts = py,png,jpg,kv,atlas,html,css,otf,txt
+
+# (list) List of inclusions using pattern matching
+#source.include_patterns = assets/*,images/*.png
+
+# (list) Source files to exclude (let empty to not exclude anything)
+#source.exclude_exts = spec
+
+# (list) List of directory to exclude (let empty to not exclude anything)
+#source.exclude_dirs = tests, bin
+
+# (list) List of exclusions using pattern matching
+#source.exclude_patterns = license,images/*/*.jpg
+
+# (str) Application versioning (method 1)
+version = 0.1
+
+# (str) Application versioning (method 2)
+# version.regex = __version__ = ['"](.*)['"]
+# version.filename = %(source.dir)s/main.py
+
+# (list) Application requirements
+# comma separated e.g. requirements = sqlite3,kivy
+requirements = python3,kivy,libffi,openssl,numpy,sqlite3
+
+# (str) Custom source folders for requirements
+# Sets custom source for any requirements with recipes
+# requirements.source.kivy = ../../kivy
+
+# (list) Garden requirements
+#garden_requirements =
+
+# (str) Presplash of the application
+#presplash.filename = %(source.dir)s/data/presplash.png
+
+# (str) Icon of the application
+#icon.filename = %(source.dir)s/data/icon.png
+
+# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
+orientation = all
+
+# (list) List of service to declare
+#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
+
+#
+# OSX Specific
+#
+
+#
+# author = © Copyright Info
+
+# change the major version of python used by the app
+osx.python_version = 3
+
+# Kivy version to use
+osx.kivy_version = 1.9.1
+
+#
+# Android specific
+#
+
+# (bool) Indicate if the application should be fullscreen or not
+fullscreen = 0
+
+# (string) Presplash background color (for new android toolchain)
+# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
+# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
+# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
+# olive, purple, silver, teal.
+#android.presplash_color = #FFFFFF
+
+# (list) Permissions
+#android.permissions = INTERNET
+
+# (int) Target Android API, should be as high as possible.
+#android.api = 27
+
+# (int) Minimum API your APK will support.
+#android.minapi = 21
+
+# (int) Android SDK version to use
+#android.sdk = 20
+
+# (str) Android NDK version to use
+#android.ndk = 17c
+
+# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
+#android.ndk_api = 21
+
+# (bool) Use --private data storage (True) or --dir public storage (False)
+#android.private_storage = True
+
+# (str) Android NDK directory (if empty, it will be automatically downloaded.)
+#android.ndk_path =
+
+# (str) Android SDK directory (if empty, it will be automatically downloaded.)
+#android.sdk_path =
+
+# (str) ANT directory (if empty, it will be automatically downloaded.)
+#android.ant_path =
+
+# (bool) If True, then skip trying to update the Android sdk
+# This can be useful to avoid excess Internet downloads or save time
+# when an update is due and you just want to test/build your package
+# android.skip_update = False
+
+# (str) Android entry point, default is ok for Kivy-based app
+#android.entrypoint = org.renpy.android.PythonActivity
+
+# (list) Pattern to whitelist for the whole project
+android.whitelist = unittest/*
+
+# (str) Path to a custom whitelist file
+#android.whitelist_src =
+
+# (str) Path to a custom blacklist file
+#android.blacklist_src =
+
+# (list) List of Java .jar files to add to the libs so that pyjnius can access
+# their classes. Don't add jars that you do not need, since extra jars can slow
+# down the build process. Allows wildcards matching, for example:
+# OUYA-ODK/libs/*.jar
+#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
+
+# (list) List of Java files to add to the android project (can be java or a
+# directory containing the files)
+#android.add_src =
+
+# (list) Android AAR archives to add (currently works only with sdl2_gradle
+# bootstrap)
+#android.add_aars =
+
+# (list) Gradle dependencies to add (currently works only with sdl2_gradle
+# bootstrap)
+#android.gradle_dependencies =
+
+# (list) Java classes to add as activities to the manifest.
+#android.add_activites = com.example.ExampleActivity
+
+# (str) python-for-android branch to use, defaults to master
+p4a.branch = develop
+
+# (str) OUYA Console category. Should be one of GAME or APP
+# If you leave this blank, OUYA support will not be enabled
+#android.ouya.category = GAME
+
+# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
+#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
+
+# (str) XML file to include as an intent filters in <activity> tag
+#android.manifest.intent_filters =
+
+# (str) launchMode to set for the main activity
+#android.manifest.launch_mode = standard
+
+# (list) Android additional libraries to copy into libs/armeabi
+#android.add_libs_armeabi = libs/android/*.so
+#android.add_libs_armeabi_v7a = libs/android-v7/*.so
+#android.add_libs_x86 = libs/android-x86/*.so
+#android.add_libs_mips = libs/android-mips/*.so
+
+# (bool) Indicate whether the screen should stay on
+# Don't forget to add the WAKE_LOCK permission if you set this to True
+#android.wakelock = False
+
+# (list) Android application meta-data to set (key=value format)
+#android.meta_data =
+
+# (list) Android library project to add (will be added in the
+# project.properties automatically.)
+#android.library_references =
+
+# (str) Android logcat filters to use
+#android.logcat_filters = *:S python:D
+
+# (bool) Copy library instead of making a libpymodules.so
+#android.copy_libs = 1
+
+# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86
+android.arch = armeabi-v7a
+
+#
+# Python for android (p4a) specific
+#
+
+# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
+#p4a.source_dir =
+
+# (str) The directory in which python-for-android should look for your own build recipes (if any)
+#p4a.local_recipes =
+
+# (str) Filename to the hook for p4a
+#p4a.hook =
+
+# (str) Bootstrap to use for android builds
+# p4a.bootstrap = sdl2
+
+# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
+#p4a.port =
+
+
+#
+# iOS specific
+#
+
+# (str) Path to a custom kivy-ios folder
+#ios.kivy_ios_dir = ../kivy-ios
+
+# (str) Name of the certificate to use for signing the debug version
+# Get a list of available identities: buildozer ios list_identities
+#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
+
+# (str) Name of the certificate to use for signing the release version
+#ios.codesign.release = %(ios.codesign.debug)s
+
+
+[buildozer]
+
+# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
+log_level = 2
+
+# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
+warn_on_root = 1
+
+# (str) Path to build artifact storage, absolute or relative to spec file
+# build_dir = ./.buildozer
+
+# (str) Path to build output (i.e. .apk, .ipa) storage
+# bin_dir = ./bin
+
+# -----------------------------------------------------------------------------
+# List as sections
+#
+# You can define all the "list" as [section:key].
+# Each line will be considered as a option to the list.
+# Let's take [app] / source.exclude_patterns.
+# Instead of doing:
+#
+#[app]
+#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
+#
+# This can be translated into:
+#
+#[app:source.exclude_patterns]
+#license
+#data/audio/*.wav
+#data/images/original/*
+#
+
+
+# -----------------------------------------------------------------------------
+# Profiles
+#
+# You can extend section / key with a profile
+# For example, you want to deploy a demo version of your application without
+# HD content. You could first change the title to add "(demo)" in the name
+# and extend the excluded directories to remove the HD content.
+#
+#[app@demo]
+#title = My Application (demo)
+#
+#[app:source.exclude_patterns@demo]
+#images/hd/*
+#
+# Then, invoke the command line with the "demo" profile:
+#
+#buildozer --profile demo android debug
diff --git a/testapps/on_device_unit_tests/setup.py b/testapps/on_device_unit_tests/setup.py
new file mode 100644
index 000000000..18fd82af7
--- /dev/null
+++ b/testapps/on_device_unit_tests/setup.py
@@ -0,0 +1,120 @@
+"""
+This is the `setup.py` file for the `on device unit test app`.
+
+In this module we can control how will be built our test app. Depending on
+our requirements we can build an kivy, flask or a non-gui app. We default to an
+kivy app, since the python-for-android project its a sister project of kivy.
+
+The parameter `requirements` is crucial to determine the unit tests we will
+perform with our app, so we must explicitly name the recipe we want to test
+and, of course, we should have the proper test for the given recipe at
+`tests.test_requirements.py` or nothing will be tested. We control our default
+app requirements via the dictionary `options`. Here you have some examples
+to build the supported app modes::
+
+ - kivy *basic*: `sqlite3,libffi,openssl,pyjnius,kivy,python3,requests,
+ urllib3,chardet,idna`
+ - kivy *images/graphs*: `kivy,python3,numpy,matplotlib,Pillow`
+ - kivy *encryption*: `kivy,python3,cryptography,pycryptodome,scrypt,
+ m2crypto,pysha3`
+ - flask (with webview bootstrap): `sqlite3,libffi,openssl,pyjnius,flask,
+ python3,genericndkbuild`
+
+
+.. note:: just noting that, for the `kivy basic` app, we add the requirements:
+ `sqlite3,libffi,openssl` so this way we will trigger the unit tests
+ that we have for such recipes.
+
+.. tip:: to force `python-for-android` generate an `flask` app without using
+ the kwarg `bootstrap`, we add the recipe `genericndkbuild`, which will
+ trigger the `webview bootstrap` at build time.
+"""
+
+import os
+import sys
+
+from setuptools import setup, find_packages
+
+# define a basic test app, which can be override passing the proper args to cli
+options = {
+ 'apk':
+ {
+ 'requirements':
+ 'sqlite3,libffi,openssl,pyjnius,kivy,python3,requests,urllib3,'
+ 'chardet,idna',
+ 'android-api': 27,
+ 'ndk-api': 21,
+ 'dist-name': 'bdist_unit_tests_app',
+ 'arch': 'armeabi-v7a',
+ 'bootstrap' : 'sdl2',
+ 'permissions': ['INTERNET', 'VIBRATE'],
+ 'orientation': ['portrait', 'landscape'],
+ 'service': 'P4a_test_service:app_service.py',
+ },
+ 'aab':
+ {
+ 'requirements':
+ 'sqlite3,libffi,openssl,pyjnius,kivy,python3,requests,urllib3,'
+ 'chardet,idna',
+ 'android-api': 27,
+ 'ndk-api': 21,
+ 'dist-name': 'bdist_unit_tests_app',
+ 'arch': 'armeabi-v7a',
+ 'bootstrap' : 'sdl2',
+ 'permissions': ['INTERNET', 'VIBRATE'],
+ 'orientation': ['portrait', 'landscape'],
+ 'service': 'P4a_test_service:app_service.py',
+ },
+ 'aar':
+ {
+ 'requirements' : 'python3',
+ 'android-api': 27,
+ 'ndk-api': 21,
+ 'dist-name': 'bdist_unit_tests_app',
+ 'arch': 'arm64-v8a',
+ 'bootstrap' : 'service_library',
+ 'permissions': ['INTERNET', 'VIBRATE'],
+ 'service': 'P4a_test_service:app_service.py',
+ }
+}
+
+# check if we overwrote the default test_app requirements via `cli`
+requirements = options['apk']['requirements'].rsplit(',')
+for n, arg in enumerate(sys.argv):
+ if arg == '--requirements':
+ print('found requirements')
+ requirements = sys.argv[n + 1].rsplit(',')
+ break
+
+# remove `orientation` in case that we don't detect a kivy or flask app,
+# since the `service_only` bootstrap does not support such argument
+if not ({'kivy', 'flask'} & set(requirements)):
+ options['apk'].pop('orientation')
+
+# write a file to let the test_app know which requirements we want to test
+# Note: later, when running the app, we will guess if we have the right test.
+app_requirements_txt = os.path.join(
+ os.path.split(__file__)[0],
+ 'test_app',
+ 'app_requirements.txt',
+)
+with open(app_requirements_txt, 'w') as requirements_file:
+ for req in requirements:
+ requirements_file.write(f'{req.split("==")[0]}\n')
+
+# run the install
+setup(
+ name='unit_tests_app',
+ version='1.1',
+ description='p4a on device unit test app',
+ author='Alexander Taylor, Pol Canelles',
+ author_email='alexanderjohntaylor@gmail.com, canellestudi@gmail.com',
+ packages=find_packages(),
+ options=options,
+ package_data={
+ 'test_app': ['*.py', '*.kv', '*.txt'],
+ 'test_app/static': ['*.png', '*.css', '*.otf'],
+ 'test_app/templates': ['*.html'],
+ 'test_app/tests': ['*.py'],
+ }
+)
diff --git a/testapps/on_device_unit_tests/test_app/app_flask.py b/testapps/on_device_unit_tests/test_app/app_flask.py
new file mode 100644
index 000000000..27ae7d4ad
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/app_flask.py
@@ -0,0 +1,185 @@
+print('main.py was successfully called')
+print('this is the new main.py')
+
+import sys
+print('python version is: ' + sys.version)
+print('python path is', sys.path)
+
+import os
+print('imported os')
+print('contents of this dir', os.listdir('./'))
+
+from flask import (
+ Flask,
+ render_template,
+ request,
+ Markup
+)
+
+print('imported flask etc')
+
+from constants import RUNNING_ON_ANDROID
+from tools import (
+ run_test_suites_into_buffer,
+ get_failed_unittests_from,
+ vibrate_with_pyjnius,
+ get_android_python_activity,
+ set_device_orientation,
+ setup_lifecycle_callbacks,
+)
+
+
+app = Flask(__name__)
+setup_lifecycle_callbacks()
+service_running = False
+TESTS_TO_PERFORM = dict()
+NON_ANDROID_DEVICE_MSG = 'Not running from Android device'
+
+
+def get_html_for_tested_modules(tested_modules, failed_tests):
+ modules_text = ''
+ for n, module in enumerate(sorted(tested_modules)):
+ print(module)
+ base_text = '<label class="{color}">{module}</label>'
+ if TESTS_TO_PERFORM[module] in failed_tests:
+ color = 'text-red'
+ else:
+ color = 'text-green'
+ if n != len(tested_modules) - 1:
+ base_text += ', '
+
+ modules_text += base_text.format(color=color, module=module)
+
+ return Markup(modules_text)
+
+
+def get_test_service():
+ from jnius import autoclass
+
+ return autoclass('org.test.unit_tests_app.ServiceP4a_test_service')
+
+
+def start_service():
+ global service_running
+ activity = get_android_python_activity()
+ test_service = get_test_service()
+ test_service.start(activity, 'Some argument')
+ service_running = True
+
+
+def stop_service():
+ global service_running
+ activity = get_android_python_activity()
+ test_service = get_test_service()
+ test_service.stop(activity)
+ service_running = False
+
+
+@app.route('/')
+def index():
+ return render_template(
+ 'index.html',
+ platform='Android' if RUNNING_ON_ANDROID else 'Desktop',
+ service_running=service_running,
+ )
+
+
+@app.route('/unittests')
+def unittests():
+ import unittest
+ print('Imported unittest')
+
+ print("loading tests...")
+ suites = unittest.TestLoader().loadTestsFromNames(
+ list(TESTS_TO_PERFORM.values()),
+ )
+
+ print("running unittest...")
+ terminal_output = run_test_suites_into_buffer(suites)
+
+ print("unittest result is:")
+ unittest_error_text = terminal_output.split('\n')
+ print(terminal_output)
+
+ # get a nice colored `html` output for our tested recipes
+ failed_tests = get_failed_unittests_from(
+ terminal_output, TESTS_TO_PERFORM.values(),
+ )
+ colored_tested_recipes = get_html_for_tested_modules(
+ TESTS_TO_PERFORM.keys(), failed_tests,
+ )
+
+ return render_template(
+ 'unittests.html',
+ tested_recipes=colored_tested_recipes,
+ unittests_output=unittest_error_text,
+ platform='Android' if RUNNING_ON_ANDROID else 'Desktop',
+ )
+
+
+@app.route('/page2')
+def page2():
+ return render_template(
+ 'page2.html',
+ platform='Android' if RUNNING_ON_ANDROID else 'Desktop',
+ )
+
+
+@app.route('/loadUrl')
+def loadUrl():
+ if not RUNNING_ON_ANDROID:
+ print(NON_ANDROID_DEVICE_MSG, '...cancelled loadUrl.')
+ return NON_ANDROID_DEVICE_MSG
+ args = request.args
+ if 'url' not in args:
+ print('ERROR: asked to open an url but without url argument')
+ print('asked to open url', args['url'])
+ activity = get_android_python_activity()
+ activity.loadUrl(args['url'])
+ return ('', 204)
+
+
+@app.route('/vibrate')
+def vibrate():
+ if not RUNNING_ON_ANDROID:
+ print(NON_ANDROID_DEVICE_MSG, '...cancelled vibrate.')
+ return NON_ANDROID_DEVICE_MSG
+
+ args = request.args
+ if 'time' not in args:
+ print('ERROR: asked to vibrate but without time argument')
+ print('asked to vibrate', args['time'])
+ vibrate_with_pyjnius(int(float(args['time']) * 1000))
+ return ('', 204)
+
+
+@app.route('/orientation')
+def orientation():
+ if not RUNNING_ON_ANDROID:
+ print(NON_ANDROID_DEVICE_MSG, '...cancelled orientation.')
+ return NON_ANDROID_DEVICE_MSG
+ args = request.args
+ if 'dir' not in args:
+ print('ERROR: asked to orient but no dir specified')
+ return 'No direction specified '
+ direction = args['dir']
+ set_device_orientation(direction)
+ return ('', 204)
+
+
+@app.route('/service')
+def service():
+ if not RUNNING_ON_ANDROID:
+ print(NON_ANDROID_DEVICE_MSG, '...cancelled service.')
+ return (NON_ANDROID_DEVICE_MSG, 400)
+ args = request.args
+ if 'action' not in args:
+ print('ERROR: asked to manage service but no action specified')
+ return ('No action specified', 400)
+
+ action = args['action']
+ if action == 'start':
+ start_service()
+ else:
+ stop_service()
+ return ('', 204)
diff --git a/testapps/on_device_unit_tests/test_app/app_kivy.py b/testapps/on_device_unit_tests/test_app/app_kivy.py
new file mode 100644
index 000000000..94ae5fe51
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/app_kivy.py
@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+
+import subprocess
+
+from os.path import split
+
+from kivy.app import App
+from kivy.clock import Clock
+from kivy.properties import (
+ BooleanProperty,
+ DictProperty,
+ ListProperty,
+ StringProperty,
+)
+from kivy.lang import Builder
+
+from constants import RUNNING_ON_ANDROID
+from tools import (
+ get_android_python_activity,
+ get_failed_unittests_from,
+ get_images_with_extension,
+ load_kv_from,
+ raise_error,
+ run_test_suites_into_buffer,
+ setup_lifecycle_callbacks,
+ vibrate_with_pyjnius,
+)
+from widgets import TestImage
+
+# define our app's screen manager and load the screen templates
+screen_manager_app = '''
+ScreenManager:
+ ScreenUnittests:
+ ScreenKeyboard:
+ ScreenOrientation:
+ ScreenService:
+'''
+load_kv_from('screen_unittests.kv')
+load_kv_from('screen_keyboard.kv')
+load_kv_from('screen_orientation.kv')
+load_kv_from('screen_service.kv')
+
+
+class TestKivyApp(App):
+
+ tests_to_perform = DictProperty()
+ unittest_error_text = StringProperty('Running unittests...')
+ test_packages = StringProperty('Unittest recipes:')
+ generated_images = ListProperty()
+ service_running = BooleanProperty(False)
+
+ def build(self):
+ self.reset_unittests_results()
+ self.sm = Builder.load_string(screen_manager_app)
+ return self.sm
+
+ def on_start(self):
+ setup_lifecycle_callbacks()
+
+ def reset_unittests_results(self, refresh_ui=False):
+ for img in get_images_with_extension():
+ subprocess.call(["rm", "-r", img])
+ print('removed image: ', img)
+ if refresh_ui:
+ self.set_color_for_tested_modules(restart=True)
+ self.unittest_error_text = ''
+ screen_unittests = self.sm.get_screen('unittests')
+ images_box = screen_unittests.ids.test_images_box
+ images_box.clear_widgets()
+ self.generated_images = []
+
+ def on_tests_to_perform(self, *args):
+ """
+ Check `test_to_perform` so we can build some special tests in our ui.
+ Also will schedule the run of our tests.
+ """
+ print('on_tests_to_perform: ', self.tests_to_perform.keys())
+ self.set_color_for_tested_modules(restart=True)
+ Clock.schedule_once(self.run_unittests, 3)
+
+ def run_unittests(self, *args):
+ import unittest
+ print('Imported unittest')
+
+ print("loading tests...")
+ suites = unittest.TestLoader().loadTestsFromNames(
+ list(self.tests_to_perform.values()),
+ )
+ self.test_packages = ', '.join(self.tests_to_perform.keys())
+
+ print("running unittest...")
+ self.unittest_error_text = run_test_suites_into_buffer(suites)
+
+ print("unittest result is:")
+ print(self.unittest_error_text)
+ print('Ran tests')
+
+ self.set_color_for_tested_modules()
+
+ # check generated images by unittests
+ self.generated_images = get_images_with_extension()
+
+ def set_color_for_tested_modules(self, restart=False):
+ tests_made = sorted(list(self.tests_to_perform.keys()))
+ failed_tests = get_failed_unittests_from(
+ self.unittest_error_text,
+ self.tests_to_perform.values(),
+ )
+
+ modules_text = 'Unittest recipes: '
+ for n, module in enumerate(tests_made):
+ base_text = '[color={color}]{module}[/color]'
+ if restart:
+ color = '#838383' # grey
+ elif self.tests_to_perform[module] in failed_tests:
+ color = '#ff0000' # red
+ else:
+ color = '#5d8000' # green
+ if n != len(tests_made) - 1:
+ base_text += ', '
+
+ modules_text += base_text.format(color=color, module=module)
+
+ self.test_packages = modules_text
+
+ def on_generated_images(self, *args):
+ screen_unittests = self.sm.get_screen('unittests')
+ images_box = screen_unittests.ids.test_images_box
+ for i in self.generated_images:
+ img = TestImage(
+ text='Generated image by unittests: {}'.format(split(i)[1]),
+ source=i,
+ )
+ images_box.add_widget(img)
+
+ def test_vibration_with_pyjnius(self, *args):
+ vibrate_with_pyjnius()
+
+ @property
+ def service_time(self):
+ from jnius import autoclass
+
+ return autoclass('org.test.unit_tests_app.ServiceP4a_test_service')
+
+ def on_service_running(self, *args):
+ if RUNNING_ON_ANDROID:
+ if self.service_running:
+ print('Starting service')
+ self.start_service()
+ else:
+ print('Stopping service')
+ self.stop_service()
+ else:
+ raise_error('Service test not supported on desktop')
+
+ def start_service(self):
+ activity = get_android_python_activity()
+ service = self.service_time
+ try:
+ service.start(activity, 'Some argument')
+ except Exception as err:
+ raise_error(str(err))
+
+ def stop_service(self):
+ service = self.service_time
+ activity = get_android_python_activity()
+ service.stop(activity)
diff --git a/testapps/on_device_unit_tests/test_app/app_service.py b/testapps/on_device_unit_tests/test_app/app_service.py
new file mode 100644
index 000000000..953d86563
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/app_service.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import datetime
+import threading
+import time
+
+from os import environ
+
+argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')
+print(
+ 'app_service.py was successfully called with argument: "{}"'.format(
+ argument,
+ ),
+)
+
+next_call = time.time()
+next_call_in = 5 # seconds
+
+
+def service_timer():
+ global next_call
+ print('P4a test service: {}'.format(datetime.datetime.now()))
+
+ next_call += next_call_in
+ threading.Timer(next_call - time.time(), service_timer).start()
+
+
+print('Starting the test service timer...')
+service_timer()
diff --git a/testapps/on_device_unit_tests/test_app/constants.py b/testapps/on_device_unit_tests/test_app/constants.py
new file mode 100644
index 000000000..55b61d48b
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/constants.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from os import environ
+
+RUNNING_ON_ANDROID = "ANDROID_APP_PATH" in environ
+
+FONT_SIZE_TITLE = 32 if RUNNING_ON_ANDROID else 60
+FONT_SIZE_SUBTITLE = 16 if RUNNING_ON_ANDROID else 32
+FONT_SIZE_TEXT = 8 if RUNNING_ON_ANDROID else 16
diff --git a/testapps/on_device_unit_tests/test_app/main.py b/testapps/on_device_unit_tests/test_app/main.py
new file mode 100644
index 000000000..31422e893
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/main.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+On device unit test app
+=======================
+
+This is a dynamic test app, which means that depending on the requirements
+supplied at build time, will perform some tests or others. Also, this app will
+have an ui, or not, depending on requirements as well.
+
+For now, we contemplate three possibilities:
+ - A kivy unittest app (sdl2 bootstrap)
+ - A unittest app (webview bootstrap)
+ - A non-gui unittests app
+ - A non-gui Qt app (qt bootstrap)
+
+If you install/build this app via the `setup.py` file, a file named
+`app_requirements.txt` will be generated which will contain the requirements
+that we passed to the `setup.py` via arguments, which will determine
+the unittests that this app will run.
+
+.. note:: This app is made to be working on desktop and on an android device.
+ Be aware that some of the functionality of this app will only work on
+ an android device.
+
+.. tip:: you can write more unit tests at `tests/test_requirements.py` and test
+ these on desktop just by editing the file `app_requirements.txt`,
+ which should be located at the same location than this file. This
+ `app_requirements.txt` file, it's autogenerated when the
+ `setup.py` is ran, so in certain circumstances, you may need
+ to create it. Also be aware that each `python-for-android` recipe
+ that you want to test should be in a new line, taking into account the
+ case of the recipe.
+
+.. warning:: If you use buildozer you only will get the basic `kivy unittest
+ app`, with a basic set of tests: sqlite3, libffi, openssl and
+ pyjnius.
+"""
+
+import sys
+import unittest
+
+from os import curdir
+from os.path import isfile, realpath
+
+print('Imported unittest')
+
+sys.path.append('./')
+
+# read `app_requirements.txt` and find out which tests to perform
+tests_to_perform = {}
+requirements = None
+if isfile('app_requirements.txt'):
+ with open('app_requirements.txt', 'r') as requirements_file:
+ requirements = set(requirements_file.read().splitlines())
+if not requirements:
+ # we will test a basic set of recipes
+ requirements = {'sqlite3', 'libffi', 'openssl', 'pyjnius'}
+print('App requirements are: ', requirements)
+
+for recipe in requirements:
+ test_name = 'tests.test_requirements.{recipe}TestCase'.format(
+ recipe=recipe.capitalize()
+ )
+ try:
+ exist_test = unittest.TestLoader().loadTestsFromName(test_name)
+ except AttributeError:
+ # python2 case
+ pass
+ else:
+ if '_exception' not in exist_test._tests[0].__dict__:
+ print('Adding Testcase: ', test_name)
+ tests_to_perform[recipe] = test_name
+print('Tests to perform are: ', tests_to_perform)
+
+# Find out which app we want to run
+if 'kivy' in requirements:
+ from app_kivy import TestKivyApp
+
+ test_app = TestKivyApp()
+ test_app.tests_to_perform = tests_to_perform
+ test_app.run()
+elif 'flask' in requirements:
+ import app_flask
+ app_flask.TESTS_TO_PERFORM = tests_to_perform
+
+ print('Current directory is ', realpath(curdir))
+ flask_debug = not realpath(curdir).startswith('/data')
+
+ # Flask is run non-threaded since it tries to resolve app classes
+ # through pyjnius from request handlers. That doesn't work since the
+ # JNI ends up using the Java system class loader in new native
+ # threads.
+ #
+ # https://github.com/kivy/python-for-android/issues/2533
+ app_flask.app.run(threaded=False, debug=flask_debug)
+else:
+ # we don't have kivy or flask in our
+ # requirements, so we run unittests in terminal
+ suite = unittest.TestLoader().loadTestsFromNames(list(tests_to_perform.values()))
+ unittest.TextTestRunner().run(suite)
diff --git a/testapps/on_device_unit_tests/test_app/screen_keyboard.kv b/testapps/on_device_unit_tests/test_app/screen_keyboard.kv
new file mode 100644
index 000000000..d0b08bbd6
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/screen_keyboard.kv
@@ -0,0 +1,109 @@
+#:import Window kivy.core.window.Window
+
+#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE
+#:import FONT_SIZE_TEXT constants.FONT_SIZE_TEXT
+#:import FONT_SIZE_TITLE constants.FONT_SIZE_TITLE
+#:import Spacer20 widgets.Spacer20
+
+<ScreenKeyboard@Screen>:
+ name: 'keyboard'
+ BoxLayout:
+ orientation: 'vertical'
+ Button:
+ text: 'Back to unittests'
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ size_hint_y: None
+ height: dp(60)
+ on_press: root.parent.current = 'unittests'
+ Image:
+ keep_ratio: False
+ allow_stretch: True
+ source: 'static/coloursinv.png'
+ size_hint_y: None
+ height: dp(100)
+ Label:
+ text:
+ '[color=#999999]Test[/color] kivy ' \
+ '[color=#999999]keyboard[/color] modes'
+ height: self.texture_size[1]
+ size_hint_y: None
+ padding: 0, 20
+ font_size: sp(FONT_SIZE_TITLE)
+ font_name: 'static/Blanka-Regular.otf'
+ text_size: root.width, None
+ markup: True
+ halign: 'center'
+ Label:
+ text:
+ 'Specifies the behavior of window contents on display ' \
+ 'of the soft keyboard on Android.\n\n\n\n' \
+ '[color=#ff5900]WARNING:[/color] ' \
+ 'these tests only works on an Android device'
+ markup: True
+ padding: 0, 20
+ size_hint_y: None
+ text_size: root.width, None
+ font_size: sp(FONT_SIZE_TEXT)
+ height: self.texture_size[1]
+ halign: 'center'
+ Spacer20:
+ Spacer20:
+ RelativeLayout:
+ size_hint_y: None
+ height: dp(50)
+ BoxLayout:
+ size_hint_x: None
+ width: min(dp(500), root.width)
+ orientation: 'horizontal'
+ pos_hint: {'center_x': .5}
+ ToggleButton:
+ text: 'None'
+ group: 'keyboard_modes'
+ state: 'down'
+ on_press: Window.softinput_mode = ''
+ ToggleButton:
+ text: 'pan'
+ group: 'keyboard_modes'
+ on_press: Window.softinput_mode = 'pan'
+ ToggleButton:
+ text: 'below_target'
+ group: 'keyboard_modes'
+ on_press: Window.softinput_mode = 'below_target'
+ ToggleButton:
+ text: 'resize'
+ group: 'keyboard_modes'
+ on_press: Window.softinput_mode = 'resize'
+ Widget:
+ Scatter:
+ id: scatter
+ size_hint: None, None
+ size: dp(300), dp(80)
+ on_parent: self.pos = (300, 100)
+ BoxLayout:
+ size: scatter.size
+ orientation: 'horizontal'
+ canvas:
+ Color:
+ rgba: 1, 0, 1, .25
+ Rectangle:
+ pos: 0, 0
+ size: self.size
+ Label:
+ size_hint_x: None
+ width: dp(30)
+ text: 'drag me'
+ canvas.before:
+ Color:
+ rgb: 1, 1, 1
+ PushMatrix
+ Translate:
+ xy: self.center_x, self.center_y
+ Rotate:
+ angle: 90
+ axis: 0, 0, 1
+ Translate:
+ xy: -self.center_x, -self.center_y
+ canvas.after:
+ PopMatrix
+ TextInput:
+ text: 'type in me'
diff --git a/testapps/on_device_unit_tests/test_app/screen_orientation.kv b/testapps/on_device_unit_tests/test_app/screen_orientation.kv
new file mode 100644
index 000000000..aa0bdfb56
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/screen_orientation.kv
@@ -0,0 +1,74 @@
+#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE
+#:import FONT_SIZE_TEXT constants.FONT_SIZE_TEXT
+#:import FONT_SIZE_TITLE constants.FONT_SIZE_TITLE
+
+#:import set_device_orientation tools.set_device_orientation
+#:import Spacer20 widgets.Spacer20
+
+<ScreenOrientation@Screen>:
+ name: 'orientation'
+ ScrollView:
+ BoxLayout:
+ orientation: 'vertical'
+ size_hint_y: None
+ height: self.minimum_height
+ Button:
+ text: 'Back to unittests'
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ size_hint_y: None
+ height: dp(60)
+ on_press: root.parent.current = 'unittests'
+ Image:
+ keep_ratio: False
+ allow_stretch: True
+ source: 'static/coloursinv.png'
+ size_hint_y: None
+ height: dp(100)
+ Label:
+ text:
+ '[color=#999999]Test[/color] device ' \
+ '[color=#999999]orientation[/color]'
+ height: self.texture_size[1]
+ size_hint_y: None
+ padding: 0, 20
+ font_size: sp(FONT_SIZE_TITLE)
+ font_name: 'static/Blanka-Regular.otf'
+ text_size: root.width, None
+ markup: True
+ halign: 'center'
+ Spacer20:
+ Spacer20:
+ RelativeLayout:
+ size_hint_y: None
+ height: dp(50)
+ BoxLayout:
+ size_hint_x: None
+ width: min(dp(500), root.width)
+ pos_hint: {'center_x': .5}
+ orientation: 'horizontal'
+ ToggleButton:
+ text: 'Sensor'
+ group: 'device_orientations'
+ state: 'down'
+ on_press: set_device_orientation('sensor')
+ ToggleButton:
+ text: 'Horizontal'
+ group: 'device_orientations'
+ on_press: set_device_orientation('horizontal')
+ ToggleButton:
+ text: 'Vertical'
+ group: 'device_orientations'
+ on_press: set_device_orientation('vertical')
+ Spacer20:
+ Spacer20:
+ Label:
+ text:
+ '[color=#ff5900]WARNING:[/color] ' \
+ 'these tests only works on an Android device'
+ markup: True
+ padding: 20, 20
+ size_hint_y: None
+ text_size: root.width, None
+ font_size: sp(FONT_SIZE_TEXT)
+ height: self.texture_size[1]
+ halign: 'center'
diff --git a/testapps/on_device_unit_tests/test_app/screen_service.kv b/testapps/on_device_unit_tests/test_app/screen_service.kv
new file mode 100644
index 000000000..8e5398f13
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/screen_service.kv
@@ -0,0 +1,67 @@
+#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE
+#:import FONT_SIZE_TEXT constants.FONT_SIZE_TEXT
+#:import FONT_SIZE_TITLE constants.FONT_SIZE_TITLE
+
+#:import set_device_orientation tools.set_device_orientation
+#:import Spacer20 widgets.Spacer20
+#:import CircularButton widgets.CircularButton
+
+#:set green_color (0.3, 0.5, 0, 1)
+#:set red_color (1.0, 0, 0, 1)
+
+<ScreenService@Screen>:
+ name: 'service'
+ ScrollView:
+ BoxLayout:
+ orientation: 'vertical'
+ size_hint_y: None
+ height: self.minimum_height
+ Button:
+ text: 'Back to unittests'
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ size_hint_y: None
+ height: dp(60)
+ on_press: root.parent.current = 'unittests'
+ Image:
+ keep_ratio: False
+ allow_stretch: True
+ source: 'static/coloursinv.png'
+ size_hint_y: None
+ height: dp(100)
+ Label:
+ text:
+ '[color=#999999]Test[/color] P4A ' \
+ '[color=#999999]service[/color]'
+ size_hint_y: None
+ padding: 0, 20
+ height: self.texture_size[1]
+ halign: 'center'
+ font_size: sp(FONT_SIZE_TITLE)
+ font_name: 'static/Blanka-Regular.otf'
+ text_size: root.width, None
+ markup: True
+ Spacer20:
+ Spacer20:
+ RelativeLayout:
+ size_hint_y: None
+ height: dp(100)
+ CircularButton:
+ text: 'Start service' if not app.service_running else 'Stop Service'
+ pos_hint: {'center_x': .5}
+ background_color: red_color
+ on_press:
+ app.service_running = not app.service_running;
+ self.background_color = green_color \
+ if app.service_running else red_color
+ Spacer20:
+ Spacer20:
+ Label:
+ text:
+ '[color=#ff5900]WARNING:[/color] ' \
+ 'this test only works on an Android device'
+ markup: True
+ size_hint_y: None
+ height: self.texture_size[1]
+ halign: 'center'
+ text_size: root.width, None
+ font_size: sp(FONT_SIZE_TEXT)
diff --git a/testapps/on_device_unit_tests/test_app/screen_unittests.kv b/testapps/on_device_unit_tests/test_app/screen_unittests.kv
new file mode 100644
index 000000000..b04d98fd4
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/screen_unittests.kv
@@ -0,0 +1,155 @@
+#:import sys sys
+
+#:import Clock kivy.clock.Clock
+#:import Metrics kivy.metrics.Metrics
+
+#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE
+#:import FONT_SIZE_TEXT constants.FONT_SIZE_TEXT
+#:import FONT_SIZE_TITLE constants.FONT_SIZE_TITLE
+#:import Spacer20 widgets.Spacer20
+
+<ScreenUnittests@Screen>:
+ name: 'unittests'
+ ScrollView:
+ id: scroll_view
+ GridLayout:
+ id: grid
+ cols: 1
+ size_hint_y: None
+ height: self.minimum_height
+ BoxLayout:
+ id: header_box
+ orientation: 'vertical'
+ size_hint_y: None
+ height: self.minimum_height
+ GridLayout:
+ rows: 1 if root.width > root.height else 2
+ size_hint_y: None
+ height: dp(60) * self.rows
+ Button:
+ text: 'Test vibration'
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ on_press: app.test_vibration_with_pyjnius()
+ Button:
+ text: 'Test Keyboard'
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ on_press: root.parent.current = 'keyboard'
+ Button:
+ text: 'Test Orientation'
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ on_press: root.parent.current = 'orientation'
+ Button:
+ text: 'Test Service'
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ on_press: root.parent.current = 'service'
+ Image:
+ keep_ratio: False
+ allow_stretch: True
+ source: 'static/colours.png'
+ size_hint_y: None
+ height: dp(100)
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ padding: 0, 20
+ font_name: 'static/Blanka-Regular.otf'
+ font_size: sp(FONT_SIZE_TITLE)
+ text_size: self.size[0], None
+ markup: True
+ text:
+ '[color=#999999]Kivy[/color] on ' \
+ '[color=#999999]SDL2[/color] on ' \
+ '[color=#999999]Android[/color] !'
+ halign: 'center'
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ text_size: self.size[0], None
+ font_size: sp(FONT_SIZE_TEXT)
+ markup: True
+ text: sys.version
+ halign: 'center'
+ padding_y: dp(10)
+ Spacer20:
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ text_size: self.size[0], None
+ markup: True
+ text:
+ 'Dpi: {}\nDensity: {}\nFontscale: {}'.format(
+ Metrics.dpi, Metrics.density, Metrics.fontscale)
+ halign: 'center'
+ Spacer20:
+ BoxLayout:
+ id: output_box
+ orientation: 'vertical'
+ size_hint_y: None
+ height: self.minimum_height
+ canvas.before:
+ Color:
+ rgba: 1, 0, 1, .25
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ Spacer20:
+ Label:
+ id: test_packages_text
+ size_hint_y: None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ padding: 40, 20
+ markup: True
+ text: app.test_packages
+ canvas.before:
+ Color:
+ rgba: 0, 0, 0, .65
+ Rectangle:
+ pos: self.x + 20, self.y
+ size: self.width - 40, self.height
+ Label:
+ id: output_text
+ height: self.texture_size[1]
+ size_hint: None, None
+ pos_hint: {'center_x': .5 }
+ width: output_box.width - 40
+ padding: 20, 20
+ font_size: sp(FONT_SIZE_TEXT)
+ text_size: self.size[0], None
+ markup: True
+ text: app.unittest_error_text
+ halign: 'justify'
+ canvas.before:
+ Color:
+ rgba: 0, 0, 0, .35
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ Widget:
+ id: fill_space
+ size_hint_y: None
+ height:
+ max(20, root.height - header_box.height - output_box.height + 20)
+ canvas.before:
+ Color:
+ rgba: 1, 0, 1, .25
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ BoxLayout:
+ id: test_images_box
+ orientation: 'vertical'
+ size_hint_y: None
+ height: self.minimum_height if self.children else 0
+ padding: 0, 20
+ Button:
+ size_hint_y: None
+ height: dp(60)
+ text: 'Restart unittests'
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ on_press:
+ app.reset_unittests_results(refresh_ui=True);
+ root.ids.scroll_view.scroll_y = 1;
+ Clock.schedule_once(app.run_unittests, 2)
diff --git a/testapps/on_device_unit_tests/test_app/static/Blanka-Regular.otf b/testapps/on_device_unit_tests/test_app/static/Blanka-Regular.otf
new file mode 100644
index 000000000..60b18d781
Binary files /dev/null and b/testapps/on_device_unit_tests/test_app/static/Blanka-Regular.otf differ
diff --git a/testapps/testapp/colours.png b/testapps/on_device_unit_tests/test_app/static/colours.png
similarity index 100%
rename from testapps/testapp/colours.png
rename to testapps/on_device_unit_tests/test_app/static/colours.png
diff --git a/testapps/testapp_flask/static/coloursinv.png b/testapps/on_device_unit_tests/test_app/static/coloursinv.png
similarity index 100%
rename from testapps/testapp_flask/static/coloursinv.png
rename to testapps/on_device_unit_tests/test_app/static/coloursinv.png
diff --git a/testapps/on_device_unit_tests/test_app/static/flask.css b/testapps/on_device_unit_tests/test_app/static/flask.css
new file mode 100644
index 000000000..80a9e8880
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/static/flask.css
@@ -0,0 +1,133 @@
+
+@font-face{
+ font-family: Blanka;
+ src: url('Blanka-Regular.otf');
+ font-weight: normal;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: "Roboto Slab", Roboto, sans-serif;
+ color: #444;
+}
+
+/*
+ * Formatting the header area
+ */
+header {
+ background-color: #6600b8;
+ height: 90px;
+ width: 100%;
+ opacity: .9;
+ margin-bottom: 10px;
+}
+header h1.logo {
+ margin-top: 4px;
+ font-size: 1.4em;
+ font-family: Blanka, sans-serif;
+ color: #fff;
+ text-transform: uppercase;
+ float: left;
+ text-align: center;
+ width: 100%;
+}
+header h1.logo:hover {
+ color: #000000;
+ text-decoration: none;
+}
+
+/*
+ * Navbar
+ */
+.menu {
+ float: right;
+ margin-top: 0px;
+ width: 100%;
+ text-align: center;
+ font-family: Blanka, sans-serif;
+}
+.menu li {
+ display: inline-block;
+}
+.menu li + li {
+ margin-left: 35px;
+}
+.menu li a {
+ color: #999999;
+ text-decoration: none;
+ text-transform: uppercase;
+}
+
+/*
+ * Centering the body content
+ */
+.container {
+ width: auto;
+ margin: 0 8px;
+ text-align: center;
+}
+
+h2.page-title {
+ text-align: center;
+ text-transform: uppercase;
+ font-family: Blanka, sans-serif;
+}
+
+.text-underline {
+ border-bottom: 0px solid #333;
+ font-family: Blanka, sans-serif;
+ width: 100%;
+ display: block;
+}
+
+.center {
+ margin: auto;
+ width: 50%;
+}
+
+/*
+ * Unittests page
+ */
+.text-green {
+ color: #5d8000;
+}
+
+.text-red {
+ color: #ff0000;
+}
+
+.terminal-box {
+ background-color: #333;
+}
+
+.terminal-content {
+ color: #fff;
+ margin: 8px;
+ padding-top: 8px;
+}
+
+/*
+ * Adapt header to bigger screens
+ */
+@media only screen and (min-width: 530px) {
+ header {
+ height: 45px;
+ }
+
+ header h1.logo {
+ width: auto;
+ text-align: left;
+ }
+
+ .menu {
+ width: auto;
+ margin-top: 12px;
+ }
+ .container {
+ text-align: left;
+ }
+ .text-underline {
+ border-bottom: 1px solid #333;
+ }
+}
\ No newline at end of file
diff --git a/testapps/on_device_unit_tests/test_app/templates/base.html b/testapps/on_device_unit_tests/test_app/templates/base.html
new file mode 100644
index 000000000..50d3383e5
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/templates/base.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="{{ url_for('static', filename='flask.css') }}">
+ <title>
+ {% block title %}
+ Flask on {{ platform }}
+ {% endblock %}
+ </title>
+
+ </head>
+ <body>
+ <header>
+ <div class="container">
+ <h1 class="logo">Flask on {{ platform }}!</h1>
+ <strong>
+ <nav>
+ <ul class="menu">
+ <li><a href="{{ url_for('index') }}">Home</a></li>
+ <li><a href="{{ url_for('page2') }}">Another Page</a></li>
+ </ul>
+ </nav>
+ </strong>
+ </div>
+ </header>
+
+ <div class="container">
+ {% block body %}
+ {% endblock %}
+ </div>
+
+ </body>
+</html>
diff --git a/testapps/on_device_unit_tests/test_app/templates/index.html b/testapps/on_device_unit_tests/test_app/templates/index.html
new file mode 100644
index 000000000..9fc6e06f5
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/templates/index.html
@@ -0,0 +1,141 @@
+{% extends "base.html" %}
+
+
+{% block body %}
+
+<h2 class="page-title">Main Page</h2>
+
+<img src="static/colours.png" style="width:50%;margin-left:auto;margin-right:auto;display:block">
+
+<div>
+ <h3 class="text-underline">Perform unittests</h3>
+ <form>
+ <button formaction="/unittests">
+ Run unittests
+ </button>
+
+ </form>
+</div>
+
+<div>
+ <h3 class="text-underline">Test navigation</h3>
+ <form>
+ <button formaction="/page2" >Second page</button>
+ </form>
+</div>
+
+<div>
+ <h3 class="text-underline">Android tests</h3>
+
+ <div>
+ <button onClick="vibrate()">
+ vibrate for 1s with pyjnius
+ </button>
+
+ <script>
+ function vibrate() {
+ var request = new XMLHttpRequest();
+ request.open('GET', 'vibrate?time=1', true);
+ request.send();
+ }
+ </script>
+ </div>
+
+ <div>
+ <button onClick="loadUrl()">
+ open url
+ </button>
+ <input type="text" value="http://www.google.com" id="url_field"/>
+
+ <script>
+ function loadUrl() {
+ var request = new XMLHttpRequest();
+ var url = document.getElementById("url_field").value
+ request.open('GET', 'loadUrl?url=' + url, true);
+ request.send();
+ }
+ </script>
+ </div>
+
+ <div>
+ <button onClick="sensor()">
+ sensor orientation
+ </button>
+
+ <button onClick="vertical()">
+ vertical orientation
+ </button>
+
+ <button onClick="horizontal()">
+ horizontal orientation
+ </button>
+
+ <script>
+ function sensor() {
+ var request = new XMLHttpRequest();
+ request.open('GET', 'orientation?dir=sensor', true);
+ request.send();
+ }
+
+ function horizontal() {
+ var request = new XMLHttpRequest();
+ request.open('GET', 'orientation?dir=horizontal', true);
+ request.send();
+ }
+
+ function vertical() {
+ var request = new XMLHttpRequest();
+ request.open('GET', 'orientation?dir=vertical', true);
+ request.send();
+ }
+ </script>
+ </div>
+
+ <div>
+ <button onClick="start_service()">
+ start service
+ </button>
+
+ <button onClick="stop_service()">
+ stop service
+ </button>
+
+ <div id="service-status">
+ {{ 'Service started' if service_running else 'Service stopped' }}
+ </div>
+
+ <script>
+ function start_service() {
+ var request = new XMLHttpRequest();
+ request.onload = function() {
+ var elem = document.getElementById('service-status');
+ if (this.status >= 400)
+ elem.textContent = `Service start failed: ${this.statusText}`;
+ else
+ elem.textContent = 'Service started';
+ };
+ request.open('GET', 'service?action=start', true);
+ request.send();
+ }
+
+ function stop_service() {
+ var request = new XMLHttpRequest();
+ request.onload = function() {
+ var elem = document.getElementById('service-status');
+ if (this.status >= 400)
+ elem.textContent = `Service stop failed: ${this.statusText}`;
+ else
+ elem.textContent = 'Service stopped';
+ };
+ request.open('GET', 'service?action=stop', true);
+ request.send();
+ alert('Service stopped')
+ }
+ </script>
+ </div>
+
+ <br>
+ <br>
+</div>
+
+{% endblock %}
diff --git a/testapps/on_device_unit_tests/test_app/templates/page2.html b/testapps/on_device_unit_tests/test_app/templates/page2.html
new file mode 100644
index 000000000..f2fd122b7
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/templates/page2.html
@@ -0,0 +1,22 @@
+
+{% extends "base.html" %}
+
+
+{% block body %}
+
+<h2 class="page-title">Page two</h2>
+
+<img src="static/coloursinv.png" style="width:50%;margin-left:auto;margin-right:auto;display:block">
+
+<div class="center">
+ <br>
+ Yeah, it seems to work, I would suggest to go to:
+ <br>
+ <form>
+ <button formaction="/">First page</button>
+ </form>
+ <br>
+ ...far more interesting ;)
+</div>
+
+{% endblock %}
diff --git a/testapps/on_device_unit_tests/test_app/templates/unittests.html b/testapps/on_device_unit_tests/test_app/templates/unittests.html
new file mode 100644
index 000000000..95b23995e
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/templates/unittests.html
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+
+
+{% block body %}
+
+<h2 class="page-title">Unittests Page</h2>
+
+<br>
+Unittest recipes: {{ tested_recipes }}
+<br>
+
+<div class="terminal-box">
+ <div class="terminal-content">
+ {% for line in unittests_output %}
+ {{ line }}<br>
+ {% endfor %}
+ </div>
+</div>
+
+{% endblock %}
\ No newline at end of file
diff --git a/testapps/on_device_unit_tests/test_app/tests/__init__.py b/testapps/on_device_unit_tests/test_app/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/testapps/on_device_unit_tests/test_app/tests/mixin.py b/testapps/on_device_unit_tests/test_app/tests/mixin.py
new file mode 100644
index 000000000..af10dae69
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/tests/mixin.py
@@ -0,0 +1,30 @@
+import importlib
+
+
+class PythonTestMixIn(object):
+
+ module_import = None
+
+ def test_import_module(self):
+ """Test importing the specified Python module name. This import test
+ is common to all Python modules, it does not test any further
+ functionality.
+ """
+ self.assertIsNotNone(
+ self.module_import,
+ 'module_import is not set (was default None)')
+
+ importlib.import_module(self.module_import)
+
+ def test_run_module(self):
+ """Import the specified module and do something with it as a minimal
+ check that it actually works.
+
+ This test fails by default, it must be overridden by every
+ child test class.
+ """
+
+ self.fail('This test must be overridden by {}'.format(self))
+
+
+print('Defined test case')
diff --git a/testapps/on_device_unit_tests/test_app/tests/test_requirements.py b/testapps/on_device_unit_tests/test_app/tests/test_requirements.py
new file mode 100644
index 000000000..451f9fbf0
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/tests/test_requirements.py
@@ -0,0 +1,275 @@
+
+from unittest import TestCase
+from .mixin import PythonTestMixIn
+
+
+class NumpyTestCase(PythonTestMixIn, TestCase):
+ module_import = 'numpy'
+
+ def test_run_module(self):
+ import numpy as np
+
+ arr = np.random.random((3, 3))
+ det = np.linalg.det(arr)
+
+class ScipyTestCase(PythonTestMixIn, TestCase):
+ module_import = 'scipy'
+
+ def test_run_module(self):
+ import numpy as np
+ from scipy.cluster.vq import vq, kmeans, whiten
+ features = np.array([[ 1.9,2.3],
+ [ 1.5,2.5],
+ [ 0.8,0.6],
+ [ 0.4,1.8],
+ [ 0.1,0.1],
+ [ 0.2,1.8],
+ [ 2.0,0.5],
+ [ 0.3,1.5],
+ [ 1.0,1.0]])
+ whitened = whiten(features)
+ book = np.array((whitened[0],whitened[2]))
+ print('kmeans', kmeans(whitened,book))
+
+
+class OpensslTestCase(PythonTestMixIn, TestCase):
+ module_import = '_ssl'
+
+ def test_run_module(self):
+ import ssl
+
+ ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ ctx.options &= ~ssl.OP_NO_SSLv3
+
+
+class Sqlite3TestCase(PythonTestMixIn, TestCase):
+ module_import = 'sqlite3'
+
+ def test_run_module(self):
+ import sqlite3
+
+ conn = sqlite3.connect('example.db')
+ conn.cursor()
+
+
+class KivyTestCase(PythonTestMixIn, TestCase):
+ module_import = 'kivy'
+
+ def test_run_module(self):
+ # This import has side effects, if it works then it's an
+ # indication that Kivy is okay
+ from kivy.core.window import Window
+
+
+class PyjniusTestCase(PythonTestMixIn, TestCase):
+ module_import = 'jnius'
+
+ def test_run_module(self):
+ from jnius import autoclass
+
+ autoclass('org.kivy.android.PythonActivity')
+
+
+class LibffiTestCase(PythonTestMixIn, TestCase):
+ module_import = 'ctypes'
+
+ def test_run_module(self):
+ from os import environ
+ from ctypes import cdll
+
+ if "ANDROID_APP_PATH" in environ:
+ libc = cdll.LoadLibrary("libc.so")
+ else:
+ from ctypes.util import find_library
+ path_libc = find_library("c")
+ libc = cdll.LoadLibrary(path_libc)
+ libc.printf(b"%s\n", b"Using the C printf function from Python ... ")
+
+
+class RequestsTestCase(PythonTestMixIn, TestCase):
+ module_import = 'requests'
+
+ def test_run_module(self):
+ import requests
+
+ requests.get('https://kivy.org/')
+
+
+class PillowTestCase(PythonTestMixIn, TestCase):
+ module_import = 'PIL'
+
+ def test_run_module(self):
+ import os
+ from PIL import (
+ Image as PilImage,
+ ImageOps,
+ ImageFont,
+ ImageDraw,
+ ImageFilter,
+ ImageChops,
+ )
+
+ text_to_draw = "Kivy"
+ img_target = "pillow_text_draw.png"
+ image_width = 200
+ image_height = 100
+
+ img = PilImage.open("static/colours.png")
+ img = img.resize((image_width, image_height), PilImage.ANTIALIAS)
+ font = ImageFont.truetype("static/Blanka-Regular.otf", 55)
+
+ draw = ImageDraw.Draw(img)
+ for n in range(2, image_width, 2):
+ draw.rectangle(
+ (n, n, image_width - n, image_height - n), outline="black"
+ )
+ img.filter(ImageFilter.GaussianBlur(radius=1.5))
+
+ text_pos = (image_width / 2.0 - 55, 5)
+ halo = PilImage.new("RGBA", img.size, (0, 0, 0, 0))
+ ImageDraw.Draw(halo).text(
+ text_pos, text_to_draw, font=font, fill="black"
+ )
+ blurred_halo = halo.filter(ImageFilter.BLUR)
+ ImageDraw.Draw(blurred_halo).text(
+ text_pos, text_to_draw, font=font, fill="white"
+ )
+ img = PilImage.composite(
+ img, blurred_halo, ImageChops.invert(blurred_halo)
+ )
+
+ img.save(img_target, "PNG")
+ self.assertTrue(os.path.isfile(img_target))
+
+
+class MatplotlibTestCase(PythonTestMixIn, TestCase):
+ module_import = 'matplotlib'
+
+ def test_run_module(self):
+ import os
+ import numpy as np
+ from matplotlib import pyplot as plt
+
+ fig, ax = plt.subplots()
+ ax.set_xlabel('test xlabel')
+ ax.set_ylabel('test ylabel')
+ ax.plot(np.random.random(50))
+ ax.plot(np.sin(np.linspace(0, 3 * np.pi, 30)))
+
+ ax.legend(['random numbers', 'sin'])
+
+ fig.set_size_inches((5, 4))
+ fig.tight_layout()
+
+ fig.savefig('matplotlib_test.png', dpi=150)
+ self.assertTrue(os.path.isfile("matplotlib_test.png"))
+
+
+class CryptographyTestCase(PythonTestMixIn, TestCase):
+ module_import = 'cryptography'
+
+ def test_run_module(self):
+ from cryptography.fernet import Fernet
+
+ key = Fernet.generate_key()
+ f = Fernet(key)
+ cryptography_encrypted = f.encrypt(
+ b'A really secret message. Not for prying eyes.')
+ cryptography_decrypted = f.decrypt(cryptography_encrypted)
+
+
+class PycryptoTestCase(PythonTestMixIn, TestCase):
+ module_import = 'Crypto'
+
+ def test_run_module(self):
+ from Crypto.Hash import SHA256
+
+ crypto_hash_message = 'A secret message'
+ hash = SHA256.new()
+ hash.update(crypto_hash_message)
+ crypto_hash_hexdigest = hash.hexdigest()
+
+
+class PycryptodomeTestCase(PythonTestMixIn, TestCase):
+ module_import = 'Crypto'
+
+ def test_run_module(self):
+ import os
+ from Crypto.PublicKey import RSA
+
+ print('Ok imported pycryptodome, testing some basic operations...')
+ secret_code = "Unguessable"
+ key = RSA.generate(2048)
+ encrypted_key = key.export_key(passphrase=secret_code, pkcs=8,
+ protection="scryptAndAES128-CBC")
+ print('\t -> Testing key for secret code "Unguessable": {}'.format(
+ encrypted_key))
+
+ file_out = open("rsa_key.bin", "wb")
+ file_out.write(encrypted_key)
+ print('\t -> Testing key write: {}'.format(
+ 'ok' if os.path.exists("rsa_key.bin") else 'fail'))
+ self.assertTrue(os.path.exists("rsa_key.bin"))
+
+ print('\t -> Testing Public key:'.format(key.publickey().export_key()))
+
+
+class ScryptTestCase(PythonTestMixIn, TestCase):
+ module_import = 'scrypt'
+
+ def test_run_module(self):
+ import scrypt
+ h1 = scrypt.hash('password', 'random salt')
+ # The hash should be 64 bytes (default value)
+ self.assertEqual(64, len(h1))
+
+
+class M2CryptoTestCase(PythonTestMixIn, TestCase):
+ module_import = 'M2Crypto'
+
+ def test_run_module(self):
+ from M2Crypto import SSL
+ ctx = SSL.Context('sslv23')
+
+
+class Pysha3TestCase(PythonTestMixIn, TestCase):
+ module_import = 'sha3'
+
+ def test_run_module(self):
+ import sha3
+
+ print('Ok imported pysha3, testing some basic operations...')
+ k = sha3.keccak_512()
+ k.update(b"data")
+ print('Test pysha3 operation (keccak_512): {}'.format(k.hexdigest()))
+
+
+class LibtorrentTestCase(PythonTestMixIn, TestCase):
+ module_import = 'libtorrent'
+
+ def test_run_module(self):
+ import libtorrent as lt
+
+ print('Imported libtorrent version {}'.format(lt.version))
+
+
+class Pyside6TestCase(PythonTestMixIn, TestCase):
+ module_import = 'PySide6'
+
+ def test_run_module(self):
+ import PySide6
+ from PySide6.QtCore import QDateTime
+ from PySide6 import QtWidgets
+
+ print(f"Imported PySide6 version {PySide6.__version__}")
+ print(f"Current date and time obtained from PySide6 : {QDateTime.currentDateTime().toString()}")
+
+
+class Shiboken6TestCase(PythonTestMixIn, TestCase):
+ module_import = 'shiboken6'
+
+ def test_run_module(self):
+ import shiboken6
+ from shiboken6 import Shiboken
+
+ print('Imported shiboken6 version {}'.format(shiboken6.__version__))
diff --git a/testapps/on_device_unit_tests/test_app/tools.py b/testapps/on_device_unit_tests/test_app/tools.py
new file mode 100644
index 000000000..398919d24
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/tools.py
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+
+import glob
+import unittest
+
+try:
+ # python2 case
+ from StringIO import StringIO
+except ImportError:
+ # python3 case
+ from io import StringIO
+from os.path import abspath, split, join
+
+from constants import RUNNING_ON_ANDROID
+
+APP_PATH = split(abspath(__file__))[0]
+
+
+def run_test_suites_into_buffer(suites):
+ """Run a suite of unittests but into a buffer so we can read the result."""
+ terminal_output = StringIO()
+ unittest.TextTestRunner(stream=terminal_output).run(suites)
+ return terminal_output.getvalue()
+
+
+def get_images_with_extension(path=APP_PATH, extension='*.png'):
+ """
+ Return a list of image files given a path and an file extension.
+
+ .. note:: those image files are supposed to be created by our unittests
+ inside the app's root folder.
+ """
+ return glob.glob(join(path, extension))
+
+
+def load_kv_from(kv_name):
+ """
+ Load a kivy's kv file givel a kv filename.
+
+ .. note:: requires `.kv` extension.
+ """
+ from kivy.lang import Builder
+
+ kv_file = join(APP_PATH, kv_name)
+ return Builder.load_file(kv_file)
+
+
+def raise_error(error):
+ """
+ A function to notify an error without raising an exception.
+
+ .. warning:: we will try to notify via an kivy's Popup, but if kivy is not
+ installed, it will only print an error message.
+ """
+ try:
+ from widgets import ErrorPopup
+ except ImportError:
+ print('raise_error:', error)
+ return
+ ErrorPopup(error_text=error).open()
+
+
+def get_failed_unittests_from(unittests_output, set_of_tests):
+ """Parse unittests output trying to find the failed tests"""
+ failed_tests = set()
+ for test in set_of_tests:
+ if test in unittests_output:
+ failed_tests.add(test)
+ return failed_tests
+
+
+def skip_if_not_running_from_android_device(func):
+ """
+ Skip run of the function in case that we are running the app form android.
+
+ .. note:: this is useful for some kind of tests that are supposed to be run
+ from an android device and relies on `pyjnius`.
+ """
+
+ def wrapper(*arg, **kwarg):
+ if RUNNING_ON_ANDROID:
+ return func(*arg, **kwarg)
+ raise_error(
+ 'Function `{func_name}` only available for android devices'.format(
+ func_name=func.__name__,
+ ),
+ )
+ return None
+
+ return wrapper
+
+
+@skip_if_not_running_from_android_device
+def get_android_python_activity():
+ """
+ Return the `PythonActivity.mActivity` using `pyjnius`.
+
+ .. warning:: This function will only be ran if executed from android"""
+ from jnius import autoclass
+
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+ return PythonActivity.mActivity
+
+
+@skip_if_not_running_from_android_device
+def vibrate_with_pyjnius(time=1000):
+ """
+ Vibrate an android device using `pyjnius`.
+
+ .. warning:: This function will only be ran if executed from android."""
+ from jnius import autoclass, cast
+
+ print('Attempting to vibrate with pyjnius')
+ ANDROID_VERSION = autoclass('android.os.Build$VERSION')
+ SDK_INT = ANDROID_VERSION.SDK_INT
+
+ Context = autoclass("android.content.Context")
+ activity = get_android_python_activity()
+
+ vibrator_service = activity.getSystemService(Context.VIBRATOR_SERVICE)
+ vibrator = cast("android.os.Vibrator", vibrator_service)
+
+ if vibrator and SDK_INT >= 26:
+ print("Using android's `VibrationEffect` (SDK >= 26)")
+ VibrationEffect = autoclass("android.os.VibrationEffect")
+ vibrator.vibrate(
+ VibrationEffect.createOneShot(
+ time, VibrationEffect.DEFAULT_AMPLITUDE,
+ ),
+ )
+ elif vibrator:
+ print("Using deprecated android's vibrate (SDK < 26)")
+ vibrator.vibrate(time)
+ else:
+ print('Something happened...vibrator service disabled?')
+
+
+@skip_if_not_running_from_android_device
+def set_device_orientation(direction):
+ """
+ Modifies the app orientation for an android device.
+
+ .. warning:: This function will only be ran if executed from android."""
+ if direction not in ('sensor', 'horizontal', 'vertical'):
+ print(
+ 'ERROR: asked to orient to `{direction}`, but we only support: '
+ 'sensor, horizontal or vertical'.format(direction=direction)
+ )
+ from jnius import autoclass
+
+ activity = get_android_python_activity()
+ ActivityInfo = autoclass('android.content.pm.ActivityInfo')
+
+ if direction == 'sensor':
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
+ elif direction == 'horizontal':
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+ else:
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+
+
+@skip_if_not_running_from_android_device
+def setup_lifecycle_callbacks():
+ """
+ Register example ActivityLifecycleCallbacks
+ """
+ from android.activity import register_activity_lifecycle_callbacks
+
+ register_activity_lifecycle_callbacks(
+ onActivityStarted=lambda activity: print('onActivityStarted'),
+ onActivityPaused=lambda activity: print('onActivityPaused'),
+ onActivityResumed=lambda activity: print('onActivityResumed'),
+ onActivityStopped=lambda activity: print('onActivityStopped'),
+ onActivityDestroyed=lambda activity: print('onActivityDestroyed'),
+ )
diff --git a/testapps/on_device_unit_tests/test_app/widgets.kv b/testapps/on_device_unit_tests/test_app/widgets.kv
new file mode 100644
index 000000000..997e81000
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/widgets.kv
@@ -0,0 +1,84 @@
+#:import get_color_from_hex kivy.utils.get_color_from_hex
+#:import FONT_SIZE_SUBTITLE constants.FONT_SIZE_SUBTITLE
+
+<Spacer20@Widget>:
+ size_hint_y: None
+ height: dp(20)
+
+
+<TestImage@BoxLayout>:
+ text: ''
+ source: ''
+ size_hint_y: None
+ height: self.minimum_height
+ orientation: 'vertical'
+ canvas.before:
+ Color:
+ rgba: 1, 0, 1, .25
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ Spacer20:
+ Label:
+ text: root.text
+ size_hint_y: None
+ height: dp(60)
+ font_size: sp(FONT_SIZE_SUBTITLE)
+ canvas.before:
+ Color:
+ rgba: 0, 0, 0, .65
+ Rectangle:
+ pos: self.x + 20, self.y
+ size: self.width - 40, self.height
+ BoxLayout:
+ size_hint_y: None
+ height: self.minimum_height
+ orientation: 'vertical'
+ canvas.before:
+ Color:
+ rgba: 0, 0, 0, .35
+ Rectangle:
+ pos: self.x + 20, self.y
+ size: self.width - 40, self.height
+ Spacer20:
+ Image:
+ source: root.source
+ allow_stretch: True
+ size_hint_y: None
+ height: dp(120)
+ Spacer20:
+ Spacer20:
+
+
+<CircularButton>:
+ size_hint: None, None
+ size: dp(120), dp(120)
+ text: ''
+ background_color: None
+ canvas.before:
+ Color:
+ rgba: .34, .34, .34, 1
+ Ellipse:
+ pos: self.pos
+ size: self.size
+ canvas:
+ Color:
+ rgba:
+ root.background_color \
+ if root.background_color \
+ else (1., 0., 1., .65) # purple
+ Ellipse:
+ pos: self.x + dp(2), self.y + dp(2)
+ size: self.width - dp(4), self.height - dp(4)
+ Label:
+ text: root.text
+ pos: root.pos
+ size_hint: None, None
+ size: root.size
+
+
+<ErrorPopup@Popup>:
+ title: 'Error'
+ size_hint: 0.75, 0.75
+ Label:
+ text: root.error_text
\ No newline at end of file
diff --git a/testapps/on_device_unit_tests/test_app/widgets.py b/testapps/on_device_unit_tests/test_app/widgets.py
new file mode 100644
index 000000000..eab93405a
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/widgets.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from kivy.properties import StringProperty
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.popup import Popup
+from kivy.uix.behaviors.button import ButtonBehavior
+from kivy.uix.widget import Widget
+from kivy.vector import Vector
+from tools import load_kv_from
+
+load_kv_from('widgets.kv')
+
+
+class Spacer20(Widget):
+ pass
+
+
+class TestImage(BoxLayout):
+ text = StringProperty()
+ source = StringProperty()
+
+
+class CircularButton(ButtonBehavior, Widget):
+ def collide_point(self, x, y):
+ return Vector(x, y).distance(self.center) <= self.width / 2
+
+
+class ErrorPopup(Popup):
+ error_text = StringProperty('')
diff --git a/testapps/on_device_unit_tests/test_qt/jar/PySide6/jar/Qt6Android.jar b/testapps/on_device_unit_tests/test_qt/jar/PySide6/jar/Qt6Android.jar
new file mode 100644
index 000000000..c09f18fa6
Binary files /dev/null and b/testapps/on_device_unit_tests/test_qt/jar/PySide6/jar/Qt6Android.jar differ
diff --git a/testapps/on_device_unit_tests/test_qt/jar/PySide6/jar/Qt6AndroidBindings.jar b/testapps/on_device_unit_tests/test_qt/jar/PySide6/jar/Qt6AndroidBindings.jar
new file mode 100644
index 000000000..d656c34d7
Binary files /dev/null and b/testapps/on_device_unit_tests/test_qt/jar/PySide6/jar/Qt6AndroidBindings.jar differ
diff --git a/testapps/on_device_unit_tests/test_qt/recipes/PySide6/__init__.py b/testapps/on_device_unit_tests/test_qt/recipes/PySide6/__init__.py
new file mode 100644
index 000000000..6c795746e
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_qt/recipes/PySide6/__init__.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import shutil
+import zipfile
+from os.path import join
+from pathlib import Path
+
+from pythonforandroid.logger import info
+from pythonforandroid.recipe import PythonRecipe
+
+
+class PySideRecipe(PythonRecipe):
+ version = '6.6.0a1'
+ # This will download the aarch64 wheel from the Qt servers.
+ # This wheel is only for testing purposes. This test will be update when PySide releases
+ # official PySide6 Android wheels.
+ url = ("https://download.qt.io/snapshots/ci/pyside/test/Android/aarch64/"
+ "PySide6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl")
+ wheel_name = 'PySide6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl'
+ depends = ["shiboken6"]
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = False
+
+ def build_arch(self, arch):
+ """Unzip the wheel and copy into site-packages of target"""
+
+ self.wheel_path = join(self.ctx.packages_path, self.name, self.wheel_name)
+ info("Copying libc++_shared.so from SDK to be loaded on startup")
+ libcpp_path = f"{self.ctx.ndk.sysroot_lib_dir}/{arch.command_prefix}/libc++_shared.so"
+ shutil.copyfile(libcpp_path, Path(self.ctx.get_libs_dir(arch.arch)) / "libc++_shared.so")
+
+ info(f"Installing {self.name} into site-packages")
+ with zipfile.ZipFile(self.wheel_path, "r") as zip_ref:
+ info("Unzip wheels and copy into {}".format(self.ctx.get_python_install_dir(arch.arch)))
+ zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))
+
+ lib_dir = Path(f"{self.ctx.get_python_install_dir(arch.arch)}/PySide6/Qt/lib")
+
+ info("Copying Qt libraries to be loaded on startup")
+ shutil.copytree(lib_dir, self.ctx.get_libs_dir(arch.arch), dirs_exist_ok=True)
+ shutil.copyfile(lib_dir.parent.parent / "libpyside6.abi3.so",
+ Path(self.ctx.get_libs_dir(arch.arch)) / "libpyside6.abi3.so")
+
+ shutil.copyfile(lib_dir.parent.parent / "QtCore.abi3.so",
+ Path(self.ctx.get_libs_dir(arch.arch)) / "QtCore.abi3.so")
+
+ shutil.copyfile(lib_dir.parent.parent / "QtWidgets.abi3.so",
+ Path(self.ctx.get_libs_dir(arch.arch)) / "QtWidgets.abi3.so")
+
+ shutil.copyfile(lib_dir.parent.parent / "QtGui.abi3.so",
+ Path(self.ctx.get_libs_dir(arch.arch)) / "QtGui.abi3.so")
+
+ plugin_path = (lib_dir.parent / "plugins" / "platforms" /
+ f"libplugins_platforms_qtforandroid_{arch.arch}.so")
+
+ if plugin_path.exists():
+ shutil.copyfile(plugin_path,
+ (Path(self.ctx.get_libs_dir(arch.arch)) /
+ f"libplugins_platforms_qtforandroid_{arch.arch}.so"))
+
+
+recipe = PySideRecipe()
diff --git a/testapps/on_device_unit_tests/test_qt/recipes/shiboken6/__init__.py b/testapps/on_device_unit_tests/test_qt/recipes/shiboken6/__init__.py
new file mode 100644
index 000000000..71feb18d2
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_qt/recipes/shiboken6/__init__.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import shutil
+import zipfile
+from os.path import join
+from pathlib import Path
+
+from pythonforandroid.logger import info
+from pythonforandroid.recipe import PythonRecipe
+
+
+class ShibokenRecipe(PythonRecipe):
+ version = '6.6.0a1'
+ # This will download the aarch64 wheel from the Qt servers.
+ # This wheel is only for testing purposes. This test will be update when PySide releases
+ # official shiboken6 Android wheels.
+ url = ("https://download.qt.io/snapshots/ci/pyside/test/Android/aarch64/"
+ "shiboken6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl")
+ wheel_name = 'shiboken6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl'
+
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = False
+
+ def build_arch(self, arch):
+ ''' Unzip the wheel and copy into site-packages of target'''
+
+ self.wheel_path = join(self.ctx.packages_path, self.name, self.wheel_name)
+ info('Installing {} into site-packages'.format(self.name))
+ with zipfile.ZipFile(self.wheel_path, 'r') as zip_ref:
+ info('Unzip wheels and copy into {}'.format(self.ctx.get_python_install_dir(arch.arch)))
+ zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))
+
+ lib_dir = Path(f"{self.ctx.get_python_install_dir(arch.arch)}/shiboken6")
+ shutil.copyfile(lib_dir / "libshiboken6.abi3.so",
+ Path(self.ctx.get_libs_dir(arch.arch)) / "libshiboken6.abi3.so")
+
+
+recipe = ShibokenRecipe()
diff --git a/testapps/setup_keyboard.py b/testapps/setup_keyboard.py
deleted file mode 100644
index 38fa78726..000000000
--- a/testapps/setup_keyboard.py
+++ /dev/null
@@ -1,30 +0,0 @@
-
-from distutils.core import setup
-from setuptools import find_packages
-
-options = {'apk': {'debug': None,
- 'requirements': 'sdl2,pyjnius,kivy,python2',
- 'android-api': 19,
- 'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
- 'dist-name': 'bdisttest',
- 'ndk-version': '10.3.2',
- 'permission': 'VIBRATE',
- }}
-
-package_data = {'': ['*.py',
- '*.png']
- }
-
-packages = find_packages()
-print('packages are', packages)
-
-setup(
- name='testapp_keyboard',
- version='1.1',
- description='p4a setup.py test',
- author='Alexander Taylor',
- author_email='alexanderjohntaylor@gmail.com',
- packages=find_packages(),
- options=options,
- package_data={'setup_keyboard': ['*.py', '*.png']}
-)
diff --git a/testapps/setup_pygame.py b/testapps/setup_pygame.py
deleted file mode 100644
index 3f96942b2..000000000
--- a/testapps/setup_pygame.py
+++ /dev/null
@@ -1,31 +0,0 @@
-
-from distutils.core import setup
-from setuptools import find_packages
-
-options = {'apk': {'debug': None,
- 'requirements': 'pygame,pyjnius,kivy,python2,android',
- 'android-api': 19,
- 'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
- 'dist-name': 'bdisttest_pygame',
- 'orientation': 'portrait',
- 'ndk-version': '10.3.2',
- 'permission': 'VIBRATE',
- }}
-
-package_data = {'': ['*.py',
- '*.png']
- }
-
-packages = find_packages()
-print('packages are', packages)
-
-setup(
- name='testapp_setup_pygame',
- version='1.1',
- description='p4a setup.py test with pygame',
- author='Alexander Taylor',
- author_email='alexanderjohntaylor@gmail.com',
- packages=find_packages(),
- options=options,
- package_data={'testapp_pygame': ['*.py', '*.png']}
-)
diff --git a/testapps/setup_testapp_flask.py b/testapps/setup_testapp_flask.py
deleted file mode 100644
index 2b364bb0f..000000000
--- a/testapps/setup_testapp_flask.py
+++ /dev/null
@@ -1,35 +0,0 @@
-
-from distutils.core import setup
-from setuptools import find_packages
-
-options = {'apk': {'debug': None,
- 'requirements': 'python2,flask,pyjnius',
- 'android-api': 19,
- 'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
- 'dist-name': 'testapp_flask',
- 'ndk-version': '10.3.2',
- 'bootstrap': 'webview',
- 'permissions': ['INTERNET', 'VIBRATE'],
- 'arch': 'armeabi-v7a',
- 'window': None,
- }}
-
-package_data = {'': ['*.py',
- '*.png']
- }
-
-packages = find_packages()
-print('packages are', packages)
-
-setup(
- name='testapp_flask',
- version='1.0',
- description='p4a flask testapp',
- author='Alexander Taylor',
- author_email='alexanderjohntaylor@gmail.com',
- packages=find_packages(),
- options=options,
- package_data={'testapp_flask': ['*.py', '*.png'],
- 'testapp_flask/static': ['*.png', '*.css'],
- 'testapp_flask/templates': ['*.html']}
-)
diff --git a/testapps/setup_testapp_python2.py b/testapps/setup_testapp_python2.py
deleted file mode 100644
index b54186f42..000000000
--- a/testapps/setup_testapp_python2.py
+++ /dev/null
@@ -1,31 +0,0 @@
-
-from distutils.core import setup
-from setuptools import find_packages
-
-options = {'apk': {'debug': None,
- 'requirements': 'sdl2,pyjnius,kivy,python2',
- 'android-api': 19,
- 'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
- 'dist-name': 'bdisttest_python2',
- 'ndk-version': '10.3.2',
- 'permission': 'VIBRATE',
- 'window': None,
- }}
-
-package_data = {'': ['*.py',
- '*.png']
- }
-
-packages = find_packages()
-print('packages are', packages)
-
-setup(
- name='testapp_python2',
- version='1.1',
- description='p4a setup.py test',
- author='Alexander Taylor',
- author_email='alexanderjohntaylor@gmail.com',
- packages=find_packages(),
- options=options,
- package_data={'testapp': ['*.py', '*.png']}
-)
diff --git a/testapps/setup_testapp_python2_sqlite_openssl.py b/testapps/setup_testapp_python2_sqlite_openssl.py
deleted file mode 100644
index b6eec63c2..000000000
--- a/testapps/setup_testapp_python2_sqlite_openssl.py
+++ /dev/null
@@ -1,29 +0,0 @@
-
-from distutils.core import setup
-from setuptools import find_packages
-
-options = {'apk': {#'debug': None,
- 'requirements': 'sdl2,pyjnius,kivy,python2,openssl,requests,peewee,sqlite3',
- 'android-api': 19,
- 'ndk-dir': '/home/sandy/android/crystax-ndk-10.3.2',
- 'dist-name': 'bdisttest_python2_sqlite_openssl',
- 'ndk-version': '10.3.2',
- 'permission': 'VIBRATE',
- 'permission': 'INTERNET',
- 'arch': 'armeabi-v7a',
- 'window': None,
- }}
-
-packages = find_packages()
-print('packages are', packages)
-
-setup(
- name='testapp_python2_sqlite_openssl',
- version='1.1',
- description='p4a setup.py test',
- author='Alexander Taylor',
- author_email='alexanderjohntaylor@gmail.com',
- packages=find_packages(),
- options=options,
- package_data={'testapp_sqlite_openssl': ['*.py', '*.png']}
-)
diff --git a/testapps/setup_testapp_python3.py b/testapps/setup_testapp_python3.py
deleted file mode 100644
index 530381d97..000000000
--- a/testapps/setup_testapp_python3.py
+++ /dev/null
@@ -1,31 +0,0 @@
-
-from distutils.core import setup
-from setuptools import find_packages
-
-options = {'apk': {'debug': None,
- 'requirements': 'sdl2,pyjnius,kivy,python3crystax',
- 'android-api': 19,
- 'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
- 'dist-name': 'bdisttest_python3',
- 'ndk-version': '10.3.2',
- 'arch': 'armeabi-v7a',
- 'permission': 'VIBRATE',
- }}
-
-package_data = {'': ['*.py',
- '*.png']
- }
-
-packages = find_packages()
-print('packages are', packages)
-
-setup(
- name='testapp_python3',
- version='1.1',
- description='p4a setup.py test',
- author='Alexander Taylor',
- author_email='alexanderjohntaylor@gmail.com',
- packages=find_packages(),
- options=options,
- package_data={'testapp': ['*.py', '*.png']}
-)
diff --git a/testapps/setup_testapp_python3_sqlite_openssl.py b/testapps/setup_testapp_python3_sqlite_openssl.py
new file mode 100644
index 000000000..c6360679f
--- /dev/null
+++ b/testapps/setup_testapp_python3_sqlite_openssl.py
@@ -0,0 +1,22 @@
+from setuptools import setup, find_packages
+
+options = {'apk': {'requirements': 'requests,peewee,sdl2,pyjnius,kivy,python3',
+ 'android-api': 27,
+ 'ndk-api': 21,
+ 'bootstrap': 'sdl2',
+ 'dist-name': 'bdisttest_python3_sqlite_openssl_googlendk',
+ 'ndk-version': '10.3.2',
+ 'arch': 'armeabi-v7a',
+ 'permissions': ['INTERNET', 'VIBRATE'],
+ }}
+
+setup(
+ name='testapp_python3_sqlite_openssl_googlendk',
+ version='1.1',
+ description='p4a setup.py test',
+ author='Alexander Taylor',
+ author_email='alexanderjohntaylor@gmail.com',
+ packages=find_packages(),
+ options=options,
+ package_data={'testapp_sqlite_openssl': ['*.py', '*.png']}
+)
diff --git a/testapps/setup_vispy.py b/testapps/setup_vispy.py
index 99e2a5563..f59d057b0 100644
--- a/testapps/setup_vispy.py
+++ b/testapps/setup_vispy.py
@@ -1,11 +1,12 @@
-
-from distutils.core import setup
-from setuptools import find_packages
+from setuptools import setup, find_packages
options = {'apk': {'debug': None,
- 'requirements': 'vispy',
- 'android-api': 19,
- 'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
+ 'requirements': 'python3,vispy',
+ 'blacklist-requirements': 'openssl,sqlite3',
+ 'android-api': 27,
+ 'ndk-api': 21,
+ 'bootstrap': 'empty',
+ 'ndk-dir': '/home/asandy/android/android-ndk-r17c',
'dist-name': 'bdisttest',
'ndk-version': '10.3.2',
'permission': 'VIBRATE',
diff --git a/testapps/testapp/main.py b/testapps/testapp/main.py
deleted file mode 100644
index c235d6ed3..000000000
--- a/testapps/testapp/main.py
+++ /dev/null
@@ -1,154 +0,0 @@
-print('main.py was successfully called')
-
-import os
-print('imported os')
-
-
-print('this dir is', os.path.abspath(os.curdir))
-
-print('contents of this dir', os.listdir('./'))
-
-import sys
-print('pythonpath is', sys.path)
-
-import kivy
-print('imported kivy')
-print('file is', kivy.__file__)
-
-from kivy.app import App
-
-from kivy.lang import Builder
-from kivy.properties import StringProperty
-
-from kivy.uix.popup import Popup
-from kivy.clock import Clock
-
-print('Imported kivy')
-from kivy.utils import platform
-print('platform is', platform)
-
-
-kv = '''
-#:import Metrics kivy.metrics.Metrics
-#:import sys sys
-
-<FixedSizeButton@Button>:
- size_hint_y: None
- height: dp(60)
-
-
-ScrollView:
- GridLayout:
- cols: 1
- size_hint_y: None
- height: self.minimum_height
- FixedSizeButton:
- text: 'test pyjnius'
- on_press: app.test_pyjnius()
- Image:
- keep_ratio: False
- allow_stretch: True
- source: 'colours.png'
- size_hint_y: None
- height: dp(100)
- Label:
- height: self.texture_size[1]
- size_hint_y: None
- font_size: 100
- text_size: self.size[0], None
- markup: True
- text: '[b]Kivy[/b] on [b]SDL2[/b] on [b]Android[/b]!'
- halign: 'center'
- Label:
- height: self.texture_size[1]
- size_hint_y: None
- text_size: self.size[0], None
- markup: True
- text: sys.version
- halign: 'center'
- padding_y: dp(10)
- Widget:
- size_hint_y: None
- height: 20
- Label:
- height: self.texture_size[1]
- size_hint_y: None
- font_size: 50
- text_size: self.size[0], None
- markup: True
- text: 'dpi: {}\\ndensity: {}\\nfontscale: {}'.format(Metrics.dpi, Metrics.density, Metrics.fontscale)
- halign: 'center'
- FixedSizeButton:
- text: 'test ctypes'
- on_press: app.test_ctypes()
- FixedSizeButton:
- text: 'test numpy'
- on_press: app.test_numpy()
- Widget:
- size_hint_y: None
- height: 1000
- on_touch_down: print('touched at', args[-1].pos)
-
-<ErrorPopup>:
- title: 'Error'
- size_hint: 0.75, 0.75
- Label:
- text: root.error_text
-'''
-
-
-class ErrorPopup(Popup):
- error_text = StringProperty('')
-
-def raise_error(error):
- print('ERROR:', error)
- ErrorPopup(error_text=error).open()
-
-class TestApp(App):
- def build(self):
- root = Builder.load_string(kv)
- Clock.schedule_interval(self.print_something, 2)
- # Clock.schedule_interval(self.test_pyjnius, 5)
- print('testing metrics')
- from kivy.metrics import Metrics
- print('dpi is', Metrics.dpi)
- print('density is', Metrics.density)
- print('fontscale is', Metrics.fontscale)
- return root
-
- def print_something(self, *args):
- print('App print tick', Clock.get_boottime())
-
- def on_pause(self):
- return True
-
- def test_pyjnius(self, *args):
- try:
- from jnius import autoclass
- except ImportError:
- raise_error('Could not import pyjnius')
- return
-
- print('Attempting to vibrate with pyjnius')
- # PythonActivity = autoclass('org.renpy.android.PythonActivity')
- # activity = PythonActivity.mActivity
- PythonActivity = autoclass('org.kivy.android.PythonActivity')
- activity = PythonActivity.mActivity
- Intent = autoclass('android.content.Intent')
- Context = autoclass('android.content.Context')
- vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
-
- vibrator.vibrate(1000)
-
- def test_ctypes(self, *args):
- import ctypes
-
- def test_numpy(self, *args):
- import numpy
-
- print(numpy.zeros(5))
- print(numpy.arange(5))
- print(numpy.random.random((3, 3)))
-
-
-TestApp().run()
diff --git a/testapps/testapp_flask/main.py b/testapps/testapp_flask/main.py
deleted file mode 100644
index f9d0feff6..000000000
--- a/testapps/testapp_flask/main.py
+++ /dev/null
@@ -1,95 +0,0 @@
-print('main.py was successfully called')
-print('this is the new main.py')
-
-import os
-print('imported os')
-
-try:
- print('contents of ./lib/python2.7/site-packages/ etc.')
- print(os.listdir('./lib'))
- print(os.listdir('./lib/python2.7'))
- print(os.listdir('./lib/python2.7/site-packages'))
-except OSError:
- print('could not look in dirs')
- print('this is expected on desktop')
-
-import flask
-print('flask1???')
-
-print('contents of this dir', os.listdir('./'))
-
-import flask
-print('flask???')
-
-import sys
-print('pythonpath is', sys.path)
-
-
-from flask import Flask
-app = Flask(__name__)
-
-from flask import (Flask, url_for, render_template, request, redirect,
- flash)
-
-print('imported flask etc')
-print('importing pyjnius')
-
-from jnius import autoclass
-Context = autoclass('android.content.Context')
-PythonActivity = autoclass('org.kivy.android.PythonActivity')
-activity = PythonActivity.mActivity
-
-vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
-
-ActivityInfo = autoclass('android.content.pm.ActivityInfo')
-
-@app.route('/')
-def page1():
- return render_template('index.html')
-
-@app.route('/page2')
-def page2():
- return render_template('page2.html')
-
-@app.route('/vibrate')
-def vibrate():
- args = request.args
- if 'time' not in args:
- print('ERROR: asked to vibrate but without time argument')
- print('asked to vibrate', args['time'])
-
- vibrator.vibrate(float(args['time']) * 1000)
- print('vibrated')
-
-@app.route('/loadUrl')
-def loadUrl():
- args = request.args
- if 'url' not in args:
- print('ERROR: asked to open an url but without url argument')
- print('asked to open url', args['url'])
- activity.loadUrl(args['url'])
-
-@app.route('/orientation')
-def orientation():
- args = request.args
- if 'dir' not in args:
- print('ERROR: asked to orient but no dir specified')
- direction = args['dir']
- if direction not in ('horizontal', 'vertical'):
- print('ERROR: asked to orient to neither horizontal nor vertical')
-
- if direction == 'horizontal':
- activity.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
- else:
- activity.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
-
-
-from os import curdir
-from os.path import realpath
-print('curdir', realpath(curdir))
-if realpath(curdir).startswith('/data'):
- app.run(debug=False)
-else:
- app.run(debug=True)
diff --git a/testapps/testapp_flask/static/colours.png b/testapps/testapp_flask/static/colours.png
deleted file mode 100644
index 30b685e32..000000000
Binary files a/testapps/testapp_flask/static/colours.png and /dev/null differ
diff --git a/testapps/testapp_flask/templates/base.html b/testapps/testapp_flask/templates/base.html
deleted file mode 100644
index 63b488130..000000000
--- a/testapps/testapp_flask/templates/base.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<html>
- <head>
- <meta charset="utf-8">
- <link href="static/style.css" rel="stylesheet" type="text/css">
- <title>
- {% block title %}
- Flask on Android
- {% endblock %}
- </title>
-
- </head>
-
- <body>
-
- <h1>
- Flask on Android!
- </h1>
-
- {% block body %}
- {% endblock %}
-
- </body>
-
-</html>
diff --git a/testapps/testapp_flask/templates/index.html b/testapps/testapp_flask/templates/index.html
deleted file mode 100644
index 78c38b3ea..000000000
--- a/testapps/testapp_flask/templates/index.html
+++ /dev/null
@@ -1,63 +0,0 @@
-{% extends "base.html" %}
-
-
-{% block body %}
-
-<h2>Page one</h2>
-
-<img src="static/colours.png" style="width:50%;margin-left:auto;margin-right:auto;display:block">
-
-<form>
- <button formaction="/page2" >Second page</button>
-</form>
-
-
-<button onClick="vibrate()">
- vibrate for 1s with pyjnius
-</button>
-
-<script>
- function vibrate() {
- var request = new XMLHttpRequest();
- request.open('GET', 'vibrate?time=1', true);
- request.send();
- }
-</script>
-
-<button onClick="loadUrl()">
- open url
-</button>
-<input type="text" value="http://www.google.com" id="url_field"/>
-
-<script>
- function loadUrl() {
- var request = new XMLHttpRequest();
- var url = document.getElementById("url_field").value
- request.open('GET', 'loadUrl?url=' + url, true);
- request.send();
- }
-</script>
-
-<button onClick="vertical()">
- vertical orientation
-</button>
-
-<button onClick="horizontal()">
- horizontal orientation
-</button>
-
-<script>
- function horizontal() {
- var request = new XMLHttpRequest();
- request.open('GET', 'orientation?dir=horizontal', true);
- request.send();
- }
-
- function vertical() {
- var request = new XMLHttpRequest();
- request.open('GET', 'orientation?dir=vertical', true);
- request.send();
- }
-</script>
-
-{% endblock %}
diff --git a/testapps/testapp_flask/templates/page2.html b/testapps/testapp_flask/templates/page2.html
deleted file mode 100644
index 70fca15f0..000000000
--- a/testapps/testapp_flask/templates/page2.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-{% extends "base.html" %}
-
-
-{% block body %}
-
-<h2>Page two</h2>
-
-<img src="static/coloursinv.png" style="width:50%;margin-left:auto;margin-right:auto;display:block">
-
-<form>
- <button formaction="/" >First page</button>
-</form>
-
-{% endblock %}
diff --git a/testapps/testapp_keyboard/colours.png b/testapps/testapp_keyboard/colours.png
deleted file mode 100644
index 30b685e32..000000000
Binary files a/testapps/testapp_keyboard/colours.png and /dev/null differ
diff --git a/testapps/testapp_keyboard/main.py b/testapps/testapp_keyboard/main.py
deleted file mode 100644
index 03e66aa3e..000000000
--- a/testapps/testapp_keyboard/main.py
+++ /dev/null
@@ -1,144 +0,0 @@
-print('main.py was successfully called')
-
-import os
-print('imported os')
-
-from kivy import platform
-
-if platform == 'android':
- print('contents of ./lib/python2.7/site-packages/ etc.')
- print(os.listdir('./lib'))
- print(os.listdir('./lib/python2.7'))
- print(os.listdir('./lib/python2.7/site-packages'))
-
- print('this dir is', os.path.abspath(os.curdir))
-
- print('contents of this dir', os.listdir('./'))
-
- with open('./lib/python2.7/site-packages/kivy/app.pyo', 'rb') as fileh:
- print('app.pyo size is', len(fileh.read()))
-
-import sys
-print('pythonpath is', sys.path)
-
-import kivy
-print('imported kivy')
-print('file is', kivy.__file__)
-
-from kivy.app import App
-
-from kivy.lang import Builder
-from kivy.properties import StringProperty
-
-from kivy.uix.popup import Popup
-from kivy.clock import Clock
-
-print('Imported kivy')
-from kivy.utils import platform
-print('platform is', platform)
-
-
-kv = '''
-#:import Metrics kivy.metrics.Metrics
-#:import Window kivy.core.window.Window
-
-<FixedSizeButton@Button>:
- size_hint_y: None
- height: dp(60)
-
-
-BoxLayout:
- orientation: 'vertical'
- BoxLayout:
- size_hint_y: None
- height: dp(50)
- orientation: 'horizontal'
- Button:
- text: 'None'
- on_press: Window.softinput_mode = ''
- Button:
- text: 'pan'
- on_press: Window.softinput_mode = 'pan'
- Button:
- text: 'below_target'
- on_press: Window.softinput_mode = 'below_target'
- Button:
- text: 'resize'
- on_press: Window.softinput_mode = 'resize'
- Widget:
- Scatter:
- id: scatter
- size_hint: None, None
- size: dp(300), dp(80)
- on_parent: self.pos = (300, 100)
- BoxLayout:
- size: scatter.size
- orientation: 'horizontal'
- canvas:
- Color:
- rgba: 1, 0, 0, 1
- Rectangle:
- pos: 0, 0
- size: self.size
- Widget:
- size_hint_x: None
- width: dp(30)
- TextInput:
- text: 'type in me'
-'''
-
-
-class ErrorPopup(Popup):
- error_text = StringProperty('')
-
-def raise_error(error):
- print('ERROR:', error)
- ErrorPopup(error_text=error).open()
-
-class TestApp(App):
- def build(self):
- root = Builder.load_string(kv)
- Clock.schedule_interval(self.print_something, 2)
- # Clock.schedule_interval(self.test_pyjnius, 5)
- print('testing metrics')
- from kivy.metrics import Metrics
- print('dpi is', Metrics.dpi)
- print('density is', Metrics.density)
- print('fontscale is', Metrics.fontscale)
- return root
-
- def print_something(self, *args):
- print('App print tick', Clock.get_boottime())
-
- def on_pause(self):
- return True
-
- def test_pyjnius(self, *args):
- try:
- from jnius import autoclass
- except ImportError:
- raise_error('Could not import pyjnius')
- return
-
- print('Attempting to vibrate with pyjnius')
- # PythonActivity = autoclass('org.renpy.android.PythonActivity')
- # activity = PythonActivity.mActivity
- PythonActivity = autoclass('org.kivy.android.PythonActivity')
- activity = PythonActivity.mActivity
- Intent = autoclass('android.content.Intent')
- Context = autoclass('android.content.Context')
- vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
-
- vibrator.vibrate(1000)
-
- def test_ctypes(self, *args):
- import ctypes
-
- def test_numpy(self, *args):
- import numpy
-
- print(numpy.zeros(5))
- print(numpy.arange(5))
- print(numpy.random.random((3, 3)))
-
-TestApp().run()
diff --git a/testapps/testapp_nogui/main.py b/testapps/testapp_nogui/main.py
deleted file mode 100644
index bb7506fb4..000000000
--- a/testapps/testapp_nogui/main.py
+++ /dev/null
@@ -1,73 +0,0 @@
-
-from math import sqrt
-print('import math worked')
-
-import sys
-
-print('sys.path is', sys.path)
-
-for i in range(45, 50):
- print(i, sqrt(i))
-
-print('trying to import six')
-try:
- import six
-except ImportError:
- print('import failed')
-
-
-print('trying to import six again')
-try:
- import six
-except ImportError:
- print('import failed (again?)')
-print('import six worked!')
-
-print('Just printing stuff apparently worked, trying pyjnius')
-
-import jnius
-
-print('Importing jnius worked')
-
-print('trying to import stuff')
-
-try:
- from jnius import cast
-except ImportError:
- print('cast failed')
-
-try:
- from jnius import ensureclass
-except ImportError:
- print('ensureclass failed')
-
-try:
- from jnius import JavaClass
-except ImportError:
- print('JavaClass failed')
-
-try:
- from jnius import jnius
-except ImportError:
- print('jnius failed')
-
-try:
- from jnius import reflect
-except ImportError:
- print('reflect failed')
-
-try:
- from jnius import find_javaclass
-except ImportError:
- print('find_javaclass failed')
-
-print('Trying to autoclass activity')
-
-from jnius import autoclass
-
-print('Imported autoclass')
-
-PythonActivity = autoclass('org.kivy.android.PythonActivity')
-
-print(':o the autoclass worked!')
-
diff --git a/testapps/testapp_sqlite_openssl/main.py b/testapps/testapp_sqlite_openssl/main.py
index a8083ab96..9b75c755e 100644
--- a/testapps/testapp_sqlite_openssl/main.py
+++ b/testapps/testapp_sqlite_openssl/main.py
@@ -192,24 +192,37 @@ def on_pause(self):
def test_pyjnius(self, *args):
try:
- from jnius import autoclass
+ from jnius import autoclass, cast
except ImportError:
raise_error('Could not import pyjnius')
return
-
print('Attempting to vibrate with pyjnius')
- # PythonActivity = autoclass('org.renpy.android.PythonActivity')
- # activity = PythonActivity.mActivity
+ ANDROID_VERSION = autoclass('android.os.Build$VERSION')
+ SDK_INT = ANDROID_VERSION.SDK_INT
+
+ Context = autoclass("android.content.Context")
PythonActivity = autoclass('org.kivy.android.PythonActivity')
activity = PythonActivity.mActivity
- Intent = autoclass('android.content.Intent')
- Context = autoclass('android.content.Context')
- vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
- vibrator.vibrate(1000)
+ vibrator_service = activity.getSystemService(Context.VIBRATOR_SERVICE)
+ vibrator = cast("android.os.Vibrator", vibrator_service)
+
+ if vibrator and SDK_INT >= 26:
+ print("Using android's `VibrationEffect` (SDK >= 26)")
+ VibrationEffect = autoclass("android.os.VibrationEffect")
+ vibrator.vibrate(
+ VibrationEffect.createOneShot(
+ 1000, VibrationEffect.DEFAULT_AMPLITUDE,
+ ),
+ )
+ elif vibrator:
+ print("Using deprecated android's vibrate (SDK < 26)")
+ vibrator.vibrate(1000)
+ else:
+ print('Something happened...vibrator service disabled?')
def test_ctypes(self, *args):
- import ctypes
+ pass
def test_numpy(self, *args):
import numpy
diff --git a/testapps/testlauncher_setup/pygame.py b/testapps/testlauncher_setup/pygame.py
deleted file mode 100644
index e2d32449f..000000000
--- a/testapps/testlauncher_setup/pygame.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from distutils.core import setup
-from setuptools import find_packages
-
-options = {'apk': {'debug': None,
- 'bootstrap': 'pygame',
- 'launcher': None,
- 'requirements': (
- 'python2,pygame,'
- 'sqlite3,docutils,pygments,kivy,pyjnius,plyer,'
- 'audiostream,cymunk,lxml,pil,' # ffmpeg, openssl
- 'twisted,numpy'), # pyopenssl
- 'android-api': 14,
- 'dist-name': 'launchertest_pygame',
- 'name': 'TestLauncher-pygame',
- 'package': 'org.kivy.testlauncher_pygame',
- 'permissions': [
- 'ACCESS_COARSE_LOCATION', 'ACCESS_FINE_LOCATION',
- 'BLUETOOTH', 'BODY_SENSORS', 'CAMERA', 'INTERNET',
- 'NFC', 'READ_EXTERNAL_STORAGE', 'RECORD_AUDIO',
- 'USE_FINGERPRINT', 'VIBRATE', 'WAKE_LOCK',
- 'WRITE_EXTERNAL_STORAGE']
- }}
-
-setup(
- name='testlauncher_pygame',
- version='1.0',
- description='p4a pygame.py apk',
- author='Peter Badida',
- options=options
-)
diff --git a/testapps/testlauncher_setup/sdl2.py b/testapps/testlauncher_setup/sdl2.py
index 9db55dc49..2e300277b 100644
--- a/testapps/testlauncher_setup/sdl2.py
+++ b/testapps/testlauncher_setup/sdl2.py
@@ -1,11 +1,10 @@
-from distutils.core import setup
-from setuptools import find_packages
+from setuptools import setup
options = {'apk': {'debug': None,
'bootstrap': 'sdl2',
'launcher': None,
'requirements': (
- 'python2,sdl2,android,'
+ 'python3,sdl2,android,'
'sqlite3,docutils,pygments,kivy,pyjnius,plyer,'
'cymunk,lxml,pil,openssl,pyopenssl,'
'twisted'), # audiostream, ffmpeg, numpy
diff --git a/testapps/testlauncherreboot_setup/sdl2.py b/testapps/testlauncherreboot_setup/sdl2.py
new file mode 100644
index 000000000..8ea0d43c4
--- /dev/null
+++ b/testapps/testlauncherreboot_setup/sdl2.py
@@ -0,0 +1,104 @@
+'''
+Clone Python implementation of Kivy Launcher from kivy/kivy-launcher repo,
+install deps specified in the OPTIONS['apk']['requirements'] and put it
+to a dist named OPTIONS['apk']['dist-name'].
+
+Tested with P4A Dockerfile at 5fc5241e01fbbc2b23b3749f53ab48f22239f4fc,
+kivy-launcher at ad5c5c6e886a310bf6dd187e992df972864d1148 on Windows 8.1
+with Docker for Windows and running on Samsung Galaxy Note 9, Android 8.1.
+
+docker run \
+ --interactive \
+ --tty \
+ -v "/c/Users/.../python-for-android/testapps":/home/user/testapps \
+ -v ".../python-for-android/pythonforandroid":/home/user/pythonforandroid \
+ p4a sh -c '\
+ . venv/bin/activate \
+ && cd testapps/testlauncherreboot_setup \
+ && python sdl2.py apk \
+ --sdk-dir $ANDROID_SDK_HOME \
+ --ndk-dir $ANDROID_NDK_HOME'
+'''
+
+# pylint: disable=import-error,no-name-in-module
+from subprocess import Popen
+from os import listdir
+from os.path import join, dirname, abspath, exists
+from pprint import pprint
+from setuptools import setup, find_packages
+
+ROOT = dirname(abspath(__file__))
+LAUNCHER = join(ROOT, 'launcherapp')
+
+if not exists(LAUNCHER):
+ PROC = Popen([
+ 'git', 'clone',
+ 'https://github.com/kivy/kivy-launcher',
+ LAUNCHER
+ ])
+ PROC.communicate()
+ assert PROC.returncode == 0, PROC.returncode
+
+ pprint(listdir(LAUNCHER))
+ pprint(listdir(ROOT))
+
+
+OPTIONS = {
+ 'apk': {
+ 'debug': None,
+ 'bootstrap': 'sdl2',
+ 'requirements': (
+ 'python3,sdl2,kivy,android,pyjnius,plyer'
+ ),
+ # 'sqlite3,docutils,pygments,'
+ # 'cymunk,lxml,pil,openssl,pyopenssl,'
+ # 'twisted,audiostream,ffmpeg,numpy'
+
+ 'android-api': 27,
+ 'ndk-api': 21,
+ 'dist-name': 'bdisttest_python3launcher_sdl2_googlendk',
+ 'name': 'TestLauncherPy3-sdl2',
+ 'package': 'org.kivy.testlauncherpy3_sdl2_googlendk',
+ 'ndk-version': '10.3.2',
+ 'arch': 'armeabi-v7a',
+ 'permissions': [
+ 'ACCESS_COARSE_LOCATION', 'ACCESS_FINE_LOCATION',
+ 'BLUETOOTH', 'BODY_SENSORS', 'CAMERA', 'INTERNET',
+ 'NFC', 'READ_EXTERNAL_STORAGE', 'RECORD_AUDIO',
+ 'USE_FINGERPRINT', 'VIBRATE', 'WAKE_LOCK',
+ 'WRITE_EXTERNAL_STORAGE'
+ ]
+ }
+}
+
+PACKAGE_DATA = {
+ 'launcherapp': [
+ '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',
+ ],
+ 'launcherapp/art': [
+ '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',
+ ],
+ 'launcherapp/art/fontello': [
+ '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',
+ ],
+ 'launcherapp/data': [
+ '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',
+ ],
+ 'launcherapp/launcher': [
+ '*.py', '*.png', '*.ttf', '*.eot', '*.svg', '*.woff',
+ ]
+}
+
+PACKAGES = find_packages()
+print('packages are', PACKAGES)
+
+setup(
+ name='testlauncherpy3_sdl2_googlendk',
+ version='1.0',
+ description='p4a sdl2.py apk',
+ author='Peter Badida',
+ author_email='keyweeusr@gmail.com',
+ packages=find_packages(),
+ options=OPTIONS,
+ package_data=PACKAGE_DATA
+)
diff --git a/tests/recipes/recipe_ctx.py b/tests/recipes/recipe_ctx.py
new file mode 100644
index 000000000..ed43bd6ff
--- /dev/null
+++ b/tests/recipes/recipe_ctx.py
@@ -0,0 +1,54 @@
+import os
+
+from pythonforandroid.bootstrap import Bootstrap
+from pythonforandroid.distribution import Distribution
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.build import Context
+from pythonforandroid.archs import ArchAarch_64
+from pythonforandroid.androidndk import AndroidNDK
+
+
+class RecipeCtx:
+ """
+ An base class for unit testing a recipe. This will create a context so we
+ can test any recipe using this context. Implement `setUp` and `tearDown`
+ methods used by unit testing.
+ """
+
+ ctx = None
+ arch = None
+ recipe = None
+
+ recipe_name = ""
+ "The name of the recipe to test."
+
+ recipes = []
+ """A List of recipes to pass to `Distribution.get_distribution`. Should
+ contain the target recipe to test as well as a python recipe."""
+ recipe_build_order = []
+ """A recipe_build_order which should take into account the recipe we want
+ to test as well as the possible dependent recipes"""
+
+ TEST_ARCH = 'arm64-v8a'
+
+ def setUp(self):
+ self.ctx = Context()
+ self.ctx.ndk_api = 21
+ self.ctx.android_api = 27
+ self.ctx._sdk_dir = "/opt/android/android-sdk"
+ self.ctx._ndk_dir = "/opt/android/android-ndk"
+ self.ctx.ndk = AndroidNDK(self.ctx._ndk_dir)
+ self.ctx.setup_dirs(os.getcwd())
+ self.ctx.bootstrap = Bootstrap().get_bootstrap("sdl2", self.ctx)
+ self.ctx.bootstrap.distribution = Distribution.get_distribution(
+ self.ctx, name="sdl2", recipes=self.recipes, archs=[self.TEST_ARCH],
+ )
+ self.ctx.recipe_build_order = self.recipe_build_order
+ self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx)
+ self.arch = ArchAarch_64(self.ctx)
+ self.ctx.ndk_sysroot = f'{self.ctx._ndk_dir}/sysroot'
+ self.ctx.ndk_include_dir = f'{self.ctx.ndk_sysroot}/usr/include'
+ self.recipe = Recipe.get_recipe(self.recipe_name, self.ctx)
+
+ def tearDown(self):
+ self.ctx = None
diff --git a/tests/recipes/recipe_lib_test.py b/tests/recipes/recipe_lib_test.py
new file mode 100644
index 000000000..1f57fe23c
--- /dev/null
+++ b/tests/recipes/recipe_lib_test.py
@@ -0,0 +1,144 @@
+from unittest import mock
+from platform import system
+from tests.recipes.recipe_ctx import RecipeCtx
+
+
+class BaseTestForMakeRecipe(RecipeCtx):
+ """
+ An unittest for testing any recipe using the standard build commands
+ (`configure/make`).
+
+ .. note:: Note that Some cmake recipe may need some more specific testing
+ ...but this should cover the basics.
+ """
+
+ recipe_name = None
+ expected_compiler = (
+ "{android_ndk}/toolchains/llvm/prebuilt/{system}-x86_64/bin/clang"
+ )
+
+ sh_command_calls = ["./configure"]
+ """The expected commands that the recipe runs via `sh.command`."""
+
+ extra_env_flags = {}
+ """
+ This must be a dictionary containing pairs of key (env var) and value.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.recipes = ["python3", "kivy", self.recipe_name]
+ self.recipe_build_order = [
+ "hostpython3", self.recipe_name, "python3", "sdl2", "kivy"
+ ]
+ print(f"We are testing recipe: {self.recipe_name}")
+
+ @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_get_recipe_env(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_check_recipe_choices,
+ ):
+ """
+ Test that get_recipe_env contains some expected arch flags and that
+ some internal methods has been called.
+ """
+ mock_shutil_which.return_value = self.expected_compiler.format(
+ android_ndk=self.ctx._ndk_dir, system=system().lower()
+ )
+ mock_check_recipe_choices.return_value = sorted(
+ self.ctx.recipe_build_order
+ )
+
+ # make sure the arch flags are in env
+ env = self.recipe.get_recipe_env(self.arch)
+ for flag in self.arch.arch_cflags:
+ self.assertIn(flag, env["CFLAGS"])
+ self.assertIn(
+ f"-target {self.arch.target}",
+ env["CFLAGS"],
+ )
+
+ for flag, value in self.extra_env_flags.items():
+ self.assertIn(value, env[flag])
+
+ # make sure that the mocked methods are actually called
+ mock_ensure_dir.assert_called()
+ mock_shutil_which.assert_called()
+ mock_check_recipe_choices.assert_called()
+
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ ):
+ mock_shutil_which.return_value = self.expected_compiler.format(
+ android_ndk=self.ctx._ndk_dir, system=system().lower()
+ )
+
+ # Since the following mocks are dynamic,
+ # we mock it inside a Context Manager
+ with mock.patch(
+ f"pythonforandroid.recipes.{self.recipe_name}.sh.Command"
+ ) as mock_sh_command, mock.patch(
+ f"pythonforandroid.recipes.{self.recipe_name}.sh.make"
+ ) as mock_make:
+ self.recipe.build_arch(self.arch)
+
+ # make sure that the mocked methods are actually called
+ for command in self.sh_command_calls:
+ self.assertIn(
+ mock.call(command),
+ mock_sh_command.mock_calls,
+ )
+ mock_make.assert_called()
+ mock_ensure_dir.assert_called()
+ mock_current_directory.assert_called()
+ mock_shutil_which.assert_called()
+
+
+class BaseTestForCmakeRecipe(BaseTestForMakeRecipe):
+ """
+ An unittest for testing any recipe using `cmake`. It inherits from
+ `BaseTestForMakeRecipe` but we override the build method to match the cmake
+ build method.
+
+ .. note:: Note that Some cmake recipe may need some more specific testing
+ ...but this should cover the basics.
+ """
+
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ ):
+ mock_shutil_which.return_value = self.expected_compiler.format(
+ android_ndk=self.ctx._ndk_dir, system=system().lower()
+ )
+
+ # Since the following mocks are dynamic,
+ # we mock it inside a Context Manager
+ with mock.patch(
+ f"pythonforandroid.recipes.{self.recipe_name}.sh.make"
+ ) as mock_make, mock.patch(
+ f"pythonforandroid.recipes.{self.recipe_name}.sh.cmake"
+ ) as mock_cmake:
+ self.recipe.build_arch(self.arch)
+
+ # make sure that the mocked methods are actually called
+ mock_cmake.assert_called()
+ mock_make.assert_called()
+ mock_ensure_dir.assert_called()
+ mock_current_directory.assert_called()
+ mock_shutil_which.assert_called()
diff --git a/tests/recipes/test_freetype.py b/tests/recipes/test_freetype.py
new file mode 100644
index 000000000..981ae631b
--- /dev/null
+++ b/tests/recipes/test_freetype.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestFreetypeRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.freetype`
+ """
+ recipe_name = "freetype"
+ sh_command_calls = ["./configure"]
diff --git a/tests/recipes/test_gevent.py b/tests/recipes/test_gevent.py
new file mode 100644
index 000000000..8c6601e25
--- /dev/null
+++ b/tests/recipes/test_gevent.py
@@ -0,0 +1,67 @@
+import unittest
+from unittest.mock import patch
+from tests.recipes.recipe_ctx import RecipeCtx
+
+
+class TestGeventRecipe(RecipeCtx, unittest.TestCase):
+
+ recipe_name = "gevent"
+
+ def test_get_recipe_env(self):
+ """
+ Makes sure `get_recipe_env()` sets compilation flags properly.
+ """
+ mocked_cflags = (
+ '-DANDROID -fomit-frame-pointer -D__ANDROID_API__=27 -mandroid '
+ '-isystem /path/to/isystem '
+ '-I/path/to/include1 '
+ '-isysroot /path/to/sysroot '
+ '-I/path/to/include2 '
+ '-march=armv7-a -mfloat-abi=softfp -mfpu=vfp -mthumb '
+ '-I/path/to/python3-libffi-openssl/include'
+ )
+ mocked_ldflags = (
+ ' --sysroot /path/to/sysroot '
+ '-lm '
+ '-L/path/to/library1 '
+ '-L/path/to/library2 '
+ '-lpython3.7m '
+ # checks the regex doesn't parse `python3-libffi-openssl` as a `-libffi`
+ '-L/path/to/python3-libffi-openssl/library3 '
+ )
+ mocked_ldlibs = ' -lm'
+ mocked_env = {
+ 'CFLAGS': mocked_cflags,
+ 'LDFLAGS': mocked_ldflags,
+ 'LDLIBS': mocked_ldlibs,
+ }
+ with patch('pythonforandroid.recipe.CythonRecipe.get_recipe_env') as m_get_recipe_env:
+ m_get_recipe_env.return_value = mocked_env
+ env = self.recipe.get_recipe_env()
+ expected_cflags = (
+ ' -fomit-frame-pointer -mandroid -isystem /path/to/isystem'
+ ' -isysroot /path/to/sysroot'
+ ' -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -mthumb'
+ )
+ expected_cppflags = (
+ '-DANDROID -D__ANDROID_API__=27 '
+ '-I/path/to/include1 '
+ '-I/path/to/include2 '
+ '-I/path/to/python3-libffi-openssl/include'
+ )
+ expected_ldflags = (
+ ' --sysroot /path/to/sysroot'
+ ' -L/path/to/library1'
+ ' -L/path/to/library2'
+ ' -L/path/to/python3-libffi-openssl/library3 '
+ )
+ expected_ldlibs = mocked_ldlibs
+ expected_libs = '-lm -lpython3.7m -lm'
+ expected_env = {
+ 'CFLAGS': expected_cflags,
+ 'CPPFLAGS': expected_cppflags,
+ 'LDFLAGS': expected_ldflags,
+ 'LDLIBS': expected_ldlibs,
+ 'LIBS': expected_libs,
+ }
+ self.assertEqual(expected_env, env)
diff --git a/tests/recipes/test_harfbuzz.py b/tests/recipes/test_harfbuzz.py
new file mode 100644
index 000000000..6006fb94c
--- /dev/null
+++ b/tests/recipes/test_harfbuzz.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestHarfbuzzRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.harfbuzz`
+ """
+ recipe_name = "harfbuzz"
+ sh_command_calls = ["./configure"]
diff --git a/tests/recipes/test_hostpython3.py b/tests/recipes/test_hostpython3.py
new file mode 100644
index 000000000..df0c5cac0
--- /dev/null
+++ b/tests/recipes/test_hostpython3.py
@@ -0,0 +1,164 @@
+import unittest
+
+from os.path import join
+from unittest import mock
+
+from pythonforandroid.recipes.hostpython3 import (
+ HOSTPYTHON_VERSION_UNSET_MESSAGE, SETUP_DIST_NOT_FIND_MESSAGE,
+)
+from pythonforandroid.util import BuildInterruptingException
+from tests.recipes.recipe_lib_test import RecipeCtx
+
+
+class TestHostPython3Recipe(RecipeCtx, unittest.TestCase):
+ """
+ TestCase for recipe :mod:`~pythonforandroid.recipes.hostpython3`
+ """
+ recipe_name = "hostpython3"
+
+ def test_property__exe_name_no_version(self):
+ hostpython_version = self.recipe.version
+ self.recipe._version = None
+ with self.assertRaises(BuildInterruptingException) as e:
+ py_exe = self.recipe._exe_name # noqa: F841
+ self.assertEqual(e.exception.args[0], HOSTPYTHON_VERSION_UNSET_MESSAGE)
+ # restore recipe's version or we will get failures with other test,
+ # since we share `self.recipe with all the tests of the class
+ self.recipe._version = hostpython_version
+
+ def test_property__exe_name(self):
+ self.assertEqual(self.recipe._exe_name, 'python3')
+
+ def test_property_python_exe(self):
+ self.assertEqual(
+ self.recipe.python_exe,
+ join(self.recipe.get_path_to_python(), 'python3')
+ )
+
+ @mock.patch("pythonforandroid.recipes.hostpython3.Path.exists")
+ def test_should_build(self, mock_exists):
+ # test case for existing python exe which shouldn't trigger the build
+ mock_exists.return_value = True
+ self.assertFalse(self.recipe.should_build(self.arch))
+ # test case for existing python exe which should trigger the build
+ mock_exists.return_value = False
+ self.assertTrue(self.recipe.should_build(self.arch))
+
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.util.makedirs")
+ def test_build_arch(self, mock_makedirs, mock_chdir):
+ """
+ Test case for
+ :meth:`~pythonforandroid.recipes.python3.HostPython3Recipe.build_arch`,
+ where we simulate the build for Python 3.8+.
+ """
+
+ with mock.patch(
+ "pythonforandroid.recipes.hostpython3.Path.exists"
+ ) as mock_path_exists, mock.patch(
+ "pythonforandroid.recipes.hostpython3.sh.Command"
+ ) as mock_sh_command, mock.patch(
+ "pythonforandroid.recipes.hostpython3.sh.make"
+ ) as mock_make, mock.patch(
+ "pythonforandroid.recipes.hostpython3.Path.is_file"
+ ) as mock_path_isfile, mock.patch(
+ "pythonforandroid.recipes.hostpython3.sh.cp"
+ ) as mock_sh_cp:
+ # here we simulate the expected behaviour for Python 3.8+
+ mock_path_exists.side_effect = [
+ False, # "config.status" not exists, so we trigger.configure
+ False, # "Modules/Setup.dist" shouldn't exist (3.8+ case)
+ True, # "Modules/Setup" exists, so we skip raise exception
+ ]
+ self.recipe.build_arch(self.arch)
+
+ # make sure that the mocked methods are actually called
+ mock_path_exists.assert_called()
+ recipe_src = self.recipe.get_build_dir(self.arch.arch)
+ self.assertIn(
+ mock.call(f"{recipe_src}/configure"),
+ mock_sh_command.mock_calls,
+ )
+ mock_make.assert_called()
+
+ exe = join(self.recipe.get_path_to_python(), 'python.exe')
+ mock_path_isfile.assert_called()
+
+ self.assertEqual(mock_sh_cp.call_count, 1)
+ mock_call_args, mock_call_kwargs = mock_sh_cp.call_args_list[0]
+ self.assertEqual(mock_call_args[0], exe)
+ self.assertEqual(mock_call_args[1], self.recipe.python_exe)
+ mock_makedirs.assert_called()
+ mock_chdir.assert_called()
+
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.util.makedirs")
+ def test_build_arch_python_lower_than_3_8(self, mock_makedirs, mock_chdir):
+ """
+ Test case for
+ :meth:`~pythonforandroid.recipes.python3.HostPython3Recipe.build_arch`,
+ where we simulate a Python 3.7 build. Here we copy an extra file:
+ - Modules/Setup.dist -> Modules/Setup.
+
+ .. note:: We omit some checks because we already dit that at
+ `test_build_arch`. Also we skip configure command for the
+ same reason.
+ """
+ with mock.patch(
+ "pythonforandroid.recipes.hostpython3.Path.exists"
+ ) as mock_path_exists, mock.patch(
+ "pythonforandroid.recipes.hostpython3.sh.make"
+ ) as mock_make, mock.patch(
+ "pythonforandroid.recipes.hostpython3.Path.is_file"
+ ) as mock_path_isfile, mock.patch(
+ "pythonforandroid.recipes.hostpython3.sh.cp"
+ ) as mock_sh_cp:
+ mock_path_exists.side_effect = [
+ True, # simulate that "config.status" exists to skip configure
+ True, # "Modules/Setup.dist" exist for Python 3.7
+ True, # "Modules/Setup" exists...we skip raise exception
+ ]
+ self.recipe.build_arch(self.arch)
+
+ build_dir = join(
+ self.recipe.get_build_dir(self.arch.arch), self.recipe.build_subdir
+ )
+ # we expect two calls to copy command, The one described below and the
+ # copy of the python binary which already chechet at `test_build_arch`.
+ self.assertEqual(mock_sh_cp.call_count, 2)
+ mock_call_args, mock_call_kwargs = mock_sh_cp.call_args_list[0]
+ self.assertEqual(mock_call_args[0], "Modules/Setup.dist")
+ self.assertEqual(mock_call_args[1], join(build_dir, "Modules/Setup"))
+
+ mock_path_exists.assert_called()
+ mock_make.assert_called()
+ mock_path_isfile.assert_called()
+ mock_makedirs.assert_called()
+ mock_chdir.assert_called()
+
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.util.makedirs")
+ def test_build_arch_setup_dist_exception(self, mock_makedirs, mock_chdir):
+ """
+ Test case for
+ :meth:`~pythonforandroid.recipes.python3.HostPython3Recipe.build_arch`,
+ where we simulate that the sources hasn't Setup.dist file, which should
+ raise an exception.
+
+ .. note:: We skip configure command because already tested at
+ `test_build_arch`.
+ """
+ with mock.patch(
+ "pythonforandroid.recipes.hostpython3.Path.exists"
+ ) as mock_path_exists:
+ mock_path_exists.side_effect = [
+ True, # simulate that "config.status" exists to skip configure
+ False, # "Modules/Setup.dist" shouldn't exist (3.8+ case)
+ False, # "Modules/Setup" doesn't exists...raise exception
+ ]
+
+ with self.assertRaises(BuildInterruptingException) as e:
+ self.recipe.build_arch(self.arch)
+ self.assertEqual(e.exception.args[0], SETUP_DIST_NOT_FIND_MESSAGE)
+ mock_makedirs.assert_called()
+ mock_chdir.assert_called()
diff --git a/tests/recipes/test_icu.py b/tests/recipes/test_icu.py
new file mode 100644
index 000000000..239b99e4c
--- /dev/null
+++ b/tests/recipes/test_icu.py
@@ -0,0 +1,117 @@
+import os
+import unittest
+from unittest import mock
+
+from tests.recipes.recipe_ctx import RecipeCtx
+from pythonforandroid.recipes.icu import ICURecipe
+
+
+class TestIcuRecipe(RecipeCtx, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.icu`
+ """
+
+ recipe_name = "icu"
+
+ def test_url(self):
+ self.assertTrue(self.recipe.versioned_url.startswith("http"))
+ self.assertIn(self.recipe.version.replace('.', '-'), self.recipe.versioned_url)
+
+ @mock.patch(
+ "pythonforandroid.recipe.Recipe.url", new_callable=mock.PropertyMock
+ )
+ def test_url_none(self, mock_url):
+ mock_url.return_value = None
+ self.assertIsNone(self.recipe.versioned_url)
+
+ def test_get_recipe_dir(self):
+ expected_dir = os.path.join(self.ctx.root_dir, "recipes", "icu")
+ self.assertEqual(self.recipe.get_recipe_dir(), expected_dir)
+
+ @mock.patch("pythonforandroid.util.makedirs")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.bootstrap.sh.Command")
+ @mock.patch("pythonforandroid.recipes.icu.sh.make")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_sh_make,
+ mock_sh_command,
+ mock_chdir,
+ mock_makedirs,
+ ):
+ mock_shutil_which.return_value = os.path.join(
+ self.ctx._ndk_dir,
+ f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang",
+ )
+ self.ctx.toolchain_version = "4.9"
+ self.recipe.build_arch(self.arch)
+
+ # We expect some calls to `sh.Command`
+ build_root = self.recipe.get_build_dir(self.arch.arch)
+ mock_sh_command.has_calls(
+ [
+ mock.call(
+ os.path.join(build_root, "source", "runConfigureICU")
+ ),
+ mock.call(os.path.join(build_root, "source", "configure")),
+ ]
+ )
+ mock_ensure_dir.assert_called()
+ mock_chdir.assert_called()
+ # we expect multiple calls to sh.make command
+ expected_host_cppflags = (
+ "-O3 -fno-short-wchar -DU_USING_ICU_NAMESPACE=1 -fno-short-enums "
+ "-DU_HAVE_NL_LANGINFO_CODESET=0 -D__STDC_INT64__ -DU_TIMEZONE=0 "
+ "-DUCONFIG_NO_LEGACY_CONVERSION=1 "
+ "-DUCONFIG_NO_TRANSLITERATION=0 "
+ )
+ for call_number, call in enumerate(mock_sh_make.call_args_list):
+ # here we expect to find the compile command `make -j`in first and
+ # third calls, the others should be the `make install` commands
+ is_host_build = call_number in [0, 1]
+ is_compile = call_number in [0, 2]
+ call_args, call_kwargs = call
+ self.assertTrue(
+ call_args[0].startswith("-j" if is_compile else "install")
+ )
+ self.assertIn("_env", call_kwargs)
+ if is_host_build:
+ self.assertIn(
+ expected_host_cppflags, call_kwargs["_env"]["CPPFLAGS"]
+ )
+ else:
+ self.assertNotIn(
+ expected_host_cppflags, call_kwargs["_env"]["CPPFLAGS"]
+ )
+ mock_makedirs.assert_called()
+
+ mock_shutil_which.assert_called_once()
+ self.assertEqual(
+ mock_shutil_which.call_args[0][0],
+ mock_shutil_which.return_value,
+ )
+
+ @mock.patch("pythonforandroid.recipes.icu.sh.cp")
+ @mock.patch("pythonforandroid.util.makedirs")
+ def test_install_libraries(self, mock_makedirs, mock_sh_cp):
+ self.recipe.install_libraries(self.arch)
+ mock_makedirs.assert_called()
+ mock_sh_cp.assert_called()
+
+ @mock.patch("pythonforandroid.recipes.icu.exists")
+ def test_get_recipe_dir_with_local_recipes(self, mock_exists):
+ self.ctx.local_recipes = "/home/user/p4a_local_recipes"
+
+ # we don't use `self.recipe` because, somehow, the modified variable
+ # above is not updated in the `ctx` and makes the test fail...
+ recipe = ICURecipe()
+ recipe.ctx = self.ctx
+ recipe_dir = recipe.get_recipe_dir()
+
+ expected_dir = os.path.join(self.ctx.local_recipes, "icu")
+ self.assertEqual(recipe_dir, expected_dir)
+ mock_exists.assert_called_once_with(expected_dir)
diff --git a/tests/recipes/test_jpeg.py b/tests/recipes/test_jpeg.py
new file mode 100644
index 000000000..2d4356206
--- /dev/null
+++ b/tests/recipes/test_jpeg.py
@@ -0,0 +1,9 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe
+
+
+class TestJpegRecipe(BaseTestForCmakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.jpeg`
+ """
+ recipe_name = "jpeg"
diff --git a/tests/recipes/test_leveldb.py b/tests/recipes/test_leveldb.py
new file mode 100644
index 000000000..f501398c6
--- /dev/null
+++ b/tests/recipes/test_leveldb.py
@@ -0,0 +1,9 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe
+
+
+class TestLeveldbRecipe(BaseTestForCmakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.leveldb`
+ """
+ recipe_name = "leveldb"
diff --git a/tests/recipes/test_libbz2.py b/tests/recipes/test_libbz2.py
new file mode 100644
index 000000000..e6f4324b7
--- /dev/null
+++ b/tests/recipes/test_libbz2.py
@@ -0,0 +1,33 @@
+import unittest
+
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibBz2Recipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """TestCase for recipe :mod:`~pythonforandroid.recipes.libbz2`."""
+ recipe_name = "libbz2"
+ sh_command_calls = []
+
+ def test_get_library_includes(self):
+ """
+ Test :meth:`~pythonforandroid.recipes.libbz2.get_library_includes`.
+ """
+ self.assertEqual(
+ self.recipe.get_library_includes(self.arch),
+ f" -I{self.recipe.get_build_dir(self.arch.arch)}",
+ )
+
+ def test_get_library_ldflags(self):
+ """
+ Test :meth:`~pythonforandroid.recipes.libbz2.get_library_ldflags`.
+ """
+ self.assertEqual(
+ self.recipe.get_library_ldflags(self.arch),
+ f" -L{self.recipe.get_build_dir(self.arch.arch)}",
+ )
+
+ def test_link_libs_flags(self):
+ """
+ Test :meth:`~pythonforandroid.recipes.libbz2.get_library_ldflags`.
+ """
+ self.assertEqual(self.recipe.get_library_libs_flag(), " -lbz2")
diff --git a/tests/recipes/test_libcurl.py b/tests/recipes/test_libcurl.py
new file mode 100644
index 000000000..d5e2fa45d
--- /dev/null
+++ b/tests/recipes/test_libcurl.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibcurlRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libcurl`
+ """
+ recipe_name = "libcurl"
+ sh_command_calls = ["./configure"]
diff --git a/tests/recipes/test_libexpat.py b/tests/recipes/test_libexpat.py
new file mode 100644
index 000000000..c9e0ed69f
--- /dev/null
+++ b/tests/recipes/test_libexpat.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibexpatRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libexpat`
+ """
+ recipe_name = "libexpat"
+ sh_command_calls = ["./buildconf.sh", "./configure"]
diff --git a/tests/recipes/test_libffi.py b/tests/recipes/test_libffi.py
new file mode 100644
index 000000000..68d4ce3db
--- /dev/null
+++ b/tests/recipes/test_libffi.py
@@ -0,0 +1,15 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibffiRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libffi`
+ """
+ recipe_name = "libffi"
+ sh_command_calls = ["./autogen.sh", "autoreconf", "./configure"]
+
+ def test_get_include_dirs(self):
+ list_of_includes = self.recipe.get_include_dirs(self.arch)
+ self.assertIsInstance(list_of_includes, list)
+ self.assertTrue(list_of_includes[0].endswith("include"))
diff --git a/tests/recipes/test_libgeos.py b/tests/recipes/test_libgeos.py
new file mode 100644
index 000000000..7a8b4258d
--- /dev/null
+++ b/tests/recipes/test_libgeos.py
@@ -0,0 +1,27 @@
+import unittest
+from unittest import mock
+from tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe
+
+
+class TestLibgeosRecipe(BaseTestForCmakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libgeos`
+ """
+ recipe_name = "libgeos"
+
+ @mock.patch("pythonforandroid.util.makedirs")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ mock_makedirs,
+ ):
+ # We overwrite the base test method because we
+ # want to avoid any file/directory creation
+ super().test_build_arch()
+ # make sure that the mocked methods are actually called
+ mock_makedirs.assert_called()
diff --git a/tests/recipes/test_libiconv.py b/tests/recipes/test_libiconv.py
new file mode 100644
index 000000000..d81649fd5
--- /dev/null
+++ b/tests/recipes/test_libiconv.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibiconvRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libiconv`
+ """
+ recipe_name = "libiconv"
+ sh_command_calls = ["./configure"]
diff --git a/tests/recipes/test_liblzma.py b/tests/recipes/test_liblzma.py
new file mode 100644
index 000000000..9f4f6ce0d
--- /dev/null
+++ b/tests/recipes/test_liblzma.py
@@ -0,0 +1,51 @@
+import unittest
+from os.path import join
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibLzmaRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """TestCase for recipe :mod:`~pythonforandroid.recipes.liblzma`."""
+ recipe_name = "liblzma"
+ sh_command_calls = ["./autogen.sh", "autoreconf", "./configure"]
+
+ def test_get_library_includes(self):
+ """
+ Test :meth:`~pythonforandroid.recipes.liblzma.get_library_includes`.
+ """
+ recipe_build_dir = self.recipe.get_build_dir(self.arch.arch)
+ self.assertEqual(
+ self.recipe.get_library_includes(self.arch),
+ f" -I{join(recipe_build_dir, 'p4a_install/include')}",
+ )
+
+ def test_get_library_ldflags(self):
+ """
+ Test :meth:`~pythonforandroid.recipes.liblzma.get_library_ldflags`.
+ """
+ recipe_build_dir = self.recipe.get_build_dir(self.arch.arch)
+ self.assertEqual(
+ self.recipe.get_library_ldflags(self.arch),
+ f" -L{join(recipe_build_dir, 'p4a_install/lib')}",
+ )
+
+ def test_link_libs_flags(self):
+ """
+ Test :meth:`~pythonforandroid.recipes.liblzma.get_library_libs_flag`.
+ """
+ self.assertEqual(self.recipe.get_library_libs_flag(), " -llzma")
+
+ def test_install_dir_not_named_install(self):
+ """
+ Tests that the install directory is not named ``install``.
+
+ liblzma already have a file named ``INSTALL`` in its source directory.
+ On case-insensitive filesystems, using a folder named ``install`` will
+ cause a conflict. (See issue: #2343).
+
+ WARNING: This test is quite flaky, but should be enough to
+ ensure that someone in the future will not accidentally rename
+ the install directory without seeing this test to fail.
+ """
+ liblzma_install_dir = self.recipe.built_libraries["liblzma.so"]
+
+ self.assertNotIn("install", liblzma_install_dir.split("/"))
diff --git a/tests/recipes/test_libmysqlclient.py b/tests/recipes/test_libmysqlclient.py
new file mode 100644
index 000000000..4c85dc92e
--- /dev/null
+++ b/tests/recipes/test_libmysqlclient.py
@@ -0,0 +1,30 @@
+import unittest
+from unittest import mock
+from tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe
+
+
+class TestLibmysqlclientRecipe(BaseTestForCmakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libmysqlclient`
+ """
+ recipe_name = "libmysqlclient"
+
+ @mock.patch("pythonforandroid.recipes.libmysqlclient.sh.rm")
+ @mock.patch("pythonforandroid.recipes.libmysqlclient.sh.cp")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ mock_sh_cp,
+ mock_sh_rm,
+ ):
+ # We overwrite the base test method because we need
+ # to mock a little more (`sh.cp` and rmdir)
+ super().test_build_arch()
+ # make sure that the mocked methods are actually called
+ mock_sh_cp.assert_called()
+ mock_sh_rm.assert_called()
diff --git a/tests/recipes/test_libogg.py b/tests/recipes/test_libogg.py
new file mode 100644
index 000000000..883cfa491
--- /dev/null
+++ b/tests/recipes/test_libogg.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLiboggRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libogg`
+ """
+ recipe_name = "libogg"
+ sh_command_calls = ["./configure"]
diff --git a/tests/recipes/test_libpq.py b/tests/recipes/test_libpq.py
new file mode 100644
index 000000000..5e2f9f3d6
--- /dev/null
+++ b/tests/recipes/test_libpq.py
@@ -0,0 +1,28 @@
+import unittest
+from unittest import mock
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibpqRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libpq`
+ """
+ recipe_name = "libpq"
+ sh_command_calls = ["./configure"]
+
+ @mock.patch("pythonforandroid.recipes.libpq.sh.cp")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ mock_sh_cp,
+ ):
+ # We overwrite the base test method because we need to mock a little
+ # more with this recipe (`sh.cp`)
+ super().test_build_arch()
+ # make sure that the mocked methods are actually called
+ mock_sh_cp.assert_called()
diff --git a/tests/recipes/test_libsecp256k1.py b/tests/recipes/test_libsecp256k1.py
new file mode 100644
index 000000000..983b0bf3d
--- /dev/null
+++ b/tests/recipes/test_libsecp256k1.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibsecp256k1Recipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libsecp256k1`
+ """
+ recipe_name = "libsecp256k1"
+ sh_command_calls = ["./autogen.sh", "./configure"]
diff --git a/tests/recipes/test_libshine.py b/tests/recipes/test_libshine.py
new file mode 100644
index 000000000..66f8ecb35
--- /dev/null
+++ b/tests/recipes/test_libshine.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibshineRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libshine`
+ """
+ recipe_name = "libshine"
+ sh_command_calls = ["./bootstrap", "./configure"]
diff --git a/tests/recipes/test_libvorbis.py b/tests/recipes/test_libvorbis.py
new file mode 100644
index 000000000..d8aed7b72
--- /dev/null
+++ b/tests/recipes/test_libvorbis.py
@@ -0,0 +1,29 @@
+import unittest
+from unittest import mock
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibvorbisRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libvorbis`
+ """
+ recipe_name = "libvorbis"
+ sh_command_calls = ["./configure"]
+ extra_env_flags = {'CFLAGS': 'libogg/include'}
+
+ @mock.patch("pythonforandroid.recipes.libvorbis.sh.cp")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ mock_sh_cp,
+ ):
+ # We overwrite the base test method because we need to mock a little
+ # more with this recipe (`sh.cp`)
+ super().test_build_arch()
+ # make sure that the mocked methods are actually called
+ mock_sh_cp.assert_called()
diff --git a/tests/recipes/test_libvpx.py b/tests/recipes/test_libvpx.py
new file mode 100644
index 000000000..ae6783f0e
--- /dev/null
+++ b/tests/recipes/test_libvpx.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibVPXRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libvpx`
+ """
+ recipe_name = "libvpx"
+ sh_command_calls = ["./configure"]
diff --git a/tests/recipes/test_libx264.py b/tests/recipes/test_libx264.py
new file mode 100644
index 000000000..d928b476a
--- /dev/null
+++ b/tests/recipes/test_libx264.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibx264Recipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libx264`
+ """
+ recipe_name = "libx264"
+ sh_command_calls = ["./configure"]
diff --git a/tests/recipes/test_libxml2.py b/tests/recipes/test_libxml2.py
new file mode 100644
index 000000000..d55909cc2
--- /dev/null
+++ b/tests/recipes/test_libxml2.py
@@ -0,0 +1,14 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibxml2Recipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libxml2`
+ """
+ recipe_name = "libxml2"
+ sh_command_calls = ["./autogen.sh", "autoreconf", "./configure"]
+ extra_env_flags = {
+ "CONFIG_SHELL": "/bin/bash",
+ "SHELL": "/bin/bash",
+ }
diff --git a/tests/recipes/test_libxslt.py b/tests/recipes/test_libxslt.py
new file mode 100644
index 000000000..24d656699
--- /dev/null
+++ b/tests/recipes/test_libxslt.py
@@ -0,0 +1,15 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestLibxsltRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.libxslt`
+ """
+ recipe_name = "libxslt"
+ sh_command_calls = ["./autogen.sh", "autoreconf", "./configure"]
+ extra_env_flags = {
+ "CONFIG_SHELL": "/bin/bash",
+ "SHELL": "/bin/bash",
+ "LIBS": "-lxml2 -lz -lm",
+ }
diff --git a/tests/recipes/test_openal.py b/tests/recipes/test_openal.py
new file mode 100644
index 000000000..a03d5cd27
--- /dev/null
+++ b/tests/recipes/test_openal.py
@@ -0,0 +1,56 @@
+import unittest
+from unittest import mock
+from tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe
+
+
+class TestOpenalRecipe(BaseTestForCmakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.openal`
+ """
+ recipe_name = "openal"
+
+ @mock.patch("pythonforandroid.recipes.openal.sh.cmake")
+ @mock.patch("pythonforandroid.recipes.openal.sh.make")
+ @mock.patch("pythonforandroid.recipes.openal.sh.cp")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_prebuild_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ mock_sh_cp,
+ mock_sh_make,
+ mock_sh_cmake,
+ ):
+ mock_shutil_which.return_value = (
+ "/opt/android/android-ndk/toolchains/"
+ "llvm/prebuilt/linux-x86_64/bin/clang"
+ )
+ self.recipe.build_arch(self.arch)
+
+ # make sure that the mocked methods are actually called
+ mock_ensure_dir.assert_called()
+ mock_current_directory.assert_called()
+ mock_shutil_which.assert_called()
+ mock_sh_cp.assert_called()
+ mock_sh_make.assert_called()
+ mock_sh_cmake.assert_called()
+
+ @mock.patch("pythonforandroid.recipes.openal.sh.cp")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ mock_sh_cp,
+ ):
+ # We overwrite the base test method because we need to mock a little
+ # more with this recipe.
+ super().test_build_arch()
+ # make sure that the mocked methods are actually called
+ mock_sh_cp.assert_called()
diff --git a/tests/recipes/test_openssl.py b/tests/recipes/test_openssl.py
new file mode 100644
index 000000000..f7ed362f6
--- /dev/null
+++ b/tests/recipes/test_openssl.py
@@ -0,0 +1,63 @@
+import unittest
+from unittest import mock
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestOpensslRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.openssl`
+ """
+
+ recipe_name = "openssl"
+ sh_command_calls = ["perl"]
+
+ @mock.patch("pythonforandroid.recipes.openssl.sh.patch")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_current_directory,
+ mock_sh_patch,
+ ):
+ # We overwrite the base test method because we need to mock a little
+ # more with this recipe.
+ super().test_build_arch()
+ # make sure that the mocked methods are actually called
+ mock_sh_patch.assert_called()
+
+ def test_versioned_url(self):
+ self.assertEqual(
+ self.recipe.url.format(url_version=self.recipe.url_version),
+ self.recipe.versioned_url,
+ )
+
+ def test_include_flags(self):
+ inc = self.recipe.include_flags(self.arch)
+ build_dir = self.recipe.get_build_dir(self.arch)
+ for i in {"include/internal", "include/openssl"}:
+ self.assertIn(f"-I{build_dir}/{i}", inc)
+
+ def test_link_flags(self):
+ build_dir = self.recipe.get_build_dir(self.arch)
+ openssl_version = self.recipe.version
+ self.assertEqual(
+ f" -L{build_dir} -lcrypto{openssl_version} -lssl{openssl_version}",
+ self.recipe.link_flags(self.arch),
+ )
+
+ def test_select_build_arch(self):
+ expected_build_archs = {
+ "armeabi": "android",
+ "armeabi-v7a": "android-arm",
+ "arm64-v8a": "android-arm64",
+ "x86": "android-x86",
+ "x86_64": "android-x86_64",
+ }
+ for arch in self.ctx.archs:
+ self.assertEqual(
+ expected_build_archs[arch.arch],
+ self.recipe.select_build_arch(arch),
+ )
diff --git a/tests/recipes/test_pandas.py b/tests/recipes/test_pandas.py
new file mode 100644
index 000000000..b8366863f
--- /dev/null
+++ b/tests/recipes/test_pandas.py
@@ -0,0 +1,47 @@
+import unittest
+
+from os.path import join
+from unittest import mock
+
+from tests.recipes.recipe_lib_test import RecipeCtx
+
+
+class TestPandasRecipe(RecipeCtx, unittest.TestCase):
+ """
+ TestCase for recipe :mod:`~pythonforandroid.recipes.pandas`
+ """
+ recipe_name = "pandas"
+
+ @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_get_recipe_env(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_check_recipe_choices,
+ ):
+ """
+ Test that method
+ :meth:`~pythonforandroid.recipes.pandas.PandasRecipe.get_recipe_env`
+ returns the expected flags
+ """
+
+ mock_shutil_which.return_value = (
+ "/opt/android/android-ndk/toolchains/"
+ "llvm/prebuilt/linux-x86_64/bin/clang"
+ )
+ mock_check_recipe_choices.return_value = sorted(
+ self.ctx.recipe_build_order
+ )
+ numpy_includes = join(
+ self.ctx.get_python_install_dir(self.arch.arch), "numpy/core/include",
+ )
+ env = self.recipe.get_recipe_env(self.arch)
+ self.assertIn(numpy_includes, env["NUMPY_INCLUDES"])
+ self.assertIn(" -landroid", env["LDFLAGS"])
+
+ # make sure that the mocked methods are actually called
+ mock_ensure_dir.assert_called()
+ mock_shutil_which.assert_called()
+ mock_check_recipe_choices.assert_called()
diff --git a/tests/recipes/test_png.py b/tests/recipes/test_png.py
new file mode 100644
index 000000000..ac734bfef
--- /dev/null
+++ b/tests/recipes/test_png.py
@@ -0,0 +1,10 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForMakeRecipe
+
+
+class TestPngRecipe(BaseTestForMakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.png`
+ """
+ recipe_name = "png"
+ sh_command_calls = ["./configure"]
diff --git a/tests/recipes/test_pyicu.py b/tests/recipes/test_pyicu.py
new file mode 100644
index 000000000..5babfbc11
--- /dev/null
+++ b/tests/recipes/test_pyicu.py
@@ -0,0 +1,48 @@
+import unittest
+from unittest import mock
+from tests.recipes.recipe_ctx import RecipeCtx
+from pythonforandroid.recipe import Recipe
+
+
+class TestPyIcuRecipe(RecipeCtx, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.pyicu`
+ """
+ recipe_name = "pyicu"
+
+ @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_get_recipe_env(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_check_recipe_choices,
+ ):
+ """
+ Test that method
+ :meth:`~pythonforandroid.recipes.pyicu.PyICURecipe.get_recipe_env`
+ returns the expected flags
+ """
+ icu_recipe = Recipe.get_recipe("icu", self.ctx)
+
+ mock_shutil_which.return_value = (
+ "/opt/android/android-ndk/toolchains/"
+ "llvm/prebuilt/linux-x86_64/bin/clang"
+ )
+ mock_check_recipe_choices.return_value = sorted(
+ self.ctx.recipe_build_order
+ )
+
+ expected_pyicu_libs = [
+ lib[3:-3] for lib in icu_recipe.built_libraries.keys()
+ ]
+ env = self.recipe.get_recipe_env(self.arch)
+ self.assertEqual(":".join(expected_pyicu_libs), env["PYICU_LIBRARIES"])
+ self.assertIn("include/icu", env["CPPFLAGS"])
+ self.assertIn("icu4c/icu_build/lib", env["LDFLAGS"])
+
+ # make sure that the mocked methods are actually called
+ mock_ensure_dir.assert_called()
+ mock_shutil_which.assert_called()
+ mock_check_recipe_choices.assert_called()
diff --git a/tests/recipes/test_python3.py b/tests/recipes/test_python3.py
new file mode 100644
index 000000000..f1d652b6c
--- /dev/null
+++ b/tests/recipes/test_python3.py
@@ -0,0 +1,211 @@
+import unittest
+
+from os.path import join
+from unittest import mock
+
+from pythonforandroid.recipes.python3 import (
+ NDK_API_LOWER_THAN_SUPPORTED_MESSAGE,
+)
+from pythonforandroid.util import BuildInterruptingException, build_platform
+from tests.recipes.recipe_lib_test import RecipeCtx
+
+
+class TestPython3Recipe(RecipeCtx, unittest.TestCase):
+ """
+ TestCase for recipe :mod:`~pythonforandroid.recipes.python3`
+ """
+ recipe_name = "python3"
+ expected_compiler = (
+ f"/opt/android/android-ndk/toolchains/"
+ f"llvm/prebuilt/{build_platform}/bin/clang"
+ )
+
+ def test_property__libpython(self):
+ self.assertEqual(
+ self.recipe._libpython,
+ f'libpython{self.recipe.link_version}.so'
+ )
+
+ @mock.patch('pythonforandroid.recipes.python3.Path.is_file')
+ def test_should_build(self, mock_is_file):
+ # in case that python lib exists, we shouldn't trigger the build
+ self.assertFalse(self.recipe.should_build(self.arch))
+ # in case that python lib doesn't exist, we should trigger the build
+ mock_is_file.return_value = False
+ self.assertTrue(self.recipe.should_build(self.arch))
+
+ def test_include_root(self):
+ expected_include_dir = join(
+ self.recipe.get_build_dir(self.arch.arch), 'Include',
+ )
+ self.assertEqual(
+ expected_include_dir, self.recipe.include_root(self.arch.arch)
+ )
+
+ def test_link_root(self):
+ expected_link_root = join(
+ self.recipe.get_build_dir(self.arch.arch), 'android-build',
+ )
+ self.assertEqual(
+ expected_link_root, self.recipe.link_root(self.arch.arch)
+ )
+
+ @mock.patch("pythonforandroid.recipes.python3.subprocess.call")
+ def test_compile_python_files(self, mock_subprocess):
+ fake_compile_dir = '/fake/compile/dir'
+ hostpy = self.recipe.ctx.hostpython = '/fake/hostpython3'
+ self.recipe.compile_python_files(fake_compile_dir)
+ mock_subprocess.assert_called_once_with(
+ [hostpy, '-OO', '-m', 'compileall', '-b', '-f', fake_compile_dir],
+ )
+
+ @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices")
+ @mock.patch("shutil.which")
+ def test_get_recipe_env(
+ self,
+ mock_shutil_which,
+ mock_check_recipe_choices,
+ ):
+ """
+ Test that method
+ :meth:`~pythonforandroid.recipes.python3.Python3Recipe.get_recipe_env`
+ returns the expected flags
+ """
+ mock_shutil_which.return_value = self.expected_compiler
+ mock_check_recipe_choices.return_value = sorted(
+ self.ctx.recipe_build_order
+ )
+ env = self.recipe.get_recipe_env(self.arch)
+
+ self.assertIn('-fPIC -DANDROID', env["CFLAGS"])
+ self.assertEqual(env["CC"], self.arch.get_clang_exe(with_target=True))
+
+ # make sure that the mocked methods are actually called
+ mock_check_recipe_choices.assert_called()
+
+ def test_set_libs_flags(self):
+ # todo: properly check `Python3Recipe.set_lib_flags`
+ pass
+
+ # These decorators are to mock calls to `get_recipe_env`
+ # and `set_libs_flags`, since these calls are tested separately
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.util.makedirs")
+ @mock.patch("shutil.which")
+ def test_build_arch(
+ self,
+ mock_shutil_which,
+ mock_makedirs,
+ mock_chdir):
+ mock_shutil_which.return_value = self.expected_compiler
+
+ # specific `build_arch` mocks
+ with mock.patch(
+ "builtins.open",
+ mock.mock_open(read_data="#define ZLIB_VERSION 1.1\nfoo")
+ ) as mock_open_zlib, mock.patch(
+ "pythonforandroid.recipes.python3.sh.Command"
+ ) as mock_sh_command, mock.patch(
+ "pythonforandroid.recipes.python3.sh.make"
+ ) as mock_make, mock.patch(
+ "pythonforandroid.recipes.python3.sh.cp"
+ ) as mock_cp:
+ self.recipe.build_arch(self.arch)
+
+ # make sure that the mocked methods are actually called
+ recipe_build_dir = self.recipe.get_build_dir(self.arch.arch)
+ sh_command_calls = {
+ f"{recipe_build_dir}/config.guess",
+ f"{recipe_build_dir}/configure",
+ }
+ for command in sh_command_calls:
+ self.assertIn(
+ mock.call(command),
+ mock_sh_command.mock_calls,
+ )
+ mock_open_zlib.assert_called()
+ self.assertEqual(mock_make.call_count, 1)
+ for make_call, kw in mock_make.call_args_list:
+ self.assertIn(
+ f'INSTSONAME={self.recipe._libpython}', make_call
+ )
+ mock_cp.assert_called_with(
+ "pyconfig.h", join(recipe_build_dir, 'Include'),
+ )
+ mock_makedirs.assert_called()
+ mock_chdir.assert_called()
+
+ def test_build_arch_wrong_ndk_api(self):
+ # we check ndk_api using recipe's ctx
+ self.recipe.ctx.ndk_api = 20
+ with self.assertRaises(BuildInterruptingException) as e:
+ self.recipe.build_arch(self.arch)
+ self.assertEqual(
+ e.exception.args[0],
+ NDK_API_LOWER_THAN_SUPPORTED_MESSAGE.format(
+ ndk_api=self.recipe.ctx.ndk_api,
+ min_ndk_api=self.recipe.MIN_NDK_API,
+ ),
+ )
+ # restore recipe's ctx or we could get failures with other test,
+ # since we share `self.recipe with all the tests of the class
+ self.recipe.ctx.ndk_api = self.ctx.ndk_api
+
+ @mock.patch('shutil.copystat')
+ @mock.patch('shutil.copyfile')
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.util.makedirs")
+ @mock.patch("pythonforandroid.util.walk")
+ @mock.patch("pythonforandroid.recipes.python3.sh.find")
+ @mock.patch("pythonforandroid.recipes.python3.sh.cp")
+ @mock.patch("pythonforandroid.recipes.python3.sh.zip")
+ @mock.patch("pythonforandroid.recipes.python3.subprocess.call")
+ def test_create_python_bundle(
+ self,
+ mock_subprocess,
+ mock_sh_zip,
+ mock_sh_cp,
+ mock_sh_find,
+ mock_walk,
+ mock_makedirs,
+ mock_chdir,
+ mock_copyfile,
+ mock_copystat,
+ ):
+ fake_compile_dir = '/fake/compile/dir'
+ simulated_walk_result = [
+ ["/fake_dir", ["__pycache__", "Lib"], ["README", "setup.py"]],
+ ["/fake_dir/Lib", ["ctypes"], ["abc.pyc", "abc.py"]],
+ ["/fake_dir/Lib/ctypes", [], ["util.pyc", "util.py"]],
+ ]
+ mock_walk.return_value = simulated_walk_result
+ self.recipe.create_python_bundle(fake_compile_dir, self.arch)
+
+ recipe_build_dir = self.recipe.get_build_dir(self.arch.arch)
+ modules_build_dir = join(
+ recipe_build_dir,
+ 'android-build',
+ 'build',
+ 'lib.linux{}-{}-{}'.format(
+ '2' if self.recipe.version[0] == '2' else '',
+ self.arch.command_prefix.split('-')[0],
+ self.recipe.major_minor_version_string
+ ))
+ expected_sp_paths = [
+ modules_build_dir,
+ join(recipe_build_dir, 'Lib'),
+ self.ctx.get_python_install_dir(self.arch.arch),
+ ]
+ for n, (sp_call, kw) in enumerate(mock_subprocess.call_args_list):
+ self.assertEqual(sp_call[0][-1], expected_sp_paths[n])
+
+ # we expect two calls to `walk_valid_filens`
+ self.assertEqual(len(mock_walk.call_args_list), 2)
+
+ mock_sh_zip.assert_called()
+ mock_sh_cp.assert_called()
+ mock_sh_find.assert_called()
+ mock_makedirs.assert_called()
+ mock_chdir.assert_called()
+ mock_copyfile.assert_called()
+ mock_copystat.assert_called()
diff --git a/tests/recipes/test_reportlab.py b/tests/recipes/test_reportlab.py
new file mode 100644
index 000000000..6129a6a96
--- /dev/null
+++ b/tests/recipes/test_reportlab.py
@@ -0,0 +1,41 @@
+import os
+import unittest
+from unittest.mock import patch
+from tests.recipes.recipe_ctx import RecipeCtx
+from pythonforandroid.util import ensure_dir
+
+
+class TestReportLabRecipe(RecipeCtx, unittest.TestCase):
+ recipe_name = "reportlab"
+
+ def setUp(self):
+ """
+ Setups recipe and context.
+ """
+ super().setUp()
+ self.recipe_dir = self.recipe.get_build_dir(self.arch.arch)
+ ensure_dir(self.recipe_dir)
+
+ def test_prebuild_arch(self):
+ """
+ Makes sure `prebuild_arch()` runs without error and patches `setup.py`
+ as expected.
+ """
+ # `prebuild_arch()` dynamically replaces strings in the `setup.py` file
+ setup_path = os.path.join(self.recipe_dir, 'setup.py')
+ with open(setup_path, 'w') as setup_file:
+ setup_file.write('_FT_LIB_\n')
+ setup_file.write('_FT_INC_\n')
+
+ # these sh commands are not relevant for the test and need to be mocked
+ with \
+ patch('sh.patch'), \
+ patch('pythonforandroid.recipe.touch'), \
+ patch('sh.unzip'), \
+ patch('os.path.isfile'):
+ self.recipe.prebuild_arch(self.arch)
+ # makes sure placeholder got replaced with library and include paths
+ with open(setup_path, 'r') as setup_file:
+ lines = setup_file.readlines()
+ self.assertTrue(lines[0].endswith('freetype/objs/.libs\n'))
+ self.assertTrue(lines[1].endswith('freetype/include\n'))
diff --git a/tests/recipes/test_sdl2_mixer.py b/tests/recipes/test_sdl2_mixer.py
new file mode 100644
index 000000000..a583d9aa6
--- /dev/null
+++ b/tests/recipes/test_sdl2_mixer.py
@@ -0,0 +1,14 @@
+import unittest
+from tests.recipes.recipe_lib_test import RecipeCtx
+
+
+class TestSDL2MixerRecipe(RecipeCtx, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.sdl2_mixer`
+ """
+ recipe_name = "sdl2_mixer"
+
+ def test_get_include_dirs(self):
+ list_of_includes = self.recipe.get_include_dirs(self.arch)
+ self.assertIsInstance(list_of_includes, list)
+ self.assertTrue(list_of_includes[0].endswith("include"))
diff --git a/tests/recipes/test_snappy.py b/tests/recipes/test_snappy.py
new file mode 100644
index 000000000..6439454e2
--- /dev/null
+++ b/tests/recipes/test_snappy.py
@@ -0,0 +1,9 @@
+import unittest
+from tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe
+
+
+class TestSnappyRecipe(BaseTestForCmakeRecipe, unittest.TestCase):
+ """
+ An unittest for recipe :mod:`~pythonforandroid.recipes.snappy`
+ """
+ recipe_name = "snappy"
diff --git a/tests/test_androidmodule_ctypes_finder.py b/tests/test_androidmodule_ctypes_finder.py
new file mode 100644
index 000000000..553287d12
--- /dev/null
+++ b/tests/test_androidmodule_ctypes_finder.py
@@ -0,0 +1,130 @@
+
+# This test is still expected to support Python 2, as it tests
+# on-Android functionality that we still maintain
+try: # Python 3+
+ from unittest import mock
+ from unittest.mock import MagicMock
+except ImportError: # Python 2
+ import mock
+ from mock import MagicMock
+import os
+import shutil
+import sys
+import tempfile
+
+
+# Import the tested android._ctypes_library_finder module,
+# making sure android._android won't crash us!
+# (since android._android is android-only / not compilable on desktop)
+android_module_folder = os.path.abspath(os.path.join(
+ os.path.dirname(__file__),
+ "..", "pythonforandroid", "recipes", "android", "src"
+))
+sys.path.insert(0, android_module_folder)
+sys.modules['android._android'] = MagicMock()
+import android._ctypes_library_finder
+sys.path.remove(android_module_folder)
+
+
+@mock.patch.dict('sys.modules', jnius=MagicMock())
+def test_get_activity_lib_dir():
+ import jnius # should get us our fake module
+
+ # Short test that it works when activity doesn't exist:
+ jnius.autoclass = MagicMock()
+ jnius.autoclass.return_value = None
+ assert android._ctypes_library_finder.get_activity_lib_dir(
+ "JavaClass"
+ ) is None
+ assert mock.call("JavaClass") in jnius.autoclass.call_args_list
+
+ # Comprehensive test that verifies getApplicationInfo() call:
+ activity = MagicMock()
+ app_context = activity.getApplicationContext()
+ app_context.getPackageName.return_value = "test.package"
+ app_info = app_context.getPackageManager().getApplicationInfo()
+ app_info.nativeLibraryDir = '/testpath'
+
+ def pick_class(name):
+ cls = MagicMock()
+ if name == "JavaClass":
+ cls.mActivity = activity
+ elif name == "android.content.pm.PackageManager":
+ # Manager class:
+ cls.GET_SHARED_LIBRARY_FILES = 1024
+ return cls
+
+ jnius.autoclass = MagicMock(side_effect=pick_class)
+ assert android._ctypes_library_finder.get_activity_lib_dir(
+ "JavaClass"
+ ) == "/testpath"
+ assert mock.call("JavaClass") in jnius.autoclass.call_args_list
+ assert mock.call("test.package", 1024) in (
+ app_context.getPackageManager().getApplicationInfo.call_args_list
+ )
+
+
+@mock.patch.dict('sys.modules', jnius=MagicMock())
+def test_find_library():
+ test_d = tempfile.mkdtemp(prefix="p4a-android-ctypes-test-libdir-")
+ try:
+ with open(os.path.join(test_d, "mymadeuplib.so.5"), "w"):
+ pass
+ import jnius # should get us our fake module
+
+ # Test with mActivity returned:
+ jnius.autoclass = MagicMock()
+ jnius.autoclass().mService = None
+ app_context = jnius.autoclass().mActivity.getApplicationContext()
+ app_info = app_context.getPackageManager().getApplicationInfo()
+ app_info.nativeLibraryDir = '/doesnt-exist-testpath'
+ assert android._ctypes_library_finder.find_library(
+ "mymadeuplib"
+ ) is None
+ assert mock.call("org.kivy.android.PythonActivity") in (
+ jnius.autoclass.call_args_list
+ )
+ app_info.nativeLibraryDir = test_d
+ assert os.path.normpath(android._ctypes_library_finder.find_library(
+ "mymadeuplib"
+ )) == os.path.normpath(os.path.join(test_d, "mymadeuplib.so.5"))
+
+ # Test with mService returned:
+ jnius.autoclass = MagicMock()
+ jnius.autoclass().mActivity = None
+ app_context = jnius.autoclass().mService.getApplicationContext()
+ app_info = app_context.getPackageManager().getApplicationInfo()
+ app_info.nativeLibraryDir = '/doesnt-exist-testpath'
+ assert android._ctypes_library_finder.find_library(
+ "mymadeuplib"
+ ) is None
+ app_info.nativeLibraryDir = test_d
+ assert os.path.normpath(android._ctypes_library_finder.find_library(
+ "mymadeuplib"
+ )) == os.path.normpath(os.path.join(test_d, "mymadeuplib.so.5"))
+ finally:
+ shutil.rmtree(test_d)
+
+
+def test_does_libname_match_filename():
+ assert android._ctypes_library_finder.does_libname_match_filename(
+ "mylib", "mylib.so"
+ )
+ assert not android._ctypes_library_finder.does_libname_match_filename(
+ "mylib", "amylib.so"
+ )
+ assert not android._ctypes_library_finder.does_libname_match_filename(
+ "mylib", "mylib.txt"
+ )
+ assert not android._ctypes_library_finder.does_libname_match_filename(
+ "mylib", "mylib"
+ )
+ assert android._ctypes_library_finder.does_libname_match_filename(
+ "mylib", "libmylib.test.so.1.2.3"
+ )
+ assert not android._ctypes_library_finder.does_libname_match_filename(
+ "mylib", "libtest.mylib.so"
+ )
+ assert android._ctypes_library_finder.does_libname_match_filename(
+ "mylib", "mylib.so.5"
+ )
diff --git a/tests/test_androidndk.py b/tests/test_androidndk.py
new file mode 100644
index 000000000..6d89b131c
--- /dev/null
+++ b/tests/test_androidndk.py
@@ -0,0 +1,140 @@
+import unittest
+from unittest import mock
+
+from pythonforandroid.androidndk import AndroidNDK
+
+
+class TestAndroidNDK(unittest.TestCase):
+ """
+ An inherited class of `unittest.TestCase`to test the module
+ :mod:`~pythonforandroid.androidndk`.
+ """
+
+ def setUp(self):
+ """Configure a :class:`~pythonforandroid.androidndk.AndroidNDK` so we can
+ perform our unittests"""
+ self.ndk = AndroidNDK("/opt/android/android-ndk")
+
+ @mock.patch("sys.platform", "linux")
+ def test_host_tag_linux(self):
+ """Test the `host_tag` property of the :class:`~pythonforandroid.androidndk.AndroidNDK`
+ class when the host is Linux."""
+ self.assertEqual(self.ndk.host_tag, "linux-x86_64")
+
+ @mock.patch("sys.platform", "darwin")
+ def test_host_tag_darwin(self):
+ """Test the `host_tag` property of the :class:`~pythonforandroid.androidndk.AndroidNDK`
+ class when the host is Darwin."""
+ self.assertEqual(self.ndk.host_tag, "darwin-x86_64")
+
+ def test_llvm_prebuilt_dir(self):
+ """Test the `llvm_prebuilt_dir` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.llvm_prebuilt_dir,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}",
+ )
+
+ def test_llvm_bin_dir(self):
+ """Test the `llvm_bin_dir` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.llvm_bin_dir,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin",
+ )
+
+ def test_clang(self):
+ """Test the `clang` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.clang,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/clang",
+ )
+
+ def test_clang_cxx(self):
+ """Test the `clang_cxx` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.clang_cxx,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/clang++",
+ )
+
+ def test_llvm_ar(self):
+ """Test the `llvm_ar` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.llvm_ar,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-ar",
+ )
+
+ def test_llvm_ranlib(self):
+ """Test the `llvm_ranlib` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.llvm_ranlib,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-ranlib",
+ )
+
+ def test_llvm_objcopy(self):
+ """Test the `llvm_objcopy` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.llvm_objcopy,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-objcopy",
+ )
+
+ def test_llvm_objdump(self):
+ """Test the `llvm_objdump` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.llvm_objdump,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-objdump",
+ )
+
+ def test_llvm_readelf(self):
+ """Test the `llvm_readelf` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.llvm_readelf,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-readelf",
+ )
+
+ def test_llvm_strip(self):
+ """Test the `llvm_strip` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.llvm_strip,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/bin/llvm-strip",
+ )
+
+ def test_sysroot(self):
+ """Test the `sysroot` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.sysroot,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/sysroot",
+ )
+
+ def test_sysroot_include_dir(self):
+ """Test the `sysroot_include_dir` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.sysroot_include_dir,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/sysroot/usr/include",
+ )
+
+ def test_sysroot_lib_dir(self):
+ """Test the `sysroot_lib_dir` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.sysroot_lib_dir,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/sysroot/usr/lib",
+ )
+
+ def test_libcxx_include_dir(self):
+ """Test the `libcxx_include_dir` property of the
+ :class:`~pythonforandroid.androidndk.AndroidNDK` class."""
+ self.assertEqual(
+ self.ndk.libcxx_include_dir,
+ f"/opt/android/android-ndk/toolchains/llvm/prebuilt/{self.ndk.host_tag}/sysroot/usr/include/c++/v1",
+ )
diff --git a/tests/test_archs.py b/tests/test_archs.py
new file mode 100644
index 000000000..58530886f
--- /dev/null
+++ b/tests/test_archs.py
@@ -0,0 +1,365 @@
+import os
+import unittest
+from os import environ
+from unittest import mock
+
+from pythonforandroid.bootstrap import Bootstrap
+from pythonforandroid.distribution import Distribution
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.build import Context
+from pythonforandroid.util import BuildInterruptingException
+from pythonforandroid.archs import (
+ Arch,
+ ArchARM,
+ ArchARMv7_a,
+ ArchAarch_64,
+ Archx86,
+ Archx86_64,
+)
+from pythonforandroid.androidndk import AndroidNDK
+
+expected_env_gcc_keys = {
+ "CFLAGS",
+ "LDFLAGS",
+ "CXXFLAGS",
+ "CC",
+ "CXX",
+ "LDSHARED",
+ "STRIP",
+ "MAKE",
+ "READELF",
+ "BUILDLIB_PATH",
+ "PATH",
+ "ARCH",
+ "NDK_API",
+}
+
+
+class ArchSetUpBaseClass(object):
+ """
+ An class object which is intended to be used as a base class to configure
+ an inherited class of `unittest.TestCase`. This class will override the
+ `setUp` method.
+ """
+
+ ctx = None
+ expected_compiler = ""
+
+ TEST_ARCH = 'armeabi-v7a'
+
+ def setUp(self):
+ self.ctx = Context()
+ self.ctx.ndk_api = 21
+ self.ctx.android_api = 27
+ self.ctx._sdk_dir = "/opt/android/android-sdk"
+ self.ctx._ndk_dir = "/opt/android/android-ndk"
+ self.ctx.ndk = AndroidNDK(self.ctx._ndk_dir)
+ self.ctx.setup_dirs(os.getcwd())
+ self.ctx.bootstrap = Bootstrap().get_bootstrap("sdl2", self.ctx)
+ self.ctx.bootstrap.distribution = Distribution.get_distribution(
+ self.ctx,
+ name="sdl2",
+ recipes=["python3", "kivy"],
+ archs=[self.TEST_ARCH],
+ )
+ self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx)
+ # Here we define the expected compiler, which, as per ndk >= r19,
+ # should be the same for all the tests (no more gcc compiler)
+ self.expected_compiler = (
+ f"/opt/android/android-ndk/toolchains/"
+ f"llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang"
+ )
+
+
+class TestArch(ArchSetUpBaseClass, unittest.TestCase):
+ """
+ An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which
+ will be used to perform tests for the base class
+ :class:`~pythonforandroid.archs.Arch`.
+ """
+
+ def test_arch(self):
+ arch = Arch(self.ctx)
+ self.assertEqual(arch.__str__(), arch.arch)
+ self.assertEqual(arch.target, "None21")
+ self.assertIsNone(arch.command_prefix)
+ self.assertIsInstance(arch.include_dirs, list)
+
+
+class TestArchARM(ArchSetUpBaseClass, unittest.TestCase):
+ """
+ An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which
+ will be used to perform tests for :class:`~pythonforandroid.archs.ArchARM`.
+ """
+
+ @mock.patch("shutil.which")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ def test_arch_arm(self, mock_ensure_dir, mock_shutil_which):
+ """
+ Test that class :class:`~pythonforandroid.archs.ArchARM` returns some
+ expected attributes and environment variables.
+
+ .. note::
+ Here we mock two methods:
+
+ - `ensure_dir` because we don't want to create any directory
+ - `shutil.which` because otherwise we will
+ get an error when trying to find the compiler (we are setting
+ some fake paths for our android sdk and ndk so probably will
+ not exist)
+
+ """
+ mock_shutil_which.return_value = self.expected_compiler
+ mock_ensure_dir.return_value = True
+
+ arch = ArchARM(self.ctx)
+ self.assertEqual(arch.arch, "armeabi")
+ self.assertEqual(arch.__str__(), "armeabi")
+ self.assertEqual(arch.command_prefix, "arm-linux-androideabi")
+ self.assertEqual(arch.target, "armv7a-linux-androideabi21")
+ arch = ArchARM(self.ctx)
+
+ # Check environment flags
+ env = arch.get_env()
+ self.assertIsInstance(env, dict)
+ self.assertEqual(
+ expected_env_gcc_keys, set(env.keys()) & expected_env_gcc_keys
+ )
+
+ # check shutil.which calls
+ mock_shutil_which.assert_called_once_with(
+ self.expected_compiler, path=environ["PATH"]
+ )
+
+ # check gcc compilers
+ self.assertEqual(env["CC"].split()[0], self.expected_compiler)
+ self.assertEqual(env["CXX"].split()[0], self.expected_compiler + "++")
+ # check android binaries
+ self.assertEqual(
+ env["STRIP"].split()[0],
+ os.path.join(
+ self.ctx._ndk_dir,
+ f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin",
+ "llvm-strip",
+ )
+ )
+ self.assertEqual(
+ env["READELF"].split()[0],
+ os.path.join(
+ self.ctx._ndk_dir,
+ f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin",
+ "llvm-readelf",
+ )
+ )
+
+ # check that cflags are in gcc
+ self.assertIn(env["CFLAGS"], env["CC"])
+
+ # check that flags aren't in gcc and also check ccache
+ self.ctx.ccache = "/usr/bin/ccache"
+ env = arch.get_env(with_flags_in_cc=False)
+ self.assertNotIn(env["CFLAGS"], env["CC"])
+ self.assertEqual(env["USE_CCACHE"], "1")
+ self.assertEqual(env["NDK_CCACHE"], "/usr/bin/ccache")
+
+ # Check exception in case that CC is not found
+ mock_shutil_which.return_value = None
+ with self.assertRaises(BuildInterruptingException) as e:
+ arch.get_env()
+ self.assertEqual(
+ e.exception.args[0],
+ "Couldn't find executable for CC. This indicates a problem "
+ "locating the {expected_compiler} executable in the Android "
+ "NDK, not that you don't have a normal compiler installed. "
+ "Exiting.".format(expected_compiler=self.expected_compiler),
+ )
+
+
+class TestArchARMv7a(ArchSetUpBaseClass, unittest.TestCase):
+ """
+ An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which
+ will be used to perform tests for
+ :class:`~pythonforandroid.archs.ArchARMv7_a`.
+ """
+
+ @mock.patch("shutil.which")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ def test_arch_armv7a(
+ self, mock_ensure_dir, mock_shutil_which
+ ):
+ """
+ Test that class :class:`~pythonforandroid.archs.ArchARMv7_a` returns
+ some expected attributes and environment variables.
+
+ .. note::
+ Here we mock the same functions than
+ :meth:`TestArchARM.test_arch_arm`.
+ This has to be done because here we tests the `get_env` with clang
+
+ """
+ mock_shutil_which.return_value = self.expected_compiler
+ mock_ensure_dir.return_value = True
+
+ arch = ArchARMv7_a(self.ctx)
+ self.assertEqual(arch.arch, "armeabi-v7a")
+ self.assertEqual(arch.__str__(), "armeabi-v7a")
+ self.assertEqual(arch.command_prefix, "arm-linux-androideabi")
+ self.assertEqual(arch.target, "armv7a-linux-androideabi21")
+
+ env = arch.get_env()
+ # check shutil.which calls
+ mock_shutil_which.assert_called_once_with(
+ self.expected_compiler, path=environ["PATH"]
+ )
+
+ # check clang
+ self.assertEqual(
+ env["CC"].split()[0],
+ "{ndk_dir}/toolchains/llvm/prebuilt/"
+ "{host_tag}/bin/clang".format(
+ ndk_dir=self.ctx._ndk_dir, host_tag=self.ctx.ndk.host_tag
+ ),
+ )
+ self.assertEqual(
+ env["CXX"].split()[0],
+ "{ndk_dir}/toolchains/llvm/prebuilt/"
+ "{host_tag}/bin/clang++".format(
+ ndk_dir=self.ctx._ndk_dir, host_tag=self.ctx.ndk.host_tag
+ ),
+ )
+
+ # For armeabi-v7a we expect some extra cflags
+ self.assertIn(
+ " -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -mthumb",
+ env["CFLAGS"],
+ )
+
+
+class TestArchX86(ArchSetUpBaseClass, unittest.TestCase):
+ """
+ An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which
+ will be used to perform tests for :class:`~pythonforandroid.archs.Archx86`.
+ """
+
+ @mock.patch("shutil.which")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ def test_arch_x86(self, mock_ensure_dir, mock_shutil_which):
+ """
+ Test that class :class:`~pythonforandroid.archs.Archx86` returns
+ some expected attributes and environment variables.
+
+ .. note::
+ Here we mock the same functions than
+ :meth:`TestArchARM.test_arch_arm` plus `glob`, so we make sure that
+ the glob result is the expected even if the folder doesn't exist,
+ which is probably the case. This has to be done because here we
+ tests the `get_env` with clang
+ """
+ mock_shutil_which.return_value = self.expected_compiler
+ mock_ensure_dir.return_value = True
+
+ arch = Archx86(self.ctx)
+ self.assertEqual(arch.arch, "x86")
+ self.assertEqual(arch.__str__(), "x86")
+ self.assertEqual(arch.command_prefix, "i686-linux-android")
+ self.assertEqual(arch.target, "i686-linux-android21")
+
+ env = arch.get_env()
+ # check shutil.which calls
+ mock_shutil_which.assert_called_once_with(
+ self.expected_compiler, path=environ["PATH"]
+ )
+
+ # For x86 we expect some extra cflags in our `environment`
+ self.assertIn(
+ " -march=i686 -mssse3 -mfpmath=sse -m32",
+ env["CFLAGS"],
+ )
+
+
+class TestArchX86_64(ArchSetUpBaseClass, unittest.TestCase):
+ """
+ An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which
+ will be used to perform tests for
+ :class:`~pythonforandroid.archs.Archx86_64`.
+ """
+
+ @mock.patch("shutil.which")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ def test_arch_x86_64(
+ self, mock_ensure_dir, mock_shutil_which
+ ):
+ """
+ Test that class :class:`~pythonforandroid.archs.Archx86_64` returns
+ some expected attributes and environment variables.
+
+ .. note::
+ Here we mock the same functions than
+ :meth:`TestArchARM.test_arch_arm` plus `glob`, so we make sure that
+ the glob result is the expected even if the folder doesn't exist,
+ which is probably the case. This has to be done because here we
+ tests the `get_env` with clang
+ """
+ mock_shutil_which.return_value = self.expected_compiler
+ mock_ensure_dir.return_value = True
+
+ arch = Archx86_64(self.ctx)
+ self.assertEqual(arch.arch, "x86_64")
+ self.assertEqual(arch.__str__(), "x86_64")
+ self.assertEqual(arch.command_prefix, "x86_64-linux-android")
+ self.assertEqual(arch.target, "x86_64-linux-android21")
+
+ env = arch.get_env()
+ # check shutil.which calls
+ mock_shutil_which.assert_called_once_with(
+ self.expected_compiler, path=environ["PATH"]
+ )
+
+ # For x86_64 we expect some extra cflags in our `environment`
+ mock_shutil_which.assert_called_once()
+ self.assertIn(
+ " -march=x86-64 -msse4.2 -mpopcnt -m64", env["CFLAGS"]
+ )
+
+
+class TestArchAArch64(ArchSetUpBaseClass, unittest.TestCase):
+ """
+ An inherited class of `ArchSetUpBaseClass` and `unittest.TestCase` which
+ will be used to perform tests for
+ :class:`~pythonforandroid.archs.ArchAarch_64`.
+ """
+
+ @mock.patch("shutil.which")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ def test_arch_aarch_64(
+ self, mock_ensure_dir, mock_shutil_which
+ ):
+ """
+ Test that class :class:`~pythonforandroid.archs.ArchAarch_64` returns
+ some expected attributes and environment variables.
+
+ .. note::
+ Here we mock the same functions than
+ :meth:`TestArchARM.test_arch_arm` plus `glob`, so we make sure that
+ the glob result is the expected even if the folder doesn't exist,
+ which is probably the case. This has to be done because here we
+ tests the `get_env` with clang
+ """
+ mock_shutil_which.return_value = self.expected_compiler
+ mock_ensure_dir.return_value = True
+
+ arch = ArchAarch_64(self.ctx)
+ self.assertEqual(arch.arch, "arm64-v8a")
+ self.assertEqual(arch.__str__(), "arm64-v8a")
+ self.assertEqual(arch.command_prefix, "aarch64-linux-android")
+ self.assertEqual(arch.target, "aarch64-linux-android21")
+
+ env = arch.get_env()
+ # check shutil.which calls
+ mock_shutil_which.assert_called_once_with(
+ self.expected_compiler, path=environ["PATH"]
+ )
+
+ # For x86_64 we expect to find an extra key in`environment`
+ for flag in {"CFLAGS", "CXXFLAGS", "CC", "CXX"}:
+ self.assertIn("-march=armv8-a", env[flag])
diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py
new file mode 100644
index 000000000..742ea0ba7
--- /dev/null
+++ b/tests/test_bootstrap.py
@@ -0,0 +1,677 @@
+
+import os
+import sh
+import unittest
+
+from unittest import mock
+
+from pythonforandroid.bootstrap import (
+ _cmp_bootstraps_by_priority, Bootstrap, expand_dependencies,
+)
+from pythonforandroid.distribution import Distribution
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.archs import ArchARMv7_a
+from pythonforandroid.build import Context
+from pythonforandroid.util import BuildInterruptingException
+from pythonforandroid.androidndk import AndroidNDK
+
+from test_graph import get_fake_recipe
+
+
+class BaseClassSetupBootstrap(object):
+ """
+ An class object which is intended to be used as a base class to configure
+ an inherited class of `unittest.TestCase`. This class will override the
+ `setUp` and `tearDown` methods.
+ """
+
+ TEST_ARCH = 'armeabi-v7a'
+
+ def setUp(self):
+ Recipe.recipes = {} # clear Recipe class cache
+ self.ctx = Context()
+ self.ctx.ndk_api = 21
+ self.ctx.android_api = 27
+ self.ctx._sdk_dir = "/opt/android/android-sdk"
+ self.ctx._ndk_dir = "/opt/android/android-ndk"
+ self.ctx.ndk = AndroidNDK(self.ctx._ndk_dir)
+ self.ctx.setup_dirs(os.getcwd())
+ self.ctx.recipe_build_order = [
+ "hostpython3",
+ "python3",
+ "sdl2",
+ "kivy",
+ ]
+
+ def setUp_distribution_with_bootstrap(self, bs):
+ """
+ Extend the setUp by configuring a distribution, because some test
+ needs a distribution to be set to be properly tested
+ """
+ self.ctx.bootstrap = bs
+ self.ctx.bootstrap.distribution = Distribution.get_distribution(
+ self.ctx, name="test_prj",
+ recipes=["python3", "kivy"],
+ archs=[self.TEST_ARCH],
+ )
+
+ def tearDown(self):
+ """
+ Extend the `tearDown` by configuring a distribution, because some test
+ needs a distribution to be set to be properly tested
+ """
+ self.ctx.bootstrap = None
+
+
+class TestBootstrapBasic(BaseClassSetupBootstrap, unittest.TestCase):
+ """
+ An inherited class of `BaseClassSetupBootstrap` and `unittest.TestCase`
+ which will be used to perform tests for the methods/attributes shared
+ between all bootstraps which inherits from class
+ :class:`~pythonforandroid.bootstrap.Bootstrap`.
+ """
+
+ def test_attributes(self):
+ """A test which will initialize a bootstrap and will check if the
+ values are the expected.
+ """
+ bs = Bootstrap().get_bootstrap("sdl2", self.ctx)
+ self.assertEqual(bs.name, "sdl2")
+ self.assertEqual(bs.jni_dir, "sdl2/jni")
+ self.assertEqual(bs.get_build_dir_name(), "sdl2")
+
+ # bs.dist_dir should raise an error if there is no distribution to query
+ bs.distribution = None
+ with self.assertRaises(BuildInterruptingException):
+ bs.dist_dir
+
+ # test dist_dir success
+ self.setUp_distribution_with_bootstrap(bs)
+ expected_folder_name = 'test_prj'
+ self.assertTrue(
+ bs.dist_dir.endswith(f"dists/{expected_folder_name}"))
+
+ def test_build_dist_dirs(self):
+ """A test which will initialize a bootstrap and will check if the
+ directories we set has the values that we expect. Here we test methods:
+
+ - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_build_dir`
+ - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_dist_dir`
+ - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_common_dir`
+ """
+ bs = Bootstrap.get_bootstrap("sdl2", self.ctx)
+
+ self.assertTrue(
+ bs.get_build_dir().endswith("build/bootstrap_builds/sdl2")
+ )
+ self.assertTrue(bs.get_dist_dir("test_prj").endswith("dists/test_prj"))
+
+ def test__cmp_bootstraps_by_priority(self):
+ # Test service_only has higher priority than sdl2:
+ # (higher priority = smaller number/comes first)
+ self.assertTrue(_cmp_bootstraps_by_priority(
+ Bootstrap.get_bootstrap("service_only", self.ctx),
+ Bootstrap.get_bootstrap("sdl2", self.ctx)
+ ) < 0)
+
+ # Test a random bootstrap is always lower priority than sdl2:
+ class _FakeBootstrap(object):
+ def __init__(self, name):
+ self.name = name
+ bs1 = _FakeBootstrap("alpha")
+ bs2 = _FakeBootstrap("zeta")
+ self.assertTrue(_cmp_bootstraps_by_priority(
+ bs1,
+ Bootstrap.get_bootstrap("sdl2", self.ctx)
+ ) > 0)
+ self.assertTrue(_cmp_bootstraps_by_priority(
+ bs2,
+ Bootstrap.get_bootstrap("sdl2", self.ctx)
+ ) > 0)
+
+ # Test bootstraps that aren't otherwise recognized are ranked
+ # alphabetically:
+ self.assertTrue(_cmp_bootstraps_by_priority(
+ bs2,
+ bs1,
+ ) > 0)
+ self.assertTrue(_cmp_bootstraps_by_priority(
+ bs1,
+ bs2,
+ ) < 0)
+
+ def test_all_bootstraps(self):
+ """A test which will initialize a bootstrap and will check if the
+ method :meth:`~pythonforandroid.bootstrap.Bootstrap.all_bootstraps `
+ returns the expected values, which should be: `empty", `service_only`,
+ `webview`, `sdl2` and `qt`
+ """
+ expected_bootstraps = {"empty", "service_only", "service_library", "webview", "sdl2", "qt"}
+ set_of_bootstraps = Bootstrap.all_bootstraps()
+ self.assertEqual(
+ expected_bootstraps, expected_bootstraps & set_of_bootstraps
+ )
+ self.assertEqual(len(expected_bootstraps), len(set_of_bootstraps))
+
+ def test_expand_dependencies(self):
+ # Test dependency expansion of a recipe with no alternatives:
+ expanded_result_1 = expand_dependencies(["pysdl2"], self.ctx)
+ self.assertTrue(
+ {"sdl2", "pysdl2", "python3"} in
+ [set(s) for s in expanded_result_1]
+ )
+
+ # Test expansion of a single element but as tuple:
+ expanded_result_1 = expand_dependencies([("pysdl2",)], self.ctx)
+ self.assertTrue(
+ {"sdl2", "pysdl2", "python3"} in
+ [set(s) for s in expanded_result_1]
+ )
+
+ # Test all alternatives are listed (they won't have dependencies
+ # expanded since expand_dependencies() is too simplistic):
+ expanded_result_2 = expand_dependencies([("pysdl2", "kivy")], self.ctx)
+ self.assertEqual([["pysdl2"], ["kivy"]], expanded_result_2)
+
+ def test_expand_dependencies_with_pure_python_package(self):
+ """Check that `expanded_dependencies`, with a pure python package as
+ one of the dependencies, returns a list of dependencies
+ """
+ expanded_result = expand_dependencies(
+ ["python3", "kivy", "peewee"], self.ctx
+ )
+ # we expect to one results for python3
+ self.assertEqual(len(expanded_result), 1)
+ self.assertIsInstance(expanded_result, list)
+ for i in expanded_result:
+ self.assertIsInstance(i, list)
+
+ def test_get_bootstraps_from_recipes(self):
+ """A test which will initialize a bootstrap and will check if the
+ method :meth:`~pythonforandroid.bootstrap.Bootstrap.
+ get_bootstraps_from_recipes` returns the expected values
+ """
+
+ import pythonforandroid.recipe
+ original_get_recipe = pythonforandroid.recipe.Recipe.get_recipe
+
+ # Test that SDL2 works with kivy:
+ recipes_sdl2 = {"sdl2", "python3", "kivy"}
+ bs = Bootstrap.get_bootstrap_from_recipes(recipes_sdl2, self.ctx)
+ self.assertEqual(bs.name, "sdl2")
+
+ # Test that pysdl2 or kivy alone will also yield SDL2 (dependency):
+ recipes_pysdl2_only = {"pysdl2"}
+ bs = Bootstrap.get_bootstrap_from_recipes(
+ recipes_pysdl2_only, self.ctx
+ )
+ self.assertEqual(bs.name, "sdl2")
+ recipes_kivy_only = {"kivy"}
+ bs = Bootstrap.get_bootstrap_from_recipes(
+ recipes_kivy_only, self.ctx
+ )
+ self.assertEqual(bs.name, "sdl2")
+
+ with mock.patch("pythonforandroid.recipe.Recipe.get_recipe") as \
+ mock_get_recipe:
+ # Test that something conflicting with sdl2 won't give sdl2:
+ def _add_sdl2_conflicting_recipe(name, ctx):
+ if name == "conflictswithsdl2":
+ if name not in pythonforandroid.recipe.Recipe.recipes:
+ pythonforandroid.recipe.Recipe.recipes[name] = (
+ get_fake_recipe("sdl2", conflicts=["sdl2"])
+ )
+ return original_get_recipe(name, ctx)
+ mock_get_recipe.side_effect = _add_sdl2_conflicting_recipe
+ recipes_with_sdl2_conflict = {"python3", "conflictswithsdl2"}
+ bs = Bootstrap.get_bootstrap_from_recipes(
+ recipes_with_sdl2_conflict, self.ctx
+ )
+ self.assertNotEqual(bs.name, "sdl2")
+
+ # Test using flask will default to webview:
+ recipes_with_flask = {"python3", "flask"}
+ bs = Bootstrap.get_bootstrap_from_recipes(
+ recipes_with_flask, self.ctx
+ )
+ self.assertEqual(bs.name, "webview")
+
+ # Test using random packages will default to service_only:
+ recipes_with_no_sdl2_or_web = {"python3", "numpy"}
+ bs = Bootstrap.get_bootstrap_from_recipes(
+ recipes_with_no_sdl2_or_web, self.ctx
+ )
+ self.assertEqual(bs.name, "service_only")
+
+ @mock.patch("pythonforandroid.bootstrap.ensure_dir")
+ def test_prepare_dist_dir(self, mock_ensure_dir):
+ """A test which will initialize a bootstrap and will check if the
+ method :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_dist_dir`
+ successfully calls once the method `endure_dir`
+ """
+ bs = Bootstrap().get_bootstrap("sdl2", self.ctx)
+
+ bs.prepare_dist_dir()
+ mock_ensure_dir.assert_called_once()
+
+ @mock.patch("pythonforandroid.bootstrap.open", create=True)
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.bootstrap.shutil.copy")
+ @mock.patch("pythonforandroid.bootstrap.os.makedirs")
+ def test_bootstrap_prepare_build_dir(
+ self, mock_os_makedirs, mock_shutil_copy, mock_chdir, mock_open
+ ):
+ """A test which will initialize a bootstrap and will check if the
+ method :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_build_dir`
+ successfully calls the methods that we need to prepare a build dir.
+ """
+
+ # prepare bootstrap
+ bs = Bootstrap().get_bootstrap("service_only", self.ctx)
+ self.ctx.bootstrap = bs
+
+ # test that prepare_build_dir runs (notice that we mock
+ # any file/dir creation so we can speed up the tests)
+ bs.prepare_build_dir()
+
+ # make sure that the open command has been called only once
+ mock_open.assert_called_once_with("project.properties", "w")
+
+ # check that the other mocks we made are actually called
+ mock_os_makedirs.assert_called()
+ mock_shutil_copy.assert_called()
+ mock_chdir.assert_called()
+
+ @mock.patch("pythonforandroid.bootstrap.os.path.isfile")
+ @mock.patch("pythonforandroid.bootstrap.os.path.exists")
+ @mock.patch("pythonforandroid.bootstrap.os.unlink")
+ @mock.patch("pythonforandroid.bootstrap.open", create=True)
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.bootstrap.listdir")
+ def test_bootstrap_prepare_build_dir_with_java_src(
+ self,
+ mock_listdir,
+ mock_chdir,
+ mock_open,
+ mock_os_unlink,
+ mock_os_path_exists,
+ mock_os_path_isfile,
+ ):
+ """A test which will initialize a bootstrap and will check perform
+ another test for method
+ :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_build_dir`. In
+ here we will simulate that we have `with_java_src` set to some value.
+ """
+ self.ctx.symlink_bootstrap_files = True
+ mock_listdir.return_value = [
+ "jnius",
+ "kivy",
+ "Kivy-1.11.0.dev0-py3.7.egg-info",
+ "pyjnius-1.2.1.dev0-py3.7.egg",
+ ]
+
+ # prepare bootstrap
+ bs = Bootstrap().get_bootstrap("sdl2", self.ctx)
+ self.ctx.bootstrap = bs
+
+ # test that prepare_build_dir runs (notice that we mock
+ # any file/dir creation so we can speed up the tests)
+ bs.prepare_build_dir()
+ # make sure that the open command has been called only once
+ mock_open.assert_called_with("project.properties", "w")
+
+ # check that the other mocks we made are actually called
+ mock_chdir.assert_called()
+ mock_os_unlink.assert_called()
+ mock_os_path_exists.assert_called()
+ mock_os_path_isfile.assert_called()
+
+
+class GenericBootstrapTest(BaseClassSetupBootstrap):
+ """
+ An inherited class of `BaseClassSetupBootstrap` which will extends his
+ functionality by adding some generic bootstrap tests, so this way we can
+ test all our sub modules of :mod:`~pythonforandroid.bootstraps` from within
+ this module.
+
+ .. warning:: This is supposed to be used as a base class, so please, don't
+ use this directly.
+ """
+
+ @property
+ def bootstrap_name(self):
+ """Subclasses must have property 'bootstrap_name'. It should be the
+ name of the bootstrap to test"""
+ raise NotImplementedError("Not implemented in GenericBootstrapTest")
+
+ @mock.patch("pythonforandroid.bootstraps.qt.open", create=True)
+ @mock.patch("pythonforandroid.bootstraps.service_only.open", create=True)
+ @mock.patch("pythonforandroid.bootstraps.webview.open", create=True)
+ @mock.patch("pythonforandroid.bootstraps.sdl2.open", create=True)
+ @mock.patch("pythonforandroid.distribution.open", create=True)
+ @mock.patch("pythonforandroid.bootstrap.Bootstrap.strip_libraries")
+ @mock.patch("pythonforandroid.util.exists")
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.bootstrap.listdir")
+ @mock.patch("pythonforandroid.bootstraps.sdl2.rmdir")
+ @mock.patch("pythonforandroid.bootstraps.service_only.rmdir")
+ @mock.patch("pythonforandroid.bootstraps.webview.rmdir")
+ @mock.patch("pythonforandroid.bootstrap.sh.cp")
+ def test_assemble_distribution(
+ self,
+ mock_sh_cp,
+ mock_rmdir1,
+ mock_rmdir2,
+ mock_rmdir3,
+ mock_listdir,
+ mock_chdir,
+ mock_ensure_dir,
+ mock_strip_libraries,
+ mock_open_dist_files,
+ mock_open_sdl2_files,
+ mock_open_webview_files,
+ mock_open_service_only_files,
+ mock_open_qt_files
+ ):
+ """
+ A test for any overwritten method of
+ `~pythonforandroid.bootstrap.Bootstrap.assemble_distribution`. Here we mock
+ any file/dir operation that it could slow down our tests, and there is
+ a lot to mock, because the `assemble_distribution` method it should take care
+ of prepare all compiled files to generate the final `apk`. The targets
+ of this test will be:
+
+ - :meth:`~pythonforandroid.bootstraps.sdl2.BootstrapSdl2
+ .assemble_distribution`
+ - :meth:`~pythonforandroid.bootstraps.service_only
+ .ServiceOnlyBootstrap.assemble_distribution`
+ - :meth:`~pythonforandroid.bootstraps.webview.WebViewBootstrap
+ .assemble_distribution`
+ - :meth:`~pythonforandroid.bootstraps.empty.EmptyBootstrap.
+ assemble_distribution`
+
+ Here we will tests all those methods that are specific for each class.
+ """
+ # prepare bootstrap and distribution
+ bs = Bootstrap.get_bootstrap(self.bootstrap_name, self.ctx)
+ self.assertNotEqual(bs.ctx, None)
+ bs.build_dir = bs.get_build_dir()
+ self.setUp_distribution_with_bootstrap(bs)
+
+ self.ctx.hostpython = "/some/fake/hostpython3"
+ self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx)
+ self.ctx.python_recipe.create_python_bundle = mock.MagicMock()
+ self.ctx.python_modules = ["requests"]
+ self.ctx.archs = [ArchARMv7_a(self.ctx)]
+ self.ctx.bootstrap = bs
+
+ bs.assemble_distribution()
+
+ mock_open_dist_files.assert_called_once_with("dist_info.json", "w")
+ mock_open_bootstraps = {
+ "sdl2": mock_open_sdl2_files,
+ "webview": mock_open_webview_files,
+ "service_only": mock_open_service_only_files,
+ "qt": mock_open_qt_files
+ }
+ expected_open_calls = {
+ "sdl2": [
+ mock.call("local.properties", "w"),
+ mock.call("blacklist.txt", "a"),
+ ],
+ "webview": [mock.call("local.properties", "w")],
+ "service_only": [mock.call("local.properties", "w")],
+ "qt": [mock.call("local.properties", "w")]
+ }
+ mock_open_bs = mock_open_bootstraps[self.bootstrap_name]
+ # test that the expected calls has been called
+ for expected_call in expected_open_calls[self.bootstrap_name]:
+ self.assertIn(expected_call, mock_open_bs.call_args_list)
+ # test that the write function has been called with the expected args
+ self.assertIn(
+ mock.call().__enter__().write("sdk.dir=/opt/android/android-sdk"),
+ mock_open_bs.mock_calls,
+ )
+ if self.bootstrap_name == "sdl2":
+ self.assertIn(
+ mock.call()
+ .__enter__()
+ .write("\nsqlite3/*\nlib-dynload/_sqlite3.so\n"),
+ mock_open_bs.mock_calls,
+ )
+
+ # check that the other mocks we made are actually called
+ mock_sh_cp.assert_called()
+ mock_chdir.assert_called()
+ mock_listdir.assert_called()
+ mock_strip_libraries.assert_called()
+ expected__python_bundle = os.path.join(
+ self.ctx.dist_dir,
+ self.ctx.bootstrap.distribution.name,
+ f"_python_bundle__{self.TEST_ARCH}",
+ "_python_bundle",
+ )
+ self.assertIn(
+ mock.call(expected__python_bundle, self.ctx.archs[0]),
+ self.ctx.python_recipe.create_python_bundle.call_args_list,
+ )
+
+ @mock.patch("pythonforandroid.bootstrap.shprint")
+ @mock.patch("pythonforandroid.bootstrap.glob.glob")
+ @mock.patch("pythonforandroid.bootstrap.ensure_dir")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ def test_distribute_methods(
+ self, mock_build_dir, mock_bs_dir, mock_glob, mock_shprint
+ ):
+ # prepare arch, bootstrap and distribution
+ arch = ArchARMv7_a(self.ctx)
+ bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx)
+ self.setUp_distribution_with_bootstrap(bs)
+
+ # a convenient method to reset mocks in one shot
+ def reset_mocks():
+ mock_glob.reset_mock()
+ mock_shprint.reset_mock()
+ mock_build_dir.reset_mock()
+ mock_bs_dir.reset_mock()
+
+ # test distribute_libs
+ mock_glob.return_value = [
+ "/fake_dir/libsqlite3.so",
+ "/fake_dir/libpng16.so",
+ ]
+ bs.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
+ libs_dir = os.path.join("libs", arch.arch)
+ # we expect two calls to glob/copy command via shprint
+ self.assertEqual(len(mock_glob.call_args_list), 2)
+ self.assertEqual(len(mock_shprint.call_args_list), 1)
+ self.assertEqual(
+ mock_shprint.call_args_list,
+ [mock.call(sh.cp, "-a", *mock_glob.return_value, libs_dir)]
+ )
+ mock_build_dir.assert_called()
+ mock_bs_dir.assert_called_once_with(libs_dir)
+ reset_mocks()
+
+ # test distribute_javaclasses
+ mock_glob.return_value = ["/fakedir/java_file.java"]
+ bs.distribute_javaclasses(self.ctx.javaclass_dir)
+ mock_glob.assert_called_once_with(self.ctx.javaclass_dir)
+ mock_build_dir.assert_called_with(self.ctx.javaclass_dir)
+ mock_bs_dir.assert_called_once_with("src")
+ self.assertEqual(
+ mock_shprint.call_args,
+ mock.call(sh.cp, "-a", "/fakedir/java_file.java", "src"),
+ )
+ reset_mocks()
+
+ # test distribute_aars
+ mock_glob.return_value = ["/fakedir/file.aar"]
+ bs.distribute_aars(arch)
+ mock_build_dir.assert_called_with(self.ctx.aars_dir)
+ # We expect three calls to shprint: unzip, cp, cp
+ zip_call, kw = mock_shprint.call_args_list[0]
+ self.assertEqual(zip_call[0], sh.unzip)
+ self.assertEqual(zip_call[2], "/fakedir/file.aar")
+ cp_java_call, kw = mock_shprint.call_args_list[1]
+ self.assertEqual(cp_java_call[0], sh.cp)
+ self.assertTrue(cp_java_call[2].endswith("classes.jar"))
+ self.assertEqual(cp_java_call[3], "libs/file.jar")
+ cp_libs_call, kw = mock_shprint.call_args_list[2]
+ self.assertEqual(cp_libs_call[0], sh.cp)
+ self.assertEqual(cp_libs_call[2], "/fakedir/file.aar")
+ self.assertEqual(cp_libs_call[3], libs_dir)
+ mock_bs_dir.assert_has_calls([mock.call("libs"), mock.call(libs_dir)])
+ mock_glob.assert_called()
+
+ @mock.patch("pythonforandroid.bootstrap.shprint")
+ @mock.patch("pythonforandroid.bootstrap.sh.Command")
+ @mock.patch("pythonforandroid.build.ensure_dir")
+ @mock.patch("shutil.which")
+ def test_bootstrap_strip(
+ self,
+ mock_shutil_which,
+ mock_ensure_dir,
+ mock_sh_command,
+ mock_sh_print,
+ ):
+ mock_shutil_which.return_value = os.path.join(
+ self.ctx._ndk_dir,
+ f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang",
+ )
+ # prepare arch, bootstrap, distribution and PythonRecipe
+ arch = ArchARMv7_a(self.ctx)
+ bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx)
+ self.setUp_distribution_with_bootstrap(bs)
+ self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx)
+
+ # test that strip_libraries runs with a fake distribution
+ bs.strip_libraries(arch)
+
+ self.assertEqual(
+ mock_shutil_which.call_args[0][0],
+ mock_shutil_which.return_value,
+ )
+ mock_sh_command.assert_called_once_with(
+ os.path.join(
+ self.ctx._ndk_dir,
+ f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin",
+ "llvm-strip",
+ )
+ )
+ # check that the other mocks we made are actually called
+ mock_ensure_dir.assert_called()
+ mock_sh_print.assert_called()
+
+ @mock.patch("pythonforandroid.bootstrap.listdir")
+ @mock.patch("pythonforandroid.bootstrap.rmdir")
+ @mock.patch("pythonforandroid.bootstrap.move")
+ @mock.patch("pythonforandroid.bootstrap.isdir")
+ def test_bootstrap_fry_eggs(
+ self, mock_isdir, mock_move, mock_rmdir, mock_listdir
+ ):
+ mock_listdir.return_value = [
+ "jnius",
+ "kivy",
+ "Kivy-1.11.0.dev0-py3.7.egg-info",
+ "pyjnius-1.2.1.dev0-py3.7.egg",
+ ]
+
+ # prepare bootstrap, context and distribution
+ bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx)
+ self.setUp_distribution_with_bootstrap(bs)
+
+ # test that fry_eggs runs with a fake distribution
+ site_packages = os.path.join(
+ bs.dist_dir, "_python_bundle", "_python_bundle"
+ )
+ bs.fry_eggs(site_packages)
+
+ mock_listdir.assert_has_calls(
+ [
+ mock.call(site_packages),
+ mock.call(
+ os.path.join(site_packages, "pyjnius-1.2.1.dev0-py3.7.egg")
+ ),
+ ]
+ )
+ self.assertEqual(
+ mock_rmdir.call_args[0][0], "pyjnius-1.2.1.dev0-py3.7.egg"
+ )
+ # check that the other mocks we made are actually called
+ mock_isdir.assert_called()
+ mock_move.assert_called()
+
+
+class TestBootstrapSdl2(GenericBootstrapTest, unittest.TestCase):
+ """
+ An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which
+ will be used to perform tests for
+ :class:`~pythonforandroid.bootstraps.sdl2.BootstrapSdl2`.
+ """
+
+ @property
+ def bootstrap_name(self):
+ return "sdl2"
+
+
+class TestBootstrapServiceOnly(GenericBootstrapTest, unittest.TestCase):
+ """
+ An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which
+ will be used to perform tests for
+ :class:`~pythonforandroid.bootstraps.service_only.ServiceOnlyBootstrap`.
+ """
+
+ @property
+ def bootstrap_name(self):
+ return "service_only"
+
+
+class TestBootstrapWebview(GenericBootstrapTest, unittest.TestCase):
+ """
+ An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which
+ will be used to perform tests for
+ :class:`~pythonforandroid.bootstraps.webview.WebViewBootstrap`.
+ """
+
+ @property
+ def bootstrap_name(self):
+ return "webview"
+
+
+class TestBootstrapEmpty(GenericBootstrapTest, unittest.TestCase):
+ """
+ An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which
+ will be used to perform tests for
+ :class:`~pythonforandroid.bootstraps.empty.EmptyBootstrap`.
+
+ .. note:: here will test most of the base class methods, because we only
+ overwrite :meth:`~pythonforandroid.bootstraps.empty.
+ EmptyBootstrap.assemble_distribution`
+ """
+
+ @property
+ def bootstrap_name(self):
+ return "empty"
+
+ def test_assemble_distribution(self, *args):
+ # prepare bootstrap
+ bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx)
+ self.ctx.bootstrap = bs
+
+ # test dist_dir error
+ with self.assertRaises(SystemExit) as e:
+ bs.assemble_distribution()
+ self.assertEqual(e.exception.args[0], 1)
+
+
+class TestBootstrapQt(GenericBootstrapTest, unittest.TestCase):
+ """
+ An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which
+ will be used to perform tests for
+ :class:`~pythonforandroid.bootstraps.qt.BootstrapQt`.
+ """
+
+ @property
+ def bootstrap_name(self):
+ return "qt"
diff --git a/tests/test_bootstrap_build.py b/tests/test_bootstrap_build.py
new file mode 100644
index 000000000..ff5f7dcac
--- /dev/null
+++ b/tests/test_bootstrap_build.py
@@ -0,0 +1,180 @@
+import unittest
+from unittest import mock
+import pytest
+import os
+
+from pythonforandroid.util import load_source
+
+
+class TestBootstrapBuild(unittest.TestCase):
+ def setUp(self):
+ os.environ["P4A_BUILD_IS_RUNNING_UNITTESTS"] = "1"
+
+ build_src = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ "../pythonforandroid/bootstraps/common/build/build.py",
+ )
+
+ self.buildpy = load_source("buildpy", build_src)
+ self.buildpy.get_bootstrap_name = mock.Mock(return_value="sdl2")
+
+ self.ap = self.buildpy.create_argument_parser()
+
+ self.common_args = [
+ "--package",
+ "org.test.app",
+ "--name",
+ "TestApp",
+ "--version",
+ "0.1",
+ ]
+
+
+class TestParsePermissions(TestBootstrapBuild):
+ def test_parse_permissions_with_migrations(self):
+ # Test that permissions declared in the old format are migrated to the
+ # new format.
+ # (Users can new declare permissions in both formats, even a mix)
+
+ self.ap = self.buildpy.create_argument_parser()
+
+ args = [
+ *self.common_args,
+ "--permission",
+ "INTERNET",
+ "--permission",
+ "com.android.voicemail.permission.ADD_VOICEMAIL",
+ "--permission",
+ "(name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)",
+ "--permission",
+ "(name=android.permission.BLUETOOTH_SCAN;usesPermissionFlags=neverForLocation)",
+ ]
+
+ args = self.ap.parse_args(args)
+
+ parsed_permissions = self.buildpy.parse_permissions(args.permissions)
+
+ assert parsed_permissions == [
+ dict(name="android.permission.INTERNET"),
+ dict(name="com.android.voicemail.permission.ADD_VOICEMAIL"),
+ dict(name="android.permission.WRITE_EXTERNAL_STORAGE", maxSdkVersion="18"),
+ dict(
+ name="android.permission.BLUETOOTH_SCAN",
+ usesPermissionFlags="neverForLocation",
+ ),
+ ]
+
+ def test_parse_permissions_invalid_property(self):
+
+ self.ap = self.buildpy.create_argument_parser()
+
+ args = [
+ *self.common_args,
+ "--permission",
+ "(name=android.permission.BLUETOOTH_SCAN;propertyThatFails=neverForLocation)",
+ ]
+
+ args = self.ap.parse_args(args)
+
+ with pytest.raises(
+ ValueError, match="Property 'propertyThatFails' is not supported."
+ ):
+ self.buildpy.parse_permissions(args.permissions)
+
+
+class TestOrientationArg(TestBootstrapBuild):
+ def test_no_orientation_args(self):
+
+ args = self.common_args
+
+ args = self.ap.parse_args(args)
+
+ assert (
+ self.buildpy.get_manifest_orientation(
+ args.orientation, args.manifest_orientation
+ )
+ == "unspecified"
+ )
+ assert self.buildpy.get_sdl_orientation_hint(args.orientation) == ""
+
+ def test_manifest_orientation_present(self):
+
+ args = [
+ *self.common_args,
+ "--orientation",
+ "landscape",
+ "--orientation",
+ "portrait",
+ "--manifest-orientation",
+ "fullSensor",
+ ]
+
+ args = self.ap.parse_args(args)
+
+ assert (
+ self.buildpy.get_manifest_orientation(
+ args.orientation, manifest_orientation=args.manifest_orientation
+ )
+ == "fullSensor"
+ )
+
+ def test_manifest_orientation_supported(self):
+
+ args = [*self.common_args, "--orientation", "landscape"]
+
+ args = self.ap.parse_args(args)
+
+ assert (
+ self.buildpy.get_manifest_orientation(
+ args.orientation, manifest_orientation=args.manifest_orientation
+ )
+ == "landscape"
+ )
+
+ def test_android_manifest_multiple_orientation_supported(self):
+
+ args = [
+ *self.common_args,
+ "--orientation",
+ "landscape",
+ "--orientation",
+ "portrait",
+ ]
+
+ args = self.ap.parse_args(args)
+
+ assert (
+ self.buildpy.get_manifest_orientation(
+ args.orientation, manifest_orientation=args.manifest_orientation
+ )
+ == "unspecified"
+ )
+
+ def test_sdl_orientation_hint_single(self):
+
+ args = [*self.common_args, "--orientation", "landscape"]
+
+ args = self.ap.parse_args(args)
+
+ assert (
+ self.buildpy.get_sdl_orientation_hint(args.orientation) == "LandscapeLeft"
+ )
+
+ def test_sdl_orientation_hint_multiple(self):
+
+ args = [
+ *self.common_args,
+ "--orientation",
+ "landscape",
+ "--orientation",
+ "portrait",
+ ]
+
+ args = self.ap.parse_args(args)
+
+ sdl_orientation_hint = self.buildpy.get_sdl_orientation_hint(
+ args.orientation
+ ).split(" ")
+
+ assert "LandscapeLeft" in sdl_orientation_hint
+ assert "Portrait" in sdl_orientation_hint
diff --git a/tests/test_build.py b/tests/test_build.py
new file mode 100644
index 000000000..7556d68bb
--- /dev/null
+++ b/tests/test_build.py
@@ -0,0 +1,135 @@
+import os
+import unittest
+from unittest import mock
+
+import jinja2
+
+from pythonforandroid.build import (
+ Context, RECOMMENDED_TARGET_API, run_pymodules_install,
+)
+from pythonforandroid.archs import ArchARMv7_a, ArchAarch_64
+
+
+class TestBuildBasic(unittest.TestCase):
+
+ def test_run_pymodules_install_optional_project_dir(self):
+ """
+ Makes sure the `run_pymodules_install()` doesn't crash when the
+ `project_dir` optional parameter is None, refs #1898
+ """
+ ctx = mock.Mock()
+ ctx.archs = [ArchARMv7_a(ctx), ArchAarch_64(ctx)]
+ modules = []
+ project_dir = None
+ with mock.patch('pythonforandroid.build.info') as m_info:
+ assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None
+ assert m_info.call_args_list[-1] == mock.call(
+ 'No Python modules and no setup.py to process, skipping')
+
+ def test_strip_if_with_debug_symbols(self):
+ ctx = mock.Mock()
+ ctx.python_recipe.major_minor_version_string = "3.6"
+ ctx.get_site_packages_dir.return_value = "test-doesntexist"
+ ctx.build_dir = "nonexistant_directory"
+ ctx.archs = ["arm64"]
+
+ modules = ["mymodule"]
+ project_dir = None
+ with mock.patch('pythonforandroid.build.info'), \
+ mock.patch('sh.Command'), \
+ mock.patch('pythonforandroid.build.open'), \
+ mock.patch('pythonforandroid.build.shprint'), \
+ mock.patch('pythonforandroid.build.current_directory'), \
+ mock.patch('pythonforandroid.build.CythonRecipe') as m_CythonRecipe, \
+ mock.patch('pythonforandroid.build.project_has_setup_py') as m_project_has_setup_py, \
+ mock.patch('pythonforandroid.build.run_setuppy_install'):
+ m_project_has_setup_py.return_value = False
+
+ # Make sure it is NOT called when `with_debug_symbols` is true:
+ ctx.with_debug_symbols = True
+ assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None
+ assert m_CythonRecipe().strip_object_files.called is False
+
+ # Make sure strip object files IS called when
+ # `with_debug_symbols` is fasle:
+ ctx.with_debug_symbols = False
+ assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None
+ assert m_CythonRecipe().strip_object_files.called is True
+
+
+class TestTemplates(unittest.TestCase):
+
+ def test_android_manifest_xml(self):
+ args = mock.Mock()
+ args.min_sdk_version = 12
+ args.build_mode = 'debug'
+ args.native_services = ['abcd', ]
+ args.permissions = [
+ dict(name="android.permission.INTERNET"),
+ dict(name="android.permission.WRITE_EXTERNAL_STORAGE", maxSdkVersion=18),
+ dict(name="android.permission.BLUETOOTH_SCAN", usesPermissionFlags="neverForLocation")]
+ args.add_activity = []
+ args.android_used_libs = []
+ args.meta_data = []
+ args.extra_manifest_xml = '<tag-a><tag-b></tag-b></tag-a>'
+ args.extra_manifest_application_arguments = 'android:someParameter="true" android:anotherParameter="false"'
+ render_args = {
+ "args": args,
+ "service": False,
+ "service_names": [],
+ "android_api": 1234,
+ "debug": "debug" in args.build_mode,
+ "native_services": args.native_services
+ }
+ environment = jinja2.Environment(
+ loader=jinja2.FileSystemLoader('pythonforandroid/bootstraps/sdl2/build/templates/')
+ )
+ template = environment.get_template('AndroidManifest.tmpl.xml')
+ xml = template.render(**render_args)
+ assert xml.count('android:minSdkVersion="12"') == 1
+ assert xml.count('android:anotherParameter="false"') == 1
+ assert xml.count('android:someParameter="true"') == 1
+ assert xml.count('<tag-a><tag-b></tag-b></tag-a>') == 1
+ assert xml.count('android:process=":service_') == 0
+ assert xml.count('targetSdkVersion="1234"') == 1
+ assert xml.count('android:debuggable="true"') == 1
+ assert xml.count('<service android:name="abcd" />') == 1
+ assert xml.count('<uses-permission android:name="android.permission.INTERNET" />') == 1
+ assert xml.count('<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />') == 1
+ assert xml.count('<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />') == 1
+ # TODO: potentially some other checks to be added here to cover other "logic" (flags and loops) in the template
+
+
+class TestContext(unittest.TestCase):
+
+ @mock.patch.dict('pythonforandroid.build.Context.env')
+ @mock.patch('pythonforandroid.build.get_available_apis')
+ @mock.patch('pythonforandroid.build.ensure_dir')
+ def test_sdk_ndk_paths(
+ self,
+ mock_ensure_dir,
+ mock_get_available_apis,
+ ):
+ mock_get_available_apis.return_value = [RECOMMENDED_TARGET_API]
+ context = Context()
+ context.setup_dirs(os.getcwd())
+ context.prepare_build_environment(
+ user_sdk_dir='sdk',
+ user_ndk_dir='ndk',
+ user_android_api=None,
+ user_ndk_api=None,
+ )
+
+ # The context was supplied with relative SDK and NDK dirs. Check
+ # that it resolved them to absolute paths.
+ real_sdk_dir = os.path.join(os.getcwd(), 'sdk')
+ real_ndk_dir = os.path.join(os.getcwd(), 'ndk')
+ assert context.sdk_dir == real_sdk_dir
+ assert context.ndk_dir == real_ndk_dir
+
+ context_paths = context.env['PATH'].split(':')
+ assert context_paths[0:3] == [
+ f'{real_ndk_dir}/toolchains/llvm/prebuilt/{context.ndk.host_tag}/bin',
+ real_ndk_dir,
+ f'{real_sdk_dir}/tools'
+ ]
diff --git a/tests/test_distribution.py b/tests/test_distribution.py
new file mode 100644
index 000000000..404091ca7
--- /dev/null
+++ b/tests/test_distribution.py
@@ -0,0 +1,274 @@
+import os
+import json
+import unittest
+from unittest import mock
+
+from pythonforandroid.bootstrap import Bootstrap
+from pythonforandroid.distribution import Distribution
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import BuildInterruptingException
+from pythonforandroid.build import Context
+
+dist_info_data = {
+ "dist_name": "sdl2_dist",
+ "bootstrap": "sdl2",
+ "archs": ["armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a"],
+ "ndk_api": 21,
+ "use_setup_py": False,
+ "recipes": ["hostpython3", "python3", "sdl2", "kivy", "requests"],
+ "hostpython": "/some/fake/hostpython3",
+ "python_version": "3.7",
+}
+
+
+class TestDistribution(unittest.TestCase):
+ """
+ An inherited class of `unittest.TestCase`to test the module
+ :mod:`~pythonforandroid.distribution`.
+ """
+
+ TEST_ARCH = 'armeabi-v7a'
+
+ def setUp(self):
+ """Configure a :class:`~pythonforandroid.build.Context` so we can
+ perform our unittests"""
+ self.ctx = Context()
+ self.ctx.ndk_api = 21
+ self.ctx.android_api = 27
+ self.ctx._sdk_dir = "/opt/android/android-sdk"
+ self.ctx._ndk_dir = "/opt/android/android-ndk"
+ self.ctx.setup_dirs(os.getcwd())
+ self.ctx.recipe_build_order = [
+ "hostpython3",
+ "python3",
+ "sdl2",
+ "kivy",
+ ]
+
+ def setUp_distribution_with_bootstrap(self, bs, **kwargs):
+ """Extend the setUp by configuring a distribution, because some test
+ needs a distribution to be set to be properly tested"""
+ self.ctx.bootstrap = bs
+ self.ctx.bootstrap.distribution = Distribution.get_distribution(
+ self.ctx,
+ name=kwargs.pop("name", "test_prj"),
+ recipes=kwargs.pop("recipes", ["python3", "kivy"]),
+ archs=[self.TEST_ARCH],
+ **kwargs
+ )
+
+ def tearDown(self):
+ """Here we make sure that we reset a possible bootstrap created in
+ `setUp_distribution_with_bootstrap`"""
+ self.ctx.bootstrap = None
+
+ def test_properties(self):
+ """Test that some attributes has the expected result (for now, we check
+ that `__repr__` and `__str__` return the proper values"""
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap().get_bootstrap("sdl2", self.ctx)
+ )
+ distribution = self.ctx.bootstrap.distribution
+ self.assertEqual(self.ctx, distribution.ctx)
+ expected_repr = (
+ "<Distribution: name test_prj with recipes (python3, kivy)>"
+ )
+ self.assertEqual(distribution.__str__(), expected_repr)
+ self.assertEqual(distribution.__repr__(), expected_repr)
+
+ @mock.patch("pythonforandroid.distribution.exists")
+ def test_folder_exist(self, mock_exists):
+ """Test that method
+ :meth:`~pythonforandroid.distribution.Distribution.folder_exist` is
+ called once with the proper arguments."""
+
+ mock_exists.return_value = False
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap.get_bootstrap("sdl2", self.ctx)
+ )
+ self.ctx.bootstrap.distribution.folder_exists()
+ mock_exists.assert_called_with(
+ self.ctx.bootstrap.distribution.dist_dir
+ )
+
+ @mock.patch("pythonforandroid.distribution.rmdir")
+ def test_delete(self, mock_rmdir):
+ """Test that method
+ :meth:`~pythonforandroid.distribution.Distribution.delete` is
+ called once with the proper arguments."""
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap().get_bootstrap("sdl2", self.ctx)
+ )
+ self.ctx.bootstrap.distribution.delete()
+ mock_rmdir.assert_called_once_with(
+ self.ctx.bootstrap.distribution.dist_dir
+ )
+
+ @mock.patch("pythonforandroid.distribution.exists")
+ def test_get_distribution_no_name(self, mock_exists):
+ """Test that method
+ :meth:`~pythonforandroid.distribution.Distribution.get_distribution`
+ returns the proper result which should `unnamed_dist_1`."""
+ mock_exists.return_value = False
+ self.ctx.bootstrap = Bootstrap().get_bootstrap("sdl2", self.ctx)
+ dist = Distribution.get_distribution(self.ctx, archs=[self.TEST_ARCH])
+ self.assertEqual(dist.name, "unnamed_dist_1")
+
+ @mock.patch("pythonforandroid.util.chdir")
+ @mock.patch("pythonforandroid.distribution.open", create=True)
+ def test_save_info(self, mock_open_dist_info, mock_chdir):
+ """Test that method
+ :meth:`~pythonforandroid.distribution.Distribution.save_info`
+ is called once with the proper arguments."""
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap().get_bootstrap("sdl2", self.ctx)
+ )
+ self.ctx.hostpython = "/some/fake/hostpython3"
+ self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx)
+ self.ctx.python_modules = ["requests"]
+ mock_open_dist_info.side_effect = [
+ mock.mock_open(read_data=json.dumps(dist_info_data)).return_value
+ ]
+ self.ctx.bootstrap.distribution.save_info("/fake_dir")
+ mock_open_dist_info.assert_called_once_with("dist_info.json", "w")
+ mock_open_dist_info.reset_mock()
+
+ @mock.patch("pythonforandroid.distribution.open", create=True)
+ @mock.patch("pythonforandroid.distribution.exists")
+ @mock.patch("pythonforandroid.distribution.glob.glob")
+ def test_get_distributions(
+ self, mock_glob, mock_exists, mock_open_dist_info
+ ):
+ """Test that method
+ :meth:`~pythonforandroid.distribution.Distribution.get_distributions`
+ returns some expected values:
+
+ - A list of instances of class
+ `~pythonforandroid.distribution.Distribution
+ - That one of the distributions returned in the result has the
+ proper values (`name`, `ndk_api` and `recipes`)
+ """
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap().get_bootstrap("sdl2", self.ctx)
+ )
+ mock_glob.return_value = ["sdl2-python3"]
+ mock_open_dist_info.side_effect = [
+ mock.mock_open(read_data=json.dumps(dist_info_data)).return_value
+ ]
+
+ dists = self.ctx.bootstrap.distribution.get_distributions(self.ctx)
+ self.assertIsInstance(dists, list)
+ self.assertEqual(len(dists), 1)
+ self.assertIsInstance(dists[0], Distribution)
+ self.assertEqual(dists[0].name, "sdl2_dist")
+ self.assertEqual(dists[0].dist_dir, "sdl2-python3")
+ self.assertEqual(dists[0].ndk_api, 21)
+ self.assertEqual(
+ dists[0].recipes,
+ ["hostpython3", "python3", "sdl2", "kivy", "requests"],
+ )
+ mock_open_dist_info.assert_called_with("sdl2-python3/dist_info.json")
+ mock_open_dist_info.reset_mock()
+
+ @mock.patch("pythonforandroid.distribution.open", create=True)
+ @mock.patch("pythonforandroid.distribution.exists")
+ @mock.patch("pythonforandroid.distribution.glob.glob")
+ def test_get_distributions_error_ndk_api(
+ self, mock_glob, mock_exists, mock_open_dist_info
+ ):
+ """Test method
+ :meth:`~pythonforandroid.distribution.Distribution.get_distributions`
+ in case that `ndk_api` is not set..which should return a `None`.
+ """
+ dist_info_data_no_ndk_api = dist_info_data.copy()
+ dist_info_data_no_ndk_api.pop("ndk_api")
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap().get_bootstrap("sdl2", self.ctx)
+ )
+ mock_glob.return_value = ["sdl2-python3"]
+ mock_open_dist_info.side_effect = [
+ mock.mock_open(
+ read_data=json.dumps(dist_info_data_no_ndk_api)
+ ).return_value
+ ]
+
+ dists = self.ctx.bootstrap.distribution.get_distributions(self.ctx)
+ self.assertEqual(dists[0].ndk_api, None)
+ mock_open_dist_info.assert_called_with("sdl2-python3/dist_info.json")
+ mock_open_dist_info.reset_mock()
+
+ @mock.patch("pythonforandroid.distribution.Distribution.get_distributions")
+ @mock.patch("pythonforandroid.distribution.exists")
+ @mock.patch("pythonforandroid.distribution.glob.glob")
+ def test_get_distributions_error_ndk_api_mismatch(
+ self, mock_glob, mock_exists, mock_get_dists
+ ):
+ """Test that method
+ :meth:`~pythonforandroid.distribution.Distribution.get_distribution`
+ raises an error in case that we have some distribution already build,
+ with a given `name` and `ndk_api`, and we try to get another
+ distribution with the same `name` but different `ndk_api`.
+ """
+ expected_dist = Distribution.get_distribution(
+ self.ctx,
+ name="test_prj",
+ recipes=["python3", "kivy"],
+ archs=[self.TEST_ARCH],
+ )
+ mock_get_dists.return_value = [expected_dist]
+ mock_glob.return_value = ["sdl2-python3"]
+
+ with self.assertRaises(BuildInterruptingException) as e:
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap().get_bootstrap("sdl2", self.ctx),
+ allow_replace_dist=False,
+ ndk_api=22,
+ )
+ self.assertEqual(
+ e.exception.args[0],
+ "Asked for dist with name test_prj with recipes (python3, kivy)"
+ " and NDK API 22, but a dist with this name already exists and has"
+ " either incompatible recipes (python3, kivy) or NDK API 21",
+ )
+
+ def test_get_distributions_error_extra_dist_dirs(self):
+ """Test that method
+ :meth:`~pythonforandroid.distribution.Distribution.get_distributions`
+ raises an exception of
+ :class:`~pythonforandroid.util.BuildInterruptingException` in case that
+ we supply the kwargs `extra_dist_dirs`.
+ """
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap().get_bootstrap("sdl2", self.ctx)
+ )
+ with self.assertRaises(BuildInterruptingException) as e:
+ self.ctx.bootstrap.distribution.get_distributions(
+ self.ctx, extra_dist_dirs=["/fake/extra/dist_dirs"]
+ )
+ self.assertEqual(
+ e.exception.args[0],
+ "extra_dist_dirs argument to get"
+ "_distributions is not yet implemented",
+ )
+
+ @mock.patch("pythonforandroid.distribution.Distribution.get_distributions")
+ def test_get_distributions_possible_dists(self, mock_get_dists):
+ """Test that method
+ :meth:`~pythonforandroid.distribution.Distribution.get_distributions`
+ returns the proper
+ `:class:`~pythonforandroid.distribution.Distribution` in case that we
+ already have it build and we request the same
+ `:class:`~pythonforandroid.distribution.Distribution`.
+ """
+ expected_dist = Distribution.get_distribution(
+ self.ctx,
+ name="test_prj",
+ recipes=["python3", "kivy"],
+ archs=[self.TEST_ARCH],
+ )
+ mock_get_dists.return_value = [expected_dist]
+ self.setUp_distribution_with_bootstrap(
+ Bootstrap().get_bootstrap("sdl2", self.ctx), name="test_prj"
+ )
+ dists = self.ctx.bootstrap.distribution.get_distributions(self.ctx)
+ self.assertEqual(dists[0], expected_dist)
diff --git a/tests/test_graph.py b/tests/test_graph.py
index fbec5c1cb..f7647bcac 100644
--- a/tests/test_graph.py
+++ b/tests/test_graph.py
@@ -1,49 +1,234 @@
-
from pythonforandroid.build import Context
-from pythonforandroid.graph import get_recipe_order_and_bootstrap
+from pythonforandroid.graph import (
+ fix_deplist, get_dependency_tuple_list_for_recipe,
+ get_recipe_order_and_bootstrap, obvious_conflict_checker,
+)
from pythonforandroid.bootstrap import Bootstrap
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.util import BuildInterruptingException
from itertools import product
+from unittest import mock
import pytest
-
ctx = Context()
-name_sets = [['python2'],
+name_sets = [['python3'],
['kivy']]
bootstraps = [None,
- Bootstrap.get_bootstrap('pygame', ctx),
Bootstrap.get_bootstrap('sdl2', ctx)]
valid_combinations = list(product(name_sets, bootstraps))
valid_combinations.extend(
- [(['python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx)),
- (['kivy', 'python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx))])
+ [(['python3'], Bootstrap.get_bootstrap('sdl2', ctx)),
+ (['kivy', 'python3'], Bootstrap.get_bootstrap('sdl2', ctx)),
+ (['flask'], Bootstrap.get_bootstrap('webview', ctx)),
+ (['pysdl2'], None), # auto-detect bootstrap! important corner case
+ ]
+)
+invalid_combinations = [
+ [['pil', 'pillow'], None],
+ [['pysdl2', 'genericndkbuild'], None],
+]
+invalid_combinations_simple = list(invalid_combinations)
+# NOTE !! keep in mind when setting invalid_combinations_simple:
+#
+# This is used to test obvious_conflict_checker(), which only
+# catches CERTAIN conflicts:
+#
+# This must be a list of conflicts where the conflict is ONLY in
+# non-tuple/non-ambiguous dependencies, e.g.:
+#
+# dependencies_1st = ["python2", "pillow"]
+# dependencies_2nd = ["python3", "pillow"]
+#
+# This however won't work:
+#
+# dependencies_1st = [("python2", "python3"), "pillow"]
+#
+# (This is simply because the conflict checker doesn't resolve this to
+# keep the code ismple enough)
+
+
+def get_fake_recipe(name, depends=None, conflicts=None):
+ recipe = mock.Mock()
+ recipe.name = name
+ recipe.get_opt_depends_in_list = lambda: []
+ recipe.get_dir_name = lambda: name
+ recipe.depends = list(depends or [])
+ recipe.conflicts = list(conflicts or [])
+ return recipe
+
+
+def register_fake_recipes_for_test(monkeypatch, recipe_list):
+ _orig_get_recipe = Recipe.get_recipe
+
+ def mock_get_recipe(name, ctx):
+ for recipe in recipe_list:
+ if recipe.name == name:
+ return recipe
+ return _orig_get_recipe(name, ctx)
+ # Note: staticmethod() needed for python ONLY, don't ask me why:
+ monkeypatch.setattr(Recipe, 'get_recipe', staticmethod(mock_get_recipe))
@pytest.mark.parametrize('names,bootstrap', valid_combinations)
def test_valid_recipe_order_and_bootstrap(names, bootstrap):
get_recipe_order_and_bootstrap(ctx, names, bootstrap)
-invalid_combinations = [[['python2', 'python3crystax'], None],
- [['python3'], Bootstrap.get_bootstrap('pygame', ctx)]]
-
@pytest.mark.parametrize('names,bootstrap', invalid_combinations)
def test_invalid_recipe_order_and_bootstrap(names, bootstrap):
- with pytest.raises(SystemExit):
+ with pytest.raises(BuildInterruptingException) as e_info:
get_recipe_order_and_bootstrap(ctx, names, bootstrap)
+ assert "conflict" in e_info.value.message.lower()
+
+
+def test_blacklist():
+ # First, get order without blacklist:
+ build_order, python_modules, bs = get_recipe_order_and_bootstrap(
+ ctx, ["python3", "kivy"], None
+ )
+ # Now, obtain again with blacklist:
+ build_order_2, python_modules_2, bs_2 = get_recipe_order_and_bootstrap(
+ ctx, ["python3", "kivy"], None, blacklist=["libffi"]
+ )
+ assert "libffi" not in build_order_2
+ assert set(build_order_2).union({"libffi"}) == set(build_order)
+
+ # Check that we get a conflict when using webview and kivy combined:
+ wbootstrap = Bootstrap.get_bootstrap('webview', ctx)
+ with pytest.raises(BuildInterruptingException) as e_info:
+ get_recipe_order_and_bootstrap(ctx, ["flask", "kivy"], wbootstrap)
+ assert "conflict" in e_info.value.message.lower()
+
+ # We should no longer get a conflict blacklisting sdl2:
+ get_recipe_order_and_bootstrap(
+ ctx, ["flask", "kivy"], wbootstrap, blacklist=["sdl2"]
+ )
+
+
+def test_get_dependency_tuple_list_for_recipe(monkeypatch):
+ r = get_fake_recipe("recipe1", depends=[
+ "libffi",
+ ("libffi", "Pillow")
+ ])
+ dep_list = get_dependency_tuple_list_for_recipe(
+ r, blacklist={"libffi"}
+ )
+ assert dep_list == [("pillow",)]
+
+
+@pytest.mark.parametrize('names,bootstrap', valid_combinations)
+def test_valid_obvious_conflict_checker(names, bootstrap):
+ # Note: obvious_conflict_checker is stricter on input
+ # (needs fix_deplist) than get_recipe_order_and_bootstrap!
+ obvious_conflict_checker(ctx, fix_deplist(names))
+
+
+@pytest.mark.parametrize('names,bootstrap',
+ invalid_combinations_simple # see above for why this
+ ) # is a separate list
+def test_invalid_obvious_conflict_checker(names, bootstrap):
+ # Note: obvious_conflict_checker is stricter on input
+ # (needs fix_deplist) than get_recipe_order_and_bootstrap!
+ with pytest.raises(BuildInterruptingException) as e_info:
+ obvious_conflict_checker(ctx, fix_deplist(names))
+ assert "conflict" in e_info.value.message.lower()
+
+
+def test_misc_obvious_conflict_checker(monkeypatch):
+ # Check that the assert about wrong input data is hit:
+ with pytest.raises(AssertionError) as e_info:
+ obvious_conflict_checker(
+ ctx,
+ ["this_is_invalid"]
+ # (invalid because it isn't properly nested as tuple)
+ )
+
+ # Test that non-recipe dependencies work in overall:
+ obvious_conflict_checker(
+ ctx, fix_deplist(["python3", "notarecipelibrary"])
+ )
+
+ # Test that a conflict with a non-recipe dependency works:
+ # This is currently not used, so we need a custom test recipe:
+ # To get that, we simply modify one!
+ with monkeypatch.context() as m:
+ register_fake_recipes_for_test(m, [
+ get_fake_recipe("recipe1", conflicts=[("fakelib")]),
+ ])
+ with pytest.raises(BuildInterruptingException) as e_info:
+ obvious_conflict_checker(ctx, fix_deplist(["recipe1", "fakelib"]))
+ assert "conflict" in e_info.value.message.lower()
+
+ # Test a case where a recipe pulls in a conditional tuple
+ # of additional dependencies. This is e.g. done for ('python3',
+ # 'python2', ...) but most recipes don't depend on this anymore,
+ # so we need to add a manual test for this case:
+ with monkeypatch.context() as m:
+ register_fake_recipes_for_test(m, [
+ get_fake_recipe("recipe1", depends=[("libffi", "Pillow")]),
+ ])
+ obvious_conflict_checker(ctx, fix_deplist(["recipe1"]))
+
+
+def test_indirectconflict_obvious_conflict_checker(monkeypatch):
+ # Test a case where there's an indirect conflict, which also
+ # makes sure the error message correctly blames the OUTER recipes
+ # as original conflict source:
+ with monkeypatch.context() as m:
+ register_fake_recipes_for_test(m, [
+ get_fake_recipe("outerrecipe1", depends=["innerrecipe1"]),
+ get_fake_recipe("outerrecipe2", depends=["innerrecipe2"]),
+ get_fake_recipe("innerrecipe1"),
+ get_fake_recipe("innerrecipe2", conflicts=["innerrecipe1"]),
+ ])
+ with pytest.raises(BuildInterruptingException) as e_info:
+ obvious_conflict_checker(
+ ctx,
+ fix_deplist(["outerrecipe1", "outerrecipe2"])
+ )
+ assert ("conflict" in e_info.value.message.lower() and
+ "outerrecipe1" in e_info.value.message.lower() and
+ "outerrecipe2" in e_info.value.message.lower())
+
+
+def test_multichoice_obvious_conflict_checker(monkeypatch):
+ # Test a case where there's a conflict with a multi-choice tuple:
+ with monkeypatch.context() as m:
+ register_fake_recipes_for_test(m, [
+ get_fake_recipe("recipe1", conflicts=["lib1", "lib2"]),
+ get_fake_recipe("recipe2", depends=[("lib1", "lib2")]),
+ ])
+ with pytest.raises(BuildInterruptingException) as e_info:
+ obvious_conflict_checker(
+ ctx,
+ fix_deplist([("lib1", "lib2"), "recipe1"])
+ )
+ assert "conflict" in e_info.value.message.lower()
def test_bootstrap_dependency_addition():
build_order, python_modules, bs = get_recipe_order_and_bootstrap(
ctx, ['kivy'], None)
- assert (('hostpython2' in build_order) or ('hostpython3' in build_order))
+ assert ('hostpython3' in build_order)
+
+
+def test_graph_deplist_transformation():
+ test_pairs = [
+ (["Pillow", ('python2', 'python3')],
+ [('pillow',), ('python2', 'python3')]),
+ (["Pillow", ('python2',)],
+ [('pillow',), ('python2',)]),
+ ]
+ for (before_list, after_list) in test_pairs:
+ assert fix_deplist(before_list) == after_list
def test_bootstrap_dependency_addition2():
build_order, python_modules, bs = get_recipe_order_and_bootstrap(
- ctx, ['kivy', 'python2'], None)
- assert 'hostpython2' in build_order
+ ctx, ['kivy', 'python3'], None)
+ assert 'hostpython3' in build_order
if __name__ == "__main__":
diff --git a/tests/test_logger.py b/tests/test_logger.py
new file mode 100644
index 000000000..773e7e54a
--- /dev/null
+++ b/tests/test_logger.py
@@ -0,0 +1,18 @@
+import unittest
+from unittest.mock import MagicMock
+from pythonforandroid import logger
+
+
+class TestShprint(unittest.TestCase):
+
+ def test_unicode_encode(self):
+ """
+ Makes sure `shprint()` can handle unicode command output.
+ Running the test with PYTHONIOENCODING=ASCII env would fail, refs:
+ https://github.com/kivy/python-for-android/issues/1654
+ """
+ expected_command_output = ["foo\xa0bar"]
+ command = MagicMock()
+ command.return_value = expected_command_output
+ output = logger.shprint(command, 'a1', k1='k1')
+ self.assertEqual(output, expected_command_output)
diff --git a/tests/test_prerequisites.py b/tests/test_prerequisites.py
new file mode 100644
index 000000000..70ffa0c0d
--- /dev/null
+++ b/tests/test_prerequisites.py
@@ -0,0 +1,302 @@
+import unittest
+from unittest import mock, skipIf
+
+import sys
+
+from pythonforandroid.prerequisites import (
+ JDKPrerequisite,
+ HomebrewPrerequisite,
+ OpenSSLPrerequisite,
+ AutoconfPrerequisite,
+ AutomakePrerequisite,
+ LibtoolPrerequisite,
+ PkgConfigPrerequisite,
+ CmakePrerequisite,
+ get_required_prerequisites,
+)
+
+
+class PrerequisiteSetUpBaseClass:
+ def setUp(self):
+ self.mandatory = dict(linux=False, darwin=False)
+ self.installer_is_supported = dict(linux=False, darwin=False)
+ self.expected_homebrew_formula_name = ""
+
+ def test_is_mandatory_on_darwin(self):
+ assert self.prerequisite.mandatory["darwin"] == self.mandatory["darwin"]
+
+ def test_is_mandatory_on_linux(self):
+ assert self.prerequisite.mandatory["linux"] == self.mandatory["linux"]
+
+ def test_installer_is_supported_on_darwin(self):
+ assert (
+ self.prerequisite.installer_is_supported["darwin"]
+ == self.installer_is_supported["darwin"]
+ )
+
+ def test_installer_is_supported_on_linux(self):
+ assert (
+ self.prerequisite.installer_is_supported["linux"]
+ == self.installer_is_supported["linux"]
+ )
+
+ def test_darwin_pkg_config_location(self):
+ self.assertEqual(self.prerequisite.darwin_pkg_config_location(), "")
+
+ def test_linux_pkg_config_location(self):
+ self.assertEqual(self.prerequisite.linux_pkg_config_location(), "")
+
+ @skipIf(sys.platform != "darwin", "Only run on macOS")
+ def test_pkg_config_location_property__darwin(self):
+ self.assertEqual(
+ self.prerequisite.pkg_config_location,
+ self.prerequisite.darwin_pkg_config_location(),
+ )
+
+ @skipIf(sys.platform != "linux", "Only run on Linux")
+ def test_pkg_config_location_property__linux(self):
+ self.assertEqual(
+ self.prerequisite.pkg_config_location,
+ self.prerequisite.linux_pkg_config_location(),
+ )
+
+
+class TestJDKPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.mandatory = dict(linux=False, darwin=True)
+ self.installer_is_supported = dict(linux=False, darwin=True)
+ self.prerequisite = JDKPrerequisite()
+
+
+class TestBrewPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.mandatory = dict(linux=False, darwin=True)
+ self.installer_is_supported = dict(linux=False, darwin=False)
+ self.prerequisite = HomebrewPrerequisite()
+
+ @mock.patch("shutil.which")
+ def test_darwin_checker(self, shutil_which):
+ shutil_which.return_value = None
+ self.assertFalse(self.prerequisite.darwin_checker())
+ shutil_which.return_value = "/opt/homebrew/bin/brew"
+ self.assertTrue(self.prerequisite.darwin_checker())
+
+ @mock.patch("pythonforandroid.prerequisites.info")
+ def test_darwin_helper(self, info):
+ self.prerequisite.darwin_helper()
+ info.assert_called_once_with(
+ "Installer for homebrew is not yet supported on macOS,"
+ "the nice news is that the installation process is easy!"
+ "See: https://brew.sh for further instructions."
+ )
+
+
+class TestOpenSSLPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.mandatory = dict(linux=False, darwin=True)
+ self.installer_is_supported = dict(linux=False, darwin=True)
+ self.prerequisite = OpenSSLPrerequisite()
+ self.expected_homebrew_formula_name = "openssl@1.1"
+ self.expected_homebrew_location_prefix = "/opt/homebrew/opt/openssl@1.1"
+
+ @mock.patch(
+ "pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix"
+ )
+ def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):
+ _darwin_get_brew_formula_location_prefix.return_value = None
+ self.assertFalse(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.return_value = (
+ self.expected_homebrew_location_prefix
+ )
+ self.assertTrue(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.assert_called_with(
+ self.expected_homebrew_formula_name, installed=True
+ )
+
+ @mock.patch("pythonforandroid.prerequisites.subprocess.check_output")
+ def test_darwin_installer(self, check_output):
+ self.prerequisite.darwin_installer()
+ check_output.assert_called_once_with(
+ ["brew", "install", self.expected_homebrew_formula_name]
+ )
+
+ @mock.patch(
+ "pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix"
+ )
+ def test_darwin_pkg_config_location(self, _darwin_get_brew_formula_location_prefix):
+ _darwin_get_brew_formula_location_prefix.return_value = (
+ self.expected_homebrew_location_prefix
+ )
+ self.assertEqual(
+ self.prerequisite.darwin_pkg_config_location(),
+ f"{self.expected_homebrew_location_prefix}/lib/pkgconfig",
+ )
+
+
+class TestAutoconfPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.mandatory = dict(linux=False, darwin=True)
+ self.installer_is_supported = dict(linux=False, darwin=True)
+ self.prerequisite = AutoconfPrerequisite()
+
+ @mock.patch(
+ "pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix"
+ )
+ def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):
+ _darwin_get_brew_formula_location_prefix.return_value = None
+ self.assertFalse(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.return_value = (
+ "/opt/homebrew/opt/autoconf"
+ )
+ self.assertTrue(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.assert_called_with(
+ "autoconf", installed=True
+ )
+
+ @mock.patch("pythonforandroid.prerequisites.subprocess.check_output")
+ def test_darwin_installer(self, check_output):
+ self.prerequisite.darwin_installer()
+ check_output.assert_called_once_with(["brew", "install", "autoconf"])
+
+
+class TestAutomakePrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.mandatory = dict(linux=False, darwin=True)
+ self.installer_is_supported = dict(linux=False, darwin=True)
+ self.prerequisite = AutomakePrerequisite()
+
+ @mock.patch(
+ "pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix"
+ )
+ def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):
+ _darwin_get_brew_formula_location_prefix.return_value = None
+ self.assertFalse(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.return_value = (
+ "/opt/homebrew/opt/automake"
+ )
+ self.assertTrue(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.assert_called_with(
+ "automake", installed=True
+ )
+
+ @mock.patch("pythonforandroid.prerequisites.subprocess.check_output")
+ def test_darwin_installer(self, check_output):
+ self.prerequisite.darwin_installer()
+ check_output.assert_called_once_with(["brew", "install", "automake"])
+
+
+class TestLibtoolPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.mandatory = dict(linux=False, darwin=True)
+ self.installer_is_supported = dict(linux=False, darwin=True)
+ self.prerequisite = LibtoolPrerequisite()
+
+ @mock.patch(
+ "pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix"
+ )
+ def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):
+ _darwin_get_brew_formula_location_prefix.return_value = None
+ self.assertFalse(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.return_value = (
+ "/opt/homebrew/opt/libtool"
+ )
+ self.assertTrue(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.assert_called_with(
+ "libtool", installed=True
+ )
+
+ @mock.patch("pythonforandroid.prerequisites.subprocess.check_output")
+ def test_darwin_installer(self, check_output):
+ self.prerequisite.darwin_installer()
+ check_output.assert_called_once_with(["brew", "install", "libtool"])
+
+
+class TestPkgConfigPrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.mandatory = dict(linux=False, darwin=True)
+ self.installer_is_supported = dict(linux=False, darwin=True)
+ self.prerequisite = PkgConfigPrerequisite()
+
+ @mock.patch(
+ "pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix"
+ )
+ def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):
+ _darwin_get_brew_formula_location_prefix.return_value = None
+ self.assertFalse(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.return_value = (
+ "/opt/homebrew/opt/pkg-config"
+ )
+ self.assertTrue(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.assert_called_with(
+ "pkg-config", installed=True
+ )
+
+ @mock.patch("pythonforandroid.prerequisites.subprocess.check_output")
+ def test_darwin_installer(self, check_output):
+ self.prerequisite.darwin_installer()
+ check_output.assert_called_once_with(["brew", "install", "pkg-config"])
+
+
+class TestCmakePrerequisite(PrerequisiteSetUpBaseClass, unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.mandatory = dict(linux=False, darwin=True)
+ self.installer_is_supported = dict(linux=False, darwin=True)
+ self.prerequisite = CmakePrerequisite()
+
+ @mock.patch(
+ "pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix"
+ )
+ def test_darwin_checker(self, _darwin_get_brew_formula_location_prefix):
+ _darwin_get_brew_formula_location_prefix.return_value = None
+ self.assertFalse(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.return_value = (
+ "/opt/homebrew/opt/cmake"
+ )
+ self.assertTrue(self.prerequisite.darwin_checker())
+ _darwin_get_brew_formula_location_prefix.assert_called_with(
+ "cmake", installed=True
+ )
+
+ @mock.patch("pythonforandroid.prerequisites.subprocess.check_output")
+ def test_darwin_installer(self, check_output):
+ self.prerequisite.darwin_installer()
+ check_output.assert_called_once_with(["brew", "install", "cmake"])
+
+
+class TestDefaultPrerequisitesCheckandInstall(unittest.TestCase):
+
+ def test_default_darwin_prerequisites_set(self):
+ self.assertListEqual(
+ [
+ p.__class__.__name__
+ for p in get_required_prerequisites(platform="darwin")
+ ],
+ [
+ "HomebrewPrerequisite",
+ "AutoconfPrerequisite",
+ "AutomakePrerequisite",
+ "LibtoolPrerequisite",
+ "PkgConfigPrerequisite",
+ "CmakePrerequisite",
+ "OpenSSLPrerequisite",
+ "JDKPrerequisite",
+ ],
+ )
+
+ def test_default_linux_prerequisites_set(self):
+ self.assertListEqual(
+ [
+ p.__class__.__name__
+ for p in get_required_prerequisites(platform="linux")
+ ],
+ [
+ ],
+ )
diff --git a/tests/test_pythonpackage.py b/tests/test_pythonpackage.py
new file mode 100644
index 000000000..21412e925
--- /dev/null
+++ b/tests/test_pythonpackage.py
@@ -0,0 +1,111 @@
+"""
+THESE TESTS DON'T RUN IN GITHUB-ACTIONS (takes too long!!)
+ONLY THE BASIC ONES IN test_pythonpackage_basic.py DO.
+
+(This file basically covers all tests for any of the
+functions that aren't already part of the basic
+test set)
+"""
+
+import os
+import shutil
+import tempfile
+
+from pythonforandroid.pythonpackage import (
+ _extract_info_from_package,
+ extract_metainfo_files_from_package,
+ get_package_as_folder,
+ get_package_dependencies,
+)
+
+
+def local_repo_folder():
+ return os.path.abspath(os.path.join(
+ os.path.dirname(__file__), ".."
+ ))
+
+
+def test_get_package_dependencies():
+ # TEST 1 from source code folder:
+ deps_nonrecursive = get_package_dependencies(
+ local_repo_folder(), recursive=False
+ )
+ deps_recursive = get_package_dependencies(
+ local_repo_folder(), recursive=True
+ )
+ # Check that jinja2 is returned as direct dep:
+ assert len([dep for dep in deps_nonrecursive
+ if "jinja2" in dep]) > 0
+ # Check that MarkupSafe is returned as indirect dep of jinja2:
+ assert [
+ dep for dep in deps_recursive
+ if "MarkupSafe" in dep
+ ]
+ # Check setuptools not being in non-recursive deps:
+ # (It will be in recursive ones due to p4a's build dependency)
+ assert "setuptools" not in deps_nonrecursive
+ # Check setuptools is present in non-recursive deps,
+ # if we also add build requirements:
+ assert "setuptools" in get_package_dependencies(
+ local_repo_folder(), recursive=False,
+ include_build_requirements=True,
+ )
+
+ # TEST 2 from external ref:
+ # Check that jinja2 is returned as direct dep:
+ assert len([dep for dep in get_package_dependencies("python-for-android")
+ if "jinja2" in dep]) > 0
+ # Check that MarkupSafe is returned as indirect dep of jinja2:
+ assert [
+ dep for dep in get_package_dependencies(
+ "python-for-android", recursive=True
+ )
+ if "MarkupSafe" in dep
+ ]
+
+
+def test_extract_metainfo_files_from_package():
+ # TEST 1 from external ref:
+ files_dir = tempfile.mkdtemp()
+ try:
+ extract_metainfo_files_from_package("python-for-android",
+ files_dir, debug=True)
+ assert os.path.exists(os.path.join(files_dir, "METADATA"))
+ finally:
+ shutil.rmtree(files_dir)
+
+ # TEST 2 from local folder:
+ files_dir = tempfile.mkdtemp()
+ try:
+ extract_metainfo_files_from_package(local_repo_folder(),
+ files_dir, debug=True)
+ assert os.path.exists(os.path.join(files_dir, "METADATA"))
+ finally:
+ shutil.rmtree(files_dir)
+
+
+def test_get_package_as_folder():
+ # WARNING !!! This function behaves DIFFERENTLY if the requested package
+ # has a wheel available vs a source package. What we're getting is
+ # essentially what pip also would fetch, but this can obviously CHANGE
+ # depending on what is happening/available on PyPI.
+ #
+ # Therefore, this test doesn't really go in-depth.
+ (obtained_type, obtained_path) = \
+ get_package_as_folder("python-for-android")
+ try:
+ assert obtained_type in {"source", "wheel"}
+ assert os.path.isdir(obtained_path)
+ finally:
+ # Try to ensure cleanup:
+ shutil.rmtree(obtained_path)
+
+
+def test__extract_info_from_package():
+ # This is indirectly already tested a lot through get_package_name()
+ # and get_package_dependencies(), so we'll just do one basic test:
+
+ assert _extract_info_from_package(
+ local_repo_folder(),
+ extract_type="name"
+ ) == "python-for-android"
diff --git a/tests/test_pythonpackage_basic.py b/tests/test_pythonpackage_basic.py
new file mode 100644
index 000000000..e98a5f99b
--- /dev/null
+++ b/tests/test_pythonpackage_basic.py
@@ -0,0 +1,317 @@
+"""
+ONLY BASIC TEST SET. The additional ones are in test_pythonpackage.py.
+
+These are in a separate file because these were picked to run in github-actions,
+while the other additional ones aren't (for build time reasons).
+"""
+
+import os
+import shutil
+import sys
+import subprocess
+import tempfile
+import textwrap
+from unittest import mock
+
+from pythonforandroid.pythonpackage import (
+ _extract_info_from_package,
+ get_dep_names_of_package,
+ get_package_name,
+ _get_system_python_executable,
+ is_filesystem_path,
+ parse_as_folder_reference,
+ transform_dep_for_pip,
+)
+
+
+def local_repo_folder():
+ return os.path.abspath(os.path.join(
+ os.path.dirname(__file__), ".."
+ ))
+
+
+def fake_metadata_extract(dep_name, output_folder, debug=False):
+ # Helper function to write out fake metadata.
+ with open(os.path.join(output_folder, "METADATA"), "w") as f:
+ f.write(textwrap.dedent("""\
+ Metadata-Version: 2.1
+ Name: testpackage
+ Version: 0.1
+ Requires-Dist: testpkg
+ Requires-Dist: testpkg2
+
+ Lorem Ipsum"""
+ ))
+ with open(os.path.join(output_folder, "metadata_source"), "w") as f:
+ f.write(u"wheel") # since we have no pyproject.toml
+
+
+def test__extract_info_from_package():
+ import pythonforandroid.pythonpackage # noqa
+ with mock.patch("pythonforandroid.pythonpackage."
+ "extract_metainfo_files_from_package",
+ fake_metadata_extract):
+ assert _extract_info_from_package(
+ "whatever", extract_type="name"
+ ) == "testpackage"
+ assert set(_extract_info_from_package(
+ "whatever", extract_type="dependencies"
+ )) == {"testpkg", "testpkg2"}
+
+
+def test_get_package_name():
+ # TEST 1 from external ref
+ with mock.patch("pythonforandroid.pythonpackage."
+ "extract_metainfo_files_from_package",
+ fake_metadata_extract):
+ assert get_package_name("TeStPackaGe") == "testpackage"
+
+ # TEST 2 from a local folder, for which we'll create a fake package:
+ temp_d = tempfile.mkdtemp(prefix="p4a-pythonpackage-test-tmp-")
+ try:
+ with open(os.path.join(temp_d, "setup.py"), "w") as f:
+ f.write(textwrap.dedent("""\
+ from setuptools import setup
+ setup(name="testpackage")
+ """
+ ))
+ pkg_name = get_package_name(temp_d)
+ assert pkg_name == "testpackage"
+ finally:
+ shutil.rmtree(temp_d)
+
+
+def test_get_dep_names_of_package():
+ # TEST 1 from external ref:
+ # Check that colorama is returned without the install condition when
+ # just getting the names (it has a "; ..." conditional originally):
+ dep_names = get_dep_names_of_package("python-for-android")
+ assert "colorama" in dep_names
+ assert "setuptools" not in dep_names
+ try:
+ dep_names = get_dep_names_of_package(
+ "python-for-android", include_build_requirements=True,
+ verbose=True,
+ )
+ except NotImplementedError as e:
+ # If python-for-android was fetched as wheel then build requirements
+ # cannot be obtained (since that is not implemented for wheels).
+ # Check for the correct error message:
+ assert "wheel" in str(e)
+ # (And yes it would be better to do a local test with something
+ # that is guaranteed to be a wheel and not remote on pypi,
+ # but that might require setting up a full local pypiserver.
+ # Not worth the test complexity for now, but if anyone has an
+ # idea in the future feel free to replace this subtest.)
+ else:
+ # We managed to obtain build requirements!
+ # Check setuptools is in here:
+ assert "setuptools" in dep_names
+
+ # TEST 2 from local folder:
+ assert "colorama" in get_dep_names_of_package(local_repo_folder())
+
+ # Now test that exact version pins are kept, but others aren't:
+ test_fake_package = tempfile.mkdtemp()
+ try:
+ with open(os.path.join(test_fake_package, "setup.py"), "w") as f:
+ f.write(textwrap.dedent("""\
+ from setuptools import setup
+
+ setup(name='fakeproject',
+ description='fake for testing',
+ install_requires=['buildozer==0.39',
+ 'python-for-android>=0.5.1'],
+ )
+ """))
+ # See that we get the deps with the exact version pin kept but
+ # the other version restriction gone:
+ assert set(get_dep_names_of_package(
+ test_fake_package, recursive=False,
+ keep_version_pins=True, verbose=True
+ )) == {"buildozer==0.39", "python-for-android"}
+
+ # Make sure we also can get the fully cleaned up variant:
+ assert set(get_dep_names_of_package(
+ test_fake_package, recursive=False,
+ keep_version_pins=False, verbose=True
+ )) == {"buildozer", "python-for-android"}
+
+ # Test with build requirements included:
+ dep_names = get_dep_names_of_package(
+ test_fake_package, recursive=False,
+ keep_version_pins=False, verbose=True,
+ include_build_requirements=True
+ )
+ assert len(
+ {"buildozer", "python-for-android", "setuptools"}.intersection(
+ dep_names
+ )
+ ) == 3 # all three must be included
+ finally:
+ shutil.rmtree(test_fake_package)
+
+
+def test_transform_dep_for_pip():
+ # A reminder, this entire function we test here is just a workaround
+ # for https://github.com/pypa/pip/issues/6097 (and not a nice one.)
+ # As soon as upstream fixes it, we should throw it & this test out
+ transformed = (
+ transform_dep_for_pip(
+ "python-for-android @ https://github.com/kivy/" +
+ "python-for-android/archive/master.zip"
+ ),
+ transform_dep_for_pip(
+ "python-for-android @ https://github.com/kivy/" +
+ "python-for-android/archive/master.zip" +
+ "#egg=python-for-android-master"
+ ),
+ transform_dep_for_pip(
+ "python-for-android @ https://github.com/kivy/" +
+ "python-for-android/archive/master.zip" +
+ "#" # common hack variant used by others to make pip parse it
+ ),
+ )
+ expected = (
+ "https://github.com/kivy/python-for-android/archive/master.zip" +
+ "#egg=python-for-android"
+ )
+ assert transformed == (expected, expected, expected)
+ assert transform_dep_for_pip("https://a@b/") == "https://a@b/"
+
+
+def test_is_filesystem_path():
+ assert is_filesystem_path("/some/test")
+ assert not is_filesystem_path("https://blubb")
+ assert not is_filesystem_path("test @ bla")
+ assert is_filesystem_path("/abc/c@d")
+ assert not is_filesystem_path("https://user:pw@host/")
+ assert is_filesystem_path(".")
+ assert is_filesystem_path("")
+
+
+def test_parse_as_folder_reference():
+ assert parse_as_folder_reference("file:///a%20test") == "/a test"
+ assert parse_as_folder_reference("https://github.com") is None
+ assert parse_as_folder_reference("/a/folder") == "/a/folder"
+ assert parse_as_folder_reference("test @ /abc") == "/abc"
+ assert parse_as_folder_reference("test @ https://bla") is None
+
+
+class TestGetSystemPythonExecutable():
+ """ This contains all tests for _get_system_python_executable().
+
+ ULTRA IMPORTANT THING TO UNDERSTAND: (if you touch this)
+
+ This code runs things with other python interpreters NOT in the tox
+ environment/virtualenv.
+ E.g. _get_system_python_executable() is outside in the regular
+ host environment! That also means all dependencies can be possibly
+ not present!
+
+ This is kind of absurd that we need this to run the test at all,
+ but we can't test this inside tox's virtualenv:
+ """
+
+ def test_basic(self):
+ # Tests function inside tox env with no further special setup.
+
+ # Get system-wide python bin:
+ pybin = _get_system_python_executable()
+
+ # The python binary needs to match our major version to be correct:
+ pyversion = subprocess.check_output([
+ pybin, "-c", "import sys; print(sys.version)"
+ ], stderr=subprocess.STDOUT).decode("utf-8", "replace")
+ assert pyversion.strip() == sys.version.strip()
+
+ def run__get_system_python_executable(self, pybin):
+ """ Helper function to run our function.
+
+ We want to see what _get_system_python_executable() does given
+ a specific python, so we need to make it import it and run it,
+ with that TARGET python, which this function does.
+ """
+ cmd = [
+ pybin,
+ "-c",
+ "import importlib\n"
+ "import build.util\n"
+ "import os\n"
+ "import sys\n"
+ "sys.path = [os.path.dirname(sys.argv[1])] + sys.path\n"
+ "m = importlib.import_module(\n"
+ " os.path.basename(sys.argv[1]).partition('.')[0]\n"
+ ")\n"
+ "print(m._get_system_python_executable())",
+ os.path.join(os.path.dirname(__file__), "..",
+ "pythonforandroid", "pythonpackage.py"),
+ ]
+ # Actual call to python:
+ try:
+ return subprocess.check_output(
+ cmd, stderr=subprocess.STDOUT
+ ).decode("utf-8", "replace").strip()
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError("call failed, with output: " + str(e.output))
+
+ def test_systemwide_python(self):
+ # Get system-wide python bin seen from here first:
+ pybin = _get_system_python_executable()
+ # (this call was not a test, we really just need the path here)
+
+ # Check that in system-wide python, the system-wide python is returned:
+ # IMPORTANT: understand that this runs OUTSIDE of any virtualenv.
+ try:
+ p1 = os.path.normpath(
+ self.run__get_system_python_executable(pybin)
+ )
+ p2 = os.path.normpath(pybin)
+ assert p1 == p2
+ except RuntimeError as e:
+ # (remember this is not in a virtualenv)
+ # Some deps may not be installed, so we just avoid to raise
+ # an exception here, as a missing dep should not make the test
+ # fail.
+ if "build" in str(e.args):
+ # System python probably doesn't have build available!
+ pass
+ elif "toml" in str(e.args):
+ # System python probably doesn't have toml available!
+ pass
+ else:
+ raise
+
+ def test_venv(self):
+ """ Verifies that _get_system_python_executable() works correctly
+ in a 'venv' (Python 3 only feature).
+ """
+
+ # Get system-wide python bin seen from here first:
+ pybin = _get_system_python_executable()
+ # (this call was not a test, we really just need the path here)
+
+ test_dir = tempfile.mkdtemp()
+ try:
+ # Check that in a venv/pyvenv, the system-wide python is returned:
+ subprocess.check_output([
+ pybin, "-m", "venv", "--",
+ os.path.join(test_dir, "venv")
+ ])
+ subprocess.check_output([
+ os.path.join(test_dir, "venv", "bin", "pip"),
+ "install", "-U", "pip"
+ ])
+ subprocess.check_output([
+ os.path.join(test_dir, "venv", "bin", "pip"),
+ "install", "-U", "build", "toml", "sh<2.0", "colorama",
+ "appdirs", "jinja2", "packaging"
+ ])
+ sys_python_path = self.run__get_system_python_executable(
+ os.path.join(test_dir, "venv", "bin", "python")
+ )
+ assert os.path.normpath(sys_python_path).startswith(
+ os.path.normpath(pybin)
+ )
+ finally:
+ shutil.rmtree(test_dir)
diff --git a/tests/test_recipe.py b/tests/test_recipe.py
new file mode 100644
index 000000000..a50ca444a
--- /dev/null
+++ b/tests/test_recipe.py
@@ -0,0 +1,328 @@
+import os
+import pytest
+import types
+import unittest
+import warnings
+from unittest import mock
+from backports import tempfile
+
+from pythonforandroid.build import Context
+from pythonforandroid.recipe import Recipe, TargetPythonRecipe, import_recipe
+from pythonforandroid.archs import ArchAarch_64
+from pythonforandroid.bootstrap import Bootstrap
+from test_bootstrap import BaseClassSetupBootstrap
+
+
+def patch_logger(level):
+ return mock.patch('pythonforandroid.recipe.{}'.format(level))
+
+
+def patch_logger_info():
+ return patch_logger('info')
+
+
+def patch_logger_debug():
+ return patch_logger('debug')
+
+
+def patch_urlretrieve():
+ return mock.patch('pythonforandroid.recipe.urlretrieve')
+
+
+class DummyRecipe(Recipe):
+ pass
+
+
+class TestRecipe(unittest.TestCase):
+
+ def test_recipe_dirs(self):
+ """
+ Trivial `recipe_dirs()` test.
+ Makes sure the list is not empty and has the root directory.
+ """
+ ctx = Context()
+ recipes_dir = Recipe.recipe_dirs(ctx)
+ # by default only the root dir `recipes` directory
+ self.assertEqual(len(recipes_dir), 1)
+ self.assertTrue(recipes_dir[0].startswith(ctx.root_dir))
+
+ def test_list_recipes(self):
+ """
+ Trivial test verifying list_recipes returns a generator with some recipes.
+ """
+ ctx = Context()
+ recipes = Recipe.list_recipes(ctx)
+ self.assertTrue(isinstance(recipes, types.GeneratorType))
+ recipes = list(recipes)
+ self.assertIn('python3', recipes)
+
+ def test_get_recipe(self):
+ """
+ Makes sure `get_recipe()` returns a `Recipe` object when possible.
+ """
+ ctx = Context()
+ recipe_name = 'python3'
+ recipe = Recipe.get_recipe(recipe_name, ctx)
+ self.assertTrue(isinstance(recipe, Recipe))
+ self.assertEqual(recipe.name, recipe_name)
+ recipe_name = 'does_not_exist'
+ with self.assertRaises(ValueError) as e:
+ Recipe.get_recipe(recipe_name, ctx)
+ self.assertEqual(
+ e.exception.args[0], 'Recipe does not exist: {}'.format(recipe_name))
+
+ def test_import_recipe(self):
+ """
+ Verifies we can dynamically import a recipe without warnings.
+ """
+ p4a_root_dir = os.path.dirname(os.path.dirname(__file__))
+ name = 'pythonforandroid.recipes.python3'
+ pathname = os.path.join(
+ *([p4a_root_dir] + name.split('.') + ['__init__.py'])
+ )
+ with warnings.catch_warnings(record=True) as recorded_warnings:
+ warnings.simplefilter("always")
+ module = import_recipe(name, pathname)
+ assert module is not None
+ assert recorded_warnings == []
+
+ def test_download_if_necessary(self):
+ """
+ Download should happen via `Recipe.download()` only if the recipe
+ specific environment variable is not set.
+ """
+ # download should happen as the environment variable is not set
+ recipe = DummyRecipe()
+ with mock.patch.object(Recipe, 'download') as m_download:
+ recipe.download_if_necessary()
+ assert m_download.call_args_list == [mock.call()]
+ # after setting it the download should be skipped
+ env_var = 'P4A_test_recipe_DIR'
+ env_dict = {env_var: '1'}
+ with mock.patch.object(Recipe, 'download') as m_download, mock.patch.dict(os.environ, env_dict):
+ recipe.download_if_necessary()
+ assert m_download.call_args_list == []
+
+ def test_download_url_not_set(self):
+ """
+ Verifies that no download happens when URL is not set.
+ """
+ recipe = DummyRecipe()
+ with patch_logger_info() as m_info:
+ recipe.download()
+ assert m_info.call_args_list == [
+ mock.call('Skipping test_recipe download as no URL is set')]
+
+ @staticmethod
+ def get_dummy_python_recipe_for_download_tests():
+ """
+ Helper method for creating a test recipe used in download tests.
+ """
+ recipe = DummyRecipe()
+ filename = 'Python-3.7.4.tgz'
+ url = 'https://www.python.org/ftp/python/3.7.4/{}'.format(filename)
+ recipe._url = url
+ recipe.ctx = Context()
+ return recipe, filename
+
+ def test_download_url_is_set(self):
+ """
+ Verifies the actual download gets triggered when the URL is set.
+ """
+ recipe, filename = self.get_dummy_python_recipe_for_download_tests()
+ url = recipe.url
+ with (
+ patch_logger_debug()) as m_debug, (
+ mock.patch.object(Recipe, 'download_file')) as m_download_file, (
+ mock.patch('pythonforandroid.recipe.touch')) as m_touch, (
+ tempfile.TemporaryDirectory()) as temp_dir:
+ recipe.ctx.setup_dirs(temp_dir)
+ recipe.download()
+ assert m_download_file.call_args_list == [mock.call(url, filename)]
+ assert m_debug.call_args_list == [
+ mock.call(
+ 'Downloading test_recipe from '
+ 'https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz')]
+ assert m_touch.call_count == 1
+
+ def test_download_file_scheme_https(self):
+ """
+ Verifies `urlretrieve()` is being called on https downloads.
+ """
+ recipe, filename = self.get_dummy_python_recipe_for_download_tests()
+ url = recipe.url
+ with (
+ patch_urlretrieve()) as m_urlretrieve, (
+ tempfile.TemporaryDirectory()) as temp_dir:
+ recipe.ctx.setup_dirs(temp_dir)
+ assert recipe.download_file(url, filename) == filename
+ assert m_urlretrieve.call_args_list == [
+ mock.call(url, filename, mock.ANY)
+ ]
+
+ def test_download_file_scheme_https_oserror(self):
+ """
+ Checks `urlretrieve()` is being retried on `OSError`.
+ After a number of retries the exception is re-reaised.
+ """
+ recipe, filename = self.get_dummy_python_recipe_for_download_tests()
+ url = recipe.url
+ with (
+ patch_urlretrieve()) as m_urlretrieve, (
+ mock.patch('pythonforandroid.recipe.time.sleep')) as m_sleep, (
+ pytest.raises(OSError)), (
+ tempfile.TemporaryDirectory()) as temp_dir:
+ recipe.ctx.setup_dirs(temp_dir)
+ m_urlretrieve.side_effect = OSError
+ assert recipe.download_file(url, filename) == filename
+ retry = 5
+ expected_call_args_list = [mock.call(url, filename, mock.ANY)] * retry
+ assert m_urlretrieve.call_args_list == expected_call_args_list
+ expected_call_args_list = [mock.call(2**i) for i in range(retry - 1)]
+ assert m_sleep.call_args_list == expected_call_args_list
+
+
+class TestTargetPythonRecipe(unittest.TestCase):
+
+ def test_major_minor_version_string(self):
+ """
+ Test that the major_minor_version_string property returns the correct
+ string.
+ """
+ class DummyTargetPythonRecipe(TargetPythonRecipe):
+ version = '1.2.3'
+
+ recipe = DummyTargetPythonRecipe()
+ assert recipe.major_minor_version_string == '1.2'
+
+
+class TestLibraryRecipe(BaseClassSetupBootstrap, unittest.TestCase):
+ def setUp(self):
+ """
+ Initialize a Context with a Bootstrap and a Distribution to properly
+ test an library recipe, to do so we reuse `BaseClassSetupBootstrap`
+ """
+ super().setUp()
+ self.ctx.bootstrap = Bootstrap().get_bootstrap('sdl2', self.ctx)
+ self.setUp_distribution_with_bootstrap(self.ctx.bootstrap)
+
+ def test_built_libraries(self):
+ """The openssl recipe is a library recipe, so it should have set the
+ attribute `built_libraries`, but not the case of `pyopenssl` recipe.
+ """
+ recipe = Recipe.get_recipe('openssl', self.ctx)
+ self.assertTrue(recipe.built_libraries)
+
+ recipe = Recipe.get_recipe('pyopenssl', self.ctx)
+ self.assertFalse(recipe.built_libraries)
+
+ @mock.patch('pythonforandroid.recipe.exists')
+ def test_should_build(self, mock_exists):
+ # avoid trying to find the recipe in a non-existing storage directory
+ self.ctx.storage_dir = None
+
+ arch = ArchAarch_64(self.ctx)
+ recipe = Recipe.get_recipe('openssl', self.ctx)
+ recipe.ctx = self.ctx
+ self.assertFalse(recipe.should_build(arch))
+
+ mock_exists.return_value = False
+ self.assertTrue(recipe.should_build(arch))
+
+ @mock.patch('pythonforandroid.recipe.Recipe.get_libraries')
+ @mock.patch('pythonforandroid.recipe.Recipe.install_libs')
+ def test_install_libraries(self, mock_install_libs, mock_get_libraries):
+ mock_get_libraries.return_value = {
+ '/build_lib/libsample1.so',
+ '/build_lib/libsample2.so',
+ }
+ self.ctx.recipe_build_order = [
+ "hostpython3",
+ "openssl",
+ "python3",
+ "sdl2",
+ "kivy",
+ ]
+ arch = ArchAarch_64(self.ctx)
+ recipe = Recipe.get_recipe('openssl', self.ctx)
+ recipe.install_libraries(arch)
+ mock_install_libs.assert_called_once_with(
+ arch, *mock_get_libraries.return_value
+ )
+
+
+class TesSTLRecipe(BaseClassSetupBootstrap, unittest.TestCase):
+ def setUp(self):
+ """
+ Initialize a Context with a Bootstrap and a Distribution to properly
+ test a recipe which depends on android's STL library, to do so we reuse
+ `BaseClassSetupBootstrap`
+ """
+ super().setUp()
+ self.ctx.bootstrap = Bootstrap().get_bootstrap('sdl2', self.ctx)
+ self.setUp_distribution_with_bootstrap(self.ctx.bootstrap)
+ self.ctx.python_recipe = Recipe.get_recipe('python3', self.ctx)
+
+ @mock.patch('shutil.which')
+ @mock.patch('pythonforandroid.build.ensure_dir')
+ def test_get_recipe_env_with(
+ self, mock_ensure_dir, mock_shutil_which
+ ):
+ """
+ Test that :meth:`~pythonforandroid.recipe.STLRecipe.get_recipe_env`
+ returns some expected keys and values.
+
+ .. note:: We don't check all the env variables, only those one specific
+ of :class:`~pythonforandroid.recipe.STLRecipe`, the others
+ should be tested in the proper test.
+ """
+ expected_compiler = (
+ f"/opt/android/android-ndk/toolchains/"
+ f"llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang"
+ )
+ mock_shutil_which.return_value = expected_compiler
+
+ arch = ArchAarch_64(self.ctx)
+ recipe = Recipe.get_recipe('libgeos', self.ctx)
+ assert recipe.need_stl_shared, True
+ env = recipe.get_recipe_env(arch)
+ # check that the mocks have been called
+ mock_ensure_dir.assert_called()
+ mock_shutil_which.assert_called_once_with(
+ expected_compiler, path=self.ctx.env['PATH']
+ )
+ self.assertIsInstance(env, dict)
+
+ @mock.patch('pythonforandroid.recipe.Recipe.install_libs')
+ @mock.patch('pythonforandroid.recipe.isfile')
+ @mock.patch('pythonforandroid.build.ensure_dir')
+ def test_install_stl_lib(
+ self, mock_ensure_dir, mock_isfile, mock_install_lib
+ ):
+ """
+ Test that :meth:`~pythonforandroid.recipe.STLRecipe.install_stl_lib`,
+ calls the method :meth:`~pythonforandroid.recipe.Recipe.install_libs`
+ with the proper arguments: a subclass of
+ :class:`~pythonforandroid.archs.Arch` and our stl lib
+ (:attr:`~pythonforandroid.recipe.STLRecipe.stl_lib_name`)
+ """
+ mock_isfile.return_value = False
+
+ arch = ArchAarch_64(self.ctx)
+ recipe = Recipe.get_recipe('libgeos', self.ctx)
+ recipe.ctx = self.ctx
+ assert recipe.need_stl_shared, True
+ recipe.install_stl_lib(arch)
+ mock_install_lib.assert_called_once_with(
+ arch,
+ os.path.join(arch.ndk_lib_dir, f"lib{recipe.stl_lib_name}.so"),
+ )
+ mock_ensure_dir.assert_called()
+
+ @mock.patch('pythonforandroid.recipe.Recipe.install_stl_lib')
+ def test_postarch_build(self, mock_install_stl_lib):
+ arch = ArchAarch_64(self.ctx)
+ recipe = Recipe.get_recipe('libgeos', self.ctx)
+ assert recipe.need_stl_shared, True
+ recipe.postbuild_arch(arch)
+ mock_install_stl_lib.assert_called_once_with(arch)
diff --git a/tests/test_recommendations.py b/tests/test_recommendations.py
new file mode 100644
index 000000000..443ec52b3
--- /dev/null
+++ b/tests/test_recommendations.py
@@ -0,0 +1,254 @@
+import unittest
+from os.path import join
+from sys import version as py_version
+
+import packaging.version
+from unittest import mock
+from pythonforandroid.recommendations import (
+ check_ndk_api,
+ check_ndk_version,
+ check_target_api,
+ read_ndk_version,
+ check_python_version,
+ print_recommendations,
+ MAX_NDK_VERSION,
+ RECOMMENDED_NDK_VERSION,
+ RECOMMENDED_TARGET_API,
+ MIN_NDK_API,
+ MIN_NDK_VERSION,
+ NDK_DOWNLOAD_URL,
+ ARMEABI_MAX_TARGET_API,
+ MIN_TARGET_API,
+ UNKNOWN_NDK_MESSAGE,
+ PARSE_ERROR_NDK_MESSAGE,
+ READ_ERROR_NDK_MESSAGE,
+ ENSURE_RIGHT_NDK_MESSAGE,
+ NDK_LOWER_THAN_SUPPORTED_MESSAGE,
+ UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE,
+ CURRENT_NDK_VERSION_MESSAGE,
+ RECOMMENDED_NDK_VERSION_MESSAGE,
+ TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE,
+ OLD_NDK_API_MESSAGE,
+ NEW_NDK_MESSAGE,
+ OLD_API_MESSAGE,
+ MIN_PYTHON_MAJOR_VERSION,
+ MIN_PYTHON_MINOR_VERSION,
+ PY2_ERROR_TEXT,
+ PY_VERSION_ERROR_TEXT,
+)
+
+from pythonforandroid.util import BuildInterruptingException
+
+running_in_py2 = int(py_version[0]) < 3
+
+
+class TestRecommendations(unittest.TestCase):
+ """
+ An inherited class of `unittest.TestCase`to test the module
+ :mod:`~pythonforandroid.recommendations`.
+ """
+
+ def setUp(self):
+ self.ndk_dir = "/opt/android/android-ndk"
+
+ @unittest.skipIf(running_in_py2, "`assertLogs` requires Python 3.4+")
+ @mock.patch("pythonforandroid.recommendations.read_ndk_version")
+ def test_check_ndk_version_greater_than_recommended(self, mock_read_ndk):
+ _version_string = f"{MIN_NDK_VERSION + 1}.0.5232133"
+ mock_read_ndk.return_value = packaging.version.Version(_version_string)
+ with self.assertLogs(level="INFO") as cm:
+ check_ndk_version(self.ndk_dir)
+ mock_read_ndk.assert_called_once_with(self.ndk_dir)
+ self.assertEqual(
+ cm.output,
+ [
+ "INFO:p4a:[INFO]: {}".format(
+ CURRENT_NDK_VERSION_MESSAGE.format(
+ ndk_version=MAX_NDK_VERSION + 1
+ )
+ ),
+ "WARNING:p4a:[WARNING]: {}".format(
+ RECOMMENDED_NDK_VERSION_MESSAGE.format(
+ recommended_ndk_version=RECOMMENDED_NDK_VERSION
+ )
+ ),
+ "WARNING:p4a:[WARNING]: {}".format(NEW_NDK_MESSAGE),
+ ],
+ )
+
+ @mock.patch("pythonforandroid.recommendations.read_ndk_version")
+ def test_check_ndk_version_lower_than_recommended(self, mock_read_ndk):
+ _version_string = f"{MIN_NDK_VERSION - 1}.0.5232133"
+ mock_read_ndk.return_value = packaging.version.Version(_version_string)
+ with self.assertRaises(BuildInterruptingException) as e:
+ check_ndk_version(self.ndk_dir)
+ self.assertEqual(
+ e.exception.args[0],
+ NDK_LOWER_THAN_SUPPORTED_MESSAGE.format(
+ min_supported=MIN_NDK_VERSION, ndk_url=NDK_DOWNLOAD_URL
+ ),
+ )
+ mock_read_ndk.assert_called_once_with(self.ndk_dir)
+
+ @unittest.skipIf(running_in_py2, "`assertLogs` requires Python 3.4+")
+ def test_check_ndk_version_error(self):
+ """
+ Test that a fake ndk dir give us two messages:
+ - first should be an `INFO` log
+ - second should be an `WARNING` log
+ """
+ with self.assertLogs(level="INFO") as cm:
+ check_ndk_version(self.ndk_dir)
+ self.assertEqual(
+ cm.output,
+ [
+ "INFO:p4a:[INFO]: {}".format(UNKNOWN_NDK_MESSAGE),
+ "WARNING:p4a:[WARNING]: {}".format(
+ READ_ERROR_NDK_MESSAGE.format(ndk_dir=self.ndk_dir)
+ ),
+ "WARNING:p4a:[WARNING]: {}".format(
+ ENSURE_RIGHT_NDK_MESSAGE.format(
+ min_supported=MIN_NDK_VERSION,
+ rec_version=RECOMMENDED_NDK_VERSION,
+ ndk_url=NDK_DOWNLOAD_URL,
+ )
+ ),
+ ],
+ )
+
+ @mock.patch("pythonforandroid.recommendations.open")
+ def test_read_ndk_version(self, mock_open_src_prop):
+ mock_open_src_prop.side_effect = [
+ mock.mock_open(
+ read_data="Pkg.Revision = 17.2.4988734"
+ ).return_value
+ ]
+ version = read_ndk_version(self.ndk_dir)
+ mock_open_src_prop.assert_called_once_with(
+ join(self.ndk_dir, "source.properties")
+ )
+ assert version.major == 17
+ assert version.minor == 2
+ assert version.micro == 4988734
+
+ @unittest.skipIf(running_in_py2, "`assertLogs` requires Python 3.4+")
+ @mock.patch("pythonforandroid.recommendations.open")
+ def test_read_ndk_version_error(self, mock_open_src_prop):
+ mock_open_src_prop.side_effect = [
+ mock.mock_open(read_data="").return_value
+ ]
+ with self.assertLogs(level="INFO") as cm:
+ version = read_ndk_version(self.ndk_dir)
+ self.assertEqual(
+ cm.output,
+ ["INFO:p4a:[INFO]: {}".format(PARSE_ERROR_NDK_MESSAGE)],
+ )
+ mock_open_src_prop.assert_called_once_with(
+ join(self.ndk_dir, "source.properties")
+ )
+ assert version is None
+
+ def test_check_target_api_error_arch_armeabi(self):
+
+ with self.assertRaises(BuildInterruptingException) as e:
+ check_target_api(RECOMMENDED_TARGET_API, "armeabi")
+ self.assertEqual(
+ e.exception.args[0],
+ UNSUPPORTED_NDK_API_FOR_ARMEABI_MESSAGE.format(
+ req_ndk_api=RECOMMENDED_TARGET_API,
+ max_ndk_api=ARMEABI_MAX_TARGET_API,
+ ),
+ )
+
+ @unittest.skipIf(running_in_py2, "`assertLogs` requires Python 3.4+")
+ def test_check_target_api_warning_target_api(self):
+
+ with self.assertLogs(level="INFO") as cm:
+ check_target_api(MIN_TARGET_API - 1, MIN_TARGET_API)
+ self.assertEqual(
+ cm.output,
+ [
+ "WARNING:p4a:[WARNING]: Target API 29 < 30",
+ "WARNING:p4a:[WARNING]: {old_api_msg}".format(
+ old_api_msg=OLD_API_MESSAGE
+ ),
+ ],
+ )
+
+ def test_check_ndk_api_error_android_api(self):
+ """
+ Given an `android api` greater than an `ndk_api`, we should get an
+ `BuildInterruptingException`.
+ """
+ ndk_api = MIN_NDK_API + 1
+ android_api = MIN_NDK_API
+ with self.assertRaises(BuildInterruptingException) as e:
+ check_ndk_api(ndk_api, android_api)
+ self.assertEqual(
+ e.exception.args[0],
+ TARGET_NDK_API_GREATER_THAN_TARGET_API_MESSAGE.format(
+ ndk_api=ndk_api, android_api=android_api
+ ),
+ )
+
+ @unittest.skipIf(running_in_py2, "`assertLogs` requires Python 3.4+")
+ def test_check_ndk_api_warning_old_ndk(self):
+ """
+ Given an `android api` lower than the supported by p4a, we should
+ get an `BuildInterruptingException`.
+ """
+ ndk_api = MIN_NDK_API - 1
+ android_api = RECOMMENDED_TARGET_API
+ with self.assertLogs(level="INFO") as cm:
+ check_ndk_api(ndk_api, android_api)
+ self.assertEqual(
+ cm.output,
+ [
+ "WARNING:p4a:[WARNING]: {}".format(
+ OLD_NDK_API_MESSAGE.format(MIN_NDK_API)
+ )
+ ],
+ )
+
+ def test_check_python_version(self):
+ """With any version info lower than the minimum, we should get a
+ BuildInterruptingException with an appropriate message.
+ """
+ with mock.patch('sys.version_info') as fake_version_info:
+
+ # Major version is Python 2 => exception
+ fake_version_info.major = MIN_PYTHON_MAJOR_VERSION - 1
+ fake_version_info.minor = MIN_PYTHON_MINOR_VERSION
+ with self.assertRaises(BuildInterruptingException) as context:
+ check_python_version()
+ assert context.exception.message == PY2_ERROR_TEXT
+
+ # Major version too low => exception
+ # Using a float valued major version just to test the logic and avoid
+ # clashing with the Python 2 check
+ fake_version_info.major = MIN_PYTHON_MAJOR_VERSION - 0.1
+ fake_version_info.minor = MIN_PYTHON_MINOR_VERSION
+ with self.assertRaises(BuildInterruptingException) as context:
+ check_python_version()
+ assert context.exception.message == PY_VERSION_ERROR_TEXT
+
+ # Minor version too low => exception
+ fake_version_info.major = MIN_PYTHON_MAJOR_VERSION
+ fake_version_info.minor = MIN_PYTHON_MINOR_VERSION - 1
+ with self.assertRaises(BuildInterruptingException) as context:
+ check_python_version()
+ assert context.exception.message == PY_VERSION_ERROR_TEXT
+
+ # Version high enough => nothing interesting happens
+ fake_version_info.major = MIN_PYTHON_MAJOR_VERSION
+ fake_version_info.minor = MIN_PYTHON_MINOR_VERSION
+ check_python_version()
+
+ def test_print_recommendations(self):
+ """
+ Simple test that the function actually runs.
+ """
+ # The main failure mode is if the function tries to print a variable
+ # that doesn't actually exist, so simply running to check all the
+ # prints work is the most important test.
+ print_recommendations()
diff --git a/tests/test_toolchain.py b/tests/test_toolchain.py
new file mode 100644
index 000000000..874453f98
--- /dev/null
+++ b/tests/test_toolchain.py
@@ -0,0 +1,170 @@
+import io
+import os
+import sys
+import pytest
+from unittest import mock
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.toolchain import ToolchainCL
+from pythonforandroid.util import BuildInterruptingException
+
+
+def patch_sys_argv(argv):
+ return mock.patch('sys.argv', argv)
+
+
+def patch_argparse_print_help():
+ return mock.patch('argparse.ArgumentParser.print_help')
+
+
+def patch_sys_stdout():
+ return mock.patch('sys.stdout', new_callable=io.StringIO)
+
+
+def raises_system_exit():
+ return pytest.raises(SystemExit)
+
+
+class TestToolchainCL:
+
+ def test_help(self):
+ """
+ Calling with `--help` should print help and exit 0.
+ """
+ argv = ['toolchain.py', '--help', '--storage-dir=/tmp']
+ with patch_sys_argv(argv), raises_system_exit(
+ ) as ex_info, patch_argparse_print_help() as m_print_help:
+ ToolchainCL()
+ assert ex_info.value.code == 0
+ assert m_print_help.call_args_list == [mock.call()]
+
+ @pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3")
+ def test_unknown(self):
+ """
+ Calling with unknown args should print help and exit 1.
+ """
+ argv = ['toolchain.py', '--unknown']
+ with patch_sys_argv(argv), raises_system_exit(
+ ) as ex_info, patch_argparse_print_help() as m_print_help:
+ ToolchainCL()
+ assert ex_info.value.code == 1
+ assert m_print_help.call_args_list == [mock.call()]
+
+ def test_create(self):
+ """
+ Basic `create` distribution test.
+ """
+ argv = [
+ 'toolchain.py',
+ 'create',
+ '--sdk-dir=/tmp/android-sdk',
+ '--ndk-dir=/tmp/android-ndk',
+ '--bootstrap=service_only',
+ '--requirements=python3',
+ '--dist-name=test_toolchain',
+ '--activity-class-name=abc.myapp.android.CustomPythonActivity',
+ '--service-class-name=xyz.myapp.android.CustomPythonService',
+ '--arch=arm64-v8a',
+ '--arch=armeabi-v7a'
+ ]
+ with patch_sys_argv(argv), mock.patch(
+ 'pythonforandroid.build.get_available_apis'
+ ) as m_get_available_apis, mock.patch(
+ 'pythonforandroid.toolchain.build_recipes'
+ ) as m_build_recipes, mock.patch(
+ 'pythonforandroid.bootstraps.service_only.'
+ 'ServiceOnlyBootstrap.assemble_distribution'
+ ) as m_run_distribute:
+ m_get_available_apis.return_value = [33]
+ tchain = ToolchainCL()
+ assert tchain.ctx.activity_class_name == 'abc.myapp.android.CustomPythonActivity'
+ assert tchain.ctx.service_class_name == 'xyz.myapp.android.CustomPythonService'
+ assert m_get_available_apis.call_args_list in [
+ [mock.call('/tmp/android-sdk')], # linux case
+ [mock.call('/private/tmp/android-sdk')] # macos case
+ ]
+ build_order = [
+ 'hostpython3', 'libffi', 'openssl', 'sqlite3', 'python3',
+ 'genericndkbuild', 'setuptools', 'six', 'pyjnius', 'android',
+ ]
+ python_modules = []
+ context = mock.ANY
+ project_dir = None
+ assert m_build_recipes.call_args_list == [
+ mock.call(
+ build_order,
+ python_modules,
+ context,
+ project_dir,
+ ignore_project_setup_py=False
+ )
+ ]
+ assert m_run_distribute.call_args_list == [mock.call()]
+
+ @mock.patch(
+ 'pythonforandroid.build.environ',
+ # Make sure that no environ variable modifies `sdk_dir`
+ {'ANDROIDSDK': None, 'ANDROID_HOME': None})
+ def test_create_no_sdk_dir(self):
+ """
+ The `--sdk-dir` is mandatory to `create` a distribution.
+ """
+ argv = ['toolchain.py', 'create', '--arch=arm64-v8a', '--arch=armeabi-v7a']
+ with patch_sys_argv(argv), pytest.raises(
+ BuildInterruptingException
+ ) as ex_info:
+ ToolchainCL()
+ assert ex_info.value.message == (
+ 'Android SDK dir was not specified, exiting.')
+
+ @pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3")
+ def test_recipes(self):
+ """
+ Checks the `recipes` command prints out recipes information without crashing.
+ """
+ argv = ['toolchain.py', 'recipes']
+ with patch_sys_argv(argv), patch_sys_stdout() as m_stdout:
+ ToolchainCL()
+ # check if we have common patterns in the output
+ expected_strings = (
+ 'conflicts:',
+ 'depends:',
+ 'kivy',
+ 'optional depends:',
+ 'python3',
+ 'sdl2',
+ )
+ for expected_string in expected_strings:
+ assert expected_string in m_stdout.getvalue()
+ # deletes static attribute to not mess with other tests
+ del Recipe.recipes
+
+ def test_local_recipes_dir(self):
+ """
+ Checks the `local_recipes` attribute in the Context is absolute.
+ """
+ cwd = os.path.realpath(os.getcwd())
+ common_args = [
+ 'toolchain.py',
+ 'recommendations',
+ ]
+
+ # Check the default ./p4a-recipes becomes absolute.
+ argv = common_args
+ with patch_sys_argv(argv):
+ toolchain = ToolchainCL()
+ expected_local_recipes = os.path.join(cwd, 'p4a-recipes')
+ assert toolchain.ctx.local_recipes == expected_local_recipes
+
+ # Check a supplied relative directory becomes absolute.
+ argv = common_args + ['--local-recipes=foo']
+ with patch_sys_argv(argv):
+ toolchain = ToolchainCL()
+ expected_local_recipes = os.path.join(cwd, 'foo')
+ assert toolchain.ctx.local_recipes == expected_local_recipes
+
+ # An absolute directory should remain unchanged.
+ local_recipes = os.path.join(cwd, 'foo')
+ argv = common_args + ['--local-recipes={}'.format(local_recipes)]
+ with patch_sys_argv(argv):
+ toolchain = ToolchainCL()
+ assert toolchain.ctx.local_recipes == local_recipes
diff --git a/tests/test_util.py b/tests/test_util.py
new file mode 100644
index 000000000..7a60bc73f
--- /dev/null
+++ b/tests/test_util.py
@@ -0,0 +1,232 @@
+import os
+from pathlib import Path
+from tempfile import TemporaryDirectory
+import types
+import unittest
+from unittest import mock
+
+from pythonforandroid import util
+
+
+class TestUtil(unittest.TestCase):
+ """
+ An inherited class of `unittest.TestCase`to test the module
+ :mod:`~pythonforandroid.util`.
+ """
+
+ @mock.patch("pythonforandroid.util.makedirs")
+ def test_ensure_dir(self, mock_makedirs):
+ """
+ Basic test for method :meth:`~pythonforandroid.util.ensure_dir`. Here
+ we make sure that the mentioned method is called only once.
+ """
+ util.ensure_dir("fake_directory")
+ mock_makedirs.assert_called_once_with("fake_directory")
+
+ @mock.patch("shutil.rmtree")
+ @mock.patch("pythonforandroid.util.mkdtemp")
+ def test_temp_directory(self, mock_mkdtemp, mock_shutil_rmtree):
+
+ """
+ Basic test for method :meth:`~pythonforandroid.util.temp_directory`. We
+ perform this test by `mocking` the command `mkdtemp` and
+ `shutil.rmtree` and we make sure that those functions are called in the
+ proper place.
+ """
+ mock_mkdtemp.return_value = "/temp/any_directory"
+ with util.temp_directory():
+ mock_mkdtemp.assert_called_once()
+ mock_shutil_rmtree.assert_not_called()
+ mock_shutil_rmtree.assert_called_once_with("/temp/any_directory")
+
+ @mock.patch("pythonforandroid.util.chdir")
+ def test_current_directory(self, moch_chdir):
+ """
+ Basic test for method :meth:`~pythonforandroid.util.current_directory`.
+ We `mock` chdir and we check that the command is executed once we are
+ inside a python's `with` statement. Then we check that `chdir has been
+ called with the proper arguments inside this `with` statement and also
+ that, once we leave the `with` statement, is called again with the
+ current working path.
+ """
+ chdir_dir = "/temp/any_directory"
+ # test chdir to existing directory
+ with util.current_directory(chdir_dir):
+ moch_chdir.assert_called_once_with("/temp/any_directory")
+ moch_chdir.assert_has_calls(
+ [mock.call("/temp/any_directory"), mock.call(os.getcwd())]
+ )
+
+ def test_current_directory_exception(self):
+ """
+ Another test for method
+ :meth:`~pythonforandroid.util.current_directory`, but here we check
+ that using the method with a non-existing-directory raises an `OSError`
+ exception.
+
+ .. note:: test chdir to non-existing directory, should raise error,
+ for py3 the exception is FileNotFoundError and IOError for py2, to
+ avoid introduce conditions, we test with a more generic exception
+ """
+ with self.assertRaises(OSError), util.current_directory(
+ "/fake/directory"
+ ):
+ pass
+
+ @mock.patch("pythonforandroid.util.walk")
+ def test_walk_valid_filens(self, mock_walk):
+ """
+ Test method :meth:`~pythonforandroid.util.walk_valid_filens`
+ In here we simulate the following directory structure:
+
+ /fake_dir
+ |-- README
+ |-- setup.py
+ |-- __pycache__
+ |-- |__
+ |__Lib
+ |-- abc.pyc
+ |-- abc.py
+ |__ ctypes
+ |-- util.pyc
+ |-- util.py
+
+ Then we execute the method in order to check that we got the expected
+ result, which should be:
+
+ .. code-block:: python
+ :emphasize-lines: 2-4
+
+ expected_result = {
+ "/fake_dir/README",
+ "/fake_dir/Lib/abc.pyc",
+ "/fake_dir/Lib/ctypes/util.pyc",
+ }
+ """
+ simulated_walk_result = [
+ ["/fake_dir", ["__pycache__", "Lib"], ["README", "setup.py"]],
+ ["/fake_dir/Lib", ["ctypes"], ["abc.pyc", "abc.py"]],
+ ["/fake_dir/Lib/ctypes", [], ["util.pyc", "util.py"]],
+ ]
+ mock_walk.return_value = simulated_walk_result
+ file_ens = util.walk_valid_filens(
+ "/fake_dir", ["__pycache__"], ["*.py"]
+ )
+ self.assertIsInstance(file_ens, types.GeneratorType)
+ expected_result = {
+ "/fake_dir/README",
+ "/fake_dir/Lib/abc.pyc",
+ "/fake_dir/Lib/ctypes/util.pyc",
+ }
+ result = set(file_ens)
+
+ self.assertEqual(result, expected_result)
+
+ def test_util_exceptions(self):
+ """
+ Test exceptions for a couple of methods:
+
+ - method :meth:`~pythonforandroid.util.BuildInterruptingException`
+ - method :meth:`~pythonforandroid.util.handle_build_exception`
+
+ Here we create an exception with method
+ :meth:`~pythonforandroid.util.BuildInterruptingException` and we run it
+ inside method :meth:`~pythonforandroid.util.handle_build_exception` to
+ make sure that it raises an `SystemExit`.
+ """
+ exc = util.BuildInterruptingException(
+ "missing dependency xxx", instructions="pip install --user xxx"
+ )
+ with self.assertRaises(SystemExit):
+ util.handle_build_exception(exc)
+
+ def test_move(self):
+ with mock.patch(
+ "pythonforandroid.util.LOGGER"
+ ) as m_logger, TemporaryDirectory() as base_dir:
+ new_path = Path(base_dir) / "new"
+
+ # Set up source
+ old_path = Path(base_dir) / "old"
+ with open(old_path, "w") as outfile:
+ outfile.write("Temporary content")
+
+ # Non existent source
+ with self.assertRaises(FileNotFoundError):
+ util.move(new_path, new_path)
+ m_logger.debug.assert_called()
+ m_logger.error.assert_not_called()
+ m_logger.reset_mock()
+ assert old_path.exists()
+ assert not new_path.exists()
+
+ # Successful move
+ util.move(old_path, new_path)
+ assert not old_path.exists()
+ assert new_path.exists()
+ m_logger.debug.assert_called()
+ m_logger.error.assert_not_called()
+ m_logger.reset_mock()
+
+ # Move over existing:
+ existing_path = Path(base_dir) / "existing"
+ existing_path.touch()
+
+ util.move(new_path, existing_path)
+ with open(existing_path, "r") as infile:
+ assert infile.read() == "Temporary content"
+ m_logger.debug.assert_called()
+ m_logger.error.assert_not_called()
+ m_logger.reset_mock()
+
+ def test_touch(self):
+ # Just checking the new file case.
+ # Assume the existing file timestamp case will work if this does.
+ with TemporaryDirectory() as base_dir:
+ new_file_path = Path(base_dir) / "new_file"
+ assert not new_file_path.exists()
+ util.touch(new_file_path)
+ assert new_file_path.exists()
+
+ def test_build_tools_version_sort_key(self):
+
+ build_tools_versions = [
+ "26.0.1",
+ "26.0.0",
+ "26.0.2",
+ "32.0.0 rc1",
+ "31.0.0",
+ "999something",
+ ]
+
+ expected_result = [
+ "999something", # invalid version
+ "26.0.0",
+ "26.0.1",
+ "26.0.2",
+ "31.0.0",
+ "32.0.0 rc1",
+ ]
+
+ result = sorted(
+ build_tools_versions, key=util.build_tools_version_sort_key
+ )
+
+ self.assertEqual(result, expected_result)
+
+ def test_max_build_tool_version(self):
+
+ build_tools_versions = [
+ "26.0.1",
+ "26.0.0",
+ "26.0.2",
+ "32.0.0 rc1",
+ "31.0.0",
+ "999something",
+ ]
+
+ expected_result = "32.0.0 rc1"
+
+ result = util.max_build_tool_version(build_tools_versions)
+
+ self.assertEqual(result, expected_result)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 000000000..9b0432c82
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,45 @@
+[tox]
+envlist = pep8,py3
+basepython = python3
+
+[testenv]
+deps =
+ pytest
+ py3: coveralls
+ backports.tempfile
+# posargs will be replaced by the tox args, so you can override pytest
+# args e.g. `tox -- tests/test_graph.py`
+commands = pytest {posargs:tests/}
+passenv = GITHUB_*
+setenv =
+ PYTHONPATH={toxinidir}
+ SKIP_PREREQUISITES_CHECK=1
+
+[testenv:py3]
+# for py3 env we will get code coverage
+commands =
+ coverage run --branch --source=pythonforandroid -m pytest {posargs:tests/}
+ coverage report -m
+
+[testenv:pep8]
+deps = flake8
+commands = flake8 pythonforandroid/ tests/ ci/ setup.py
+
+[flake8]
+ignore =
+ # Closing bracket does not match indentation of opening bracket's line
+ E123,
+ # Closing bracket does not match visual indentation
+ E124,
+ # Continuation line over-indented for hanging indent
+ E126,
+ # Missing whitespace around arithmetic operator
+ E226,
+ # Module level import not at top of file
+ E402,
+ # Line too long (82 > 79 characters)
+ E501,
+ # Line break occurred before a binary operator
+ W503,
+ # Line break occurred after a binary operator
+ W504