diff options
28 files changed, 278 insertions, 29 deletions
diff --git a/.gitignore b/.gitignore index 88730f82cd..6230ec1c99 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ man/* # rspec failure tracking .rspec_status + +# files generated during packaging +/lib/bundler/generated/ @@ -366,10 +366,8 @@ task :update_certs => "spec:rubygems:clone_rubygems_master" do Bundler::SSLCerts::CertificateManager.update_from!(RUBYGEMS_REPO) end -require "bundler/gem_tasks" -task :build => ["man:build"] -task :release => ["man:require", "man:build"] - task :default => :spec Dir["task/*.{rb,rake}"].each(&method(:load)) + +task :generate_files => Rake::Task.tasks.select {|t| t.name.start_with?("lib/bundler/generated") } diff --git a/lib/bundler.rb b/lib/bundler.rb index 1228785734..c6d68c49de 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -13,6 +13,7 @@ require "bundler/rubygems_integration" require "bundler/version" require "bundler/constants" require "bundler/current_ruby" +require "bundler/build_metadata" module Bundler environment_preserver = EnvironmentPreserver.new(ENV, EnvironmentPreserver::BUNDLER_KEYS) @@ -206,7 +207,7 @@ module Bundler def root @root ||= begin - default_gemfile.dirname.expand_path + SharedHelpers.root rescue GemfileNotFound bundle_dir = default_bundle_dir raise GemfileNotFound, "Could not locate Gemfile or .bundle/ directory" unless bundle_dir diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb new file mode 100644 index 0000000000..54436f982d --- /dev/null +++ b/lib/bundler/build_metadata.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Bundler + # Represents metadata from when the Bundler gem was built. + module BuildMetadata + # begin ivars + @release = false + # end ivars + + # A hash representation of the build metadata. + def self.to_h + { + "Built At" => built_at, + "Git SHA" => git_commit_sha, + "Released Version" => release?, + } + end + + # A string representing the date the bundler gem was built. + def self.built_at + @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze + end + + # The SHA for the git commit the bundler gem was built from. + def self.git_commit_sha + @git_commit_sha ||= Dir.chdir(File.expand_path("..", __FILE__)) do + `git rev-parse --short HEAD`.strip.freeze + end + end + + # Whether this is an official release build of Bundler. + def self.release? + @release + end + end +end diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index e93fc9529d..838dcb3e08 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -262,6 +262,8 @@ module Bundler "Overwrite existing binstubs if they exist" method_option "path", :type => :string, :lazy_default => "bin", :banner => "Binstub destination directory (default bin)" + method_option "shebang", :type => :string, :banner => + "Specify a different shebang executable name than the default (usually 'ruby')" method_option "standalone", :type => :boolean, :banner => "Make binstubs that can work without the Bundler runtime" def binstubs(*gems) @@ -395,7 +397,10 @@ module Bundler desc "version", "Prints the bundler's version information" def version - Bundler.ui.info "Bundler version #{Bundler::VERSION}" + if ARGV.include?("version") + build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})" + end + Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}" end map %w[-v --version] => :version diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb index 82815062d3..f872a2dcc7 100644 --- a/lib/bundler/cli/binstubs.rb +++ b/lib/bundler/cli/binstubs.rb @@ -13,6 +13,7 @@ module Bundler Bundler.definition.validate_runtime! Bundler.settings[:bin] = options["path"] if options["path"] Bundler.settings[:bin] = nil if options["path"] && options["path"].empty? + Bundler.settings[:shebang] = options["shebang"] if options["shebang"] installer = Installer.new(Bundler.root, Bundler.definition) if gems.empty? diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 4b75b39065..d7faff53e7 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -194,10 +194,19 @@ module Bundler missing end - def missing_dependencies + def missing_dependencies? missing = [] resolve.materialize(current_dependencies, missing) - missing + return false if missing.empty? + Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}" + true + rescue BundlerError => e + Bundler.ui.debug "The definition is missing dependencies, failed to resolve & materialize locally (#{e})" + true + ensure + @index = nil + @resolve = nil + @specs = nil end def requested_specs diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb index 325b96fbfa..333173ac27 100644 --- a/lib/bundler/env.rb +++ b/lib/bundler/env.rb @@ -14,6 +14,7 @@ module Bundler out = String.new append_formatted_table("Environment", environment, out) + append_formatted_table("Bundler Build Metadata", BuildMetadata.to_h, out) unless Bundler.settings.all.empty? out << "\n## Bundler settings\n\n```\n" diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index f472ba2b7b..34fddb1ecc 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -19,6 +19,7 @@ module Bundler settings_flag(:init_gems_rb) { bundler_2_mode? } settings_flag(:only_update_to_newer_versions) { bundler_2_mode? } settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } + settings_flag(:prefer_gems_rb) { bundler_2_mode? } settings_flag(:update_requires_all_flag) { bundler_2_mode? } def initialize(bundler_version) diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index ae8055408f..7c13c0c8a9 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -78,7 +78,7 @@ module Bundler return end - resolve_if_need(options) + resolve_if_needed(options) ensure_specs_are_compatible! install(options) @@ -229,18 +229,11 @@ module Bundler "because a file already exists at that path. Either remove or rename the file so the directory can be created." end - def resolve_if_need(options) + def resolve_if_needed(options) if !options["update"] && !options[:inline] && !options["force"] && Bundler.default_lockfile.file? - local = Bundler.ui.silence do - begin - tmpdef = Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, nil) - true unless tmpdef.new_platform? || tmpdef.missing_dependencies.any? - rescue BundlerError - end - end + return if @definition.nothing_changed? && !@definition.missing_dependencies? end - return if local options["local"] ? @definition.resolve_with_cache! : @definition.resolve_remotely! end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 34747cbed7..9f4d5b30a0 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -28,6 +28,7 @@ module Bundler no_prune only_update_to_newer_versions plugins + prefer_gems_rb silence_root_warning update_requires_all_flag ].freeze diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 593d2c1e06..39f0423f7f 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -19,9 +19,15 @@ end module Bundler module SharedHelpers - def default_gemfile + def root gemfile = find_gemfile raise GemfileNotFound, "Could not locate Gemfile" unless gemfile + Pathname.new(gemfile).untaint.expand_path.parent + end + + def default_gemfile + gemfile = find_gemfile(:order_matters) + raise GemfileNotFound, "Could not locate Gemfile" unless gemfile Pathname.new(gemfile).untaint end @@ -137,7 +143,7 @@ module Bundler end def print_major_deprecations! - deprecate_gemfile(find_gemfile) if find_gemfile == find_file("Gemfile") + deprecate_gemfile(find_gemfile) if find_gemfile(:order_matters) == find_file("Gemfile") if RUBY_VERSION < "2" major_deprecation("Bundler will only support ruby >= 2.0, you are running #{RUBY_VERSION}") end @@ -172,10 +178,16 @@ module Bundler private - def find_gemfile + def find_gemfile(order_matters = false) given = ENV["BUNDLE_GEMFILE"] return given if given && !given.empty? - find_file("Gemfile", "gems.rb") + names = gemfile_names + names.reverse! if order_matters && Bundler.feature_flag.prefer_gems_rb? + find_file(*names) + end + + def gemfile_names + ["Gemfile", "gems.rb"] end def find_file(*names) @@ -228,7 +240,7 @@ module Bundler end # Set BUNDLE_GEMFILE - Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s + Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile(:order_matters).to_s Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION end diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 82f987fdd8..99808d2ef2 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -35,10 +35,12 @@ module Bundler end def remote! + @local_specs = nil @allow_remote = true end def cached! + @local_specs = nil @allow_cached = true end diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 353194f53f..a1d4266960 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -31,6 +31,7 @@ module Bundler end def cached! + @specs = nil @allow_cached = true end diff --git a/lib/bundler/templates/Gemfile b/lib/bundler/templates/Gemfile index 7db8998d32..21c6283123 100644 --- a/lib/bundler/templates/Gemfile +++ b/lib/bundler/templates/Gemfile @@ -1,4 +1,6 @@ # frozen_string_literal: true source "https://rubygems.org" +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + # gem "rails" diff --git a/lib/bundler/templates/gems.rb b/lib/bundler/templates/gems.rb index a6ef23ba3e..9495f50c0e 100644 --- a/lib/bundler/templates/gems.rb +++ b/lib/bundler/templates/gems.rb @@ -2,4 +2,6 @@ # A sample gems.rb source "https://rubygems.org" +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + # gems "rails" diff --git a/lib/bundler/templates/newgem/Gemfile.tt b/lib/bundler/templates/newgem/Gemfile.tt index 4cd2e40f4f..c114bd6665 100644 --- a/lib/bundler/templates/newgem/Gemfile.tt +++ b/lib/bundler/templates/newgem/Gemfile.tt @@ -1,4 +1,6 @@ source "https://rubygems.org" +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + # Specify your gem's dependencies in <%= config[:name] %>.gemspec gemspec diff --git a/man/bundle-config.ronn b/man/bundle-config.ronn index d387350f8b..4fa0025815 100644 --- a/man/bundle-config.ronn +++ b/man/bundle-config.ronn @@ -223,9 +223,11 @@ learn more about their operation in [bundle install(1)][bundle-install]. Sets the `--key` parameter for `gem push` when using the `rake release` command with a private gemstash server. * `error_on_stderr` (`BUNDLE_ERROR_ON_STDERR`) - Print Bundler errors to stderr + Print Bundler errors to stderr. * `init_gems_rb` (`BUNDLE_NEW_GEMFILE_NAME`) - generate a new gems.rb on `bundle init` instead of the `Gemfile` + Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`. +* `prefer_gems_rb` (`BUNDLE_PREFER_GEMS_RB`) + Prefer `gems.rb` to `Gemfile` when Bundler is searching for a Gemfile. * `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`) Require passing `--all` to `bundle update` when everything should be updated, and disallow passing no options to `bundle update`. diff --git a/spec/bundler/definition_spec.rb b/spec/bundler/definition_spec.rb index 4e93d5e01c..cb642da9f7 100644 --- a/spec/bundler/definition_spec.rb +++ b/spec/bundler/definition_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Bundler::Definition do describe "#lock" do before do allow(Bundler).to receive(:settings) { Bundler::Settings.new(".") } - allow(Bundler).to receive(:default_gemfile) { Pathname.new("Gemfile") } + allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") } allow(Bundler).to receive(:ui) { double("UI", :info => "", :debug => "") } end context "when it's not possible to write to the file" do diff --git a/spec/commands/binstubs_spec.rb b/spec/commands/binstubs_spec.rb index f3ca2301a7..430dbef314 100644 --- a/spec/commands/binstubs_spec.rb +++ b/spec/commands/binstubs_spec.rb @@ -103,6 +103,19 @@ RSpec.describe "bundle binstubs <gem>" do expect(File.stat(binary).mode.to_s(8)).to eq("100775") end end + + context "when using --shebang" do + it "sets the specified shebang for the the binstub" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack --shebang jruby" + + expect(File.open("bin/rackup").gets).to eq("#!/usr/bin/env jruby\n") + end + end end context "when the gem doesn't exist" do diff --git a/spec/commands/version_spec.rb b/spec/commands/version_spec.rb new file mode 100644 index 0000000000..478edb9e67 --- /dev/null +++ b/spec/commands/version_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +RSpec.describe "bundle version" do + context "with -v" do + it "outputs the version" do + bundle! "-v" + expect(out).to eq("Bundler version #{Bundler::VERSION}") + end + end + + context "with --version" do + it "outputs the version" do + bundle! "--version" + expect(out).to eq("Bundler version #{Bundler::VERSION}") + end + end + + context "with version" do + it "outputs the version with build metadata" do + date = Bundler::BuildMetadata.built_at + git_commit_sha = Bundler::BuildMetadata.git_commit_sha + bundle! "version" + expect(out).to eq("Bundler version #{Bundler::VERSION} (#{date} commit #{git_commit_sha})") + end + end +end diff --git a/spec/install/gemfile/git_spec.rb b/spec/install/gemfile/git_spec.rb index 31abb4de72..59bbc9c016 100644 --- a/spec/install/gemfile/git_spec.rb +++ b/spec/install/gemfile/git_spec.rb @@ -1144,11 +1144,11 @@ RSpec.describe "bundle install with git sources" do end `git commit -m 'commit for iteration #{i}' ext/foo.c` end - git_sha = git_reader.ref_for("HEAD") + git_commit_sha = git_reader.ref_for("HEAD") install_gemfile <<-G source "file://#{gem_repo1}" - gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_sha}" + gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_commit_sha}" G run <<-R diff --git a/spec/install/gemfile/lockfile_spec.rb b/spec/install/gemfile/lockfile_spec.rb new file mode 100644 index 0000000000..dc1baca6ea --- /dev/null +++ b/spec/install/gemfile/lockfile_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with a lockfile present" do + let(:gf) { <<-G } + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + G + + subject do + install_gemfile(gf) + end + + context "gemfile evaluation" do + let(:gf) { super() + "\n\n File.open('evals', 'a') {|f| f << %(1\n) } unless ENV['BUNDLER_SPEC_NO_APPEND']" } + + context "with plugins disabled" do + before do + bundle! "config plugins false" + subject + end + + it "does not evaluate the gemfile twice" do + bundle! :install + + with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "rack 1.0.0" } + + # The first eval is from the initial install, we're testing that the + # second install doesn't double-eval + expect(bundled_app("evals").read.lines.to_a.size).to eq(2) + end + + context "when the gem is not installed" do + before { FileUtils.rm_rf ".bundle" } + + it "does not evaluate the gemfile twice" do + bundle! :install + + with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "rack 1.0.0" } + + # The first eval is from the initial install, we're testing that the + # second install doesn't double-eval + expect(bundled_app("evals").read.lines.to_a.size).to eq(2) + end + end + end + end +end diff --git a/spec/install/gemfile_spec.rb b/spec/install/gemfile_spec.rb index 9262dcc92d..d1b1f1e6b8 100644 --- a/spec/install/gemfile_spec.rb +++ b/spec/install/gemfile_spec.rb @@ -66,6 +66,22 @@ RSpec.describe "bundle install" do end end + context "with prefer_gems_rb set" do + before { bundle! "config prefer_gems_rb true" } + + it "prefers gems.rb to Gemfile" do + create_file("gems.rb", "gem 'bundler'") + create_file("Gemfile", "raise 'wrong Gemfile!'") + + bundle! :install + + expect(bundled_app("gems.rb")).to be_file + expect(bundled_app("Gemfile.lock")).not_to be_file + + expect(the_bundle).to include_gem "bundler #{Bundler::VERSION}" + end + end + context "with engine specified in symbol" do it "does not raise any error parsing Gemfile" do simulate_ruby_version "2.3.0" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f6700ca315..782cffc1d1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -46,7 +46,6 @@ Spec::Rubygems.setup FileUtils.rm_rf(Spec::Path.gem_repo1) ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -r#{Spec::Path.root}/spec/support/hax.rb" ENV["BUNDLE_SPEC_RUN"] = "true" -ENV["BUNDLE_PLUGINS"] = "true" # Don't wrap output in tests ENV["THOR_COLUMNS"] = "10000" diff --git a/task/build_metadata.rake b/task/build_metadata.rake new file mode 100644 index 0000000000..e06a795259 --- /dev/null +++ b/task/build_metadata.rake @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +def write_build_metadata(build_metadata) + build_metadata_file = "lib/bundler/build_metadata.rb" + + ivars = build_metadata.sort.map do |k, v| + " @#{k} = #{BUNDLER_SPEC.send(:ruby_code, v)}" + end.join("\n") + + contents = File.read(build_metadata_file) + contents.sub!(/^(\s+# begin ivars).+(^\s+# end ivars)/m, "\\1\n#{ivars}\n\\2") + File.open(build_metadata_file, "w") {|f| f << contents } +end + +task :build_metadata do + build_metadata = { + :built_at => BUNDLER_SPEC.date.utc.strftime("%Y-%m-%d"), + :git_commit_sha => `git rev-parse --short HEAD`.strip, + :release => Rake::Task["release"].instance_variable_get(:@already_invoked), + } + + write_build_metadata(build_metadata) +end + +namespace :build_metadata do + task :clean do + build_metadata = { + :release => false, + } + + write_build_metadata(build_metadata) + end +end diff --git a/task/git_hooks.rake b/task/git_hooks.rake new file mode 100644 index 0000000000..e70d66afaa --- /dev/null +++ b/task/git_hooks.rake @@ -0,0 +1,35 @@ +# frozen_string_literal: true +directory ".git/hooks" + +file ".git/hooks/pre-commit" => [__FILE__] do |t| + File.open(t.name, "w") {|f| f << <<-SH } +#!/bin/sh + +set -e + +.git/hooks/run-ruby bin/rubocop +.git/hooks/run-ruby bin/rspec spec/quality_spec.rb + SH + + chmod 0o755, t.name, :verbose => false +end + +file ".git/hooks/pre-push" => [__FILE__] do |_t| + Dir.chdir(".git/hooks") do + safe_ln "pre-commit", "pre-push", :verbose => false + end +end + +file ".git/hooks/run-ruby" => [__FILE__] do |t| + File.open(t.name, "w") {|f| f << <<-SH } +#!/bin/bash + +ruby="ruby" +command -v chruby-exec >/dev/null 2>&1 && [[ -f ~/.ruby-version ]] && ruby="chruby-exec $(cat ~/.ruby-version) --" +ruby $@ + SH + + chmod 0o755, t.name, :verbose => false +end + +task :git_hooks => Rake::Task.tasks.select {|t| t.name.start_with?(".git/hooks") } diff --git a/task/release.rake b/task/release.rake index 104fe1f857..8feaec3ae4 100644 --- a/task/release.rake +++ b/task/release.rake @@ -1,4 +1,11 @@ # frozen_string_literal: true + +require "bundler/gem_tasks" +task :build => ["build_metadata", "man:build", "generate_files"] do + Rake::Task["build_metadata:clean"].tap(&:reenable).real_invoke +end +task :release => ["man:require", "man:build", "build_metadata"] + namespace :release do def confirm(prompt = "") loop do |