isolate 1.10.2 → 2.0.0.pre.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.
@@ -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