Slide 1

Slide 1 text

A Ruby version manager written in Rust, which is 7 seconds faster than rbenv Takayuki Maeda (@TaKO8Ki) Rust.Tokyo 2021

Slide 2

Slide 2 text

About myself

Slide 3

Slide 3 text

About myself Takayuki Maeda GitHub: @TaKO8Ki Twitter: @TaKOBKi Software Engineer @ Money Forward, Inc. Senior at Kobe University majoring Civil Engineering

Slide 4

Slide 4 text

Sometimes contribute to rust-clippy

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

What are rbenv and frum?

Slide 8

Slide 8 text

$ 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.

Slide 9

Slide 9 text

What is ruby-build? ruby-build is a command-line utility that makes it easy to install virtually any version of Ruby, from source.

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Motivations

Slide 12

Slide 12 text

There are many products written in Ruby at Money Forward

Slide 13

Slide 13 text

You need to wait for 2 to 3 minutes every time you install new versions of Ruby

Slide 14

Slide 14 text

This is not cool

Slide 15

Slide 15 text

Of course. There is a limit to how much faster I can make installing Ruby

Slide 16

Slide 16 text

I feel like I can make it about 10 seconds faster

Slide 17

Slide 17 text

Benchmark

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Benchmark frum init vs rbenv init

Slide 20

Slide 20 text

What did I improve?

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

rbenv/libexec

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

ruby-build/bin ruby-build has 1468 lines

Slide 26

Slide 26 text

So do I need to rewrite all of them in Rust?

Slide 27

Slide 27 text

No. It's not necessary.

Slide 28

Slide 28 text

$ ./configure $ make $ sudo make install What to do basically is Download source and unpack a tarball, then just do this:

Slide 29

Slide 29 text

./configure ⬅︎ make ⬅︎

Slide 30

Slide 30 text

make ⬅︎ make install ⬅︎

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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)

Slide 33

Slide 33 text

shims in ~/.rbenv/shims

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

store the command name into $program ⬅︎

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

store the command name into $program ⬅︎ ⬅︎ setup $RBENV_DIR ⬅︎ setup $RBENV_ROOT rbenv exec

Slide 38

Slide 38 text

In case you use Ruby 2.6.5 rbenv exec ruby -v turns into PATH=”~/.rbenv/versions/2.6.5/bin:$PATH” ruby -v

Slide 39

Slide 39 text

When does rbenv create shims? rbenv rehash creates shims. rbenv init runs it internally.

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

1. rbenv rehash checks if the directory ~/.rbenv/shims exists

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

5. Eventually, rehash deletes the prototype file

Slide 47

Slide 47 text

rbenv runs these steps every time you execute rbenv init

Slide 48

Slide 48 text

How does frum solve this problem? frum uses symlinks to switch Ruby versions. src/commands/init.rs

Slide 49

Slide 49 text

⬅︎ generate temp dir src/commands/init.rs

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

In short, frum init simply creates a symlink

Slide 52

Slide 52 text

That results in

Slide 53

Slide 53 text

Tips

Slide 54

Slide 54 text

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 ⬅︎ ⬅︎

Slide 55

Slide 55 text

Tips for good E2E testing e2e_test macro for E2E testing

Slide 56

Slide 56 text

Tips for good E2E testing ⬅︎ define a test function ⬅︎ setup

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Tips for good E2E testing If stdout or stderr is not as expected, the error is displayed like the following:

Slide 59

Slide 59 text

Tips to create CLI with multiple subcommands

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

⬅︎ function you need to implement

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

--ruby-build-mirror ⬅︎

Slide 64

Slide 64 text

⬅︎ Custom Error Type

Slide 65

Slide 65 text

⬅︎ Custom Error Type ⬅︎install command arguments

Slide 66

Slide 66 text

⬅︎ Custom Error Type ⬅︎install command arguments ⬅︎ return custom error

Slide 67

Slide 67 text

Tips to implement flexible Command-line completion using clap

Slide 68

Slide 68 text

Tips to implement flexible Command-line completion using clap You can implement completion using clap::App::gen_completions However it is not useful enough

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

In frum ⬅︎ prepare a empty vector

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

In frum ⬅︎ prepare a empty vector store completions into buffer ⬅︎ ⬅︎ iterating over lines in completions

Slide 74

Slide 74 text

In frum

Slide 75

Slide 75 text

In frum

Slide 76

Slide 76 text

Conclusions

Slide 77

Slide 77 text

Conclusions frum install runs a little bit faster than rbenv install frum init runs about 6x faster than rbenv init

Slide 78

Slide 78 text

No content