From a0cd8f9acb8c5c04c1082cc980cc8cca28aff5a9 Mon Sep 17 00:00:00 2001 From: Ivan Lozano Date: Thu, 9 Apr 2020 09:56:02 -0400 Subject: [PATCH] Add gcov coverage support to Rust modules. This adds gcov coverage support for Rust device library and binary modules (including test modules). Support is provided to pass Rust static library gcno files to CC modules and visa versa. Additional changes: * Begin mutator added for Rust modules. * SuffixInList added to android package. * CoverageEnabled added to Coverage interface. * CoverageFiles added to LinkableLibrary interface. * Fix in coverage mutator for non-CC modules which marked the wrong variant as the coverage variant. * Added coverage libraries to the cc.GatherRequiredDepsForTest. Bug: 146448203 Test: NATIVE_COVERAGE=true COVERAGE_PATHS='*' m -j Change-Id: If20728bdde42a1dd544a35a40f0d981b80a5835f --- Android.bp | 2 + android/util.go | 10 +++ apex/apex.go | 4 + apex/vndk_test.go | 39 +-------- cc/cc.go | 15 +++- cc/coverage.go | 47 ++++++----- cc/linkable.go | 2 + cc/testing.go | 39 +++++++++ java/app.go | 2 + rust/androidmk.go | 13 +++ rust/binary.go | 27 ++++++- rust/builder.go | 89 ++++++++++++++++----- rust/compiler.go | 9 +++ rust/coverage.go | 72 +++++++++++++++++ rust/coverage_test.go | 181 ++++++++++++++++++++++++++++++++++++++++++ rust/library.go | 36 ++++++--- rust/prebuilt.go | 4 + rust/rust.go | 107 ++++++++++++++++++++++++- rust/rust_test.go | 16 ++++ rust/test.go | 4 + rust/testing.go | 1 + 21 files changed, 628 insertions(+), 91 deletions(-) create mode 100644 rust/coverage.go create mode 100644 rust/coverage_test.go 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