gemist 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +23 -0
- data/README.md +173 -0
- data/Rakefile +4 -0
- data/lib/gemist/require.rb +3 -0
- data/lib/gemist/setup.rb +3 -0
- data/lib/gemist.rb +183 -0
- data/test/bogus_test.rb +15 -0
- data/test/fixtures/bogus.gemfile +4 -0
- data/test/fixtures/sample.gemfile +6 -0
- data/test/gemist_test.rb +25 -0
- data/test/test_helper.rb +30 -0
- metadata +67 -0
data/HISTORY.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
v0.0.4 - May 15, 2011
|
2
|
+
---------------------
|
3
|
+
|
4
|
+
### Fixed:
|
5
|
+
* Apps now exit with an exit code when it can't load certain gems
|
6
|
+
|
7
|
+
### Added:
|
8
|
+
* Allow multiple version requirements
|
9
|
+
* Passthru require errors
|
10
|
+
* Show gem loading errors that aren't the usual 'can't load X'
|
11
|
+
|
12
|
+
### Changed:
|
13
|
+
* Use plain old gemspecs instead of joe
|
14
|
+
* Use the more-canonical Gem.activate
|
15
|
+
|
16
|
+
v0.0.3 - May 04, 2011
|
17
|
+
---------------------
|
18
|
+
|
19
|
+
### Fixed:
|
20
|
+
* Fix the missing gems list print.
|
21
|
+
|
22
|
+
### Added:
|
23
|
+
* Allow multiple requires.
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# Gemist
|
2
|
+
#### An extremely minimal solution to gem isolation
|
3
|
+
|
4
|
+
**Bundler + faster + smaller (<4kb) = Gemist**
|
5
|
+
|
6
|
+
You don't need Bundler for gem management. Rubygems can do it already. Let me
|
7
|
+
show you what I mean:
|
8
|
+
|
9
|
+
## Getting started
|
10
|
+
|
11
|
+
Make a file in your project called `Gemfile`.
|
12
|
+
|
13
|
+
``` ruby
|
14
|
+
# Gemfile
|
15
|
+
gem "sinatra"
|
16
|
+
gem "ohm", "0.1.3"
|
17
|
+
|
18
|
+
# These will only be for the development environment
|
19
|
+
group :development do
|
20
|
+
gem "json-pure", require: "json"
|
21
|
+
end
|
22
|
+
|
23
|
+
# You may specify multiple files to be required
|
24
|
+
gem "rails", ">= 3.0", require: ['rails', 'action_controller']
|
25
|
+
```
|
26
|
+
|
27
|
+
In your project file, do this.
|
28
|
+
This `require`s the gems defined in the Gemfile.
|
29
|
+
|
30
|
+
``` ruby
|
31
|
+
require 'gemist'
|
32
|
+
Gemist.require
|
33
|
+
```
|
34
|
+
|
35
|
+
When you run your app, and some gems are not present, a message will show:
|
36
|
+
|
37
|
+
$ ruby init.rb
|
38
|
+
Some gems cannot be loaded. Try:
|
39
|
+
|
40
|
+
gem install ohm -v 0.1.3
|
41
|
+
gem install json-pure
|
42
|
+
|
43
|
+
## How does it work?
|
44
|
+
|
45
|
+
Gemist uses Rubygems to load specific gems. Did you know you can specify a gem
|
46
|
+
version using Rubygems's `Gem.activate`? Gemist is merely a light bridge that
|
47
|
+
does that for you by reading your Gemfile.
|
48
|
+
|
49
|
+
For example, if your project has this Gemfile:
|
50
|
+
|
51
|
+
``` ruby
|
52
|
+
gem "sinatra", "~> 1.2.0", require: "sinatra/base"
|
53
|
+
gem "nokogiri", ">= 1.2"
|
54
|
+
```
|
55
|
+
|
56
|
+
Then you do:
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
Gemist.require
|
60
|
+
````
|
61
|
+
|
62
|
+
All `Gemist.require` does in the background is:
|
63
|
+
|
64
|
+
``` ruby
|
65
|
+
require 'rubygems'
|
66
|
+
|
67
|
+
::Gem.activate "sinatra", "~> 1.2.0"
|
68
|
+
::Gem.activate "nokogiri", ">= 1.2"
|
69
|
+
require "sinatra/base"
|
70
|
+
require "nokogiri"
|
71
|
+
```
|
72
|
+
|
73
|
+
## How to do other things
|
74
|
+
|
75
|
+
Gemist doesn't have some of Bundler's conveniences that Rubygems can already
|
76
|
+
handle.
|
77
|
+
|
78
|
+
### Freezing gem versions
|
79
|
+
|
80
|
+
Gemist doesn't care about your `Gemfile.lock`.
|
81
|
+
|
82
|
+
This means that to ensure your app will work with future gem releases, you
|
83
|
+
should add versions like so (using `~>` is highly recommended):
|
84
|
+
|
85
|
+
``` ruby
|
86
|
+
# Gemfile
|
87
|
+
gem "sinatra", "~> 1.1"
|
88
|
+
```
|
89
|
+
|
90
|
+
If you need a Gemfile.lock for whatever reason, use `bundle update --local`.
|
91
|
+
|
92
|
+
### Vendoring gems
|
93
|
+
|
94
|
+
Gemist does NOT vendor gems for you. Rubygems helps you with that already!
|
95
|
+
|
96
|
+
First, don't specify your vendored gems in your Gemfile.
|
97
|
+
|
98
|
+
Second, freeze your gems like so:
|
99
|
+
|
100
|
+
$ mkdir vendor
|
101
|
+
$ cd vendor
|
102
|
+
$ gem unpack sinatra
|
103
|
+
|
104
|
+
Then load them manually:
|
105
|
+
|
106
|
+
``` ruby
|
107
|
+
# init.rb
|
108
|
+
$:.unshift *Dir['./vendor/*/lib']
|
109
|
+
require 'sinatra/base'
|
110
|
+
```
|
111
|
+
|
112
|
+
### More common usage
|
113
|
+
|
114
|
+
If you prefer to `require` gems individually yourself, use `Gemist.setup`.
|
115
|
+
|
116
|
+
``` ruby
|
117
|
+
require 'gemist'
|
118
|
+
Gemist.setup
|
119
|
+
```
|
120
|
+
|
121
|
+
Alternatively, you may also use the syntactic sugar (does the same thing as
|
122
|
+
above):
|
123
|
+
|
124
|
+
``` ruby
|
125
|
+
require 'gemist/setup'
|
126
|
+
```
|
127
|
+
|
128
|
+
To require gems from a specific group, use `Gemist.require <group>`.
|
129
|
+
(By default, Gemist assumes whatever is in `RACK_ENV`.)
|
130
|
+
|
131
|
+
``` ruby
|
132
|
+
require 'gemist'
|
133
|
+
Gemist.require :development
|
134
|
+
```
|
135
|
+
|
136
|
+
There's also syntactic sugar for `Gemist.require ENV['RACK_ENV']`:
|
137
|
+
|
138
|
+
``` ruby
|
139
|
+
require 'gemist/require'
|
140
|
+
```
|
141
|
+
|
142
|
+
## Benchmarks
|
143
|
+
|
144
|
+
Informal benchmarks with a Gemfile of one of my projects on Ruby 1.9.2:
|
145
|
+
|
146
|
+
``` ruby
|
147
|
+
Benchmark.measure { require 'bundler'; Bundler.require } #=> 2.5s average
|
148
|
+
Benchmark.measure { require 'gemist'; Gemist.require } #=> 1.6s average
|
149
|
+
```
|
150
|
+
|
151
|
+
## Don't use this
|
152
|
+
|
153
|
+
This is merely a proof-of-concept. It works (very well), but:
|
154
|
+
|
155
|
+
1. The world has enough gem management tools, it doesn't need another.
|
156
|
+
|
157
|
+
2. Bundler is better (though it's more bloated and does more things).
|
158
|
+
|
159
|
+
## Not going to happen
|
160
|
+
|
161
|
+
Gemist will never have:
|
162
|
+
|
163
|
+
- **Dependency resolution.**
|
164
|
+
If there are conflicts in your gems's requirements, just manually specify the
|
165
|
+
gem version that will satisfy both. Alternatively, stop using too many gems.
|
166
|
+
|
167
|
+
- **An installer (like 'bundle install').**
|
168
|
+
Seriously, just install the gems yourself! Gemist even gives you the exact
|
169
|
+
command to do it.
|
170
|
+
|
171
|
+
## Authors
|
172
|
+
|
173
|
+
Done by Rico Sta. Cruz and released under the MIT license.
|
data/Rakefile
ADDED
data/lib/gemist/setup.rb
ADDED
data/lib/gemist.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
# Gem environment manager.
|
4
|
+
module Gemist
|
5
|
+
VERSION = "0.0.4"
|
6
|
+
|
7
|
+
def self.version
|
8
|
+
VERSION
|
9
|
+
end
|
10
|
+
|
11
|
+
# Loads the gems via +require+.
|
12
|
+
def self.require(env=ENV['RACK_ENV'])
|
13
|
+
load_rubygems
|
14
|
+
setup env
|
15
|
+
gemfile.gems_for(env).each { |g| g.require! }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Loads the gems for a given environment.
|
19
|
+
def self.setup(env=ENV['RACK_ENV'])
|
20
|
+
@fail = Array.new
|
21
|
+
|
22
|
+
gemfile.gems_for(env).each do |g|
|
23
|
+
g.load! or @fail << g
|
24
|
+
end
|
25
|
+
|
26
|
+
if @fail.any?
|
27
|
+
commands = @fail.map { |g| g.to_command }.compact
|
28
|
+
list = commands.map { |cmd| "gem install #{cmd}" }
|
29
|
+
|
30
|
+
if list.any?
|
31
|
+
$stderr << "Some gems failed to load. Try:\n\n"
|
32
|
+
$stderr << "#{list.join("\n")}\n\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
print_errors_for(@fail)
|
36
|
+
exit 256
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the Gemfile for the current project.
|
41
|
+
def self.gemfile
|
42
|
+
@@gemfile ||= Gemfile.load
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
# Prints errors for failed gems
|
47
|
+
def self.print_errors_for(gems)
|
48
|
+
# Remove those
|
49
|
+
gems = gems.reject { |g| g.error.name == g.name }
|
50
|
+
|
51
|
+
if gems.any?
|
52
|
+
$stderr << "These errors occured:\n"
|
53
|
+
gems.each { |gem| $stderr << " [#{gem.name}] #{gem.error.to_s}\n" }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Loads rubygems. Skips if it's not needed (like in Ruby 1.9)
|
58
|
+
def self.load_rubygems
|
59
|
+
Kernel.require 'rubygems' unless Object.const_defined?(:Gem)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# A definition of a project Gemfile manifest.
|
64
|
+
class Gemist::Gemfile
|
65
|
+
# Returns the path of the project's Gemfile manifest, or +nil+ if
|
66
|
+
# not available.
|
67
|
+
def self.path
|
68
|
+
%w(GEMFILE BUNDLER_GEMFILE).each do |spec|
|
69
|
+
return ENV[spec] if ENV[spec] && File.exists?(ENV[spec])
|
70
|
+
end
|
71
|
+
|
72
|
+
Dir["./{Gemistfile,Gemfile,Isolate}"].first
|
73
|
+
end
|
74
|
+
|
75
|
+
# Checks if the project has a Gemfile manifest.
|
76
|
+
def self.exists?
|
77
|
+
!!path
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a Gemfile instance made from the project's manifest.
|
81
|
+
def self.load
|
82
|
+
new File.read(path) if exists?
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(contents)
|
86
|
+
instance_eval contents
|
87
|
+
end
|
88
|
+
|
89
|
+
# The list of gems the Gemfile. Returns an array of Gem instances.
|
90
|
+
def gems()
|
91
|
+
@gems ||= Array.new
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a list of Gem instances for the given environment.
|
95
|
+
def gems_for(env)
|
96
|
+
gems.select { |g| g.group == nil || g.group.include?(env.to_s.to_sym) }
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
# (DSL) Adds a gem.
|
101
|
+
#
|
102
|
+
# == Example
|
103
|
+
#
|
104
|
+
# # Gemfile
|
105
|
+
# gem "sinatra"
|
106
|
+
# gem "sinatra", "1.1"
|
107
|
+
# gem "sinatra", "1.1", :require => "sinatra/base"
|
108
|
+
#
|
109
|
+
def gem(name, *args)
|
110
|
+
options = args.last.is_a?(Hash) ? args.pop : Hash.new
|
111
|
+
|
112
|
+
options[:name] ||= name
|
113
|
+
options[:version] ||= args
|
114
|
+
options[:group] ||= @group
|
115
|
+
|
116
|
+
self.gems << Gemist::Gem.new(options)
|
117
|
+
end
|
118
|
+
|
119
|
+
# (DSL) Defines a group.
|
120
|
+
#
|
121
|
+
# == Example
|
122
|
+
#
|
123
|
+
# # Gemfile
|
124
|
+
# group :test do
|
125
|
+
# gem "capybara"
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
def group(*names, &blk)
|
129
|
+
@group = names.map { |s| s.to_sym }
|
130
|
+
yield
|
131
|
+
@group = nil
|
132
|
+
end
|
133
|
+
|
134
|
+
# Does nothing. Here for Bundler compatibility.
|
135
|
+
def source(src)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# A Gem in the gemfile.
|
140
|
+
class Gemist::Gem
|
141
|
+
attr_accessor :name
|
142
|
+
attr_accessor :versions
|
143
|
+
attr_accessor :require
|
144
|
+
attr_accessor :group
|
145
|
+
attr_reader :error
|
146
|
+
|
147
|
+
def initialize(options)
|
148
|
+
self.name ||= options[:name]
|
149
|
+
self.versions ||= options[:version]
|
150
|
+
self.group ||= options[:group]
|
151
|
+
self.require ||= options[:require] || self.name
|
152
|
+
end
|
153
|
+
|
154
|
+
# Activates the gem; returns +false+ if it's not available.
|
155
|
+
def load!
|
156
|
+
::Gem.activate name, *versions
|
157
|
+
true
|
158
|
+
rescue ::Gem::LoadError => e
|
159
|
+
@error = e
|
160
|
+
false
|
161
|
+
end
|
162
|
+
|
163
|
+
# Loads the gem via +require+. Make sure you load! it first.
|
164
|
+
# Returns true if loaded.
|
165
|
+
def require!
|
166
|
+
[*require].each { |r| Kernel.require r }
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns the +gem install+ paramaters needed to install the gem.
|
170
|
+
def to_command
|
171
|
+
if error
|
172
|
+
[error.name, *version_join(error.requirement.to_s.split(', '))].join(' ') if error.name
|
173
|
+
else
|
174
|
+
[name, version_join(versions)].compact.join ' '
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def version_join(vers)
|
180
|
+
versions = [*vers].sort.map { |v| "-v #{v.to_s.inspect}" unless v.to_s == '>= 0' }.compact
|
181
|
+
versions.join(' ') unless versions.empty?
|
182
|
+
end
|
183
|
+
end
|
data/test/bogus_test.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class BogusTest < Test::Unit::TestCase
|
4
|
+
test "bogus - print requirements" do
|
5
|
+
Gemist.expects(:exit).returns(true)
|
6
|
+
|
7
|
+
use_gemfile 'bogus.gemfile'
|
8
|
+
|
9
|
+
Gemist.setup
|
10
|
+
assert err.include?("gem install xyzzyabc\n")
|
11
|
+
assert err.include?("gem install aoeuidhtns\n")
|
12
|
+
assert err.include?("gem install pyfgcrl -v \">= 3.0\"\n")
|
13
|
+
assert err.include?("gem install qjkxbmwvz -v \"<= 4.0\" -v \">= 3.0\"\n")
|
14
|
+
end
|
15
|
+
end
|
data/test/gemist_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class GemistTest < Test::Unit::TestCase
|
4
|
+
setup do
|
5
|
+
Gem.expects(:activate).with('sinatra', '>= 1.0', '<= 1.3').returns(true)
|
6
|
+
Gem.expects(:activate).with('yard').returns(true)
|
7
|
+
Gem.expects(:activate).with('test-unit', '~> 0.5').returns(true)
|
8
|
+
Gem.expects(:activate).with('ffaker', '>= 1.0', '<= 1.3').returns(true)
|
9
|
+
|
10
|
+
use_gemfile 'sample.gemfile'
|
11
|
+
end
|
12
|
+
|
13
|
+
test "sample - setup" do
|
14
|
+
Gemist.setup
|
15
|
+
end
|
16
|
+
|
17
|
+
test "sample - require" do
|
18
|
+
Kernel.expects(:require).with('sinatra').returns(true)
|
19
|
+
Kernel.expects(:require).with('yard').returns(true)
|
20
|
+
Kernel.expects(:require).with('test/unit').returns(true)
|
21
|
+
Kernel.expects(:require).with('faker').returns(true)
|
22
|
+
|
23
|
+
Gemist.require
|
24
|
+
end
|
25
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'contest'
|
3
|
+
require 'mocha'
|
4
|
+
|
5
|
+
require File.expand_path('../../lib/gemist', __FILE__)
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
setup do
|
9
|
+
$_stderr = $stderr
|
10
|
+
$stderr = StringIO.new
|
11
|
+
end
|
12
|
+
|
13
|
+
teardown do
|
14
|
+
ENV.delete 'GEMFILE'
|
15
|
+
$stderr = $_stderr
|
16
|
+
end
|
17
|
+
|
18
|
+
def err
|
19
|
+
$stderr.string
|
20
|
+
end
|
21
|
+
|
22
|
+
def fixture(path)
|
23
|
+
File.expand_path("../fixtures/#{path}", __FILE__)
|
24
|
+
end
|
25
|
+
|
26
|
+
def use_gemfile(what)
|
27
|
+
ENV['GEMFILE'] = fixture(what)
|
28
|
+
Gemist.class_variable_set :@@gemfile, nil
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gemist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.4
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rico Sta. Cruz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-15 00:00:00 +08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Gemist leverages on purely Rubygems to require the correct gem versions in a project.
|
18
|
+
email:
|
19
|
+
- rico@sinefunc.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- lib/gemist/require.rb
|
28
|
+
- lib/gemist/setup.rb
|
29
|
+
- lib/gemist.rb
|
30
|
+
- test/bogus_test.rb
|
31
|
+
- test/fixtures/bogus.gemfile
|
32
|
+
- test/fixtures/sample.gemfile
|
33
|
+
- test/gemist_test.rb
|
34
|
+
- test/test_helper.rb
|
35
|
+
- HISTORY.md
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/rstacruz/gemist
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.6.2
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: An extremely minimal solution to gem isolation
|
66
|
+
test_files: []
|
67
|
+
|