appbundler 0.6.0 → 0.7.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.
@@ -1,113 +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 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
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
@@ -1,3 +1,3 @@
1
- module Appbundler
2
- VERSION = "0.6.0"
3
- end
1
+ module Appbundler
2
+ VERSION = "0.7.0"
3
+ end
@@ -1,421 +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
- 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
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