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