Walkthrough: A Maven Assignment
Tested with divekit v2.49.1 and the maven plugin suite (builder v1.0.3, maven/pmd v2.1.0, maven/surefire v2.1.0).
This walkthrough builds one assignment with one Java class, hidden tests, and
a PMD check. At the end, every student has a work repository to push to, and
every push produces a report page with the test and PMD results. You do not
write a .gitlab-ci.yml yourself; Divekit generates both pipelines.
You need a GitLab group for the course, its numeric group ID, and a host alias
for that GitLab instance (see Installation).
The examples use the group ID 5551, a distribution named greeting, and the
students user12 and user13.
1. Create The Origin
mkdir greeting && cd greeting
git init
divekit init
Answer the prompts:
| Prompt | Answer |
|---|---|
| Distribution Name | greeting |
| Enable Evaluation Pipeline? | Yes |
| Main Group ID | 5551 |
| Set Member Permissions | developer, so students can push |
| Select Remote Host | the host alias of your GitLab |
| Evaluation Pipeline Group ID | leave empty |
divekit init writes .divekit/distributions/greeting/config.json with the
evaluation pipeline enabled.
2. Add The Assignment Content
The origin holds both what students see and what only the evaluation sees. File and directory suffixes decide where each path ends up:
greeting/
├── TASK.md
├── pom.xml
├── pmd-ruleset.xml
└── src/
├── main/java/de/example_overwrite_to_eval/
│ └── Greeter.java
└── test/java/de/example/
└── GreeterTest_evalrepo.java
TASK.md,pom.xml, andpmd-ruleset.xmlgo to both repositories.de/example_overwrite_to_eval/becomesde/example/in the work repository. On every evaluation run, the student’s version replaces the one in the eval repository.GreeterTest_evalrepo.javabecomesGreeterTest.javaand exists only in the eval repository, so students never see the tests or the expected value.
The class students complete:
package de.example;
public class Greeter {
public String greet() {
return null;
}
}
The hidden test carries the solution value:
package de.example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class GreeterTest {
@Test
void greetReturnsTheExpectedGreeting() {
assertEquals("Hello, Divekit!", new Greeter().greet());
}
}
The pom.xml declares JUnit, Surefire, and the PMD plugin with the course
ruleset. The PMD plugin of the suite runs mvn pmd:pmd, so the rules in the
pom.xml decide what is checked:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.example</groupId>
<artifactId>greeting</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.26.0</version>
<configuration>
<rulesets>
<ruleset>pmd-ruleset.xml</ruleset>
</rulesets>
</configuration>
</plugin>
</plugins>
</build>
</project>
pmd-ruleset.xml lists the rules of the course:
<?xml version="1.0"?>
<ruleset name="course"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>Rules checked for this assignment.</description>
<rule ref="category/java/bestpractices.xml/UnusedLocalVariable"/>
<rule ref="category/java/codestyle.xml/MethodNamingConventions"/>
<rule ref="category/java/errorprone.xml/EmptyCatchBlock"/>
</ruleset>
3. Add The Students
printf 'user12\nuser13\n' > members.txt
divekit members -d greeting --members-file members.txt
Each line becomes one repository pair. Divekit stores the members file under
~/.divekit/members/ and points members.path in config.json at it.
4. Enable The Maven Suite
divekit plugin install
divekit plugin add maven
divekit plugin disable maven/jacoco-pit
plugin install writes the pipeline templates into .divekit/eval-pipeline/.
Run it before plugin add: the suite’s jobs are composed into those templates,
and without them the evaluation repositories run no plugin jobs.
plugin add maven locks the suite’s builder and plugins to exact releases and
image digests in .divekit/eval-pipeline/plugins.json. This assignment needs
no coverage or mutation testing, so JaCoCo/PIT is switched off.
5. Check Before You Distribute
divekit plugin list
Plugin suites
maven enabled plugin-suite
maven/jacoco-pit disabled v2.1.1 · sha256:3ef259f0a9cc
maven/pmd enabled v2.1.0 · sha256:3db3b2d112f8
maven/surefire enabled v2.1.0 · sha256:374b3088d8a3
The composed evaluation pipeline must contain the build job and one job per enabled plugin:
grep -nE "^(build|'maven:[a-z-]+'):" .divekit/eval-pipeline/eval/.gitlab-ci.yml
195:build:
238:'maven:pmd':
258:'maven:surefire':
If maven:pmd and maven:surefire are missing, run divekit plugin install
again. Then preview the rollout:
divekit distribute -d greeting --dry-run
Commit the origin, including .divekit/, so the course can be rebuilt from it.
6. Distribute
divekit distribute -d greeting
Divekit creates one subgroup per distribution with a work and an eval
group below it:
<course group>/greeting/
├── work/
│ ├── greeting-<uuid-1> (user12, developer)
│ └── greeting-<uuid-2> (user13, developer)
└── eval/
├── greeting-<uuid-1>
└── greeting-<uuid-2>
A work repository contains TASK.md, pom.xml, pmd-ruleset.xml,
src/main/java/de/example/Greeter.java, and the generated
.gitlab-ci.yml with .gitlab/ci/divekit/eval-trigger.yml. Its only job
triggers the matching eval pipeline.
An eval repository additionally contains src/test/java/de/example/GreeterTest.java,
the generated .gitlab-ci.yml, and .divekit/materialization.json, which
tells the eval pipeline to copy src/main/java/de/example from the work
repository before each run. Students have no access to it. Divekit links each
pair with CI variables and tokens.
distribute also writes remotes.json and individuals.json next to
config.json; commit them too, since follow-up commands such as
divekit patch read them.
7. Read The Report
Each push to a work repository starts its eval pipeline:
prepare-test-environment → build → maven:pmd, maven:surefire → generate_report → pages
The report is published on the eval repository’s GitLab Page. For the
unchanged starter code it shows the five PMD principles as passed and
greetReturnsTheExpectedGreeting as failed, because greet() still returns
null. After a student changes it to return "Hello, Divekit!" and pushes,
the next report shows the test as passed.
The same results are available as public/export.json in the
generate_report job artifacts.
When You Need More
- Settings for a suite plugin, such as coverage thresholds:
divekit plugin configure maven, see Plugin Suites. - Per-student variants of the task: Individualization.
- Changing CI or plugin versions after distribution:
divekit plugin updatefollowed bydivekit patch ci -d greeting. - More copy and overwrite rules: Work-To-Eval Materialization.