Add erofs support to Soong filesystem modules

Test: go test ./filesystem
Bug: 372522486
Change-Id: I57c5a15a225e9e02ebec188d45b640f4185a33c0
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 9b3eae4..87c6381 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -92,7 +92,7 @@
 	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
 	Partition_name *string
 
-	// Type of the filesystem. Currently, ext4, cpio, and compressed_cpio are supported. Default
+	// Type of the filesystem. Currently, ext4, erofs, cpio, and compressed_cpio are supported. Default
 	// is ext4.
 	Type *string
 
@@ -143,6 +143,20 @@
 	// build modules, where we want to emit some not-yet-working filesystems and we don't want them
 	// to be built.
 	Unchecked_module *bool `blueprint:"mutated"`
+
+	Erofs ErofsProperties
+}
+
+// Additional properties required to generate erofs FS partitions.
+type ErofsProperties struct {
+	// Compressor and Compression level passed to mkfs.erofs. e.g. (lz4hc,9)
+	// Please see external/erofs-utils/README for complete documentation.
+	Compressor *string
+
+	// Used as --compress-hints for mkfs.erofs
+	Compress_hints *string `android:"path"`
+
+	Sparse *bool
 }
 
 // android_filesystem packages a set of modules and their transitive dependencies into a filesystem
@@ -178,6 +192,7 @@
 
 const (
 	ext4Type fsType = iota
+	erofsType
 	compressedCpioType
 	cpioType // uncompressed
 	unknown
@@ -195,6 +210,8 @@
 	switch typeStr {
 	case "ext4":
 		return ext4Type
+	case "erofs":
+		return erofsType
 	case "compressed_cpio":
 		return compressedCpioType
 	case "cpio":
@@ -224,7 +241,7 @@
 func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	validatePartitionType(ctx, f)
 	switch f.fsType(ctx) {
-	case ext4Type:
+	case ext4Type, erofsType:
 		f.output = f.buildImageUsingBuildImage(ctx)
 	case compressedCpioType:
 		f.output = f.buildCpioImage(ctx, true)
@@ -437,6 +454,8 @@
 		// TODO(372522486): add more types like f2fs, erofs, etc.
 		case ext4Type:
 			return "ext4"
+		case erofsType:
+			return "erofs"
 		}
 		panic(fmt.Errorf("unsupported fs type %v", t))
 	}
@@ -486,6 +505,24 @@
 		addStr("uuid", uuid)
 		addStr("hash_seed", uuid)
 	}
+	// Add erofs properties
+	if f.fsType(ctx) == erofsType {
+		if compressor := f.properties.Erofs.Compressor; compressor != nil {
+			addStr("erofs_default_compressor", proptools.String(compressor))
+		}
+		if compressHints := f.properties.Erofs.Compress_hints; compressHints != nil {
+			addStr("erofs_default_compress_hints", android.PathForModuleSrc(ctx, *compressHints).String())
+		}
+		if proptools.BoolDefault(f.properties.Erofs.Sparse, true) {
+			// https://source.corp.google.com/h/googleplex-android/platform/build/+/88b1c67239ca545b11580237242774b411f2fed9:core/Makefile;l=2292;bpv=1;bpt=0;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b
+			addStr("erofs_sparse_flag", "-s")
+		}
+	}
+	// Raise an exception if the propfile contains erofs properties, but the fstype is not erofs
+	if fs := fsTypeStr(f.fsType(ctx)); fs != "erofs" && (f.properties.Erofs.Compressor != nil || f.properties.Erofs.Compress_hints != nil || f.properties.Erofs.Sparse != nil) {
+		ctx.PropertyErrorf("erofs", "erofs is non-empty, but FS type is %s\n. Please delete erofs properties if this partition should use %s\n", fs, fs)
+	}
+
 	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
 	android.WriteFileRuleVerbatim(ctx, propFile, propFileString.String())
 	return propFile, deps
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 8c0d111..057dcaa 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -559,3 +559,28 @@
 		}
 	}
 }
+
+func TestErofsPartition(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "erofs_partition",
+			type: "erofs",
+			erofs: {
+				compressor: "lz4hc,9",
+				compress_hints: "compress_hints.txt",
+			},
+			deps: ["binfoo"],
+		}
+
+		cc_binary {
+			name: "binfoo",
+		}
+	`)
+
+	partition := result.ModuleForTests("erofs_partition", "android_common")
+	buildImageConfig := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("prop"))
+	android.AssertStringDoesContain(t, "erofs fs type", buildImageConfig, "fs_type=erofs")
+	android.AssertStringDoesContain(t, "erofs fs type compress algorithm", buildImageConfig, "erofs_default_compressor=lz4hc,9")
+	android.AssertStringDoesContain(t, "erofs fs type compress hint", buildImageConfig, "erofs_default_compress_hints=compress_hints.txt")
+	android.AssertStringDoesContain(t, "erofs fs type sparse", buildImageConfig, "erofs_sparse_flag=-s")
+}