From 7a5b3ab65a61cb6296ea4174ca183e5534beb860 Mon Sep 17 00:00:00 2001 From: kohsuke Date: Mon, 8 Jan 2007 05:48:23 +0000 Subject: [PATCH] added more Maven related code (all experimental) git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@1707 71c3de6d-444a-0410-be80-ed276b4c234a --- .../main/java/hudson/maven/MavenBuild.java | 4 +- .../java/hudson/maven/MavenBuildProxy.java | 53 ++++++++++++ core/src/main/java/hudson/maven/MavenJob.java | 28 ++++++- ...{MavenListener.java => MavenReporter.java} | 34 ++++++-- .../java/hudson/maven/MavenReporters.java | 19 +++++ core/src/main/java/hudson/maven/MojoInfo.java | 80 +++++++++++++++++++ core/src/main/java/hudson/maven/Name.java | 53 ++++++++++++ .../maven/PluginManagetInterceptor.java | 48 +++++++++++ .../reporters/MavenArtifactArchiver.java | 62 ++++++++++++++ .../MavenArtifactArchiver/config.jelly | 2 + 10 files changed, 372 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/hudson/maven/MavenBuildProxy.java rename core/src/main/java/hudson/maven/{MavenListener.java => MavenReporter.java} (60%) create mode 100644 core/src/main/java/hudson/maven/MavenReporters.java create mode 100644 core/src/main/java/hudson/maven/MojoInfo.java create mode 100644 core/src/main/java/hudson/maven/Name.java create mode 100644 core/src/main/java/hudson/maven/reporters/MavenArtifactArchiver.java create mode 100644 core/src/main/resources/hudson/maven/reporters/MavenArtifactArchiver/config.jelly diff --git a/core/src/main/java/hudson/maven/MavenBuild.java b/core/src/main/java/hudson/maven/MavenBuild.java index 795cd4b436..ffeb067fb9 100644 --- a/core/src/main/java/hudson/maven/MavenBuild.java +++ b/core/src/main/java/hudson/maven/MavenBuild.java @@ -49,6 +49,8 @@ public class MavenBuild extends AbstractBuild { /** * Runs Maven and builds the project. + * + * This code is executed on the remote machine. */ private static final class Builder implements FileCallable { private final BuildListener listener; @@ -125,7 +127,7 @@ public class MavenBuild extends AbstractBuild { //private boolean preBuild(BuildListener listener,Map steps) { // for( BuildStep bs : steps.values() ) - // if(!bs.prebuild(Build.this,listener)) + // if(!bs.preBuild(Build.this,listener)) // return false; // return true; //} diff --git a/core/src/main/java/hudson/maven/MavenBuildProxy.java b/core/src/main/java/hudson/maven/MavenBuildProxy.java new file mode 100644 index 0000000000..282f241aab --- /dev/null +++ b/core/src/main/java/hudson/maven/MavenBuildProxy.java @@ -0,0 +1,53 @@ +package hudson.maven; + +import hudson.FilePath; + +import java.io.Serializable; +import java.io.IOException; + +/** + * Remoting proxy interface for {@link MavenReporter}s to talk to {@link MavenBuild} + * during the build. + * + * @author Kohsuke Kawaguchi + */ +public interface MavenBuildProxy { + /** + * Executes the given {@link BuildCallable} on the master, where one + * has access to {@link MavenBuild} and all the other Hudson objects. + * + *

+ * The parameter, return value, and exception are all transfered by using + * Java serialization. + * + * @return + * the value that {@link BuildCallable} returned. + * @throws T + * if {@link BuildCallable} throws this exception. + * @throws IOException + * if the remoting failed. + * @throws InterruptedException + * if the remote execution is aborted. + */ + V execute( BuildCallable program ) throws T, IOException, InterruptedException; + + /** + * Root directory of the build. + * + * @see MavenBuild#getRootDir() + */ + FilePath getRootDir(); + + /** + * @see MavenBuild#getArtifactsDir() + */ + FilePath getArtifactsDir(); + + public interface BuildCallable extends Serializable { + /** + * Performs computation and returns the result, + * or throws some exception. + */ + V call(MavenBuild build) throws T; + } +} diff --git a/core/src/main/java/hudson/maven/MavenJob.java b/core/src/main/java/hudson/maven/MavenJob.java index 44b5f1bdb6..c5482a76ee 100644 --- a/core/src/main/java/hudson/maven/MavenJob.java +++ b/core/src/main/java/hudson/maven/MavenJob.java @@ -1,9 +1,12 @@ package hudson.maven; import hudson.model.AbstractProject; +import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.Job; import hudson.model.JobDescriptor; +import hudson.model.Descriptor.FormException; +import hudson.util.DescribableList; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -16,11 +19,22 @@ import java.io.IOException; * * @author Kohsuke Kawaguchi */ -public final class MavenJob extends AbstractProject { +public final class MavenJob extends AbstractProject implements DescribableList.Owner { + private DescribableList> reporters = + new DescribableList>(this); + public MavenJob(Hudson parent, String name) { super(parent, name); } + @Override + protected void onLoad(Hudson root, String name) throws IOException { + super.onLoad(root, name); + if(reporters==null) + reporters = new DescribableList>(this); + reporters.setOwner(this); + } + @Override public MavenBuild newBuild() throws IOException { MavenBuild lastBuild = new MavenBuild(this); @@ -33,11 +47,21 @@ public final class MavenJob extends AbstractProject { return new MavenBuild(this,dir); } + /** + * List of active {@link MavenReporter}s configured for this project. + */ + public DescribableList> getReporters() { + return reporters; + } public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { super.doConfigSubmit(req, rsp); - // TODO + try { + reporters.rebuild(req,MavenReporters.LIST,"reporter"); + } catch (FormException e) { + sendError(e,req,rsp); + } save(); } diff --git a/core/src/main/java/hudson/maven/MavenListener.java b/core/src/main/java/hudson/maven/MavenReporter.java similarity index 60% rename from core/src/main/java/hudson/maven/MavenListener.java rename to core/src/main/java/hudson/maven/MavenReporter.java index a34bd50caf..845bab81b1 100644 --- a/core/src/main/java/hudson/maven/MavenListener.java +++ b/core/src/main/java/hudson/maven/MavenReporter.java @@ -9,26 +9,36 @@ import hudson.tasks.BuildStep; import java.io.IOException; +import org.apache.maven.project.MavenProject; + /** * Listens to the build execution of {@link MavenBuild}, * and normally records some information and exposes thoses * in {@link MavenBuild} later. * *

+ * TODO: talk about two nodes involved + * Because builds may happen on a remote slave node, {@link MavenReporter} + * implementation needs ... + * + *

* This is the {@link MavenBuild} equivalent of {@link BuildStep}. * * @author Kohsuke Kawaguchi + * @see MavenReporters */ -public abstract class MavenListener implements Describable, ExtensionPoint { +public abstract class MavenReporter implements Describable, ExtensionPoint { /** * Called before the actual maven2 execution begins. * + * @param pom + * Represents the POM to be executed. * @return * true if the build can continue, false if there was an error * and the build needs to be aborted. * @throws InterruptedException * If the build is interrupted by the user (in an attempt to abort the build.) - * Normally the {@link MavenListener} implementations may simply forward the exception + * Normally the {@link MavenReporter} implementations may simply forward the exception * it got from its lower-level functions. * @throws IOException * If the implementation wants to abort the processing when an {@link IOException} @@ -38,27 +48,35 @@ public abstract class MavenListener implements Describable, Exten * provide a better error message, if it can do so, so that users have better * understanding on why it failed. */ - public boolean prebuild(MavenBuild build, BuildListener listener) throws InterruptedException, IOException { + public boolean preBuild(MavenBuildProxy build, MavenProject pom, BuildListener listener) throws InterruptedException, IOException { return true; } + public void preExecute(MavenBuildProxy build, MavenProject pom, MojoInfo mojo, BuildListener listener) throws InterruptedException, IOException { + + } + + public void postExecute(MavenBuildProxy build, MavenProject pom, MojoInfo mojo, BuildListener listener) throws InterruptedException, IOException { + + } + /** * Called after the actual maven2 execution completed. * * @return - * See {@link #prebuild(MavenBuild, BuildListener)} + * See {@link #preBuild} * @throws InterruptedException - * See {@link #prebuild(MavenBuild, BuildListener)} + * See {@link # preBuild} * @throws IOException - * See {@link #prebuild(MavenBuild, BuildListener)} + * See {@link # preBuild} */ - public boolean postbuild(MavenBuild build, BuildListener listener) throws InterruptedException, IOException { + public boolean postBuild(MavenBuildProxy build, MavenProject pom, BuildListener listener) throws InterruptedException, IOException { return true; } /** * Equivalent of {@link BuildStep#getProjectAction(Project)} - * for {@link MavenListener}. + * for {@link MavenReporter}. */ public Action getProjectAction(MavenJob project) { return null; diff --git a/core/src/main/java/hudson/maven/MavenReporters.java b/core/src/main/java/hudson/maven/MavenReporters.java new file mode 100644 index 0000000000..a0ea44522b --- /dev/null +++ b/core/src/main/java/hudson/maven/MavenReporters.java @@ -0,0 +1,19 @@ +package hudson.maven; + +import hudson.model.Descriptor; +import hudson.maven.reporters.MavenArtifactArchiver; + +import java.util.List; + +/** + * @author Kohsuke Kawaguchi + * @see MavenReporter + */ +public final class MavenReporters { + /** + * List of all installed {@link MavenReporter}s. + */ + public static final List> LIST = Descriptor.>toList( + MavenArtifactArchiver.DescriptorImpl.DESCRIPTOR + ); +} diff --git a/core/src/main/java/hudson/maven/MojoInfo.java b/core/src/main/java/hudson/maven/MojoInfo.java new file mode 100644 index 0000000000..8f0f795f44 --- /dev/null +++ b/core/src/main/java/hudson/maven/MojoInfo.java @@ -0,0 +1,80 @@ +package hudson.maven; + +import org.apache.maven.plugin.MojoExecution; +import org.codehaus.plexus.configuration.PlexusConfiguration; +import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator; +import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup; +import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup; +import org.codehaus.plexus.component.configurator.converters.ConfigurationConverter; +import org.codehaus.plexus.component.configurator.ComponentConfigurationException; + +/** + * Information about Mojo to be executed. This object provides + * convenient access to various mojo information, so that {@link MavenReporter} + * implementations are shielded to some extent from Maven internals. + * + *

+ * For each mojo to be executed, this object is created and passed to + * {@link MavenReporter}. + * + * @author Kohsuke Kawaguchi + * @see MavenReporter + */ +public final class MojoInfo { + /** + * Object from Maven that describes the Mojo to be executed. + */ + public final MojoExecution mojoExecution; + + /** + * Name of the plugin that contains this mojo. + */ + public final Name pluginName; + + /** + * Configuration of the mojo for the current execution. + * This reflects the default values, as well as values configured from POM, + * including inherited values. + */ + public final PlexusConfiguration configuration; + + /** + * Object that Maven uses to resolve variables like "${project}" to its + * corresponding object. + */ + public final ExpressionEvaluator expressionEvaluator; + + /** + * Used to obtain a value from {@link PlexusConfiguration} as a typed object, + * instead of String. + */ + private final ConverterLookup converterLookup = new DefaultConverterLookup(); + + public MojoInfo(MojoExecution mojoExecution, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator) { + this.mojoExecution = mojoExecution; + this.configuration = configuration; + this.expressionEvaluator = expressionEvaluator; + this.pluginName = new Name(mojoExecution.getMojoDescriptor().getPluginDescriptor()); + } + + /** + * Gets the goal name of the mojo to be executed, + * such as "javadoc". This is local to the plugin name. + */ + public String getGoal() { + return mojoExecution.getMojoDescriptor().getGoal(); + } + + public T getConfigurationValue(String configName, Class type) throws ComponentConfigurationException { + PlexusConfiguration child = configuration.getChild(configName); + if(child==null) return null; // no such config + + ConfigurationConverter converter = converterLookup.lookupConverterForType(type); + return type.cast(converter.fromConfiguration(converterLookup,child,type, + // the implementation seems to expect the type of the bean for which the configuration is done + // in this parameter, but we have no such type. So passing in a dummy + Object.class, + mojoExecution.getMojoDescriptor().getPluginDescriptor().getClassRealm().getClassLoader(), + expressionEvaluator)); + } +} diff --git a/core/src/main/java/hudson/maven/Name.java b/core/src/main/java/hudson/maven/Name.java new file mode 100644 index 0000000000..c2eaf5e760 --- /dev/null +++ b/core/src/main/java/hudson/maven/Name.java @@ -0,0 +1,53 @@ +package hudson.maven; + +import org.apache.maven.plugin.descriptor.PluginDescriptor; + +/** + * Identifier of an artifact (like jar) in Maven, + * that consists of groupId, artifactId, and version. + * + * + * @author Kohsuke Kawaguchi + */ +public final class Name { + public final String groupId; + public final String artifactId; + public final String version; + + public Name(String groupId, String artifactId, String version) { + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + } + + public Name(PluginDescriptor pd) { + this(pd.getGroupId(), pd.getArtifactId(), pd.getVersion()); + } + + /** + * Returns the "groupId:artifactId:version" form. + */ + public String toString() { + return groupId+':'+artifactId+':'+version; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Name that = (Name) o; + + return artifactId.equals(that.artifactId) + && groupId.equals(that.groupId) + && version.equals(that.version); + + } + + public int hashCode() { + int result; + result = groupId.hashCode(); + result = 31 * result + artifactId.hashCode(); + result = 31 * result + version.hashCode(); + return result; + } +} diff --git a/core/src/main/java/hudson/maven/PluginManagetInterceptor.java b/core/src/main/java/hudson/maven/PluginManagetInterceptor.java index ac47f5ccd2..6b2c8417d9 100644 --- a/core/src/main/java/hudson/maven/PluginManagetInterceptor.java +++ b/core/src/main/java/hudson/maven/PluginManagetInterceptor.java @@ -16,6 +16,11 @@ import org.apache.maven.artifact.resolver.ArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactNotFoundException; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator; +import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; +import org.codehaus.plexus.configuration.PlexusConfiguration; + +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; /** * Description in META-INF/plexus/components.xml makes it possible to use this instead of the default @@ -25,14 +30,57 @@ import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator */ public class PluginManagetInterceptor extends DefaultPluginManager { + private final Method mergeMojoConfiguration; + + public PluginManagetInterceptor() { + try { + this.mergeMojoConfiguration = DefaultPluginManager.class.getDeclaredMethod( + "mergeMojoConfiguration", XmlPlexusConfiguration.class,MojoDescriptor.class); + mergeMojoConfiguration.setAccessible(true); + } catch (NoSuchMethodException e) { + NoSuchMethodError x = new NoSuchMethodError("Unable to find DefaultPluginManager.mergeMojoConfiguration()"); + x.initCause(e); + throw x; + } + } + public void executeMojo(MavenProject project, MojoExecution mojoExecution, MavenSession session) throws ArtifactResolutionException, MojoExecutionException, MojoFailureException, ArtifactNotFoundException, InvalidDependencyVersionException, PluginManagerException, PluginConfigurationException { Xpp3Dom dom = getConfigDom(mojoExecution, project); + XmlPlexusConfiguration pomConfiguration; + if ( dom == null ) + { + pomConfiguration = new XmlPlexusConfiguration( "configuration" ); + } + else + { + pomConfiguration = new XmlPlexusConfiguration( dom ); + } + + MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); + PlexusConfiguration mergedConfiguration; + + try { + mergedConfiguration = (PlexusConfiguration) mergeMojoConfiguration.invoke(this, pomConfiguration, mojoDescriptor ); + } catch (IllegalAccessException e) { + IllegalAccessError x = new IllegalAccessError(); + x.initCause(e); + throw x; + } catch (InvocationTargetException e) { + throw new MojoExecutionException("Failed to check configuration",e); + } + // this just seems like an error check + //PlexusConfiguration extractedMojoConfiguration = + // extractMojoConfiguration( mergedConfiguration, mojoDescriptor ); // what does this do? + ExpressionEvaluator eval = new PluginParameterExpressionEvaluator( session, mojoExecution, pathTranslator, getLogger(), project, session.getExecutionProperties() ); + // the proper step is first check the value, then check the default-value attribute. + + System.out.println("EXECUTING "+mojoExecution.getExecutionId()); super.executeMojo(project, mojoExecution, session); } diff --git a/core/src/main/java/hudson/maven/reporters/MavenArtifactArchiver.java b/core/src/main/java/hudson/maven/reporters/MavenArtifactArchiver.java new file mode 100644 index 0000000000..5047479f61 --- /dev/null +++ b/core/src/main/java/hudson/maven/reporters/MavenArtifactArchiver.java @@ -0,0 +1,62 @@ +package hudson.maven.reporters; + +import hudson.model.Descriptor; +import hudson.model.BuildListener; +import hudson.FilePath; +import hudson.maven.MavenReporter; +import hudson.maven.MavenBuildProxy; +import hudson.maven.MojoInfo; +import org.kohsuke.stapler.StaplerRequest; +import org.apache.maven.project.MavenProject; +import org.apache.maven.artifact.Artifact; + +import java.io.IOException; + +/** + * Archives artifacts of the build. + * + * @author Kohsuke Kawaguchi + */ +public class MavenArtifactArchiver extends MavenReporter { + + public void postExecute(MavenBuildProxy build, MavenProject pom, MojoInfo mojo, BuildListener listener) throws InterruptedException, IOException { + record(build,pom.getArtifact()); + for( Object a : pom.getAttachedArtifacts() ) + record(build,(Artifact)a); + } + + /** + * Archives the given {@link Artifact}. + */ + private void record(MavenBuildProxy build, Artifact a) throws IOException, InterruptedException { + if(a.getFile()==null) + return; // perhaps build failed and didn't leave an artifact + + new FilePath(a.getFile()).copyTo( + build.getArtifactsDir() + .child(a.getGroupId()) + .child(a.getArtifactId()) + .child(a.getVersion()) + .child(a.getArtifactId()+'-'+a.getVersion()+(a.getClassifier()!=null?'-'+a.getClassifier():"")+'.'+a.getType())); + } + + public DescriptorImpl getDescriptor() { + return DescriptorImpl.DESCRIPTOR; + } + + public static final class DescriptorImpl extends Descriptor { + public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); + + private DescriptorImpl() { + super(MavenArtifactArchiver.class); + } + + public String getDisplayName() { + return "Archive the artifacts"; + } + + public MavenArtifactArchiver newInstance(StaplerRequest req) throws FormException { + return new MavenArtifactArchiver(); + } + } +} diff --git a/core/src/main/resources/hudson/maven/reporters/MavenArtifactArchiver/config.jelly b/core/src/main/resources/hudson/maven/reporters/MavenArtifactArchiver/config.jelly new file mode 100644 index 0000000000..67520d4cee --- /dev/null +++ b/core/src/main/resources/hudson/maven/reporters/MavenArtifactArchiver/config.jelly @@ -0,0 +1,2 @@ + + \ No newline at end of file -- GitLab