We recently had an interesting user request for our open-source project Maru that I think will be educational to explain here for anyone interested in customizing the Android Open Source Project (AOSP).
For those of you unfamiliar with Maru, it's software that turns your phone into a PC. Maru is built on top of AOSP, and enables AOSP to run lightweight system containers that can run fully interactive desktop environments like Linux.
As part of Maru's build process, we copy over a prebuilt desktop root filesystem image to the target device image. In Android makefile speak, it looks like this:
PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/prebuilts/desktop-rootfs.tar.gz:system/maru/containers/default/rootfs.tar.gz
A user who wanted to run a custom build informed us that he would like to specify his own prebuilt desktop image in his custom build directory, rather than using our default image.
Taking inspiration from how Lineage OS handles overriding the
TARGET_BOOTANIMATION build variable to set a device's custom boot animation, we decided to introduce a variable called
TARGET_DESKTOP_ROOTFS that users can override to specify the path to their own rootfs image.
The problem is, with AOSP's default build script for prebuilt modules, which uses
$(BUILD_PREBUILT), we can't specify files outside of our current module directory. To get around this, we had to customize our build recipe.
Here is what our final
Android.mk ended up looking like:
# This is the current relative path from the AOSP workspace root directory. LOCAL_PATH := $(call my-dir) # TARGET_DESKTOP_ROOTFS can be set in vendor makefiles to override the default # desktop rootfs image for Maru. Note that the path must be relative to the # AOSP workspace root directory, which can easily be done by prefixing the path # with $(LOCAL_PATH). ifeq ($(TARGET_DESKTOP_ROOTFS),) TARGET_DESKTOP_ROOTFS := $(LOCAL_PATH)/desktop-rootfs.tar.gz endif # Here is our module declaration. include $(CLEAR_VARS) LOCAL_MODULE := rootfs.tar.gz LOCAL_MODULE_TAGS := optional LOCAL_MODULE_CLASS := ETC LOCAL_MODULE_PATH := $(TARGET_OUT)/maru/containers/default # Instead of using LOCAL_SRC_FILES and $(BUILD_PREBUILT), we explicitly set up # the rule ourselves so that we can copy a rootfs path from outside of this # directory if a vendor overrides TARGET_DESKTOP_ROOTFS. include $(BUILD_SYSTEM)/base_rules.mk $(LOCAL_BUILT_MODULE): $(TARGET_DESKTOP_ROOTFS) @mkdir -p $(dir $@) @cp $(TARGET_DESKTOP_ROOTFS) $@
Note how we specify our own custom make rule using
$(LOCAL_BUILT_MODULE), rather than using the standard
$(BUILD_PREBUILT) script. Now we can specify any custom commands we want to run. For our module, we just had to copy over the prebuilt image specified by
$(TARGET_DESKTOP_ROOTFS), but you can run whichever commands you'd like.
This approach plays nice with AOSP and will let you depend on your custom modules just like any other stock AOSP module:
PRODUCT_PACKAGES += rootfs.tar.gz
In summary, if you need to customize your build beyond what AOSP's standard build scripts offer, you can do so by creating a custom rule with the target
$(LOCAL_BUILT_MODULE), and specifying your custom build commands in that rule.
I hope you find this pattern useful in your future AOSP hacks!