Skip to content

Commit 3b40ba0

Browse files
author
Jonas Thiem
committed
Implement --blacklist option and include more modules/recipes by default
* Adds `--blacklist` option that prevents recipes/packages from being added even when they are in the `depends` of recipes or otherwise added * Makes the following modules included per default: `android` (via `bootstrap.py` dependency of all bootstraps), `openssl`/`libffi`/`sqlite3` (via `python3` recipe dependency) * Documents `--blacklist` option and that `android` is now included by default * Cleans up the packaging kivy/sdl2 apps part in the Quickstart section
1 parent 50afdf1 commit 3b40ba0

File tree

8 files changed

+151
-54
lines changed

8 files changed

+151
-54
lines changed

doc/source/apis.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ Runtime permissions
1212
With API level >= 21, you will need to request runtime permissions
1313
to access the SD card, the camera, and other things.
1414

15-
This can be done through the `android` module, just add it to
16-
your `--requirements` (as `android`) and then use it in your app like this::
15+
This can be done through the `android` module which is *available per default*
16+
unless you blacklist it. Use it in your app like this::
1717

1818
from android.permissions import request_permission, Permission
1919
request_permission(Permission.WRITE_EXTERNAL_STORAGE)
@@ -34,8 +34,8 @@ longer than necessary (with your app already being loaded) due to a
3434
limitation with the way we check if the app has properly started.
3535
In this case, the splash screen overlaps the app gui for a short time.
3636

37-
To dismiss the loading screen explicitely in your code, add p4a's `android`
38-
module to your `--requirements` and use this::
37+
To dismiss the loading screen explicitely in your code, use the `android`
38+
module::
3939

4040
from android import hide_loading_screen
4141
hide_loading_screen()
@@ -92,14 +92,14 @@ Under SDL2, you can handle the `appropriate events <https://wiki.libsdl.org/SDL_
9292
Advanced Android API use
9393
------------------------
9494

95+
.. _reference-label-for-android-module:
96+
9597
`android` for Android API access
9698
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9799

98100
As mentioned above, the ``android`` Python module provides a simple
99-
wrapper around many native Android APIS, and it can be included by
100-
adding it to your requirements, e.g. :code:`--requirements=kivy,android`.
101-
It is not automatically included by Kivy unless you use the old (Pygame)
102-
bootstrap.
101+
wrapper around many native Android APIS, and it is *included per default*
102+
unless you blacklist it.
103103

104104
The available functionality of this module is not separately documented.
105105
You can read the source `on
@@ -136,7 +136,7 @@ This is obviously *much* less verbose than with Pyjnius!
136136

137137

138138
`Pyjnius` - raw lowlevel API access
139-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
139+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
140140

141141
Pyjnius lets you call the Android API directly from Python Pyjnius is
142142
works by dynamically wrapping Java classes, so you don't have to wait

