chiark / gitweb /
Wow, is that a proper Android build system?
[tripe-android] / Makefile
index 1e747137fc2882566c8c0e24dcdb46e8bc086967..0675cd43fe4489fbd34e538cfd358ee8b1b3d0be 100644 (file)
--- a/Makefile
+++ b/Makefile
 
 PACKAGE                         = tripe-android
 VERSION                        := $(shell ./auto-version)
+VSN                     = 1
 
 .SECONDEXPANSION: #sorry
 
 ###--------------------------------------------------------------------------
-### Build parameters.
+### Preliminary magic.
 
-## Where to put the object files.
-OUTDIR                  = out/
+empty                   =
+space                   = $(empty) $(empty)
+comma                   = ,
 
-## Native C compiler.
-CC                      = gcc
-CFLAGS                  = -O2 -g -Wall -pedantic -Werror
+definedp                = $(filter-out undefined,$(origin $1))
+defaulting              = $(if $(call definedp,$1),$($1),$2)
 
-## Native linker.
-LD                      = gcc
-LDFLAGS.so              = -shared
+all::
+
+###--------------------------------------------------------------------------
+### Build parameters.
 
-## External `pkg-config' packages required.
-PKGS                    = mLib catacomb
+abs_srcdir             := $(abspath .)
+hostos                 := $(shell uname -s | tr A-Z a-z)
+hostcpu                        := $(shell uname -m | tr A-Z a-z)
 
-## Java development kit.
-JDKDIR                  = /usr/lib/jvm/default-java
-JDK_PLAT                = linux
-JNI_INCLUDES            = $(JDKDIR)/include $(JDKDIR)/include/$(JDK_PLAT)
+## Of course we have `ccache'.
+CCACHE                 := $(shell \
+       if ccache --version >/dev/null 2>&1; then echo ccache; fi)
 
-## Default arguments for the Java runtime.
-JAVADEFS                =
+## Where to put the object files.
+OUTDIR                  = out
+CONFIGDIR               = $(OUTDIR)/config
 
 ## The Java runtime, for some reason, hardcodes its default for
 ## `java.io.tmpdir', inviting security problems.  If the user has defined a
 ## `TMPDIR', then persuade Java to use it.
-explicit-tmpdir-p       = $(if $(filter-out undefined,$(origin TMPDIR)),t,nil)
+explicit-tmpdir-p       = $(if $(call definedp,TMPDIR),t,nil)
 ifeq ($(explicit-tmpdir-p), t)
   JAVADEFS             += -Djava.io.tmpdir=$(TMPDIR)
 endif
 
 ## Java compiler.
-JAVAC                   = javac $(JAVADEFS)
-JAVAFLAGS               =
+JAVAC                   = javac $(addprefix -J,$(JAVADEFS))
+JAVAFLAGS               = -source 1.6 -target 1.6
 
 ## Scala compiler.
 ##
@@ -76,15 +79,134 @@ ifeq ($(noip-available-p), t)
   NOIP                  = noip
 endif
 ifeq "$(explicit-tmpdir-p),$(noip-available-p)" "t,t"
-  SCALAC                = $(NOIP) fsc $(JAVADEFS)
+  SCALAC                = noip fsc $(JAVADEFS)
+  SCALA                         = noip scala $(JAVADEFS)
   SCALAC_RESET          = $(SCALAC) -reset
 else
   SCALAC                = scalac $(JAVADEFS)
+  SCALA                         = scala $(JAVADEFS) -nc
   SCALAC_RESET          =
 endif
