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 50// Instruments a jar using the Jacoco command line interface. Uses stripSpec to extract a subset 51// of the classes in inputJar into strippedJar, instruments strippedJar into tmpJar, and then 52// combines the classes in tmpJar with inputJar (preferring the instrumented classes in tmpJar) 53// to produce instrumentedJar. 54func jacocoInstrumentJar(ctx android.ModuleContext, instrumentedJar, strippedJar android.WritablePath, 55 inputJar android.Path, stripSpec string) { 56 57 // The basename of tmpJar has to be the same as the basename of strippedJar 58 tmpJar := android.PathForModuleOut(ctx, "jacoco", "tmp", strippedJar.Base()) 59 60 ctx.Build(pctx, android.BuildParams{ 61 Rule: jacoco, 62 Description: "jacoco", 63 Output: instrumentedJar, 64 ImplicitOutput: strippedJar, 65 Input: inputJar, 66 Args: map[string]string{ 67 "strippedJar": strippedJar.String(), 68 "stripSpec": stripSpec, 69 "tmpDir": filepath.Dir(tmpJar.String()), 70 "tmpJar": tmpJar.String(), 71 }, 72 }) 73} 74 75func (j *Module) jacocoModuleToZipCommand(ctx android.ModuleContext) string { 76 includes, err := jacocoFiltersToSpecs(j.properties.Jacoco.Include_filter) 77 if err != nil { 78 ctx.PropertyErrorf("jacoco.include_filter", "%s", err.Error()) 79 } 80 // Also include the default list of classes to exclude from instrumentation. 81 excludes, err := jacocoFiltersToSpecs(append(j.properties.Jacoco.Exclude_filter, config.DefaultJacocoExcludeFilter...)) 82 if err != nil { 83 ctx.PropertyErrorf("jacoco.exclude_filter", "%s", err.Error()) 84 } 85 86 return jacocoFiltersToZipCommand(includes, excludes) 87} 88 89func jacocoFiltersToZipCommand(includes, excludes []string) string { 90 specs := "" 91 if len(excludes) > 0 { 92 specs += android.JoinWithPrefix(excludes, "-x ") + " " 93 } 94 if len(includes) > 0 { 95 specs += strings.Join(includes, " ") 96 } else { 97 specs += "**/*.class" 98 } 99 return specs 100} 101 102func jacocoFiltersToSpecs(filters []string) ([]string, error) { 103 specs := make([]string, len(filters)) 104 var err error 105 for i, f := range filters { 106 specs[i], err = jacocoFilterToSpec(f) 107 if err != nil { 108 return nil, err 109 } 110 } 111 return proptools.NinjaAndShellEscapeList(specs), nil 112} 113 114func jacocoFilterToSpec(filter string) (string, error) { 115 recursiveWildcard := strings.HasSuffix(filter, "**") 116 nonRecursiveWildcard := false 117 if !recursiveWildcard { 118 nonRecursiveWildcard = strings.HasSuffix(filter, "*") 119 filter = strings.TrimSuffix(filter, "*") 120 } else { 121 filter = strings.TrimSuffix(filter, "**") 122 } 123 124 if recursiveWildcard && !(strings.HasSuffix(filter, ".") || filter == "") { 125 return "", fmt.Errorf("only '**' or '.**' is supported as recursive wildcard in a filter") 126 } 127 128 if strings.ContainsRune(filter, '*') { 129 return "", fmt.Errorf("'*' is only supported as the last character in a filter") 130 } 131 132 spec := strings.Replace(filter, ".", "/", -1) 133 134 if recursiveWildcard { 135 spec += "**/*.class" 136 } else if nonRecursiveWildcard { 137 spec += "*.class" 138 } else { 139 spec += ".class" 140 } 141 142 return spec, nil 143} 144