View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.surefire.booterclient;
20  
21  import javax.annotation.Nonnegative;
22  import javax.annotation.Nonnull;
23  import javax.annotation.Nullable;
24  
25  import java.io.File;
26  import java.io.FileWriter;
27  import java.io.IOException;
28  import java.util.Collection;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Properties;
33  
34  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Commandline;
35  import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
36  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
37  import org.apache.maven.surefire.api.util.TempFileManager;
38  import org.apache.maven.surefire.booter.AbstractPathConfiguration;
39  import org.apache.maven.surefire.booter.Classpath;
40  import org.apache.maven.surefire.booter.ModularClasspath;
41  import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
42  import org.apache.maven.surefire.booter.StartupConfiguration;
43  import org.apache.maven.surefire.booter.SurefireBooterForkException;
44  import org.apache.maven.surefire.extensions.ForkNodeFactory;
45  
46  import static java.io.File.pathSeparatorChar;
47  import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
48  import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
49  import static org.apache.maven.surefire.shared.utils.StringUtils.replace;
50  
51  /**
52   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
53   * @since 2.21.0.Jigsaw
54   */
55  public class ModularClasspathForkConfiguration extends DefaultForkConfiguration {
56      @SuppressWarnings("checkstyle:parameternumber")
57      public ModularClasspathForkConfiguration(
58              @Nonnull Classpath bootClasspath,
59              @Nonnull File tempDirectory,
60              @Nullable String debugLine,
61              @Nonnull File workingDirectory,
62              @Nonnull Properties modelProperties,
63              @Nullable String argLine,
64              @Nonnull Map<String, String> environmentVariables,
65              @Nonnull String[] excludedEnvironmentVariables,
66              boolean debug,
67              @Nonnegative int forkCount,
68              boolean reuseForks,
69              @Nonnull Platform pluginPlatform,
70              @Nonnull ConsoleLogger log,
71              @Nonnull ForkNodeFactory forkNodeFactory) {
72          super(
73                  bootClasspath,
74                  tempDirectory,
75                  debugLine,
76                  workingDirectory,
77                  modelProperties,
78                  argLine,
79                  environmentVariables,
80                  excludedEnvironmentVariables,
81                  debug,
82                  forkCount,
83                  reuseForks,
84                  pluginPlatform,
85                  log,
86                  forkNodeFactory);
87      }
88  
89      @Override
90      protected void resolveClasspath(
91              @Nonnull Commandline cli,
92              @Nonnull String startClass,
93              @Nonnull StartupConfiguration config,
94              @Nonnull File dumpLogDirectory)
95              throws SurefireBooterForkException {
96          try {
97              AbstractPathConfiguration pathConfig = config.getClasspathConfiguration();
98  
99              ModularClasspathConfiguration modularClasspathConfiguration =
100                     pathConfig.toRealPath(ModularClasspathConfiguration.class);
101 
102             ModularClasspath modularClasspath = modularClasspathConfiguration.getModularClasspath();
103 
104             boolean isMainDescriptor = modularClasspath.isMainDescriptor();
105             String moduleName = modularClasspath.getModuleNameFromDescriptor();
106             List<String> modulePath = modularClasspath.getModulePath();
107             Collection<String> packages = modularClasspath.getPackages();
108             File patchFile = modularClasspath.getPatchFile();
109             List<String> classpath = toCompleteClasspath(config);
110 
111             File argsFile = createArgsFile(
112                     moduleName,
113                     modulePath,
114                     classpath,
115                     packages,
116                     patchFile,
117                     startClass,
118                     isMainDescriptor,
119                     config.getJpmsArguments());
120 
121             cli.createArg().setValue("@" + escapeToPlatformPath(argsFile.getAbsolutePath()));
122         } catch (IOException e) {
123             String error = "Error creating args file";
124             InPluginProcessDumpSingleton.getSingleton().dumpException(e, error, dumpLogDirectory);
125             throw new SurefireBooterForkException(error, e);
126         }
127     }
128 
129     @Nonnull
130     File createArgsFile(
131             @Nonnull String moduleName,
132             @Nonnull List<String> modulePath,
133             @Nonnull List<String> classPath,
134             @Nonnull Collection<String> packages,
135             File patchFile,
136             @Nonnull String startClassName,
137             boolean isMainDescriptor,
138             @Nonnull List<String[]> providerJpmsArguments)
139             throws IOException {
140         File surefireArgs = TempFileManager.instance(getTempDirectory()).createTempFile("surefireargs", "");
141         if (isDebug()) {
142             getLogger().debug("Path to args file: " + surefireArgs.getCanonicalPath());
143         } else {
144             surefireArgs.deleteOnExit();
145         }
146 
147         try (FileWriter io = new FileWriter(surefireArgs)) {
148             StringBuilder args = new StringBuilder(64 * 1024);
149             if (!modulePath.isEmpty()) {
150                 // https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-4856361B-8BFD-4964-AE84-121F5F6CF111
151                 args.append("--module-path").append(NL).append('"');
152 
153                 for (Iterator<String> it = modulePath.iterator(); it.hasNext(); ) {
154                     args.append(replace(it.next(), "\\", "\\\\"));
155                     if (it.hasNext()) {
156                         args.append(pathSeparatorChar);
157                     }
158                 }
159 
160                 args.append('"').append(NL);
161             }
162 
163             if (!classPath.isEmpty()) {
164                 args.append("--class-path").append(NL).append('"');
165 
166                 for (Iterator<String> it = classPath.iterator(); it.hasNext(); ) {
167                     args.append(replace(it.next(), "\\", "\\\\"));
168                     if (it.hasNext()) {
169                         args.append(pathSeparatorChar);
170                     }
171                 }
172 
173                 args.append('"').append(NL);
174             }
175 
176             if (isMainDescriptor) {
177                 args.append("--patch-module")
178                         .append(NL)
179                         .append(moduleName)
180                         .append('=')
181                         .append('"')
182                         .append(replace(patchFile.getPath(), "\\", "\\\\"))
183                         .append('"')
184                         .append(NL);
185 
186                 for (String pkg : packages) {
187                     args.append("--add-opens")
188                             .append(NL)
189                             .append(moduleName)
190                             .append('/')
191                             .append(pkg)
192                             .append('=')
193                             .append("ALL-UNNAMED")
194                             .append(NL);
195                 }
196 
197                 args.append("--add-reads")
198                         .append(NL)
199                         .append(moduleName)
200                         .append('=')
201                         .append("ALL-UNNAMED")
202                         .append(NL);
203             }
204 
205             args.append("--add-modules").append(NL).append("ALL-MODULE-PATH").append(NL);
206 
207             for (String[] entries : providerJpmsArguments) {
208                 for (String entry : entries) {
209                     args.append(entry).append(NL);
210                 }
211             }
212 
213             args.append(startClassName);
214 
215             String argsFileContent = args.toString();
216 
217             if (isDebug()) {
218                 getLogger().debug("args file content:" + NL + argsFileContent);
219             }
220 
221             io.write(argsFileContent);
222 
223             return surefireArgs;
224         }
225     }
226 }