first commit

This commit is contained in:
Yura 2024-09-15 15:12:16 +03:00
commit 417e54da96
5696 changed files with 900003 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
from buildozer.scripts.client import main
if __name__ == '__main__':
main()

View file

@ -0,0 +1,452 @@
[app]
# (str) Title of your application
title = My Application
# (str) Package name
package.name = myapp
# (str) Package domain (needed for android/ios packaging)
package.domain = org.test
# (str) Source code where the main.py live
source.dir = .
# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas
# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin, venv
# (list) List of exclusions using pattern matching
# Do not prefix with './'
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.1
# (str) Application versioning (method 2)
# version.regex = __version__ = ['"](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (list) Supported orientations
# Valid options are: landscape, portrait, portrait-reverse or landscape-reverse
orientation = portrait
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# change the major version of python used by the app
osx.python_version = 3
# Kivy version to use
osx.kivy_version = 1.9.1
#
# Android specific
#
# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0
# (string) Presplash background color (for android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (string) Presplash animation using Lottie format.
# see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/
# for general documentation.
# Lottie files can be created using various tools, like Adobe After Effect or Synfig.
#android.presplash_lottie = "path/to/lottie/file.json"
# (str) Adaptive icon of the application (used if Android API level is 26+ at runtime)
#icon.adaptive_foreground.filename = %(source.dir)s/data/icon_fg.png
#icon.adaptive_background.filename = %(source.dir)s/data/icon_bg.png
# (list) Permissions
# (See https://python-for-android.readthedocs.io/en/latest/buildoptions/#build-options-1 for all the supported syntaxes and properties)
#android.permissions = android.permission.INTERNET, (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)
# (list) features (adds uses-feature -tags to manifest)
#android.features = android.hardware.usb.host
# (int) Target Android API, should be as high as possible.
#android.api = 31
# (int) Minimum API your APK / AAB will support.
#android.minapi = 21
# (int) Android SDK version to use
#android.sdk = 20
# (str) Android NDK version to use
#android.ndk = 23b
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =
# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.kivy.android.PythonActivity
# (str) Full name including package path of the Java class that implements Android Activity
# use that parameter together with android.entrypoint to set custom Java class instead of PythonActivity
#android.activity_class_name = org.kivy.android.PythonActivity
# (str) Extra xml to write directly inside the <manifest> element of AndroidManifest.xml
# use that parameter to provide a filename from where to load your custom XML code
#android.extra_manifest_xml = ./src/android/extra_manifest.xml
# (str) Extra xml to write directly inside the <manifest><application> tag of AndroidManifest.xml
# use that parameter to provide a filename from where to load your custom XML arguments:
#android.extra_manifest_application_arguments = ./src/android/extra_manifest_application_arguments.xml
# (str) Full name including package path of the Java class that implements Python Service
# use that parameter to set custom Java class which extends PythonService
#android.service_class_name = org.kivy.android.PythonService
# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add
#android.add_aars =
# (list) Put these files or directories in the apk assets directory.
# Either form may be used, and assets need not be in 'source.include_exts'.
# 1) android.add_assets = source_asset_relative_path
# 2) android.add_assets = source_asset_path:destination_asset_relative_path
#android.add_assets =
# (list) Put these files or directories in the apk res directory.
# The option may be used in three ways, the value may contain one or zero ':'
# Some examples:
# 1) A file to add to resources, legal resource names contain ['a-z','0-9','_']
# android.add_resources = my_icons/all-inclusive.png:drawable/all_inclusive.png
# 2) A directory, here 'legal_icons' must contain resources of one kind
# android.add_resources = legal_icons:drawable
# 3) A directory, here 'legal_resources' must contain one or more directories,
# each of a resource kind: drawable, xml, etc...
# android.add_resources = legal_resources
#android.add_resources =
# (list) Gradle dependencies to add
#android.gradle_dependencies =
# (bool) Enable AndroidX support. Enable when 'android.gradle_dependencies'
# contains an 'androidx' package, or any package from Kotlin source.
# android.enable_androidx requires android.api >= 28
#android.enable_androidx = True
# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =
# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_packaging_options =
# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (list) Copy these files to src/main/res/xml/ (used for example with intent-filters)
#android.res_xml = PATH_TO_FILE,
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (str) screenOrientation to set for the main activity.
# Valid values can be found at https://developer.android.com/guide/topics/manifest/activity-element
#android.manifest.orientation = fullSensor
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Android logcat only display log for activity's pid
#android.logcat_pid_only = False
# (str) Android additional adb arguments
#android.adb_args = -H host.docker.internal
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
# In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time.
android.archs = arm64-v8a, armeabi-v7a
# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1
# (bool) enables Android auto backup feature (Android API >=23)
android.allow_backup = True
# (str) XML file for custom backup rules (see official auto backup documentation)
# android.backup_rules =
# (str) If you need to insert variables into your AndroidManifest.xml file,
# you can do so with the manifestPlaceholders property.
# This property takes a map of key-value pairs. (via a string)
# Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"]
# android.manifest_placeholders = [:]
# (bool) Skip byte compile for .py files
# android.no-byte-compile-python = False
# (str) The format used to package the app for release mode (aab or apk or aar).
# android.release_artifact = aab
# (str) The format used to package the app for debug mode (apk or aar).
# android.debug_artifact = apk
#
# Python for android (p4a) specific
#
# (str) python-for-android URL to use for checkout
#p4a.url =
# (str) python-for-android fork to use in case if p4a.url is not specified, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android specific commit to use, defaults to HEAD, must be within p4a.branch
#p4a.commit = HEAD
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
# Control passing the --use-setup-py vs --ignore-setup-py to p4a
# "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not
# Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py
# NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate
# setup.py if you're using Poetry, but you need to add "toml" to source.include_exts.
#p4a.setup_py = false
# (str) extra command line arguments to pass when invoking pythonforandroid.toolchain
#p4a.extra_args =
#
# iOS specific
#
# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master
# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.10.0
# (bool) Whether or not to sign the code
ios.codesign.allowed = false
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) The development team to use for signing the debug version
#ios.codesign.development_team.debug = <hexstring>
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
# (str) The development team to use for signing the release version
#ios.codesign.development_team.release = <hexstring>
# (str) URL pointing to .ipa file to be installed
# This option should be defined along with `display_image_url` and `full_size_image_url` options.
#ios.manifest.app_url =
# (str) URL pointing to an icon (57x57px) to be displayed during download
# This option should be defined along with `app_url` and `full_size_image_url` options.
#ios.manifest.display_image_url =
# (str) URL pointing to a large icon (512x512px) to be used by iTunes
# This option should be defined along with `app_url` and `display_image_url` options.
#ios.manifest.full_size_image_url =
[buildozer]
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2
# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer
# (str) Path to build output (i.e. .apk, .aab, .ipa) storage
# bin_dir = ./bin
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug

View file

