LineageOS 15.1 for Axon 7 (A2017G): Using Docker to Build from Source

Image for post
Image for post

Going through my clutter of gadgets, I came across an old Android ZTE Axon 7 (A2017G) phone that was running Nougat. It’s been lying around for a while, and I was looking for (as usual) an excuse to kick off another one of my tech-discovery projects.

I was reading up on LineageOS — a fork of CyanogenMod. CyanogenMod’s roots originate from the Android Open Source Platform (AOSP).

I could just download an existing Lineage build zip and flash it onto the Axon…but that’s no fun.

What was more of an appealing option, was building the OS from source using base LineageOS 15.1 repo and the official ZTE Axon 7 git device-specific source branch (LineageOS 15.1 Axon Tree). There’d more satisfaction from installing a build I’ve generated vs somebody else’. It would also be a stepping stone into the sphere of Custom ROMs and Android tweaking.

Going the extra mile, I wanted to throw docker into the equation, see if I could come up with a portable docker “template” to support alternate Android builds.

There were a few reasons for choosing LineageOS, and this particular version (v15.1).

The first of these was that version 15.1 is based on Android Oreo, where the system-as-root partitioning had yet to be introduced. One other reason, was that Lineage 15.1 was an already bedded down code-base for this device. Versions beyond 15.1, were not listed for the Axon at https://wiki.lineageos.org.

This article is an exploration piece which aims to:

  • Go through the process of building LineageOS15.1 (an Android Oreo 8.1.x based ROM) for the Axon 7 A2017G using a docker container derived from a docker image I built to support this process.
  • Touch on topic Qualcomm Snapdragon 820 MSM8996 requirements for unlocking the bootloader and enabling Fastboot for certain firmware variants.
  • Outline the procedure for installing/flashing the build onto the physical device.

Axon 7 (A2017G) — Key Hardware Details

Here are some of the key hardware details for the Axon 7 (A2017G) variant:

  • Chipset/SoC: Qualcomm Snapdragon 820 MSM8996
  • CPU: 2x Dual Core Kryo (total of 4 cores)
  • Architecture: 64 bit armv8-a
  • GPU: Qualcomm Adreno 530
  • Built in Storage Type: UFS 2.0
  • RAM: 4GB LPDDR4

Further hardware details can be sourced from here.

Host Environment Requirements

Using a Linux host running docker is preferable, and likely to give a better chance of successfully building LineageOS15.1 using the docker image.

The whole process was tested on a VMWare Ubuntu 19.10 VM. I refer to this as the Host throughout the article.

The recommended amount of RAM for the build process is 16GB, and ~ 200GB of free space.

The code sync itself takes up around 65GB of space, and after factoring in the output from the build components, you’re looking at around ~160GB.

Build and Configure Docker Image

The components required to build the docker image can be found within the following Git repo. The README.md within the repo, contains further details on usage/components.

The commands below clone the repo to target: $HOME/docker_repo/dev-docker-android.

$ cd $HOME
$ mkdir docker_repo

$ cd $HOME/docker_repo/

$ git clone \
https://github.com/tonys-code-base/dev-docker-android.git

A git identity is required for cloning the LineageOS15.1 repo source. You can configure the Dockerfile to reflect your preferred git identity. The existing docker image treats the git profile below as "system wide" within containers derived from the image (i.e. target location within the container/image at/etc/gitconfig).

Edit the local repo file and update to reflect your git identity:

gitconfig: $HOME/docker_repo/dev-docker-android/gitconfig

[user]
name = Your Name
email = me@example.com

Run the following to create the image android-dev :

$ cd $HOME/docker_repo/dev-docker-android

$ docker build --build-arg userid=$(id -u) \
--build-arg groupid=$(id -g) \
--build-arg username=$(id -un) -t android-dev .

Create Host Directories for LineageOS 15.1 Build

Just about all of the activities involved in the build process will be performed from within a docker container. To ensure the project components are persisted on the host (and not purged for cases such as container/image pruning), a common host:container directory should be established. This directory can later be mounted when running the docker image.

For this article, the location of this directory on the host machine is assumed to be at:

$ mkdir $HOME/lineage15

A similar setup to that of the above is recommended for accessing/sharing non-specific build/project components. This is assumed to be:

