ruby_find_pkgsdeps 7.9 KB

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