require"digest/sha1"moduleBundlerclassDefinitionincludeGemHelpersattr_reader:dependencies,:platforms,:sourcesdefself.build(gemfile,lockfile,unlock)unlock||={}gemfile=Pathname.new(gemfile).expand_pathunlessgemfile.file?raiseGemfileNotFound,"#{gemfile} not found"endDsl.evaluate(gemfile,lockfile,unlock)end=begin
How does the new system work?
===
* Load information from Gemfile and Lockfile
* Invalidate stale locked specs
* All specs from stale source are stale
* All specs that are reachable only through a stale
dependency are stale.
* If all fresh dependencies are satisfied by the locked
specs, then we can try to resolve locally.
=enddefinitialize(lockfile,dependencies,sources,unlock)@dependencies,@sources,@unlock=dependencies,sources,unlock@remote=false@specs=nil@lockfile_contents=""iflockfile&&File.exists?(lockfile)@lockfile_contents=Bundler.read_file(lockfile)locked=LockfileParser.new(@lockfile_contents)@platforms=locked.platformsifunlock!=true@locked_deps=locked.dependencies@locked_specs=SpecSet.new(locked.specs)@locked_sources=locked.sourceselse@unlock={}@locked_deps=[]@locked_specs=SpecSet.new([])@locked_sources=[]endelse@unlock={}@platforms=[]@locked_deps=[]@locked_specs=SpecSet.new([])@locked_sources=[]end@unlock[:gems]||=[]@unlock[:sources]||=[]current_platform=Bundler.rubygems.platforms.map{|p|generic(p)}.compact.last@new_platform=!@platforms.include?(current_platform)@platforms|=[current_platform]eager_unlock=expand_dependencies(@unlock[:gems])@unlock[:gems]=@locked_specs.for(eager_unlock).map{|s|s.name}converge_sourcesconverge_dependenciesenddefresolve_with_cache!raise"Specs already loaded"if@specs@sources.each{|s|s.cached!}specsenddefresolve_remotely!raise"Specs already loaded"if@specs@remote=true@sources.each{|s|s.remote!}specsenddefspecs@specs||=beginspecs=resolve.materialize(requested_dependencies)unlessspecs["bundler"].any?local=Bundler.settings[:frozen]?rubygems_index:indexbundler=local.search(Gem::Dependency.new('bundler',VERSION)).lastspecs["bundler"]=bundlerifbundlerendspecsendenddefnew_specsspecs-@locked_specsenddefremoved_specs@locked_specs-specsenddefnew_platform?@new_platformenddefmissing_specsmissing=[]resolve.materialize(requested_dependencies,missing)missingenddefrequested_specs@requested_specs||=begingroups=self.groups-Bundler.settings.withoutgroups.map!{|g|g.to_sym}specs_for(groups)endenddefcurrent_dependenciesdependencies.reject{|d|!d.should_include?}enddefspecs_for(groups)deps=dependencies.select{|d|(d.groups&groups).any?}deps.delete_if{|d|!d.should_include?}specs.for(expand_dependencies(deps))enddefresolve@resolve||=beginifBundler.settings[:frozen]@locked_specselselast_resolve=converge_locked_specs# Record the specs available in each gem's source, so that those# specs will be available later when the resolver knows where to# look for that gemspec (or its dependencies)source_requirements={}dependencies.eachdo|dep|nextunlessdep.sourcesource_requirements[dep.name]=dep.source.specsend# Run a resolve against the locally available gemslast_resolve.mergeResolver.resolve(expanded_dependencies,index,source_requirements,last_resolve)endendenddefindex@index||=Index.builddo|idx|other_sources=@sources.find_all{|s|!s.is_a?(Bundler::Source::Rubygems)}rubygems_sources=@sources.find_all{|s|s.is_a?(Bundler::Source::Rubygems)}dependency_names=@dependencies.dup||[]dependency_names.map!{|d|d.name}other_sources.eachdo|s|source_index=s.specsdependency_names+=source_index.unmet_dependency_namesidx.add_sourcesource_indexendrubygems_sources.eachdo|s|s.dependency_names=dependency_names.uniqifs.is_a?(Bundler::Source::Rubygems)idx.add_sources.specsendendend# used when frozen is enabled so we can find the bundler# spec, even if (say) a git gem is not checked out.defrubygems_index@rubygems_index||=Index.builddo|idx|rubygems=@sources.find{|s|s.is_a?(Source::Rubygems)}idx.add_sourcerubygems.specsendenddefno_sources?@sources.length==1&&@sources.first.remotes.empty?enddefgroupsdependencies.map{|d|d.groups}.flatten.uniqenddeflock(file)contents=to_lock# Convert to \r\n if the existing lock has them# i.e., Windows with `git config core.autocrlf=true`contents.gsub!(/\n/,"\r\n")if@lockfile_contents.match("\r\n")returnif@lockfile_contents==contentsifBundler.settings[:frozen]# TODO: Warn here if we got here.returnendFile.open(file,'wb'){|f|f.puts(contents)}enddefto_lockout=""sorted_sources.eachdo|source|# Add the source headerout<<source.to_lock# Find all specs for this sourceresolve.select{|s|s.source==source}.# This needs to be sorted by full name so that# gems with the same name, but different platform# are ordered consistantlysort_by{|s|s.full_name}.eachdo|spec|nextifspec.name=='bundler'out<<spec.to_lockendout<<"\n"endout<<"PLATFORMS\n"platforms.map{|p|p.to_s}.sort.eachdo|p|out<<" #{p}\n"endout<<"\n"out<<"DEPENDENCIES\n"handled=[]dependencies.sort_by{|d|d.to_s}.eachdo|dep|nextifhandled.include?(dep.name)out<<dep.to_lockhandled<<dep.nameendoutenddefensure_equivalent_gemfile_and_lockfile(explicit_flag=false)changes=falsemsg="You are trying to install in deployment mode after changing\n"\"your Gemfile. Run `bundle install` elsewhere and add the\n"\"updated Gemfile.lock to version control."unlessexplicit_flagmsg+="\n\nIf this is a development machine, remove the Gemfile "\"freeze \nby running `bundle install --no-deployment`."endadded=[]deleted=[]changed=[]if@locked_sources!=@sourcesnew_sources=@sources-@locked_sourcesdeleted_sources=@locked_sources-@sourcesifnew_sources.any?added.concatnew_sources.map{|source|"* source: #{source}"}endifdeleted_sources.any?deleted.concatdeleted_sources.map{|source|"* source: #{source}"}endchanges=trueendboth_sources=Hash.new{|h,k|h[k]=["no specified source","no specified source"]}@dependencies.each{|d|both_sources[d.name][0]=d.sourceifd.source}@locked_deps.each{|d|both_sources[d.name][1]=d.sourceifd.source}both_sources.delete_if{|k,v|v[0]==v[1]}if@dependencies!=@locked_depsnew_deps=@dependencies-@locked_depsdeleted_deps=@locked_deps-@dependenciesifnew_deps.any?added.concatnew_deps.map{|d|"* #{pretty_dep(d)}"}endifdeleted_deps.any?deleted.concatdeleted_deps.map{|d|"* #{pretty_dep(d)}"}endboth_sources.eachdo|name,sources|changed<<"* #{name} from `#{sources[0]}` to `#{sources[1]}`"endchanges=trueendmsg<<"\n\nYou have added to the Gemfile:\n"<<added.join("\n")ifadded.any?msg<<"\n\nYou have deleted from the Gemfile:\n"<<deleted.join("\n")ifdeleted.any?msg<<"\n\nYou have changed in the Gemfile:\n"<<changed.join("\n")ifchanged.any?msg<<"\n"raiseProductionError,msgifadded.any?||deleted.any?||changed.any?endprivatedefpretty_dep(dep,source=false)msg="#{dep.name}"msg<<" (#{dep.requirement})"unlessdep.requirement==Gem::Requirement.defaultmsg<<" from the `#{dep.source}` source"ifsource&&dep.sourcemsgenddefconverge_sourceslocked_gem=@locked_sources.find{|s|Source::Rubygems===s}actual_gem=@sources.find{|s|Source::Rubygems===s}iflocked_gem&&actual_gemlocked_gem.merge_remotesactual_gemend@sources.map!do|source|@locked_sources.find{|s|s==source}||sourceend@sources.eachdo|source|source.unlock!ifsource.respond_to?(:unlock!)&&@unlock[:sources].include?(source.name)endenddefconverge_dependencies(@dependencies+@locked_deps).eachdo|dep|ifdep.sourcedep.source=@sources.find{|s|dep.source==s}endendend# Remove elements from the locked specs that are expired. This will most# commonly happen if the Gemfile has changed since the lockfile was last# generateddefconverge_locked_specsdeps=[]# Build a list of dependencies that are the same in the Gemfile# and Gemfile.lock. If the Gemfile modified a dependency, but# the gem in the Gemfile.lock still satisfies it, this is fine# too.@dependencies.eachdo|dep|locked_dep=@locked_deps.find{|d|dep==d}ifin_locked_deps?(dep,locked_dep)||satisfies_locked_spec?(dep)deps<<depelsifdep.source.is_a?(Source::Path)&&dep.current_platform?&&(!locked_dep||dep.source!=locked_dep.source)@locked_specs.eachdo|s|@unlock[:gems]<<s.nameifs.source==dep.sourceenddep.source.unlock!ifdep.source.respond_to?(:unlock!)dep.source.specs.each{|s|@unlock[:gems]<<s.name}endendconverged=[]@locked_specs.eachdo|s|s.source=@sources.find{|src|s.source==src}# Don't add a spec to the list if its source is expired. For example,# if you change a Git gem to Rubygems.nextifs.source.nil?||@unlock[:sources].include?(s.name)# If the spec is from a path source and it doesn't exist anymore# then we just unlock it.# Path sources have special logicifs.source.instance_of?(Source::Path)other=s.source.specs[s].first# If the spec is no longer in the path source, unlock it. This# commonly happens if the version changed in the gemspecnextunlessotherdeps2=other.dependencies.select{|d|d.type!=:development}# If the dependencies of the path source have changed, unlock itnextunlesss.dependencies.sort==deps2.sortendconverged<<sendresolve=SpecSet.new(converged)resolve=resolve.for(expand_dependencies(deps,true),@unlock[:gems])diff=@locked_specs.to_a-resolve.to_a# Now, we unlock any sources that do not have anymore gems pinned to it@sources.eachdo|source|nextunlesssource.respond_to?(:unlock!)unlessresolve.any?{|s|s.source==source}source.unlock!if!diff.empty?&&diff.any?{|s|s.source==source}endendresolveenddefin_locked_deps?(dep,d)d&&dep.source==d.sourceenddefsatisfies_locked_spec?(dep)@locked_specs.any?{|s|s.satisfies?(dep)&&(!dep.source||s.source==dep.source)}enddefexpanded_dependencies@expanded_dependencies||=expand_dependencies(dependencies,@remote)enddefexpand_dependencies(dependencies,remote=false)deps=[]dependencies.eachdo|dep|dep=Dependency.new(dep,">= 0")unlessdep.respond_to?(:name)dep.gem_platforms(@platforms).eachdo|p|deps<<DepProxy.new(dep,p)ifremote||p==generic(Gem::Platform.local)endenddepsenddefsorted_sources@sources.sort_bydo|s|# Place GEM at the top[s.is_a?(Source::Rubygems)?1:0,s.to_s]endenddefrequested_dependenciesgroups=self.groups-Bundler.settings.withoutgroups.map!{|g|g.to_sym}dependencies.reject{|d|!d.should_include?||(d.groups&groups).empty?}endendend