Skip to content
Merged
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
38 changes: 0 additions & 38 deletions .github/scripts/resolve-sonar-java-plugin-version.sh

This file was deleted.

31 changes: 12 additions & 19 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
pull_request:
workflow_dispatch:
schedule:
- cron: "30 1 * * *" # Run daily at 1:30 AM UTC
- cron: "30 1 * * *"

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -33,8 +33,10 @@ jobs:
id: build-step
with:
deploy-pull-request: true
artifactory-reader-role: private-reader # Override default public-reader
artifactory-deployer-role: qa-deployer # Override default public-deployer
artifactory-reader-role: private-reader
artifactory-deployer-role: qa-deployer
use-develocity: ${{ env.USE_DEVELOCITY }}
develocity-url: ${{ env.DEVELOCITY_URL }}

qa:
needs: [build]
Expand All @@ -46,9 +48,8 @@ jobs:
fail-fast: false
matrix:
item:
- { name: "with Lastest SonarJava Plugin", profile: "without-sonarqube-project", java_plugin_version: "LATEST_MASTER" }
- { name: "for SonarQube Project Only", profile: "only-sonarqube-project", java_plugin_version: "LATEST_MASTER" }
- { name: "with Prod SonarJava Plugin", profile: "without-sonarqube-project", java_plugin_version: "POM_PROPERTY" }
- { name: "with Latest SonarJava Plugin", profile: "without-sonarqube-project" }
- { name: "for SonarQube Project Only", profile: "only-sonarqube-project" }
name: "QA Tests ${{ matrix.item.name }}"
env:
BUILD_NUMBER: ${{ needs.build.outputs.build-number }}
Expand All @@ -59,7 +60,6 @@ jobs:
submodules: recursive
- uses: jdx/mise-action@d6e32c1796099e0f1f3ac741c220a8b7eae9e5dd # v3.2.0
with:
working-directory: its/ruling
version: 2025.7.12
- name: Get GitHub Token for QA Licenses
id: secrets
Expand All @@ -71,35 +71,28 @@ jobs:
id: configure-maven
uses: SonarSource/ci-github-actions/config-maven@v1
with:
artifactory-reader-role: private-reader # Override default public-reader
- name: Get Sonar Java plugin version
id: resolve-sonar-java-plugin-version
run: |
VERSION=$(.github/scripts/resolve-sonar-java-plugin-version.sh "${{ matrix.item.java_plugin_version }}")
echo "version=${VERSION}" >> $GITHUB_OUTPUT
artifactory-reader-role: private-reader
use-develocity: ${{ env.USE_DEVELOCITY }}
develocity-url: ${{ env.DEVELOCITY_URL }}
- name: Run QA Tests
working-directory: its/ruling
env:
GITHUB_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).GITHUB_TOKEN }}
BUILD_NUMBER: ${{ needs.build.outputs.build-number }}
MAVEN_OPTS: "-Xmx3g"
SONAR_JAVA_VERSION: ${{ steps.resolve-sonar-java-plugin-version.outputs.version }}
run: |
mvn package --batch-mode \
"-Pit-ruling,${{ matrix.item.profile }}" \
"-Dsonar.java.version=${SONAR_JAVA_VERSION}" \
"-Dorchestrator.artifactory.accessToken=${ARTIFACTORY_ACCESS_TOKEN}" \
"-Dsonar.runtimeVersion=LATEST_RELEASE" \
"-Dmaven.test.redirectTestOutputToFile=false" \
"-DbuildNumber=${BUILD_NUMBER}" \
-B -e -V \
"-Dparallel=methods" \
"-DuseUnlimitedThreads=true"
- name: Upload ruling artifacts on failure
if: failure()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.5.0
with:
name: ruling-actual-${{ matrix.item.profile }}-${{ matrix.item.java_plugin_version }}
name: ruling-actual-${{ matrix.item.profile }}
path: its/ruling/target/actual/**/*

promote:
Expand All @@ -115,4 +108,4 @@ jobs:
BUILD_NUMBER: ${{ needs.build.outputs.build-number }}
steps:
- name: Promote artifacts
uses: SonarSource/ci-github-actions/promote@v1
uses: SonarSource/ci-github-actions/promote@v1
1 change: 0 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[submodule "its/sources"]
path = its/sources
url = https://github.com/SonarSource/ruling_java.git
branch = se-engine
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,20 @@ To submit a contribution, create a pull request for this repository. Please make

### Build the Project and Run Unit Tests

Requirements: Java 17
Requirements: Java 21

To build the plugin and run its unit tests, execute this command from the project's root directory:

mvn clean install
```sh
mvn clean install -DskipLicenseValidation
```

### Update licenses:
When dependencies change, update the committed license files using the `updateLicenses` profile:
```sh
mvn clean package -PupdateLicenses
```
This regenerates licenses in `java-symbolic-execution/java-symbolic-execution-plugin/src/main/resources/licenses/` based on current project dependencies.

#### Ruling Test

Expand All @@ -47,7 +55,7 @@ To run the test, first make sure the submodules are checked out:

git submodule update --init --recursive

Then, ensure that the `JAVA_HOME` environment variable is set for the ruling tests execution and that it points to your local JDK 17 installation.
Then, ensure that the `JAVA_HOME` environment variable is set for the ruling tests execution and that it points to your local JDK 21 installation.
Failing to do so will produce inconsistencies with the expected results.

