build(licenses): Module-specific license. Add dependency overrides. (#7049)

# Description of Changes

This change adds a version-scoped override mechanism for dependencies
whose published metadata does not expose a detectable license.

- Added `app/license-overrides.json` with verified Apache License 2.0
metadata for:
  - `com.hubspot.immutables:immutables-exceptions:1.9`
  - `com.hubspot:algebra:1.5`
- Added `ModuleLicenseOverrideFilter` as custom `buildSrc` logic for the
Gradle dependency license report plugin.
- Applied overrides only when the exact `group:artifact:version` matches
and no usable license metadata was detected.
- Added automatic maintenance of the override file:
  - Removes overrides when the dependency is no longer resolved.
- Removes overrides when the dependency starts publishing valid license
metadata.
- Migrates stale overrides to newer unresolved versions and clears their
metadata for re-verification.
- Adds null-valued placeholders for newly detected dependencies without
license metadata.
- Preserves populated overrides for newer versions when already present.
- Added Gradle version-aware dependency ordering for override migration.
- Registered `app/license-overrides.json` as an input for license-report
and license-check preparation tasks.
- Centralized the dependency license report plugin version in
`buildSrc`.
- Added unit tests covering override application, cleanup, migration,
exact-version matching, concurrent versions, placeholder generation, and
numeric version ordering.
- Added documentation describing the override lifecycle, verification
requirements, maintenance workflow, and validation commands.
- Replaced broad null-license allowances for the two HubSpot modules
with explicit Apache License 2.0 metadata.
- Added accepted GNU Lesser General Public License name variants
encountered in dependency metadata.

The change was made because some dependencies have known upstream
licenses but do not publish license metadata in a form detected by the
Gradle license report plugin. Previously, these dependencies were
permitted through module-specific null-license exceptions, leaving
incomplete information in the generated report. The new mechanism
supplies verified metadata without overriding valid metadata published
by dependencies.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
This commit is contained in:
Ludy
2026-08-06 11:44:12 +00:00
committed by GitHub
parent 2265e48b32
commit 8094765bab
11 changed files with 668 additions and 9 deletions
+8 -8
View File
@@ -156,6 +156,14 @@
"moduleName": ".*",
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception"
},
{
"moduleName": ".*",
"moduleLicense": "GNU Lesser Public License"
},
{
"moduleName": ".*",
"moduleLicense": "The GNU Lesser General Public License"
},
{
"moduleName": "com.martiansoftware:jsap",
"moduleLicense": "LGPL"
@@ -224,14 +232,6 @@
"moduleName": "com.google.re2j:re2j",
"moduleLicense": "Go License"
},
{
"moduleName": "com.hubspot:algebra",
"moduleLicense": null
},
{
"moduleName": "com.hubspot.immutables:immutables-exceptions",
"moduleLicense": null
},
{
"moduleName": ".*",
"moduleLicense": "UnRar License"
+12
View File
@@ -0,0 +1,12 @@
{
"com.hubspot.immutables:immutables-exceptions:1.9": {
"name": "The Apache License, Version 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"projectUrl": "https://github.com/HubSpot/hubspot-immutables/tree/58628096ac99b286fe4f8bfe12aa3cff0f0589d3"
},
"com.hubspot:algebra:1.5": {
"name": "The Apache License, Version 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"projectUrl": "https://github.com/HubSpot/algebra/tree/5d42983fd3a26539df9ba2cbeac32a1bddce0494"
}
}
+12 -1
View File
@@ -6,7 +6,7 @@ plugins {
id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
id "io.swagger.swaggerhub" version "1.3.2"
id "com.diffplug.spotless" version "8.8.0"
id "com.github.jk1.dependency-license-report" version "3.1.2"
id "com.github.jk1.dependency-license-report"
//id "nebula.lint" version "19.0.3"
id "org.sonarqube" version "7.2.3.7755"
}
@@ -18,6 +18,7 @@ import groovy.xml.XmlSlurper
import org.gradle.api.JavaVersion
import org.gradle.api.tasks.testing.Test
import org.gradle.jvm.toolchain.JavaLanguageVersion
import stirling.software.gradle.ModuleLicenseOverrideFilter
ext {
springBootVersion = "4.0.6"
@@ -550,6 +551,7 @@ gradle.taskGraph.whenReady { graph ->
}
def allProjects = ((subprojects as Set<Project>) + project) as Set<Project>
def moduleLicenseOverridesFile = project.layout.projectDirectory.file("app/license-overrides.json").asFile
licenseReport {
projects = allProjects
@@ -557,6 +559,15 @@ licenseReport {
allowedLicensesFile = project.layout.projectDirectory.file("app/allowed-licenses.json").asFile
outputDir = project.layout.buildDirectory.dir("reports/dependency-license").get().asFile.path
configurations = [ "productionRuntimeClasspath", "runtimeClasspath" ]
filters = [new ModuleLicenseOverrideFilter(moduleLicenseOverridesFile)]
}
tasks.named('generateLicenseReport') {
inputs.file(moduleLicenseOverridesFile)
}
tasks.named('checkLicensePreparation') {
inputs.file(moduleLicenseOverridesFile)
}
// Configure the forked spring boot run task to properly delegate to the stirling-pdf module
+168
View File
@@ -0,0 +1,168 @@
# Dependency license overrides
The backend dependency license report is generated by the
[`com.github.jk1.dependency-license-report`](https://github.com/jk1/Gradle-License-Report)
Gradle plugin. Most license information is read from dependency POM files, manifests, or packaged
license files. Some artifacts do not publish license metadata in a form the plugin can detect, even
though the artifact has a known license.
This directory contains the build logic used to provide narrowly scoped fallback license metadata
for those artifacts.
## Files
- `build.gradle` makes version 3.1.4 of the license report plugin available to the custom build
logic. The root build applies that plugin without a second version declaration so both use the
same classpath.
- `src/main/groovy/stirling/software/gradle/ModuleLicenseOverrideFilter.groovy` implements the
plugin's `DependencyFilter` interface.
- `../app/license-overrides.json` contains the actual module-specific fallback values.
- `../app/allowed-licenses.json` defines which detected or supplied licenses are accepted by
`checkLicense`.
## How it works
The root `build.gradle` passes `app/license-overrides.json` to
`ModuleLicenseOverrideFilter`:
```groovy
filters = [new ModuleLicenseOverrideFilter(moduleLicenseOverridesFile)]
```
For every dependency discovered by the license plugin, the filter builds an identifier in this
format:
```text
group:artifact:version
```
The filter applies a populated override only when both conditions are true:
1. The complete identifier, including the version, exists in `app/license-overrides.json`.
2. The plugin did not discover a non-empty license name for that dependency.
When both conditions match, the filter adds the configured license as fallback manifest metadata.
The normal report renderer and `checkLicense` then consume that metadata in the same way as
metadata discovered from the dependency itself.
An override never replaces a license that the plugin already detected. Updating a dependency also
does not silently reuse the override because a different version produces a different identifier.
Overrides are temporary fallbacks, not a permanent license catalog. If the plugin starts detecting
the original license for an overridden module, the filter automatically removes that exact entry
from `app/license-overrides.json` and logs the cleanup. When the overridden version is no longer
resolved, a newer resolved version takes its place: if it declares a license, the stale entry is
removed; otherwise the entry moves to the new exact version and its values are cleared for
re-verification. An already populated entry for the new version is preserved. If no higher version
is resolved, the unused override is removed instead.
Because the report aggregates several projects and configurations, multiple versions of the same
`group:artifact` can be present at once. An override is retained whenever its exact version is still
resolved. Only when that exact version is absent may the filter treat a higher version as an update;
version ordering then follows Gradle's own dependency version comparator. Overrides for dependency
versions that are no longer resolved and have no higher replacement are deleted automatically.
The filter also records every resolved dependency without detected license metadata that has no
override yet. It writes a placeholder with `null` values for `name`, `url`, and `projectUrl`.
Placeholders deliberately do not affect the generated report until `name` is filled in. This makes
new missing metadata visible in the source-controlled override file instead of only in a generated
report. Review and fill or remove every new placeholder before committing the resulting JSON.
## Adding an override
First verify the license from an authoritative source such as the upstream repository, the
published artifact metadata, or the license file shipped inside the artifact. Do not infer a
license from the organization name or from a related artifact.
Add an entry to `app/license-overrides.json`:
```json
{
"com.example:example-library:1.2.3": {
"name": "Apache License, Version 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0",
"projectUrl": "https://github.com/example/example-library/tree/0123456789abcdef0123456789abcdef01234567"
}
}
```
The key must contain the exact resolved version. `name` must be non-empty for the override to be
applied. `url` should point to the canonical license text. `projectUrl` must point to the immutable
Git tree for the exact module version, using the commit hash at which that version was introduced:
```text
https://github.com/<owner>/<repository>/tree/<full-commit-hash>
```
Do not use the repository's default branch or another moving URL. See the existing entries in
`app/license-overrides.json` for concrete examples.
If the license name is not already accepted, add a suitably narrow rule to
`app/allowed-licenses.json`. Adding an override and allowing a license are separate operations:
- `license-overrides.json` supplies missing metadata for a specific artifact version.
- `allowed-licenses.json` defines the policy enforced by `checkLicense`.
## Updating a dependency
When an overridden dependency changes version:
1. Verify the license for the new version again.
2. Run the license report so the filter can move the old key or add a placeholder for the new full
`group:artifact:version` key.
3. Re-verify and fill the license values and the version's immutable Git-tree `projectUrl`; moved
values are intentionally cleared because a license conclusion for one release is not assumed
for another.
4. Regenerate and inspect the report.
If the new artifact publishes usable license metadata, no override is necessary. The next license
report or license check removes the old entry from `app/license-overrides.json` automatically. The
file must contain only overrides that are still needed.
## Verification
Run the filter unit tests:
```powershell
.\gradlew.bat -p buildSrc test
```
The tests use `com.example:example-library` versions 1.4 and 1.7 to cover the missing
metadata fallback, placeholder creation, version migration, preservation of a populated newer
override, automatic cleanup after license metadata appears, exact-version matching, and concurrent
resolved versions. They also verify removal when a dependency version disappears. A separate `1.9`
to `1.11.0` case verifies numeric Gradle version ordering.
Run the normal backend license workflow from the repository root:
```powershell
task backend:licenses:generate
```
Then inspect:
- `build/reports/dependency-license/index.json` for the rendered module, version, license name, and
URL.
- `build/reports/dependency-license/dependencies-without-allowed-license.json` when `checkLicense`
reports a policy failure.
Also run the backend quality gate after changing the filter or its build wiring:
```powershell
task backend:check
```
The override JSON is registered as an input of `generateLicenseReport` and
`checkLicensePreparation`, so changing the file invalidates the corresponding Gradle task outputs.
## What not to do
- Do not use an unversioned key. It cannot match the filter and would make the intended scope
ambiguous.
- Do not use an override to replace valid license metadata published by a dependency.
- Do not add an empty license to `allowed-licenses.json` merely to silence `checkLicense`; that
would still leave the generated report without useful license information.
- Do not exclude a dependency from the report solely because it is transitive. Runtime transitive
dependencies are still distributed components and their licenses remain relevant.
- Do not edit generated files under `build/reports/dependency-license` or the copied static license
report by hand.
+22
View File
@@ -0,0 +1,22 @@
plugins {
id 'groovy'
}
repositories {
gradlePluginPortal()
}
dependencies {
implementation localGroovy()
implementation gradleApi()
implementation 'com.github.jk1:gradle-license-report:3.1.4'
testImplementation platform('org.junit:junit-bom:6.1.2')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED'
testLogging.showStandardStreams = true
}
@@ -0,0 +1,173 @@
package stirling.software.gradle
import com.github.jk1.license.License
import com.github.jk1.license.ManifestData
import com.github.jk1.license.ModuleData
import com.github.jk1.license.ProjectData
import com.github.jk1.license.filter.DependencyFilter
import com.github.jk1.license.render.LicenseDataCollector
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionComparator
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.Version
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionParser
class ModuleLicenseOverrideFilter implements DependencyFilter {
private static final VersionParser VERSION_PARSER = new VersionParser()
private static final Comparator<Version> VERSION_COMPARATOR =
new DefaultVersionComparator().asVersionComparator()
private final File overridesFile
ModuleLicenseOverrideFilter(File overridesFile) {
this.overridesFile = overridesFile
}
@Override
ProjectData filter(ProjectData projectData) {
Map<String, Map<String, String>> overrides = loadOverrides()
List<ModuleData> modules = projectData.configurations
.collectMany { configuration -> configuration.dependencies }
Map<String, List<ModuleData>> modulesByCoordinate = modules
.groupBy { module -> moduleCoordinate(module) }
boolean overridesChanged = false
overrides.keySet().toList().each { overrideId ->
ModuleCoordinates overrideModule = parseModuleId(overrideId)
List<ModuleData> coordinateModules = modulesByCoordinate[overrideModule.coordinate]
ModuleData currentModule = coordinateModules
?.find { module -> module.version == overrideModule.version }
if (currentModule == null) {
currentModule = newestModule(coordinateModules, overrideModule.version)
}
if (currentModule == null) {
overrides.remove(overrideId)
overridesChanged = true
projectData.project.logger.lifecycle(
"Removed unused license override for ${overrideId}: "
+ 'dependency version is no longer resolved')
return
}
if (hasDeclaredLicense(currentModule)) {
overrides.remove(overrideId)
overridesChanged = true
projectData.project.logger.lifecycle(
"Removed stale license override for ${overrideId}: "
+ "${moduleId(currentModule)} now declares a license")
return
}
if (compareVersions(currentModule.version, overrideModule.version) > 0) {
String currentModuleId = moduleId(currentModule)
overrides.remove(overrideId)
if (!overrides.containsKey(currentModuleId)) {
overrides[currentModuleId] = [name: null, url: null, projectUrl: null]
}
overridesChanged = true
projectData.project.logger.lifecycle(
"Updated license override from ${overrideId} to ${currentModuleId}: "
+ 'newer dependency still declares no license')
}
}
modules.groupBy { module -> moduleId(module) }.each { currentModuleId, matchingModules ->
ModuleData module = matchingModules.first()
if (!overrides.containsKey(currentModuleId) && !hasDeclaredLicense(module)) {
overrides[currentModuleId] = [name: null, url: null, projectUrl: null]
overridesChanged = true
projectData.project.logger.lifecycle(
"Added missing license override for ${currentModuleId}. "
+ "Set 'name' and 'url' in ${overridesFile}.")
}
}
if (overridesChanged) {
saveOverrides(overrides)
}
projectData.configurations.each { configuration ->
configuration.dependencies.each { module -> applyOverride(module, overrides) }
}
return projectData
}
private void applyOverride(
ModuleData module, Map<String, Map<String, String>> overrides) {
String moduleId = moduleId(module)
Map<String, String> override = overrides[moduleId]
if (override == null) {
return
}
String licenseName = override.name
String licenseUrl = override.url
String projectUrl = override.projectUrl
if (licenseName == null || licenseName.isBlank()) {
return
}
Set<License> licenses = [new License(licenseName, licenseUrl)] as LinkedHashSet<License>
ManifestData manifest =
new ManifestData(module.name, module.version, null, null, projectUrl, licenses, false)
Set<ManifestData> manifests = new LinkedHashSet<>(module.manifests ?: [])
manifests.add(manifest)
module.manifests = manifests
}
private Map<String, Map<String, String>> loadOverrides() {
Object parsed = new JsonSlurper().parse(overridesFile)
if (!(parsed instanceof Map)) {
throw new IllegalArgumentException(
"License overrides file ${overridesFile} must contain a JSON object")
}
return parsed as Map<String, Map<String, String>>
}
private void saveOverrides(Map<String, Map<String, String>> overrides) {
String json = JsonOutput.prettyPrint(JsonOutput.toJson(overrides)) + System.lineSeparator()
overridesFile.setText(json, 'UTF-8')
}
private static String moduleId(ModuleData module) {
return "${module.group}:${module.name}:${module.version}"
}
private static String moduleCoordinate(ModuleData module) {
return "${module.group}:${module.name}"
}
private static ModuleCoordinates parseModuleId(String moduleId) {
List<String> parts = moduleId.split(':', 3) as List<String>
if (parts.size() != 3 || parts.any { part -> part.isBlank() }) {
throw new IllegalArgumentException(
"License override key ${moduleId} must use group:module:version")
}
return new ModuleCoordinates("${parts[0]}:${parts[1]}", parts[2])
}
private static ModuleData newestModule(List<ModuleData> modules, String minimumVersion) {
return modules
?.findAll { module -> compareVersions(module.version, minimumVersion) > 0 }
?.max { left, right -> compareVersions(left.version, right.version) }
}
private static int compareVersions(String left, String right) {
return VERSION_COMPARATOR.compare(
VERSION_PARSER.transform(left), VERSION_PARSER.transform(right))
}
private static boolean hasDeclaredLicense(ModuleData module) {
Set<License> licenses = LicenseDataCollector.multiModuleLicenseInfo(module).licenses
return licenses.any { license -> license.name != null && !license.name.isBlank() }
}
private static class ModuleCoordinates {
final String coordinate
final String version
ModuleCoordinates(String coordinate, String version) {
this.coordinate = coordinate
this.version = version
}
}
}
@@ -0,0 +1,265 @@
package stirling.software.gradle
import com.github.jk1.license.ConfigurationData
import com.github.jk1.license.License
import com.github.jk1.license.ManifestData
import com.github.jk1.license.ModuleData
import com.github.jk1.license.ProjectData
import com.github.jk1.license.render.LicenseDataCollector
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import java.nio.file.Path
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertFalse
import static org.junit.jupiter.api.Assertions.assertTrue
class ModuleLicenseOverrideFilterTest {
private static final String GROUP = 'com.example'
private static final String MODULE = 'example-library'
private static final String VERSION_WITHOUT_LICENSE = '1.4'
private static final String VERSION_WITH_LICENSE = '1.7'
private static final String APACHE_NAME = 'Apache License, Version 2.0'
private static final String APACHE_URL = 'https://www.apache.org/licenses/LICENSE-2.0'
private static final String PROJECT_URL = 'https://github.com/HubSpot/hubspot-immutables'
@TempDir
Path temporaryDirectory
@Test
void keepsOverrideForVersionWithoutLicenseMetadata() {
ModuleData module = createModule(VERSION_WITHOUT_LICENSE, null)
File overridesFile = createOverridesFile(moduleId(VERSION_WITHOUT_LICENSE))
debugState('before filter', module, overridesFile)
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData(module))
debugState('after filter', module, overridesFile)
Map<String, Object> overrides = readOverrides(overridesFile)
assertTrue(overrides.containsKey(moduleId(VERSION_WITHOUT_LICENSE)))
assertEquals([APACHE_NAME], licenseNames(module))
}
@Test
void removesOverrideWhenLaterVersionDeclaresLicense() {
License publishedLicense = new License(APACHE_NAME, APACHE_URL)
ModuleData module = createModule(VERSION_WITH_LICENSE, publishedLicense)
File overridesFile = createOverridesFile(moduleId(VERSION_WITHOUT_LICENSE))
debugState('before filter', module, overridesFile)
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData(module))
debugState('after filter', module, overridesFile)
Map<String, Object> overrides = readOverrides(overridesFile)
assertFalse(overrides.containsKey(moduleId(VERSION_WITHOUT_LICENSE)))
assertEquals([APACHE_NAME], licenseNames(module))
}
@Test
void removesOverrideWhenOnlyOlderVersionIsResolved() {
License publishedLicense = new License(APACHE_NAME, APACHE_URL)
ModuleData module = createModule(VERSION_WITHOUT_LICENSE, publishedLicense)
File overridesFile = createOverridesFile(moduleId(VERSION_WITH_LICENSE))
debugState('before filter', module, overridesFile)
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData(module))
debugState('after filter', module, overridesFile)
Map<String, Object> overrides = readOverrides(overridesFile)
assertFalse(overrides.containsKey(moduleId(VERSION_WITH_LICENSE)))
assertEquals([APACHE_NAME], licenseNames(module))
}
@Test
void removesOverrideWhenModuleIsNoLongerResolved() {
File overridesFile = createOverridesFile(moduleId(VERSION_WITHOUT_LICENSE))
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData())
assertTrue(readOverrides(overridesFile).isEmpty())
}
@Test
void keepsOverrideWhenExactAndNewerVersionsAreBothResolved() {
ModuleData olderModule = createModule(VERSION_WITHOUT_LICENSE, null)
License publishedLicense = new License(APACHE_NAME, APACHE_URL)
ModuleData newerModule = createModule(VERSION_WITH_LICENSE, publishedLicense)
File overridesFile = createOverridesFile(moduleId(VERSION_WITHOUT_LICENSE))
new ModuleLicenseOverrideFilter(overridesFile)
.filter(createProjectData(olderModule, newerModule))
Map<String, Object> overrides = readOverrides(overridesFile)
assertTrue(overrides.containsKey(moduleId(VERSION_WITHOUT_LICENSE)))
assertEquals([APACHE_NAME], licenseNames(olderModule))
assertEquals([APACHE_NAME], licenseNames(newerModule))
}
@Test
void movesOverrideUsingGradleNumericVersionOrdering() {
String oldVersion = '1.9'
String newVersion = '1.11.0'
ModuleData module = createModule(newVersion, null)
File overridesFile = createOverridesFile(moduleId(oldVersion))
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData(module))
Map<String, Object> overrides = readOverrides(overridesFile)
assertFalse(overrides.containsKey(moduleId(oldVersion)))
assertEquals(
[name: null, url: null, projectUrl: null], overrides[moduleId(newVersion)])
}
@Test
void movesOverrideToLaterVersionWithoutLicenseAndClearsLicenseData() {
ModuleData module = createModule(VERSION_WITH_LICENSE, null)
File overridesFile = createOverridesFile(moduleId(VERSION_WITHOUT_LICENSE))
debugState('before filter', module, overridesFile)
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData(module))
debugState('after filter', module, overridesFile)
Map<String, Object> overrides = readOverrides(overridesFile)
assertFalse(overrides.containsKey(moduleId(VERSION_WITHOUT_LICENSE)))
assertEquals(
[name: null, url: null, projectUrl: null],
overrides[moduleId(VERSION_WITH_LICENSE)])
assertTrue(licenseNames(module).isEmpty())
}
@Test
void preservesExistingOverrideWhenRemovingOlderVersion() {
ModuleData module = createModule(VERSION_WITH_LICENSE, null)
File overridesFile = createOverridesFile(
[
(moduleId(VERSION_WITHOUT_LICENSE)): [
name: APACHE_NAME, url: APACHE_URL
],
(moduleId(VERSION_WITH_LICENSE)): [
name: APACHE_NAME, url: APACHE_URL, projectUrl: PROJECT_URL
]
])
debugState('before filter', module, overridesFile)
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData(module))
debugState('after filter', module, overridesFile)
Map<String, Object> overrides = readOverrides(overridesFile)
assertFalse(overrides.containsKey(moduleId(VERSION_WITHOUT_LICENSE)))
assertEquals(
[name: APACHE_NAME, url: APACHE_URL, projectUrl: PROJECT_URL],
overrides[moduleId(VERSION_WITH_LICENSE)])
assertEquals([APACHE_NAME], licenseNames(module))
}
@Test
void addsMissingOverrideForModuleWithoutLicense() {
ModuleData module = createModule(VERSION_WITHOUT_LICENSE, null)
File overridesFile = createEmptyOverridesFile()
debugState('before filter', module, overridesFile)
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData(module))
debugState('after filter', module, overridesFile)
Map<String, Object> overrides = readOverrides(overridesFile)
assertEquals(
[name: null, url: null, projectUrl: null],
overrides[moduleId(VERSION_WITHOUT_LICENSE)])
assertTrue(licenseNames(module).isEmpty())
}
@Test
void doesNotAddOverrideForModuleWithLicense() {
License publishedLicense = new License(APACHE_NAME, APACHE_URL)
ModuleData module = createModule(VERSION_WITH_LICENSE, publishedLicense)
File overridesFile = createEmptyOverridesFile()
debugState('before filter', module, overridesFile)
new ModuleLicenseOverrideFilter(overridesFile).filter(createProjectData(module))
debugState('after filter', module, overridesFile)
assertTrue(readOverrides(overridesFile).isEmpty())
assertEquals([APACHE_NAME], licenseNames(module))
}
private File createOverridesFile(String moduleId) {
Map<String, Map<String, String>> overrides = [
(moduleId): [name: APACHE_NAME, url: APACHE_URL]
]
return createOverridesFile(overrides)
}
private File createOverridesFile(Map<String, Map<String, String>> overrides) {
File overridesFile = temporaryDirectory.resolve('license-overrides.json').toFile()
overridesFile.setText(JsonOutput.prettyPrint(JsonOutput.toJson(overrides)), 'UTF-8')
return overridesFile
}
private File createEmptyOverridesFile() {
File overridesFile = temporaryDirectory.resolve('license-overrides.json').toFile()
overridesFile.setText('{}', 'UTF-8')
return overridesFile
}
private static Map<String, Object> readOverrides(File overridesFile) {
return new JsonSlurper().parse(overridesFile) as Map<String, Object>
}
private static void debugState(String stage, ModuleData module, File overridesFile) {
String resolvedModuleId = "${module.group}:${module.name}:${module.version}"
Map<String, Object> overrides = readOverrides(overridesFile)
System.out.println(
"[license-override-test] ${stage}: module=${resolvedModuleId}, "
+ "licenses=${licenseNames(module)}, "
+ "matchingOverride=${overrides.containsKey(resolvedModuleId)}, "
+ "overrideKeys=${overrides.keySet().sort()}")
}
private static ProjectData createProjectData(ModuleData module) {
return createProjectData(module as ModuleData[])
}
private static ProjectData createProjectData(ModuleData... modules) {
ConfigurationData configuration =
new ConfigurationData(
'runtimeClasspath', modules as LinkedHashSet<ModuleData>)
return new ProjectData(
ProjectBuilder.builder().build(),
[configuration] as LinkedHashSet<ConfigurationData>)
}
private static ModuleData createModule(String version, License license) {
Set<ManifestData> manifests = new LinkedHashSet<>()
if (license != null) {
manifests.add(
new ManifestData(
MODULE,
version,
null,
null,
null,
[license] as LinkedHashSet<License>,
false))
}
return new ModuleData(
GROUP,
MODULE,
version,
true,
manifests,
new LinkedHashSet<>(),
new LinkedHashSet<>())
}
private static String moduleId(String version) {
return "${GROUP}:${MODULE}:${version}"
}
private static List<String> licenseNames(ModuleData module) {
Set<License> licenses = LicenseDataCollector.multiModuleLicenseInfo(module).licenses
return licenses.collect { license -> license.name }.sort()
}
}
+2
View File
@@ -17,6 +17,8 @@ WORKDIR /app
COPY build.gradle settings.gradle gradlew ./
COPY gradle/ gradle/
COPY buildSrc/build.gradle buildSrc/
COPY buildSrc/src/main/ buildSrc/src/main/
COPY app/core/build.gradle app/core/
COPY app/common/build.gradle app/common/
COPY app/proprietary/build.gradle app/proprietary/
+2
View File
@@ -30,6 +30,8 @@ WORKDIR /app
COPY build.gradle settings.gradle gradlew ./
COPY gradle/ gradle/
COPY buildSrc/build.gradle buildSrc/
COPY buildSrc/src/main/ buildSrc/src/main/
COPY app/core/build.gradle app/core/
COPY app/common/build.gradle app/common/
COPY app/proprietary/build.gradle app/proprietary/
+2
View File
@@ -31,6 +31,8 @@ WORKDIR /app
COPY build.gradle settings.gradle gradlew ./
COPY gradle/ gradle/
COPY buildSrc/build.gradle buildSrc/
COPY buildSrc/src/main/ buildSrc/src/main/
COPY app/core/build.gradle app/core/
COPY app/common/build.gradle app/common/
COPY app/proprietary/build.gradle app/proprietary/
+2
View File
@@ -23,6 +23,8 @@ WORKDIR /app
# Copy gradle files for dependency resolution
COPY build.gradle settings.gradle gradlew ./
COPY gradle/ gradle/
COPY buildSrc/build.gradle buildSrc/
COPY buildSrc/src/main/ buildSrc/src/main/
COPY app/core/build.gradle app/core/
COPY app/common/build.gradle app/common/
COPY app/proprietary/build.gradle app/proprietary/