Skip to content

Commit

Permalink
DEVEX-657 Only include licenses of FSM dependencies
Browse files Browse the repository at this point in the history
- License information is now only collected for FSM dependencies
- Fixed an issue where 'developmentOnly' dependencies added through the Spring Boot Gradle plugin could be included in the FSM
  • Loading branch information
jptrn committed Oct 11, 2024
1 parent 8bbe387 commit 5220da5
Show file tree
Hide file tree
Showing 20 changed files with 218 additions and 81 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ plugins {
id("java-gradle-plugin")
id("net.researchgate.release") version "3.0.2"
id("org.ajoberstar.grgit") version "5.0.0"
id("com.github.jk1.dependency-license-report") version "2.3"
id("com.github.jk1.dependency-license-report") version "2.9"
id("org.cyclonedx.bom") version "1.7.4"
}

Expand Down
46 changes: 30 additions & 16 deletions src/main/kotlin/org/gradle/plugins/fsm/FSMPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,24 @@ import org.gradle.api.artifacts.Dependency
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.plugins.fsm.annotations.FSMAnnotationsPlugin
import org.gradle.plugins.fsm.configurations.FSMConfigurationsPlugin
import org.gradle.plugins.fsm.configurations.FSMConfigurationsPlugin.Companion.FS_CONFIGURATIONS
import org.gradle.plugins.fsm.tasks.bundling.FSM
import org.gradle.plugins.fsm.tasks.verification.IsolationCheck
import org.gradle.plugins.fsm.tasks.verification.ValidateDescriptor
import java.util.*

class FSMPlugin: Plugin<Project> {
class FSMPlugin : Plugin<Project> {

override fun apply(project: Project) {
project.plugins.apply(JavaPlugin::class.java)
project.plugins.apply(FSMConfigurationsPlugin::class.java)
project.plugins.apply(FSMAnnotationsPlugin::class.java)
registerWebappsConfiguration(project)

project.extensions.create(FSM_EXTENSION_NAME, FSMPluginExtension::class.java, project)

Expand All @@ -49,6 +49,13 @@ class FSMPlugin: Plugin<Project> {
configureManifest(project)
}

private fun registerWebappsConfiguration(project: Project) {
val webAppsConfiguration = project.configurations.create(WEBAPPS_CONFIGURATION_NAME)
webAppsConfiguration.description = "Combined Runtime Classpath of all Web-Apps registered with the 'webAppComponent' method."
val implementation = project.configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME)
implementation.extendsFrom(webAppsConfiguration)
}

private fun configureConfigurationTask(project: Project): Task {
val configurationTask = project.tasks.create(CONFIGURE_FSM_TASK_NAME)
configurationTask.dependsOn(project.tasks.getByName(GENERATE_LICENSE_REPORT_TASK_NAME))
Expand Down Expand Up @@ -77,10 +84,6 @@ class FSMPlugin: Plugin<Project> {

removeDefaultJarArtifactFromArchives(project)

val javaPlugin = project.extensions.getByType(JavaPluginExtension::class.java)
val runtimeClasspath = javaPlugin.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).runtimeClasspath

fsmTask.dependsOn(runtimeClasspath)
fsmTask.dependsOn(project.tasks.getByName(GENERATE_LICENSE_REPORT_TASK_NAME))
fsmTask.dependsOn(JavaPlugin.JAR_TASK_NAME)
fsmTask.dependsOn(configurationTask)
Expand All @@ -89,9 +92,6 @@ class FSMPlugin: Plugin<Project> {
// Validate FSM immediately
fsmTask.finalizedBy(validateTask)

val outputs = project.tasks.getByName(JavaPlugin.JAR_TASK_NAME).outputs.files
fsmTask.addToClasspath(runtimeClasspath + outputs)

return fsmTask
}

Expand Down Expand Up @@ -126,16 +126,22 @@ class FSMPlugin: Plugin<Project> {
jarTask.exclude("module-isolated.xml")
}

/**
* Configures the generation of the FSM license report. A CSV license file is added into the module's META-INF/
* directory. Additionally, we attempt to include all license texts into a subfolder.
* All licenses of third-party libs included in the FSM should be included, i.e.,
*
* - Libraries added with `fsModuleCompile`, `fsServerCompile` or `fsWebCompile`
* - Libraries included from web apps added with the `webAppComponent` method in the `firstSpiritModule` block
* - Libraries included with the `libraries` method in the `firstSpiritModule` block
*/
private fun configureLicenseReport(project: Project) {
val licenseReportTask = project.tasks.withType(ReportTask::class.java).first()

// Library names and web apps are only available after the configuration phase
val preparationAction = { _: Task ->
val fsmPluginExtension = project.extensions.getByType(FSMPluginExtension::class.java)
val configurationNames = mutableListOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)
// Library names are only available after the configuration phase
configurationNames.addAll(fsmPluginExtension.libraries.asSequence().mapNotNull { it.configuration?.name })
with(project.extensions.getByType(LicenseReportExtension::class.java)) {
configurations = configurationNames.toTypedArray()
configurations = getLicenseReportConfigurations(project).toTypedArray()
}
}
licenseReportTask.doFirst(preparationAction)
Expand All @@ -151,11 +157,18 @@ class FSMPlugin: Plugin<Project> {
with(project.extensions.getByType(LicenseReportExtension::class.java)) {
// Set output directory for the report data.
outputDir = project.layout.buildDirectory.dir(FSM.LICENSES_DIR_NAME).get().asFile.absolutePath
configurations = arrayOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)
configurations = arrayOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME) // Replaced with the correct configurations later, see preparationAction above
renderers = arrayOf(CsvReportRenderer())
}
}

private fun getLicenseReportConfigurations(project: Project): Set<String> {
val fsmPluginExtension = project.extensions.getByType(FSMPluginExtension::class.java)
return FS_CONFIGURATIONS + // fsModuleCompile, fsServerCompile, fsWebCompile
fsmPluginExtension.libraries.asSequence().mapNotNull { it.configuration?.name } + // library components
WEBAPPS_CONFIGURATION_NAME // webapps declared with the 'webAppComponent' method
}

private fun configureManifest(project: Project) {
// Configure each JAR's manifest in the project. This also includes the .fsm file
// We cannot configure the tasks directly, because the project is not evaluated yet
Expand Down Expand Up @@ -205,6 +218,7 @@ class FSMPlugin: Plugin<Project> {
const val VALIDATE_DESCRIPTOR_TASK_NAME = "validateDescriptor"
const val ISOLATION_CHECK_TASK_NAME = "checkIsolation"
const val GENERATE_LICENSE_REPORT_TASK_NAME = "generateLicenseReport"
const val WEBAPPS_CONFIGURATION_NAME = "fsmWebappsRuntime"
const val VERSIONS_PROPERTIES_FILE = "/fsm-gradle-plugin/versions.properties"
}

Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/org/gradle/plugins/fsm/FSMPluginExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import de.espirit.mavenplugins.fsmchecker.ComplianceLevel
import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin

open class FSMPluginExtension(val project: Project) {

Expand All @@ -21,12 +20,14 @@ open class FSMPluginExtension(val project: Project) {
fun webAppComponent(webAppName: String, webAppProject: Project) {
fsmWebApps[webAppName] = webAppProject

// Register webapp dependency in extra configuration
val webAppsConfiguration = project.configurations.getByName(FSMPlugin.WEBAPPS_CONFIGURATION_NAME)
// This is the same as
// implementation project("projectName", configuration: "default")
// and is required because of an error with the variant selection regarding the license report plugin.
// For more information, see https://github.com/jk1/Gradle-License-Report/issues/170
val projectDependency = project.dependencies.project(mapOf("path" to webAppProject.path, "configuration" to "default"))
project.dependencies.add(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, projectDependency)
project.dependencies.add(webAppsConfiguration.name, projectDependency)
}

/**
Expand Down
22 changes: 2 additions & 20 deletions src/main/kotlin/org/gradle/plugins/fsm/tasks/bundling/FSM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package org.gradle.plugins.fsm.tasks.bundling

import org.gradle.api.Project
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.jvm.tasks.Jar
import org.gradle.plugins.fsm.FSMPlugin
import org.gradle.plugins.fsm.FSMPluginExtension
import org.gradle.plugins.fsm.compileDependencies
import org.gradle.plugins.fsm.configurations.FSMConfigurationsPlugin
Expand Down Expand Up @@ -40,12 +39,6 @@ abstract class FSM: Jar() {
@Internal("Visible for tests")
val duplicateFsmResourceFiles = mutableSetOf<File>()

/**
* The fsm runtime classpath. All libraries in this classpath will be copied to 'fsm/lib' folder
*/
@get:InputFiles
var classpath: FileCollection = project.files()

init {
archiveExtension.set(FSM_EXTENSION)
destinationDirectory.set(project.layout.buildDirectory.dir("fsm"))
Expand All @@ -59,9 +52,7 @@ abstract class FSM: Jar() {
project.moduleScopeDependencies().forEach { lib.from(it.file) }
project.configurations.getByName(FS_WEB_COMPILE_CONFIGURATION_NAME).resolve().forEach { lib.from(it) }
lib.from(project.tasks.named(JavaPlugin.JAR_TASK_NAME))
pluginExtension.getWebApps().values
.map { project -> project.configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME) }
.forEach { lib.from(it) }
project.configurations.getByName(FSMPlugin.WEBAPPS_CONFIGURATION_NAME).resolve().forEach { lib.from(it) }
pluginExtension.getWebApps().values
.map { project -> project.tasks.named(JavaPlugin.JAR_TASK_NAME) }
.filter { task -> task.isPresent }
Expand Down Expand Up @@ -228,15 +219,6 @@ abstract class FSM: Jar() {
}


/**
* Adds files to the classpath to include in the FSM archive.
*
* @param classpathToAdd The files to add.
*/
fun addToClasspath(classpathToAdd: FileCollection) {
classpath += classpathToAdd
}

/**
* Helper method for executing Unit tests
*/
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/org/gradle/plugins/fsm/FSMManifestTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class FSMManifestTest {
fun manifest() {
// Copy test project files from resources folder to temp dir
val resourcesUrl = FSMManifestTest::class.java.classLoader.getResource("manifest")
?: error("manifest not found")
?: error("test project files not found")
val resourcesPath = Paths.get(resourcesUrl.toURI())
resourcesPath.toFile().copyRecursively(testDir)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package org.gradle.plugins.fsm
import org.assertj.core.api.Assertions.assertThat
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.plugins.JavaPlugin
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand All @@ -30,7 +29,7 @@ class FSMPluginExtensionTest {
assertThat(testling.getWebApps()).containsEntry("web_b", webAppSubprojectB)

// Ensure the project has a compile dependency on the subprojects
val dependencyProjects = project.configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME)
val dependencyProjects = project.configurations.getByName(FSMPlugin.WEBAPPS_CONFIGURATION_NAME)
.dependencies.filterIsInstance<ProjectDependency>().map { it.dependencyProject }
assertThat(dependencyProjects).containsExactly(webAppSubprojectA, webAppSubprojectB)
}
Expand Down
5 changes: 2 additions & 3 deletions src/test/kotlin/org/gradle/plugins/fsm/FSMPluginTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,11 @@ class FSMPluginTest {
}

@Test
fun `FSM-task depends on jar and classes tasks`() {
fun `FSM-task depends on jar task`() {
project.plugins.apply(FSMPlugin.NAME)

val fsm = project.tasks.getByName(FSMPlugin.FSM_TASK_NAME)
assertThat(fsm).dependsOn(JavaPlugin.JAR_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME,
FSMPlugin.GENERATE_LICENSE_REPORT_TASK_NAME, FSMPlugin.CONFIGURE_FSM_TASK_NAME)
assertThat(fsm).dependsOn(JavaPlugin.JAR_TASK_NAME, FSMPlugin.GENERATE_LICENSE_REPORT_TASK_NAME, FSMPlugin.CONFIGURE_FSM_TASK_NAME)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.plugins.JavaPlugin
import org.gradle.plugins.fsm.FSMPlugin.Companion.WEBAPPS_CONFIGURATION_NAME
import org.gradle.plugins.fsm.FSMPluginExtension
import org.gradle.plugins.fsm.annotations.FSMAnnotationsPlugin
import org.gradle.plugins.fsm.configurations.FSMConfigurationsPlugin
Expand All @@ -25,6 +27,8 @@ class WebAppComponentsTest {
project.plugins.apply(FSMAnnotationsPlugin::class.java)
project.plugins.apply(FSMConfigurationsPlugin::class.java)
project.extensions.create("fsmPlugin", FSMPluginExtension::class.java)
val webAppConfiguration = project.configurations.create(WEBAPPS_CONFIGURATION_NAME)
project.configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(webAppConfiguration)
project.setArtifactoryCredentialsFromLocalProperties()
project.defineArtifactoryForProject()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.gradle.plugins.fsm.tasks.bundling

import org.assertj.core.api.Assertions.assertThat
import org.gradle.plugins.fsm.FSMPlugin
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.io.File
import java.nio.file.Paths
import java.util.zip.ZipFile

class FSMLibraryTest {

@Test
fun `webApp libs`(@TempDir testDir: File) {
val resourcesUrl = FSMLibraryTest::class.java.classLoader.getResource("webapp-project")
?: error("test project files not found")
val resourcesPath = Paths.get(resourcesUrl.toURI())
resourcesPath.toFile().copyRecursively(testDir)

// Execute the gradle build
val result = GradleRunner.create()
.withProjectDir(testDir)
.withArguments(FSMPlugin.FSM_TASK_NAME)
.withPluginClasspath()
.build()
assertThat(result.task(':' + FSMPlugin.FSM_TASK_NAME)?.outcome).isEqualTo(TaskOutcome.SUCCESS)

// get result
val fsmFile = testDir.resolve("build/fsm/testproject-webapp-1.0-SNAPSHOT.fsm")
assertThat(fsmFile).exists()
ZipFile(fsmFile).use { zipFile ->
// check contents of lib folder
assertThat(zipFile.getEntry("lib/slf4j-api-2.0.13.jar")).isNotNull
assertThat(zipFile.getEntry("lib/fs-isolated-runtime-5.2.220309.jar")).isNull()
assertThat(zipFile.getEntry("lib/fs-isolated-webrt-5.2.220309.jar")).isNull()
}
}
}
Loading

0 comments on commit 5220da5

Please sign in to comment.