dubのつくりかた

今年も私の作ったいくつかのdub機能がマージされました。
コントリビュートの際に得られた知見を共有したいと思います。


# dubとは
dubはD言語のソースコードをビルドするためのビルドツールであり、またパッケージ管理システムでもあります。
ビルドコマンドを実行すると、依存性を解決し、必要なパッケージをダウンロードし、コンパイル、リンクをして実行ファイルやライブラリを作ってくれます。
依存先のライブラリなどを記載する設定ファイルは`dub.json`や`dub.sdl`というファイルに記載します。

本記事ではdubの使い方などの詳細は省略し、dubを改造してコントリビュートするまでにやることについて説明します。

- ビルド方法
- テスト方法 (Dockerfile紹介)
- dub改造のために構造について少しだけ紹介
- コントリビュートの仕方


# dubのビルド方法
dubは自身をdubでビルドすることができ、以下のコマンドでビルドできます。

```d
dub build
```


# dubのテスト方法
dubのテストはLinuxを前提にしています。普段Linuxを使用している方は良いのですが、普段Windowsを使っている場合は、WSLやHyper-V、Docker for Windows等を利用して、Linux環境を準備します。
ここではDockerを使ったテスト方法を紹介します。

## Dockerfile

https://hub.docker.com/r/dlang2/dmd-ubuntu この辺りを元に、dmdでもldc2でもビルドできるようにしたシロモノ。

```Dockerfile
FROM ubuntu

ENV DPATH=/dlang
ENV LIB_FOLDER lib
ENV DEBIAN_FRONTEND=noninteractive

RUN set -ex && \
	apt-get update && \
	apt-get install --no-install-recommends -y \
	ca-certificates \
	sudo \
	curl \
	libc6-dev \
	gcc \
	libevent-dev \
	libssl-dev \
	libxml2 \
	libz-dev \
	gpg \
	make \
	xz-utils \
	git \
	jq \
	libcurl4-openssl-dev \
	rsync \
	netcat \
	pkg-config \
	&& update-alternatives --install "/usr/bin/ld" "ld" "/usr/bin/ld.gold" 20 \
	&& update-alternatives --install "/usr/bin/ld" "ld" "/usr/bin/ld.bfd" 10


RUN groupadd --gid 1000 dlang \
  && useradd --uid 1000 --gid dlang --shell /bin/bash --create-home dlang -G sudo,root \
  && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

RUN mkdir ${DPATH}\
    && chown dlang ${DPATH}

USER dlang

RUN curl -fsS https://dlang.org/install.sh | bash -s dmd -p ${DPATH} \
    && bash ${DPATH}/install.sh ldc -p ${DPATH} \
    && ln -s ${DPATH}/$(bash ${DPATH}/install.sh -p ${DPATH} list | grep dmd) ${DPATH}/dmd \
    && ln -s ${DPATH}/$(bash ${DPATH}/install.sh -p ${DPATH} list | grep ldc) ${DPATH}/ldc \
    && ln -s ${DPATH} ${HOME}/dlang \
    && ls ${DPATH}

USER root

RUN chmod 755 -R ${DPATH}

ENV PATH="${DPATH}/dmd/linux/bin64:${DPATH}/ldc/bin:${PATH}"
ENV LIBRARY_PATH="${DPATH}/dmd/linux/lib64:${DPATH}/ldc/lib:${LIBRARY_PATH}"
ENV LD_LIBRARY_PATH="${DPATH}/dmd/linux/lib64:${DPATH}/ldc/lib:${LD_LIBRARY_PATH}"
ENV DC="dmd"
```

あとはおもむろにDockerでbash立ち上げて、

```sh
. $(/dlang/install.sh -a dmd)
bash scripts/ci/travis.sh
```
でテスト実行できます。

# dubの構造

いにしえの情報(最後にメンテされたのが4年前)ですが、以下にアーキテクチャ図があります。
https://github.com/dlang/dub/blob/master/ARCHITECTURE.md

アーキテクチャでも書くクラスなどの紹介がありますが、個人的に重要かなと思ったのについていかに列挙します。

- `source/app.d`
  メイン関数があります。ここから`source/dub/commandline.d`の関数を呼び出します。
- `source/dub/commandline.d`
  コマンドラインの解析を行って、Dubクラスの初期化・関数呼び出しを行います。
