isolate 1.10.2 → 2.0.0.pre.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ require "rubygems"
2
+ require "rubygems/command"
3
+ require "rubygems/dependency_installer"
4
+ require "rubygems/requirement"
5
+ require "rubygems/version"
6
+
7
+ module Isolate
8
+
9
+ # An isolated Gem, with requirement, environment restrictions, and
10
+ # installation options. Internal use only.
11
+
12
+ class Entry
13
+
14
+ # Which environments does this entry care about? Generally an
15
+ # Array of Strings. An empty array means "all", not "none".
16
+
17
+ attr_reader :environments
18
+
19
+ # What's the name of this entry? Generally the name of a gem.
20
+
21
+ attr_reader :name
22
+
23
+ # Extra information or hints for installation. See +initialize+
24
+ # for well-known keys.
25
+
26
+ attr_reader :options
27
+
28
+ # What version of this entry is required? Expressed as a
29
+ # Gem::Requirement, which see.
30
+
31
+ attr_reader :requirement
32
+
33
+ # Create a new entry. Takes +sandbox+ (currently an instance of
34
+ # Isolate), +name+ (as above), and any number of optional version
35
+ # requirements (generally Strings). Options can be passed as a
36
+ # trailing hash. FIX: document well-known keys.
37
+
38
+ def initialize sandbox, name, *requirements
39
+ @environments = []
40
+ @file = nil
41
+ @name = name
42
+ @options = {}
43
+ @requirement = Gem::Requirement.default
44
+ @sandbox = sandbox
45
+
46
+ if /\.gem$/ =~ @name && File.file?(@name)
47
+ @file = File.expand_path @name
48
+
49
+ @name = File.basename(@file, ".gem").
50
+ gsub(/-#{Gem::Version::VERSION_PATTERN}$/, "")
51
+ end
52
+
53
+ update(*requirements)
54
+ end
55
+
56
+ def activate
57
+ Gem.activate name, *requirement.as_list
58
+ end
59
+
60
+ # Install this entry in the sandbox.
61
+
62
+ def install
63
+ old = Gem.sources.dup
64
+
65
+ begin
66
+ cache = File.join @sandbox.path, "cache"
67
+
68
+ installer = Gem::DependencyInstaller.new :development => false,
69
+ :generate_rdoc => false, :generate_ri => false,
70
+ :install_dir => @sandbox.path
71
+
72
+ Gem.sources += Array(options[:source]) if options[:source]
73
+ Gem::Command.build_args = Array(options[:args]) if options[:args]
74
+
75
+ installer.install @file || name, requirement
76
+ ensure
77
+ Gem.sources = old
78
+ Gem::Command.build_args = nil
79
+ end
80
+ end
81
+
82
+ # Is this entry interested in +environment+?
83
+
84
+ def matches? environment
85
+ environments.empty? || environments.include?(environment)
86
+ end
87
+
88
+ # Is this entry satisfied by +spec+ (generally a
89
+ # Gem::Specification)?
90
+
91
+ def matches_spec? spec
92
+ name == spec.name and requirement.satisfied_by? spec.version
93
+ end
94
+
95
+ # Updates this entry's environments, options, and
96
+ # requirement. Environments and options are merged, requirement is
97
+ # replaced.
98
+
99
+ def update *reqs
100
+ @environments |= @sandbox.environments
101
+ @options.merge! reqs.pop if Hash === reqs.last
102
+ @requirement = Gem::Requirement.new reqs unless reqs.empty?
103
+
104
+ self
105
+ end
106
+ end
107
+ end
data/lib/isolate/now.rb CHANGED
@@ -1,2 +1,4 @@
1
1
  require "isolate"
2
+ require "isolate/rake" if defined?(Rake)
3
+
2
4
  Isolate.now!
data/lib/isolate/rake.rb CHANGED
@@ -1,18 +1,43 @@
1
1
  namespace :isolate do
2
- desc "Generate a .gems manifest for your isolated gems."
3
- task :dotgems, [:env] do |_, args|
4
- env = args.env || Isolate.env
2
+ desc "Show current isolated environment."
3
+ task :debug do
4
+ require "pathname"
5
5
 
6
- File.open ".gems", "wb" do |f|
7
- Isolate.instance.entries.each do |entry|
8
- next unless entry.matches? env
6
+ sandbox = Isolate.sandbox
7
+ here = Pathname Dir.pwd
8
+ path = Pathname(sandbox.path).relative_path_from here
9
+ files = sandbox.files.map { |f| Pathname(f) }
9
10
 
10
- gems = [entry.name]
11
- gems << "--version '#{entry.requirement}'"
12
- gems << "--source #{entry.options[:source]}" if entry.options[:source]
11
+ puts
12
+ puts " sandbox: #{path}"
13
+ puts " env: #{Isolate.env}"
13
14
 
14
- f.puts gems.join(" ")
15
+ files.collect! { |f| f.absolute? ? f.relative_path_from(here) : f }
16
+ puts " files: #{files.join ', '}"
17
+ puts
18
+
19
+ %w(cleanup? enabled? install? multiruby? system? verbose?).each do |flag|
20
+ printf "%10s %s\n", flag, sandbox.send(flag)
21
+ end
22
+
23
+ grouped = Hash.new { |h, k| h[k] = [] }
24
+ sandbox.entries.each { |e| grouped[e.environments] << e }
25
+
26
+ puts
27
+
28
+ grouped.keys.sort.each do |envs|
29
+ title = "all environments" if envs.empty?
30
+ title ||= envs.join ", "
31
+
32
+ puts "[#{title}]"
33
+
34
+ grouped[envs].each do |e|
35
+ gem = "gem #{e.name}, #{e.requirement}"
36
+ gem << ", #{e.options.inspect}" unless e.options.empty?
37
+ puts gem
15
38
  end
39
+
40
+ puts
16
41
  end
17
42
  end
18
43
 
@@ -0,0 +1,256 @@
1
+ require "fileutils"
2
+ require "isolate/entry"
3
+ require "rbconfig"
4
+ require "rubygems/uninstaller"
5
+
6
+ module Isolate
7
+ class Sandbox
8
+ attr_reader :entries # :nodoc:
9
+ attr_reader :environments # :nodoc:
10
+ attr_reader :files # :nodoc:
11
+
12
+ # Create a new Isolate instance. See Isolate.gems for the public
13
+ # API. You probably don't want to use this constructor directly.
14
+
15
+ def initialize options = {}, &block
16
+ @enabled = false
17
+ @entries = []
18
+ @environments = []
19
+ @files = []
20
+ @options = options
21
+
22
+ path options.fetch(:path, "tmp/isolate")
23
+
24
+ file, local = nil
25
+
26
+ unless FalseClass === options[:file]
27
+ file = options[:file] || Dir["{Isolate,config/isolate.rb}"].first
28
+ local = "#{file}.local" if file
29
+ end
30
+
31
+ load file if file
32
+
33
+ if block_given?
34
+ block.to_s =~ /\@([^:]+):/
35
+ files << ($1 || "inline block")
36
+ instance_eval(&block)
37
+ end
38
+
39
+ load local if local && File.exist?(local)
40
+ end
41
+
42
+ # Activate this set of isolated entries, respecting an optional
43
+ # +environment+. Points RubyGems to a separate repository, messes
44
+ # with paths, auto-installs gems (if necessary), activates
45
+ # everything, and removes any superfluous gem (again, if
46
+ # necessary). If +environment+ isn't specified, +ISOLATE_ENV+,
47
+ # +RAILS_ENV+, and +RACK_ENV+ are checked before falling back to
48
+ # <tt>"development"</tt>.
49
+
50
+ def activate environment = nil
51
+ enable unless enabled?
52
+
53
+ env = (environment || Isolate.env).to_s
54
+
55
+ install env if install?
56
+
57
+ entries.each do |e|
58
+ e.activate if e.matches? env
59
+ end
60
+
61
+ cleanup if cleanup?
62
+
63
+ self
64
+ end
65
+
66
+ def cleanup # :nodoc:
67
+ activated = Gem.loaded_specs.values.map { |s| s.full_name }
68
+ available = Gem.source_index.gems.values.sort
69
+
70
+ extra = available.reject do |spec|
71
+ active = activated.include? spec.full_name
72
+ entry = entries.detect { |e| e.matches_spec? spec }
73
+ system = !spec.loaded_from.include?(path)
74
+
75
+ active or entry or system
76
+ end
77
+
78
+ return if extra.empty?
79
+
80
+ padding = Math.log10(extra.size).to_i + 1
81
+ format = "[%0#{padding}d/%s] Nuking %s."
82
+
83
+ extra.each_with_index do |e, i|
84
+ log format % [i + 1, extra.size, e.full_name]
85
+
86
+ Gem::DefaultUserInteraction.use_ui Gem::SilentUI.new do
87
+ Gem::Uninstaller.new(e.name,
88
+ :version => e.version,
89
+ :ignore => true,
90
+ :executables => true,
91
+ :install_dir => path).uninstall
92
+ end
93
+ end
94
+ end
95
+
96
+ def cleanup?
97
+ install? and @options.fetch(:cleanup, true)
98
+ end
99
+
100
+ def disable &block
101
+ return self if not enabled?
102
+
103
+ ENV["GEM_PATH"] = @old_gem_path
104
+ ENV["GEM_HOME"] = @old_gem_home
105
+ ENV["PATH"] = @old_path
106
+ ENV["RUBYOPT"] = @old_ruby_opt
107
+
108
+ $LOAD_PATH.replace @old_load_path
109
+
110
+ @enabled = false
111
+
112
+ Isolate.refresh
113
+ begin; return yield ensure enable end if block_given?
114
+
115
+ self
116
+ end
117
+
118
+ def enable # :nodoc:
119
+ return self if enabled?
120
+
121
+ @old_gem_path = ENV["GEM_PATH"]
122
+ @old_gem_home = ENV["GEM_HOME"]
123
+ @old_path = ENV["PATH"]
124
+ @old_ruby_opt = ENV["RUBYOPT"]
125
+ @old_load_path = $LOAD_PATH.dup
126
+
127
+ FileUtils.mkdir_p path
128
+ ENV["GEM_HOME"] = path
129
+
130
+ unless system?
131
+ $LOAD_PATH.reject! do |p|
132
+ p != File.dirname(__FILE__) &&
133
+ Gem.path.any? { |gp| p.include?(gp) }
134
+ end
135
+
136
+ # HACK: Gotta keep isolate explicitly in the LOAD_PATH in
137
+ # subshells, and the only way I can think of to do that is by
138
+ # abusing RUBYOPT.
139
+
140
+ dirname = Regexp.escape File.dirname(__FILE__)
141
+
142
+ unless ENV["RUBYOPT"] =~ /\s+-I\s*#{dirname}\b/
143
+ ENV["RUBYOPT"] = "#{ENV['RUBYOPT']} -I#{File.dirname(__FILE__)}"
144
+ end
145
+
146
+ ENV["GEM_PATH"] = path
147
+ end
148
+
149
+ bin = File.join path, "bin"
150
+
151
+ unless ENV["PATH"].split(File::PATH_SEPARATOR).include? bin
152
+ ENV["PATH"] = [bin, ENV["PATH"]].join File::PATH_SEPARATOR
153
+ end
154
+
155
+ Isolate.refresh
156
+ Gem.path.unshift path if system?
157
+
158
+ @enabled = true
159
+
160
+ self
161
+ end
162
+
163
+ def enabled?
164
+ @enabled
165
+ end
166
+
167
+ # Restricts +gem+ calls inside +block+ to a set of +environments+.
168
+
169
+ def environment *environments, &block
170
+ old = @environments
171
+ @environments = @environments.dup.concat environments.map { |e| e.to_s }
172
+
173
+ instance_eval(&block)
174
+ ensure
175
+ @environments = old
176
+ end
177
+
178
+ # Express a gem dependency. Works pretty much like RubyGems' +gem+
179
+ # method, but respects +environment+ and doesn't activate 'til
180
+ # later.
181
+
182
+ def gem name, *requirements
183
+ entry = entries.detect { |e| e.name == name }
184
+ return entry.update(*requirements) if entry
185
+
186
+ entries << entry = Entry.new(self, name, *requirements)
187
+ entry
188
+ end
189
+
190
+ def install environment # :nodoc:
191
+ installable = entries.select do |e|
192
+ !Gem.available?(e.name, *e.requirement.as_list) &&
193
+ e.matches?(environment)
194
+ end
195
+
196
+ return self if installable.empty?
197
+
198
+ padding = Math.log10(installable.size).to_i + 1
199
+ format = "[%0#{padding}d/%s] Isolating %s (%s)."
200
+
201
+ installable.each_with_index do |entry, i|
202
+ log format % [i + 1, installable.size, entry.name, entry.requirement]
203
+ entry.install
204
+ end
205
+
206
+ Gem.source_index.refresh!
207
+
208
+ self
209
+ end
210
+
211
+ def install? # :nodoc:
212
+ @options.fetch :install, true
213
+ end
214
+
215
+ def load file # :nodoc:
216
+ files << file
217
+ instance_eval IO.read(file), file, 1
218
+ end
219
+
220
+ def log s # :nodoc:
221
+ $stderr.puts s if verbose?
222
+ end
223
+
224
+
225
+ def multiruby?
226
+ @options.fetch :multiruby, true
227
+ end
228
+ def options options = nil
229
+ @options.merge! options if options
230
+ @options
231
+ end
232
+
233
+ def path path = nil
234
+ if path
235
+ unless @options.key?(:multiruby) && @options[:multiruby] == false
236
+ suffix = RbConfig::CONFIG.
237
+ values_at("ruby_install_name", "ruby_version").join "-"
238
+
239
+ path = File.join(path, suffix) unless path =~ /#{suffix}/
240
+ end
241
+
242
+ @path = File.expand_path path
243
+ end
244
+
245
+ @path
246
+ end
247
+
248
+ def system?
249
+ @options.fetch :system, true
250
+ end
251
+
252
+ def verbose?
253
+ @options.fetch :verbose, true
254
+ end
255
+ end
256
+ end
Binary file
@@ -0,0 +1 @@
1
+ gem "monkey", :args => "--threaten"
@@ -0,0 +1,3 @@
1
+ environment :bar do
2
+ gem "monkey", :args => "--asplode"
3
+ end
@@ -0,0 +1,53 @@
1
+ require "isolate"
2
+ require "minitest/autorun"
3
+
4
+ module Isolate
5
+ class Test < MiniTest::Unit::TestCase
6
+ def setup
7
+ Isolate.refresh
8
+
9
+ @env = ENV.to_hash
10
+ @lp = $LOAD_PATH.dup
11
+ @lf = $LOADED_FEATURES.dup
12
+ end
13
+
14
+ def teardown
15
+ Gem::DependencyInstaller.reset_value
16
+ Gem::Uninstaller.reset_value
17
+
18
+ ENV.replace @env
19
+ $LOAD_PATH.replace @lp
20
+ $LOADED_FEATURES.replace @lf
21
+
22
+ FileUtils.rm_rf "tmp/isolate"
23
+ end
24
+ end
25
+ end
26
+
27
+ module BrutalStub
28
+ @@value = []
29
+ def value; @@value end
30
+ def reset_value; value.clear end
31
+ end
32
+
33
+ class Gem::DependencyInstaller
34
+ extend BrutalStub
35
+
36
+ alias old_install install
37
+ def install name, requirement
38
+ self.class.value << [name, requirement]
39
+ end
40
+ end
41
+
42
+ class Gem::Uninstaller
43
+ extend BrutalStub
44
+
45
+ attr_reader :gem, :version, :gem_home
46
+ alias old_uninstall uninstall
47
+
48
+ def uninstall
49
+ self.class.value << [self.gem,
50
+ self.version.to_s,
51
+ self.gem_home.sub(Dir.pwd + "/", '')]
52
+ end
53
+ end