I need some help on ant. How can I make ant behave similarly to make -j <n>?
Note that I am not building java, but invoking an external compiler for a proprietary language.
While there is the task <parallel> (and an analogous attribute for <for>), they do not seem to support dependencies, or rather I haven't found a way to apply dependencies on these.
However, I do have the dependencies available in a properties file, formatted like this:
fileA=fileB fileC
fileB=fileC
fileA/B/C are the basenames of my target files.
Currently there are some 850 files to build. Using a <for> I walk over this list (properly sorted by ironing out the dependencies) to invoke the compiler.
I was already advised to use <sequential>, however this would mean to translate the dependencies into blocks of <sequential> which is kind of bad.
Any ideas on how to make the <for ... parallel="yes"> loop respect dependencies, or any other idea on how to solve this?
I don't think there's anything in native Ant that can do this, but this example is an absolutely ideal use case for the "dataflow concurrency" model supported by Groovy's GPars, so you can do something with a script task:
<project name="gpars-test">
<path id="groovy.path">
<pathelement location="groovy-all-1.8.8.jar" />
<pathelement location="gpars-0.12.jar" />
</path>
<!-- Target to build one file - expects a property "filetobuild"
containing the name of the file to build -->
<target name="buildOneFile">
<echo>Imagine I just built ${filetobuild}...</echo>
</target>
<target name="main">
<script language="groovy" classpathref="groovy.path"><![CDATA[
import static groovyx.gpars.dataflow.Dataflow.task
import groovyx.gpars.dataflow.Dataflows
// load dependencies
def deps = new Properties()
new File(basedir, 'dependencies.properties').withInputStream {
deps.load(it)
}
def df = new Dataflows()
// spawn one "task" per file to be compiled
deps.each { file, dependencies ->
task {
if(dependencies) {
// wait for dependencies - reading df.something will suspend
// this task until another task has written the same variable
dependencies.split(/ /).each { dep ->
def dummy = df."${dep}"
}
}
// now we know all our dependencies are done, call out to build
// this one.
def compiler = project.createTask('antcall')
compiler.target = "buildOneFile"
def prop = compiler.createParam()
prop.name = "filetobuild"
prop.value = file
try {
compiler.perform()
} catch(Exception e) {
// do something
} finally {
// we're done - this will release any other tasks that are blocked
// depending on us
df."${file}" = "done"
}
}
}
// now wait for all tasks to complete
deps.each { file, dependencies ->
def dummy = df."${file}"
}
println "finished"
]]></script>
</target>
</project>
Note that this does assume that all the files you want to build are listed in the dependencies file, including those with no dependencies, i.e.
fileA=fileB fileC
fileB=fileC
fileC=
If the no-deps file names aren't listed in the properties then you'll have to do some more fiddling to put them in there - immediately before the def df = new Dataflows() you'd need to add something along the lines of
deps.values().collect().each { depString ->
depString.split(/ /).each {
// if we find a fileX that something depends on but which does not
// itself appear in the deps map, assume it has no dependencies
if(!deps.containsKey(it)) {
deps.setProperty(it, "")
}
}
}
Related
I set up a gradle task to generate java classes from XSD files:
ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask', classpath: configurations.jaxb.asPath)
ant.jaxbTargetDir = jaxbTargetDir
ant.xjc(destdir: '${jaxbTargetDir}', package: 'com.example') {
schema(dir:'/home/bruckwald/proj/schema/xsd', includes: '*.xsd')
}
How can I pass the argument -episode my.episode to the ant task so that the episode file will be generated?
I'm using the following dependencies:
jaxb(
'com.sun.xml.bind:jaxb-core:2.2.11',
'com.sun.xml.bind:jaxb-impl:2.2.11',
'com.sun.xml.bind:jaxb-xjc:2.2.11',
'javax.xml.bind:jaxb-api:2.2.12',
'org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.9.4'
)
Here's an example from a build of mine that passes other arguments to the XJC task:
ant.xjc(destdir: genDir, package: pkgName, extension: true) {
classpath { pathelement(path: configurations.xjcrun.asPath) }
schema(dir: "src/main/resources/schema", includes: schemaName)
arg(value: "-Xxew")
arg(value: "-Xfluent-api")
}
I would imagine your "-episode" arg would work just like that.
Note that the "arg" function takes a SINGLE argument. If you to specify a command-line option that takes a value besides the presence of the option itself, then you'll need TWO arg calls, one for the option string, and one for the value itself, so it might be like this:
arg(value: "-episode")
arg(value: "my.episode")
I'm using Gradle 2.1 and have an ANT task defined something like this:
task myTask {
doFirst {
ant.taskdef(name: 'mytask',
classname: 'com.blah.Blah',
classpath: configurations.gen.asPath
)
ant.mytask(foo: 'bar')
}
}
There is a property I need to pass to the com.blah.Blah as a JVM argument (because, instead of doing something sane like passing parameter values in as parameters, the creators of this ANT task have decided that system properties are a reasonable way of conveying information). I've tried a number of things, including:
Setting the systemProperty on all tasks with JavaForkOptions:
tasks.withType(JavaForkOptions) {
systemProperty 'myproperty', 'blah'
}
Passing -Dmyproperty=blah when I invoke gradle.
Various things involving ant.systemPropery, ant.options.forkOptions, ant.forkOptions, etc. (I can't actually find reliable documentation on this anywhere)
I'm at a loss here. It feels like I should be able to say something like:
task myTask {
doFirst {
ant.taskdef(name: 'mytask',
classname: 'com.blah.Blah',
classpath: configurations.gen.asPath
)
ant.systemProperty 'myProperty', 'blah'
ant.mytask(foo: 'bar')
}
}
...but that obviously doesn't work.
In Gradle you can use Groovy so there's nothing preventing you from setting the system property programmatically as shown below:
task myTask {
doFirst {
System.setProperty('myProperty', 'blah')
// Use AntBuilder
System.clearProperty('myProperty')
}
}
Keep in mind that Gradle's AntBuilder executes Ant logic in the same process used for Gradle. Therefore, setting a system property will be available to other tasks in your build. This might have side effects when two tasks use the same system property (depending on the execution order) or if you run your build in parallel.
Instead you might want to change your Ant task to use Ant properties instead to drive your logic (if that's even an option). Ant properties can be set from Gradle as such:
task myTask {
doFirst {
ant.properties.myProperty = 'blah'
// Use AntBuilder
}
}
I found this question:
How can I get a list of build targets in Ant?
What I'd like to know: Is there a way to get a list of targets, together with their depends-on values? We have a large build.xml file and the way it's currently written the presence or absence of a description doesn't really tell me much as to whether a target is a main target or an "other" target.
Running ant 1.8.1, this is an initial bit of due diligence as I prepare to upgrade to Gradle so I need to figure out which targets are truly the "high level" targets and which ones are "supporting" targets.
Note I work in a locked-down environment so downloading third-party software or ant extensions is not an option.
Additional Note If this level of detail is not possible in ant, that is a valid answer as well
In Ant 1.8.2 and above, use the -d flag to print debug info:
ant -p -d <your main build file>
and you'll get details like this:
javadoc
depends on: resolve
javadoc.distribute
latest-ivy
package
depends on: -maybe-package-by-bom, -maybe-package-by-spec, -maybe-package-for-dc
The -d flag will also print the "other" targets (those without descriptions) that aren't printed by ant -p, along with their dependencies.
If you want a recursive tree listing, you can use this XQuery script with Saxon:
(:~
: XQuery to display the dependencies of an Ant target.
:
: There are two modes of operation:
: 1) Display all targets and immediate dependencies, specified by $project-file
: 2) Show a tree of a single targets dependencies, this happens when $target-name is set as well.
:
: External parameters:
: $project-file The initial Ant file to start parsing from (imports will be expanded)
: $target-name If specified we examine only a single target and produce a tree of all dependencies (recursively)
: $show-file Whether the file path of the dependency should be shown
:
: Example Usage: java -cp Saxon-HE-9.7.0-18.jar net.sf.saxon.Query -q:ant-show-deps.xqy \!indent=yes project-file=file:/Users/are/exist-git/build.xml target-name=installer show-file=true
:
: If you don't want to specify the $target-name you can pass ?target-name=\(\) to Saxon on the command line.
:
: #author Adam Retter
:)
xquery version "1.0";
declare variable $project-file external;
declare variable $target-name as xs:string? external;
declare variable $show-file as xs:boolean external;
declare function local:expand-import-targets($file as xs:string) as element(target)* {
local:expand-import-targets($file, ())
};
declare function local:expand-import-targets($file as xs:string, $visited as xs:string*) as element(target)* {
let $path := local:resolve($file, $visited[1])
return
if(not($visited = $path))then
let $imported-project := doc($path)/project
return
(
for $target in $imported-project/target
return
<target name="{$target/#name}" file="{$path}">
{
for $dependency in tokenize(replace($target/#depends, '\s+', ''), ',')
return
<dependency name="{$dependency}"/>
}
</target>
,
for $import in $imported-project/import
return
local:expand-import-targets($import/#file, ($path, $visited))
)
else()
};
declare function local:resolve($file as xs:string, $prev-file as xs:string?) {
if(not($prev-file))then
$file
else if(starts-with($file, "/") or starts-with($file, "file:/"))then
$file
else
resolve-uri($file, $prev-file)
};
declare function local:target-tree($target-name as xs:string, $targets as element(target)*) as element(target)? {
let $target := $targets[#name eq $target-name]
return
element target {
$target/#name,
if($show-file)then
$target/#file
else(),
for $dependency in $target/dependency
return
local:expand-dependency($dependency/#name, $targets)
}
};
declare function local:expand-dependency($dependency-name as xs:string, $targets as element(target)*) {
for $expanded in $targets[#name eq $dependency-name]
return
element dependency {
$expanded/#name,
if($show-file)then
$expanded/#file
else(),
for $sub-dependency in $expanded/dependency
return
local:expand-dependency($sub-dependency/#name, $targets)
}
};
let $targets := local:expand-import-targets($project-file)
return
if($target-name)then
local:target-tree($target-name, $targets)
else
<targets>
{
for $target in $targets
order by $target/#name
return $target
}
</targets>
We needed to automate testing that all of the Java samples we ship compile properly. We need it to build all files without our listing each one. Listing each one means if someone forgets to add a new one (which will happen someday), explicit calls will miss it. By walking all build.xml files, we always get everything.
Doing this is pretty easy:
Install the samples on a clean VM (that we revert back to the snapshot for each test run).
Create a build.xml file that calls all the build.xml files installed.
Use ant to run the generated build.xml
Step 2 requires a means to generate the build.xml file. Is there any way to tell ant to run all build.xml files under a sub-directory or to create a build.xml that calls all the underlying build.xml files?
It sounds like what you want to do is run the same build process for a number of sub-projects that (hopefully) follow a standard layout pattern.
If that's the case, you can create a single build.xml that knows how to compile those projects, and make a top-level build script which finds all the sub directories, then calls the common build script in each one. Subant was taylor-made for this, and doesn't require a magic C# program to generate scripts in each directory.
We couldn't find anything so we wrote a program that creates a build.xml that calls all build.xml files under a directory. Full solution is at Windward Wrocks (my blog).
The code is (yep, using C# to create a build file for Java):
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace BuildJavaTestScript
{
public class Program
{
/// <summary>
/// Build build.xml for all build.xml files in sub-directories.
/// </summary>
/// <param name="args">Optional: build.xml root_folder</param>
public static void Main(string[] args)
{
string projFile = Path.GetFullPath(args.Length > 0 ? args[0] : "build.xml");
string rootDirectory = Path.GetFullPath(args.Length > 1 ? args[1] : Directory.GetCurrentDirectory());
Console.Out.WriteLine(string.Format("Creating build file {0}", projFile));
Console.Out.WriteLine(string.Format("Root directory {0}", rootDirectory));
XDocument xdoc = new XDocument();
XElement elementProject = new XElement("project");
xdoc.Add(elementProject);
elementProject.Add(new XAttribute("name", "BuildAll"));
elementProject.Add(new XAttribute("default", "compile"));
XElement elementTarget = new XElement("target");
elementProject.Add(elementTarget);
elementTarget.Add(new XAttribute("name", "compile"));
XElement elementEcho = new XElement("echo");
elementTarget.Add(elementEcho);
elementEcho.Add(new XAttribute("message", "Build All: jdk = ${java.home}, version = ${java.version}"));
// add .sln files - recursively
AddBuildXmlFiles(elementTarget, rootDirectory, rootDirectory);
Console.Out.WriteLine("writing build file to disk");
// no BOM
using (var writer = new XmlTextWriter(projFile, new UTF8Encoding(false)))
{
writer.Formatting = Formatting.Indented;
xdoc.Save(writer);
}
Console.Out.WriteLine("all done");
}
private static void AddBuildXmlFiles(XElement elementTarget, string rootDirectory, string folder)
{
// add build.xml files
foreach (string fileOn in Directory.GetFiles(folder, "build.xml"))
{
string filename = Path.GetFileName(fileOn);
string workingFolder;
if (folder.StartsWith(rootDirectory))
{
workingFolder = folder.Substring(rootDirectory.Length).Trim();
if ((workingFolder.Length > 0) && (workingFolder[0] == Path.DirectorySeparatorChar || workingFolder[0] == Path.AltDirectorySeparatorChar))
workingFolder = workingFolder.Substring(1);
}
else
workingFolder = folder;
if (workingFolder.Length == 0)
continue;
XElement elementExec = new XElement("ant");
elementExec.Add(new XAttribute("dir", workingFolder));
elementExec.Add(new XAttribute("antfile", filename));
elementTarget.Add(elementExec);
}
// look in sub-directories
foreach (string subDirectory in Directory.GetDirectories(folder))
AddBuildXmlFiles(elementTarget, rootDirectory, subDirectory);
}
}
}
I have folder test containing:
test
-> groovy
-> MyClass.groovy
-> build.xml
The file MyClass.groovy contains:
class MyClass {
void firstMethod(int i) {
println i
}
String secondMethod(String txt) {
return txt + "added text"
}
static void main(String[] args) {
}
}
In my build.xml file I have (based on http://docs.codehaus.org/display/GROOVY/The+groovy+Ant+Task):
<target name="run-groovy-script-test">
<groovy src="groovy/MyClass.groovy">
<classpath>
<pathelement location="groovy"/>
</classpath>
def aClass = new MyClass()
aClass.secondMethod("asd")
</groovy>
</target>
Running the above gives:
groovy.lang.MissingMethodException: No signature of method: MyClass.secondMethod() is applicable for argument types: (java.lang.String) values: [some-text]
Solution: Remove the src attribute - see below comments.
I know that I can specify a main method in the .groovy file which will automatically be executed using the above. But it could be nice to control which methods should be called directly.
//Declare a property in your ant.xml.
<property name="myproperty" value=""/>
<groovy>
//This will instantiate
def aClass = new MyClass()
//This will store the return value in the ant property
properties["myproperty"] = aClass.secondMethods()
</groovy>
"But it could be nice to control which methods should be called directly."
#u123: I'm no groovy expert and last year I didn't find an obvious solution to address that. So at the time I made a tiny tool out of frustration, to solve my issues with the ant groovy task:
Feniseca.
It's open source and I just updated the doc, but note that I tested Feniseca only against POSIX environments. Hoping this can help anyway!