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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ The expected results are listed in [src/test/resources](its/autoscan/src/test/re
#### Debugging Integration Tests
You can debug ITs by adding `-Dmaven.binary=mvnDebug` as an option when running the tests. This will cause the analyzer JVM to wait for a debugger to be attached before continuing.

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

### License

Copyright 2012-2026 SonarSource.
Expand Down
181 changes: 181 additions & 0 deletions sonar-java-plugin/license-resources/LicenseValidator.java
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();
}
}
}
5 changes: 5 additions & 0 deletions sonar-java-plugin/license-resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The plugin JAR contains a `licenses/` folder which contains the licenses of all the used libraries.
These licenses have to be txt files. However, some libraries ship html files, in which case they need to be overwritten.

The files which overwrite the html file are located in this folder. The overwriting itself is configured
in the `pom.xml` of the plugin module.
33 changes: 33 additions & 0 deletions sonar-java-plugin/license-resources/bsd.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
This copy of StaxMate processing library is licensed under
BSD License ("new BSD").
See http://www.opensource.org/licenses/bsd-license.php
for details.

Full license text is as follows:

--------------------------------------------------------------------------
* Copyright (c) 2007, Tatu Saloranta
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY <copyright holder> ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------
Loading
Loading