瀏覽代碼

Merge changes from topic "mkboot"

* changes:
  Add bootimg module type
  android_filesystem supports compressed cpio format
  add prebuilt_kernel_modules module
  arch.<arch>.deps now works in android_filesystem
Treehugger Robot 3 年之前
父節點
當前提交
7a1c2b181c

+ 28 - 0
android/packaging.go

@@ -49,6 +49,11 @@ func (p *PackagingSpec) FileName() string {
 	return ""
 }
 
+// Path relative to the root of the package
+func (p *PackagingSpec) RelPathInPackage() string {
+	return p.relPathInPackage
+}
+
 type PackageModule interface {
 	Module
 	packagingBase() *PackagingBase
@@ -87,9 +92,17 @@ type packagingMultilibProperties struct {
 	Lib64  depsProperty `android:"arch_variant"`
 }
 
+type packagingArchProperties struct {
+	Arm64  depsProperty
+	Arm    depsProperty
+	X86_64 depsProperty
+	X86    depsProperty
+}
+
 type PackagingProperties struct {
 	Deps     []string                    `android:"arch_variant"`
 	Multilib packagingMultilibProperties `android:"arch_variant"`
+	Arch     packagingArchProperties
 }
 
 func InitPackageModule(p PackageModule) {
@@ -116,6 +129,7 @@ func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []s
 	} else if arch == Common {
 		ret = append(ret, p.properties.Multilib.Common.Deps...)
 	}
+
 	for i, t := range ctx.MultiTargets() {
 		if t.Arch.ArchType == arch {
 			ret = append(ret, p.properties.Deps...)
@@ -124,6 +138,20 @@ func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []s
 			}
 		}
 	}
+
+	if ctx.Arch().ArchType == Common {
+		switch arch {
+		case Arm64:
+			ret = append(ret, p.properties.Arch.Arm64.Deps...)
+		case Arm:
+			ret = append(ret, p.properties.Arch.Arm.Deps...)
+		case X86_64:
+			ret = append(ret, p.properties.Arch.X86_64.Deps...)
+		case X86:
+			ret = append(ret, p.properties.Arch.X86.Deps...)
+		}
+	}
+
 	return FirstUniqueStrings(ret)
 }
 

+ 158 - 10
android/packaging_test.go

@@ -61,13 +61,20 @@ type packageTestModule struct {
 	entries []string
 }
 
-func packageTestModuleFactory() Module {
+func packageMultiTargetTestModuleFactory() Module {
 	module := &packageTestModule{}
 	InitPackageModule(module)
 	InitAndroidMultiTargetsArchModule(module, DeviceSupported, MultilibCommon)
 	return module
 }
 
+func packageTestModuleFactory() Module {
+	module := &packageTestModule{}
+	InitPackageModule(module)
+	InitAndroidArchModule(module, DeviceSupported, MultilibBoth)
+	return module
+}
+
 func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) {
 	m.AddDeps(ctx, installDepTag{})
 }
@@ -77,14 +84,22 @@ func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	m.entries = m.CopyDepsToZip(ctx, zipFile)
 }
 
-func runPackagingTest(t *testing.T, bp string, expected []string) {
+func runPackagingTest(t *testing.T, multitarget bool, bp string, expected []string) {
 	t.Helper()
 
 	config := TestArchConfig(buildDir, nil, bp, nil)
 
 	ctx := NewTestArchContext(config)
 	ctx.RegisterModuleType("component", componentTestModuleFactory)
-	ctx.RegisterModuleType("package_module", packageTestModuleFactory)
+
+	var archVariant string
+	if multitarget {
+		archVariant = "android_common"
+		ctx.RegisterModuleType("package_module", packageMultiTargetTestModuleFactory)
+	} else {
+		archVariant = "android_arm64_armv8-a"
+		ctx.RegisterModuleType("package_module", packageTestModuleFactory)
+	}
 	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
@@ -92,7 +107,7 @@ func runPackagingTest(t *testing.T, bp string, expected []string) {
 	_, errs = ctx.PrepareBuildActions(config)
 	FailIfErrored(t, errs)
 
-	p := ctx.ModuleForTests("package", "android_common").Module().(*packageTestModule)
+	p := ctx.ModuleForTests("package", archVariant).Module().(*packageTestModule)
 	actual := p.entries
 	actual = SortedUniqueStrings(actual)
 	expected = SortedUniqueStrings(expected)
@@ -101,8 +116,9 @@ func runPackagingTest(t *testing.T, bp string, expected []string) {
 	}
 }
 
-func TestPackagingBase(t *testing.T) {
-	runPackagingTest(t,
+func TestPackagingBaseMultiTarget(t *testing.T) {
+	multiTarget := true
+	runPackagingTest(t, multiTarget,
 		`
 		component {
 			name: "foo",
@@ -114,7 +130,7 @@ func TestPackagingBase(t *testing.T) {
 		}
 		`, []string{"lib64/foo"})
 
-	runPackagingTest(t,
+	runPackagingTest(t, multiTarget,
 		`
 		component {
 			name: "foo",
@@ -131,7 +147,7 @@ func TestPackagingBase(t *testing.T) {
 		}
 		`, []string{"lib64/foo", "lib64/bar"})
 
-	runPackagingTest(t,
+	runPackagingTest(t, multiTarget,
 		`
 		component {
 			name: "foo",
@@ -149,7 +165,7 @@ func TestPackagingBase(t *testing.T) {
 		}
 		`, []string{"lib32/foo", "lib32/bar", "lib64/foo", "lib64/bar"})
 
-	runPackagingTest(t,
+	runPackagingTest(t, multiTarget,
 		`
 		component {
 			name: "foo",
@@ -172,7 +188,7 @@ func TestPackagingBase(t *testing.T) {
 		}
 		`, []string{"lib32/foo", "lib32/bar", "lib64/foo"})
 
-	runPackagingTest(t,
+	runPackagingTest(t, multiTarget,
 		`
 		component {
 			name: "foo",
@@ -193,4 +209,136 @@ func TestPackagingBase(t *testing.T) {
 			compile_multilib: "both",
 		}
 		`, []string{"lib32/foo", "lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t, multiTarget,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+		}
+
+		component {
+			name: "baz",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			arch: {
+				arm64: {
+					deps: ["bar"],
+				},
+				x86_64: {
+					deps: ["baz"],
+				},
+			},
+			compile_multilib: "both",
+		}
+		`, []string{"lib32/foo", "lib64/foo", "lib64/bar"})
+}
+
+func TestPackagingBaseSingleTarget(t *testing.T) {
+	multiTarget := false
+	runPackagingTest(t, multiTarget,
+		`
+		component {
+			name: "foo",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo"})
+
+	runPackagingTest(t, multiTarget,
+		`
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t, multiTarget,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+			compile_multilib: "32",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			multilib: {
+				lib32: {
+					deps: ["bar"],
+				},
+			},
+		}
+		`, []string{"lib64/foo"})
+
+	runPackagingTest(t, multiTarget,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			multilib: {
+				lib64: {
+					deps: ["bar"],
+				},
+			},
+		}
+		`, []string{"lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t, multiTarget,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+		}
+
+		component {
+			name: "baz",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			arch: {
+				arm64: {
+					deps: ["bar"],
+				},
+				x86_64: {
+					deps: ["baz"],
+				},
+			},
+		}
+		`, []string{"lib64/foo", "lib64/bar"})
 }

+ 1 - 0
filesystem/Android.bp

@@ -7,6 +7,7 @@ bootstrap_go_package {
         "soong-android",
     ],
     srcs: [
+        "bootimg.go",
         "filesystem.go",
     ],
     testSrcs: [

+ 222 - 0
filesystem/bootimg.go

@@ -0,0 +1,222 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filesystem
+
+import (
+	"fmt"
+	"strconv"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+func init() {
+	android.RegisterModuleType("bootimg", bootimgFactory)
+}
+
+type bootimg struct {
+	android.ModuleBase
+
+	properties bootimgProperties
+
+	output     android.OutputPath
+	installDir android.InstallPath
+}
+
+type bootimgProperties struct {
+	// Path to the linux kernel prebuilt file
+	Kernel_prebuilt *string `android:"arch_variant,path"`
+
+	// Filesystem module that is used as ramdisk
+	Ramdisk_module *string
+
+	// Path to the device tree blob (DTB) prebuilt file to add to this boot image
+	Dtb_prebuilt *string `android:"arch_variant,path"`
+
+	// Header version number. Must be set to one of the version numbers that are currently
+	// supported. Refer to
+	// https://source.android.com/devices/bootloader/boot-image-header
+	Header_version *string
+
+	// Determines if this image is for the vendor_boot partition. Default is false. Refer to
+	// https://source.android.com/devices/bootloader/partitions/vendor-boot-partitions
+	Vendor_boot *bool
+
+	// Optional kernel commandline
+	Cmdline *string
+
+	// When set to true, sign the image with avbtool. Default is false.
+	Use_avb *bool
+
+	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
+	Partition_name *string
+
+	// Path to the private key that avbtool will use to sign this filesystem image.
+	// TODO(jiyong): allow apex_key to be specified here
+	Avb_private_key *string `android:"path"`
+
+	// Hash and signing algorithm for avbtool. Default is SHA256_RSA4096.
+	Avb_algorithm *string
+}
+
+// bootimg is the image for the boot partition. It consists of header, kernel, ramdisk, and dtb.
+func bootimgFactory() android.Module {
+	module := &bootimg{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
+type bootimgDep struct {
+	blueprint.BaseDependencyTag
+	kind string
+}
+
+var bootimgRamdiskDep = bootimgDep{kind: "ramdisk"}
+
+func (b *bootimg) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ramdisk := proptools.String(b.properties.Ramdisk_module)
+	if ramdisk != "" {
+		ctx.AddDependency(ctx.Module(), bootimgRamdiskDep, ramdisk)
+	}
+}
+
+func (b *bootimg) installFileName() string {
+	return b.BaseModuleName() + ".img"
+}
+
+func (b *bootimg) partitionName() string {
+	return proptools.StringDefault(b.properties.Partition_name, b.BaseModuleName())
+}
+
+func (b *bootimg) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var unsignedOutput android.OutputPath
+	if proptools.Bool(b.properties.Vendor_boot) {
+		unsignedOutput = b.buildVendorBootImage(ctx)
+	} else {
+		// TODO(jiyong): fix this
+		ctx.PropertyErrorf("vendor_boot", "only vendor_boot:true is supported")
+	}
+
+	if proptools.Bool(b.properties.Use_avb) {
+		b.output = b.signImage(ctx, unsignedOutput)
+	} else {
+		b.output = unsignedOutput
+	}
+
+	b.installDir = android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(b.installDir, b.installFileName(), b.output)
+}
+
+func (b *bootimg) buildVendorBootImage(ctx android.ModuleContext) android.OutputPath {
+	output := android.PathForModuleOut(ctx, "unsigned.img").OutputPath
+	builder := android.NewRuleBuilder(pctx, ctx)
+	cmd := builder.Command().BuiltTool("mkbootimg")
+
+	kernel := android.OptionalPathForModuleSrc(ctx, b.properties.Kernel_prebuilt)
+	if kernel.Valid() {
+		ctx.PropertyErrorf("kernel_prebuilt", "vendor_boot partition can't have kernel")
+		return output
+	}
+
+	dtbName := proptools.String(b.properties.Dtb_prebuilt)
+	if dtbName == "" {
+		ctx.PropertyErrorf("dtb_prebuilt", "must be set")
+		return output
+	}
+	dtb := android.PathForModuleSrc(ctx, dtbName)
+	cmd.FlagWithInput("--dtb ", dtb)
+
+	cmdline := proptools.String(b.properties.Cmdline)
+	if cmdline != "" {
+		cmd.FlagWithArg("--vendor_cmdline ", "\""+cmdline+"\"")
+	}
+
+	headerVersion := proptools.String(b.properties.Header_version)
+	if headerVersion == "" {
+		ctx.PropertyErrorf("header_version", "must be set")
+		return output
+	}
+	verNum, err := strconv.Atoi(headerVersion)
+	if err != nil {
+		ctx.PropertyErrorf("header_version", "%q is not a number", headerVersion)
+		return output
+	}
+	if verNum < 3 {
+		ctx.PropertyErrorf("header_version", "must be 3 or higher for vendor_boot")
+		return output
+	}
+	cmd.FlagWithArg("--header_version ", headerVersion)
+
+	ramdiskName := proptools.String(b.properties.Ramdisk_module)
+	if ramdiskName == "" {
+		ctx.PropertyErrorf("ramdisk_module", "must be set")
+		return output
+	}
+	ramdisk := ctx.GetDirectDepWithTag(ramdiskName, bootimgRamdiskDep)
+	if filesystem, ok := ramdisk.(*filesystem); ok {
+		cmd.FlagWithInput("--vendor_ramdisk ", filesystem.OutputPath())
+	} else {
+		ctx.PropertyErrorf("ramdisk", "%q is not android_filesystem module", ramdisk.Name())
+		return output
+	}
+
+	cmd.FlagWithOutput("--vendor_boot ", output)
+
+	builder.Build("build_vendor_bootimg", fmt.Sprintf("Creating %s", b.BaseModuleName()))
+	return output
+}
+
+func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.OutputPath) android.OutputPath {
+	signedImage := android.PathForModuleOut(ctx, "signed.img").OutputPath
+	key := android.PathForModuleSrc(ctx, proptools.String(b.properties.Avb_private_key))
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Text("cp").Input(unsignedImage).Output(signedImage)
+	builder.Command().
+		BuiltTool("avbtool").
+		Flag("add_hash_footer").
+		FlagWithArg("--partition_name ", b.partitionName()).
+		FlagWithInput("--key ", key).
+		FlagWithOutput("--image ", signedImage)
+
+	builder.Build("sign_bootimg", fmt.Sprintf("Signing %s", b.BaseModuleName()))
+
+	return signedImage
+}
+
+var _ android.AndroidMkEntriesProvider = (*bootimg)(nil)
+
+// Implements android.AndroidMkEntriesProvider
+func (b *bootimg) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(b.output),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", b.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", b.installFileName())
+			},
+		},
+	}}
+}
+
+var _ Filesystem = (*bootimg)(nil)
+
+func (b *bootimg) OutputPath() android.Path {
+	return b.output
+}

+ 86 - 6
filesystem/filesystem.go

@@ -47,6 +47,10 @@ type filesystemProperties struct {
 
 	// Hash and signing algorithm for avbtool. Default is SHA256_RSA4096.
 	Avb_algorithm *string
+
+	// Type of the filesystem. Currently, ext4 and compressed_cpio are supported. Default is
+	// ext4.
+	Type *string
 }
 
 // android_filesystem packages a set of modules and their transitive dependencies into a filesystem
@@ -71,6 +75,27 @@ func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
 	f.AddDeps(ctx, dependencyTag)
 }
 
+type fsType int
+
+const (
+	ext4Type fsType = iota
+	compressedCpioType
+	unknown
+)
+
+func (f *filesystem) fsType(ctx android.ModuleContext) fsType {
+	typeStr := proptools.StringDefault(f.properties.Type, "ext4")
+	switch typeStr {
+	case "ext4":
+		return ext4Type
+	case "compressed_cpio":
+		return compressedCpioType
+	default:
+		ctx.PropertyErrorf("type", "%q not supported", typeStr)
+		return unknown
+	}
+}
+
 func (f *filesystem) installFileName() string {
 	return f.BaseModuleName() + ".img"
 }
@@ -78,6 +103,20 @@ func (f *filesystem) installFileName() string {
 var pctx = android.NewPackageContext("android/soong/filesystem")
 
 func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	switch f.fsType(ctx) {
+	case ext4Type:
+		f.output = f.buildImageUsingBuildImage(ctx)
+	case compressedCpioType:
+		f.output = f.buildCompressedCpioImage(ctx)
+	default:
+		return
+	}
+
+	f.installDir = android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
+}
+
+func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
 	zipFile := android.PathForModuleOut(ctx, "temp.zip").OutputPath
 	f.CopyDepsToZip(ctx, zipFile)
 
@@ -89,19 +128,18 @@ func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 		Input(zipFile)
 
 	propFile, toolDeps := f.buildPropFile(ctx)
-	f.output = android.PathForModuleOut(ctx, f.installFileName()).OutputPath
+	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
 	builder.Command().BuiltTool("build_image").
 		Text(rootDir.String()). // input directory
 		Input(propFile).
 		Implicits(toolDeps).
-		Output(f.output).
+		Output(output).
 		Text(rootDir.String()) // directory where to find fs_config_files|dirs
 
 	// rootDir is not deleted. Might be useful for quick inspection.
 	builder.Build("build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
 
-	f.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
+	return output
 }
 
 func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
@@ -120,8 +158,17 @@ func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.
 		deps = append(deps, path)
 	}
 
-	// TODO(jiyong): support more filesystem types other than ext4
-	addStr("fs_type", "ext4")
+	// Type string that build_image.py accepts.
+	fsTypeStr := func(t fsType) string {
+		switch t {
+		// TODO(jiyong): add more types like f2fs, erofs, etc.
+		case ext4Type:
+			return "ext4"
+		}
+		panic(fmt.Errorf("unsupported fs type %v", t))
+	}
+
+	addStr("fs_type", fsTypeStr(f.fsType(ctx)))
 	addStr("mount_point", "system")
 	addStr("use_dynamic_partition_size", "true")
 	addPath("ext_mkuserimg", ctx.Config().HostToolPath(ctx, "mkuserimg_mke2fs"))
@@ -154,6 +201,39 @@ func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.
 	return propFile, deps
 }
 
+func (f *filesystem) buildCompressedCpioImage(ctx android.ModuleContext) android.OutputPath {
+	if proptools.Bool(f.properties.Use_avb) {
+		ctx.PropertyErrorf("use_avb", "signing compresed cpio image using avbtool is not supported."+
+			"Consider adding this to bootimg module and signing the entire boot image.")
+	}
+
+	zipFile := android.PathForModuleOut(ctx, "temp.zip").OutputPath
+	f.CopyDepsToZip(ctx, zipFile)
+
+	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().
+		BuiltTool("zipsync").
+		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
+		Input(zipFile)
+
+	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
+	builder.Command().
+		BuiltTool("mkbootfs").
+		Text(rootDir.String()). // input directory
+		Text("|").
+		BuiltTool("lz4").
+		Flag("--favor-decSpeed"). // for faster boot
+		Flag("-12").              // maximum compression level
+		Flag("-l").               // legacy format for kernel
+		Text(">").Output(output)
+
+	// rootDir is not deleted. Might be useful for quick inspection.
+	builder.Build("build_compressed_cpio_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
+
+	return output
+}
+
 var _ android.AndroidMkEntriesProvider = (*filesystem)(nil)
 
 // Implements android.AndroidMkEntriesProvider

+ 18 - 0
kernel/Android.bp

@@ -0,0 +1,18 @@
+bootstrap_go_package {
+    name: "soong-kernel",
+    pkgPath: "android/soong/kernel",
+    deps: [
+        "blueprint",
+        "soong",
+        "soong-android",
+        "soong-cc",
+        "soong-cc-config",
+    ],
+    srcs: [
+        "prebuilt_kernel_modules.go",
+    ],
+    testSrcs: [
+        "prebuilt_kernel_modules_test.go",
+    ],
+    pluginFor: ["soong_build"],
+}

+ 166 - 0
kernel/prebuilt_kernel_modules.go

@@ -0,0 +1,166 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package kernel
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"android/soong/android"
+	_ "android/soong/cc/config"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	android.RegisterModuleType("prebuilt_kernel_modules", prebuiltKernelModulesFactory)
+	pctx.Import("android/soong/cc/config")
+}
+
+type prebuiltKernelModules struct {
+	android.ModuleBase
+
+	properties prebuiltKernelModulesProperties
+
+	installDir android.InstallPath
+}
+
+type prebuiltKernelModulesProperties struct {
+	// List or filegroup of prebuilt kernel module files. Should have .ko suffix.
+	Srcs []string `android:"path,arch_variant"`
+
+	// Kernel version that these modules are for. Kernel modules are installed to
+	// /lib/modules/<kernel_version> directory in the corresponding partition. Default is "".
+	Kernel_version *string
+}
+
+// prebuilt_kernel_modules installs a set of prebuilt kernel module files to the correct directory.
+// In addition, this module builds modules.load, modules.dep, modules.softdep and modules.alias
+// using depmod and installs them as well.
+func prebuiltKernelModulesFactory() android.Module {
+	module := &prebuiltKernelModules{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
+func (pkm *prebuiltKernelModules) KernelVersion() string {
+	return proptools.StringDefault(pkm.properties.Kernel_version, "")
+}
+
+func (pkm *prebuiltKernelModules) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// do nothing
+}
+
+func (pkm *prebuiltKernelModules) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	modules := android.PathsForModuleSrc(ctx, pkm.properties.Srcs)
+
+	depmodOut := runDepmod(ctx, modules)
+	strippedModules := stripDebugSymbols(ctx, modules)
+
+	installDir := android.PathForModuleInstall(ctx, "lib", "module")
+	if pkm.KernelVersion() != "" {
+		installDir = installDir.Join(ctx, pkm.KernelVersion())
+	}
+
+	for _, m := range strippedModules {
+		ctx.InstallFile(installDir, filepath.Base(m.String()), m)
+	}
+	ctx.InstallFile(installDir, "modules.load", depmodOut.modulesLoad)
+	ctx.InstallFile(installDir, "modules.dep", depmodOut.modulesDep)
+	ctx.InstallFile(installDir, "modules.softdep", depmodOut.modulesSoftdep)
+	ctx.InstallFile(installDir, "modules.alias", depmodOut.modulesAlias)
+}
+
+var (
+	pctx = android.NewPackageContext("android/soong/kernel")
+
+	stripRule = pctx.AndroidStaticRule("strip",
+		blueprint.RuleParams{
+			Command:     "$stripCmd -o $out --strip-debug $in",
+			CommandDeps: []string{"$stripCmd"},
+		}, "stripCmd")
+)
+
+func stripDebugSymbols(ctx android.ModuleContext, modules android.Paths) android.OutputPaths {
+	dir := android.PathForModuleOut(ctx, "stripped").OutputPath
+	var outputs android.OutputPaths
+
+	for _, m := range modules {
+		stripped := dir.Join(ctx, filepath.Base(m.String()))
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   stripRule,
+			Input:  m,
+			Output: stripped,
+			Args: map[string]string{
+				"stripCmd": "${config.ClangBin}/llvm-strip",
+			},
+		})
+		outputs = append(outputs, stripped)
+	}
+
+	return outputs
+}
+
+type depmodOutputs struct {
+	modulesLoad    android.OutputPath
+	modulesDep     android.OutputPath
+	modulesSoftdep android.OutputPath
+	modulesAlias   android.OutputPath
+}
+
+func runDepmod(ctx android.ModuleContext, modules android.Paths) depmodOutputs {
+	baseDir := android.PathForModuleOut(ctx, "depmod").OutputPath
+	fakeVer := "0.0" // depmod demands this anyway
+	modulesDir := baseDir.Join(ctx, "lib", "modules", fakeVer)
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+
+	// Copy the module files to a temporary dir
+	builder.Command().Text("rm").Flag("-rf").Text(modulesDir.String())
+	builder.Command().Text("mkdir").Flag("-p").Text(modulesDir.String())
+	for _, m := range modules {
+		builder.Command().Text("cp").Input(m).Text(modulesDir.String())
+	}
+
+	// Enumerate modules to load
+	modulesLoad := modulesDir.Join(ctx, "modules.load")
+	var basenames []string
+	for _, m := range modules {
+		basenames = append(basenames, filepath.Base(m.String()))
+	}
+	builder.Command().
+		Text("echo").Flag("\"" + strings.Join(basenames, " ") + "\"").
+		Text("|").Text("tr").Flag("\" \"").Flag("\"\\n\"").
+		Text(">").Output(modulesLoad)
+
+	// Run depmod to build modules.dep/softdep/alias files
+	modulesDep := modulesDir.Join(ctx, "modules.dep")
+	modulesSoftdep := modulesDir.Join(ctx, "modules.softdep")
+	modulesAlias := modulesDir.Join(ctx, "modules.alias")
+	builder.Command().
+		BuiltTool("depmod").
+		FlagWithArg("-b ", baseDir.String()).
+		Text(fakeVer).
+		ImplicitOutput(modulesDep).
+		ImplicitOutput(modulesSoftdep).
+		ImplicitOutput(modulesAlias)
+
+	builder.Build("depmod", fmt.Sprintf("depmod %s", ctx.ModuleName()))
+
+	return depmodOutputs{modulesLoad, modulesDep, modulesSoftdep, modulesAlias}
+}