From the `its/ruling` folder, launch the ruling tests:
Expand Down
2 changes: 0 additions & 2 deletions its/ruling/mise.toml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ public void guava() throws Exception {
String projectKey = "com.google.guava:guava";
MavenBuild build = test_project(projectKey, projectName);
build
// by default guava is compatible with java 6, however this is not supported with JDK 17
.setProperty("java.version", "1.7")
// by default guava is compatible with java 6, however this is not supported with JDK 17+
.setProperty("java.version", "1.8")
.setProperty("maven.javadoc.skip", "true")
// use batch
.setProperty("sonar.java.experimental.batchModeSizeInKB", "8192");
Expand Down Expand Up @@ -175,7 +175,7 @@ public void apache_commons_beanutils() throws Exception {
String projectName = "commons-beanutils";
MavenBuild build = test_project("commons-beanutils:commons-beanutils", projectName);
build
// by default it can not be built with jdk 17 without changing some plugin versions
// by default it can not be built with recent JDK versions without changing some plugin versions
.setProperty("maven-bundle-plugin.version", "5.1.4")
// use batch
.setProperty("sonar.java.experimental.batchModeSizeInKB", "8192");
Expand Down
11 changes: 11 additions & 0 deletions its/ruling/src/test/resources/guava/java-S3824.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"com.google.guava:guava:src/com/google/common/collect/StandardTable.java": [
131
],
"com.google.guava:guava:src/com/google/common/eventbus/SubscriberRegistry.java": [
198
],
"com.google.guava:guava:src/com/google/common/reflect/ClassPath.java": [
395
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>17</release>
<source>17</source>
<target>17</target>
<release>21</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Copyright (C) 2021-2025 SonarSource SA
* All rights reserved
* mailto:info AT sonarsource DOT com
*/

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Validates that generated license files match committed license files.
* Used during Maven verify phase to ensure license files are up-to-date.
*/
public final class LicenseValidator {

private LicenseValidator() {
// Utility class
}

public static void main(String[] args) {
try {
var arguments = parseArguments(args);
var tempLicensesPath = Path.of(arguments.get("temp_licenses"));
var committedLicensesPath = Path.of(arguments.get("committed_licenses"));

validateDirectoriesExist(tempLicensesPath, committedLicensesPath);

var result = compareDirectories(tempLicensesPath, committedLicensesPath);

if (result.hasErrors()) {
printFailureMessage(result);
System.exit(1);
} else {
System.out.println("[SUCCESS] License validation passed - generated files match committed files");
System.exit(0);
}
} catch (IllegalArgumentException e) {
System.err.println("Error: " + e.getMessage());
printUsage();
System.exit(1);
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
System.exit(1);
}
}

static Map<String, String> parseArguments(String[] args) {
var arguments = new java.util.HashMap<String, String>();
for (var arg : args) {
if (arg.startsWith("--")) {
var parts = arg.substring(2).split("=", 2);
if (parts.length == 2) {
arguments.put(parts[0], parts[1]);
}
}
}

if (!arguments.containsKey("temp_licenses") || !arguments.containsKey("committed_licenses")) {
throw new IllegalArgumentException("Missing required arguments");
}

return arguments;
}

static void validateDirectoriesExist(Path tempLicenses, Path committedLicenses) throws IOException {
if (!Files.exists(tempLicenses)) {
throw new IOException(
"Temporary licenses directory not found: " + tempLicenses + "\n" +
"Please run: mvn clean package -PupdateLicenses"
);
}
if (!Files.exists(committedLicenses)) {
throw new IOException(
"Committed licenses directory not found: " + committedLicenses + "\n" +
"Please run: mvn clean package -PupdateLicenses"
);
}
}

static ValidationResult compareDirectories(Path tempDir, Path committedDir) throws IOException {
var tempFiles = buildFileMap(tempDir);
var committedFiles = buildFileMap(committedDir);

var newFiles = new ArrayList<String>();
var missingFiles = new ArrayList<String>();
var differentFiles = new ArrayList<String>();

// Find new files (in temp but not in committed)
for (var relativePath : tempFiles.keySet()) {
if (!committedFiles.containsKey(relativePath)) {
newFiles.add(relativePath);
}
}

// Find missing files (in committed but not in temp)
for (var relativePath : committedFiles.keySet()) {
if (!tempFiles.containsKey(relativePath)) {
missingFiles.add(relativePath);
}
}

// Find files with different content
for (var relativePath : tempFiles.keySet()) {
if (committedFiles.containsKey(relativePath)) {
var tempFile = tempFiles.get(relativePath);
var committedFile = committedFiles.get(relativePath);
if (Files.mismatch(tempFile, committedFile) != -1L) {
differentFiles.add(relativePath);
}
}
}

newFiles.sort(String::compareTo);
missingFiles.sort(String::compareTo);
differentFiles.sort(String::compareTo);

return new ValidationResult(newFiles, missingFiles, differentFiles);
}

static Map<String, Path> buildFileMap(Path rootDir) throws IOException {
try (Stream<Path> paths = Files.walk(rootDir)) {
return paths
.filter(Files::isRegularFile)
.collect(Collectors.toMap(
path -> rootDir.relativize(path).toString().replace('\\', '/'),
path -> path
));
}
}

static void printFailureMessage(ValidationResult result) {
System.err.println("[FAILURE] License validation failed!");
System.err.println();

if (!result.newFiles().isEmpty()) {
System.err.println("New files in generated licenses (not in committed):");
for (var file : result.newFiles()) {
System.err.println(" + " + file);
}
System.err.println();
}

if (!result.missingFiles().isEmpty()) {
System.err.println("Missing files in generated licenses (present in committed):");
for (var file : result.missingFiles()) {
System.err.println(" - " + file);
}
System.err.println();
}

if (!result.differentFiles().isEmpty()) {
System.err.println("Files with different content:");
for (var file : result.differentFiles()) {
System.err.println(" ~ " + file);
}
System.err.println();
}

System.err.println("To fix this, run: mvn clean package -PupdateLicenses");
}

static void printUsage() {
System.err.println("Usage: LicenseValidator --temp_licenses=<path> --committed_licenses=<path>");
}

record ValidationResult(
List<String> newFiles,
List<String> missingFiles,
List<String> differentFiles
) {
boolean hasErrors() {
return !newFiles.isEmpty() || !missingFiles.isEmpty() || !differentFiles.isEmpty();
}
}
}
Loading
Loading