Help us understand the problem. What is going on with this article?

SonarQubeのプラグイン作成(その1)

目的

SonarQubeのプラグインを作成してみます。

参考: sonar-custom-plugin-example

必要なもの

括弧内は試したバージョンになります。なお、JDKやMavenの準備については説明しません。

  • JDK (1.8)
  • Maven (3.6.1)
  • SonarQube (7.9.1) この手順ではdockerで環境構築しているが環境は問わない

準備: SonarQube 環境構築

SonarQubeサーバ起動

動作確認に使用するSonarQubeはdockerで構築します。

まずは docker-compose.yml を任意のディレクトリに作成します。これは以下を参考に作りました。
https://github.com/SonarSource/docker-sonarqube/blob/master/recipes/docker-compose-postgres-example.yml

上記例のdocker-compose.ymlとは異なりトップのvolumesを削除して、ボリュームの記述を相対パスに変えてます。これはカレントディレクトリに永続化したディレクトリを作成したいからです。

docker-compose.yml
version: "2"

services:
  sonarqube:
    image: sonarqube
    ports:
      - "9000:9000"
    networks:
      - sonarnet
    environment:
      - sonar.jdbc.url=jdbc:postgresql://db:5432/sonar
    volumes:
      - ./sonarqube_conf:/opt/sonarqube/conf
      - ./sonarqube_data:/opt/sonarqube/data
      - ./sonarqube_extensions:/opt/sonarqube/extensions

  db:
    image: postgres
    networks:
      - sonarnet
    environment:
      - POSTGRES_USER=sonar
      - POSTGRES_PASSWORD=sonar
    volumes:
      - ./postgresql:/var/lib/postgresql
      # This needs explicit mapping due to https://github.com/docker-library/postgres/blob/4e48e3228a30763913ece952c611e5e9b95c8759/Dockerfile.template#L52
      - ./postgresql_data:/var/lib/postgresql/data

networks:
  sonarnet:
    driver: bridge

docker-compose.yml を作成したディレクトリで以下を実行します。

docker-compose up -d

これでしばらくすると、 http://localhost:9000/ でSonarQubeにアクセスできます。
うまくいかない場合は、以下でログを参照しエラーがないかを確認してください。

docker-compose logs

正常に起動すると以下の画面が開きます。画面右上の「Log in」から、ユーザ名admin,パスワードadminでログインしてください。

スクリーンショット 2019-10-21 15.23.04.png

ログイン後の画面

スクリーンショット 2019-10-21 15.32.38.png

任意のプラグインをダウンロードしておきます。以降、javaプログラムの解析を例に説明するので、
SonarJavaの「Install」をクリックします。

スクリーンショット 2019-10-21 15.51.21.png

スクリーンショット 2019-10-21 16.04.53.png

完了したら「Restart Server」をクリックします。

スクリーンショット 2019-10-21 16.38.41.png

なお、この時点でカレントディレクトリのsonarqube_extensions/downloads/ にプラグインファイルがダウンロードされます。

ls sonarqube_extensions/downloads/
sonar-java-plugin-5.14.0.18788.jar

再起動後は、sonarqube_extensions/plugins/ にこのファイルは移動しています。

ls sonarqube_extensions/plugins/
sonar-java-plugin-5.14.0.18788.jar

以降はこの sonarqube_extensions/plugins ディレクトリにプラグインのjarファイルが残っている限りは個別のインストールは不要になります。他にプラグインが必要であれば任意に追加してください。解析対象の言語以外では sonar-scm-git-plugin はあった方が良いでしょう。

SonarScanner 準備

サーバの準備ができたら解析対象の準備になります。
まずは以下から自分の環境にあったSonarScannerをダウンロードします。

https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/

ここでは、"Mac OS X 64" をクリックしてダウンロードされる sonar-scanner-cli-4.2.0.1873-macosx.zip を使って説明します。

ダウンロードした sonar-scanner のzipファイルを任意の場所に展開し、binディレクトリをPATHに追加します。
ここでは、docker-compose.ymlを置いたカレントディレクトリに展開したとして説明します。

unzip ~/Download/sonar-scanner-cli-4.2.0.1873-macosx.zip

PATH=$PWD/sonar-scanner-4.2.0.1873-macosx/bin:$PATH

解析対象のプログラムを適当に準備します。
sample ディレクトリを作成し、以下のファイルを作ってください。

sample/Foo.java
public class Foo {
  String hello = "Hello, world!\n";

