appbundler 0.7.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f04956c11e697c74391c55957f9ce53d481ad9af
4
- data.tar.gz: 7d66500341fb6b45015bbfea43998925dc600a7a
3
+ metadata.gz: 3bdcdbca4e10ef8f04ec364fad2250690e062992
4
+ data.tar.gz: 4c0408acd0c32ab33376397d78616c1a50dcca7a
5
5
  SHA512:
6
- metadata.gz: e59472334d62a6dd82717759c893c81fca8e83c89aea5fc9b3400f10a02b6da6fe6e46274e0d5a85533ede1bf07a2e2b2499df6e16405d52d5da33f9b1ddab01
7
- data.tar.gz: f298914a231339920417481a451f795358e32079056d1ec6acb52994d3412ca1e740fa467675bdd1576910cfeff175de14c1ed7ef5b09072a9ec16b2aea42b4a
6
+ metadata.gz: 144df4d360bfebafd5b99730dfc428d7ecc409efcdcd54bb68c0f7c1624eeb913be17a575ee83aff3960d5aacace7f409c2593a11481932172abe75f0184eaba
7
+ data.tar.gz: 625f02ebeefb40ee421047c6c406f248b5d35bbe7ad7f43cd2711a8963cbf816ffe806acd1e47c74c2bf0ef5ded3816e711f6c5ae762ff4ebf5c51558d1d2bc0
data/.gitignore CHANGED
@@ -1,6 +1,8 @@
1
1
  *.gem
2
2
  *.rbc
3
3
  /.bundle
4
+ b
5
+ binstubs
4
6
  .config
5
7
  .yardoc
6
8
  Gemfile.lock
@@ -14,4 +16,4 @@ rdoc
14
16
  spec/reports
15
17
  test/tmp
16
18
  test/version_tmp
17
- tmp
19
+ tmp
@@ -2,30 +2,33 @@ require 'bundler'
2
2
  require 'fileutils'
3
3
  require 'pp'
4
4
 
5
+
5
6
  module Appbundler
7
+
8
+ class AppbundlerError < StandardError; end
9
+
10
+ class InaccessibleGemsInLockfile < AppbundlerError; end
11
+
6
12
  class App
7
13
 
8
14
  BINSTUB_FILE_VERSION=1
9
15
 
10
- attr_reader :app_root
16
+ attr_reader :bundle_path
11
17
  attr_reader :target_bin_dir
18
+ attr_reader :name
12
19
 
13
- def self.demo
14
- demo = new("/Users/ddeleo/oc/chef")
15
-
16
- knife = demo.executables.grep(/knife/).first
17
- puts demo.binstub(knife)
18
- end
19
-
20
- def initialize(app_root, target_bin_dir)
21
- @app_root = app_root
20
+ def initialize(bundle_path, target_bin_dir, name)
21
+ @bundle_path = bundle_path
22
22
  @target_bin_dir = target_bin_dir
23
+ @name = name
23
24
  end
24
25
 
25
26
  # Copy over any .bundler and Gemfile.lock files to the target gem
26
27
  # directory. This will let us run tests from under that directory.
27
28
  def copy_bundler_env
28
29
  gem_path = app_gemspec.gem_dir
30
+ # If we're already using that directory, don't copy (it won't work anyway)
31
+ return if gem_path == File.dirname(gemfile_lock)
29
32
  FileUtils.install(gemfile_lock, gem_path, :mode => 0644)
30
33
  if File.exist?(dot_bundle_dir) && File.directory?(dot_bundle_dir)
31
34
  FileUtils.cp_r(dot_bundle_dir, gem_path)
@@ -55,16 +58,12 @@ module Appbundler
55
58
  executables_to_create
56
59
  end
57
60
 
58
- def name
59
- File.basename(app_root)
60
- end
61
-
62
61
  def dot_bundle_dir
63
- File.join(app_root, ".bundle")
62
+ File.join(bundle_path, ".bundle")
64
63
  end
65
64
 
66
65
  def gemfile_lock
67
- File.join(app_root, "Gemfile.lock")
66
+ File.join(bundle_path, "Gemfile.lock")
68
67
  end
69
68
 
70
69
  def ruby
@@ -118,6 +117,7 @@ E
118
117
  end
119
118
 
120
119
  def runtime_activate
