Skip to content

feat: auto-wire assembleRequirements task dependencies and warn on missing annotation files #54

@jimisola

Description

@jimisola

Summary

After #49 the plugin auto-discovers all non-main source sets for svcsAnnotationsFiles. The remaining gap: the plugin does not auto-wire task dependencies or warn when annotation files are missing.

Users still need to add this to every consuming build.gradle:

tasks.named('build') {
    finalizedBy tasks.named('assembleRequirements')
}
tasks.named('assembleRequirements') {
    dependsOn test, testing.suites.integrationTest
}

(As seen in tests/fixtures/test_project/build.gradle.)

Problem 1: Manual task dependency wiring

RequirementsToolPlugin.apply() never calls task.dependsOn(...). Every consuming project must re-implement the same boilerplate.

Explicit vs auto-discovered configuration

svcsAnnotationsFiles is a ConfigurableFileCollection. Users can interact with it in two ways:

  • svcsAnnotationsFiles.from(...)additive: their files are added to the auto-discovered ones
  • svcsAnnotationsFiles.setFrom(...)replaces: clears auto-discovery, uses only explicit files

Similarly, requirementsAnnotationsFile can be overridden explicitly in the requirementsTool {} block.

When a user provides explicit files, we cannot know which Gradle task generates them — auto-wiring dependsOn is not possible. Instead, we should warn at task execution time if those files don't exist.

Design

Track explicit configuration via a flag in the extension:

// Extension
private boolean svcsAnnotationsFilesExplicit = false;
private boolean requirementsAnnotationsFileExplicit = false;

/** Explicitly set svcs annotation files — disables auto-wired compile task dependencies. */
public void setSvcsAnnotationsFiles(Object... paths) {
    this.svcsAnnotationsFilesExplicit = true;
    this.svcsAnnotationsFiles.setFrom(paths);
}

In RequirementsToolPlugin.apply(), wire dependencies only for auto-discovered source sets:

project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
    JavaPluginExtension javaExt = project.getExtensions().getByType(JavaPluginExtension.class);
    SourceSetContainer sourceSets = javaExt.getSourceSets();

    project.afterEvaluate(p -> {
        sourceSets.forEach(sourceSet -> {
            String compileTaskName = sourceSet.getCompileJavaTaskName();

            boolean isMain = SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName());
            boolean skip = isMain
                ? extension.isRequirementsAnnotationsFileExplicit()
                : extension.isSvcsAnnotationsFilesExplicit();

            if (!skip) {
                // Auto-wire: annotation processor runs at compile time
                assembleTask.configure(task ->
                    task.dependsOn(project.getTasks().named(compileTaskName))
                );
            }
            // else: user manages their own deps; task will warn at execution if file missing
        });

        // Wire build → assembleRequirements
        project.getTasks().named(JavaPlugin.BUILD_TASK_NAME, build ->
            build.dependsOn(assembleTask)
        );
    });
});

Result:

  • ./gradlew assembleRequirements — auto-compiles all source sets (unless explicit override), generates all annotation files
  • ./gradlew build — assembleRequirements runs as part of build (no user config needed)
  • ./gradlew compileJava — only compiles main, assembleRequirements doesn't run
  • Explicit files — no auto-wired deps; WARN at execution if file missing (see Problem 2)

Note on compile vs test tasks: Annotation processors run at compile time (compileXxxJava), not during test execution. Wiring on compile tasks is cheaper (no test execution needed). Users who also want tests to run before assembling can still add dependsOn test manually.

Problem 2: Silent skip on missing annotation files

When a source set was not compiled, the corresponding annotations.yml doesn't exist. The current execute() silently skips it, producing silently incomplete output. This matters most when explicit files are configured (no auto-wired deps) but also helps diagnose any accidental --exclude-task usage.

Proposed fix — warn in RequirementsToolTask.execute():

for (File svcsAnnotFile : svcsAnnotationsFiles.getFiles()) {
    if (svcsAnnotFile.exists()) {
        JsonNode fileTestsNode = yamlMapper.readTree(svcsAnnotFile)
            .path(XML_REQUIREMENT_ANNOTATIONS).path(XML_TESTS);
        mergeTestNodes(mergedTestsNode, fileTestsNode);
    } else {
        getLogger().warn(
            "reqstool: no annotations found at {} — ensure the task that generates it runs before assembleRequirements",
            svcsAnnotFile.getAbsolutePath()
        );
    }
}

Same warning for requirementsAnnotationsFile if it doesn't exist.

Consuming project simplification

Before (current):

tasks.named('assembleRequirements') {
    dependsOn test, compileIntegrationTestJava
}
tasks.named('build') {
    dependsOn assembleRequirements
}

After (with this fix — no explicit file overrides):

// Nothing — plugin handles it automatically

After (with explicit override — user manages their own deps):

requirementsTool {
    requirementsAnnotationsFile = file("custom/path/annotations.yml")
    // Must also wire: assembleRequirements.dependsOn(myCustomGeneratorTask)
}

Files to change

File Change
src/main/java/io/github/reqstool/gradle/RequirementsToolPlugin.java Add source set iteration in afterEvaluate; auto-wire dependsOn on compile tasks when not explicit; wire build.dependsOn(assembleRequirements)
src/main/java/io/github/reqstool/gradle/RequirementsToolExtension.java Add svcsAnnotationsFilesExplicit and requirementsAnnotationsFileExplicit flags with custom setters
src/main/java/io/github/reqstool/gradle/RequirementsToolTask.java Add WARN log in execute() for missing annotation files
tests/fixtures/test_project/build.gradle Remove manual dependsOn and finalizedBy blocks — plugin handles it
docs/modules/ROOT/pages/configuration.adoc Document that wiring is automatic; add override instructions

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions