diff options
author | Alyssa Ross <hi@alyssa.is> | 2019-08-24 09:47:56 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2019-08-25 09:24:33 +0000 |
commit | 6f87d54e6f72bf8f25dad5e9ff9cce82c10b4781 (patch) | |
tree | bf82200a42c070620864c19b84222f84c77d4097 /nixpkgs/doc/languages-frameworks/ruby.section.md | |
parent | 1b9a13c4689af7e088eb7af5589f8c811282846a (diff) | |
parent | 61cc0b580ce601f98eef4c7bba36bbe1a9112236 (diff) | |
download | nixlib-6f87d54e6f72bf8f25dad5e9ff9cce82c10b4781.tar nixlib-6f87d54e6f72bf8f25dad5e9ff9cce82c10b4781.tar.gz nixlib-6f87d54e6f72bf8f25dad5e9ff9cce82c10b4781.tar.bz2 nixlib-6f87d54e6f72bf8f25dad5e9ff9cce82c10b4781.tar.lz nixlib-6f87d54e6f72bf8f25dad5e9ff9cce82c10b4781.tar.xz nixlib-6f87d54e6f72bf8f25dad5e9ff9cce82c10b4781.tar.zst nixlib-6f87d54e6f72bf8f25dad5e9ff9cce82c10b4781.zip |
Merge commit '61cc0b580ce601f98eef4c7bba36bbe1a9112236'
Diffstat (limited to 'nixpkgs/doc/languages-frameworks/ruby.section.md')
-rw-r--r-- | nixpkgs/doc/languages-frameworks/ruby.section.md | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/nixpkgs/doc/languages-frameworks/ruby.section.md b/nixpkgs/doc/languages-frameworks/ruby.section.md new file mode 100644 index 000000000000..f58ada029dce --- /dev/null +++ b/nixpkgs/doc/languages-frameworks/ruby.section.md @@ -0,0 +1,441 @@ +--- +title: Ruby +author: Michael Fellinger +date: 2019-05-23 +--- + +# Ruby + +## User Guide + +### Using Ruby + +#### Overview + +Several versions of Ruby interpreters are available on Nix, as well as over 250 gems and many applications written in Ruby. +The attribute `ruby` refers to the default Ruby interpreter, which is currently +MRI 2.5. It's also possible to refer to specific versions, e.g. `ruby_2_6`, `jruby`, or `mruby`. + +In the nixpkgs tree, Ruby packages can be found throughout, depending on what +they do, and are called from the main package set. Ruby gems, however are +separate sets, and there's one default set for each interpreter (currently MRI +only). + +There are two main approaches for using Ruby with gems. +One is to use a specifically locked `Gemfile` for an application that has very strict dependencies. +The other is to depend on the common gems, which we'll explain further down, and +rely on them being updated regularly. + +The interpreters have common attributes, namely `gems`, and `withPackages`. So +you can refer to `ruby.gems.nokogiri`, or `ruby_2_5.gems.nokogiri` to get the +Nokogiri gem already compiled and ready to use. + +Since not all gems have executables like `nokogiri`, it's usually more +convenient to use the `withPackages` function like this: +`ruby.withPackages (p: with p; [ nokogiri ])`. This will also make sure that the +Ruby in your environment will be able to find the gem and it can be used in your +Ruby code (for example via `ruby` or `irb` executables) via `require "nokogiri"` +as usual. + +#### Installing Ruby and gems + +The Nix and NixOS manuals explain how packages are generally installed. In the +case of Ruby and Nix, it is important to make a distinction between whether the +package is considered an application or a library. + +Applications on Nix are typically installed into your user +profile imperatively using `nix-env -iA`, and on NixOS declaratively by adding the +package name to `environment.systemPackages` in `/etc/nixos/configuration.nix`. +Dependencies such as libraries are automatically installed and should not be +installed explicitly. + +The same goes for Ruby applications and libraries. Ruby applications can be +installed in your profile. But Ruby libraries you would like to use for +development cannot be installed, at least not individually, because they won't +be able to find each other resulting in load errors. Instead, it is possible to +create an environment with `bundlerEnv` or `ruby.withPackages` where the +interpreter and other executables are able to find each other and all of the +gems. + +In the following examples we create an environment with Ruby 2.5, `nokogiri` and +`pry`. There is one limitation here, and that's that you can install only one +environment at a time, otherwise you'll encounter collisions. + +##### Environment defined in separate `.nix` file + +Create a file, e.g. `build.nix`, with the following expression: + +```nix +with import <nixpkgs> {}; +ruby_2_5.withPackages (ps: with ps; [ nokogiri pry ]) +``` + +and install it in your profile with: + +```shell +nix-env -if build.nix +``` + +Now you can use the Ruby interpreter, as well as the extra packages (`pry`, +`nokogiri`) that you added to the environment. + +##### Environment defined in ~/.config/nixpkgs/config.nix` + +If you prefer to, you could also add the environment as a package override to +the nixpkgs set like this: + +```nix +{ # ... + packageOverrides = pkgs: + with pkgs; { + myRubyEnv = ruby.withPackages (ps: with ps; [ nokogiri pry ]); + }; +} +``` + +and install it in your profile path with: + +```shell +nix-env -iA nixpkgs.myRubyEnv +``` + +The environment is installed by referring to the attribute, and considering the +`nixpkgs` channel was used. + +##### Environment defined in `/etc/nixos/configuration.nix` + +For the sake of completeness, here's another example of how to install the +environment system-wide. + +```nix +{ # ... + environment.systemPackages = with pkgs; + [ (ruby.withPackages (ps: with ps; [ nokogiri pry ])) ]; +} +``` + +#### Temporary Ruby environment with `nix-shell` + +The examples in the previous section showed how to install a Ruby environment +into a profile. For development you may need to use multiple environments. +`nix-shell` gives you the possibility to temporarily load another environment +akin to a combined `chruby` or `rvm` and `bundle exec`. + +There are two methods for loading a shell with Ruby packages. The first and +recommended method is to create an environment with `ruby.withPackages` and load +that. + +```shell +nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" +``` + +The other method, which is not recommended, is to create an environment and list +all the packages directly. + +```shell +nix-shell -p ruby.gems.nokogiri ruby.gems.pry +``` + +Again, it's possible to launch the interpreter from the shell. The Ruby +interpreter has the attribute `gems` which contains all Ruby gems for that +specific interpreter. + +##### Load environment from `.nix` expression + +As explained in the Nix manual, `nix-shell` can also load an expression from a +`.nix` file. Say we want to have Ruby 2.5, `nokogori`, and `pry`. Consider a +`shell.nix` file with: + +```nix +with import <nixpkgs> {}; +ruby.withPackages (ps: with ps; [ nokogiri pry ]) +``` + +What's happening here? + +1. We begin with importing the Nix Packages collections. `import <nixpkgs>` + imports the `<nixpkgs>` function, `{}` calls it and the `with` statement + brings all attributes of `nixpkgs` in the local scope. These attributes form + the main package set. +2. Then we create a Ruby environment with the `withPackages` function. +3. The `withPackages` function expects us to provide a function as an argument + that takes the set of all ruby gems and returns a list of packages to include + in the environment. Here, we select the packages `nokogiri` and `pry` from + the package set. + +##### Execute command with `--run` + +A convenient flag for `nix-shell` is `--run`. It executes a command in the +`nix-shell`. We can e.g. directly open a `pry` REPL: + +```shell +nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "pry" +``` + +Or immediately require `nokogiri` in pry: + +```shell +nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "pry -rnokogiri" +``` + +Or run a script using this environment: + +```shell +nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "ruby example.rb" +``` + +##### Using `nix-shell` as shebang + +In fact, for the last case, there is a more convenient method. You can add a +[shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) to your script +specifying which dependencies `nix-shell` needs. With the following shebang, you +can just execute `./example.rb`, and it will run with all dependencies. + +```ruby +#! /usr/bin/env nix-shell +#! nix-shell -i ruby -p "ruby.withPackages (ps: with ps; [ nokogiri rest-client ])" + +require 'nokogiri' +require 'rest-client' + +body = RestClient.get('http://example.com').body +puts Nokogiri::HTML(body).at('h1').text +``` + +### Developing with Ruby + +#### Using an existing Gemfile + +In most cases, you'll already have a `Gemfile.lock` listing all your dependencies. +This can be used to generate a `gemset.nix` which is used to fetch the gems and +combine them into a single environment. +The reason why you need to have a separate file for this, is that Nix requires +you to have a checksum for each input to your build. +Since the `Gemfile.lock` that `bundler` generates doesn't provide us with +checksums, we have to first download each gem, calculate its SHA256, and store +it in this separate file. + +So the steps from having just a `Gemfile` to a `gemset.nix` are: + +```shell +bundle lock +bundix +``` + +If you already have a `Gemfile.lock`, you can simply run `bundix` and it will +work the same. + +To update the gmes in your `Gemfile.lock`, you may use the `bundix -l` flag, +which will create a new `Gemfile.lock` in case the `Gemfile` has a more recent +time of modification. + +Once the `gemset.nix` is generated, it can be used in a +`bundlerEnv` derivation. Here is an example you could use for your `shell.nix`: + +```nix +# ... +let + gems = bundlerEnv { + name = "gems-for-some-project"; + gemdir = ./.; + }; +in mkShell { buildInputs = [ gems gems.wrappedRuby ]; } +``` + +With this file in your directory, you can run `nix-shell` to build and use the gems. +The important parts here are `bundlerEnv` and `wrappedRuby`. + +The `bundlerEnv` is a wrapper over all the gems in your gemset. This means that +all the `/lib` and `/bin` directories will be available, and the executables of +all gems (even of indirect dependencies) will end up in your `$PATH`. +The `wrappedRuby` provides you with all executables that come with Ruby itself, +but wrapped so they can easily find the gems in your gemset. + +One common issue that you might have is that you have Ruby 2.6, but also +`bundler` in your gemset. That leads to a conflict for `/bin/bundle` and +`/bin/bundler`. You can resolve this by wrapping either your Ruby or your gems +in a `lowPrio` call. So in order to give the `bundler` from your gemset +priority, it would be used like this: + +```nix +# ... +mkShell { buildInputs = [ gems (lowPrio gems.wrappedRuby) ]; } +``` + + +#### Gem-specific configurations and workarounds + +In some cases, especially if the gem has native extensions, you might need to +modify the way the gem is built. + +This is done via a common configuration file that includes all of the +workarounds for each gem. + +This file lives at `/pkgs/development/ruby-modules/gem-config/default.nix`, +since it already contains a lot of entries, it should be pretty easy to add the +modifications you need for your needs. + +In the meanwhile, or if the modification is for a private gem, you can also add +the configuration to only your own environment. + +Two places that allow this modification are the `ruby` derivation, or `bundlerEnv`. + +Here's the `ruby` one: + +```nix +{ pg_version ? "10", pkgs ? import <nixpkgs> { } }: +let + myRuby = pkgs.ruby.override { + defaultGemConfig = pkgs.defaultGemConfig // { + pg = attrs: { + buildFlags = + [ "--with-pg-config=${pkgs."postgresql_${pg_version}"}/bin/pg_config" ]; + }; + }; + }; +in myRuby.withPackages (ps: with ps; [ pg ]) +``` + +And an example with `bundlerEnv`: + +```nix +{ pg_version ? "10", pkgs ? import <nixpkgs> { } }: +let + gems = pkgs.bundlerEnv { + name = "gems-for-some-project"; + gemdir = ./.; + gemConfig = pkgs.defaultGemConfig // { + pg = attrs: { + buildFlags = + [ "--with-pg-config=${pkgs."postgresql_${pg_version}"}/bin/pg_config" ]; + }; + }; + }; +in mkShell { buildInputs = [ gems gems.wrappedRuby ]; } +``` + +And finally via overlays: + +```nix +{ pg_version ? "10" }: +let + pkgs = import <nixpkgs> { + overlays = [ + (self: super: { + defaultGemConfig = super.defaultGemConfig // { + pg = attrs: { + buildFlags = [ + "--with-pg-config=${ + pkgs."postgresql_${pg_version}" + }/bin/pg_config" + ]; + }; + }; + }) + ]; + }; +in pkgs.ruby.withPackages (ps: with ps; [ pg ]) +``` + +Then we can get whichever postgresql version we desire and the `pg` gem will +always reference it correctly: + +```shell +$ nix-shell --argstr pg_version 9_4 --run 'ruby -rpg -e "puts PG.library_version"' +90421 + +$ nix-shell --run 'ruby -rpg -e "puts PG.library_version"' +100007 +``` + +Of course for this use-case one could also use overlays since the configuration +for `pg` depends on the `postgresql` alias, but for demonstration purposes this +has to suffice. + +#### Adding a gem to the default gemset + +Now that you know how to get a working Ruby environment with Nix, it's time to +go forward and start actually developing with Ruby. +We will first have a look at how Ruby gems are packaged on Nix. Then, we will +look at how you can use development mode with your code. + +All gems in the standard set are automatically generated from a single +`Gemfile`. The dependency resolution is done with `bundler` and makes it more +likely that all gems are compatible to each other. + +In order to add a new gem to nixpkgs, you can put it into the +`/pkgs/development/ruby-modules/with-packages/Gemfile` and run +`./maintainers/scripts/update-ruby-packages`. + +To test that it works, you can then try using the gem with: + +```shell +NIX_PATH=nixpkgs=$PWD nix-shell -p "ruby.withPackages (ps: with ps; [ name-of-your-gem ])" +``` + +#### Packaging applications + +A common task is to add a ruby executable to nixpkgs, popular examples would be +`chef`, `jekyll`, or `sass`. A good way to do that is to use the `bundlerApp` +function, that allows you to make a package that only exposes the listed +executables, otherwise the package may cause conflicts through common paths like +`bin/rake` or `bin/bundler` that aren't meant to be used. + +The absolute easiest way to do that is to write a +`Gemfile` along these lines: + +```ruby +source 'https://rubygems.org' do + gem 'mdl' +end +``` + +If you want to package a specific version, you can use the standard Gemfile +syntax for that, e.g. `gem 'mdl', '0.5.0'`, but if you want the latest stable +version anyway, it's easier to update by simply running the `bundle lock` and +`bundix` steps again. + +Now you can also also make a `default.nix` that looks like this: + +```nix +{ lib, bundlerApp }: + +bundlerApp { + pname = "mdl"; + gemdir = ./.; + exes = [ "mdl" ]; +} +``` + +All that's left to do is to generate the corresponding `Gemfile.lock` and +`gemset.nix` as described above in the `Using an existing Gemfile` section. + +##### Packaging executables that require wrapping + +Sometimes your app will depend on other executables at runtime, and tries to +find it through the `PATH` environment variable. + +In this case, you can provide a `postBuild` hook to `bundlerApp` that wraps the +gem in another script that prefixes the `PATH`. + +Of course you could also make a custom `gemConfig` if you know exactly how to +patch it, but it's usually much easier to maintain with a simple wrapper so the +patch doesn't have to be adjusted for each version. + +Here's another example: + +```nix +{ lib, bundlerApp, makeWrapper, git, gnutar, gzip }: + +bundlerApp { + pname = "r10k"; + gemdir = ./.; + exes = [ "r10k" ]; + + buildInputs = [ makeWrapper ]; + + postBuild = '' + wrapProgram $out/bin/r10k --prefix PATH : ${lib.makeBinPath [ git gnutar gzip ]} + ''; +} +``` |