-SCALAFLAGS              = -optimise -Xlint -Xlint:-package-object-classes \
-                               -Yinline-warnings:false
-SCALA_REPL              = $(NOIP) scala $(JAVADEFS)
+SCALAFLAGS              = -optimise -feature -deprecation -Xfatal-warnings \
+                               -Xlint -Xlint:-package-object-classes \
+                               -Yinline-warnings:false -target:jvm-1.6 \
+                               -Yno-load-impl-class
+
+## Basic C toolchain flags.
+CFLAGS                  = -O2 -g -Wall
+LDFLAGS                         = -Wl,-z,defs
+
+## Host toolchain.
+FLAVOURS               += host
+ENV.host                =
+CC.host                         = gcc
+CFLAGS.host             = $(CFLAGS) -fPIC
+LD.host                         = $(CC.host)
+LDFLAGS.host            = $(LDFLAGS)
+CONFIG.host             =
+
+## Host JNI machinery.
+$(CONFIGDIR)/jdkdir.mk:
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,CONFIG)java -XshowSettings:properties 2>&1 | \
+       sed -n 's:^  *java\.home *= *\(.*\)/jre$$:JDKDIR = \1:p' >$@.new
+       $(V_AT)mv $@.new $@
+REALCLEANFILES         += $(CONFIGDIR)/jdkdir.mk
+include $(CONFIGDIR)/jdkdir.mk
+
+JDKPLAT                        := $(shell \
+       case $(hostos) in \
+         (darwin) echo macosx ;; \
+         (*) echo $(hostos) ;; \
+       esac)
+CFLAGS.host            += -I$(JDKDIR)/include -I$(JDKDIR)/include/$(JDKPLAT)
+
+## Android SDK location.
+ANDROID_SDKDIR          = /usr/local/android/sdk
+MINAPI                  = 15
+TARGETAPI               = 23
+TOOLVERSION             = 4.9
+
+## Android ABI definitions.
+ANDROID_ABIS           += armeabi
+GNUARCH.armeabi                 = arm-linux-androideabi
+PLATARCH.armeabi        = arm
+CFLAGS.ndk-armeabi      =
+
+ANDROID_ABIS.inhibit   += armeabi-v7a
+GNUARCH.armeabi-v7a     = arm-linux-androideabi
+PLATARCH.armeabi-v7a    = arm
+CFLAGS.ndk-armeabi-v7a  =
+
+ANDROID_ABIS.inhibit   += arm64-v8a
+GNUARCH.arm64-v8a       = aarch64-linux-android
+PLATARCH.arm64-v8a      = arm64
+MINAPI.arm64-v8a        = 21
+
+ANDROID_ABIS           += x86
+TOOLCHAINDIR.x86        = x86
+GNUARCH.x86             = i686-linux-android
+PLATARCH.x86            = x86
+
+ANDROID_ABIS.inhibit   += x86_64
+TOOLCHAINDIR.x86_64     = x86_64
+GNUARCH.x86_64          = x86_64-linux-android
+PLATARCH.x86_64                 = x86_64
+MINAPI.x86_64           = 21
+
+FLAVOURS               += $(ANDROID_ABIS)
+
+## Build variants.
+VARIANTS               += debug
+AAPTFLAGS.debug                 = --debug-mode \
+       --rename-manifest-package uk.org.distorted.tripe-debug
+KEYSTORE.debug          = debug.keystore
+JARSIGNERFLAGS.debug    = -storepass public -keypass public
+
+VARIANTS               += release
+KEYSTORE.release        = release.keystore
+JARSIGNERFLAGS.release  =
+
+## Android NDK location.
+ANDROID_NDKDIR          = $(ANDROID_SDKDIR)/ndk-bundle
+NDK_HOSTARCH            = $(hostos)-$(hostcpu)
+
+## Android NDK toolchains.
+ndk-sysroot             = \
+       $(ANDROID_NDKDIR)/platforms/android-$(call defaulting,MINAPI.$1,$(MINAPI))/arch-$(PLATARCH.$1)
+ndk-toolchain-bin       = \
+       $(ANDROID_NDKDIR)/toolchains/$(call defaulting,TOOLCHAINDIR.$1,$(GNUARCH.$1))-$(TOOLVERSION)/prebuilt/$(NDK_HOSTARCH)/bin
+ENV.ndk                         = env PATH=$(call ndk-toolchain-bin,$1):$$PATH
+CC.ndk                  = $(GNUARCH.$1)-gcc
+LD.ndk                  = $(CC.ndk)
+CFLAGS.ndk              = $(CFLAGS) -fPIC -D__ANDROID_API__=$(MINAPI) \
+       $(CFLAGS.ndk-$1) \
+       --sysroot=$(call ndk-sysroot,$1) \
+       -isystem $(ANDROID_NDKDIR)/sysroot/usr/include \
+       -isystem $(ANDROID_NDKDIR)/sysroot/usr/include/$(GNUARCH.$1)
+LDFLAGS.ndk             = $(LDFLAGS) -pie \
+       --sysroot=$(call ndk-sysroot,$1)
+CONFIG.ndk              = --host=$(GNUARCH.$1)
+
+## Flavour options.
+tool                    = $(call defaulting,$2.$1,$(call $2.ndk,$1))
+
+## Subject name for debug key.
+DEBUGDN                         = CN=Straylight/Edgeware, L=Cambridge, C=GB
+
+## Android libraries.
+ANDROID_JAR             = \
+       $(ANDROID_SDKDIR)/platforms/android-$(MINAPI)/android.jar
+
+## Scala libraries.
+$(CONFIGDIR)/scaladir.mk:
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,CONFIG)$(SCALA) -J-XshowSettings:properties -help 2>&1 | \
+               sed -n 's:^  *scala\.home *= *:SCALADIR = :p' >$@.new
+       $(V_AT)mv $@.new $@
+include $(CONFIGDIR)/scaladir.mk
+REALCLEANFILES         += $(CONFIGDIR)/scaladir.mk
+SCALA_LIB               = $(SCALADIR)/lib/scala-library.jar
 
 ## Silent-rules is on by default.
 V                       = 0
