diff options
-rw-r--r-- | lib/bubble.rb | 11 | ||||
-rw-r--r-- | lib/bubble/cli.rb | 9 | ||||
-rw-r--r-- | lib/bubble/definition.rb | 74 | ||||
-rw-r--r-- | lib/bubble/dsl.rb | 19 | ||||
-rw-r--r-- | lib/bubble/environment.rb | 21 | ||||
-rw-r--r-- | lib/bubble/source.rb | 11 | ||||
-rw-r--r-- | spec/lock/gems_spec.rb | 23 | ||||
-rw-r--r-- | spec/runtime/load_spec.rb | 84 | ||||
-rw-r--r-- | spec/support/builders.rb | 2 | ||||
-rw-r--r-- | spec/support/matchers.rb | 2 |
10 files changed, 231 insertions, 25 deletions
diff --git a/lib/bubble.rb b/lib/bubble.rb index 47e990f621..944a6c645e 100644 --- a/lib/bubble.rb +++ b/lib/bubble.rb @@ -1,4 +1,5 @@ require 'pathname' +require 'yaml' require 'bubble/rubygems' module Bubble @@ -18,17 +19,23 @@ module Bubble class GemfileNotFound < StandardError; end class GemNotFound < StandardError; end class VersionConflict < StandardError; end + class GemfileError < StandardError; end def self.setup(gemfile = nil) load(gemfile).setup end def self.load(gemfile = nil) - Environment.new(definition(gemfile)) + Environment.new definition(gemfile) end def self.definition(gemfile = nil) - Definition.from_gemfile(gemfile) + lockfile = Pathname.new(gemfile || Definition.default_gemfile).dirname.join('omg.yml') + if lockfile.exist? + Definition.from_lock(lockfile) + else + Definition.from_gemfile(gemfile) + end end def self.home diff --git a/lib/bubble/cli.rb b/lib/bubble/cli.rb index 53a9b0707b..d99d2e4a18 100644 --- a/lib/bubble/cli.rb +++ b/lib/bubble/cli.rb @@ -8,6 +8,10 @@ Gem.configuration module Bubble class CLI < Thor + def self.banner(task) + task.formatted_usage(self, false) + end + desc "init", "Generates a Gemfile into the current working directory" def init if File.exist?("Gemfile") @@ -43,6 +47,11 @@ module Bubble Installer.install(Bubble.definition) end + desc "lock", "Locks a resolve" + def lock + Bubble.load.lock + end + private def with_rescue diff --git a/lib/bubble/definition.rb b/lib/bubble/definition.rb index c775593f9d..242e2c0b3f 100644 --- a/lib/bubble/definition.rb +++ b/lib/bubble/definition.rb @@ -7,9 +7,7 @@ module Bubble raise GemfileNotFound, "`#{gemfile}` not found" end - definition = new - Dsl.evaluate(gemfile, definition) - definition + Dsl.evaluate(gemfile) end def self.default_gemfile @@ -24,10 +22,76 @@ module Bubble raise GemfileNotFound, "The default Gemfile was not found" end + def self.from_lock(lockfile) + gemfile_definition = from_gemfile(nil) + + details = YAML.load_file(lockfile) + sources = details["sources"].map do |args| + name, options = args.to_a.flatten + Bubble::Source.const_get(name).new(options) + end + + dependencies = details["dependencies"].map do |args| + Gem::Dependency.new(*args.to_a.flatten) + end + + specs = details["specs"].map do |args| + Gem::Dependency.new(*args.to_a.flatten) + end + + locked_definition = new(dependencies, sources, specs) + + raise GemfileError unless gemfile_definition.matches?(locked_definition) && + locked_definition.matches?(gemfile_definition) + + locked_definition + end + attr_reader :dependencies, :sources - def initialize - @dependencies, @sources = [], Gem.sources.map { |s| Source::Rubygems.new(:uri => s) } + def initialize(dependencies, sources, resolved_dependencies = nil) + @dependencies = dependencies + @sources = sources + + if resolved_dependencies + @specs = resolved_dependencies.map do |dep| + index.search(dep).first + end + end + end + + def matches?(other) + dependencies.all? do |dep| + dep =~ other.specs.find {|spec| spec.name == dep.name } + end + end + + def specs + @specs ||= Resolver.resolve(dependencies, index) + end + + def index + @index ||= begin + index = Index.new + sources.reverse_each do |source| + index.merge! source.local_specs + end + index + end + end + + def to_yaml(options = {}) + details.to_yaml(options) + end + + private + + def details + {}.tap do |det| + det["sources"] = sources.map { |s| { s.class.name.split("::").last => s.options} } + det["specs"] = specs.map { |s| {s.name => s.version.to_s} } + det["dependencies"] = dependencies.map { |d| {d.name => d.version_requirements.to_s} } + end end end end
\ No newline at end of file diff --git a/lib/bubble/dsl.rb b/lib/bubble/dsl.rb index 02668a2db7..7459b97c5c 100644 --- a/lib/bubble/dsl.rb +++ b/lib/bubble/dsl.rb @@ -2,14 +2,15 @@ module Bubble class DslError < StandardError; end class Dsl - def self.evaluate(gemfile, definition) - builder = new(definition) + def self.evaluate(gemfile) + builder = new builder.instance_eval(File.read(gemfile.to_s), gemfile.to_s, 1) - definition + builder.to_definition end - def initialize(definition) - @definition = definition + def initialize + @sources = Gem.sources.map { |s| Source::Rubygems.new(:uri => s) } + @dependencies = [] @git = nil @git_sources = {} end @@ -18,7 +19,7 @@ module Bubble options = Hash === args.last ? args.pop : {} version = args.last || ">= 0" - @definition.dependencies << Dependency.new(name, version, options) + @dependencies << Dependency.new(name, version, options) end def source(source) @@ -28,7 +29,7 @@ module Bubble else source end - @definition.sources << source + @sources << source end def path(path, options = {}) @@ -39,5 +40,9 @@ module Bubble source Source::Git.new(options.merge(:uri => uri)) end + def to_definition + Definition.new(@dependencies, @sources) + end + end end
\ No newline at end of file diff --git a/lib/bubble/environment.rb b/lib/bubble/environment.rb index a06bea85f4..adcb3e3bd7 100644 --- a/lib/bubble/environment.rb +++ b/lib/bubble/environment.rb @@ -1,5 +1,9 @@ module Bubble class Environment + def self.from_gemfile(gemfile) + new Definition.from_gemfile(gemfile) + end + def initialize(definition) @definition = definition end @@ -16,18 +20,19 @@ module Bubble @definition.dependencies end + def lock + yml = @definition.to_yaml + File.open("#{Definition.default_gemfile.dirname}/omg.yml", 'w') do |f| + f.puts yml + end + end + def specs - @specs ||= Resolver.resolve(dependencies, index) + @definition.specs end def index - @index ||= begin - index = Index.new - @definition.sources.reverse_each do |source| - index.merge! source.local_specs - end - index - end + @definition.index end end diff --git a/lib/bubble/source.rb b/lib/bubble/source.rb index 137a247cb1..1e347a92f0 100644 --- a/lib/bubble/source.rb +++ b/lib/bubble/source.rb @@ -4,9 +4,10 @@ require "digest/sha1" module Bubble module Source class Rubygems - attr_reader :uri + attr_reader :uri, :options def initialize(options = {}) + @options = options @uri = options[:uri] @uri = URI.parse(@uri) unless @uri.is_a?(URI) raise ArgumentError, "The source must be an absolute URI" unless @uri.absolute? @@ -52,9 +53,10 @@ module Bubble end class Path - attr_reader :path + attr_reader :path, :options def initialize(options) + @options = options @glob = options[:glob] || "{,*/}*.gemspec" @path = options[:path] end @@ -86,11 +88,16 @@ module Bubble attr_reader :uri, :ref def initialize(options) + @options = options @glob = options[:glob] || "{,*/}*.gemspec" @uri = options[:uri] @ref = options[:ref] || options[:branch] || 'master' end + def to_yaml(options = {}) + { :uri => @uri.to_s, :ref => @ref, :glob => @glob }.to_yaml + end + def path Bubble.install_path.join("#{base_name}-#{uri_hash}-#{ref}") end diff --git a/spec/lock/gems_spec.rb b/spec/lock/gems_spec.rb new file mode 100644 index 0000000000..25d3baddf8 --- /dev/null +++ b/spec/lock/gems_spec.rb @@ -0,0 +1,23 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "bbl lock with gems" do + before :each do + in_app_root + end + + it "locks the gemfile resolve to the versions available at the time" do + gemfile <<-G + gem "rack" + G + + system_gems "rack-0.9.1" do + bbl :lock + end + + system_gems "rack-1.0.0", "rack-0.9.1" do + should_be_available "rack 0.9.1" + end + end + + it "includes the ruby version as a dependency of the lock" +end
\ No newline at end of file diff --git a/spec/runtime/load_spec.rb b/spec/runtime/load_spec.rb index 9b60673b7c..2813f1af92 100644 --- a/spec/runtime/load_spec.rb +++ b/spec/runtime/load_spec.rb @@ -37,4 +37,88 @@ describe "Bubble.load" do Bubble.load("omg.rb") }.should raise_error(Bubble::GemfileNotFound, /omg\.rb/) end + + describe "when locked" do + before :each do + system_gems "rack-1.0.0", "activesupport-2.3.2", "activerecord-2.3.2", "activerecord-2.3.1" + end + + it "raises an exception if the Gemfile adds a dependency" do + gemfile <<-G + gem "rack" + G + + bbl :lock + + gemfile <<-G + gem "rack" + gem "activerecord" + G + + lambda { Bubble.load }.should raise_error(Bubble::GemfileError) + end + + it "raises an exception if the Gemfile removes a dependency" do + gemfile <<-G + gem "rack" + gem "activerecord" + G + + bbl :lock + + gemfile <<-G + gem "rack" + G + + lambda { Bubble.load }.should raise_error(Bubble::GemfileError) + end + + it "raises an exception if the Gemfile changes a dependency in an incompatible way" do + gemfile <<-G + gem "rack" + gem "activerecord" + G + + bbl :lock + + gemfile <<-G + gem "rack" + gem "activerecord", "2.3.1" + G + + lambda { Bubble.load }.should raise_error(Bubble::GemfileError) + end + + it "raises an exception if the Gemfile replaces a root with a child dep of the root" do + gemfile <<-G + gem "rack" + gem "activerecord" + G + + bbl :lock + + gemfile <<-G + gem "rack" + gem "activesupport" + G + + lambda { Bubble.load }.should raise_error(Bubble::GemfileError) + end + + it "works if the Gemfile changes in a compatible way" do + gemfile <<-G + gem "rack" + gem "activerecord", "2.3.2" + G + + bbl :lock + + gemfile <<-G + gem "rack" + gem "activerecord", ">= 2.0.0" + G + + lambda { Bubble.load }.should_not raise_error(Bubble::GemfileError) + end + end end
\ No newline at end of file diff --git a/spec/support/builders.rb b/spec/support/builders.rb index 43612947e1..5404bd43aa 100644 --- a/spec/support/builders.rb +++ b/spec/support/builders.rb @@ -23,7 +23,7 @@ module Spec build_gem "actionpack", "2.3.2" do |s| s.add_dependency "activesupport", "2.3.2" end - build_gem "activerecord", "2.3.2" do |s| + build_gem "activerecord", ["2.3.1", "2.3.2"] do |s| s.add_dependency "activesupport", "2.3.2" end build_gem "actionmailer", "2.3.2" do |s| diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb index 948de5a797..edd7215074 100644 --- a/spec/support/matchers.rb +++ b/spec/support/matchers.rb @@ -22,5 +22,7 @@ module Spec Gem::Version.new(out).should == Gem::Version.new(version) end end + + alias should_be_available should_be_installed end end
\ No newline at end of file |