1# frozen_string_literal: true
2
3RSpec.describe "bundle binstubs <gem>" do
4  context "when the gem exists in the lockfile" do
5    it "sets up the binstub" do
6      install_gemfile <<-G
7        source "file://#{gem_repo1}"
8        gem "rack"
9      G
10
11      bundle "binstubs rack"
12
13      expect(bundled_app("bin/rackup")).to exist
14    end
15
16    it "does not install other binstubs" do
17      install_gemfile <<-G
18        source "file://#{gem_repo1}"
19        gem "rack"
20        gem "rails"
21      G
22
23      bundle "binstubs rails"
24
25      expect(bundled_app("bin/rackup")).not_to exist
26      expect(bundled_app("bin/rails")).to exist
27    end
28
29    it "does install multiple binstubs" do
30      install_gemfile <<-G
31        source "file://#{gem_repo1}"
32        gem "rack"
33        gem "rails"
34      G
35
36      bundle "binstubs rails rack"
37
38      expect(bundled_app("bin/rackup")).to exist
39      expect(bundled_app("bin/rails")).to exist
40    end
41
42    it "allows installing all binstubs" do
43      install_gemfile! <<-G
44        source "file://#{gem_repo1}"
45        gem "rails"
46      G
47
48      bundle! :binstubs, :all => true
49
50      expect(bundled_app("bin/rails")).to exist
51      expect(bundled_app("bin/rake")).to exist
52    end
53
54    it "displays an error when used without any gem" do
55      install_gemfile <<-G
56        source "file://#{gem_repo1}"
57        gem "rack"
58      G
59
60      bundle "binstubs"
61      expect(exitstatus).to eq(1) if exitstatus
62      expect(out).to include("`bundle binstubs` needs at least one gem to run.")
63    end
64
65    it "displays an error when used with --all and gems" do
66      install_gemfile <<-G
67        source "file://#{gem_repo1}"
68        gem "rack"
69      G
70
71      bundle "binstubs rack", :all => true
72      expect(last_command).to be_failure
73      expect(last_command.bundler_err).to include("Cannot specify --all with specific gems")
74    end
75
76    context "when generating bundle binstub outside bundler" do
77      it "should abort" do
78        install_gemfile <<-G
79          source "file://#{gem_repo1}"
80          gem "rack"
81        G
82
83        bundle "binstubs rack"
84
85        File.open("bin/bundle", "wb") do |file|
86          file.print "OMG"
87        end
88
89        sys_exec "bin/rackup"
90
91        expect(last_command.stderr).to include("was not generated by Bundler")
92      end
93    end
94
95    context "the bundle binstub" do
96      before do
97        if system_bundler_version == :bundler
98          system_gems :bundler
99        elsif system_bundler_version
100          build_repo4 do
101            build_gem "bundler", system_bundler_version do |s|
102              s.executables = "bundle"
103              s.bindir = "exe"
104              s.write "exe/bundle", "puts %(system bundler #{system_bundler_version}\\n\#{ARGV.inspect})"
105            end
106          end
107          system_gems "bundler-#{system_bundler_version}", :gem_repo => gem_repo4
108        end
109        build_repo2 do
110          build_gem "prints_loaded_gems", "1.0" do |s|
111            s.executables = "print_loaded_gems"
112            s.bindir = "exe"
113            s.write "exe/print_loaded_gems", <<-R
114              specs = Gem.loaded_specs.values.reject {|s| Bundler.rubygems.spec_default_gem?(s) }
115              puts specs.map(&:full_name).sort.inspect
116            R
117          end
118        end
119        install_gemfile! <<-G
120          source "file://#{gem_repo2}"
121          gem "rack"
122          gem "prints_loaded_gems"
123        G
124        bundle! "binstubs bundler rack prints_loaded_gems"
125      end
126
127      # When environment has a same version of bundler as default gems.
128      # `system_gems "bundler-x.y.z"` will detect system binstub.
129      # We need to avoid it by virtual version of bundler.
130      let(:system_bundler_version) { Gem::Version.new(Bundler::VERSION).bump.to_s }
131
132      context "when system bundler was used" do
133        # Support master branch of bundler
134        if ENV["BUNDLER_SPEC_SUB_VERSION"]
135          let(:system_bundler_version) { Bundler::VERSION }
136        end
137        it "runs bundler" do
138          sys_exec! "#{bundled_app("bin/bundle")} install"
139          expect(out).to eq %(system bundler #{system_bundler_version}\n["install"])
140        end
141      end
142
143      context "when BUNDLER_VERSION is set" do
144        let(:system_bundler_version) { Bundler::VERSION }
145
146        it "runs the correct version of bundler" do
147          sys_exec "BUNDLER_VERSION='999.999.999' #{bundled_app("bin/bundle")} install"
148          expect(exitstatus).to eq(42) if exitstatus
149          expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:").
150            and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
151        end
152      end
153
154      context "when a lockfile exists with a locked bundler version" do
155        let(:system_bundler_version) { Bundler::VERSION }
156
157        it "runs the correct version of bundler when the version is newer" do
158          lockfile lockfile.gsub(system_bundler_version, "999.999.999")
159          sys_exec "#{bundled_app("bin/bundle")} install"
160          expect(exitstatus).to eq(42) if exitstatus
161          expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:").
162            and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
163        end
164
165        it "runs the correct version of bundler when the version is older" do
166          simulate_bundler_version "55"
167          lockfile lockfile.gsub(system_bundler_version, "44.0")
168          sys_exec "#{bundled_app("bin/bundle")} install"
169          expect(exitstatus).to eq(42) if exitstatus
170          expect(last_command.stderr).to include("Activating bundler (44.0) failed:").
171            and include("To install the version of bundler this project requires, run `gem install bundler -v '44.0'`")
172        end
173
174        it "runs the correct version of bundler when the version is a pre-release" do
175          simulate_bundler_version "55"
176          lockfile lockfile.gsub(system_bundler_version, "2.12.0.a")
177          sys_exec "#{bundled_app("bin/bundle")} install"
178          expect(exitstatus).to eq(42) if exitstatus
179          expect(last_command.stderr).to include("Activating bundler (2.12.0.a) failed:").
180            and include("To install the version of bundler this project requires, run `gem install bundler -v '2.12.0.a'`")
181        end
182      end
183
184      context "when update --bundler is called" do
185        before { lockfile.gsub(system_bundler_version, "1.1.1") }
186
187        it "calls through to the latest bundler version" do
188          sys_exec! "#{bundled_app("bin/bundle")} update --bundler"
189          expect(last_command.stdout).to eq %(system bundler #{system_bundler_version}\n["update", "--bundler"])
190        end
191
192        it "calls through to the explicit bundler version" do
193          sys_exec "#{bundled_app("bin/bundle")} update --bundler=999.999.999"
194          expect(exitstatus).to eq(42) if exitstatus
195          expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:").
196            and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
197        end
198      end
199
200      context "without a lockfile" do
201        it "falls back to the latest installed bundler" do
202          FileUtils.rm bundled_app("Gemfile.lock")
203          sys_exec! bundled_app("bin/bundle").to_s
204          expect(out).to eq "system bundler #{system_bundler_version}\n[]"
205        end
206      end
207
208      context "using another binstub" do
209        let(:system_bundler_version) { :bundler }
210        it "loads all gems" do
211          sys_exec! bundled_app("bin/print_loaded_gems").to_s
212          # RG < 2.0.14 didn't have a `Gem::Specification#default_gem?`
213          # This is dirty detection for old RG versions.
214          if File.dirname(Bundler.load.specs["bundler"][0].loaded_from) =~ %r{specifications/default}
215            expect(out).to eq %(["prints_loaded_gems-1.0", "rack-1.2"])
216          else
217            expect(out).to eq %(["bundler-#{Bundler::VERSION}", "prints_loaded_gems-1.0", "rack-1.2"])
218          end
219        end
220
221        context "when requesting a different bundler version" do
222          before { lockfile lockfile.gsub(Bundler::VERSION, "999.999.999") }
223
224          it "attempts to load that version", :ruby_repo do
225            sys_exec bundled_app("bin/rackup").to_s
226            expect(exitstatus).to eq(42) if exitstatus
227            expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:").
228              and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`")
229          end
230        end
231      end
232    end
233
234    it "installs binstubs from git gems" do
235      FileUtils.mkdir_p(lib_path("foo/bin"))
236      FileUtils.touch(lib_path("foo/bin/foo"))
237      build_git "foo", "1.0", :path => lib_path("foo") do |s|
238        s.executables = %w[foo]
239      end
240      install_gemfile <<-G
241        gem "foo", :git => "#{lib_path("foo")}"
242      G
243
244      bundle "binstubs foo"
245
246      expect(bundled_app("bin/foo")).to exist
247    end
248
249    it "installs binstubs from path gems" do
250      FileUtils.mkdir_p(lib_path("foo/bin"))
251      FileUtils.touch(lib_path("foo/bin/foo"))
252      build_lib "foo", "1.0", :path => lib_path("foo") do |s|
253        s.executables = %w[foo]
254      end
255      install_gemfile <<-G
256        gem "foo", :path => "#{lib_path("foo")}"
257      G
258
259      bundle "binstubs foo"
260
261      expect(bundled_app("bin/foo")).to exist
262    end
263
264    it "sets correct permissions for binstubs" do
265      with_umask(0o002) do
266        install_gemfile <<-G
267          source "file://#{gem_repo1}"
268          gem "rack"
269        G
270
271        bundle "binstubs rack"
272        binary = bundled_app("bin/rackup")
273        expect(File.stat(binary).mode.to_s(8)).to eq("100775")
274      end
275    end
276
277    context "when using --shebang" do
278      it "sets the specified shebang for the the binstub" do
279        install_gemfile <<-G
280          source "file://#{gem_repo1}"
281          gem "rack"
282        G
283
284        bundle "binstubs rack --shebang jruby"
285
286        expect(File.open("bin/rackup").gets).to eq("#!/usr/bin/env jruby\n")
287      end
288    end
289  end
290
291  context "when the gem doesn't exist" do
292    it "displays an error with correct status" do
293      install_gemfile <<-G
294        source "file://#{gem_repo1}"
295      G
296
297      bundle "binstubs doesnt_exist"
298
299      expect(exitstatus).to eq(7) if exitstatus
300      expect(out).to include("Could not find gem 'doesnt_exist'.")
301    end
302  end
303
304  context "--path" do
305    it "sets the binstubs dir" do
306      install_gemfile <<-G
307        source "file://#{gem_repo1}"
308        gem "rack"
309      G
310
311      bundle "binstubs rack --path exec"
312
313      expect(bundled_app("exec/rackup")).to exist
314    end
315
316    it "setting is saved for bundle install", :bundler => "< 2" do
317      install_gemfile <<-G
318        source "file://#{gem_repo1}"
319        gem "rack"
320        gem "rails"
321      G
322
323      bundle! "binstubs rack", forgotten_command_line_options([:path, :bin] => "exec")
324      bundle! :install
325
326      expect(bundled_app("exec/rails")).to exist
327    end
328  end
329
330  context "with --standalone option" do
331    before do
332      install_gemfile <<-G
333        source "file://#{gem_repo1}"
334        gem "rack"
335      G
336    end
337
338    it "generates a standalone binstub" do
339      bundle! "binstubs rack --standalone"
340      expect(bundled_app("bin/rackup")).to exist
341    end
342
343    it "generates a binstub that does not depend on rubygems or bundler" do
344      bundle! "binstubs rack --standalone"
345      expect(File.read(bundled_app("bin/rackup"))).to_not include("Gem.bin_path")
346    end
347
348    context "when specified --path option" do
349      it "generates a standalone binstub at the given path" do
350        bundle! "binstubs rack --standalone --path foo"
351        expect(bundled_app("foo/rackup")).to exist
352      end
353    end
354  end
355
356  context "when the bin already exists" do
357    it "doesn't overwrite and warns" do
358      FileUtils.mkdir_p(bundled_app("bin"))
359      File.open(bundled_app("bin/rackup"), "wb") do |file|
360        file.print "OMG"
361      end
362
363      install_gemfile <<-G
364        source "file://#{gem_repo1}"
365        gem "rack"
366      G
367
368      bundle "binstubs rack"
369
370      expect(bundled_app("bin/rackup")).to exist
371      expect(File.read(bundled_app("bin/rackup"))).to eq("OMG")
372      expect(out).to include("Skipped rackup")
373      expect(out).to include("overwrite skipped stubs, use --force")
374    end
375
376    context "when using --force" do
377      it "overwrites the binstub" do
378        FileUtils.mkdir_p(bundled_app("bin"))
379        File.open(bundled_app("bin/rackup"), "wb") do |file|
380          file.print "OMG"
381        end
382
383        install_gemfile <<-G
384          source "file://#{gem_repo1}"
385          gem "rack"
386        G
387
388        bundle "binstubs rack --force"
389
390        expect(bundled_app("bin/rackup")).to exist
391        expect(File.read(bundled_app("bin/rackup"))).not_to eq("OMG")
392      end
393    end
394  end
395
396  context "when the gem has no bins" do
397    it "suggests child gems if they have bins" do
398      install_gemfile <<-G
399        source "file://#{gem_repo1}"
400        gem "rack-obama"
401      G
402
403      bundle "binstubs rack-obama"
404      expect(out).to include("rack-obama has no executables")
405      expect(out).to include("rack has: rackup")
406    end
407
408    it "works if child gems don't have bins" do
409      install_gemfile <<-G
410        source "file://#{gem_repo1}"
411        gem "actionpack"
412      G
413
414      bundle "binstubs actionpack"
415      expect(out).to include("no executables for the gem actionpack")
416    end
417
418    it "works if the gem has development dependencies" do
419      install_gemfile <<-G
420        source "file://#{gem_repo1}"
421        gem "with_development_dependency"
422      G
423
424      bundle "binstubs with_development_dependency"
425      expect(out).to include("no executables for the gem with_development_dependency")
426    end
427  end
428
429  context "when BUNDLE_INSTALL is specified" do
430    it "performs an automatic bundle install" do
431      gemfile <<-G
432        source "file://#{gem_repo1}"
433        gem "rack"
434      G
435
436      bundle "config auto_install 1"
437      bundle "binstubs rack"
438      expect(out).to include("Installing rack 1.0.0")
439      expect(the_bundle).to include_gems "rack 1.0.0"
440    end
441
442    it "does nothing when already up to date" do
443      install_gemfile <<-G
444        source "file://#{gem_repo1}"
445        gem "rack"
446      G
447
448      bundle "config auto_install 1"
449      bundle "binstubs rack", :env => { "BUNDLE_INSTALL" => 1 }
450      expect(out).not_to include("Installing rack 1.0.0")
451    end
452  end
453end
454