doc/source/buildoptions.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,25 @@ options (this list may not be exhaustive):
243243
- ``add-source``: Add a source directory to the app's Java code.
244244
- ``--compile-pyo``: Optimise .py files to .pyo.
245245
- ``--resource``: A key=value pair to add in the string.xml resource file.
246+
247+
248+
Blacklist (APK size optimization)
249+
---------------------------------
250+
251+
To optimize the size of the `.apk` file that p4a builds for you,
252+
you can **blacklist** certain core components. Per default, p4a
253+
will add python *with batteries included* as would be expected on
254+
desktop, including openssl, sqlite3 and other components you may
255+
not use.
256+
257+
To blacklist an item, specify the ``--blacklist`` option::
258+
259+
p4a apk ... --blacklist=sqlite3
260+
261+
At the moment, the following core components can be blacklisted
262+
(if you don't want to use them) to decrease APK size:
263+
264+
- ``android`` disables p4a's android module (see :ref:`reference-label-for-android-module`)
265+
- ``libffi`` disables ctypes stdlib module
266+
- ``openssl`` disables ssl stdlib module
267+
- ``sqlite3`` disables sqlite3 stdlib module

doc/source/quickstart.rst

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -149,18 +149,34 @@ You have the possibility to configure on any command the PATH to the SDK, NDK an
149149
Usage
150150
-----
151151

152-
Build a Kivy application
153-
~~~~~~~~~~~~~~~~~~~~~~~~
152+
Build a Kivy or SDL2 application
153+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
154154

155-
To build your application, you need to have a name, version, a package
156-
identifier, and explicitly write the bootstrap you want to use, as
157-
well as the requirements::
155+
To build your application, you need to specify name, version, a package
156+
identifier, the bootstrap you want to use (`sdl2` for kivy or sdl2 apps)
157+
and the requirements::
158+
159+
p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My application" --version 0.1 --bootstrap=sdl2 --requirements=python3,kivy
160+
161+
**Note on `--requirements`: you must add all
162+
libraries/dependencies your app needs to run.**
163+
Example: `--requirements=python3,kivy,vispy`. For an SDL2 app,
164+
`kivy` is not needed, but you need to add any wrappers you might
165+
use (e.g. `pysdl2`).
166+
167+
This `p4a apk ...` command builds a distribution with `python3`,
168+
`kivy`, and everything else you specified in the requirements.
169+
It will be packaged using a SDL2 bootstrap, and produce
170+
an `.apk` file.
158171

159-
p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My application" --version 0.1 --bootstrap=sdl2 --requirements=python2,kivy
172+
*Compatibility notes:*
160173

161-
This will first build a distribution that contains `python2` and `kivy`, and using a SDL2 bootstrap. Python2 is here explicitely written as kivy can work with python2 or python3.
174+
- While python2 is still supported by python-for-android,
175+
it will possibly no longer receive patches by the python creators
176+
themselves in 2020. Migration to Python 3 is recommended!
162177

163-
You can also use ``--bootstrap=pygame``, but this bootstrap is deprecated for use with Kivy and SDL2 is preferred.
178+
- You can also use ``--bootstrap=pygame``, but this bootstrap
179+
is deprecated and not well-tested.
164180

165181
Build a WebView application
166182
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -171,46 +187,34 @@ well as the requirements::
171187

172188
p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My WebView Application" --version 0.1 --bootstrap=webview --requirements=flask --port=5000
173189

190+
**Please note as with kivy/SDL2, you need to specify all your
191+
additional requirements/depenencies.**
192+
174193
You can also replace flask with another web framework.
175194

176195
Replace ``--port=5000`` with the port on which your app will serve a
177196
website. The default for Flask is 5000.
178197

179-
Build an SDL2 based application
180-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
181-
182-
This includes e.g. `PySDL2
183-
<https://pysdl2.readthedocs.io/en/latest/>`__.
184-
185-
To build your application, you need to have a name, version, a package
186-
identifier, and explicitly write the sdl2 bootstrap, as well as the
187-
requirements::
188-
189-
p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My SDL2 application" --version 0.1 --bootstrap=sdl2 --requirements=your_requirements
190-
191-
Add your required modules in place of ``your_requirements``,
192-
e.g. ``--requirements=pysdl2`` or ``--requirements=vispy``.
193-
194198
Other options
195199
~~~~~~~~~~~~~
196200

197201
You can pass other command line arguments to control app behaviours
198202
such as orientation, wakelock and app permissions. See
199203
:ref:`bootstrap_build_options`.
200204

201-
205+
202206

203207
Rebuild everything
204208
~~~~~~~~~~~~~~~~~~
205209

206210
If anything goes wrong and you want to clean the downloads and builds to retry everything, run::
207211

208212
p4a clean_all
209-
213+
210214
If you just want to clean the builds to avoid redownloading dependencies, run::
211215

212216
p4a clean_builds && p4a clean_dists
213-
217+
214218
Getting help
215219
~~~~~~~~~~~~
216220

@@ -269,7 +273,7 @@ You can list the available distributions::
269273
And clean all of them::
270274

271275
p4a clean_dists
272-
276+
273277
Configuration file
274278
~~~~~~~~~~~~~~~~~~
275279

pythonforandroid/bootstrap.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Bootstrap(object):
5252
# All bootstraps should include Python in some way:
5353
recipe_depends = [
5454
("python2", "python2legacy", "python3", "python3crystax"),
55+
'android',
5556
]
5657

5758
can_be_chosen_automatically = True

pythonforandroid/graph.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,13 @@ def conflicts(self):
3939
return False
4040

4141

42-
def get_dependency_tuple_list_for_recipe(recipe, blacklist=[]):
42+
def get_dependency_tuple_list_for_recipe(recipe, blacklist=None):
4343
""" Get the dependencies of a recipe with filtered out blacklist, and
4444
turned into tuples with fix_deplist()
4545
"""
46+
if blacklist is None:
47+
blacklist = set()
48+
assert(type(blacklist) == set)
4649
if recipe.depends is None:
4750
dependencies = []
4851
else:
@@ -51,21 +54,26 @@ def get_dependency_tuple_list_for_recipe(recipe, blacklist=[]):
5154

5255
# Filter out blacklisted items and turn lowercase:
5356
dependencies = [
54-
deptuple for deptuple in dependencies
55-
if not set(deptuple).intersection(set(blacklist))
57+
tuple(set(deptuple) - blacklist)
58+
for deptuple in dependencies
59+
if tuple(set(deptuple) - blacklist)
5660
]
5761
return dependencies
5862

5963

6064
def recursively_collect_orders(
61-
name, ctx, all_inputs, orders=[], blacklist=[]
65+
name, ctx, all_inputs, orders=None, blacklist=None
6266
):
6367
'''For each possible recipe ordering, try to add the new recipe name
6468
to that order. Recursively do the same thing with all the
6569
dependencies of each recipe.
6670
6771
'''
6872
name = name.lower()
73+
if orders is None:
74+
orders = []
75+
if blacklist is None:
76+
blacklist = set()
6977
try:
7078
recipe = Recipe.get_recipe(name, ctx)
7179
dependencies = get_dependency_tuple_list_for_recipe(
@@ -75,7 +83,8 @@ def recursively_collect_orders(
7583
# handle opt_depends: these impose requirements on the build
7684
# order only if already present in the list of recipes to build
7785
dependencies.extend(fix_deplist(
78-
[[d] for d in recipe.get_opt_depends_in_list(all_inputs)]
86+
[[d] for d in recipe.get_opt_depends_in_list(all_inputs)
87+
if d.lower() not in blacklist]
7988
))
8089

8190
if recipe.conflicts is None:
@@ -106,7 +115,9 @@ def recursively_collect_orders(
106115
dependency_new_orders = [new_order]
107116
for dependency in dependency_set:
108117
dependency_new_orders = recursively_collect_orders(
109-
dependency, ctx, all_inputs, dependency_new_orders)
118+
dependency, ctx, all_inputs, dependency_new_orders,
119+
blacklist=blacklist
120+
)
110121

111122
new_orders.extend(dependency_new_orders)
112123

@@ -132,14 +143,16 @@ def find_order(graph):
132143
bset.discard(result)
133144

134145

135-
def obvious_conflict_checker(ctx, name_tuples, blacklist=[]):
146+
def obvious_conflict_checker(ctx, name_tuples, blacklist=None):
136147
""" This is a pre-flight check function that will completely ignore
137148
recipe order or choosing an actual value in any of the multiple
138149
choice tuples/dependencies, and just do a very basic obvious
139150
conflict check.
140151
"""
141152
deps_were_added_by = dict()
142153
deps = set()
154+
if blacklist is None:
155+
blacklist = set()
143156

144157
# Add dependencies for all recipes:
145158
to_be_added = [(name_tuple, None) for name_tuple in name_tuples]
@@ -227,7 +240,7 @@ def obvious_conflict_checker(ctx, name_tuples, blacklist=[]):
227240
return None
228241

229242

230-
def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=[]):
243+
def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None):
231244
# Get set of recipe/dependency names, clean up and add bootstrap deps:
232245
names = set(names)
233246
if bs is not None and bs.recipe_depends:
@@ -236,7 +249,9 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=[]):
236249
([name] if not isinstance(name, (list, tuple)) else name)
237250
for name in names
238251
])
239-
blacklist = [bitem.lower() for bitem in blacklist]
252+
if blacklist is None:
253+
blacklist = set()
254+
blacklist = {bitem.lower() for bitem in blacklist}
240255

