ruby-virtualenv 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +13 -0
- data/.gitignore +6 -0
- data/CHANGELOG +17 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +28 -0
- data/README.md +120 -0
- data/Rakefile +2 -0
- data/TODO +68 -0
- data/bin/ruby-virtualenv +6 -0
- data/features/development.feature +13 -0
- data/features/steps/common.rb +174 -0
- data/features/steps/env.rb +10 -0
- data/lib/sandbox.rb +41 -0
- data/lib/sandbox/cli.rb +173 -0
- data/lib/sandbox/errors.rb +38 -0
- data/lib/sandbox/installer.rb +175 -0
- data/lib/sandbox/output.rb +25 -0
- data/lib/sandbox/templates/activate.erb +97 -0
- data/lib/sandbox/templates/gemrc.erb +7 -0
- data/lib/sandbox/version.rb +3 -0
- data/ruby-virtualenv.gemspec +20 -0
- data/spec/sandbox/cli_spec.rb +234 -0
- data/spec/sandbox/errors_spec.rb +32 -0
- data/spec/sandbox/installer_spec.rb +303 -0
- data/spec/sandbox/output_spec.rb +145 -0
- data/spec/sandbox_spec.rb +25 -0
- data/spec/spec_helper.rb +46 -0
- metadata +106 -0
data/lib/sandbox.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless \
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
module Sandbox
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def verbosity
|
8
|
+
@verbosity ||= 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def increase_verbosity
|
12
|
+
@verbosity = verbosity + 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def decrease_verbosity
|
16
|
+
@verbosity = verbosity - 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def quiet?
|
20
|
+
verbosity < 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def really_quiet?
|
24
|
+
verbosity < -1
|
25
|
+
end
|
26
|
+
|
27
|
+
def verbose?
|
28
|
+
verbosity > 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def really_verbose?
|
32
|
+
verbosity > 1
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
require 'sandbox/version'
|
39
|
+
require 'sandbox/errors'
|
40
|
+
require 'sandbox/output'
|
41
|
+
require 'sandbox/installer'
|
data/lib/sandbox/cli.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'sandbox'
|
3
|
+
|
4
|
+
module Sandbox
|
5
|
+
class CLI
|
6
|
+
include Sandbox::Output
|
7
|
+
extend Sandbox::Output
|
8
|
+
|
9
|
+
DEFAULTS = {
|
10
|
+
:gems => []
|
11
|
+
}
|
12
|
+
|
13
|
+
## CLASS METHODS
|
14
|
+
class << self
|
15
|
+
|
16
|
+
# invokes sandbox via command-line ARGV as the options
|
17
|
+
def execute(args = ARGV)
|
18
|
+
verify_environment!
|
19
|
+
parse(args).execute!
|
20
|
+
rescue Exception => error
|
21
|
+
handle_error(error)
|
22
|
+
end
|
23
|
+
|
24
|
+
# returns a new CLI instance which has parsed the given arguments.
|
25
|
+
# will load the command to execute based upon the arguements.
|
26
|
+
# if an error occurs, it will print out simple error message and exit.
|
27
|
+
def parse(args)
|
28
|
+
cli = new
|
29
|
+
cli.parse_args!(args)
|
30
|
+
cli
|
31
|
+
end
|
32
|
+
|
33
|
+
def verify_environment!
|
34
|
+
raise LoadedSandboxError if ENV['SANDBOX']
|
35
|
+
end
|
36
|
+
|
37
|
+
# pretty error handling
|
38
|
+
def handle_error(error)
|
39
|
+
case error
|
40
|
+
when Sandbox::Error
|
41
|
+
tell_unless_really_quiet(error.message)
|
42
|
+
when StandardError #, Timeout::Error
|
43
|
+
tell_unless_really_quiet("Error: #{error.message}")
|
44
|
+
tell_when_really_verbose(error.backtrace.collect { |bt| " #{bt}" }.join( "\n" )) if error.backtrace
|
45
|
+
when Interrupt
|
46
|
+
tell_unless_really_quiet("Interrupted")
|
47
|
+
else
|
48
|
+
raise error
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
## END CLASS METHODS
|
54
|
+
|
55
|
+
## PUBLIC INSTANCE METHODS
|
56
|
+
public
|
57
|
+
|
58
|
+
# The options for this execution.
|
59
|
+
attr_reader :options
|
60
|
+
|
61
|
+
# setup of a new CLI instance
|
62
|
+
def initialize
|
63
|
+
@options = DEFAULTS.dup
|
64
|
+
@parser = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# perform the sandbox creation
|
68
|
+
def execute!
|
69
|
+
targets = options.delete(:args)
|
70
|
+
|
71
|
+
if targets.size < 1
|
72
|
+
raise Sandbox::Error.new('no target specified - see `ruby-virtualenv --help` for assistance')
|
73
|
+
elsif targets.size > 1
|
74
|
+
raise Sandbox::Error.new('multiple targets specified - see `ruby-virtualenv --help` for assistance')
|
75
|
+
end
|
76
|
+
|
77
|
+
options[ :target ] = targets[0]
|
78
|
+
|
79
|
+
Sandbox::Installer.new(options).populate
|
80
|
+
end
|
81
|
+
|
82
|
+
# processes +args+ to:
|
83
|
+
#
|
84
|
+
# * load global option for the application
|
85
|
+
# * determine command name to lookup in CommandManager
|
86
|
+
# * load command and have it process any add't options
|
87
|
+
# * catches exceptions for unknown switches or commands
|
88
|
+
def parse_args!( args )
|
89
|
+
options[:original_args] = args.dup
|
90
|
+
parser.parse!(args)
|
91
|
+
rescue OptionParser::ParseError => ex
|
92
|
+
raise_parse_error(ex.reason, ex.args)
|
93
|
+
else
|
94
|
+
options[:args] = args
|
95
|
+
end
|
96
|
+
|
97
|
+
def parser
|
98
|
+
@parser ||= create_parser
|
99
|
+
end
|
100
|
+
|
101
|
+
def create_parser
|
102
|
+
OptionParser.new do |o|
|
103
|
+
o.set_summary_indent(' ')
|
104
|
+
o.program_name = 'ruby-virtualenv TARGET'
|
105
|
+
o.define_head "Create virtual ruby/rubygems environments."
|
106
|
+
o.separator ""
|
107
|
+
|
108
|
+
o.separator "ARGUMENTS:"
|
109
|
+
o.separator " TARGET Target path to new virtualenv. Must not exist beforehand."
|
110
|
+
o.separator ""
|
111
|
+
|
112
|
+
o.separator "OPTIONS"
|
113
|
+
o.on('-g', '--gems gem1,gem2', Array, 'Gems to install after virtualenv is created.') { |gems| @options[:gems] = gems }
|
114
|
+
o.on('-n', '--no-gems', 'Do not install any gems after virtualenv is created.') { @options[:gems] = [] }
|
115
|
+
o.on('-q', '--quiet', 'Show less output. (multiple allowed)') { |f| Sandbox.decrease_verbosity }
|
116
|
+
o.on('-v', '--verbose', 'Show more output. (multiple allowed)') { |f| Sandbox.increase_verbosity }
|
117
|
+
o.on_tail('-h', '--help', 'Show this help message and exit.') { tell_unless_really_quiet( o ); exit }
|
118
|
+
o.on_tail('-H', '--long-help', 'Show the full description about the program.') { tell_unless_really_quiet( long_help ); exit }
|
119
|
+
o.on_tail('-V', '--version', 'Display the program version and exit.' ) { tell_unless_really_quiet( Sandbox::VERSION ); exit }
|
120
|
+
o.separator ""
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def long_help
|
125
|
+
<<-HELP
|
126
|
+
Sandbox is a utility to create sandboxed Ruby/Rubygems environments.
|
127
|
+
|
128
|
+
It is meant to address the following issues:
|
129
|
+
|
130
|
+
1. Conflicts with unspecified gem dependency versions.
|
131
|
+
2. Applications can have their own gem repositories.
|
132
|
+
3. Permissions for installing your own gems.
|
133
|
+
4. Ability to try gems out without installing into your global repository.
|
134
|
+
5. A Simple way to enable this.
|
135
|
+
|
136
|
+
Running from your own gem repositories is fairly straight-forward, but
|
137
|
+
managing the necessary environment is a pain. This utility will create a new
|
138
|
+
environment which may be activated by the script `bin/activate` in
|
139
|
+
your sandbox directory.
|
140
|
+
|
141
|
+
Run the script with the following to enable your new environment:
|
142
|
+
|
143
|
+
$ source bin/activate
|
144
|
+
|
145
|
+
When you want to leave the environment:
|
146
|
+
|
147
|
+
$ deactivate
|
148
|
+
|
149
|
+
NOTES:
|
150
|
+
|
151
|
+
1. It creates an environment that has its own installation directory for Gems.
|
152
|
+
2. It doesn't share gems with other sandbox environments.
|
153
|
+
3. It (optionally) doesn't use the globally installed gems either.
|
154
|
+
4. It will use a local to the sandbox .gemrc file
|
155
|
+
|
156
|
+
WARNINGS:
|
157
|
+
|
158
|
+
Activating your sandbox environment will change your HOME directory
|
159
|
+
temporarily to the sandbox directory. Other environment variables are set to
|
160
|
+
enable this funtionality, so if you may experience odd behavior. Everything
|
161
|
+
should be reset when you deactivate the virtualenv.
|
162
|
+
|
163
|
+
HELP
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def raise_parse_error(reason, args=[])
|
169
|
+
raise Sandbox::ParseError.new(reason, args)
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Sandbox
|
2
|
+
class Error < StandardError
|
3
|
+
|
4
|
+
def initialize(msg=nil)
|
5
|
+
super(msg)
|
6
|
+
end
|
7
|
+
|
8
|
+
def message
|
9
|
+
out = ["Sandbox error: #{super}"]
|
10
|
+
out.concat(backtrace.collect { |bt| " #{bt}" }) if Sandbox.really_verbose?
|
11
|
+
out.join("\n")
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class LoadedSandboxError < Sandbox::Error
|
17
|
+
def initialize(msg="You cannot run sandbox from a loaded sandbox environment")
|
18
|
+
super(msg)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ParseError < Sandbox::Error
|
23
|
+
|
24
|
+
def initialize(reason=nil, args=[])
|
25
|
+
msg = if args.is_a?(Array) && args.size > 0
|
26
|
+
"#{reason} => #{args.join( ' ' )}"
|
27
|
+
elsif args.is_a?(String) && args.length > 0
|
28
|
+
"#{reason} => #{args}"
|
29
|
+
else
|
30
|
+
reason
|
31
|
+
end
|
32
|
+
|
33
|
+
super(msg)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Sandbox
|
5
|
+
class Installer
|
6
|
+
include Sandbox::Output
|
7
|
+
extend Sandbox::Output
|
8
|
+
|
9
|
+
class << self
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :options
|
13
|
+
|
14
|
+
def initialize(options={})
|
15
|
+
@options = options.dup
|
16
|
+
@target = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def target
|
20
|
+
return @target unless @target.nil?
|
21
|
+
@target = resolve_target(options[:target])
|
22
|
+
end
|
23
|
+
|
24
|
+
def populate
|
25
|
+
tell("creating sandbox at: #{target}")
|
26
|
+
create_directories
|
27
|
+
tell("installing activation script")
|
28
|
+
install_scripts
|
29
|
+
tell("installing .gemrc")
|
30
|
+
install_gemrc
|
31
|
+
tell("installing gems")
|
32
|
+
install_gems
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Create folders:
|
37
|
+
#
|
38
|
+
# mkdir -p /path/to/sandbox/rubygems/bin
|
39
|
+
#
|
40
|
+
# Symlink the bin directory, because when gems are installed, binaries
|
41
|
+
# are installed in GEM_HOME/bin:
|
42
|
+
#
|
43
|
+
# $ ln -s /path/to/sandbox/rubygems/bin /path/to/sandbox/bin
|
44
|
+
#
|
45
|
+
def create_directories
|
46
|
+
gembin = File.join(target, 'rubygems', 'bin')
|
47
|
+
FileUtils.mkdir_p(gembin)
|
48
|
+
|
49
|
+
bin = File.join(target, 'bin')
|
50
|
+
FileUtils.ln_s( gembin, bin )
|
51
|
+
end
|
52
|
+
|
53
|
+
def install_gemrc
|
54
|
+
filename = File.join(target, '.gemrc')
|
55
|
+
template = File.read(File.dirname( __FILE__ ) + '/templates/gemrc.erb')
|
56
|
+
script = ERB.new(template)
|
57
|
+
output = script.result(binding)
|
58
|
+
|
59
|
+
File.open(filename, 'w') do |f|
|
60
|
+
f.write output
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def install_scripts
|
65
|
+
filename = File.join(target, 'bin', 'activate')
|
66
|
+
template = File.read(File.dirname( __FILE__ ) + '/templates/activate.erb')
|
67
|
+
script = ERB.new(template)
|
68
|
+
output = script.result(binding)
|
69
|
+
|
70
|
+
File.open(filename, 'w') do |f|
|
71
|
+
f.write output
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def install_gems
|
76
|
+
# gem = `which gem`.chomp
|
77
|
+
# return if gem.empty?
|
78
|
+
gems = options[ :gems ] || []
|
79
|
+
if gems.size == 0
|
80
|
+
tell( " nothing to install" )
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
begin
|
85
|
+
setup_sandbox_env
|
86
|
+
gems.each do |gem|
|
87
|
+
tell_unless_really_quiet( " gem: #{gem}" )
|
88
|
+
cmd = "gem install #{gem}"
|
89
|
+
# cmd = cmd + ' -V' if Sandbox.really_verbose?
|
90
|
+
status, output = shell_out( cmd )
|
91
|
+
unless status
|
92
|
+
tell_unless_really_quiet( " failed to install gem: #{gem}" )
|
93
|
+
end
|
94
|
+
end
|
95
|
+
ensure
|
96
|
+
restore_sandbox_env
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def shell_out( cmd )
|
101
|
+
# err_capture = Sandbox.really_verbose? '2>&1' : '2>/dev/null'
|
102
|
+
# out = `#{cmd} #{err_capture}`
|
103
|
+
out = `#{cmd} 2>/dev/null`
|
104
|
+
result = $?.exitstatus == 0
|
105
|
+
[ result, out ]
|
106
|
+
end
|
107
|
+
|
108
|
+
def setup_sandbox_env
|
109
|
+
@old_env = Hash[ *ENV.select { |k,v| ['HOME','GEM_HOME','GEM_PATH'].include?( k ) }.flatten ]
|
110
|
+
# @old_env = {}
|
111
|
+
# @old_env[ 'HOME' ] = ENV[ 'HOME' ]
|
112
|
+
# @old_env[ 'GEM_HOME' ] = ENV[ 'GEM_HOME' ]
|
113
|
+
# @old_env[ 'GEM_PATH' ] = ENV[ 'GEM_PATH' ]
|
114
|
+
|
115
|
+
ENV[ 'HOME' ] = target
|
116
|
+
ENV[ 'GEM_HOME' ] = "#{target}/rubygems"
|
117
|
+
ENV[ 'GEM_PATH' ] = "#{target}/rubygems"
|
118
|
+
end
|
119
|
+
|
120
|
+
def restore_sandbox_env
|
121
|
+
# ENV.update( @old_env )
|
122
|
+
ENV[ 'HOME' ] = @old_env[ 'HOME' ]
|
123
|
+
ENV[ 'GEM_HOME' ] = @old_env[ 'GEM_HOME' ]
|
124
|
+
ENV[ 'GEM_PATH' ] = @old_env[ 'GEM_PATH' ]
|
125
|
+
end
|
126
|
+
|
127
|
+
def resolve_target( path )
|
128
|
+
# should consider replacing with 'pathname' => Pathname.new( path )
|
129
|
+
path = fix_path( path )
|
130
|
+
if File.exists?( path )
|
131
|
+
raise Sandbox::Error, "target '#{path}' exists"
|
132
|
+
end
|
133
|
+
|
134
|
+
base = path
|
135
|
+
while base = File.dirname( base )
|
136
|
+
if check_path!( base )
|
137
|
+
break
|
138
|
+
elsif base == '/'
|
139
|
+
raise "something is seriously wrong; we should never get here"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
return path
|
143
|
+
end
|
144
|
+
|
145
|
+
def check_path!( path )
|
146
|
+
if File.directory?( path )
|
147
|
+
if File.writable?( path )
|
148
|
+
return true
|
149
|
+
else
|
150
|
+
raise Sandbox::Error, "path '#{path}' has a permission problem"
|
151
|
+
end
|
152
|
+
elsif File.exists?( path )
|
153
|
+
raise Sandbox::Error, "path '#{path}' is not a directory"
|
154
|
+
end
|
155
|
+
false
|
156
|
+
end
|
157
|
+
|
158
|
+
def fix_path( path )
|
159
|
+
unless path.index( '/' ) == 0
|
160
|
+
path = File.join( FileUtils.pwd, path )
|
161
|
+
end
|
162
|
+
path
|
163
|
+
end
|
164
|
+
|
165
|
+
## END PUBLIC INSTANCE METHODS
|
166
|
+
|
167
|
+
|
168
|
+
## PRIVATE INSTANCE METHODS
|
169
|
+
private
|
170
|
+
|
171
|
+
## END PRIVATE INSTANCE METHODS
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sandbox
|
2
|
+
module Output
|
3
|
+
|
4
|
+
def tell(msg)
|
5
|
+
tell_unless_quiet(msg)
|
6
|
+
end
|
7
|
+
|
8
|
+
def tell_when_verbose(msg)
|
9
|
+
puts msg if Sandbox.verbose?
|
10
|
+
end
|
11
|
+
|
12
|
+
def tell_when_really_verbose(msg)
|
13
|
+
puts msg if Sandbox.really_verbose?
|
14
|
+
end
|
15
|
+
|
16
|
+
def tell_unless_quiet(msg)
|
17
|
+
puts msg unless Sandbox.quiet?
|
18
|
+
end
|
19
|
+
|
20
|
+
def tell_unless_really_quiet(msg)
|
21
|
+
puts msg unless Sandbox.really_quiet?
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|