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