require"digest/sha1"moduleBundlerclassDefinitionincludeGemHelpersattr_reader:dependencies,:platforms,:sourcesdefself.build(gemfile,lockfile,unlock)unlock||={}gemfile=Pathname.new(gemfile).expand_pathunlessgemfile.file?raiseGemfileNotFound,"#{gemfile} not found"end# TODO: move this back into DSLbuilder=Dsl.newbuilder.instance_eval(File.read(gemfile.to_s),gemfile.to_s,1)builder.to_definition(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=File.read(lockfile)locked=LockfileParser.new(@lockfile_contents)@platforms=locked.platformsifunlock!=true@locked_deps=locked.dependencies@last_resolve=SpecSet.new(locked.specs)@locked_sources=locked.sourceselse@unlock={}@locked_deps=[]@last_resolve=SpecSet.new([])@locked_sources=[]endelse@unlock={}@platforms=[]@locked_deps=[]@last_resolve=SpecSet.new([])@locked_sources=[]end@unlock[:gems]||=[]@unlock[:sources]||=[]current_platform=Gem.platforms.map{|p|generic(p)}.compact.last@new_platform=!@platforms.include?(current_platform)@platforms|=[current_platform]ensure_equivalent_gemfile_and_lockfileifBundler.deploymentconverge_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?bundler=index.search(Gem::Dependency.new('bundler',VERSION)).lastspecs["bundler"]=bundlerifbundlerendspecsendenddefnew_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||=beginconverge_locked_specssource_requirements={}dependencies.eachdo|dep|nextunlessdep.sourcesource_requirements[dep.name]=dep.source.specsend# Run a resolve against the locally available gems@last_resolve.mergeResolver.resolve(expanded_dependencies,index,source_requirements,@last_resolve)endenddefindex@index||=Index.builddo|idx|@sources.eachdo|s|idx.uses.specsendendenddefno_sources?@sources.length==1&&@sources.first.remotes.empty?enddefgroupsdependencies.map{|d|d.groups}.flatten.uniqenddeflock(file)contents=to_lockreturnif@lockfile_contents==contentsFile.open(file,'w')do|f|f.putscontentsendenddefto_lockout=""sorted_sources.eachdo|source|# Add the source headerout<<source.to_lock# Find all specs for this sourceresolve.select{|s|s.source==source}.sort_by{|s|s.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.name}.eachdo|dep|nextifhandled.include?(dep.name)out<<dep.to_lockhandled<<dep.nameendoutendprivatedefensure_equivalent_gemfile_and_lockfilechanges=falsemsg="You have modified your Gemfile in development but did not check\n"\"the resulting snapshot (Gemfile.lock) into version control"added=[]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?raiseProductionError,msgifadded.any?||deleted.any?||changed.any?enddefpretty_dep(dep,source=false)msg="#{dep.name}"msg<<" (#{dep.requirement})"unlessdep.requirement==Gem::Requirement.defaultmsg<<" from the `#{dep.source}` source"ifsource&&dep.sourcemsgenddefconverge_sources@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)&&(!locked_dep||dep.source!=locked_dep.source)@last_resolve.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=[]@last_resolve.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 gemspecnextunlessother# If the dependencies of the path source have changed, unlock itnextunlesss.dependencies.sort==other.dependencies.sortendconverged<<sendresolve=SpecSet.new(converged)resolve=resolve.for(expand_dependencies(deps,true),@unlock[:gems])diff=@last_resolve.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!ifdiff.any?{|s|s.source==source}endend@last_resolve=resolveenddefin_locked_deps?(dep,d)d&&dep.source==d.sourceenddefsatisfies_locked_spec?(dep)@last_resolve.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.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