FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

DSLでビルドツールを自作する (21日目 Rumy-Templateでお手軽プロジェクトテンプレート作成)

この記事は「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++用のプロジェクトテンプレートを作成しました。これで、少しだけ最近のビルドツールっぽくなったように思います。