この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の20日目の記事です。
21日目 Rumy-Templateでお手軽プロジェクトテンプレート作成
例えばRustのパッケージ管理・ビルドツールであるCargoには、Rustプロジェクトを立ち上げるための一連のコマンド群が用意されています。
cargo init hello # hello プロジェクトを作成 cargo build # プロジェクトをビルド
このように、特定のプログラミング言語でプロジェクトを開発する際、そのプロジェクトのためのテンプレートディレクトリ構成を自動的に生成してくれると便利です。Rumy-Makeでもやってみましょう。
C++のプロジェクトテンプレートを作成するcpp_init_project
コマンド
例えば、C++のプロジェクトを作成する際にとりあえず必要なのは、ソースコードを格納するsrc
、インクルードファイルを管理するinc
そしてビルドスクリプトです。Cargoのように、C++のプロジェクトを作成する際の必要最低限のディレクトリ構成とRumyプロジェクトビルドファイルを生成するためのcpp_init_project
コマンドを作ります。
rumy-cpp.rb
に、cpp_init_project
関数用意します。この関数では、愚直にプロジェクト用のディレクトリの作成、Rumyルールファイルの作成、.gitignore
の作成などを行います。サンプルプログラムとしてHello World
を表示するだけのプログラムも入れておきます。
src/rumy-cpp.rb
def cpp_init_project(proj_name) Dir.mkdir(proj_name) Dir.mkdir(proj_name + "/src") Dir.mkdir(proj_name + "/include") fp = File.open(proj_name + "/src/main.cpp", "w") {|f| f.puts("#include <iostream>") f.puts("int main ()") f.puts("{") f.puts(" std::cout << \"Hello World\\n\";") f.puts("}") } fp = File.open(proj_name + "/.gitignore", "w") {|f| f.puts("*\.o # object file") f.puts("#{proj_name} # executable") } fp = File.open(proj_name + "/build.rb", "w") {|f| f.puts("#!/usr/bin/env ruby\n") f.puts("load \"rumy-cpp.rb\"") f.puts("cpp_lists = [\"./src/main.cpp\"]") f.puts("compile_options = [\"-I./include\"]") f.puts("make_execute(\"" + proj_name + "\", cpp_lists, [], compile_options, [], [], [])") f.puts("make_target :all do") f.puts(" global") f.puts(" depends [\"" + proj_name + "\"]") f.puts("end") } end
CLI管理スクリプトであるrumy
にも、cpp_init
コマンドを定義しておきます。
src/rumy
class RumyCLI < Thor default_command :build ... # Project Initializer for C++ Project desc "rumy cpp_init proj_name", "Generate C++ Project Template" def cpp_init(proj_name) cpp_init_project (proj_name) end
これで完成です。さっそく、rumy cpp_init hello_c
をコマンドラインから入力します。
$ rumy cpp_init hello_c $ tree hello_c hello_c ├── build.rb ├── include └── src └── main.cpp 2 directories, 2 files
このように、テンプレートディレクトリが作られました。main.cpp
も用意されているので、もうRumyで一発でビルドできるようになっています。
$ cd hello_c $ rumy ... [DEBUG] : Depends Target "hello_c" depends result = [true] [DEBUG] : Depends Target "all" depends result = [true] $ ./hello_c Hello World
Rust用のテンプレートを作る
Cargoが発想の原点になっているので、ついでにRust用のテンプレートも作っておきましょう。といっても、ベースはC++のものと変わりません。
rumy-rust.rb
#!/usr/bin/ruby require "rumy-main.rb" def make_execute_rust(exec_name, cpp_file_list, lib_file_list, compile_options, link_options, link_libs, additional_depends = []) additional_depends.each {|dep| do_target dep } obj_file_list = cpp_file_list.map {|cpp| cpp + ".o"} make_target exec_name do global depends obj_file_list + lib_file_list cc_cmd = "rustc" executes ["#{cc_cmd} -o #{exec_name} #{link_options.join(' ').to_s} \ #{cpp_file_list.join(' ').to_s} \ #{lib_file_list.join(' ').to_s} \ #{link_libs.join(' ').to_s}"] end end def rust_init_project(proj_name) Dir.mkdir(proj_name) Dir.mkdir(proj_name + "/src") fp = File.open(proj_name + "/src/main.rs", "w") {|f| f.puts("fn main() {") f.puts(" println!(\"Hello, world!\");") f.puts("}") } fp = File.open(proj_name + "/build.rb", "w") {|f| f.puts("#!/usr/bin/env ruby\n") f.puts("load \"rumy-rust.rb\"") f.puts("cpp_lists = [\"./src/main.rs\"]") f.puts("compile_options = []") f.puts("make_execute_rust(\"" + proj_name + "\", cpp_lists, [], compile_options, [], [], [])") f.puts("make_target :all do") f.puts(" global") f.puts(" depends [\"" + proj_name + "\"]") f.puts("end") } end
こちらも結局はinit_rust
でプロジェクトテンプレートを作るだけです。
$ rumy rust_init hello_lust $ rumy rust_init hello_rust $ tree hello_rust hello_rust ├── build.rb └── src └── main.rs 1 directory, 2 files
こちらも一発でビルドと実行が可能です。
$ cd hello_rust
$ rumy
$ ./hello_rust
Hello, world!
Rust用、C++用のプロジェクトテンプレートを作成しました。これで、少しだけ最近のビルドツールっぽくなったように思います。