@@ -102,22 +224,16 @@ V_AT                       = $(V_AT_$V)
 V_AT_0                  = @
 
 ###--------------------------------------------------------------------------
-### External native packages.
-
-PKGS_CFLAGS            := $(foreach p,$(PKGS),$(shell pkg-config --cflags $p))
-PKGS_LIBS              := $(foreach p,$(PKGS),$(shell pkg-config --libs $p))
-
-ALL_CFLAGS              = $(CFLAGS) -fPIC \
-                               $(addprefix -I,$(JNI_INCLUDES)) \
-                               $(PKGS_CFLAGS)
+### Other handy functions.
 
-LIBS                    = $(PKGS_LIBS)
+join-paths              = $(if $(filter /%,$2),$2,$1/$2)
 
-###--------------------------------------------------------------------------
-### Various other tweaks and overrides.
-
-## Hack around https://issues.scala-lang.org/browse/SI-9689
-SCALAFLAGS             += -Yno-load-impl-class
+## Datestamp files.
+STAMPDIR                = $(OUTDIR)/stamp
+stamps                  = $(patsubst %,$(STAMPDIR)/%.$1-stamp,$2)
+stamp-base              = $(patsubst $(STAMPDIR)/%-stamp,%,$1)
+stamp-name              = $(basename $(call stamp-base,$1))
+stamp-type              = $(patsubst .%,%,$(suffix $(call stamp-base,$1)))
 
 ###--------------------------------------------------------------------------
 ### And now we can start building things.
@@ -125,133 +241,315 @@ SCALAFLAGS              += -Yno-load-impl-class
 all::
 .PHONY: all
 
-CLEANFILES              =
 clean::
 .PHONY: clean
 
-###--------------------------------------------------------------------------
-### Native C code.
+realclean:: clean
+.PHONY: realclean
 
-out/%.o: %.c
-       $(call v_tag,CC)mkdir -p $(OUTDIR) && \
-               $(CC) -c $(ALL_CFLAGS) -MMD -o$@ $<
+distclean:: realclean
+.PHONY: distclean
 
-ALL_SOURCES             =
-DISTFILES              += $(ALL_SOURCES)
+###--------------------------------------------------------------------------
+### External native packages.
 