120
+
121
121
  @runtime_activate ||= begin
122
122
  statements = runtime_dep_specs.map {|s| %Q|gem "#{s.name}", "= #{s.version}"|}
123
123
  activate_code = ""
@@ -173,8 +173,69 @@ E
173
173
  @parsed_gemfile_lock ||= Bundler::LockfileParser.new(IO.read(gemfile_lock))
174
174
  end
175
175
 
176
+ # Bundler stores gems loaded from git in locations like this:
177
+ # `lib/ruby/gems/2.1.0/bundler/gems/chef-b5860b44acdd`. Rubygems cannot
178
+ # find these during normal (non-bundler) operation. This will cause
179
+ # problems if there is no gem of the same version installed to the "normal"
180
+ # gem location, because the appbundler executable will end up containing a
181
+ # statement like `gem "foo", "= x.y.z"` which fails.
182
+ #
183
+ # However, if this gem/version has been manually installed (by building and
184
+ # installing via `gem` commands), then we end up with the correct
185
+ # appbundler file, even if it happens somewhat by accident.
186
+ #
187
+ # Therefore, this method lists all the git-sourced gems in the
188
+ # Gemfile.lock, then it checks if that version of the gem can be loaded via
189
+ # `Gem::Specification.find_by_name`. If there are any unloadable gems, then
190
+ # the InaccessibleGemsInLockfile error is raised.
191
+ def verify_deps_are_accessible!
192
+ inaccessable_gems = inaccessable_git_sourced_gems
193
+ return true if inaccessable_gems.empty?
194
+
195
+ message = <<-MESSAGE
196
+ Application '#{name}' contains gems in the lockfile which are
197
+ not accessible by rubygems. This usually occurs when you fetch gems from git in
198
+ your Gemfile and do not install the same version of the gems beforehand.
199
+
200
+ MESSAGE
201
+
202
+ message << "The Gemfile.lock is located here:\n- #{gemfile_lock}\n\n"
203
+
204
+ message << "The offending gems are:\n"
205
+ inaccessable_gems.each do |gemspec|
206
+ message << "- #{gemspec.name} (#{gemspec.version}) from #{gemspec.source}\n"
207
+ end
208
+
209
+ message << "\n"
210
+
211
+ message << "Rubygems is configured to search the following paths:\n"
212
+ Gem.paths.path.each { |p| message << "- #{p}\n" }
213
+
214
+ message << "\n"
215
+ message << "If these seem wrong, you might need to set GEM_HOME or other environment\nvariables before running appbundler\n"
216
+
217
+ raise InaccessibleGemsInLockfile, message
218
+ end
219
+
176
220
  private
177
221
 
222
+ def git_sourced_gems
223
+ runtime_dep_specs.select { |i| i.source.kind_of?(Bundler::Source::Git) }
224
+ end
225
+
226
+ def inaccessable_git_sourced_gems
227
+ git_sourced_gems.reject do |spec|
228
+ gem_available?(spec)
229
+ end
230
+ end
231
+
232
+ def gem_available?(spec)
233
+ Gem::Specification.find_by_name(spec.name, "= #{spec.version}")
234
+ true
235
+ rescue Gem::LoadError
236
+ false
237
+ end
238
+
178
239
  def add_dependencies_from(spec, collected_deps=[])
179
240
  spec.dependencies.each do |dep|
180
241
  next if collected_deps.any? {|s| s.name == dep.name }
@@ -7,10 +7,14 @@ module Appbundler
7
7
  include Mixlib::CLI
8
8
 
9
9
  banner(<<-BANNER)
10
- Usage: appbundler APPLICATION_DIR BINSTUB_DIR
10
+ * appbundler #{VERSION} *
11
11
 
12
- APPLICATION_DIR is the root directory to a working copy of your app
12
+ Usage: appbundler BUNDLE_DIR BINSTUB_DIR [GEM_NAME] [GEM_NAME] ...
13
+
14
+ BUNDLE_DIR is the root directory to the bundle containing your app
13
15
  BINSTUB_DIR is the directory where you want generated executables to be written
16
+ GEM_NAME is the name of a gem you want to appbundle. Default is the directory name
17
+ of BUNDLE_DIR (e.g. /src/chef -> chef)
14
18
 
15
19
  Your bundled application must already be gem installed. Generated binstubs
16
20
  will point to the gem, not your working copy.
@@ -42,8 +46,9 @@ BANNER
42
46
 
43
47
  attr_reader :argv
44
48
 
45
- attr_reader :app_path
49
+ attr_reader :bundle_path
46
50
  attr_reader :bin_path
51
+ attr_reader :gems
47
52
 
48
53
  def initialize(argv)
49
54
  @argv = argv
@@ -55,23 +60,26 @@ BANNER
55
60
  end
56
61
 
57
62
  def validate!
58
- if cli_arguments.size != 2
63
+ if cli_arguments.size < 2
59
64
  usage_and_exit!
60
65
  else
61
- @app_path = File.expand_path(cli_arguments[0])
66
+ @bundle_path = File.expand_path(cli_arguments[0])
62
67
  @bin_path = File.expand_path(cli_arguments[1])
63
- verify_app_path
68
+ @gems = cli_arguments[2..-1]
69
+ @gems = [ File.basename(@bundle_path) ] if @gems.empty?
70
+ verify_bundle_path
64
71
  verify_bin_path
65
- verify_gem_installed
72
+ verify_gems_installed
73
+ verify_deps_are_accessible
66
74
  end
67
75
  end
68
76
 
69
- def verify_app_path
70
- if !File.directory?(app_path)
71
- err("APPLICATION_DIR `#{app_path}' is not a directory or doesn't exist")
77
+ def verify_bundle_path
78
+ if !File.directory?(bundle_path)
79
+ err("BUNDLE_DIR `#{bundle_path}' is not a directory or doesn't exist")
72
80
  usage_and_exit!
73
- elsif !File.exist?(File.join(app_path, "Gemfile.lock"))
74
- err("APPLICATION_DIR does not contain required Gemfile.lock")
81
+ elsif !File.exist?(File.join(bundle_path, "Gemfile.lock"))
82
+ err("BUNDLE_DIR does not contain required Gemfile.lock")
75
83
  usage_and_exit!
76
84
  end
77
85
  end
@@ -83,22 +91,40 @@ BANNER
83
91
  end
84
92
  end
85
93
 
86
- def verify_gem_installed
87
- app = App.new(app_path, bin_path)
88
- app.app_gemspec
89
- rescue Gem::LoadError
90
- err("Unable to find #{app.app_spec.name} #{app.app_spec.version} installed as a gem")
91
- err("You must install the top-level app as a gem before calling app-bundler")
92
- usage_and_exit!
94
+ def verify_gems_installed
95
+ gems.each do |g|
96
+ begin
97
+ app = App.new(bundle_path, bin_path, g)
98
+ app.app_gemspec
99
+ rescue Gem::LoadError
100
+ err("Unable to find #{app.app_spec.name} #{app.app_spec.version} installed as a gem")
101
+ err("You must install the top-level app as a gem before calling app-bundler")
102
+ usage_and_exit!
103
+ end
104
+ end
105
+ end
106
+
107
+ def verify_deps_are_accessible
108
+ gems.each do |g|
109
+ begin
110
+ app = App.new(bundle_path, bin_path, g)
111
+ app.verify_deps_are_accessible!
112
+ rescue InaccessibleGemsInLockfile => e
113
+ err(e.message)
114
+ exit 1
115
+ end
116
+ end
93
117
  end
94
118
 
95
119
  def run
96
- app = App.new(app_path, bin_path)
97
- created_stubs = app.write_executable_stubs
98
- created_stubs.each do |real_executable_path, stub_path|
99
- $stdout.puts "Generated binstub #{stub_path} => #{real_executable_path}"
120
+ gems.each do |g|
121
+ app = App.new(bundle_path, bin_path, g)
122
+ created_stubs = app.write_executable_stubs
123
+ created_stubs.each do |real_executable_path, stub_path|
124
+ $stdout.puts "Generated binstub #{stub_path} => #{real_executable_path}"
125
+ end
126
+ app.copy_bundler_env
100
127
  end
101
- app.copy_bundler_env
102
128
  end
103
129
 
104
130
  def err(message)
@@ -1,3 +1,3 @@
1
1
  module Appbundler
2
- VERSION = "0.7.0"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -12,7 +12,8 @@ describe Appbundler do
12
12
 
13
13
  def double_spec(name, version, dep_names)
14
14
  deps = dep_names.map {|n| double("Bundler::Dependency #{n}", :name => n.to_s) }
15
- spec = double("Bundler::LazySpecification '#{name}'", :name => name.to_s, :version => version, :dependencies => deps)
15
+ source = double("Bundler::Source::Rubygems")
16
+ spec = double("Bundler::LazySpecification '#{name}'", :name => name.to_s, :version => version, :dependencies => deps, :source => source)
16
17
  all_specs << spec
17
18
  spec
18
19
  end
@@ -59,7 +60,7 @@ describe Appbundler do
59
60
  let(:app_root) { "/opt/app/embedded/apps/app" }
60
61
 
61
62
  let(:app) do
62
- Appbundler::App.new(app_root, target_bindir)
63
+ Appbundler::App.new(app_root, target_bindir, File.basename(app_root))
63
64
  end
64
65
 
65
66
  before do
@@ -137,6 +138,48 @@ E
137
138
 
138
139
  end
139
140
 
141
+ context "when there are git-sourced gems in the Gemfile.lock" do
142
+
143
+ let!(:second_level_dep_b_a) do
144
+ source = double("Bundler::Source::Git")
145
+ allow(source).to receive(:kind_of?).with(Bundler::Source::Git).and_return(true)
146
+ spec = double_spec(:second_level_dep_b_a, "2.2.0", [])
147
+ allow(spec).to receive(:source).and_return(source)
148
+ spec
149
+ end
150
+
151
+ # Ensure that the behavior we emulate in our stubs is correct:
152
+ it "sanity checks rubygems behavior" do
153
+ expect { Gem::Specification.find_by_name("there-is-no-such-gem-named-this", "= 999.999.999") }.
154
+ to raise_error(Gem::LoadError)
155
+ end
156
+
157
+ context "and the gems are not accessible by rubygems" do
158
+
159
+ before do
160
+ allow(Gem::Specification).to receive(:find_by_name).with("second_level_dep_b_a", "= 2.2.0").and_raise(Gem::LoadError)
161
+ end
162
+
163
+ it "raises an error validating gem accessibility" do
164
+ expect { app.verify_deps_are_accessible! }.to raise_error(Appbundler::InaccessibleGemsInLockfile)
165
+ end
166
+
167
+ end
168
+
169
+ context "and the gems are accessible by rubygems" do
170
+
171
+ before do
172
+ allow(Gem::Specification).to receive(:find_by_name).with("second_level_dep_b_a", "= 2.2.0").and_return(true)
173
+ end
174
+
175
+ it "raises an error validating gem accessibility" do
176
+ expect { app.verify_deps_are_accessible! }.to_not raise_error
177
+ end
178
+
179
+ end
180
+
181
+ end
182
+
140
183
  end
141
184
 
142
185
  context "when created with the example application" do
@@ -147,7 +190,7 @@ E
147
190
  let(:app_root) { APP_ROOT }
148
191
 
149
192
  let(:app) do
150
- Appbundler::App.new(APP_ROOT, target_bindir)
193
+ Appbundler::App.new(APP_ROOT, target_bindir, File.basename(APP_ROOT))
151
194
  end
152
195
 
153
196
  before(:all) do
@@ -331,6 +374,7 @@ E
331
374
  end
332
375
 
333
376
  it "generates executable stubs for all executables in the app" do
377
+ app.verify_deps_are_accessible!
334
378
  app.write_executable_stubs
335
379
  binary_1 = File.join(target_bindir, "app-binary-1")
336
380
  binary_2 = File.join(target_bindir, "app-binary-2")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appbundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - danielsdeleo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-28 00:00:00.000000000 Z
11
+ date: 2016-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -133,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
133
  version: '0'
134
134
  requirements: []
135
135
  rubyforge_project:
136
- rubygems_version: 2.4.8
136
+ rubygems_version: 2.6.3
137
137
  signing_key:
138
138
  specification_version: 4
139
139
  summary: Extracts a dependency solution from bundler's Gemfile.lock to speed gem activation
@@ -149,3 +149,4 @@ test_files:
149
149
  - spec/fixtures/appbundler-example-app/bin/app-binary-2
150
150
  - spec/fixtures/appbundler-example-app/lib/example_app.rb
151
151
  - spec/spec_helper.rb
152
+ has_rdoc: