appbundler 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +17 -17
- data/.rspec +2 -2
- data/.travis.yml +12 -11
- data/Gemfile +4 -4
- data/LICENSE.txt +202 -202
- data/README.md +70 -70
- data/Rakefile +1 -1
- data/appbundler.gemspec +27 -27
- data/bin/appbundler +13 -13
- data/lib/appbundler.rb +4 -4
- data/lib/appbundler/app.rb +192 -188
- data/lib/appbundler/cli.rb +113 -98
- data/lib/appbundler/version.rb +3 -3
- data/spec/appbundler/app_spec.rb +421 -335
- data/spec/fixtures/appbundler-example-app/.bundle/config +2 -0
- data/spec/fixtures/appbundler-example-app/Gemfile +2 -2
- data/spec/fixtures/appbundler-example-app/Gemfile.lock.unix +135 -0
- data/spec/fixtures/appbundler-example-app/Gemfile.lock.windows +172 -0
- data/spec/fixtures/appbundler-example-app/README.md +3 -3
- data/spec/fixtures/appbundler-example-app/appbundler-example-app.gemspec +22 -22
- data/spec/fixtures/appbundler-example-app/bin/app-binary-1 +2 -2
- data/spec/fixtures/appbundler-example-app/bin/app-binary-2 +2 -2
- data/spec/fixtures/appbundler-example-app/lib/example_app.rb +3 -3
- data/spec/spec_helper.rb +9 -4
- metadata +8 -5
data/lib/appbundler/cli.rb
CHANGED
@@ -1,98 +1,113 @@
|
|
1
|
-
require 'appbundler/version'
|
2
|
-
require 'appbundler/app'
|
3
|
-
require 'mixlib/cli'
|
4
|
-
|
5
|
-
module Appbundler
|
6
|
-
class CLI
|
7
|
-
include Mixlib::CLI
|
8
|
-
|
9
|
-
banner(<<-BANNER)
|
10
|
-
Usage: appbundler APPLICATION_DIR BINSTUB_DIR
|
11
|
-
|
12
|
-
APPLICATION_DIR is the root directory of your app
|
13
|
-
BINSTUB_DIR is the directory where you want generated executables to be written
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
|
24
|
-
|
25
|
-
:
|
26
|
-
|
27
|
-
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
cli
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
attr_reader :
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
1
|
+
require 'appbundler/version'
|
2
|
+
require 'appbundler/app'
|
3
|
+
require 'mixlib/cli'
|
4
|
+
|
5
|
+
module Appbundler
|
6
|
+
class CLI
|
7
|
+
include Mixlib::CLI
|
8
|
+
|
9
|
+
banner(<<-BANNER)
|
10
|
+
Usage: appbundler APPLICATION_DIR BINSTUB_DIR
|
11
|
+
|
12
|
+
APPLICATION_DIR is the root directory to a working copy of your app
|
13
|
+
BINSTUB_DIR is the directory where you want generated executables to be written
|
14
|
+
|
15
|
+
Your bundled application must already be gem installed. Generated binstubs
|
16
|
+
will point to the gem, not your working copy.
|
17
|
+
BANNER
|
18
|
+
|
19
|
+
option :version,
|
20
|
+
:short => '-v',
|
21
|
+
:long => '--version',
|
22
|
+
:description => 'Show appbundler version',
|
23
|
+
:boolean => true,
|
24
|
+
:proc => lambda {|v| $stdout.puts("Appbundler Version: #{::Appbundler::VERSION}")},
|
25
|
+
:exit => 0
|
26
|
+
|
27
|
+
option :help,
|
28
|
+
:short => "-h",
|
29
|
+
:long => "--help",
|
30
|
+
:description => "Show this message",
|
31
|
+
:on => :tail,
|
32
|
+
:boolean => true,
|
33
|
+
:show_options => true,
|
34
|
+
:exit => 0
|
35
|
+
|
36
|
+
def self.run(argv)
|
37
|
+
cli = new(argv)
|
38
|
+
cli.handle_options
|
39
|
+
cli.validate!
|
40
|
+
cli.run
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :argv
|
44
|
+
|
45
|
+
attr_reader :app_path
|
46
|
+
attr_reader :bin_path
|
47
|
+
|
48
|
+
def initialize(argv)
|
49
|
+
@argv = argv
|
50
|
+
super()
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_options
|
54
|
+
parse_options(@argv)
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate!
|
58
|
+
if cli_arguments.size != 2
|
59
|
+
usage_and_exit!
|
60
|
+
else
|
61
|
+
@app_path = File.expand_path(cli_arguments[0])
|
62
|
+
@bin_path = File.expand_path(cli_arguments[1])
|
63
|
+
verify_app_path
|
64
|
+
verify_bin_path
|
65
|
+
verify_gem_installed
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
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")
|
72
|
+
usage_and_exit!
|
73
|
+
elsif !File.exist?(File.join(app_path, "Gemfile.lock"))
|
74
|
+
err("APPLICATION_DIR does not contain required Gemfile.lock")
|
75
|
+
usage_and_exit!
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def verify_bin_path
|
80
|
+
if !File.directory?(bin_path)
|
81
|
+
err("BINSTUB_DIR `#{bin_path}' is not a directory or doesn't exist")
|
82
|
+
usage_and_exit!
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
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!
|
93
|
+
end
|
94
|
+
|
95
|
+
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}"
|
100
|
+
end
|
101
|
+
app.copy_bundler_env
|
102
|
+
end
|
103
|
+
|
104
|
+
def err(message)
|
105
|
+
$stderr.print("#{message}\n")
|
106
|
+
end
|
107
|
+
|
108
|
+
def usage_and_exit!
|
109
|
+
err(banner)
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/appbundler/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module Appbundler
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
1
|
+
module Appbundler
|
2
|
+
VERSION = "0.6.0"
|
3
|
+
end
|
data/spec/appbundler/app_spec.rb
CHANGED
@@ -1,335 +1,421 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'tmpdir'
|
3
|
-
require 'fileutils'
|
4
|
-
require 'mixlib/shellout'
|
5
|
-
require 'appbundler/app'
|
6
|
-
|
7
|
-
describe Appbundler do
|
8
|
-
|
9
|
-
def all_specs
|
10
|
-
@all_specs ||= []
|
11
|
-
end
|
12
|
-
|
13
|
-
def double_spec(name, version, dep_names)
|
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)
|
16
|
-
all_specs << spec
|
17
|
-
spec
|
18
|
-
end
|
19
|
-
|
20
|
-
def shellout!(cmd)
|
21
|
-
s = Mixlib::ShellOut.new(cmd, :env => {"RUBYOPT" => nil, "BUNDLE_GEMFILE" => nil, "APPBUNDLER_ALLOW_RVM" => "true"})
|
22
|
-
s.run_command
|
23
|
-
s.error!
|
24
|
-
s
|
25
|
-
end
|
26
|
-
|
27
|
-
def target_bindir
|
28
|
-
File.expand_path("../../test-tmp/bin", __FILE__)
|
29
|
-
end
|
30
|
-
|
31
|
-
context "given an app with multiple levels of dependencies" do
|
32
|
-
|
33
|
-
let!(:second_level_dep_a_a) do
|
34
|
-
double_spec(:second_level_dep_a_a, "2.1.0", [])
|
35
|
-
end
|
36
|
-
|
37
|
-
let!(:second_level_dep_shared) do
|
38
|
-
double_spec(:second_level_dep_shared, "2.3.0", [])
|
39
|
-
end
|
40
|
-
|
41
|
-
let!(:second_level_dep_b_a) do
|
42
|
-
double_spec(:second_level_dep_b_a, "2.2.0", [])
|
43
|
-
end
|
44
|
-
|
45
|
-
let!(:first_level_dep_a) do
|
46
|
-
double_spec(:first_level_dep_a, "1.1.0", [:second_level_dep_a_a, :second_level_dep_shared])
|
47
|
-
end
|
48
|
-
|
49
|
-
let!(:first_level_dep_b) do
|
50
|
-
double_spec(:first_level_dep_b, "1.2.0", [:second_level_dep_b_a, :second_level_dep_shared])
|
51
|
-
end
|
52
|
-
|
53
|
-
let!(:app_spec) do
|
54
|
-
double_spec(:app, "1.0.0", [:first_level_dep_a, :first_level_dep_b])
|
55
|
-
end
|
56
|
-
|
57
|
-
let(:bin_path) { File.join(target_bindir, "foo") }
|
58
|
-
|
59
|
-
let(:app_root) { "/opt/app/embedded/apps/app" }
|
60
|
-
|
61
|
-
let(:app) do
|
62
|
-
Appbundler::App.new(app_root, target_bindir)
|
63
|
-
end
|
64
|
-
|
65
|
-
before do
|
66
|
-
allow(app).to receive(:gemfile_lock_specs).and_return(all_specs)
|
67
|
-
end
|
68
|
-
|
69
|
-
it "finds the running ruby interpreter" do
|
70
|
-
expect(app.ruby).to eq(Gem.ruby)
|
71
|
-
end
|
72
|
-
|
73
|
-
it "finds all runtime dependencies of the app" do
|
74
|
-
# we want to find the minimum set of gems that we need to activate to run
|
75
|
-
# the application. To do this, we look at the app's runtime deps and
|
76
|
-
# recursively search through the list of gemspecs that we got from the
|
77
|
-
# Gemfile.lock, collecting all the runtime deps. This should get us the
|
78
|
-
# smallest possible "activate.rb" file that can launch the application
|
79
|
-
# with locked gem versions.
|
80
|
-
expect(app.runtime_dep_specs).to include(first_level_dep_a)
|
81
|
-
expect(app.runtime_dep_specs).to include(first_level_dep_b)
|
82
|
-
expect(app.runtime_dep_specs).to include(second_level_dep_a_a)
|
83
|
-
expect(app.runtime_dep_specs).to include(second_level_dep_b_a)
|
84
|
-
expect(app.runtime_dep_specs).to include(second_level_dep_shared)
|
85
|
-
expect(app.runtime_dep_specs.select {|s| s == second_level_dep_shared}.size).to eq(1)
|
86
|
-
end
|
87
|
-
|
88
|
-
it "generates gem activation code for the app" do
|
89
|
-
# this is code with a bunch of gem "foo", "= X.Y.Z" statements. The top
|
90
|
-
# level application is _not_ included in this, it's added to the load
|
91
|
-
# path instead.
|
92
|
-
expect(app.runtime_activate).to include(%q{gem "first_level_dep_a", "= 1.1.0"})
|
93
|
-
expect(app.runtime_activate).to include(%q{gem "second_level_dep_a_a", "= 2.1.0"})
|
94
|
-
expect(app.runtime_activate).to include(%q{gem "second_level_dep_shared", "= 2.3.0"})
|
95
|
-
expect(app.runtime_activate).to include(%q{gem "first_level_dep_b", "= 1.2.0"})
|
96
|
-
expect(app.runtime_activate).to include(%q{gem "second_level_dep_b_a", "= 2.2.0"})
|
97
|
-
expect(app.runtime_activate).to_not include(%q{gem "app"})
|
98
|
-
end
|
99
|
-
|
100
|
-
it "locks the main app's gem via rubygems, and loads the proper binary" do
|
101
|
-
expected_loading_code = <<-CODE
|
102
|
-
gem "app", "= 1.0.0"
|
103
|
-
|
104
|
-
spec = Gem::Specification.find_by_name("app", "= 1.0.0")
|
105
|
-
bin_file = spec.bin_file("foo")
|
106
|
-
|
107
|
-
Kernel.load(bin_file)
|
108
|
-
CODE
|
109
|
-
expect(app.load_statement_for(bin_path)).to eq(expected_loading_code)
|
110
|
-
end
|
111
|
-
|
112
|
-
it "generates code to override GEM_HOME and GEM_PATH (e.g., rvm)" do
|
113
|
-
expected = %Q{ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true"}
|
114
|
-
expect(app.env_sanitizer).to eq(expected)
|
115
|
-
expect(app.runtime_activate).to include(expected)
|
116
|
-
end
|
117
|
-
|
118
|
-
context "on windows" do
|
119
|
-
|
120
|
-
let(:target_bindir) { "C:/opscode/chef/bin" }
|
121
|
-
|
122
|
-
before do
|
123
|
-
allow(app).to receive(:ruby).and_return("C:/opscode/chef/embedded/bin/ruby.exe")
|
124
|
-
end
|
125
|
-
|
126
|
-
it "computes the relative path to ruby" do
|
127
|
-
expect(app.ruby_relative_path).to eq("../embedded/bin/ruby.exe")
|
128
|
-
end
|
129
|
-
|
130
|
-
it "generates batchfile stub code" do
|
131
|
-
expected_batch_code=<<-E
|
132
|
-
@ECHO OFF
|
133
|
-
"%~dp0\\..\\embedded\\bin\\ruby.exe" "%~dpn0" %*
|
134
|
-
E
|
135
|
-
expect(app.batchfile_stub).to eq(expected_batch_code)
|
136
|
-
end
|
137
|
-
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
141
|
-
|
142
|
-
context "when created with the example application" do
|
143
|
-
FIXTURES_PATH = File.expand_path("../../fixtures/", __FILE__).freeze
|
144
|
-
|
145
|
-
APP_ROOT = File.join(FIXTURES_PATH, "appbundler-example-app").freeze
|
146
|
-
|
147
|
-
let(:app_root) { APP_ROOT }
|
148
|
-
|
149
|
-
let(:app) do
|
150
|
-
Appbundler::App.new(APP_ROOT, target_bindir)
|
151
|
-
end
|
152
|
-
|
153
|
-
before(:all) do
|
154
|
-
Dir.chdir(APP_ROOT) do
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
FileUtils.rm_rf(target_bindir) if File.exist?(target_bindir)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
gem "
|
196
|
-
gem "
|
197
|
-
gem "
|
198
|
-
gem "
|
199
|
-
gem "
|
200
|
-
gem "
|
201
|
-
gem "
|
202
|
-
gem "
|
203
|
-
gem "
|
204
|
-
gem "
|
205
|
-
gem "
|
206
|
-
gem "
|
207
|
-
gem "
|
208
|
-
gem "
|
209
|
-
gem "
|
210
|
-
gem "
|
211
|
-
gem "
|
212
|
-
gem "
|
213
|
-
gem "
|
214
|
-
gem "
|
215
|
-
gem "
|
216
|
-
gem "
|
217
|
-
gem "
|
218
|
-
gem "
|
219
|
-
gem "
|
220
|
-
gem "
|
221
|
-
gem "
|
222
|
-
gem "
|
223
|
-
gem "
|
224
|
-
gem "
|
225
|
-
gem "
|
226
|
-
gem "
|
227
|
-
gem "
|
228
|
-
gem "
|
229
|
-
gem "
|
230
|
-
gem "
|
231
|
-
gem "
|
232
|
-
gem "
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
end
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'mixlib/shellout'
|
5
|
+
require 'appbundler/app'
|
6
|
+
|
7
|
+
describe Appbundler do
|
8
|
+
|
9
|
+
def all_specs
|
10
|
+
@all_specs ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
def double_spec(name, version, dep_names)
|
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)
|
16
|
+
all_specs << spec
|
17
|
+
spec
|
18
|
+
end
|
19
|
+
|
20
|
+
def shellout!(cmd)
|
21
|
+
s = Mixlib::ShellOut.new(cmd, :env => {"RUBYOPT" => nil, "BUNDLE_GEMFILE" => nil, "APPBUNDLER_ALLOW_RVM" => "true"})
|
22
|
+
s.run_command
|
23
|
+
s.error!
|
24
|
+
s
|
25
|
+
end
|
26
|
+
|
27
|
+
def target_bindir
|
28
|
+
File.expand_path("../../test-tmp/bin", __FILE__)
|
29
|
+
end
|
30
|
+
|
31
|
+
context "given an app with multiple levels of dependencies" do
|
32
|
+
|
33
|
+
let!(:second_level_dep_a_a) do
|
34
|
+
double_spec(:second_level_dep_a_a, "2.1.0", [])
|
35
|
+
end
|
36
|
+
|
37
|
+
let!(:second_level_dep_shared) do
|
38
|
+
double_spec(:second_level_dep_shared, "2.3.0", [])
|
39
|
+
end
|
40
|
+
|
41
|
+
let!(:second_level_dep_b_a) do
|
42
|
+
double_spec(:second_level_dep_b_a, "2.2.0", [])
|
43
|
+
end
|
44
|
+
|
45
|
+
let!(:first_level_dep_a) do
|
46
|
+
double_spec(:first_level_dep_a, "1.1.0", [:second_level_dep_a_a, :second_level_dep_shared])
|
47
|
+
end
|
48
|
+
|
49
|
+
let!(:first_level_dep_b) do
|
50
|
+
double_spec(:first_level_dep_b, "1.2.0", [:second_level_dep_b_a, :second_level_dep_shared])
|
51
|
+
end
|
52
|
+
|
53
|
+
let!(:app_spec) do
|
54
|
+
double_spec(:app, "1.0.0", [:first_level_dep_a, :first_level_dep_b])
|
55
|
+
end
|
56
|
+
|
57
|
+
let(:bin_path) { File.join(target_bindir, "foo") }
|
58
|
+
|
59
|
+
let(:app_root) { "/opt/app/embedded/apps/app" }
|
60
|
+
|
61
|
+
let(:app) do
|
62
|
+
Appbundler::App.new(app_root, target_bindir)
|
63
|
+
end
|
64
|
+
|
65
|
+
before do
|
66
|
+
allow(app).to receive(:gemfile_lock_specs).and_return(all_specs)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "finds the running ruby interpreter" do
|
70
|
+
expect(app.ruby).to eq(Gem.ruby)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "finds all runtime dependencies of the app" do
|
74
|
+
# we want to find the minimum set of gems that we need to activate to run
|
75
|
+
# the application. To do this, we look at the app's runtime deps and
|
76
|
+
# recursively search through the list of gemspecs that we got from the
|
77
|
+
# Gemfile.lock, collecting all the runtime deps. This should get us the
|
78
|
+
# smallest possible "activate.rb" file that can launch the application
|
79
|
+
# with locked gem versions.
|
80
|
+
expect(app.runtime_dep_specs).to include(first_level_dep_a)
|
81
|
+
expect(app.runtime_dep_specs).to include(first_level_dep_b)
|
82
|
+
expect(app.runtime_dep_specs).to include(second_level_dep_a_a)
|
83
|
+
expect(app.runtime_dep_specs).to include(second_level_dep_b_a)
|
84
|
+
expect(app.runtime_dep_specs).to include(second_level_dep_shared)
|
85
|
+
expect(app.runtime_dep_specs.select {|s| s == second_level_dep_shared}.size).to eq(1)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "generates gem activation code for the app" do
|
89
|
+
# this is code with a bunch of gem "foo", "= X.Y.Z" statements. The top
|
90
|
+
# level application is _not_ included in this, it's added to the load
|
91
|
+
# path instead.
|
92
|
+
expect(app.runtime_activate).to include(%q{gem "first_level_dep_a", "= 1.1.0"})
|
93
|
+
expect(app.runtime_activate).to include(%q{gem "second_level_dep_a_a", "= 2.1.0"})
|
94
|
+
expect(app.runtime_activate).to include(%q{gem "second_level_dep_shared", "= 2.3.0"})
|
95
|
+
expect(app.runtime_activate).to include(%q{gem "first_level_dep_b", "= 1.2.0"})
|
96
|
+
expect(app.runtime_activate).to include(%q{gem "second_level_dep_b_a", "= 2.2.0"})
|
97
|
+
expect(app.runtime_activate).to_not include(%q{gem "app"})
|
98
|
+
end
|
99
|
+
|
100
|
+
it "locks the main app's gem via rubygems, and loads the proper binary" do
|
101
|
+
expected_loading_code = <<-CODE
|
102
|
+
gem "app", "= 1.0.0"
|
103
|
+
|
104
|
+
spec = Gem::Specification.find_by_name("app", "= 1.0.0")
|
105
|
+
bin_file = spec.bin_file("foo")
|
106
|
+
|
107
|
+
Kernel.load(bin_file)
|
108
|
+
CODE
|
109
|
+
expect(app.load_statement_for(bin_path)).to eq(expected_loading_code)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "generates code to override GEM_HOME and GEM_PATH (e.g., rvm)" do
|
113
|
+
expected = %Q{ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true"}
|
114
|
+
expect(app.env_sanitizer).to eq(expected)
|
115
|
+
expect(app.runtime_activate).to include(expected)
|
116
|
+
end
|
117
|
+
|
118
|
+
context "on windows" do
|
119
|
+
|
120
|
+
let(:target_bindir) { "C:/opscode/chef/bin" }
|
121
|
+
|
122
|
+
before do
|
123
|
+
allow(app).to receive(:ruby).and_return("C:/opscode/chef/embedded/bin/ruby.exe")
|
124
|
+
end
|
125
|
+
|
126
|
+
it "computes the relative path to ruby" do
|
127
|
+
expect(app.ruby_relative_path).to eq("../embedded/bin/ruby.exe")
|
128
|
+
end
|
129
|
+
|
130
|
+
it "generates batchfile stub code" do
|
131
|
+
expected_batch_code=<<-E
|
132
|
+
@ECHO OFF
|
133
|
+
"%~dp0\\..\\embedded\\bin\\ruby.exe" "%~dpn0" %*
|
134
|
+
E
|
135
|
+
expect(app.batchfile_stub).to eq(expected_batch_code)
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
context "when created with the example application" do
|
143
|
+
FIXTURES_PATH = File.expand_path("../../fixtures/", __FILE__).freeze
|
144
|
+
|
145
|
+
APP_ROOT = File.join(FIXTURES_PATH, "appbundler-example-app").freeze
|
146
|
+
|
147
|
+
let(:app_root) { APP_ROOT }
|
148
|
+
|
149
|
+
let(:app) do
|
150
|
+
Appbundler::App.new(APP_ROOT, target_bindir)
|
151
|
+
end
|
152
|
+
|
153
|
+
before(:all) do
|
154
|
+
Dir.chdir(APP_ROOT) do
|
155
|
+
if windows?
|
156
|
+
FileUtils.cp("Gemfile.lock.windows", "Gemfile.lock")
|
157
|
+
else
|
158
|
+
FileUtils.cp("Gemfile.lock.unix", "Gemfile.lock")
|
159
|
+
end
|
160
|
+
shellout!("bundle install")
|
161
|
+
shellout!("gem build appbundler-example-app.gemspec")
|
162
|
+
shellout!("gem install appbundler-example-app-1.0.0.gem")
|
163
|
+
Gem.clear_paths
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
before do
|
168
|
+
FileUtils.rm_rf(target_bindir) if File.exist?(target_bindir)
|
169
|
+
FileUtils.mkdir_p(target_bindir)
|
170
|
+
end
|
171
|
+
|
172
|
+
after(:all) do
|
173
|
+
shellout!("gem uninstall appbundler-example-app -a -q -x")
|
174
|
+
FileUtils.rm_rf(target_bindir) if File.exist?(target_bindir)
|
175
|
+
FileUtils.rm(File.join(APP_ROOT, "Gemfile.lock"))
|
176
|
+
end
|
177
|
+
|
178
|
+
it "initializes ok" do
|
179
|
+
app
|
180
|
+
end
|
181
|
+
|
182
|
+
it "names the app using the directory basename" do
|
183
|
+
expect(app.name).to eq("appbundler-example-app")
|
184
|
+
end
|
185
|
+
|
186
|
+
it "lists the app's dependencies" do
|
187
|
+
# only runtime deps
|
188
|
+
expect(app.app_dependency_names).to eq(["chef"])
|
189
|
+
end
|
190
|
+
|
191
|
+
it "generates runtime activation code for the app" do
|
192
|
+
expected_gem_activates= if windows?
|
193
|
+
<<-E
|
194
|
+
ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true"
|
195
|
+
gem "chef", "= 12.4.1"
|
196
|
+
gem "chef-config", "= 12.4.1"
|
197
|
+
gem "mixlib-config", "= 2.2.1"
|
198
|
+
gem "mixlib-shellout", "= 2.2.0"
|
199
|
+
gem "win32-process", "= 0.7.5"
|
200
|
+
gem "ffi", "= 1.9.10"
|
201
|
+
gem "chef-zero", "= 4.3.0"
|
202
|
+
gem "ffi-yajl", "= 2.2.2"
|
203
|
+
gem "libyajl2", "= 1.2.0"
|
204
|
+
gem "hashie", "= 2.1.2"
|
205
|
+
gem "mixlib-log", "= 1.6.0"
|
206
|
+
gem "rack", "= 1.6.4"
|
207
|
+
gem "uuidtools", "= 2.1.5"
|
208
|
+
gem "diff-lcs", "= 1.2.5"
|
209
|
+
gem "erubis", "= 2.7.0"
|
210
|
+
gem "highline", "= 1.7.3"
|
211
|
+
gem "mixlib-authentication", "= 1.3.0"
|
212
|
+
gem "mixlib-cli", "= 1.5.0"
|
213
|
+
gem "net-ssh", "= 2.9.2"
|
214
|
+
gem "net-ssh-multi", "= 1.2.1"
|
215
|
+
gem "net-ssh-gateway", "= 1.2.0"
|
216
|
+
gem "ohai", "= 8.5.1"
|
217
|
+
gem "ipaddress", "= 0.8.0"
|
218
|
+
gem "mime-types", "= 2.6.1"
|
219
|
+
gem "rake", "= 10.1.1"
|
220
|
+
gem "systemu", "= 2.6.5"
|
221
|
+
gem "wmi-lite", "= 1.0.0"
|
222
|
+
gem "plist", "= 3.1.0"
|
223
|
+
gem "pry", "= 0.9.12.6"
|
224
|
+
gem "coderay", "= 1.1.0"
|
225
|
+
gem "method_source", "= 0.8.2"
|
226
|
+
gem "slop", "= 3.4.7"
|
227
|
+
gem "win32console", "= 1.3.2"
|
228
|
+
gem "rspec-core", "= 3.3.2"
|
229
|
+
gem "rspec-support", "= 3.3.0"
|
230
|
+
gem "rspec-expectations", "= 3.3.1"
|
231
|
+
gem "rspec-mocks", "= 3.3.2"
|
232
|
+
gem "rspec_junit_formatter", "= 0.2.3"
|
233
|
+
gem "builder", "= 3.2.2"
|
234
|
+
gem "serverspec", "= 2.23.1"
|
235
|
+
gem "multi_json", "= 1.11.2"
|
236
|
+
gem "rspec", "= 3.3.0"
|
237
|
+
gem "rspec-its", "= 1.2.0"
|
238
|
+
gem "specinfra", "= 2.43.3"
|
239
|
+
gem "net-scp", "= 1.2.1"
|
240
|
+
gem "net-telnet", "= 0.1.1"
|
241
|
+
gem "sfl", "= 2.2"
|
242
|
+
gem "syslog-logger", "= 1.6.8"
|
243
|
+
gem "win32-api", "= 1.5.3"
|
244
|
+
gem "win32-dir", "= 0.5.0"
|
245
|
+
gem "win32-event", "= 0.6.1"
|
246
|
+
gem "win32-ipc", "= 0.6.6"
|
247
|
+
gem "win32-eventlog", "= 0.6.3"
|
248
|
+
gem "win32-mmap", "= 0.4.1"
|
249
|
+
gem "win32-mutex", "= 0.4.2"
|
250
|
+
gem "win32-service", "= 0.8.6"
|
251
|
+
gem "windows-api", "= 0.4.4"
|
252
|
+
gem "windows-pr", "= 1.2.4"
|
253
|
+
E
|
254
|
+
else
|
255
|
+
<<-E
|
256
|
+
ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true"
|
257
|
+
gem "chef", "= 12.4.1"
|
258
|
+
gem "chef-config", "= 12.4.1"
|
259
|
+
gem "mixlib-config", "= 2.2.1"
|
260
|
+
gem "mixlib-shellout", "= 2.2.0"
|
261
|
+
gem "chef-zero", "= 4.3.0"
|
262
|
+
gem "ffi-yajl", "= 2.2.2"
|
263
|
+
gem "libyajl2", "= 1.2.0"
|
264
|
+
gem "hashie", "= 2.1.2"
|
265
|
+
gem "mixlib-log", "= 1.6.0"
|
266
|
+
gem "rack", "= 1.6.4"
|
267
|
+
gem "uuidtools", "= 2.1.5"
|
268
|
+
gem "diff-lcs", "= 1.2.5"
|
269
|
+
gem "erubis", "= 2.7.0"
|
270
|
+
gem "highline", "= 1.7.3"
|
271
|
+
gem "mixlib-authentication", "= 1.3.0"
|
272
|
+
gem "mixlib-cli", "= 1.5.0"
|
273
|
+
gem "net-ssh", "= 2.9.2"
|
274
|
+
gem "net-ssh-multi", "= 1.2.1"
|
275
|
+
gem "net-ssh-gateway", "= 1.2.0"
|
276
|
+
gem "ohai", "= 8.5.1"
|
277
|
+
gem "ffi", "= 1.9.10"
|
278
|
+
gem "ipaddress", "= 0.8.0"
|
279
|
+
gem "mime-types", "= 2.6.1"
|
280
|
+
gem "rake", "= 10.1.1"
|
281
|
+
gem "systemu", "= 2.6.5"
|
282
|
+
gem "wmi-lite", "= 1.0.0"
|
283
|
+
gem "plist", "= 3.1.0"
|
284
|
+
gem "pry", "= 0.9.12.6"
|
285
|
+
gem "coderay", "= 1.1.0"
|
286
|
+
gem "method_source", "= 0.8.2"
|
287
|
+
gem "slop", "= 3.4.7"
|
288
|
+
gem "rspec-core", "= 3.3.2"
|
289
|
+
gem "rspec-support", "= 3.3.0"
|
290
|
+
gem "rspec-expectations", "= 3.3.1"
|
291
|
+
gem "rspec-mocks", "= 3.3.2"
|
292
|
+
gem "rspec_junit_formatter", "= 0.2.3"
|
293
|
+
gem "builder", "= 3.2.2"
|
294
|
+
gem "serverspec", "= 2.23.1"
|
295
|
+
gem "multi_json", "= 1.11.2"
|
296
|
+
gem "rspec", "= 3.3.0"
|
297
|
+
gem "rspec-its", "= 1.2.0"
|
298
|
+
gem "specinfra", "= 2.43.3"
|
299
|
+
gem "net-scp", "= 1.2.1"
|
300
|
+
gem "net-telnet", "= 0.1.1"
|
301
|
+
gem "sfl", "= 2.2"
|
302
|
+
gem "syslog-logger", "= 1.6.8"
|
303
|
+
E
|
304
|
+
end
|
305
|
+
expect(app.runtime_activate).to include(expected_gem_activates)
|
306
|
+
end
|
307
|
+
|
308
|
+
it "lists the app's executables" do
|
309
|
+
spec = Gem::Specification.find_by_name("appbundler-example-app", "= 1.0.0")
|
310
|
+
expected_executables = %w[app-binary-1 app-binary-2].map do |basename|
|
311
|
+
File.join(spec.gem_dir, "/bin", basename)
|
312
|
+
end
|
313
|
+
expect(app.executables).to match_array(expected_executables)
|
314
|
+
end
|
315
|
+
|
316
|
+
it "generates an executable 'stub' for an executable in the app" do
|
317
|
+
app_binary_1_path = app.executables.grep(/app\-binary\-1/).first
|
318
|
+
executable_content = app.binstub(app_binary_1_path)
|
319
|
+
|
320
|
+
shebang = executable_content.lines.first
|
321
|
+
expect(shebang).to match(/^\#\!/)
|
322
|
+
expect(shebang).to include(Gem.ruby)
|
323
|
+
|
324
|
+
expect(executable_content).to include(app.runtime_activate)
|
325
|
+
|
326
|
+
load_binary = executable_content.lines.to_a.last
|
327
|
+
|
328
|
+
expected_load_path = %Q[Kernel.load(bin_file)\n]
|
329
|
+
|
330
|
+
expect(load_binary).to eq(expected_load_path)
|
331
|
+
end
|
332
|
+
|
333
|
+
it "generates executable stubs for all executables in the app" do
|
334
|
+
app.write_executable_stubs
|
335
|
+
binary_1 = File.join(target_bindir, "app-binary-1")
|
336
|
+
binary_2 = File.join(target_bindir, "app-binary-2")
|
337
|
+
expect(File.exist?(binary_1)).to be(true)
|
338
|
+
expect(File.exist?(binary_2)).to be(true)
|
339
|
+
expect(File.executable?(binary_1) || File.exists?(binary_1 + ".bat")).to be(true)
|
340
|
+
expect(File.executable?(binary_1) || File.exists?(binary_1 + ".bat")).to be(true)
|
341
|
+
expect(shellout!(binary_1).stdout.strip).to eq("binary 1 ran")
|
342
|
+
expect(shellout!(binary_2).stdout.strip).to eq("binary 2 ran")
|
343
|
+
end
|
344
|
+
|
345
|
+
it "copies over Gemfile.lock to the gem directory" do
|
346
|
+
spec = Gem::Specification.find_by_name("appbundler-example-app", "= 1.0.0")
|
347
|
+
gem_path = spec.gem_dir
|
348
|
+
app.copy_bundler_env
|
349
|
+
expect(File.exists?(File.join(gem_path, 'Gemfile.lock'))).to be(true)
|
350
|
+
end
|
351
|
+
|
352
|
+
it "copies over .bundler to the gem directory" do
|
353
|
+
spec = Gem::Specification.find_by_name("appbundler-example-app", "= 1.0.0")
|
354
|
+
gem_path = spec.gem_dir
|
355
|
+
app.copy_bundler_env
|
356
|
+
expect(File.directory?(File.join(gem_path, '.bundle'))).to be(true)
|
357
|
+
expect(File.exists?(File.join(gem_path, '.bundle/config'))).to be(true)
|
358
|
+
end
|
359
|
+
context "and the executable is symlinked to a different directory", :not_supported_on_windows do
|
360
|
+
|
361
|
+
let(:symlinks_root_dir) do
|
362
|
+
Dir.mktmpdir
|
363
|
+
end
|
364
|
+
|
365
|
+
let(:symlinks_bin_dir) do
|
366
|
+
d = File.join(symlinks_root_dir, "bin")
|
367
|
+
FileUtils.mkdir(d)
|
368
|
+
d
|
369
|
+
end
|
370
|
+
|
371
|
+
let(:binary_symlinked_path) { File.join(symlinks_bin_dir, "app-binary-1") }
|
372
|
+
|
373
|
+
let(:binary_orignal_path) { File.join(target_bindir, "app-binary-1") }
|
374
|
+
|
375
|
+
before do
|
376
|
+
app.write_executable_stubs
|
377
|
+
FileUtils.ln_s(binary_orignal_path, binary_symlinked_path)
|
378
|
+
end
|
379
|
+
|
380
|
+
after do
|
381
|
+
FileUtils.rm_rf(symlinks_root_dir)
|
382
|
+
end
|
383
|
+
|
384
|
+
it "correctly runs the executable via the symlinked executable" do
|
385
|
+
expect(shellout!(binary_symlinked_path).stdout).to eq("binary 1 ran\n")
|
386
|
+
end
|
387
|
+
|
388
|
+
end
|
389
|
+
|
390
|
+
context "on windows" do
|
391
|
+
|
392
|
+
let(:expected_ruby_relpath) do
|
393
|
+
app.ruby_relative_path.gsub('/', '\\')
|
394
|
+
end
|
395
|
+
|
396
|
+
let(:expected_batch_code) do
|
397
|
+
<<-E
|
398
|
+
@ECHO OFF
|
399
|
+
"%~dp0\\#{expected_ruby_relpath}" "%~dpn0" %*
|
400
|
+
E
|
401
|
+
end
|
402
|
+
|
403
|
+
before do
|
404
|
+
stub_const("RUBY_PLATFORM", "mingw")
|
405
|
+
end
|
406
|
+
|
407
|
+
it "creates a batchfile wrapper for each executable" do
|
408
|
+
app.write_executable_stubs
|
409
|
+
binary_1 = File.join(target_bindir, "app-binary-1.bat")
|
410
|
+
binary_2 = File.join(target_bindir, "app-binary-2.bat")
|
411
|
+
expect(File.exist?(binary_1)).to be(true)
|
412
|
+
expect(File.exist?(binary_2)).to be(true)
|
413
|
+
expect(IO.read(binary_1)).to eq(expected_batch_code)
|
414
|
+
expect(IO.read(binary_2)).to eq(expected_batch_code)
|
415
|
+
end
|
416
|
+
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|