fbpx

GitLab CI/CDを使ってソースコードからクラス図を出力する #gitlab #gitlabjp #developers #Csharp

この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。

先日のGitLab Womenのアンケートにて、クラス図に関するコメントをいただきましたので、
今回はGitLabを使ったクラス図関連の内容を取り上げてみたいと思います。
前回のイベントの様子はこちらをご覧ください。

PlantUMLと連携してクラス図を書いたり、mermaidを使ってフローチャートを書いたりすることが出来ます。
ただ、実際の開発においてはクラス図は、ソースコードからクラス図を出力したいケースもあるかなと思います。
(現状クラス図を手で書いている人も開発速度や2重管理を考えて一考の余地があると思います)

今回は以下のrepositoryに入っているソースコード群(C#)を入力としクラス図を出力する仕組みを紹介したいと思います。

https://gitlab.com/creationline/apindy

大雑把には「ソースコードがpushされたら、クラス図を作って、そのドキュメントをWebに公開する」流れになります。

図にするとこんな感じになります。

今回はCI/CDで上記を実施しますが、CI/CDをやったことのない方も本記事を通して気楽にCI/CDに触れてもらえたら幸いです。

今回使用する技術要素は以下になります。
- GitLab CI/CD(GitLab Runner)
- GitLab Container Registory
- GitLab Pages

クラス図を出力するツール

いろいろなツールがありますが、今回はDoxygen+Graphvizの組み合わせで行います。
本記事執筆時点ではDoxygenは、C++、C、Java、Objective-C、Python、IDL (Corba、Microsoft 風)、Fortran、VHDL、PHP、C#が対応言語。
Doxygenがクラスリファレンス、Graphvizでクラス図を出力します(Doxygenが内部でGraphvizを呼び出す形)

事前準備

Doxygen + Graphvizの実行環境準備

今回はDocker imageを利用しますが、せっかくなので、Docker imageの用意から自前でやってみます。
実際に利用したDockerfileはこちらです。
余談ですが、ttf-freefontを導入しないと出力したクラス図が文字化けするようです。
今回はこのファイルをrepositoryのルートディレクトリにpushします。
Dockerfileを元に作成したDocker imageはGitLab Container Registryに保管します。
※ GitLabはContainer Registryも提供しています。

Doxygen設定ファイルの準備

Doxygenを動かすためには設定ファイル(Doxyfile)が必要になります。
今回はテンプレートの設定からいくつか設定を変更したものを利用しています。
実際に利用したDoxyfileはこちらです。
今回はこのファイルをrepositoryのルートディレクトリにpushします。
本記事ではDoxyfileについて触れませんが、設定内容に興味がある方はこちらをご覧ください。

ここまでが事前準備になります。

CI/CDを実装する

ルートディレクトリに.gitlab-ci.ymlを配置すると、そのYAMLファイルに書かれた処理が実行されます。
YAMLはJSONなどに比べると開発者にとってあまり馴染みのないデータ形式かもしれませんが、kubernetesなどでも良く使うデータ形式になりますので覚えておいて損はないと思います。
(YAMLは人によっては取っつきにくい面もあるかなと思います。執筆者もまだまだYAMLには不慣れです)

今回実装するCIジョブ

  • Doxygen + Graphvizを実行するためのDocker imageを作成する
  • Doxygenを実行し、ドキュメントを生成する
  • 生成したドキュメントをWebで公開する

今回はこの3つのジョブを実装したいと思います。
また、今回はある程度実利用を想定してジョブの実行条件をそれぞれ以下のように設定したいと思います。

  • すべてのジョブはdevelopブランチにマージリクエストを投げた場合のみ実行する
  • Docker imageを作成するジョブはDockerfileを更新した場合のみ実行する(それ以外の変更はDocker imageに影響がないため)
  • ドキュメント生成、公開するジョブは.md、.txt、Dockerfileを更新した場合は実行しない(これらの更新は自動生成するドキュメントに影響がないため)

今回用意した.gitlab-ci.ymlはこちらです。

以下ピックアップして内容を説明します。

Doxygen + Graphvizの実行イメージを用意するジョブ

build_doxygen_graphviz_image:
  image: docker:stable
  stage: build
  services:
    - docker:dind
  script:
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
    - docker build -t registry.gitlab.com/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/${DOXYGEN_IMAGE_MANE}:${DOXYGEN_IMAGE_TAG} .
    - docker push registry.gitlab.com/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/${DOXYGEN_IMAGE_MANE}:${DOXYGEN_IMAGE_TAG}
  only:
    refs:
      - merge_requests
    changes:
      - Dockerfile
    variables:
      - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"

script:に記載された処理が実行されます。
docker buildコマンドでrepositoryのルートディレクトリにあるDocker fileを読み込んで(".")イメージを作成します。

$CIではじまっている変数を利用していますが、こちらはGitLab CIであらかじめ定義されている変数になります。
詳細はPredefined environment variables referenceをご覧ください。

only:refsの指定項目(merge_requests)により、マージリクエストが投げられた場合のみ本ジョブが実行されます。
また、only:variablesの指定項目($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop")により、developブランチへのマージリクエスト作成のみが条件となります。

only:changesの指定項目(Dockerfile)により、Dockerfileが更新された場合のみ本ジョブが実行されます。

他にも特定のブランチにpushされた場合、コミットメッセージに特定の文字列が含まれている場合など、様々な指定が可能です。
自前でそのような条件を実装する必要がないので、個人的にはこれは結構ありがたい機能だなと感じます。

Doxygenを実行し、ドキュメントを生成するジョブ

create_class_diagram:
  image: registry.gitlab.com/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/${DOXYGEN_IMAGE_MANE}:${DOXYGEN_IMAGE_TAG}
  stage: create
  script:
    - doxygen Doxyfile
  artifacts:
    paths:
      - html
    expire_in: 1 week
  except:
    changes:
      - "*.md"
      - "*.txt"
      - Dockerfile
  only:
    refs:
      - merge_requests
    variables:
      - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"

※) *がうまく表示されなかったので*で表現しています。

doxygenコマンドによって、ドキュメントが生成されます。doxygenはデフォルトではhtmlフォルダ配下にドキュメントを生成します。

artifactsは成果物に関する設定です。
artifacts:pathsはartifacts対象とするパスを指定します。今回はhtmlディレクトリを指定しているため、doxygenで生成したドキュメントが成果物になります。
artifactsを指定することによって、成果物のダウンロードや別のジョブへの共有が可能になります。
artifacts:expore_inは保持期間の設定です。

exceptは除外項目に関する設定です。
except:changesの指定により、.md、.txt、Dockerfileを更新しても本ジョブは実行されないようになります。

onlyは先ほど説明済ですので割愛します。

生成したドキュメントをWebで公開するジョブ

pages:
  image: alpine:latest
  stage: upload
  dependencies:
    - create_class_diagram
  script:
    - mv html public
  artifacts:
    paths:
      - public
  except:
    changes:
      - "*.md"
      - "*.txt"
      - Dockerfile
  only:
    refs:
      - merge_requests
    variables:
      - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"

dependencies(Mavenを使っている人には聞きなれた単語かなと思います)の指定により、指定したジョブのartifactsを参照できるようになります。

Web公開はGitLab Pagesを用いて行います。
GitLab PagesをCI/CDで行う方法はこちらをご覧ください。
なお、GitLab Pagesのコンテンツはpublic/privateの公開設定が可能です。
Settings -> Pagesから公開設定やURLの確認などが行えます。

今回は行っていませんが、配置方法に関してはこちらもご覧ください。
ちなみにはじめてGitLab Pagesにコンテンツを展開する際は準備時間が必要となりますので、少しお待ちいただくと見えるようになると思います。

実行結果

CIの実行結果

こちらがDockerfileの更新をdevelopブランチにマージした際のpipeline。
イメージ作成のみ行われています。

こちらがcsファイルの更新をdevelopブランチにマージした際のpipeline。
ドキュメント出力と公開のみ行われています。

クラスリファレンス、クラス図

こちらがdoxygenで出力し、GitLab Pagesに公開されたドキュメントです。
Classes -> Class Hierarchyと辿っていくとクラス図(っぽいもの)が見えると思います。

最後に

今回はCI/CDの一例ということで、ソースコードからクラス図(とクラスリファレンス)をドキュメントとして出力する方法を紹介しました。
また、GitLab CI/CDの他に、GitLab Container RegistryやGitLab Pageなどを組み合わせる方法も紹介しました。

今回使用したDockerfile, Doxyfile, gitlab-ci.ymlは、GitLab Container RegistryとGitLab Pagesが利用可能な状態であれば、恐らく他のプロジェクトにそのままもっていっても恐らく動作すると思います(試していないので動かなかったらすみません)

これってCI/CDで自動化できたら便利だな、楽になるな、ミスがなくなるなーってものはいろいろあるかと思います。
ファーストステップとしては影響の小さな作業からCI/CDで出来ないか試していくと良いのかなと思います。

今回の例は、アジャイルソフトウェア開発宣言にある
「包括的なドキュメントよりも動くソフトウェアを価値とする。左記の事柄にも価値があるよ!でも右記の事柄に価値をおくよ!」
に対する1つの取組と捉えることもできるでしょう。

バックナンバー

新規CTA