@ -0,0 +1,47 @@
"""
Replacement for shelve, using json.
This was needed to correctly support db between Python 2 and 3.
"""
__all__ = ["JsonStore"]
import io
from json import load, dump
from os.path import exists
class JsonStore:
def __init__(self, filename):
self.filename = filename
self.data = {}
if exists(filename):
try:
with io.open(filename, encoding='utf-8') as fd:
self.data = load(fd)
except ValueError:
print("Unable to read the state.db, content will be replaced.")
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
self.sync()
def __delitem__(self, key):
del self.data[key]
self.sync()
def __contains__(self, item):
return item in self.data
def get(self, item, default=None):
return self.data.get(item, default)
def keys(self):
return self.data.keys()
def sync(self):
with open(self.filename, 'w') as fd:
dump(self.data, fd, ensure_ascii=False)

View file

@ -0,0 +1,78 @@
# Copyright 2014 Donald Stufft
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class Infinity:
def __repr__(self):
return "Infinity"
def __hash__(self):
return hash(repr(self))
def __lt__(self, other):
return False
def __le__(self, other):
return False
def __eq__(self, other):
return isinstance(other, self.__class__)
def __ne__(self, other):
return not isinstance(other, self.__class__)
def __gt__(self, other):
return True
def __ge__(self, other):
return True
def __neg__(self):
return NegativeInfinity
Infinity = Infinity()
class NegativeInfinity:
def __repr__(self):
return "-Infinity"
def __hash__(self):
return hash(repr(self))
def __lt__(self, other):
return True
def __le__(self, other):
return True
def __eq__(self, other):
return isinstance(other, self.__class__)
def __ne__(self, other):
return not isinstance(other, self.__class__)
def __gt__(self, other):
return False
def __ge__(self, other):
return False
def __neg__(self):
return Infinity
NegativeInfinity = NegativeInfinity()

View file

@ -0,0 +1,400 @@
# Copyright 2014 Donald Stufft
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
import itertools
import re
from ._structures import Infinity
__all__ = [
"parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
]
_Version = collections.namedtuple(
"_Version",
["epoch", "release", "dev", "pre", "post", "local"],
)
def parse(version):
"""
Parse the given version string and return either a :class:`Version` object
or a :class:`LegacyVersion` object depending on if the given version is
a valid PEP 440 version or a legacy version.
"""
try:
return Version(version)
except InvalidVersion:
return LegacyVersion(version)
class InvalidVersion(ValueError):
"""
An invalid version was found, users should refer to PEP 440.
"""
class _BaseVersion:
def __hash__(self):
return hash(self._key)
def __lt__(self, other):
return self._compare(other, lambda s, o: s < o)
def __le__(self, other):
return self._compare(other, lambda s, o: s <= o)
def __eq__(self, other):
return self._compare(other, lambda s, o: s == o)
def __ge__(self, other):
return self._compare(other, lambda s, o: s >= o)
def __gt__(self, other):
return self._compare(other, lambda s, o: s > o)
def __ne__(self, other):
return self._compare(other, lambda s, o: s != o)
def _compare(self, other, method):
if not isinstance(other, _BaseVersion):
return NotImplemented
return method(self._key, other._key)
class LegacyVersion(_BaseVersion):
def __init__(self, version):
self._version = str(version)
self._key = _legacy_cmpkey(self._version)
def __str__(self):
return self._version
def __repr__(self):
return "<LegacyVersion({0})>".format(repr(str(self)))
@property
def public(self):
return self._version
@property
def base_version(self):
return self._version
@property
def local(self):
return None
@property
def is_prerelease(self):
return False
@property
def is_postrelease(self):
return False
_legacy_version_component_re = re.compile(
r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
)
_legacy_version_replacement_map = {
"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
}
def _parse_version_parts(s):
for part in _legacy_version_component_re.split(s):
part = _legacy_version_replacement_map.get(part, part)
if not part or part == ".":
continue
if part[:1] in "0123456789":
# pad for numeric comparison
yield part.zfill(8)
else:
yield "*" + part
# ensure that alpha/beta/candidate are before final
yield "*final"
def _legacy_cmpkey(version):
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
# greater than or equal to 0. This will effectively put the LegacyVersion,
# which uses the defacto standard originally implemented by setuptools,
# as before all PEP 440 versions.
epoch = -1
# This scheme is taken from pkg_resources.parse_version setuptools prior to
# it's adoption of the packaging library.
parts = []
for part in _parse_version_parts(version.lower()):
if part.startswith("*"):
# remove "-" before a prerelease tag
if part < "*final":
while parts and parts[-1] == "*final-":
parts.pop()
# remove trailing zeros from each series of numeric parts
while parts and parts[-1] == "00000000":
parts.pop()
parts.append(part)
parts = tuple(parts)
return epoch, parts
# Deliberately not anchored to the start and end of the string, to make it
# easier for 3rd party code to reuse
VERSION_PATTERN = r"""
v?
(?:
(?:(?P<epoch>[0-9]+)!)? # epoch
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
(?P<pre> # pre-release
[-_\.]?
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
[-_\.]?
(?P<pre_n>[0-9]+)?
)?
(?P<post> # post release
(?:-(?P<post_n1>[0-9]+))
|
(?:
[-_\.]?
(?P<post_l>post|rev|r)
[-_\.]?
(?P<post_n2>[0-9]+)?
)
)?
(?P<dev> # dev release
[-_\.]?
(?P<dev_l>dev)
[-_\.]?
(?P<dev_n>[0-9]+)?
)?
)
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
"""
class Version(_BaseVersion):
_regex = re.compile(
r"^\s*" + VERSION_PATTERN + r"\s*$",
re.VERBOSE | re.IGNORECASE,
)
def __init__(self, version):
# Validate the version and parse it into pieces
match = self._regex.search(version)
if not match:
raise InvalidVersion("Invalid version: '{0}'".format(version))
# Store the parsed out pieces of the version
self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")),
pre=_parse_letter_version(
match.group("pre_l"),
match.group("pre_n"),
),
post=_parse_letter_version(
match.group("post_l"),
match.group("post_n1") or match.group("post_n2"),
),
dev=_parse_letter_version(
match.group("dev_l"),
match.group("dev_n"),
),
local=_parse_local_version(match.group("local")),
)
# Generate a key which will be used for sorting
self._key = _cmpkey(
self._version.epoch,
self._version.release,
self._version.pre,
self._version.post,
self._version.dev,
self._version.local,
)
def __repr__(self):
return "<Version({0})>".format(repr(str(self)))
def __str__(self):
parts = []
# Epoch
if self._version.epoch != 0:
parts.append("{0}!".format(self._version.epoch))
# Release segment
parts.append(".".join(str(x) for x in self._version.release))
# Pre-release
if self._version.pre is not None:
parts.append("-" + "".join(str(x) for x in self._version.pre))
# Post-release
if self._version.post is not None:
parts.append(".post{0}".format(self._version.post[1]))
# Development release
if self._version.dev is not None:
parts.append(".dev{0}".format(self._version.dev[1]))
# Local version segment
if self._version.local is not None:
parts.append(
"+{0}".format(".".join(str(x) for x in self._version.local))
)
return "".join(parts)
@property
def public(self):
return str(self).split("+", 1)[0]
@property
def base_version(self):
parts = []
# Epoch
if self._version.epoch != 0:
parts.append("{0}!".format(self._version.epoch))
# Release segment
parts.append(".".join(str(x) for x in self._version.release))
return "".join(parts)
@property
def local(self):
version_string = str(self)
if "+" in version_string:
return version_string.split("+", 1)[1]
@property
def is_prerelease(self):
return bool(self._version.dev or self._version.pre)
@property
def is_postrelease(self):
return bool(self._version.post)
def _parse_letter_version(letter, number):
if letter:
# We consider there to be an implicit 0 in a pre-release if there is
# not a numeral associated with it.
if number is None:
number = 0
# We normalize any letters to their lower case form
letter = letter.lower()
# We consider some words to be alternate spellings of other words and
# in those cases we want to normalize the spellings to our preferred
# spelling.
if letter == "alpha":
letter = "a"
elif letter == "beta":
letter = "b"
elif letter in ["c", "pre", "preview"]:
letter = "rc"
return letter, int(number)
if not letter and number:
# We assume if we are given a number, but we are not given a letter
# then this is using the implicit post release syntax (e.g. 1.0-1)
letter = "post"
return letter, int(number)
_local_version_separators = re.compile(r"[\._-]")
def _parse_local_version(local):
"""
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
"""
if local is not None:
return tuple(
part.lower() if not part.isdigit() else int(part)
for part in _local_version_separators.split(local)
)
def _cmpkey(epoch, release, pre, post, dev, local):
# When we compare a release version, we want to compare it with all of the
# trailing zeros removed. So we'll use a reverse the list, drop all the now
# leading zeros until we come to something non zero, then take the rest
# re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key.
release = tuple(
reversed(list(
itertools.dropwhile(
lambda x: x == 0,
reversed(release),
)
))
)
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
# We'll do this by abusing the pre segment, but we _only_ want to do this
# if there is not a pre or a post segment. If we have one of those then
# the normal sorting rules will handle this case correctly.
if pre is None and post is None and dev is not None:
pre = -Infinity
# Versions without a pre-release (except as noted above) should sort after
# those with one.
elif pre is None:
pre = Infinity
# Versions without a post segment should sort before those with one.
if post is None:
post = -Infinity
# Versions without a development segment should sort after those with one.
if dev is None:
dev = Infinity
if local is None:
# Versions without a local segment should sort before those with one.
local = -Infinity
else:
# Versions with a local segment need that segment parsed to implement
# the sorting rules in PEP440.
# - Alpha numeric segments sort before numeric segments
# - Alpha numeric segments sort lexicographically
# - Numeric segments sort numerically
# - Shorter versions sort before longer versions when the prefixes
# match exactly
local = tuple(
(i, "") if isinstance(i, int) else (-Infinity, i)
for i in local
)
return epoch, release, pre, post, dev, local

View file

@ -0,0 +1,24 @@
'''
Main Buildozer client
=====================
'''
import sys
from buildozer import Buildozer, BuildozerCommandException, BuildozerException
def main():
try:
Buildozer().run_command(sys.argv[1:])
except BuildozerCommandException:
# don't show the exception in the command line. The log already show
# the command failed.
sys.exit(1)
except BuildozerException as error:
Buildozer().error('%s' % error)
sys.exit(1)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,278 @@
'''
Buildozer remote
================
.. warning::
This is an experimental tool and not widely used. It might not fit for you.
Pack and send the source code to a remote SSH server, bundle buildozer with it,
and start the build on the remote.
You need paramiko to make it work.
'''
__all__ = ["BuildozerRemote"]
import socket
import sys
from buildozer import (
Buildozer, BuildozerCommandException, BuildozerException, __version__)
from sys import stdout, stdin, exit
from select import select
from os.path import join, expanduser, realpath, exists, splitext
from os import makedirs, walk, getcwd
from configparser import ConfigParser
try:
import termios
has_termios = True
except ImportError:
has_termios = False
try:
import paramiko
except ImportError:
print('Paramiko missing: pip install paramiko')
class BuildozerRemote(Buildozer):
def run_command(self, args):
while args:
if not args[0].startswith('-'):
break
arg = args.pop(0)
if arg in ('-v', '--verbose'):
self.log_level = 2
elif arg in ('-p', '--profile'):
self.config_profile = args.pop(0)
elif arg in ('-h', '--help'):
self.usage()
exit(0)
elif arg == '--version':
print('Buildozer (remote) {0}'.format(__version__))
exit(0)
self._merge_config_profile()
if len(args) < 2:
self.usage()
return
remote_name = args[0]
remote_section = 'remote:{}'.format(remote_name)
if not self.config.has_section(remote_section):
self.error('Unknown remote "{}", must be configured first.'.format(
remote_name))
return
self.remote_host = remote_host = self.config.get(
remote_section, 'host', '')
self.remote_port = self.config.get(
remote_section, 'port', '22')
self.remote_user = remote_user = self.config.get(
remote_section, 'user', '')
self.remote_build_dir = remote_build_dir = self.config.get(
remote_section, 'build_directory', '')
self.remote_identity = self.config.get(
remote_section, 'identity', '')
if not remote_host:
self.error('Missing "host = " for {}'.format(remote_section))
return
if not remote_user:
self.error('Missing "user = " for {}'.format(remote_section))
return
if not remote_build_dir:
self.error('Missing "build_directory = " for {}'.format(remote_section))
return
# fake the target
self.targetname = 'remote'
self.check_build_layout()
# prepare our source code
self.info('Prepare source code to sync')
self._copy_application_sources()
self._ssh_connect()
try:
self._ensure_buildozer()
self._sync_application_sources()
self._do_remote_commands(args[1:])
self._ssh_sync(getcwd(), mode='get')
finally:
self._ssh_close()
def _ssh_connect(self):
self.info('Connecting to {}'.format(self.remote_host))
self._ssh_client = client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.load_system_host_keys()
kwargs = {}
if self.remote_identity:
kwargs['key_filename'] = expanduser(self.remote_identity)
client.connect(self.remote_host, username=self.remote_user,
port=int(self.remote_port), **kwargs)
self._sftp_client = client.open_sftp()
def _ssh_close(self):
self.debug('Closing remote connection')
self._sftp_client.close()
self._ssh_client.close()
def _ensure_buildozer(self):
s = self._sftp_client
root_dir = s.normalize('.')
self.remote_build_dir = join(root_dir, self.remote_build_dir,
self.package_full_name)
self.debug('Remote build directory: {}'.format(self.remote_build_dir))
self._ssh_mkdir(self.remote_build_dir)
self._ssh_sync(__path__[0]) # noqa: F821 undefined name
def _sync_application_sources(self):
self.info('Synchronize application sources')
self._ssh_sync(self.app_dir)
# create custom buildozer.spec
self.info('Create custom buildozer.spec')
config = ConfigParser()
config.read('buildozer.spec')
config.set('app', 'source.dir', 'app')
fn = join(self.remote_build_dir, 'buildozer.spec')
fd = self._sftp_client.open(fn, 'wb')
config.write(fd)
fd.close()
def _do_remote_commands(self, args):
self.info('Execute remote buildozer')
cmd = (
'source ~/.profile;'
'cd {0};'
'env PYTHONPATH={0}:$PYTHONPATH '
'python -c "import buildozer, sys;'
'buildozer.Buildozer().run_command(sys.argv[1:])" {1} {2} 2>&1').format(
self.remote_build_dir,
'--verbose' if self.log_level == 2 else '',
' '.join(args),
)
self._ssh_command(cmd)
def _ssh_mkdir(self, *args):
directory = join(*args)
self.debug('Create remote directory {}'.format(directory))
try:
self._sftp_client.mkdir(directory)
except IOError:
# already created?
try:
self._sftp_client.stat(directory)
except IOError:
self.error('Unable to create remote directory {}'.format(directory))
raise
def _ssh_sync(self, directory, mode='put'):
self.debug('Syncing {} directory'.format(directory))
directory = realpath(expanduser(directory))
base_strip = directory.rfind('/')
if mode == 'get':
local_dir = join(directory, 'bin')
remote_dir = join(self.remote_build_dir, 'bin')
if not exists(local_dir):
makedirs(local_dir)
for _file in self._sftp_client.listdir(path=remote_dir):
self._sftp_client.get(join(remote_dir, _file),
join(local_dir, _file))
return
for root, dirs, files in walk(directory):
self._ssh_mkdir(self.remote_build_dir, root[base_strip + 1:])
for fn in files:
if splitext(fn)[1] in ('.pyo', '.pyc', '.swp'):
continue
local_file = join(root, fn)
remote_file = join(self.remote_build_dir, root[base_strip + 1:], fn)
self.debug('Sync {} -> {}'.format(local_file, remote_file))
self._sftp_client.put(local_file, remote_file)
def _ssh_command(self, command):
self.debug('Execute remote command {}'.format(command))
transport = self._ssh_client.get_transport()
channel = transport.open_session()
try:
channel.exec_command(command)
self._interactive_shell(channel)
finally:
channel.close()
def _interactive_shell(self, chan):
if has_termios:
self._posix_shell(chan)
else:
self._windows_shell(chan)
def _posix_shell(self, chan):
oldtty = termios.tcgetattr(stdin)
try:
chan.settimeout(0.0)
while True:
r, w, e = select([chan, stdin], [], [])
if chan in r:
try:
x = chan.recv(128)
if len(x) == 0:
print('\r\n*** EOF\r\n',)
break
stdout.write(x)
stdout.flush()
except socket.timeout:
pass
if stdin in r:
x = stdin.read(1)
if len(x) == 0:
break
chan.sendall(x)
finally:
termios.tcsetattr(stdin, termios.TCSADRAIN, oldtty)
# thanks to Mike Looijmans for this code
def _windows_shell(self, chan):
import threading
stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")
def writeall(sock):
while True:
data = sock.recv(256)
if not data:
stdout.write('\r\n*** EOF ***\r\n\r\n')
stdout.flush()
break
stdout.write(data)
stdout.flush()
writer = threading.Thread(target=writeall, args=(chan,))
writer.start()
try:
while True:
d = stdin.read(1)
if not d:
break
chan.send(d)
except EOFError:
# user hit ^Z or F6
pass
def main():
try:
BuildozerRemote().run_command(sys.argv[1:])
except BuildozerCommandException:
pass
except BuildozerException as error:
Buildozer().error('%s' % error)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,3 @@
from os.path import join, dirname
import sys
sys.path.append(join(dirname(__file__), '_applibs'))

