lib/bundler/source.rb in bundler-0.8.1 vs lib/bundler/source.rb in bundler-0.9.0.pre1
- old
+ new
@@ -1,357 +1,247 @@
+require "rubygems/remote_fetcher"
+require "rubygems/format"
+require "digest/sha1"
+
module Bundler
- class DirectorySourceError < StandardError; end
- class GitSourceError < StandardError ; end
- # Represents a source of rubygems. Initially, this is only gem repositories, but
- # eventually, this will be git, svn, HTTP
- class Source
- attr_reader :bundle
+ module Source
+ class Rubygems
+ attr_reader :uri, :options
- def initialize(bundle, options)
- @bundle = bundle
- end
+ 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?
+ end
- private
-
- def process_source_gems(gems)
- new_gems = Hash.new { |h,k| h[k] = [] }
- gems.values.each do |spec|
- spec.source = self
- new_gems[spec.name] << spec
+ def specs
+ @specs ||= fetch_specs
end
- new_gems
- end
- end
- class GemSource < Source
- attr_reader :uri
+ def install(spec)
+ Bundler.ui.info "* #{spec.name} (#{spec.version})"
+ if Index.from_installed_gems[spec].any?
+ Bundler.ui.info " * already installed... skipping"
+ return
+ end
- def initialize(bundle, options)
- super
- @uri = options[:uri]
- @uri = URI.parse(@uri) unless @uri.is_a?(URI)
- raise ArgumentError, "The source must be an absolute URI" unless @uri.absolute?
- end
+ destination = Gem.dir
- def local?
- false
- end
+ Bundler.ui.info " * Downloading..."
+ gem_path = Gem::RemoteFetcher.fetcher.download(spec, uri, destination)
+ Bundler.ui.info " * Installing..."
+ installer = Gem::Installer.new gem_path,
+ :install_dir => Gem.dir,
+ :ignore_dependencies => true
- def gems
- @specs ||= fetch_specs
- end
+ installer.install
+ end
- def ==(other)
- uri == other.uri
- end
+ private
- def to_s
- @uri.to_s
- end
+ def fetch_specs
+ index = Index.new
+ Bundler.ui.info "Source: Fetching remote index for `#{uri}`... "
+ (main_specs + prerelease_specs).each do |name, version, platform|
+ spec = RemoteSpecification.new(name, version, platform, @uri)
+ spec.source = self
+ index << spec
+ end
+ Bundler.ui.info "done."
+ index.freeze
+ end
- class RubygemsRetardation < StandardError; end
-
- def download(spec)
- Bundler.logger.info "Downloading #{spec.full_name}.gem"
-
- destination = bundle.gem_path
-
- unless destination.writable?
- raise RubygemsRetardation, "destination: #{destination} is not writable"
+ 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
- # Download the gem
- Gem::RemoteFetcher.fetcher.download(spec, uri, destination)
-
- # Re-read the gemspec from the downloaded gem to correct
- # any errors that were present in the Rubyforge specification.
- new_spec = Gem::Format.from_file_by_path(destination.join('cache', "#{spec.full_name}.gem")).spec
- spec.__swap__(new_spec)
+ 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
- private
-
- def fetch_specs
- Bundler.logger.info "Updating source: #{to_s}"
- build_gem_index(fetch_main_specs + fetch_prerelease_specs)
- end
-
- def build_gem_index(index)
- gems = Hash.new { |h,k| h[k] = [] }
- index.each do |name, version, platform|
- spec = RemoteSpecification.new(name, version, platform, @uri)
- spec.source = self
- gems[spec.name] << spec if Gem::Platform.match(spec.platform)
+ class GemCache
+ def initialize(options)
+ @path = options[:path]
end
- gems
- end
- def fetch_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 specs
+ @specs ||= begin
+ index = Index.new
- def fetch_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
+ Dir["#{@path}/*.gem"].each do |gemfile|
+ spec = Gem::Format.from_file_by_path(gemfile).spec
+ spec.source = self
+ index << spec
+ end
- class SystemGemSource < Source
+ index.freeze
+ end
+ end
- def self.instance
- @instance
- end
+ def install(spec)
+ destination = Gem.dir
- def self.new(*args)
- @instance ||= super
- end
+ installer = Gem::Installer.new "#{@path}/#{spec.full_name}.gem",
+ :install_dir => Gem.dir,
+ :ignore_dependencies => true
- def initialize(bundle, options = {})
- super
- @source = Gem::SourceIndex.from_installed_gems
+ installer.install
+ end
end
- def local?
- false
- end
+ class Path
+ attr_reader :path, :options
- def gems
- @gems ||= process_source_gems(@source.gems)
- end
+ def initialize(options)
+ @options = options
+ @glob = options[:glob] || "{,*/}*.gemspec"
+ @path = options[:path]
+ end
- def ==(other)
- other.is_a?(SystemGemSource)
- end
+ def default_spec(*args)
+ return @default_spec if args.empty?
+ name, version = *args
+ @default_spec = Specification.new do |s|
+ s.name = name
+ s.source = self
+ s.version = Gem::Version.new(version)
+ s.relative_loaded_from = "#{name}.gemspec"
+ end
+ end
- def to_s
- "system"
- end
+ def local_specs
+ @local_specs ||= begin
+ index = Index.new
- def download(spec)
- gemfile = Pathname.new(spec.loaded_from)
- gemfile = gemfile.dirname.join('..', 'cache', "#{spec.full_name}.gem")
- bundle.cache(gemfile)
- end
+ if File.directory?(path)
+ Dir["#{path}/#{@glob}"].each do |file|
+ file = Pathname.new(file)
+ if spec = eval(File.read(file))
+ spec = Specification.from_gemspec(spec)
+ spec.loaded_from = file
+ spec.source = self
+ index << spec
+ end
+ end
- private
+ index << default_spec if default_spec && index.empty?
+ end
- end
+ index.freeze
+ end
+ end
- class GemDirectorySource < Source
- attr_reader :location
+ alias specs local_specs
- def initialize(bundle, options)
- super
- @location = options[:location]
end
- def local?
- true
- end
+ class Git < Path
+ attr_reader :uri, :ref
- def gems
- @specs ||= fetch_specs
- end
+ def initialize(options)
+ @options = options
+ @glob = options[:glob] || "{,*/}*.gemspec"
+ @uri = options[:uri]
+ @ref = options[:ref] || options[:branch] || 'master'
+ end
- def ==(other)
- location == other.location
- end
-
- def to_s
- location.to_s
- end
-
- def download(spec)
- # raise NotImplementedError
- end
-
- private
-
- def fetch_specs
- specs = Hash.new { |h,k| h[k] = [] }
-
- Dir["#{@location}/*.gem"].each do |gemfile|
- spec = Gem::Format.from_file_by_path(gemfile).spec
- spec.source = self
- specs[spec.name] << spec
+ def options
+ @options.merge(:ref => revision)
end
- specs
- end
- end
-
- class DirectorySource < Source
- attr_reader :location, :specs, :required_specs
-
- def initialize(bundle, options)
- super
- if options[:location]
- @location = Pathname.new(options[:location]).expand_path
+ def path
+ Bundler.install_path.join("#{base_name}-#{uri_hash}-#{ref}")
end
- @glob = options[:glob] || "**/*.gemspec"
- @specs = {}
- @required_specs = []
- end
- def add_spec(path, name, version, require_paths = %w(lib))
- raise DirectorySourceError, "already have a gem defined for '#{path}'" if @specs[path.to_s]
- @specs[path.to_s] = Gem::Specification.new do |s|
- s.name = name
- s.version = Gem::Version.new(version)
+ def to_s
+ @uri
end
- end
- def local?
- true
- end
+ def specs
+ @specs ||= begin
+ index = Index.new
+ # Start by making sure the git cache is up to date
+ cache
+ # Find all gemspecs in the repo
+ in_cache do
+ out = %x(git ls-tree -r #{revision}).strip
+ lines = out.split("\n").select { |l| l =~ /\.gemspec$/ }
+ # Loop over the lines and extract the relative path and the
+ # git hash
+ lines.each do |line|
+ next unless line =~ %r{^(\d+) (blob|tree) ([a-zf0-9]+)\t(.*)$}
+ hash, file = $3, $4
+ # Read the gemspec
+ if spec = eval(%x(git cat-file blob #{$3}))
+ spec = Specification.from_gemspec(spec)
+ spec.relative_loaded_from = file
+ spec.source = self
+ index << spec
+ end
+ end
+ end
- def gems
- @gems ||= begin
- # Locate all gemspecs from the directory
- specs = locate_gemspecs
- specs = merge_defined_specs(specs)
+ index << default_spec if default_spec && index.empty?
- required_specs.each do |required|
- unless specs.any? {|k,v| v.name == required }
- raise DirectorySourceError, "No gemspec for '#{required}' was found in" \
- " '#{location}'. Please explicitly specify a version."
- end
+ index.freeze
end
-
- process_source_gems(specs)
end
- end
- def locate_gemspecs
- Dir["#{location}/#{@glob}"].inject({}) do |specs, file|
- file = Pathname.new(file)
- if spec = eval(File.read(file)) # and validate_gemspec(file.dirname, spec)
- spec.location = file.dirname.expand_path
- specs[spec.full_name] = spec
+ def install(spec)
+ @installed ||= begin
+ FileUtils.mkdir_p(path)
+ Dir.chdir(path) do
+ unless File.exist?(".git")
+ %x(git clone --recursive --no-checkout #{cache_path} #{path})
+ end
+ %x(git fetch --quiet)
+ %x(git reset --hard #{revision})
+ %x(git submodule init)
+ %x(git submodule update)
+ end
+ true
end
- specs
end
- end
- def merge_defined_specs(specs)
- @specs.each do |path, spec|
- # Set the spec location
- spec.location = "#{location}/#{path}"
+ private
- if existing = specs.values.find { |s| s.name == spec.name }
- if existing.version != spec.version
- raise DirectorySourceError, "The version you specified for #{spec.name}" \
- " is #{spec.version}. The gemspec is #{existing.version}."
- # Not sure if this is needed
- # ====
- # elsif File.expand_path(existing.location) != File.expand_path(spec.location)
- # raise DirectorySourceError, "The location you specified for #{spec.name}" \
- # " is '#{spec.location}'. The gemspec was found at '#{existing.location}'."
- end
- # elsif !validate_gemspec(spec.location, spec)
- # raise "Your gem definition is not valid: #{spec}"
- else
- specs[spec.full_name] = spec
- end
+ def base_name
+ File.basename(uri, ".git")
end
- specs
- end
- def validate_gemspec(path, spec)
- path = Pathname.new(path)
- msg = "Gemspec for #{spec.name} (#{spec.version}) is invalid:"
- # Check the require_paths
- (spec.require_paths || []).each do |require_path|
- unless path.join(require_path).directory?
- Bundler.logger.warn "#{msg} Missing require path: '#{require_path}'"
- return false
- end
+ def uri_hash
+ Digest::SHA1.hexdigest(URI.parse(uri).normalize.to_s.sub(%r{/$}, ''))
end
- # Check the executables
- (spec.executables || []).each do |exec|
- unless path.join(spec.bindir, exec).file?
- Bundler.logger.warn "#{msg} Missing executable: '#{File.join(spec.bindir, exec)}'"
- return false
- end
+ def cache_path
+ @cache_path ||= Bundler.cache.join("git", "#{base_name}-#{uri_hash}")
end
- true
- end
-
- def ==(other)
- # TMP HAX
- other.is_a?(DirectorySource)
- end
-
- def to_s
- "directory: '#{location}'"
- end
-
- def download(spec)
- # Nothing needed here
- end
- end
-
- class GitSource < DirectorySource
- attr_reader :ref, :uri, :branch
-
- def initialize(bundle, options)
- super
- @uri = options[:uri]
- @branch = options[:branch] || 'master'
- @ref = options[:ref] || "origin/#{@branch}"
- end
-
- def local?
- raise SourceNotCached, "Git repository '#{@uri}' has not been cloned yet" unless location.directory?
- super
- end
-
- def location
- # TMP HAX to get the *.gemspec reading to work
- bundle.gem_path.join('dirs', File.basename(@uri, '.git'))
- end
-
- def gems
- update if Bundler.remote?
- checkout if Bundler.writable?
- super
- end
-
- def download(spec)
- # Nothing needed here
- end
-
- def to_s
- "git: #{uri}"
- end
-
- private
- def update
- if location.directory?
- fetch
+ def cache
+ if cache_path.exist?
+ Bundler.ui.info "Source: Updating `#{uri}`... "
+ in_cache { `git fetch --quiet #{uri} master:master` }
else
- clone
+ Bundler.ui.info "Source: Cloning `#{uri}`... "
+ FileUtils.mkdir_p(cache_path.dirname)
+ `git clone #{uri} #{cache_path} --bare --no-hardlinks`
end
+ Bundler.ui.info "Done."
end
- def fetch
- Bundler.logger.info "Fetching git repository at: #{@uri}"
- Dir.chdir(location) { `git fetch origin` }
+ def revision
+ @revision ||= in_cache { `git rev-parse #{ref}`.strip }
end
- def clone
- Bundler.logger.info "Cloning git repository at: #{@uri}"
- FileUtils.mkdir_p(location.dirname)
- `git clone #{@uri} #{location} --no-hardlinks`
+ def in_cache(&blk)
+ Dir.chdir(cache_path, &blk)
end
-
- def checkout
- Dir.chdir(location) { `git checkout --quiet #{@ref}` }
- end
+ end
end
-end
+end
\ No newline at end of file