#!/usr/bin/ruby -Eutf-8 # encoding: utf-8 # # Find dependencies between ruby packages # # Must run inside a openwrt with all *ruby* packages installed # require "rbconfig" RUBY_SIMPLE_VERSION = RUBY_VERSION.split(".")[0..1].join(".") failed = false puts "Loading all installed gems (unstable after external gems are instaled/update)" require 'rubygems' Gem::Specification.collect{ |g| g.name.downcase }.uniq.each {|g| gem g } puts "Looking for installed ruby packages..." packages=`opkg list-installed '*ruby*' | cut -d' ' -f 1`.split("\n") puts "Looking for packages files..." package_files=Hash.new { |h,k| h[k]=[] } packages.each do |pkg| files=`opkg files "#{pkg}" | sed -e 1d`.split("\n") package_files[pkg]=files if files end # Fake enc/utf_16 to dummy enc: package_files["ruby-enc"]+=[RbConfig::CONFIG["rubylibdir"] + "/enc/utf_16.rb" ] require_regex=/^ *require ["']([^"']+)["'].*/ require_regex_ignore=/^ *require ([a-zA-Z\$]|["']\$|.*\/$|.*#.*|.*\.$)/ require_ignore=%w{ bundler capistrano/version coverage/helpers dbm ffi fiber foo gettext/mo gettext/po_parser graphviz iconv java jruby json/pure minitest/proveit open3/jruby_windows prism/prism profile racc/cparse-jruby.jar repl_type_completor rubygems/defaults/operating_system rubygems/net/http rubygems/timeout sorted_set stackprof thread tracer uconv webrick webrick/https win32api win32ole win32/resolv win32/sspi xml/encoding-ja xmlencoding-ja xml/parser xmlparser xmlscan/scanner } matched_ignored={} builtin_enc=[ Encoding.find("ASCII-8BIT"), Encoding.find("UTF-8"), Encoding.find("UTF-7"), Encoding.find("US-ASCII"), ] puts "Looking for requires in files..." files_requires=Hash.new { |h,k| h[k]=[] } packages.each do |pkg| package_files[pkg].each do |file| next if not File.file?(file) if not file =~ /.rb$/ if File.executable?(file) magic=`head -c50 '#{file}' | head -1` begin if not magic =~ /ruby/ next end rescue next end else next end end #puts "Checking #{file}..." File.open(file, "r") do |f| lineno=0 while line=f.gets() do lineno+=1; encs=[]; requires=[]; need_encdb=false line=line.chomp.gsub!(/^[[:blank:]]*/,"") case line when /^#.*coding *:/ if lineno <= 2 enc=line.sub(/.*coding *: */,"").sub(/ .*/,"") encs << Encoding.find(enc) end end line.gsub!(/#.*/,"") case line when "__END__" break when /^require / #puts "#{file}:#{line}" if require_regex_ignore =~ line puts "Ignoring #{line} at #{file}:#{lineno} (REGEX)..." next end if not require_regex =~ line puts "Unknown require: '#{line}' at file #{file}:#{lineno} and it did not match #{require_regex_ignore}" failed=true end require=line.gsub(require_regex,"\\1") require.gsub!(/\.(so|rb)$/,"") if require_ignore.include?(require) puts "Ignoring #{line} at #{file}:#{lineno} (STR)..." matched_ignored[require]=1 next end files_requires[file] += [require] when /Encoding::/ encs=line.scan(/Encoding::[[:alnum:]_]+/).collect {|enc| eval(enc) }.select {|enc| enc.kind_of? Encoding } need_encdb=true end next if encs.empty? required_encs = (encs - builtin_enc).collect {|enc| "enc/#{enc.name.downcase.gsub("-","_")}" } required_encs << "enc/encdb" if need_encdb files_requires[file] += required_encs end end end end exit(1) if failed missed_ignored = (require_ignore - matched_ignored.keys).sort.join(",") if not missed_ignored.empty? puts "These 'require_ignore' didn't match anything: ",(require_ignore - matched_ignored.keys).sort.join(","),"" end # From ruby source: grep -E 'rb_require' -R . | grep -E '\.c:.*rb_require.*' # Add dependencies of ruby files from ruby lib.so package_files.each do |(pkg,files)| files.each do |file| case file when /\/nkf\.so$/ ; files_requires[file]=files_requires[file] + ["enc/encdb"] when /\/objspace\.so$/; files_requires[file]=files_requires[file] + ["tempfile"] # dump_output from ext/objspace/objspace_dump.c when /\/openssl\.so$/; files_requires[file]=files_requires[file] + ["digest"] # Init_ossl_digest from ext/openssl/ossl_digest.c end end; end puts "Grouping package requirements per package" package_requires_files = Hash.new{|h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } } package_files.each do |(pkg,files)| package_requires_files[pkg] files.each do |file| files_requires[file].each do |requires| package_requires_files[pkg][requires] << file end end end # For optional require or for breaking cycle dependencies weak_dependency=Hash.new { |h,k| h[k]=[] } weak_dependency.merge!({ "ruby-irb" =>%w{ruby-rdoc ruby-readline ruby-debug}, # irb/cmd/help.rb irb/cmd/debug.rb,3.2/irb/cmd/debug.rb "ruby-gems" =>%w{ruby-bundler ruby-rdoc}, # rubygems.rb rubygems/server.rb rdoc/rubygems_hook "ruby-racc" =>%w{ruby-gems}, # /usr/bin/racc* "ruby-rake" =>%w{ruby-gems ruby-debug}, # /usr/bin/rake gems/3.3/gems/rake-13.1.0/lib/rake/application.rb "ruby-rdoc" =>%w{ruby-readline}, # rdoc/ri/driver.rb "ruby-testunit" =>%w{ruby-io-console}, # gems/test-unit-3.1.5/lib/test/unit/ui/console/testrunner.rb "ruby-net-http" =>%w{ruby-open-uri} # net/http/status.rb }) puts "Looking for package dependencies..." package_provides = {} package_dependencies = Hash.new { |h,k| h[k]=[] } package_requires_files.each do |(pkg,requires_files)| requires_files.each do |(require,files)| if package_provides.include?(require) found = package_provides[require] else found = package_files.detect {|(pkg,files)| files.detect {|file| $:.detect {|path| "#{path}/#{require}" == file.gsub(/\.(so|rb)$/,"") } } } if not found $stderr.puts "#{pkg}: Nothing provides #{require} for #{files.collect {|file| file.sub("/usr/lib/ruby/","") }.join(",")}" failed = true next end found = found.first package_provides[require] = found end if weak_dependency[pkg].include?(found) puts "#{pkg}: #{found} provides #{require} (weak depedendency ignored) for #{files.collect {|file| file.sub("/usr/lib/ruby/","") }.join(",")}" else puts "#{pkg}: #{found} provides #{require} for #{files.collect {|file| file.sub("/usr/lib/ruby/","") }.join(",")}" package_dependencies[pkg] += [found] end end end if failed puts "There is some missing requirements not mapped to files in packages." puts "Please, fix the missing files or ignore them on require_ignore var" exit(1) end # Remove self dependency package_dependencies = Hash[package_dependencies.collect {|(pkg,deps)| [pkg,package_dependencies[pkg]=deps.uniq.sort - [pkg]]}] package_dependencies.default = [] puts "Expanding dependencies..." begin changed=false package_dependencies.each do |(pkg,deps)| next if deps.empty? deps.each {|dep| puts "#{pkg}: #{dep} also depends on #{pkg}" if package_dependencies[dep].include?(pkg) } deps_new = deps.collect {|dep| [dep] + package_dependencies[dep] }.inject([],:+).uniq.sort if not deps == deps_new puts "#{pkg}: {deps.join(",")} (OLD)" puts "#{pkg}: #{deps_new.join(",")} (NEW)" package_dependencies[pkg]=deps_new if deps_new.include?(pkg) $stderr.puts "#{pkg}: Circular dependency detected (#1)!" exit 1 end changed=true end end end if not changed puts "Removing redundant dependencies..." package_dependencies.each do |(pkg,deps)| package_dependencies[pkg]=deps.uniq - [pkg] end puts "Checking for mutual dependencies..." package_dependencies.each do |(pkg,deps)| if deps.include? pkg $stderr.puts "#{pkg}: Circular dependency detected (#2)!" failed = true end end exit(1) if failed package_dependencies2=package_dependencies.dup package_dependencies.each do |(pkg,deps)| # Ignore dependencies that are already required by another dependency deps_clean = deps.reject {|dep_suspect| deps.detect {|dep_provider| if package_dependencies[dep_provider].include?(dep_suspect) puts "#{pkg}: #{dep_suspect} is already required by #{dep_provider}" true end } } if not deps==deps_clean puts "before: #{deps.join(",")}" puts "after: #{deps_clean.join(",")}" package_dependencies2[pkg]=deps_clean end end package_dependencies=package_dependencies2 puts "Checking current packages dependencies..." ok=true package_dependencies.each do |(pkg,deps)| current_deps=`opkg depends #{pkg} | sed -r -e '1d;s/^[[:blank:]]*//'`.split("\n") current_deps.reject!{|dep| dep =~ /^lib/ } current_deps -= ["ruby"] extra_dep = current_deps - deps $stderr.puts "Package #{pkg} does not need to depend on #{extra_dep.join(" ")} " if not extra_dep.empty? missing_dep = deps - current_deps $stderr.puts "Package #{pkg} needs to depend on #{missing_dep.join(" ")} " if not missing_dep.empty? if not extra_dep.empty? or not missing_dep.empty? $stderr.puts "define Package/#{pkg}" $stderr.puts " DEPENDS:=ruby#{([""] +deps).join(" +")}" ok=false end end puts "All dependencies are OK." if ok __END__