lib/appbundler/app.rb in appbundler-0.7.0 vs lib/appbundler/app.rb in appbundler-0.9.0

- old
+ new

@@ -1,33 +1,36 @@ require 'bundler' require 'fileutils' require 'pp' + module Appbundler + + class AppbundlerError < StandardError; end + + class InaccessibleGemsInLockfile < AppbundlerError; end + class App BINSTUB_FILE_VERSION=1 - attr_reader :app_root + attr_reader :bundle_path attr_reader :target_bin_dir + attr_reader :name - def self.demo - demo = new("/Users/ddeleo/oc/chef") - - knife = demo.executables.grep(/knife/).first - puts demo.binstub(knife) - end - - def initialize(app_root, target_bin_dir) - @app_root = app_root + def initialize(bundle_path, target_bin_dir, name) + @bundle_path = bundle_path @target_bin_dir = target_bin_dir + @name = name end # Copy over any .bundler and Gemfile.lock files to the target gem # directory. This will let us run tests from under that directory. def copy_bundler_env gem_path = app_gemspec.gem_dir + # If we're already using that directory, don't copy (it won't work anyway) + return if gem_path == File.dirname(gemfile_lock) FileUtils.install(gemfile_lock, gem_path, :mode => 0644) if File.exist?(dot_bundle_dir) && File.directory?(dot_bundle_dir) FileUtils.cp_r(dot_bundle_dir, gem_path) FileUtils.chmod_R("ugo+rX", File.join(gem_path, ".bundle")) end @@ -53,20 +56,16 @@ end executables_to_create end - def name - File.basename(app_root) - end - def dot_bundle_dir - File.join(app_root, ".bundle") + File.join(bundle_path, ".bundle") end def gemfile_lock - File.join(app_root, "Gemfile.lock") + File.join(bundle_path, "Gemfile.lock") end def ruby Gem.ruby end @@ -116,10 +115,11 @@ def env_sanitizer %Q{ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true"} end def runtime_activate + @runtime_activate ||= begin statements = runtime_dep_specs.map {|s| %Q|gem "#{s.name}", "= #{s.version}"|} activate_code = "" activate_code << env_sanitizer << "\n" activate_code << statements.join("\n") << "\n" @@ -171,10 +171,71 @@ def parsed_gemfile_lock @parsed_gemfile_lock ||= Bundler::LockfileParser.new(IO.read(gemfile_lock)) end + # Bundler stores gems loaded from git in locations like this: + # `lib/ruby/gems/2.1.0/bundler/gems/chef-b5860b44acdd`. Rubygems cannot + # find these during normal (non-bundler) operation. This will cause + # problems if there is no gem of the same version installed to the "normal" + # gem location, because the appbundler executable will end up containing a + # statement like `gem "foo", "= x.y.z"` which fails. + # + # However, if this gem/version has been manually installed (by building and + # installing via `gem` commands), then we end up with the correct + # appbundler file, even if it happens somewhat by accident. + # + # Therefore, this method lists all the git-sourced gems in the + # Gemfile.lock, then it checks if that version of the gem can be loaded via + # `Gem::Specification.find_by_name`. If there are any unloadable gems, then + # the InaccessibleGemsInLockfile error is raised. + def verify_deps_are_accessible! + inaccessable_gems = inaccessable_git_sourced_gems + return true if inaccessable_gems.empty? + + message = <<-MESSAGE +Application '#{name}' contains gems in the lockfile which are +not accessible by rubygems. This usually occurs when you fetch gems from git in +your Gemfile and do not install the same version of the gems beforehand. + +MESSAGE + + message << "The Gemfile.lock is located here:\n- #{gemfile_lock}\n\n" + + message << "The offending gems are:\n" + inaccessable_gems.each do |gemspec| + message << "- #{gemspec.name} (#{gemspec.version}) from #{gemspec.source}\n" + end + + message << "\n" + + message << "Rubygems is configured to search the following paths:\n" + Gem.paths.path.each { |p| message << "- #{p}\n" } + + message << "\n" + message << "If these seem wrong, you might need to set GEM_HOME or other environment\nvariables before running appbundler\n" + + raise InaccessibleGemsInLockfile, message + end + private + + def git_sourced_gems + runtime_dep_specs.select { |i| i.source.kind_of?(Bundler::Source::Git) } + end + + def inaccessable_git_sourced_gems + git_sourced_gems.reject do |spec| + gem_available?(spec) + end + end + + def gem_available?(spec) + Gem::Specification.find_by_name(spec.name, "= #{spec.version}") + true + rescue Gem::LoadError + false + end def add_dependencies_from(spec, collected_deps=[]) spec.dependencies.each do |dep| next if collected_deps.any? {|s| s.name == dep.name } # a bundler dep will not get pinned in Gemfile.lock