/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi * * 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. */ package hudson.maven; import hudson.AbortException; import hudson.Util; import hudson.model.BuildListener; import hudson.model.TaskListener; import hudson.model.AbstractProject; import hudson.model.Hudson; import hudson.tasks.Maven.MavenInstallation; import hudson.tasks.Maven.ProjectWithMaven; import org.apache.maven.embedder.MavenEmbedderException; import org.apache.maven.embedder.MavenEmbedderLogger; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuildingException; import org.apache.commons.io.IOUtils; import java.io.File; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; /** * @author Kohsuke Kawaguchi */ public class MavenUtil { /** * @deprecated * Use {@link #createEmbedder(TaskListener, File, String, Properties)} * or other overloaded versions that infers maven home. */ public static MavenEmbedder createEmbedder(TaskListener listener, String profiles) throws MavenEmbedderException, IOException { return createEmbedder(listener,(File)null,profiles); } /** * This version tries to infer mavenHome by looking at a project. * * @see #createEmbedder(TaskListener, File, String) */ public static MavenEmbedder createEmbedder(TaskListener listener, AbstractProject project, String profiles) throws MavenEmbedderException, IOException, InterruptedException { MavenInstallation m=null; if (project instanceof ProjectWithMaven) m = ((ProjectWithMaven) project).inferMavenInstallation().forNode(Hudson.getInstance(),listener); return createEmbedder(listener,m!=null?m.getHomeDir():null,profiles); } public static MavenEmbedder createEmbedder(TaskListener listener, File mavenHome, String profiles) throws MavenEmbedderException, IOException { return createEmbedder(listener,mavenHome,profiles,new Properties()); } public static MavenEmbedder createEmbedder(TaskListener listener, File mavenHome, String profiles, Properties systemProperties) throws MavenEmbedderException, IOException { return createEmbedder(listener,mavenHome,profiles,systemProperties,null); } public static MavenEmbedder createEmbedder(TaskListener listener, File mavenHome, String profiles, Properties systemProperties, String privateRepository) throws MavenEmbedderException, IOException { return createEmbedder(listener,mavenHome,profiles,systemProperties,privateRepository,null); } /** * Creates a fresh {@link MavenEmbedder} instance. * * @param listener * This is where the log messages from Maven will be recorded. * @param mavenHome * Directory of the Maven installation. We read {@code conf/settings.xml} * from here. Can be null. * @param profiles * Profiles to activate/deactivate. Can be null. * @param systemProperties * The system properties that the embedded Maven sees. See {@link MavenEmbedder#setSystemProperties(Properties)}. * @param privateRepository * Optional private repository to use as the local repository. * @param alternateSettings * Optional alternate settings.xml file. */ public static MavenEmbedder createEmbedder(TaskListener listener, File mavenHome, String profiles, Properties systemProperties, String privateRepository, File alternateSettings) throws MavenEmbedderException, IOException { MavenEmbedder maven = new MavenEmbedder(mavenHome); ClassLoader cl = MavenUtil.class.getClassLoader(); maven.setClassLoader(new MaskingClassLoader(cl)); EmbedderLoggerImpl logger = new EmbedderLoggerImpl(listener); if(debugMavenEmbedder) logger.setThreshold(MavenEmbedderLogger.LEVEL_DEBUG); maven.setLogger(logger); { Enumeration e = cl.getResources("META-INF/plexus/components.xml"); while (e.hasMoreElements()) { URL url = e.nextElement(); LOGGER.fine("components.xml from "+url); } } // make sure ~/.m2 exists to avoid http://www.nabble.com/BUG-Report-tf3401736.html File m2Home = new File(MavenEmbedder.userHome, ".m2"); m2Home.mkdirs(); if(!m2Home.exists()) throw new AbortException("Failed to create "+m2Home+ "\nSee https://hudson.dev.java.net/cannot-create-.m2.html"); if (privateRepository!=null) maven.setLocalRepositoryDirectory(new File(privateRepository)); maven.setProfiles(profiles); if (alternateSettings!=null) maven.setAlternateSettings(alternateSettings); maven.setSystemProperties(systemProperties); maven.start(); return maven; } /** * Recursively resolves module POMs that are referenced from * the given {@link MavenProject} and parses them into * {@link MavenProject}s. * * @param rel * Used to compute the relative path. Pass in "" to begin. * @param relativePathInfo * Upon the completion of this method, this variable stores the relative path * from the root directory of the given {@link MavenProject} to the root directory * of each of the newly parsed {@link MavenProject}. * * @throws AbortException * errors will be reported to the listener and the exception thrown. */ public static void resolveModules(MavenEmbedder embedder, MavenProject project, String rel, Map relativePathInfo, BuildListener listener, boolean nonRecursive) throws ProjectBuildingException, AbortException { File basedir = project.getFile().getParentFile(); relativePathInfo.put(project,rel); if (!nonRecursive) { List modules = new ArrayList(); for (String modulePath : (List) project.getModules()) { if (Util.fixEmptyAndTrim(modulePath)!=null) { File moduleFile = new File(basedir, modulePath); if (moduleFile.exists() && moduleFile.isDirectory()) { moduleFile = new File(basedir, modulePath + "/pom.xml"); } if(!moduleFile.exists()) throw new AbortException(moduleFile+" is referenced from "+project.getFile()+" but it doesn't exist"); String relativePath = rel; if(relativePath.length()>0) relativePath+='/'; relativePath+=modulePath; MavenProject child = embedder.readProject(moduleFile); resolveModules(embedder,child,relativePath,relativePathInfo,listener,nonRecursive); modules.add(child); } } 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; } }; } } /** * If set to true, maximize the logging level of Maven embedder. */ public static boolean debugMavenEmbedder = false; private static final Logger LOGGER = Logger.getLogger(MavenUtil.class.getName()); }