开发者

What are some good ways to distribute a common ant file to be included in builds?

开发者 https://www.devze.com 2023-02-13 07:55 出处:网络
I work in a group where we produce many small apps, and use ANT for our build processes. We would like to have some of our commonly used direct开发者_如何学运维ives housed in a common way. Currently,

I work in a group where we produce many small apps, and use ANT for our build processes.

We would like to have some of our commonly used direct开发者_如何学运维ives housed in a common way. Currently, we require a mapped drive to a common location, and use

<import file="${env.MAPPED_DRIVE}/common_directive.xml">

There must be a better way to distribute a common ant file to include in many projects without having to map a drive. Do you have any other suggestions?

Import is a "top level" directive, which means it won't work inside of a target. Therefore, I cannot simply create a target that downloads the file, and then import it.


If you are using ANT 1.8+, you could specify a URL and host the common build fragment on a website.

Since Ant 1.8.0 the task can also import resources from URLs or classpath resources (which are URLs, really). If you need to know whether the current build file's source has been a file or an URL you can consult the property ant.file.type.projectname (using the same example as above ant.file.type.builddocs) which either have the value "file" or "url".


I've worked out a solution that creates a jar file containing our reusable build scripts in a directory, say com/example/ant/sharedbuild, which can be imported in Ant 1.8:

<project>
    <import>
        <javaresource name="com/example/ant/sharedbuild/java.xml">
            <classpath location="../../../../target/ant-shared-build.jar" />
        </javaresource>
    </import>
</project>

In my case this defines all of the "public" targets for the project to do a java-based build.

The syntax is a little verbose, especially as I add more and more include files (say, to add the ability to create an OSGi jar). By adding an antlib.xml that contains a combination of a macrodef and scriptdef to the jar file (in the same directory as the shared build scripts), the build file can now look like this (and now also creating an OSGi jar bundle):

<project xmlns:build="antlib:com.example.ant.sharedbuild">
    <taskdef uri="antlib:com.example.ant.sharedbuild"
             classpath="../../../../target/ant-shared-build.jar" />
    <build:build using="java, jar, bundle" />
</project>

Unfortunately, I can't share the code in the macrodef or scriptdef, but really it isn't hard: a little javascript to parse the using attribute and loop over each, derive a file name from it, and import.

I reference the jar file in a fixed location (relative to my project) on my hard drive. I think we can do better. Ideally, I'd like to fetch a (versioned!) jar file from a central location. Since we're already using Ivy (with an HTTP repository) we can publish the jar file there (again, with a version) and fetch it directly from there:

<project xmlns:build="antlib:com.example.ant.sharedbuild">
    <property name="ant.shared.build.jar.file"
              location="${user.home}/ant/ant-shared-build-1.5.3.jar" />
    <get src="http://repo.example.com/.../ant-shared-build-1.5.3.jar"
         dest="${ant.shared.build.jar.file}"
         skipexisting="true" />
    <taskdef uri="antlib:com.example.ant.sharedbuild"
             classpath="${ant.shared.build.jar.file}" />
    <build:build using="java, jar, bundle" />
</project>

There are some problems with this:

  1. It's getting verbose again.
  2. The verbosity is repeated for every build.xml.
  3. There's a lot of repeated boilerplate, especially the version number.

To mitigate these problems, in each directory containing a build.xml I also have a bootstrap.xml (the name doesn't really matter). Each build.xml then includes this file:

<project xmlns:build="antlib:com.example.ant.sharedbuild">
    <include file="bootstrap.xml" />
    <build:build using="java, jar, bundle" />
</project>

Each bootstrap.xml, at a minimum, includes it's parent's bootstrap.xml:

<project>
    <include file="../bootstrap.xml" />
</project>

The top-level bootstrap.xml (the root), then does the work of getting the jar file and creating the custom tasks, as above:

<project>
    <property name="ant.shared.build.version"
              value="1.5.3" />
    <property name="ant.shared.build.jar.filename"
              value="ant-shared-build-${ant.shared.build.version}.jar" />
    <property name="ant.shared.build.jar.file"
              location="${user.home}/ant/${ant.shared.build.jar.filename}" />
    <get src="http://repo.example.com/.../${ant.shared.build.jar.filename}"
         dest="${ant.shared.build.jar.file}"
         skipexisting="true" />
    <taskdef uri="antlib:com.example.ant.sharedbuild"
             classpath="${ant.shared.build.jar.file}" />
</project>

Though not directly related to the question, I'm actually reworking the macrodef and scriptdef into a custom ant task, because I want to be able to support a syntax that looks like this:

<project xmlns:build="antlib:com.example.ant.sharedbuild">
    <include file="bootstrap.xml" />
    <build:build>
        <using>
            <java />
            <bundle>
                <manifest>
                    Import-Package: *,org.joda.time;version="[1.6.0,1.6.0]"
                    Bundle-Activator: com.example.time.impl.Activator
                </manifest>
            </bundle>
        </using>
    </build:build>
</project>

I should point out that just creating a redistributable build doesn't mean it's going to be useful. You still need to put in the time and effort to create a cohesive, modular, consistent implementation in line with a design of similar characteristics. This is more important as you need to share scripts across projects, across teams, across organizational boundaries, etc.

In conclusion, by creating a jar file, with a version number, that can be distributed independent of a specific file location or an SCM tool we can get real shared but reproducible builds.


Dominic Mitchell's comment about the URL reference being a bad idea if you want repeatable builds got me thinking...

Another solution to consider, if you are using SVN for version control, is to create an SVN Externals Definition that points to the common_directive.xml.

Then, just use a relative path for your ANT import file reference.

Sometimes it is useful to construct a working copy that is made out of a number of different checkouts. For example, you may want different subdirectories to come from different locations in a repository or perhaps from different repositories altogether. You could certainly set up such a scenario by hand—using svn checkout to create the sort of nested working copy structure you are trying to achieve. But if this layout is important for everyone who uses your repository, every other user will need to perform the same checkout operations that you did.

Fortunately, Subversion provides support for externals definitions. An externals definition is a mapping of a local directory to the URL—and ideally a particular revision—of a versioned directory. In Subversion, you declare externals definitions in groups using the svn:externals property. You can create or modify this property using svn propset or svn propedit (see the section called “Manipulating Properties”). It can be set on any versioned directory, and its value describes both the external repository location and the client-side directory to which that location should be checked out.

The convenience of the svn:externals property is that once it is set on a versioned directory, everyone who checks out a working copy with that directory also gets the benefit of the externals definition. In other words, once one person has made the effort to define the nested working copy structure, no one else has to bother—Subversion will, after checking out the original working copy, automatically also check out the external working copies.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号