1if(NOT ANDROID_PACKAGE_NAME)
2  set(ANDROID_PACKAGE_NAME "com.github.stevenlovegrove.pangolin")
3endif()
4
5if(NOT ANDROID_DEFERRED_ENTRY_SO)
6  set(ANDROID_DEFERRED_ENTRY_SO "libpangolin.so")
7endif()
8
9# Configure build environment to automatically generate APK's instead of executables.
10if(ANDROID AND NOT TARGET apk)
11    # virtual targets which we'll add apks and push actions to.
12    add_custom_target( apk )
13    add_custom_target( push )
14    add_custom_target( run )
15
16    # Reset output directories to be in binary folder (rather than source)
17    set(LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_CURRENT_BINARY_DIR})
18    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME})
19    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME})
20    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME})
21
22    macro( create_android_manifest_xml filename prog_name package_name activity_name)
23        file( WRITE ${filename}
24"<?xml version=\"1.0\" encoding=\"utf-8\"?>
25<!-- BEGIN_INCLUDE(manifest) -->
26<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"
27        package=\"${package_name}.${prog_name}\"
28        android:versionCode=\"1\"
29        android:versionName=\"1.0\">
30
31    <!-- This is the platform API where NativeActivity was introduced. -->
32    <uses-sdk android:minSdkVersion=\"14\" />
33    <uses-feature android:glEsVersion=\"0x00020000\" />
34    <uses-feature android:name=\"android.hardware.camera\" />
35    <uses-permission android:name=\"android.permission.CAMERA\"/>
36    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>
37    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>
38
39    <!-- This .apk has no Java code itself, so set hasCode to false. -->
40    <application android:label=\"${activity_name}\" android:hasCode=\"false\">
41
42        <!-- Our activity is the built-in NativeActivity framework class.
43             This will take care of integrating with our NDK code. -->
44        <activity android:name=\"android.app.NativeActivity\"
45                android:label=\"${activity_name}\"
46                android:screenOrientation=\"landscape\"
47                android:configChanges=\"orientation|keyboard|keyboardHidden\"
48                android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\"
49                >
50            <!-- Tell NativeActivity the name of our .so -->
51            <meta-data android:name=\"android.app.lib_name\"
52                    android:value=\"${prog_name}_start\" />
53            <intent-filter>
54                <action android:name=\"android.intent.action.MAIN\" />
55                <category android:name=\"android.intent.category.LAUNCHER\" />
56            </intent-filter>
57        </activity>
58    </application>
59
60</manifest>
61<!-- END_INCLUDE(manifest) -->" )
62    endmacro()
63
64    macro( create_bootstrap_library prog_name package_name)
65        set(bootstrap_cpp "${CMAKE_CURRENT_BINARY_DIR}/${prog_name}_start.cpp" )
66        file( WRITE ${bootstrap_cpp}
67"#include <android/native_activity.h>
68#include <android/log.h>
69#include <dlfcn.h>
70#include <errno.h>
71#include <stdlib.h>
72#include <cstdio>
73
74#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, \"AndroidUtils.cmake\", __VA_ARGS__))
75#define LIB_PATH \"/data/data/${package_name}.${prog_name}/lib/\"
76
77void * load_lib(const char * l) {
78    void * handle = dlopen(l, RTLD_NOW | RTLD_GLOBAL);
79    if (!handle) LOGE( \"dlopen('%s'): %s\", l, strerror(errno) );
80    return handle;
81}
82
83void ANativeActivity_onCreate(ANativeActivity * app, void * ud, size_t udsize) {
84    #include \"${prog_name}_shared_load.h\"
85
86    // Look for standard entrypoint in user lib
87    void (*stdentrypoint)(ANativeActivity*, void*, size_t);
88    *(void **) (&stdentrypoint) = dlsym(load_lib( LIB_PATH \"lib${prog_name}.so\"), \"ANativeActivity_onCreate\");
89    if (stdentrypoint) {
90        (*stdentrypoint)(app, ud, udsize);
91    }else{
92        // Look for deferred load entry point
93        void (*exdentrypoint)(ANativeActivity*, void*, size_t, const char*);
94        *(void **) (&exdentrypoint) = dlsym(load_lib( LIB_PATH \"lib${prog_name}.so\"), \"DeferredNativeActivity_onCreate\");
95        if (!exdentrypoint) {
96            // Look in specific shared lib
97            *(void **) (&exdentrypoint) = dlsym(load_lib( LIB_PATH \"${ANDROID_DEFERRED_ENTRY_SO}\"), \"DeferredNativeActivity_onCreate\");
98        }
99        if(exdentrypoint) {
100            (*exdentrypoint)(app, ud, udsize, LIB_PATH \"lib${prog_name}.so\" );
101        }else{
102            LOGE( \"Unable to find compatible entry point\" );
103        }
104    }
105}" )
106        add_library( "${prog_name}_start" SHARED ${bootstrap_cpp} )
107        target_link_libraries( "${prog_name}_start" android log )
108        add_dependencies( ${prog_name} "${prog_name}_start" )
109    endmacro()
110
111    macro( android_update android_project_name)
112        # Find which android platforms are available.
113        execute_process(
114            COMMAND android list targets -c
115            OUTPUT_VARIABLE android_target_list
116        )
117
118        # Pick first platform from this list.
119        string(REGEX MATCH "^[^\n]+" android_target "${android_target_list}" )
120        message(STATUS "Android Target: ${android_target}")
121
122        if( NOT "${android_target}" STREQUAL "" )
123            # Generate ant build scripts for making APK
124            execute_process(
125                COMMAND android update project --name ${android_project_name} --path . --target ${android_target} --subprojects
126                WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
127            )
128        else()
129            message( FATAL_ERROR "No Android SDK platforms found. Please install an Android platform SDK. On Linux, run 'android'." )
130        endif()
131    endmacro()
132
133    # Override add_executable to build android .so instead!
134    macro( add_executable prog_name)
135        set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/libs/${ANDROID_NDK_ABI_NAME})
136        add_library( ${prog_name} SHARED ${ARGN} )
137
138        # Add required link libs for android
139        target_link_libraries(${prog_name} log android )
140
141        # Create manifest required for APK
142        create_android_manifest_xml(
143            "${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml" "${prog_name}"
144            "${ANDROID_PACKAGE_NAME}" "${prog_name}"
145        )
146
147        # Create library that will launch this program and load shared libs
148        create_bootstrap_library( ${prog_name} ${ANDROID_PACKAGE_NAME} )
149
150        # Generate ant build system for APK
151        android_update( ${prog_name} )
152
153        # Target to invoke ant build system for APK
154        set( APK_FILE "${CMAKE_CURRENT_BINARY_DIR}/bin/${prog_name}-debug.apk" )
155        add_custom_command(
156            OUTPUT ${APK_FILE}
157            COMMAND ant debug
158            DEPENDS ${prog_name}
159            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
160        )
161
162        # Target to install on device
163        add_custom_target( ${prog_name}-apk
164            DEPENDS ${APK_FILE}
165        )
166        add_dependencies(apk ${prog_name}-apk)
167
168        # Target to install on device
169        add_custom_target( ${prog_name}-push
170            COMMAND adb install -r ${APK_FILE}
171            DEPENDS ${APK_FILE}
172        )
173        add_dependencies(push ${prog_name}-push)
174
175        # install and run on device
176        add_custom_target( ${prog_name}-run
177            COMMAND adb shell am start -n ${ANDROID_PACKAGE_NAME}.${prog_name}/android.app.NativeActivity
178            DEPENDS ${prog_name}-push
179            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
180        )
181        add_dependencies(run ${prog_name}-run)
182
183        # Flag to package dependent libs
184        set_property(TARGET ${prog_name} APPEND PROPERTY MAKE_APK 1 )
185
186        # Clear shared library loading header
187        file( WRITE "${CMAKE_CURRENT_BINARY_DIR}/${prog_name}_shared_load.h" "")
188    endmacro()
189
190    macro( package_with_target prog_name lib_path )
191        # Mark lib_path as dependent of prog_name
192        set_property(TARGET ${prog_name} APPEND PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE ${lib_path} )
193
194        # If prog_name is to be packaged, add file copy command to package .so's.
195        get_target_property( package_dependent_libs ${prog_name} MAKE_APK )
196        if( package_dependent_libs )
197            get_filename_component(target_filename ${lib_path} NAME)
198            file( APPEND ${depend_file} "load_lib(LIB_PATH \"${target_filename}\" );\n")
199            add_custom_command(TARGET ${prog_name} POST_BUILD
200                COMMAND ${CMAKE_COMMAND} -E copy_if_different
201                ${lib_path} "${CMAKE_CURRENT_BINARY_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
202            )
203        endif()
204    endmacro()
205
206    macro( add_to_depend_libs prog_name depend_file lib_name )
207        # Recursively Process dependents of lib_name
208        get_target_property(TARGET_LIBS ${lib_name} IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE)
209        if(NOT TARGET_LIBS)
210            get_target_property(TARGET_LIBS ${lib_name} IMPORTED_LINK_INTERFACE_LIBRARIES_NOCONFIG)
211        endif()
212        if(NOT TARGET_LIBS)
213            get_target_property(TARGET_LIBS ${lib_name} IMPORTED_LINK_INTERFACE_LIBRARIES_DEBUG)
214        endif()
215
216        foreach(SUBLIB ${TARGET_LIBS})
217            if(SUBLIB)
218                add_to_depend_libs( ${prog_name} ${depend_file} ${SUBLIB} )
219            endif()
220        endforeach()
221
222        # Check if lib itself is an external shared library
223        if("${lib_name}" MATCHES "\\.so$")
224            package_with_target( ${prog_name} ${lib_name} )
225        endif()
226
227        # Check if lib itself is an internal shared library
228        get_target_property(TARGET_LIB ${lib_name} LOCATION)
229        if("${TARGET_LIB}" MATCHES "\\.so$")
230            package_with_target( ${prog_name} ${TARGET_LIB} )
231        endif()
232    endmacro()
233
234    macro( target_link_libraries prog_name)
235        # _target_link_libraries corresponds to original
236        _target_link_libraries( ${prog_name} ${ARGN} )
237
238        # Recursively process dependencies
239        set(depend_file "${CMAKE_CURRENT_BINARY_DIR}/${prog_name}_shared_load.h" )
240        foreach( LIB ${ARGN} )
241            add_to_depend_libs( ${prog_name} ${depend_file} ${LIB} )
242        endforeach()
243    endmacro()
244
245endif()
246