torutkのブログ

ソフトウェア・エンジニアのブログ

Githubのブランチ管理方針~Redmineプラグインの場合

Gitのブランチ管理方針~Redmineプラグインの場合

Redmineプラグインのコード管理の背景

RedmineのGlossary Pluginをフォークして細々とRedmineバージョンアップに対応しています。

https://github.com/torutk/redmine_glossary

Redmineプラグインのコードを管理する上で、ターゲットとなるRedmineバージョンを明確にしておくことはとても重要なことです。 というのは、Redmine本体がバージョンアップする際に、RedmineがベースとしているフレームワークであるRuby on Railsをメジャーバージョンアップした場合、プラグインにも大きな変更が必要になるからです。

Ruby on Railsはバージョンアップに対して後方互換性を考慮することはあまりなく、APIの廃止、変更がばしばしと入ってきます。そのため、RedmineプラグインRuby on Railsのメジャーバージョンアップに対応させるには、プラグインのコードにAPIの廃止と新しいAPIへの移行を行うなどの修正を入れていきます。

Redmine 3.4(Ruby on Rails 4.2)からRedmine 4.0(Ruby on Rails 5.2)へのバージョンアップに際してプラグインの修正が必要となった非互換性については一昨年に次の記事にまとめました。

Redmine 4.0(Rails 5.2)に用語集(Glossary)プラグインを対応させる - Qiita

これらの修正では、同じコードでRedmineの旧バージョンと新バージョンに対応することが困難であり、あきらめてRedmine 4.0向けとしました。もし、ひとつのコードで複数のRedmineバージョンに対応するためには、if文でRuby on Railsのバージョンをチェックして新旧の双方のコードを記載するなど、そうとうに煩雑なコードになりそうでした。

そこで、一つのコードで複数のRedmineバージョンに対応するのをあきらめて、Redmineの各メジャーバージョンに合わせてプラグインのコードを分けることにします。ただし、プラグインのコードを分けて管理するには、複数のブランチでリリースを管理することになります。

いままでは、ブランチ管理方針を考えることなく、その場その場で適当にブランチを作成していましたが、これからは混乱しないようにブランチ管理方針を決めてコードを管理していきたいと思います。

Gitのブランチ管理方針を調べる

Git でよく見かけるブランチ方針「A successful Git branching model」は、masterブランチにリリースしたバージョンのタグを打って管理し、releaseブランチはリリース作業中の一時的なブランチとし、リリース準備が完了するとreleaseブランチからmasterブランチにマージを行い、リリースタグを削除します。この場合、新しいバージョン(例:Ver.2.0.0)をリリースした後に、前のバージョン(例:Ver.1.4)の修正をして前のバージョンのマイナーバージョンアップ(例:Ver.1.4.1)をすると、masterへの反映ができなくなってしまいます。

この「A successful Git branching model」に基づいていると思われる「Git flow」というワークフローでは、オプションとしてsupportブランチを設け、master以外に古いバージョンを管理する場合に使用するようになっています。 supportブランチは、リリース後の管理をするためのブランチなので、リリース準備をするreleaseブランチとは別に設け、releaseブランチで準備が完了しmasterへマージをした後に、masterからsupportをブランチさせて使用します。

以下はGit flowで使われるブランチとその概要です。

  • master リリースしているコードの最新。常に1つ。直コミット厳禁でリリースブランチあるいはhotfixブランチからマージによってのみ更新。
  • develop/xxx 開発中のコード。複数のdevelopブランチが並走することがあり、スラッシュに続いてバージョン名を記した命名をする。直コミットも可(1回のコミットで完了しない変更はfeatureブランチを設ける)。
  • feature/xxx 複数のコミットで完了する大掛かりな開発はいったんfeatureブランチを設けてそこへコミットし、完了後にdevelopにマージする。スラッシュに続いて開発コード名(あるいは目指すバージョン番号、開発チケット番号等)を記した命名をする。
  • release/xxx リリース準備をするコード。スラッシュに続いてリリース番号を記した命名をする。リリース準備が完了したらmasterにマージする。masterにはリリース番号のタグを付与する。masterにマージ後そのreleaseブランチは削除する。developに反映したい場合は、masterからdevelopへマージする。
  • hotfix/xxx masterに素早く変更を加えたい場合に、masterからブランチし、masterおよび必要があればdevelopへマージする。xxxはバージョン番号(マイナーバージョンあるいはマイクロバージョン番号を更新したもの)など。
  • support/xxx 過去のバージョンのサポート。リリースしたmasterからブランチし、サポート期間中維持する。

Redmineのバージョンと利用状況

プラグインの保守対象バージョンを判断するには、Redmineの各バージョンがどの程度使われているかを知りたいところです。 年に2回実施されているRedmine.tokyo勉強会では、使用しているRedmineのバージョンなどが参加者アンケート集計結果として公開されています。

Redmine.tokyo 17 questionnaire

