appbundler 0.7.0 → 0.9.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.
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: