Android Lint Aosp App With SonarQube

背景

第三方 App 在 Android Studio 中可以直接使用 Lint 展示结果.
源码编译时在 make 之后工程根目录执行 lint packages/apps/Calendar/ 也可以 Lint.
但在实际使用中 lint vendor/letv/apps/Camera/ 时提示 No bytecode found: Has the project been built? (Camera), 同时还有资源使用的误报。

原因

分析 lint 源码可知
lint/detector/api/Project.java#654

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private List<File> getAospJavaClassPath() {
List<File> classDirs = new ArrayList<File>(1);
for (File dir : getIntermediateDirs()) {
File classes = new File(dir, "classes"); //$NON-NLS-1$
if (classes.exists()) {
classDirs.add(classes);
} else {
classes = new File(dir, "classes.jar"); //$NON-NLS-1$
if (classes.exists()) {
classDirs.add(classes);
}
}
}
if (classDirs.size() == 0) {
mClient.log(null,
"No bytecode found: Has the project been built? (%1$s)", getName());
}
return classDirs;
}

/** Find the _intermediates directories for a given module name */
private List<File> getIntermediateDirs() {
// See build/core/definitions.mk and in particular the "intermediates-dir-for" definition
List<File> intermediates = new ArrayList<File>();
// TODO: Look up the module name, e.g. LOCAL_MODULE. However,
// some Android.mk files do some complicated things with it - and most
// projects use the same module name as the directory name.
String moduleName = mDir.getName();
String top = getAospTop();
final String[] outFolders = new String[] {
top + "/out/host/common/obj", //$NON-NLS-1$
top + "/out/target/common/obj", //$NON-NLS-1$
getAospHostOut() + "/obj", //$NON-NLS-1$
getAospProductOut() + "/obj" //$NON-NLS-1$
};
final String[] moduleClasses = new String[] {
"APPS", //$NON-NLS-1$
"JAVA_LIBRARIES", //$NON-NLS-1$
};
for (String out : outFolders) {
assert new File(out.replace('/', File.separatorChar)).exists() : out;
for (String moduleClass : moduleClasses) {
String path = out + '/' + moduleClass + '/' + moduleName
+ "_intermediates"; //$NON-NLS-1$
File file = new File(path.replace('/', File.separatorChar));
if (file.exists()) {
intermediates.add(file);
}
}
}
return intermediates;
}

Lint 会尝试从 out 目录中寻找 ModuleName_intermediates/classes.jar, 找不到则会报 No bytecode found.
比如 /letv/workspace/DEMETER_FINAL/out/target/common/obj/APPS/StvCamera_intermediates
而找不到有两个原因:

  1. 从 Android M 开始 AOSP 使用 Jack 编译 (Android N 开始使用 JaCoCo?), Jack 编译时生成 classes.jack / classes.dex 而非 classes.jar
  2. getIntermediateDirs() 方法中 moduleName = mDir.getName() 而不是 vendor/letv/app/**/Android.mk 中指定的 LOCAL_PACKAGE_NAME

禁用 Jack / dex2jar

对于问题 1 要么禁用掉 Jack make 时生成 classed.jar, 要么把 classes.dex 转换成 classes.jar
禁用掉整个项目的 Jack 则会 make 不过, 尝试了几个 module 级别的禁用都无效
测试 dex2jar 可行

修改 lint-api.jar

对于问题 2 修改 lint-api.jar 比编译整个 lint 工具更简单, 方法与 2. CrossWalk.jar 修改 类似
修改过程见 https://github.com/likaci/android-lint-mod-jar/commits/master
另外源码环境中的 lint 使用的是 DEMETER_FINAL/prebuilts/devtools/tools/lint 版本太旧, 直接使用最新的 lint-api 修改, 但最新的 lint 需要 jre8 才能执行

使用

以 DEMETER_FINAL/vendor/letv/apps/Camera/ 为例
下载 https://github.com/likaci/android-lint-mod
修改 bin/lint 92行 JAVACMD="/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java" 成合适的 jre8 路径
make 整个 AOSP 工程
下载 dex2jar - https://github.com/pxb1988/dex2jar/releases 解压到合适的路径

1
2
3
4
5
6
7
# dex2jar
cd DEMETER_FINAL/out/target/common/obj/APPS/StvCamera_intermediates
dex2jar-2.0/d2j-dex2jar.sh classes.dex -o classes.jar

# lint
cd DEMETER_FINAL/vendor/letv/apps/Camera/
pathToModedLint/bin/lint . --xml lint-report.xml

之后运行 SonarScanner 指定 -Dsonar.android.lint.report=./lint-report.xml 即可

1
2
3
4
5
6
7
8
9
10
11
SonarQubeScanner/bin/sonar-scanner \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.projectName=`basename $PWD` \
-Dsonar.projectVersion=1.0 \
-Dsonar.projectKey=`basename $PWD` \
-Dsonar.projectBaseDir=`$PWD` \
-Dsonar.sources="./src,./res,./AndroidManifest.xml" \
-Dsonar.profile="Android Lint" \
-Dsonar.import_unknown_files=true \
-Dsonar.android.lint.report=./lint-report.xml
#最后四行是关键

参考

使用 Lint 改进您的代码 - https://developer.android.com/studio/write/lint.html?hl=zh-cn
Compiling with Jack - https://source.android.com/source/jackey
Using SonarQube with Jenkins Continuous Integration and GitHub to Improve Code Review

目录

  1. 1. 背景
  2. 2. 原因
  3. 3. 禁用 Jack / dex2jar
  4. 4. 修改 lint-api.jar
  5. 5. 使用
  6. 6. 参考