2019年11月の場合では、Redmine 4.0が25%弱、3.4が40%弱、3.3~3.0が20%弱、2.6~2.0が10%弱といった分布です(目分量)。

勉強会参加者なので、比較的新しいバージョンを使う動機の大きい人たちという点を考慮に入れても、Redmine 2.0以降のバージョンは現時点では対象と考えておくのがよさそうです。

Redmineのバージョン、Ruby on Railsのバージョン、Rubyのバージョンの関係は次となります。

Redmineバージョン Ruby on Railsバージョン Rubyバージョン
4.0, 4.1 5.2 2.2.2, 2.3, 2.4, 2.5
3.4 4.2 1.9.2, 1.9.3, 2.0, 2.1, 2.2, 2.3, 2.4
3.3 4.2 1.9.2, 1.9.3, 2.0, 2.1, 2.2, 2.3
3.0 4.2 1.9.2, 1.9.3, 2.0, 2.1, 2.2
2.5.2 3.2 1.8.7, 1.9.2, 1.9.3, 2.0, 2.1
2.3.0 3.2 1.8.7, 1.9.2, 1.9.3, 2.0

そこで、supportブランチの下には

| ブランチ名 | 対応Redmine | |++|++| |support/2.x | Redmine 2.3, 2.5 |support/3.x | Redmine 3.0, 3.3, 3.4 | |support/4.0or1/based_original | Redmine 4.0, 4.1 |

の3つを用意します。Redmine 4.0および4.1への対応は、これまでのコードを修正して対応するバージョンと、ゼロから作ったバージョンとがあり、これまでのコードの修正をsupport/4.0or1/based_originalブランチで行っています。ゼロから作り直しは現在developブランチで行っており、最初のリリースをmasterにマージしています。

このブランチの見直しがリポジトリに入っています。

Redmineのテキスト編集をIE上で行う際Escキーで編集内容が消える問題の対処

はじめに

自宅PCや開発PCでは、Webブラウザとして主にFirefoxを使い、たまにChromeを、稀にEdgeを使います。 先月初めに、職場が移って(現職出向から帰任)新しいPCが配布されました。業務システムの対応Webブラウザの関係か、デフォルトがIE 11となっています。IE 11でRedmineのチケットを編集していたところ、漢字変換のON/OFF操作の際に誤ってEscキーを押してしまい、なんとそれまで入力していたテキストが消失してしまいました。

IEのメニューには「元に戻す」的なメニュー項目は存在せず、ググって見つけた元に戻す操作のショートカットキーCtrl-Zでも何故か戻らず、再度入力し直す事態となりました。

IE上でHTMLのFormでテキスト編集をする際にEscキーを押すとそれまで入力した内容が消えるというのは、IEの仕様のようです。しかし、FirefoxChrome、Edgeにはその仕様はありません。

Redmineを使う際、職場では多くの人がIEを使うので、このEsc問題への対処をしたいところです。周囲に聞くと、Escの誤操作で消えるから、エディターでテキストを作成してからコピーするといった対処をしている人もいました。

IEでEscキーによるテキスト消失を避ける手段を調べてみた

調べてみると、対処の手段はHTMLページにJavaScriptでEscキー入力があるとそれを無視する処理を埋め込むというものでした。その実装の多くは、documentのkeydownイベントでキーコードがEscキーの27であればイベントをつぶすというものでした。

Redmineではどう対処するか

Redmineで各ページにJavaScriptを埋め込むぅ? Redmineの内部をいじるのか?と思いましたが、ふとView Customizeプラグインがこの対処に使えそうと思い当たりました。

View Customizeでこの件と似たような処理で、チケットの作成途中にEnterキーが押されるとチケットが登録されてしまうという問題に対処するため、特定の条件でEnterキーを無視するという例を紹介しているブログがありました。

qiita.com

この記事では、次のコードでEnterキーを抑制していました。

$(function(){

    // form内のエンターキー入力を無視
    // トラッカー・ステータス操作時のDOM操作で破棄されないようdocumentにイベントを設定
    $(document).on('keypress', '#issue-form input[type="text"]', function(event) {
        if(event.keyCode == 13) {
            return false;
        }
    })
});

これを真似て、テキスト編集箇所でキー入力があった際、Escキーであれば無視するコードを作成します。 Redmineでは、Wiki機能を持つテキスト編集領域は、HTMLのTextArea要素にclass属性で .wiki-editが指定されています。そこで、次の様に記述しView Customizeに登録しました。なお、パスは .* を指定します。

/*
 テキスト編集中にEscキーを無効にする
(IEでテキスト編集中にEscキーを押すと入力したテキストが消える問題の対処)
*/
$(function() { 
  $(document).on('keydown', '.wiki-edit', function(event) {
    if (event.keyCode == 27) {
      return false;
    }
  });
});

結果

IERedmineのテキスト編集箇所を開いてテキスト編集し、Escキーを押したところ、編集途中のテキストが消えることなく存在し続けました。

WSLでCentOSが利用できたかも?(CentOS 7)

