Skip to content

Commit e44804d

Browse files
committed
bootstrap: move all the logic for bootstraping python into python as soon as possible.
1 parent 7be5981 commit e44804d

File tree

2 files changed

+104
-65
lines changed

2 files changed

+104
-65
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
Python minimal bootstrap
3+
========================
4+
5+
This is the file extracted from the APK assets, and directly executed.
6+
The bootstrap handle the execution of the main.py application file, and
7+
correctly set the sys.path, logs, site, etc.
8+
9+
The minimal bootstrap prefer to put all the python into the assets directory,
10+
uncompressed (aapt might compress). So we need to do few tricks do be able to
11+
start without uncompressing all the data.
12+
"""
13+
14+
import sys
15+
import androidembed
16+
import posix
17+
from zipimport import zipimporter
18+
19+
20+
def extract_asset(apk, asset_fn, dest_fn):
21+
# This function can be called only after step 2
22+
from os.path import exists, dirname
23+
from os import makedirs
24+
25+
dest_dir = dirname(dest_fn)
26+
if not exists(dest_dir):
27+
makedirs(dest_dir)
28+
with open(dest_fn, "wb") as fd:
29+
data = apk.get_data("assets/" + asset_fn)
30+
fd.write(data)
31+
32+
33+
#
34+
# Step 1: redirect all the stdout/stderr to Android log.
35+
#
36+
37+
class LogFile(object):
38+
39+
def __init__(self):
40+
self._buf = ''
41+
42+
def write(self, message):
43+
message = self._buf + message
44+
lines = message.split("\n")
45+
for line in lines[:-1]:
46+
androidembed.log(line)
47+
self._buf = lines[-1]
48+
49+
def flush(self):
50+
return
51+
52+
sys.stdout = sys.stderr = LogFile()
53+
54+
55+
#
56+
# Step 2: rewrite the sys.path to use the APK assets as source
57+
#
58+
59+
android_apk_fn = posix.environ["ANDROID_APK_FN"]
60+
pylib_path = "{}/assets/lib/python2.7/".format(android_apk_fn)
61+
sys.path[:] = [p.format(android_apk_fn) for p in [
62+
"{}/assets",
63+
"{}/assets/lib/python2.7/",
64+
"{}/assets/lib/python2.7/site-packages/"]]
65+
66+
67+
#
68+
# Step 3: Python needs the Makefile and pyconfig.h to be able to import site
69+
#
70+
71+
data_path = posix.environ["ANDROID_INTERNAL_DATA_PATH"]
72+
makefile_fn = "{}/python2.7/lib/config/Makefile".format(data_path)
73+
pyconfig_fn = "{}/python2.7/include/pyconfig.h".format(data_path)
74+
75+
# extract both makefile and config
76+
apk = zipimporter(posix.environ["ANDROID_APK_FN"])
77+
extract_asset(apk, "lib/python2.7/config/Makefile", makefile_fn)
78+
extract_asset(apk, "include/python2.7/pyconfig.h", pyconfig_fn)
79+
80+
# ensure sysconfig will find our file
81+
import sysconfig
82+
sysconfig._get_makefile_filename = lambda: makefile_fn
83+
sysconfig.get_config_h_filename = lambda: pyconfig_fn
84+
85+
86+
#
87+
# Step 4: bootstrap the application !
88+
#
89+
90+
import runpy
91+
runpy._run_module_as_main("main", False)

bootstrap/minimal/jni/main.c

Lines changed: 13 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -94,84 +94,32 @@ void android_main(struct android_app* state) {
9494
LOGI("Internal data path is: %s", state->activity->internalDataPath);
9595
LOGI("External data path is: %s", state->activity->externalDataPath);
9696

97-
// inject our bootstrap code to redirect python stdin/stdout
98-
PyRun_SimpleString(
99-
"import sys, androidembed\n" \
100-
"class LogFile(object):\n" \
101-
" def __init__(self):\n" \
102-
" self.buffer = ''\n" \
103-
" def write(self, s):\n" \
104-
" s = self.buffer + s\n" \
105-
" lines = s.split(\"\\n\")\n" \
106-
" for l in lines[:-1]:\n" \
107-
" androidembed.log(l)\n" \
108-
" self.buffer = lines[-1]\n" \
109-
" def flush(self):\n" \
110-
" return\n" \
111-
"sys.stdout = sys.stderr = LogFile()\n");
112-
113-
// let python knows where the python2.7 library is within the APK
114-
PyRun_SimpleString(
115-
"import sys, posix;" \
116-
"lib_path = '{}/assets/lib/python2.7/'.format(" \
117-
" posix.environ['ANDROID_APK_FN'])\n" \
118-
"sys.path[:] = [lib_path, '{}/site-packages'.format(lib_path)]\n" \
119-
"import os; from os.path import exists, join\n" \
120-
"config_path = join(posix.environ['ANDROID_INTERNAL_DATA_PATH'], 'python2.7', 'config')\n" \
121-
"if not exists(config_path): os.makedirs(config_path)\n" \
122-
"import sysconfig\n" \
123-
"sysconfig._get_makefile_filename = lambda: '{}/Makefile'.format(config_path)\n" \
124-
"sysconfig.get_config_h_filename = lambda: '{}/pyconfig.h'.format(config_path)\n" \
125-
);
126-
12797
// extract the Makefile, needed for sysconfig
12898
AAssetManager *am = state->activity->assetManager;
129-
char dest_fn[512];
130-
131-
snprintf(dest_fn, 512, "%s/python2.7/config/Makefile", state->activity->internalDataPath);
132-
if (asset_extract(am, "lib/python2.7/config/Makefile", dest_fn) < 0)
133-
return;
134-
135-
snprintf(dest_fn, 512, "%s/python2.7/config/pyconfig.h", state->activity->internalDataPath);
136-
if (asset_extract(am, "include/python2.7/pyconfig.h", dest_fn) < 0)
137-
return;
138-
139-
// test import site
140-
PyRun_SimpleString(
141-
"import site; print site.getsitepackages()\n"\
142-
"print 'Android path', sys.path\n" \
143-
"print 'Android bootstrap done. __name__ is', __name__");
144-
145-
/* run it !
146-
*/
147-
LOGI("Extract main.py from assets");
148-
char main_fn[512];
149-
snprintf(main_fn, 512, "%s/main.pyo", state->activity->internalDataPath);
150-
if (asset_extract(am, "main.pyo", main_fn) < 0)
99+
char bootstrap_fn[512];
100+
snprintf(bootstrap_fn, 512, "%s/_bootstrap.py", state->activity->internalDataPath);
101+
if (asset_extract(am, "_bootstrap.py", bootstrap_fn) < 0) {
102+
LOGW("Unable to extract _bootstrap.py");
151103
return;
104+
}
152105

153-
/* run python !
154-
*/
155-
LOGI("Run main.py >>>");
156-
FILE *fhd = fopen(main_fn, "rb");
106+
// run the python bootstrap
107+
LOGI("Run _bootstrap.py >>>");
108+
FILE *fhd = fopen(bootstrap_fn, "rb");
157109
if (fhd == NULL) {
158-
LOGW("Cannot open main.pyo (errno=%d:%s)", errno, strerror(errno));
110+
LOGW("Cannot open _bootstrap.py (errno=%d:%s)", errno, strerror(errno));
159111
return;
160112
}
161-
int ret = PyRun_SimpleFile(fhd, main_fn);
113+
int ret = PyRun_SimpleFile(fhd, bootstrap_fn);
162114
fclose(fhd);
163-
LOGI("Run main.py (ret=%d) <<<", ret);
115+
LOGI("Run _bootstrap.py (ret=%d) <<<", ret);
164116

165117
if (PyErr_Occurred() != NULL) {
166-
LOGW("An error occured.");
167-
PyErr_Print(); /* This exits with the right code if SystemExit. */
118+
PyErr_Print();
168119
if (Py_FlushLine())
169120
PyErr_Clear();
170121
}
171122

172-
/* close everything
173-
*/
174123
Py_Finalize();
175-
176-
LOGW("Python for android ended.");
124+
LOGI("Python for android ended.");
177125
}

0 commit comments

Comments
 (0)