241256
# Remove all values that are in the blacklist:
242257
names_before_blacklist = list(names)
@@ -261,7 +276,9 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=[]):
261276
new_possible_orders = [RecipeOrder(ctx)]
262277
for name in name_set:
263278
new_possible_orders = recursively_collect_orders(
264-
name, ctx, name_set, orders=new_possible_orders)
279+
name, ctx, name_set, orders=new_possible_orders,
280+
blacklist=blacklist
281+
)
265282
possible_orders.extend(new_possible_orders)
266283

267284
# turn each order graph into a linear list if possible
@@ -305,7 +322,8 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=[]):
305322
"Could not find any compatible bootstrap!"
306323
)
307324
recipes, python_modules, bs = get_recipe_order_and_bootstrap(
308-
ctx, chosen_order, bs=bs)
325+
ctx, chosen_order, bs=bs, blacklist=blacklist
326+
)
309327
else:
310328
# check if each requirement has a recipe
311329
recipes = []

pythonforandroid/recipes/python3/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Python3Recipe(GuestPythonRecipe):
2323

2424
patches = ["patches/fix-ctypes-util-find-library.patch"]
2525

26-
depends = ['hostpython3']
26+
depends = ['hostpython3', 'sqlite3', 'openssl', 'libffi']
2727
conflicts = ['python3crystax', 'python2', 'python2legacy']
2828

2929
configure_args = (

pythonforandroid/toolchain.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,24 @@ def build_dist_from_args(ctx, dist, args):
171171
"""Parses out any bootstrap related arguments, and uses them to build
172172
a dist."""
173173
bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)
174-
build_order, python_modules, bs \
175-
= get_recipe_order_and_bootstrap(ctx, dist.recipes, bs)
174+
blacklist = getattr(args, "blacklist", "").split(",")
175+
if len(blacklist) == 1 and blacklist[0] == "":
176+
blacklist = []
177+
build_order, python_modules, bs = (
178+
get_recipe_order_and_bootstrap(
179+
ctx, dist.recipes, bs,
180+
blacklist=blacklist
181+
))
176182
ctx.recipe_build_order = build_order
177183
ctx.python_modules = python_modules
178184

179185
info('The selected bootstrap is {}'.format(bs.name))
180186
info_main('# Creating dist with {} bootstrap'.format(bs.name))
181187
bs.distribution = dist
182-
info_notify('Dist will have name {} and recipes ({})'.format(
188+
info_notify('Dist will have name {} and requirements ({})'.format(
183189
dist.name, ', '.join(dist.recipes)))
190+
info('Dist contains the following requirements as recipes: {}'.format(
191+
ctx.recipe_build_order))
184192
info('Dist will also contain modules ({}) installed from pip'.format(
185193
', '.join(ctx.python_modules)))
186194

@@ -301,6 +309,13 @@ def __init__(self):
301309
'Python modules'),
302310
default='')
303311

312+
generic_parser.add_argument(
313+
'--blacklist',
314+
help=('Blacklist an internal recipe from use. Allows '
315+
'disabling Python 3 core modules to save size'),
316+
dest="blacklist",
317+
default='')
318+
304319
generic_parser.add_argument(
305320
'--bootstrap',
306321
help='The bootstrap to build with. Leave unset to choose '
@@ -448,7 +463,6 @@ def add_parser(subparsers, *args, **kwargs):
448463
help='Symlink the dist instead of copying')
449464

450465
parser_apk = add_parser(
451-
452466
subparsers,
453467
'apk', help='Build an APK',
454468
parents=[generic_parser])
@@ -527,9 +541,11 @@ def add_parser(subparsers, *args, **kwargs):
527541
if args.debug:
528542
logger.setLevel(logging.DEBUG)
529543

530-
# strip version from requirements, and put them in environ
544+
# Process requirements and put version in environ
531545
if hasattr(args, 'requirements'):
532546
requirements = []
547+
548+
# Parse --requirements argument list:
533549
for requirement in split_argument_list(args.requirements):
534550
if "==" in requirement:
535551
requirement, version = requirement.split(u"==", 1)

0 commit comments

Comments
 (0)