WSLで動くLinuxディストリビューション

現在、WSLの上で動くLinuxディストリビューション(WSLではLinuxカーネルは動かさないので実際にはユーザーランド)は、マイクロソフトストア上にUbuntuOpenSUSE 他いくつか用意されていますが、CentOSはありません。普段CentOSを使っている場合、別なディストリビューションを使うとコマンド体系等が異なるので不便を感じます。

そんな折、WSLへCentOS 7を入れるブログを見かけました。

vogel.at.webry.info

ということで、この方法を追ってみたいと思いました。

方法

WSL上で動かすCentOS 7のユーザーランドを作成・公開しているGitHubリポジトリが次です。

github.com

GitHubのリリースには、バイナリのzipアーカイブCentOS.zip)とソースアーカイブが公開されています。 本日時点では、7.0.1907.1 が最新リリースです。これは、CentOS 7.6.1907 に基づくWSL用ユーザーランドです。

CentOS.zipを解凍し、その中にあるCentOS.exeを管理者権限で実行します(展開したフォルダ上で実行)。 なお、ノートPCのmicroSD上(exFAT)で実行するとエラーとなりました。参考まで。

CentOS7.exeを実行すると、コマンドプロンプトが開き、インストールが完了すると Press any key to continue... と表示されます。 何かキーをたたくとコマンドプロンプトが消えます。

Installing...
Installation Complete!
Press any key to continue...

インストール後、再度CentOS7.exeを実行すると、Bashシェルが開きます。

アンインストール(レジストリ登録の消去)は、CentOS.exe clean を実行します。

コマンドプロンプトからwslconfig /lを実行すると、CentOS7が登録されているのが分かります。

C:> wslconfig /l
Windows Subsystem for Linux ディストリビューション:
openSUSE-Leap-15 (既定)
CentOS7

初期設定

Windows上のコマンド環境Cmderへの設定

タブ機能を持つコマンドプロンプトツール Cmder でCentOS7を開けるよう、CentOS7.exeへのパスを設定したTaskを新規作成します。

ユーザー作成

デフォルトはrootユーザーとなっているので、自分用のユーザーを作成します。管理権限をsudoで実行できるようにwheelグループに登録します。

# adduser <自分用のユーザー名>
# gpasswd -a <自分用のユーザー名> wheel

sudoコマンドでパスワードなしに実行できるよう/etc/sudoersを修正します。

%wheel  ALL=(ALL)       NOPASSWD: ALL

先のCentOS7.exeを実行したときに、ここで作成したユーザーでbashが開くよう設定します。

C:\path\to\CentOS7> CentOS7 config --default-user <自分用のユーザー>

うまくいかないときは、CentOS7 config --default-user root でいったんrootユーザーに戻してbashを実行し再設定します。

SoftwareCollections.org リポジトリ設定

OS標準パッケージとは別に、RHELおよびCentOS向けに新しいバージョンのパッケージを提供するSoftware Collectionsを利用できるようにします。

$ sudo yum install centos-release-scl-rh

例えば、rubyの場合、CentOS 7標準のパッケージは ruby-2.0.0ですが、Software Collectionsで提供されるパッケージには、

  • rh-ruby22
  • rh-ruby23
  • rh-ruby24
  • rh-ruby25
  • rh-ruby26

があります。

ruby 2.6のインストール
$  sudo yum install rh-ruby26

Software Collectionsでインストールしたパッケージは、/opt下にインストールされます。rh-ruby26の場合は次になります。

/opt/rh/rh-ruby26/
  +-- enable
  +-- root/

enableファイルには、環境変数定義が記述されているので、これをbash起動時に読み込まれるよう設定します。

$ sudo ln -s /opt/rh/rh-ruby26/enable /etc/profile.d/rh-ruby26.sh

以降新たにbashを起動すると、ruby26が利用できるようになります。

~$ ruby -v
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-linux]

パスを通すには、/opt/rh/rh-ruby26/enable へのシンボリックリンクを、/etc/profile.d/rh-ruby26.sh として作成します。

~$ sudo ln -s /opt/rh/rh-ruby26/enable /etc/profile.d/rh-ruby26.sh
~$ bash
~$ ruby -v
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-linux]

Redmine プラグイン開発環境を作ってみる

Redmineプラグイン開発環境を作ってみます。前に作成した環境は次です。

Redmineプラグイン開発環境 - ソフトウェアエンジニアリング - Torutk

git

RedmineGitHubリポジトリから取得するために、gitをインストールします。 CentOS 7の標準パッケージのgitはバージョンが1.8.3と今や相当古いバージョンです。Software Collectionsでgit 2.9を取得します。

~$ sudo yum install rh-git29

環境設定を/etc/profile.d下に作成します。

~$ sudo ln -s /opt/rh/rh-git29/enable /etc/profile.d/rh-git29.sh

RedmineGitHubレポジトリからクローンしてきます。

~$ mkdir redmine
~$ cd redmine
redmine$ git clone  https://github.com/redmine/redmine.git redmine_glossary_dev
  :

