Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at 20.09 365 lines 12 kB view raw view rendered
1--- 2title: Ruby 3author: Michael Fellinger 4date: 2019-05-23 5--- 6 7# Ruby 8 9## User Guide 10 11### Using Ruby 12 13#### Overview 14 15Several versions of Ruby interpreters are available on Nix, as well as over 250 gems and many applications written in Ruby. 16The attribute `ruby` refers to the default Ruby interpreter, which is currently 17MRI 2.5. It's also possible to refer to specific versions, e.g. `ruby_2_6`, `jruby`, or `mruby`. 18 19In the nixpkgs tree, Ruby packages can be found throughout, depending on what 20they do, and are called from the main package set. Ruby gems, however are 21separate sets, and there's one default set for each interpreter (currently MRI 22only). 23 24There are two main approaches for using Ruby with gems. 25One is to use a specifically locked `Gemfile` for an application that has very strict dependencies. 26The other is to depend on the common gems, which we'll explain further down, and 27rely on them being updated regularly. 28 29The interpreters have common attributes, namely `gems`, and `withPackages`. So 30you can refer to `ruby.gems.nokogiri`, or `ruby_2_5.gems.nokogiri` to get the 31Nokogiri gem already compiled and ready to use. 32 33Since not all gems have executables like `nokogiri`, it's usually more 34convenient to use the `withPackages` function like this: 35`ruby.withPackages (p: with p; [ nokogiri ])`. This will also make sure that the 36Ruby in your environment will be able to find the gem and it can be used in your 37Ruby code (for example via `ruby` or `irb` executables) via `require "nokogiri"` 38as usual. 39 40#### Temporary Ruby environment with `nix-shell` 41 42Rather than having a single Ruby environment shared by all Ruby 43development projects on a system, Nix allows you to create separate 44environments per project. `nix-shell` gives you the possibility to 45temporarily load another environment akin to a combined `chruby` or 46`rvm` and `bundle exec`. 47 48There are two methods for loading a shell with Ruby packages. The first and 49recommended method is to create an environment with `ruby.withPackages` and load 50that. 51 52```shell 53nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" 54``` 55 56The other method, which is not recommended, is to create an environment and list 57all the packages directly. 58 59```shell 60nix-shell -p ruby.gems.nokogiri ruby.gems.pry 61``` 62 63Again, it's possible to launch the interpreter from the shell. The Ruby 64interpreter has the attribute `gems` which contains all Ruby gems for that 65specific interpreter. 66 67##### Load environment from `.nix` expression 68 69As explained in the Nix manual, `nix-shell` can also load an expression from a 70`.nix` file. Say we want to have Ruby 2.5, `nokogori`, and `pry`. Consider a 71`shell.nix` file with: 72 73```nix 74with import <nixpkgs> {}; 75ruby.withPackages (ps: with ps; [ nokogiri pry ]) 76``` 77 78What's happening here? 79 801. We begin with importing the Nix Packages collections. `import <nixpkgs>` 81 imports the `<nixpkgs>` function, `{}` calls it and the `with` statement 82 brings all attributes of `nixpkgs` in the local scope. These attributes form 83 the main package set. 842. Then we create a Ruby environment with the `withPackages` function. 853. The `withPackages` function expects us to provide a function as an argument 86 that takes the set of all ruby gems and returns a list of packages to include 87 in the environment. Here, we select the packages `nokogiri` and `pry` from 88 the package set. 89 90##### Execute command with `--run` 91 92A convenient flag for `nix-shell` is `--run`. It executes a command in the 93`nix-shell`. We can e.g. directly open a `pry` REPL: 94 95```shell 96nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "pry" 97``` 98 99Or immediately require `nokogiri` in pry: 100 101```shell 102nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "pry -rnokogiri" 103``` 104 105Or run a script using this environment: 106 107```shell 108nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "ruby example.rb" 109``` 110 111##### Using `nix-shell` as shebang 112 113In fact, for the last case, there is a more convenient method. You can add a 114[shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) to your script 115specifying which dependencies `nix-shell` needs. With the following shebang, you 116can just execute `./example.rb`, and it will run with all dependencies. 117 118```ruby 119#! /usr/bin/env nix-shell 120#! nix-shell -i ruby -p "ruby.withPackages (ps: with ps; [ nokogiri rest-client ])" 121 122require 'nokogiri' 123require 'rest-client' 124 125body = RestClient.get('http://example.com').body 126puts Nokogiri::HTML(body).at('h1').text 127``` 128 129### Developing with Ruby 130 131#### Using an existing Gemfile 132 133In most cases, you'll already have a `Gemfile.lock` listing all your dependencies. 134This can be used to generate a `gemset.nix` which is used to fetch the gems and 135combine them into a single environment. 136The reason why you need to have a separate file for this, is that Nix requires 137you to have a checksum for each input to your build. 138Since the `Gemfile.lock` that `bundler` generates doesn't provide us with 139checksums, we have to first download each gem, calculate its SHA256, and store 140it in this separate file. 141 142So the steps from having just a `Gemfile` to a `gemset.nix` are: 143 144```shell 145bundle lock 146bundix 147``` 148 149If you already have a `Gemfile.lock`, you can simply run `bundix` and it will 150work the same. 151 152To update the gems in your `Gemfile.lock`, you may use the `bundix -l` flag, 153which will create a new `Gemfile.lock` in case the `Gemfile` has a more recent 154time of modification. 155 156Once the `gemset.nix` is generated, it can be used in a 157`bundlerEnv` derivation. Here is an example you could use for your `shell.nix`: 158 159```nix 160# ... 161let 162 gems = bundlerEnv { 163 name = "gems-for-some-project"; 164 gemdir = ./.; 165 }; 166in mkShell { buildInputs = [ gems gems.wrappedRuby ]; } 167``` 168 169With this file in your directory, you can run `nix-shell` to build and use the gems. 170The important parts here are `bundlerEnv` and `wrappedRuby`. 171 172The `bundlerEnv` is a wrapper over all the gems in your gemset. This means that 173all the `/lib` and `/bin` directories will be available, and the executables of 174all gems (even of indirect dependencies) will end up in your `$PATH`. 175The `wrappedRuby` provides you with all executables that come with Ruby itself, 176but wrapped so they can easily find the gems in your gemset. 177 178One common issue that you might have is that you have Ruby 2.6, but also 179`bundler` in your gemset. That leads to a conflict for `/bin/bundle` and 180`/bin/bundler`. You can resolve this by wrapping either your Ruby or your gems 181in a `lowPrio` call. So in order to give the `bundler` from your gemset 182priority, it would be used like this: 183 184```nix 185# ... 186mkShell { buildInputs = [ gems (lowPrio gems.wrappedRuby) ]; } 187``` 188 189 190#### Gem-specific configurations and workarounds 191 192In some cases, especially if the gem has native extensions, you might need to 193modify the way the gem is built. 194 195This is done via a common configuration file that includes all of the 196workarounds for each gem. 197 198This file lives at `/pkgs/development/ruby-modules/gem-config/default.nix`, 199since it already contains a lot of entries, it should be pretty easy to add the 200modifications you need for your needs. 201 202In the meanwhile, or if the modification is for a private gem, you can also add 203the configuration to only your own environment. 204 205Two places that allow this modification are the `ruby` derivation, or `bundlerEnv`. 206 207Here's the `ruby` one: 208 209```nix 210{ pg_version ? "10", pkgs ? import <nixpkgs> { } }: 211let 212 myRuby = pkgs.ruby.override { 213 defaultGemConfig = pkgs.defaultGemConfig // { 214 pg = attrs: { 215 buildFlags = 216 [ "--with-pg-config=${pkgs."postgresql_${pg_version}"}/bin/pg_config" ]; 217 }; 218 }; 219 }; 220in myRuby.withPackages (ps: with ps; [ pg ]) 221``` 222 223And an example with `bundlerEnv`: 224 225```nix 226{ pg_version ? "10", pkgs ? import <nixpkgs> { } }: 227let 228 gems = pkgs.bundlerEnv { 229 name = "gems-for-some-project"; 230 gemdir = ./.; 231 gemConfig = pkgs.defaultGemConfig // { 232 pg = attrs: { 233 buildFlags = 234 [ "--with-pg-config=${pkgs."postgresql_${pg_version}"}/bin/pg_config" ]; 235 }; 236 }; 237 }; 238in mkShell { buildInputs = [ gems gems.wrappedRuby ]; } 239``` 240 241And finally via overlays: 242 243```nix 244{ pg_version ? "10" }: 245let 246 pkgs = import <nixpkgs> { 247 overlays = [ 248 (self: super: { 249 defaultGemConfig = super.defaultGemConfig // { 250 pg = attrs: { 251 buildFlags = [ 252 "--with-pg-config=${ 253 pkgs."postgresql_${pg_version}" 254 }/bin/pg_config" 255 ]; 256 }; 257 }; 258 }) 259 ]; 260 }; 261in pkgs.ruby.withPackages (ps: with ps; [ pg ]) 262``` 263 264Then we can get whichever postgresql version we desire and the `pg` gem will 265always reference it correctly: 266 267```shell 268$ nix-shell --argstr pg_version 9_4 --run 'ruby -rpg -e "puts PG.library_version"' 26990421 270 271$ nix-shell --run 'ruby -rpg -e "puts PG.library_version"' 272100007 273``` 274 275Of course for this use-case one could also use overlays since the configuration 276for `pg` depends on the `postgresql` alias, but for demonstration purposes this 277has to suffice. 278 279#### Adding a gem to the default gemset 280 281Now that you know how to get a working Ruby environment with Nix, it's time to 282go forward and start actually developing with Ruby. 283We will first have a look at how Ruby gems are packaged on Nix. Then, we will 284look at how you can use development mode with your code. 285 286All gems in the standard set are automatically generated from a single 287`Gemfile`. The dependency resolution is done with `bundler` and makes it more 288likely that all gems are compatible to each other. 289 290In order to add a new gem to nixpkgs, you can put it into the 291`/pkgs/development/ruby-modules/with-packages/Gemfile` and run 292`./maintainers/scripts/update-ruby-packages`. 293 294To test that it works, you can then try using the gem with: 295 296```shell 297NIX_PATH=nixpkgs=$PWD nix-shell -p "ruby.withPackages (ps: with ps; [ name-of-your-gem ])" 298``` 299 300#### Packaging applications 301 302A common task is to add a ruby executable to nixpkgs, popular examples would be 303`chef`, `jekyll`, or `sass`. A good way to do that is to use the `bundlerApp` 304function, that allows you to make a package that only exposes the listed 305executables, otherwise the package may cause conflicts through common paths like 306`bin/rake` or `bin/bundler` that aren't meant to be used. 307 308The absolute easiest way to do that is to write a 309`Gemfile` along these lines: 310 311```ruby 312source 'https://rubygems.org' do 313 gem 'mdl' 314end 315``` 316 317If you want to package a specific version, you can use the standard Gemfile 318syntax for that, e.g. `gem 'mdl', '0.5.0'`, but if you want the latest stable 319version anyway, it's easier to update by simply running the `bundle lock` and 320`bundix` steps again. 321 322Now you can also also make a `default.nix` that looks like this: 323 324```nix 325{ lib, bundlerApp }: 326 327bundlerApp { 328 pname = "mdl"; 329 gemdir = ./.; 330 exes = [ "mdl" ]; 331} 332``` 333 334All that's left to do is to generate the corresponding `Gemfile.lock` and 335`gemset.nix` as described above in the `Using an existing Gemfile` section. 336 337##### Packaging executables that require wrapping 338 339Sometimes your app will depend on other executables at runtime, and tries to 340find it through the `PATH` environment variable. 341 342In this case, you can provide a `postBuild` hook to `bundlerApp` that wraps the 343gem in another script that prefixes the `PATH`. 344 345Of course you could also make a custom `gemConfig` if you know exactly how to 346patch it, but it's usually much easier to maintain with a simple wrapper so the 347patch doesn't have to be adjusted for each version. 348 349Here's another example: 350 351```nix 352{ lib, bundlerApp, makeWrapper, git, gnutar, gzip }: 353 354bundlerApp { 355 pname = "r10k"; 356 gemdir = ./.; 357 exes = [ "r10k" ]; 358 359 buildInputs = [ makeWrapper ]; 360 361 postBuild = '' 362 wrapProgram $out/bin/r10k --prefix PATH : ${lib.makeBinPath [ git gnutar gzip ]} 363 ''; 364} 365```