cheind / ruby-snippets
- Source
- Commits
- Network (0)
- Issues (0)
- Downloads (0)
- Wiki (1)
- Graphs
-
Branch:
master
ruby-snippets / dependencies / boost.rb
| 5467e9f9 » | cheind | 2008-12-18 | 1 | # | |
| 2 | # Project:: Ruby-Snippets | ||||
| 3 | # | ||||
| 4 | # Author:: Christoph Heindl (mailto:christoph.heindl@gmail.com) | ||||
| 5 | # Homepage:: http://cheind.blogspot.com | ||||
| 6 | # | ||||
| 7 | # == Overview | ||||
| 8 | # | ||||
| 05c08866 » | cheind | 2008-12-18 | 9 | # Ruby script that builds inter-project dependencies of boost (http://www.boost.org) | |
| 701e4f9a » | cheind | 2008-12-18 | 10 | # via include file parsing using methods and classes defined in <tt>Dependencies</tt>. | |
| 11 | # | ||||
| dcada3be » | cheind | 2008-12-19 | 12 | # == Prerequisites | |
| 13 | # | ||||
| 14 | # To run the application you need to install ruby and RGL (Ruby Graph Library). | ||||
| 15 | # After ruby has been installed, install RGL via command line | ||||
| 16 | # | ||||
| 17 | # > gem install rgl | ||||
| 18 | # | ||||
| 19 | # See http://rgl.rubyforge.org/rgl/index.html for RGL documentation and install | ||||
| 20 | # instructions. | ||||
| 21 | # | ||||
| 22 | # Graphviz http://www.graphviz.org/ is required to transform graphs to images. | ||||
| 23 | # | ||||
| 24 | # == What is it? | ||||
| 25 | # | ||||
| 26 | # The script records all header files within the boost include directory. Each | ||||
| 27 | # file path is assigned a vertex name: if file path contains a nested directory | ||||
| 28 | # inside boost include directory, then the first nested directory name is used | ||||
| 29 | # as a vertex name in the graph. If the file path points directly to the boost | ||||
| 30 | # include directory the vertex name as follows: | ||||
| 31 | # if a directory with the same name exists (except for the file extension '.hpp') | ||||
| 32 | # then the directory name is used. Else, the basename of the file including the | ||||
| 33 | # extension is used (considered as mini-library). | ||||
| 34 | # | ||||
| 35 | # Next, each recorded file is parsed for matching '#include' preprocessor statements. | ||||
| 36 | # When such a statement is encountered, the script tries to lookup the file | ||||
| 37 | # from previous recordings. If found, the vertex name of the file recorded previously | ||||
| 38 | # is used. A dependency is then generated between the vertex name of the file | ||||
| 39 | # parsed and the vertex name from the resolved include statement. If the dependency | ||||
| 40 | # causes a cycle in the dependency graph, the dependency is not added but error'd | ||||
| 41 | # to the logger. | ||||
| 42 | # | ||||
| 43 | # After all files are parsed, the dependency graph is reduced by removing | ||||
| 44 | # edges u -> w, where u -> ... -> w exists and written to a '.dot' file | ||||
| 45 | # that can be converted into various formats using http://www.graphviz.org/. | ||||
| 46 | # | ||||
| 47 | # == What is it not? | ||||
| 48 | # | ||||
| 49 | # This script is not | ||||
| 50 | # | ||||
| 51 | # - <b>a complete preprocessor parser</b>: it does not care about conditional | ||||
| a96710f2 » | cheind | 2008-12-19 | 52 | # include statements or commented ones. Parsing is based on simple pattern matching | |
| 53 | # to keep to code small nice. You might however add a complete parser if you like to. | ||||
| dcada3be » | cheind | 2008-12-19 | 54 | # | |
| 55 | # - <b>handling cyclic dependencies</b>: some include statements cause cyclic dependencies | ||||
| 56 | # that simply result from the choice of mapping to vertex names. I.e. when | ||||
| 57 | # file boost/a/detail/detail.hpp includes boost/b/win32/abc.hpp which in turn | ||||
| 58 | # includes boost/a/other/other.hpp and the mapping resolves vertex names from | ||||
| 59 | # the first nested directory inside boost, we generate a dependency from a -> b and | ||||
| 60 | # finally another one from b -> a which will not be added (unless explicitly allowed | ||||
| 61 | # see <tt>Dependencies::Walker#on_cycle</tt>). | ||||
| 62 | # | ||||
| a96710f2 » | cheind | 2008-12-19 | 63 | # | |
| 5467e9f9 » | cheind | 2008-12-18 | 64 | ||
| dcada3be » | cheind | 2008-12-19 | 65 | require 'logger' | |
| 5467e9f9 » | cheind | 2008-12-18 | 66 | require 'dependencies/walker' | |
| 67 | require 'dependencies/dot' | ||||
| dcada3be » | cheind | 2008-12-19 | 68 | require 'dependencies/all_dependencies' | |
| 5467e9f9 » | cheind | 2008-12-18 | 69 | ||
| dcada3be » | cheind | 2008-12-19 | 70 | def usage(notboost=false) | |
| 71 | puts "ruby #{$0} path_to_boost" unless notboost | ||||
| 72 | puts "path #{ARGV[0]} does not seem to be the boost include directory." if notboost | ||||
| 5467e9f9 » | cheind | 2008-12-18 | 73 | exit(1) | |
| 74 | end | ||||
| 75 | |||||
| 76 | # Sanity check for command line arguments | ||||
| dcada3be » | cheind | 2008-12-19 | 77 | usage(false) unless ARGV.length == 1 | |
| 5467e9f9 » | cheind | 2008-12-18 | 78 | exit(1) unless File.directory?(ARGV[0]) | |
| dcada3be » | cheind | 2008-12-19 | 79 | usage(true) unless File.exists?(File.join(ARGV[0], 'boost/config.hpp')) | |
| 5467e9f9 » | cheind | 2008-12-18 | 80 | ||
| 81 | boost_dir = ARGV[0] | ||||
| 82 | |||||
| 83 | # Instance a walker that records files and dependencies between files | ||||
| dcada3be » | cheind | 2008-12-19 | 84 | logger = Logger.new(STDOUT) | |
| 85 | w = Dependencies::Walker.new(logger) | ||||
| 5467e9f9 » | cheind | 2008-12-18 | 86 | ||
| 87 | # Record all file paths of files ending with '.hpp' residing in any directory | ||||
| 88 | # nested one-level below boost root include directory | ||||
| 89 | w.index(boost_dir, 'boost/*/**/*.hpp') do |path| | ||||
| 90 | # When such a file is discoverd, the nested directory name is used as vertex | ||||
| 91 | # name in the graph | ||||
| 92 | path.split('/')[1] | ||||
| 93 | end | ||||
| 94 | |||||
| 95 | # Index all files residing directly in the boost root include directory. | ||||
| 96 | w.index(boost_dir, 'boost/*.hpp') do |path| | ||||
| 97 | # The vertex named is determined from the following rule: | ||||
| 98 | # When a nested directory with the same name as the file (except for the extension) | ||||
| 99 | # exists, then the directory name is used as vertex name. | ||||
| dcada3be » | cheind | 2008-12-19 | 100 | # Else, the filename is used (considered as mini library) | |
| 5467e9f9 » | cheind | 2008-12-18 | 101 | dirname_exists = File.directory?(File.join(boost_dir, 'boost/', File.basename(path, '.hpp'))) | |
| 102 | if dirname_exists | ||||
| 103 | File.basename(path, '.hpp') | ||||
| 104 | else | ||||
| dcada3be » | cheind | 2008-12-19 | 105 | File.basename(path) | |
| 5467e9f9 » | cheind | 2008-12-18 | 106 | end | |
| 107 | end | ||||
| 108 | |||||
| 109 | # Read the content of all header files inside the boost directory. | ||||
| 110 | w.parse(boost_dir,'boost/**/*.hpp') do |path, file| | ||||
| 111 | # Record dependencies in file by matching include statements | ||||
| 112 | dependencies = [] | ||||
| 113 | while (line = file.gets) | ||||
| 114 | if line =~ /\#include\s+[\"<]([^\">]+)?/ | ||||
| 115 | # Try looking up the file inside the boost directory. | ||||
| 116 | # On success use the same name as the recorded file. | ||||
| 117 | vertex_name = w.try_resolve($1, boost_dir) | ||||
| 118 | dependencies << vertex_name if vertex_name | ||||
| 119 | end | ||||
| 120 | end | ||||
| 121 | dependencies | ||||
| 122 | end | ||||
| 123 | |||||
| dcada3be » | cheind | 2008-12-19 | 124 | logger.info('Performing transitive reduction on graph') | |
| 5467e9f9 » | cheind | 2008-12-18 | 125 | # Reduce the graph by removing all edges between vertex v -> w, when | |
| 126 | # and a path v -> ... -> w exists. | ||||
| dcada3be » | cheind | 2008-12-19 | 127 | boost_graph = w.graph.transitive_reduction | |
| 128 | |||||
| d7ae81f6 » | cheind | 2008-12-19 | 129 | Dir.mkdir('images') unless File.directory?('images') | |
| dcada3be » | cheind | 2008-12-19 | 130 | ||
| 131 | # Write to png file using dot and the default template 'dependencies\graph.template' | ||||
| 132 | logger.info("Saving dependency graph for boost as 'images/boost_graph.png'") | ||||
| 133 | Dependencies.to_png(boost_graph, 'images/boost_graph.png', 'images/boost_graph.dot') | ||||
| 134 | |||||
| 135 | # Finally plot each project seperately | ||||
| 136 | boost_graph.each_vertex do |v| | ||||
| a96710f2 » | cheind | 2008-12-19 | 137 | dot_path = "images/#{v.gsub(/\./, '_')}.dot" | |
| 138 | img_path = "images/#{v.gsub(/\./, '_')}.png" | ||||
| dcada3be » | cheind | 2008-12-19 | 139 | logger.info("Saving dependency graph for #{v} as '#{img_path}'") | |
| 140 | g = boost_graph.all_dependencies(v) | ||||
| 141 | Dependencies.to_png(g, img_path, dot_path) | ||||
| 142 | end | ||||
