Permalink
Browse files

More work towards awesomeness

  • Loading branch information...
1 parent f1cfdf8 commit f31835ec73e90db52f1fe9c5426c971c5723a60b Carlhuda committed Jan 13, 2010
View
13 lib/bubble.rb
@@ -8,6 +8,7 @@ module Bubble
autoload :Dependency, 'bubble/dependency'
autoload :Dsl, 'bubble/dsl'
autoload :Environment, 'bubble/environment'
+ autoload :Index, 'bubble/index'
autoload :Installer, 'bubble/installer'
autoload :RemoteSpecification, 'bubble/remote_specification'
autoload :Resolver, 'bubble/resolver'
@@ -18,7 +19,7 @@ class GemNotFound < StandardError; end
class VersionConflict < StandardError; end
def self.setup(gemfile = nil)
- # Does nothing yet
+ load(gemfile).setup
end
def self.load(gemfile = nil)
@@ -28,4 +29,14 @@ def self.load(gemfile = nil)
def self.definition(gemfile = nil)
Definition.from_gemfile(gemfile)
end
+
+ def self.home
+ Pathname.new(Gem.user_home).join(".bbl")
+ end
+
+ def self.cache
+ home.join("cache")
+ end
+
+
end
View
4 lib/bubble/definition.rb
@@ -24,10 +24,10 @@ def self.default_gemfile
raise GemfileNotFound, "the default Gemfile was not found"
end
- attr_reader :dependencies
+ attr_reader :dependencies, :sources
def initialize
- @dependencies = []
+ @dependencies, @sources = [], Gem.sources.map { |s| Source::Rubygems.new(:uri => s) }
end
end
end
View
23 lib/bubble/dsl.rb
@@ -1,4 +1,6 @@
module Bubble
+ class DslError < StandardError; end
+
class Dsl
def self.evaluate(gemfile, definition)
builder = new(definition)
@@ -8,6 +10,8 @@ def self.evaluate(gemfile, definition)
def initialize(definition)
@definition = definition
+ @git = nil
+ @git_sources = {}
end
def gem(name, *args)
@@ -16,5 +20,24 @@ def gem(name, *args)
@definition.dependencies << Dependency.new(name, version, options)
end
+
+ def source(source)
+ source = case source
+ when :gemcutter, :rubygems, :rubyforge then Source::Rubygems.new(:uri => "http://gemcutter.org")
+ when String then Source::Rubygems.new(:uri => source)
+ else source
+ end
+
+ @definition.sources << source
+ end
+
+ def path(path, options = {})
+ source Source::Path.new(options.merge(:path => path))
+ end
+
+ def git(uri, options = {})
+ source Source::Git.new(options.merge(:uri => uri))
+ end
+
end
end
View
18 lib/bubble/environment.rb
@@ -4,12 +4,26 @@ def initialize(definition)
@definition = definition
end
+ def setup
+ specs.each do |spec|
+ $LOAD_PATH.unshift *spec.load_paths
+ Gem.loaded_specs[spec.name] = spec
+ end
+ self
+ end
+
def dependencies
@definition.dependencies
end
- def gems
- []
+ def specs
+ @specs ||= begin
+ index = Index.new
+ @definition.sources.reverse_each do |source|
+ index.merge! source.local_specs
+ end
+ Resolver.resolve(dependencies, index)
+ end
end
end
end
View
70 lib/bubble/index.rb
@@ -0,0 +1,70 @@
+module Bubble
+ class Index
+ def self.from_installed_gems
+ from_gem_index(Gem::SourceIndex.from_installed_gems)
+ end
+
+ def self.from_gem_index(gem_index)
+ index = new
+ gem_index.each { |name, spec| index << spec }
+ index
+ end
+
+ def initialize
+ @cache = {}
+ @specs = Hash.new { |h,k| h[k] = [] }
+ end
+
+ def initialize_copy(o)
+ super
+ @cache = {}
+ @specs = @specs.dup
+ end
+
+ def search(dependency)
+ @cache[dependency.hash] ||= begin
+ specs = @specs[dependency.name]
+
+ wants_prerelease = dependency.version_requirements.prerelease?
+ only_prerelease = specs.all? {|spec| spec.version.prerelease? }
+
+ found = specs.select { |spec| dependency =~ spec }
+
+ unless wants_prerelease || only_prerelease
+ found.reject! { |spec| spec.version.prerelease? }
+ end
+
+ found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
+ end
+ end
+
+ def <<(spec)
+ arr = @specs[spec.name]
+
+ arr.delete_if do |s|
+ s.version == spec.version && s.platform == spec.platform
+ end
+
+ arr << spec
+ spec
+ end
+
+ def each
+ @specs.values.flatten.each do |spec|
+ yield spec
+ end
+ end
+
+ def merge!(other)
+ other.each do |spec|
+ self << spec
+ end
+ self
+ end
+
+ def merge(other)
+ dup.merge!(other)
+ end
+
+ end
+end
View
13 lib/bubble/installer.rb
@@ -12,8 +12,7 @@ def initialize(definition)
def run
specs.each do |spec|
- inst = Gem::DependencyInstaller.new(:ignore_dependencies => true)
- inst.install spec.name, spec.version
+ spec.source.install spec
end
end
@@ -22,13 +21,19 @@ def dependencies
end
def specs
- @specs ||= Resolver.resolve(dependencies, sources)
+ @specs ||= begin
+ index = Index.new
+ sources.reverse_each do |source|
+ index.merge!(source.specs)
+ end
+ Resolver.resolve(dependencies, index)
+ end
end
private
def sources
- @sources ||= Gem.sources.map { |s| Source::Rubygems.new(:uri => s) }
+ @definition.sources
end
end
View
1 lib/bubble/remote_specification.rb
@@ -5,6 +5,7 @@ module Bubble
# full specification will only be fetched when necesary.
class RemoteSpecification
attr_reader :name, :version, :platform
+ attr_accessor :source
def initialize(name, version, platform, source_uri)
@name = name
View
43 lib/bubble/resolver.rb
@@ -34,15 +34,10 @@ class Resolver
# ==== Returns
# <GemBundle>,nil:: If the list of dependencies can be resolved, a
# collection of gemspecs is returned. Otherwise, nil is returned.
- def self.resolve(requirements, sources)
+ def self.resolve(requirements, index)
source_requirements = {}
- requirements.each do |r|
- next unless r.source
- source_requirements[r.name] = r.source
- end
-
- resolver = new(sources, source_requirements)
+ resolver = new(index)
result = catch(:success) do
resolver.resolve(requirements, {})
output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
@@ -71,24 +66,10 @@ def self.resolve(requirements, sources)
end
end
- def initialize(sources, source_requirements)
+ def initialize(index)
@errors = {}
@stack = []
- @specs = Hash.new { |h,k| h[k] = [] }
- @by_gem = source_requirements
- @cache = {}
- @index = {}
-
- sources.each do |source|
- source.specs.each do |name, specs|
- # Hack to work with a regular Gem::SourceIndex
- specs = [specs] unless specs.is_a?(Array)
- specs.compact.each do |spec|
- next if @specs[spec.name].any? { |s| s.version == spec.version && s.platform == spec.platform }
- @specs[spec.name] << spec
- end
- end
- end
+ @index = index
end
def debug
@@ -227,21 +208,7 @@ def resolve_requirement(spec, requirement, reqs, activated)
end
def search(dependency)
- @cache[dependency.hash] ||= begin
- pinned = @by_gem[dependency.name].gems if @by_gem[dependency.name]
- specs = (pinned || @specs)[dependency.name]
-
- wants_prerelease = dependency.version_requirements.prerelease?
- only_prerelease = specs.all? {|spec| spec.version.prerelease? }
-
- found = specs.select { |spec| dependency =~ spec }
-
- unless wants_prerelease || (pinned && only_prerelease)
- found.reject! { |spec| spec.version.prerelease? }
- end
-
- found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
- end
+ @index.search(dependency)
end
end
end
View
8 lib/bubble/rubygems.rb
@@ -2,7 +2,13 @@
require 'rubygems/specification'
module Gem
+ @loaded_stacks = Hash.new { |h,k| h[k] = [] }
+
class Specification
- attr_accessor :source
+ attr_accessor :source, :location
+
+ def load_paths
+ require_paths.map {|p| File.join(full_gem_path, p) }
+ end
end
end
View
69 lib/bubble/source.rb
@@ -1,4 +1,5 @@
require "rubygems/remote_fetcher"
+require "digest/sha1"
module Bubble
module Source
@@ -15,33 +16,81 @@ def specs
@specs ||= fetch_specs
end
- private
+ def local_specs
+ Index.from_installed_gems
+ end
- def fetch_specs
- transform(fetch_main_specs + fetch_prerelease_specs)
+ def install(spec)
+ inst = Gem::DependencyInstaller.new(:ignore_dependencies => true)
+ inst.install spec.name, spec.version
end
- def transform(index)
- gems = Hash.new { |h,k| h[k] = [] }
- index.each do |name, version, platform|
+ private
+
+ def fetch_specs
+ index = Index.new
+ (main_specs + prerelease_specs).each do |name, version, platform|
spec = RemoteSpecification.new(name, version, platform, @uri)
- gems[spec.name] << spec if Gem::Platform.match(spec.platform)
+ spec.source = self
+ index << spec
end
- gems
+ index
end
- def fetch_main_specs
+ def main_specs
Marshal.load(Gem::RemoteFetcher.fetcher.fetch_path("#{uri}/specs.4.8.gz"))
rescue Gem::RemoteFetcher::FetchError => e
raise ArgumentError, "#{to_s} is not a valid source: #{e.message}"
end
- def fetch_prerelease_specs
+ def prerelease_specs
Marshal.load(Gem::RemoteFetcher.fetcher.fetch_path("#{uri}/prerelease_specs.4.8.gz"))
rescue Gem::RemoteFetcher::FetchError
Bundler.logger.warn "Source '#{uri}' does not support prerelease gems"
[]
end
end
+
+ class Path
+ attr_reader :path
+
+ def initialize(options)
+ @glob = options[:glob] || "{,*/}*.gemspec"
+ @path = options[:path]
+ end
+
+ def specs
+ @specs ||= begin
+ index = Index.new
+
+ Dir["#{path}/#{@glob}"].each do |file|
+ file = Pathname.new(file)
+ if spec = eval(File.read(file))
+ spec.location = file.dirname.expand_path
+ index << spec
+ end
+ end
+ index
+ end
+ end
+
+ def install(spec)
+ end
+ end
+
+ class Git < Path
+ def initialize(options)
+ @uri = options[:uri]
+ sha = Digest::SHA1.hexdigest(URI.parse(@uri).normalize.to_s.sub(%r{/$}, ''))
+ @location = Bubble.cache.join("git", "#{File.basename(@uri, ".git")}-#{sha}")
+ @branch = options[:branch] || 'master'
+ @ref = options[:ref] || "origin/#{@branch}"
+ end
+
+ def specs
+ FileUtils.mkdir_p(@location.dirname)
+ `git clone #{@uri} #{@location} --bare --no-hardlinks`
+ end
+ end
end
end
View
14 lib/bubble/specification.rb
@@ -0,0 +1,14 @@
+module Bubble
+ class Specification
+ attr_reader :full_gem_path
+
+ def initialize(specification, full_gem_path)
+ @specification = specification
+ @full_gem_path = full_gem_path
+ end
+
+ def method_missing(meth, *args, &block)
+ send(meth, *args, &block)
+ end
+ end
+end
View
18 spec/install/directory_spec.rb
@@ -0,0 +1,18 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "bbl install with git sources" do
+ before :each do
+ in_app_root
+ end
+
+ it "fetches gems" do
+ build_lib "foo"
+
+ install_gemfile <<-G
+ path "#{lib_path('foo-1.0')}"
+ gem 'foo'
+ G
+
+ should_be_installed("foo 1.0")
+ end
+end
View
15 spec/install/gems_spec.rb
@@ -1,6 +1,6 @@
require File.expand_path('../../spec_helper', __FILE__)
-describe "bbl install" do
+describe "bbl install with gem sources" do
before :each do
in_app_root
end
@@ -37,4 +37,17 @@
should_be_installed "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2"
end
+
+ it "activates gem correctly according to the resolved gems" do
+ install_gemfile <<-G
+ gem "activesupport", "2.3.5"
+ G
+
+ install_gemfile <<-G
+ gem "activemerchant"
+ gem "rails"
+ G
+
+ should_be_installed "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2"
+ end
end
View
19 spec/install/git_spec.rb
@@ -0,0 +1,19 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "bbl install with git sources" do
+ before :each do
+ pending
+ in_app_root
+ end
+
+ it "fetches gems" do
+ build_git "foo"
+
+ install_gemfile <<-G
+ git "#{lib_path('foo-1.0')}"
+ gem 'foo'
+ G
+
+ should_be_installed("foo 1.0")
+ end
+end
View
18 spec/support/builders.rb
@@ -143,6 +143,10 @@ def build_gem(name, *args, &blk)
build_with(GemBuilder, name, args, &blk)
end
+ def build_git(name, *args, &block)
+ build_with(GitBuilder, name, args, &block)
+ end
+
private
def build_with(builder, name, args, &blk)
@@ -226,7 +230,7 @@ def executables=(val)
def _build(options)
path = options[:path] || _default_path
- @files["#{name}.gemspec"] = @spec.to_ruby if options[:gemspec]
+ @files["#{name}.gemspec"] = @spec.to_ruby unless options[:gemspec] == false
unless options[:no_default]
@files = @default_files.merge(@files)
end
@@ -244,6 +248,18 @@ def _default_path
end
end
+ class GitBuilder < LibBuilder
+ def _build(options)
+ path = options[:path] || _default_path
+ super(options.merge(:path => path))
+ Dir.chdir(path) do
+ `git init`
+ `git add *`
+ `git commit -m 'OMG INITIAL COMMIT'`
+ end
+ end
+ end
+
class GemBuilder < LibBuilder
def _build(opts)
View
4 spec/support/path.rb
@@ -32,6 +32,10 @@ def system_gem_path
tmp("gems/system")
end
+ def lib_path(*args)
+ tmp("libs", *args)
+ end
+
extend self
end
end

0 comments on commit f31835e

Please sign in to comment.