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