Skip to content

Commit fe5ff4e

Browse files
authored
Merge pull request kivy#764 from inclement/apk_distutils_command
Add distutils command for 'python setup.py apk'
2 parents 2bbca89 + 3972591 commit fe5ff4e

File tree

9 files changed

+412
-17
lines changed

9 files changed

+412
-17
lines changed

doc/source/distutils.rst

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
2+
distutils/setuptools integration
3+
================================
4+
5+
Instead of running p4a via the command line, you can integrate with
6+
distutils and setup.py.
7+
8+
The base command is::
9+
10+
python setup.py apk
11+
12+
The files included in the APK will be all those specified in the
13+
``package_data`` argument to setup. For instance, the following
14+
example will include all .py and .png files in the ``testapp``
15+
folder::
16+
17+
from distutils.core import setup
18+
from setup
19+
20+
setup(
21+
name='testapp_setup',
22+
version='1.1',
23+
description='p4a setup.py example',
24+
author='Your Name',
25+
author_email='[email protected]',
26+
packages=find_packages(),
27+
options=options,
28+
package_data={'testapp': ['*.py', '*.png']}
29+
)
30+
31+
The app name and version will also be read automatically from the
32+
setup.py.
33+
34+
The Android package name uses ``org.test.lowercaseappname``
35+
if not set explicitly.
36+
37+
The ``--private`` argument is set automatically using the
38+
package_data, you should *not* set this manually.
39+
40+
The target architecture defaults to ``--armeabi``.
41+
42+
All of these automatic arguments can be overridden by passing them manually on the command line, e.g.::
43+
44+
python setup.py apk --name="Testapp Setup" --version=2.5
45+
46+
Adding p4a arguments in setup.py
47+
--------------------------------
48+
49+
Instead of providing extra arguments on the command line, you can
50+
store them in setup.py by passing the ``options`` parameter to
51+
:code:`setup`. For instance::
52+
53+
from distutils.core import setup
54+
from setuptools import find_packages
55+
56+
options = {'apk': {'debug': None, # use None for arguments that don't pass a value
57+
'requirements': 'sdl2,pyjnius,kivy,python2',
58+
'android-api': 19,
59+
'ndk-dir': '/path/to/ndk',
60+
'dist-name': 'bdisttest',
61+
}}
62+
63+
packages = find_packages()
64+
print('packages are', packages)
65+
66+
setup(
67+
name='testapp_setup',
68+
version='1.1',
69+
description='p4a setup.py example',
70+
author='Your Name',
71+
author_email='[email protected]',
72+
packages=find_packages(),
73+
options=options,
74+
package_data={'testapp': ['*.py', '*.png']}
75+
)
76+
77+
These options will be automatically included when you run ``python
78+
setup.py apk``. Any options passed on the command line will override
79+
these values.
80+
81+
Adding p4a arguments in setup.cfg
82+
---------------------------------
83+
84+
You can also provide p4a arguments in the setup.cfg file, as normal
85+
for distutils. The syntax is::
86+
87+
[apk]
88+
89+
argument=value
90+
91+
requirements=sdl2,kivy

doc/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Contents
2929
quickstart
3030
buildoptions
3131
commands
32+
distutils
3233
recipes
3334
bootstraps
3435
services

pythonforandroid/bdist_apk.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

