SonarScanner 源码分析
title: SonarScanner 源码分析
date: 2020-11-24 14:00:00
author: mamian521#gmail.com
介绍:
SonarQube是一个静态代码扫描平台,支持主流语言的静态代码扫描分析。
SonarQube主要分为几个模块:WebServer、CeServer、Scanner
今天主要对 Scanner 分析一下源码,其中Scanner是在本地执行扫描分析的工具,也就是说SonarScanner是在本地执行扫描分析之后将结果上传到服务端进行分析。本文重点介绍一下 Scanner 的源码和原理。
SonarScanner 的源码主要有三部分,一个是 SonarScannerCli 用来执行命令和接收参数(https://github.com/SonarSource/sonar-scanner-cli.git),另一个是 SonarScannerApi ,主要用来下载插件包和加载类 (https://github.com/SonarSource/sonar-scanner-api.git),还有真正用来执行扫描的 SonarScannerEngine (https://github.com/SonarSource/sonarqube/tree/master/sonar-scanner-engine)
本文主要分析一下前两部分。
SonarScannerCli
目录结构:
可以看出,Cli 的源码并不多,入口是 Main
类的 main
函数
Logs logs = new Logs(System.out, System.err);
Exit exit = new Exit();
Cli cli = new Cli(exit, logs).parse(args);
Main main = new Main(exit, cli, new Conf(cli, logs, System.getenv()), new ScannerFactory(logs), logs);
main.execute();
主要的几个类:
Logs 用来记录日志
Exit 记录退出码
Cli 用来解析命令行执行时传递的变量
Conf 类用来接收 Cli 解析之后的参数
参数主要从几个地方获取
- Scanner 全局设置(Scanner安装目录/
conf/sonar-scanner.properties
) - 环境变量 (key =
SONARQUBE_SCANNER_PARAMS
value 格式为 JSON ) - 仓库文件 (
sonar-project.properties
) - 命令行参数 (通过 Java Properties 传递)
最后在 Main 里的 execute
方法中执行扫描
再来看看这个方法
execute
void execute() {
// 记录执行耗时
Stats stats = new Stats(logger).start();
// 默认退出码为1
int status = Exit.INTERNAL_ERROR;
try {
Properties p = conf.properties();
// 检查是否有 skip 跳过扫描的设置
checkSkip(p);
// 根据参数设置 log 级别
configureLogging(p);
// 初始化 runner
init(p);
// 初始化 变量 ,下载插件
runner.start();
logger.info(String.format("Analyzing on %s", conf.isSonarCloud(null) ? "SonarCloud" : ("SonarQube server " + runner.serverVersion())));
// 执行扫描
execute(stats, p);
status = Exit.SUCCESS;
} catch (Throwable e) {
displayExecutionResult(stats, "FAILURE");
showError("Error during SonarScanner execution", e, cli.isDebugEnabled());
status = isUserError(e) ? Exit.USER_ERROR : Exit.INTERNAL_ERROR;
} finally {
exit.exit(status);
}
}
里边最重要的是 3 个方法,
init(p);
runner.start();
execute(stats, p);
init
private void init(Properties p) {
SystemInfo.print(logger);
if (cli.isDisplayVersionOnly()) {
exit.exit(Exit.SUCCESS);
}
runner = runnerFactory.create(p, cli.getInvokedFrom());
}
class ScannerFactory {
private final Logs logger;
public ScannerFactory(Logs logger) {
this.logger = logger;
}
EmbeddedScanner create(Properties props, String isInvokedFrom) {
String appName = "ScannerCLI";
String appVersion = ScannerVersion.version();
if (!isInvokedFrom.equals("") && isInvokedFrom.contains("/")) {
appName = isInvokedFrom.split("/")[0];
appVersion = isInvokedFrom.split("/")[1];
}
return EmbeddedScanner.create(appName, appVersion, new DefaultLogOutput())
.addGlobalProperties((Map) props);
}
EmbeddedScanner runner
是真正的执行的扫描的类,创建该类的实例化时,提供了一个 create
方法,实现了工厂设计模式。
到 ScannerFactory
就进入到了 ScannerApi 这个库里
可以看到,更多的细节处理在 SonarScannerApi 中,SonarScannerCli 这个库主要是进行了命令的解析及处理,后续的工作由 SonarScannerApi 来完成。
SonarScannerApi
目录结构:
我们先看一下 EmbeddedScanner
这个入口类
/**
* Entry point to run SonarQube analysis programmatically.
*
* @since 2.2
*/
public class EmbeddedScanner {
private static final String BITBUCKET_CLOUD_ENV_VAR = "BITBUCKET_BUILD_NUMBER";
private static final String SONAR_HOST_URL_ENV_VAR = "SONAR_HOST_URL";
private static final String SONARCLOUD_HOST = "<https://sonarcloud.io>";
private final IsolatedLauncherFactory launcherFactory;
private IsolatedLauncher launcher;
private final LogOutput logOutput;
private final Map<String, String> globalProperties = new HashMap<>();
private final Logger logger;
private