Cannot execute RVM shell commands in ruby - ruby

Long time lurker, first time poster!
Goal
My ultimate goal is to make a Rake setup script to setup my rvm environment stuff (I need to dynamically create gemsets, install gems to those gemsets, and run ruby scripts within those gemsets).
Problem
I need to setup rvm in the shell that I'm executing rvm commands in. The basic idea is to source the rvm scripts as outlined here.
The problem arises when I try and source the rvm script when executing a shell command within ruby. Its well documented that rvm only supports bash, but ruby doesn't seem to be using bash when executing shell commands.
What I've Tried
I've tried all the methods to execute shell commands listed here to no avail. I'll use the 'exec' method below for simplicity.
It seems that although ruby thinks its using the bash shell to execute these commands ... it is not. Observe!
exec 'echo $SHELL'
=> /bin/bash
But
exec 'source ~/.rvm/scripts/rvm; type rvm | head -1;'
=> sh: source: not found
=> rvm is ~/.rvm/bin/rvm
Which tells me that ruby is really using /bin/sh not /bin/bash (that output should return rvm is a function). I even went so far as to print the ruby env stuff, and ENV[SHELL] is '/bin/bash'
'Brute Force' Solution
I do have a workaround, but its really kludgy (this would necessitate that I 'AND' all of the commands together):
exec 'echo \'source ~/.rvm/scripts/rvm && type rvm | head -1\' | /bin/bash'
I'd like to avoid using shell scripts if possible -- it seems reasonable that I can accomplish this within ruby.

As it happens, RVM actually exposes a Ruby API that's included by default. Add $HOME/.rvm/lib to your $LOAD_PATH; you can now use require 'rvm'.
As far as I can tell, the main documentation for this is in the source files themselves (a summary is in rvm.rb).
Now you can write Ruby scripts that manipulate RVM, like this:
require 'rvm'
env = RVM.current
env.gemset.create('newgemset')
And so on.

Call bash with the -c parameter:
command = 'source ~/.rvm/scripts/rvm; type rvm | head -1'
exec "bash -c #{command.inspect}"

Related

Run RSpec for more than one Ruby version using single command

I was looking for a way to run 'rake spec' for all ruby versions.
So far I wrote this code in Rakefile but it doesn't work:
RUBIES = ['ruby-2.0.0', 'ruby-2.1.1', 'ruby-2.2.1']
RUBIES.each do |ruby_v|
sh "rvm use #{ruby_v}"
RSpec::Core::RakeTask.new(:spec)
end
Any ideas how may I accomplish this?
It would be great if I could pass down arguments specifying whether I want to run the tests for a single ruby version or for all.
EDIT:
Here's the Rakefile I used and the error I am getting. It's not the same file which I mentioned at the top. It uses rvm use #{ruby_v}; rspec spec as suggested by Keith in the answer.
rvm is a shell command and its Ruby setting applies only to the current instance of the shell. So when you run sh "rvm use #{ruby_v}" you're changing the rvm version of the shell that you have invoked, and then exiting from that shell.
In addition, your Ruby program is an operating system process running the Ruby virtual machine, so when you make the call to RSpec::Core::RakeTask.new(:spec), you're still in that same OS process and Ruby version with which you started your script.
You'll need to run the rspec tests in the shell that you invoke and change the Ruby version on. I thought something like this would work:
`rvm use #{ruby_v}; rspec spec`
...but as you pointed out, it does not. You need to run the new shell as a "login shell" so that rvm is set up correctly. In addition, the new shell must be told that the thing you're invoking is a shell command and not a script or binary executable. In other words:
1) have your command explicitly invoke bash or zsh (sh did not work for me on my Mac).
2) specify (probably with -l) that it is a login shell.
3) specify (probably with -c) that you are executing a shell command (rvm) and not a script or executable.
I am using zsh as my shell, but you should be able to substitute bash in the code below and it should work (and of course put your rspec command in there):
2.3.0 :024 > puts `zsh -lc "rvm current"`
ruby-2.3.0
=> nil
2.3.0 :025 > puts `zsh -lc "rvm use jruby; rvm current"`
Using /Users/kbennett/.rvm/gems/jruby-9.0.5.0
jruby-9.0.5.0
=> nil
2.3.0 :026 > puts `zsh -lc "rvm current"`
ruby-2.3.0
=> nil

Install Homebrew from within a ruby script

I've created a ruby script that sets up a new mac.
Among other things it creates a .bash_profile, .gitconfig and configures various system settings such as displaying the full POSIX path as the Finder window title (super useful).
Mostly I'm running commands in backticks such as `defaults write com.apple.finder _FXShowPosixPathInTitle -bool true` the aforementioned full POSIX path as the Finder window title trick.
All this works just fine.
What I want to do is have this ruby script run the Homebrew installer too. The bash command for this is :
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
However this doesn't work when called using backticks.
So my question is how do I run another ruby script (which the Homebrew installer is) from within a ruby script?
And more specifically how would I kick off the web based interactive Homebrew installer (well you have to press return at least once) from within a ruby script and for it's output to show in the terminal?
I know that I could rewrite this all as bash script but I'd really rather keep it all within ruby.
Let's decompose what $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" actually does:
download, via curl, the homebrew install ruby file. Since the command is surrounded by $(), it executes the command and passes the output to ruby.
execute the script via Ruby. The -e flag instructs Ruby to execute the script from the command line instead of loading a specified file.
Since we know that it's a ruby script, we can just do the following:
using Net::HTTP or some other ruby library, download, the homebrew install file.
eval() or otherwise execute the homebrew ruby script.
Of course, eval() is dangerous, especially with untrusted input, but you're already essentially running eval on the script anyways with the install command provided.
In script form that would be:
require 'net/http'
homebrew_uri = URI('https://raw.githubusercontent.com/Homebrew/install/master/install')
homebrew_script = Net::HTTP.get(homebrew_uri)
eval(homebrew_script)

