提交 ff91381b 编写于 作者: K Kohsuke Kawaguchi

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.
上级 679f1eb4
......@@ -41,7 +41,7 @@ THE SOFTWARE.
<url>http://wiki.jenkins-ci.org/display/JENKINS/Maven+2+Project+Plugin</url>
<properties>
<mavenInterceptorsVersion>1.3-SNAPSHOT</mavenInterceptorsVersion>
<mavenInterceptorsVersion>1.3</mavenInterceptorsVersion>
<mavenVersion>3.0.5</mavenVersion>
<maven.version>${mavenVersion}</maven.version>
<aetherVersion>1.13.1</aetherVersion>
......
......@@ -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.
*/
......
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<ClassLoader> sides;
public AggregatingClassLoader(ClassLoader parent, List<ClassLoader> sides) {
super(parent);
this.sides = new ArrayList<ClassLoader>(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<URL> findResources(String name) throws IOException {
List<URL> resources = new ArrayList<URL>();
for (ClassLoader cl : sides) {
resources.addAll(Collections.list(cl.getResources(name)));
}
return Collections.enumeration(resources);
}
}
......@@ -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
......
......@@ -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<Void,IOException> {
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
*/
......
......@@ -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.
*
* <p>
* 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.
*
* <p>
* 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<URL> getResources(String name) throws IOException {
final Enumeration<URL> e = super.getResources(name);
return new Enumeration<URL>() {
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;
}
};
}
}
}
......@@ -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<MavenModuleSet,Maven
new MavenProcessFactory( project, launcher, envVars,getMavenOpts(listener, envVars),
pom.getParent() ) );
}
PlexusModuleContributor.apply(process);
ArgumentListBuilder margs = new ArgumentListBuilder().add("-B").add("-f", pom.getRemote());
FilePath localRepo = project.getLocalRepository().locate(MavenModuleSetBuild.this);
if(localRepo!=null)
......@@ -748,7 +755,7 @@ public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,Maven
if (newMargs != null) {
margs = newMargs;
}
}
}
final AbstractMavenBuilder builder;
if (maven3orLater) {
......@@ -1038,6 +1045,8 @@ public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,Maven
private boolean updateSnapshots = false;
String rootPOMRelPrefix;
private final PlexusModuleContributor plexusContributors = PlexusModuleContributor.aggregate();
PomParser(BuildListener listener, MavenInstallation mavenHome, String mavenVersion, EnvVars envVars, MavenModuleSetBuild build) {
// project cannot be shipped to the remote JVM, so all the relevant properties need to be captured now.
......@@ -1185,7 +1194,15 @@ public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,Maven
reactorReader = new ReactorReader( new HashMap<String, MavenProject>(), new File(workspaceProper) );
mavenEmbedderRequest.setWorkspaceReader( reactorReader );
}
{// create a classloader that loads extensions
List<URL> 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 );
......
......@@ -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<Void,IOException> {
PlexusModuleContributor c = PlexusModuleContributor.aggregate();
public Void call() throws IOException {
Main.addPlexusComponents(c.getPlexusComponentJars().toArray(new URL[0]));
return null;
}
}
/**
* Finds classworlds.jar
*/
......
......@@ -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<URL> 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.
*
* <p>
* 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.
*
* <p>
* 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<URL> getResources(String name) throws IOException {
final Enumeration<URL> e = super.getResources(name);
return new Enumeration<URL>() {
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 )) {
......
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.
*
* <p>
* 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<URL> getPlexusComponentJars();
/**
* Returns all the registered {@link PlexusModuleContributor}s.
*/
public static ExtensionList<PlexusModuleContributor> 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<Void, IOException>() {
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<PlexusModuleContributor> all = new ArrayList<PlexusModuleContributor>(all());
return new PlexusModuleContributor() {
@Override
public List<URL> getPlexusComponentJars() {
List<URL> urls = new ArrayList<URL>();
for (PlexusModuleContributor pc : all) {
urls.addAll(pc.getPlexusComponentJars());
}
return urls;
}
};
}
}
......@@ -161,6 +161,12 @@ THE SOFTWARE.
<artifactId>geb-implicit-assertions</artifactId>
<version>0.7.2</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.test</groupId>
<artifactId>sample-plexus-component</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
......
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<URL> getPlexusComponentJars() {
return Collections.singletonList(bogusPlexusJar);
}
}
}
......@@ -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());
}
}
}
}
<!--
The MIT License
Copyright (c) 2012- CloudBees, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<!--
POM that refers to a custom Plexus component
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>bogus</packaging>
</project>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册