sqlite開発環境

~$ sudo yum install sqlite-devel

ruby bundler

~$ sudo yum install rh-ruby26-rubygem-bundler

Redmineが必要とするgemsのインストール

redmine_glossary_dev$ bundle install --path vendor/bundler
bundle installでエラーになった対処
~$ sudo yum groupinstall "Development Tools"
  :
sudo yum install openssl-devel readline-devel zlib-devel curl-devel libyaml-devel
~$ sudo yum install rh-ruby26-ruby-devel

セッション秘密鍵の生成

redmine_glossary_dev$ bundle exec rails generate_secret_token

データベース初期化(スキーマ生成)

redmine_glossary_dev$ bundle exec rails db:migrate

Redmineの実行

redmine_glossary_dev$ bundle exec rails server

Redmineへのアクセス

同一マシン上で、Webブラウザから http://localhost:3000 にアクセスするとRedmineが表示されます。

環境変数NAMEを無効化

WSLで使用するCentOS 7のユーザー環境変数NAMEにはホスト名が定義されています。Redmineプラグインをインストールするときに影響が出るので、未定義にしておきます。

  • ~/.bashrc に次を追記
unset NAME

ファイルシステムをwslfsに更新

WSLでインストールした際のファイルシステムは、デフォルトではlxfsですが、これをWslFsに更新することができます。 更新すると何がいいのかは明確には語られていないようなので、更新して良くなると思ったら実施で・・・。

更新するには、コマンドプロンプトから以下を実行します。

C:\> wslconfig /upgrade CentOS7

更新されたかどうかは、ルートファイルシステムを表示して確認します。

~$ df -T
Filesystem     Type  1K-blocks     Used Available Use% Mounted on
rootfs         wslfs 103472168 81965432  21506736  80% /
none           tmpfs 103472168 81965432  21506736  80% /dev
  :

X Window Systemの利用

コマンドライン環境であれば、これで一通り操作できるようになりました。しかし、GUIを伴う環境も使いたくなります。 そこで、Windows OS側にXサーバー機能を入れ、Linux(WSL)側でXクライアントプログラムを実行したらその画面をWindows側に表示できるようにします。

Windows OSへXサーバー機能(VcXsrv)をインストール

Windows用のXサーバー機能には、今回VcXsrvソフトウェアをインストールしました。

sourceforge.net

インストール後、XLaunchをダブルクリックし、Xサーバーを起動します。

HiDPI画面でXの画面がぼやける

VcXsrvのアイコンを右クリックしプロパティで互換性タブを選択、[高DPI設定の変更]を押し、[高いDPIスケールの動作を上書きします。]にチェックを付け、種類は[アプリケーション]を選択。VcXsrvを再起動します。

ただし、Xのアプリケーションが小さく表示されるので、アプリケーション個別にフォントを大きくするなどして使いやすくする必要があります。

Xクライアントプログラムのインストール

~$ sudo yum install xorg-x11-apps

X Window Systemのライブラリ等を含めてインストールされます。

リモートのXサーバーに表示する場合、環境変数DISPLAYを設定します。

~$ export DISPLAY=0:0

xeyesツールを実行します。

~$ xeyes

目玉が表示されればOKです。

RubyMine Linux

RubyMineのLinux版をインストールしてみます。Windows上であらかじめダウンロードしたアーカイブファイルを読み込むことができます。

~$ cd /opt
opt$ sudo tar xvzf /mnt/c/Users/torutk/Downloads/RubyMine-2019.2.4.tar.gz
  :
opt$ cd RubyMine-2019.2.4/bin
bin$ ./rubymine.sh
OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.

Start Failed: Internal error. Please refer to http://jb.gg/ide/critical-startup-errors
  :(略)
Caused by: java.lang.UnsatisfiedLinkError: /opt/RubyMine-2019.2.4/jbr/lib/libawt_xawt.so: libX
tst.so.6: cannot open shared object file: No such file or directory                           

libXtst.so.6 を探す

~$ yum provides libXtst.so.6
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * centos-sclo-rh: ftp.iij.ad.jp
 * extras: ftp.iij.ad.jp
 * updates: ftp.iij.ad.jp
libXtst-1.2.3-1.el7.i686 : X.Org X11 libXtst runtime library
Repo        : base
Matched from:
Provides    : libXtst.so.6
~$ sudo yum install libXtst

これで、RubyMingeが起動するようになりました。

Redmine をRubyMine上で実行する

  • RubyMineでRedmineを展開したディレクトリを開きます。([File]メニュー > [Open]から)
  • rubyコマンド(インタープリター)の場所を設定します。([File]メニュー > [Settings]から)
  • 初回実行時は、Railsを指定します。

日本語環境設定

日本語フォント

IPAフォント
~$ sudo yum install ipa-*-fonts
  :
VLゴシックフォント
~$ sudo yum install vlgothic-*
WindowsのフォントをVcXsrvで使う
~$ sudo ln -s /mnt/c/Windows/Fonts /usr/share/fonts/windows
~$ sudo fc-cache -fv
  :

日本語入力

ibus-kkcを動かそうと試みるが・・・

CentOS 7標準のIbusを入れてみる。

~$ sudo yum install ibus-kkc
  :

79個のパッケージがインストールされます。 ibus-setup を実行すると、ibus-daemonが動いていないので動かすか聞いてきます。動かすを選択したがエラーで動きません。

$ ibus-setup
Gtk-Message: 14:17:48.995: GtkDialog mapped without a transient parent. This is discouraged.

(ibus-setup:2931): IBUS-WARNING **: 14:17:58.140: Unable to connect to ibus: Unexpected lack of content trying to read a line
portal is not running: GDBus.Error:org.freedesktop.DBus.Error.Spawn.ChildExited: Process org.freedesktop.portal.IBus exited with status 1
Gtk-Message: 14:18:03.295: GtkDialog mapped without a transient parent. This is discouraged.
ibus-mozcを動かそうと試みる
~$ sudo yum install ibus-mozc dbus-x11
  :

84パッケージがインストールされます。

.bash_profileに以下を追記

# Input Method
export LANG=ja_JP.UTF-8
export DefaultIMModule=ibus
export XMODIFIERS="@im=ibus"
export GTK_IM_MODULE=ibus
export QT_IM_MODULE=ibus
export IBUS_ENABLE_SYNC_MODE=1
ibus-daemon -d -x

bashを再度開いて、

~$ ibus-setup

ibus-setup を実行すると、ibus-daemonが動いていないので動かすか聞いてきます。動かすを選択したがエラーで動きません。

Windowsマシン上でのLinux環境は混沌と

PC上に直接Linux OSをインストールして起動する(デュアルブート)環境は対象外とします。 Windows OSの動作を前提に設計されたノートPCでは、Linux OS向けドライバが存在しないデバイスが搭載されていることが多く、Windows OSの上でLinux環境を動かすことになります。

Windows 7の頃は、Windows OS上でLinux環境を動かすには、VMwareVirtualBoxなどの仮想マシンソフトウェアを入れて、その上にLinux OSをインストールするか、CygwinなどのLinuxエミュレーションソフトウェアを入れていました。CentOSFedoraUbuntuなど特定のLinuxディストリビューションを動かしたいときは仮想マシンを使い、Linuxコマンド環境を手軽に利用したい(ファイルシステムWindowsと共用したい)ときはCygwinを使う、といった使い分けをしていました。なお、CygwinでもXサーバーのGUI環境を使うことはできました。

Windows 10になると、Proエディションでは標準で仮想マシンHyper-Vが搭載されました。また、WSL(Windows Subsystem for Linux)がWindows 10 1709版からHomeエディションを含めて利用可能となりました。これで、VMware、Virtual Box、Cygwinは不要になるか、と思われましたが、なかなかそうもいきません。

Hyper-Vですが、この上にLinuxを入れてLinuxデスクトップを動かすと動作がもっさりして快適とは程遠いレスポンスです(細い回線でリモートデスクトップ接続して操作している感じ)。さらにHyper-Vを入れると他の仮想マシンソフトウェア(VMwareVirtualBox)が動かなくなるという問題もあります(ハードウェアの仮想化機構 VT-xをHyper-Vが占有するため。今後解消に向かう模様ですが)。

WSLは、利用可能なLinuxディストリビューションが少ない(UbuntuOpenSUSESUSE Enterprise、Kali、有償のWLinux、Fedora Remix、他)ほか、ファイルシステムWindowsと共有することに制約が大きく(ファイルを壊す等)、またファイルI/Oが致命的に遅い問題があります(以前日記に記載)。 WSL(Windows Subsystem for Linux)とHyper-V上のLinuxとの重さの違い - torutkのブログ

来年のWindows 10アップデートで計画されているWSL2は、WSLの問題点を解決すると期待していましたが、なんとHyper-V上でLinuxカーネルを動かす環境となるので、WSLとはコンセプトが随分と変わっています。

というところで、Windows上でLinuxを動かすには選択肢は多くなったものの、これぞという決定打にかけている状況です。

RubyMineでRedmineプラグイン開発をする(Windows環境)

はじめに

これまでは、Windowsマシン上でRedmineプラグイン開発環境を用意するのに、仮想マシンHyper-Vなど)にLinuxを入れてその上でLinuxベースの開発環境を整える方法を取っていました。ただしCUI環境が主です。Hyper-VGUI環境を動かすとかなり動作がもっさりして快適とは程遠いので(キー入力も一呼吸待たされる)、開発環境として使う気になれないほどです。 そこで、WSL(Windows Subsystem for Linux)でLinux環境を使う方法を試してみました。こちらはRailsサーバーの起動が著しく重く、やはり開発環境としては使いたくないほどです。また、Windows環境(ツール)からWSL上のファイルを編集するとパーミッションがおかしくなって崩壊することがあります。

そのため、RubyMineが宝の持ち腐れとなっていました。今回、Windows上にRubyを展開して素のWindowsのみでRedmineプラグイン開発環境を用意してみようとおもいました。

インストール

Rubyのインストール

Windows上のRubyは、次のサイトから入手しました。 https://rubyinstaller.org/downloads/

[with Devkit]と書かれている開発キット込みのRubyをインストールします。2019年10月14日現在、2.6系の最新は

rubyinstaller-devkit-2.6.5-1-x64.exe

です。

インストーラーを実行し、ライセンス受諾し、インストール先を指定、PATH、関連付け、UTF-8デフォルトをチェックして(最初の2つはデフォルトでチェック)、MSYS2 development toolchainをチェックして(デフォルトでチェック)インストールします。

f:id:torutk:20191014152015p:plain

f:id:torutk:20191014152028p:plain

インストール完了するときに、MSYS2セットアップを実行するチェックを付けます。

f:id:torutk:20191014152331p:plain

すると、コマンドプロンプトでMSYS2セットアップが実行されます。

f:id:torutk:20191014152523p:plain

インストールするコンポーネントはデフォルトの[1,2,3]を実行(ENTERキーのみ入力でよい)します。

インストールが終わると、ENTERのみ入力してコマンドプロンプトを閉じます。

SQLite3のインストール

Windows用のSQLite3は次のサイトから入手しました。 https://www.sqlite.org/download.html

まず、Precompiled Binaries for Windows にある次のファイルを入手します。ここにはDLLファイルが含まれます。

sqlite-dll-win64-x64-3300100.zip

このDLLファイルを環境変数PATHの通っているディレクトリにコピーします。ここでは暫定でruby.exeのある場所(C:\tools\Ruby26-x64\bin\)に置きました。

次に、Source Codeにある次のファイルを入手します。開発用のヘッダーファイル、ソースファイルが含まれます。

sqlite-amalgamation-3300100.zip

このうちヘッダーファイル(sqlite3.h、sqlite3ext.h)を、rubyのincludeディレクトリ(ここではC:\tools\Ruby26-x64\include\ruby-2.6.0\)にコピーします。

RubyMineでRedmineのセットアップ

Ruby SDKの設定

RubyMineを起動し、「Welcome to RubyMine」画面の右下[Configure]をクリックし、ドロップダウンリストから[Settings]を選択します。 「Settings for New Projects」画面の左側ペインで[Languages & Frameworks] > [Ruby SDK and Gems]を選択、[+]をクリックし、先ほどインストールしたRubyを追加します。環境変数PATHにruby.exeがあれば候補に登場するのでそれを選択します。

RedmineGithubからクローンしプロジェクト作成

「Welcome to RubyMine」画面の[Check out from Version Control]をクリックし、リポジトリURLとクローン先のローカルディレクトリを指定します。

database.ymlの編集

config\database.ymlファイルを作成し、SQLite3の設定を記述します。

production:
  adapter: sqlite3
  database: db/redmine.sqlite3

development:
  adapter: sqlite3
  database: db/redmine.sqlite3

test:
  adapter: sqlite3
  database: db/redmine_test.sqlite3

bundlerでGemをインストール

Redmineが必要とするGemファイル群をプロジェクト配下にインストールします。

[Tools]メニュー > [Bundler] > [Install]をクリックし、「Bundle Install」ダイアログでオプション

--path vendor/bundler

を追記して実行します。

sqlite3でエラー
Fetching sqlite3 1.4.1
Installing sqlite3 1.4.1 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

current directory:
D:/work/trunk_redmine/vendor/bundler/ruby/2.6.0/gems/sqlite3-1.4.1/ext/sqlite3
d:/tools/Ruby26-x64/bin/ruby.exe -I d:/tools/Ruby26-x64/lib/ruby/2.6.0 -r
./siteconf20191014-17576-1unxgm.rb extconf.rb
checking for sqlite3.h... yes
checking for pthread_create() in -lpthread... yes
checking for -ldl... no
checking for sqlite3_libversion_number() in -lsqlite3... no
sqlite3 is missing. Install SQLite3 from http://www.sqlite.org/ first.
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=d:/tools/Ruby26-x64/bin/$(RUBY_BASE_NAME)
        --with-sqlcipher
        --without-sqlcipher
        --with-sqlite3-config
        --without-sqlite3-config
        --with-pkg-config
        --without-pkg-config
        --with-sqlcipher
        --without-sqlcipher
        --with-sqlite3-dir
        --without-sqlite3-dir
        --with-sqlite3-include
        --without-sqlite3-include=${sqlite3-dir}/include
        --with-sqlite3-lib
        --without-sqlite3-lib=${sqlite3-dir}/lib
        --with-pthreadlib
        --without-pthreadlib
        --with-dllib
        --without-dllib
        --with-sqlcipher
        --without-sqlcipher
        --with-sqlite3lib
        --without-sqlite3lib

