diff --git a/Android.bp b/Android.bp index a9eceb248..342ca4ca9 100644 --- a/Android.bp +++ b/Android.bp @@ -391,6 +391,7 @@ bootstrap_go_package { srcs: [ "rust/androidmk.go", "rust/compiler.go", + "rust/coverage.go", "rust/binary.go", "rust/builder.go", "rust/library.go", @@ -403,6 +404,7 @@ bootstrap_go_package { testSrcs: [ "rust/binary_test.go", "rust/compiler_test.go", + "rust/coverage_test.go", "rust/library_test.go", "rust/rust_test.go", "rust/test_test.go", diff --git a/android/util.go b/android/util.go index e74b64e61..8dbf21459 100644 --- a/android/util.go +++ b/android/util.go @@ -141,6 +141,16 @@ func PrefixInList(list []string, prefix string) bool { return false } +// Returns true if any string in the given list has the given suffix. +func SuffixInList(list []string, suffix string) bool { + for _, s := range list { + if strings.HasSuffix(s, suffix) { + return true + } + } + return false +} + // IndexListPred returns the index of the element which in the given `list` satisfying the predicate, or -1 if there is no such element. func IndexListPred(pred func(s string) bool, list []string) int { for i, l := range list { diff --git a/apex/apex.go b/apex/apex.go index 714cf9c4c..04f3a4705 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -1611,6 +1611,8 @@ func (a *apexBundle) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizer return android.InList(sanitizerName, globalSanitizerNames) } +var _ cc.Coverage = (*apexBundle)(nil) + func (a *apexBundle) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool { return ctx.Device() && (ctx.DeviceConfig().NativeCoverageEnabled() || ctx.DeviceConfig().ClangCoverageEnabled()) } @@ -1627,6 +1629,8 @@ func (a *apexBundle) MarkAsCoverageVariant(coverage bool) { a.properties.IsCoverageVariant = coverage } +func (a *apexBundle) EnableCoverageIfNeeded() {} + // TODO(jiyong) move apexFileFor* close to the apexFile type definition func apexFileForNativeLibrary(ctx android.BaseModuleContext, ccMod *cc.Module, handleSpecialLibs bool) apexFile { // Decide the APEX-local directory by the multilib of the library diff --git a/apex/vndk_test.go b/apex/vndk_test.go index 523ac2630..05cdfcd92 100644 --- a/apex/vndk_test.go +++ b/apex/vndk_test.go @@ -117,44 +117,7 @@ func TestVndkApexUsesVendorVariant(t *testing.T) { }) t.Run("VNDK APEX supports coverage variants", func(t *testing.T) { - ctx, _ := testApex(t, bp+` - cc_library { - name: "libprofile-extras", - vendor_available: true, - recovery_available: true, - native_coverage: false, - system_shared_libs: [], - stl: "none", - notice: "custom_notice", - } - cc_library { - name: "libprofile-clang-extras", - vendor_available: true, - recovery_available: true, - native_coverage: false, - system_shared_libs: [], - stl: "none", - notice: "custom_notice", - } - cc_library { - name: "libprofile-extras_ndk", - vendor_available: true, - native_coverage: false, - system_shared_libs: [], - stl: "none", - notice: "custom_notice", - sdk_version: "current", - } - cc_library { - name: "libprofile-clang-extras_ndk", - vendor_available: true, - native_coverage: false, - system_shared_libs: [], - stl: "none", - notice: "custom_notice", - sdk_version: "current", - } - `, func(fs map[string][]byte, config android.Config) { + ctx, _ := testApex(t, bp, func(fs map[string][]byte, config android.Config) { config.TestProductVariables.Native_coverage = proptools.BoolPtr(true) }) diff --git a/cc/cc.go b/cc/cc.go index 082816ef2..545916ec3 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -437,7 +437,6 @@ var ( ndkLateStubDepTag = DependencyTag{Name: "ndk late stub", Library: true} vndkExtDepTag = DependencyTag{Name: "vndk extends", Library: true} runtimeDepTag = DependencyTag{Name: "runtime lib"} - coverageDepTag = DependencyTag{Name: "coverage"} testPerSrcDepTag = DependencyTag{Name: "test_per_src"} ) @@ -741,6 +740,15 @@ func (c *Module) OutputFile() android.OptionalPath { return c.outputFile } +func (c *Module) CoverageFiles() android.Paths { + if c.linker != nil { + if library, ok := c.linker.(libraryInterface); ok { + return library.objs().coverageFiles + } + } + panic(fmt.Errorf("CoverageFiles called on non-library module: %q", c.BaseModuleName())) +} + var _ LinkableInterface = (*Module)(nil) func (c *Module) UnstrippedOutputFile() android.Path { @@ -2489,13 +2497,16 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { // When combining coverage files for shared libraries and executables, coverage files // in static libraries act as if they were whole static libraries. The same goes for // source based Abi dump files. - // This should only be done for cc.Modules if c, ok := ccDep.(*Module); ok { staticLib := c.linker.(libraryInterface) depPaths.StaticLibObjs.coverageFiles = append(depPaths.StaticLibObjs.coverageFiles, staticLib.objs().coverageFiles...) depPaths.StaticLibObjs.sAbiDumpFiles = append(depPaths.StaticLibObjs.sAbiDumpFiles, staticLib.objs().sAbiDumpFiles...) + } else if c, ok := ccDep.(LinkableInterface); ok { + // Handle non-CC modules here + depPaths.StaticLibObjs.coverageFiles = append(depPaths.StaticLibObjs.coverageFiles, + c.CoverageFiles()...) } } diff --git a/cc/coverage.go b/cc/coverage.go index bde07fd63..cc9a1adab 100644 --- a/cc/coverage.go +++ b/cc/coverage.go @@ -65,10 +65,10 @@ func (cov *coverage) deps(ctx DepsContext, deps Deps) Deps { if cov.Properties.NeedCoverageVariant { ctx.AddVariationDependencies([]blueprint.Variation{ {Mutator: "link", Variation: "static"}, - }, coverageDepTag, getGcovProfileLibraryName(ctx)) + }, CoverageDepTag, getGcovProfileLibraryName(ctx)) ctx.AddVariationDependencies([]blueprint.Variation{ {Mutator: "link", Variation: "static"}, - }, coverageDepTag, getClangProfileLibraryName(ctx)) + }, CoverageDepTag, getClangProfileLibraryName(ctx)) } return deps } @@ -134,14 +134,14 @@ func (cov *coverage) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags if gcovCoverage { flags.Local.LdFlags = append(flags.Local.LdFlags, "--coverage") - coverage := ctx.GetDirectDepWithTag(getGcovProfileLibraryName(ctx), coverageDepTag).(*Module) + coverage := ctx.GetDirectDepWithTag(getGcovProfileLibraryName(ctx), CoverageDepTag).(*Module) deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path()) flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--wrap,getenv") } else if clangCoverage { flags.Local.LdFlags = append(flags.Local.LdFlags, "-fprofile-instr-generate") - coverage := ctx.GetDirectDepWithTag(getClangProfileLibraryName(ctx), coverageDepTag).(*Module) + coverage := ctx.GetDirectDepWithTag(getClangProfileLibraryName(ctx), CoverageDepTag).(*Module) deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path()) } } @@ -150,25 +150,30 @@ func (cov *coverage) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags } func (cov *coverage) begin(ctx BaseModuleContext) { + if ctx.Host() { + // TODO(dwillemsen): because of -nodefaultlibs, we must depend on libclang_rt.profile-*.a + // Just turn off for now. + } else { + cov.Properties = SetCoverageProperties(ctx, cov.Properties, ctx.nativeCoverage(), ctx.useSdk(), ctx.sdkVersion()) + } +} + +func SetCoverageProperties(ctx android.BaseModuleContext, properties CoverageProperties, moduleTypeHasCoverage bool, + useSdk bool, sdkVersion string) CoverageProperties { // Coverage is disabled globally if !ctx.DeviceConfig().NativeCoverageEnabled() && !ctx.DeviceConfig().ClangCoverageEnabled() { - return + return properties } var needCoverageVariant bool var needCoverageBuild bool - if ctx.Host() { - // TODO(dwillemsen): because of -nodefaultlibs, we must depend on libclang_rt.profile-*.a - // Just turn off for now. - } else if !ctx.nativeCoverage() { - // Native coverage is not supported for this module type. - } else { + if moduleTypeHasCoverage { // Check if Native_coverage is set to false. This property defaults to true. - needCoverageVariant = BoolDefault(cov.Properties.Native_coverage, true) - if sdk_version := ctx.sdkVersion(); ctx.useSdk() && sdk_version != "current" { + needCoverageVariant = BoolDefault(properties.Native_coverage, true) + if useSdk && sdkVersion != "current" { // Native coverage is not supported for SDK versions < 23 - if fromApi, err := strconv.Atoi(sdk_version); err == nil && fromApi < 23 { + if fromApi, err := strconv.Atoi(sdkVersion); err == nil && fromApi < 23 { needCoverageVariant = false } } @@ -179,8 +184,10 @@ func (cov *coverage) begin(ctx BaseModuleContext) { } } - cov.Properties.NeedCoverageBuild = needCoverageBuild - cov.Properties.NeedCoverageVariant = needCoverageVariant + properties.NeedCoverageBuild = needCoverageBuild + properties.NeedCoverageVariant = needCoverageVariant + + return properties } // Coverage is an interface for non-CC modules to implement to be mutated for coverage @@ -190,6 +197,7 @@ type Coverage interface { PreventInstall() HideFromMake() MarkAsCoverageVariant(bool) + EnableCoverageIfNeeded() } func coverageMutator(mctx android.BottomUpMutatorContext) { @@ -212,14 +220,17 @@ func coverageMutator(mctx android.BottomUpMutatorContext) { m[1].(*Module).coverage.Properties.IsCoverageVariant = true } } else if cov, ok := mctx.Module().(Coverage); ok && cov.IsNativeCoverageNeeded(mctx) { - // APEX modules fall here + // APEX and Rust modules fall here // Note: variant "" is also created because an APEX can be depended on by another // module which are split into "" and "cov" variants. e.g. when cc_test refers // to an APEX via 'data' property. m := mctx.CreateVariations("", "cov") - m[0].(Coverage).MarkAsCoverageVariant(true) + m[0].(Coverage).MarkAsCoverageVariant(false) m[0].(Coverage).PreventInstall() m[0].(Coverage).HideFromMake() + + m[1].(Coverage).MarkAsCoverageVariant(true) + m[1].(Coverage).EnableCoverageIfNeeded() } } diff --git a/cc/linkable.go b/cc/linkable.go index 4a70d48f7..de36f9017 100644 --- a/cc/linkable.go +++ b/cc/linkable.go @@ -12,6 +12,7 @@ type LinkableInterface interface { CcLibraryInterface() bool OutputFile() android.OptionalPath + CoverageFiles() android.Paths IncludeDirs() android.Paths SetDepsInLinkOrder([]android.Path) @@ -83,4 +84,5 @@ var ( CrtBeginDepTag = DependencyTag{Name: "crtbegin"} CrtEndDepTag = DependencyTag{Name: "crtend"} + CoverageDepTag = DependencyTag{Name: "coverage"} ) diff --git a/cc/testing.go b/cc/testing.go index 53f09955a..a7dde10b9 100644 --- a/cc/testing.go +++ b/cc/testing.go @@ -192,6 +192,45 @@ func GatherRequiredDepsForTest(oses ...android.OsType) string { symbol_file: "", sdk_version: "current", } + + // Coverage libraries + cc_library { + name: "libprofile-extras", + vendor_available: true, + recovery_available: true, + native_coverage: false, + system_shared_libs: [], + stl: "none", + notice: "custom_notice", + } + cc_library { + name: "libprofile-clang-extras", + vendor_available: true, + recovery_available: true, + native_coverage: false, + system_shared_libs: [], + stl: "none", + notice: "custom_notice", + } + cc_library { + name: "libprofile-extras_ndk", + vendor_available: true, + native_coverage: false, + system_shared_libs: [], + stl: "none", + notice: "custom_notice", + sdk_version: "current", + } + cc_library { + name: "libprofile-clang-extras_ndk", + vendor_available: true, + native_coverage: false, + system_shared_libs: [], + stl: "none", + notice: "custom_notice", + sdk_version: "current", + } + cc_library { name: "libdl", no_libcrt: true, diff --git a/java/app.go b/java/app.go index 2fd397ae5..8f2af7241 100755 --- a/java/app.go +++ b/java/app.go @@ -728,6 +728,8 @@ func (a *AndroidApp) MarkAsCoverageVariant(coverage bool) { a.appProperties.IsCoverageVariant = coverage } +func (a *AndroidApp) EnableCoverageIfNeeded() {} + var _ cc.Coverage = (*AndroidApp)(nil) // android_app compiles sources and Android resources into an Android application package `.apk` file. diff --git a/rust/androidmk.go b/rust/androidmk.go index 0fba739a2..0e2bea340 100644 --- a/rust/androidmk.go +++ b/rust/androidmk.go @@ -46,6 +46,12 @@ func (mod *Module) subAndroidMk(data *android.AndroidMkData, obj interface{}) { } func (mod *Module) AndroidMk() android.AndroidMkData { + if mod.Properties.HideFromMake { + return android.AndroidMkData{ + Disabled: true, + } + } + ret := android.AndroidMkData{ OutputFile: mod.outputFile, Include: "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk", @@ -84,6 +90,9 @@ func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.Andr ret.DistFile = binary.distFile ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) { fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", binary.unstrippedOutputFile.String()) + if binary.coverageOutputZipFile.Valid() { + fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+binary.coverageOutputZipFile.String()) + } }) } @@ -124,6 +133,10 @@ func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.An if !library.rlib() { fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", library.unstrippedOutputFile.String()) } + if library.coverageOutputZipFile.Valid() { + fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+library.coverageOutputZipFile.String()) + } + }) } diff --git a/rust/binary.go b/rust/binary.go index fda056e43..c25ae0969 100644 --- a/rust/binary.go +++ b/rust/binary.go @@ -35,9 +35,10 @@ type BinaryCompilerProperties struct { type binaryDecorator struct { *baseCompiler - Properties BinaryCompilerProperties - distFile android.OptionalPath - unstrippedOutputFile android.Path + Properties BinaryCompilerProperties + distFile android.OptionalPath + coverageOutputZipFile android.OptionalPath + unstrippedOutputFile android.Path } var _ compiler = (*binaryDecorator)(nil) @@ -104,6 +105,10 @@ func (binary *binaryDecorator) compilerProps() []interface{} { &binary.Properties) } +func (binary *binaryDecorator) nativeCoverage() bool { + return true +} + func (binary *binaryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path { fileName := binary.getStem(ctx) + ctx.toolchain().ExecutableSuffix() @@ -114,7 +119,21 @@ func (binary *binaryDecorator) compile(ctx ModuleContext, flags Flags, deps Path flags.RustFlags = append(flags.RustFlags, deps.depFlags...) - TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + outputs := TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + binary.coverageFile = outputs.coverageFile + + var coverageFiles android.Paths + if outputs.coverageFile != nil { + coverageFiles = append(coverageFiles, binary.coverageFile) + } + if len(deps.coverageFiles) > 0 { + coverageFiles = append(coverageFiles, deps.coverageFiles...) + } + binary.coverageOutputZipFile = TransformCoverageFilesToZip(ctx, coverageFiles, binary.getStem(ctx)) return outputFile } + +func (binary *binaryDecorator) coverageOutputZipPath() android.OptionalPath { + return binary.coverageOutputZipFile +} diff --git a/rust/builder.go b/rust/builder.go index 2d5e602ca..fbe0e5372 100644 --- a/rust/builder.go +++ b/rust/builder.go @@ -18,6 +18,7 @@ import ( "strings" "github.com/google/blueprint" + "github.com/google/blueprint/pathtools" "android/soong/android" ) @@ -36,44 +37,57 @@ var ( Depfile: "$out.d", }, "rustcFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd") + + zip = pctx.AndroidStaticRule("zip", + blueprint.RuleParams{ + Command: "cat $out.rsp | tr ' ' '\\n' | tr -d \\' | sort -u > ${out}.tmp && ${SoongZipCmd} -o ${out} -C $$OUT_DIR -l ${out}.tmp", + CommandDeps: []string{"${SoongZipCmd}"}, + Rspfile: "$out.rsp", + RspfileContent: "$in", + }) ) -func init() { +type buildOutput struct { + outputFile android.Path + coverageFile android.Path +} +func init() { + pctx.HostBinToolVariable("SoongZipCmd", "soong_zip") } func TransformSrcToBinary(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, - outputFile android.WritablePath, includeDirs []string) { + outputFile android.WritablePath, includeDirs []string) buildOutput { flags.RustFlags = append(flags.RustFlags, "-C lto") - transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin", includeDirs) + return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin", includeDirs) } func TransformSrctoRlib(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, - outputFile android.WritablePath, includeDirs []string) { - transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "rlib", includeDirs) + outputFile android.WritablePath, includeDirs []string) buildOutput { + return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "rlib", includeDirs) } func TransformSrctoDylib(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, - outputFile android.WritablePath, includeDirs []string) { - transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "dylib", includeDirs) + outputFile android.WritablePath, includeDirs []string) buildOutput { + return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "dylib", includeDirs) } func TransformSrctoStatic(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, - outputFile android.WritablePath, includeDirs []string) { + outputFile android.WritablePath, includeDirs []string) buildOutput { flags.RustFlags = append(flags.RustFlags, "-C lto") - transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib", includeDirs) + return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib", includeDirs) } func TransformSrctoShared(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, - outputFile android.WritablePath, includeDirs []string) { + outputFile android.WritablePath, includeDirs []string) buildOutput { flags.RustFlags = append(flags.RustFlags, "-C lto") - transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib", includeDirs) + return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib", includeDirs) } func TransformSrctoProcMacro(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, - flags Flags, outputFile android.WritablePath, includeDirs []string) { - transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "proc-macro", includeDirs) + flags Flags, outputFile android.WritablePath, includeDirs []string) buildOutput { + return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "proc-macro", includeDirs) } func rustLibsToPaths(libs RustLibraries) android.Paths { @@ -85,11 +99,15 @@ func rustLibsToPaths(libs RustLibraries) android.Paths { } func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps PathDeps, flags Flags, - outputFile android.WritablePath, crate_type string, includeDirs []string) { + outputFile android.WritablePath, crate_type string, includeDirs []string) buildOutput { var inputs android.Paths var implicits android.Paths + var output buildOutput var libFlags, rustcFlags, linkFlags []string + var implicitOutputs android.WritablePaths + + output.outputFile = outputFile crate_name := ctx.(ModuleContext).CrateName() targetTriple := ctx.(ModuleContext).toolchain().RustTriple() @@ -141,12 +159,26 @@ func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps Path implicits = append(implicits, deps.CrtBegin.Path(), deps.CrtEnd.Path()) } + if flags.Coverage { + var gcnoFile android.WritablePath + + if outputFile.Ext() != "" { + gcnoFile = android.PathForModuleOut(ctx, pathtools.ReplaceExtension(outputFile.Base(), "gcno")) + } else { + gcnoFile = android.PathForModuleOut(ctx, outputFile.Base()+".gcno") + } + + implicitOutputs = append(implicitOutputs, gcnoFile) + output.coverageFile = gcnoFile + } + ctx.Build(pctx, android.BuildParams{ - Rule: rustc, - Description: "rustc " + main.Rel(), - Output: outputFile, - Inputs: inputs, - Implicits: implicits, + Rule: rustc, + Description: "rustc " + main.Rel(), + Output: outputFile, + ImplicitOutputs: implicitOutputs, + Inputs: inputs, + Implicits: implicits, Args: map[string]string{ "rustcFlags": strings.Join(rustcFlags, " "), "linkFlags": strings.Join(linkFlags, " "), @@ -156,4 +188,23 @@ func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps Path }, }) + return output +} + +func TransformCoverageFilesToZip(ctx android.ModuleContext, + covFiles android.Paths, baseName string) android.OptionalPath { + if len(covFiles) > 0 { + + outputFile := android.PathForModuleOut(ctx, baseName+".zip") + + ctx.Build(pctx, android.BuildParams{ + Rule: zip, + Description: "zip " + outputFile.Base(), + Inputs: covFiles, + Output: outputFile, + }) + + return android.OptionalPathForPath(outputFile) + } + return android.OptionalPath{} } diff --git a/rust/compiler.go b/rust/compiler.go index 74997761b..5f098bc04 100644 --- a/rust/compiler.go +++ b/rust/compiler.go @@ -110,6 +110,7 @@ type baseCompiler struct { linkDirs []string edition string src android.Path //rustc takes a single src file + coverageFile android.Path //rustc generates a single gcno file // Install related dir string @@ -120,6 +121,10 @@ type baseCompiler struct { location installLocation } +func (compiler *baseCompiler) coverageOutputZipPath() android.OptionalPath { + panic("baseCompiler does not implement coverageOutputZipPath()") +} + var _ compiler = (*baseCompiler)(nil) func (compiler *baseCompiler) inData() bool { @@ -235,6 +240,10 @@ func (compiler *baseCompiler) installDir(ctx ModuleContext) android.InstallPath compiler.relativeInstallPath(), compiler.relative) } +func (compiler *baseCompiler) nativeCoverage() bool { + return false +} + func (compiler *baseCompiler) install(ctx ModuleContext, file android.Path) { compiler.path = ctx.InstallFile(compiler.installDir(ctx), file.Base(), file) } diff --git a/rust/coverage.go b/rust/coverage.go new file mode 100644 index 000000000..9be57dccf --- /dev/null +++ b/rust/coverage.go @@ -0,0 +1,72 @@ +// Copyright 2020 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 rust + +import ( + "github.com/google/blueprint" + + "android/soong/cc" +) + +var CovLibraryName = "libprofile-extras" + +type coverage struct { + Properties cc.CoverageProperties + + // Whether binaries containing this module need --coverage added to their ldflags + linkCoverage bool +} + +func (cov *coverage) props() []interface{} { + return []interface{}{&cov.Properties} +} + +func (cov *coverage) deps(ctx DepsContext, deps Deps) Deps { + if cov.Properties.NeedCoverageVariant { + ctx.AddVariationDependencies([]blueprint.Variation{ + {Mutator: "link", Variation: "static"}, + }, cc.CoverageDepTag, CovLibraryName) + } + + return deps +} + +func (cov *coverage) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) { + + if !ctx.DeviceConfig().NativeCoverageEnabled() && !ctx.DeviceConfig().ClangCoverageEnabled() { + return flags, deps + } + + if cov.Properties.CoverageEnabled { + flags.Coverage = true + coverage := ctx.GetDirectDepWithTag(CovLibraryName, cc.CoverageDepTag).(cc.LinkableInterface) + flags.RustFlags = append(flags.RustFlags, + "-Z profile", "-g", "-C opt-level=0", "-C link-dead-code", "-Z no-landing-pads") + flags.LinkFlags = append(flags.LinkFlags, + "--coverage", "-g", coverage.OutputFile().Path().String(), "-Wl,--wrap,getenv") + deps.StaticLibs = append(deps.StaticLibs, coverage.OutputFile().Path()) + } + + return flags, deps +} + +func (cov *coverage) begin(ctx BaseModuleContext) { + if ctx.Host() { + // Host coverage not yet supported. + } else { + // Update useSdk and sdkVersion args if Rust modules become SDK aware. + cov.Properties = cc.SetCoverageProperties(ctx, cov.Properties, ctx.nativeCoverage(), false, "") + } +} diff --git a/rust/coverage_test.go b/rust/coverage_test.go new file mode 100644 index 000000000..27acad325 --- /dev/null +++ b/rust/coverage_test.go @@ -0,0 +1,181 @@ +// Copyright 2020 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 rust + +import ( + "strings" + "testing" + + "android/soong/android" +) + +// Test that coverage flags are being correctly generated. +func TestCoverageFlags(t *testing.T) { + ctx := testRustCov(t, ` + rust_library { + name: "libfoo_cov", + srcs: ["foo.rs"], + crate_name: "foo", + } + rust_binary { + name: "fizz_cov", + srcs: ["foo.rs"], + } + rust_binary { + name: "buzzNoCov", + srcs: ["foo.rs"], + native_coverage: false, + } + rust_library { + name: "libbar_nocov", + srcs: ["foo.rs"], + crate_name: "bar", + native_coverage: false, + }`) + + // Make sure native_coverage: false isn't creating a coverage variant. + if android.InList("android_arm64_armv8-a_dylib_cov", ctx.ModuleVariantsForTests("libbar_nocov")) { + t.Fatalf("coverage variant created for module 'libbar_nocov' with native coverage disabled") + } + + // Just test the dylib variants unless the library coverage logic changes to distinguish between the types. + libfooCov := ctx.ModuleForTests("libfoo_cov", "android_arm64_armv8-a_dylib_cov").Rule("rustc") + libbarNoCov := ctx.ModuleForTests("libbar_nocov", "android_arm64_armv8-a_dylib").Rule("rustc") + fizzCov := ctx.ModuleForTests("fizz_cov", "android_arm64_armv8-a_cov").Rule("rustc") + buzzNoCov := ctx.ModuleForTests("buzzNoCov", "android_arm64_armv8-a").Rule("rustc") + + rustcCoverageFlags := []string{"-Z profile", " -g ", "-C opt-level=0", "-C link-dead-code", "-Z no-landing-pads"} + for _, flag := range rustcCoverageFlags { + missingErrorStr := "missing rustc flag '%s' for '%s' module with coverage enabled; rustcFlags: %#v" + containsErrorStr := "contains rustc flag '%s' for '%s' module with coverage disabled; rustcFlags: %#v" + + if !strings.Contains(fizzCov.Args["rustcFlags"], flag) { + t.Fatalf(missingErrorStr, flag, "fizz_cov", fizzCov.Args["rustcFlags"]) + } + if !strings.Contains(libfooCov.Args["rustcFlags"], flag) { + t.Fatalf(missingErrorStr, flag, "libfoo_cov dylib", libfooCov.Args["rustcFlags"]) + } + if strings.Contains(buzzNoCov.Args["rustcFlags"], flag) { + t.Fatalf(containsErrorStr, flag, "buzzNoCov", buzzNoCov.Args["rustcFlags"]) + } + if strings.Contains(libbarNoCov.Args["rustcFlags"], flag) { + t.Fatalf(containsErrorStr, flag, "libbar_cov", libbarNoCov.Args["rustcFlags"]) + } + } + + linkCoverageFlags := []string{"--coverage", " -g "} + for _, flag := range linkCoverageFlags { + missingErrorStr := "missing rust linker flag '%s' for '%s' module with coverage enabled; rustcFlags: %#v" + containsErrorStr := "contains rust linker flag '%s' for '%s' module with coverage disabled; rustcFlags: %#v" + + if !strings.Contains(fizzCov.Args["linkFlags"], flag) { + t.Fatalf(missingErrorStr, flag, "fizz_cov", fizzCov.Args["linkFlags"]) + } + if !strings.Contains(libfooCov.Args["linkFlags"], flag) { + t.Fatalf(missingErrorStr, flag, "libfoo_cov dylib", libfooCov.Args["linkFlags"]) + } + if strings.Contains(buzzNoCov.Args["linkFlags"], flag) { + t.Fatalf(containsErrorStr, flag, "buzzNoCov", buzzNoCov.Args["linkFlags"]) + } + if strings.Contains(libbarNoCov.Args["linkFlags"], flag) { + t.Fatalf(containsErrorStr, flag, "libbar_cov", libbarNoCov.Args["linkFlags"]) + } + } + +} + +// Test coverage files are included correctly +func TestCoverageZip(t *testing.T) { + ctx := testRustCov(t, ` + rust_library { + name: "libfoo", + srcs: ["foo.rs"], + rlibs: ["librlib"], + crate_name: "foo", + } + rust_library_rlib { + name: "librlib", + srcs: ["foo.rs"], + crate_name: "rlib", + } + rust_binary { + name: "fizz", + rlibs: ["librlib"], + static_libs: ["libfoo"], + srcs: ["foo.rs"], + } + cc_binary { + name: "buzz", + static_libs: ["libfoo"], + srcs: ["foo.c"], + } + cc_library { + name: "libbar", + static_libs: ["libfoo"], + compile_multilib: "64", + srcs: ["foo.c"], + }`) + + fizzZipInputs := ctx.ModuleForTests("fizz", "android_arm64_armv8-a_cov").Rule("zip").Inputs.Strings() + libfooZipInputs := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib_cov").Rule("zip").Inputs.Strings() + buzzZipInputs := ctx.ModuleForTests("buzz", "android_arm64_armv8-a_cov").Rule("zip").Inputs.Strings() + libbarZipInputs := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared_cov").Rule("zip").Inputs.Strings() + + // Make sure the expected number of input files are included. + if len(fizzZipInputs) != 3 { + t.Fatalf("expected only 3 coverage inputs for rust 'fizz' binary, got %#v: %#v", len(fizzZipInputs), fizzZipInputs) + } + if len(libfooZipInputs) != 2 { + t.Fatalf("expected only 2 coverage inputs for rust 'libfoo' library, got %#v: %#v", len(libfooZipInputs), libfooZipInputs) + } + if len(buzzZipInputs) != 2 { + t.Fatalf("expected only 2 coverage inputs for cc 'buzz' binary, got %#v: %#v", len(buzzZipInputs), buzzZipInputs) + } + if len(libbarZipInputs) != 2 { + t.Fatalf("expected only 2 coverage inputs for cc 'libbar' library, got %#v: %#v", len(libbarZipInputs), libbarZipInputs) + } + + // Make sure the expected inputs are provided to the zip rule. + if !android.SuffixInList(fizzZipInputs, "android_arm64_armv8-a_rlib_cov/librlib.gcno") || + !android.SuffixInList(fizzZipInputs, "android_arm64_armv8-a_static_cov/libfoo.gcno") || + !android.SuffixInList(fizzZipInputs, "android_arm64_armv8-a_cov/fizz.gcno") { + t.Fatalf("missing expected coverage files for rust 'fizz' binary: %#v", fizzZipInputs) + } + if !android.SuffixInList(libfooZipInputs, "android_arm64_armv8-a_rlib_cov/librlib.gcno") || + !android.SuffixInList(libfooZipInputs, "android_arm64_armv8-a_dylib_cov/libfoo.dylib.gcno") { + t.Fatalf("missing expected coverage files for rust 'fizz' binary: %#v", libfooZipInputs) + } + if !android.SuffixInList(buzzZipInputs, "android_arm64_armv8-a_cov/obj/foo.gcno") || + !android.SuffixInList(buzzZipInputs, "android_arm64_armv8-a_static_cov/libfoo.gcno") { + t.Fatalf("missing expected coverage files for cc 'buzz' binary: %#v", buzzZipInputs) + } + if !android.SuffixInList(libbarZipInputs, "android_arm64_armv8-a_static_cov/obj/foo.gcno") || + !android.SuffixInList(libbarZipInputs, "android_arm64_armv8-a_static_cov/libfoo.gcno") { + t.Fatalf("missing expected coverage files for cc 'libbar' library: %#v", libbarZipInputs) + } +} + +func TestCoverageDeps(t *testing.T) { + ctx := testRustCov(t, ` + rust_binary { + name: "fizz", + srcs: ["foo.rs"], + }`) + + fizz := ctx.ModuleForTests("fizz", "android_arm64_armv8-a_cov").Rule("rustc") + if !strings.Contains(fizz.Args["linkFlags"], "libprofile-extras.a") { + t.Fatalf("missing expected coverage 'libprofile-extras' dependency in linkFlags: %#v", fizz.Args["linkFlags"]) + } +} diff --git a/rust/library.go b/rust/library.go index 87e816dba..8aa033c0f 100644 --- a/rust/library.go +++ b/rust/library.go @@ -75,11 +75,12 @@ type LibraryMutatedProperties struct { type libraryDecorator struct { *baseCompiler - Properties LibraryCompilerProperties - MutatedProperties LibraryMutatedProperties - distFile android.OptionalPath - unstrippedOutputFile android.Path - includeDirs android.Paths + Properties LibraryCompilerProperties + MutatedProperties LibraryMutatedProperties + distFile android.OptionalPath + coverageOutputZipFile android.OptionalPath + unstrippedOutputFile android.Path + includeDirs android.Paths } type libraryInterface interface { @@ -107,6 +108,10 @@ type libraryInterface interface { BuildOnlyShared() } +func (library *libraryDecorator) nativeCoverage() bool { + return true +} + func (library *libraryDecorator) exportedDirs() []string { return library.linkDirs } @@ -351,24 +356,37 @@ func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps Pa fileName := library.getStem(ctx) + ctx.toolchain().RlibSuffix() outputFile = android.PathForModuleOut(ctx, fileName) - TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + outputs := TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + library.coverageFile = outputs.coverageFile } else if library.dylib() { fileName := library.getStem(ctx) + ctx.toolchain().DylibSuffix() outputFile = android.PathForModuleOut(ctx, fileName) - TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + outputs := TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + library.coverageFile = outputs.coverageFile } else if library.static() { fileName := library.getStem(ctx) + ctx.toolchain().StaticLibSuffix() outputFile = android.PathForModuleOut(ctx, fileName) - TransformSrctoStatic(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + outputs := TransformSrctoStatic(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + library.coverageFile = outputs.coverageFile } else if library.shared() { fileName := library.getStem(ctx) + ctx.toolchain().SharedLibSuffix() outputFile = android.PathForModuleOut(ctx, fileName) - TransformSrctoShared(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + outputs := TransformSrctoShared(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + library.coverageFile = outputs.coverageFile } + var coverageFiles android.Paths + if library.coverageFile != nil { + coverageFiles = append(coverageFiles, library.coverageFile) + } + if len(deps.coverageFiles) > 0 { + coverageFiles = append(coverageFiles, deps.coverageFiles...) + } + library.coverageOutputZipFile = TransformCoverageFilesToZip(ctx, coverageFiles, library.getStem(ctx)) + if library.rlib() || library.dylib() { library.reexportDirs(deps.linkDirs...) library.reexportDepFlags(deps.depFlags...) diff --git a/rust/prebuilt.go b/rust/prebuilt.go index 45bef9ea3..1d9765053 100644 --- a/rust/prebuilt.go +++ b/rust/prebuilt.go @@ -69,3 +69,7 @@ func (prebuilt *prebuiltLibraryDecorator) compilerDeps(ctx DepsContext, deps Dep deps = prebuilt.baseCompiler.compilerDeps(ctx, deps) return deps } + +func (prebuilt *prebuiltLibraryDecorator) nativeCoverage() bool { + return false +} diff --git a/rust/rust.go b/rust/rust.go index 5cc884572..8cf2e6df1 100644 --- a/rust/rust.go +++ b/rust/rust.go @@ -40,6 +40,7 @@ func init() { android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { ctx.BottomUp("rust_libraries", LibraryMutator).Parallel() ctx.BottomUp("rust_unit_tests", TestPerSrcMutator).Parallel() + ctx.BottomUp("rust_begin", BeginMutator).Parallel() }) pctx.Import("android/soong/rust/config") } @@ -51,6 +52,7 @@ type Flags struct { LinkFlags []string // Flags that apply to linker RustFlagsDeps android.Paths // Files depended on by compiler flags Toolchain config.Toolchain + Coverage bool } type BaseProperties struct { @@ -60,6 +62,8 @@ type BaseProperties struct { AndroidMkSharedLibs []string AndroidMkStaticLibs []string SubName string `blueprint:"mutated"` + PreventInstall bool + HideFromMake bool } type Module struct { @@ -72,6 +76,7 @@ type Module struct { multilib android.Multilib compiler compiler + coverage *coverage cachedToolchain config.Toolchain subAndroidMkOnce map[subAndroidMkProvider]bool outputFile android.OptionalPath @@ -224,6 +229,8 @@ type PathDeps struct { depFlags []string //ReexportedDeps android.Paths + coverageFiles android.Paths + CrtBegin android.OptionalPath CrtEnd android.OptionalPath } @@ -245,6 +252,34 @@ type compiler interface { inData() bool install(ctx ModuleContext, path android.Path) relativeInstallPath() string + + nativeCoverage() bool +} + +func (mod *Module) isCoverageVariant() bool { + return mod.coverage.Properties.IsCoverageVariant +} + +var _ cc.Coverage = (*Module)(nil) + +func (mod *Module) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool { + return mod.coverage != nil && mod.coverage.Properties.NeedCoverageVariant +} + +func (mod *Module) PreventInstall() { + mod.Properties.PreventInstall = true +} + +func (mod *Module) HideFromMake() { + mod.Properties.HideFromMake = true +} + +func (mod *Module) MarkAsCoverageVariant(coverage bool) { + mod.coverage.Properties.IsCoverageVariant = coverage +} + +func (mod *Module) EnableCoverageIfNeeded() { + mod.coverage.Properties.CoverageEnabled = mod.coverage.Properties.NeedCoverageBuild } func defaultsFactory() android.Module { @@ -268,6 +303,7 @@ func DefaultsFactory(props ...interface{}) android.Module { &ProcMacroCompilerProperties{}, &PrebuiltProperties{}, &TestProperties{}, + &cc.CoverageProperties{}, ) android.InitDefaultsModule(module) @@ -395,6 +431,18 @@ func (mod *Module) HasStaticVariant() bool { return false } +func (mod *Module) CoverageFiles() android.Paths { + if mod.compiler != nil { + if library, ok := mod.compiler.(*libraryDecorator); ok { + if library.coverageFile != nil { + return android.Paths{library.coverageFile} + } + return android.Paths{} + } + } + panic(fmt.Errorf("CoverageFiles called on non-library module: %q", mod.BaseModuleName())) +} + var _ cc.LinkableInterface = (*Module)(nil) func (mod *Module) Init() android.Module { @@ -403,6 +451,10 @@ func (mod *Module) Init() android.Module { if mod.compiler != nil { mod.AddProperties(mod.compiler.compilerProps()...) } + if mod.coverage != nil { + mod.AddProperties(mod.coverage.props()...) + } + android.InitAndroidArchModule(mod, mod.hod, mod.multilib) android.InitDefaultableModule(mod) @@ -432,6 +484,7 @@ func newBaseModule(hod android.HostOrDeviceSupported, multilib android.Multilib) } func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module { module := newBaseModule(hod, multilib) + module.coverage = &coverage{} return module } @@ -454,6 +507,7 @@ type ModuleContextIntf interface { toolchain() config.Toolchain baseModuleName() string CrateName() string + nativeCoverage() bool } type depsContext struct { @@ -466,6 +520,14 @@ type moduleContext struct { moduleContextImpl } +func (ctx *moduleContextImpl) nativeCoverage() bool { + return ctx.mod.nativeCoverage() +} + +func (mod *Module) nativeCoverage() bool { + return mod.compiler != nil && mod.compiler.nativeCoverage() +} + type moduleContextImpl struct { mod *Module ctx BaseModuleContext @@ -508,9 +570,17 @@ func (mod *Module) GenerateAndroidBuildActions(actx android.ModuleContext) { if mod.compiler != nil { flags = mod.compiler.compilerFlags(ctx, flags) + } + if mod.coverage != nil { + flags, deps = mod.coverage.flags(ctx, flags, deps) + } + + if mod.compiler != nil { outputFile := mod.compiler.compile(ctx, flags, deps) mod.outputFile = android.OptionalPathForPath(outputFile) - mod.compiler.install(ctx, mod.outputFile.Path()) + if !mod.Properties.PreventInstall { + mod.compiler.install(ctx, mod.outputFile.Path()) + } } } @@ -521,6 +591,10 @@ func (mod *Module) deps(ctx DepsContext) Deps { deps = mod.compiler.compilerDeps(ctx, deps) } + if mod.coverage != nil { + deps = mod.coverage.deps(ctx, deps) + } + deps.Rlibs = android.LastUniqueStrings(deps.Rlibs) deps.Dylibs = android.LastUniqueStrings(deps.Dylibs) deps.ProcMacros = android.LastUniqueStrings(deps.ProcMacros) @@ -553,6 +627,12 @@ var ( testPerSrcDepTag = dependencyTag{name: "rust_unit_tests"} ) +func (mod *Module) begin(ctx BaseModuleContext) { + if mod.coverage != nil { + mod.coverage.begin(ctx) + } +} + func (mod *Module) depsToPaths(ctx android.ModuleContext) PathDeps { var depPaths PathDeps @@ -588,6 +668,7 @@ func (mod *Module) depsToPaths(ctx android.ModuleContext) PathDeps { ctx.ModuleErrorf("mod %q not an rlib library", depName) return } + depPaths.coverageFiles = append(depPaths.coverageFiles, rustDep.CoverageFiles()...) directRlibDeps = append(directRlibDeps, rustDep) mod.Properties.AndroidMkRlibs = append(mod.Properties.AndroidMkRlibs, depName) case procMacroDepTag: @@ -642,6 +723,7 @@ func (mod *Module) depsToPaths(ctx android.ModuleContext) PathDeps { depFlag = "-lstatic=" + libName depPaths.linkDirs = append(depPaths.linkDirs, linkPath) depPaths.depFlags = append(depPaths.depFlags, depFlag) + depPaths.coverageFiles = append(depPaths.coverageFiles, ccDep.CoverageFiles()...) directStaticLibDeps = append(directStaticLibDeps, ccDep) mod.Properties.AndroidMkStaticLibs = append(mod.Properties.AndroidMkStaticLibs, depName) case cc.SharedDepTag: @@ -772,6 +854,29 @@ func (mod *Module) DepsMutator(actx android.BottomUpMutatorContext) { actx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), procMacroDepTag, deps.ProcMacros...) } +func BeginMutator(ctx android.BottomUpMutatorContext) { + if mod, ok := ctx.Module().(*Module); ok && mod.Enabled() { + mod.beginMutator(ctx) + } +} + +type baseModuleContext struct { + android.BaseModuleContext + moduleContextImpl +} + +func (mod *Module) beginMutator(actx android.BottomUpMutatorContext) { + ctx := &baseModuleContext{ + BaseModuleContext: actx, + moduleContextImpl: moduleContextImpl{ + mod: mod, + }, + } + ctx.ctx = ctx + + mod.begin(ctx) +} + func (mod *Module) Name() string { name := mod.ModuleBase.Name() if p, ok := mod.compiler.(interface { diff --git a/rust/rust_test.go b/rust/rust_test.go index 32eddc161..d658ee201 100644 --- a/rust/rust_test.go +++ b/rust/rust_test.go @@ -21,6 +21,8 @@ import ( "strings" "testing" + "github.com/google/blueprint/proptools" + "android/soong/android" "android/soong/cc" ) @@ -57,6 +59,7 @@ func testConfig(bp string) android.Config { fs := map[string][]byte{ "foo.rs": nil, + "foo.c": nil, "src/bar.rs": nil, "liby.so": nil, "libz.so": nil, @@ -68,6 +71,14 @@ func testConfig(bp string) android.Config { } func testRust(t *testing.T, bp string) *android.TestContext { + return testRustContext(t, bp, false) +} + +func testRustCov(t *testing.T, bp string) *android.TestContext { + return testRustContext(t, bp, true) +} + +func testRustContext(t *testing.T, bp string, coverage bool) *android.TestContext { // TODO (b/140435149) if runtime.GOOS != "linux" { t.Skip("Only the Linux toolchain is supported for Rust") @@ -76,6 +87,11 @@ func testRust(t *testing.T, bp string) *android.TestContext { t.Helper() config := testConfig(bp) + if coverage { + config.TestProductVariables.Native_coverage = proptools.BoolPtr(true) + config.TestProductVariables.CoveragePaths = []string{"*"} + } + t.Helper() ctx := CreateTestContext() ctx.Register(config) diff --git a/rust/test.go b/rust/test.go index 04f844cbc..94568c1cc 100644 --- a/rust/test.go +++ b/rust/test.go @@ -50,6 +50,10 @@ type testDecorator struct { testConfig android.Path } +func (test *testDecorator) nativeCoverage() bool { + return true +} + func NewRustTest(hod android.HostOrDeviceSupported) (*Module, *testDecorator) { module := newModule(hod, android.MultilibFirst) diff --git a/rust/testing.go b/rust/testing.go index aad4ffe35..09008a85f 100644 --- a/rust/testing.go +++ b/rust/testing.go @@ -98,6 +98,7 @@ func CreateTestContext() *android.TestContext { // rust mutators ctx.BottomUp("rust_libraries", LibraryMutator).Parallel() ctx.BottomUp("rust_unit_tests", TestPerSrcMutator).Parallel() + ctx.BottomUp("rust_begin", BeginMutator).Parallel() }) return ctx