How to use Makefile wildcards/globbing on Android NDK JNI
Here is a short little thing I ran into a few days ago. Many of you may know how a normal Android Makefile looks, something like this:
LOCAL_PATH := $(call my-dir)
MY_PATH := $(LOCAL_PATH)
TARGET_ABI := android-4-armeabi
include $(CLEAR_VARS)
LOCAL_MODULE := libmain
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/Box2D \
$(LOCAL_PATH)/libpng \
$(LOCAL_PATH)/libzip \
LOCAL_CFLAGS := \
-g3 \
-ggdb \
-gstabs+ \
-DANDROID_NDK \
# Look here!
LOCAL_SRC_FILES := \
main.cpp \
cool_file.cpp \
LOCAL_LDLIBS := -lGLESv1_CM -llog -lz
LOCAL_STATIC_LIBRARIES := libBox2D libzip libpng
include $(BUILD_SHARED_LIBRARY)
Ok, notice the section LOCAL_SRC_FILES . If you have many files in your project this could grow huge. O(N) where N is the number of files. I am way to lazy to type all that (though vim does this with :read !ls jni/, but still). I want a better solution.
Ah, remember Makefile wildcards? Yeah, just like command line globs, they can pull in every file that matches your globbing pattern to pull in multiple files. This is really handy for us, since all our C++ files will be *.cpp (or whatever you like to suffix them with).
So we know from the android docs that LOCAL_PATH is applied to the prefix of all files in LOCAL_SRC_FILES.
So we could (incorrectly) try this:
LOCAL_SRC_FILES := \
$(wildcard *.cpp)
We would assume this would pick up both jni/main.cpp and jni/cool_file.cpp. But oh no Android make system... you crazy guy, you. For some reason it globs NOTHING!
Well, ok lets try appending the whole path you say, great let's do that:
LOCAL_SRC_FILES := \
$(wildcard $(LOCAL_PATH)/*.cpp)
Error! Now make complains about not finding jni/jni/main.cpp. I didn't mistype that, it appended the prefix twice. What the heck is that all about? So it doesn't glob the files if I don't prepend the LOCAL_PATH (to something that should already have LOCAL_PATH prepend to it).
Ok, lets do this, lets prepend the LOCAL_PATH, just like last time, so Make globs the files, but then lets clean up their LOCAL_PATH mess at the end so it will actually compile them.
We do this by deleting the extra "jni/" with a call to "subst". Then when the Android Make system calls g++, it will have the corrected path without the extra jni.
#---------------------------------------------------------
# Nonsense, for some reason if I use a wildcard without local
# path, it doesn't find the source files.
# If I do it appends LOCAL_PATH to the prefix twice!
#---------------------------------------------------------
MY_LOCAL_SRC_FILES := \
$(wildcard $(MY_PATH)/*.cpp)
# This fixes the problem!
LOCAL_SRC_FILES := \
$(subst jni/, , $(MY_LOCAL_SRC_FILES))
Yay, that did it! Now we are compiling all cpp files in the jni directory and we don't even have to add them to the Makefile as new ones are created.
Cool! Happy coding!
You can check out a working example here:
http://code.google.com/p/razzlegames-android-ndk-tutorial/source/browse/trunk/jni/Android.mk
LOCAL_PATH := $(call my-dir)
MY_PATH := $(LOCAL_PATH)
TARGET_ABI := android-4-armeabi
include $(CLEAR_VARS)
LOCAL_MODULE := libmain
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/Box2D \
$(LOCAL_PATH)/libpng \
$(LOCAL_PATH)/libzip \
LOCAL_CFLAGS := \
-g3 \
-ggdb \
-gstabs+ \
-DANDROID_NDK \
# Look here!
LOCAL_SRC_FILES := \
main.cpp \
cool_file.cpp \
LOCAL_LDLIBS := -lGLESv1_CM -llog -lz
LOCAL_STATIC_LIBRARIES := libBox2D libzip libpng
include $(BUILD_SHARED_LIBRARY)
Ok, notice the section LOCAL_SRC_FILES . If you have many files in your project this could grow huge. O(N) where N is the number of files. I am way to lazy to type all that (though vim does this with :read !ls jni/, but still). I want a better solution.
Makefile Wildcards:
So we know from the android docs that LOCAL_PATH is applied to the prefix of all files in LOCAL_SRC_FILES.
So we could (incorrectly) try this:
Attempt 1:
LOCAL_SRC_FILES := \
$(wildcard *.cpp)
We would assume this would pick up both jni/main.cpp and jni/cool_file.cpp. But oh no Android make system... you crazy guy, you. For some reason it globs NOTHING!
Well, ok lets try appending the whole path you say, great let's do that:
Attempt 2:
LOCAL_SRC_FILES := \
$(wildcard $(LOCAL_PATH)/*.cpp)
Error! Now make complains about not finding jni/jni/main.cpp. I didn't mistype that, it appended the prefix twice. What the heck is that all about? So it doesn't glob the files if I don't prepend the LOCAL_PATH (to something that should already have LOCAL_PATH prepend to it).
Ok, lets do this, lets prepend the LOCAL_PATH, just like last time, so Make globs the files, but then lets clean up their LOCAL_PATH mess at the end so it will actually compile them.
We do this by deleting the extra "jni/" with a call to "subst". Then when the Android Make system calls g++, it will have the corrected path without the extra jni.
Attempt 3 (Winner!):
#---------------------------------------------------------
# Nonsense, for some reason if I use a wildcard without local
# path, it doesn't find the source files.
# If I do it appends LOCAL_PATH to the prefix twice!
#---------------------------------------------------------
MY_LOCAL_SRC_FILES := \
$(wildcard $(MY_PATH)/*.cpp)
# This fixes the problem!
LOCAL_SRC_FILES := \
$(subst jni/, , $(MY_LOCAL_SRC_FILES))
Yay, that did it! Now we are compiling all cpp files in the jni directory and we don't even have to add them to the Makefile as new ones are created.
Cool! Happy coding!
You can check out a working example here:
http://code.google.com/p/razzlegames-android-ndk-tutorial/source/browse/trunk/jni/Android.mk
great, that's what I need
ReplyDeleteNo problem friend! Glad to help.
DeleteHey would just like to point out that your solution works but you can replace 'subst jni/, ,' with the notdir instruction which accomplishes the same thing like this:
ReplyDeleteMY_LOCAL_SRC_FILES := \$(wildcard $(MY_PATH)/*.c)
LOCAL_SRC_FILES := \$(notdir $(MY_LOCAL_SRC_FILES))
Thanks for your comment. Funny enough, I think I may have used notdir at first. This is a nice solution if you're only going to be using the flat JNI directory and no subdirs for c/cpp/etc. However, if you wanted to organize your source into subdirectories "notdir" breaks as it will only take the file name neglecting everything before the last /.
Deletehttp://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html
You can probably do recursive make -C to get around this, but Android NDK doesn't let you invoke these Android.mk that way with the ndk-build tool (AFAIK). Maybe just do a include subdir/Android.mk to get around it. But everytime I did this, it considered the parent as the CWD from within the subdir/Android.mk (expected), and I had to append the full path to all source files included. Which means notdir would still break.
http://code.google.com/p/razzlegames-android-ndk-tutorial/source/browse/trunk/jni/Box2D/Android.mk
Also, AFAIK, notdir doesn't support spaces in directory names (even though I recommend not using spaces in dir names anyhow):
http://stackoverflow.com/questions/1189781/using-make-dir-or-notdir-on-a-path-with-spaces
All in all, I think the most complete solution is to just delete EXACTLY the extra path info that is added to our variables by the NDK Make system. Yeah, this is a hack, but until the problem is fixed in the NDK (ever?) than I cannot think of a better way (handling the general case).