+ 129 - 0
kernel/prebuilt_kernel_modules_test.go

@@ -0,0 +1,129 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package kernel
+
+import (
+	"io/ioutil"
+	"os"
+	"reflect"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+)
+
+func testKernelModules(t *testing.T, bp string, fs map[string][]byte) (*android.TestContext, android.Config) {
+	bp = bp + `
+		cc_binary_host {
+			name: "depmod",
+			srcs: ["depmod.cpp"],
+			stl: "none",
+			static_executable: true,
+			system_shared_libs: [],
+		}
+	`
+	bp = bp + cc.GatherRequiredDepsForTest(android.Android)
+
+	fs["depmod.cpp"] = nil
+	cc.GatherRequiredFilesForTest(fs)
+
+	config := android.TestArchConfig(buildDir, nil, bp, fs)
+
+	ctx := android.NewTestArchContext(config)
+	ctx.RegisterModuleType("prebuilt_kernel_modules", prebuiltKernelModulesFactory)
+	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	cc.RegisterRequiredBuildComponentsForTest(ctx)
+
+	ctx.Register()
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	android.FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	android.FailIfErrored(t, errs)
+
+	return ctx, config
+}
+
+func ensureListContains(t *testing.T, result []string, expected string) {
+	t.Helper()
+	if !android.InList(expected, result) {
+		t.Errorf("%q is not found in %v", expected, result)
+	}
+}
+
+func ensureContains(t *testing.T, result string, expected string) {
+	t.Helper()
+	if !strings.Contains(result, expected) {
+		t.Errorf("%q is not found in %q", expected, result)
+	}
+}
+
+func TestKernelModulesFilelist(t *testing.T) {
+	ctx, _ := testKernelModules(t, `
+		prebuilt_kernel_modules {
+			name: "foo",
+			srcs: ["*.ko"],
+			kernel_version: "5.10",
+		}
+	`,
+		map[string][]byte{
+			"mod1.ko": nil,
+			"mod2.ko": nil,
+		})
+
+	expected := []string{
+		"lib/module/5.10/mod1.ko",
+		"lib/module/5.10/mod2.ko",
+		"lib/module/5.10/modules.load",
+		"lib/module/5.10/modules.dep",
+		"lib/module/5.10/modules.softdep",
+		"lib/module/5.10/modules.alias",
+	}
+
+	var actual []string
+	for _, ps := range ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().PackagingSpecs() {
+		actual = append(actual, ps.RelPathInPackage())
+	}
+	actual = android.SortedUniqueStrings(actual)
+	expected = android.SortedUniqueStrings(expected)
+	if !reflect.DeepEqual(actual, expected) {
+		t.Errorf("\ngot: %v\nexpected: %v\n", actual, expected)
+	}
+}
+
+var buildDir string
+
+func setUp() {
+	var err error
+	buildDir, err = ioutil.TempDir("", "soong_kernel_test")
+	if err != nil {
+		panic(err)
+	}
+}
+
+func tearDown() {
+	os.RemoveAll(buildDir)
+}
+
+func TestMain(m *testing.M) {
+	run := func() int {
+		setUp()
+		defer tearDown()
+
+		return m.Run()
+	}
+
+	os.Exit(run())
+}