ruby_find_pkgsdeps 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. #!/usr/bin/ruby -Eutf-8
  2. # encoding: utf-8
  3. #
  4. # Find dependencies between ruby packages
  5. #
  6. # Must run inside a openwrt with all *ruby* packages installed
  7. #
  8. RUBY_SIMPLE_VERSION = RUBY_VERSION.split(".")[0..1].join(".")
  9. failed = false
  10. puts "Looking for installed ruby packages..."
  11. packages=`opkg list-installed '*ruby*' | cut -d' ' -f 1`.split("\n")
  12. puts "Looking for packages files..."
  13. package_files=Hash.new { |h,k| h[k]=[] }
  14. packages.each do
  15. |pkg|
  16. files=`opkg files "#{pkg}" | sed -e 1d`.split("\n")
  17. package_files[pkg]=files if files
  18. end
  19. require_regex=/^require ["']([^"']+)["'].*/
  20. require_regex_ignore=/^require ([a-zA-Z\$]|["']$|.*\/$)/
  21. require_ignore=%w{drb/invokemethod16 foo rubygems/defaults/operating_system win32console java Win32API
  22. builder/xchar json/pure simplecov win32/sspi rdoc/markdown/literals_1_8 enumerator win32/resolv rbtree
  23. nqxml/streamingparser nqxml/treeparser xmlscan/parser xmlscan/scanner xmltreebuilder xml/parser xmlparser xml/encoding-ja xmlencoding-ja
  24. iconv uconv win32ole gettext/po_parser gettext/mo libxml psych.jar psych_jars jar-dependencies thread minitest/proveit}
  25. builtin_enc=[
  26. Encoding.find("ASCII-8BIT"),
  27. Encoding.find("UTF-8"),
  28. Encoding.find("UTF-7"),
  29. Encoding.find("US-ASCII"),
  30. ]
  31. puts "Looking for requires in files..."
  32. files_requires=Hash.new { |h,k| h[k]=[] }
  33. packages.each do
  34. |pkg|
  35. package_files[pkg].each do
  36. |file|
  37. next if not File.file?(file)
  38. if not file =~ /.rb$/
  39. if File.executable?(file)
  40. magic=`head -c50 '#{file}' | head -1`
  41. begin
  42. if not magic =~ /ruby/
  43. next
  44. end
  45. rescue
  46. next
  47. end
  48. else
  49. next
  50. end
  51. end
  52. #puts "Checking #{file}..."
  53. File.open(file, "r") do
  54. |f|
  55. lineno=0
  56. while line=f.gets() do
  57. lineno+=1; encs=[]; requires=[]; need_encdb=false
  58. line=line.chomp.gsub!(/^[[:blank:]]*/,"")
  59. case line
  60. when /^#.*coding *:/
  61. if lineno <= 2
  62. enc=line.sub(/.*coding *: */,"").sub(/ .*/,"")
  63. encs << Encoding.find(enc)
  64. end
  65. end
  66. line.gsub!(/#.*/,"")
  67. case line
  68. when "__END__"
  69. break
  70. when /^require /
  71. #puts "#{file}:#{line}"
  72. if require_regex_ignore =~ line
  73. #puts "Ignoring #{line} at #{file}:#{lineno} (REGEX)..."
  74. next
  75. end
  76. if not require_regex =~ line
  77. $stderr.puts "Unknown require: '#{line}' at file #{file}:#{lineno}"
  78. failed=true
  79. end
  80. require=line.gsub(require_regex,"\\1")
  81. require.gsub!(/\.(so|rb)$/,"")
  82. if require_ignore.include?(require)
  83. #puts "Ignoring #{line} at #{file}:#{lineno} (STR)..."
  84. next
  85. end
  86. files_requires[file] += [require]
  87. when /Encoding::/
  88. encs=line.scan(/Encoding::[[:alnum:]_]+/).collect {|enc| eval(enc) }.select {|enc| enc.kind_of? Encoding }
  89. need_encdb=true
  90. end
  91. next if encs.empty?
  92. required_encs = (encs - builtin_enc).collect {|enc| "enc/#{enc.name.downcase.gsub("-","_")}" }
  93. required_encs << "enc/encdb" if need_encdb
  94. files_requires[file] += required_encs
  95. end
  96. end
  97. end
  98. end
  99. exit(1) if failed
  100. # Add deps from .so
  101. package_files.each do |(pkg,files)| files.each do |file|
  102. case file
  103. when /\/nkf\.so$/
  104. files_requires[file]= files_requires[file] + ["enc/encdb"]
  105. end
  106. end; end
  107. puts "Grouping package requirements per package"
  108. package_requires_files = Hash.new{|h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
  109. package_files.each do |(pkg,files)|
  110. package_requires_files[pkg]
  111. files.each do |file|
  112. files_requires[file].each do |requires|
  113. package_requires_files[pkg][requires] << file
  114. end
  115. end
  116. end
  117. weak_dependency=Hash.new { |h,k| h[k]=[] }
  118. weak_dependency.merge!({
  119. "ruby-misc"=>["ruby-openssl","ruby-fiddle"], #securerandom.rb
  120. "ruby-debuglib"=>["ruby-readline"], #debug.rb
  121. "ruby-drb"=>["ruby-openssl"], #drb/ssl.rb
  122. "ruby-irb"=>["ruby-rdoc", "ruby-readline"], #irb/cmd/help.rb
  123. "ruby-gems"=>["ruby-openssl","ruby-io-console","ruby-webrick"], #rubygems/commands/cert_command.rb rubygems/user_interaction.rb rubygems/server.rb
  124. "ruby-mkmf"=>["ruby-webrick"], #un.rb
  125. "ruby-net"=>["ruby-openssl","ruby-io-console","ruby-zlib"], #net/*.rb
  126. "ruby-optparse"=>["ruby-uri","ruby-datetime"], #optparse/date.rb optparse/uri.rb
  127. "ruby-rake"=>["ruby-net","ruby-gems"], #rake/contrib/ftptools.rb /usr/bin/rake
  128. "ruby-rdoc"=>["ruby-gems","ruby-readline","ruby-webrick", #/usr/bin/rdoc and others
  129. "ruby-io-console"], #rdoc/stats/normal.rb
  130. "ruby-webrick"=>["ruby-openssl"], #webrick/ssl.rb
  131. "ruby-testunit"=>["ruby-io-console"], #gems/test-unit-3.1.5/lib/test/unit/ui/console/testrunner.rb
  132. })
  133. puts "Preloading gems..."
  134. Gem::Specification.all.each{ |x| gem x.name }
  135. puts "Looking for package dependencies..."
  136. package_provides = {}
  137. package_dependencies = Hash.new { |h,k| h[k]=[] }
  138. package_requires_files.each do
  139. |(pkg,requires_files)|
  140. requires_files.each do
  141. |(require,files)|
  142. if package_provides.include?(require)
  143. found = package_provides[require]
  144. else
  145. found = package_files.detect {|(pkg,files)| files.detect {|file| $:.detect {|path| "#{path}/#{require}" == file.gsub(/\.(so|rb)$/,"") } } }
  146. if not found
  147. $stderr.puts "#{pkg}: Nothing provides #{require} for #{files.collect {|file| file.sub("/usr/lib/ruby/","") }.join(",")}"
  148. failed = true
  149. next
  150. end
  151. found = found.first
  152. package_provides[require] = found
  153. end
  154. if weak_dependency[pkg].include?(found)
  155. puts "#{pkg}: #{found} provides #{require} (weak depedendency ignored)"
  156. else
  157. puts "#{pkg}: #{found} provides #{require} for #{files.collect {|file| file.sub("/usr/lib/ruby/","") }.join(",")}"
  158. package_dependencies[pkg] += [found]
  159. end
  160. end
  161. end
  162. if failed
  163. puts "There is some missing requirements not mapped to files in packages."
  164. puts "Please, fix the missing files or ignore them on require_ignore var"
  165. exit(1)
  166. end
  167. # Remove self dependency
  168. package_dependencies = Hash[package_dependencies.collect {|(pkg,deps)| [pkg,package_dependencies[pkg]=deps.uniq.sort - [pkg]]}]
  169. package_dependencies.default = []
  170. puts "Expanding dependencies..."
  171. begin
  172. changed=false
  173. package_dependencies.each do
  174. |(pkg,deps)|
  175. next if deps.empty?
  176. deps_new = deps.collect {|dep| [dep] + package_dependencies[dep] }.inject([],:+).uniq.sort
  177. if not deps == deps_new
  178. puts "#{pkg}: #{deps.join(",")}"
  179. puts "#{pkg}: #{deps_new.join(",")}"
  180. package_dependencies[pkg]=deps_new
  181. changed=true
  182. end
  183. end
  184. end if not changed
  185. puts "Removing redundant dependencies..."
  186. package_dependencies.each do
  187. |(pkg,deps)|
  188. package_dependencies[pkg]=deps.uniq - [pkg]
  189. end
  190. puts "Checking for mutual dependencies..."
  191. package_dependencies.each do
  192. |(pkg,deps)|
  193. if deps.include? pkg
  194. $stderr.puts "#{pkg}: Cycle dependency detected! "
  195. failed = true
  196. end
  197. end
  198. exit(1) if failed
  199. package_dependencies2=package_dependencies.dup
  200. package_dependencies.each do
  201. |(pkg,deps)|
  202. # Ignore dependencies that are already required by another dependency
  203. deps_clean = deps.reject {|dep_suspect| deps.detect {|dep_provider|
  204. if package_dependencies[dep_provider].include?(dep_suspect)
  205. puts "#{pkg}: #{dep_suspect} is already required by #{dep_provider}"
  206. true
  207. end
  208. } }
  209. if not deps==deps_clean
  210. puts "before: #{deps.join(",")}"
  211. puts "after: #{deps_clean.join(",")}"
  212. package_dependencies2[pkg]=deps_clean
  213. end
  214. end
  215. package_dependencies=package_dependencies2
  216. puts "Checking current packages dependencies..."
  217. ok=true
  218. package_dependencies.each do
  219. |(pkg,deps)|
  220. current_deps=`opkg depends #{pkg} | sed -r -e '1d;s/^[[:blank:]]*//'`.split("\n")
  221. current_deps.reject!{|dep| dep =~ /^lib/ }
  222. current_deps -= ["ruby"]
  223. extra_dep = current_deps - deps
  224. $stderr.puts "Package #{pkg} does not need to depend on #{extra_dep.join(" ")} " if not extra_dep.empty?
  225. missing_dep = deps - current_deps
  226. $stderr.puts "Package #{pkg} needs to depend on #{missing_dep.join(" ")} " if not missing_dep.empty?
  227. if not extra_dep.empty? or not missing_dep.empty?
  228. $stderr.puts "define Package/#{pkg}"
  229. $stderr.puts " DEPENDS:=ruby#{([""] +deps).join(" +")}"
  230. ok=false
  231. end
  232. end
  233. puts "All dependencies are OK." if ok
  234. __END__