99#include <stdlib.h>
1010#include <unistd.h>
1111#include <dirent.h>
12+ #include <time.h>
13+ #include <dlfcn.h>
14+ #include <libgen.h>
1215#include <jni.h>
1316#include <sys/stat.h>
1417#include <sys/types.h>
3033
3134#define ENTRYPOINT_MAXLEN 128
3235#define LOG (n , x ) __android_log_write(ANDROID_LOG_INFO, (n), (x))
33- #define LOGP (x ) LOG("python", (x))
3436#define P4A_MIN_VER 11
37+ static void LOGP (const char * fmt , ...) {
38+ va_list args ;
39+ va_start (args , fmt );
40+ __android_log_vprint (ANDROID_LOG_INFO , "python" , fmt , args );
41+ va_end (args );
42+ }
3543
3644static PyObject * androidembed_log (PyObject * self , PyObject * args ) {
3745 char * logstr = NULL ;
@@ -69,14 +77,125 @@ int dir_exists(char *filename) {
6977}
7078
7179int file_exists (const char * filename ) {
72- FILE * file ;
73- if ((file = fopen (filename , "r" ))) {
74- fclose (file );
75- return 1 ;
76- }
77- return 0 ;
80+ return access (filename , F_OK ) == 0 ;
81+ }
82+
83+ static void get_dirname (const char * path , char * dir , size_t size ) {
84+ strncpy (dir , path , size - 1 );
85+ dir [size - 1 ] = '\0' ;
86+ char * last_slash = strrchr (dir , '/' );
87+ if (last_slash ) * last_slash = '\0' ;
88+ else dir [0 ] = '\0' ;
89+ }
90+
91+ // strip "lib" prefix and "bin.so" suffix
92+ static void get_exe_name (const char * filename , char * out , size_t size ) {
93+ size_t len = strlen (filename );
94+ if (len < 7 ) { // too short to be valid
95+ strncpy (out , filename , size - 1 );
96+ out [size - 1 ] = '\0' ;
97+ return ;
98+ }
99+
100+ const char * start = filename ;
101+ if (strncmp (filename , "lib" , 3 ) == 0 ) start += 3 ;
102+ size_t start_len = strlen (start );
103+
104+ if (start_len > 6 ) {
105+ size_t copy_len = start_len - 6 ; // remove "bin.so"
106+ if (copy_len >= size ) copy_len = size - 1 ;
107+ strncpy (out , start , copy_len );
108+ out [copy_len ] = '\0' ;
109+ } else {
110+ strncpy (out , start , size - 1 );
111+ out [size - 1 ] = '\0' ;
112+ }
113+ }
114+
115+ char * setup_symlinks () {
116+ Dl_info info ;
117+ char lib_path [512 ];
118+ char * interpreter = NULL ;
119+
120+ if (!(dladdr ((void * )setup_symlinks , & info ) && info .dli_fname )) {
121+ LOGP ("symlinking failed: failed to get libdir" );
122+ return interpreter ;
123+ }
124+
125+ strncpy (lib_path , info .dli_fname , sizeof (lib_path ) - 1 );
126+ lib_path [sizeof (lib_path ) - 1 ] = '\0' ;
127+
128+ char native_lib_dir [512 ];
129+ get_dirname (lib_path , native_lib_dir , sizeof (native_lib_dir ));
130+ if (native_lib_dir [0 ] == '\0' ) {
131+ LOGP ("symlinking failed: could not determine lib directory" );
132+ return interpreter ;
133+ }
134+
135+ const char * files_dir_env = getenv ("ANDROID_APP_PATH" );
136+ char bin_dir [512 ];
137+
138+ snprintf (bin_dir , sizeof (bin_dir ), "%s/.bin" , files_dir_env );
139+ if (mkdir (bin_dir , 0755 ) != 0 && errno != EEXIST ) {
140+ LOGP ("Failed to create .bin directory" );
141+ return interpreter ;
142+ }
143+
144+ DIR * dir = opendir (native_lib_dir );
145+ if (!dir ) {
146+ LOGP ("Failed to open native lib dir" );
147+ return interpreter ;
148+ }
149+
150+ struct dirent * entry ;
151+ while ((entry = readdir (dir )) != NULL ) {
152+ const char * name = entry -> d_name ;
153+ size_t len = strlen (name );
154+
155+ if (len < 7 ) continue ;
156+ if (strcmp (name + len - 6 , "bin.so" ) != 0 ) continue ; // only bin.so at end
157+
158+ // get cleaned executable name
159+ char exe_name [128 ];
160+ get_exe_name (name , exe_name , sizeof (exe_name ));
161+
162+ char src [512 ], dst [512 ];
163+ snprintf (src , sizeof (src ), "%s/%s" , native_lib_dir , name );
164+ snprintf (dst , sizeof (dst ), "%s/%s" , bin_dir , exe_name );
165+
166+ // interpreter found?
167+ if (strcmp (exe_name , "python" ) == 0 ) {
168+ interpreter = strdup (dst );
169+ }
170+
171+ struct stat st ;
172+ if (lstat (dst , & st ) == 0 ) continue ; // already exists
173+ if (symlink (src , dst ) == 0 ) {
174+ LOGP ("symlink: %s -> %s" , name , exe_name );
175+ } else {
176+ LOGP ("Symlink failed" );
177+ }
178+ }
179+
180+ closedir (dir );
181+
182+ // append bin_dir to PATH
183+ const char * old_path = getenv ("PATH" );
184+ char new_path [1024 ];
185+ if (old_path && strlen (old_path ) > 0 ) {
186+ snprintf (new_path , sizeof (new_path ), "%s:%s" , old_path , bin_dir );
187+ } else {
188+ snprintf (new_path , sizeof (new_path ), "%s" , bin_dir );
189+ }
190+ setenv ("PATH" , new_path , 1 );
191+
192+ // set lib path
193+ setenv ("LD_LIBRARY_PATH" , native_lib_dir , 1 );
194+
195+ return interpreter ;
78196}
79197
198+
80199/* int main(int argc, char **argv) { */
81200int main (int argc , char * argv []) {
82201
@@ -87,7 +206,10 @@ int main(int argc, char *argv[]) {
87206 int ret = 0 ;
88207 FILE * fd ;
89208
90- LOGP ("Initializing Python for Android" );
209+ LOGP ("======== Initializing P4A ==========" );
210+
211+ struct timespec start , end ;
212+ clock_gettime (CLOCK_MONOTONIC , & start );
91213
92214 // Set a couple of built-in environment vars:
93215 setenv ("P4A_BOOTSTRAP" , bootstrap_name , 1 ); // env var to identify p4a to applications
@@ -147,10 +269,11 @@ int main(int argc, char *argv[]) {
147269 LOGP ("Warning: no p4a_env_vars.txt found / failed to open!" );
148270 }
149271
150- LOGP ("Changing directory to the one provided by ANDROID_ARGUMENT" );
151- LOGP (env_argument );
272+ LOGP ("Changing directory to '%s'" , env_argument );
152273 chdir (env_argument );
153274
275+ char * interpreter = setup_symlinks ();
276+
154277#if PY_MAJOR_VERSION < 3
155278 Py_NoSiteFlag = 1 ;
156279#endif
@@ -257,12 +380,20 @@ int main(int argc, char *argv[]) {
257380 "sys.path.append('%s/site-packages')" ,
258381 python_bundle_dir );
259382
260- PyRun_SimpleString ("import sys\n"
261- "sys.argv = ['notaninterpreterreally']\n"
262- "from os.path import realpath, join, dirname" );
383+ PyRun_SimpleString ("import sys, os\n"
384+ "from os.path import realpath, join, dirname" );
385+
386+ char buf_exec [512 ];
387+ char buf_argv [512 ];
388+ snprintf (buf_exec , sizeof (buf_exec ), "sys.executable = '%s'\n" , interpreter );
389+ snprintf (buf_argv , sizeof (buf_argv ), "sys.argv = ['%s']\n" , interpreter );
390+ PyRun_SimpleString (buf_exec );
391+ PyRun_SimpleString (buf_argv );
392+
263393 PyRun_SimpleString (add_site_packages_dir );
264394 /* "sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))") */
265395 PyRun_SimpleString ("sys.path = ['.'] + sys.path" );
396+ PyRun_SimpleString ("os.environ['PYTHONPATH'] = ':'.join(sys.path)" );
266397 }
267398
268399 PyRun_SimpleString (
@@ -280,23 +411,12 @@ int main(int argc, char *argv[]) {
280411 " androidembed.log(l.replace('\\x00', ''))\n"
281412 " self.__buffer = lines[-1]\n"
282413 "sys.stdout = sys.stderr = LogFile()\n"
283- "print('Android path', sys.path)\n"
284- "# import os\n"
285- "# print('os.environ is', os.environ)\n"
286414 "print('Android kivy bootstrap done. __name__ is', __name__)" );
287415
288416#if PY_MAJOR_VERSION < 3
289417 PyRun_SimpleString ("import site; print site.getsitepackages()\n" );
290418#endif
291419
292- LOGP ("AND: Ran string" );
293-
294- /* run it !
295- */
296- LOGP ("Run user program, change dir and execute entrypoint" );
297-
298- /* Get the entrypoint, search the .pyc then .py
299- */
300420 char * dot = strrchr (env_entrypoint , '.' );
301421 char * ext = ".pyc" ;
302422 if (dot <= 0 ) {
@@ -346,6 +466,14 @@ int main(int argc, char *argv[]) {
346466 return -1 ;
347467 }
348468
469+ clock_gettime (CLOCK_MONOTONIC , & end );
470+ double elapsed =
471+ (end .tv_sec - start .tv_sec ) +
472+ (end .tv_nsec - start .tv_nsec ) / 1e9 ;
473+
474+
475+ LOGP ("======= Initialized in %.3fs =======" , elapsed );
476+
349477 /* run python !
350478 */
351479 ret = PyRun_SimpleFile (fd , entrypoint );
@@ -361,34 +489,16 @@ int main(int argc, char *argv[]) {
361489
362490 LOGP ("Python for android ended." );
363491
364- /* Shut down: since regular shutdown causes issues sometimes
365- (seems to be an incomplete shutdown breaking next launch)
366- we'll use sys.exit(ret) to shutdown, since that one works.
367-
368- Reference discussion:
369-
370- https://github.com/kivy/kivy/pull/6107#issue-246120816
371- */
372- char terminatecmd [256 ];
373- snprintf (
374- terminatecmd , sizeof (terminatecmd ),
375- "import sys; sys.exit(%d)\n" , ret
376- );
377- PyRun_SimpleString (terminatecmd );
378-
379- /* This should never actually be reached, but we'll leave the clean-up
380- * here just to be safe.
381- */
382492#if PY_MAJOR_VERSION < 3
383493 Py_Finalize ();
384494 LOGP ("Unexpectedly reached Py_FinalizeEx(), but was successful." );
385495#else
386- if (Py_FinalizeEx () != 0 ) // properly check success on Python 3
496+ if (Py_FinalizeEx () != 0 ) { // properly check success on Python 3
387497 LOGP ("Unexpectedly reached Py_FinalizeEx(), and got error!" );
388- else
389- LOGP ("Unexpectedly reached Py_FinalizeEx(), but was successful." );
498+ }
390499#endif
391500
501+ exit (ret );
392502 return ret ;
393503}
394504
0 commit comments