Build Android GSI

Generic System Image


Table of contents

I hate Reddit and forums.

Especially popular forums like XDA, the source of unique and valuable information and good advice. But. All information is lost in 100 page threads. Good comments indistinguishable from bad comments. That’s why I like StackOverflow so much. I was on this forum and some discussion went me on a wrong track. So, let's see what happenned.

...I think this is the explanation why my Lineage ROM stuck on boot logo:

"In Android 9 and higher, this requirement has changed to enable vendors to boot a GSI. Specifically, Keymaster shouldn't perform verification because the version info reported by the GSI may not match the version info reported by vendor's bootloader. For devices implementing Keymaster 3 or lower, vendors must modify the Keymaster implementation to skip verification (or upgrade to Keymaster 4). For details on Keymaster, refer to Hardware-backed Keystore.”

Comment on reddit

So, no Lineage, instead I need to build GSI first. Why? Because it will work. That's what they said.

Okay, there is no android9-gsi branch. I’ll skip Android 9 GSI for now. Android 10 GSI it is.

Docker setup

I build on Manjaro and get exactly the same error as these guys.

Invalid filesystem option set: has_journal,extent,huge_file,flex_bg,metadata_csum,metadata_csum_seed,64bit,dir_nlink,extra_isize,orphan_file

Then I decide to make right, in the Docker. Because I already have the whole build tree setup on my host machine, then I will mount this folder into the docker container.

docker run -it -v $(pwd)/gsi:/gsi --name gsi-build ubuntu:18.04 bash
# re-enter later
docker start -a -i gsi-build
docker exec --tty --interactive --privileged gsi-build bash

In the Docker container:

apt update && apt install python3 unzip m4 rsync openssl

I had openssl missing and as a result of my almost empty docker setup

  • I did some research in external/avb/avbtool and realized that it is a Go tool that executes different programs including apexer that spits out Python traceback. apexer is a beefy binary, 28MB. Apparently it includes Python interpreter inside. So, I can add the --verbose flag into the Go program, and add more prints into the Python scripts in order to understand what is wrong.
  • Also I manually converted device/generic/goldfish/tools/ from Python 2 to 3 because it was the only script that failed in my container with Python 3.6
diff --git a/tools/ b/tools/
index 1b7bbff0..c67d18b2 100755
--- a/tools/
+++ b/tools/
@@ -11,7 +11,8 @@ def check_sparse(filename):
     magic = 3978755898
     with open(filename, 'rb') as i:
         word =
-        if magic == int(word[::-1].encode('hex'), 16):
+        # if magic == int(word[::-1].encode('hex'), 16):
+        if magic == int.from_bytes(word, byteorder='big'):
             return True
     return False

@@ -45,18 +46,18 @@ def parse_input(input_file):
             partition_info["num"] = int(line[2])
         except ValueError:
-            print "'%s' cannot be converted to int" % (line[2])
+            print("'%s' cannot be converted to int" % (line[2]))

         # check if the partition number is out of range
         if partition_info["num"] > len(lines) or partition_info["num"] < 0:
-            print "Invalid partition number: %d, range [1..%d]" % \
-                    (partition_info["num"], len(lines))
+            print("Invalid partition number: %d, range [1..%d]" % \
+                    (partition_info["num"], len(lines)))

         # check if the partition number is duplicated
         if partition_info["num"] in num_used:
-            print "Duplicated partition number:%d" % (partition["num"])
+            print("Duplicated partition number:%d" % (partition["num"]))
@@ -68,23 +69,23 @@ def write_partition(partition, output_file, offset):
     # $ dd if=/path/to/image of=/path/to/output conv=notrunc,sync \
     #   ibs=1024k obs=1024k seek=<offset>
     dd_comm = ['dd', 'if='+partition["path"], 'of='+output_file,'conv=notrunc,sync',
-                'ibs=1024k','obs=1024k', 'seek='+str(offset)]
+                'ibs=1024k','obs=1024k', 'seek='+str(int(offset))]

 def unsparse_partition(partition):
     # if the input image is in sparse format, unsparse it
     simg2img = os.environ.get('SIMG2IMG', 'simg2img')
-    print "Unsparsing %s" % (partition["path"]),
+    print("Unsparsing %s" % (partition["path"]))
     partition["fd"], temp_file = mkstemp()
     shell_command([simg2img, partition["path"], temp_file])
     partition["path"] = temp_file
-    print "Done"
+    print("Done")

 def clear_partition_table(filename):
     sgdisk = os.environ.get('SGDISK', 'sgdisk')
-    print "%s --clear %s" % (sgdisk, filename)
+    print("%s --clear %s" % (sgdisk, filename))
     shell_command([sgdisk, '--clear', filename])

@@ -116,7 +117,7 @@ def main():
     # check input file
     config_filename = args.input
     if not os.path.exists(config_filename):
-        print "Invalid config file name " + config_filename
+        print("Invalid config file name " + config_filename)

     # read input file
@@ -144,7 +145,7 @@ def main():
     # add padding
     # $ dd if=/dev/zero of=/path/to/output conv=notrunc bs=1 \
     #   count=1024k seek=<offset>
-    offset = os.path.getsize(output_filename) / 1024 / 1024
+    offset = int(os.path.getsize(output_filename) / 1024 / 1024)
     shell_command(['dd', 'if=/dev/zero', 'of='+output_filename,
                 'conv=notrunc', 'bs=1024k', 'count=1', 'seek='+str(offset)])

The end result: out/target/product/generic/system.img. But I still don't know why it fails on Manjaro.


Oh. After I've read about the process of making old devices Treble-ready I realized that I will need new boot, system, and vendor partitions. And GSI only provides the system partition (because it's system.img).


Install GSI from TWRP link

Rate this page