Add support for running Android lint on java and android modules.
Add a rule that runs Android lint on each java and android module
and produces reports in xml, html and text formats.
Bug: 153485543
Test: m out/soong/.intermediates/packages/apps/Settings/Settings-core/android_common/lint-report.html
Change-Id: I5a530975b73ba767fef45b257d4f9ec901a19fcb
Merged-In: I5a530975b73ba767fef45b257d4f9ec901a19fcb
(cherry picked from commit 014489c1e6
)
This commit is contained in:
parent
1c14b4ecf6
commit
1e28e3c615
|
@ -37,6 +37,7 @@ bootstrap_go_package {
|
|||
"jdeps.go",
|
||||
"java_resources.go",
|
||||
"kotlin.go",
|
||||
"lint.go",
|
||||
"platform_compat_config.go",
|
||||
"plugin.go",
|
||||
"prebuilt_apis.go",
|
||||
|
|
|
@ -102,6 +102,7 @@ type aapt struct {
|
|||
sdkLibraries []string
|
||||
hasNoCode bool
|
||||
LoggingParent string
|
||||
resourceFiles android.Paths
|
||||
|
||||
splitNames []string
|
||||
splits []split
|
||||
|
@ -275,6 +276,7 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex
|
|||
|
||||
var compiledResDirs []android.Paths
|
||||
for _, dir := range resDirs {
|
||||
a.resourceFiles = append(a.resourceFiles, dir.files...)
|
||||
compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files, compileFlags).Paths())
|
||||
}
|
||||
|
||||
|
@ -473,6 +475,10 @@ func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext)
|
|||
// apps manifests are handled by aapt, don't let Module see them
|
||||
a.properties.Manifest = nil
|
||||
|
||||
a.linter.mergedManifest = a.aapt.mergedManifestFile
|
||||
a.linter.manifest = a.aapt.manifestPath
|
||||
a.linter.resources = a.aapt.resourceFiles
|
||||
|
||||
a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles,
|
||||
a.proguardOptionsFile)
|
||||
|
||||
|
@ -512,6 +518,7 @@ func AndroidLibraryFactory() android.Module {
|
|||
&module.androidLibraryProperties)
|
||||
|
||||
module.androidLibraryProperties.BuildAAR = true
|
||||
module.Module.linter.library = true
|
||||
|
||||
android.InitApexModule(module)
|
||||
InitJavaModule(module, android.DeviceSupported)
|
||||
|
|
|
@ -737,6 +737,10 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
|
|||
|
||||
a.proguardBuildActions(ctx)
|
||||
|
||||
a.linter.mergedManifest = a.aapt.mergedManifestFile
|
||||
a.linter.manifest = a.aapt.manifestPath
|
||||
a.linter.resources = a.aapt.resourceFiles
|
||||
|
||||
dexJarFile := a.dexBuildActions(ctx)
|
||||
|
||||
jniLibs, certificateDeps := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), !Bool(a.appProperties.Jni_uses_platform_apis))
|
||||
|
@ -1089,6 +1093,7 @@ func AndroidTestFactory() android.Module {
|
|||
module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true)
|
||||
module.appProperties.AlwaysPackageNativeLibs = true
|
||||
module.Module.dexpreopter.isTest = true
|
||||
module.Module.linter.test = true
|
||||
|
||||
module.addHostAndDeviceProperties()
|
||||
module.AddProperties(
|
||||
|
@ -1138,6 +1143,7 @@ func AndroidTestHelperAppFactory() android.Module {
|
|||
module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true)
|
||||
module.appProperties.AlwaysPackageNativeLibs = true
|
||||
module.Module.dexpreopter.isTest = true
|
||||
module.Module.linter.test = true
|
||||
|
||||
module.addHostAndDeviceProperties()
|
||||
module.AddProperties(
|
||||
|
|
|
@ -1224,6 +1224,21 @@ func DroidstubsHostFactory() android.Module {
|
|||
return module
|
||||
}
|
||||
|
||||
func (d *Droidstubs) OutputFiles(tag string) (android.Paths, error) {
|
||||
switch tag {
|
||||
case "":
|
||||
return android.Paths{d.stubsSrcJar}, nil
|
||||
case ".docs.zip":
|
||||
return android.Paths{d.docZip}, nil
|
||||
case ".annotations.zip":
|
||||
return android.Paths{d.annotationsZip}, nil
|
||||
case ".api_versions.xml":
|
||||
return android.Paths{d.apiVersionsXml}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported module reference tag %q", tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Droidstubs) ApiFilePath() android.Path {
|
||||
return d.apiFilePath
|
||||
}
|
||||
|
|
27
java/java.go
27
java/java.go
|
@ -475,6 +475,7 @@ type Module struct {
|
|||
|
||||
hiddenAPI
|
||||
dexpreopter
|
||||
linter
|
||||
|
||||
// list of the xref extraction files
|
||||
kytheFiles android.Paths
|
||||
|
@ -494,6 +495,7 @@ func (j *Module) addHostAndDeviceProperties() {
|
|||
j.AddProperties(
|
||||
&j.deviceProperties,
|
||||
&j.dexpreoptProperties,
|
||||
&j.linter.properties,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1635,6 +1637,28 @@ func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) {
|
|||
outputFile = implementationAndResourcesJar
|
||||
}
|
||||
|
||||
if ctx.Device() {
|
||||
lintSDKVersionString := func(sdkSpec sdkSpec) string {
|
||||
if v := sdkSpec.version; v.isNumbered() {
|
||||
return v.String()
|
||||
} else {
|
||||
return ctx.Config().DefaultAppTargetSdk()
|
||||
}
|
||||
}
|
||||
|
||||
j.linter.name = ctx.ModuleName()
|
||||
j.linter.srcs = srcFiles
|
||||
j.linter.srcJars = srcJars
|
||||
j.linter.classpath = append(append(android.Paths(nil), flags.bootClasspath...), flags.classpath...)
|
||||
j.linter.classes = j.implementationJarFile
|
||||
j.linter.minSdkVersion = lintSDKVersionString(j.minSdkVersion())
|
||||
j.linter.targetSdkVersion = lintSDKVersionString(j.targetSdkVersion())
|
||||
j.linter.compileSdkVersion = lintSDKVersionString(j.sdkVersion())
|
||||
j.linter.javaLanguageLevel = flags.javaVersion.String()
|
||||
j.linter.kotlinLanguageLevel = "1.3"
|
||||
j.linter.lint(ctx)
|
||||
}
|
||||
|
||||
ctx.CheckbuildFile(outputFile)
|
||||
|
||||
// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
|
||||
|
@ -2235,6 +2259,7 @@ func TestFactory() android.Module {
|
|||
|
||||
module.Module.properties.Installable = proptools.BoolPtr(true)
|
||||
module.Module.dexpreopter.isTest = true
|
||||
module.Module.linter.test = true
|
||||
|
||||
InitJavaModule(module, android.HostAndDeviceSupported)
|
||||
return module
|
||||
|
@ -2249,6 +2274,7 @@ func TestHelperLibraryFactory() android.Module {
|
|||
|
||||
module.Module.properties.Installable = proptools.BoolPtr(true)
|
||||
module.Module.dexpreopter.isTest = true
|
||||
module.Module.linter.test = true
|
||||
|
||||
InitJavaModule(module, android.HostAndDeviceSupported)
|
||||
return module
|
||||
|
@ -2823,6 +2849,7 @@ func DefaultsFactory() android.Module {
|
|||
&DexImportProperties{},
|
||||
&android.ApexProperties{},
|
||||
&RuntimeResourceOverlayProperties{},
|
||||
&LintProperties{},
|
||||
)
|
||||
|
||||
android.InitDefaultsModule(module)
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
// Copyright 2020 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package java
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"android/soong/android"
|
||||
)
|
||||
|
||||
type LintProperties struct {
|
||||
// Controls for running Android Lint on the module.
|
||||
Lint struct {
|
||||
|
||||
// If true, run Android Lint on the module. Defaults to true.
|
||||
Enabled *bool
|
||||
|
||||
// Flags to pass to the Android Lint tool.
|
||||
Flags []string
|
||||
|
||||
// Checks that should be treated as fatal.
|
||||
Fatal_checks []string
|
||||
|
||||
// Checks that should be treated as errors.
|
||||
Error_checks []string
|
||||
|
||||
// Checks that should be treated as warnings.
|
||||
Warning_checks []string
|
||||
|
||||
// Checks that should be skipped.
|
||||
Disabled_checks []string
|
||||
}
|
||||
}
|
||||
|
||||
type linter struct {
|
||||
name string
|
||||
manifest android.Path
|
||||
mergedManifest android.Path
|
||||
srcs android.Paths
|
||||
srcJars android.Paths
|
||||
resources android.Paths
|
||||
classpath android.Paths
|
||||
classes android.Path
|
||||
extraLintCheckJars android.Paths
|
||||
test bool
|
||||
library bool
|
||||
minSdkVersion string
|
||||
targetSdkVersion string
|
||||
compileSdkVersion string
|
||||
javaLanguageLevel string
|
||||
kotlinLanguageLevel string
|
||||
outputs lintOutputs
|
||||
properties LintProperties
|
||||
}
|
||||
|
||||
type lintOutputs struct {
|
||||
html android.ModuleOutPath
|
||||
text android.ModuleOutPath
|
||||
xml android.ModuleOutPath
|
||||
}
|
||||
|
||||
func (l *linter) enabled() bool {
|
||||
return BoolDefault(l.properties.Lint.Enabled, true)
|
||||
}
|
||||
|
||||
func (l *linter) writeLintProjectXML(ctx android.ModuleContext,
|
||||
rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir android.WritablePath, deps android.Paths) {
|
||||
|
||||
var resourcesList android.WritablePath
|
||||
if len(l.resources) > 0 {
|
||||
// The list of resources may be too long to put on the command line, but
|
||||
// we can't use the rsp file because it is already being used for srcs.
|
||||
// Insert a second rule to write out the list of resources to a file.
|
||||
resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list")
|
||||
resListRule := android.NewRuleBuilder()
|
||||
resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList)
|
||||
resListRule.Build(pctx, ctx, "lint_resources_list", "lint resources list")
|
||||
deps = append(deps, l.resources...)
|
||||
}
|
||||
|
||||
projectXMLPath = android.PathForModuleOut(ctx, "lint", "project.xml")
|
||||
// Lint looks for a lint.xml file next to the project.xml file, give it one.
|
||||
configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml")
|
||||
cacheDir = android.PathForModuleOut(ctx, "lint", "cache")
|
||||
|
||||
srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars")
|
||||
srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
|
||||
|
||||
cmd := rule.Command().
|
||||
BuiltTool(ctx, "lint-project-xml").
|
||||
FlagWithOutput("--project_out ", projectXMLPath).
|
||||
FlagWithOutput("--config_out ", configXMLPath).
|
||||
FlagWithArg("--name ", ctx.ModuleName())
|
||||
|
||||
if l.library {
|
||||
cmd.Flag("--library")
|
||||
}
|
||||
if l.test {
|
||||
cmd.Flag("--test")
|
||||
}
|
||||
if l.manifest != nil {
|
||||
deps = append(deps, l.manifest)
|
||||
cmd.FlagWithArg("--manifest ", l.manifest.String())
|
||||
}
|
||||
if l.mergedManifest != nil {
|
||||
deps = append(deps, l.mergedManifest)
|
||||
cmd.FlagWithArg("--merged_manifest ", l.mergedManifest.String())
|
||||
}
|
||||
|
||||
// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
|
||||
// lint separately.
|
||||
cmd.FlagWithRspFileInputList("--srcs ", l.srcs)
|
||||
deps = append(deps, l.srcs...)
|
||||
|
||||
cmd.FlagWithInput("--generated_srcs ", srcJarList)
|
||||
deps = append(deps, l.srcJars...)
|
||||
|
||||
if resourcesList != nil {
|
||||
cmd.FlagWithInput("--resources ", resourcesList)
|
||||
}
|
||||
|
||||
if l.classes != nil {
|
||||
deps = append(deps, l.classes)
|
||||
cmd.FlagWithArg("--classes ", l.classes.String())
|
||||
}
|
||||
|
||||
cmd.FlagForEachArg("--classpath ", l.classpath.Strings())
|
||||
deps = append(deps, l.classpath...)
|
||||
|
||||
cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings())
|
||||
deps = append(deps, l.extraLintCheckJars...)
|
||||
|
||||
// The cache tag in project.xml is relative to the project.xml file.
|
||||
cmd.FlagWithArg("--cache_dir ", "cache")
|
||||
|
||||
cmd.FlagWithInput("@",
|
||||
android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
|
||||
|
||||
cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks)
|
||||
cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks)
|
||||
cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
|
||||
cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
|
||||
|
||||
return projectXMLPath, configXMLPath, cacheDir, deps
|
||||
}
|
||||
|
||||
// generateManifest adds a command to the rule to write a dummy manifest cat contains the
|
||||
// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest.
|
||||
func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.Path {
|
||||
manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml")
|
||||
|
||||
rule.Command().Text("(").
|
||||
Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`).
|
||||
Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`).
|
||||
Text(`echo " android:versionCode='1' android:versionName='1' >" &&`).
|
||||
Textf(`echo " <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`,
|
||||
l.minSdkVersion, l.targetSdkVersion).
|
||||
Text(`echo "</manifest>"`).
|
||||
Text(") >").Output(manifestPath)
|
||||
|
||||
return manifestPath
|
||||
}
|
||||
|
||||
func (l *linter) lint(ctx android.ModuleContext) {
|
||||
if !l.enabled() {
|
||||
return
|
||||
}
|
||||
|
||||
rule := android.NewRuleBuilder()
|
||||
|
||||
if l.manifest == nil {
|
||||
manifest := l.generateManifest(ctx, rule)
|
||||
l.manifest = manifest
|
||||
}
|
||||
|
||||
projectXML, lintXML, cacheDir, deps := l.writeLintProjectXML(ctx, rule)
|
||||
|
||||
l.outputs.html = android.PathForModuleOut(ctx, "lint-report.html")
|
||||
l.outputs.text = android.PathForModuleOut(ctx, "lint-report.txt")
|
||||
l.outputs.xml = android.PathForModuleOut(ctx, "lint-report.xml")
|
||||
|
||||
rule.Command().Text("rm -rf").Flag(cacheDir.String())
|
||||
rule.Command().Text("mkdir -p").Flag(cacheDir.String())
|
||||
|
||||
rule.Command().
|
||||
Text("(").
|
||||
Flag("JAVA_OPTS=-Xmx2048m").
|
||||
FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath(ctx)).
|
||||
FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXmlPath(ctx)).
|
||||
Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
|
||||
Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")).
|
||||
Flag("--quiet").
|
||||
FlagWithInput("--project ", projectXML).
|
||||
FlagWithInput("--config ", lintXML).
|
||||
FlagWithOutput("--html ", l.outputs.html).
|
||||
FlagWithOutput("--text ", l.outputs.text).
|
||||
FlagWithOutput("--xml ", l.outputs.xml).
|
||||
FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
|
||||
FlagWithArg("--java-language-level ", l.javaLanguageLevel).
|
||||
FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
|
||||
FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
|
||||
Flag("--exitcode").
|
||||
Flags(l.properties.Lint.Flags).
|
||||
Implicits(deps).
|
||||
Text("|| (").Text("cat").Input(l.outputs.text).Text("; exit 7)").
|
||||
Text(")")
|
||||
|
||||
rule.Command().Text("rm -rf").Flag(cacheDir.String())
|
||||
|
||||
rule.Build(pctx, ctx, "lint", "lint")
|
||||
}
|
||||
|
||||
func (l *linter) lintOutputs() *lintOutputs {
|
||||
return &l.outputs
|
||||
}
|
||||
|
||||
type lintOutputIntf interface {
|
||||
lintOutputs() *lintOutputs
|
||||
}
|
||||
|
||||
var _ lintOutputIntf = (*linter)(nil)
|
||||
|
||||
type lintSingleton struct {
|
||||
htmlZip android.WritablePath
|
||||
textZip android.WritablePath
|
||||
xmlZip android.WritablePath
|
||||
}
|
||||
|
||||
func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) {
|
||||
l.generateLintReportZips(ctx)
|
||||
l.copyLintDependencies(ctx)
|
||||
}
|
||||
|
||||
func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
|
||||
if ctx.Config().UnbundledBuild() {
|
||||
return
|
||||
}
|
||||
|
||||
var frameworkDocStubs android.Module
|
||||
ctx.VisitAllModules(func(m android.Module) {
|
||||
if ctx.ModuleName(m) == "framework-doc-stubs" {
|
||||
if frameworkDocStubs == nil {
|
||||
frameworkDocStubs = m
|
||||
} else {
|
||||
ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s",
|
||||
ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if frameworkDocStubs == nil {
|
||||
if !ctx.Config().AllowMissingDependencies() {
|
||||
ctx.Errorf("lint: missing framework-doc-stubs")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Build(pctx, android.BuildParams{
|
||||
Rule: android.Cp,
|
||||
Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
|
||||
Output: annotationsZipPath(ctx),
|
||||
})
|
||||
|
||||
ctx.Build(pctx, android.BuildParams{
|
||||
Rule: android.Cp,
|
||||
Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
|
||||
Output: apiVersionsXmlPath(ctx),
|
||||
})
|
||||
}
|
||||
|
||||
func annotationsZipPath(ctx android.PathContext) android.WritablePath {
|
||||
return android.PathForOutput(ctx, "lint", "annotations.zip")
|
||||
}
|
||||
|
||||
func apiVersionsXmlPath(ctx android.PathContext) android.WritablePath {
|
||||
return android.PathForOutput(ctx, "lint", "api_versions.xml")
|
||||
}
|
||||
|
||||
func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
|
||||
var outputs []*lintOutputs
|
||||
var dirs []string
|
||||
ctx.VisitAllModules(func(m android.Module) {
|
||||
if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
|
||||
return
|
||||
}
|
||||
|
||||
if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() {
|
||||
// There are stray platform variants of modules in apexes that are not available for
|
||||
// the platform, and they sometimes can't be built. Don't depend on them.
|
||||
return
|
||||
}
|
||||
|
||||
if l, ok := m.(lintOutputIntf); ok {
|
||||
outputs = append(outputs, l.lintOutputs())
|
||||
}
|
||||
})
|
||||
|
||||
dirs = android.SortedUniqueStrings(dirs)
|
||||
|
||||
zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) {
|
||||
var paths android.Paths
|
||||
|
||||
for _, output := range outputs {
|
||||
paths = append(paths, get(output))
|
||||
}
|
||||
|
||||
sort.Slice(paths, func(i, j int) bool {
|
||||
return paths[i].String() < paths[j].String()
|
||||
})
|
||||
|
||||
rule := android.NewRuleBuilder()
|
||||
|
||||
rule.Command().BuiltTool(ctx, "soong_zip").
|
||||
FlagWithOutput("-o ", outputPath).
|
||||
FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
|
||||
FlagWithRspFileInputList("-l ", paths)
|
||||
|
||||
rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
|
||||
}
|
||||
|
||||
l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
|
||||
zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html })
|
||||
|
||||
l.textZip = android.PathForOutput(ctx, "lint-report-text.zip")
|
||||
zip(l.textZip, func(l *lintOutputs) android.Path { return l.text })
|
||||
|
||||
l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip")
|
||||
zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml })
|
||||
|
||||
ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip)
|
||||
}
|
||||
|
||||
func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
|
||||
ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
|
||||
}
|
||||
|
||||
var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
|
||||
|
||||
func init() {
|
||||
android.RegisterSingletonType("lint",
|
||||
func() android.Singleton { return &lintSingleton{} })
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
# Treat LintError as fatal to catch invocation errors
|
||||
--fatal_check LintError
|
||||
|
||||
# Downgrade existing errors to warnings
|
||||
--warning_check AppCompatResource # 55 occurences in 10 modules
|
||||
--warning_check AppLinkUrlError # 111 occurences in 53 modules
|
||||
--warning_check BlockedPrivateApi # 2 occurences in 2 modules
|
||||
--warning_check ByteOrderMark # 2 occurences in 2 modules
|
||||
--warning_check DuplicateActivity # 3 occurences in 3 modules
|
||||
--warning_check DuplicateDefinition # 3623 occurences in 48 modules
|
||||
--warning_check DuplicateIds # 207 occurences in 22 modules
|
||||
--warning_check EllipsizeMaxLines # 12 occurences in 7 modules
|
||||
--warning_check ExtraTranslation # 21276 occurences in 27 modules
|
||||
--warning_check FontValidationError # 4 occurences in 1 modules
|
||||
--warning_check FullBackupContent # 16 occurences in 1 modules
|
||||
--warning_check GetContentDescriptionOverride # 3 occurences in 2 modules
|
||||
--warning_check HalfFloat # 31 occurences in 1 modules
|
||||
--warning_check HardcodedDebugMode # 99 occurences in 95 modules
|
||||
--warning_check ImpliedQuantity # 703 occurences in 27 modules
|
||||
--warning_check ImpliedTouchscreenHardware # 4 occurences in 4 modules
|
||||
--warning_check IncludeLayoutParam # 11 occurences in 6 modules
|
||||
--warning_check Instantiatable # 145 occurences in 19 modules
|
||||
--warning_check InvalidPermission # 6 occurences in 4 modules
|
||||
--warning_check InvalidUsesTagAttribute # 6 occurences in 2 modules
|
||||
--warning_check InvalidWakeLockTag # 111 occurences in 37 modules
|
||||
--warning_check JavascriptInterface # 3 occurences in 2 modules
|
||||
--warning_check LibraryCustomView # 9 occurences in 4 modules
|
||||
--warning_check LogTagMismatch # 81 occurences in 13 modules
|
||||
--warning_check LongLogTag # 249 occurences in 12 modules
|
||||
--warning_check MenuTitle # 5 occurences in 4 modules
|
||||
--warning_check MissingClass # 537 occurences in 141 modules
|
||||
--warning_check MissingConstraints # 39 occurences in 10 modules
|
||||
--warning_check MissingDefaultResource # 1257 occurences in 40 modules
|
||||
--warning_check MissingIntentFilterForMediaSearch # 1 occurences in 1 modules
|
||||
--warning_check MissingLeanbackLauncher # 3 occurences in 3 modules
|
||||
--warning_check MissingLeanbackSupport # 2 occurences in 2 modules
|
||||
--warning_check MissingOnPlayFromSearch # 1 occurences in 1 modules
|
||||
--warning_check MissingPermission # 2071 occurences in 150 modules
|
||||
--warning_check MissingPrefix # 46 occurences in 41 modules
|
||||
--warning_check MissingQuantity # 100 occurences in 1 modules
|
||||
--warning_check MissingSuperCall # 121 occurences in 36 modules
|
||||
--warning_check MissingTvBanner # 3 occurences in 3 modules
|
||||
--warning_check NamespaceTypo # 3 occurences in 3 modules
|
||||
--warning_check NetworkSecurityConfig # 46 occurences in 12 modules
|
||||
--warning_check NewApi # 1996 occurences in 122 modules
|
||||
--warning_check NotSibling # 15 occurences in 10 modules
|
||||
--warning_check ObjectAnimatorBinding # 14 occurences in 5 modules
|
||||
--warning_check OnClick # 49 occurences in 21 modules
|
||||
--warning_check Orientation # 77 occurences in 19 modules
|
||||
--warning_check Override # 385 occurences in 36 modules
|
||||
--warning_check ParcelCreator # 23 occurences in 2 modules
|
||||
--warning_check ProtectedPermissions # 2413 occurences in 381 modules
|
||||
--warning_check Range # 80 occurences in 28 modules
|
||||
--warning_check RecyclerView # 1 occurences in 1 modules
|
||||
--warning_check ReferenceType # 4 occurences in 1 modules
|
||||
--warning_check ResourceAsColor # 19 occurences in 14 modules
|
||||
--warning_check RequiredSize # 52 occurences in 13 modules
|
||||
--warning_check ResAuto # 3 occurences in 1 modules
|
||||
--warning_check ResourceCycle # 37 occurences in 10 modules
|
||||
--warning_check ResourceType # 137 occurences in 36 modules
|
||||
--warning_check RestrictedApi # 28 occurences in 5 modules
|
||||
--warning_check RtlCompat # 9 occurences in 6 modules
|
||||
--warning_check ServiceCast # 3 occurences in 1 modules
|
||||
--warning_check SoonBlockedPrivateApi # 5 occurences in 3 modules
|
||||
--warning_check StringFormatInvalid # 148 occurences in 11 modules
|
||||
--warning_check StringFormatMatches # 4800 occurences in 30 modules
|
||||
--warning_check UnknownId # 8 occurences in 7 modules
|
||||
--warning_check ValidFragment # 12 occurences in 5 modules
|
||||
--warning_check ValidRestrictions # 5 occurences in 1 modules
|
||||
--warning_check WebViewLayout # 3 occurences in 1 modules
|
||||
--warning_check WrongCall # 21 occurences in 3 modules
|
||||
--warning_check WrongConstant # 894 occurences in 126 modules
|
||||
--warning_check WrongManifestParent # 10 occurences in 4 modules
|
||||
--warning_check WrongThread # 14 occurences in 6 modules
|
||||
--warning_check WrongViewCast # 1 occurences in 1 modules
|
||||
|
||||
# TODO(b/158390965): remove this when lint doesn't crash
|
||||
--disable_check HardcodedDebugMode
|
|
@ -221,6 +221,7 @@ func RobolectricTestFactory() android.Module {
|
|||
&module.robolectricProperties)
|
||||
|
||||
module.Module.dexpreopter.isTest = true
|
||||
module.Module.linter.test = true
|
||||
|
||||
InitJavaModule(module, android.DeviceSupported)
|
||||
return module
|
||||
|
|
|
@ -1101,6 +1101,7 @@ func (module *SdkLibrary) createImplLibrary(mctx android.DefaultableHookContext)
|
|||
&module.protoProperties,
|
||||
&module.deviceProperties,
|
||||
&module.dexpreoptProperties,
|
||||
&module.linter.properties,
|
||||
&props,
|
||||
module.sdkComponentPropertiesForChildLibrary(),
|
||||
}
|
||||
|
|
|
@ -148,3 +148,9 @@ python_test_host {
|
|||
],
|
||||
test_suites: ["general-tests"],
|
||||
}
|
||||
|
||||
python_binary_host {
|
||||
name: "lint-project-xml",
|
||||
main: "lint-project-xml.py",
|
||||
srcs: ["lint-project-xml.py"],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (C) 2018 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
"""This file generates project.xml and lint.xml files used to drive the Android Lint CLI tool."""
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
def check_action(check_type):
|
||||
"""
|
||||
Returns an action that appends a tuple of check_type and the argument to the dest.
|
||||
"""
|
||||
class CheckAction(argparse.Action):
|
||||
def __init__(self, option_strings, dest, nargs=None, **kwargs):
|
||||
if nargs is not None:
|
||||
raise ValueError("nargs must be None, was %s" % nargs)
|
||||
super(CheckAction, self).__init__(option_strings, dest, **kwargs)
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
checks = getattr(namespace, self.dest, [])
|
||||
checks.append((check_type, values))
|
||||
setattr(namespace, self.dest, checks)
|
||||
return CheckAction
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse commandline arguments."""
|
||||
|
||||
def convert_arg_line_to_args(arg_line):
|
||||
for arg in arg_line.split():
|
||||
if arg.startswith('#'):
|
||||
return
|
||||
if not arg.strip():
|
||||
continue
|
||||
yield arg
|
||||
|
||||
parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
|
||||
parser.convert_arg_line_to_args = convert_arg_line_to_args
|
||||
parser.add_argument('--project_out', dest='project_out',
|
||||
help='file to which the project.xml contents will be written.')
|
||||
parser.add_argument('--config_out', dest='config_out',
|
||||
help='file to which the lint.xml contents will be written.')
|
||||
parser.add_argument('--name', dest='name',
|
||||
help='name of the module.')
|
||||
parser.add_argument('--srcs', dest='srcs', action='append', default=[],
|
||||
help='file containing whitespace separated list of source files.')
|
||||
parser.add_argument('--generated_srcs', dest='generated_srcs', action='append', default=[],
|
||||
help='file containing whitespace separated list of generated source files.')
|
||||
parser.add_argument('--resources', dest='resources', action='append', default=[],
|
||||
help='file containing whitespace separated list of resource files.')
|
||||
parser.add_argument('--classes', dest='classes', action='append', default=[],
|
||||
help='file containing the module\'s classes.')
|
||||
parser.add_argument('--classpath', dest='classpath', action='append', default=[],
|
||||
help='file containing classes from dependencies.')
|
||||
parser.add_argument('--extra_checks_jar', dest='extra_checks_jars', action='append', default=[],
|
||||
help='file containing extra lint checks.')
|
||||
parser.add_argument('--manifest', dest='manifest',
|
||||
help='file containing the module\'s manifest.')
|
||||
parser.add_argument('--merged_manifest', dest='merged_manifest',
|
||||
help='file containing merged manifest for the module and its dependencies.')
|
||||
parser.add_argument('--library', dest='library', action='store_true',
|
||||
help='mark the module as a library.')
|
||||
parser.add_argument('--test', dest='test', action='store_true',
|
||||
help='mark the module as a test.')
|
||||
parser.add_argument('--cache_dir', dest='cache_dir',
|
||||
help='directory to use for cached file.')
|
||||
group = parser.add_argument_group('check arguments', 'later arguments override earlier ones.')
|
||||
group.add_argument('--fatal_check', dest='checks', action=check_action('fatal'), default=[],
|
||||
help='treat a lint issue as a fatal error.')
|
||||
group.add_argument('--error_check', dest='checks', action=check_action('error'), default=[],
|
||||
help='treat a lint issue as an error.')
|
||||
group.add_argument('--warning_check', dest='checks', action=check_action('warning'), default=[],
|
||||
help='treat a lint issue as a warning.')
|
||||
group.add_argument('--disable_check', dest='checks', action=check_action('ignore'), default=[],
|
||||
help='disable a lint issue.')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
class NinjaRspFileReader:
|
||||
"""
|
||||
Reads entries from a Ninja rsp file. Ninja escapes any entries in the file that contain a
|
||||
non-standard character by surrounding the whole entry with single quotes, and then replacing
|
||||
any single quotes in the entry with the escape sequence '\''.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.f = open(filename, 'r')
|
||||
self.r = self.character_reader(self.f)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def character_reader(self, f):
|
||||
"""Turns a file into a generator that returns one character at a time."""
|
||||
while True:
|
||||
c = f.read(1)
|
||||
if c:
|
||||
yield c
|
||||
else:
|
||||
return
|
||||
|
||||
def __next__(self):
|
||||
entry = self.read_entry()
|
||||
if entry:
|
||||
return entry
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
def read_entry(self):
|
||||
c = next(self.r, "")
|
||||
if not c:
|
||||
return ""
|
||||
elif c == "'":
|
||||
return self.read_quoted_entry()
|
||||
else:
|
||||
entry = c
|
||||
for c in self.r:
|
||||
if c == " " or c == "\n":
|
||||
break
|
||||
entry += c
|
||||
return entry
|
||||
|
||||
def read_quoted_entry(self):
|
||||
entry = ""
|
||||
for c in self.r:
|
||||
if c == "'":
|
||||
# Either the end of the quoted entry, or the beginning of an escape sequence, read the next
|
||||
# character to find out.
|
||||
c = next(self.r)
|
||||
if not c or c == " " or c == "\n":
|
||||
# End of the item
|
||||
return entry
|
||||
elif c == "\\":
|
||||
# Escape sequence, expect a '
|
||||
c = next(self.r)
|
||||
if c != "'":
|
||||
# Malformed escape sequence
|
||||
raise "malformed escape sequence %s'\\%s" % (entry, c)
|
||||
entry += "'"
|
||||
else:
|
||||
raise "malformed escape sequence %s'%s" % (entry, c)
|
||||
else:
|
||||
entry += c
|
||||
raise "unterminated quoted entry %s" % entry
|
||||
|
||||
|
||||
def write_project_xml(f, args):
|
||||
test_attr = "test='true' " if args.test else ""
|
||||
|
||||
f.write("<?xml version='1.0' encoding='utf-8'?>\n")
|
||||
f.write("<project>\n")
|
||||
f.write(" <module name='%s' android='true' %sdesugar='full' >\n" % (args.name, "library='true' " if args.library else ""))
|
||||
if args.manifest:
|
||||
f.write(" <manifest file='%s' %s/>\n" % (args.manifest, test_attr))
|
||||
if args.merged_manifest:
|
||||
f.write(" <merged-manifest file='%s' %s/>\n" % (args.merged_manifest, test_attr))
|
||||
for src_file in args.srcs:
|
||||
for src in NinjaRspFileReader(src_file):
|
||||
f.write(" <src file='%s' %s/>\n" % (src, test_attr))
|
||||
for src_file in args.generated_srcs:
|
||||
for src in NinjaRspFileReader(src_file):
|
||||
f.write(" <src file='%s' generated='true' %s/>\n" % (src, test_attr))
|
||||
for res_file in args.resources:
|
||||
for res in NinjaRspFileReader(res_file):
|
||||
f.write(" <resource file='%s' %s/>\n" % (res, test_attr))
|
||||
for classes in args.classes:
|
||||
f.write(" <classes jar='%s' />\n" % classes)
|
||||
for classpath in args.classpath:
|
||||
f.write(" <classpath jar='%s' />\n" % classpath)
|
||||
for extra in args.extra_checks_jars:
|
||||
f.write(" <lint-checks jar='%s' />\n" % extra)
|
||||
f.write(" </module>\n")
|
||||
if args.cache_dir:
|
||||
f.write(" <cache dir='%s'/>\n" % args.cache_dir)
|
||||
f.write("</project>\n")
|
||||
|
||||
|
||||
def write_config_xml(f, args):
|
||||
f.write("<?xml version='1.0' encoding='utf-8'?>\n")
|
||||
f.write("<lint>\n")
|
||||
for check in args.checks:
|
||||
f.write(" <issue id='%s' severity='%s' />\n" % (check[1], check[0]))
|
||||
f.write("</lint>\n")
|
||||
|
||||
|
||||
def main():
|
||||
"""Program entry point."""
|
||||
args = parse_args()
|
||||
|
||||
if args.project_out:
|
||||
with open(args.project_out, 'w') as f:
|
||||
write_project_xml(f, args)
|
||||
|
||||
if args.config_out:
|
||||
with open(args.config_out, 'w') as f:
|
||||
write_config_xml(f, args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue