Sonar-Scanner-Engine源码分析
title: Sonar-Scanner-Engine源码分析
date: 2021-09-18
author: mamian521#gmail.com
介绍
根据 sonar-scanner-cli 和 sonar-scanner-api 的源码分析,我们得知,真正执行本地扫描的源码并不在这两处,而是在另外一个模块里,sonar-scanner-engine
地址:[https://github.com/SonarSource/sonarqube.git](https://github.com/SonarSource/sonarqube.git)
本文基于 branch-9.0
分支
初始化 Batch
@Override
public Batch createBatch(Map<String, String> properties, final org.sonarsource.scanner.api.internal.batch.LogOutput logOutput) {
EnvironmentInformation env = new EnvironmentInformation(properties.get(SCANNER_APP_KEY), properties.get(SCANNER_APP_VERSION_KEY));
return Batch.builder()
.setEnvironment(env)
.setGlobalProperties(properties)
.setLogOutput((formattedMessage, level) -> logOutput.log(formattedMessage, LogOutput.Level.valueOf(level.name())))
.build();
}
入口类
org/sonar/batch/bootstrapper/Batch.java
// 入口方法
public synchronized Batch execute() {
return doExecute(this.globalProperties, this.components);
}
public synchronized Batch doExecute(Map<String, String> scannerProperties, List<Object> components) {
configureLogging();
try {
GlobalContainer.create(scannerProperties, components).execute();
} catch (RuntimeException e) {
throw handleException(e);
}
return this;
}
private Batch(Builder builder) {
components = new ArrayList<>();
components.addAll(builder.components);
if (builder.environment != null) {
components.add(builder.environment);
}
if (builder.globalProperties != null) {
globalProperties.putAll(builder.globalProperties);
}
if (builder.isEnableLoggingConfiguration()) {
loggingConfig = new LoggingConfiguration(builder.environment).setProperties(globalProperties);
if (builder.logOutput != null) {
loggingConfig.setLogOutput(builder.logOutput);
}
}
}
根据源码可以看到
Batch
初始化的时候,主要有两个参数,一个是读取配置信息,properties,另一个是 component 这里传入的是 EnvironmentInformation
,环境信息的类,主要保存的是scanner的版本。
然后开始执行 execute
方法,加了同步锁。
然后执行 GlobalContainer
的 create
和 execute
方法。顾名思义,一个是实例化类的方法,另一个是执行的方法。
public static GlobalContainer create(Map<String, String> scannerProperties, List<?> extensions) {
GlobalContainer container = new GlobalContainer(scannerProperties);
container.add(extensions);
return container;
}
............
@Override
public ComponentContainer add(Object... objects) {
for (Object object : objects) {
if (object instanceof ComponentAdapter) {
addPicoAdapter((ComponentAdapter) object);
} else if (object instanceof Iterable) {
// 递归,重复注入
add(Iterables.toArray((Iterable) object, Object.class));
} else {
addSingleton(object);
}
}
return this;
}
........
public ComponentContainer addComponent(Object component, boolean singleton) {
Object key = componentKeys.of(component);
if (component instanceof ComponentAdapter) {
pico.addAdapter((ComponentAdapter) component);
} else {
try {
pico.as(singleton ? Characteristics.CACHE : Characteristics.NO_CACHE).addComponent(key, component);
} catch (Throwable t) {
throw new IllegalStateException("Unable to register component " + getName(component), t);
}
declareExtension("", component);
}
return this;
}
执行 add 方法时,可以看到一些关键字,例如 container/singleton ,其实可以推断出,他这个地方是使用了一个依赖注入的一个框架,类似于我们现在频繁使用的 Spring
,如果是可迭代的对象 Iterable
,那就会进入递归调用里,循环注入。如果是其他类型,例如我们传入的 EnvironmentInformation
就会注入为一个单例的对象。
这里使用到的框架为 picocontainer
官网:http://picocontainer.com/introduction.html
介绍:PicoContainer是非常轻量级的Ioc容器,提供依赖注入和对象生命周期管理的功能,纯粹的小而美的Ioc容器。而Spring是Ioc+,提供如AOP等其他功能,是大而全的框架,不只是Ioc容器。
推测使用该框架的原因是,轻量级或者SonarQubu创始人比较熟悉,所以使用了该框架,看起来目前已经不怎么更新维护了,目前SonarQube也没有替换的动作。我们就把它当作成一个Spring框架来看即可。
接着看 execute
方法
public void execute() {
try {
startComponents();
} finally {
// 手动销毁注入过的容器
stopComponents();
}
}
// 模版设计模式
/**
* This method MUST NOT be renamed start() because the container is registered itself in picocontainer. Starting
* a component twice is not authorized.
*/
public ComponentContainer startComponents() {
try {
// 调用子类 GlobalContainer/ProjectScanContainer 的方法
doBeforeStart();
// 手动启动容器
pico.start();
// 调用子类 GlobalContainer/ProjectScanContainer 的方法
doAfterStart();
return this;
} catch (Exception e) {
throw PicoUtils.propagate(e);
}
}
......
@Override
protected void doBeforeStart() {
GlobalProperties bootstrapProps = new GlobalProperties(bootstrapProperties);
GlobalAnalysisMode globalMode = new GlobalAnalysisMode(bootstrapProps);
// 注入
add(bootstrapProps);
add(globalMode);
addBootstrapComponents();
}
// 依次注入
private void addBootstrapComponents() {
Version apiVersion = MetadataLoader.loadVersion(System2.INSTANCE);
SonarEdition edition = MetadataLoader.loadEdition(System2.INSTANCE);
DefaultAnalysisWarnings analysisWarnings = new DefaultAnalysisWarnings(System2.INSTANCE);
LOG.debug("{} {}", edition.getLabel(), apiVersion);
add(
// plugins
ScannerPluginRepository.class,
PluginClassLoader.class,
PluginClassloaderFactory.class,
ScannerPluginJarExploder.class,
ExtensionInstaller.class,
new SonarQubeVersion(apiVersion),
new GlobalServerSettingsProvider(),
new GlobalConfigurationProvider(),
new ScannerWsClientProvider(),
DefaultServer.class,
new GlobalTempFolderProvider(),
DefaultHttpDownloader.class,
analysisWarnings,
UriReader.class,
PluginFiles.class,
System2.INSTANCE,
Clock.systemDefaultZone(),
new MetricsRepositoryProvider(),
UuidFactoryImpl.INSTANCE);
addIfMissing(SonarRuntimeImpl.forSonarQube(apiVersion, SonarQubeSide.SCANNER, edition), SonarRuntime.class);
addIfMissing(ScannerPluginInstaller.class, PluginInstaller.class);
add(CoreExtensionRepositoryImpl.class, CoreExtensionsLoader.class, ScannerCoreExtensionsInstaller.class);
addIfMissing(DefaultGlobalSettingsLoader.class, GlobalSettingsLoader.class);
addIfMissing(DefaultNewCodePeriodLoader.class, NewCodePeriodLoader.class);
addIfMissing(DefaultMetricsRepositoryLoader.class, MetricsRepositoryLoader.class);
}
@Override
protected void doAfterStart() {
// 安装插件
installPlugins();
// 使用类加载器加载
loadCoreExtensions();
long startTime = System.currentTimeMillis();
String taskKey = StringUtils.defaultIfEmpty(scannerProperties.get(CoreProperties.TASK), CoreProperties.SCAN_TASK);
if (taskKey.equals("views")) {
throw MessageException.of("The task 'views' was removed with SonarQube 7.1. " +
"You can safely remove this call since portfolios and applications are automatically re-calculated.");
} else if (!taskKey.equals(CoreProperties.SCAN_TASK)) {
throw MessageException.of("Tasks support was removed in SonarQube 7.6.");
}
String analysisMode = StringUtils.defaultIfEmpty(scannerProperties.get("sonar.analysis.mode"), "publish");
if (!analysisMode.equals("publish")) {
throw MessageException.of("The preview mode, along with the 'sonar.analysis.mode' parameter, is no more supported. You should stop using this parameter.");
}
new ProjectScanContainer(this).execute();
LOG.info("Analysis total time: {}", formatTime(System.currentTimeMillis() - startTime));
}
梳理一下,这里就是把要启动的类作注入进去,然后手动调用启动的方法。
最后真正的执行方法是 new ProjectScanContainer(this).execute();
这个类 ProjectScanContainer
同样和 GlobalContainer
都继承自 ComponentContainer
,所以父类的 execute 方法里的 doBeforeStart
doAfterStart
他们将其重写了。这里其实用到了一个设计模式,就是模版设计模式,父类实现了一个 execute 方法,但是子类不能覆盖,该方法内的其他方法,子类是可以覆盖的,这就是模版设计模式
我们主要看下 ProjectScanContainer 这个类,这个是执行扫描的类,可以看到这几个方法
我们回顾一下父类的这几个方法
// ComponentContaienr
public ComponentContainer startComponents() {
try {
// 调用子类 GlobalContainer/ProjectScanContainer 的方法
doBeforeStart();
// 手动启动容器
pico.start();
// 调用子类 GlobalContainer/ProjectScanContainer 的方法
doAfterStart();
return this;
} catch (Exception e) {
throw PicoUtils.propagate(e);
}
}
- doBeforeStart()
- pico.start()
- doAfterStart()
所以,我们按照这个顺序看一下这些方法
// ProjectScanContainer
@Override
protected void doBeforeStart() {
// 加载插件相关类
addScannerExtensions();
// 重要,加载扫描的组件
addScannerComponents();
// 仓库文件锁,检测是否有相同的扫描
ProjectLock lock = getComponentByType(ProjectLock.class);
lock.tryLock();
// 初始化工作目录
getComponentByType(WorkDirectoriesInitializer.class).execute();
}
private void addScannerExtensions() {
getComponentByType(CoreExtensionsInstaller.class)
.install(this, noExtensionFilter(), extension -> getScannerProjectExtensionsFilter().accept(extension));
getComponentByType(ExtensionInstaller.class)
.install(this, getScannerProjectExtensionsFilter());
}
// 加载需要的组件
private void addScannerComponents() {
add(
ScanProperties.class,
ProjectReactorBuilder.class,
WorkDirectoriesInitializer.class,
new MutableProjectReactorProvider(),
ProjectBuildersExecutor.class,
ProjectLock.class,
ResourceTypes.class,
ProjectReactorValidator.class,
ProjectInfo.class,
new RulesProvider(),
new BranchConfigurationProvider(),
new ProjectBranchesProvider(),
new ProjectPullRequestsProvider(),
ProjectRepositoriesSupplier.class,
new ProjectServerSettingsProvider(),
// temp
new AnalysisTempFolderProvider(),
// file system
ModuleIndexer.class,
InputComponentStore.class,
PathResolver.class,
new InputProjectProvider(),
new InputModuleHierarchyProvider(),
ScannerComponentIdGenerator.class,
new ScmChangedFilesProvider(),
StatusDetection.class,
LanguageDetection.class,
MetadataGenerator.class,
FileMetadata.class,
FileIndexer.class,
ProjectFileIndexer.class,
ProjectExclusionFilters.class,
// rules
new ActiveRulesProvider(),
new QualityProfilesProvider(),
CheckFactory.class,
QProfileVerifier.class,
// issues
DefaultNoSonarFilter.class,
IssueFilters.class,
IssuePublisher.class,
// metrics
DefaultMetricFinder.class,
// lang
Languages.class,
DefaultLanguagesRepository.class,