pythonforandroid/bdistapk.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
from __future__ import print_function
2+
from setuptools import Command
3+
from pythonforandroid import toolchain
4+
5+
import sys
6+
from os.path import realpath, join, exists, dirname, curdir, basename, split
7+
from os import makedirs
8+
from glob import glob
9+
from shutil import rmtree, copyfile
10+
11+
12+
def argv_contains(t):
13+
for arg in sys.argv:
14+
if arg.startswith(t):
15+
return True
16+
return False
17+
18+
19+
class BdistAPK(Command):
20+
description = 'Create an APK with python-for-android'
21+
22+
user_options = []
23+
24+
def initialize_options(self):
25+
for option in self.user_options:
26+
setattr(self, option[0].strip('=').replace('-', '_'), None)
27+
28+
option_dict = self.distribution.get_option_dict('apk')
29+
30+
# This is a hack, we probably aren't supposed to loop through
31+
# the option_dict so early because distutils does exactly the
32+
# same thing later to check that we support the
33+
# options. However, it works...
34+
for (option, (source, value)) in option_dict.items():
35+
setattr(self, option, str(value))
36+
37+
38+
def finalize_options(self):
39+
40+
setup_options = self.distribution.get_option_dict('apk')
41+
for (option, (source, value)) in setup_options.items():
42+
if source == 'command line':
43+
continue
44+
if not argv_contains('--' + option):
45+
if value in (None, 'None'):
46+
sys.argv.append('--{}'.format(option))
47+
else:
48+
sys.argv.append('--{}={}'.format(option, value))
49+
50+
# Inject some argv options from setup.py if the user did not
51+
# provide them
52+
if not argv_contains('--name'):
53+
name = self.distribution.get_name()
54+
sys.argv.append('--name={}'.format(name))
55+
self.name = name
56+
57+
if not argv_contains('--package'):
58+
package = 'org.test.{}'.format(self.name.lower().replace(' ', ''))
59+
print('WARNING: You did not supply an Android package '
60+
'identifier, trying {} instead.'.format(package))
61+
print(' This may fail if this is not a valid identifier')
62+
sys.argv.append('--package={}'.format(package))
63+
64+
if not argv_contains('--version'):
65+
version = self.distribution.get_version()
66+
sys.argv.append('--version={}'.format(version))
67+
68+
if not argv_contains('--arch'):
69+
arch = 'armeabi'
70+
self.arch = arch
71+
sys.argv.append('--arch={}'.format(arch))
72+
73+
def run(self):
74+
75+
self.prepare_build_dir()
76+
77+
from pythonforandroid.toolchain import main
78+
sys.argv[1] = 'apk'
79+
main()
80+
81+
def prepare_build_dir(self):
82+
83+
if argv_contains('--private'):
84+
print('WARNING: Received --private argument when this would '
85+
'normally be generated automatically.')
86+
print(' This is probably bad unless you meant to do '
87+
'that.')
88+
89+
bdist_dir = 'build/bdist.android-{}'.format(self.arch)
90+
rmtree(bdist_dir)
91+
makedirs(bdist_dir)
92+
93+
globs = []
94+
for directory, patterns in self.distribution.package_data.items():
95+
for pattern in patterns:
96+
globs.append(join(directory, pattern))
97+
98+
filens = []
99+
for pattern in globs:
100+
filens.extend(glob(pattern))
101+
102+
main_py_dirs = []
103+
for filen in filens:
104+
new_dir = join(bdist_dir, dirname(filen))
105+
if not exists(new_dir):
106+
makedirs(new_dir)
107+
print('Including {}'.format(filen))
108+
copyfile(filen, join(bdist_dir, filen))
109+
if basename(filen) in ('main.py', 'main.pyo'):
110+
main_py_dirs.append(filen)
111+
112+
# This feels ridiculous, but how else to define the main.py dir?
113+
# Maybe should just fail?
114+
if len(main_py_dirs) == 0:
115+
print('ERROR: Could not find main.py, so no app build dir defined')
116+
print('You should name your app entry point main.py')
117+
exit(1)
118+
if len(main_py_dirs) > 1:
119+
print('WARNING: Multiple main.py dirs found, using the shortest path')
120+
main_py_dirs = sorted(main_py_dirs, key=lambda j: len(split(j)))
121+
122+
sys.argv.append('--private={}'.format(join(realpath(curdir), bdist_dir,
123+
dirname(main_py_dirs[0]))))
124+
125+
126+
def _set_user_options():
127+
# This seems like a silly way to do things, but not sure if there's a
128+
# better way to pass arbitrary options onwards to p4a
129+
user_options = [('requirements=', None, None),]
130+
for i, arg in enumerate(sys.argv):
131+
if arg.startswith('--'):
132+
if ('=' in arg or
133+
(i < (len(sys.argv) - 1) and not sys.argv[i+1].startswith('-'))):
134+
user_options.append((arg[2:].split('=')[0] + '=', None, None))
135+
else:
136+
user_options.append((arg[2:], None, None))
137+
138+
BdistAPK.user_options = user_options
139+
140+
_set_user_options()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def recursively_include(results, directory, patterns):
5757
'p4a = pythonforandroid.toolchain:main',
5858
],
5959
'distutils.commands': [
60-
'bdist_apk = pythonforandroid.bdist_apk:BdistAPK',
60+
'apk = pythonforandroid.bdistapk:BdistAPK',
6161
],
6262
},
6363
classifiers = [

testapps/testapp_setup/setup.cfg

Whitespace-only changes.

testapps/testapp_setup/setup.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
from distutils.core import setup
3+
from setuptools import find_packages
4+
5+
options = {'apk': {'debug': None,
6+
'requirements': 'sdl2,pyjnius,kivy,python2',
7+
'android-api': 19,
8+
'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.1',
9+
'dist-name': 'bdisttest',
10+
'ndk-version': '10.3.1',
11+
}}
12+
13+
package_data = {'': ['*.py',
14+
'*.png']
15+
}
16+
17+
packages = find_packages()
18+
print('packages are', packages)
19+
20+
setup(
21+
name='testapp_setup',
22+
version='1.1',
23+
description='p4a setup.py test',
24+
author='Alexander Taylor',
25+
author_email='[email protected]',
26+
packages=find_packages(),
27+
options=options,
28+
package_data={'testapp': ['*.py', '*.png']}
29+
)
187 KB
Loading

0 commit comments

Comments
 (0)