View file

@ -0,0 +1,263 @@
from sys import exit
import os
from os.path import join
def no_config(f):
f.__no_config = True
return f
class Target:
def __init__(self, buildozer):
self.buildozer = buildozer
self.build_mode = 'debug'
self.artifact_format = 'apk'
self.platform_update = False
def check_requirements(self):
pass
def check_configuration_tokens(self, errors=None):
if errors:
self.buildozer.info('Check target configuration tokens')
self.buildozer.error(
'{0} error(s) found in the buildozer.spec'.format(
len(errors)))
for error in errors:
print(error)
exit(1)
def compile_platform(self):
pass
def install_platform(self):
pass
def get_custom_commands(self):
result = []
for x in dir(self):
if not x.startswith('cmd_'):
continue
if x[4:] in self.buildozer.standard_cmds:
continue
result.append((x[4:], getattr(self, x).__doc__))
return result
def get_available_packages(self):
return ['kivy']
def run_commands(self, args):
if not args:
self.buildozer.error('Missing target command')
self.buildozer.usage()
exit(1)
result = []
last_command = []
while args:
arg = args.pop(0)
if arg == '--':
if last_command:
last_command += args
break
elif not arg.startswith('--'):
if last_command:
result.append(last_command)
last_command = []
last_command.append(arg)
else:
if not last_command:
self.buildozer.error('Argument passed without a command')
self.buildozer.usage()
exit(1)
last_command.append(arg)
if last_command:
result.append(last_command)
config_check = False
for item in result:
command, args = item[0], item[1:]
if not hasattr(self, 'cmd_{0}'.format(command)):
self.buildozer.error('Unknown command {0}'.format(command))
exit(1)
func = getattr(self, 'cmd_{0}'.format(command))
need_config_check = not hasattr(func, '__no_config')
if need_config_check and not config_check:
config_check = True
self.check_configuration_tokens()
func(args)
def cmd_clean(self, *args):
self.buildozer.clean_platform()
def cmd_update(self, *args):
self.platform_update = True
self.buildozer.prepare_for_build()
def cmd_debug(self, *args):
self.buildozer.prepare_for_build()
self.build_mode = 'debug'
self.artifact_format = self.buildozer.config.getdefault('app', 'android.debug_artifact', 'apk')
self.buildozer.build()
def cmd_release(self, *args):
error = self.buildozer.error
self.buildozer.prepare_for_build()
if self.buildozer.config.get("app", "package.domain") == "org.test":
error("")
error("ERROR: Trying to release a package that starts with org.test")
error("")
error("The package.domain org.test is, as the name intented, a test.")
error("Once you published an application with org.test,")
error("you cannot change it, it will be part of the identifier")
error("for Google Play / App Store / etc.")
error("")
error("So change package.domain to anything else.")
error("")
error("If you messed up before, set the environment variable to force the build:")
error("export BUILDOZER_ALLOW_ORG_TEST_DOMAIN=1")
error("")
if "BUILDOZER_ALLOW_ORG_TEST_DOMAIN" not in os.environ:
exit(1)
if self.buildozer.config.get("app", "package.domain") == "org.kivy":
error("")
error("ERROR: Trying to release a package that starts with org.kivy")
error("")
error("The package.domain org.kivy is reserved for the Kivy official")
error("applications. Please use your own domain.")
error("")
error("If you are a Kivy developer, add an export in your shell")
error("export BUILDOZER_ALLOW_KIVY_ORG_DOMAIN=1")
error("")
if "BUILDOZER_ALLOW_KIVY_ORG_DOMAIN" not in os.environ:
exit(1)
self.build_mode = 'release'
self.artifact_format = self.buildozer.config.getdefault('app', 'android.release_artifact', 'aab')
self.buildozer.build()
def cmd_deploy(self, *args):
self.buildozer.prepare_for_build()
def cmd_run(self, *args):
self.buildozer.prepare_for_build()
def cmd_serve(self, *args):
self.buildozer.cmd_serve()
def path_or_git_url(self, repo, owner='kivy', branch='master',
url_format='https://github.com/{owner}/{repo}.git',
platform=None,
squash_hyphen=True):
"""Get source location for a git checkout
This method will check the `buildozer.spec` for the keys:
{repo}_dir
{repo}_url
{repo}_branch
and use them to determine the source location for a git checkout.
If a `platform` is specified, {platform}.{repo} will be used
as the base for the buildozer key
`{repo}_dir` specifies a custom checkout location
(relative to `buildozer.root_dir`). If present, `path` will be
set to this value and `url`, `branch` will be set to None,
None. Otherwise, `{repo}_url` and `{repo}_branch` will be
examined.
If no keys are present, the kwargs will be used to create
a sensible default URL and branch.
:Parameters:
`repo`: str (required)
name of repository to fetch. Used both for buildozer
keys ({platform}.{repo}_dir|_url|_branch) and in building
default git URL
`branch`: str (default 'master')
Specific branch to retrieve if none specified in
buildozer.spec.
`owner`: str
owner of repo.
`platform`: str or None
platform prefix to use when retrieving `buildozer.spec`
keys. If specified, key names will be {platform}.{repo}
instead of just {repo}
`squash_hyphen`: boolean
if True, change '-' to '_' when looking for
keys in buildozer.spec. This lets us keep backwards
compatibility with old buildozer.spec files
`url_format`: format string
Used to construct default git URL.
can use {repo} {owner} and {branch} if needed.
:Returns:
A Tuple (path, url, branch) where
`path`
Path to a custom git checkout. If specified,
both `url` and `branch` will be None
`url`
URL of git repository from where code should be
checked-out
`branch`
branch name (or tag) that should be used for the
check-out.
"""
if squash_hyphen:
key = repo.replace('-', '_')
else:
key = repo
if platform:
key = "{}.{}".format(platform, key)
config = self.buildozer.config
path = config.getdefault('app', '{}_dir'.format(key), None)
if path is not None:
path = join(self.buildozer.root_dir, path)
url = None
branch = None
else:
branch = config.getdefault('app', '{}_branch'.format(key), branch)
default_url = url_format.format(owner=owner, repo=repo, branch=branch)
url = config.getdefault('app', '{}_url'.format(key), default_url)
return path, url, branch
def install_or_update_repo(self, repo, **kwargs):
"""Install or update a git repository into the platform directory.
This will clone the contents of a git repository to
`buildozer.platform_dir`. The location of this repo can be
specified via URL and branch name, or via a custom (local)
directory name.
:Parameters:
**kwargs:
Any valid arguments for :meth:`path_or_git_url`
:Returns:
fully qualified path to updated git repo
"""
cmd = self.buildozer.cmd
install_dir = join(self.buildozer.platform_dir, repo)
custom_dir, clone_url, clone_branch = self.path_or_git_url(repo, **kwargs)
if not self.buildozer.file_exists(install_dir):
if custom_dir:
cmd(["mkdir", "-p", install_dir])
cmd(["cp", "-a", f"{custom_dir}/*", f"{install_dir}/"])
else:
cmd(["git", "clone", "--branch", clone_branch, clone_url], cwd=self.buildozer.platform_dir)
elif self.platform_update:
if custom_dir:
cmd(["cp", "-a", f"{custom_dir}/*", f"{install_dir}/"])
else:
cmd(["git", "clean", "-dxf"], cwd=install_dir)
cmd(["git", "pull", "origin", clone_branch], cwd=install_dir)
return install_dir

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,478 @@
'''
iOS target, based on kivy-ios project
'''
import sys
import plistlib
from buildozer import BuildozerCommandException
from buildozer.target import Target, no_config
from os.path import join, basename, expanduser, realpath
from getpass import getpass
PHP_TEMPLATE = '''
<?php
// credits goes to http://jeffreysambells.com/2010/06/22/ios-wireless-app-distribution
$ipas = glob('*.ipa');
$provisioningProfiles = glob('*.mobileprovision');
$plists = glob('*.plist');
$sr = stristr( $_SERVER['SCRIPT_URI'], '.php' ) === false ?
$_SERVER['SCRIPT_URI'] : dirname($_SERVER['SCRIPT_URI']) . '/';
$provisioningProfile = $sr . $provisioningProfiles[0];
$ipa = $sr . $ipas[0];
$itmsUrl = urlencode( $sr . 'index.php?plist=' . str_replace( '.plist', '', $plists[0] ) );
if ($_GET['plist']) {
$plist = file_get_contents( dirname(__FILE__)
. DIRECTORY_SEPARATOR
. preg_replace( '/![A-Za-z0-9-_]/i', '', $_GET['plist']) . '.plist' );
$plist = str_replace('_URL_', $ipa, $plist);
header('content-type: application/xml');
echo $plist;
die();
}
?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Install {appname}</title>
<style type="text/css">
li { padding: 1em; }
</style>
</head>
<body>
<ul>
<li><a href="<? echo $provisioningProfile; ?>">Install Team Provisioning File</a></li>
<li><a href="itms-services://?action=download-manifest&url=<? echo $itmsUrl; ?>">
Install Application</a></li>
</ul>
</body>
</html>
'''
class TargetIos(Target):
targetname = "ios"
def __init__(self, buildozer):
super().__init__(buildozer)
executable = sys.executable or 'python'
self._toolchain_cmd = [executable, "toolchain.py"]
self._xcodebuild_cmd = ["xcodebuild"]
# set via install_platform()
self.ios_dir = None
self.ios_deploy_dir = None
def check_requirements(self):
if sys.platform != "darwin":
raise NotImplementedError("Only macOS is supported for iOS target")
checkbin = self.buildozer.checkbin
cmd = self.buildozer.cmd
checkbin('Xcode xcodebuild', 'xcodebuild')
checkbin('Xcode xcode-select', 'xcode-select')
checkbin('Git git', 'git')
checkbin('Cython cython', 'cython')
checkbin('pkg-config', 'pkg-config')
checkbin('autoconf', 'autoconf')
checkbin('automake', 'automake')
checkbin('libtool', 'libtool')
self.buildozer.debug('Check availability of a iPhone SDK')
sdk = cmd('xcodebuild -showsdks | fgrep "iphoneos" |'
'tail -n 1 | awk \'{print $2}\'',
get_stdout=True, shell=True)[0]
if not sdk:
raise Exception(
'No iPhone SDK found. Please install at least one iOS SDK.')
else:
self.buildozer.debug(' -> found %r' % sdk)
self.buildozer.debug('Check Xcode path')
xcode = cmd(["xcode-select", "-print-path"], get_stdout=True)[0]
if not xcode:
raise Exception('Unable to get xcode path')
self.buildozer.debug(' -> found {0}'.format(xcode))
def install_platform(self):
"""
Clones `kivy/kivy-ios` and `phonegap/ios-deploy` then sets `ios_dir`
and `ios_deploy_dir` accordingly.
"""
self.ios_dir = self.install_or_update_repo('kivy-ios', platform='ios')
self.ios_deploy_dir = self.install_or_update_repo('ios-deploy',
platform='ios',
branch='1.7.0',
owner='phonegap')
def toolchain(self, cmd, **kwargs):
kwargs.setdefault('cwd', self.ios_dir)
return self.buildozer.cmd([*self._toolchain_cmd, *cmd], **kwargs)
def xcodebuild(self, *args, **kwargs):
filtered_args = [arg for arg in args if arg is not None]
return self.buildozer.cmd([*self._xcodebuild_cmd, *filtered_args], **kwargs)
@property
def code_signing_allowed(self):
allowed = self.buildozer.config.getboolean("app", "ios.codesign.allowed")
allowed = "YES" if allowed else "NO"
return f"CODE_SIGNING_ALLOWED={allowed}"
@property
def code_signing_development_team(self):
team = self.buildozer.config.getdefault("app", f"ios.codesign.development_team.{self.build_mode}", None)
return f"DEVELOPMENT_TEAM={team}" if team else None
def get_available_packages(self):
available_modules = self.toolchain(["recipes", "--compact"], get_stdout=True)[0]
return available_modules.splitlines()[0].split()
def load_plist_from_file(self, plist_rfn):
with open(plist_rfn, 'rb') as f:
return plistlib.load(f)
def dump_plist_to_file(self, plist, plist_rfn):
with open(plist_rfn, 'wb') as f:
plistlib.dump(plist, f)
def compile_platform(self):
# for ios, the compilation depends really on the app requirements.
# compile the distribution only if the requirements changed.
last_requirements = self.buildozer.state.get('ios.requirements', '')
app_requirements = self.buildozer.config.getlist('app', 'requirements',
'')
# we need to extract the requirements that kivy-ios knows about
available_modules = self.get_available_packages()
onlyname = lambda x: x.split('==')[0] # noqa: E731 do not assign a lambda expression, use a def
ios_requirements = [x for x in app_requirements if onlyname(x) in
available_modules]
need_compile = 0
if last_requirements != ios_requirements:
need_compile = 1
# len('requirements.source.') == 20, so use name[20:]
source_dirs = {'{}_DIR'.format(name[20:].upper()):
realpath(expanduser(value))
for name, value in self.buildozer.config.items('app')
if name.startswith('requirements.source.')}
if source_dirs:
need_compile = 1
self.buildozer.environ.update(source_dirs)
self.buildozer.info('Using custom source dirs:\n {}'.format(
'\n '.join(['{} = {}'.format(k, v)
for k, v in source_dirs.items()])))
if not need_compile:
self.buildozer.info('Distribution already compiled, pass.')
return
self.toolchain(["build", *ios_requirements])
if not self.buildozer.file_exists(self.ios_deploy_dir, 'ios-deploy'):
self.xcodebuild(cwd=self.ios_deploy_dir)
self.buildozer.state['ios.requirements'] = ios_requirements
self.buildozer.state.sync()
def _get_package(self):
config = self.buildozer.config
package_domain = config.getdefault('app', 'package.domain', '')
package = config.get('app', 'package.name')
if package_domain:
package = package_domain + '.' + package
return package.lower()
def build_package(self):
self._unlock_keychain()
# create the project
app_name = self.buildozer.namify(self.buildozer.config.get('app',
'package.name'))
ios_frameworks = self.buildozer.config.getlist('app', 'ios.frameworks', '')
frameworks_cmd = []
for framework in ios_frameworks:
frameworks_cmd.append(f"--add-framework={framework}")
self.app_project_dir = join(self.ios_dir, '{0}-ios'.format(app_name.lower()))
if not self.buildozer.file_exists(self.app_project_dir):
cmd = ["create", *frameworks_cmd, app_name, self.buildozer.app_dir]
else:
cmd = ["update", *frameworks_cmd, f"{app_name}-ios"]
self.toolchain(cmd)
# fix the plist
plist_fn = '{}-Info.plist'.format(app_name.lower())
plist_rfn = join(self.app_project_dir, plist_fn)
version = self.buildozer.get_version()
self.buildozer.info('Update Plist {}'.format(plist_fn))
plist = self.load_plist_from_file(plist_rfn)
plist['CFBundleIdentifier'] = self._get_package()
plist['CFBundleShortVersionString'] = version
plist['CFBundleVersion'] = '{}.{}'.format(version,
self.buildozer.build_id)
# add icons
self._create_icons()
# Generate OTA distribution manifest if `app_url`, `display_image_url` and `full_size_image_url` are defined.
app_url = self.buildozer.config.getdefault("app", "ios.manifest.app_url", None)
display_image_url = self.buildozer.config.getdefault("app", "ios.manifest.display_image_url", None)
full_size_image_url = self.buildozer.config.getdefault("app", "ios.manifest.full_size_image_url", None)
if any((app_url, display_image_url, full_size_image_url)):
if not all((app_url, display_image_url, full_size_image_url)):
self.buildozer.error("Options ios.manifest.app_url, ios.manifest.display_image_url"
" and ios.manifest.full_size_image_url should be defined all together")
return
plist['manifest'] = {
'appURL': app_url,
'displayImageURL': display_image_url,
'fullSizeImageURL': full_size_image_url,
}
# ok, write the modified plist.
self.dump_plist_to_file(plist, plist_rfn)
mode = self.build_mode.capitalize()
self.xcodebuild(
"-configuration",
mode,
'-allowProvisioningUpdates',
'ENABLE_BITCODE=NO',
self.code_signing_allowed,
self.code_signing_development_team,
'clean',
'build',
cwd=self.app_project_dir)
ios_app_dir = '{app_lower}-ios/build/{mode}-iphoneos/{app_lower}.app'.format(
app_lower=app_name.lower(), mode=mode)
self.buildozer.state['ios:latestappdir'] = ios_app_dir
intermediate_dir = join(self.ios_dir, '{}-{}.intermediates'.format(app_name, version))
xcarchive = join(intermediate_dir, '{}-{}.xcarchive'.format(
app_name, version))
ipa_name = '{}-{}.ipa'.format(app_name, version)
ipa_tmp = join(intermediate_dir, ipa_name)
ipa = join(self.buildozer.bin_dir, ipa_name)
build_dir = join(self.ios_dir, '{}-ios'.format(app_name.lower()))
self.buildozer.rmdir(intermediate_dir)
self.buildozer.info('Creating archive...')
self.xcodebuild(
'-alltargets',
'-configuration',
mode,
'-scheme',
app_name.lower(),
'-archivePath',
xcarchive,
'-destination',
'generic/platform=iOS',
'archive',
'ENABLE_BITCODE=NO',
self.code_signing_allowed,
self.code_signing_development_team,
cwd=build_dir)
key = 'ios.codesign.{}'.format(self.build_mode)
ioscodesign = self.buildozer.config.getdefault('app', key, '')
if not ioscodesign:
self.buildozer.error('Cannot create the IPA package without'
' signature. You must fill the "{}" token.'.format(key))
return
elif ioscodesign[0] not in ('"', "'"):
ioscodesign = '"{}"'.format(ioscodesign)
self.buildozer.info('Creating IPA...')
self.xcodebuild(
'-exportArchive',
f'-archivePath "{xcarchive}"',
f'-exportOptionsPlist "{plist_rfn}"',
f'-exportPath "{ipa_tmp}"',
f'CODE_SIGN_IDENTITY={ioscodesign}',
'ENABLE_BITCODE=NO',
cwd=build_dir)
self.buildozer.info('Moving IPA to bin...')
self.buildozer.file_rename(ipa_tmp, ipa)
self.buildozer.info('iOS packaging done!')
self.buildozer.info('IPA {0} available in the bin directory'.format(
basename(ipa)))
self.buildozer.state['ios:latestipa'] = ipa
self.buildozer.state['ios:latestmode'] = self.build_mode
def cmd_deploy(self, *args):
super().cmd_deploy(*args)
self._run_ios_deploy(lldb=False)
def cmd_run(self, *args):
super().cmd_run(*args)
self._run_ios_deploy(lldb=True)
def cmd_xcode(self, *args):
'''Open the xcode project.
'''
app_name = self.buildozer.namify(self.buildozer.config.get('app',
'package.name'))
app_name = app_name.lower()
ios_dir = ios_dir = join(self.buildozer.platform_dir, 'kivy-ios')
self.buildozer.cmd(
["open", f"{app_name}.xcodeproj"], cwd=join(ios_dir, f"{app_name}-ios")
)
def _run_ios_deploy(self, lldb=False):
state = self.buildozer.state
if 'ios:latestappdir' not in state:
self.buildozer.error(
'App not built yet. Run "debug" or "release" first.')
return
ios_app_dir = state.get('ios:latestappdir')
if lldb:
debug_mode = '-d'
self.buildozer.info('Deploy and start the application')
else:
debug_mode = ''
self.buildozer.info('Deploy the application')
self.buildozer.cmd(
[join(self.ios_deploy_dir, "ios-deploy"), debug_mode, "-b", ios_app_dir],
cwd=self.ios_dir,
show_output=True,
)
def _create_icons(self):
icon = self.buildozer.config.getdefault('app', 'icon.filename', '')
if not icon:
return
icon_fn = join(self.buildozer.app_dir, icon)
if not self.buildozer.file_exists(icon_fn):
self.buildozer.error('Icon {} does not exists'.format(icon_fn))
return
self.toolchain(["icon", self.app_project_dir, icon_fn])
def check_configuration_tokens(self):
errors = []
config = self.buildozer.config
if not config.getboolean('app', 'ios.codesign.allowed'):
return
identity_debug = config.getdefault('app', 'ios.codesign.debug', '')
identity_release = config.getdefault('app', 'ios.codesign.release',
identity_debug)
available_identities = self._get_available_identities()
if not identity_debug:
errors.append('[app] "ios.codesign.debug" key missing, '
'you must give a certificate name to use.')
elif identity_debug not in available_identities:
errors.append('[app] identity {} not found. '
'Check with list_identities'.format(identity_debug))
if not identity_release:
errors.append('[app] "ios.codesign.release" key missing, '
'you must give a certificate name to use.')
elif identity_release not in available_identities:
errors.append('[app] identity "{}" not found. '
'Check with list_identities'.format(identity_release))
super().check_configuration_tokens(errors)
@no_config
def cmd_list_identities(self, *args):
'''List the available identities to use for signing.
'''
identities = self._get_available_identities()
print('Available identities:')
for x in identities:
print(' - {}'.format(x))
def _get_available_identities(self):
output = self.buildozer.cmd(
["security", "find-identity", "-v", "-p", "codesigning"], get_stdout=True
)[0]
lines = output.splitlines()[:-1]
lines = [u'"{}"'.format(x.split('"')[1]) for x in lines]
return lines
def _unlock_keychain(self):
password_file = join(self.buildozer.buildozer_dir, '.ioscodesign')
password = None
if self.buildozer.file_exists(password_file):
with open(password_file) as fd:
password = fd.read()
if not password:
# no password available, try to unlock anyway...
error = self.buildozer.cmd(["security", "unlock-keychain", "-u"],
break_on_error=False)[2]
if not error:
return
else:
# password available, try to unlock
error = self.buildozer.cmd(
["security", "unlock-keychain", "-p", password],
break_on_error=False,
sensible=True,
)[2]
if not error:
return
# we need the password to unlock.
correct = False
attempt = 3
while attempt:
attempt -= 1
password = getpass('Password to unlock the default keychain:')
error = self.buildozer.cmd(
["security", "unlock-keychain", "-p", password],
break_on_error=False,
sensible=True,
)[2]
if not error:
correct = True
break
self.buildozer.error('Invalid keychain password')
if not correct:
self.buildozer.error('Unable to unlock the keychain, exiting.')
raise BuildozerCommandException()
# maybe user want to save it for further reuse?
print(
'The keychain password can be saved in the build directory\n'
'As soon as the build directory will be cleaned, '
'the password will be erased.')
save = None
while save is None:
q = input('Do you want to save the password (Y/n): ')
if q in ('', 'Y'):
save = True
elif q == 'n':
save = False
else:
print('Invalid answer!')
if save:
with open(password_file, 'wb') as fd:
fd.write(password.encode())
def get_target(buildozer):
return TargetIos(buildozer)

