Sonar-Scanner-Engine源码分析

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 方法,加了同步锁。

然后执行 GlobalContainercreateexecute 方法。顾名思义,一个是实例化类的方法,另一个是执行的方法。

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);
    }
  }

  1. doBeforeStart()
  2. pico.start()
  3. 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,

      
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值