-objects                         = $(patsubst %.c,$(OUTDIR)%$2,$1)
-CLEANFILES             += $(OUTDIR)*.o $(OUTDIR)*.d
+## Definitions.
+EXTERNALS              += adns
+adns_CONFIG             = --disable-dynamic
+
+EXTERNALS              += mLib
+mLib_DEPS               = adns
+mLib_CONFIG             = --enable-static --disable-shared --with-adns
+
+EXTERNALS              += catacomb
+catacomb_DEPS           = mLib
+catacomb_CONFIG                 = --enable-static --disable-shared
+
+EXTERNALS              += tripe
+tripe_DEPS              = mLib catacomb
+tripe_CONFIG            = --without-wireshark --with-adns --with-tunnel=slip
+
+## Machinery.
+ext-stamps              = $(foreach f,$2,$(call stamps,$f,$1))
+
+ext-srcdir              = $(call defaulting,$1_SRCDIR,../$1)
+ext-builddir            = $(OUTDIR)/build.$1/$2
+ext-prefix              = $(OUTDIR)/inst.$1
+ext-absprefix           = $(abs_srcdir)/$(call ext-prefix,$1)
+
+ext-stamp-srcdir        = $(call ext-srcdir,$(call stamp-name,$1))
+ext-stamp-builddir      = \
+       $(call ext-builddir,$(call stamp-type,$1),$(call stamp-name,$1))
+ext-stamp-absprefix     = $(call ext-absprefix,$(call stamp-type,$1))
+ext-stamp-tool          = $(call tool,$(call stamp-type,$1),$2)
+
+EXTSTAMPS               = $(call ext-stamps,$(EXTERNALS),$(FLAVOURS))
+
+$(EXTSTAMPS): \
+               $(STAMPDIR)/%-stamp: \
+               $$(call ext-stamps, \
+                       $$($$(call stamp-name,$$@)_DEPS), \
+                       $$(call stamp-type,$$@))
+       $(V_AT)mkdir -p $(STAMPDIR)
+       $(V_AT)rm -rf $(call ext-stamp-builddir,$@)
+       $(V_AT)mkdir -p $(call ext-stamp-builddir,$@)
+       cd $(call ext-stamp-builddir,$@) && \
+       $(call ext-stamp-tool,$@,ENV) \
+               $(call join-paths,../../..,$(call ext-stamp-srcdir,$@))/configure \
+               --prefix=$(call ext-stamp-absprefix,$@) \
+               $(call ext-stamp-tool,$@,CONFIG) \
+               $($(call stamp-name,$@)_CONFIG) \
+               CC="$(CCACHE) $(call ext-stamp-tool,$@,CC)" \
+               CFLAGS="$(call ext-stamp-tool,$@,CFLAGS) -I$(call ext-stamp-absprefix,$@)/include" \
+               LD="$(call ext-stamp-tool,$@,LD)" \
+               LDFLAGS="$(call ext-stamp-tool,$@,LDFLAGS) -L$(call ext-stamp-absprefix,$@)/lib" \
+               PKG_CONFIG="pkg-config --static" \
+               PKG_CONFIG_LIBDIR=$(call ext-stamp-absprefix,$@)/lib/pkgconfig
+       $(call ext-stamp-tool,$@,ENV) \
+               $(MAKE) -C$(call ext-stamp-builddir,$@)
+       $(call ext-stamp-tool,$@,ENV) \
+               $(MAKE) -C$(call ext-stamp-builddir,$@) install
+       $(V_AT)touch $@
+
+$(foreach f,$(FLAVOURS),$(foreach e,$(EXTERNALS),clean-$e.$f)): clean-%:
+       rm -f $(STAMPDIR)/$*-stamp
+       rm -rf $(call ext-stamp-builddir,$*)
+.PHONY: $(foreach f,$(FLAVOURS),$(foreach e,$(EXTERNALS),clean-$e.$f))
+$(foreach f,$(FLAVOURS),clean-inst.$f): clean-inst.%:
+       rm -rf $(call ext-prefix,$*)
+.PHONY: $(foreach f,$(FLAVOURS),clean-inst.$f)
+
+$(foreach f,$(FLAVOURS),realclean-$f): realclean-%: \
+               $$(foreach e,$$(EXTERNALS),clean-$$e.$$*) \
+               clean-inst.$$*
+realclean:: $(foreach f,$(FLAVOURS),realclean-$f)
 
 ###--------------------------------------------------------------------------
