View Javadoc
1   package org.apache.maven.plugin.surefire.booterclient;
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 org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
23  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
24  import org.apache.maven.surefire.booter.AbstractPathConfiguration;
25  import org.apache.maven.surefire.booter.Classpath;
26  import org.apache.maven.surefire.booter.ModularClasspath;
27  import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
28  import org.apache.maven.surefire.booter.StartupConfiguration;
29  import org.apache.maven.surefire.booter.SurefireBooterForkException;
30  import org.objectweb.asm.ClassReader;
31  import org.objectweb.asm.ClassVisitor;
32  import org.objectweb.asm.ModuleVisitor;
33  
34  import javax.annotation.Nonnegative;
35  import javax.annotation.Nonnull;
36  import javax.annotation.Nullable;
37  import java.io.BufferedWriter;
38  import java.io.File;
39  import java.io.FileInputStream;
40  import java.io.FileWriter;
41  import java.io.IOException;
42  import java.util.Collection;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Properties;
47  
48  import static java.io.File.createTempFile;
49  import static java.io.File.pathSeparatorChar;
50  import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
51  import static org.objectweb.asm.Opcodes.ASM6;
52  
53  /**
54   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
55   * @since 2.21.0.Jigsaw
56   */
57  public class ModularClasspathForkConfiguration
58          extends DefaultForkConfiguration
59  {
60      @SuppressWarnings( "checkstyle:parameternumber" )
61      public ModularClasspathForkConfiguration( @Nonnull Classpath bootClasspath,
62                                                @Nonnull File tempDirectory,
63                                                @Nullable String debugLine,
64                                                @Nonnull File workingDirectory,
65                                                @Nonnull Properties modelProperties,
66                                                @Nullable String argLine,
67                                                @Nonnull Map<String, String> environmentVariables,
68                                                boolean debug,
69                                                @Nonnegative int forkCount,
70                                                boolean reuseForks,
71                                                @Nonnull Platform pluginPlatform,
72                                                @Nonnull ConsoleLogger log )
73      {
74          super( bootClasspath, tempDirectory, debugLine, workingDirectory, modelProperties, argLine,
75                  environmentVariables, debug, forkCount, reuseForks, pluginPlatform, log );
76      }
77  
78      @Override
79      protected void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli, @Nonnull String startClass,
80                                       @Nonnull StartupConfiguration config )
81              throws SurefireBooterForkException
82      {
83          try
84          {
85              AbstractPathConfiguration pathConfig = config.getClasspathConfiguration();
86  
87              ModularClasspathConfiguration modularClasspathConfiguration =
88                      pathConfig.toRealPath( ModularClasspathConfiguration.class );
89  
90              ModularClasspath modularClasspath = modularClasspathConfiguration.getModularClasspath();
91  
92              File descriptor = modularClasspath.getModuleDescriptor();
93              List<String> modulePath = modularClasspath.getModulePath();
94              Collection<String> packages = modularClasspath.getPackages();
95              File patchFile = modularClasspath.getPatchFile();
96              List<String> classpath = toCompleteClasspath( config );
97  
98              File argsFile = createArgsFile( descriptor, modulePath, classpath, packages, patchFile, startClass );
99  
100             cli.createArg().setValue( "@" + escapeToPlatformPath( argsFile.getAbsolutePath() ) );
101         }
102         catch ( IOException e )
103         {
104             throw new SurefireBooterForkException( "Error creating args file", e );
105         }
106     }
107 
108     @Nonnull
109     File createArgsFile( @Nonnull File moduleDescriptor, @Nonnull List<String> modulePath,
110                          @Nonnull List<String> classPath, @Nonnull Collection<String> packages,
111                          @Nonnull File patchFile, @Nonnull String startClassName )
112             throws IOException
113     {
114         File surefireArgs = createTempFile( "surefireargs", "", getTempDirectory() );
115         if ( !isDebug() )
116         {
117             surefireArgs.deleteOnExit();
118         }
119 
120         BufferedWriter writer = null;
121         try
122         {
123             writer = new BufferedWriter( new FileWriter( surefireArgs ) );
124 
125             if ( !modulePath.isEmpty() )
126             {
127                 writer.write( "--module-path" );
128                 writer.newLine();
129 
130                 for ( Iterator<String> it = modulePath.iterator(); it.hasNext(); )
131                 {
132                     writer.append( it.next() );
133                     if ( it.hasNext() )
134                     {
135                         writer.append( pathSeparatorChar );
136                     }
137                 }
138 
139                 writer.newLine();
140             }
141 
142             if ( !classPath.isEmpty() )
143             {
144                 writer.write( "--class-path" );
145                 writer.newLine();
146                 for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
147                 {
148                     writer.append( it.next() );
149                     if ( it.hasNext() )
150                     {
151                         writer.append( pathSeparatorChar );
152                     }
153                 }
154 
155                 writer.newLine();
156             }
157 
158             final String moduleName = toModuleName( moduleDescriptor );
159 
160             writer.write( "--patch-module" );
161             writer.newLine();
162             writer.append( moduleName )
163                     .append( '=' )
164                     .append( patchFile.getPath() );
165 
166             writer.newLine();
167 
168             for ( String pkg : packages )
169             {
170                 writer.write( "--add-exports" );
171                 writer.newLine();
172                 writer.append( moduleName )
173                         .append( '/' )
174                         .append( pkg )
175                         .append( '=' )
176                         .append( "ALL-UNNAMED" );
177 
178                 writer.newLine();
179             }
180 
181             writer.write( "--add-modules" );
182             writer.newLine();
183             writer.append( moduleName );
184 
185             writer.newLine();
186 
187             writer.write( "--add-reads" );
188             writer.newLine();
189             writer.append( moduleName )
190                     .append( '=' )
191                     .append( "ALL-UNNAMED" );
192 
193             writer.newLine();
194 
195             writer.write( startClassName );
196 
197             writer.newLine();
198         }
199         finally
200         {
201             if ( writer != null )
202             {
203                 writer.close();
204             }
205         }
206 
207         return surefireArgs;
208     }
209 
210     @Nonnull
211     String toModuleName( @Nonnull File moduleDescriptor ) throws IOException
212     {
213         if ( !moduleDescriptor.isFile() )
214         {
215             throw new IOException( "No such Jigsaw module-descriptor exists " + moduleDescriptor.getAbsolutePath() );
216         }
217 
218         final StringBuilder sb = new StringBuilder();
219         new ClassReader( new FileInputStream( moduleDescriptor ) ).accept( new ClassVisitor( ASM6 )
220         {
221             @Override
222             public ModuleVisitor visitModule( String name, int access, String version )
223             {
224                 sb.setLength( 0 );
225                 sb.append( name );
226                 return super.visitModule( name, access, version );
227             }
228         }, 0 );
229 
230         return sb.toString();
231     }
232 }