Search code examples
gwtgwt-2.7gwt-2.8

Precise control of GWT permutations via *.gwt.xml


I need to precisely specify GWT permutations and control the variation within them (combinations of property values supported by each) but have hard time finding detailed behaviour specification.

During my experimentation I have learned that I have to watch for creating set-property ... when-property-is cycles even though these cycles are "stable" - i.e. they do not change any part of the cycle, just "confirm" it. That restricted what I can do, so I decided to try another way - define a brand new property, (just) for example:

  <define-property name="precise.permutation" values="webkit,gecko,ie,unsupported"/>

... and then have something like:

  <set-property name="precise.permutation" value="webkit">
    <!-- ... custom conditions ... -->
  </set-property>
  <set-property name="precise.permutation" value="gecko">
    <!-- ... custom conditions ... -->
  </set-property>
  <set-property name="precise.permutation" value="ie">
    <!-- ... custom conditions ... -->
  </set>
  <set-property name="precise.permutation" value="unsupported">
    <!-- ... custom conditions ... -->
  </set-property>

... I then tried restricting this to all but unsupported which is used as an example but is entirely counter-intuitive as it uses the same set-property tag as above:

  <set-property name="precise.permutation" value="webkit,gecko,ie" />

I also had to collapse other properties to make sure that they don't cause additional permutations.

Unfortunately, that does not seem to work as intended (tried with GWT 2.8 RC2). Even though the "unsupported" permutation does not show up (this is desired), the combinations of properties from it reappear in other permutations (which isn't desired).

Can someone help me find authoritative and complete documentation on this or help me figure this out, please?

Thanks!


Solution

  • I still have't found the detailed documentation but I have done a ton of experimentation and have concluded the following...

    (Note: my examples use some concepts from Sencha GXT which I develop with)

    <define-property> is used to define the property and its possible values but is not the last word in terms of which values will be actually used. There should be exactly one such definition per property in the hierarchy of the *.gwt.xml files.

    <set-property> does NOT set the property to A value but creates a sort of a rule with the COLLECTION of POSSIBLE values for that property (ideally to a subset of what is defined in &ltdefine-property>). The last declaration of <set-property> for a given property wins. Be careful! If you use a <set-property> after <inherits> (inheriting a module), you may/will be overriding any rules that you inherited. Note that there are conditional rules and unconditional rules. For example,

    <set-property name="gxt.user.agent" value="ie10, ie11, gecko1_9, safari5, chrome"/>
    

    ... will unconditionally state that, when determining permutations, the property 'gxt.user.agent' can have any one of the listed values. Conversely,

    <set-property name="user.agent" value="safari">
      <any>
        <when-property-is name="gxt.user.agent" value="safari5" />
        <when-property-is name="gxt.user.agent" value="chrome" />
      </any>
    </set-property>
    

    Will state that user.agent can only be "safari" when "gxt.user.agent" is either "safari5" or "chrome". The order seems important, so you would want rules for dependent properties declared after the rules for their dependencies. Cyclic dependencies will fail the compilation. You can create many conditional rules as long as they don't contradict one another. I do not know yet what would happen if they do, but I guess that the last declared would win.

    Note that the conditional rules can also specify multiple possible values. For example (illustration only, may not match your needs):

    <!-- On desktops we do all browsers including IE -->
    <set-property name="gxt.user.agent" value="safari5, chrome, gecko1_9, ie11">
      <when-property-is name="gxt.device" value="desktop" />
    </set-property>
    <!-- ... but on tablets and phones we exclude IE -->
    <set-property name="gxt.user.agent" value="safari5, chrome, gecko1_9">
      <any>
        <when-property-is name="gxt.device" value="tablet" />
        <when-property-is name="gxt.device" value="phone" />
      </any>
    </set-property>
    

    You can use <any> and <all> to create complex/compound criteria. These can be nested inside one another.

    How can this be used to get precise control of permutations? You may not need this but it helped me to begin by defining two properties like these:

    <define-property name="custom.use.case" values="case1, case2, ..."/>
    <property-provider name="helix.product.mode"><![CDATA[   
        var useCase = ...; // JavaScript code to determine the use case
        return useCase;
      ]]>
    </property-provider>
    <define-property name="custom.permutation" values="perm1, perm2, ..."/>
    

    The first property defines the use case. Above I have a way to determine it at runtime. You may not and may skip it. It also serves as a starting point to define everything else as we can define all the other properties based on the use case. For example:

    <!-- Case 1 restrictions -->
    <set-property name="gxt.device" value="desktop">
      <when-property-is name="custom.use.case" value="case1" />
    </set-property>
    <set-property name="gxt.user.agent" value="chrome, safari5, gecko1_9, ie11">
      <when-property-is name="custom.use.case" value="case1" />
    </set-property>
    ...
    
    <!-- Case 2 restrictions -->
    <set-property name="gxt.device" value="tablet, phone">
      <when-property-is name="custom.use.case" value="case2" />
    </set-property>
    <set-property name="gxt.user.agent" value="chrome, safari5, gecko1_9">
      <when-property-is name="custom.use.case" value="case2" />
    </set-property>
    ...
    
    <!-- Case 3 restrictions -->
    <set-property name="gxt.device" value="tablet, phone">
      <when-property-is name="custom.use.case" value="case3" />
    </set-property>
    <set-property name="gxt.user.agent" value="safari5">
      <when-property-is name="custom.use.case" value="case3" />
    </set-property>
    ...
    
    etc.
    

    Next thing to do is to collapse all the properties except for the custom.permutation, because we only want custom.permutation to drive the permutations:

      <collapse-property name="custom.use.case" values="*" />  
      <collapse-property name="gxt.device" values="*" />
      <collapse-property name="gxt.user.agent" values="*" />
      <collapse-property name="user.agent" values="*" />
      <collapse-property name="user.agent.os" values="*" />
    

    This approach allows extremely fine grain control over "permutations" and their internal complexity, the "soft permutations" as some call them. There will be exactly one permutation for each possible "custom.permutation" property value. Each permutation will only have the needed "soft permutations" in it if you do a good job of setting up the rules above.

    Note that both actual and soft permutations cost. Having many soft permutations (grouped into actual permutations or not) costs compile performance and runtime code size of the permutation they are grouped in. Do not ignore this, especially if you have many properties. Actual permutations have even higher compile and linking time cost (but having more of them as opposed to combining many soft permutations into grouped actual permutations reduces the size of each permutation).

    If using GXT, starting with version 4, you will notice that it adds gxt.device property having the values such as desktop, tablet and phone. This caused our compilation time to increase about 6-7 times after upgrading to GXT 4 because our permutations went out of control. Some ended up being, quite literally, made for Internet Explorer running on a Mac OS tablet. You can appreciate what a waste of time permutations for non-existent use cases are. By implementing the above we were able to reduce the GWT compilation time down to about half of the original time or about 10-12 times faster than what they were right after GXT 4 upgrade.