diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 1faaa18d70..c6c5f55c2f 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -15,9 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout python-for-android - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python 3.x - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.x - name: Run flake8 @@ -32,13 +32,13 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10', '3.11'] os: [ubuntu-latest, macOs-latest] steps: - name: Checkout python-for-android - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Tox tests @@ -53,8 +53,8 @@ jobs: parallel: true flag-name: run-${{ matrix.os }}-${{ matrix.python-version }} - ubuntu_build_apk: - name: Unit test apk [ ${{ matrix.runs_on }} | ${{ matrix.bootstrap.name }} ] + ubuntu_build: + name: Build test APP [ ${{ matrix.runs_on }} | ${{ matrix.bootstrap.name }} ] needs: [flake8] runs-on: ${{ matrix.runs_on }} continue-on-error: true @@ -68,36 +68,41 @@ jobs: target: testapps-with-scipy - name: webview target: testapps-webview + - name: service_library + target: testapps-service_library-aar steps: - name: Checkout python-for-android - uses: actions/checkout@v2 - # 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: Build multi-arch apk Python 3 (armeabi-v7a, arm64-v8a, x86_64, x86) - run: | - mkdir -p apks - make docker/run/make/with-artifact/apk/${{ matrix.bootstrap.target }} - - name: Rename apk artifact to include the build platform name - run: | - mv apks/${{ env.APK_ARTIFACT_FILENAME }} apks/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.APK_ARTIFACT_FILENAME }} - - name: Upload apk artifact - uses: actions/upload-artifact@v1 + uses: actions/checkout@v3 + - 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 }}-${{ env.APK_ARTIFACT_FILENAME }} - path: apks/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.APK_ARTIFACT_FILENAME }} + name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-artifacts + path: dist - macos_build_apk: - name: Unit test apk [ ${{ matrix.runs_on }} | ${{ matrix.bootstrap.name }} ] + macos_build: + name: Build test APP [ ${{ matrix.runs_on }} | ${{ matrix.bootstrap.name }} ] needs: [flake8] runs-on: ${{ matrix.runs_on }} continue-on-error: true @@ -116,7 +121,7 @@ jobs: ANDROID_NDK_HOME: ${HOME}/.android/android-ndk steps: - name: Checkout python-for-android - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install python-for-android run: | source ci/osx_ci.sh @@ -137,146 +142,21 @@ jobs: source ci/osx_ci.sh arm64_set_path_and_python_version 3.9.7 make ${{ matrix.bootstrap.target }} - - name: Rename apk artifact to include the build platform name + - name: Copy produced artifacts into dist/ (*.apk, *.aab) run: | - mv testapps/on_device_unit_tests/${{ env.APK_ARTIFACT_FILENAME }} ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.APK_ARTIFACT_FILENAME }} - - name: Upload apk artifact - uses: actions/upload-artifact@v1 - with: - name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.APK_ARTIFACT_FILENAME }} - path: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.APK_ARTIFACT_FILENAME }} - - ubuntu_build_aab: - name: Unit test aab [ ${{ matrix.runs_on }} ] - needs: [flake8] - runs-on: ${{ matrix.runs_on }} - continue-on-error: true - strategy: - matrix: - runs_on: [ubuntu-latest] - bootstrap: - - name: sdl2 - target: testapps-with-numpy-aab - - name: webview - target: testapps-webview-aab - steps: - - name: Checkout python-for-android - uses: actions/checkout@v2 - # 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: Build Android App Bundle Python 3 (armeabi-v7a, arm64-v8a, x86_64, x86) - run: | - mkdir -p aabs - make docker/run/make/with-artifact/aab/${{ matrix.bootstrap.target }} - - name: Rename artifact to include the build platform name - run: | - mv aabs/${{ env.AAB_ARTIFACT_FILENAME }} aabs/${{ matrix.runs_on }}-${{ matrix.bootstrap.name}}-${{ env.AAB_ARTIFACT_FILENAME }} - - name: Upload apk artifact - uses: actions/upload-artifact@v1 - with: - name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name}}-${{ env.AAB_ARTIFACT_FILENAME }} - path: aabs/${{ matrix.runs_on }}-${{ matrix.bootstrap.name}}-${{ env.AAB_ARTIFACT_FILENAME }} - - - ubuntu_build_aar: - name: Unit test aar [ ${{ matrix.runs_on }} ] - needs: [flake8] - runs-on: ${{ matrix.runs_on }} - continue-on-error: true - strategy: - matrix: - runs_on: [ubuntu-latest] - bootstrap: - - name: service_library - target: testapps-service_library-aar - steps: - - name: Checkout python-for-android - uses: actions/checkout@v2 - # 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: Build Android AAR Python 3 (arm64-v8a) - run: | - mkdir -p aars - make docker/run/make/with-artifact/aar/${{ matrix.bootstrap.target }} - - name: Rename artifact to include the build platform name - run: | - mv aars/${{ env.AAR_ARTIFACT_FILENAME }} aars/${{ matrix.runs_on }}-${{ matrix.bootstrap.name}}-${{ env.AAR_ARTIFACT_FILENAME }} - - name: Upload aar artifact - uses: actions/upload-artifact@v1 - with: - name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name}}-${{ env.AAR_ARTIFACT_FILENAME }} - path: aars/${{ matrix.runs_on }}-${{ matrix.bootstrap.name}}-${{ env.AAR_ARTIFACT_FILENAME }} - - - macos_build_aab: - name: Unit test aab [ ${{ 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-aab - - name: webview - target: testapps-webview-aab - 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@v2 - - name: Install python-for-android + 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: | - 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 sdl2 aab 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: Rename sdl2 artifact to include the build platform name - run: | - mv testapps/on_device_unit_tests/${{ env.AAB_ARTIFACT_FILENAME }} ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.AAB_ARTIFACT_FILENAME }} - - name: Upload sdl2 apk artifact - uses: actions/upload-artifact@v1 + 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 }}-${{ env.AAB_ARTIFACT_FILENAME }} - path: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.AAB_ARTIFACT_FILENAME }} + 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 ] @@ -289,8 +169,8 @@ jobs: env: REBUILD_UPDATED_RECIPES_EXTRA_ARGS: --arch=${{ matrix.android_arch }} steps: - - name: Checkout python-for-android - uses: actions/checkout@v2 + - name: Checkout python-for-android (all-history) + uses: actions/checkout@v3 with: fetch-depth: 0 # helps with GitHub runner getting out of space @@ -325,8 +205,8 @@ jobs: ANDROID_NDK_HOME: ${HOME}/.android/android-ndk REBUILD_UPDATED_RECIPES_EXTRA_ARGS: --arch=${{ matrix.android_arch }} steps: - - name: Checkout python-for-android - uses: actions/checkout@v2 + - name: Checkout python-for-android (all-history) + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install python-for-android diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index b5bb5fbb6c..a66a30567e 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -5,11 +5,11 @@ jobs: pypi_release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.x - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.x + python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade setuptools wheel twine diff --git a/CHANGELOG.md b/CHANGELOG.md index 2393dc204e..2ce4711cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,201 @@ # Changelog +## [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) diff --git a/Makefile b/Makefile index 97f502219e..9747c6c43d 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ all: virtualenv $(VIRTUAL_ENV): python3 -m venv $(VIRTUAL_ENV) - $(PIP) install Cython + $(PIP) install Cython==0.29.36 $(PIP) install -e . virtualenv: $(VIRTUAL_ENV) @@ -36,45 +36,51 @@ rebuild_updated_recipes: virtualenv 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: virtualenv +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 apk --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \ + 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 + --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: virtualenv +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 apk --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \ + 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-with-numpy-aab: virtualenv - . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \ - python setup.py aab --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 --release +testapps-webview: testapps-webview/debug/apk testapps-webview/release/aab -testapps-service_library-aar: virtualenv +# 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 aar --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \ - --bootstrap service_library \ - --requirements python3 \ - --arch=arm64-v8a --arch=x86 --release - -testapps-webview: virtualenv - . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \ - python setup.py apk --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \ + 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-webview-aab: virtualenv +testapps-service_library-aar: virtualenv . $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \ - python setup.py aab --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 --release + 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/%: virtualenv $(eval $@_APP_ARCH := $(shell basename $*)) @@ -104,21 +110,6 @@ docker/run/test: docker/build docker/run/command: docker/build docker run --rm --env-file=.env $(DOCKER_IMAGE) /bin/sh -c "$(COMMAND)" -docker/run/make/with-artifact/apk/%: docker/build - docker run --name p4a-latest --env-file=.env $(DOCKER_IMAGE) make $* - docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/bdist_unit_tests_app-debug-1.1.apk ./apks - docker rm -fv p4a-latest - -docker/run/make/with-artifact/aar/%: docker/build - docker run --name p4a-latest --env-file=.env $(DOCKER_IMAGE) make $* - docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/bdist_unit_tests_app-release-1.1.aar ./aars - docker rm -fv p4a-latest - -docker/run/make/with-artifact/aab/%: docker/build - docker run --name p4a-latest --env-file=.env $(DOCKER_IMAGE) make $* - docker cp p4a-latest:/home/user/app/testapps/on_device_unit_tests/bdist_unit_tests_app-release-1.1.aab ./aabs - docker rm -fv p4a-latest - 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 diff --git a/README.md b/README.md index 269673a0b6..764ea7b7a7 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ other API levels may not work. With everything installed, build an APK with SDL2 with e.g.: - p4a apk --requirements=kivy --private /home/username/devel/planewave_frozen/ --package=net.inclem.planewavessdl2 --name="planewavessdl2" --version=0.5 --bootstrap=sdl2 + p4a apk --private PATH_TO_YOUR_APP_CODE --package=org.example.myapp --name "My application" --version 0.1 --bootstrap=sdl2 --requirements=python3,kivy **If you need to deploy your app on Google Play, Android App Bundle (aab) is required since 1 August 2021:** @@ -96,7 +96,7 @@ Please refer to the LICENSE file. 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 at at +status is recorded for posterity at https://github.com/kivy/python-for-android/tree/old_toolchain. In the last quarter of 2018 the python recipes were changed. The diff --git a/ci/constants.py b/ci/constants.py index 021e64a74a..6a81a23405 100644 --- a/ci/constants.py +++ b/ci/constants.py @@ -33,6 +33,13 @@ class TargetPython(Enum): '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', + ]) BROKEN_RECIPES = { diff --git a/doc/source/buildoptions.rst b/doc/source/buildoptions.rst index d9f97175a2..8ad067b205 100644 --- a/doc/source/buildoptions.rst +++ b/doc/source/buildoptions.rst @@ -8,8 +8,8 @@ This page contains instructions for using different build options. Python versions --------------- -python-for-android supports using Python 3.7 or higher. To explicitly select a Python -version in your requirements, use e.g. ``--requirements=python3==3.7.1,hostpython3==3.7.1``. +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``. The last python-for-android version supporting Python2 was `v2019.10.06 `__ @@ -57,16 +57,37 @@ options (this list may not be exhaustive): - ``--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 - `__. +- ``--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 `__. - ``--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. @@ -89,8 +110,9 @@ 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 @@ -120,12 +142,16 @@ ready. - ``--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 - `__. +- ``--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 `__. - ``--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 diff --git a/doc/source/launcher.rst b/doc/source/launcher.rst index f863e493fc..cc4ccf6ea5 100644 --- a/doc/source/launcher.rst +++ b/doc/source/launcher.rst @@ -48,7 +48,7 @@ grab an old (cached) package instead of a fresh one. .. 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 + adding `main.py` or `main.pyc` to the app. The argument `--launcher` is above them and tells the p4a to build the launcher version of the APK. Usage diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst index 6797bcadc6..2e3fe21108 100644 --- a/doc/source/quickstart.rst +++ b/doc/source/quickstart.rst @@ -232,7 +232,7 @@ Exporting the Android App Bundle (aab) for distributing it on Google Play 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. -python-for-android handles by itself the needed work to accomplish the new requirements: +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 diff --git a/doc/source/services.rst b/doc/source/services.rst index c66657f2a2..d202190056 100644 --- a/doc/source/services.rst +++ b/doc/source/services.rst @@ -107,6 +107,14 @@ 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 diff --git a/pythonforandroid/__init__.py b/pythonforandroid/__init__.py index f39a847cb5..bcabd44859 100644 --- a/pythonforandroid/__init__.py +++ b/pythonforandroid/__init__.py @@ -1 +1 @@ -__version__ = '2022.09.04' +__version__ = '2023.09.16' diff --git a/pythonforandroid/bdistapk.py b/pythonforandroid/bdistapk.py index cc1487c85e..b4467b9f91 100644 --- a/pythonforandroid/bdistapk.py +++ b/pythonforandroid/bdistapk.py @@ -1,10 +1,10 @@ +from glob import glob +from os.path import realpath, join, dirname, curdir, basename, split from setuptools import Command - +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): @@ -43,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: @@ -87,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(): @@ -104,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? diff --git a/pythonforandroid/bootstrap.py b/pythonforandroid/bootstrap.py index 0a5225e526..8bbbcf0eb6 100755 --- a/pythonforandroid/bootstrap.py +++ b/pythonforandroid/bootstrap.py @@ -10,7 +10,8 @@ from pythonforandroid.logger import (shprint, info, logger, debug) from pythonforandroid.util import ( - current_directory, ensure_dir, temp_directory, BuildInterruptingException) + current_directory, ensure_dir, temp_directory, BuildInterruptingException, + rmdir, move) from pythonforandroid.recipe import Recipe @@ -70,7 +71,6 @@ class Bootstrap: '''An Android project template, containing recipe stuff for compilation and templated fields for APK info. ''' - name = '' jni_subdir = '/jni' ctx = None @@ -395,9 +395,9 @@ 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) + for f in files: + move(f, sitepackages) + rmdir(d) def expand_dependencies(recipes, ctx): diff --git a/pythonforandroid/bootstraps/common/build/build.py b/pythonforandroid/bootstraps/common/build/build.py index 2bc1163d68..93ec611799 100644 --- a/pythonforandroid/bootstraps/common/build/build.py +++ b/pythonforandroid/bootstraps/common/build/build.py @@ -21,6 +21,8 @@ from fnmatch import fnmatch import jinja2 +from pythonforandroid.util import rmdir, ensure_dir + def get_dist_info_for(key, error_if_missing=True): try: @@ -40,10 +42,6 @@ def get_hostpython(): return get_dist_info_for('hostpython') -def get_python_version(): - return get_dist_info_for('python_version') - - def get_bootstrap_name(): return get_dist_info_for('bootstrap') @@ -57,11 +55,6 @@ def get_bootstrap_name(): curdir = dirname(__file__) -PYTHON = get_hostpython() -PYTHON_VERSION = get_python_version() -if PYTHON is not None and not exists(PYTHON): - PYTHON = None - BLACKLIST_PATTERNS = [ # code versionning '^*.hg/*', @@ -73,17 +66,26 @@ def get_bootstrap_name(): '~', '*.bak', '*.swp', + + # Android artifacts + '*.apk', + '*.aab', ] -# pyc/py -if PYTHON is not None: - BLACKLIST_PATTERNS.append('*.py') WHITELIST_PATTERNS = [] -if get_bootstrap_name() in ('sdl2', 'webview', 'service_only'): - WHITELIST_PATTERNS.append('pyconfig.h') -python_files = [] +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'): + WHITELIST_PATTERNS.append('pyconfig.h') environment = jinja2.Environment(loader=jinja2.FileSystemLoader( join(curdir, 'templates'))) @@ -93,11 +95,6 @@ def get_bootstrap_name(): DEFAULT_PYTHON_SERVICE_JAVA_CLASS = 'org.kivy.android.PythonService' -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 @@ -150,23 +147,11 @@ def listfiles(d): yield fn -def make_tar(tfn, source_dirs, ignore_path=[], optimize_python=True): +def make_tar(tfn, source_dirs, byte_compile_python=False, optimize_python=True): ''' 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) - def clean(tinfo): """cleaning function (for reproducible builds)""" tinfo.uid = tinfo.gid = 0 @@ -178,9 +163,12 @@ def clean(tinfo): files = [] for sd in source_dirs: sd = realpath(sd) - compile_dir(sd, optimize_python=optimize_python) - files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd) - if select(x)] + 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 @@ -210,18 +198,15 @@ def clean(tinfo): gf.close() -def compile_dir(dfn, optimize_python=True): +def compile_py_file(python_file, optimize_python=True): ''' - Compile *.py in directory `dfn` to *.pyo + Compile python_file to *.pyc and return the filename of the *.pyc file. ''' if PYTHON is None: return - if int(PYTHON_VERSION[0]) >= 3: - args = [PYTHON, '-m', 'compileall', '-b', '-f', dfn] - else: - args = [PYTHON, '-m', 'compileall', '-f', dfn] + args = [PYTHON, '-m', 'compileall', '-b', '-f', python_file] if optimize_python: # -OO = strip docstrings args.insert(1, '-OO') @@ -233,16 +218,18 @@ def compile_dir(dfn, optimize_python=True): '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.pyo: + # 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.pyo'))): - print('''BUILD FAILURE: No main.py(o) found in your app directory. This + 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.''') @@ -251,7 +238,7 @@ def make_package(args): assets_dir = "src/main/assets" # Delete the old assets. - shutil.rmtree(assets_dir, ignore_errors=True) + rmdir(assets_dir, ignore_errors=True) ensure_dir(assets_dir) # Add extra environment variable file into tar-able directory: @@ -259,8 +246,8 @@ def make_package(args): 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, "orientation"): - f.write("P4A_ORIENTATION=" + str(args.orientation) + "\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") @@ -290,7 +277,6 @@ def make_package(args): variants = [ copy_path, copy_path.partition(".")[0] + ".pyc", - copy_path.partition(".")[0] + ".pyo", ] # Check in all variants with all possible endings: for variant in variants: @@ -301,7 +287,7 @@ def make_package(args): not exists( join(main_py_only_dir, dir_path) )): - os.mkdir(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), @@ -326,20 +312,44 @@ def make_package(args): for arch in get_dist_info_for("archs"): libs_dir = f"libs/{arch}" make_tar( - join(libs_dir, 'libpybundle.so'), [f'_python_bundle__{arch}'], args.ignore_path, - optimize_python=args.optimize_python) + 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, args.ignore_path, - optimize_python=args.optimize_python) + 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: - shutil.rmtree(directory) + rmdir(directory) # Remove extra env vars tar-able directory: - shutil.rmtree(env_vars_tarpath) + 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( @@ -485,14 +495,16 @@ def make_package(args): with open('project.properties', 'r') as fileh: target = fileh.read().strip() android_api = target.split('-')[1] - try: - int(android_api) - except (ValueError, TypeError): + + 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:] @@ -639,20 +651,92 @@ def make_package(args): subprocess.check_output(patch_command) -def parse_args_and_make_package(args=None): - global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON +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) - default_min_api = int(info['ndk_api']) - ndk_api = default_min_api + ndk_api = int(info['ndk_api']) except (OSError, KeyError, ValueError, TypeError): print('WARNING: Failed to read ndk_api from dist info, defaulting to 12') - default_min_api = 12 # The old default before ndk_api was introduced - ndk_api = 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 @@ -688,6 +772,8 @@ def parse_args_and_make_package(args=None): 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=[], @@ -698,6 +784,10 @@ def parse_args_and_make_package(args=None): 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.')) @@ -731,19 +821,21 @@ def parse_args_and_make_package(args=None): 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', - 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')) + 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', @@ -798,9 +890,9 @@ def parse_args_and_make_package(args=None): 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_min_api, type=int, + default=ndk_api, type=int, help=('Minimum Android SDK version that the app supports. ' - 'Defaults to {}.'.format(default_min_api))) + '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 ' @@ -824,8 +916,6 @@ def parse_args_and_make_package(args=None): 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.')) @@ -844,9 +934,12 @@ def parse_args_and_make_package(args=None): '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 .pyo files, using -OO ' + 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 element of' @@ -865,6 +958,15 @@ def parse_args_and_make_package(args=None): 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') + 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:] @@ -884,8 +986,6 @@ def _read_configuration(): 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] @@ -906,13 +1006,19 @@ def _read_configuration(): print('Billing not yet supported!') sys.exit(1) - if args.sdk_version == -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 - if args.permissions and isinstance(args.permissions[0], list): - args.permissions = [p for perm in args.permissions for p in perm] + 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] @@ -928,10 +1034,6 @@ def _read_configuration(): 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() @@ -956,4 +1058,6 @@ def _read_configuration(): if __name__ == "__main__": + if get_bootstrap_name() in ('sdl2', 'webview', 'service_only'): + WHITELIST_PATTERNS.append('pyconfig.h') parse_args_and_make_package() diff --git a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c b/pythonforandroid/bootstraps/common/build/jni/application/src/start.c index d46b1fae0d..ce93ca27fd 100644 --- a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c +++ b/pythonforandroid/bootstraps/common/build/jni/application/src/start.c @@ -206,7 +206,7 @@ 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"); + PyRun_SimpleString("import io, sys, posix\n"); char add_site_packages_dir[256]; @@ -224,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" + " 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" " for l in lines[:-1]:\n" " androidembed.log(l.replace('\\x00', ''))\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" @@ -251,14 +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, '.'); -#if PY_MAJOR_VERSION > 2 char *ext = ".pyc"; -#else - char *ext = ".pyo"; -#endif if (dot <= 0) { LOGP("Invalid entrypoint, abort."); return -1; @@ -281,14 +279,10 @@ int main(int argc, char *argv[]) { 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'; -#if PY_MAJOR_VERSION > 2 entrypoint[strlen(env_entrypoint)] = 'c'; -#else - entrypoint[strlen(env_entrypoint)] = 'o'; -#endif if (!file_exists(entrypoint)) { /* fallback on pure python version */ if (!file_exists(env_entrypoint)) { diff --git a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java index dd6f307ec7..76d3b2e77b 100644 --- a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java +++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java @@ -102,43 +102,61 @@ protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) 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_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + // 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 { // 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"; //TODO: make this configurable - String channelName = "Background Service"; //TODO: make this configurable - NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, - NotificationManager.IMPORTANCE_NONE); - + 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(serviceTitle); - builder.setContentText(serviceDescription); + builder.setContentTitle(contentTitle); + builder.setContentText(contentText); builder.setContentIntent(pIntent); - builder.setSmallIcon(context.getApplicationInfo().icon); + builder.setSmallIcon(smallIconId); notification = builder.build(); } startForeground(getServiceId(), notification); 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 index 2d6ca9f73e..5bc23bacf4 100644 --- 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 @@ -54,6 +54,7 @@ protected static ArrayList getLibraries(File libsDir) { libsList.add("python3.7m"); libsList.add("python3.8"); libsList.add("python3.9"); + libsList.add("python3.10"); libsList.add("main"); return libsList; } @@ -73,7 +74,7 @@ public static void loadLibraries(File filesDir, File libsDir) { // load, and it has failed, give a more // general error Log.v(TAG, "Library loading error: " + e.getMessage()); - if (lib.startsWith("python3.9") && !foundPython) { + if (lib.startsWith("python3.10") && !foundPython) { throw new RuntimeException("Could not load any libpythonXXX.so"); } else if (lib.startsWith("python")) { continue; diff --git a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java index de84ac42bf..9406f91d89 100644 --- a/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java +++ b/pythonforandroid/bootstraps/common/build/templates/Service.tmpl.java @@ -17,31 +17,49 @@ public int startType() { 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) { - Intent intent = getDefaultIntent(ctx, pythonServiceArgument); - ctx.startService(intent); + _start(ctx, "", "{{ args.name }}", "{{ name|capitalize }}", pythonServiceArgument); } - static public Intent getDefaultIntent(Context ctx, String 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("serviceDescription", "{{ name|capitalize }}"); 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); + return Service{{ name|capitalize }}.getDefaultIntent(ctx, "", "", "", + pythonServiceArgument); } static public void stop(Context ctx) { diff --git a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle b/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle index bb000393a4..ce105736d3 100644 --- a/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle +++ b/pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle @@ -16,9 +16,6 @@ allprojects { {%- for repo in args.gradle_repositories %} {{repo}} {%- endfor %} - flatDir { - dirs 'libs' - } } } diff --git a/pythonforandroid/bootstraps/sdl2/__init__.py b/pythonforandroid/bootstraps/sdl2/__init__.py index 662d43c0ef..9334724a33 100644 --- a/pythonforandroid/bootstraps/sdl2/__init__.py +++ b/pythonforandroid/bootstraps/sdl2/__init__.py @@ -1,9 +1,11 @@ -from pythonforandroid.toolchain import ( - Bootstrap, shprint, current_directory, info, info_main) -from pythonforandroid.util import ensure_dir from os.path import join + import sh +from pythonforandroid.toolchain import ( + Bootstrap, shprint, current_directory, info, info_main) +from pythonforandroid.util import ensure_dir, rmdir + class SDL2GradleBootstrap(Bootstrap): name = 'sdl2' @@ -15,8 +17,8 @@ class SDL2GradleBootstrap(Bootstrap): def assemble_distribution(self): info_main("# Creating Android project ({})".format(self.name)) + rmdir(self.dist_dir) info("Copying SDL2/gradle build") - shprint(sh.rm, "-rf", self.dist_dir) shprint(sh.cp, "-r", self.build_dir, self.dist_dir) # either the build use environment variable (ANDROID_HOME) 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 ded381f161..361975a4cf 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 @@ -419,11 +419,10 @@ public void run() { } public String getEntryPoint(String search_dir) { - /* Get the main file (.pyc|.pyo|.py) depending on if we + /* Get the main file (.pyc|.py) depending on if we * have a compiled version or not. */ List entryPoints = new ArrayList(); - entryPoints.add("main.pyo"); // python 2 compiled files entryPoints.add("main.pyc"); // python 3 compiled files for (String value : entryPoints) { File mainFile = new File(search_dir + "/" + value); diff --git a/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch b/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch index c7aa4bf666..71d2537e7c 100644 --- a/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch +++ b/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch @@ -1,6 +1,6 @@ --- a/src/main/java/org/libsdl/app/SDLActivity.java +++ b/src/main/java/org/libsdl/app/SDLActivity.java -@@ -225,6 +225,8 @@ +@@ -222,6 +222,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // This is what SDL runs in. It invokes SDL_main(), eventually protected static Thread mSDLThread; @@ -9,7 +9,7 @@ protected static SDLGenericMotionListener_API12 getMotionListener() { if (mMotionListener == null) { if (Build.VERSION.SDK_INT >= 26) { -@@ -323,6 +325,15 @@ +@@ -324,6 +326,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh Log.v(TAG, "onCreate()"); super.onCreate(savedInstanceState); @@ -25,7 +25,7 @@ try { Thread.currentThread().setName("SDLActivity"); } catch (Exception e) { -@@ -824,7 +835,7 @@ +@@ -835,7 +846,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh Handler commandHandler = new SDLCommandHandler(); // Send a message from the SDLMain thread @@ -34,11 +34,11 @@ Message msg = commandHandler.obtainMessage(); msg.arg1 = command; msg.obj = data; -@@ -1302,6 +1313,20 @@ +@@ -1384,6 +1395,20 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh return SDLActivity.mSurface.getNativeSurface(); } -+ /** ++ /** + * Calls turnActive() on singleton to keep loading screen active + */ + public static void triggerAppConfirmedActive() { @@ -55,21 +55,21 @@ // Input /** -@@ -1795,7 +1820,7 @@ - } +@@ -1878,6 +1903,7 @@ class SDLMain implements Runnable { Log.v("SDL", "Running main function " + function + " from library " + library); -- + + SDLActivity.mSingleton.appConfirmedActive(); SDLActivity.nativeRunMain(library, function, arguments); Log.v("SDL", "Finished main function"); -@@ -2316,7 +2341,7 @@ +@@ -1935,8 +1961,7 @@ class DummyEdit extends View implements View.OnKeyListener { public InputConnection onCreateInputConnection(EditorInfo outAttrs) { ic = new SDLInputConnection(this, true); -- outAttrs.inputType = InputType.TYPE_CLASS_TEXT; +- 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 */; + 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 76837f5836..38115442ce 100644 --- a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml +++ b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml @@ -24,14 +24,9 @@ - - + {% for perm in args.permissions %} - {% if '.' in perm %} - - {% else %} - - {% endif %} + {% endfor %} {% if args.wakelock %} @@ -77,26 +72,29 @@ - {% if args.launcher %} + {% if args.launcher %} - {% else %} - - {% endif %} + {% if args.home_app %} + + + {% endif %} + + {%- if args.intent_filters -%} {{- args.intent_filters -}} {%- endif -%} diff --git a/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java index f1eaf0702d..ff889b462c 100644 --- a/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java +++ b/pythonforandroid/bootstraps/service_library/build/templates/Service.tmpl.java @@ -34,10 +34,13 @@ public static void prepare(Context ctx) { PythonUtil.unpackAsset(ctx, "private", app_root_file, true); PythonUtil.unpackPyBundle(ctx, ctx.getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false); } - - public static void start(Context ctx, String pythonServiceArgument) { - Intent intent = getDefaultIntent(ctx, pythonServiceArgument); - + + 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) { @@ -50,26 +53,43 @@ public static void start(Context ctx, String pythonServiceArgument) { {% endif %} } - static public Intent getDefaultIntent(Context ctx, String pythonServiceArgument) { + 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("serviceDescription", ""); 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); + return Service{{ name|capitalize }}.getDefaultIntent(ctx, "", "", "", + pythonServiceArgument); } diff --git a/pythonforandroid/bootstraps/service_only/__init__.py b/pythonforandroid/bootstraps/service_only/__init__.py index b9e000c012..4f0d6cf20b 100644 --- a/pythonforandroid/bootstraps/service_only/__init__.py +++ b/pythonforandroid/bootstraps/service_only/__init__.py @@ -2,7 +2,7 @@ from os.path import join from pythonforandroid.toolchain import ( Bootstrap, current_directory, info, info_main, shprint) -from pythonforandroid.util import ensure_dir +from pythonforandroid.util import ensure_dir, rmdir class ServiceOnlyBootstrap(Bootstrap): @@ -18,7 +18,7 @@ def assemble_distribution(self): 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: diff --git a/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java index 87ea061c41..57112dd555 100644 --- a/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java +++ b/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java @@ -46,11 +46,10 @@ public String getAppRoot() { } public String getEntryPoint(String search_dir) { - /* Get the main file (.pyc|.pyo|.py) depending on if we + /* Get the main file (.pyc|.py) depending on if we * have a compiled version or not. */ List entryPoints = new ArrayList(); - entryPoints.add("main.pyo"); // python 2 compiled files entryPoints.add("main.pyc"); // python 3 compiled files for (String value : entryPoints) { File mainFile = new File(search_dir + "/" + value); diff --git a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml index 10630989ec..a9b49fb59f 100644 --- a/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml +++ b/pythonforandroid/bootstraps/service_only/build/templates/AndroidManifest.tmpl.xml @@ -20,11 +20,7 @@ {% for perm in args.permissions %} - {% if '.' in perm %} - - {% else %} - - {% endif %} + {% endfor %} {% if args.wakelock %} diff --git a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java index 598549d345..eeda810bef 100644 --- a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java +++ b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java @@ -34,16 +34,40 @@ public static void start(Context ctx, String pythonServiceArgument) { 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); diff --git a/pythonforandroid/bootstraps/webview/__init__.py b/pythonforandroid/bootstraps/webview/__init__.py index da33ac72d5..7604ed3b84 100644 --- a/pythonforandroid/bootstraps/webview/__init__.py +++ b/pythonforandroid/bootstraps/webview/__init__.py @@ -1,8 +1,10 @@ -from pythonforandroid.toolchain import Bootstrap, current_directory, info, info_main, shprint -from pythonforandroid.util import ensure_dir 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' @@ -15,7 +17,7 @@ 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: 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 index 8aa308b24a..2f0afdc6f4 100644 --- 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 @@ -68,11 +68,10 @@ public String getAppRoot() { } public String getEntryPoint(String search_dir) { - /* Get the main file (.pyc|.pyo|.py) depending on if we + /* Get the main file (.pyc|.py) depending on if we * have a compiled version or not. */ List entryPoints = new ArrayList(); - entryPoints.add("main.pyo"); // python 2 compiled files entryPoints.add("main.pyc"); // python 3 compiled files for (String value : entryPoints) { File mainFile = new File(search_dir + "/" + value); diff --git a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml index 9bb11c3817..db4296ebe1 100644 --- a/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml +++ b/pythonforandroid/bootstraps/webview/build/templates/AndroidManifest.tmpl.xml @@ -21,11 +21,7 @@ {% for perm in args.permissions %} - {% if '.' in perm %} - - {% else %} - - {% endif %} + {% endfor %} {% if args.wakelock %} @@ -68,7 +64,7 @@ 1: # No obvious commitment in what to add, don't check it itself # but throw it into deps for later comparing against diff --git a/pythonforandroid/patching.py b/pythonforandroid/patching.py index 96a3b273bb..1e143cef90 100644 --- a/pythonforandroid/patching.py +++ b/pythonforandroid/patching.py @@ -1,89 +1,178 @@ -from os import uname -from distutils.version import LooseVersion +""" + 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. -def check_any(*callables): - def check(**kwargs): - return any(c(**kwargs) for c in callables) - return check + 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 + + +# 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. + """ + def check(arch, recipe): + return uname().system.lower() == platform.lower() + + return check -is_linux = is_platform('Linux') -is_darwin = is_platform('Darwin') + +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 - -def is_api(apiver): - def is_x(recipe, **kwargs): - return recipe.ctx.android_api == apiver - return is_x + return check -def will_build(recipe_name): - def will(recipe, **kwargs): - return recipe_name in recipe.ctx.recipe_build_order - return will +# Android API comparisons: def is_ndk(ndk): - def is_x(recipe, **kwargs): + """ + 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 is_x + + 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): - def is_x(recipe, **kwargs): - return LooseVersion(recipe.version) > 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): - def is_x(recipe, **kwargs): - return LooseVersion(recipe.version) < version - return is_x + """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): - def is_x(recipe, **kwargs): - return recipe.version.startswith(version) - return is_x +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): + """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 check + + +# 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 index d85eb0b76d..d5e013f11d 100644 --- a/pythonforandroid/prerequisites.py +++ b/pythonforandroid/prerequisites.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 -import sys -import platform import os -import subprocess +import platform import shutil +import subprocess +import sys + from pythonforandroid.logger import info, warning, error +from pythonforandroid.util import ensure_dir class Prerequisite(object): @@ -247,13 +249,7 @@ def darwin_installer(self): "~/Library/Java/JavaVirtualMachines" ) info(f"Extracting {filename} to {user_library_java_path}") - subprocess.check_output( - [ - "mkdir", - "-p", - user_library_java_path, - ], - ) + ensure_dir(user_library_java_path) subprocess.check_output( ["tar", "xzf", f"/tmp/{filename}", "-C", user_library_java_path], ) @@ -370,22 +366,19 @@ def darwin_installer(self): def get_required_prerequisites(platform="linux"): - DEFAULT_PREREQUISITES = dict( - darwin=[ - HomebrewPrerequisite(), - AutoconfPrerequisite(), - AutomakePrerequisite(), - LibtoolPrerequisite(), - PkgConfigPrerequisite(), - CmakePrerequisite(), - OpenSSLPrerequisite(), - JDKPrerequisite(), - ], - linux=[], - all_platforms=[], - ) - - return DEFAULT_PREREQUISITES["all_platforms"] + DEFAULT_PREREQUISITES[platform] + 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(): diff --git a/pythonforandroid/pythonpackage.py b/pythonforandroid/pythonpackage.py index 3b03a513f5..9e4c29bd81 100644 --- a/pythonforandroid/pythonpackage.py +++ b/pythonforandroid/pythonpackage.py @@ -4,17 +4,17 @@ Usage examples: # Getting package name from pip reference: - from pytonforandroid.pythonpackage import get_package_name + from pythonforandroid.pythonpackage import get_package_name print(get_package_name("pillow")) # Outputs: "Pillow" (note the spelling!) # Getting package dependencies: - from pytonforandroid.pythonpackage import get_package_dependencies + from pythonforandroid.pythonpackage import get_package_dependencies print(get_package_dependencies("pep517")) # Outputs: "['pytoml']" # Get package name from arbitrary package source: - from pytonforandroid.pythonpackage import get_package_name + from pythonforandroid.pythonpackage import get_package_name print(get_package_name("/some/local/project/folder/")) # Outputs package name @@ -34,22 +34,22 @@ import functools +from io import open # needed for python 2 import os import shutil import subprocess import sys import tarfile import tempfile -import textwrap import time -import zipfile -from io import open # needed for python 2 from urllib.parse import unquote as urlunquote from urllib.parse import urlparse +import zipfile import toml -from pep517.envbuild import BuildEnvironment -from pep517.wrappers import Pep517HookCaller +import build.util + +from pythonforandroid.util import rmdir, ensure_dir def transform_dep_for_pip(dependency): @@ -113,42 +113,9 @@ def extract_metainfo_files_from_package( ) package = os.path.join(temp_folder, "package") - # Because PEP517 can be noisy and contextlib.redirect_* fails to - # contain it, we will run the actual analysis in a separate process: - try: - subprocess.check_output([ - sys.executable, - "-c", - "import importlib\n" - "import json\n" - "import os\n" - "import sys\n" - "sys.path = [os.path.dirname(sys.argv[3])] + sys.path\n" - "m = importlib.import_module(\n" - " os.path.basename(sys.argv[3]).partition('.')[0]\n" - ")\n" - "m._extract_metainfo_files_from_package_unsafe(" - " sys.argv[1]," - " sys.argv[2]," - ")", - package, output_folder, os.path.abspath(__file__)], - stderr=subprocess.STDOUT, # make sure stderr is muted. - cwd=os.path.join(os.path.dirname(__file__), "..") - ) - except subprocess.CalledProcessError as e: - output = e.output.decode("utf-8", "replace") - if debug: - print("Got error obtaining meta info.") - print("Detail output:") - print(output) - print("End of Detail output.") - raise ValueError( - "failed to obtain meta info - " - "is '{}' a valid package? " - "Detailed output:\n{}".format(package, output) - ) + _extract_metainfo_files_from_package_unsafe(package, output_folder) finally: - shutil.rmtree(temp_folder) + rmdir(temp_folder) def _get_system_python_executable(): @@ -349,7 +316,7 @@ def get_package_as_folder(dependency): ) # Create download subfolder: - os.mkdir(os.path.join(venv_path, "download")) + 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"), @@ -429,11 +396,11 @@ def to_unicode(s): # Needed for Python 2. # Copy result to new dedicated folder so we can throw away # our entire virtualenv nonsense after returning: result_path = tempfile.mkdtemp() - shutil.rmtree(result_path) + rmdir(result_path) shutil.copytree(result_folder_or_file, result_path) return (dl_type, result_path) finally: - shutil.rmtree(venv_parent) + rmdir(venv_parent) def _extract_metainfo_files_from_package_unsafe( @@ -461,51 +428,17 @@ def _extract_metainfo_files_from_package_unsafe( clean_up_path = True try: - build_requires = [] metadata_path = None if path_type != "wheel": - # We need to process this first to get the metadata. - - # Ensure pyproject.toml is available (pep517 expects it) - if not os.path.exists(os.path.join(path, "pyproject.toml")): - with open(os.path.join(path, "pyproject.toml"), "w") as f: - f.write(textwrap.dedent(u"""\ - [build-system] - requires = ["setuptools", "wheel"] - build-backend = "setuptools.build_meta" - """)) - - # Copy the pyproject.toml: - shutil.copyfile( - os.path.join(path, 'pyproject.toml'), - os.path.join(output_path, 'pyproject.toml') - ) - - # Get build backend and requirements from pyproject.toml: - with open(os.path.join(path, 'pyproject.toml')) as f: - build_sys = toml.load(f)['build-system'] - backend = build_sys["build-backend"] - build_requires.extend(build_sys["requires"]) - - # Get a virtualenv with build requirements and get all metadata: - env = BuildEnvironment() - metadata = None - with env: - hooks = Pep517HookCaller(path, backend) - env.pip_install( - [transform_dep_for_pip(req) for req in build_requires] - ) - reqs = hooks.get_requires_for_build_wheel({}) - env.pip_install([transform_dep_for_pip(req) for req in reqs]) - try: - metadata = hooks.prepare_metadata_for_build_wheel(path) - except Exception: # sadly, pep517 has no good error here - pass - if metadata is not None: - metadata_path = os.path.join( - path, metadata, "METADATA" - ) + # 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( @@ -527,7 +460,7 @@ def _extract_metainfo_files_from_package_unsafe( shutil.copyfile(metadata_path, os.path.join(output_path, "METADATA")) finally: if clean_up_path: - shutil.rmtree(path) + rmdir(path) def is_filesystem_path(dep): @@ -645,7 +578,7 @@ def _extract_info_from_package(dependency, return list(set(requirements)) # remove duplicates finally: - shutil.rmtree(output_folder) + rmdir(output_folder) package_name_cache = dict() diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index 130db7a559..fa0bb4e790 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -1,6 +1,5 @@ from os.path import basename, dirname, exists, isdir, isfile, join, realpath, split import glob -from shutil import rmtree import hashlib from re import match @@ -10,16 +9,18 @@ import fnmatch import urllib.request from urllib.request import urlretrieve -from os import listdir, unlink, environ, mkdir, curdir, walk +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, debug, shprint, info_main) -from pythonforandroid.util import (current_directory, ensure_dir, - BuildInterruptingException) +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 @@ -218,7 +219,7 @@ def report_hook(index, blksize, size): url = url[4:] # if 'version' is specified, do a shallow clone if self.version: - shprint(sh.mkdir, '-p', target) + ensure_dir(target) with current_directory(target): shprint(sh.git, 'init') shprint(sh.git, 'remote', 'add', 'origin', url) @@ -367,7 +368,7 @@ def download(self): if expected_digest: expected_digests[alg] = expected_digest - shprint(sh.mkdir, '-p', join(self.ctx.packages_path, self.name)) + 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') @@ -396,7 +397,7 @@ def download(self): shprint(sh.rm, '-f', marker_filename) self.download_file(self.versioned_url, filename) - shprint(sh.touch, marker_filename) + touch(marker_filename) if exists(filename) and isfile(filename): for alg, expected_digest in expected_digests.items(): @@ -423,9 +424,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 @@ -460,20 +459,20 @@ def unpack(self, arch): 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) + 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 = sh.tar('tf', extraction_filename).stdout.decode( 'utf-8').split('\n')[0].split('/')[0] if root_directory != basename(directory_name): - shprint(sh.mv, root_directory, 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', @@ -533,7 +532,7 @@ def apply_patches(self, arch, build_dir=None): patch.format(version=self.version, arch=arch.arch), arch.arch, build_dir=build_dir) - shprint(sh.touch, join(build_dir, '.patched')) + touch(join(build_dir, '.patched')) def should_build(self, arch): '''Should perform any necessary test and return True only if it needs @@ -614,13 +613,11 @@ def clean_build(self, arch=None): '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) @@ -631,7 +628,7 @@ 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. @@ -721,7 +718,7 @@ def prepare_build_dir(self, arch): if self.src_filename is None: raise BuildInterruptingException( 'IncludedFilesBehaviour failed: no src_filename specified') - shprint(sh.rm, '-rf', self.get_build_dir(arch)) + rmdir(self.get_build_dir(arch)) shprint(sh.cp, '-a', join(self.get_recipe_dir(), self.src_filename), self.get_build_dir(arch)) @@ -861,7 +858,7 @@ 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): @@ -1171,7 +1168,7 @@ def reduce_object_file_names(self, dirn): parts = file_basename.split('.') if len(parts) <= 2: continue - shprint(sh.mv, filen, join(file_dirname, parts[0] + '.so')) + move(filen, join(file_dirname, parts[0] + '.so')) def algsum(alg, filen): diff --git a/pythonforandroid/recipes/audiostream/__init__.py b/pythonforandroid/recipes/audiostream/__init__.py index 888aca443c..00c92b3864 100644 --- a/pythonforandroid/recipes/audiostream/__init__.py +++ b/pythonforandroid/recipes/audiostream/__init__.py @@ -17,14 +17,18 @@ class AudiostreamRecipe(CythonRecipe): def get_recipe_env(self, arch): env = super().get_recipe_env(arch) sdl_include = 'SDL2' - sdl_mixer_include = 'SDL2_mixer' + 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 -I{jni_path}/{sdl_mixer_include}'.format( + env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include'.format( jni_path=join(self.ctx.bootstrap.build_dir, 'jni'), - sdl_include=sdl_include, - sdl_mixer_include=sdl_mixer_include) + 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 diff --git a/pythonforandroid/recipes/av/__init__.py b/pythonforandroid/recipes/av/__init__.py new file mode 100644 index 0000000000..816f27e35f --- /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 0000000000..9952f9ea48 --- /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 fc17f8e4b0..0000000000 --- a/pythonforandroid/recipes/babel/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from pythonforandroid.recipe import PythonRecipe - - -class BabelRecipe(PythonRecipe): - name = 'babel' - version = '2.2.0' - url = 'https://pypi.python.org/packages/source/B/Babel/Babel-{version}.tar.gz' - - depends = ['setuptools', 'pytz'] - - call_hostpython_via_targetpython = False - install_in_hostpython = True - - -recipe = BabelRecipe() diff --git a/pythonforandroid/recipes/cdecimal/__init__.py b/pythonforandroid/recipes/cdecimal/__init__.py deleted file mode 100644 index a444eb1b25..0000000000 --- a/pythonforandroid/recipes/cdecimal/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from pythonforandroid.recipe 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 = [] - - patches = ['locale.patch', - 'cross-compile.patch'] - - def prebuild_arch(self, arch): - super().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 cc15f33ba2..0000000000 --- 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 4b8df6b373..0000000000 --- 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 - #include - #include --#include -+#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 -+ -+__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 a198a3db0d..f0c25a92c9 100644 --- a/pythonforandroid/recipes/cffi/__init__.py +++ b/pythonforandroid/recipes/cffi/__init__.py @@ -7,7 +7,7 @@ class CffiRecipe(CompiledComponentsPythonRecipe): Extra system dependencies: autoconf, automake and libtool. """ name = 'cffi' - version = '1.13.2' + version = '1.15.1' url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz' depends = ['setuptools', 'pycparser', 'libffi'] diff --git a/pythonforandroid/recipes/cffi/disable-pkg-config.patch b/pythonforandroid/recipes/cffi/disable-pkg-config.patch index cf2abd5b86..b1a5ff9b4c 100644 --- a/pythonforandroid/recipes/cffi/disable-pkg-config.patch +++ b/pythonforandroid/recipes/cffi/disable-pkg-config.patch @@ -1,19 +1,19 @@ -diff --git a/setup.py b/setup.py -index c1db368..57311c3 100644 +diff --git a/setup.py b/setup copy.py +index 4ce0007..9be4a6d 100644 --- a/setup.py -+++ b/setup.py -@@ -5,8 +5,7 @@ import errno ++++ 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 = os.environ['FFI_INC'].split(",") if 'FFI_INC' in os.environ else [] - 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 @@ def ask_supports_thread(): - 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'): @@ -25,6 +25,4 @@ index c1db368..57311c3 100644 - _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/dateutil/__init__.py b/pythonforandroid/recipes/dateutil/__init__.py deleted file mode 100644 index 3367f8d145..0000000000 --- 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 = ["setuptools"] - call_hostpython_via_targetpython = False - install_in_hostpython = True - - -recipe = DateutilRecipe() diff --git a/pythonforandroid/recipes/ffpyplayer/__init__.py b/pythonforandroid/recipes/ffpyplayer/__init__.py index 7aa8b0db2e..6260037a70 100644 --- a/pythonforandroid/recipes/ffpyplayer/__init__.py +++ b/pythonforandroid/recipes/ffpyplayer/__init__.py @@ -20,7 +20,11 @@ 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? diff --git a/pythonforandroid/recipes/freetype/__init__.py b/pythonforandroid/recipes/freetype/__init__.py index 0b04c95da6..e5ddfe1424 100644 --- a/pythonforandroid/recipes/freetype/__init__.py +++ b/pythonforandroid/recipes/freetype/__init__.py @@ -25,7 +25,7 @@ class FreetypeRecipe(Recipe): """ version = '2.10.1' - url = 'http://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.gz' # noqa + url = 'https://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.gz' # noqa built_libraries = {'libfreetype.so': 'objs/.libs'} def get_recipe_env(self, arch=None, with_harfbuzz=False): diff --git a/pythonforandroid/recipes/hostpython3/__init__.py b/pythonforandroid/recipes/hostpython3/__init__.py index ef2324aea4..ee53b6ef09 100644 --- a/pythonforandroid/recipes/hostpython3/__init__.py +++ b/pythonforandroid/recipes/hostpython3/__init__.py @@ -35,7 +35,7 @@ class HostPython3Recipe(Recipe): :class:`~pythonforandroid.python.HostPythonRecipe` ''' - version = '3.9.9' + version = '3.10.10' name = 'hostpython3' build_subdir = 'native-build' diff --git a/pythonforandroid/recipes/ifaddrs/__init__.py b/pythonforandroid/recipes/ifaddrs/__init__.py index 7d44f9cd72..1317dc2556 100644 --- a/pythonforandroid/recipes/ifaddrs/__init__.py +++ b/pythonforandroid/recipes/ifaddrs/__init__.py @@ -1,10 +1,13 @@ """ ifaddrs for Android """ -from os.path import join, exists +from os.path import join + import sh -from pythonforandroid.logger import info, shprint + +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): @@ -19,9 +22,7 @@ class IFAddrRecipe(CompiledComponentsPythonRecipe): 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""" @@ -30,9 +31,7 @@ def build_arch(self, arch): 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) + 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 diff --git a/pythonforandroid/recipes/kivy/__init__.py b/pythonforandroid/recipes/kivy/__init__.py index bece49ee6f..ebf7b29e82 100644 --- a/pythonforandroid/recipes/kivy/__init__.py +++ b/pythonforandroid/recipes/kivy/__init__.py @@ -22,12 +22,12 @@ def is_kivy_affected_by_deadlock_issue(recipe=None, arch=None): class KivyRecipe(CythonRecipe): - version = '2.1.0' + version = '2.2.1' url = 'https://github.com/kivy/kivy/archive/{version}.zip' name = 'kivy' depends = ['sdl2', 'pyjnius', 'setuptools'] - python_depends = ['certifi'] + python_depends = ['certifi', 'chardet', 'idna', 'requests', 'urllib3'] # sdl-gl-swapwindow-nogil.patch is needed to avoid a deadlock. # See: https://github.com/kivy/kivy/pull/8025 @@ -65,10 +65,11 @@ def get_recipe_env(self, arch): 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) 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', 'include'), + *sdl2_mixer_recipe.get_include_dirs(arch), join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'), ]) diff --git a/pythonforandroid/recipes/lapack/__init__.py b/pythonforandroid/recipes/lapack/__init__.py index ae20e69538..b6124dc285 100644 --- a/pythonforandroid/recipes/lapack/__init__.py +++ b/pythonforandroid/recipes/lapack/__init__.py @@ -11,7 +11,7 @@ import sh import shutil from os import environ -from pythonforandroid.util import build_platform +from pythonforandroid.util import build_platform, rmdir arch_to_sysroot = {'armeabi': 'arm', 'armeabi-v7a': 'arm', 'arm64-v8a': 'arm64'} @@ -57,7 +57,8 @@ def build_arch(self, arch): with current_directory(build_target): env = self.get_recipe_env(arch) ndk_dir = environ["LEGACY_NDK"] - shprint(sh.rm, '-rf', 'CMakeFiles/', 'CMakeCache.txt', _env=env) + rmdir('CMakeFiles') + shprint(sh.rm, '-f', 'CMakeCache.txt', _env=env) opts = [ '-DCMAKE_SYSTEM_NAME=Android', '-DCMAKE_POSITION_INDEPENDENT_CODE=1', diff --git a/pythonforandroid/recipes/libglob/__init__.py b/pythonforandroid/recipes/libglob/__init__.py index f63db42628..39a68b7ee2 100644 --- a/pythonforandroid/recipes/libglob/__init__.py +++ b/pythonforandroid/recipes/libglob/__init__.py @@ -2,11 +2,14 @@ android libglob available via '-lglob' LDFLAG """ -from os.path import exists, join +from os.path import join + +import sh + +from pythonforandroid.logger import shprint from pythonforandroid.recipe import Recipe from pythonforandroid.toolchain import current_directory -from pythonforandroid.logger import info, shprint -import sh +from pythonforandroid.util import ensure_dir class LibGlobRecipe(Recipe): @@ -32,9 +35,7 @@ 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""" @@ -43,9 +44,7 @@ def build_arch(self, arch): 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) + 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 diff --git a/pythonforandroid/recipes/libmysqlclient/__init__.py b/pythonforandroid/recipes/libmysqlclient/__init__.py index 31ebd3c540..84fd8d30ac 100644 --- a/pythonforandroid/recipes/libmysqlclient/__init__.py +++ b/pythonforandroid/recipes/libmysqlclient/__init__.py @@ -26,7 +26,7 @@ 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') + # 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', diff --git a/pythonforandroid/recipes/libtorrent/__init__.py b/pythonforandroid/recipes/libtorrent/__init__.py index 24f94081c6..1086e00fcc 100644 --- a/pythonforandroid/recipes/libtorrent/__init__.py +++ b/pythonforandroid/recipes/libtorrent/__init__.py @@ -1,9 +1,12 @@ -from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory from multiprocessing import cpu_count -from os.path import join, basename 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 diff --git a/pythonforandroid/recipes/mysqldb/__init__.py b/pythonforandroid/recipes/mysqldb/__init__.py deleted file mode 100644 index 768cb72af2..0000000000 --- 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 = ['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().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().get_recipe_env(arch) - - hostpython = self.get_recipe('hostpython3', 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 51f804ea2c..0000000000 --- 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 195ebdacbc..0000000000 --- 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/opencv/__init__.py b/pythonforandroid/recipes/opencv/__init__.py index c760cbdda7..650c77e508 100644 --- a/pythonforandroid/recipes/opencv/__init__.py +++ b/pythonforandroid/recipes/opencv/__init__.py @@ -1,9 +1,11 @@ +from multiprocessing import cpu_count from os.path import join + import sh -from pythonforandroid.recipe import NDKRecipe -from pythonforandroid.util import current_directory + from pythonforandroid.logger import shprint -from multiprocessing import cpu_count +from pythonforandroid.recipe import NDKRecipe +from pythonforandroid.util import current_directory, ensure_dir class OpenCVRecipe(NDKRecipe): @@ -45,7 +47,7 @@ def get_recipe_env(self, arch): def build_arch(self, arch): build_dir = join(self.get_build_dir(arch.arch), 'build') - shprint(sh.mkdir, '-p', build_dir) + ensure_dir(build_dir) opencv_extras = [] if 'opencv_extras' in self.ctx.recipe_build_order: diff --git a/pythonforandroid/recipes/pandas/__init__.py b/pythonforandroid/recipes/pandas/__init__.py index a217ab635a..a43209a339 100644 --- a/pythonforandroid/recipes/pandas/__init__.py +++ b/pythonforandroid/recipes/pandas/__init__.py @@ -7,9 +7,9 @@ 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', 'pytz', 'libbz2', 'liblzma'] + depends = ['cython', 'numpy', 'libbz2', 'liblzma'] - python_depends = ['python-dateutil'] + python_depends = ['python-dateutil', 'pytz'] patches = ['fix_numpy_includes.patch'] call_hostpython_via_targetpython = False diff --git a/pythonforandroid/recipes/protobuf_cpp/__init__.py b/pythonforandroid/recipes/protobuf_cpp/__init__.py index c1149f2714..7209e0909b 100644 --- a/pythonforandroid/recipes/protobuf_cpp/__init__.py +++ b/pythonforandroid/recipes/protobuf_cpp/__init__.py @@ -1,12 +1,13 @@ -from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe -from pythonforandroid.logger import shprint, info_notify -from pythonforandroid.util import current_directory -from os.path import exists, join -import sh from multiprocessing import cpu_count +import os +from os.path import exists, join from pythonforandroid.toolchain import info +import sh import sys -import os + +from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe +from pythonforandroid.logger import shprint, info_notify +from pythonforandroid.util import current_directory, touch class ProtobufCppRecipe(CppCompiledComponentsPythonRecipe): @@ -30,7 +31,7 @@ def prebuild_arch(self, 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) - shprint(sh.touch, patch_mark) + 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. diff --git a/pythonforandroid/recipes/pydantic/__init__.py b/pythonforandroid/recipes/pydantic/__init__.py index eb4c504704..16e61e1b61 100644 --- a/pythonforandroid/recipes/pydantic/__init__.py +++ b/pythonforandroid/recipes/pydantic/__init__.py @@ -2,10 +2,10 @@ class PydanticRecipe(PythonRecipe): - version = '1.8.2' - url = 'https://github.com/samuelcolvin/pydantic/archive/refs/tags/v{version}.zip' + version = '1.10.4' + url = 'https://github.com/pydantic/pydantic/archive/refs/tags/v{version}.zip' depends = ['setuptools'] - python_depends = ['Cython', 'devtools', 'email-validator', 'dataclasses', 'typing-extensions', 'python-dotenv'] + python_depends = ['Cython', 'devtools', 'email-validator', 'typing-extensions', 'python-dotenv'] call_hostpython_via_targetpython = False diff --git a/pythonforandroid/recipes/pygame/__init__.py b/pythonforandroid/recipes/pygame/__init__.py index 72e624ea32..99124deff7 100644 --- a/pythonforandroid/recipes/pygame/__init__.py +++ b/pythonforandroid/recipes/pygame/__init__.py @@ -37,6 +37,11 @@ def prebuild_arch(self, arch): jpeg = self.get_recipe('jpeg', self.ctx) jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch) + 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} " + setup_file = setup_template.format( sdl_includes=( " -I" + join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include') + @@ -44,7 +49,7 @@ def prebuild_arch(self, 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="-I"+join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_image'), - sdl_mixer_includes="-I"+join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_mixer'), + sdl_mixer_includes=sdl_mixer_includes, jpeg_includes="-I"+jpeg_inc_dir, png_includes="-I"+png_inc_dir, freetype_includes="" diff --git a/pythonforandroid/recipes/pyjnius/__init__.py b/pythonforandroid/recipes/pyjnius/__init__.py index 58103e23b0..85a5860d5c 100644 --- a/pythonforandroid/recipes/pyjnius/__init__.py +++ b/pythonforandroid/recipes/pyjnius/__init__.py @@ -6,7 +6,7 @@ class PyjniusRecipe(CythonRecipe): - version = '1.4.2' + version = '1.5.0' url = 'https://github.com/kivy/pyjnius/archive/{version}.zip' name = 'pyjnius' depends = [('genericndkbuild', 'sdl2'), 'six'] diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py index c3c28c70fb..387922718e 100644 --- a/pythonforandroid/recipes/python3/__init__.py +++ b/pythonforandroid/recipes/python3/__init__.py @@ -56,7 +56,7 @@ class Python3Recipe(TargetPythonRecipe): :class:`~pythonforandroid.python.GuestPythonRecipe` ''' - version = '3.9.9' + version = '3.10.10' url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz' name = 'python3' @@ -70,14 +70,16 @@ class Python3Recipe(TargetPythonRecipe): # 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.9")), + ('patches/py3.8.1.patch', version_starts_with("3.10")) ] if shutil.which('lld') is not None: patches = 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.9")), + ("patches/py3.8.1_fix_cortex_a8.patch", version_starts_with("3.10")) ] depends = ['hostpython3', 'sqlite3', 'openssl', 'libffi'] @@ -96,6 +98,7 @@ class Python3Recipe(TargetPythonRecipe): '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') @@ -364,11 +367,11 @@ def create_python_bundle(self, dirn, arch): self.major_minor_version_string )) - # Compile to *.pyc/*.pyo the python modules + # Compile to *.pyc the python modules self.compile_python_files(modules_build_dir) - # Compile to *.pyc/*.pyo the standard python library + # Compile to *.pyc the standard python library self.compile_python_files(join(self.get_build_dir(arch.arch), 'Lib')) - # Compile to *.pyc/*.pyo the other python packages (site-packages) + # 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 diff --git a/pythonforandroid/recipes/pytz/__init__.py b/pythonforandroid/recipes/pytz/__init__.py deleted file mode 100644 index ff9bc37573..0000000000 --- a/pythonforandroid/recipes/pytz/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from pythonforandroid.recipe import PythonRecipe - - -class PytzRecipe(PythonRecipe): - name = 'pytz' - version = '2019.3' - url = 'https://pypi.python.org/packages/source/p/pytz/pytz-{version}.tar.gz' - - depends = [] - - call_hostpython_via_targetpython = False - install_in_hostpython = True - - -recipe = PytzRecipe() diff --git a/pythonforandroid/recipes/reportlab/__init__.py b/pythonforandroid/recipes/reportlab/__init__.py index 60f1a078ea..ee5de38214 100644 --- a/pythonforandroid/recipes/reportlab/__init__.py +++ b/pythonforandroid/recipes/reportlab/__init__.py @@ -1,8 +1,9 @@ import os import sh + +from pythonforandroid.logger import info from pythonforandroid.recipe import CompiledComponentsPythonRecipe -from pythonforandroid.util import (current_directory, ensure_dir) -from pythonforandroid.logger import (info, shprint) +from pythonforandroid.util import current_directory, ensure_dir, touch class ReportLabRecipe(CompiledComponentsPythonRecipe): @@ -28,7 +29,7 @@ def prebuild_arch(self, arch): # Apply patches: self.apply_patch('patches/fix-setup.patch', arch.arch) - shprint(sh.touch, os.path.join(recipe_dir, '.patched')) + 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')) diff --git a/pythonforandroid/recipes/scrypt/__init__.py b/pythonforandroid/recipes/scrypt/__init__.py index 7f235396a0..e41ba59054 100644 --- a/pythonforandroid/recipes/scrypt/__init__.py +++ b/pythonforandroid/recipes/scrypt/__init__.py @@ -3,8 +3,8 @@ class ScryptRecipe(CythonRecipe): - version = '0.8.6' - url = 'https://bitbucket.org/mhallin/py-scrypt/get/v{version}.zip' + 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"] diff --git a/pythonforandroid/recipes/sdl2/__init__.py b/pythonforandroid/recipes/sdl2/__init__.py index d1929d06d8..e04458a793 100644 --- a/pythonforandroid/recipes/sdl2/__init__.py +++ b/pythonforandroid/recipes/sdl2/__init__.py @@ -6,14 +6,16 @@ class LibSDL2Recipe(BootstrapNDKRecipe): - version = "2.24.0" + version = "2.26.1" url = "https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL2-{version}.tar.gz" - md5sum = 'cf539ffe9e0dd6f943ac9de75fd2e56e' + md5sum = 'fba211fe2c67609df6fa3cf55d3c74dc' dir_name = 'SDL' depends = ['sdl2_image', 'sdl2_mixer', 'sdl2_ttf'] + patches = ['sdl-orientation-pr-6984.diff'] + 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) diff --git a/pythonforandroid/recipes/sdl2/sdl-orientation-pr-6984.diff b/pythonforandroid/recipes/sdl2/sdl-orientation-pr-6984.diff new file mode 100644 index 0000000000..bbe2ca2243 --- /dev/null +++ b/pythonforandroid/recipes/sdl2/sdl-orientation-pr-6984.diff @@ -0,0 +1,27 @@ +diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +index 2d7d69b76a25..edb42fb55461 100644 +--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java ++++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +@@ -971,15 +971,18 @@ public void setOrientationBis(int w, int h, boolean resizable, String hint) + /* If set, hint "explicitly controls which UI orientations are allowed". */ + if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; +- } else if (hint.contains("LandscapeRight")) { +- orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + } else if (hint.contains("LandscapeLeft")) { ++ orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; ++ } else if (hint.contains("LandscapeRight")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } + +- if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { ++ /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */ ++ boolean contains_Portrait = hint.contains("Portrait ") || hint.endsWith("Portrait"); ++ ++ if (contains_Portrait && hint.contains("PortraitUpsideDown")) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; +- } else if (hint.contains("Portrait")) { ++ } else if (contains_Portrait) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } else if (hint.contains("PortraitUpsideDown")) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; diff --git a/pythonforandroid/recipes/sdl2_mixer/__init__.py b/pythonforandroid/recipes/sdl2_mixer/__init__.py index 733ef21148..0f02c4c3a4 100644 --- a/pythonforandroid/recipes/sdl2_mixer/__init__.py +++ b/pythonforandroid/recipes/sdl2_mixer/__init__.py @@ -1,3 +1,5 @@ +import os + from pythonforandroid.recipe import BootstrapNDKRecipe @@ -6,5 +8,10 @@ class LibSDL2Mixer(BootstrapNDKRecipe): url = 'https://github.com/libsdl-org/SDL_mixer/releases/download/release-{version}/SDL2_mixer-{version}.tar.gz' dir_name = 'SDL2_mixer' + 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/secp256k1/cross_compile.patch b/pythonforandroid/recipes/secp256k1/cross_compile.patch index bfef228193..bcff1955fb 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/sqlite3/__init__.py b/pythonforandroid/recipes/sqlite3/__init__.py index 955d808141..1f4292c1eb 100644 --- a/pythonforandroid/recipes/sqlite3/__init__.py +++ b/pythonforandroid/recipes/sqlite3/__init__.py @@ -1,7 +1,8 @@ -from pythonforandroid.recipe import NDKRecipe -from pythonforandroid.toolchain import shutil from os.path import join -import sh +import shutil + +from pythonforandroid.recipe import NDKRecipe +from pythonforandroid.util import ensure_dir class Sqlite3Recipe(NDKRecipe): @@ -16,7 +17,7 @@ def should_build(self, arch): def prebuild_arch(self, 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')) diff --git a/pythonforandroid/recipes/twisted/__init__.py b/pythonforandroid/recipes/twisted/__init__.py index 0c390a5b14..30a7af4bb9 100644 --- a/pythonforandroid/recipes/twisted/__init__.py +++ b/pythonforandroid/recipes/twisted/__init__.py @@ -1,7 +1,7 @@ import os -import shutil from pythonforandroid.recipe import CythonRecipe +from pythonforandroid.util import rmdir class TwistedRecipe(CythonRecipe): @@ -23,7 +23,7 @@ def prebuild_arch(self, arch): for item in os.walk(source_dir): if os.path.basename(item[0]) == 'test': full_path = os.path.join(source_dir, item[0]) - shutil.rmtree(full_path, ignore_errors=True) + rmdir(full_path, ignore_errors=True) def get_recipe_env(self, arch): env = super().get_recipe_env(arch) diff --git a/pythonforandroid/recipes/vlc/__init__.py b/pythonforandroid/recipes/vlc/__init__.py index 490c4f2a18..0995576f5f 100644 --- a/pythonforandroid/recipes/vlc/__init__.py +++ b/pythonforandroid/recipes/vlc/__init__.py @@ -6,7 +6,7 @@ class VlcRecipe(Recipe): - version = '3.0.0' + version = '3.0.18' url = None name = 'vlc' @@ -52,7 +52,7 @@ def prebuild_arch(self, arch): def build_arch(self, 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): @@ -67,7 +67,7 @@ def build_arch(self, arch): 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-medialibrary.sh'), _env=env, _tail=50, _critical=True) shprint(sh.cp, '-a', aar, self.ctx.aars_dir) diff --git a/pythonforandroid/recipes/zope_interface/__init__.py b/pythonforandroid/recipes/zope_interface/__init__.py index 46a1820c2b..7e7fecaea5 100644 --- a/pythonforandroid/recipes/zope_interface/__init__.py +++ b/pythonforandroid/recipes/zope_interface/__init__.py @@ -1,7 +1,8 @@ +from os.path import join + from pythonforandroid.recipe import PythonRecipe from pythonforandroid.toolchain import current_directory -from os.path import join -import sh +from pythonforandroid.util import rmdir class ZopeInterfaceRecipe(PythonRecipe): @@ -25,11 +26,8 @@ def build_arch(self, arch): def prebuild_arch(self, 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/toolchain.py b/pythonforandroid/toolchain.py index 1b81aa923c..7a5461f30d 100644 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -6,96 +6,42 @@ This module defines the entry point for command line and programmatic use. """ +from appdirs import user_data_dir +import argparse +from functools import wraps +import glob +import logging +import os from os import environ -from pythonforandroid import __version__ -from pythonforandroid.pythonpackage import get_dep_names_of_package -from pythonforandroid.recommendations import ( - RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API, print_recommendations) -from pythonforandroid.util import BuildInterruptingException, load_source -from pythonforandroid.entrypoints import main -from pythonforandroid.prerequisites import check_and_install_default_prerequisites - - -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'] - - 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 logged above') - exit(1) - - -if not environ.get('SKIP_PREREQUISITES_CHECK', '0') == '1': - check_and_install_default_prerequisites() -check_python_dependencies() - - -import sys -from sys import platform from os.path import (join, dirname, realpath, exists, expanduser, basename) -import os -import glob -import shutil import re 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, InvalidVersion import sh -from appdirs import user_data_dir -import logging -from distutils.version import LooseVersion -from pythonforandroid.recipe import Recipe -from pythonforandroid.logger import (logger, info, warning, setup_color, - Out_Style, Out_Fore, - info_notify, info_main, shprint) -from pythonforandroid.util import current_directory +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) user_dir = dirname(realpath(os.path.curdir)) toolchain_dir = dirname(__file__) @@ -512,6 +458,10 @@ def add_parser(subparsers, *args, **kwargs): '--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' + @@ -880,18 +830,16 @@ 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) + rmdir(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')) + 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 @@ -902,13 +850,10 @@ def clean_builds(self, _args): of a specific recipe. """ ctx = self.ctx - 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. @@ -938,14 +883,14 @@ def clean_download_cache(self, args): 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)) 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)) @@ -1000,6 +945,14 @@ def _fix_args(args): 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: @@ -1056,13 +1009,22 @@ def _build_package(self, args, package_type): self.hook("before_apk_assemble") build_tools_versions = os.listdir(join(ctx.sdk_dir, 'build-tools')) - build_tools_versions = sorted(build_tools_versions, - key=LooseVersion) + + def sort_key(version_text): + try: + # Historically, Android build release candidates have had + # spaces in the version number. + return Version(version_text.replace(" ", "")) + except InvalidVersion: + # Put badly named versions at worst position. + return Version("0") + + build_tools_versions.sort(key=sort_key) 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': + 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"): @@ -1096,7 +1058,10 @@ def _build_package(self, args, package_type): else: raise BuildInterruptingException( "Unknown build mode {} for apk()".format(args.build_mode)) - output = shprint(gradlew, gradle_task, _tail=20, + + # 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 diff --git a/pythonforandroid/tools/liblink b/pythonforandroid/tools/liblink index de837e6ca5..27e4cfee1b 100755 --- a/pythonforandroid/tools/liblink +++ b/pythonforandroid/tools/liblink @@ -35,6 +35,7 @@ while i < len(sys.argv): if opt.startswith( ("-I", "-isystem", "-m", "-f", "-O", "-g", "-D", "-R")): + continue if opt.startswith("-"): print(sys.argv) diff --git a/pythonforandroid/util.py b/pythonforandroid/util.py index f290cdcb25..af363b2e3f 100644 --- a/pythonforandroid/util.py +++ b/pythonforandroid/util.py @@ -1,14 +1,20 @@ import contextlib +from fnmatch import fnmatch +import logging from os.path import exists, join -from os import getcwd, chdir, makedirs, walk, uname +from os import getcwd, chdir, makedirs, walk +from pathlib import Path +from platform import uname import shutil -from fnmatch import fnmatch from tempfile import mkdtemp + from pythonforandroid.logger import (logger, Err_Fore, error, info) +LOGGER = logging.getLogger("p4a.util") -build_platform = '{system}-{machine}'.format( - system=uname()[0], machine=uname()[-1]).lower() +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""" @@ -39,11 +45,6 @@ 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`` @@ -104,3 +105,26 @@ def handle_build_exception(exception): 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() diff --git a/setup.py b/setup.py index 1f61113818..9ced788ea8 100644 --- a/setup.py +++ b/setup.py @@ -21,10 +21,10 @@ # https://github.com/kivy/buildozer/issues/722 install_reqs = [ 'appdirs', 'colorama>=0.3.3', 'jinja2', - 'sh>=1.10; sys_platform!="nt"', - 'pep517', 'toml', 'packaging', + 'sh>=1.10, <2.0; sys_platform!="win32"', + 'build', 'toml', 'packaging', ] -# (pep517 and toml are used by pythonpackage.py) +# (build and toml are used by pythonpackage.py) # By specifying every file manually, package_data will be able to diff --git a/testapps/on_device_unit_tests/setup.py b/testapps/on_device_unit_tests/setup.py index 5e8d05ed35..2efeff0cc0 100644 --- a/testapps/on_device_unit_tests/setup.py +++ b/testapps/on_device_unit_tests/setup.py @@ -48,7 +48,7 @@ 'arch': 'armeabi-v7a', 'bootstrap' : 'sdl2', 'permissions': ['INTERNET', 'VIBRATE'], - 'orientation': 'sensor', + 'orientation': ['portrait', 'landscape'], 'service': 'P4a_test_service:app_service.py', }, 'aab': @@ -62,7 +62,7 @@ 'arch': 'armeabi-v7a', 'bootstrap' : 'sdl2', 'permissions': ['INTERNET', 'VIBRATE'], - 'orientation': 'sensor', + 'orientation': ['portrait', 'landscape'], 'service': 'P4a_test_service:app_service.py', }, 'aar': diff --git a/tests/recipes/test_libmysqlclient.py b/tests/recipes/test_libmysqlclient.py index 1be4b71e50..e484393398 100644 --- a/tests/recipes/test_libmysqlclient.py +++ b/tests/recipes/test_libmysqlclient.py @@ -23,7 +23,7 @@ def test_build_arch( mock_sh_rm, ): # We overwrite the base test method because we need - # to mock a little more (`sh.cp` and `sh.rm`) + # 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() diff --git a/tests/recipes/test_openal.py b/tests/recipes/test_openal.py index 27634d9013..21f3196798 100644 --- a/tests/recipes/test_openal.py +++ b/tests/recipes/test_openal.py @@ -50,7 +50,7 @@ def test_build_arch( mock_sh_cp, ): # We overwrite the base test method because we need to mock a little - # more with this recipe (`sh.cp` and `sh.rm`) + # 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 index 509c1cc1e8..861e73bd39 100644 --- a/tests/recipes/test_openssl.py +++ b/tests/recipes/test_openssl.py @@ -23,7 +23,7 @@ def test_build_arch( mock_sh_patch, ): # We overwrite the base test method because we need to mock a little - # more with this recipe (`sh.cp` and `sh.rm`) + # more with this recipe. super().test_build_arch() # make sure that the mocked methods are actually called mock_sh_patch.assert_called() diff --git a/tests/recipes/test_reportlab.py b/tests/recipes/test_reportlab.py index 83191e5286..6129a6a963 100644 --- a/tests/recipes/test_reportlab.py +++ b/tests/recipes/test_reportlab.py @@ -30,7 +30,7 @@ def test_prebuild_arch(self): # these sh commands are not relevant for the test and need to be mocked with \ patch('sh.patch'), \ - patch('sh.touch'), \ + patch('pythonforandroid.recipe.touch'), \ patch('sh.unzip'), \ patch('os.path.isfile'): self.recipe.prebuild_arch(self.arch) diff --git a/tests/recipes/test_sdl2_mixer.py b/tests/recipes/test_sdl2_mixer.py new file mode 100644 index 0000000000..a583d9aa63 --- /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/test_bootstrap.py b/tests/test_bootstrap.py index e997eba8c2..99620fee75 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -352,12 +352,16 @@ def bootstrap_name(self): @mock.patch("pythonforandroid.util.exists") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.bootstrap.listdir") - @mock.patch("pythonforandroid.bootstrap.sh.rm") + @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_sh_rm, + mock_rmdir1, + mock_rmdir2, + mock_rmdir3, mock_listdir, mock_chdir, mock_ensure_dir, @@ -433,7 +437,6 @@ def test_assemble_distribution( ) # check that the other mocks we made are actually called - mock_sh_rm.assert_called() mock_sh_cp.assert_called() mock_chdir.assert_called() mock_listdir.assert_called() @@ -558,11 +561,11 @@ def test_bootstrap_strip( mock_sh_print.assert_called() @mock.patch("pythonforandroid.bootstrap.listdir") - @mock.patch("pythonforandroid.bootstrap.sh.rm") - @mock.patch("pythonforandroid.bootstrap.sh.mv") + @mock.patch("pythonforandroid.bootstrap.rmdir") + @mock.patch("pythonforandroid.bootstrap.move") @mock.patch("pythonforandroid.bootstrap.isdir") def test_bootstrap_fry_eggs( - self, mock_isdir, mock_sh_mv, mock_sh_rm, mock_listdir + self, mock_isdir, mock_move, mock_rmdir, mock_listdir ): mock_listdir.return_value = [ "jnius", @@ -590,11 +593,11 @@ def test_bootstrap_fry_eggs( ] ) self.assertEqual( - mock_sh_rm.call_args[0][1], "pyjnius-1.2.1.dev0-py3.7.egg" + 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_sh_mv.assert_called() + mock_move.assert_called() class TestBootstrapSdl2(GenericBootstrapTest, unittest.TestCase): diff --git a/tests/test_bootstrap_build.py b/tests/test_bootstrap_build.py new file mode 100644 index 0000000000..ff5f7dcacc --- /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 index 6d30f996e7..cf9fa7801d 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -36,10 +36,10 @@ def test_strip_if_with_debug_symbols(self): 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('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'): @@ -64,7 +64,10 @@ def test_android_manifest_xml(self): args.min_sdk_version = 12 args.build_mode = 'debug' args.native_services = ['abcd', ] - args.permissions = [] + 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 = [] @@ -91,6 +94,9 @@ def test_android_manifest_xml(self): assert xml.count('targetSdkVersion="1234"') == 1 assert xml.count('android:debuggable="true"') == 1 assert xml.count('') == 1 + assert xml.count('') == 1 + assert xml.count('') == 1 + assert xml.count('') == 1 # TODO: potentially some other checks to be added here to cover other "logic" (flags and loops) in the template diff --git a/tests/test_distribution.py b/tests/test_distribution.py index 423d572252..404091ca75 100644 --- a/tests/test_distribution.py +++ b/tests/test_distribution.py @@ -91,8 +91,8 @@ def test_folder_exist(self, mock_exists): self.ctx.bootstrap.distribution.dist_dir ) - @mock.patch("pythonforandroid.distribution.rmtree") - def test_delete(self, mock_rmtree): + @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.""" @@ -100,7 +100,7 @@ def test_delete(self, mock_rmtree): Bootstrap().get_bootstrap("sdl2", self.ctx) ) self.ctx.bootstrap.distribution.delete() - mock_rmtree.assert_called_once_with( + mock_rmdir.assert_called_once_with( self.ctx.bootstrap.distribution.dist_dir ) diff --git a/tests/test_pythonpackage.py b/tests/test_pythonpackage.py index 2f88cf2aa4..21412e9258 100644 --- a/tests/test_pythonpackage.py +++ b/tests/test_pythonpackage.py @@ -42,7 +42,7 @@ def test_get_package_dependencies(): if "MarkupSafe" in dep ] # Check setuptools not being in non-recursive deps: - # (It will be in recursive ones due to p4a's pep517 dependency) + # (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: diff --git a/tests/test_pythonpackage_basic.py b/tests/test_pythonpackage_basic.py index b05344b56b..e98a5f99b0 100644 --- a/tests/test_pythonpackage_basic.py +++ b/tests/test_pythonpackage_basic.py @@ -236,7 +236,7 @@ def run__get_system_python_executable(self, pybin): pybin, "-c", "import importlib\n" - "import json\n" + "import build.util\n" "import os\n" "import sys\n" "sys.path = [os.path.dirname(sys.argv[1])] + sys.path\n" @@ -273,8 +273,8 @@ def test_systemwide_python(self): # 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 "pep517" in str(e.args): - # System python probably doesn't have pep517 available! + 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! @@ -304,11 +304,8 @@ def test_venv(self): ]) subprocess.check_output([ os.path.join(test_dir, "venv", "bin", "pip"), - "install", "-U", "pep517" - ]) - subprocess.check_output([ - os.path.join(test_dir, "venv", "bin", "pip"), - "install", "-U", "toml" + "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") diff --git a/tests/test_recipe.py b/tests/test_recipe.py index 666d089caa..b6e4c99225 100644 --- a/tests/test_recipe.py +++ b/tests/test_recipe.py @@ -134,7 +134,7 @@ def test_download_url_is_set(self): with ( patch_logger_debug()) as m_debug, ( mock.patch.object(Recipe, 'download_file')) as m_download_file, ( - mock.patch('pythonforandroid.recipe.sh.touch')) as m_touch, ( + mock.patch('pythonforandroid.recipe.touch')) as m_touch, ( tempfile.TemporaryDirectory()) as temp_dir: recipe.ctx.setup_dirs(temp_dir) recipe.download() diff --git a/tests/test_util.py b/tests/test_util.py index ff57dc7a47..a4d7ea816e 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,4 +1,6 @@ import os +from pathlib import Path +from tempfile import TemporaryDirectory import types import unittest from unittest import mock @@ -24,6 +26,7 @@ def test_ensure_dir(self, mock_makedirs): @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 @@ -136,3 +139,51 @@ def test_util_exceptions(self): ) 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() diff --git a/tox.ini b/tox.ini index ceafe446b5..9b0432c82b 100644 --- a/tox.ini +++ b/tox.ini @@ -27,11 +27,19 @@ commands = flake8 pythonforandroid/ tests/ ci/ setup.py [flake8] ignore = - E123, # Closing bracket does not match indentation of opening bracket's line - E124, # Closing bracket does not match visual indentation - E126, # Continuation line over-indented for hanging indent - E226, # Missing whitespace around arithmetic operator - E402, # Module level import not at top of file - E501, # Line too long (82 > 79 characters) - W503, # Line break occurred before a binary operator - W504 # Line break occurred after a binary operator + # 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