1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package java 16 17// Rules for instrumenting classes using jacoco 18 19import ( 20 "fmt" 21 "path/filepath" 22 "strings" 23 24 "github.com/google/blueprint" 25 "github.com/google/blueprint/proptools" 26 27 "android/soong/android" 28 "android/soong/java/config" 29) 30 31var ( 32 jacoco = pctx.AndroidStaticRule("jacoco", blueprint.RuleParams{ 33 Command: `rm -rf $tmpDir && mkdir -p $tmpDir && ` + 34 `${config.Zip2ZipCmd} -i $in -o $strippedJar $stripSpec && ` + 35 `${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.JacocoCLIJar} ` + 36 ` instrument --quiet --dest $tmpDir $strippedJar && ` + 37 `${config.Ziptime} $tmpJar && ` + 38 `${config.MergeZipsCmd} --ignore-duplicates -j $out $tmpJar $in`, 39 CommandDeps: []string{ 40 "${config.Zip2ZipCmd}", 41 "${config.JavaCmd}", 42 "${config.JacocoCLIJar}", 43 "${config.Ziptime}", 44 "${config.MergeZipsCmd}", 45 }, 46 }, 47 "strippedJar", "stripSpec", "tmpDir", "tmpJar") 48) 49 50func jacocoDepsMutator(ctx android.BottomUpMutatorContext) { 51 type instrumentable interface { 52 shouldInstrument(ctx android.BaseModuleContext) bool 53 shouldInstrumentInApex(ctx android.BaseModuleContext) bool 54 setInstrument(value bool) 55 } 56 57 j, ok := ctx.Module().(instrumentable) 58 if !ctx.Module().Enabled() || !ok { 59 return 60 } 61 62 if j.shouldInstrumentInApex(ctx) { 63 j.setInstrument(true) 64 } 65 66 if j.shouldInstrument(ctx) && ctx.ModuleName() != "jacocoagent" { 67 // We can use AddFarVariationDependencies here because, since this dep 68 // is added as libs only (i.e. a compiletime CLASSPATH entry only), 69 // the first variant of jacocoagent is sufficient to prevent 70 // compile time errors. 71 // At this stage in the build, AddVariationDependencies is not always 72 // able to procure a variant of jacocoagent that matches the calling 73 // module. 74 ctx.AddFarVariationDependencies(ctx.Module().Target().Variations(), libTag, "jacocoagent") 75 } 76} 77 78// Instruments a jar using the Jacoco command line interface. Uses stripSpec to extract a subset 79// of the classes in inputJar into strippedJar, instruments strippedJar into tmpJar, and then 80// combines the classes in tmpJar with inputJar (preferring the instrumented classes in tmpJar) 81// to produce instrumentedJar. 82func jacocoInstrumentJar(ctx android.ModuleContext, instrumentedJar, strippedJar android.WritablePath, 83 inputJar android.Path, stripSpec string) { 84 85 // The basename of tmpJar has to be the same as the basename of strippedJar 86 tmpJar := android.PathForModuleOut(ctx, "jacoco", "tmp", strippedJar.Base()) 87 88 ctx.Build(pctx, android.BuildParams{ 89 Rule: jacoco, 90 Description: "jacoco", 91 Output: instrumentedJar, 92 ImplicitOutput: strippedJar, 93 Input: inputJar, 94 Args: map[string]string{ 95 "strippedJar": strippedJar.String(), 96 "stripSpec": stripSpec, 97 "tmpDir": filepath.Dir(tmpJar.String()), 98 "tmpJar": tmpJar.String(), 99 }, 100 }) 101} 102 103func (j *Module) jacocoModuleToZipCommand(ctx android.ModuleContext) string { 104 includes, err := jacocoFiltersToSpecs(j.properties.Jacoco.Include_filter) 105 if err != nil { 106 ctx.PropertyErrorf("jacoco.include_filter", "%s", err.Error()) 107 } 108 // Also include the default list of classes to exclude from instrumentation. 109 excludes, err := jacocoFiltersToSpecs(append(j.properties.Jacoco.Exclude_filter, config.DefaultJacocoExcludeFilter...)) 110 if err != nil { 111 ctx.PropertyErrorf("jacoco.exclude_filter", "%s", err.Error()) 112 } 113 114 return jacocoFiltersToZipCommand(includes, excludes) 115} 116 117func jacocoFiltersToZipCommand(includes, excludes []string) string { 118 specs := "" 119 if len(excludes) > 0 { 120 specs += android.JoinWithPrefix(excludes, "-x ") + " " 121 } 122 if len(includes) > 0 { 123 specs += strings.Join(includes, " ") 124 } else { 125 specs += "'**/*.class'" 126 } 127 return specs 128} 129 130func jacocoFiltersToSpecs(filters []string) ([]string, error) { 131 specs := make([]string, len(filters)) 132 var err error 133 for i, f := range filters { 134 specs[i], err = jacocoFilterToSpec(f) 135 if err != nil { 136 return nil, err 137 } 138 } 139 return proptools.NinjaAndShellEscapeList(specs), nil 140} 141 142func jacocoFilterToSpec(filter string) (string, error) { 143 recursiveWildcard := strings.HasSuffix(filter, "**") 144 nonRecursiveWildcard := false 145 if !recursiveWildcard { 146 nonRecursiveWildcard = strings.HasSuffix(filter, "*") 147 filter = strings.TrimSuffix(filter, "*") 148 } else { 149 filter = strings.TrimSuffix(filter, "**") 150 } 151 152 if recursiveWildcard && !(strings.HasSuffix(filter, ".") || filter == "") { 153 return "", fmt.Errorf("only '**' or '.**' is supported as recursive wildcard in a filter") 154 } 155 156 if strings.ContainsRune(filter, '*') { 157 return "", fmt.Errorf("'*' is only supported as the last character in a filter") 158 } 159 160 spec := strings.Replace(filter, ".", "/", -1) 161 162 if recursiveWildcard { 163 spec += "**/*.class" 164 } else if nonRecursiveWildcard { 165 spec += "*.class" 166 } else { 167 spec += ".class" 168 } 169 170 return spec, nil 171} 172