Bug 32638 - Enhancement to <macrodef> to support new feature.
Summary: Enhancement to <macrodef> to support new feature.
Status: NEW
Alias: None
Product: Ant
Classification: Unclassified
Component: Core (show other bugs)
Version: 1.6.2
Hardware: PC Windows XP
: P2 enhancement (vote)
Target Milestone: ---
Assignee: Ant Notifications List
URL:
Keywords: PatchAvailable
Depends on:
Blocks:
 
Reported: 2004-12-10 17:01 UTC by Dominique Devienne
Modified: 2009-07-31 05:50 UTC (History)
1 user (show)



Attachments
Modified Ant 1.6 based MacroDef.java (22.99 KB, text/plain)
2004-12-10 17:02 UTC, Dominique Devienne
Details
Modified Ant 1.6 based MacroInstance.java (15.50 KB, text/plain)
2004-12-10 17:04 UTC, Dominique Devienne
Details
patch to set local macro-properties for defined elements and attributs as described in paragraph 1 of the feature request (4.64 KB, patch)
2008-09-11 07:27 UTC, Christoph Dittberner
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Dominique Devienne 2004-12-10 17:01:11 UTC
From the email 'About <macrodef> in the trenches' I sent out today:

This past two weeks, I've worked on coming up with a generic build file
for our multiple projects, which I'd say are 80% to 99% common from one
project to the next. Of course, I'm using heavily <import>, target
overriding, and <macrodef>, but even with these fine features, it's not
always easy to accomodate the variations from one build to the next, 
especially if I want to avoid having to rewrite whole tasks/targets.

To achieve this maximum reuse, and minimum overriding, i.e. to avoid being
forced to rewrite whole tasks, I've defined complex macros with built-in
conditionals, and lots of default attributes. Alas, there's no default
'value' for macro elements, so providing default tags for a macro element
is currently not possible. I believe this lack of element default makes
<macrodef> less powerful that it can be. 

So finally today I had a look at the macrodef code, and tried to understand
how things worked under the hood. I was surprised to find out I understood
the code ;-) (if I glossed over most of the UE/RC cuisine that is).

  BTW, I'm amazed this can all be implemented in a couple of tasks with no
  changes to the framework itself. The new fully dynamic nature of Ant 1.6
  (everything's a UE) is powerful indeed!

I then proceeded to copy MacroDef/MacroInstance to my own antlib, and
amazingly after repackaging and adding a single import, everything worked
fine. (I duplicate the code because we only use an official Ant release.)
I then tweaked <macrodef> to add the following features:

1) Everytime a macro attribute or element is explicitly defined in a macro
   instance (where the macro is used), I define an additional macro attribute
   (local property in the code) which allows to find out in the macro body
   whether the attribute or element was explicitly used/specified.

   When an 'foo' attribute is used, I define '@foo?' with value of true.
   When an 'bar' element is used, I define 'bar?' with value of true

   My macro bodies/impls then do things or not (or differently) based on
   this info. For example:

     <bm:macrodef name="my-copy">
      ...
      <attribute name="tos" default="/dev/null" />
      <sequential>
        ...
        <bm:sequential ifTrue="@{@tos?}">
          <copy file="@{tos}" tofile="..." />
        </bm:sequential>
      </sequential>
    </bm:macrodef>

    <bm:macrodef name="my-module-image">
      ...
      <element name="sources" optional="true" />
      <sequential>
        ...
        <bm:sequential ifTrue="@{@sources?}">
          <zip destfile="@{todir}/sdk/src.zip">
            <sources />
          </zip>
        </bm:sequential>
      </sequential>
    </bm:macrodef>

2) Allow defining an macro element 'default value'. Instead of the default
   being inside the <macrodef> <element>, its inside the macro <sequential>
   where the element is used. I did it this way because the implementation
   was easier, and it makes it easy to read the macro impl, although it
   could be confusing to some I guess?!

   If a macro element is not explicitly specified in the macro instance,
   and the element is optional, then the 'default value' from the macro
   definition is used. If specified, the macro instance value is used as
   usual otherwise. For example:

    <bm:macrodef name="my-register">
      ...
      <element name="patterns" optional="true" />
      <sequential>
        <ds:register ...>
          ...
          <fileset dir="@{dir}">
            <patterns>
              <include name="**/*.class" />
              <exclude name="**/test/*Test.class" />
            </patterns>
          </fileset>
        </ds:register>
      </sequential>
    </bm:macrodef>

    The macro above defaults the patterns element to 1 include + 1 exclude.

3) When coming up with elements a macro could contain, I often end up
   wanting to contain a classpath macro element that I want to pass in
   to <java> or another task taking a classpath. If I can't use the single
   implicit element for some reason, I then have to do something ugly:

   <macrodef name="ugly">
     ...
     <element name="my-classpath" optional="true" />
     <sequential>
       ...
       <java ...>
         <my-classpath />
       </java>
     </sequential>
   </macrodef>

  and I'm forced to use:

  <ugly>
    <my-classpath>
      <classpath refid="classpath" />
    </my-classpath>
  </ugly>

  If I throw in element defaults from (2), the macro becomes:

   <macrodef name="ugly">
     ...
     <element name="my-classpath" optional="true" />
     <sequential>
       ...
       <java ...>
         <my-classpath>
           <classpath refid="classpath" />
         </my-classpath>
       </java>
     </sequential>
   </macrodef>

  To work around this, I added a new useContentOnly attribute to <element>,
  which defaults to true for BC, but when false, allows to pass in the element
  itself as-is, as defined in the macro definition or instance. I can now do

   <macrodef name="nicer">
     ...
     <element name="classpath" optional="true" useContentOnly="false" />
     <sequential>
       ...
       <java ...>
         <classpath refid="classpath" />
       </java>
     </sequential>
   </macrodef>

  and I can use it as

  <nicer />

  or

  <nicer>
    <classpath refid="alt.classpath" />
  </nicer>

  or 

  <nicer>
    <classpath>
      <pathelement location="..." />
      <path refid="classpath" />
    </classpath>
  </nicer>

  With (2) + (3), you can take some ant code composed of 3 tasks for example,
  put it in a macro, and define an optional element for each task with
  useContentOnly="false", and have the macro user be able to override only
  one of the macro's task.

  To be truly complete, we'd also need a way to refer to the default content
  of the element to reuse it in the macro instance itself (i.e super).
  I haven't done that.

4) Finally, the last thing I did was to allow using the macro attributes
   in the macro instance, to benefit from the default values computed by
   the macro. Currently, if one defines:

   <macrodef name="macrosub">
     <attribute name="primary" default="foo" />
     <attribute name="secondary" default="@{primary}bar" />
     <element name="nested-elements" optional="true" implicit="true" />
     <sequential>
       <nested-elements />
     </sequential>
   </macrodef>

   and does:

   <macrosub>
     <echo>secondary = @{secondary}</echo>
   </macrosub>

   One will not get secondary = foobar, because the macro instance does
   not have access to the macro 'local properties', which includes the
   computed default attributes. I consider 'secondary' to be part of the
   macro API, and thus logic that it can be used in the macro instance.
   Implementation wise, it means using copy() more often.

My changes are not extensive, and are mostly in MacroInstance#copy and
the addition of MacroDef.TemplateElement#setUseContentOnly.

I'd appreciate some feedback on whether these new features are desirable.
I believe there would be zero BC issues. Thanks, --DD
Comment 1 Dominique Devienne 2004-12-10 17:02:47 UTC
Created attachment 13725 [details]
Modified Ant 1.6 based MacroDef.java

Simply add the useContentOnly attribute to a macrodef element.
Comment 2 Dominique Devienne 2004-12-10 17:04:45 UTC
Created attachment 13726 [details]
Modified Ant 1.6 based MacroInstance.java

Modifies execute() and copy() to add all the new <macrodef> features.
Comment 3 Peter Reilly 2004-12-10 17:29:18 UTC
Another idea would be to use local properties to say if an attribute/element
is set or not, then one could use the usually ant idiom of using the if
attribute to test for the presence of an attribute/element.

<macrodef name="x">
   <element name="contents" optional="yes"/>
   <sequential>
      <my:sequential if="contents.element.set">
          <copy ../>
      </my:seqential>
   </sequential>
</macrodef>
Comment 4 Dominique Devienne 2004-12-10 17:41:21 UTC
Would that work? There wouldn't be any textual substitution by macroSubs, and 
<my:sequential> would know nothing about contents.element.set since not in the 
eral Project properties, and if you did put it in the project properties, then 
we have the usual tmp/immutable property issue.

The properly pull it off, you'd need the notion of a proper AntContext, the 
macro instance one on top of the normal project one, no?

That's why the ifTrue condition works will in this case, since it sidesteps 
the issue by letting macrodef do the macroSubs, and evaluate the result as a 
boolean. --DD
Comment 5 Peter Reilly 2004-12-10 17:46:00 UTC
> The properly pull it off, you'd need the notion of a proper AntContext, the 
> macro instance one on top of the normal project one, no?

Yes, the previous version of the local-property patch does this, it is a
local-property scope not the ant project context.
Comment 6 Christoph Dittberner 2008-09-11 07:27:50 UTC
Created attachment 22555 [details]
patch to set local macro-properties for defined elements and attributs as described in paragraph 1 of the feature request

Hi,

I was looking after a feature like this since I started to use macrodefs in our ant-based build-environment. After a lot of reading through the mailinglists I found this feature request and I implemented the paragraph 1) on current 1.7.1-sources.
Comment 7 SlugFiller 2009-07-12 16:14:39 UTC
The potential use of an "if" construct inside the macrodef could go beyond the capabilities of "sequencial". Taking the "nicer" example above, supposed you wanted the default classpath element to be no element at all, but wanted to keep the "nicer" syntax where it is used.

You could use:
  <macrodef name="nicer">
    ...
    <element name="classpath" optional="true" useContentOnly="false" />
    <sequential>
      ...
      <java ...>
        <macroif ifTrue="classpath.element.set">
          <classpath />
        </macroif>
      </java>
    </sequential>
  </macrodef>

That way you can use:

  <nicer />
  <nicer>
    <classpath>
      <pathelement location="..." />
      <path refid="classpath" />
    </classpath>
  </nicer>

Which expands to:

  <sequential>
    ...
    <java ... />
  </sequential>
  <sequential>
    ...
    <java ...>
      <classpath>
        <pathelement location="..." />
        <path refid="classpath" />
      </classpath>
    </java>
  </sequential>

Note the slight difference between the use of a modified "sequencial", and the use of "marcoif" - the latter effects the expanding phase and can contain any element, not just tasks.

One aspect I am slightly worried about is name collision: If you make an additional macro element called classpath, you cannot enter an additional literal classpath element. The usual solution is to use different element names, but this doesn't work, and could become a common problem, if you want to use the useContentOnly feature.

A possible solution is to add a special element namespace which is removed during expansion:

  <macrodef name="namespace">
    ...
    <element name="include" namespace="element" useContentOnly="false" />
    <sequential>
      ...
      <java ...>
        <element:include />
        <include name="src/**/*.java" />
      </java>
    </sequential>
  </macrodef>

Then you can use:

  <namespace>
    <include name="plugins/**/*.java" />
  </namespace>

This would expand to:

  <sequential>
    ...
    <java ...>
      <include name="src/**/*.java" />
      <include name="plugins/**/*.java" />
    </java>
  </sequential>

Note that the namespace is only used in the macrodef, not the macro instance nor the expansion, otherwise it wouldn't add anything to just using a different name. An alternative to a namespace would be to give an alias which is only used inside the macrodef:

  <macrodef name="namespace">
    ...
    <element name="include" alias="moreinclude" useContentOnly="false" />
    <sequential>
      ...
      <java ...>
        <moreinclude />
        <include name="plugins/**/*.java" />
      </java>
    </sequential>
  </macrodef>

The usage and expansion are the same as above. An alternative, more self-explanatory name for the attribute could be "internally" or "internalName".
One could take this one step further by having different element names during macro instance and post expansion, but that might be overkill.

Not sure how useful all of this is, but it would certainly add some nice possibilities.