View file

@ -0,0 +1,246 @@
'''
OSX target, based on kivy-sdk-packager
'''
import sys
if sys.platform != 'darwin':
raise NotImplementedError('This will only work on osx')
from buildozer.target import Target
from os.path import exists, join, abspath, dirname
from subprocess import check_call, check_output
class TargetOSX(Target):
targetname = "osx"
def ensure_sdk(self):
self.buildozer.info('Check if kivy-sdk-packager exists')
if exists(
join(self.buildozer.platform_dir, 'kivy-sdk-packager-master')):
self.buildozer.info(
'kivy-sdk-packager found at '
'{}'.format(self.buildozer.platform_dir))
return
self.buildozer.info('kivy-sdk-packager does not exist, clone it')
platdir = self.buildozer.platform_dir
check_call(
('curl', '-O', '-L',
'https://github.com/kivy/kivy-sdk-packager/archive/master.zip'),
cwd=platdir)
check_call(('unzip', 'master.zip'), cwd=platdir)
check_call(('rm', 'master.zip'), cwd=platdir)
def download_kivy(self, cwd, py_branch=2):
current_kivy_vers = self.buildozer.config.get('app', 'osx.kivy_version')
if exists('/Applications/Kivy{}.app'.format(py_branch)):
self.buildozer.info('Kivy found in Applications dir...')
check_call(
('cp', '-a', '/Applications/Kivy{}.app'.format(py_branch),
'Kivy.app'), cwd=cwd)
else:
if not exists(join(cwd, 'Kivy{}.dmg'.format(py_branch))):
self.buildozer.info('Downloading kivy...')
status_code = check_output(
('curl', '-L', '--write-out', '%{http_code}', '-o', 'Kivy{}.dmg'.format(py_branch),
'https://kivy.org/downloads/{}/Kivy-{}-osx-python{}.dmg'
.format(current_kivy_vers, current_kivy_vers, py_branch)),
cwd=cwd)
if status_code == "404":
self.buildozer.error(
"Unable to download the Kivy App. Check osx.kivy_version in your buildozer.spec, and verify "
"Kivy servers are accessible. https://kivy.org/downloads/")
check_call(("rm", "Kivy{}.dmg".format(py_branch)), cwd=cwd)
sys.exit(1)
self.buildozer.info('Extracting and installing Kivy...')
check_call(('hdiutil', 'attach', cwd + '/Kivy{}.dmg'.format(py_branch)))
check_call(('cp', '-a', '/Volumes/Kivy/Kivy.app', './Kivy.app'), cwd=cwd)
def ensure_kivyapp(self):
self.buildozer.info('check if Kivy.app exists in local dir')
kivy_app_dir = join(self.buildozer.platform_dir, 'kivy-sdk-packager-master', 'osx')
py_branch = self.buildozer.config.get('app', 'osx.python_version')
if not int(py_branch) in (2, 3):
self.buildozer.error('incompatible python version... aborting')
sys.exit(1)
if exists(join(kivy_app_dir, 'Kivy.app')):
self.buildozer.info('Kivy.app found at ' + kivy_app_dir)
else:
self.download_kivy(kivy_app_dir, py_branch)
def check_requirements(self):
self.ensure_sdk()
self.ensure_kivyapp()
def check_configuration_tokens(self, errors=None):
if errors:
self.buildozer.info('Check target configuration tokens')
self.buildozer.error(
'{0} error(s) found in the buildozer.spec'.format(
len(errors)))
for error in errors:
print(error)
sys.exit(1)
# check
def build_package(self):
self.buildozer.info('Building package')
bc = self.buildozer.config
bcg = bc.get
package_name = bcg('app', 'package.name')
domain = bcg('app', 'package.domain')
title = bcg('app', 'title')
app_deps = open('requirements.txt').read()
icon = bc.getdefault('app', 'icon.filename', '')
version = self.buildozer.get_version()
author = bc.getdefault('app', 'author', '')
self.buildozer.info('Create {}.app'.format(package_name))
cwd = join(self.buildozer.platform_dir, 'kivy-sdk-packager-master', 'osx')
# remove kivy from app_deps
app_deps = [a for a in app_deps.split('\n') if not a.startswith('#') and a not in ['kivy', '']]
cmd = [
'Kivy.app/Contents/Resources/script',
'-m', 'pip', 'install',
]
cmd.extend(app_deps)
check_output(cmd, cwd=cwd)
cmd = [
'python', 'package_app.py', self.buildozer.app_dir,
'--appname={}'.format(package_name),
'--bundlename={}'.format(title),
'--bundleid={}'.format(domain),
'--bundleversion={}'.format(version),
'--displayname={}'.format(title)
]
if icon:
cmd.append('--icon={}'.format(icon))
if author:
cmd.append('--author={}'.format(author))
check_output(cmd, cwd=cwd)
self.buildozer.info('{}.app created.'.format(package_name))
self.buildozer.info('Creating {}.dmg'.format(package_name))
check_output(
('sh', '-x', 'create-osx-dmg.sh', package_name + '.app'),
cwd=cwd)
self.buildozer.info('{}.dmg created'.format(package_name))
self.buildozer.info('moving {}.dmg to bin.'.format(package_name))
binpath = join(
self.buildozer.user_build_dir or
dirname(abspath(self.buildozer.specfilename)), 'bin')
check_output(
('cp', '-a', package_name + '.dmg', binpath),
cwd=cwd)
self.buildozer.info('All Done!')
def compile_platform(self):
pass
def install_platform(self):
# ultimate configuration check.
# some of our configuration cannot be check without platform.
self.check_configuration_tokens()
#
self.buildozer.environ.update({
'PACKAGES_PATH': self.buildozer.global_packages_dir,
})
def get_custom_commands(self):
result = []
for x in dir(self):
if not x.startswith('cmd_'):
continue
if x[4:] in self.buildozer.standard_cmds:
continue
result.append((x[4:], getattr(self, x).__doc__))
return result
def get_available_packages(self):
return ['kivy']
def run_commands(self, args):
if not args:
self.buildozer.error('Missing target command')
self.buildozer.usage()
sys.exit(1)
result = []
last_command = []
for arg in args:
if not arg.startswith('--'):
if last_command:
result.append(last_command)
last_command = []
last_command.append(arg)
else:
if not last_command:
self.buildozer.error('Argument passed without a command')
self.buildozer.usage()
sys.exit(1)
last_command.append(arg)
if last_command:
result.append(last_command)
config_check = False
for item in result:
command, args = item[0], item[1:]
if not hasattr(self, 'cmd_{0}'.format(command)):
self.buildozer.error('Unknown command {0}'.format(command))
sys.exit(1)
func = getattr(self, 'cmd_{0}'.format(command))
need_config_check = not hasattr(func, '__no_config')
if need_config_check and not config_check:
config_check = True
self.check_configuration_tokens()
func(args)
def check_build_prepared(self):
self._build_prepared = False
def cmd_clean(self, *args):
self.buildozer.clean_platform()
def cmd_update(self, *args):
self.platform_update = True
self.buildozer.prepare_for_build()
def cmd_debug(self, *args):
self.buildozer.prepare_for_build()
self.build_mode = 'debug'
self.check_build_prepared()
self.buildozer.build()
def cmd_release(self, *args):
self.buildozer.prepare_for_build()
self.build_mode = 'release'
self.buildozer.build()
def cmd_deploy(self, *args):
self.buildozer.prepare_for_build()
def cmd_run(self, *args):
self.buildozer.prepare_for_build()
def cmd_serve(self, *args):
self.buildozer.cmd_serve()
def get_target(buildozer):
return TargetOSX(buildozer)