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 +4 -4
- data/.gitignore +3 -1
- data/lib/appbundler/app.rb +77 -16
- data/lib/appbundler/cli.rb +50 -24
- data/lib/appbundler/version.rb +1 -1
- data/spec/appbundler/app_spec.rb +47 -3
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bdcdbca4e10ef8f04ec364fad2250690e062992
|
4
|
+
data.tar.gz: 4c0408acd0c32ab33376397d78616c1a50dcca7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 144df4d360bfebafd5b99730dfc428d7ecc409efcdcd54bb68c0f7c1624eeb913be17a575ee83aff3960d5aacace7f409c2593a11481932172abe75f0184eaba
|
7
|
+
data.tar.gz: 625f02ebeefb40ee421047c6c406f248b5d35bbe7ad7f43cd2711a8963cbf816ffe806acd1e47c74c2bf0ef5ded3816e711f6c5ae762ff4ebf5c51558d1d2bc0
|
data/.gitignore
CHANGED
data/lib/appbundler/app.rb
CHANGED
@@ -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 :
|
16
|
+
attr_reader :bundle_path
|
11
17
|
attr_reader :target_bin_dir
|
18
|
+
attr_reader :name
|
12
19
|
|
13
|
-
def
|
14
|
-
|
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(
|
62
|
+
File.join(bundle_path, ".bundle")
|
64
63
|
end
|
65
64
|
|
66
65
|
def gemfile_lock
|
67
|
-
File.join(
|
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 }
|
data/lib/appbundler/cli.rb
CHANGED
@@ -7,10 +7,14 @@ module Appbundler
|
|
7
7
|
include Mixlib::CLI
|
8
8
|
|
9
9
|
banner(<<-BANNER)
|
10
|
-
|
10
|
+
* appbundler #{VERSION} *
|
11
11
|
|
12
|
-
|
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 :
|
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
|
63
|
+
if cli_arguments.size < 2
|
59
64
|
usage_and_exit!
|
60
65
|
else
|
61
|
-
@
|
66
|
+
@bundle_path = File.expand_path(cli_arguments[0])
|
62
67
|
@bin_path = File.expand_path(cli_arguments[1])
|
63
|
-
|
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
|
-
|
72
|
+
verify_gems_installed
|
73
|
+
verify_deps_are_accessible
|
66
74
|
end
|
67
75
|
end
|
68
76
|
|
69
|
-
def
|
70
|
-
if !File.directory?(
|
71
|
-
err("
|
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(
|
74
|
-
err("
|
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
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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)
|
data/lib/appbundler/version.rb
CHANGED
data/spec/appbundler/app_spec.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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.
|
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:
|