Skip to content

Commit fcf2251

Browse files
committed
p4a support for creating aar libraries
* ported @xuhcc's PR 1063 to recent p4a master * added python3.8 support to pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java * openssl recipe switched to version '1.1.1e' because in 1.1.1 there was a print without () * bootstraps dirs are cumulatively copied based on their inheritance, can be arbitrarily deep * add symlink param to copy_files, when set the copy target are symlinked * support for the aar directive in buildozer * create a 'p4a aar' command, so that lots of cluttering conditionals can be moved away from toolchain.apk() * began to remove ant support (@inclement allowed me to do so) * renamed library bootstrap to service_library, because that describe it better * test setup setup_testlib_service.py * renamed symlink_java_src to symlink_bootstrap_files * None is not allowed as bootstrap parameter * switched tests to use the sdl2 bootstrap
1 parent f5dbf21 commit fcf2251

File tree

21 files changed

+557
-196
lines changed

21 files changed

+557
-196
lines changed

pythonforandroid/bdistapk.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ def argv_contains(t):
1414
return False
1515

1616

17-
class BdistAPK(Command):
18-
description = 'Create an APK with python-for-android'
17+
class Bdist(Command):
1918

2019
user_options = []
20+
package_type = None
2121

2222
def initialize_options(self):
2323
for option in self.user_options:
2424
setattr(self, option[0].strip('=').replace('-', '_'), None)
2525

26-
option_dict = self.distribution.get_option_dict('apk')
26+
option_dict = self.distribution.get_option_dict(self.package_type)
2727

2828
# This is a hack, we probably aren't supposed to loop through
2929
# the option_dict so early because distutils does exactly the
@@ -34,7 +34,7 @@ def initialize_options(self):
3434

3535
def finalize_options(self):
3636

37-
setup_options = self.distribution.get_option_dict('apk')
37+
setup_options = self.distribution.get_option_dict(self.package_type)
3838
for (option, (source, value)) in setup_options.items():
3939
if source == 'command line':
4040
continue
@@ -75,7 +75,7 @@ def run(self):
7575
self.prepare_build_dir()
7676

7777
from pythonforandroid.entrypoints import main
78-
sys.argv[1] = 'apk'
78+
sys.argv[1] = self.package_type
7979
main()
8080

8181
def prepare_build_dir(self):
@@ -127,6 +127,22 @@ def prepare_build_dir(self):
127127
)
128128

129129

130+
class BdistAPK(Bdist):
131+
"""
132+
distutil command handler for 'apk'
133+
"""
134+
description = 'Create an APK with python-for-android'
135+
package_type = 'apk'
136+
137+
138+
class BdistAAR(Bdist):
139+
"""
140+
distutil command handler for 'aar'
141+
"""
142+
description = 'Create an AAR with python-for-android'
143+
package_type = 'aar'
144+
145+
130146
def _set_user_options():
131147
# This seems like a silly way to do things, but not sure if there's a
132148
# better way to pass arbitrary options onwards to p4a

pythonforandroid/bootstrap.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from pythonforandroid.recipe import Recipe
1515

1616

17-
def copy_files(src_root, dest_root, override=True):
17+
def copy_files(src_root, dest_root, override=True, symlink=False):
1818
for root, dirnames, filenames in walk(src_root):
1919
for filename in filenames:
2020
subdir = normpath(root.replace(src_root, ""))
@@ -29,7 +29,10 @@ def copy_files(src_root, dest_root, override=True):
2929
if override and os.path.exists(dest_file):
3030
os.unlink(dest_file)
3131
if not os.path.exists(dest_file):
32-
shutil.copy(src_file, dest_file)
32+
if symlink:
33+
os.symlink(src_file, dest_file)
34+
else:
35+
shutil.copy(src_file, dest_file)
3336
else:
3437
os.makedirs(dest_file)
3538

@@ -109,7 +112,7 @@ def check_recipe_choices(self):
109112
and optional dependencies are being used,
110113
and returns a list of these.'''
111114
recipes = []
112-
built_recipes = self.ctx.recipe_build_order
115+
built_recipes = self.ctx.recipe_build_order or []
113116
for recipe in self.recipe_depends:
114117
if isinstance(recipe, (tuple, list)):
115118
for alternative in recipe:
@@ -137,21 +140,27 @@ def name(self):
137140
modname = self.__class__.__module__
138141
return modname.split(".", 2)[-1]
139142

143+
def get_bootstrap_dirs(self):
144+
"""get all bootstrap directories, following the MRO path"""
145+
146+
# get all bootstrap names along the __mro__, cutting off Bootstrap and object
147+
classes = self.__class__.__mro__[:-2]
148+
bootstrap_names = [cls.name for cls in classes] + ['common']
149+
bootstrap_dirs = [
150+
join(self.ctx.root_dir, 'bootstraps', bootstrap_name)
151+
for bootstrap_name in reversed(bootstrap_names)
152+
]
153+
return bootstrap_dirs
154+
140155
def prepare_build_dir(self):
141-
'''Ensure that a build dir exists for the recipe. This same single
142-
dir will be used for building all different archs.'''
156+
"""Ensure that a build dir exists for the recipe. This same single
157+
dir will be used for building all different archs."""
158+
bootstrap_dirs = self.get_bootstrap_dirs()
159+
# now do a cumulative copy of all bootstrap dirs
143160
self.build_dir = self.get_build_dir()
144-
self.common_dir = self.get_common_dir()
145-
copy_files(join(self.bootstrap_dir, 'build'), self.build_dir)
146-
copy_files(join(self.common_dir, 'build'), self.build_dir,
147-
override=False)
148-
if self.ctx.symlink_java_src:
149-
info('Symlinking java src instead of copying')
150-
shprint(sh.rm, '-r', join(self.build_dir, 'src'))
151-
shprint(sh.mkdir, join(self.build_dir, 'src'))
152-
for dirn in listdir(join(self.bootstrap_dir, 'build', 'src')):
153-
shprint(sh.ln, '-s', join(self.bootstrap_dir, 'build', 'src', dirn),
154-
join(self.build_dir, 'src'))
161+
for bootstrap_dir in bootstrap_dirs:
162+
copy_files(join(bootstrap_dir, 'build'), self.build_dir, symlink=self.ctx.symlink_bootstrap_files)
163+
155164
with current_directory(self.build_dir):
156165
with open('project.properties', 'w') as fileh:
157166
fileh.write('target=android-{}'.format(self.ctx.android_api))

pythonforandroid/bootstraps/common/build/build.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def compile_dir(dfn, optimize_python=True):
225225
def make_package(args):
226226
# If no launcher is specified, require a main.py/main.pyo:
227227
if (get_bootstrap_name() != "sdl" or args.launcher is None) and \
228-
get_bootstrap_name() != "webview":
228+
get_bootstrap_name() not in ["webview", "service_library"]:
229229
# (webview doesn't need an entrypoint, apparently)
230230
if args.private is None or (
231231
not exists(join(realpath(args.private), 'main.py')) and
@@ -476,8 +476,9 @@ def make_package(args):
476476
aars=aars,
477477
jars=jars,
478478
android_api=android_api,
479-
build_tools_version=build_tools_version
480-
)
479+
build_tools_version=build_tools_version,
480+
is_library=(get_bootstrap_name() == 'service_library'),
481+
)
481482

482483
# ant build templates
483484
render(

pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ protected static ArrayList<String> getLibraries(File libsDir) {
3939
libsList.add("python3.5m");
4040
libsList.add("python3.6m");
4141
libsList.add("python3.7m");
42+
libsList.add("python3.8m");
4243
libsList.add("main");
4344
return libsList;
4445
}
@@ -59,7 +60,7 @@ public static void loadLibraries(File filesDir, File libsDir) {
5960
// load, and it has failed, give a more
6061
// general error
6162
Log.v(TAG, "Library loading error: " + e.getMessage());
62-
if (lib.startsWith("python3.7") && !foundPython) {
63+
if (lib.startsWith("python3.8") && !foundPython) {
6364
throw new java.lang.RuntimeException("Could not load any libpythonXXX.so");
6465
} else if (lib.startsWith("python")) {
6566
continue;

pythonforandroid/bootstraps/common/build/templates/build.tmpl.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ allprojects {
2222
}
2323
}
2424

25+
{% if is_library %}
26+
apply plugin: 'com.android.library'
27+
{% else %}
2528
apply plugin: 'com.android.application'
29+
{% endif %}
2630

2731
android {
2832
compileSdkVersion {{ android_api }}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
# put files here that you need to un-blacklist
1+
# put files here that you need to un-blacklist
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from pythonforandroid.bootstraps.service_only import ServiceOnlyBootstrap
2+
3+
4+
class ServiceLibraryBootstrap(ServiceOnlyBootstrap):
5+
6+
name = 'service_library'
7+
8+
9+
bootstrap = ServiceLibraryBootstrap()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
#define BOOTSTRAP_NAME_LIBRARY
3+
#define BOOTSTRAP_USES_NO_SDL_HEADERS
4+
5+
const char bootstrap_name[] = "service_library";
6+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.kivy.android;
2+
3+
import android.app.Activity;
4+
5+
// Required by PythonService class
6+
public class PythonActivity extends Activity {
7+
8+
}
9+
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// This string is autogenerated by ChangeAppSettings.sh, do not change
2+
// spaces amount
3+
package org.renpy.android;
4+
5+
import java.io.*;
6+
7+
import android.content.Context;
8+
import android.util.Log;
9+
10+
import java.io.BufferedInputStream;
11+
import java.io.BufferedOutputStream;
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
import java.io.FileInputStream;
15+
import java.io.FileOutputStream;
16+
import java.io.File;
17+
18+
import java.util.zip.GZIPInputStream;
19+
20+
import android.content.res.AssetManager;
21+
22+
import org.kamranzafar.jtar.*;
23+
24+
public class AssetExtract {
25+
26+
private AssetManager mAssetManager = null;
27+
private Context ctx = null;
28+
29+
public AssetExtract(Context context) {
30+
ctx = context;
31+
mAssetManager = ctx.getAssets();
32+
}
33+
34+
public boolean extractTar(String asset, String target) {
35+
36+
byte buf[] = new byte[1024 * 1024];
37+
38+
InputStream assetStream = null;
39+
TarInputStream tis = null;
40+
41+
try {
42+
assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
43+
tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
44+
} catch (IOException e) {
45+
Log.e("python", "opening up extract tar", e);
46+
return false;
47+
}
48+
49+
while (true) {
50+
TarEntry entry = null;
51+
52+
try {
53+
entry = tis.getNextEntry();
54+
} catch ( java.io.IOException e ) {
55+
Log.e("python", "extracting tar", e);
56+
return false;
57+
}
58+
59+
if ( entry == null ) {
60+
break;
61+
}
62+
63+
Log.v("python", "extracting " + entry.getName());
64+
65+
if (entry.isDirectory()) {
66+
67+
try {
68+
new File(target +"/" + entry.getName()).mkdirs();
69+
} catch ( SecurityException e ) { };
70+
71+
continue;
72+
}
73+
74+
OutputStream out = null;
75+
String path = target + "/" + entry.getName();
76+
77+
try {
78+
out = new BufferedOutputStream(new FileOutputStream(path), 8192);
79+
} catch ( FileNotFoundException e ) {
80+
} catch ( SecurityException e ) { };
81+
82+
if ( out == null ) {
83+
Log.e("python", "could not open " + path);
84+
return false;
85+
}
86+
87+
try {
88+
while (true) {
89+
int len = tis.read(buf);
90+
91+
if (len == -1) {
92+
break;
93+
}
94+
95+
out.write(buf, 0, len);
96+
}
97+
98+
out.flush();
99+
out.close();
100+
} catch ( java.io.IOException e ) {
101+
Log.e("python", "extracting zip", e);
102+
return false;
103+
}
104+
}
105+
106+
try {
107+
tis.close();
108+
assetStream.close();
109+
} catch (IOException e) {
110+
// pass
111+
}
112+
113+
return true;
114+
}
115+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="{{ args.package }}"
4+
android:versionCode="{{ args.numeric_version }}"
5+
android:versionName="{{ args.version }}">
6+
7+
<!-- Android 2.3.3 -->
8+
<uses-sdk android:minSdkVersion="{{ args.min_sdk_version }}" android:targetSdkVersion="{{ android_api }}" />
9+
10+
<application>
11+
{% for name in service_names %}
12+
<service android:name="{{ args.package }}.Service{{ name|capitalize }}"
13+
android:process=":service_{{ name }}"
14+
android:exported="true" />
15+
{% endfor %}
16+
</application>
17+
18+
</manifest>

0 commit comments

Comments
 (0)