Automating A Custom Architecture

Abstract: Build a custom architecture using the Perl or Python API to virtually reorganize your code however you wish.

“Hey Natasha, you should write an article about architectures.” It’s an important feature. I have used architectures. BUT, there is no way I’d take the time to build an architecture by hand using the Architecture Designer. I’m much too lazy. Although, making the Architecture Graph edit architectures by drag and drop was fun:

Using the Architecture Designer (or right click, Graph->Graph Architecture) you can edit an architecture by dragging entities.

Still, for anything bigger than Fastgrep, I wouldn’t use it. After all, isn’t the point of being a programmer to automate these things? That’s why my biggest architecture usage, described in this article, used a script.

However, that script isn’t an example of best practice. It only works for files and it exports an old format no longer used. In order to write this article, we needed a better way to make architectures using the Perl and Python APIs. 

Now, just like you can add an annotation to an entity with ent.annotate(), you can add an entity to an architecture with ent.add_to_arch(). The function can take a string or an understand.Arch object. If you use a string, then then the architecture doesn’t have to exist. Here’s an example in Perl:

use Understand;
$db = Understand::open("fastgrep.und");

foreach $file ($db->lookup("regsub.c","file")) {
  $file->add_to_arch("perl/test"); # perl/test doesn't exist yet
}

$arch = $db->lookup_arch("perl/test"); # $file->add_to_arch created it, so now it can be looked up
print $arch->name() . "\n";

foreach $file ($db->lookup("regexp.c","file")) {
  $file->add_to_arch($arch); # and you can add to the arch object directly instead of by a longname string
}

Now that I have this cool functionality, how can I use it? Well, I’ve been working with graphs a lot lately, and it would be useful to know what the maximum call level for a particular function would be. For example, I might want a really small graph as a test case for a Visio export. I want a deep graph for testing the level option. So, I’ll create an architecture with functions that groups the functions by calls depth. 

import understand
import sys

def depth(ent):
  level = -1
  cur = [ent]
  next = []
  visited = set()
  while len(cur):
    level += 1
    for curEnt in cur:
      if curEnt in visited:
        continue
      visited.add(curEnt)
      for ref in curEnt.refs("call"):
        next.append(ref.ent())
    cur = next
    next = []
  return level

db = understand.open(sys.argv[1]);
cache = dict()
for ent in db.ents("function"):
  ent.add_to_arch("calllevels/"+str(depth(ent)))

The script is obviously lacking in optimization and it’s probably a bad idea to run it on any large projects, but it worked great for Fastgrep. At the very least, I can learn if there is anything with a call depth greater than 6 (which is the call depth of chimaera, the go-to function for testing). The generated architecture shows several functions with a greater depth:

The CallLevels architecture, created using the script above for Fastgrep.

With this capability, you can quickly automate your own architecture creation however you wish!