views:

664

answers:

6

I have the following project structure:

root/
    comp/
        env/
           version/
                  build.xml
           build.xml
        build.xml

Where root/comp/env/version/build.xml is:

<project name="comp-env-version" basedir=".">
    <import file="../build.xml" optional="true" />
    <echo>Comp Env Version tasks</echo>
    <target name="run">
        <echo>Comp Env Version run task</echo>
    </target>
</project>

root/comp/env/build.xml is:

<project name="comp-env" basedir=".">
    <import file="../build.xml" optional="true" />
    <echo>Comp Env tasks</echo>
    <target name="run">
        <echo>Comp Env run task</echo>
    </target>
</project>

root/comp/build.xml is:

<project name="comp" basedir=".">
    <echo>Comp tasks</echo>
</project>

Each build file imports the parent build file and each child inherits and overrides parent tasks/properties.

What I need is to get the generated build XML without run anything.

For example, if I run "ant" (or something like that) on root/comp/env/version/, I would like to get the following output:

<project name="comp-env-version" basedir=".">
    <echo>Comp tasks</echo>
    <echo>Comp Env tasks</echo>
    <echo>Comp Env Version tasks</echo>
    <target name="run">
        <echo>Comp Env Version run task</echo>
    </target>
</project>

Is there an Ant plugin to do this? With Maven? What are my options if not?

EDIT: I need something like "mvn help:effective-pom" for Ant.

A: 

Eclipse understands Ant files. You can see what tasks are visible in your inner build.xml. It won't be quite the format you requested, but it may meet your needs.

Jeremy Stein
Interesting, but I need to make that build file automaticaly, from the command line for example.
inakiabt
A: 

I've been writing Ant build scripts for 7-8 years now but I really don't understand what you are trying to achieve here. Maybe it's me but I fear that even if you get your build working (I'm sure you can), hardly anyone else will understand/maintain it.

Why not keeping things very simple and having sibling projects?

root
    build.xml
    comp
        build.xml
    env
        build.xml
    version
        build.xml

individual build.xml files could import tasks define somewhere else (use Macrodef for this) and your top level build.xml would call the individual ones sequentially?

Once you get your basic build running, you can play around with Ivy or Maven for more interesting stuff.

But if you really want to generate build files, you can give Groovy and its templating engine a try.

Vladimir
Thanks, what I'm looking for is to generate the result build.xml of a "version" (in this case). Somethig like "mvn help:effective-pom" for Ant.
inakiabt
A: 

I would recommend structuring your build files so that they use a dependency tree which is what ant is really good at. If you follow @Vladimir's advice and structure your build files like that, then you can have a single build file in 'root' and have it recursively execute your build. For example:

<!-- iterate finds all build files, excluding this one
     and invokes the named target 
-->
<macrodef name="iterate">
    <attribute name="target"/>
    <sequential>
        <subant target="@{target}">
            <fileset dir="." 
                     includes="**/build.xml"
                     excludes="build.xml"/>
        </subant>
    </sequential>
</macrodef>


<target name="build"  description="Build all sub projects">
    <iterate target="build"/>
</target>

<target name="clean"  description="Clean all sub projects">
    <iterate target="clean"/>
</target>
DaveParillo
Good advice, but is not what I'm looking for.I need to generate a XML build file inheriting all build.xml files from parents.I don't want to build the project, I want to get the generated build file for your example.
inakiabt
Take a look at the documentation / example for the subant task - http://ant.apache.org/manual/CoreTasks/subant.html - if your includes in your macrodef looks like "**/version/build.xml" and you set things up 'just so' you should be able to use the xslt task and the common2master.xsl to write out a master build file.
DaveParillo
A: 

Sounds like gradle could help you. Gradle is able to import your ant build.xml file. Then you can start a dry run to get a list of targets that would be executed.

tangens
+3  A: 

Based on the description of the include task, it works very much like an entity includes with two additional features:

  • target overriding
  • special properties

For the purposes of viewing the "effective build" I don't think the special properties processing is required (though it could be added by iterating the inserted targets). So the processing to achieve this becomes.

  1. Parse the build.xml to a DOM
  2. For each top-level include tag found (only top-level are allowed), find the referenced source file.
  3. Parse the referenced build.xml
  4. insert any content from the referenced build.xml that don't collide with those in the current file.
  5. Repeat step 2 for referenced build.xml file(s) until no more found
  6. Output the resultant DOM

You can define a custom ant task so that this processing can be defined in a task to be run from within your build. See this tutorial for more details.

Here's a basic implementation that recurses through imports and inserts the DOM elements from the referenced files. There's almost certainly a few bugs in it as I threw it together, but it should do largely what you're after:

/**
 * Reads the build.xml and outputs the resolved build to stdout
 */
public static void main(String[] args) {
    try {
        Element root = new EffectiveBuild().parse(new File(args[0]));

        XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());

        outputter.output(root, System.out);
    } catch (Exception e) {
        // TODO handle errors
        e.printStackTrace();
    }

}

/**
 * Get the DOM for the passed file and iterate all imports, replacing with 
 * non-duplicate referenced content
 */
private Element parse(File buildFile) throws JDOMException, IOException {
    Element root = getRootElement(buildFile);

    List<Element> imports = root.getChildren("import");

    for (int i = 0; i < imports.size(); i++) {
        Element element = imports.get(i);

        List<Content> importContent = parseImport(element, root, buildFile);

        int replaceIndex = root.indexOf(element);

        root.addContent(replaceIndex, importContent);

        root.removeContent(element);
    }

    root.removeChildren("import");

    return root;
}

/**
 * Get the imported file and merge it into the parent.
 */
private List<Content> parseImport(Element element, Element currentRoot,
        File buildFile) throws JDOMException, IOException {
    String importFileName = element.getAttributeValue("file");
    File importFile = new File(buildFile.getParentFile(), importFileName)
            .getAbsoluteFile();
    if (importFileName != null) {
        Element importRoot = getRootElement(importFile);

        return getImportContent(element, currentRoot, importRoot,
                importFile);
    }

    return Collections.emptyList();
}

/**
 * Replace the passed element with the content of the importRoot 
 * (not the project tag)
 */
private List<Content> getImportContent(Element element,
        Element currentRoot, Element importRoot, File buildFile)
        throws JDOMException, IOException {

    if (currentRoot != null) {
        // copy all the reference import elements to the parent if needed
        List<Content> childNodes = importRoot.cloneContent();
        List<Content> importContent = new ArrayList<Content>();

        for (Content content : childNodes) {
            if (content instanceof Element
                    && ((Element) content).getName().equals("import")) {
                importContent.addAll(parseImport((Element) content,
                        currentRoot, buildFile));
            }
            if (!existsInParent(currentRoot, content)) {
                importContent.add(content);
            } else {
                // TODO note the element was skipped
            }
        }

        return importContent;
    }

    return Collections.emptyList();
}

/**
 * Return true if the content already defined in the parent
 */
private boolean existsInParent(Element parent, Content content) {
    if (content instanceof Text) {
        if (((Text) content).getText().trim().length() == 0) {
            // let the pretty printer deal with the whitespace
            return false;
        }
        return true;
    }
    if (content instanceof Element) {
        String id = ((Element) content).getAttributeValue("name");

        String name = ((Element) content).getName();
        List<Content> parentContent = parent.getChildren();

        if (id != null) {
            for (Content content2 : parentContent) {
                if (content2 instanceof Element
                        && ((Element) content2).getName().equals(name)) {
                    String parentId = ((Element) content2)
                            .getAttributeValue("name");

                    if (parentId != null && parentId.equals(id)) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

/**
 * Parse the passed file.
 */
private Element getRootElement(File buildFile) throws JDOMException,
        IOException {
    SAXBuilder builder = new SAXBuilder();
    builder.setValidation(false);
    builder.setIgnoringElementContentWhitespace(true);
    Document doc = builder.build(buildFile);

    Element root = doc.getRootElement();
    return root;
}
Rich Seller
A: 

You could write a script/app in Java, Groovy, Ruby or whatever you know best... the script would parse the xml of the build files and replace "over-ridden" targets with their overrides by actually swapping out the appropriate DOM Nodes. What you will end up with is your composite build.xml as a DOM which can then be serialized out.

You could keep the script on hand in your source control so that you can regenerate as needed.

This may sound a bit extreme but it sounds like most other solutions are out of the scope of what you are looking for.

NOTE: You can run some scripting langs from Ant, so you could still use And as your initiator.

Good luck.

cjstehno
Actually see Rich Sellers post... I didnt see that one... he beat me to the idea.
cjstehno
feel free to upvote me :)
Rich Seller