Merge rules allow you to define that certain files should be merged to certain other files. The scenario where this is most commonly used is to merge .h files to .c files headers (or vice-versa).
Rules take the form of a source pattern plus a directive: merge to, do not merge or follow. When specifying a merge to rule, a target pattern must also be specified plus (optionally) an includes relationship. Patterns may include wildcards '*' (to denote one or more tokens or characters) and/or '?' (to denote precisely one token or character). Importantly, "{varname}" can be used instead of '*' - this imposes the additional constraint that the value of the wildcard must be identical in both source and target expressions.
For header/body merging, a small number of general rules should be sufficient to capture most cases, with exceptions dealt with by more specific rules. For example, the rule merge {a}/{b}.h to {a}/{b}.cpp will automatically merge a file X.h (Y.h etc.) to the file X.cpp (Y.cpp) in the same folder (if it exists). This can be formulated more loosely as merge */{b}.h to */{b}.cpp. Now X.h will be merged to a file X.cpp in any location as long as there is only occurrence of X.cpp (a merge-to rule will only be executed where a source can be matched to one unique target).
As you define rules, you can see the effect in the Merged files viewer, while the Unmerged files viewer shows any exceptions (cases where a file matches at least one source pattern but it was not possible to identify a unique target file. To apply the rules to the entire Structure101 Studio model, choose Project/Refresh from the main menu. If you save the rules to your project file, they will be automatically applied (globally) next time you open the project.
For the remainder of this topic, let's run through a concrete example of how we might start defining the rules to merge the .h files to the corresponding .cpp files in the qt code-base (http://trolltech.com). We'll start with a bad rule, examine why it is wrong, and then keep improving it incrementally until it is right.
Merge */*.h to */*.cpp // bad
This states that, for any .h file in any directory, merge it to a .cpp file in any directory.
To apply this rule, the processing logic will iterate through each file in the model, seeing if its pattern matches the source pattern. If yes, it will look to see if there is one and any only one other file in the model that matches the target pattern (and constraints). In this case, it will always find more than one candidate target and so will not execute the merge.
Now lets refine that rule.
Merge */{x}.h to */{x}.cpp // better
This states that, for any file somename.h file in any directory, merge it to the file somename.cpp in any directory. The difference here to the previous (bad) rule is that we are using the named variable "x" instead of the wildcard "*". This has the same semantics when it comes to deciding whether the source and target patterns match, but imposes the additional constraint that the local names of both source and target must also match.
Unlike the first rule, this one will now find a number of unique source/target matches and execute these merges accordingly. However, there are still a couple of things we can do to make our general rule better.
Merge */{x}.h to */{x}.cpp [where target includes source] // better still
This is the same as the previous rule but imposes the additional constraint that the merge should only apply if the target file has an "includes" relationship to the source file. This constraint removes (most) accidental filename collisions from the equation.
Merge {f}/{x}.h to {f}/{x}.cpp [where target includes source] // almost perfect
This captures the additional constraint that the qt code-base generally co-locates its header and body files in the same directory. Similar rules could be used to capture the case e.g. where the a single includes directory is used for all header files, or where the header tree mirrors the body tree.
This rule is now almost perfect (as the single rule that captures the general case). The only weakness is that it casts the net wider than it needs to, picking up e.g. system headers (that only contain .h files), rather than limiting it to the file pairs that form the heart of the qt code-base. This is addressed by qualifying the outer scope as follows:
Merge */qt*/{f}/{a}.h to */qt*/{f}/{a}.h [where target includes source] // perfect ;-)
Note: An alternative would be to use excludes to remove non-core qt code from the model by defining an exclude.
This single rule is sufficient to capture - unambiguously - the overwhelming majority of "desirable" merge operations in the qt code-base. A second rule takes care of the practice, common in qt, of working with dual header files (x.h and x_p.h).
Merge */qt*/{f}/{a}_p.h to */qt*/{f}/{a}.h [where target includes source] // handle _p suffix
A third rule handles merging of (co-located) C code within the 3rd party area (again, this is something that could maybe be excluded from the model or perhaps transformed away).
Merge */3rdparty/{f}/{a}.c to */3rdparty/{f}/{a}.cpp[where target includes source] // handle 3d party stuff
Not all header/body files in qt are co-located in the same directory. In a bunch of cases, the headers are arranged under an includes directory. These could be addressed either with one general rule and a bunch of exception handling rules, or by more specific rules e.g. as follows:
Merge */includes/Qt3Support/private/{a}_p.h to */src/qt3support/{a}.cpp[where target includes source] // handle qt3support
Exception Handling
In most cases, a few general rules will capture all the general cases, and it is then a case of trawling through a small number of exceptions to mop up.
An exception, in this context, means a file that matched a source pattern but where the merge process was unable to find one and only one target file matching the target pattern and meeting all constraints (for example, the very first bad rule above would give rise to a large number of exceptions, namely every single .h file in the code-base).
It is often useful to differentiate between cases where there is or could be a natural merge candidate (but this could not be uniquely identified by a general merge rule), versus those where a direct merge makes little or no sense. You can handle the latter case by defining a do not merge rule (requires only a source pattern). This makes no substantive difference to the merge process itself, but does affect the exception reporting, allowing you to focus in on the genuine exceptions.
Finally, you can also define follow rules. Like do not merge rules, these only require a source pattern.
This type of rule is best explained with an example. Supposing we have two folder as follows:
- f1 contains 11 files, namely 1.h, 2.h, 10.h, and odd.h
- f2 contains 10 files, namely 1.c, 2.c, 10.c
As a result of other merge rules, f1/1.h gets merged to f2/1.cpp, f1/2.h to f2/2.cpp, etc.
At the end of this, odd.h is left hanging on its own in f1. This is probably undesirable.
But if we define a follow rule to catch f1/odd.h, then it will (as the name suggests) follow its siblings across to f2. In this sense, it makes sense to think of follow rules as a kind of conditional transformation.
There are a couple of things to be aware of here:
- A follow is only applied if (i) a file matches the source pattern of the rule and (ii) a unique follows target can be inferred from the merge rules applied to its siblings
- Follow directives are always applied after all other rules have been executed
See also: Hierarchies