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.MergeZipsCmd} --ignore-duplicates -j $out $tmpJar $in`, 38 CommandDeps: []string{ 39 "${config.Zip2ZipCmd}", 40 "${config.JavaCmd}", 41 "${config.JacocoCLIJar}", 42 "${config.MergeZipsCmd}", 43 }, 44 }, 45 "strippedJar", "stripSpec", "tmpDir", "tmpJar") 46) 47 48func jacocoDepsMutator(ctx android.BottomUpMutatorContext) { 49 type instrumentable interface { 50 shouldInstrument(ctx android.BaseModuleContext) bool 51 shouldInstrumentInApex(ctx android.BaseModuleContext) bool 52 setInstrument(value bool) 53 } 54 55 j, ok := ctx.Module().(instrumentable) 56 if !ctx.Module().Enabled(ctx) || !ok { 57 return 58 } 59 60 if j.shouldInstrumentInApex(ctx) { 61 j.setInstrument(true) 62 } 63 64 if j.shouldInstrument(ctx) && ctx.ModuleName() != "jacocoagent" { 65 // We can use AddFarVariationDependencies here because, since this dep 66 // is added as libs only (i.e. a compiletime CLASSPATH entry only), 67 // the first variant of jacocoagent is sufficient to prevent 68 // compile time errors. 69 // At this stage in the build, AddVariationDependencies is not always 70 // able to procure a variant of jacocoagent that matches the calling 71 // module. 72 ctx.AddFarVariationDependencies(ctx.Module().Target().Variations(), libTag, "jacocoagent") 73 } 74} 75 76// Instruments a jar using the Jacoco command line interface. Uses stripSpec to extract a subset 77// of the classes in inputJar into strippedJar, instruments strippedJar into tmpJar, and then 78// combines the classes in tmpJar with inputJar (preferring the instrumented classes in tmpJar) 79// to produce instrumentedJar. 80func jacocoInstrumentJar(ctx android.ModuleContext, instrumentedJar, strippedJar android.WritablePath, 81 inputJar android.Path, stripSpec string) { 82 83 // The basename of tmpJar has to be the same as the basename of strippedJar 84 tmpJar := android.PathForModuleOut(ctx, "jacoco", "tmp", strippedJar.Base()) 85 86 ctx.Build(pctx, android.BuildParams{ 87 Rule: jacoco, 88 Description: "jacoco", 89 Output: instrumentedJar, 90 ImplicitOutput: strippedJar, 91 Input: inputJar, 92 Args: map[string]string{ 93 "strippedJar": strippedJar.String(), 94 "stripSpec": stripSpec, 95 "tmpDir": filepath.Dir(tmpJar.String()), 96 "tmpJar": tmpJar.String(), 97 }, 98 }) 99} 100 101func (j *Module) jacocoModuleToZipCommand(ctx android.ModuleContext) string { 102 includes, err := jacocoFiltersToSpecs(j.properties.Jacoco.Include_filter) 103 if err != nil { 104 ctx.PropertyErrorf("jacoco.include_filter", "%s", err.Error()) 105 } 106 // Also include the default list of classes to exclude from instrumentation. 107 excludes, err := jacocoFiltersToSpecs(append(j.properties.Jacoco.Exclude_filter, config.DefaultJacocoExcludeFilter...)) 108 if err != nil { 109 ctx.PropertyErrorf("jacoco.exclude_filter", "%s", err.Error()) 110 } 111 112 return jacocoFiltersToZipCommand(includes, excludes) 113} 114 115func jacocoFiltersToZipCommand(includes, excludes []string) string { 116 specs := "" 117 if len(excludes) > 0 { 118 specs += android.JoinWithPrefix(excludes, "-x ") + " " 119 } 120 if len(includes) > 0 { 121 specs += strings.Join(includes, " ") 122 } else { 123 specs += "'**/*.class'" 124 } 125 return specs 126} 127 128func jacocoFiltersToSpecs(filters []string) ([]string, error) { 129 specs := make([]string, len(filters)) 130 var err error 131 for i, f := range filters { 132 specs[i], err = jacocoFilterToSpec(f) 133 if err != nil { 134 return nil, err 135 } 136 } 137 return proptools.NinjaAndShellEscapeList(specs), nil 138} 139 140func jacocoFilterToSpec(filter string) (string, error) { 141 recursiveWildcard := strings.HasSuffix(filter, "**") 142 nonRecursiveWildcard := false 143 if !recursiveWildcard { 144 nonRecursiveWildcard = strings.HasSuffix(filter, "*") 145 filter = strings.TrimSuffix(filter, "*") 146 } else { 147 filter = strings.TrimSuffix(filter, "**") 148 } 149 150 if recursiveWildcard && !(strings.HasSuffix(filter, ".") || filter == "") { 151 return "", fmt.Errorf("only '**' or '.**' is supported as recursive wildcard in a filter") 152 } 153 154 if strings.ContainsRune(filter, '*') { 155 return "", fmt.Errorf("'*' is only supported as the last character in a filter") 156 } 157 158 spec := strings.Replace(filter, ".", "/", -1) 159 160 if recursiveWildcard { 161 spec += "**/*.class" 162 } else if nonRecursiveWildcard { 163 spec += "*.class" 164 } else { 165 spec += ".class" 166 } 167 168 return spec, nil 169} 170