Repository: conan-io/conan-center-index

Issue:
[android-ndk] Fix broken symlinks
Specify library name and version:  **android-ndk/r23**

The NDK r23 zip file contains the symlinks now, but tools.get() can't extract the links properly.
https://github.com/conan-io/conan/issues/9469
 

---

- [x] I've read the [guidelines](https://github.com/conan-io/conan-center-index/blob/master/docs/how_to_add_packages.md) for contributing.
- [x] I've followed the [PEP8](https://www.python.org/dev/peps/pep-0008/) style guides for Python code in the recipes.
- [x] I've used the [latest](https://github.com/conan-io/conan/releases/latest) Conan client version.
- [x] I've tried at least one configuration locally with the
      [conan-center hook](https://github.com/conan-io/hooks.git) activated.



Discussion:

Resolving patch:
diff --git a/recipes/android-ndk/all/conanfile.py b/recipes/android-ndk/all/conanfile.py
--- a/recipes/android-ndk/all/conanfile.py
+++ b/recipes/android-ndk/all/conanfile.py
@@ -1,14 +1,14 @@
 from conans import ConanFile, tools
 from conans.errors import ConanInvalidConfiguration
 import os
+import shutil
 
 required_conan_version = ">=1.33.0"
 
 
 class AndroidNDKConan(ConanFile):
     name = "android-ndk"
-    description = "The Android NDK is a toolset that lets you implement parts of your app in " \
-                  "native code, using languages such as C and C++"
+    description = "The Android NDK is a toolset that lets you implement parts of your app in native code, using languages such as C and C++"
     url = "https://github.com/conan-io/conan-center-index"
     homepage = "https://developer.android.com/ndk/"
     topics = ("android", "NDK", "toolchain", "compiler")
@@ -36,8 +36,12 @@ def validate(self):
             raise ConanInvalidConfiguration(f"os,arch={self.settings.os},{self.settings.arch} is not supported by {self.name} (no binaries are available)")
 
     def build(self):
-        tools.get(**self.conan_data["sources"][self.version]["url"][str(self.settings.os)][str(self.settings.arch)],
-            destination=self._source_subfolder, strip_root=True)
+        if self.version in ['r23']:
+            data = self.conan_data["sources"][self.version]["url"][str(self.settings.os)][str(self.settings.arch)]
+            unzip_fix_symlinks(url=data["url"], target_folder=self._source_subfolder, sha256=data["sha256"])
+        else:
+            tools.get(**self.conan_data["sources"][self.version]["url"][str(self.settings.os)][str(self.settings.arch)],
+                  destination=self._source_subfolder, strip_root=True)
 
     def package(self):
         self.copy("*", src=self._source_subfolder, dst=".", keep_path=True, symlinks=True)
@@ -117,7 +121,7 @@ def _fix_permissions(self):
 
     @property
     def _host(self):
-        return f"{self._platform}_{self.settings.arch}"
+        return f"{self._platform}-{self.settings.arch}"
 
     @property
     def _ndk_root(self):
@@ -248,3 +252,40 @@ def package_info(self):
         self.env_info.CMAKE_FIND_ROOT_PATH_MODE_LIBRARY = "BOTH"
         self.env_info.CMAKE_FIND_ROOT_PATH_MODE_INCLUDE = "BOTH"
         self.env_info.CMAKE_FIND_ROOT_PATH_MODE_PACKAGE = "BOTH"
+
+
+def unzip_fix_symlinks(url, target_folder, sha256):
+    # Python's built-in module 'zipfile' won't handle symlinks (https://bugs.python.org/issue37921)
+    # Most of the logic borrowed from this PR https://github.com/conan-io/conan/pull/8100
+
+    filename = "android_sdk.zip"
+    tools.download(url, filename)
+    tools.check_sha256(filename, sha256)
+    tools.unzip(filename, destination=target_folder, strip_root=True)
+
+    def is_symlink_zipinfo(zi):
+        return (zi.external_attr >> 28) == 0xA
+
+    full_path = os.path.normpath(target_folder)
+    import zipfile
+    with zipfile.ZipFile(filename, "r") as z:
+        zip_info = z.infolist()
+        
+        names = [n.replace("\\", "/") for n in z.namelist()]
+        common_folder = os.path.commonprefix(names).split("/", 1)[0]
+
+        for file_ in zip_info:
+            if is_symlink_zipinfo(file_):
+                rel_path = os.path.relpath(file_.filename, common_folder)
+                full_name = os.path.join(full_path, rel_path)
+                target = tools.load(full_name)
+                os.unlink(full_name)
+
+                try:
+                    os.symlink(target, full_name)
+                except OSError:
+                    if not os.path.isabs(target):
+                        target = os.path.normpath(os.path.join(os.path.dirname(full_name), target))
+                    shutil.copy2(target, full_name)
+
+    os.unlink(filename)


Test patch:
diff --git a/recipes/android-ndk/all/test_package/CMakeLists.txt b/recipes/android-ndk/all/test_package/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/recipes/android-ndk/all/test_package/CMakeLists.txt
@@ -0,0 +1,8 @@
+cmake_minimum_required(VERSION 3.0)
+project(test_package)
+
+include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+conan_basic_setup(TARGETS)
+
+add_executable(${PROJECT_NAME} test_package.cpp)
+target_link_libraries(${PROJECT_NAME})
diff --git a/recipes/android-ndk/all/test_package/conanfile.py b/recipes/android-ndk/all/test_package/conanfile.py
--- a/recipes/android-ndk/all/test_package/conanfile.py
+++ b/recipes/android-ndk/all/test_package/conanfile.py
@@ -1,11 +1,18 @@
-from conans import ConanFile, tools
+import os
+from conans import ConanFile, tools, CMake
 
 
 class TestPackgeConan(ConanFile):
     settings = "os", "arch"
+    test_type = "build_requires"
+    generators = "cmake"
 
     def build(self):
-        pass
+        # It only makes sense to build a library, if the target os is Android
+        if self.settings.os == "Android":
+            cmake = CMake(self)
+            cmake.configure()
+            cmake.build()
 
     def test(self):
         if not tools.cross_building(self):
@@ -13,3 +20,9 @@ def test(self):
                 self.run("ndk-build.cmd --version", run_environment=True)
             else:
                 self.run("ndk-build --version", run_environment=True)
+
+        # Run the project that was built using Android NDK
+        if self.settings.os == "Android":
+            test_file = os.path.join("bin", "test_package")
+            assert os.path.exists(test_file)
+            # self.run("android-emulator {}".format(test_file), run_environment=True)
diff --git a/recipes/android-ndk/all/test_package/test_package.cpp b/recipes/android-ndk/all/test_package/test_package.cpp
new file mode 100644
--- /dev/null
+++ b/recipes/android-ndk/all/test_package/test_package.cpp
@@ -0,0 +1,8 @@
+#include <cstdlib>
+#include <iostream>
+
+int main()
+{
+    std::cout << "conan-center-index\n";
+    return EXIT_SUCCESS;
+}