View Javadoc
1   package org.apache.maven.shared.verifier;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.PrintStream;
27  import java.lang.reflect.Constructor;
28  import java.lang.reflect.InvocationTargetException;
29  import java.lang.reflect.Method;
30  import java.net.MalformedURLException;
31  import java.net.URL;
32  import java.net.URLClassLoader;
33  import java.nio.file.Files;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.Properties;
37  
38  import org.apache.maven.shared.utils.io.IOUtil;
39  
40  /**
41   * Launches an embedded Maven 3.x instance from some Maven installation directory.
42   * 
43   * @author Benjamin Bentmann
44   */
45  class Embedded3xLauncher
46      implements MavenLauncher
47  {
48  
49      private final Object mavenCli;
50  
51      private final Method doMain;
52  
53      private Embedded3xLauncher( Object mavenCli, Method doMain )
54      {
55          this.mavenCli = mavenCli;
56          this.doMain = doMain;
57      }
58  
59      /**
60       * Launches an embedded Maven 3.x instance from some Maven installation directory.
61       */
62      public static Embedded3xLauncher createFromMavenHome( String mavenHome, String classworldConf, List<URL> classpath )
63          throws LauncherException
64      {
65          if ( mavenHome == null || mavenHome.length() <= 0 )
66          {
67              throw new LauncherException( "Invalid Maven home directory " + mavenHome );
68          }
69  
70          System.setProperty( "maven.home", mavenHome );
71  
72          File config;
73          if ( classworldConf != null )
74          {
75              config = new File( classworldConf );
76          }
77          else
78          {
79              config = new File( mavenHome, "bin/m2.conf" );
80          }
81  
82          ClassLoader bootLoader = getBootLoader( mavenHome, classpath );
83  
84          ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
85          Thread.currentThread().setContextClassLoader( bootLoader );
86          try
87          {
88              Class<?> launcherClass = bootLoader.loadClass( "org.codehaus.plexus.classworlds.launcher.Launcher" );
89  
90              Object launcher = launcherClass.newInstance();
91  
92              Method configure = launcherClass.getMethod( "configure", new Class[] { InputStream.class } );
93  
94              configure.invoke( launcher, new Object[] { new FileInputStream( config ) } );
95  
96              Method getWorld = launcherClass.getMethod( "getWorld" );
97              Object classWorld = getWorld.invoke( launcher );
98  
99              Method getMainClass = launcherClass.getMethod( "getMainClass" );
100             Class<?> cliClass = (Class<?>) getMainClass.invoke( launcher );
101 
102             Constructor<?> newMavenCli = cliClass.getConstructor( new Class[] { classWorld.getClass() } );
103             Object mavenCli = newMavenCli.newInstance( new Object[] { classWorld } );
104 
105             Class<?>[] parameterTypes = { String[].class, String.class, PrintStream.class, PrintStream.class };
106             Method doMain = cliClass.getMethod( "doMain", parameterTypes );
107 
108             return new Embedded3xLauncher( mavenCli, doMain );
109         }
110         catch ( ReflectiveOperationException | IOException e )
111         {
112             throw new LauncherException( "Failed to initialize Laucher", e );
113         }
114         finally
115         {
116             Thread.currentThread().setContextClassLoader( oldClassLoader );
117         }
118     }
119 
120     /**
121      * Launches an embedded Maven 3.x instance from the current class path, i.e. the Maven 3.x dependencies are assumed
122      * to be present on the class path.
123      */
124     public static Embedded3xLauncher createFromClasspath()
125         throws LauncherException
126     {
127         ClassLoader coreLoader = Thread.currentThread().getContextClassLoader();
128 
129         try
130         {
131             Class<?> cliClass = coreLoader.loadClass( "org.apache.maven.cli.MavenCli" );
132 
133             Object mavenCli = cliClass.newInstance();
134 
135             Class<?>[] parameterTypes = { String[].class, String.class, PrintStream.class, PrintStream.class };
136             Method doMain = cliClass.getMethod( "doMain", parameterTypes );
137 
138             return new Embedded3xLauncher( mavenCli, doMain );
139         }
140         catch ( ReflectiveOperationException e )
141         {
142             throw new LauncherException( "Failed to initialize Laucher", e );
143         }
144     }
145 
146     private static ClassLoader getBootLoader( String mavenHome, List<URL> classpath )
147     {
148         List<URL> urls = classpath;
149 
150         if ( urls == null )
151         {
152             urls = new ArrayList<>();
153 
154             File bootDir = new File( mavenHome, "boot" );
155             addUrls( urls, bootDir );
156         }
157 
158         if ( urls.isEmpty() )
159         {
160             throw new IllegalArgumentException( "Invalid Maven home directory " + mavenHome );
161         }
162 
163         URL[] ucp = urls.toArray( new URL[0] );
164 
165         return new URLClassLoader( ucp, ClassLoader.getSystemClassLoader().getParent() );
166     }
167 
168     private static void addUrls( List<URL> urls, File directory )
169     {
170         File[] jars = directory.listFiles();
171 
172         if ( jars != null )
173         {
174             for ( int i = 0; i < jars.length; i++ )
175             {
176                 File jar = jars[i];
177 
178                 if ( jar.getName().endsWith( ".jar" ) )
179                 {
180                     try
181                     {
182                         urls.add( jar.toURI().toURL() );
183                     }
184                     catch ( MalformedURLException e )
185                     {
186                         throw (RuntimeException) new IllegalStateException().initCause( e );
187                     }
188                 }
189             }
190         }
191     }
192 
193     public int run( String[] cliArgs, Properties systemProperties, String workingDirectory, File logFile )
194         throws IOException, LauncherException
195     {
196         PrintStream out = ( logFile != null )
197             ? new PrintStream( Files.newOutputStream( logFile.toPath() ) ) : System.out;
198         try
199         {
200             File workingDirectoryPath = new File( workingDirectory );
201             Properties originalProperties = System.getProperties();
202             System.setProperties( null );
203             System.setProperty( "maven.home", originalProperties.getProperty( "maven.home", "" ) );
204             System.setProperty( "user.dir", workingDirectoryPath.getAbsolutePath() );
205             System.setProperty( "maven.multiModuleProjectDirectory", 
206                     findProjectBaseDirectory( workingDirectoryPath ).getAbsolutePath() );
207 
208             for ( Object o : systemProperties.keySet() )
209             {
210                 String key = (String) o;
211                 String value = systemProperties.getProperty( key );
212                 System.setProperty( key, value );
213             }
214 
215             ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
216             Thread.currentThread().setContextClassLoader( mavenCli.getClass().getClassLoader() );
217             try
218             {
219                 Object result = doMain.invoke( mavenCli, new Object[] { cliArgs, workingDirectory, out, out } );
220 
221                 return ( (Number) result ).intValue();
222             }
223             finally
224             {
225                 Thread.currentThread().setContextClassLoader( originalClassLoader );
226 
227                 System.setProperties( originalProperties );
228             }
229         }
230         catch ( IllegalAccessException | InvocationTargetException e )
231         {
232             throw new LauncherException( "Failed to run Maven", e );
233         }
234         finally
235         {
236             if ( logFile != null )
237             {
238                 out.close();
239             }
240         }
241     }
242 
243     public String getMavenVersion()
244         throws LauncherException
245     {
246         Properties props = new Properties();
247 
248         InputStream is =
249             mavenCli.getClass().getResourceAsStream( "/META-INF/maven/org.apache.maven/maven-core/pom.properties" );
250         if ( is != null )
251         {
252             try
253             {
254                 props.load( is );
255             }
256             catch ( IOException e )
257             {
258                 throw new LauncherException( "Failed to read Maven version", e );
259             }
260             finally
261             {
262                 IOUtil.close( is );
263             }
264         }
265 
266         String version = props.getProperty( "version" );
267         if ( version != null )
268         {
269             return version;
270         }
271 
272         throw new LauncherException( "Could not determine embedded Maven version" );
273     }
274 
275     /**
276      * Replicates the logic from <a href="https://git.io/JSMug">Maven start script</a> to find 
277      * the project's base directory.
278      * @param workingDirectory the working directory
279      * @return the project's base directory
280      */
281     private static File findProjectBaseDirectory( File workingDirectory )
282     {
283         File currentDirectory = workingDirectory;
284         // traverses directory structure from process work directory to filesystem root
285         // first directory with .mvn subdirectory is considered project base directory
286         while ( currentDirectory != null && currentDirectory.getParentFile() != null )
287         {
288             // see if /.mvn exists
289             if ( new File( currentDirectory, ".mvn" ).isDirectory() )
290             {
291                 return currentDirectory;
292             }
293             currentDirectory = currentDirectory.getParentFile();
294         }
295         return workingDirectory;
296     }
297 }