diff --git a/app/allowed-licenses.json b/app/allowed-licenses.json index 9b5ef66556..033661629f 100644 --- a/app/allowed-licenses.json +++ b/app/allowed-licenses.json @@ -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" diff --git a/app/license-overrides.json b/app/license-overrides.json new file mode 100644 index 0000000000..0ea43fcaf9 --- /dev/null +++ b/app/license-overrides.json @@ -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" + } +} diff --git a/build.gradle b/build.gradle index 2af5522a73..31fa6b1bdb 100644 --- a/build.gradle +++ b/build.gradle @@ -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) as Set +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 diff --git a/buildSrc/README.md b/buildSrc/README.md new file mode 100644 index 0000000000..bbcfb0de6c --- /dev/null +++ b/buildSrc/README.md @@ -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///tree/ +``` + +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. diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000000..25eee5c0f5 --- /dev/null +++ b/buildSrc/build.gradle @@ -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 +} diff --git a/buildSrc/src/main/groovy/stirling/software/gradle/ModuleLicenseOverrideFilter.groovy b/buildSrc/src/main/groovy/stirling/software/gradle/ModuleLicenseOverrideFilter.groovy new file mode 100644 index 0000000000..df0d76158c --- /dev/null +++ b/buildSrc/src/main/groovy/stirling/software/gradle/ModuleLicenseOverrideFilter.groovy @@ -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_COMPARATOR = + new DefaultVersionComparator().asVersionComparator() + + private final File overridesFile + + ModuleLicenseOverrideFilter(File overridesFile) { + this.overridesFile = overridesFile + } + + @Override + ProjectData filter(ProjectData projectData) { + Map> overrides = loadOverrides() + List modules = projectData.configurations + .collectMany { configuration -> configuration.dependencies } + Map> modulesByCoordinate = modules + .groupBy { module -> moduleCoordinate(module) } + + boolean overridesChanged = false + overrides.keySet().toList().each { overrideId -> + ModuleCoordinates overrideModule = parseModuleId(overrideId) + List 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> overrides) { + String moduleId = moduleId(module) + Map 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 licenses = [new License(licenseName, licenseUrl)] as LinkedHashSet + ManifestData manifest = + new ManifestData(module.name, module.version, null, null, projectUrl, licenses, false) + Set manifests = new LinkedHashSet<>(module.manifests ?: []) + manifests.add(manifest) + module.manifests = manifests + } + + private Map> 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> + } + + private void saveOverrides(Map> 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 parts = moduleId.split(':', 3) as List + 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 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 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 + } + } +} diff --git a/buildSrc/src/test/groovy/stirling/software/gradle/ModuleLicenseOverrideFilterTest.groovy b/buildSrc/src/test/groovy/stirling/software/gradle/ModuleLicenseOverrideFilterTest.groovy new file mode 100644 index 0000000000..7536d08af0 --- /dev/null +++ b/buildSrc/src/test/groovy/stirling/software/gradle/ModuleLicenseOverrideFilterTest.groovy @@ -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 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 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 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 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 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 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 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 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> overrides = [ + (moduleId): [name: APACHE_NAME, url: APACHE_URL] + ] + return createOverridesFile(overrides) + } + + private File createOverridesFile(Map> 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 readOverrides(File overridesFile) { + return new JsonSlurper().parse(overridesFile) as Map + } + + private static void debugState(String stage, ModuleData module, File overridesFile) { + String resolvedModuleId = "${module.group}:${module.name}:${module.version}" + Map 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) + return new ProjectData( + ProjectBuilder.builder().build(), + [configuration] as LinkedHashSet) + } + + private static ModuleData createModule(String version, License license) { + Set manifests = new LinkedHashSet<>() + if (license != null) { + manifests.add( + new ManifestData( + MODULE, + version, + null, + null, + null, + [license] as LinkedHashSet, + 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 licenseNames(ModuleData module) { + Set licenses = LicenseDataCollector.multiModuleLicenseInfo(module).licenses + return licenses.collect { license -> license.name }.sort() + } +} diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index 5c6680661f..ab79caf9f0 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -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/ diff --git a/docker/embedded/Dockerfile b/docker/embedded/Dockerfile index 54458667be..4ae0ee81e7 100644 --- a/docker/embedded/Dockerfile +++ b/docker/embedded/Dockerfile @@ -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/ diff --git a/docker/embedded/Dockerfile.fat b/docker/embedded/Dockerfile.fat index 9b2655987f..6e679b97c8 100644 --- a/docker/embedded/Dockerfile.fat +++ b/docker/embedded/Dockerfile.fat @@ -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/ diff --git a/docker/embedded/Dockerfile.ultra-lite b/docker/embedded/Dockerfile.ultra-lite index bcf189e9ee..14f9e934d0 100644 --- a/docker/embedded/Dockerfile.ultra-lite +++ b/docker/embedded/Dockerfile.ultra-lite @@ -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/