appbundler 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- BANNER
15
-
16
- option :version,
17
- :short => '-v',
18
- :long => '--version',
19
- :description => 'Show appbundler version',
20
- :boolean => true,
21
- :proc => lambda {|v| $stdout.puts("Appbundler Version: #{::Appbundler::VERSION}")},
22
- :exit => 0
23
-
24
- option :help,
25
- :short => "-h",
26
- :long => "--help",
27
- :description => "Show this message",
28
- :on => :tail,
29
- :boolean => true,
30
- :show_options => true,
31
- :exit => 0
32
-
33
- def self.run(argv)
34
- cli = new(argv)
35
- cli.handle_options
36
- cli.validate!
37
- cli.run
38
- end
39
-
40
- attr_reader :argv
41
-
42
- attr_reader :app_path
43
- attr_reader :bin_path
44
-
45
- def initialize(argv)
46
- @argv = argv
47
- super()
48
- end
49
-
50
- def handle_options
51
- parse_options(@argv)
52
- end
53
-
54
- def validate!
55
- if cli_arguments.size != 2
56
- usage_and_exit!
57
- else
58
- @app_path = File.expand_path(cli_arguments[0])
59
- @bin_path = File.expand_path(cli_arguments[1])
60
- verify_app_path
61
- verify_bin_path
62
- end
63
- end
64
-
65
- def verify_app_path
66
- if !File.directory?(app_path)
67
- err("APPLICATION_DIR `#{app_path}' is not a directory or doesn't exist")
68
- usage_and_exit!
69
- elsif !File.exist?(File.join(app_path, "Gemfile.lock"))
70
- err("APPLICATION_DIR does not contain require Gemfile.lock")
71
- usage_and_exit!
72
- end
73
- end
74
-
75
- def verify_bin_path
76
- if !File.directory?(bin_path)
77
- err("BINSTUB_DIR `#{bin_path}' is not a directory or doesn't exist")
78
- usage_and_exit!
79
- end
80
- end
81
-
82
- def run
83
- created_stubs = App.new(app_path, bin_path).write_executable_stubs
84
- created_stubs.each do |real_executable_path, stub_path|
85
- $stdout.puts "Generated binstub #{stub_path} => #{real_executable_path}"
86
- end
87
- end
88
-
89
- def err(message)
90
- $stderr.print("#{message}\n")
91
- end
92
-
93
- def usage_and_exit!
94
- err(banner)
95
- exit 1
96
- end
97
- end
98
- 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.5.0"
3
- end
1
+ module Appbundler
2
+ VERSION = "0.6.0"
3
+ end
@@ -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
- shellout!("bundle install")
156
- shellout!("gem build appbundler-example-app.gemspec")
157
- shellout!("gem install appbundler-example-app-1.0.0.gem")
158
- end
159
- end
160
-
161
- before do
162
- FileUtils.rm_rf(target_bindir) if File.exist?(target_bindir)
163
- FileUtils.mkdir_p(target_bindir)
164
- end
165
-
166
- after(:all) do
167
- shellout!("gem uninstall appbundler-example-app -a -q -x")
168
- FileUtils.rm_rf(target_bindir) if File.exist?(target_bindir)
169
- end
170
-
171
- it "initializes ok" do
172
- app
173
- end
174
-
175
- it "names the app using the directory basename" do
176
- expect(app.name).to eq("appbundler-example-app")
177
- end
178
-
179
- it "lists the app's dependencies" do
180
- # only runtime deps
181
- expect(app.app_dependency_names).to eq(["chef"])
182
- end
183
-
184
- it "generates runtime activation code for the app" do
185
- expected_gem_activates=<<-E
186
- ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true"
187
- gem "chef", "= 12.4.1"
188
- gem "chef-config", "= 12.4.1"
189
- gem "mixlib-config", "= 2.2.1"
190
- gem "mixlib-shellout", "= 2.2.0"
191
- gem "chef-zero", "= 4.3.0"
192
- gem "ffi-yajl", "= 2.2.2"
193
- gem "libyajl2", "= 1.2.0"
194
- gem "hashie", "= 2.1.2"
195
- gem "mixlib-log", "= 1.6.0"
196
- gem "rack", "= 1.6.4"
197
- gem "uuidtools", "= 2.1.5"
198
- gem "diff-lcs", "= 1.2.5"
199
- gem "erubis", "= 2.7.0"
200
- gem "highline", "= 1.7.3"
201
- gem "mixlib-authentication", "= 1.3.0"
202
- gem "mixlib-cli", "= 1.5.0"
203
- gem "net-ssh", "= 2.9.2"
204
- gem "net-ssh-multi", "= 1.2.1"
205
- gem "net-ssh-gateway", "= 1.2.0"
206
- gem "ohai", "= 8.5.1"
207
- gem "ffi", "= 1.9.10"
208
- gem "ipaddress", "= 0.8.0"
209
- gem "mime-types", "= 2.6.1"
210
- gem "rake", "= 10.1.1"
211
- gem "systemu", "= 2.6.5"
212
- gem "wmi-lite", "= 1.0.0"
213
- gem "plist", "= 3.1.0"
214
- gem "pry", "= 0.9.12.6"
215
- gem "coderay", "= 1.1.0"
216
- gem "method_source", "= 0.8.2"
217
- gem "slop", "= 3.4.7"
218
- gem "rspec-core", "= 3.3.2"
219
- gem "rspec-support", "= 3.3.0"
220
- gem "rspec-expectations", "= 3.3.1"
221
- gem "rspec-mocks", "= 3.3.2"
222
- gem "rspec_junit_formatter", "= 0.2.3"
223
- gem "builder", "= 3.2.2"
224
- gem "serverspec", "= 2.23.1"
225
- gem "multi_json", "= 1.11.2"
226
- gem "rspec", "= 3.3.0"
227
- gem "rspec-its", "= 1.2.0"
228
- gem "specinfra", "= 2.43.3"
229
- gem "net-scp", "= 1.2.1"
230
- gem "net-telnet", "= 0.1.1"
231
- gem "sfl", "= 2.2"
232
- gem "syslog-logger", "= 1.6.8"
233
- E
234
- expect(app.runtime_activate).to include(expected_gem_activates)
235
- end
236
-
237
- it "lists the app's executables" do
238
- expected_executables = %w[app-binary-1 app-binary-2].map do |basename|
239
- File.join(app_root, "/bin", basename)
240
- end
241
- expect(app.executables).to match_array(expected_executables)
242
- end
243
-
244
- it "generates an executable 'stub' for an executable in the app" do
245
- app_binary_1_path = app.executables.grep(/app\-binary\-1/).first
246
- executable_content = app.binstub(app_binary_1_path)
247
-
248
- shebang = executable_content.lines.first
249
- expect(shebang).to match(/^\#\!/)
250
- expect(shebang).to include(Gem.ruby)
251
-
252
- expect(executable_content).to include(app.runtime_activate)
253
-
254
- load_binary = executable_content.lines.to_a.last
255
-
256
- expected_load_path = %Q[Kernel.load(bin_file)\n]
257
-
258
- expect(load_binary).to eq(expected_load_path)
259
- end
260
-
261
- it "generates executable stubs for all executables in the app" do
262
- app.write_executable_stubs
263
- binary_1 = File.join(target_bindir, "app-binary-1")
264
- binary_2 = File.join(target_bindir, "app-binary-2")
265
- expect(File.exist?(binary_1)).to be(true)
266
- expect(File.exist?(binary_2)).to be(true)
267
- expect(File.executable?(binary_1)).to be(true)
268
- expect(File.executable?(binary_1)).to be(true)
269
- expect(shellout!(binary_1).stdout).to eq("binary 1 ran\n")
270
- expect(shellout!(binary_2).stdout).to eq("binary 2 ran\n")
271
- end
272
-
273
- context "and the executable is symlinked to a different directory" do
274
-
275
- let(:symlinks_root_dir) do
276
- Dir.mktmpdir
277
- end
278
-
279
- let(:symlinks_bin_dir) do
280
- d = File.join(symlinks_root_dir, "bin")
281
- FileUtils.mkdir(d)
282
- d
283
- end
284
-
285
- let(:binary_symlinked_path) { File.join(symlinks_bin_dir, "app-binary-1") }
286
-
287
- let(:binary_orignal_path) { File.join(target_bindir, "app-binary-1") }
288
-
289
- before do
290
- app.write_executable_stubs
291
- FileUtils.ln_s(binary_orignal_path, binary_symlinked_path)
292
- end
293
-
294
- after do
295
- FileUtils.rm_rf(symlinks_root_dir)
296
- end
297
-
298
- it "correctly runs the executable via the symlinked executable" do
299
- expect(shellout!(binary_symlinked_path).stdout).to eq("binary 1 ran\n")
300
- end
301
-
302
- end
303
-
304
- context "on windows" do
305
-
306
- let(:expected_ruby_relpath) do
307
- app.ruby_relative_path.gsub('/', '\\')
308
- end
309
-
310
- let(:expected_batch_code) do
311
- <<-E
312
- @ECHO OFF
313
- "%~dp0\\#{expected_ruby_relpath}" "%~dpn0" %*
314
- E
315
- end
316
-
317
- before do
318
- stub_const("RUBY_PLATFORM", "mingw")
319
- end
320
-
321
- it "creates a batchfile wrapper for each executable" do
322
- app.write_executable_stubs
323
- binary_1 = File.join(target_bindir, "app-binary-1.bat")
324
- binary_2 = File.join(target_bindir, "app-binary-2.bat")
325
- expect(File.exist?(binary_1)).to be(true)
326
- expect(File.exist?(binary_2)).to be(true)
327
- expect(IO.read(binary_1)).to eq(expected_batch_code)
328
- expect(IO.read(binary_2)).to eq(expected_batch_code)
329
- end
330
-
331
- end
332
-
333
- end
334
-
335
- 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