ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

mruby 本を支えた Continuous Delivery

Rubyアドベントカレンダー、7日目の記事です。前日はjerrywdleeさんでした。

qiita.com

今年の半分ぐらいの時間をかけて、「Webで使える mrubyシステムプログラミング入門」という本をリリースしました。

www.c-r.com

多くの知人に手に取っていただけているようで何よりですが、今回は、この本の執筆を支える周辺技術の話について、Rubyアドベントカレンダーの場をお借りして公開しようと思います。

Re:VIEW (Re:VIEW Starter) によるPDF生成の自動化

今回の本は、Re:VIEW というフォーマットを用いて執筆しています。

reviewml.org

ただし、カスタマイズ性などの観点で、Re:VIEWの一種のディストリビューションである「Re:VIEW Starter」を用いて仮PDFを作成し、定期確認することにしました。

kauplan.org

上記のRe:VIEW Starterのプロジェクトテンプレート機能(Webアプリ)のおかげで、以下のコマンド一つでPDFが作成できるようになりました。

$ ./bin/rake pdf

ただし、Re:VIEW Starterはその設計思想からRe:VIEW 2.5系を利用しているので、そのバージョンを指定したGemfileを用意します。

また、PDF生成に必要なTeXパッケージが入ったイメージを作成し、その中でビルドする必要があります。以下はそのDockerfileです。

FROM ruby:2.7.1-slim-buster
LABEL maintainer="udzura@udzura.jp"
# Original from https://hub.docker.com/r/kauplan/review2.5/dockerfile
ENV REVIEW_VERSION 2.5.0
ENV REVIEW_PEG_VERSION 0.2.2
# ENV NODEJS_VERSION 14

ENV LANG en_US.UTF-8

RUN gem install bundler -v=2.0.2 --no-document

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      locales git-core curl ca-certificates && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
RUN echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
RUN locale-gen en_US.UTF-8 && update-locale en_US.UTF-8

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      texlive-lang-japanese texlive-fonts-recommended texlive-latex-extra lmodern fonts-lmodern tex-gyre fonts-texgyre texlive-pictures \
      ghostscript gsfonts zip ruby-zip ruby-nokogiri mecab ruby-mecab mecab-ipadic-utf8 poppler-data cm-super \
      graphviz gnuplot python-blockdiag python-aafigure \
      texlive-plain-generic texlive-fonts-extra \
      fonts-noto-cjk fonts-noto-cjk-extra && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# noto mapfile: https://github.com/kauplan/docker-review/tree/kauplan/review2.5/noto
COPY noto/ /usr/share/texlive/texmf-dist/fonts/map/dvipdfmx/ptex-fontmaps/noto/
RUN texhash && kanji-config-updmap-sys noto

PDFを定期作成する技自体は @sugamasao(id:seiunsky) さんがパーフェクトシリーズで実施していた記憶がありますが、CIを行うメリットとして:

  • なるべく現実的な書籍に近い形で原稿の確認ができる。
    • Re:VIEW のフォーマットそのままでは見落としがちなTypoなどに気づきやすい気がする
  • 原稿のフォーマットが壊れていないことを定期確認できる。
    • Re:VIEW からmarkdown形式などを生成して編集さんに提出することになるが、その際に変なミスが出てワタワタすることがなくなる

などが挙げられます。

Re:VIEW/Re:VIEW Starterの詳細なセットアップ方法は公式のドキュメントなどに譲りますが、上記のDockerfileの記述も参考にできるかもしれません。

PDF の Continuous Delivery

また、原稿については定期的にタグを打ち、リリースを作成してPDFを添付するようにしました。PDFまではできているのでGitHub Actionの組み合わせで難なく実現できます。

その際に利用したGitHub Actionのワークフローを公開します。イメージは先述のDockerfileから作成しています。

name: Upload Release Asset

on:
  push:
    tags:
    - '*'

jobs:
  release:
    name: Upload Release Asset
    runs-on: ubuntu-latest
    container: udzura/review-pdf-ja:review2.5-ruby2.7
    steps:
      - name: Checkout code
        uses: actions/checkout@v1
      - name: Build project
        id: create_build
        run: |
          bundle install --jobs 4 --retry 3
          ./bin/rake pdf
          echo "::set-output name=current_tag::$(echo ${{ github.ref }} | awk -F'/' '{print $3}')"
          zip --junk-paths mruby-handson-book.$(echo ${{ github.ref }} | awk -F'/' '{print $3}').zip mruby-handson-book.pdf
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          draft: false
          prerelease: false
      - name: Upload Release Asset
        id: upload_release_asset 
        uses: actions/upload-release-asset@v1.0.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./mruby-handson-book.${{ steps.create_build.outputs.current_tag }}.zip
          asset_name: mruby-handson-book.${{ steps.create_build.outputs.current_tag }}.zip
          asset_content_type: application/zip

少し困っていた点は ${{ github.ref }} がそのままタグ名になるのはどうも actions/create-release の中だけのようで、今回は別途 outputs の仕組みを用いてタグ名部分のみを抜き出し後続のstepに渡しています。参考:

swfz.hatenablog.com

タグを打って定期リリースするメリットは、レビュワーの方に依頼してから大きく享受できたように思います。レビュワーの方々には、 このタグに紐づくリリースのPDFで レビューしていただくよう指示することができるので、修正の反映やそれらの前後関係などを共有しやすくなったように思います。

もちろん、テキストではなくPDFで読むことで間違いに気づきやすいというメリットについては、レビュワーにとっても大きなものになっていたと思います。

その他のTips

pdfinfo コマンドの結果や、 wc -m の結果などを定期表示するのは、分量の見積もりやモチベーションの助けになるので良いかと思います。章ごとのマルチバイト文字数を把握できたのは、章によって極端に文字数が多い・少ないという現象を早期に検知することにつながったので良かったです(結局ある程度偏ってしまいましたが)。

最終的な手元の原稿の文字数を公開しておきます。4 〜 7、 9 章は意図して分量をほぼ同じようにしたつもりです。8章はこの本のクライマックスで(9章は裏面)、長くなるのは仕方ないので、諦めました。

$ rake count
find contents -depth 1 -name '*.re' | sort | xargs wc -m
     837 contents/00-mruby-handson-book.re
   17106 contents/01-what-is-syspro.re
   16604 contents/02-hello-mruby.re
   28998 contents/03-creating-mgem.re
   39834 contents/04-accessing-procfs.re
   41093 contents/05-writing-c-in-mruby.re
   43192 contents/06-implementation-of-file-stat.re
   40665 contents/07-introduce-mod-mruby.re
   68884 contents/08-write-mruby-module.re
   44780 contents/09-secure-coding.re
    5195 contents/10-afterwords.re
   78864 contents/a1-mruby-debugging-tools.re
    3402 contents/a2-mruby-3.re
  429454 total

ちなみに、Re:VIEWコメントの一覧表示(TODOの把握のため)や、リスト内の1行が長すぎる箇所の検知などもRake taskを作って実施できるようにしていました。めちゃくちゃ雑なコードですが... 今回あまりにもRubyのコードがない記事なので、タスクの一部を公開してみます。

これが、リスト内のプログラムコードなどが長過ぎる箇所を検知、表示するRakeタスクです。状態付きスキャナみたいなのを自力で書いてて涙ぐましい。

LINE_MAX_SIZE = 90

desc "長いリスト内のコードを表示"
task :longline do
  chap = ENV['CHAPTER']
  res = Dir.glob("contents/*.re").sort
  if chap
    res.select! {|re| re.include?(chap) }
  end
  keys = []
  warned_files = {}
  warned = {}
  list_in = nil
  res.each do |re|
    lines = File.read(re).lines.map(&:chomp)
    lines.each_with_index do |l, i|
      if list_in
        if l =~ %r<^//\}>
          list_in = nil
        end
        if l.size > LINE_MAX_SIZE
          unless keys.include?(list_in)
            keys << list_in
            warned_files[list_in] = re
            warned[list_in] ||= []
          end
          warned[list_in] << [re, i+1, l]
        end
      else
        if l =~ %r<^//(list|cmd)(?:\[([-_\w]+)\])?>
          if $2
            list_in = $2
          else
            nr = re.scan(/\d\d/).flatten.first
            list_in = "__cmd__#{nr}"
          end
        end
      end
    end
  end

  keys.each do |name|
    fname = warned_files[name]
    puts "#{fname}@\e[31m#{name}\e[0m:"
    warned[name].each do |(re, i, l)|
      puts "%-41s %s" % [re.sub("contents/", "") + (":%04d:"%i), l[0, LINE_MAX_SIZE] + "..."]
    end
    puts
  end
  puts "-" * 32
  puts "#{keys.size} lists"
end

最後に

Rubyを用いてmrubyの本を書くための周辺Tipsを公開しました。

原稿自体もそうですが、必要な確認や周辺作業は 自動化 しないと厳しかったというのが実感です。みなさんも、いろいろな雑事をRubyで自動化しよう!(なんか綺麗にまとまったぞ?)


明日のご担当は @mtsmfm さんです!

RbBCC + RubyGems で手軽に eBPF ツールを配布できるようにした

BCCRubyバインディングを開発しています。

udzura.hatenablog.jp

で、RbBCCを omnibus を使ってOSパッケージに固めて、配布することにしました(TODO: 今度ちゃんとGitHub Packagesにする)。

github.com

このパッケージはlibbccとそれが依存するライブラリを同梱した形で配布しているので、インストールしたらいきなり動かせると思います。 Ubuntu Focalであれば...。 Fedora 向けrpmはそのうち追加します。

なお、もしかしたら libgcc-s1 を追加でインストールしないと動かないかも。

続きを読む

WSA研究会 第7回 でCRIUとMiehistöの発表をしました #WSA研

wsa.connpass.com

ふりかえります。

予稿

www.notion.so

スライド

speakerdeck.com

デモ動画

当日時間がなく披露できなかった、今動いてるところまでのデモです

youtu.be

続きを読む

自分で書いてきた「やわらかシリーズ」のまとめ

某ョルドさんのSlackで紹介してて思い出したんだけれど、2013年ごろから やわらか.* で始まるチュートリアル「やわらかシリーズ」を作っていて、結構な数存在している模様。

このやわらかシリーズは、「初学者向けの見た目・表現をしながら、作者の若干偏った『これが本質だ!!!』と言う見解を練り込んでそっち側に誘導する」と言うコンセプトで作られ続けたもので、今回意外な数溜まっていて自分でも忘れていたのでまとめておこうと思った。

続きを読む