bundle installでは、--with-sqlite3-libのオプションを指定しても認識できない模様なので、直接gem installで指定してみました。ただしgem installなのでシステム共通のgemとしてインストールされてしまいます。

>gem install sqlite3 --platform=ruby -- --with-sqlite3-include=c:/tools/ruby26-x64/include/ruby-2.6.0 --with-sqlite3-lib=c:/tools/ruby26-x64/bin
Fetching sqlite3-1.4.1.gem
Temporarily enhancing PATH for MSYS/MINGW...
Installing required msys2 packages: mingw-w64-x86_64-sqlite3
Building native extensions with: '--with-sqlite3-include=c:/tools/ruby26-x64/include/ruby-2.6.0 --with-sqlite3-lib=c:/tools/ruby26-x64/bin'
This could take a while...
Successfully installed sqlite3-1.4.1
Parsing documentation for sqlite3-1.4.1
Installing ri documentation for sqlite3-1.4.1
Done installing documentation for sqlite3 after 1 seconds
1 gem installed

セッション秘密鍵の生成

RubyMineからの実行方法が分からないので、コマンドプロンプトから実行しました。

bundle exec rails generate_secret_token

データベーススキーマの生成

bundle exec rails db:migrate

Railsサーバーの実行

RubyMineの[Run]メニュー > [Run] で [Development: trunk_redmine]を選択すると、デフォルトサーバーが起動します。

めでたく起動しました

No Rails とエラーが出てRailsサーバーが実行できない場合

コマンドプロンプト上でbundle installを実行した後に、Runメニューから実行したらエラーとなってしまいました。その後、RubyMineの[Tools]メニューからbundle installを再実行し、Runメニューから実行したら実行できました。

今月のJava読書会は「The Java Module System(洋書)」を読みます

Java読書会の新しい本

毎月1回川崎市Javaに関する技術書籍の読書会を開催しているJava読書会BOFでは、 先月で「Java 11 and 12 - New Features」が読了し、新しい課題図書のWeb投票を実施ました。 その結果得票数4票(!)で第1位となったのがThe Java Module System です。

本書は、Java SE 9で導入されたJava Platform Module Systemについて丸々1冊をかけて述べています。

書籍の入手

Amazon日本サイトでは、紙の書籍が2909円(送料無料)で販売されています。販売はBook Depositoryというところでイギリスにある会社です。どうやら海を渡って送られてくるようです。 電子書籍はと探してみると出版社のMANNINGでPDF/ePub/Kindle/libeBookの形式データが$39.99で販売されています。日本円では4300円ほどでしょうか。

Manning | The Java Module System

ちょっと悩んで安いAmazonのペーパーバックの方を購入しました。ただちょっと時間がかかります。9月21日に注文して、手元に届いたのが10月1日と10日間かかっていました。

予習

読書会の日までにちょっとずつ読んでおこうと思います。 (まえがき:forewordからちょっと難しいぞ)

ResultSetがStreamになったら嬉しいかも

はじめに

JDBCでデータベース検索のプログラムを書いていると、テーブル毎に似たような、そしてちょっとずつ異なるコードを書くことになります。 異なる部分は、カラム名、データ型、そして取り出したバラバラの値をJavaのデータクラス(ドメインクラスであったり、DTOであったり)に詰める処理です。

典型的には以下のようなプログラミングになるかと思います。

class DbAccessor {
    Connection conn;

    void initialize() {
        conn = DriverManager.getConnection("...");  // データベース接続用のURL・パラメータを指定しコネクションを取得
    }

    List<MyModel> getMyModelListOfDelta(int delta) {
        List<MyModel> models = new ArrayList<>();
        try (PreparedStatement statement = conn.prepareStatement("SELECT alfa,bravo,charlie,kilo FROM Phonetics WHERE delta=?")) {
            statement.setInt(1, delta);
            ResultSet result = statement.executeQuery();
            while (result.next()) {
                int alfa = result.getInt("alfa");
                String bravo = result.getNString("bravo");
                double charlie = result.getDouble("charlie");
                LocalDateTime kilo = result.getObject("kilo", LocalDateTime.class);
                models.add(new MyModel(alfa, bravo, charlie, kilo));
            }
        } catch (SQLException ex) {
            // ...
        }
        return models;
    }
}

JDBCでは、SQL検索文を発行した結果をResultSetで取得します。ResultSetはnext()でカーソルを進めて、getXXメソッドでカラムの値を取り出す仕様となっています。SQL文、PreparedStatementへのプレースホルダーの値設定、ResultSetからのカラムの取り出し、といった部分はテーブル固有の記載となります。

上述のように結構長々と書くことになり、また、同じテーブルでも検索の条件毎にSQL文とプレースホルダー、戻り値が異なるので、別々なメソッドにResultSetのイテレーション処理を記述することになります。

ResultSetをStreamにしたら

探してみると、ResultSetをjava.util.stream.Streamでラップするというブログ等がいくつか見つかりました。 また、jooq というライブラリも存在します。

ResultSetをStreamにするブログ等

  • Implementing a Spliterator for a JDBC ResultSet
    java.util.Spliterator インタフェースを使ってResultSetをStreamにするという内容。実際にはjava.util.SpliteratorsクラスのネストクラスAbstractSpliteratorを継承し、ResultSetからオブジェクトへのマッピングは著者の別ライブラリSqlMapperを使っている。Stream処理内(tryAdvanceメソッド内)で発生したSQLExceptionは、ResultSetのnextで生じたものはRuntimeExceptionにラップして投げており、ResultSetから値を取り出す際に生じたものは結果を返すオブジェクトにエラーを格納して戻り値としている。

  • 書籍 Java Closures and Lambda | Robert Fischer | Apress
    java.util.Spliterator インタフェースを使ってResultSetをStreamにする内容。こちらもAbstractSpliteratorを継承したResultSetSpliteratorクラスを定義し、ReslutSetからオブジェクトへのマッピングは、そのResultSetSpliteratorクラスの抽象メソッドprocessRowをさらに用途に応じてサブクラスで実装するというアプローチ。Stream処理内(tryAdvanceメソッド内)で発生したSQLExceptionはRuntimeExceptionにラップして投げている。

  • Against Boredom: Java 8: JDBC ResultSet to Stream AbstractSpliteratorのtryAdvanceを実装した無名クラスを生成し、ResultSetからは固定のRecordクラスにマッピングしている。Recordクラスは、カラム名をキーに、値をObject型で格納するMapをフィールドに保持する。

  • ResultSet の Stream 化 - なんとなくな Developer のメモ Spliteratorインタフェースを実装したクラスを定義している。ResultSetから任意の型のオブジェクトに変換するFunction(検査例外をスローする独自インタフェース)を受け取り、Stream処理内からSQLExceptionをスローし外側でキャッチできるようにしている。

お試し実装

AbstractSpliteratorを継承したResultSetSpliteratorを定義し、ResultSetの各行を任意の型のオブジェクトに変換するFunctionを外から渡せるようにします。上述ブログ・書籍の最後の記事にほぼ沿った実装です。

ResultSetSpliterator クラス
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class ResultSetSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
    public static final int CHARACTERISTICS = Spliterator.IMMUTABLE | Spliterator.NONNULL;
    private final ResultSet resultSet;
    private TryFunction<ResultSet, T, SQLException> converter;

    public ResultSetSpliterator(
            ResultSet resultSet, TryFunction<ResultSet, T, SQLException> converter
    ) {
        this(resultSet, converter, 0);
    }

    public ResultSetSpliterator(
            ResultSet resultSet, TryFunction<ResultSet, T, SQLException> converter, int additionalCharacteristics
    ) {
        super(Long.MAX_VALUE, CHARACTERISTICS | additionalCharacteristics);
        Objects.requireNonNull(resultSet, "result set");
        this.resultSet = resultSet;
        this.converter = converter;
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        Objects.requireNonNull(action, "action to be performed");
        try {
            if (resultSet.isClosed() || !resultSet.next()) {
                return false;
            }
            action.accept(converter.apply(resultSet));
            return true;
        } catch (SQLException ex) {
            throw new RuntimeException(ex);
        }
    }

    public Stream<T> stream() {
        return StreamSupport.stream(this, false);
    }
}
TryFunction<T, R, E>クラス
@FunctionalInterface
public interface TryFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}

使用例

ResultSetをStreamにしたら、次のように記述できます。

    List<MyModel> getMyModelListOfDelta(int delta) {
        try (PreparedStatement statement = conn.prepareStatement("SELECT alfa,bravo,charlie,kilo FROM Phonetics WHERE delta=?")) {
            statement.setInt(1, delta);
            ResultSet result = statement.executeQuery();
            return new ResultSetSpliterator<>(result, r -> new MyModel(
                    r.getInt("alfa"), r.getNString("bravo"), r.getDouble("charlie"), r.getObject("kilo", LocalDateTime.class)
            ).stream().collect(toList());
        } catch (SQLException ex) {
            return List.of();
        }
    }

ここでは、ResultSetからオブジェクトへの変換をラムダ式でインライン記述しています。 複数個所で同じラムダ式を記述する必要が生じた場合は、ラムダ式をフィールドに定義して再利用する等ができます。

class Xxx {
    private TryFunction<ResultSet, MyModel, SQLException> resultSet2MyModel = r -> new MyModel(
            r.getInt("alfa"), r.getNString("bravo"), r.getDouble("charlie"), r.getObject("kilo", LocalDateTime.class)
    );

    List<MyModel> getAllMyModel() {
        try (var stmt = conn.prepareStatement("SELECT alfa,bravo,charlie, kilo FROM Phonetics")) {
            var resultSet = stmt.executeQuery();
            return new ResultSetSpliterator<>(resultSet, resultSet2MyModel).stream().collect(toList());
        } catch (SQLException ex) {
            return List.of();
        }
    }
}