  public static void main(String[] args) {
    Integer a = null;
    if (a != null) {
      System.out.println("Hello, world!\n" + a);
    }
  }
}
sample/sonar-project.properties
sonar.projectKey=helloworld
sonar.projectName=Hello
sonar.projectVersion=1.0

sonar.sources=.
sonar.java.binaries=.
sonar.sourceEncoding=UTF-8
sonar.java.source=1.6

解析対象のソースとsonar-project.propertiesが準備できたら sample ディレクトリで sonnar-scanner を実行します。(sonar-scannerはサーバと通信するので、SonarQubeが起動している事は確認しておいてください)

cd sample
sonnar-scanner

sonar-scanner がうまく行けば最後に以下の出力になります。


INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 6.865s
INFO: Final Memory: 6M/24M
INFO: ------------------------------------------------------------------------

sonar-scannerが完了した後でも、サーバではバックグラウンドタスクが実行中の場合があります。
これが完了するのを待つ必要があります。(上記サンプル程度ならすぐに完了します。)

スクリーンショット 2019-10-21 16.56.41.png

以下の通り、解析が完了しました。

スクリーンショット 2019-10-21 17.00.19.png

スクリーンショット 2019-10-21 17.01.50.png

スクリーンショット 2019-10-21 17.02.21.png

スクリーンショット 2019-10-21 17.02.33.png

プラグイン作成

最終的に作成するプラグインはあらかじめ用意した正誤表をもとに誤表記をチェックするものにします。

例えば、以下のような正誤表にしたがって左の語句が出てきたらエラーにするというものです。
このようなチェックで問題となるのは誤判定です。誤判定をいつまでも報告し続けてもゴミになり、そのゴミのせいで本来検出したい誤記が目立たなくなります。そのゴミを SonarQubeの機能により訂正できるなら便利なのではないかと考えたのがこのプラグイン作成の動機になってます。

タイポ タイプ
昨日 機能

ただし、この記事の進め方としては、プラグインの作成の仕方を説明するために順番に機能を追加する形で説明をしていきます。

まず、最初のステップとして Java ソースにチェックルールを追加するプラグインを作成します。
また作成するルールの第一段階目では(参考にしたsonar-custom-plugin-exampleに倣って)先頭の1行目が必ずissueになる限定的な機能になっています。

さて、プラグインを作成するには以下を決めておく必要があります。

項目
pluginKey errata
artifactId sonar-errata-plugin
packaging sonar-plugin (必ずこれを指定します)
バージョン 0.0.1
パッケージ,groupId org.jca02266.sonarplugins.errata
pluginClass org.jca02266.sonarplugins.errata.ErrataPlugin
name Errata Plugin for SonarQube 7.9.x LTS
description Check eratta for some text files
sonar.apiVersion 7.1
jdk.min.version 1.8

pluginKeyさえ決まれば、(パッケージ, name, descriptionは適当にしてますが)自ずと決まります。なおerrataとは正誤表のことです。

Create a Maven Projectのpom.xmlの例では artifactId を sonar-{key}-plugin という名前にすることを推奨されているのでそれに従っています。

packaging は通常、jarなどを指定する箇所ですが、ここは sonar-plugin 固定です。

最初のプラグイン

まずは、Maven Project作成にあたって、以下のpom.xmlを準備します。
前項で決めた内容にしたがって決まった通りに記述します。

sonar-errata-plugin/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.jca02266.sonarplugins.errata</groupId>
  <artifactId>sonar-errata-plugin</artifactId>
  <packaging>sonar-plugin</packaging>
  <version>0.0.1</version>

  <name>Errata Plugin for SonarQube 7.9.x LTS</name>
  <description>Check eratta for some text files</description>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <sonar.apiVersion>7.1</sonar.apiVersion>
    <jdk.min.version>1.8</jdk.min.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.sonarsource.sonarqube</groupId>
      <artifactId>sonar-plugin-api</artifactId>
      <version>${sonar.apiVersion}</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
        <artifactId>sonar-packaging-maven-plugin</artifactId>
        <version>1.18.0.372</version>
        <extensions>true</extensions>
        <configuration>
          <pluginKey>errata</pluginKey>
          <pluginClass>org.jca02266.sonarplugins.errata.ErrataPlugin</pluginClass>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <source>${jdk.min.version}</source>
          <target>${jdk.min.version}</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

プラグインの入り口

pom.xmlで、pluginClass に指定したクラスがプラグインの入り口になります。
以下のファイルを準備します。

src/main/java/org/jca02266/sonarplugins/errata/ErrataPlugin.java
package org.jca02266.sonarplugins.errata;

