[Ruby] Exposing new gems to a bundlerApp-installed tool

nixpkgs.coocapods is a bundlerApp derivation that installs a binary called pod. This binary implements a plugin system wherein it checks RubyGems for pods whose name starts with cocoapods-, e.g. there’s a plugin provided by a gem named cocoapods-art.

If I have cocoapods installed via Nix, how can I instruct Nix to install the cocoapods-art gem and make this gem visible to the pod binary? AIUI the pod binary is a wrapper that instructs Ruby to look in the derivation’s store path to find the gems it depends on, so installing a gem outside this path wouldn’t be picked up (and I can’t install it inside this path).

I’m willing to override the cocoapods derivation, but I don’t see any obvious way to say “and include these extra gems”.

I’ve determined that I can solve my immediate problem by basically copying the existing pkgs/development/mobile/cocoapods directory, adding gem 'cocoapods-art' to the Gemfile, and producing a brand new package without referencing the original one. So that lets me move forward. But it’s obviously not a general solution to “take an existing ruby derivation and just expose one more gem to it”.

FWIW it appears that I can’t use overrideAttrs on the result of bundlerApp (it exists, but it just overrides the attributes of the ultimate mkDerivation call), so I can’t use that to just override the gemdir of the original.


I just re-solved my original problem in a slightly more reusable fashion, by writing something like

{ callPackage, bundlerApp }:
callPackage <nixpkgs/pkgs/development/mobile/cocoapods> {
  bundlerApp = args: bundlerApp (args // {
    gemfile = ./Gemfile;
    lockfile = ./Gemfile.lock;
    gemset = ./gemset.nix;
  });
}

This relies on the filepath to the package, but it lets me override just the gemset and keep whatever else is in the original package (which in this case admittedly isn’t much, but it’s nice to be future-compatible). Though I’ll need to update this if the original package ever switches to bundlerEnv (e.g. how fastlane uses bundlerEnv on a nested attribute and then is a wrapper around that).

This still requires having detailed knowledge about the original package of course. I really wish there was a simple way to say “give me the original package but include <x> in the list of gems visible to its binaries”.

I’m actually working on something like this. It’s at ruby: add ruby.withPackages by manveru · Pull Request #61114 · NixOS/nixpkgs · GitHub and while not meant for bundlerApp directly, could be made to work with it by adding a tiny wrapper that only exposes the binaries you want on the fly.

1 Like

If you have a list of popular cocoapod gems (or even just the ones you need), please let me know so we can add them to the set.

I don’t mind producing my own Gemfile for just the gems I want as long as I can insert those into the environment of an existing bundlerApp package, but having them just be available would be convenient.

I don’t actually know what the popular cocoapods plugins are. The one I needed was cocoapods-art (which adds support for Artifactory) but searching gem list -r cocoapods right now finds a fair number of gems and I don’t know what they do.

Similarly gem list -r fastlane-plugin finds a lot of Fastlane plugins. The one I needed was fastlane-plugin-trainer but there sure are a lot of other options.

1 Like

I worked a bit yesterday on getting the cocoapods plugins with most downloads in.
Took a bit of effort, but in the end it’s working pretty well, but would appreciate if you could give it a try since I don’t have a mac to test it with. I’m still busy writing more docs, but with my PR

nix-shell -E '(import <nixpkgs> {}).ruby.withPackages (p: [ p.cocoapods p.cocoapods-art ])'

will work. I’ll push an updated version for this later today.

1 Like

That looks really convenient! I’ll check it out once you update your PR.

For now it lives at https://github.com/manveru/nixpkgs/tree/ruby-with-packages-and-docs while i’m working on docs a bit more.

1 Like

Just tested, and I was able to load the cocoapods-art plugin into cocoapods! I got an error from cocoapods-art but I’m also testing with a sample Podfile instead of my real one (I’m not on my work computer right now) so hopefully this is the same behavior I’d get with the non-Nix version. I’ll definitely have to test again on my work computer tomorrow.

I did notice that the shell environment has binaries from cocoapods’s dependencies too (notably, xcodeproj, which comes from a gem it depends on). And of course it has ruby’s binaries. I’m assuming this is expected and that I’ll have to wrap it in another derivation that just exposes the binaries I want, but I’ve never used python and haskell from Nix so I don’t know if this matches how those work.

Yes, having the executables is a side-effect. It’s a hard decision because some gems might depend on executables from others. In theory we can get the “direct” executables (just for the gems specified in the list you pass to withPackages) from the gemspec.

But I think that should be a separate function and could then even replace bundlerApp.
For that I need to fetch more meta-data for the gems to build, like homepage and license, which requires some changes to bundix.

Something that’s not clear to me is can I mix my own gems into this ruby.withPackages? I’m hoping the answer is “yes”, that I just need to provide my own gemset.

Definitely, I didn’t even think of that, so there’s no nice helper function. Currently you can do this (using the nix gem as an example):

Gemfile:

source('https://rubygems.org') { gem 'nix' }

The usual:

bundle lock && bundix

my-gems.nix:

with import <nixpkgs> {};
let

  functions = import <nixpkgs/pkgs/development/ruby-modules/bundled-common/functions.nix> {
    inherit lib;
    gemConfig = defaultGemConfig;
  };

  additional-gems = lib.mapAttrs (name: initialAttrs:
    let
      attrs = functions.applyGemConfigs ({ inherit ruby; gemName = name; } // initialAttrs);
    in
      buildRubyGem (functions.composeGemAttrs ruby additional-gems name attrs)
  ) (import ./gemset.nix);

in

  ruby_2_5.withPackages (ps: with ps; [
    nokogiri
    pry
    additional-gems.nix
  ])

You could even inline the gemset.nix here if it’s just a few. But that makes updating a bit harder.
It also won’t guarantee that the gems you chose are compatible with the ones in the standard gemset, as the resolution of bundler won’t take them into account.