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.

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