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 <rust_module>
Change-Id: If20728bdde42a1dd544a35a40f0d981b80a5835f
This commit is contained in:
Ivan Lozano 2020-04-09 09:56:02 -04:00
parent b8bb5d05ce
commit a0cd8f9acb
21 changed files with 628 additions and 91 deletions

View file

@ -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",

View file

@ -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 {

View file

@ -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

View file

@ -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)
})

View file

@ -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()...)
}
}

View file

@ -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()
}
}

View file

@ -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"}
)

View file

@ -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,

View file

@ -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.

View file

@ -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())
}
})
}

View file

@ -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
}

View file

@ -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{}
}

View file

@ -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)
}

72
rust/coverage.go Normal file
View file

@ -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, "")
}
}

181
rust/coverage_test.go Normal file
View file

@ -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"])
}
}

View file

@ -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...)

View file

@ -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
}

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -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