Repository: kivy/python-for-android

Issue:
Build crashes if NDK is installed system-wide without write permissions
### Versions

shouldn't be relevant

### Description

There is an issue (I thought it was a regression introduced by #1618 at first, but I think this was broken before) where in an environment where the NDK is installed system-wide, the build will crash because then files cannot simply be changed:

```
[INFO]:    Building lxml for armeabi-v7a
[INFO]:    lxml apparently isn't already in site-packages
[DEBUG]:   -> running ln -sf /ndk/platforms/android-21/arch-arm/usr/lib/libc.so /ndk/platforms/android-21/arch-arm/usr/lib/librt.so
[DEBUG]:   	/bin/ln: failed to create symbolic link '/ndk/platforms/android-21/arch-arm/usr/lib/librt.so': Permission denied
Exception in thread background thread for pid 18688:
Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/home/userhome/.local/lib/python3.6/site-packages/sh.py", line 1540, in wrap
    fn(*args, **kwargs)
  File "/home/userhome/.local/lib/python3.6/site-packages/sh.py", line 2459, in background_thread
    handle_exit_code(exit_code)
  File "/home/userhome/.local/lib/python3.6/site-packages/sh.py", line 2157, in fn
    return self.command.handle_command_exit_code(exit_code)
  File "/home/userhome/.local/lib/python3.6/site-packages/sh.py", line 815, in handle_command_exit_code
    raise exc
sh.ErrorReturnCode_1: 

  RAN: /bin/ln -sf /ndk/platforms/android-21/arch-arm/usr/lib/libc.so /ndk/platforms/android-21/arch-arm/usr/lib/librt.so

  STDOUT:
/bin/ln: failed to create symbolic link '/ndk/platforms/android-21/arch-arm/usr/lib/librt.so': Permission denied


  STDERR:


Traceback (most recent call last):
  File "/home/userhome/.local/bin/p4a", line 11, in <module>
    sys.exit(main())
  File "/home/userhome/.local/lib/python3.6/site-packages/pythonforandroid/toolchain.py", line 1033, in main
    ToolchainCL()
  File "/home/userhome/.local/lib/python3.6/site-packages/pythonforandroid/toolchain.py", line 553, in __init__
    getattr(self, args.subparser_name.replace('-', '_'))(args)
  File "/home/userhome/.local/lib/python3.6/site-packages/pythonforandroid/toolchain.py", line 150, in wrapper_func
    build_dist_from_args(ctx, dist, args)
  File "/home/userhome/.local/lib/python3.6/site-packages/pythonforandroid/toolchain.py", line 191, in build_dist_from_args
    build_recipes(build_order, python_modules, ctx)
  File "/home/userhome/.local/lib/python3.6/site-packages/pythonforandroid/build.py", line 634, in build_recipes
    recipe.build_arch(arch)
  File "/home/userhome/.local/lib/python3.6/site-packages/pythonforandroid/recipe.py", line 902, in build_arch
    self.build_compiled_components(arch)
  File "/home/userhome/.local/lib/python3.6/site-packages/pythonforandroid/recipes/lxml/__init__.py", line 35, in build_compiled_components
    shprint(sh.ln, '-sf', link_c + '.so', link_rt + '.so')
  File "/home/userhome/.local/lib/python3.6/site-packages/pythonforandroid/logger.py", line 178, in shprint
    for line in output:
  File "/home/userhome/.local/lib/python3.6/site-packages/sh.py", line 863, in next
    self.wait()
  File "/home/userhome/.local/lib/python3.6/site-packages/sh.py", line 792, in wait
    self.handle_command_exit_code(exit_code)
  File "/home/userhome/.local/lib/python3.6/site-packages/sh.py", line 815, in handle_command_exit_code
    raise exc
sh.ErrorReturnCode_1: 

  RAN: /bin/ln -sf /ndk/platforms/android-21/arch-arm/usr/lib/libc.so /ndk/platforms/android-21/arch-arm/usr/lib/librt.so

  STDOUT:
/bin/ln: failed to create symbolic link '/ndk/platforms/android-21/arch-arm/usr/lib/librt.so': Permission denied
```

Now that I am pondering this, this also seems somewhat ugly to me (not the symlink in itself, but actually writing around inside the NDK folder - I don't think this is to be expected from the user). Therefore I think a better solution should be found that doesn't require write permissions in the NDK folder


Discussion:
Hmmmmm, shouldn't it be possible to set LD_LIBRARY_PATH to some separate place, and just add this symlink in a completely different folder? Or can anyone think of a reason why that wouldn't possibly work

Resolving patch:
diff --git a/pythonforandroid/archs.py b/pythonforandroid/archs.py
--- a/pythonforandroid/archs.py
+++ b/pythonforandroid/archs.py
@@ -1,8 +1,9 @@
-from os.path import (exists, join, dirname, split)
+from distutils.spawn import find_executable
 from os import environ, uname
+from os.path import (exists, join, dirname, split)
 from glob import glob
+import shlex
 import sys
-from distutils.spawn import find_executable
 
 from pythonforandroid.recipe import Recipe
 from pythonforandroid.util import BuildInterruptingException, build_platform
@@ -20,6 +21,12 @@ def __init__(self, ctx):
         super(Arch, self).__init__()
         self.ctx = ctx
 
+        # Allows injecting additional linker paths used by any recipe.
+        # This can also be modified by recipes (like the librt recipe)
+        # to make sure that some sort of global resource is available &
+        # linked for all others.
+        self.extra_global_link_paths = []
+
     def __str__(self):
         return self.arch
 
@@ -56,7 +63,13 @@ def get_env(self, with_flags_in_cc=True, clang=False):
             cflags.append('-gcc-toolchain {}'.format(toolchain))
 
         env['CFLAGS'] = ' '.join(cflags)
-        env['LDFLAGS'] = ' '
+
+        # Link the extra global link paths first before anything else
+        # (such that overriding system libraries with them is possible)
+        env['LDFLAGS'] = ' ' + " ".join([
+            "-L" + shlex.quote(l)
+            for l in self.extra_global_link_paths
+        ]) + ' '
 
         sysroot = join(self.ctx._ndk_dir, 'sysroot')
         if exists(sysroot):
diff --git a/pythonforandroid/recipes/librt/__init__.py b/pythonforandroid/recipes/librt/__init__.py
--- a/pythonforandroid/recipes/librt/__init__.py
+++ b/pythonforandroid/recipes/librt/__init__.py
@@ -1,5 +1,7 @@
+from os import makedirs, remove
+from os.path import exists, join
 import sh
-from os.path import join
+
 from pythonforandroid.recipe import Recipe
 from pythonforandroid.logger import shprint
 
@@ -20,17 +22,34 @@ class LibRt(Recipe):
     def libc_path(self):
         return join(self.ctx.ndk_platform, 'usr', 'lib', 'libc')
 
-    @property
-    def librt_path(self):
-        return join(self.ctx.ndk_platform, 'usr', 'lib', 'librt')
-
     def build_arch(self, arch):
-        shprint(sh.ln, '-sf', self.libc_path + '.so', self.librt_path + '.so')
-        shprint(sh.ln, '-sf', self.libc_path + '.a', self.librt_path + '.a')
-
-    def postbuild_arch(self, arch):
-        shprint(sh.rm, self.librt_path + '.so')
-        shprint(sh.rm, self.librt_path + '.a')
+        # Create a temporary folder to add to link path with a fake librt.so:
+        fake_librt_temp_folder = join(
+            self.get_build_dir(arch.arch),
+            "p4a-librt-recipe-tempdir"
+        )
+        if not exists(fake_librt_temp_folder):
+            makedirs(fake_librt_temp_folder)
+
+        # Set symlinks, and make sure to update them on every build run:
+        if exists(join(fake_librt_temp_folder, "librt.so")):
+            remove(join(fake_librt_temp_folder, "librt.so"))
+        shprint(sh.ln, '-sf',
+                self.libc_path + '.so',
+                join(fake_librt_temp_folder, "librt.so"),
+                )
+        if exists(join(fake_librt_temp_folder, "librt.a")):
+            remove(join(fake_librt_temp_folder, "librt.a"))
+        shprint(sh.ln, '-sf',
+                self.libc_path + '.a',
+                join(fake_librt_temp_folder, "librt.a"),
+               )
+
+        # Add folder as -L link option for all recipes if not done yet:
+        if fake_librt_temp_folder not in arch.extra_global_link_paths:
+            arch.extra_global_link_paths.append(
+                fake_librt_temp_folder
+            )
 
 
 recipe = LibRt()


Test patch: