fileset();
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/ant/tasks/FileSet.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.ant.tasks;
2 |
3 | import org.jenkinsci.ant.AntElementProxy;
4 |
5 | import java.io.File;
6 |
7 | /**
8 | * Ant file set
9 | *
10 | * @author Kohsuke Kawaguchi
11 | */
12 | public interface FileSet extends AntElementProxy {
13 | /**
14 | * the root of the directory tree of this FileSet.
15 | */
16 | File dir();
17 |
18 | /**
19 | * shortcut for specifying a single-file fileset
20 | */
21 | File file();
22 | }
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ant Spy
2 |
3 | This library provides a convenient wrapper around Ant build event notification so that programs can inject themselves into Ant invocation and get notified of what targets/tasks are running with what parameters.
4 |
5 | Users would invoke Ant with the `-lib path/to/ant-spy.jar -listener AntSpyListener` command line option. Note that `ANT_ARGS` environment variable is a convenient way to insert these arguments without polluting the command line.
6 |
7 | Your program implements `AntListener` and put a META-INF/services entry for your class. This class also needs to be passed to Ant invocation by the `-lib` option. See a sample code in `src/test/java` for more details.
8 |
--------------------------------------------------------------------------------
/src/main/java/AntSpyListener.java:
--------------------------------------------------------------------------------
1 | import org.jenkinsci.ant.AntListener;
2 | import org.jenkinsci.ant.BuildListenerAdapter;
3 | import org.jenkinsci.ant.ServiceLoader;
4 |
5 | /**
6 | * {@link BuildListenerAdapter} that picks up {@link AntListener} from META-INF/services.
7 | *
8 | *
9 | * Because this class is meant to be used in Ant command line option as "-listener AntSpyListener",
10 | * I'm putting this to the root package.
11 | *
12 | * @author Kohsuke Kawaguchi
13 | */
14 | public class AntSpyListener extends BuildListenerAdapter {
15 | public AntSpyListener() {
16 | for (AntListener l : ServiceLoader.load(getClass().getClassLoader(),AntListener.class))
17 | addListener(l);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/test/SampleListenerImpl.java:
--------------------------------------------------------------------------------
1 | package test;
2 |
3 | import org.jenkinsci.ant.AntEvent;
4 | import org.jenkinsci.ant.AntListener;
5 | import org.jenkinsci.ant.AntTask;
6 | import org.jenkinsci.ant.tasks.JavacTask;
7 | import org.kohsuke.MetaInfServices;
8 |
9 | import javax.annotation.Nonnull;
10 |
11 | /**
12 | * @author Kohsuke Kawaguchi
13 | */
14 | @MetaInfServices
15 | public class SampleListenerImpl extends AntListener {
16 | @Override
17 | public void taskFinished(@Nonnull AntEvent event) {
18 | AntTask t = event.getTask();
19 | if (t.getTaskName().equals("javac")) {
20 | System.out.println("srcdir="+t.getStructure(JavacTask.class).srcdir());
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/ant/AntElementProxy.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.ant;
2 |
3 | import org.jenkinsci.ant.tasks.JavacTask;
4 |
5 | import java.lang.reflect.Proxy;
6 |
7 | /**
8 | * Base type for the type-safe proxy interface to represent Ant tasks.
9 | *
10 | *
11 | * First, for elements that appear in Ant build script, define interfaces that extends from this marker interface.
12 | * We call them "type-safe proxy interfaces". See {@link JavacTask} for example.
13 | *
14 | *
15 | * A type-safe proxy interface is {@linkplain Proxy implemented at runtime dynamically}. Methods should be
16 | * defined for attributes and child elements. Return types must match the expected datatype of the attributes
17 | * and elements.
18 | *
19 | * @author Kohsuke Kawaguchi
20 | * @see AntElement
21 | */
22 | public interface AntElementProxy {
23 | /**
24 | * Retrieves the underlying {@link AntElement} that this proxy represents.
25 | *
26 | * @see AntElement#as(Class)
27 | */
28 | AntElement unwrap();
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/ant/AntEvent.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.ant;
2 |
3 | import org.apache.tools.ant.BuildEvent;
4 | import org.apache.tools.ant.Project;
5 | import org.apache.tools.ant.Target;
6 |
7 | /**
8 | * Wraps {@link BuildEvent} and provides convenience methods.
9 | *
10 | * @author Kohsuke Kawaguchi
11 | */
12 | public class AntEvent {
13 | private final BuildEvent event;
14 | private AntTask task;
15 |
16 | public AntEvent(BuildEvent event) {
17 | this.event = event;
18 | }
19 |
20 | public AntTask getTask() {
21 | if (task==null)
22 | task = new AntTask(this,event.getTask());
23 | return task;
24 | }
25 |
26 | public Project getProject() {
27 | return event.getProject();
28 | }
29 |
30 | public Target getTarget() {
31 | return event.getTarget();
32 | }
33 |
34 | public Throwable getException() {
35 | return event.getException();
36 | }
37 |
38 | public int getPriority() {
39 | return event.getPriority();
40 | }
41 |
42 | public String getMessage() {
43 | return event.getMessage();
44 | }
45 |
46 | public String getProperty(String name) {
47 | return getProject().getProperty(name);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/ant/AntTask.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.ant;
2 |
3 | import org.apache.tools.ant.Location;
4 | import org.apache.tools.ant.Task;
5 |
6 | /**
7 | * Wraps Ant {@link Task} and provides convenience methods.
8 | *
9 | * @author Kohsuke Kawaguchi
10 | */
11 | public class AntTask {
12 | /**
13 | * This is what we are wrapping.
14 | */
15 | private final Task core;
16 |
17 | /**
18 | * A life of {@link AntTask} is scoped to one event callback.
19 | */
20 | private final AntEvent event;
21 |
22 | public AntTask(AntEvent event, Task core) {
23 | this.event = event;
24 | this.core = core;
25 | }
26 |
27 | /**
28 | * Introspect structure of Ant task invocation.
29 | */
30 | public AntElement getStructure() {
31 | return new AntElement(event,core.getRuntimeConfigurableWrapper());
32 | }
33 |
34 | public T getStructure(Class type) {
35 | return getStructure().as(type);
36 | }
37 |
38 | public String getTaskName() {
39 | return core.getTaskName();
40 | }
41 |
42 | public Location getLocation() {
43 | return core.getLocation();
44 | }
45 |
46 | public String getDescription() {
47 | return core.getDescription();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/ant/BuildListenerAdapter.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.ant;
2 |
3 | import org.apache.tools.ant.BuildEvent;
4 | import org.apache.tools.ant.BuildListener;
5 |
6 | import java.util.List;
7 | import java.util.concurrent.CopyOnWriteArrayList;
8 |
9 | /**
10 | * Receives build events as {@link BuildListener}
11 | * and forwards them to {@link AntListener}.
12 | *
13 | * @author Kohsuke Kawaguchi
14 | */
15 | public class BuildListenerAdapter implements BuildListener {
16 | private final List listeners = new CopyOnWriteArrayList();
17 |
18 | public void addListener(AntListener l) {
19 | listeners.add(l);
20 | }
21 |
22 | public void removeListener(AntListener l) {
23 | listeners.remove(l);
24 | }
25 |
26 | public void buildStarted(BuildEvent event) {
27 | AntEvent ev = new AntEvent(event);
28 | for (AntListener l : listeners)
29 | l.buildStarted(ev);
30 | }
31 |
32 | public void buildFinished(BuildEvent event) {
33 | AntEvent ev = new AntEvent(event);
34 | for (AntListener l : listeners)
35 | l.buildFinished(ev);
36 | }
37 |
38 | public void targetStarted(BuildEvent event) {
39 | AntEvent ev = new AntEvent(event);
40 | for (AntListener l : listeners)
41 | l.targetStarted(ev);
42 | }
43 |
44 | public void targetFinished(BuildEvent event) {
45 | AntEvent ev = new AntEvent(event);
46 | for (AntListener l : listeners)
47 | l.targetFinished(ev);
48 | }
49 |
50 | public void taskStarted(BuildEvent event) {
51 | AntEvent ev = new AntEvent(event);
52 | for (AntListener l : listeners)
53 | l.taskStarted(ev);
54 | }
55 |
56 | public void taskFinished(BuildEvent event) {
57 | AntEvent ev = new AntEvent(event);
58 | for (AntListener l : listeners)
59 | l.taskFinished(ev);
60 | }
61 |
62 | public void messageLogged(BuildEvent event) {
63 | AntEvent ev = new AntEvent(event);
64 | for (AntListener l : listeners)
65 | l.messageLogged(ev);
66 | }
67 | }
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/ant/AntListener.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.ant;
2 |
3 | import javax.annotation.Nonnull;
4 |
5 | /**
6 | * Gets notified of the build in progress as {@link AntEvent}s.
7 | *
8 | * @author Kohsuke Kawaguchi
9 | */
10 | public abstract class AntListener {
11 | /**
12 | * Signals that a build has started. This event
13 | * is fired before any targets have started.
14 | *
15 | * @param event An event with any relevant extra information.
16 | */
17 | public void buildStarted(@Nonnull AntEvent event) {}
18 |
19 | /**
20 | * Signals that the last target has finished. This event
21 | * will still be fired if an error occurred during the build.
22 | *
23 | * @param event An event with any relevant extra information.
24 | *
25 | * @see AntEvent#getException()
26 | */
27 | public void buildFinished(@Nonnull AntEvent event) {}
28 |
29 | /**
30 | * Signals that a target is starting.
31 | *
32 | * @param event An event with any relevant extra information.
33 | *
34 | * @see AntEvent#getTarget()
35 | */
36 | public void targetStarted(@Nonnull AntEvent event) {}
37 |
38 | /**
39 | * Signals that a target has finished. This event will
40 | * still be fired if an error occurred during the build.
41 | *
42 | * @param event An event with any relevant extra information.
43 | *
44 | * @see AntEvent#getException()
45 | */
46 | public void targetFinished(@Nonnull AntEvent event) {}
47 |
48 | /**
49 | * Signals that a task is starting.
50 | *
51 | * @param event An event with any relevant extra information.
52 | *
53 | * @see AntEvent#getTask()
54 | */
55 | public void taskStarted(@Nonnull AntEvent event) {}
56 |
57 | /**
58 | * Signals that a task has finished. This event will still
59 | * be fired if an error occurred during the build.
60 | *
61 | * @param event An event with any relevant extra information.
62 | *
63 | * @see AntEvent#getException()
64 | */
65 | public void taskFinished(@Nonnull AntEvent event) {}
66 |
67 | /**
68 | * Signals a message logging event.
69 | *
70 | * @param event An event with any relevant extra information.
71 | *
72 | * @see AntEvent#getMessage()
73 | * @see AntEvent#getException()
74 | * @see AntEvent#getPriority()
75 | */
76 | public void messageLogged(@Nonnull AntEvent event) {}
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/ant/ServiceLoader.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.ant;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStreamReader;
6 | import java.net.URL;
7 | import java.util.ArrayList;
8 | import java.util.Enumeration;
9 | import java.util.List;
10 | import java.util.logging.Level;
11 | import java.util.logging.Logger;
12 |
13 | import static java.util.logging.Level.WARNING;
14 |
15 | /**
16 | * @author Kohsuke Kawaguchi
17 | */
18 | public class ServiceLoader {
19 | public static List load(ClassLoader cl, Class type) {
20 | List impls = new ArrayList();
21 |
22 | try {
23 | Enumeration en = cl.getResources("META-INF/services/" + type.getName());
24 | while (en.hasMoreElements()) {
25 | URL url = en.nextElement();
26 | BufferedReader r = null;
27 | try {
28 | r = new BufferedReader(new InputStreamReader(url.openStream(),"UTF-8"));
29 | String line;
30 | while ((line=r.readLine())!=null) {
31 | line = line.trim();
32 | if (line.startsWith("#") || line.length()==0) continue;
33 |
34 | try {
35 | Object impl = cl.loadClass(line).newInstance();
36 | if (type.isInstance(impl))
37 | impls.add(type.cast(impl));
38 | } catch (InstantiationException e) {
39 | LOGGER.log(WARNING, "Failed to instantiate "+line,e);
40 | } catch (IllegalAccessException e) {
41 | LOGGER.log(WARNING, "Failed to instantiate " + line, e);
42 | } catch (ClassNotFoundException e) {
43 | LOGGER.log(WARNING, "Failed to load " + line, e);
44 | }
45 | }
46 | } catch (IOException e) {
47 | LOGGER.log(WARNING, "Failed to load "+url,e);
48 | } finally {
49 | try {
50 | if (r!=null)
51 | r.close();
52 | } catch (IOException _) {
53 | }
54 | }
55 | }
56 | } catch (IOException e) {
57 | LOGGER.log(WARNING, "Failed to list up /META-INF/services/"+type.getName(),e);
58 | }
59 |
60 | return impls;
61 | }
62 |
63 | private static final Logger LOGGER = Logger.getLogger(ServiceLoader.class.getName());
64 | }
65 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | org.jenkins-ci
5 | jenkins
6 | 1.24
7 |
8 |
9 | ant-spy
10 | 1.1-SNAPSHOT
11 |
12 | ant-spy
13 | http://jenkins-ci.org/
14 |
15 |
16 | scm:git:git://github.com/jenkinsci/${project.artifactId}.git
17 | scm:git:ssh://git@github.com/jenkinsci/${project.artifactId}.git
18 | https://github.com/jenkinsci/${project.artifactId}
19 |
20 |
21 |
22 |
23 |
24 | maven-assembly-plugin
25 | 2.3
26 |
27 |
28 | package
29 |
30 | single
31 |
32 |
33 |
34 | jar-with-dependencies
35 |
36 |
37 |
38 |
39 |
40 |
41 | maven-jar-plugin
42 |
43 |
44 | package
45 |
46 | test-jar
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | com.google.code.findbugs
57 | jsr305
58 | 1.3.9
59 | provided
60 | true
61 |
62 |
63 | org.jvnet
64 | tiger-types
65 | 1.4
66 |
67 |
68 | org.apache.ant
69 | ant
70 | 1.7.0
71 | provided
72 |
73 |
74 |
75 | org.kohsuke.metainf-services
76 | metainf-services
77 | 1.4
78 | test
79 |
80 |
81 | junit
82 | junit
83 | 3.8.1
84 | test
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/ant/AntElement.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.ant;
2 |
3 | import org.apache.tools.ant.RuntimeConfigurable;
4 | import org.jvnet.tiger_types.Types;
5 |
6 | import java.io.File;
7 | import java.lang.reflect.InvocationHandler;
8 | import java.lang.reflect.Method;
9 | import java.lang.reflect.Proxy;
10 | import java.util.ArrayList;
11 | import java.util.Enumeration;
12 | import java.util.List;
13 |
14 | /**
15 | * Represents an XML element that constitute an Ant task invocation.
16 | *
17 | * @author Kohsuke Kawaguchi
18 | * @see AntElementProxy
19 | */
20 | public class AntElement {
21 | /**
22 | * This is what we are wrapping.
23 | */
24 | private final RuntimeConfigurable core;
25 |
26 | /**
27 | * A life of {@link AntTask} is scoped to one event callback.
28 | */
29 | private final AntEvent event;
30 |
31 | public AntElement(AntEvent event, RuntimeConfigurable core) {
32 | this.core = core;
33 | this.event = event;
34 | }
35 |
36 | /**
37 | * Gets the element name.
38 | */
39 | public String getElementName() {
40 | return core.getElementTag();
41 | }
42 |
43 | public String getStringAttribute(String name) {
44 | return eval((String) core.getAttributeMap().get(name));
45 | }
46 |
47 | public File getFileAttribute(String name) {
48 | String v = getStringAttribute(name);
49 | if (v == null) return null;
50 |
51 | File f = new File(v);
52 | if (f.isAbsolute()) return f;
53 |
54 | return new File(event.getProperty("basedir"), v);
55 | }
56 |
57 | public AntElement getChildElement(String name) {
58 | Enumeration en = core.getChildren();
59 | while (en.hasMoreElements()) {
60 | RuntimeConfigurable child = (RuntimeConfigurable)en.nextElement();
61 | if (child.getElementTag().equals(name))
62 | return new AntElement(event,child);
63 | }
64 | return null;
65 | }
66 |
67 | public List getChildElements(String name) {
68 | List r = new ArrayList();
69 | Enumeration en = core.getChildren();
70 | while (en.hasMoreElements()) {
71 | RuntimeConfigurable child = (RuntimeConfigurable)en.nextElement();
72 | if (child.getElementTag().equals(name))
73 | r.add(new AntElement(event,child));
74 | }
75 | return r;
76 | }
77 |
78 | /**
79 | * Gets all the child elements of the same name, as a type-safe proxy.
80 | */
81 | public List getChildElements(String name, Class type) {
82 | List r = new ArrayList();
83 | for (AntElement t : getChildElements(name))
84 | r.add(t.as(type));
85 | return r;
86 | }
87 |
88 |
89 | /**
90 | * Evaluate and expands variable references.
91 | */
92 | private String eval(String val) {
93 | if (val==null) return null;
94 | return event.getProject().replaceProperties(val);
95 | }
96 |
97 | /**
98 | * Wraps this {@link AntElement} into a type-safe proxy.
99 | */
100 | public T as(Class type) {
101 | return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new InvocationHandlerImpl()));
102 | }
103 |
104 | private class InvocationHandlerImpl implements InvocationHandler, AntElementProxy {
105 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
106 | if (method.getDeclaringClass()==Object.class) {// handle equals/hashCode methods on AntElement
107 | return method.invoke(AntElement.this,args);
108 | }
109 |
110 | if (method.getDeclaringClass()==AntElement.class) {// handle AntElement methods here
111 | return method.invoke(this,args);
112 | }
113 |
114 | String name = method.getName();
115 | Class type = method.getReturnType();
116 |
117 | if (type==String.class) return getStringAttribute(name);
118 | if (type==File.class) return getFileAttribute(name);
119 | if (type==AntElement.class)
120 | return getChildElement(name);
121 | if (AntElementProxy.class.isAssignableFrom(type)) {
122 | AntElement v = getChildElement(name);
123 | return v!=null ? v.as(type) : null;
124 | }
125 | if (type==List.class) {
126 | Class component = Types.erasure(Types.getTypeArgument(method.getGenericReturnType(),0));
127 | if (component==AntElement.class)
128 | return getChildElements(name);
129 | if (AntElementProxy.class.isAssignableFrom(component))
130 | return getChildElements(name,component);
131 | }
132 |
133 | throw new IllegalStateException("Unrecognized binding: "+method);
134 | }
135 |
136 | public AntElement unwrap() {
137 | return AntElement.this;
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------