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 +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:
|