Wednesday, May 23, 2012

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)
TARGET_ABI      := android-4-armeabi
include $(CLEAR_VARS)

LOCAL_MODULE    := libmain

  $(LOCAL_PATH)/Box2D \
  $(LOCAL_PATH)/libpng \
  $(LOCAL_PATH)/libzip \

  -g3 \
  -ggdb \
  -gstabs+ \

# Look here!
  main.cpp  \
  cool_file.cpp  \
LOCAL_LDLIBS := -lGLESv1_CM -llog -lz
LOCAL_STATIC_LIBRARIES := libBox2D libzip libpng


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:

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:

Attempt 1:

  $(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:

  $(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!
  $(wildcard $(MY_PATH)/*.cpp)

# This fixes the problem!
  $(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: