ruby-virtualenv 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|