1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.cling.invoker.mvn;
20
21 import java.io.FileNotFoundException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.util.ArrayList;
25 import java.util.Date;
26 import java.util.HashMap;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.function.Consumer;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 import org.apache.maven.InternalErrorException;
35 import org.apache.maven.Maven;
36 import org.apache.maven.api.Constants;
37 import org.apache.maven.api.cli.InvokerRequest;
38 import org.apache.maven.api.cli.Logger;
39 import org.apache.maven.api.cli.mvn.MavenOptions;
40 import org.apache.maven.api.services.BuilderProblem;
41 import org.apache.maven.api.services.Lookup;
42 import org.apache.maven.api.services.SettingsBuilderRequest;
43 import org.apache.maven.api.services.SettingsBuilderResult;
44 import org.apache.maven.api.services.Source;
45 import org.apache.maven.api.services.ToolchainsBuilder;
46 import org.apache.maven.api.services.ToolchainsBuilderRequest;
47 import org.apache.maven.api.services.ToolchainsBuilderResult;
48 import org.apache.maven.api.services.model.ModelProcessor;
49 import org.apache.maven.cling.event.ExecutionEventLogger;
50 import org.apache.maven.cling.invoker.LookupInvoker;
51 import org.apache.maven.cling.invoker.ProtoLookup;
52 import org.apache.maven.cling.invoker.Utils;
53 import org.apache.maven.cling.transfer.ConsoleMavenTransferListener;
54 import org.apache.maven.cling.transfer.QuietMavenTransferListener;
55 import org.apache.maven.cling.transfer.SimplexTransferListener;
56 import org.apache.maven.cling.transfer.Slf4jMavenTransferListener;
57 import org.apache.maven.cling.utils.CLIReportingUtils;
58 import org.apache.maven.eventspy.internal.EventSpyDispatcher;
59 import org.apache.maven.exception.DefaultExceptionHandler;
60 import org.apache.maven.exception.ExceptionHandler;
61 import org.apache.maven.exception.ExceptionSummary;
62 import org.apache.maven.execution.DefaultMavenExecutionRequest;
63 import org.apache.maven.execution.ExecutionListener;
64 import org.apache.maven.execution.MavenExecutionRequest;
65 import org.apache.maven.execution.MavenExecutionRequestPopulator;
66 import org.apache.maven.execution.MavenExecutionResult;
67 import org.apache.maven.execution.ProfileActivation;
68 import org.apache.maven.execution.ProjectActivation;
69 import org.apache.maven.jline.MessageUtils;
70 import org.apache.maven.lifecycle.LifecycleExecutionException;
71 import org.apache.maven.logging.BuildEventListener;
72 import org.apache.maven.logging.LoggingExecutionListener;
73 import org.apache.maven.logging.MavenTransferListener;
74 import org.apache.maven.logging.ProjectBuildLogAppender;
75 import org.apache.maven.logging.SimpleBuildEventListener;
76 import org.apache.maven.project.MavenProject;
77 import org.codehaus.plexus.PlexusContainer;
78 import org.eclipse.aether.DefaultRepositoryCache;
79 import org.eclipse.aether.transfer.TransferListener;
80
81 import static java.util.Comparator.comparing;
82 import static org.apache.maven.cling.invoker.Utils.toProperties;
83
84
85
86
87
88
89 public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker<C> {
90 public MavenInvoker(ProtoLookup protoLookup) {
91 super(protoLookup);
92 }
93
94 @Override
95 protected int execute(C context) throws Exception {
96 MavenExecutionRequest request = prepareMavenExecutionRequest();
97 toolchains(context, request);
98 populateRequest(context, context.lookup, request);
99 return doExecute(context, request);
100 }
101
102 protected MavenExecutionRequest prepareMavenExecutionRequest() throws Exception {
103
104 DefaultMavenExecutionRequest mavenExecutionRequest = new DefaultMavenExecutionRequest();
105 mavenExecutionRequest.setRepositoryCache(new DefaultRepositoryCache());
106 mavenExecutionRequest.setInteractiveMode(true);
107 mavenExecutionRequest.setCacheTransferError(false);
108 mavenExecutionRequest.setIgnoreInvalidArtifactDescriptor(true);
109 mavenExecutionRequest.setIgnoreMissingArtifactDescriptor(true);
110 mavenExecutionRequest.setRecursive(true);
111 mavenExecutionRequest.setReactorFailureBehavior(MavenExecutionRequest.REACTOR_FAIL_FAST);
112 mavenExecutionRequest.setStartTime(new Date());
113 mavenExecutionRequest.setLoggingLevel(MavenExecutionRequest.LOGGING_LEVEL_INFO);
114 mavenExecutionRequest.setDegreeOfConcurrency(1);
115 mavenExecutionRequest.setBuilderId("singlethreaded");
116 return mavenExecutionRequest;
117 }
118
119 @Override
120 protected void lookup(C context) throws Exception {
121 context.eventSpyDispatcher = context.lookup.lookup(EventSpyDispatcher.class);
122 context.maven = context.lookup.lookup(Maven.class);
123 }
124
125 @Override
126 protected void init(C context) throws Exception {
127 super.init(context);
128 InvokerRequest invokerRequest = context.invokerRequest;
129 Map<String, Object> data = new HashMap<>();
130 data.put("plexus", context.lookup.lookup(PlexusContainer.class));
131 data.put("workingDirectory", invokerRequest.cwd().toString());
132 data.put("systemProperties", toProperties(context.protoSession.getSystemProperties()));
133 data.put("userProperties", toProperties(context.protoSession.getUserProperties()));
134 data.put("versionProperties", CLIReportingUtils.getBuildProperties());
135 context.eventSpyDispatcher.init(() -> data);
136 }
137
138 @Override
139 protected void postCommands(C context) throws Exception {
140 super.postCommands(context);
141
142 InvokerRequest invokerRequest = context.invokerRequest;
143 MavenOptions options = (MavenOptions) invokerRequest.options();
144 Logger logger = context.logger;
145 if (options.relaxedChecksums().orElse(false)) {
146 logger.info("Disabling strict checksum verification on all artifact downloads.");
147 } else if (options.strictChecksums().orElse(false)) {
148 logger.info("Enabling strict checksum verification on all artifact downloads.");
149 }
150 }
151
152 @Override
153 protected void configureLogging(C context) throws Exception {
154 super.configureLogging(context);
155
156 ProjectBuildLogAppender projectBuildLogAppender =
157 new ProjectBuildLogAppender(determineBuildEventListener(context));
158 context.closeables.add(projectBuildLogAppender);
159 }
160
161 protected BuildEventListener determineBuildEventListener(C context) {
162 if (context.buildEventListener == null) {
163 context.buildEventListener = doDetermineBuildEventListener(context);
164 }
165 return context.buildEventListener;
166 }
167
168 protected BuildEventListener doDetermineBuildEventListener(C context) {
169 Consumer<String> writer = determineWriter(context);
170 return new SimpleBuildEventListener(writer);
171 }
172
173 @Override
174 protected void customizeSettingsRequest(C context, SettingsBuilderRequest settingsBuilderRequest) {
175 if (context.eventSpyDispatcher != null) {
176 context.eventSpyDispatcher.onEvent(settingsBuilderRequest);
177 }
178 }
179
180 @Override
181 protected void customizeSettingsResult(C context, SettingsBuilderResult settingsBuilderResult) throws Exception {
182 if (context.eventSpyDispatcher != null) {
183 context.eventSpyDispatcher.onEvent(settingsBuilderResult);
184 }
185 }
186
187 protected void toolchains(C context, MavenExecutionRequest request) throws Exception {
188 Path userToolchainsFile = null;
189
190 if (context.invokerRequest.options().altUserToolchains().isPresent()) {
191 userToolchainsFile = context.cwdResolver.apply(
192 context.invokerRequest.options().altUserToolchains().get());
193
194 if (!Files.isRegularFile(userToolchainsFile)) {
195 throw new FileNotFoundException(
196 "The specified user toolchains file does not exist: " + userToolchainsFile);
197 }
198 } else {
199 String userToolchainsFileStr =
200 context.protoSession.getUserProperties().get(Constants.MAVEN_USER_TOOLCHAINS);
201 if (userToolchainsFileStr != null) {
202 userToolchainsFile = context.cwdResolver.apply(userToolchainsFileStr);
203 }
204 }
205
206 Path installationToolchainsFile = null;
207
208 if (context.invokerRequest.options().altInstallationToolchains().isPresent()) {
209 installationToolchainsFile = context.cwdResolver.apply(
210 context.invokerRequest.options().altInstallationToolchains().get());
211
212 if (!Files.isRegularFile(installationToolchainsFile)) {
213 throw new FileNotFoundException(
214 "The specified installation toolchains file does not exist: " + installationToolchainsFile);
215 }
216 } else {
217 String installationToolchainsFileStr =
218 context.protoSession.getUserProperties().get(Constants.MAVEN_INSTALLATION_TOOLCHAINS);
219 if (installationToolchainsFileStr != null) {
220 installationToolchainsFile = context.cwdResolver.apply(installationToolchainsFileStr);
221 }
222 }
223
224 request.setInstallationToolchainsFile(
225 installationToolchainsFile != null ? installationToolchainsFile.toFile() : null);
226 request.setUserToolchainsFile(userToolchainsFile != null ? userToolchainsFile.toFile() : null);
227
228 ToolchainsBuilderRequest toolchainsRequest = ToolchainsBuilderRequest.builder()
229 .session(context.protoSession)
230 .installationToolchainsSource(
231 installationToolchainsFile != null && Files.isRegularFile(installationToolchainsFile)
232 ? Source.fromPath(installationToolchainsFile)
233 : null)
234 .userToolchainsSource(
235 userToolchainsFile != null && Files.isRegularFile(userToolchainsFile)
236 ? Source.fromPath(userToolchainsFile)
237 : null)
238 .build();
239
240 context.eventSpyDispatcher.onEvent(toolchainsRequest);
241
242 context.logger.debug("Reading installation toolchains from '" + installationToolchainsFile + "'");
243 context.logger.debug("Reading user toolchains from '" + userToolchainsFile + "'");
244
245 ToolchainsBuilderResult toolchainsResult =
246 context.lookup.lookup(ToolchainsBuilder.class).build(toolchainsRequest);
247
248 context.eventSpyDispatcher.onEvent(toolchainsResult);
249
250 context.lookup
251 .lookup(MavenExecutionRequestPopulator.class)
252 .populateFromToolchains(
253 request,
254 new org.apache.maven.toolchain.model.PersistedToolchains(
255 toolchainsResult.getEffectiveToolchains()));
256
257 if (!toolchainsResult.getProblems().isEmpty()) {
258 context.logger.warn("");
259 context.logger.warn("Some problems were encountered while building the effective toolchains");
260
261 for (BuilderProblem problem : toolchainsResult.getProblems()) {
262 context.logger.warn(problem.getMessage() + " @ " + problem.getLocation());
263 }
264
265 context.logger.warn("");
266 }
267 }
268
269 @Override
270 protected void populateRequest(C context, Lookup lookup, MavenExecutionRequest request) throws Exception {
271 super.populateRequest(context, lookup, request);
272 if (context.invokerRequest.rootDirectory().isEmpty()) {
273
274 request.setMultiModuleProjectDirectory(
275 context.invokerRequest.topDirectory().toFile());
276 request.setRootDirectory(context.invokerRequest.topDirectory());
277 }
278
279 MavenOptions options = (MavenOptions) context.invokerRequest.options();
280 request.setNoSnapshotUpdates(options.suppressSnapshotUpdates().orElse(false));
281 request.setGoals(options.goals().orElse(List.of()));
282 request.setReactorFailureBehavior(determineReactorFailureBehaviour(context));
283 request.setRecursive(!options.nonRecursive().orElse(!request.isRecursive()));
284 request.setOffline(options.offline().orElse(request.isOffline()));
285 request.setUpdateSnapshots(options.updateSnapshots().orElse(false));
286 request.setGlobalChecksumPolicy(determineGlobalChecksumPolicy(context));
287
288 Path pom = determinePom(context, lookup);
289 if (pom != null) {
290 request.setPom(pom.toFile());
291 if (pom.getParent() != null) {
292 request.setBaseDirectory(pom.getParent().toFile());
293 }
294
295
296 if (context.invokerRequest.rootDirectory().isEmpty()) {
297 Path rootDirectory = Utils.findMandatoryRoot(context.invokerRequest.topDirectory());
298 request.setMultiModuleProjectDirectory(rootDirectory.toFile());
299 request.setRootDirectory(rootDirectory);
300 }
301 }
302
303 request.setTransferListener(
304 determineTransferListener(context, options.noTransferProgress().orElse(false)));
305 request.setExecutionListener(determineExecutionListener(context));
306
307 request.setResumeFrom(options.resumeFrom().orElse(null));
308 request.setResume(options.resume().orElse(false));
309 request.setMakeBehavior(determineMakeBehavior(context));
310 request.setCacheNotFound(options.cacheArtifactNotFound().orElse(true));
311 request.setCacheTransferError(false);
312
313 if (options.strictArtifactDescriptorPolicy().orElse(false)) {
314 request.setIgnoreMissingArtifactDescriptor(false);
315 request.setIgnoreInvalidArtifactDescriptor(false);
316 } else {
317 request.setIgnoreMissingArtifactDescriptor(true);
318 request.setIgnoreInvalidArtifactDescriptor(true);
319 }
320
321 request.setIgnoreTransitiveRepositories(
322 options.ignoreTransitiveRepositories().orElse(false));
323
324 performProjectActivation(context, request.getProjectActivation());
325 performProfileActivation(context, request.getProfileActivation());
326
327
328
329
330
331
332
333
334
335 if (options.threads().isPresent()) {
336 int degreeOfConcurrency =
337 calculateDegreeOfConcurrency(options.threads().get());
338 if (degreeOfConcurrency > 1) {
339 request.setBuilderId("multithreaded");
340 request.setDegreeOfConcurrency(degreeOfConcurrency);
341 }
342 }
343
344
345
346
347 if (options.builder().isPresent()) {
348 request.setBuilderId(options.builder().get());
349 }
350 }
351
352 protected Path determinePom(C context, Lookup lookup) {
353 Path current = context.invokerRequest.cwd();
354 MavenOptions options = (MavenOptions) context.invokerRequest.options();
355 if (options.alternatePomFile().isPresent()) {
356 current = context.cwdResolver.apply(options.alternatePomFile().get());
357 }
358 ModelProcessor modelProcessor =
359 lookup.lookupOptional(ModelProcessor.class).orElse(null);
360 if (modelProcessor != null) {
361 return modelProcessor.locateExistingPom(current);
362 } else {
363 return Files.isRegularFile(current) ? current : null;
364 }
365 }
366
367 protected String determineReactorFailureBehaviour(C context) {
368 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
369 if (mavenOptions.failFast().isPresent()) {
370 return MavenExecutionRequest.REACTOR_FAIL_FAST;
371 } else if (mavenOptions.failAtEnd().isPresent()) {
372 return MavenExecutionRequest.REACTOR_FAIL_AT_END;
373 } else if (mavenOptions.failNever().isPresent()) {
374 return MavenExecutionRequest.REACTOR_FAIL_NEVER;
375 } else {
376 return MavenExecutionRequest.REACTOR_FAIL_FAST;
377 }
378 }
379
380 protected String determineGlobalChecksumPolicy(C context) {
381 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
382 if (mavenOptions.strictChecksums().orElse(false)) {
383 return MavenExecutionRequest.CHECKSUM_POLICY_FAIL;
384 } else if (mavenOptions.relaxedChecksums().orElse(false)) {
385 return MavenExecutionRequest.CHECKSUM_POLICY_WARN;
386 } else {
387 return null;
388 }
389 }
390
391 protected ExecutionListener determineExecutionListener(C context) {
392 ExecutionListener listener = new ExecutionEventLogger(context.invokerRequest.messageBuilderFactory());
393 if (context.eventSpyDispatcher != null) {
394 listener = context.eventSpyDispatcher.chainListener(listener);
395 }
396 return new LoggingExecutionListener(listener, determineBuildEventListener(context));
397 }
398
399 protected TransferListener determineTransferListener(C context, boolean noTransferProgress) {
400 boolean quiet = context.invokerRequest.options().quiet().orElse(false);
401 boolean logFile = context.invokerRequest.options().logFile().isPresent();
402 boolean runningOnCI = isRunningOnCI(context);
403 boolean quietCI = runningOnCI
404 && !context.invokerRequest.options().forceInteractive().orElse(false);
405
406 TransferListener delegate;
407 if (quiet || noTransferProgress || quietCI) {
408 delegate = new QuietMavenTransferListener();
409 } else if (context.interactive && !logFile) {
410 delegate = new SimplexTransferListener(new ConsoleMavenTransferListener(
411 context.invokerRequest.messageBuilderFactory(),
412 context.terminal.writer(),
413 context.invokerRequest.options().verbose().orElse(false)));
414 } else {
415 delegate = new Slf4jMavenTransferListener();
416 }
417 return new MavenTransferListener(delegate, determineBuildEventListener(context));
418 }
419
420 protected String determineMakeBehavior(C context) {
421 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
422 if (mavenOptions.alsoMake().isPresent()
423 && mavenOptions.alsoMakeDependents().isEmpty()) {
424 return MavenExecutionRequest.REACTOR_MAKE_UPSTREAM;
425 } else if (mavenOptions.alsoMake().isEmpty()
426 && mavenOptions.alsoMakeDependents().isPresent()) {
427 return MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM;
428 } else if (mavenOptions.alsoMake().isPresent()
429 && mavenOptions.alsoMakeDependents().isPresent()) {
430 return MavenExecutionRequest.REACTOR_MAKE_BOTH;
431 } else {
432 return null;
433 }
434 }
435
436 protected void performProjectActivation(C context, ProjectActivation projectActivation) {
437 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
438 if (mavenOptions.projects().isPresent()
439 && !mavenOptions.projects().get().isEmpty()) {
440 List<String> optionValues = mavenOptions.projects().get();
441 for (final String optionValue : optionValues) {
442 for (String token : optionValue.split(",")) {
443 String selector = token.trim();
444 boolean active = true;
445 if (!selector.isEmpty()) {
446 if (selector.charAt(0) == '-' || selector.charAt(0) == '!') {
447 active = false;
448 selector = selector.substring(1);
449 } else if (token.charAt(0) == '+') {
450 selector = selector.substring(1);
451 }
452 }
453 boolean optional = false;
454 if (!selector.isEmpty() && selector.charAt(0) == '?') {
455 optional = true;
456 selector = selector.substring(1);
457 }
458 projectActivation.addProjectActivation(selector, active, optional);
459 }
460 }
461 }
462 }
463
464 protected void performProfileActivation(C context, ProfileActivation profileActivation) {
465 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
466 if (mavenOptions.activatedProfiles().isPresent()
467 && !mavenOptions.activatedProfiles().get().isEmpty()) {
468 List<String> optionValues = mavenOptions.activatedProfiles().get();
469 for (final String optionValue : optionValues) {
470 for (String token : optionValue.split(",")) {
471 String profileId = token.trim();
472 boolean active = true;
473 if (!profileId.isEmpty()) {
474 if (profileId.charAt(0) == '-' || profileId.charAt(0) == '!') {
475 active = false;
476 profileId = profileId.substring(1);
477 } else if (token.charAt(0) == '+') {
478 profileId = profileId.substring(1);
479 }
480 }
481 boolean optional = false;
482 if (!profileId.isEmpty() && profileId.charAt(0) == '?') {
483 optional = true;
484 profileId = profileId.substring(1);
485 }
486 profileActivation.addProfileActivation(profileId, active, optional);
487 }
488 }
489 }
490 }
491
492 protected int doExecute(C context, MavenExecutionRequest request) throws Exception {
493 context.eventSpyDispatcher.onEvent(request);
494
495 MavenExecutionResult result;
496 try {
497 result = context.maven.execute(request);
498 context.eventSpyDispatcher.onEvent(result);
499 } finally {
500 context.eventSpyDispatcher.close();
501 }
502
503 if (result.hasExceptions()) {
504 ExceptionHandler handler = new DefaultExceptionHandler();
505 Map<String, String> references = new LinkedHashMap<>();
506 List<MavenProject> failedProjects = new ArrayList<>();
507
508 for (Throwable exception : result.getExceptions()) {
509 ExceptionSummary summary = handler.handleException(exception);
510 logSummary(context, summary, references, "");
511
512 if (exception instanceof LifecycleExecutionException) {
513 failedProjects.add(((LifecycleExecutionException) exception).getProject());
514 }
515 }
516
517 context.logger.error("");
518
519 if (!context.invokerRequest.options().showErrors().orElse(false)) {
520 context.logger.error("To see the full stack trace of the errors, re-run Maven with the '"
521 + MessageUtils.builder().strong("-e") + "' switch");
522 }
523 if (!context.invokerRequest.options().verbose().orElse(false)) {
524 context.logger.error("Re-run Maven using the '"
525 + MessageUtils.builder().strong("-X") + "' switch to enable verbose output");
526 }
527
528 if (!references.isEmpty()) {
529 context.logger.error("");
530 context.logger.error("For more information about the errors and possible solutions"
531 + ", please read the following articles:");
532
533 for (Map.Entry<String, String> entry : references.entrySet()) {
534 context.logger.error(MessageUtils.builder().strong(entry.getValue()) + " " + entry.getKey());
535 }
536 }
537
538 if (result.canResume()) {
539 logBuildResumeHint(context, "mvn [args] -r");
540 } else if (!failedProjects.isEmpty()) {
541 List<MavenProject> sortedProjects = result.getTopologicallySortedProjects();
542
543
544 failedProjects.sort(comparing(sortedProjects::indexOf));
545
546 MavenProject firstFailedProject = failedProjects.get(0);
547 if (!firstFailedProject.equals(sortedProjects.get(0))) {
548 String resumeFromSelector = getResumeFromSelector(sortedProjects, firstFailedProject);
549 logBuildResumeHint(context, "mvn [args] -rf " + resumeFromSelector);
550 }
551 }
552
553 if (((MavenOptions) context.invokerRequest.options()).failNever().orElse(false)) {
554 context.logger.info("Build failures were ignored.");
555 return 0;
556 } else {
557 return 1;
558 }
559 } else {
560 return 0;
561 }
562 }
563
564 protected void logBuildResumeHint(C context, String resumeBuildHint) {
565 context.logger.error("");
566 context.logger.error("After correcting the problems, you can resume the build with the command");
567 context.logger.error(
568 MessageUtils.builder().a(" ").strong(resumeBuildHint).toString());
569 }
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588 protected String getResumeFromSelector(List<MavenProject> mavenProjects, MavenProject firstFailedProject) {
589 boolean hasOverlappingArtifactId = mavenProjects.stream()
590 .filter(project -> firstFailedProject.getArtifactId().equals(project.getArtifactId()))
591 .count()
592 > 1;
593
594 if (hasOverlappingArtifactId) {
595 return firstFailedProject.getGroupId() + ":" + firstFailedProject.getArtifactId();
596 }
597
598 return ":" + firstFailedProject.getArtifactId();
599 }
600
601 protected static final Pattern NEXT_LINE = Pattern.compile("\r?\n");
602
603 protected static final Pattern LAST_ANSI_SEQUENCE = Pattern.compile("(\u001B\\[[;\\d]*[ -/]*[@-~])[^\u001B]*$");
604
605 protected static final String ANSI_RESET = "\u001B\u005Bm";
606
607 protected void logSummary(C context, ExceptionSummary summary, Map<String, String> references, String indent) {
608 String referenceKey = "";
609
610 if (summary.getReference() != null && !summary.getReference().isEmpty()) {
611 referenceKey =
612 references.computeIfAbsent(summary.getReference(), k -> "[Help " + (references.size() + 1) + "]");
613 }
614
615 String msg = summary.getMessage();
616
617 if (!referenceKey.isEmpty()) {
618 if (msg.indexOf('\n') < 0) {
619 msg += " -> " + MessageUtils.builder().strong(referenceKey);
620 } else {
621 msg += "\n-> " + MessageUtils.builder().strong(referenceKey);
622 }
623 }
624
625 String[] lines = NEXT_LINE.split(msg);
626 String currentColor = "";
627
628 for (int i = 0; i < lines.length; i++) {
629
630 String line = currentColor + lines[i];
631
632
633 Matcher matcher = LAST_ANSI_SEQUENCE.matcher(line);
634 String nextColor = "";
635 if (matcher.find()) {
636 nextColor = matcher.group(1);
637 if (ANSI_RESET.equals(nextColor)) {
638
639 nextColor = "";
640 }
641 }
642
643
644 line = indent + line + ("".equals(nextColor) ? "" : ANSI_RESET);
645
646 if ((i == lines.length - 1)
647 && (context.invokerRequest.options().showErrors().orElse(false)
648 || (summary.getException() instanceof InternalErrorException))) {
649 context.logger.error(line, summary.getException());
650 } else {
651 context.logger.error(line);
652 }
653
654 currentColor = nextColor;
655 }
656
657 indent += " ";
658
659 for (ExceptionSummary child : summary.getChildren()) {
660 logSummary(context, child, references, indent);
661 }
662 }
663 }