この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の22日目の記事です。
22日目 gitコマンドに埋め込む1
最近のビルドツールには、gitによるバージョン管理と融合し、リポジトリの管理の補助をしてくれる機能が当たりします。Rumy-Makeにも、gitと融合して開発を支援してくれる機能が欲しいので、これらを実装してみました。
まず、最初に考えたのは、リグレッションテストなどを実行する際、バージョン管理中のファイルの中に変更途中のものが混じっており、すべてのファイルがコミット状態になっていないままリグレッションテストを実行して結果が誤ってしまうケースがあります。このことを防ぐために、設定によっては、依存関係に含まれるファイルに編集中(gitのmodified状態)に入っているものがあればそこでビルドを止める、という機能が欲しいです。
その機能はリグレッション中や、特別にcloneしたリポジトリにのみ適用されてほしいです(つまり開発用のリポジトリには適用されない)。そこで、rumy専用のgit cloneコマンドを用意して、このgit cloneコマンドを使用すると自動的にリポジトリにロックがかかり、編集状態でビルドを実行すると自動的に停止するように改造してみましょう。
RubyでGitを扱うためのパッケージruby-git
Ruby内でgitのコマンドやリポジトリを扱うために、ruby-gitパッケージを使用しました。
https://github.com/ruby-git/ruby-git/tree/master/lib/git
このパッケージを使用すれば、gitのコマンド操作をRuby上で簡単に扱うことができます。
まずは、Rumy上でgit cloneするためのrumy clone-git
コマンドを作ります。
rumy
# Project Initializer for Rust Project desc "clone-git repo_location", "Clone Git Repository" def clone_git(repo_location) rumy_clone_git(repo_location, repo_location.split('/').last.sub('.git', '')) end # Project Initializer for Rust Project desc "clone-git repo_location dir", "Clone Git Repository" def clone_git(repo_location, dir) rumy_clone_git(repo_location, dir) end
ダウンロード先のディレクトリを指定するバージョンとそうでないバージョンを作りました。
clone-git repo_location clone-git repo_location dir
どちらでも使用できます。実際の動作は、rumy_clone_git()
に記述しています。
src/rumy-git.rb
def rumy_clone_git(url, dir) # g = Git.open(toplevel_path) Git.clone(url, dir, :log => Logger.new(STDOUT)) lock_file_loc = dir + "/" + ".rumy-lock" if not File.exist?(lock_file_loc) then loc_fp = File.open(lock_file_loc, "w") loc_fp.write("lock = true") loc_fp.close end end
よく見ると、clone-git
をした場合にそのリポジトリの直下に.rumy-lock
というファイルを配置するようにしています。.rumy-lock
内にはlock = true
と書いた設定ファイルを置いてあり、ビルド中にこのファイルを見つけると、modified
状態になっているファイルを見つけた場合にビルドを中止するような処理を適用する仕組みにしました。
Modifiedなファイルを見つけた場合にビルドを中止する機能の実装
では、いよいよ本命のmodified
な状態の依存ファイルを見つけるとそこでビルドを中止する機能を実装してみます。条件としては下記のようにします。
- リポジトリである(.gitファイルが見つからなければこの機能は適用しない)
- リポジトリの先頭に
.rumy-lock
ファイルが配置してあり、lock = true
という行が存在している(この辺の実装は適当) - 依存関係を探索していく中で、Git管理ファイルの中にmodifiedなものが存在すればビルドを中止する。
まずは、当該ターゲットのディレクトリがGitの管理リポジトリであることを確認します。簡単に、いかのようにしてgitのコマンドで確認を行います。
src/rumy-git.rb
toplevel_path = Pathname.new(`git rev-parse --show-toplevel`.sub("\n", "")) if toplevel_path.to_s == "" then return false end
Gitのrev-parse
コマンドを使用してGitのトップディレクトリを探索します。これが見つからなければGitリポジトリではないので、この関数からFalseを返して抜けます。
次に、.rumy-lock
ファイルとlock = true
の行を確認します。
git_root_relative = toplevel_path.relative_path_from(current_path).to_s lock_file_loc = git_root_relative + "/" + ".rumy-lock" if not File.exist?(lock_file_loc) then return false else File.open(lock_file_loc, "r"){|fp| fp.readlines.each{|line| if line.include?("lock = true") return true end } } return false end
まずはGitのリポジトリの先頭から、.rumy-lock
のファイルの存在を確認し、存在していなければFalseを返して終了です。存在している場合、行を探索していき、lock = true
の行を見つければTrueを返すことにしました。.rumy-lock
の探索の実装はかなり適当ですが、どうせ人が手を加えるファイルではないので、適当な実装でも良しとしました。
最後に、ビルドの依存関係探索中にmodifiedのファイルを確認するコードです。
src/rumy-target.rb
if $target_list.key?(name) then target = $target_list[name] # 依存関係を探索時に、最初に.rumy-lockによる確認を行うかを調べておく need_check_modify = rumy_is_git_modify_lock() ... # 依存ファイルを探索する場合 elsif not $target_list.key?(dep) and not Symbol.all_symbols.include?(dep) then # modifyのチェックを行う必要があり、当該ファイルがmodifiedであれば if need_check_modify and git_file_modified?(dep) then puts "[ERROR] : Rumy build is stop due to File #{dep} is modified. Exit." exit end
まず、上記のrumy_is_git_modify_lock()
を呼んで、Modified状態ファイルの確認を行う必要があるか見ておきます。次に、依存関係ファイルを探索している間、git_file_modified?(dep)
によりファイルのgitによる管理状態を確認し、modifiedであれば強制的に終了します。
def git_file_modified?(filename) current_path = Pathname.new(`pwd`.sub("\n", "")) toplevel_path = Pathname.new(`git rev-parse --show-toplevel`.sub("\n", "")) git_root_relative = toplevel_path.relative_path_from(current_path).to_s g = Git.open(toplevel_path) file_dir_from_root = current_path.relative_path_from(toplevel_path).to_s file_location = file_dir_from_root + "/" + filename if not File.exist?(filename) then puts "This file " + filename + " is not exist." return end return g.status.changed?(file_location) end
無理やりな実装ですが、上記のようにしました。ruby-gitを使って、当該ファイルの状態を確認します。changed?
であれば、modified状態なのでビルドを中止します。
このように、.rumy-lock
ファイルを先頭に配置することによりビルドツールの挙動を変える機能を実装しました。これは、手元でリグレッションなどを行う場合に使えそうです。
しかしこの機能にはまだ不十分な点があります。まず、サブリポジトリなどの複数にわたるリポジトリの階層がある場合に、一番トップのリポジトリの.rumy-lock
を見なければならないはずですが、それができていません。複数階層のリポジトリを正しく扱う場合には、さらなる改造が必要そうです。