You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
8.4 KiB

  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 bcrypt net/http/pipeline capistrano/version rubygems/builder rubygems/format diff/lcs graphviz
  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. # From ruby source: grep -E 'rb_require' -R . | grep -E '\.c:.*rb_require.*'
  103. # Add dependencies of ruby files from ruby lib.so
  104. package_files.each do |(pkg,files)| files.each do |file|
  105. case file
  106. when /\/nkf\.so$/ ; files_requires[file]=files_requires[file] + ["enc/encdb"]
  107. when /\/objspace\.so$/; files_requires[file]=files_requires[file] + ["tempfile"] # dump_output from ext/objspace/objspace_dump.c
  108. when /\/openssl\.so$/; files_requires[file]=files_requires[file] + ["digest"] # Init_ossl_digest from ext/openssl/ossl_digest.c
  109. end
  110. end; end
  111. puts "Grouping package requirements per package"
  112. package_requires_files = Hash.new{|h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
  113. package_files.each do |(pkg,files)|
  114. package_requires_files[pkg]
  115. files.each do |file|
  116. files_requires[file].each do |requires|
  117. package_requires_files[pkg][requires] << file
  118. end
  119. end
  120. end
  121. weak_dependency=Hash.new { |h,k| h[k]=[] }
  122. weak_dependency.merge!({
  123. "ruby-misc"=>["ruby-openssl","ruby-fiddle"], #securerandom.rb
  124. "ruby-debuglib"=>["ruby-readline"], #debug.rb
  125. "ruby-drb"=>["ruby-openssl"], #drb/ssl.rb
  126. "ruby-irb"=>["ruby-rdoc", "ruby-readline"], #irb/cmd/help.rb
  127. "ruby-gems"=>["ruby-openssl","ruby-io-console","ruby-webrick"], #rubygems/commands/cert_command.rb rubygems/user_interaction.rb rubygems/server.rb
  128. "ruby-mkmf"=>["ruby-webrick"], #un.rb
  129. "ruby-net"=>["ruby-openssl","ruby-io-console","ruby-zlib"], #net/*.rb
  130. "ruby-optparse"=>["ruby-uri","ruby-datetime"], #optparse/date.rb optparse/uri.rb
  131. "ruby-rake"=>["ruby-net","ruby-gems"], #rake/contrib/ftptools.rb /usr/bin/rake
  132. "ruby-rdoc"=>["ruby-gems","ruby-readline","ruby-webrick", #/usr/bin/rdoc and others
  133. "ruby-io-console"], #rdoc/stats/normal.rb
  134. "ruby-webrick"=>["ruby-openssl"], #webrick/ssl.rb
  135. "ruby-testunit"=>["ruby-io-console"], #gems/test-unit-3.1.5/lib/test/unit/ui/console/testrunner.rb
  136. })
  137. puts "Preloading gems..."
  138. Gem::Specification.all.each{ |x| gem x.name }
  139. puts "Looking for package dependencies..."
  140. package_provides = {}
  141. package_dependencies = Hash.new { |h,k| h[k]=[] }
  142. package_requires_files.each do
  143. |(pkg,requires_files)|
  144. requires_files.each do
  145. |(require,files)|
  146. if package_provides.include?(require)
  147. found = package_provides[require]
  148. else
  149. found = package_files.detect {|(pkg,files)| files.detect {|file| $:.detect {|path| "#{path}/#{require}" == file.gsub(/\.(so|rb)$/,"") } } }
  150. if not found
  151. $stderr.puts "#{pkg}: Nothing provides #{require} for #{files.collect {|file| file.sub("/usr/lib/ruby/","") }.join(",")}"
  152. failed = true
  153. next
  154. end
  155. found = found.first
  156. package_provides[require] = found
  157. end
  158. if weak_dependency[pkg].include?(found)
  159. puts "#{pkg}: #{found} provides #{require} (weak depedendency ignored)"
  160. else
  161. puts "#{pkg}: #{found} provides #{require} for #{files.collect {|file| file.sub("/usr/lib/ruby/","") }.join(",")}"
  162. package_dependencies[pkg] += [found]
  163. end
  164. end
  165. end
  166. if failed
  167. puts "There is some missing requirements not mapped to files in packages."
  168. puts "Please, fix the missing files or ignore them on require_ignore var"
  169. exit(1)
  170. end
  171. # Remove self dependency
  172. package_dependencies = Hash[package_dependencies.collect {|(pkg,deps)| [pkg,package_dependencies[pkg]=deps.uniq.sort - [pkg]]}]
  173. package_dependencies.default = []
  174. puts "Expanding dependencies..."
  175. begin
  176. changed=false
  177. package_dependencies.each do
  178. |(pkg,deps)|
  179. next if deps.empty?
  180. deps_new = deps.collect {|dep| [dep] + package_dependencies[dep] }.inject([],:+).uniq.sort
  181. if not deps == deps_new
  182. puts "#{pkg}: #{deps.join(",")}"
  183. puts "#{pkg}: #{deps_new.join(",")}"
  184. package_dependencies[pkg]=deps_new
  185. changed=true
  186. end
  187. end
  188. end if not changed
  189. puts "Removing redundant dependencies..."
  190. package_dependencies.each do
  191. |(pkg,deps)|
  192. package_dependencies[pkg]=deps.uniq - [pkg]
  193. end
  194. puts "Checking for mutual dependencies..."
  195. package_dependencies.each do
  196. |(pkg,deps)|
  197. if deps.include? pkg
  198. $stderr.puts "#{pkg}: Cycle dependency detected! "
  199. failed = true
  200. end
  201. end
  202. exit(1) if failed
  203. package_dependencies2=package_dependencies.dup
  204. package_dependencies.each do
  205. |(pkg,deps)|
  206. # Ignore dependencies that are already required by another dependency
  207. deps_clean = deps.reject {|dep_suspect| deps.detect {|dep_provider|
  208. if package_dependencies[dep_provider].include?(dep_suspect)
  209. puts "#{pkg}: #{dep_suspect} is already required by #{dep_provider}"
  210. true
  211. end
  212. } }
  213. if not deps==deps_clean
  214. puts "before: #{deps.join(",")}"
  215. puts "after: #{deps_clean.join(",")}"
  216. package_dependencies2[pkg]=deps_clean
  217. end
  218. end
  219. package_dependencies=package_dependencies2
  220. puts "Checking current packages dependencies..."
  221. ok=true
  222. package_dependencies.each do
  223. |(pkg,deps)|
  224. current_deps=`opkg depends #{pkg} | sed -r -e '1d;s/^[[:blank:]]*//'`.split("\n")
  225. current_deps.reject!{|dep| dep =~ /^lib/ }
  226. current_deps -= ["ruby"]
  227. extra_dep = current_deps - deps
  228. $stderr.puts "Package #{pkg} does not need to depend on #{extra_dep.join(" ")} " if not extra_dep.empty?
  229. missing_dep = deps - current_deps
  230. $stderr.puts "Package #{pkg} needs to depend on #{missing_dep.join(" ")} " if not missing_dep.empty?
  231. if not extra_dep.empty? or not missing_dep.empty?
  232. $stderr.puts "define Package/#{pkg}"
  233. $stderr.puts " DEPENDS:=ruby#{([""] +deps).join(" +")}"
  234. ok=false
  235. end
  236. end
  237. puts "All dependencies are OK." if ok
  238. __END__