20

I installed Ruby 1.9.3-p286 with rbenv. Now, after installing a newer version (p327), obviously, it doesn't know anything about the GEMs installed with the previous version.

Is it possible to copy Gems from that version to the newer one, so that it won't be needed to download them all again?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
ArashM
  • 1,379
  • 13
  • 18

3 Answers3

35

You can copy the gems/ and bin/ folders, but this will lead to problems. The files in bin/ have hardcoded paths in them.

I'd recommend reinstalling them, which would be as easy as this:

$ rbenv local 1.9.3-p286
$ gem list | cut -d" " -f1 > my-gems
$ rbenv local 1.9.3-p327
$ gem install $(cat my-gems)
Bojan Dimovski
  • 1,216
  • 11
  • 25
  • hmmm... I thought there might be a better way. thanks anyway :) – ArashM Nov 30 '12 at 17:56
  • 2
    a better way is to keep a `Gemfile` in your home folder and when installing a new version you just install `bundler` on it. then you simply do `bundle install` in your home folder :) –  Nov 30 '12 at 17:59
  • 1
    Actually, the main problem here is that I don't want to download the gems again. So, that won't help either. – ArashM Nov 30 '12 at 18:05
  • 7
    Nice, thanks! BTW, `gem list --no-versions` is simpler than cut. It may be future compatible if there are changes to how gems are listed. – Ivan -Oats- Storck Oct 16 '14 at 18:53
7

I've been looking at this specifically from the perspective of upgrading and reinstalling without downloading. It's not trivial, and I recommend you do some cleanup of your gems to minimize the amount of processing/installation that needs to be done (e.g., I had five versions of ZenTest installed; I did 'gem cleanup ZenTest' before doing this). Be careful with 'gem cleanup', though, as it removes all but the LAST version: if you need to support an older version of Rails, manually clean up the versions you don't need.

I called this script 'migrate-gems.sh':

#! /bin/bash

if [ ${#} -ne 2 ]; then
  echo >&2 Usage: $(basename ${0}) old-version new-version
  exit 1
fi

home_path=$(cd ~; pwd -P)
old_version=${1}
new_version=${2}

rbenv shell ${old_version}

declare -a old_gem_paths old_gems
old_gem_paths=($(gem env gempath | sed -e 's/:/ /'))

rbenv shell ${new_version}

for ogp in "${old_gem_paths[@]}"; do
  case "${ogp}" in
    ${home_path}/.gem/ruby*|*/.gem/ruby*)
      # Skip ~/.gem/ruby.
      continue
      ;;
  esac

  for old_gem in $(ls -1 ${ogp}/cache/*.gem); do
    gem install --local --ignore-dependencies ${ogp}/cache/${old_gem}
  done
done

There are three pieces that make this work:

  1. gem env gempath contains the paths (:-separated) where gems are installed. Because gems are shared in ~/.gem/ruby, I skip this one.
  2. gem install accepts --local, which forces no network connections.
  3. gem install accepts --ignore-dependencies, which disables dependency checking.

I had a fairly large list of gems to move over today and I didn't want to download from rubygems.org (plus, I needed older versions), so I whipped this up fairly quickly.

Austin Ziegler
  • 776
  • 10
  • 17
  • 1
    I changed it a little bit and it works now! I made a gist for it: https://gist.github.com/4701056 – ArashM Feb 03 '13 at 09:33
  • That's probably a difference between a BSD-ish `ls` (I'm on a Mac) and a GNU-ish `ls` (you're on Ubuntu). The correct fix is to probably just use `${ogp}/cache/$(basename ${old_gem})`. You will get some errors—some older gems just aren't compatible with a newer Ruby—but by and large it will work fairly well. – Austin Ziegler Feb 03 '13 at 19:10
0

For posterity, I wrote rbenv-copy-gems.sh to help me do this. It doesn't meet the criteria of this question (it installs from the Internet, not locally), but it's been useful for me as I upgrade or install new version of Ruby via rbenv install.

Current version pasted below for reference, but I keep the gist up to date as I make improvements.

#!/bin/bash
# copy is a misnomer; it's actually LIST + INSTALL
# --from 2.2.1 [--to other-version-else-whatever-is-currently-set]
#
# TODO: install only most-recent version that's installed in FROM
# TODO: use gem names only from FROM, install latest available version (might be more recent than in FROM)
# TODO: pass arguments to gem command (e.g. --no-document)

CURRENT_VERSION=`rbenv version | cut -d' ' -f1`
GEM_LIST_ARGS="--local"

while [[ $# -gt 0 ]]; do
  option="$1"

  case $option in
    --from)
    FROM="$2"
    shift
    ;;
    --to)
    TO="$2"
    shift
    ;;
  esac
  shift # past argument or value
done

if [ -z "${FROM}" ]; then
  FROM="${CURRENT_VERSION}"
fi

if [ -z "${TO}" ]; then
  TO="${CURRENT_VERSION}"
fi

echo ">>> Install gems from v${FROM} to v${TO}"

# Get gems and versions, reformat to GEMNAME:version[,version[...]]
gems=(`RBENV_VERSION=${FROM} gem list ${GEM_LIST_ARGS} | sed -e 's/[\(\)]//g' -e 's/, /,/g' -e 's/ /:/'`)

for geminfo in "${gems[@]}"; do
  gem=`echo $geminfo | cut -d: -f1`
  versions=(`echo $geminfo | sed -e 's/^.*://' -e 's/,/ /g'`)
  for version in "${versions[@]}"; do
    installed=`RBENV_VERSION=${TO} gem query -i $gem -v $version`
    if [ "${installed}" == "false" ]; then
      echo ">>> Installing ${gem} ${version}:"
      RBENV_VERSION=${TO} gem install $gem -v $version
    else
      echo ">>> ${gem} ${version} already installed"
    fi
    echo ""
  done
done
Jim Meyer
  • 1,326
  • 1
  • 11
  • 12
  • p.s. If you name whichever script you choose 'rbenv-copy-gems.sh', make it executable, and put it somewhere in your path, you can `rbenv shell && rbenv copy-gems --from ` or `rbenv copy-gems --from --to ` – Jim Meyer Jul 07 '16 at 16:32