Merge pull request #108 from anestisb/android-deps-cc-automation

[Android] Improve deps compilation automation & error handling
diff --git a/Makefile b/Makefile
index b6123d8..f9fc5f0 100644
--- a/Makefile
+++ b/Makefile
@@ -76,8 +76,8 @@
     # Figure out which crash reporter to use.
     CRASHWRANGLER := third_party/mac
     OS_VERSION := $(shell sw_vers -productVersion)
-	ifneq (,$(findstring 10.12,$(OS_VERSION)))
-		CRASH_REPORT := $(CRASHWRANGLER)/CrashReport_Yosemite.o
+    ifneq (,$(findstring 10.12,$(OS_VERSION)))
+        CRASH_REPORT := $(CRASHWRANGLER)/CrashReport_Yosemite.o
     else ifneq (,$(findstring 10.11,$(OS_VERSION)))
         # El Capitan didn't break compatibility
         CRASH_REPORT := $(CRASHWRANGLER)/CrashReport_Yosemite.o
@@ -176,24 +176,32 @@
   # clang works only against APIs >= 23
   ifeq ($(ANDROID_APP_ABI),$(filter $(ANDROID_APP_ABI),armeabi armeabi-v7a))
     ANDROID_NDK_TOOLCHAIN ?= arm-linux-androideabi-clang
+    ANDROID_ARCH_CPU := arm
   else ifeq ($(ANDROID_APP_ABI),$(filter $(ANDROID_APP_ABI),x86))
     ANDROID_NDK_TOOLCHAIN ?= x86-clang
+    ANDROID_ARCH_CPU := x86
   else ifeq ($(ANDROID_APP_ABI),$(filter $(ANDROID_APP_ABI),arm64-v8a))
     ANDROID_NDK_TOOLCHAIN ?= aarch64-linux-android-clang
+    ANDROID_ARCH_CPU := arm64
   else ifeq ($(ANDROID_APP_ABI),$(filter $(ANDROID_APP_ABI),x86_64))
     ANDROID_NDK_TOOLCHAIN ?= x86_64-clang
+    ANDROID_ARCH_CPU := x86_64
   else
     $(error Unsuported / Unknown APP_API '$(ANDROID_APP_ABI)')
   endif
 else
   ifeq ($(ANDROID_APP_ABI),$(filter $(ANDROID_APP_ABI),armeabi armeabi-v7a))
     ANDROID_NDK_TOOLCHAIN ?= arm-linux-androideabi-4.9
+    ANDROID_ARCH_CPU := arm
   else ifeq ($(ANDROID_APP_ABI),$(filter $(ANDROID_APP_ABI),x86))
     ANDROID_NDK_TOOLCHAIN ?= x86-4.9
+    ANDROID_ARCH_CPU := x86
   else ifeq ($(ANDROID_APP_ABI),$(filter $(ANDROID_APP_ABI),arm64-v8a))
     ANDROID_NDK_TOOLCHAIN ?= aarch64-linux-android-4.9
+    ANDROID_ARCH_CPU := arm64
   else ifeq ($(ANDROID_APP_ABI),$(filter $(ANDROID_APP_ABI),x86_64))
     ANDROID_NDK_TOOLCHAIN ?= x86_64-4.9
+    ANDROID_ARCH_CPU := x86_64
   else
     $(error Unsuported / Unknown APP_API '$(ANDROID_APP_ABI)')
   endif
@@ -240,6 +248,17 @@
 
 .PHONY: android
 android:
+	@ANDROID_API=$(ANDROID_API) third_party/android/scripts/compile-libunwind.sh \
+	third_party/android/libunwind $(ANDROID_ARCH_CPU)
+
+	@ANDROID_API=$(ANDROID_API) third_party/android/scripts/compile-capstone.sh \
+	third_party/android/capstone $(ANDROID_ARCH_CPU)
+
+  ifeq ($(ANDROID_CLANG),true)
+		@ANDROID_API=$(ANDROID_API) third_party/android/scripts/compile-libBlocksRuntime.sh \
+		third_party/android/libBlocksRuntime $(ANDROID_ARCH_CPU)
+  endif
+
 	ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./android/Android.mk \
     APP_PLATFORM=$(ANDROID_API) APP_ABI=$(ANDROID_APP_ABI) \
     NDK_TOOLCHAIN=$(ANDROID_NDK_TOOLCHAIN) $(NDK_BUILD_ARGS)
@@ -259,6 +278,18 @@
 	  echo ""; \
 	done
 
