Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Gradle plugin for the requirement tracing suite [OpenFastTrace](https://github.c

```groovy
plugins {
id "org.itsallcode.openfasttrace" version "3.0.1"
id "org.itsallcode.openfasttrace" version "3.2.0"
}
```

Expand Down Expand Up @@ -50,10 +50,12 @@ You can configure the following properties:

* `failBuild`: Fail build when tracing finds any issues (default: `true`)
* `inputDirectories`: Files or directories to import
* `reportFile`: Path to the report file
* `reportFile`: Path to the report file (do not specify when using the `ux` `reportFormat`)
* `reportFormat`: Format of the report
* `plain` - Plain Text (default)
* `html` - HTML
* `aspec` - Extended requirement.xml format
* `ux` - HTML based requirement browser
* `reportVerbosity`: Report verbosity
* `quiet` - no output (in case only the return code is used)
* `minimal` - display ok or not ok
Expand Down
50 changes: 48 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ plugins {
}

repositories {
if (!gradle.startParameter.taskNames.contains("publish")) {
mavenLocal()
}
mavenCentral()
}

apply from: 'gradle/workAroundJacocoGradleTestKitIssueOnWindows.gradle'

version = '3.0.1'
version = '3.2.0'
group = 'org.itsallcode'

ext {
gradlePluginId = 'org.itsallcode.openfasttrace'
oftVersion = '4.1.0'
oftVersion = '4.2.0'
uxVersion = '0.1.0'
junitVersion = '5.11.0'
if (project.hasProperty('oftSourceDir')) {
oftSourceDir = file(project.oftSourceDir)
Expand Down Expand Up @@ -93,6 +97,45 @@ task compileOft(type: Exec) {
'-Djava.version=' + getJavaVersion()
}

// >> openfasttrace-ux


ext {
if (project.hasProperty('uxSourceDir')) {
uxSourceDir = file(project.uxSourceDir)
} else {
uxSourceDir = 'oft-dir-not-found'
}
}

tasks.register("compileUx", Exec) {
workingDir uxSourceDir

commandLine 'npm', 'run', 'deploy'
}
compileUx.onlyIf { project.file(uxSourceDir).isDirectory() }

configurations {
create("openfasttraceux")
}
dependencies {
openfasttraceux "org.itsallcode:openfasttrace-ux:$uxVersion"
}

task addOftUxAsResource(type: Copy) {
description "Adds the OpenFastTrace-UX ZIP artifact as file resources to the gradle plugin"
dependsOn(compileUx)

from configurations.getByName("openfasttraceux").getSingleFile()
into layout.buildDirectory.dir("resources/main")
rename { "openfasttrace-ux.zip" }
}
tasks.named("processResources") {
dependsOn("addOftUxAsResource")
}

// << openfasttrace-ux

clean {
def exampleProjects = rootProject.file('example-projects').listFiles()
def propertyFiles = exampleProjects.collect { new File(it, 'gradle.properties') }
Expand All @@ -118,6 +161,9 @@ signing {
def signingPassword = findProperty("signingPassword")
useInMemoryPgpKeys(signingKey, signingPassword)
}
tasks.withType(Sign) {
onlyIf { !gradle.startParameter.taskNames.contains("publishToMavenLocal") }
}

testing {
suites {
Expand Down
28 changes: 28 additions & 0 deletions example-projects/ux-report/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
id "base"
id 'org.itsallcode.openfasttrace' version '3.2.0'
}


requirementTracing {
failBuild = false
if (project.hasProperty('oftSourceDir')) {
oftSourceDir = file(project.oftSourceDir)
if (!oftSourceDir.exists()) {
logger.warn "OFT source directory $oftSourceDir does not exist"
}
inputDirectories = files(project.fileTree(dir:oftSourceDir).matching {
include "doc/spec/**"
include "api/src/main/**"
include "api/src/test/**"
include "core/src/main/**"
include "core/src/test/java/**"
include "exporter/**/src/main/**"
include "importer/**/src/main/**"
include "reporter/**/src/main/**"
})
} else {
inputDirectories = files('custom-dir')
}
reportFormat = 'ux'
}
1 change: 1 addition & 0 deletions example-projects/ux-report/custom-dir/source.java
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// [impl->dsn~exampleB~1]
16 changes: 16 additions & 0 deletions example-projects/ux-report/custom-dir/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Tracing Example

`dsn~exampleB~1`

Example requirement

Needs: utest, impl

# Rejected Example

`dsn~rejected1~1`
Status: rejected

Rejected requirement

Needs: utest, impl
8 changes: 8 additions & 0 deletions example-projects/ux-report/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pluginManagement {
repositories {
mavenLocal()
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = 'UX Reporter Example'
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
package org.itsallcode.openfasttrace.gradle;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import java.io.File;
import java.util.*;
import java.util.stream.Stream;

import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
Expand All @@ -20,6 +13,16 @@
import org.itsallcode.openfasttrace.gradle.task.config.SerializableTagPathConfig;
import org.slf4j.Logger;

import java.io.File;
import java.net.URL;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

public class OpenFastTracePlugin implements Plugin<Project>
{
private static final Logger LOG = Logging.getLogger(OpenFastTracePlugin.class);
Expand All @@ -30,6 +33,7 @@ public void apply(final Project rootProject)
{
LOG.info("Initializing OpenFastTrack plugin for project '{}'", rootProject);
rootProject.allprojects(OpenFastTracePlugin::createConfigDsl);
System.setProperty( "oftProjectName", rootProject.getName() );
createTasks(rootProject);
}

Expand Down Expand Up @@ -79,17 +83,7 @@ private static void configureTask(final Project rootProject,
final TracingConfig config = getConfig(rootProject);
task.getFailBuild().set(config.getFailBuild());
task.getRequirementsFile().set(collectTask.get().getOutputFile());
if (config.getReportFile().isPresent())
{
task.getOutputFile().set(config.getReportFile());
}
else
{
final String extension = "html".equals(config.getReportFormat().get()) ? "html" : "txt";
task.getOutputFile()
.set(new File(rootProject.getLayout().getBuildDirectory().getAsFile().get(),
"reports/tracing." + extension));
}
configureOutputs(rootProject, task, config);
task.getReportVerbosity().set(config.getReportVerbosity());
task.getReportFormat().set(config.getReportFormat());
task.getImportedRequirements().set(getImportedRequirements(rootProject.getAllprojects()));
Expand All @@ -99,6 +93,39 @@ private static void configureTask(final Project rootProject,
task.getDetailsSectionDisplay().set(config.getDetailsSectionDisplay());
}

private static void configureOutputs( final Project rootProject,
final TraceTask task,
final TracingConfig config )
{
if( config.getReportFile().isPresent() )
{
task.getOutputFile().set( config.getReportFile() );
}
else
{
final String reporterFormat = config.getReportFormat().get();
task.getOutputFile()
.set( new File( rootProject.getLayout().getBuildDirectory().getAsFile().get(),
toReporterFile( reporterFormat ) ) );
final String resourceName = "openfasttrace-" + reporterFormat + ".zip";
final URL resource = task.getClass().getClassLoader().getResource( resourceName );
if( "ux".equals( reporterFormat ) )
{
task.getReportFile().set( "build/reports/openfasttrace/openfasttrace.html" );
}
if( resource != null )
task.getAdditionalResources().add( resourceName );
}
}

private static String toReporterFile( final String reporterFormat )
{
return "ux".equals( reporterFormat ) ? "reports/openfasttrace/resources/js/specitem_data.js"
: "html".equals( reporterFormat ) ? "reports/tracing.html"
: "reports/tracing.txt";

}

private static Set<File> getAllInputDirectories(final Set<Project> allProjects)
{
return allProjects.stream() //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@
import static java.util.stream.Collectors.toList;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
Expand All @@ -25,6 +31,7 @@ public class TraceTask extends DefaultTask
{
private final RegularFileProperty requirementsFile = getProject().getObjects().fileProperty();
private final RegularFileProperty outputFile = getProject().getObjects().fileProperty();
private final Property<String> reportFile = getProject().getObjects().property(String.class);
private final Property<ReportVerbosity> reportVerbosity = getProject().getObjects()
.property(ReportVerbosity.class);
private final Property<String> reportFormat = getProject().getObjects().property(String.class);
Expand All @@ -39,6 +46,7 @@ public class TraceTask extends DefaultTask
private final Property<Boolean> filterAcceptsItemsWithoutTag = getProject().getObjects()
.property(Boolean.class);
private final Property<Boolean> failBuild = getProject().getObjects().property(Boolean.class);
private final SetProperty<String> additionalResources = getProject().getObjects().setProperty(String.class);

@InputFile
public RegularFileProperty getRequirementsFile()
Expand All @@ -52,6 +60,12 @@ public RegularFileProperty getOutputFile()
return outputFile;
}

@Input
@Optional
public Property<String> getReportFile() {
return reportFile;
}

@Input
public Property<ReportVerbosity> getReportVerbosity()
{
Expand Down Expand Up @@ -101,30 +115,48 @@ public Property<Boolean> getFailBuild()
return failBuild;
}

@Input
public SetProperty<String> getAdditionalResources()
{
return additionalResources;
}

private boolean shouldFailBuild()
{
return failBuild.getOrElse(true);
}

private String reportFile() {
return reportFile.getOrElse(getOutputFileInternal().toPath().toString());
}

@TaskAction
public void trace()
{
createReportOutputDir();

final Path reportPath = getOutputFileInternal().toPath();
for( final String resourceName : additionalResources.get() ) {
final URL resourceURL = this.getClass().getClassLoader().getResource(resourceName);
if( resourceURL == null )
throw new IllegalStateException("Resource " + resourceName + " does not exist");
extractZipResource(resourceURL, reportPath.getParent().getParent().getParent().toFile());
}

final Oft oft = new OftRunner();
final ImportSettings importSettings = getImportSettings();
final List<SpecificationItem> importedItems = oft.importItems(importSettings);
getLogger().info("Read {} spec items from {}", importedItems.size(),
importSettings.getInputs());
final List<LinkedSpecificationItem> linkedItems = oft.link(importedItems);
final Trace trace = oft.trace(linkedItems);
final Path reportPath = getOutputFileInternal().toPath();
getLogger().info("Tracing result: {} total items, {} defects. Writing report to {}",
trace.count(), trace.countDefects(), reportPath);
oft.reportToPath(trace, reportPath, getReportSettings());
if (trace.countDefects() > 0)
{
final String message = "Requirement tracing found " + trace.countDefects()
+ " defects. See report at " + reportPath + " for details.";
+ " defects. See report at " + reportFile() + " for details.";
if (shouldFailBuild())
{
throw new IllegalStateException(message);
Expand Down Expand Up @@ -195,4 +227,36 @@ private File getOutputFileInternal()
{
return outputFile.getAsFile().get();
}

private void extractZipResource(final URL resource, final File outputDir) {
getLogger().info("Extracting additional resource {}", resource.getPath());
try(final ZipInputStream zipStream = new ZipInputStream(resource.openStream())) {
if( !outputDir.isDirectory() && !outputDir.mkdirs() ) {
throw new IllegalStateException("Error creating directory " + outputDir);
}
while( true ) {
final ZipEntry entry = zipStream.getNextEntry();
if( entry == null )
break;
File outputFile = new File(outputDir, entry.getName());

if( entry.isDirectory() ) {
if( !outputFile.isDirectory() && !outputFile.mkdirs() ) {
throw new IllegalStateException("Error creating directory " + outputFile);
}
}
else {
final File path = outputFile.getParentFile();
if( !path.isDirectory() && !path.mkdirs() ) {
throw new IllegalStateException("Error creating directory " + path);
}
Files.copy(zipStream, outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
zipStream.closeEntry();
}
}
catch (IOException e) {
throw new IllegalStateException("Failed to extract resource " + resource.getPath(), e);
}
}
}
Loading
Loading