Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Ruby version manager written in Rust, which is 7 seconds faster than rbenv

8e686c2dcfd4cc143fbc21baf893626b?s=47 TaKO8Ki
September 18, 2021

A Ruby version manager written in Rust, which is 7 seconds faster than rbenv

Link to a slide on Canva

frum: https://github.com/TaKO8Ki/frum
gobang: https://github.com/TaKO8Ki/gobang
Article about frum: https://zenn.dev/tako8ki/articles/2021-09-ruby-version-manager-written-in-rust

I built a Ruby version manager with Rust, which is 7 seconds faster than rbenv. In detail, it takes about 7 seconds faster to install a particular version and set it with frum than rbenv.

Ruby has a lot of code that depends on the runtime version. Therefore, it is important to manage Ruby versions. However, it takes 2 to 3 minutes to run rbenv install, depending on the environment, and waiting for this to happen every time you install a new version is inefficient. The main bottleneck is a command-line utility called ruby-build, which makes it easy to install virtually any version of Ruby. In order to solve this problem, I created a Pure Rust Ruby version manager called frum.

In this talk, I will share my efforts to build a simple and easy to use Ruby version manager with Rust, and what I learned in the process of making frum like the following things.

- Tips on good E2E testing a CLI that manipulates files using declarative macros
- Tips for writing a CLI with multiple subcommands in Rust
- How to use the command line parser clap to implement flexible command-line completion

Q&A

frumはプロダクションでも利用できますか?例えば、今後どのくらいの期間保守が続けられる予定でしょうか?(追記)例えば、moneyforward社で利用予定はありますか?

> frumはプロダクションでも利用できますか?例えば、今後どのくらいの期間保守が続けられる予定でしょうか?

プロダクションでの利用はあまり想定していません。例えば、tempdirを利用しているので、ユーザー間でそれが競合してしまうことなどが考えられます。基本的に僕が仕事でRubyを書いている間はメンテする予定なので、少なくとも2、3年は問題ないと思います。基本的にはユーザーがいる限りメンテし続けようとは考えています。

> 例えば、moneyforward社で利用予定はありますか?

基本的に本番環境でバージョンマネージャーを用いたバージョンの切り替えなどは行ってないはずなので、本番環境での利用はしないと思われます。ローカルで使ってくださる方は、もしかしたらいるかもしれないですね。そこら辺は他のOSSと同じで個々の判断で使っていただく形になると思います。

8e686c2dcfd4cc143fbc21baf893626b?s=128

TaKO8Ki

September 18, 2021
Tweet

Transcript

  1. A Ruby version manager written in Rust, which is 7

    seconds faster than rbenv Takayuki Maeda (@TaKO8Ki) Rust.Tokyo 2021
  2. About myself

  3. About myself Takayuki Maeda GitHub: @TaKO8Ki Twitter: @TaKOBKi Software Engineer

    @ Money Forward, Inc. Senior at Kobe University majoring Civil Engineering
  4. Sometimes contribute to rust-clippy

  5. I've been building TUI SQL client with Rust lately. https://github.com/TaKO8Ki/gobang

  6. None
  7. What are rbenv and frum?

  8. $ rbenv install 2.6.5 # Install a Ruby version using

    ruby-build $ rbenv local 2.6.5 # Set or show the local application-specific Ruby version $ rbenv global 2.6.5 # Set or show the global Ruby version What is rbenv? rbenv is a tool that lets you install and run multiple versions of Ruby side-by-side.
  9. What is ruby-build? ruby-build is a command-line utility that makes

    it easy to install virtually any version of Ruby, from source.
  10. What is frum? A little bit fast and modern Ruby

    version manager written in Rust Pure Rust implementation not using ruby-build Works with .ruby-version files Faster than rbenv github.com/TaKO8Ki/frum
  11. Motivations

  12. There are many products written in Ruby at Money Forward

  13. You need to wait for 2 to 3 minutes every

    time you install new versions of Ruby
  14. This is not cool

  15. Of course. There is a limit to how much faster

    I can make installing Ruby
  16. I feel like I can make it about 10 seconds

    faster
  17. Benchmark

  18. Benchmark eval "$(rbenv init -)" → rbenv install → rbenv

    local vs eval "$(frum init)" → frum install → frum local
  19. Benchmark frum init vs rbenv init

  20. What did I improve?

  21. What did I improve? rbenv is written in bash script

    ruby-build is also written in bash script rbenv uses shims to switch Ruby versions
  22. ・rbenv is written in bash script ・ruby-build is also written

    in bash script ・rbenv uses shims to switch Ruby versions
  23. rbenv/libexec

  24. ・rbenv is written in bash script ・ruby-build is also written

    in bash script ・rbenv uses shims to switch Ruby versions
  25. ruby-build/bin ruby-build has 1468 lines

  26. So do I need to rewrite all of them in

    Rust?
  27. No. It's not necessary.

  28. $ ./configure $ make $ sudo make install What to

    do basically is Download source and unpack a tarball, then just do this:
  29. ./configure ⬅︎ make ⬅︎

  30. make ⬅︎ make install ⬅︎

  31. ・rbenv is written in bash script ・ruby-build is also written

    in bash script ・rbenv uses shims to switch Ruby versions
  32. What is shim? > In computer programming, a shim is

    a small library that transparently intercepts API calls and changes the arguments passed, handles the operation itself, or redirects the operation elsewhere. https://en.wikipedia.org/wiki/Shim_(computing)
  33. shims in ~/.rbenv/shims

  34. None
  35. store the command name into $program ⬅︎

  36. store the command name into $program ⬅︎ ⬅︎ setup $RBENV_DIR

  37. store the command name into $program ⬅︎ ⬅︎ setup $RBENV_DIR

    ⬅︎ setup $RBENV_ROOT rbenv exec
  38. In case you use Ruby 2.6.5 rbenv exec ruby -v

    turns into PATH=”~/.rbenv/versions/2.6.5/bin:$PATH” ruby -v
  39. When does rbenv create shims? rbenv rehash creates shims. rbenv

    init runs it internally.
  40. When does rbenv create shims? So what does rbenv rehash

    do? rbenv rehash creates shims. rbenv init runs it internally.
  41. 1. rbenv rehash checks if the directory ~/.rbenv/shims exists

  42. 2. Rehash checks if ~/.rbenv/shims/.rbenv-shim exists

  43. 3. Rehash creates ~/.rbenv/shims/.rbenv-shim and get a lock on it

  44. None
  45. 4. Rehash iterates through all files in ~/.rbenv/versions/${version}/bin/ of all

    Ruby versions installed. Then, it copies prototype file into the shim scripts
  46. 5. Eventually, rehash deletes the prototype file

  47. rbenv runs these steps every time you execute rbenv init

  48. How does frum solve this problem? frum uses symlinks to

    switch Ruby versions. src/commands/init.rs
  49. ⬅︎ generate temp dir src/commands/init.rs

  50. ⬅︎ generate temp dir src/commands/init.rs ⬅︎ create a symlink to

    a Ruby directory
  51. In short, frum init simply creates a symlink

  52. That results in

  53. Tips

  54. Tips for good E2E testing Since frum has some subcommands,

    It is important to check if your state is as expected after running multiple commands. frum init frum install frum local ⬅︎ ⬅︎
  55. Tips for good E2E testing e2e_test macro for E2E testing

  56. Tips for good E2E testing ⬅︎ define a test function

    ⬅︎ setup
  57. Tips for good E2E testing utils::setup does the following things:

    prepares a temp directory for each test functions sets some environment variables frum needs
  58. Tips for good E2E testing If stdout or stderr is

    not as expected, the error is displayed like the following:
  59. Tips to create CLI with multiple subcommands

  60. None
  61. ⬅︎ function you need to implement

  62. None
  63. --ruby-build-mirror ⬅︎

  64. ⬅︎ Custom Error Type

  65. ⬅︎ Custom Error Type ⬅︎install command arguments

  66. ⬅︎ Custom Error Type ⬅︎install command arguments ⬅︎ return custom

    error
  67. Tips to implement flexible Command-line completion using clap

  68. Tips to implement flexible Command-line completion using clap You can

    implement completion using clap::App::gen_completions However it is not useful enough
  69. That is because clap doesn't support adding additional code to

    complete values.
  70. That is because clap doesn't support adding additional code to

    complete values.
  71. In frum ⬅︎ prepare a empty vector

  72. In frum ⬅︎ prepare a empty vector store completions into

    buffer ⬅︎
  73. In frum ⬅︎ prepare a empty vector store completions into

    buffer ⬅︎ ⬅︎ iterating over lines in completions
  74. In frum

  75. In frum

  76. Conclusions

  77. Conclusions frum install runs a little bit faster than rbenv

    install frum init runs about 6x faster than rbenv init
  78. None