-### Java classes.
-
-## Java and Scala source files turn into multiple `.class' files with
-## unpredictable names.  Rather than try to guess stable outputs for these
-## sources, we make artificial `timestamp' files and uses these in our
-## dependencies.
-CLASSDIR                = $(OUTDIR)cls/
-stamps                  = $(patsubst %,$(OUTDIR)%.stamp,$1)
-
-clean::; rm -rf $(CLASSDIR)
-CLEANFILES             += $(OUTDIR)*.stamp
-
-## Compiling actual Java code.  Note that this confuses the resident Scala
-## compiler, so we have to reset it here.
-CLSISH                 += java
-$(OUTDIR)%.stamp: %.java
-       $(call v_tag,JAVAC)mkdir -p $(CLASSDIR) && \
-               $(JAVAC) -d $(CLASSDIR) -cp $(CLASSDIR) $(JAVAFLAGS) $< && \
-               echo built >$@
-       $(V_AT)$(SCALAC_RESET)
-
-## Compiling Scala code.
-CLSEXT                 += scala
-$(OUTDIR)%.stamp: %.scala
-       $(call v_tag,SCALAC)mkdir -p $(CLASSDIR) && \
-               $(SCALAC) -d $(CLASSDIR) -cp $(CLASSDIR) $(SCALAFLAGS) $< && \
-               echo built >$@
+### Our own native programs.
+
+## Fetching package configuration.
+PKG_CONFIG              = pkg-config --static
+PKGS                    = catacomb mLib
+
+PCOPT.CFLAGS            = --cflags
+PCOPT.LIBS              = --libs
+
+pkg-config              = $(shell \
+       env PKG_CONFIG_LIBDIR=$(call ext-prefix,$1)/lib/pkgconfig \
+               $(PKG_CONFIG) $(PCOPT.$3) $2)
+
+## Definitions.
+APKLIBS                        += libtripe.so
+libtripe.so_SRCS        = jni.c
+libtripe.so_EXTS        = mLib catacomb tripe
+libtripe.so_CFLAGS      = -I$(call ext-prefix,$1)/include \
+                               -I$(call ext-srcdir,tripe)/common \
+                               -I$(call ext-srcdir,tripe)/priv \
+                               -I$(call ext-srcdir,tripe)/server \
+                               -I$(call ext-builddir,$1,tripe)/config \
+                               $(call pkg-config,$1,mLib,CFLAGS) \
+                               $(call pkg-config,$1,catacomb,CFLAGS)
+libtripe.so_LIBS        = $(call ext-builddir,$1,tripe)/server/libtripe.a \
+                               $(call ext-builddir,$1,tripe)/priv/libpriv.a \
+                               $(call ext-builddir,$1,tripe)/common/libcommon.a \
+                               -L$(call ext-prefix,$1)/lib \
+                               $(call pkg-config,$1,catacomb,LIBS) \
+                               $(call pkg-config,$1,mLib,LIBS)
+
+## Machinery for compiling.
+objdir                  = $(OUTDIR)/obj.$1
+objects                         = $(patsubst %.c,$(call objdir,$1)/%$3,$2)
+
+apklib-objects          = $(call objects,$1,$($2_SRCS),$3)
+obj-base                = $(patsubst $(OUTDIR)/obj.%.o,%,$1)
+obj-type                = $(patsubst %/,%,$(dir $(call obj-base,$1)))
+obj-name                = $(notdir $(call obj-base,$1))
+obj-tool                = $(call tool,$(call obj-type,$1),$2)
+
+define obj-rule
+$1_OBJS                        := $$(foreach f,$$(FLAVOURS),\
+                               $$(call objects,$$f,$$($1_SRCS),.o))
+DEPFILES               += $$(foreach f,$$(FLAVOURS),\
+                               $$(call objects,$$f,$$($1_SRCS),.d))
+$$($1_OBJS): $$(OUTDIR)/obj.%.o: \
+               $$$$(call obj-name,$$$$@).c \
+               $$$$(call stamps,$$$$(call obj-type,$$$$@),$$$$($1_EXTS))
+       $$(V_AT)mkdir -p $$(dir $$@)
+       $$(call v_tag,CC)$$(call obj-tool,$$@,ENV) \
+       $$(call obj-tool,$$@,CC) -c -o$$@ -MD \
+               $$(call obj-tool,$$@,CFLAGS) \
+               $$(call $1_CFLAGS,$$(call obj-type,$$@)) \
+               $$<
+endef
+$(foreach a,$(APKLIBS), $(eval $(call obj-rule,$a)))
+
+CLEANFILES             += $(OUTDIR)/obj.*/*.o $(OUTDIR)/obj.*/*.d
+
+## Machinery for linking.
+JNIDIR.host             = $(OUTDIR)
+JNIDIR.ndk              = $(OUTDIR)/pkg/lib/$1
+
+define apklib-rule
+INSTFILES              += $$(call tool,$1,JNIDIR)/$2
+$$(call tool,$1,JNIDIR)/$2: $$(call objects,$$f,$$($2_SRCS),.o)
+       $$(V_AT)mkdir -p $$(dir $$@)
+       $$(call v_tag,LD)$$(call tool,$1,ENV) \
+       $$(call tool,$1,LD) -o$$@ \
+               $$(call tool,$1,LDFLAGS) -shared \
+               $$^ \
+               $$(call $2_LIBS,$1)
+endef
+
+$(foreach f,$(FLAVOURS), \
+$(foreach a,$(APKLIBS), \
+       $(eval $(call apklib-rule,$f,$a))))
+
+CLEANFILES             += $(OUTDIR)/pkg/lib/*/lib*.so $(OUTDIR)/lib*.so
 
 ###--------------------------------------------------------------------------
-### Native-code libraries.
+### Java and Scala building.
 
-SHLIBS                 += toy
-toy_SOURCES             = jni.c
+JARDIR                  = $(OUTDIR)/jar
+CLASSDIR                = $(OUTDIR)/cls
 
-shlibfile               = $(patsubst %,$(OUTDIR)lib%.so,$1)
-SHLIBFILES              = $(call shlibfile,$(SHLIBS))
-TARGETS                        += $(SHLIBFILES)
-ALL_SOURCES            += $(foreach l,$(SHLIBS),$($l_SOURCES))
+## External libraries we need to adopt.  Grab them and feed them to `dx'
+## separately, because they take aaaaages and we don't want to have to do
+## this on every incremental build.
+JARS                    = $(SCALA_LIB)
 
-$(SHLIBFILES): $(OUTDIR)lib%.so: $$(call objects,$$($$*_SOURCES),.o)
-       $(call v_tag,LD)$(LD) $(LDFLAGS.so) -o$@ $^ $(LIBS)
+DEXJARS                         =
+define jar-rule
+DEXJARS                        += $$(JARDIR)/$$(notdir $1)
+$$(JARDIR)/$$(notdir $1): $1
+       $$(V_AT)mkdir -p $$(JARDIR)/
+       $$(V_AT)rm -f $$@ $$(basename $$@)-new.jar
+       $$(call v_tag,DX)dx --dex --output=$$(basename $$@)-new.jar $1
+       $$(V_AT)mv $$(basename $$@)-new.jar $$@
+endef
+$(foreach j,$(JARS), $(eval $(call jar-rule,$j)))
 
-###--------------------------------------------------------------------------
-### Java classes.
+REALCLEANFILES         += $(JARDIR)/*.jar
 
 ## Writing things out longhand is tedious.  `CLASSES' is a list of entries of
 ## the form `SRC[:DEP,...]', saying that `SRC.ext', being a source file
-## capable of being built into `.class' files and setting `SRC.stamp', should
-## be so built, and that it depends on other similar sources named DEP having
-## been so built.
+## capable of being built into `.class' files and setting `SRC.class-stamp',
+## should be so built, and that it depends on other similar sources named DEP
+## having been so built.
 CLASSES                        += util
 CLASSES                        += sys:util
 CLASSES                        += admin:sys,util
 CLASSES                        += tar:util
-CLASSES                        += keys:tar,sys,util
-CLASSES                        += main:sys
+CLASSES                        += progress:sys,util
+CLASSES                        += keys:progress,tar,sys,util
+CLASSES                        += terminal:progress,sys,util
+CLASSES                        += R
+CLASSES                        += toy-activity:R
+
+## Building class files.
+$(STAMPDIR)/%.class-stamp: %.java
+       $(V_AT)mkdir -p $(CLASSDIR)/
+       $(call v_tag,JAVAC)$(JAVAC) $(JAVAFLAGS) \
+               -bootclasspath $(ANDROID_JAR) \
+               -d $(CLASSDIR) -cp $(CLASSDIR) \
+               $<
+       $(V_AT)$(SCALAC_RESET)
+       $(V_AT)touch $@
+$(STAMPDIR)/%.class-stamp: %.scala
+       $(V_AT)mkdir -p $(CLASSDIR)/
+       $(call v_tag,SCALAC)$(SCALAC) $(SCALAFLAGS) \
+               -javabootclasspath $(ANDROID_JAR) \
+               -bootclasspath $(SCALA_LIB) \
+               -d $(CLASSDIR) -cp $(CLASSDIR) \
+               $<
+       $(V_AT)touch $@
 
 ## Machinery for parsing the `CLASSES' list.
-COMMA                   = ,
 class-name              = $(firstword $(subst :, ,$1))
-class-deps              = $(subst $(COMMA), ,$(word 2,$(subst :, ,$1)))
-
-CLASS_NAMES             = $(foreach c,$(CLASSES),$(call class-name,$c))
+class-deps              = $(subst $(comma), ,$(word 2,$(subst :, ,$1)))
 
-all:: $(call stamps,$(CLASS_NAMES))
+CLASSNAMES              = $(foreach c,$(CLASSES),$(call class-name,$c))
+CLASSSTAMPS             = $(call stamps,class,$(CLASSNAMES))
 
-$(foreach c,$(CLASSES),$(eval $(call stamps,$(call class-name,$c)): \
-       $(call stamps,$(call class-deps,$c))))
+$(foreach c,$(CLASSES), \
+$(eval $(call stamps,class,$(call class-name,$c)): \
+       $(call stamps,class,$(call class-deps,$c))))
 
-DISTFILES              += $(foreach c,$(CLASSES),\
-                               $(foreach e,$(CLSEXT),\
-                                 $(wildcard $(call class-name,$c).$e)))
+CLEANFILES             += $(CLASSSTAMPS)
+clean::; rm -rf $(OUTDIR)/cls
 
 ###--------------------------------------------------------------------------
-### Distribution arrangements.
-
-DISTFILES              += COPYING
-DISTFILES              += Makefile
-DISTFILES              += auto-version
-
-distdir                         = $(PACKAGE)-$(VERSION)
-DISTTAR                         = $(distdir).tar.gz
-
-distdir:
-       rm -rf $(OUTDIR)$(distdir)
-       mkdir $(OUTDIR)$(distdir)
-       echo $(VERSION) >$(OUTDIR)$(distdir)/RELEASE
-       set -e; for i in $(DISTFILES); do \
-         case $$i in */*) mkdir -p $(OUTDIR)$(distdir)/$${i%/*} ;; esac; \
-         cp $$i $(OUTDIR)$(distdir)/; \
-       done
-.PHONY: distdir
-
-dist: distdir
-       set -e; cd $(OUTDIR); tar chozf ../$(DISTTAR) $(distdir)
-       rm -rf $(distdir)
-.PHONY: dist
+### Android building machinery.
+
+VPATH                  += $(OUTDIR)/src
+CLEANFILES             += $(OUTDIR)/*.apk
+
+AAPTFLAGS               = \
+       --min-sdk-version $(MINAPI) --target-sdk-version $(TARGETAPI) \
+       --version-name "$(VERSION)" --version-code $(VSN)
+
+$(OUTDIR)/src/R.java: AndroidManifest.xml
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,AAPT)aapt package $(AAPTFLAGS) \
+               -M AndroidManifest.xml -S res/ -I $(ANDROID_JAR) \
+               -J $(dir $@) --generate-dependencies
+CLEANFILES             += $(OUTDIR)/src/R.java $(OUTDIR)/src/R.java.d
+-include $(OUTDIR)/src/R.java.d
+
+BINS                    = catsign key pathmtu
+CLEANFILES             += $(INSTFILES)
+define bin-rule
+INSTFILES              += $$(OUTDIR)/pkg/assets/bin/$1/$2
+$$(OUTDIR)/pkg/assets/bin/$1/$2: $$$$(call ext-stamps,$$$$(EXTERNALS),$1)
+       $$(V_AT)mkdir -p $$(dir $$@)
+       $$(call v_tag,CP)cp $$(call ext-prefix,$1)/bin/$2 $$@
+endef
+$(foreach f,$(FLAVOURS), \
+$(foreach b,$(BINS), \
+       $(eval $(call bin-rule,$f,$b))))
+
+DISTCLEANFILES         += debug.keystore
+debug.keystore:
+       $(call v_tag,KEYTOOL)keytool -genkeypair -alias debug \
+               -keyalg RSA -keysize 3072 \
+               -dname "$(DEBUGDN)" -validity 10000 \
+               -keystore $@ -storetype PKCS12 \
+               -storepass public -keypass public
+
+INSTFILES              += $(OUTDIR)/pkg/classes.dex
+$(OUTDIR)/pkg/classes.dex: $(CLASSSTAMPS) $(DEXJARS)
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,DX)dx --dex --output=$@ $(CLASSDIR) $(JARDIR)
+
+$(foreach v,$(VARIANTS),$(OUTDIR)/tripe-$v.unsigned.apk): \
+$(OUTDIR)/tripe-%.unsigned.apk: $(INSTFILES)
+       $(call v_tag,AAPT)aapt package -f $(AAPTFLAGS) $(AAPTFLAGS.$*) \
+               -M AndroidManifest.xml -S res/ -I $(ANDROID_JAR) \
+               -F $@ $(OUTDIR)/pkg/
+
+$(foreach v,$(VARIANTS),$(OUTDIR)/tripe-$v.signed.apk): \
+$(OUTDIR)/tripe-%.signed.apk: $(OUTDIR)/tripe-%.unsigned.apk $$(KEYSTORE.$$*)
+       $(call v_tag,JARSIGN)jarsigner -keystore $(KEYSTORE.$*) \
+               $(JARSIGNERFLAGS.$*) \
+               -signedjar $@ $< $(call defaulting,KEYALIAS.$*,$*)
+
+$(foreach v,$(VARIANTS),$(OUTDIR)/tripe-$v.apk): \
+$(OUTDIR)/tripe-%.apk: $(OUTDIR)/tripe-%.signed.apk
+       $(call v_tag,ZIPALGN)zipalign $(ZIPALIGNFLAGS.$*) -f 4 $< $@
+
+$(VARIANTS): %: $(OUTDIR)/tripe-%.apk
+.PHONY: $(VARIANTS)
 
 ###--------------------------------------------------------------------------
 ### Finishing touches.
 
-all:: $(TARGETS)
+all:: debug
 
-clean::; rm -f $(CLEANFILES) $(TARGETS)
+clean::; rm -f $(CLEANFILES)
+realclean::; rm -f $(REALCLEANFILES)
 
-repl: $(TARGETS)
-       $(SCALA_REPL) -cp $(CLASSDIR) -Djava.library.path=$(OUTDIR)
-.PHONY: repl
+t:; : $(show)
+.PHONY: t
 
--include $(call objects,$(ALL_SOURCES),.d)
+-include $(DEPFILES)
 
 ###----- That's all, folks --------------------------------------------------