
org.itsallcode.openfasttrace.maven.TraceMojo Maven / Gradle / Ivy
package org.itsallcode.openfasttrace.maven;
import java.io.*;
import java.nio.file.*;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.*;
import org.apache.maven.project.*;
import org.itsallcode.openfasttrace.api.ReportSettings;
import org.itsallcode.openfasttrace.api.core.*;
import org.itsallcode.openfasttrace.api.importer.ImportSettings;
import org.itsallcode.openfasttrace.api.report.ReportVerbosity;
import org.itsallcode.openfasttrace.core.Oft;
import org.itsallcode.openfasttrace.core.OftRunner;
/**
* Trace requirements using OpenFastTrace
*/
@Mojo(name = "trace", defaultPhase = LifecyclePhase.VERIFY)
public class TraceMojo extends AbstractMojo
{
/**
* Location of the directory where the reports are generated.
*
* Default: ${project.build.directory}
*/
@Parameter(defaultValue = "${project.build.directory}", property = "outputDirectory", required = true)
private File outputDirectory;
/**
* Let build fail when tracing fails.
*
* Default: true
*/
@Parameter(defaultValue = "true", property = "failBuild", required = true)
private boolean failBuild;
/**
* The report output format, e.g. plain
or html
.
*
* Default: html
*/
@Parameter(defaultValue = "html", property = "reportOutputFormat", required = true)
private String reportOutputFormat;
/**
* The report verbosity
*
* QUIET
* MINIMAL
* SUMMARY
* FAILURES
* FAILURE_SUMMARIES
* FAILURE_DETAILS
* ALL
*
*
* Default: FAILURE_DETAILS
*/
@Parameter(defaultValue = "FAILURE_DETAILS", property = "reportVerbosity", required = true)
private ReportVerbosity reportVerbosity;
/**
* Show the origin in the tracing report.
*
* Default: false
*/
@Parameter(defaultValue = "false", property = "reportShowOrigin", required = true)
private boolean reportShowOrigin;
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
@Component
private ProjectBuilder mavenProjectBuilder;
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession session;
@Override
public void execute() throws MojoFailureException
{
final Oft oft = new OftRunner();
getLog().info("Importing spec items...");
final List items = oft.importItems(createImportSettings());
getLog().info("Imported " + items.size() + " items.");
final List linkedItems = oft.link(items);
final Trace trace = oft.trace(linkedItems);
writeTracingReport(oft, trace);
if (trace.countDefects() == 0)
{
getLog().info("Tracing found no defects in " + trace.count() + " items");
return;
}
final String message = "Tracing found " + trace.countDefects() + " defects out of " + trace.count() + " items";
getLog().warn(message);
logTracingReport(oft, trace);
if (failBuild)
{
throw new MojoFailureException(message);
}
}
private void logTracingReport(final Oft oft, final Trace trace)
{
final ReportSettings reportSettings = ReportSettings.builder()
.outputFormat("plain")
.verbosity(ReportVerbosity.FAILURE_DETAILS)
.showOrigin(false)
.build();
oft.reportToStdOut(trace, reportSettings);
}
private void writeTracingReport(final Oft oft, final Trace trace)
{
final Path outputPath = getOutputPath();
final ReportSettings reportSettings = ReportSettings.builder()
.outputFormat(reportOutputFormat)
.verbosity(reportVerbosity)
.showOrigin(reportShowOrigin)
.build();
getLog().info("Writing tracing report to " + outputPath + " using settings " + formatSettings(reportSettings));
oft.reportToPath(trace, outputPath, reportSettings);
}
private String formatSettings(ReportSettings reportSettings)
{
return "[output format: " + reportSettings.getOutputFormat()
+ ", verbosity: " + reportSettings.getReportVerbosity()
+ ", show origin: " + reportSettings.showOrigin()
+ ", newline: " + reportSettings.getNewline().name()
+ "]";
}
private Path getOutputPath()
{
final String reportSuffix = "html".equals(reportOutputFormat) ? "html" : "txt";
final Path outputPath = outputDirectory.toPath().resolve("tracing-report." + reportSuffix);
createDir(outputPath.getParent());
return outputPath;
}
private void createDir(final Path path)
{
if (path.toFile().exists())
{
return;
}
try
{
Files.createDirectories(path);
}
catch (final IOException e)
{
throw new UncheckedIOException("Error creating directory " + path, e);
}
}
private ImportSettings createImportSettings()
{
final ImportSettings.Builder settings = ImportSettings.builder() //
.addInputs(getSourcePaths());
final Optional docPath = getProjectSubPath("doc");
if (docPath.isPresent())
{
settings.addInputs(docPath.get());
}
return settings.build();
}
private List getSourcePaths()
{
return getSourcePathOfProject(this.project);
}
private Path getPomOfSubModule(String moduleName)
{
return this.project.getBasedir().toPath().resolve(moduleName).resolve("pom.xml");
}
private MavenProject readProject(final Path pomFile)
{
try
{
final ProjectBuildingResult build = this.mavenProjectBuilder.build(pomFile.toFile(),
this.session.getProjectBuildingRequest());
return build.getProject();
}
catch (final ProjectBuildingException exception)
{
throw new IllegalStateException(
"Failed to read sub module \"" + pomFile + "\".", exception);
}
}
private List getSourcePathOfProject(MavenProject project)
{
final Stream sourcePathsOfSubModules = project.getModules().stream()
.map(moduleName -> readProject(getPomOfSubModule(moduleName)))
.flatMap(eachProject -> this.getSourcePathOfProject(eachProject).stream());
final Stream thisProjectsSourcePaths = Stream
.concat(project.getCompileSourceRoots().stream(),
project.getTestCompileSourceRoots().stream())
.map(Paths::get);
return Stream.concat(sourcePathsOfSubModules, thisProjectsSourcePaths).collect(Collectors.toList());
}
private Optional getProjectSubPath(String dir)
{
final File file = new File(project.getBasedir(), dir);
return file.exists() ? Optional.of(file.toPath()) : Optional.empty();
}
}