1// Copyright 2016 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 cc 16 17import ( 18 "path/filepath" 19 "regexp" 20 "strings" 21 22 "github.com/google/blueprint/proptools" 23 24 "android/soong/android" 25 "android/soong/cc/config" 26) 27 28type TidyProperties struct { 29 // whether to run clang-tidy over C-like sources. 30 Tidy *bool 31 32 // Extra flags to pass to clang-tidy 33 Tidy_flags []string 34 35 // Extra checks to enable or disable in clang-tidy 36 Tidy_checks []string 37 38 // Checks that should be treated as errors. 39 Tidy_checks_as_errors []string 40} 41 42type tidyFeature struct { 43 Properties TidyProperties 44} 45 46var quotedFlagRegexp, _ = regexp.Compile(`^-?-[^=]+=('|").*('|")$`) 47 48// When passing flag -name=value, if user add quotes around 'value', 49// the quotation marks will be preserved by NinjaAndShellEscapeList 50// and the 'value' string with quotes won't work like the intended value. 51// So here we report an error if -*='*' is found. 52func checkNinjaAndShellEscapeList(ctx ModuleContext, prop string, slice []string) []string { 53 for _, s := range slice { 54 if quotedFlagRegexp.MatchString(s) { 55 ctx.PropertyErrorf(prop, "Extra quotes in: %s", s) 56 } 57 } 58 return proptools.NinjaAndShellEscapeList(slice) 59} 60 61func (tidy *tidyFeature) props() []interface{} { 62 return []interface{}{&tidy.Properties} 63} 64 65func (tidy *tidyFeature) flags(ctx ModuleContext, flags Flags) Flags { 66 CheckBadTidyFlags(ctx, "tidy_flags", tidy.Properties.Tidy_flags) 67 CheckBadTidyChecks(ctx, "tidy_checks", tidy.Properties.Tidy_checks) 68 69 // Check if tidy is explicitly disabled for this module 70 if tidy.Properties.Tidy != nil && !*tidy.Properties.Tidy { 71 return flags 72 } 73 74 // If not explicitly disabled, set flags.Tidy to generate .tidy rules. 75 // Note that libraries and binaries will depend on .tidy files ONLY if 76 // the global WITH_TIDY or module 'tidy' property is true. 77 flags.Tidy = true 78 79 // If explicitly enabled, by global default or local tidy property, 80 // set flags.NeedTidyFiles to make this module depend on .tidy files. 81 if ctx.Config().ClangTidy() || Bool(tidy.Properties.Tidy) { 82 flags.NeedTidyFiles = true 83 } 84 85 // Add global WITH_TIDY_FLAGS and local tidy_flags. 86 withTidyFlags := ctx.Config().Getenv("WITH_TIDY_FLAGS") 87 if len(withTidyFlags) > 0 { 88 flags.TidyFlags = append(flags.TidyFlags, withTidyFlags) 89 } 90 esc := checkNinjaAndShellEscapeList 91 flags.TidyFlags = append(flags.TidyFlags, esc(ctx, "tidy_flags", tidy.Properties.Tidy_flags)...) 92 // If TidyFlags does not contain -header-filter, add default header filter. 93 // Find the substring because the flag could also appear as --header-filter=... 94 // and with or without single or double quotes. 95 if !android.SubstringInList(flags.TidyFlags, "-header-filter=") { 96 defaultDirs := ctx.Config().Getenv("DEFAULT_TIDY_HEADER_DIRS") 97 headerFilter := "-header-filter=" 98 if defaultDirs == "" { 99 headerFilter += ctx.ModuleDir() + "/" 100 } else { 101 headerFilter += "\"(" + ctx.ModuleDir() + "/|" + defaultDirs + ")\"" 102 } 103 flags.TidyFlags = append(flags.TidyFlags, headerFilter) 104 } 105 // Work around RBE bug in parsing clang-tidy flags, replace "--flag" with "-flag". 106 // Some C/C++ modules added local tidy flags like --header-filter= and --extra-arg-before=. 107 doubleDash := regexp.MustCompile("^('?)--(.*)$") 108 for i, s := range flags.TidyFlags { 109 flags.TidyFlags[i] = doubleDash.ReplaceAllString(s, "$1-$2") 110 } 111 112 // If clang-tidy is not enabled globally, add the -quiet flag. 113 if !ctx.Config().ClangTidy() { 114 flags.TidyFlags = append(flags.TidyFlags, "-quiet") 115 flags.TidyFlags = append(flags.TidyFlags, "-extra-arg-before=-fno-caret-diagnostics") 116 } 117 118 extraArgFlags := []string{ 119 // We might be using the static analyzer through clang tidy. 120 // https://bugs.llvm.org/show_bug.cgi?id=32914 121 "-D__clang_analyzer__", 122 123 // A recent change in clang-tidy (r328258) enabled destructor inlining, which 124 // appears to cause a number of false positives. Until that's resolved, this turns 125 // off the effects of r328258. 126 // https://bugs.llvm.org/show_bug.cgi?id=37459 127 "-Xclang", "-analyzer-config", "-Xclang", "c++-temp-dtor-inlining=false", 128 } 129 130 for _, f := range extraArgFlags { 131 flags.TidyFlags = append(flags.TidyFlags, "-extra-arg-before="+f) 132 } 133 134 tidyChecks := "-checks=" 135 if checks := ctx.Config().TidyChecks(); len(checks) > 0 { 136 tidyChecks += checks 137 } else { 138 tidyChecks += config.TidyChecksForDir(ctx.ModuleDir()) 139 } 140 if len(tidy.Properties.Tidy_checks) > 0 { 141 tidyChecks = tidyChecks + "," + strings.Join(esc(ctx, "tidy_checks", 142 config.ClangRewriteTidyChecks(tidy.Properties.Tidy_checks)), ",") 143 } 144 if ctx.Windows() { 145 // https://b.corp.google.com/issues/120614316 146 // mingw32 has cert-dcl16-c warning in NO_ERROR, 147 // which is used in many Android files. 148 tidyChecks = tidyChecks + ",-cert-dcl16-c" 149 } 150 // https://b.corp.google.com/issues/153464409 151 // many local projects enable cert-* checks, which 152 // trigger bugprone-reserved-identifier. 153 tidyChecks = tidyChecks + ",-bugprone-reserved-identifier*,-cert-dcl51-cpp,-cert-dcl37-c" 154 // http://b/153757728 155 tidyChecks = tidyChecks + ",-readability-qualified-auto" 156 // http://b/155034563 157 tidyChecks = tidyChecks + ",-bugprone-signed-char-misuse" 158 // http://b/155034972 159 tidyChecks = tidyChecks + ",-bugprone-branch-clone" 160 // http://b/193716442 161 tidyChecks = tidyChecks + ",-bugprone-implicit-widening-of-multiplication-result" 162 // Too many existing functions trigger this rule, and fixing it requires large code 163 // refactoring. The cost of maintaining this tidy rule outweighs the benefit it brings. 164 tidyChecks = tidyChecks + ",-bugprone-easily-swappable-parameters" 165 // http://b/216364337 - TODO: Follow-up after compiler update to 166 // disable or fix individual instances. 167 tidyChecks = tidyChecks + ",-cert-err33-c" 168 flags.TidyFlags = append(flags.TidyFlags, tidyChecks) 169 170 if ctx.Config().IsEnvTrue("WITH_TIDY") { 171 // WITH_TIDY=1 enables clang-tidy globally. There could be many unexpected 172 // warnings from new checks and many local tidy_checks_as_errors and 173 // -warnings-as-errors can break a global build. 174 // So allow all clang-tidy warnings. 175 inserted := false 176 for i, s := range flags.TidyFlags { 177 if strings.Contains(s, "-warnings-as-errors=") { 178 // clang-tidy accepts only one -warnings-as-errors 179 // replace the old one 180 re := regexp.MustCompile(`'?-?-warnings-as-errors=[^ ]* *`) 181 newFlag := re.ReplaceAllString(s, "") 182 if newFlag == "" { 183 flags.TidyFlags[i] = "-warnings-as-errors=-*" 184 } else { 185 flags.TidyFlags[i] = newFlag + " -warnings-as-errors=-*" 186 } 187 inserted = true 188 break 189 } 190 } 191 if !inserted { 192 flags.TidyFlags = append(flags.TidyFlags, "-warnings-as-errors=-*") 193 } 194 } else if len(tidy.Properties.Tidy_checks_as_errors) > 0 { 195 tidyChecksAsErrors := "-warnings-as-errors=" + strings.Join(esc(ctx, "tidy_checks_as_errors", tidy.Properties.Tidy_checks_as_errors), ",") 196 flags.TidyFlags = append(flags.TidyFlags, tidyChecksAsErrors) 197 } 198 return flags 199} 200 201func init() { 202 android.RegisterSingletonType("tidy_phony_targets", TidyPhonySingleton) 203} 204 205// This TidyPhonySingleton generates both tidy-* and obj-* phony targets for C/C++ files. 206func TidyPhonySingleton() android.Singleton { 207 return &tidyPhonySingleton{} 208} 209 210type tidyPhonySingleton struct{} 211 212// Given a final module, add its tidy/obj phony targets to tidy/objModulesInDirGroup. 213func collectTidyObjModuleTargets(ctx android.SingletonContext, module android.Module, 214 tidyModulesInDirGroup, objModulesInDirGroup map[string]map[string]android.Paths) { 215 allObjFileGroups := make(map[string]android.Paths) // variant group name => obj file Paths 216 allTidyFileGroups := make(map[string]android.Paths) // variant group name => tidy file Paths 217 subsetObjFileGroups := make(map[string]android.Paths) // subset group name => obj file Paths 218 subsetTidyFileGroups := make(map[string]android.Paths) // subset group name => tidy file Paths 219 220 // (1) Collect all obj/tidy files into OS-specific groups. 221 ctx.VisitAllModuleVariants(module, func(variant android.Module) { 222 if ctx.Config().KatiEnabled() && android.ShouldSkipAndroidMkProcessing(variant) { 223 return 224 } 225 if m, ok := variant.(*Module); ok { 226 osName := variant.Target().Os.Name 227 addToOSGroup(osName, m.objFiles, allObjFileGroups, subsetObjFileGroups) 228 addToOSGroup(osName, m.tidyFiles, allTidyFileGroups, subsetTidyFileGroups) 229 } 230 }) 231 232 // (2) Add an all-OS group, with "" or "subset" name, to include all os-specific phony targets. 233 addAllOSGroup(ctx, module, allObjFileGroups, "", "obj") 234 addAllOSGroup(ctx, module, allTidyFileGroups, "", "tidy") 235 addAllOSGroup(ctx, module, subsetObjFileGroups, "subset", "obj") 236 addAllOSGroup(ctx, module, subsetTidyFileGroups, "subset", "tidy") 237 238 tidyTargetGroups := make(map[string]android.Path) 239 objTargetGroups := make(map[string]android.Path) 240 genObjTidyPhonyTargets(ctx, module, "obj", allObjFileGroups, objTargetGroups) 241 genObjTidyPhonyTargets(ctx, module, "obj", subsetObjFileGroups, objTargetGroups) 242 genObjTidyPhonyTargets(ctx, module, "tidy", allTidyFileGroups, tidyTargetGroups) 243 genObjTidyPhonyTargets(ctx, module, "tidy", subsetTidyFileGroups, tidyTargetGroups) 244 245 moduleDir := ctx.ModuleDir(module) 246 appendToModulesInDirGroup(tidyTargetGroups, moduleDir, tidyModulesInDirGroup) 247 appendToModulesInDirGroup(objTargetGroups, moduleDir, objModulesInDirGroup) 248} 249 250func (m *tidyPhonySingleton) GenerateBuildActions(ctx android.SingletonContext) { 251 // For tidy-* directory phony targets, there are different variant groups. 252 // tidyModulesInDirGroup[G][D] is for group G, directory D, with Paths 253 // of all phony targets to be included into direct dependents of tidy-D_G. 254 tidyModulesInDirGroup := make(map[string]map[string]android.Paths) 255 // Also for obj-* directory phony targets. 256 objModulesInDirGroup := make(map[string]map[string]android.Paths) 257 258 // Collect tidy/obj targets from the 'final' modules. 259 ctx.VisitAllModules(func(module android.Module) { 260 if module == ctx.FinalModule(module) { 261 collectTidyObjModuleTargets(ctx, module, tidyModulesInDirGroup, objModulesInDirGroup) 262 } 263 }) 264 265 suffix := "" 266 if ctx.Config().KatiEnabled() { 267 suffix = "-soong" 268 } 269 generateObjTidyPhonyTargets(ctx, suffix, "obj", objModulesInDirGroup) 270 generateObjTidyPhonyTargets(ctx, suffix, "tidy", tidyModulesInDirGroup) 271} 272 273// The name for an obj/tidy module variant group phony target is Name_group-obj/tidy, 274func objTidyModuleGroupName(module android.Module, group string, suffix string) string { 275 if group == "" { 276 return module.Name() + "-" + suffix 277 } 278 return module.Name() + "_" + group + "-" + suffix 279} 280 281// Generate obj-* or tidy-* phony targets. 282func generateObjTidyPhonyTargets(ctx android.SingletonContext, suffix string, prefix string, objTidyModulesInDirGroup map[string]map[string]android.Paths) { 283 // For each variant group, create a <prefix>-<directory>_group target that 284 // depends on all subdirectories and modules in the directory. 285 for group, modulesInDir := range objTidyModulesInDirGroup { 286 groupSuffix := "" 287 if group != "" { 288 groupSuffix = "_" + group 289 } 290 mmTarget := func(dir string) string { 291 return prefix + "-" + strings.Replace(filepath.Clean(dir), "/", "-", -1) + groupSuffix 292 } 293 dirs, topDirs := android.AddAncestors(ctx, modulesInDir, mmTarget) 294 // Create a <prefix>-soong_group target that depends on all <prefix>-dir_group of top level dirs. 295 var topDirPaths android.Paths 296 for _, dir := range topDirs { 297 topDirPaths = append(topDirPaths, android.PathForPhony(ctx, mmTarget(dir))) 298 } 299 ctx.Phony(prefix+suffix+groupSuffix, topDirPaths...) 300 // Create a <prefix>-dir_group target that depends on all targets in modulesInDir[dir] 301 for _, dir := range dirs { 302 if dir != "." && dir != "" { 303 ctx.Phony(mmTarget(dir), modulesInDir[dir]...) 304 } 305 } 306 } 307} 308 309// Append (obj|tidy)TargetGroups[group] into (obj|tidy)ModulesInDirGroups[group][moduleDir]. 310func appendToModulesInDirGroup(targetGroups map[string]android.Path, moduleDir string, modulesInDirGroup map[string]map[string]android.Paths) { 311 for group, phonyPath := range targetGroups { 312 if _, found := modulesInDirGroup[group]; !found { 313 modulesInDirGroup[group] = make(map[string]android.Paths) 314 } 315 modulesInDirGroup[group][moduleDir] = append(modulesInDirGroup[group][moduleDir], phonyPath) 316 } 317} 318 319// Add given files to the OS group and subset group. 320func addToOSGroup(osName string, files android.Paths, allGroups, subsetGroups map[string]android.Paths) { 321 if len(files) > 0 { 322 subsetName := osName + "_subset" 323 allGroups[osName] = append(allGroups[osName], files...) 324 // Now include only the first variant in the subsetGroups. 325 // If clang and clang-tidy get faster, we might include more variants. 326 if _, found := subsetGroups[subsetName]; !found { 327 subsetGroups[subsetName] = files 328 } 329 } 330} 331 332// Add an all-OS group, with groupName, to include all os-specific phony targets. 333func addAllOSGroup(ctx android.SingletonContext, module android.Module, phonyTargetGroups map[string]android.Paths, groupName string, objTidyName string) { 334 if len(phonyTargetGroups) > 0 { 335 var targets android.Paths 336 for group, _ := range phonyTargetGroups { 337 targets = append(targets, android.PathForPhony(ctx, objTidyModuleGroupName(module, group, objTidyName))) 338 } 339 phonyTargetGroups[groupName] = targets 340 } 341} 342 343// Create one phony targets for each group and add them to the targetGroups. 344func genObjTidyPhonyTargets(ctx android.SingletonContext, module android.Module, objTidyName string, fileGroups map[string]android.Paths, targetGroups map[string]android.Path) { 345 for group, files := range fileGroups { 346 groupName := objTidyModuleGroupName(module, group, objTidyName) 347 ctx.Phony(groupName, files...) 348 targetGroups[group] = android.PathForPhony(ctx, groupName) 349 } 350} 351