+.PHONY: android-clean-deps
+android-clean-deps:
+	@for cpu in arm arm64 x86 x86_64; do \
+	  make -C "third_party/android/capstone" clean; \
+	  rm -rf "third_party/android/capstone/$$cpu"; \
+	  make -C "third_party/android/libunwind" clean; \
+	  rm -rf "third_party/android/libunwind/$$cpu"; \
+	  ndk-build -C "third_party/android/libBlocksRuntime" \
+	    NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk clean; \
+	  rm -rf "third_party/android/libBlocksRuntime/$$cpu"; \
+	done
+
 # DO NOT DELETE
 
 cmdline.o: cmdline.h common.h log.h files.h util.h
diff --git a/android/Android.mk b/android/Android.mk
index 41f924a..492d295 100644
--- a/android/Android.mk
+++ b/android/Android.mk
@@ -15,22 +15,36 @@
 
 LOCAL_PATH := $(abspath $(call my-dir)/..)
 
+# Force a clean if target API has changed and a previous build exists
+ifneq ("$(wildcard $(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/android_api.txt)","")
+  CACHED_API := $(shell cat "$(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/android_api.txt")
+  ifneq ($(ANDROID_API),$(CACHED_API))
+    $(info [!] Previous build was targeting different API level - cleaning)
+    DUMMY_CLEAN := $(shell make clean)
+  endif
+endif
+
+# Force a clean if selected toolchain has changed and a previous build exists
+ifneq ("$(wildcard $(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/ndk_toolchain.txt)","")
+  CACHED_TOOLCHAIN := $(shell cat "$(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/ndk_toolchain.txt")
+  ifneq ($(NDK_TOOLCHAIN),$(CACHED_TOOLCHAIN))
+    $(info [!] Previous build was using different toolchain - cleaning)
+    DUMMY_CLEAN := $(shell make clean)
+  endif
+endif
+
 # Enable Linux ptrace() instead of POSIX signal interface by default
 ANDROID_WITH_PTRACE ?= true
 
 ifeq ($(ANDROID_WITH_PTRACE),true)
   ifeq ($(APP_ABI),$(filter $(APP_ABI),armeabi armeabi-v7a))
     ARCH_ABI := arm
-    UNW_ARCH := arm
   else ifeq ($(APP_ABI),$(filter $(APP_ABI),x86))
     ARCH_ABI := x86
-    UNW_ARCH := x86
   else ifeq ($(APP_ABI),$(filter $(APP_ABI),arm64-v8a))
     ARCH_ABI := arm64
-    UNW_ARCH := aarch64
   else ifeq ($(APP_ABI),$(filter $(APP_ABI),x86_64))
     ARCH_ABI := x86_64
-    UNW_ARCH := x86_64
   else
     $(error Unsuported / Unknown APP_API '$(APP_ABI)')
   endif
@@ -43,9 +57,9 @@
   endif
 
   # Upstream libunwind compiled from sources with Android NDK toolchain
-  LIBUNWIND_A := third_party/android/libunwind/$(ARCH_ABI)/libunwind-$(UNW_ARCH).a
+  LIBUNWIND_A := third_party/android/libunwind/$(ARCH_ABI)/libunwind-$(ARCH_ABI).a
   ifeq ("$(wildcard $(LIBUNWIND_A))","")
