Dependencies in code can be defined in different ways. What we have found is that an important thing when looking at a dependency, is to know the rules that determined it’s relationship. Here are the details of how Understand determines what a dependency is.
An entity is anything that Understand has information on from your source code, such as a file, class, function, or variable.
A reference is a location in the code that connects two entities, such as function 1 calling function 2, or class C declaring member function Foo. You can view an entity’s references in the Information Browser. For example, in the GitAhead sample project, the local object app in src/app/GitAhead.cpp has this information.
A dependency is a reference that connects two groups of entities. Entity groups are based on the “parent” property of an entity. You can use the interactive report “API Info” (in the right-click menu of any entity) to view detailed information about an entity, including the parent. For example, the local object app is a child of main.
The parent of an entity is determined during analysis by the parser and is language specific. In general, the parent is based on the “Define in” reference kind. From the Information Browser, you can see that app is defined in main, so app and main have a parent-child relationship. The function main is a child of GitAhead.cpp. File entities have no parent entity.
Entities can be grouped by architectures, extending the parent chain of an entity. The built-in Directory Structure architecture groups file entities by folder. So, the full parent chain of the app object within the Directory Structure Architecture is app (Local Object) ->main (Function) → GitAhead.cpp (File) → app (Architecture) → src (Architecture) → Directory Structure (Architecture). Custom architectures can group non-file entities in addition to file entities. The parent chain of an entity can be viewed from the Architecture Browser with “Show Implicitly Mapped Child Entities” turned on.
Dependencies are calculated at a given level of the parent chain of an entity. The available levels are File, Class, and Architecture. So, files can depend on other files, classes can depend on other classes, and architectures can depend on other architectures within the same root architecture. If a group cannot be found for an entity at a given level then the reference will not be considered as a dependency. So class dependencies won’t include any references to non-member functions, and architectures won’t include references to anything that isn’t mapped within the root architecture.
The basic steps of dependency calculation are:
- Find all entities in the starting group.
- Get the references for each entity
- Determine the group at the given level for the second (referenced) entity
- If the group of the second entity exists and is not the same as the starting group, then the reference is a dependency connecting the two groups.
There are a few things to be aware of with dependency calculation.
First, as an optimization, entities can’t belong to multiple groups.
· At a class level, an entity belongs to the first class parent found in the parent chain.
· At a file level, an entity usually belongs to the file it is defined in (Ada has some exceptions described below). That file is not necessarily the same file found by walking up the parent chain. For example, a C++ member function defined in a cpp file belongs to the cpp file even though the class parent may belong to a header file. Entities defined in multiple files such as C++ namespaces, are not supported and may be assigned to an arbitrary file that can change with each analysis.
· At an architecture level, an entity belongs to the first architecture it maps to within the given root architecture. If the entity is not directly mapped to the architecture, the parent chain is used with the first directly mapped parent determining the architecture. Entities that can be mapped to multiple architectures within a root architecture are not supported. Currently a multiply mapped entity will belong to the first architecture found which is not guaranteed to be consistent, and it will be considered in the starting group of any architecture it or a parent is mapped to.
Second, not all references are considered as potential dependencies. References have a reference kind. The Perl API documentation lists all possible reference kinds. Dependency references are filtered based on a reference kind string. Users can build a custom reference kind string from the dependency browser and dependency graphs. The “All Dependencies” reference kind string is available on request and is not equivalent to a custom reference kind string with all reference kinds checked.
Third, when Ada file dependencies are calculated, the reference kind impacts the dependent file. Ada call, instance of, and separate from references depend on the body file. For packages, procedures, functions, and non-object tasks and protected units, the reference kinds rename, use, and typed also depend on the body file. Ada parent, use package, and with references depend on the spec file. Anything else depends on the file determined by the parser.