From ff91381bc5cec4bf55815b20e092f97d6183b867 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 7 Jun 2013 10:56:24 +0300 Subject: [PATCH] Added PlexusModuleContributor extension point. This came up with a conversation with JFrog folks who neede to insert a custom Plexus component to tweak the behaviour. This scheme allows the caller to insert additional jar files into the Maven process. --- maven-plugin/pom.xml | 2 +- .../maven/AbstractMavenProcessFactory.java | 21 +++- .../hudson/maven/AggregatingClassLoader.java | 57 +++++++++++ .../main/java/hudson/maven/Maven3Builder.java | 4 +- .../hudson/maven/Maven3ProcessFactory.java | 20 ++++ .../hudson/maven/MavenEmbedderRequest.java | 97 ++++++++++++++++++- .../hudson/maven/MavenModuleSetBuild.java | 21 +++- .../hudson/maven/MavenProcessFactory.java | 17 +++- .../src/main/java/hudson/maven/MavenUtil.java | 78 +-------------- .../hudson/maven/PlexusModuleContributor.java | 69 +++++++++++++ test/pom.xml | 6 ++ .../maven/PlexusModuleContributorTest.java | 64 ++++++++++++ .../src/test/java/hudson/tasks/MavenTest.java | 10 +- .../hudson/maven/custom-plexus-component.pom | 34 +++++++ 14 files changed, 410 insertions(+), 90 deletions(-) create mode 100644 maven-plugin/src/main/java/hudson/maven/AggregatingClassLoader.java create mode 100644 maven-plugin/src/main/java/hudson/maven/PlexusModuleContributor.java create mode 100644 test/src/test/java/hudson/maven/PlexusModuleContributorTest.java create mode 100644 test/src/test/resources/hudson/maven/custom-plexus-component.pom diff --git a/maven-plugin/pom.xml b/maven-plugin/pom.xml index d79e316564..1e512fd512 100644 --- a/maven-plugin/pom.xml +++ b/maven-plugin/pom.xml @@ -41,7 +41,7 @@ THE SOFTWARE. http://wiki.jenkins-ci.org/display/JENKINS/Maven+2+Project+Plugin - 1.3-SNAPSHOT + 1.3 3.0.5 ${mavenVersion} 1.13.1 diff --git a/maven-plugin/src/main/java/hudson/maven/AbstractMavenProcessFactory.java b/maven-plugin/src/main/java/hudson/maven/AbstractMavenProcessFactory.java index bc02fa5076..9ea895c7e1 100644 --- a/maven-plugin/src/main/java/hudson/maven/AbstractMavenProcessFactory.java +++ b/maven-plugin/src/main/java/hudson/maven/AbstractMavenProcessFactory.java @@ -69,6 +69,11 @@ import org.kohsuke.stapler.framework.io.IOException2; */ /** + * Launches the maven process. + * + * This class captures the common part, and {@link MavenProcessFactory} and {@link Maven3ProcessFactory} + * adds Maven2/Maven3 flavors to it to make it concrete. + * * @author Olivier Lamy */ public abstract class AbstractMavenProcessFactory @@ -234,11 +239,14 @@ public abstract class AbstractMavenProcessFactory throw e; } - return new NewProcess( - Channels.forProcess("Channel to Maven "+ Arrays.toString(cmds), + Channel ch = Channels.forProcess("Channel to Maven " + Arrays.toString(cmds), Computer.threadPoolForRemoting, new BufferedInputStream(con.in), new BufferedOutputStream(con.out), - listener.getLogger(), proc), - proc); + listener.getLogger(), proc); + + if (!PlexusModuleContributor.all().isEmpty()) + applyPlexusModuleContributor(ch); + + return new NewProcess(ch,proc); } catch (IOException e) { if(fixNull(e.getMessage()).contains("java: not found")) { // diagnose issue #659 @@ -250,6 +258,11 @@ public abstract class AbstractMavenProcessFactory } } + /** + * Apply extension plexus modules to the newly launched Maven process. + */ + protected abstract void applyPlexusModuleContributor(Channel channel) throws InterruptedException, IOException; + /** * Builds the command line argument list to launch the maven process. */ diff --git a/maven-plugin/src/main/java/hudson/maven/AggregatingClassLoader.java b/maven-plugin/src/main/java/hudson/maven/AggregatingClassLoader.java new file mode 100644 index 0000000000..c18672acb3 --- /dev/null +++ b/maven-plugin/src/main/java/hudson/maven/AggregatingClassLoader.java @@ -0,0 +1,57 @@ +package hudson.maven; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +/** + * ClassLoader that delegates to multiple side classloaders, in addition to the parent. + * + * The side classloaders generally need to be carefully crafted + * to avoid classloader constraint violations. + * + * @author Kohsuke Kawaguchi + */ +class AggregatingClassLoader extends ClassLoader { + private final List sides; + + public AggregatingClassLoader(ClassLoader parent, List sides) { + super(parent); + this.sides = new ArrayList(sides); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + for (ClassLoader cl : sides) { + try { + return cl.loadClass(name); + } catch (ClassNotFoundException e) { + //not found. try next + } + } + // not found in any of the classloader. delegate. + throw new ClassNotFoundException(name); + } + + @Override + protected URL findResource(String name) { + for (ClassLoader cl : sides) { + URL url = cl.getResource(name); + if (url!=null) + return url; + } + return null; + } + + @Override + protected Enumeration findResources(String name) throws IOException { + List resources = new ArrayList(); + for (ClassLoader cl : sides) { + resources.addAll(Collections.list(cl.getResources(name))); + } + return Collections.enumeration(resources); + } +} diff --git a/maven-plugin/src/main/java/hudson/maven/Maven3Builder.java b/maven-plugin/src/main/java/hudson/maven/Maven3Builder.java index 182adb0830..50010b4130 100644 --- a/maven-plugin/src/main/java/hudson/maven/Maven3Builder.java +++ b/maven-plugin/src/main/java/hudson/maven/Maven3Builder.java @@ -95,8 +95,8 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal registerSystemProperties(); listener.getLogger().println(formatArgs(goals)); - - + + int r = Maven3Main.launch( goals.toArray(new String[goals.size()])); // now check the completion status of async ops diff --git a/maven-plugin/src/main/java/hudson/maven/Maven3ProcessFactory.java b/maven-plugin/src/main/java/hudson/maven/Maven3ProcessFactory.java index b4235d62bb..0d6fc6c7cb 100644 --- a/maven-plugin/src/main/java/hudson/maven/Maven3ProcessFactory.java +++ b/maven-plugin/src/main/java/hudson/maven/Maven3ProcessFactory.java @@ -26,16 +26,19 @@ package hudson.maven; import hudson.EnvVars; import hudson.FilePath; import hudson.Launcher; +import hudson.maven.agent.Main; import hudson.model.BuildListener; import hudson.model.Run.RunnerAbortedException; import hudson.model.TaskListener; import hudson.remoting.Callable; +import hudson.remoting.Channel; import hudson.remoting.Which; import hudson.tasks.Maven.MavenInstallation; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.net.URL; import org.jvnet.hudson.maven3.agent.Maven3Main; import org.jvnet.hudson.maven3.launcher.Maven3Launcher; @@ -78,6 +81,23 @@ public class Maven3ProcessFactory extends AbstractMavenProcessFactory implements return null; } + @Override + protected void applyPlexusModuleContributor(Channel channel) throws InterruptedException, IOException { + channel.call(new InstallPlexusModulesTask()); + } + + private static final class InstallPlexusModulesTask implements Callable { + PlexusModuleContributor c = PlexusModuleContributor.aggregate(); + + public Void call() throws IOException { + Maven3Main.addPlexusComponents(c.getPlexusComponentJars().toArray(new URL[0])); + return null; + } + + private static final long serialVersionUID = 1L; + } + + /** * Finds classworlds.jar */ diff --git a/maven-plugin/src/main/java/hudson/maven/MavenEmbedderRequest.java b/maven-plugin/src/main/java/hudson/maven/MavenEmbedderRequest.java index 2b8be76a8b..975d860670 100755 --- a/maven-plugin/src/main/java/hudson/maven/MavenEmbedderRequest.java +++ b/maven-plugin/src/main/java/hudson/maven/MavenEmbedderRequest.java @@ -24,9 +24,15 @@ package hudson.maven; import hudson.model.TaskListener; +import java.io.BufferedReader; import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.Enumeration; import java.util.Properties; +import org.apache.commons.io.IOUtils; import org.apache.maven.model.building.ModelBuildingRequest; import org.sonatype.aether.repository.WorkspaceReader; import org.sonatype.aether.transfer.TransferListener; @@ -51,10 +57,14 @@ public class MavenEmbedderRequest private TransferListener transferListener; /** + * The classloader used to create Maven embedder. + * + * This needs to be able to see all the plexus components for core Maven stuff. + * * @since 1.393 */ - private ClassLoader classLoader; - + private ClassLoader classLoader = getDefaultMavenClassLoader(); + /** * will processPlugins during project reading * @since 1.393 @@ -88,7 +98,7 @@ public class MavenEmbedderRequest * @since 1.461 */ private boolean updateSnapshots; - + /** * @param listener * This is where the log messages from Maven will be recorded. @@ -180,6 +190,13 @@ public class MavenEmbedderRequest return this; } + /** + * Default value of {@link #getClassLoader()} + */ + public static ClassLoader getDefaultMavenClassLoader() { + return new MaskingClassLoader( MavenUtil.class.getClassLoader() ); + } + public ClassLoader getClassLoader() { return classLoader; } @@ -241,4 +258,78 @@ public class MavenEmbedderRequest public boolean isUpdateSnapshots() { return updateSnapshots; } + + + /** + * When we run in Jetty during development, embedded Maven will end up + * seeing some of the Maven class visible through Jetty, and this confuses it. + * + *

+ * Specifically, embedded Maven will find all the component descriptors + * visible through Jetty, yet when it comes to loading classes, classworlds + * still load classes from local realms created inside embedder. + * + *

+ * This classloader prevents this issue by hiding the component descriptor + * visible through Jetty. + */ + private static final class MaskingClassLoader extends ClassLoader { + + public MaskingClassLoader(ClassLoader parent) { + super(parent); + } + + public Enumeration getResources(String name) throws IOException { + final Enumeration e = super.getResources(name); + return new Enumeration() { + URL next; + + public boolean hasMoreElements() { + fetch(); + return next!=null; + } + + public URL nextElement() { + fetch(); + URL r = next; + next = null; + return r; + } + + private void fetch() { + while(next==null && e.hasMoreElements()) { + next = e.nextElement(); + if(shouldBeIgnored(next)) + next = null; + } + } + + private boolean shouldBeIgnored(URL url) { + String s = url.toExternalForm(); + if(s.contains("maven-plugin-tools-api")) + return true; + // because RemoteClassLoader mangles the path, we can't check for plexus/components.xml, + // which would have otherwise made the test cheaper. + if(s.endsWith("components.xml")) { + BufferedReader r=null; + try { + // is this designated for interception purpose? If so, don't load them in the MavenEmbedder + // earlier I tried to use a marker file in the same directory, but that won't work + r = new BufferedReader(new InputStreamReader(url.openStream())); + for (int i=0; i<2; i++) { + String l = r.readLine(); + if(l!=null && l.contains("MAVEN-INTERCEPTION-TO-BE-MASKED")) + return true; + } + } catch (IOException _) { + // let whoever requesting this resource re-discover an error and report it + } finally { + IOUtils.closeQuietly(r); + } + } + return false; + } + }; + } + } } diff --git a/maven-plugin/src/main/java/hudson/maven/MavenModuleSetBuild.java b/maven-plugin/src/main/java/hudson/maven/MavenModuleSetBuild.java index 7fac51bb8d..1e5c85a204 100644 --- a/maven-plugin/src/main/java/hudson/maven/MavenModuleSetBuild.java +++ b/maven-plugin/src/main/java/hudson/maven/MavenModuleSetBuild.java @@ -51,6 +51,7 @@ import hudson.model.Result; import hudson.model.Run; import hudson.model.StringParameterDefinition; import hudson.model.TaskListener; +import hudson.remoting.Callable; import hudson.remoting.VirtualChannel; import hudson.scm.ChangeLogSet; import hudson.tasks.BuildStep; @@ -66,6 +67,8 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.io.PrintStream; import java.io.Serializable; +import java.net.URL; +import java.net.URLClassLoader; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; @@ -691,6 +694,10 @@ public class MavenModuleSetBuild extends AbstractMavenBuild(), new File(workspaceProper) ); mavenEmbedderRequest.setWorkspaceReader( reactorReader ); } - + + {// create a classloader that loads extensions + List urls = plexusContributors.getPlexusComponentJars(); + if (!urls.isEmpty()) { + mavenEmbedderRequest.setClassLoader( + new URLClassLoader(urls.toArray(new URL[urls.size()]), + mavenEmbedderRequest.getClassLoader())); + } + } if (this.mavenValidationLevel >= 0) { mavenEmbedderRequest.setValidationLevel( this.mavenValidationLevel ); diff --git a/maven-plugin/src/main/java/hudson/maven/MavenProcessFactory.java b/maven-plugin/src/main/java/hudson/maven/MavenProcessFactory.java index f2f7323e57..5268e99e28 100644 --- a/maven-plugin/src/main/java/hudson/maven/MavenProcessFactory.java +++ b/maven-plugin/src/main/java/hudson/maven/MavenProcessFactory.java @@ -32,16 +32,17 @@ import hudson.model.BuildListener; import hudson.model.Run.RunnerAbortedException; import hudson.model.TaskListener; import hudson.remoting.Callable; +import hudson.remoting.Channel; import hudson.remoting.Which; import hudson.tasks.Maven.MavenInstallation; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.net.URL; /** - * Launches the maven process. * * @author Kohsuke Kawaguchi */ @@ -86,6 +87,20 @@ final class MavenProcessFactory extends AbstractMavenProcessFactory implements P return null; } + @Override + protected void applyPlexusModuleContributor(Channel channel) throws InterruptedException, IOException { + channel.call(new InstallPlexusModulesTask()); + } + + private static final class InstallPlexusModulesTask implements Callable { + PlexusModuleContributor c = PlexusModuleContributor.aggregate(); + + public Void call() throws IOException { + Main.addPlexusComponents(c.getPlexusComponentJars().toArray(new URL[0])); + return null; + } + } + /** * Finds classworlds.jar */ diff --git a/maven-plugin/src/main/java/hudson/maven/MavenUtil.java b/maven-plugin/src/main/java/hudson/maven/MavenUtil.java index 5fb22978ee..0ff693b542 100755 --- a/maven-plugin/src/main/java/hudson/maven/MavenUtil.java +++ b/maven-plugin/src/main/java/hudson/maven/MavenUtil.java @@ -29,6 +29,7 @@ import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; +import hudson.util.MaskingClassLoader; import jenkins.model.Jenkins; import jenkins.mvn.SettingsProvider; import hudson.model.TaskListener; @@ -195,9 +196,7 @@ public class MavenUtil { : org.codehaus.plexus.logging.Logger.LEVEL_INFO ); mavenRequest.setMavenLoggerManager( logger ); - ClassLoader mavenEmbedderClassLoader = - mavenEmbedderRequest.getClassLoader() == null ? new MaskingClassLoader( MavenUtil.class.getClassLoader() ) - : mavenEmbedderRequest.getClassLoader(); + ClassLoader mavenEmbedderClassLoader = mavenEmbedderRequest.getClassLoader(); {// are we loading the right components.xml? (and not from Maven that's running Jetty, if we are running in "mvn hudson-dev:run" or "mvn hpi:run"? Enumeration e = mavenEmbedderClassLoader.getResources("META-INF/plexus/components.xml"); @@ -272,79 +271,6 @@ public class MavenUtil { project.setCollectedProjects( modules ); } - /** - * When we run in Jetty during development, embedded Maven will end up - * seeing some of the Maven class visible through Jetty, and this confuses it. - * - *

- * Specifically, embedded Maven will find all the component descriptors - * visible through Jetty, yet when it comes to loading classes, classworlds - * still load classes from local realms created inside embedder. - * - *

- * This classloader prevents this issue by hiding the component descriptor - * visible through Jetty. - */ - private static final class MaskingClassLoader extends ClassLoader { - - public MaskingClassLoader(ClassLoader parent) { - super(parent); - } - - public Enumeration getResources(String name) throws IOException { - final Enumeration e = super.getResources(name); - return new Enumeration() { - URL next; - - public boolean hasMoreElements() { - fetch(); - return next!=null; - } - - public URL nextElement() { - fetch(); - URL r = next; - next = null; - return r; - } - - private void fetch() { - while(next==null && e.hasMoreElements()) { - next = e.nextElement(); - if(shouldBeIgnored(next)) - next = null; - } - } - - private boolean shouldBeIgnored(URL url) { - String s = url.toExternalForm(); - if(s.contains("maven-plugin-tools-api")) - return true; - // because RemoteClassLoader mangles the path, we can't check for plexus/components.xml, - // which would have otherwise made the test cheaper. - if(s.endsWith("components.xml")) { - BufferedReader r=null; - try { - // is this designated for interception purpose? If so, don't load them in the MavenEmbedder - // earlier I tried to use a marker file in the same directory, but that won't work - r = new BufferedReader(new InputStreamReader(url.openStream())); - for (int i=0; i<2; i++) { - String l = r.readLine(); - if(l!=null && l.contains("MAVEN-INTERCEPTION-TO-BE-MASKED")) - return true; - } - } catch (IOException _) { - // let whoever requesting this resource re-discover an error and report it - } finally { - IOUtils.closeQuietly(r); - } - } - return false; - } - }; - } - } - public static boolean maven3orLater(String mavenVersion) { // null or empty so false ! if (StringUtils.isBlank( mavenVersion )) { diff --git a/maven-plugin/src/main/java/hudson/maven/PlexusModuleContributor.java b/maven-plugin/src/main/java/hudson/maven/PlexusModuleContributor.java new file mode 100644 index 0000000000..314014d5ec --- /dev/null +++ b/maven-plugin/src/main/java/hudson/maven/PlexusModuleContributor.java @@ -0,0 +1,69 @@ +package hudson.maven; + +import hudson.ExtensionList; +import hudson.ExtensionPoint; +import hudson.maven.ProcessCache.MavenProcess; +import hudson.remoting.Callable; +import jenkins.model.Jenkins; +import org.apache.maven.AbstractMavenLifecycleParticipant; + +import java.io.IOException; +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * Contributes additional code into Plexus container when we run Maven. + * + *

+ * Injecting custom plexus components, such as {@link AbstractMavenLifecycleParticipant}, allows plugins to + * participate into the Maven internals more deeply. + * + * @author Kohsuke Kawaguchi + * @since 1.519 + */ +public abstract class PlexusModuleContributor implements ExtensionPoint, Serializable { + public abstract List getPlexusComponentJars(); + + /** + * Returns all the registered {@link PlexusModuleContributor}s. + */ + public static ExtensionList all() { + return Jenkins.getInstance().getExtensionList(PlexusModuleContributor.class); + } + + private static final long serialVersionUID = 1L; + + /** + * Tweaks the classworlds setup in the given Maven process to insert extension plexus modules. + */ + public static void apply(MavenProcess process) throws InterruptedException, IOException { + PlexusModuleContributor all = aggregate(); + process.channel.call(new Callable() { + public Void call() throws IOException { + + return null; + } + }); + } + + /** + * Returns a single {@link PlexusModuleContributor} that aggregates all the registered + * {@link PlexusModuleContributor}s in the system. The instance is remoting portable. + */ + public static PlexusModuleContributor aggregate() { + // capture in a serializable form + final List all = new ArrayList(all()); + return new PlexusModuleContributor() { + @Override + public List getPlexusComponentJars() { + List urls = new ArrayList(); + for (PlexusModuleContributor pc : all) { + urls.addAll(pc.getPlexusComponentJars()); + } + return urls; + } + }; + } +} diff --git a/test/pom.xml b/test/pom.xml index 5d1ee3b2c7..a8d491678c 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -161,6 +161,12 @@ THE SOFTWARE. geb-implicit-assertions 0.7.2 + + org.jenkins-ci.test + sample-plexus-component + 1.0 + test + diff --git a/test/src/test/java/hudson/maven/PlexusModuleContributorTest.java b/test/src/test/java/hudson/maven/PlexusModuleContributorTest.java new file mode 100644 index 0000000000..638dc03842 --- /dev/null +++ b/test/src/test/java/hudson/maven/PlexusModuleContributorTest.java @@ -0,0 +1,64 @@ +package hudson.maven; + +import hudson.remoting.Which; +import hudson.tasks.Maven.MavenInstallation; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.SingleFileSCM; +import org.jvnet.hudson.test.TestExtension; +import test.BogusPlexusComponent; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.List; + +/** + * @author Kohsuke Kawaguchi + */ +public class PlexusModuleContributorTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + + /** + * Tests the effect of PlexusModuleContributor by trying to parse a POM that uses a custom packaging + * that only exists inside our custom jar. + */ + @Test + public void testCustomPlexusComponent() throws Exception { + j.configureDefaultMaven("apache-maven-2.2.1", MavenInstallation.MAVEN_21); + MavenModuleSet p = j.createMavenProject(); + p.setScm(new SingleFileSCM("pom.xml",getClass().getResource("custom-plexus-component.pom"))); + p.setGoals("clean"); + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + } + + @Test + public void testCustomPlexusComponent_Maven3() throws Exception { + j.configureDefaultMaven("apache-maven-3.0.1", MavenInstallation.MAVEN_30); + MavenModuleSet p = j.createMavenProject(); + p.setScm(new SingleFileSCM("pom.xml",getClass().getResource("custom-plexus-component.pom"))); + p.setGoals("clean"); + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + } + + @TestExtension + public static class PlexusLoader extends PlexusModuleContributor { + private URL bogusPlexusJar; + + public PlexusLoader() { + try { + this.bogusPlexusJar = Which.jarFile(BogusPlexusComponent.class).toURL(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + @Override + public List getPlexusComponentJars() { + return Collections.singletonList(bogusPlexusJar); + } + } +} diff --git a/test/src/test/java/hudson/tasks/MavenTest.java b/test/src/test/java/hudson/tasks/MavenTest.java index 6daf14b6cd..e6758f502c 100644 --- a/test/src/test/java/hudson/tasks/MavenTest.java +++ b/test/src/test/java/hudson/tasks/MavenTest.java @@ -23,6 +23,9 @@ */ package hudson.tasks; +import hudson.maven.MavenModuleSet; +import hudson.maven.MavenModuleSetBuild; +import hudson.maven.PlexusModuleContributor; import hudson.model.Build; import hudson.model.FreeStyleProject; import jenkins.model.Jenkins; @@ -48,7 +51,10 @@ import hudson.tools.ToolPropertyDescriptor; import hudson.tools.InstallSourceProperty; import hudson.util.DescribableList; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Collections; +import java.util.List; import junit.framework.Assert; @@ -61,6 +67,8 @@ import com.gargoylesoftware.htmlunit.html.HtmlButton; import hudson.model.FreeStyleBuild; import hudson.model.PasswordParameterDefinition; import org.jvnet.hudson.test.ExtractResourceSCM; +import org.jvnet.hudson.test.SingleFileSCM; +import org.jvnet.hudson.test.TestExtension; /** * @author Kohsuke Kawaguchi @@ -228,5 +236,5 @@ public class MavenTest extends HudsonTestCase { assertEquals("/tmp/settigns.xml", ((FilePathSettingsProvider)m.getSettings()).getPath()); assertEquals("/tmp/global-settigns.xml", ((FilePathGlobalSettingsProvider)m.getGlobalSettings()).getPath()); } - } + } } diff --git a/test/src/test/resources/hudson/maven/custom-plexus-component.pom b/test/src/test/resources/hudson/maven/custom-plexus-component.pom new file mode 100644 index 0000000000..941a182de9 --- /dev/null +++ b/test/src/test/resources/hudson/maven/custom-plexus-component.pom @@ -0,0 +1,34 @@ + + + + + 4.0.0 + test + test + 0.1-SNAPSHOT + bogus + \ No newline at end of file -- GitLab