import org.jca02266.sonarplugins.errata.rules.CreateIssuesOnTextFilesSensor;
import org.jca02266.sonarplugins.errata.rules.TextRulesDefinition;
import org.sonar.api.Plugin;

/**
 * This class is the entry point for all extensions. It is referenced in pom.xml.
 */
public class ErrataPlugin implements Plugin {

  @Override
  public void define(Context context) {
    context.addExtensions(TextRulesDefinition.class, CreateIssuesOnTextFilesSensor.class);
  }
}

作成するクラスは、Plugin インタフェースの実装になります。唯一必要なのは、defineメソッドで、
context.addExtensions()でプラグインの機能を追加します。

context.addExtension()(sなしの単数形)も用意されており、以下のように必要な数だけ呼び出す形で利用します。どちらを使っても同じです。

    context.addExtension(TextRulesDefinition.class);     
    context.addExtension(CreateIssuesOnTextFilesSensor.class);

大事なことは用意した機能はこの addExtension または addExtensions で機能を追加しなければならない(そうでなければSonarQubeに認識されない)ということです.

リポジトリとルール

TextRulesDefinition.class というのは以下になります。
ここでは、ルールの集まりを表す「リポジトリ」を作成し、そこに一つ一つのルールを追加します。
(この例では追加するルールは一つだけです)

src/main/java/org/jca02266/sonarplugins/errata/rules/TextRulesDefinition.java
package org.jca02266.sonarplugins.errata.rules;

import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.rule.RulesDefinition;

public class TextRulesDefinition implements RulesDefinition {

  public static final String REPOSITORY = "text-errata";
  public static final String LANGUAGE = "java";
  public static final RuleKey RULE = RuleKey.of(REPOSITORY, "errata");

  @Override
  public void define(Context context) {
    NewRepository repository = context.createRepository(REPOSITORY, LANGUAGE).setName("Check for text files");

    NewRule rule = repository.createRule(RULE.rule())
      .setName("Errata")
      .setHtmlDescription("Check errata for all text files")
      .setTags("errata")
      .setStatus(RuleStatus.READY)
      .setSeverity(Severity.MINOR);

    rule.setDebtRemediationFunction(rule.debtRemediationFunctions().linearWithOffset("0h", "1min"));

    repository.done();
  }
}

大事なのは

context.createRepository(REPOSITORY, LANGUAGE)

でリポジトリを作り、

public static final RuleKey RULE = RuleKey.of(REPOSITORY, "errata");

でリポジトリにルールを追加している部分になります。あとはそれぞれの属性値になります。

後の手順で確認しますが、このプラグインを組み込むことで以下のようにリポジトリとルールが追加されていることが確認できます。

スクリーンショット 2019-10-21 18.26.01.png

スクリーンショット 2019-10-21 18.42.14.png

センサー

CreateIssuesOnTextFilesSensor.class のソースは以下になります。

src/main/java/org/jca02266/sonarplugins/errata/rules/CreateIssuesOnTextFilesSensor.java
package org.jca02266.sonarplugins.errata.rules;

import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;

public class CreateIssuesOnTextFilesSensor implements Sensor {

  private static final double ARBITRARY_GAP = 1.0;

  @Override
  public void describe(SensorDescriptor descriptor) {
    descriptor.name("Check errata for text files");

    descriptor.onlyOnLanguage("java");
    descriptor.createIssuesForRuleRepositories(TextRulesDefinition.REPOSITORY);
  }

  @Override
  public void execute(SensorContext context) {
    FileSystem fs = context.fileSystem();
    Iterable<InputFile> textFiles = fs.inputFiles(fs.predicates().all());
    for (InputFile textFile : textFiles) {
      NewIssue newIssue = context.newIssue()
        .forRule(TextRulesDefinition.RULE)
        .gap(ARBITRARY_GAP);

      NewIssueLocation primaryLocation = newIssue.newLocation()
        .on(textFile)
        .at(textFile.selectLine(1))
        // .at(textFile.newRange(textFile.newPointer(1, 10), textFile.newPointer(2,5)))
        .message("Fix typo");
      newIssue.at(primaryLocation);

      newIssue.save();
    }
  }
}

上記のように、インタフェース Sensor を実装したクラスを用意することで、SonarScannarの実行に応じてルールの適用を試みます。

具体的には、execute()メソッドにあるように以下のステップで検出した問題を登録します。

  • newIssue ルール(ここではTextRulesDefinition.RULE)を生成する
  • そのルールに適合するソースを検出したらNewIssueLocationを生成する
  • newIssue.at()でその検出箇所を登録する
  • newIssue.save()で問題を登録する

