1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package org.eluder.coveralls.maven.plugin;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.nio.charset.Charset;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Properties;
33
34 import org.apache.maven.plugin.AbstractMojo;
35 import org.apache.maven.plugin.MojoExecutionException;
36 import org.apache.maven.plugin.MojoFailureException;
37 import org.apache.maven.plugins.annotations.Mojo;
38 import org.apache.maven.plugins.annotations.Parameter;
39 import org.apache.maven.project.MavenProject;
40 import org.apache.maven.settings.Settings;
41 import org.eluder.coveralls.maven.plugin.domain.GitRepository;
42 import org.eluder.coveralls.maven.plugin.domain.Job;
43 import org.eluder.coveralls.maven.plugin.httpclient.CoverallsClient;
44 import org.eluder.coveralls.maven.plugin.httpclient.CoverallsProxyClient;
45 import org.eluder.coveralls.maven.plugin.json.JsonWriter;
46 import org.eluder.coveralls.maven.plugin.logging.CoverageTracingLogger;
47 import org.eluder.coveralls.maven.plugin.logging.DryRunLogger;
48 import org.eluder.coveralls.maven.plugin.logging.JobLogger;
49 import org.eluder.coveralls.maven.plugin.logging.Logger;
50 import org.eluder.coveralls.maven.plugin.logging.Logger.Position;
51 import org.eluder.coveralls.maven.plugin.service.Appveyor;
52 import org.eluder.coveralls.maven.plugin.service.Bamboo;
53 import org.eluder.coveralls.maven.plugin.service.Circle;
54 import org.eluder.coveralls.maven.plugin.service.General;
55 import org.eluder.coveralls.maven.plugin.service.GitHub;
56 import org.eluder.coveralls.maven.plugin.service.Jenkins;
57 import org.eluder.coveralls.maven.plugin.service.ServiceSetup;
58 import org.eluder.coveralls.maven.plugin.service.Shippable;
59 import org.eluder.coveralls.maven.plugin.service.Travis;
60 import org.eluder.coveralls.maven.plugin.service.Wercker;
61 import org.eluder.coveralls.maven.plugin.source.SourceCallback;
62 import org.eluder.coveralls.maven.plugin.source.SourceLoader;
63 import org.eluder.coveralls.maven.plugin.source.UniqueSourceCallback;
64 import org.eluder.coveralls.maven.plugin.util.CoverageParsersFactory;
65 import org.eluder.coveralls.maven.plugin.util.SourceLoaderFactory;
66 import org.eluder.coveralls.maven.plugin.util.TimestampParser;
67
68
69
70
71 @Mojo(name = "report", threadSafe = false, aggregator = true)
72 public class CoverallsReportMojo extends AbstractMojo {
73
74
75
76
77
78
79
80
81 @Parameter(property = "jacocoAggregateReport")
82 private File jacocoAggregateReport;
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 @Parameter(property = "jacocoReports")
108 private List<File> jacocoReports;
109
110
111
112
113
114
115
116
117 @Deprecated(since = "5.0.0", forRemoval = true)
118 @Parameter(property = "coberturaReports")
119 private List<File> coberturaReports;
120
121
122
123
124 @Parameter(property = "sagaReports")
125 private List<File> sagaReports;
126
127
128
129
130 @Parameter(property = "cloverReports")
131 private List<File> cloverReports;
132
133
134
135
136 @Parameter(property = "relativeReportDirs")
137 private List<String> relativeReportDirs;
138
139
140
141
142 @Parameter(property = "coverallsFile", defaultValue = "${project.build.directory}/coveralls.json")
143 File coverallsFile;
144
145
146
147
148 @Parameter(property = "coverallsUrl", defaultValue = "https://coveralls.io/api/v1/jobs")
149 private String coverallsUrl;
150
151
152
153
154 @Parameter(property = "sourceDirectories")
155 List<File> sourceDirectories;
156
157
158
159
160 @Parameter(property = "sourceEncoding", defaultValue = "${project.build.sourceEncoding}")
161 String sourceEncoding;
162
163
164
165
166 @Parameter(property = "serviceName")
167 String serviceName;
168
169
170
171
172 @Parameter(property = "serviceJobId")
173 String serviceJobId;
174
175
176
177
178 @Parameter(property = "serviceBuildNumber")
179 String serviceBuildNumber;
180
181
182
183
184 @Parameter(property = "serviceBuildUrl")
185 String serviceBuildUrl;
186
187
188
189
190 @Parameter(property = "serviceEnvironment")
191 Properties serviceEnvironment;
192
193
194
195
196 @Parameter(property = "repoToken")
197 String repoToken;
198
199
200
201
202 @Parameter(property = "branch")
203 String branch;
204
205
206
207
208 @Parameter(property = "pullRequest")
209 String pullRequest;
210
211
212
213
214 @Parameter(property = "parallel")
215 private boolean parallel;
216
217
218
219
220 @Parameter(property = "timestampFormat", defaultValue = "${maven.build.timestamp.format}")
221 private String timestampFormat;
222
223
224
225
226
227 @Parameter(property = "timestamp", defaultValue = "${maven.build.timestamp}")
228 private String timestamp;
229
230
231
232
233 @Parameter(property = "dryRun", defaultValue = "false")
234 boolean dryRun;
235
236
237
238
239 @Parameter(property = "failOnServiceError", defaultValue = "true")
240 boolean failOnServiceError;
241
242
243
244
245 @Parameter(property = "scanForSources", defaultValue = "false")
246 private boolean scanForSources;
247
248
249
250
251 @Parameter(property = "coveralls.basedir", defaultValue = "${project.basedir}")
252 File basedir;
253
254
255
256
257 @Parameter(property = "coveralls.skip", defaultValue = "false")
258 boolean skip;
259
260
261
262
263 @Parameter(defaultValue = "${settings}", readonly = true, required = true)
264 Settings settings;
265
266
267
268
269 @Parameter(defaultValue = "${project}", readonly = true)
270 MavenProject project;
271
272
273
274
275 public CoverallsReportMojo() {
276
277 }
278
279 @Override
280 public final void execute() throws MojoExecutionException, MojoFailureException {
281 if (this.skip) {
282 this.getLog().info("Skip property set, skipping plugin execution");
283 return;
284 }
285
286 try {
287 this.createEnvironment().setup();
288
289 final var job = this.createJob();
290 job.validate().throwOrInform(this.getLog());
291
292 final var sourceLoader = this.createSourceLoader(job);
293
294 final var parsers = this.createCoverageParsers(sourceLoader);
295
296 final var client = this.createCoverallsClient();
297
298 final List<Logger> reporters = new ArrayList<>();
299 reporters.add(new JobLogger(job));
300
301 try (var writer = this.createJsonWriter(job)) {
302
303 this.coverallsFile = writer.getCoverallsFile();
304
305 final var sourceCallback = this.createSourceCallbackChain(writer, reporters);
306 reporters.add(new DryRunLogger(job.isDryRun(), this.coverallsFile));
307
308 this.report(reporters, Position.BEFORE);
309 this.writeCoveralls(writer, sourceCallback, parsers);
310 this.report(reporters, Position.AFTER);
311 }
312
313 if (!job.isDryRun()) {
314 this.submitData(client, this.coverallsFile);
315 }
316 } catch (final ProcessingException e) {
317 throw new MojoFailureException("Processing of input or output data failed", e);
318 } catch (final IOException e) {
319 throw new MojoFailureException("I/O operation failed", e);
320 } catch (final Exception e) {
321 throw new MojoExecutionException("Build error", e);
322 }
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336 protected List<CoverageParser> createCoverageParsers(final SourceLoader sourceLoader) throws IOException {
337 return new CoverageParsersFactory(this.project, sourceLoader).withJaCoCoReports(this.jacocoReports)
338 .withJacocoAggregateReport(this.jacocoAggregateReport).withCoberturaReports(this.coberturaReports)
339 .withSagaReports(this.sagaReports).withCloverReports(this.cloverReports)
340 .withRelativeReportDirs(this.relativeReportDirs).createParsers();
341 }
342
343
344
345
346
347
348
349
350
351 protected SourceLoader createSourceLoader(final Job job) {
352 return new SourceLoaderFactory(job.getGit().getBaseDir(), this.project, Charset.forName(this.sourceEncoding))
353 .withSourceDirectories(this.sourceDirectories).withScanForSources(this.scanForSources)
354 .createSourceLoader();
355 }
356
357
358
359
360
361
362 protected Environment createEnvironment() {
363 return new Environment(this, this.getServices());
364 }
365
366
367
368
369
370
371 protected List<ServiceSetup> getServices() {
372 final var env = System.getenv();
373 final List<ServiceSetup> services = new ArrayList<>();
374 services.add(new GitHub(env));
375 services.add(new Shippable(env));
376 services.add(new Travis(env));
377 services.add(new Circle(env));
378 services.add(new Jenkins(env));
379 services.add(new Bamboo(env));
380 services.add(new Appveyor(env));
381 services.add(new Wercker(env));
382 services.add(new General(env));
383 return services;
384 }
385
386
387
388
389
390
391
392
393
394
395
396 protected Job createJob() throws ProcessingException, IOException {
397 final var git = new GitRepository(this.basedir).load();
398 final var time = this.timestamp == null ? null
399 : new TimestampParser(this.timestampFormat).parse(this.timestamp).toEpochMilli();
400
401
402 this.getLog().info("Coveralls Job Configuration:");
403 this.getLog().info(" serviceName: " + this.serviceName);
404 this.getLog().info(" serviceJobId: " + this.serviceJobId);
405 this.getLog().info(" serviceBuildNumber: " + this.serviceBuildNumber);
406 this.getLog().info(" serviceBuildUrl: " + this.serviceBuildUrl);
407 this.getLog().info(" parallel: " + this.parallel);
408 this.getLog().info(" branch: " + this.branch);
409 this.getLog().info(" pullRequest: " + this.pullRequest);
410 this.getLog().info(" timestamp: " + time);
411 this.getLog().info(" basedir: " + (this.basedir != null ? this.basedir.getAbsolutePath() : "null"));
412 this.getLog().info(" sourceEncoding: " + this.sourceEncoding);
413 this.getLog().info(
414 " coverallsFile: " + (this.coverallsFile != null ? this.coverallsFile.getAbsolutePath() : "null"));
415 this.getLog().info(" coverallsUrl: " + this.coverallsUrl);
416
417 return new Job().withRepoToken(this.repoToken).withServiceName(this.serviceName)
418 .withServiceJobId(this.serviceJobId).withServiceBuildNumber(this.serviceBuildNumber)
419 .withServiceBuildUrl(this.serviceBuildUrl).withParallel(this.parallel)
420 .withServiceEnvironment(this.serviceEnvironment).withDryRun(this.dryRun).withBranch(this.branch)
421 .withPullRequest(this.pullRequest).withTimestamp(time).withGit(git);
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435 protected JsonWriter createJsonWriter(final Job job) throws IOException {
436 return new JsonWriter(job, this.coverallsFile);
437 }
438
439
440
441
442
443
444 protected CoverallsClient createCoverallsClient() {
445 return new CoverallsProxyClient(this.coverallsUrl, this.settings.getActiveProxy());
446 }
447
448
449
450
451
452
453
454
455
456
457
458 protected SourceCallback createSourceCallbackChain(final JsonWriter writer, final List<Logger> reporters) {
459 SourceCallback chain = writer;
460 if (this.getLog().isInfoEnabled()) {
461 final var coverageTracingReporter = new CoverageTracingLogger(chain);
462 chain = coverageTracingReporter;
463 reporters.add(coverageTracingReporter);
464 }
465 return new UniqueSourceCallback(chain);
466 }
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483 protected void writeCoveralls(final JsonWriter writer, final SourceCallback sourceCallback,
484 final List<CoverageParser> parsers) throws ProcessingException, IOException {
485 this.getLog().info("Writing Coveralls data to " + this.coverallsFile.getAbsolutePath() + "...");
486 final var now = System.currentTimeMillis();
487 sourceCallback.onBegin();
488 for (final CoverageParser parser : parsers) {
489 this.getLog().info("Processing coverage report from " + parser.getCoverageFile().getAbsolutePath());
490 parser.parse(sourceCallback);
491 }
492 sourceCallback.onComplete();
493 final var duration = System.currentTimeMillis() - now;
494 this.getLog().info("Successfully wrote Coveralls data in " + duration + "ms");
495 }
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510 private void submitData(final CoverallsClient client, final File coverallsFile)
511 throws ProcessingException, IOException {
512 this.getLog().info("Submitting Coveralls data to API");
513 final var now = System.currentTimeMillis();
514 try {
515 final var response = client.submit(coverallsFile);
516 final var duration = System.currentTimeMillis() - now;
517 this.getLog()
518 .info("Successfully submitted Coveralls data in " + duration + "ms for " + response.getMessage());
519 this.getLog().info(response.getUrl());
520 this.getLog().info("*** Coverage results are usually available immediately on Coveralls.");
521 this.getLog().info(" If you see question marks or missing data, please allow some time for processing.");
522 } catch (final ProcessingException e) {
523 final var duration = System.currentTimeMillis() - now;
524 final var message = "Submission failed in " + duration + "ms while processing data";
525 this.handleSubmissionError(e, message, true);
526 } catch (final IOException e) {
527 final var duration = System.currentTimeMillis() - now;
528 final var message = "Submission failed in " + duration + "ms while handling I/O operations";
529 this.handleSubmissionError(e, message, this.failOnServiceError);
530 } catch (final InterruptedException e) {
531 final var duration = System.currentTimeMillis() - now;
532 final var message = "Submission failed in " + duration + "ms due to an interuption";
533 this.getLog().error(message, e);
534 Thread.currentThread().interrupt();
535 }
536 }
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553 private <T extends Exception> void handleSubmissionError(final T ex, final String message,
554 final boolean failOnException) throws T {
555 if (failOnException) {
556 this.getLog().error(message);
557 throw ex;
558 }
559 this.getLog().warn(message);
560 }
561
562
563
564
565
566
567
568
569
570 private void report(final List<Logger> reporters, final Position position) {
571 for (final Logger reporter : reporters) {
572 if (position.equals(reporter.getPosition())) {
573 reporter.log(this.getLog());
574 }
575 }
576 }
577 }