rust modules can be included in apex

We will have some APEXes having rust binaries and libraries. So, adding
the support for the types of modules.

rust.Module now inherits from android.ApexModuleBase and implements
the android.ApexModule interface.

Bug: 172414324
Test: m

Exempt-From-Owner-Approval: rebased after +2 from the owner
Change-Id: I356ef4c45f782a6460f001e83af96d1710642d80
This commit is contained in:
Jiyong Park 2020-11-17 22:21:02 +09:00
parent d35d92a7b5
commit 99644e92c8
11 changed files with 177 additions and 11 deletions

View file

@ -75,6 +75,10 @@ toolchain_library {
product_available: true, product_available: true,
recovery_available: true, recovery_available: true,
native_bridge_supported: true, native_bridge_supported: true,
apex_available: [
"//apex_available:platform",
"//apex_available:anyapex",
],
arch: { arch: {
arm: { arm: {

View file

@ -9,6 +9,7 @@ bootstrap_go_package {
"soong-cc", "soong-cc",
"soong-java", "soong-java",
"soong-python", "soong-python",
"soong-rust",
"soong-sh", "soong-sh",
], ],
srcs: [ srcs: [

View file

@ -32,6 +32,7 @@ import (
prebuilt_etc "android/soong/etc" prebuilt_etc "android/soong/etc"
"android/soong/java" "android/soong/java"
"android/soong/python" "android/soong/python"
"android/soong/rust"
"android/soong/sh" "android/soong/sh"
) )
@ -179,6 +180,9 @@ type ApexNativeDependencies struct {
// List of JNI libraries that are embedded inside this APEX. // List of JNI libraries that are embedded inside this APEX.
Jni_libs []string Jni_libs []string
// List of rust dyn libraries
Rust_dyn_libs []string
// List of native executables that are embedded inside this APEX. // List of native executables that are embedded inside this APEX.
Binaries []string Binaries []string
@ -513,13 +517,15 @@ var (
func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeModules ApexNativeDependencies, target android.Target, imageVariation string) { func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeModules ApexNativeDependencies, target android.Target, imageVariation string) {
binVariations := target.Variations() binVariations := target.Variations()
libVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"}) libVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
rustLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "rust_libraries", Variation: "dylib"})
if ctx.Device() { if ctx.Device() {
binVariations = append(binVariations, blueprint.Variation{Mutator: "image", Variation: imageVariation}) binVariations = append(binVariations, blueprint.Variation{Mutator: "image", Variation: imageVariation})
libVariations = append(libVariations, libVariations = append(libVariations,
blueprint.Variation{Mutator: "image", Variation: imageVariation}, blueprint.Variation{Mutator: "image", Variation: imageVariation},
blueprint.Variation{Mutator: "version", Variation: ""}, // "" is the non-stub variant blueprint.Variation{Mutator: "version", Variation: ""}) // "" is the non-stub variant
) rustLibVariations = append(rustLibVariations,
blueprint.Variation{Mutator: "image", Variation: imageVariation})
} }
// Use *FarVariation* to be able to depend on modules having conflicting variations with // Use *FarVariation* to be able to depend on modules having conflicting variations with
@ -529,6 +535,7 @@ func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeM
ctx.AddFarVariationDependencies(binVariations, testTag, nativeModules.Tests...) ctx.AddFarVariationDependencies(binVariations, testTag, nativeModules.Tests...)
ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...) ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...)
ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...) ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...)
ctx.AddFarVariationDependencies(rustLibVariations, sharedLibTag, nativeModules.Rust_dyn_libs...)
} }
func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) { func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
@ -1264,6 +1271,35 @@ func apexFileForExecutable(ctx android.BaseModuleContext, cc *cc.Module) apexFil
return af return af
} }
func apexFileForRustExecutable(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
dirInApex := "bin"
if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
}
fileToCopy := rustm.OutputFile().Path()
androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, rustm)
return af
}
func apexFileForRustLibrary(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
// Decide the APEX-local directory by the multilib of the library
// In the future, we may query this to the module.
var dirInApex string
switch rustm.Arch().ArchType.Multilib {
case "lib32":
dirInApex = "lib"
case "lib64":
dirInApex = "lib64"
}
if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
}
fileToCopy := rustm.OutputFile().Path()
androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, rustm)
}
func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile { func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile {
dirInApex := "bin" dirInApex := "bin"
fileToCopy := py.HostToolPath().Path() fileToCopy := py.HostToolPath().Path()
@ -1501,8 +1537,11 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py)) filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() { } else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb)) filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb))
} else if rust, ok := child.(*rust.Module); ok {
filesInfo = append(filesInfo, apexFileForRustExecutable(ctx, rust))
return true // track transitive dependencies
} else { } else {
ctx.PropertyErrorf("binaries", "%q is neither cc_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName) ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
} }
case javaLibTag: case javaLibTag:
switch child.(type) { switch child.(type) {
@ -1663,6 +1702,13 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok { if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName)) filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
} }
} else if rust.IsDylibDepTag(depTag) {
if rustm, ok := child.(*rust.Module); ok && rustm.IsInstallableToApex() {
af := apexFileForRustLibrary(ctx, rustm)
af.transitiveDep = true
filesInfo = append(filesInfo, af)
return true // track transitive dependencies
}
} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok { } else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
// nothing // nothing
} else if am.CanHaveApexVariants() && am.IsInstallableToApex() { } else if am.CanHaveApexVariants() && am.IsInstallableToApex() {

View file

@ -32,6 +32,7 @@ import (
"android/soong/dexpreopt" "android/soong/dexpreopt"
prebuilt_etc "android/soong/etc" prebuilt_etc "android/soong/etc"
"android/soong/java" "android/soong/java"
"android/soong/rust"
"android/soong/sh" "android/soong/sh"
) )
@ -136,6 +137,8 @@ func testApexContext(_ *testing.T, bp string, handlers ...testCustomizer) (*andr
bp = bp + cc.GatherRequiredDepsForTest(android.Android) bp = bp + cc.GatherRequiredDepsForTest(android.Android)
bp = bp + rust.GatherRequiredDepsForTest()
bp = bp + java.GatherRequiredDepsForTest() bp = bp + java.GatherRequiredDepsForTest()
fs := map[string][]byte{ fs := map[string][]byte{
@ -185,6 +188,7 @@ func testApexContext(_ *testing.T, bp string, handlers ...testCustomizer) (*andr
"bar/baz": nil, "bar/baz": nil,
"testdata/baz": nil, "testdata/baz": nil,
"AppSet.apks": nil, "AppSet.apks": nil,
"foo.rs": nil,
} }
cc.GatherRequiredFilesForTest(fs) cc.GatherRequiredFilesForTest(fs)
@ -241,6 +245,7 @@ func testApexContext(_ *testing.T, bp string, handlers ...testCustomizer) (*andr
ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer) ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer)
cc.RegisterRequiredBuildComponentsForTest(ctx) cc.RegisterRequiredBuildComponentsForTest(ctx)
rust.RegisterRequiredBuildComponentsForTest(ctx)
ctx.RegisterModuleType("cc_test", cc.TestFactory) ctx.RegisterModuleType("cc_test", cc.TestFactory)
ctx.RegisterModuleType("vndk_prebuilt_shared", cc.VndkPrebuiltSharedFactory) ctx.RegisterModuleType("vndk_prebuilt_shared", cc.VndkPrebuiltSharedFactory)
@ -349,10 +354,12 @@ func TestBasicApex(t *testing.T) {
manifest: ":myapex.manifest", manifest: ":myapex.manifest",
androidManifest: ":myapex.androidmanifest", androidManifest: ":myapex.androidmanifest",
key: "myapex.key", key: "myapex.key",
binaries: ["foo.rust"],
native_shared_libs: ["mylib"], native_shared_libs: ["mylib"],
rust_dyn_libs: ["libfoo.dylib.rust"],
multilib: { multilib: {
both: { both: {
binaries: ["foo",], binaries: ["foo"],
} }
}, },
java_libs: [ java_libs: [
@ -415,6 +422,28 @@ func TestBasicApex(t *testing.T) {
apex_available: [ "myapex", "com.android.gki.*" ], apex_available: [ "myapex", "com.android.gki.*" ],
} }
rust_binary {
name: "foo.rust",
srcs: ["foo.rs"],
rlibs: ["libfoo.rlib.rust"],
dylibs: ["libfoo.dylib.rust"],
apex_available: ["myapex"],
}
rust_library_rlib {
name: "libfoo.rlib.rust",
srcs: ["foo.rs"],
crate_name: "foo",
apex_available: ["myapex"],
}
rust_library_dylib {
name: "libfoo.dylib.rust",
srcs: ["foo.rs"],
crate_name: "foo",
apex_available: ["myapex"],
}
apex { apex {
name: "com.android.gki.fake", name: "com.android.gki.fake",
binaries: ["foo"], binaries: ["foo"],
@ -529,16 +558,20 @@ func TestBasicApex(t *testing.T) {
ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_apex10000") ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("myjar"), "android_common_apex10000") ensureListContains(t, ctx.ModuleVariantsForTests("myjar"), "android_common_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("myjar_dex"), "android_common_apex10000") ensureListContains(t, ctx.ModuleVariantsForTests("myjar_dex"), "android_common_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("foo.rust"), "android_arm64_armv8-a_apex10000")
// Ensure that apex variant is created for the indirect dep // Ensure that apex variant is created for the indirect dep
ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_apex10000") ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("myotherjar"), "android_common_apex10000") ensureListContains(t, ctx.ModuleVariantsForTests("myotherjar"), "android_common_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.rlib.rust"), "android_arm64_armv8-a_rlib_dylib-std_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.dylib.rust"), "android_arm64_armv8-a_dylib_apex10000")
// Ensure that both direct and indirect deps are copied into apex // Ensure that both direct and indirect deps are copied into apex
ensureContains(t, copyCmds, "image.apex/lib64/mylib.so") ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
ensureContains(t, copyCmds, "image.apex/lib64/mylib2.so") ensureContains(t, copyCmds, "image.apex/lib64/mylib2.so")
ensureContains(t, copyCmds, "image.apex/javalib/myjar_stem.jar") ensureContains(t, copyCmds, "image.apex/javalib/myjar_stem.jar")
ensureContains(t, copyCmds, "image.apex/javalib/myjar_dex.jar") ensureContains(t, copyCmds, "image.apex/javalib/myjar_dex.jar")
ensureContains(t, copyCmds, "image.apex/lib64/libfoo.dylib.rust.dylib.so")
// .. but not for java libs // .. but not for java libs
ensureNotContains(t, copyCmds, "image.apex/javalib/myotherjar.jar") ensureNotContains(t, copyCmds, "image.apex/javalib/myotherjar.jar")
ensureNotContains(t, copyCmds, "image.apex/javalib/msharedjar.jar") ensureNotContains(t, copyCmds, "image.apex/javalib/msharedjar.jar")

View file

@ -166,6 +166,10 @@ func GatherRequiredDepsForTest(oses ...android.OsType) string {
product_available: true, product_available: true,
recovery_available: true, recovery_available: true,
src: "", src: "",
apex_available: [
"//apex_available:platform",
"//apex_available:anyapex",
],
} }
toolchain_library { toolchain_library {

View file

@ -43,7 +43,7 @@ func (mod *Module) SubAndroidMk(data *android.AndroidMkEntries, obj interface{})
} }
func (mod *Module) AndroidMkEntries() []android.AndroidMkEntries { func (mod *Module) AndroidMkEntries() []android.AndroidMkEntries {
if mod.Properties.HideFromMake { if mod.Properties.HideFromMake || mod.hideApexVariantFromMake {
return []android.AndroidMkEntries{android.AndroidMkEntries{Disabled: true}} return []android.AndroidMkEntries{android.AndroidMkEntries{Disabled: true}}
} }

View file

@ -18,6 +18,7 @@ import (
"testing" "testing"
"android/soong/android" "android/soong/android"
"android/soong/cc"
) )
func TestClippy(t *testing.T) { func TestClippy(t *testing.T) {
@ -45,6 +46,7 @@ func TestClippy(t *testing.T) {
}` }`
bp = bp + GatherRequiredDepsForTest() bp = bp + GatherRequiredDepsForTest()
bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
fs := map[string][]byte{ fs := map[string][]byte{
// Reuse the same blueprint file for subdirectories. // Reuse the same blueprint file for subdirectories.

View file

@ -19,6 +19,7 @@ import (
"testing" "testing"
"android/soong/android" "android/soong/android"
"android/soong/cc"
) )
// Test that feature flags are being correctly generated. // Test that feature flags are being correctly generated.
@ -132,6 +133,7 @@ func TestLints(t *testing.T) {
}` }`
bp = bp + GatherRequiredDepsForTest() bp = bp + GatherRequiredDepsForTest()
bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
fs := map[string][]byte{ fs := map[string][]byte{
// Reuse the same blueprint file for subdirectories. // Reuse the same blueprint file for subdirectories.

View file

@ -74,6 +74,7 @@ type BaseProperties struct {
type Module struct { type Module struct {
android.ModuleBase android.ModuleBase
android.DefaultableModuleBase android.DefaultableModuleBase
android.ApexModuleBase
Properties BaseProperties Properties BaseProperties
@ -88,6 +89,8 @@ type Module struct {
subAndroidMkOnce map[SubAndroidMkProvider]bool subAndroidMkOnce map[SubAndroidMkProvider]bool
outputFile android.OptionalPath outputFile android.OptionalPath
hideApexVariantFromMake bool
} }
func (mod *Module) OutputFiles(tag string) (android.Paths, error) { func (mod *Module) OutputFiles(tag string) (android.Paths, error) {
@ -508,6 +511,7 @@ func (mod *Module) Init() android.Module {
} }
android.InitAndroidArchModule(mod, mod.hod, mod.multilib) android.InitAndroidArchModule(mod, mod.hod, mod.multilib)
android.InitApexModule(mod)
android.InitDefaultableModule(mod) android.InitDefaultableModule(mod)
return mod return mod
@ -605,6 +609,11 @@ func (mod *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
ModuleContext: actx, ModuleContext: actx,
} }
apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
if !apexInfo.IsForPlatform() {
mod.hideApexVariantFromMake = true
}
toolchain := mod.toolchain(ctx) toolchain := mod.toolchain(ctx)
if !toolchain.Supported() { if !toolchain.Supported() {
@ -694,6 +703,11 @@ var (
sourceDepTag = dependencyTag{name: "source"} sourceDepTag = dependencyTag{name: "source"}
) )
func IsDylibDepTag(depTag blueprint.DependencyTag) bool {
tag, ok := depTag.(dependencyTag)
return ok && tag == dylibDepTag
}
type autoDep struct { type autoDep struct {
variation string variation string
depTag dependencyTag depTag dependencyTag
@ -1052,6 +1066,58 @@ func (mod *Module) HostToolPath() android.OptionalPath {
return android.OptionalPath{} return android.OptionalPath{}
} }
var _ android.ApexModule = (*Module)(nil)
func (mod *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
return nil
}
func (mod *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
depTag := ctx.OtherModuleDependencyTag(dep)
if ccm, ok := dep.(*cc.Module); ok {
if ccm.HasStubsVariants() {
if cc.IsSharedDepTag(depTag) {
// dynamic dep to a stubs lib crosses APEX boundary
return false
}
if cc.IsRuntimeDepTag(depTag) {
// runtime dep to a stubs lib also crosses APEX boundary
return false
}
if cc.IsHeaderDepTag(depTag) {
return false
}
}
if mod.Static() && cc.IsSharedDepTag(depTag) {
// shared_lib dependency from a static lib is considered as crossing
// the APEX boundary because the dependency doesn't actually is
// linked; the dependency is used only during the compilation phase.
return false
}
}
if depTag == procMacroDepTag {
return false
}
return true
}
// Overrides ApexModule.IsInstallabeToApex()
func (mod *Module) IsInstallableToApex() bool {
if mod.compiler != nil {
if lib, ok := mod.compiler.(*libraryDecorator); ok && (lib.shared() || lib.dylib()) {
return true
}
if _, ok := mod.compiler.(*binaryDecorator); ok {
return true
}
}
return false
}
var Bool = proptools.Bool var Bool = proptools.Bool
var BoolDefault = proptools.BoolDefault var BoolDefault = proptools.BoolDefault
var String = proptools.String var String = proptools.String

View file

@ -120,6 +120,7 @@ func (tctx *testRustCtx) useMockedFs() {
// attributes of the testRustCtx. // attributes of the testRustCtx.
func (tctx *testRustCtx) generateConfig() { func (tctx *testRustCtx) generateConfig() {
tctx.bp = tctx.bp + GatherRequiredDepsForTest() tctx.bp = tctx.bp + GatherRequiredDepsForTest()
tctx.bp = tctx.bp + cc.GatherRequiredDepsForTest(android.NoOsType)
cc.GatherRequiredFilesForTest(tctx.fs) cc.GatherRequiredFilesForTest(tctx.fs)
config := android.TestArchConfig(buildDir, tctx.env, tctx.bp, tctx.fs) config := android.TestArchConfig(buildDir, tctx.env, tctx.bp, tctx.fs)
tctx.config = &config tctx.config = &config

View file

@ -77,6 +77,7 @@ func GatherRequiredDepsForTest() string {
no_libcrt: true, no_libcrt: true,
nocrt: true, nocrt: true,
system_shared_libs: [], system_shared_libs: [],
apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
} }
cc_library { cc_library {
name: "libprotobuf-cpp-full", name: "libprotobuf-cpp-full",
@ -93,6 +94,7 @@ func GatherRequiredDepsForTest() string {
host_supported: true, host_supported: true,
native_coverage: false, native_coverage: false,
sysroot: true, sysroot: true,
apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
} }
rust_library { rust_library {
name: "libtest", name: "libtest",
@ -102,6 +104,7 @@ func GatherRequiredDepsForTest() string {
host_supported: true, host_supported: true,
native_coverage: false, native_coverage: false,
sysroot: true, sysroot: true,
apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
} }
rust_library { rust_library {
name: "libprotobuf", name: "libprotobuf",
@ -122,15 +125,11 @@ func GatherRequiredDepsForTest() string {
host_supported: true, host_supported: true,
} }
` + cc.GatherRequiredDepsForTest(android.NoOsType) `
return bp return bp
} }
func CreateTestContext(config android.Config) *android.TestContext { func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
ctx := android.NewTestArchContext(config)
android.RegisterPrebuiltMutators(ctx)
ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
cc.RegisterRequiredBuildComponentsForTest(ctx)
ctx.RegisterModuleType("rust_binary", RustBinaryFactory) ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory) ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory) ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)
@ -164,6 +163,14 @@ func CreateTestContext(config android.Config) *android.TestContext {
ctx.BottomUp("rust_begin", BeginMutator).Parallel() ctx.BottomUp("rust_begin", BeginMutator).Parallel()
}) })
ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton) ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
}
func CreateTestContext(config android.Config) *android.TestContext {
ctx := android.NewTestArchContext(config)
android.RegisterPrebuiltMutators(ctx)
ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
cc.RegisterRequiredBuildComponentsForTest(ctx)
RegisterRequiredBuildComponentsForTest(ctx)
return ctx return ctx
} }