newIssue.at() に対して、textFile.selectLine(1)を渡していますが、これは対象ファイルの1行目を表しています。
コメントにしているtextFile.newRange(textFile.newPointer(1, 10), textFile.newPointer(2,5))であれば、1行目の10文字目から2行目の5文字目までの意味になります。問題を検出する処理を作った場合に、これらのメソッドで検出した場所を示します。

メソッド describe() では、以下の処理を行なっています。

    descriptor.onlyOnLanguage("java");
    descriptor.createIssuesForRuleRepositories(TextRulesDefinition.REPOSITORY);

これは以下の意味になっています。

  • 対象ファイルが java 言語の場合にのみセンサーを実行する
  • 指定したリポジトリ(TextRulesDefinition.REPOSITORY)のルールが一つもactiveでなければセンサーを実行しない

コンパイルとプラグインの登録

作成したプラグインは以下のコマンドでコンパイルします。

$ mvn clean package

これにより、targetディレクトリにsonar-errata-plugin-0.0.1.jarが生成されます。
このファイルを最初の方で説明したplugin格納ディレクトリにコピーします。

$ ls target/sonar-errata-plugin-0.0.1.jar
target/sonar-errata-plugin-0.0.1.jar
$ cp target/sonar-errata-plugin-0.0.1.jar ../sonarqube_extensions/plugins/ 

そして、SonarQubeを再起動します。Web画面からAdministration→System→Restart Serverでも良いのですが、dockerの再起動でも大丈夫です。

$ cd ..
$ docker-compose restart
Restarting sonarqube_sonarqube_1 ... done
Restarting sonarqube_db_1        ... done

ルールを有効にする

追加したルールを有効にするにはいくつかステップが必要になります。

まず、Quality Profiles を選びます。そうすると、(現在 sonar-java-plugin のプラグインを登録しているので、) Java 向けに Sonar way プロファイルが表示されています。
この行の歯車マークをクリックすると Compare, Copy, Extend のプルダウンが表示されます。

※ ここで、Compare しか表示されない場合は admin でログインできていません。サーバの再起動によりセッションの状態がおかしくなっている可能性があるので、ログインしている場合でもLogout→再ログインを試してみてください。

スクリーンショット 2019-10-22 9.01.47.png

Copyを選びます。(Extendでも良いです。Extendの場合は元のProfileを継承したProfileを作成します)

スクリーンショット 2019-10-22 9.07.04.png

コピーした Sonar way2プロファイルを選択した画面になります。

スクリーンショット 2019-10-22 9.08.48.png

プロファイルの一覧に戻って作成したプロファイルをデフォルトにします。

スクリーンショット 2019-10-22 9.12.20.png

これにより、今後のスキャンではこのプロファイルが利用されるようになります。

次に、ルールを選びます。リポジトリを選ぶとルールErrataが一覧に表示されるのでそれを選択します。

スクリーンショット 2019-10-22 9.17.26.png

Quality Profilesの欄に「Activate」というボタンが表示されるので、Activateします。
最初のステップでSonar wayをコピーした編集可能なプロファイルを作成しないと、このActivateボタンは表示されないので注意してください。

スクリーンショット 2019-10-22 9.19.23.png

スクリーンショット 2019-10-22 9.19.53.png

以上の手順で、プラグインで追加したルールをプロファイルに登録することができました。
改めてソースをスキャンすることで、ルールが有効になっていることを確認しましょう。

cd sample
sonar-scanner
...
INFO: Sensor Check errata for text files [errata]
INFO: Sensor Check errata for text files [errata] (done) | time=11ms
...
INFO: ------------------------------------------------------------------------
INFO: Total time: 5.677s
INFO: Final Memory: 6M/27M
INFO: ------------------------------------------------------------------------

scannerの出力をよく見るとプラグインで追加したセンサーが動作しているログが表示されます。

プロジェクト Helloを見るとちゃんと(ニセの)バグが検出されています。
また、sonar-project.propertiesというソースとは異なるファイルも検出対象になってしまってます。
対象ファイルの制御についてはこの後おいおいやっていくことにします。

スクリーンショット 2019-10-22 9.28.20.png

続く...
SonarQubeのプラグイン作成(その2)

jca02266
Family BASIC→N88-BASIC→Z80 マシン語→C,C++→EmacsLisp→sh,awk→Perl→Ruby→TclTk→vb6, vba→SQL→Java (ほぼ学習のみ -> Pascal,Prolog,C#,Python,Haskell,Scala,Groovy,Javascript,powershell,Go,Rust,Kotlin)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした