【问题标题】:Generate Ant build file生成 Ant 构建文件
【发布时间】:2010-11-30 23:38:36
【问题描述】:

我的项目结构如下:

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

其中 root/comp/env/version/build.xml 是:

<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 是:

<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 是:

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

每个构建文件都导入父构建文件,每个子继承覆盖父任务/属性。

我需要的是在不运行任何东西的情况下获取生成的构建 XML

例如,如果我在 root/comp/env/version/ 上运行“ant”(或类似的东西),我希望得到以下输出:

<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>

是否有一个 Ant 插件可以做到这一点?与马文?如果没有,我有什么选择?

编辑: 我需要类似“mvn help:effective-pom”之类的东西供 Ant 使用。

【问题讨论】:

  • 您到底想完成什么? Comp = Component & Env = Environment 吗?您是否正在尝试构建各种组件的特定于环境的构建?
  • 是的,类似的。你有什么想法?
  • 类似 "mvn -Doutput= help:effective-pom" 但对于 Ant。嗯嗯,我不记得看到过类似的东西。
  • 我只是想知道我是否可以用 maven 做到这一点,我知道“帮助:有效的 pom”目标。不过,这是一种选择?
  • 如果这是您的目标(特定于环境的组件构建),您可能希望远离 Maven。 Maven 的设计目标是保护您免受环境依赖性的影响,而您必须解决这个问题。

标签: java xml ant build


【解决方案1】:

根据import task 的描述,它的工作原理非常类似于一个实体,包含两个附加功能:

  • 目标覆盖
  • 特殊属性

出于查看“有效构建”的目的,我认为不需要特殊属性处理(尽管可以通过迭代插入的目标来添加)。所以实现这个的处理就变成了。

  1. 将 build.xml 解析为 DOM
    • 对于找到的每个顶级包含标记(仅允许顶级),找到引用的源文件。
    • 解析引用的build.xml
    • 插入引用的 build.xml 中不与当前文件中的内容冲突的任何内容。
    • 对引用的 build.xml 文件重复第 2 步,直到不再找到为止
    • 输出生成的 DOM

您可以定义自定义 Ant 任务,以便可以在要在构建中运行的任务中定义此处理。有关详细信息,请参阅此tutorial

这是一个通过导入递归并插入引用文件中的 DOM 元素的基本实现。当我把它放在一起时,几乎可以肯定它有一些错误,但它应该主要做你所追求的:

/**
 * 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;
}

【讨论】:

    【解决方案2】:

    Eclipse 理解 Ant 文件。您可以在内部 build.xml 中看到哪些任务可见。它不会完全符合您要求的格式,但可能会满足您的需求。

    【讨论】:

    • 有趣,但我需要自动生成该构建文件,例如从命令行。
    【解决方案3】:

    我已经编写 Ant 构建脚本 7 到 8 年了,但我真的不明白你在这里想要实现什么。也许是我,但我担心即使你的构建工作正常(我相信你可以),几乎没有其他人会理解/维护它。

    为什么不让事情变得非常简单并拥有兄弟项目?

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

    单个 build.xml 文件可以导入在其他地方定义的任务(为此使用 Macrodef),而您的顶级 build.xml 会按顺序调用单个文件?

    一旦您的基本构建运行起来,您就可以使用 Ivy 或 Maven 来获得更多有趣的东西。

    但是如果你真的想要生成构建文件,你可以试试 Groovy 和它的模板引擎。

    【讨论】:

    • 谢谢,我正在寻找的是生成“版本”的结果 build.xml(在这种情况下)。类似于 Ant 的“mvn help:effective-pom”。
    【解决方案4】:

    我会建议构建您的构建文件,以便它们使用 ant 真正擅长的依赖关系树。如果您遵循@Vladimir 的建议并像这样构建您的构建文件,那么您可以在“root”中拥有一个构建文件并让它递归地执行您的构建。例如:

    <!-- 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>
    

    【讨论】:

    • 好建议,但不是我想要的。我需要生成一个 XML 构建文件,该文件从父级继承所有 build.xml 文件。我不想构建项目,我想为您的示例获取生成的构建文件。
    • 查看 subant 任务的文档/示例 - ant.apache.org/manual/CoreTasks/subant.html - 如果您在宏定义中包含的内容看起来像“**/version/build.xml”并且您设置了“只是所以'你应该能够使用 xslt 任务和 common2master.xsl 来写出一个主构建文件。
    【解决方案5】:

    听起来像gradle 可以帮助你。 Gradle 可以import your ant build.xml file。 然后您可以启动dry run 来获取执行的目标列表。

    【讨论】:

      【解决方案6】:

      您可以使用 Java、Groovy、Ruby 或任何您最了解的语言编写脚本/应用程序...该脚本将解析构建文件的 xml,并通过实际交换适当的 DOM 节点。你最终会得到你的复合 build.xml 作为一个 DOM,然后可以序列化出来。

      您可以将脚本保存在源代码管理中,以便根据需要重新生成。

      这听起来可能有点极端,但听起来大多数其他解决方案都超出了您正在寻找的范围。

      注意:您可以从 Ant 运行一些脚本语言,因此您仍然可以使用 And 作为您的启动器。

      祝你好运。

      【讨论】:

      • 居然看到Rich Sellers的帖子……我没看到那个……他比我早了。
      猜你喜欢
      • 2011-12-10
      • 2013-07-15
      • 1970-01-01
      • 2012-04-16
      • 1970-01-01
      • 2011-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多