-    $(error libunwind-$(UNW_ARCH) is missing - to build execute \
+    $(error libunwind-$(ARCH_ABI) is missing - to build execute \
             'third_party/android/scripts/compile-libunwind.sh third_party/android/libunwind $(ARCH_ABI)')
   endif
 
@@ -57,7 +71,7 @@
 
   include $(CLEAR_VARS)
   LOCAL_MODULE := libunwind-arch
-  LOCAL_SRC_FILES := third_party/android/libunwind/$(ARCH_ABI)/libunwind-$(UNW_ARCH).a
+  LOCAL_SRC_FILES := third_party/android/libunwind/$(ARCH_ABI)/libunwind-$(ARCH_ABI).a
   LOCAL_EXPORT_C_INCLUDES := third_party/android/libunwind/include
   include $(PREBUILT_STATIC_LIBRARY)
 
@@ -165,9 +179,13 @@
 include $(BUILD_EXECUTABLE)
 
 # The NDK build system does not copy static libraries into project/packages
-# so it has to be done manually in order to have all output under a single path
+# so it has to be done manually in order to have all output under a single path.
+# Also save some build attribute cache files so that cleans can be enforced when
+# required.
 all:POST_BUILD_EVENT
 POST_BUILD_EVENT:
+	@echo $(ANDROID_API) > $(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/android_api.txt
+	@echo $(NDK_TOOLCHAIN) > $(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/ndk_toolchain.txt
 	@test -f $(LOCAL_PATH)/obj/local/$(TARGET_ARCH_ABI)/libhfuzz.a && \
 	  cp $(LOCAL_PATH)/obj/local/$(TARGET_ARCH_ABI)/libhfuzz.a \
 	    $(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/libhfuzz.a || true
diff --git a/third_party/android/scripts/compile-capstone.sh b/third_party/android/scripts/compile-capstone.sh
index 452f973..efc831f 100755
--- a/third_party/android/scripts/compile-capstone.sh
+++ b/third_party/android/scripts/compile-capstone.sh
@@ -15,6 +15,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+#set -x # debug
+
 abort() {
   cd - &>/dev/null
   exit "$1"
@@ -29,7 +31,7 @@
   exit 1
 fi
 
-readonly CAPSTONE_DIR=$1
+readonly CAPSTONE_DIR="$1"
 
 if [ ! -d "$CAPSTONE_DIR/.git" ]; then
   git submodule update --init third_party/android/capstone || {
@@ -63,7 +65,7 @@
     NDK=$(dirname $(which ndk-build))
   else
     echo "[-] Could not detect Android NDK dir"
-    exit 1
+    abort 1
   fi
 fi
 
@@ -74,10 +76,22 @@
     ;;
   *)
     echo "[-] Invalid CPU architecture"
-    exit 1
+    abort 1
     ;;
 esac
 
+# Check if previous build exists and matches selected ANDROID_API level
+# If API cache file not there always rebuild
+if [ -f "$ARCH/libcapstone.a" ]; then
+  if [ -f "$ARCH/android_api.txt" ]; then
+    old_api=$(cat "$ARCH/android_api.txt")
+    if [[ "$old_api" == "$ANDROID_API" ]]; then
+      # No need to recompile
+      abort 0
+    fi
+  fi
+fi
+
 case "$ARCH" in
   arm)
     CS_ARCH="arm"
@@ -113,15 +127,23 @@
     $NDK=$(dirname $(which ndk-build))
   else
     echo "[-] Could not detect Android NDK dir"
-    exit 1
+    abort 1
   fi
 fi
 
+if [ -z "$ANDROID_API" ]; then
+  ANDROID_API="android-21"
+fi
+if ! echo "$ANDROID_API" | grep -qoE 'android-[0-9]{1,2}'; then
+  echo "[-] Invalid ANDROID_API '$ANDROID_API'"
+  abort 1
+fi
+
 # Support both Linux & Darwin
 HOST_OS=$(uname -s | tr '[:upper:]' '[:lower:]')
 HOST_ARCH=$(uname -m)
 
-SYSROOT="$NDK/platforms/android-21/arch-$ARCH"
+SYSROOT="$NDK/platforms/$ANDROID_API/arch-$ARCH"
 export CC="$NDK/toolchains/$TOOLCHAIN_S/prebuilt/$HOST_OS-$HOST_ARCH/bin/$TOOLCHAIN-gcc --sysroot=$SYSROOT"
 export CXX="$NDK/toolchains/$TOOLCHAIN_S/prebuilt/$HOST_OS-$HOST_ARCH/bin/$TOOLCHAIN-g++ --sysroot=$SYSROOT"
 export PATH="$NDK/toolchains/$TOOLCHAIN_S/prebuilt/$HOST_OS-$HOST_ARCH/bin":$PATH
@@ -137,11 +159,12 @@
 eval $CS_BUILD_BIN
 if [ $? -ne 0 ]; then
     echo "[-] Compilation failed"
-    exit 1
+    abort 1
 else
     echo "[*] '$ARCH' libcapstone available at '$CAPSTONE_DIR/$ARCH'"
 fi
 
-cp libcapstone.a $ARCH/
+cp libcapstone.a "$ARCH/"
+echo "$ANDROID_API" > "$ARCH/android_api.txt"
 
 abort 0
diff --git a/third_party/android/scripts/compile-libBlocksRuntime.sh b/third_party/android/scripts/compile-libBlocksRuntime.sh
index f854fb5..870f84b 100755
--- a/third_party/android/scripts/compile-libBlocksRuntime.sh
+++ b/third_party/android/scripts/compile-libBlocksRuntime.sh
@@ -15,8 +15,6 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-readonly ANDROID_API="android-23"
-
 if [ -z "$NDK" ]; then
   # Search in $PATH
   if [[ $(which ndk-build) != "" ]]; then
@@ -27,6 +25,14 @@
   fi
 fi
 
+if [ -z "$ANDROID_API" ]; then
+  ANDROID_API="android-23"
+fi
+if ! echo "$ANDROID_API" | grep -qoE 'android-[0-9]{1,2}'; then
+  echo "[-] Invalid ANDROID_API '$ANDROID_API'"
+  exit 1
+fi
+
 if [ $# -ne 2 ]; then
   echo "[-] Invalid arguments"
   echo "[!] $0 <libBlocksRuntime_DIR> <ARCH>"
@@ -34,7 +40,7 @@
   exit 1
 fi
 
-readonly BRT_DIR=$1
+readonly BRT_DIR="$1"
 
 case "$2" in
   arm|arm64|x86|x86_64)
@@ -47,6 +53,18 @@
     ;;
 esac
 
+# Check if previous build exists and matches selected ANDROID_API level
+# If API cache file not there always rebuild
+if [ -f "$BRT_DIR/$ARCH/libblocksruntime.a" ]; then
+  if [ -f "$BRT_DIR/$ARCH/android_api.txt" ]; then
+    old_api=$(cat "$BRT_DIR/$ARCH/android_api.txt")
+    if [[ "$old_api" == "$ANDROID_API" ]]; then
+      # No need to recompile
+      exit 0
+    fi
+  fi
+fi
+
 case "$ARCH" in
   arm)
     BRT_ARCH="armeabi-v7a"
@@ -85,7 +103,8 @@
 # Change workdir to simplify args
 cd $BRT_DIR
 
-cp obj/local/$BRT_ARCH/libblocksruntime.a $ARCH/
+cp obj/local/$BRT_ARCH/libblocksruntime.a "$ARCH/"
+echo "$ANDROID_API" > "$ARCH/android_api.txt"
 
 # Revert workdir to caller
 cd - &>/dev/null
diff --git a/third_party/android/scripts/compile-libunwind.sh b/third_party/android/scripts/compile-libunwind.sh
index 0a81ce9..4d6980f 100755
--- a/third_party/android/scripts/compile-libunwind.sh
+++ b/third_party/android/scripts/compile-libunwind.sh
@@ -23,7 +23,7 @@
     echo "[!] git patches are not reverted since running under debug mode"
   else
     # Extra care to ensure we're under expected project
-    if [[ "$(basename $(git rev-parse --show-toplevel))" == "libunwind" ]]; then
+    if [[ $# -eq 1 && "$(basename $(git rev-parse --show-toplevel))" == "libunwind" ]]; then
       echo "[*] Resetting locally applied patches"
       git reset --hard &>/dev/null || {
         echo "[-] git reset failed"
@@ -44,7 +44,6 @@
   exit 1
 fi
 
-# Change workspace
 readonly LIBUNWIND_DIR="$1"
 
 if [ ! -d "$LIBUNWIND_DIR/.git" ]; then
@@ -83,6 +82,14 @@
   fi
 fi
 
+if [ -z "$ANDROID_API" ]; then
+  ANDROID_API="android-21"
+fi
+if ! echo "$ANDROID_API" | grep -qoE 'android-[0-9]{1,2}'; then
+  echo "[-] Invalid ANDROID_API '$ANDROID_API'"
+  abort 1
+fi
+
 case "$2" in
   arm|arm64|x86|x86_64)
     readonly ARCH="$2"
@@ -94,6 +101,18 @@
     ;;
 esac
 
+# Check if previous build exists and matches selected ANDROID_API level
+# If API cache file not there always rebuild
+if [ -f "$ARCH/libunwind-$ARCH.a" ]; then
+  if [ -f "$ARCH/android_api.txt" ]; then
+    old_api=$(cat "$ARCH/android_api.txt")
+    if [[ "$old_api" == "$ANDROID_API" ]]; then
+      # No need to recompile
+      abort 0 true
+    fi
+  fi
+fi
+
 LC_LDFLAGS="-static"
 
 # For debugging
@@ -180,7 +199,7 @@
 HOST_OS=$(uname -s | tr '[:upper:]' '[:lower:]')
 HOST_ARCH=$(uname -m)
 
-SYSROOT="$NDK/platforms/android-21/arch-$ARCH"
+SYSROOT="$NDK/platforms/$ANDROID_API/arch-$ARCH"
 export CC="$NDK/toolchains/$TOOLCHAIN_S/prebuilt/$HOST_OS-$HOST_ARCH/bin/$TOOLCHAIN-gcc --sysroot=$SYSROOT"
 export CXX="$NDK/toolchains/$TOOLCHAIN_S/prebuilt/$HOST_OS-$HOST_ARCH/bin/$TOOLCHAIN-g++ --sysroot=$SYSROOT"
 export PATH="$NDK/toolchains/$TOOLCHAIN_S/prebuilt/$HOST_OS-$HOST_ARCH/bin":$PATH
@@ -218,6 +237,19 @@
 fi
 
 echo "[*] '$ARCH' libunwind available at '$LIBUNWIND_DIR/$ARCH'"
-cp src/.libs/*.a $ARCH
+cp src/.libs/*.a "$ARCH"
+echo "$ANDROID_API" > "$ARCH/android_api.txt"
+
+# Naming conventions for arm64
+if [[ "$ARCH" == "arm64" ]]; then
+  cd "$ARCH"
+  find . -type f -name "*aarch64*.a" | while read -r libFile
+  do
+    fName=$(basename "$libFile")
+    newFName=$(echo "$fName" | sed "s#aarch64#arm64#")
+    ln -sf "$fName" "$newFName"
+  done
+  cd -
+fi
 
 abort 0