- `source/dub/dub.d`
  dubのメイン機能を備えたDubクラスが定義されています。Dubをライブラリとして使う場合も、このクラスを使用します。
- Dubクラスを簡単に紹介
    - `generateProject()` / `testProject()` / `lintProject()` / `listProjectData()` などの関数は、runやbuild、test、lint、describeなどのdubコマンドと対応しています。
    - `project` は、読み込んだ dub.json/dub.sdl と対応したプロジェクトです。
    - `packageManager` は、dub.json/dub.sdlの読み込みの管理を行うマネージャクラスです。
    - `this()` / `init()` Dubクラスにプロジェクトの情報を読み込ませる場合に呼び出します。コンストラクタは内部でinit()を呼び出します。依存性もここで解決します。
- `source/dub/recipe/packagerecipe.d` `PackageRecipe` レシピです。dub.json/dub.sdl から読み込まれます。
- `source/dub/package_.d` パッケージは、ベース・サブそれぞれのレシピを持っていて、バージョンも知っているようなクラスです。`getBuildSettings(platform, config)`を使って、ビルド設定を取り出すことができます。
- `source/dub/compilers/buildsettings.d` `BuildSettings` ビルド設定です。レシピをもとに、コンフィグレーション(--configオプションで指定するやつ)や設定ファイル(settings.json)を加味して、コンパイラに渡すフラグなどを管理している構造体です。
- `source/dub/project.d` `Project` プロジェクトは、ルートのパッケージや依存先のパッケージ情報を持っているクラスです。
- `source/dub/generators/generator.d` `ProjectGenerator` / `source/dub/generators/build.d` `BuildGenerator` ジェネレータは、コンパイル・リンクを含めたビルドのための情報や、VisualStudioなどの別開発環境プロジェクト設定データの書き出しのための生成情報をまとめたクラスです。これがビルドするためのジェネレータに特化させると`BuildGenerator`になります。


# コントリビュートの仕方
基本的には一般的なプルリクエスト方式で、[Dubのリポジトリ](https://github.com/dlang/dub)をforkして、改造してfork先のリポジトリにコミットして、[公式へプルリクエスト](https://github.com/dlang/dub/pulls)を出せばよいです。
注意点としては、コントリビュートする前には、テストを行います。


```sh
bash scripts/ci/travis.sh
```

上記スクリプトでは、単体テスト(unittest)と、テスト用のプログラムを自動的にすべて実行するインテグレーションテストが行われます。
インテグレーションテスト用のプログラムは `test`ディレクトリの中に入っています。この中に入っているものは自動的に実行されてテストされます。

- フォルダはdubでビルド・テスト・実行などが行われます。フォルダの中に`.no_build`などが入っているとそれぞれビルドやテストなどをスキップします。
- シェルスクリプトは自動的に実行されます。
- `*.script.d`ファイルはdubのシングルパッケージとして実行されます。

コントリビュートする際には、単体テスト(unittest)か、インテグレーションテスト用のスクリプトを書いてからプルリクエストするようにします。

また、プルリクエストを出す前に自分のアカウントにforkしたリポジトリでCIのテストを走らせたい場合、ブランチ名を"github-actions"にしてプッシュすると、GitHub Actionsが走って確認することができます。
https://github.com/dlang/dub/blob/a72d2e5acbe2f289800d5eab6d5299a27fdc05ef/.github/workflows/main.yml#L21

プルリクエストを出した後は、マージされるまで根気強くフォローアップします。（最重要）
1週間～2週間たっても誰もレビューしてくれない場合は、レビューできる人がいないかプルリクエストへ書き込んでやりましょう。
また、レビューにすべて対処した後でもマージしてくれない場合は、まだほかにやるべきことがないか問い合わせてみましょう。
dubのプロジェクトでは、2週間何もレスがつかない場合、何かしらアクションを取らなければ放置コース送りと思ったほうがよいでしょう。

マージ or リジェクトされるまで、フォローアップすることが重要です。（大切なことなので2回言いました）


# さいごに

本記事ではビルド方法やテスト方法、dubのプログラムの構造紹介、コントリビュートの仕方について簡単に説明しました。
不明点、間違い指摘等ありましたら、お気軽にコメントをお寄せください。

この記事読んでもらってdubの開発が加速したらいいなぁ・・・