"RVM is not a function" error when running in ruby script and irb

I get an error when I run %x{rvm use #myapp} in ruby and irb. The error is "RVM is not a function, selecting rubies with 'rvm use ...' will not work".
Here's what I've tried:
1. the "rvm use #myapp" command works in OSX command prompt (using OSX Mavericks)
2. made sure RVM is the latest version.
3. reloaded RVM check RVM is a function in the command prompt
4. (still fails in irb and ruby's %x{})
5. According to some SO posts, I changed OSX terminal preferences from login shell to /bin/bash and /bin/bash --login. Quit, opened new terminal windows but all efforts were in vain.
6. checked .bash_profile for [[-s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*
Any ideas on how I can get %x{rvm use #myapp} to work in ruby and irb?
What happens here is that the shell you had started ruby or irb with had rvm defined both as a function and added to PATH the function takes precedence in shell and it all worked fine, but when you open ruby or irb it is a new process and it inherits only environment variables which includes PATH and does not inherit functions, additionally running %x{} from ruby creates another shell process which is neither a login or interactive shell, and they respectively would make shell load ~/.bash_profile and ~/.bashrc.
Depending on what do you want to do you have few options, to execute another ruby/gem you can use rvm ... do ... for from %x{} like this:
rvm #myapp do ruby -e '...'
OR:
rvm #myapp do gem install ...
OR:
rvm #myapp do bundle install
it allows single command to run in the context of given ruby
Try this trick:
%x{bash -c 'source "$HOME/.rvm/scripts/rvm"; rvm use #myapp'}
However, you really can use rvm as you've specified because even if you've set up the rvm you then lost your session because your terminal will be closed. Try to setup your environment with session gem, and control bash session with it.
require 'session'
#myapp = 'ruby-1.8.7-p374'
bash = Session::Bash.new
stdout, stderr = bash.execute 'source "$HOME/.rvm/scripts/rvm"'
stdout, stderr = bash.execute "rvm use #{#myapp}"
puts stdout
# => Using /home/malo/.rvm/gems/ruby-1.8.7-p374

Use rvm to force specific Ruby in Xcode Run Script build phase

Outside of Xcode I use a specific version of Ruby, using RVM to manage multiple Ruby installations.
Apple's command line dev tools install Ruby at /usr/bin/ruby and is version 1.8.7.
I use 1.9.3 through RVM.
Is there a way to force Xcode to use my 1.9.3 installation when running its Run Script build phases?
I already tried setting the Shell path to the full path of my specific Ruby, but that didn't seem to make a difference, by which I mean that the particular Gems I have installed in my 1.9.3 weren't available/visible to the script when run within Xcode.
If I run my project through xcodebuild on the command line, the Run Script phase uses my specific Ruby because it's being run from within my shell environment (even if the Shell path in the project file is set to /usr/bin/ruby, it still uses my 1.9.3).
What can I do to make the IDE use my 1.9.3 Ruby install?
I had the same (well, worse) problem, and the code that follows worked for me.
The key thing to realize is that, on the command line, you are using <something>/bin/rvm, but in a shell script, in order for rvm to change that environment, you must use a function, and you must first load that function to your shell script by calling source <something>/scripts/rvm. More on all this here.
This code is also gisted.
#!/usr/bin/env bash
# Xcode scripting does not invoke rvm. To get the correct ruby,
# we must invoke rvm manually. This requires loading the rvm
# *shell function*, which can manipulate the active shell-script
# environment.
# cf. http://rvm.io/workflow/scripting
# Load RVM into a shell session *as a function*
if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
# First try to load from a user install
source "$HOME/.rvm/scripts/rvm"
elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
# Then try to load from a root install
source "/usr/local/rvm/scripts/rvm"
else
printf "ERROR: An RVM installation was not found.\n"
exit 128
fi
# rvm will use the controlling versioning (e.g. .ruby-version) for the
# pwd using this function call.
rvm use .
As a protip, I find embedding shell code in a project.pbxproj file yucky. For all but the most trivial stuff, my actual run script step is usually just a one-line call out to an external script:
Try this at the beginning of your script in Xcode:
source "$HOME/.rvm/scripts/rvm"

Switching rubies in shell script

When executing a bash shell script I am using ruby 1.9.3. Then, within the script, I want to switch to JRub (I'm using rvm). I tried switching to JRuby by doing rvm use jruby within the script, but this didn't work, it said:
RVM is not a function, selecting rubies with 'rvm use ...' will not work.
You need to change your terminal emulator preferences to allow login shell.
Sometimes it is required to use `/bin/bash --login` as the command.
Please visit https://rvm.io/integration/gnome-terminal/ for a example.
./run.sh: line 10: jruby: command not found
When I do type rvm | head -n1 at the command prompt, I get: rvm is a function. So I'm not sure of the problem. I thought it might be because I installed JRuby using sudo (sudo rvm install jruby). So I ran the shell script again using sudo. Again I received the error.
How can I switch rubies from within a bash shell script with rvm?
Thanks
I ended up adding this to the line before:
[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"
as explained in this thread (sorry - though I was aware of this thread before, I didn't quite grasp it):
RVM doesn't switch Rubies
/complete/path/to/rvm your shell seems to have an 'rvm' builtin command defined.

Resources