$ mkdir $HOME/lineage_dloads

Run Docker Container

  • We spin up a container using our image as follows:
$ sudo docker run -it --rm --privileged \
--cap-add=ALL \
-v $HOME/lineage15:/lineage15 \
-v $HOME/lineage_dloads:/lineage_dloads \
android-dev
  • This creates a container with the following host:container directory mappings:
|-------------------------------------------|
| host | container |
-----------------------|--------------------|
| $HOME/lineage15 | /lineage15 |
| $HOME/lineage_dloads | /lineage_dloads |
--------------------------------------------|

Initialise and Sync LineageOS15.1 Repo

  • Change into our project directory. This will be the top level of our build (in Android’s official documentation, it is often referred to as $ANDROID_BUILD_TOP).
$ cd /lineage15
  • Run repo init, to initialise the repository. The value of lineage-15.1 supplied for parameter -b, ensures we are initialising for the correct Lineage source branch.
$ repo init -u https://github.com/LineageOS/android.git -b \
lineage-15.1

You will be requested to confirm your git identify, which should match the details supplied in your docker gitconfig file. Respond as you see fit for repository appearance preferences.

  • To download/sync local repo from the remote, run the following:
$ cd /lineage15
$ repo sync
  • Take a long break. There is around 65GB of code to download.

One the download is complete, there are some slight modifications to make before continuing.

Add Axon 7 Device to the Build

By now your top level project folder, $ANDROID_BUILD_TOP (/lineage15) should contain the Lineage15.1 code branch. We still need to fetch the device specific repo.

The file /lineage15/vendor/lineage/vendorsetup.sh that was fetched from the 15.1 base repo contains a reference to a remote file path (https://raw.githubusercontent.com/LineageOS/hudson/master/lineage-build-targets) from which device target build details are sourced. This remote file no longer has an entry for Lineage15.1, so for the purposes of consistency, we can replace contents of existing file /lineage15/vendor/lineage/vendorsetup.sh to point to /lineage-build-targets.txt:

for combo in $(cat /lineage-build-targets.txt | sed -e 's/#.*$//' | grep lineage-15.1 | awk '{printf "lineage_%s-%s\n", $1, $2}')
do
add_lunch_combo $combo
done
  • /lineage-build-targets.txt was included as part of the docker image build and contains the the entry for our Axon-specific Lineage15.1 build.

We need to ensure that we have all the appropriate make/config files & their dependencies. These include a directory structure for our device configuration ($ANDROID_BUILD_TOP/device/<company-name>/<device-name>).

Luckily, the folks at over at Axon 7 LineageOS Wiki have already included these in the Axon-specific repo. If you’re interested in further details configuring a product/device, refer to the Android documentation.

The makefiles (.mk) for the Axon are fetched/generated as part of the scripts mentioned in the quote below. The response to option we provide to the breakfast command (later on) fetches device-specific code from github.

Note: When script /lineage15/build/envsetup.sh is invoked, it calls/includes the script at /lineage15/vendor/lineage/build/envsetup.sh

As of now, an ls on /lineage15/device shows the following:

/lineage15/device
├── common
├── generic
├── google
├── lineage
├── qcom
└── sample

Let’s run the necessary commands/scripts to generate/fetch the Axon 7 specific code into <company-name>/<device-name> above:

  • Start by activating our environment:
$ cd /lineage15
$ source build/envsetup.sh
  • The envsetup.sh imports many functions, commands and environment variables that are required for the build.
  • To choose the product we wish to build for:
$ breakfast
  • breakfast generates the following output:
including vendor/lineage/vendorsetup.sh

You're building on Linux

Lunch menu... pick a combo:
1. full-eng
2. lineage_axon7-userdebug
Which would you like? [aosp_arm-eng]
  • Choose option 2 to generate/fetch the Axon 7 specific code.
  • Once done, part of the output log from breakfast is shown below. You will notice in the output that the Axon 7 specific code is retrieved from remote repo https://github.com/LineageOS/android_device_zte_axon7, and the branch being checked out is lineage-15.1.
build/core/product_config.mk:238: *** Can not locate config makefile for product "lineage_axon7".  Stop.
Device axon7 not found. Attempting to retrieve device repository from LineageOS Github (http://github.com/LineageOS).
Found repository: android_device_zte_axon7
Default revision: lineage-15.1
Checking branch info
Checking if device/zte/axon7 is fetched from android_device_zte_axon7
...
Using default branch for android_device_zte_axon7
Syncing repository to retrieve project.
...
repo sync has finished successfully.
Repository synced!
Looking for dependencies in device/zte/axon7
Adding dependencies to manifest
Checking if device/qcom/common is fetched from android_device_qcom_common
Adding dependency: LineageOS/android_device_qcom_common -> device/qcom/common
...
  • Once complete, the final output provides a summary of the target we’re building
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=8.1.0
LINEAGE_VERSION=15.1-20200629-UNOFFICIAL-axon7
TARGET_PRODUCT=lineage_axon7
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_PLATFORM_VERSION=OPM1
TARGET_BUILD_APPS=
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=kryo
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv7-a-neon
TARGET_2ND_CPU_VARIANT=kryo
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-5.3.0-61-generic-x86_64-with-Ubuntu-19.10-eoan
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=OPM7.181205.001
OUT_DIR=/lineage15/out
AUX_OS_VARIANT_LIST=
============================================
  • You will also notice that path /lineage15/device/<company-name>/<device-name> has been created at:
/lineage15/device/zte
└── axon7

and that some .mk files exist:

$ ls /lineage15/device/zte/axon7/*.mk
Android.mk BoardConfig.mk device.mk lineage.mk

Extracting Proprietary Blobs

Proprietary blobs are files/modules that are required for the build to successfully complete.

If you are already running Lineage on your Axon 7, then you can use your device to extract the blobs.

If you do not have Lineage running on the device, you will need to have access to a flashable zip from which you can extract the blobs.

Procedures for each of the above scenarios is described below.

To extract blobs from an Axon 7 with LineageOS already installed and running:

  • Exit the docker container.
  • Attach your Axon 7 to your host via USB.
  • Ensure that the ADB Debug Bridge is enabled via your device settings: Developer Options -> Debugging -> Android Debugging.
  • Run the following to bring up the docker container:
$ sudo docker run -it --rm --privileged \
--cap-add=ALL \
-v $HOME/lineage15:/lineage15 \
-v $HOME/lineage_dloads:/lineage_dloads \
android-dev
  • Once at a prompt within the container, change directory to top level project folder and activate the environment:
$ cd /lineage15
$ source build/envsetup.sh
  • Change directory so that we are in the Axon 7 device tree:
$ cd /lineage15/device/zte/axon7/
  • Execute the following script to pull the blobs from your device:
$ ./extract-files.sh

The method that will be described is based on a flashable zip that is classified as Blocked Based:

Block-based OTA: the content of the system partition is stored inside of an .dat/.dat.br file as binary data.

If your zip has no system folder or it is nearly empty and a file named system.transfer.list exists at the root level, then what you have is a block-based OTA

  • Download lineage-15.1–20200622-UNOFFICIAL-axon7.zip and save on the host at location $HOME/lineage_dloads, remembering that this host directory is mapped/mounted inside the container at /lineage_dloads.
  • From within the docker container, check to ensure this in fact is a Block-based zip by checking that the system* suffixes are dat/dat.br, the system folder is nearly empty and that system.transfer.list exists:
$ cd /lineage_dloads
$ unzip -l lineage-15.1-20200622-UNOFFICIAL-axon7.zip | \
grep -i -e system -e vendor

...
system.new.dat.br
system.transfer.list
system/build.prop
...
vendor.new.dat.br
vendor.transfer.list
...
  • We also have a similar scenario for the vendor* files, and they will also need to be included when extracting the blobs.
  • Create working directory for the extract process:
$ cd /lineage15
$ mkdir system_dump
$ cd system_dump
  • Unzip the required system* & vendor* files:
$ unzip /lineage_dloads/lineage-15.1-20200622-UNOFFICIAL-axon7.zip  system.transfer.list system.new.dat*

$ unzip /lineage_dloads/lineage-15.1-20200622-UNOFFICIAL-axon7.zip vendor.transfer.list vendor.new.dat*
  • Decompress the output files. This method uses brothli, which is already include as part of the Dockerfile:
$ brotli --decompress --output=system.new.dat system.new.dat.br
$ brotli --decompress --output=vendor.new.dat vendor.new.dat.br
  • Convert into mountable .img files:
$ python /usr/local/bin/sdat2img.py system.transfer.list system.new.dat  system.img

$ python /usr/local/bin/sdat2img.py vendor.transfer.list vendor.new.dat vendor.img
  • Note, the script used (sdat2img.py) for converting the vendor/system .new.dat files to mountable images, was originally sourced from this git repo.
  • Create mount point and mount system.img:
$ mkdir system
$ sudo mount system.img system/
  • Remove existing symbolic link on the mounted image and create mount point for our vendor.img, then mount it:
$ sudo rm system/vendor
$ sudo mkdir system/vendor
$ sudo mount vendor.img system/vendor/
  • Change to the location containing extract-files.sh and execute the script, passing in the location from which the blobs are to be extracted (system_dump/)
$ cd /lineage15/device/zte/axon7
$ sudo ./extract-files.sh /lineage15/system_dump/
  • Unmount images and remove the system_dump directory:
$ sudo umount /lineage15/system_dump/system/vendor/
$ sudo umount /lineage15/system_dump/system/
$ rm -fr /lineage15/system_dump

Build LineageOS15.1

  • Kick off the build by running:
$ cd /lineage15
$ brunch axon7
  • The build can take up to several hours and should be monitored intermittently for errors.
  • Once the build has completed successfully, the output should display the location and name of the LineageOS15.1 flashable zip produced by the build:
Package Complete: 
/lineage15/out/target/product/axon7/lineage-15.1-<YYYYMMDD>-UNOFFICIAL-axon7.zip

#### build completed successfully (04:42:23 (hh:mm:ss)) ####

The file has the following naming convention : lineage-15.1-<YYYYMMDD>-UNOFFICIAL-axon7.zip.

Axon 7 A2017G — State Prior to Flashing LineageOS15.1

Note the instructions below were tested on an Axon 7, A2017G variant. The testing was carried out on a device that was in the following state prior to flashing the build onto the device:

  • Android Version — Nougat 7.1.1
  • Build Number — ZTE A2017GV1.2.0B10
  • Bootloader — LOCKED
  • SanDisk 32GB SD Card for storing flash files

Unlock Bootloader & Enable Fastboot

NOTE: The procedure that follows will perform a FORMAT/WIPE of all user and system data. Make a FULL backup before continuing.

Before commencing, ensure you have enabled ADB Debugging on your phone and that OEM Unlocking is enabled in your phone Developer Options - Advanced Settings.

Reboot into the bootloader using:

$ adb reboot bootloader
  • If the above command worked successfully, you should see the bootloader screen appear on your phone. If so, the below command's output should list your device:
$ fastboot devices -l
...
488f9532 fastboot
  • Run the following to unlock the bootloader:
$ fastboot oem unlock
  • Your phone screen should then ask you to confirm the details, using your phone’s Volume Up/Down and Power buttons.

If you’ve gotten this far, you can skip to the section “Install Custom Recovery (TWRP)”.

If you still can’t boot into the bootloader, then it's likely that updates to partitions residing on the Qualcomm chipset embedded storage (aka UFS), would need to be updated. Generally, the partitions affected are named aboot and/or fbop . This method requires booting into Emergency Download Mode (EDL), and flashing these partitions using a proprietary programmer, which utilises the firehose protocol to access the Snapdragon 820 MSM8996 SoC flash memory. These programmers have made their way onto various online sites and come packaged in toolkits for unlocking, as well as full EDL image downloads.

Below are the physical partitions and files names which are manipulated/overwritten by the various toolkits.

filename="emmc_appsboot.mbn" label="aboot" 
filename="fastboot.img" label="fbop"

This is a topic for another discussion, but I’d suggest you head over to XDA and familiarise yourself with topics/threads related to bootloader unlocking. You will find references to “XiaoMiFlash/MiFlash” and several “Toolkits” to help with the unlocking of the bootloader.

Install Custom Recovery (TWRP)

In order to be able to prepare the device for LineageOS15.1, we’ll need a custom recovery.

  • Head over to https://twrp.me/zte/zteaxon7.html and download the latest TWRP .img recovery file for the Axon.
  • Ensure you are in fastboot mode, if you are not, then run adb reboot bootloader to reboot the device into fastboot mode.
  • Run the following to ensure your device is listed:
$ fastboot devices -l
...
488f9532 fastboot
  • Flash the TWRP recovery .img using the following (the below assumes an img file named twrp-3.3.1-0-ailsa_ii.img):
$ fastboot flash recovery twrp-3.3.1-0-ailsa_ii.img

Once the flash is complete you will need to boot into Recovery Mode, otherwise the default recovery.img for the stock ROM will load.

  • Boot into TWRP recovery by using the phone’s “Volume Up/Down” to toggle between the options near the top of the phone’s screen until you see “Recovery Mode”, then press “Power” button to trigger a boot into TWRP.
  • From TWRP, perform a “Wipe--> "Advanced Wipe" and tick "Dalvik/ART Cache", "Data", "Cache" and "System". Perform a "format data", if you had encrypted storage.
  • You will need to stay in recovery mode for the next section ("Installing a Universal Bootstack")
  • If you accidentally exit recovery mode boot back into recovery as follows: a) Long pressing Power to switch off the device.
    b) Once the device has switched off, simultaneously hold down Power + Volume Up.
    c) When the ZTE logo appears, release the Power Button and continue holding the.Volume Up.
    d) Once the TWRP recovery screen appears, release the Volume Up button.

Install Universal Oreo Bootstack, Modem and LineageOS15.1

Before we can flash our build of LineageOS15.1, we will need a compatible bootstack (elements of the firmware, i.e. in this case, the phone’s modem and bootloader) that will support loading LineageOS15.1.

You can download a Universal Oreo Bootstack and modem as described in the following XDA thread.

I downloaded the following from links posted in the above thread:

Save the above files, along with the flashable zip from your build’s output (lineage-15.1-<YYYYMMDD>-UNOFFICIAL-axon7.zip), onto the SD Card (or internal storage). Your Axon should be accessible as a USB mass storage device via the host machine once you're in TWRP recovery.

Go back to the TWRP main menu to start installing/flashing the files:

  1. Choose install and navigate to location of the bootstack zip file (A2017x_LineageOS15.1_UniversalBootstack_v2_by_DrakenFX.zip).
  2. Untick “Reboot after installation is compete” and swipe to flash.
  3. Repeat the above steps for the A2017G_OreoModem.zip file.
  4. Now reboot into TWRP recovery again with the new bootstack/modem. TWRP has an option to select booting back into recovery. During the reboot, accept any prompt/warning relating to corrupt/unlocked bootloader by pressing the "Power" button.
  5. Once back in TWRP, you can finally flash your lineage-15.1-<YYYYMMDD>-UNOFFICIAL-axon7.zip.
  6. Perform a Wipe cache/Dalvik.
  7. Reboot the system, accept bootloader prompts regarding “unlocked/corrupt” bootloader by pressing the “Power” button.
  8. Give the initial boot a few minutes to load. If you don’t see the LineageOS splash screen after a few minutes, power off the device by long pressing Power key and let it reboot once more.

Hopefully by now, you’ve made it to the Lineage welcome screen where you can begin your device configuration.

After you’re done, you might notice that the details in “About Phone” in your System settings, lists the device as “A2017U” and not “A2017G”. This may be due to the options configured for the default build. The phone should still function as expected.

You should be able to see your build details listed in “About phone”.

Image for post
Image for post

The LineageOS version shown, is the same as that generated by our breakfast summary, i.e :

LINEAGE_VERSION=15.1-20200629-UNOFFICIAL-axon7

Final Points

If you’ve stumbled on this article, I hope you’ve found it to be informative. If you come across inaccurate details, feel free to comment and I will update as required.

Note that Google apps/services were not included in the default build. Refer to this link for further details on installing. If you intend on installing these, then I’d suggest they be flashed from TWRP, immediately following the step described for flashing your lineage-15.1-<YYYYMMDD>-UNOFFICIAL-axon7.zip.

The docker repo that was specifically setup for the discovery, could be used for other Android builds. Depending on your specific build requirement, modifications to the existing Dockerfile may be required.

Written by

Primarily a Learner/Coder with interests in Python, Cloud Technologies, Security and Automation. Pandas munching on Bamboo sticks give me the “Giggles” :))

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store