From 924c0ec81b3295395da4354d90bd5a48b5b00279 Mon Sep 17 00:00:00 2001 From: Roberto Perez Alcolea Date: Mon, 6 Apr 2026 12:54:27 -0700 Subject: [PATCH 1/2] Fix Task.getProject() deprecation warnings in DebCopyAction and SystemPackagingTask --- .../com/netflix/gradle/plugins/deb/Deb.groovy | 4 +-- .../gradle/plugins/deb/DebPlugin.groovy | 2 +- .../docker/OsPackageDockerBasePlugin.groovy | 2 +- .../packaging/SystemPackagingTask.groovy | 29 ++++++++++--------- .../com/netflix/gradle/plugins/rpm/Rpm.groovy | 4 +-- .../gradle/plugins/rpm/RpmPlugin.groovy | 2 +- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/main/groovy/com/netflix/gradle/plugins/deb/Deb.groovy b/src/main/groovy/com/netflix/gradle/plugins/deb/Deb.groovy index 0dfbc246..eea15795 100755 --- a/src/main/groovy/com/netflix/gradle/plugins/deb/Deb.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/deb/Deb.groovy @@ -60,8 +60,8 @@ abstract class Deb extends SystemPackagingTask { } @Override - protected void applyConventions() { - super.applyConventions() + protected void applyConventions(org.gradle.api.Project projectRef = project) { + super.applyConventions(projectRef) // Apply default conventions FIRST (lowest priority) exten.uid.convention(0) diff --git a/src/main/groovy/com/netflix/gradle/plugins/deb/DebPlugin.groovy b/src/main/groovy/com/netflix/gradle/plugins/deb/DebPlugin.groovy index 6b532cc2..674d3606 100644 --- a/src/main/groovy/com/netflix/gradle/plugins/deb/DebPlugin.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/deb/DebPlugin.groovy @@ -36,7 +36,7 @@ class DebPlugin implements Plugin { project.tasks.withType(Deb).configureEach { Deb deb -> RpmPlugin.applyAliases(deb) // RPM Specific aliases DebPlugin.applyAliases(deb) // DEB-specific aliases - deb.applyConventions() + deb.applyConventions(project) } } diff --git a/src/main/groovy/com/netflix/gradle/plugins/docker/OsPackageDockerBasePlugin.groovy b/src/main/groovy/com/netflix/gradle/plugins/docker/OsPackageDockerBasePlugin.groovy index 4df9d918..f728e1b3 100644 --- a/src/main/groovy/com/netflix/gradle/plugins/docker/OsPackageDockerBasePlugin.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/docker/OsPackageDockerBasePlugin.groovy @@ -21,7 +21,7 @@ class OsPackageDockerBasePlugin implements Plugin { project.plugins.apply(CommonPackagingPlugin) // Some defaults, if not set by the user project.tasks.withType(SystemPackageDockerfile).configureEach { SystemPackageDockerfile systemPackageDockerfile -> - systemPackageDockerfile.applyConventions() + systemPackageDockerfile.applyConventions(project) } project.plugins.withType(DockerRemoteApiPlugin).configureEach { DockerRemoteApiPlugin dockerRemoteApiPlugin -> diff --git a/src/main/groovy/com/netflix/gradle/plugins/packaging/SystemPackagingTask.groovy b/src/main/groovy/com/netflix/gradle/plugins/packaging/SystemPackagingTask.groovy index a60a5246..4e2aac51 100755 --- a/src/main/groovy/com/netflix/gradle/plugins/packaging/SystemPackagingTask.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/packaging/SystemPackagingTask.groovy @@ -18,6 +18,7 @@ package com.netflix.gradle.plugins.packaging import com.netflix.gradle.plugins.utils.DeprecationLoggerUtils import groovy.transform.CompileDynamic +import org.gradle.api.Project import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.file.FileCollection import org.gradle.api.file.ProjectLayout @@ -27,6 +28,7 @@ import org.gradle.api.internal.file.copy.CopyActionExecuter import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.* import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.gradle.util.GradleVersion @@ -43,6 +45,9 @@ abstract class SystemPackagingTask extends OsPackageAbstractArchiveTask { @Inject abstract ObjectFactory getObjectFactory() + @Inject + abstract ProviderFactory getProviders() + @Nested abstract SystemPackagingExtension getExten() // Not File extension or ext list of properties, different kind of Extension @@ -489,30 +494,30 @@ abstract class SystemPackagingTask extends OsPackageAbstractArchiveTask { def customField(Map fields) { getExten().customField(fields) } // Apply conventions to task extension properties using modern Property API - protected void applyConventions() { + protected void applyConventions(Project projectRef = project) { // Apply default conventions FIRST (lowest priority) exten.packageName.convention(getArchiveBaseName()) exten.release.convention(getArchiveClassifier()) - exten.version.convention(project.provider { sanitizeVersion() }) + exten.version.convention(getProviders().provider { + sanitizeVersion(parentExten?.getVersion()?.getOrNull() ?: projectRef.getVersion().toString()) + }) exten.epoch.convention(0) exten.signingKeyId.convention('') exten.signingKeyPassphrase.convention('') exten.signingKeyRingFile.convention( - project.layout.file(project.provider { + projectLayout.file(getProviders().provider { File defaultFile = new File(System.getProperty('user.home'), '.gnupg/secring.gpg') defaultFile.exists() ? defaultFile : null }) ) - exten.user.convention(project.provider { getPackager() }) - exten.maintainer.convention(project.provider { getPackager() }) - exten.uploaders.convention(project.provider { getPackager() }) + exten.user.convention(getProviders().provider { getPackager() }) + exten.maintainer.convention(getProviders().provider { getPackager() }) + exten.uploaders.convention(getProviders().provider { getPackager() }) exten.permissionGroup.convention('') exten.setgid.convention(false) exten.buildHost.convention(HOST_NAME) exten.summary.convention(exten.packageName) - exten.packageDescription.convention(project.provider { - project.getDescription() ?: '' - }) + exten.packageDescription.convention(getProviders().provider { projectRef.getDescription() ?: '' }) exten.license.convention('') exten.packager.convention(System.getProperty('user.name', '')) exten.distribution.convention('') @@ -557,15 +562,11 @@ abstract class SystemPackagingTask extends OsPackageAbstractArchiveTask { // Task-specific conventions if(GradleVersion.current().compareTo(GradleVersion.version("7.0.0")) >= 0) { - getArchiveFileName().convention(project.provider { assembleArchiveName() }) + getArchiveFileName().convention(getProviders().provider { assembleArchiveName() }) getArchiveVersion().convention(determineArchiveVersion()) } } - private String sanitizeVersion() { - sanitizeVersion(parentExten?.getVersion()?.getOrNull() ?: project.getVersion().toString()) - } - private String sanitizeVersion(String version) { version == 'unspecified' ? '0' : version.replaceAll(/\+.*/, '').replaceAll(/-/, '~') } diff --git a/src/main/groovy/com/netflix/gradle/plugins/rpm/Rpm.groovy b/src/main/groovy/com/netflix/gradle/plugins/rpm/Rpm.groovy index 15334e9b..08b61741 100755 --- a/src/main/groovy/com/netflix/gradle/plugins/rpm/Rpm.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/rpm/Rpm.groovy @@ -68,8 +68,8 @@ abstract class Rpm extends SystemPackagingTask { } @Override - protected void applyConventions() { - super.applyConventions() + protected void applyConventions(org.gradle.api.Project projectRef = project) { + super.applyConventions(projectRef) // Apply default conventions FIRST (lowest priority) exten.addParentDirs.convention(true) diff --git a/src/main/groovy/com/netflix/gradle/plugins/rpm/RpmPlugin.groovy b/src/main/groovy/com/netflix/gradle/plugins/rpm/RpmPlugin.groovy index 5663a5c9..1c7552b9 100644 --- a/src/main/groovy/com/netflix/gradle/plugins/rpm/RpmPlugin.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/rpm/RpmPlugin.groovy @@ -39,7 +39,7 @@ class RpmPlugin implements Plugin { // Some defaults, if not set by the user project.tasks.withType(Rpm).configureEach { Rpm rpm -> RpmPlugin.applyAliases(rpm) // RPM Specific aliases - rpm.applyConventions() + rpm.applyConventions(project) } } From 8c8efbfc0a66f3b8a4f8bafe5306307c8d90622a Mon Sep 17 00:00:00 2001 From: Roberto Perez Alcolea Date: Mon, 6 Apr 2026 13:18:55 -0700 Subject: [PATCH 2/2] Add configuration cache tests covering cache invalidation, deprecation warnings, from configurations, file-based scripts, mixed deb+rpm, file permissions, and Spring Boot limitation --- ...spackageApplicationSpringBootPlugin.groovy | 3 +- .../daemon/OspackageDaemonPlugin.groovy | 6 +- .../com/netflix/gradle/plugins/deb/Deb.groovy | 5 +- .../gradle/plugins/deb/DebCopyAction.groovy | 2 +- .../packaging/SystemPackagingTask.groovy | 6 +- .../com/netflix/gradle/plugins/rpm/Rpm.groovy | 5 +- .../plugins/ConfigurationCacheSpec.groovy | 683 ++++++++++++++++++ 7 files changed, 697 insertions(+), 13 deletions(-) diff --git a/src/main/groovy/com/netflix/gradle/plugins/application/OspackageApplicationSpringBootPlugin.groovy b/src/main/groovy/com/netflix/gradle/plugins/application/OspackageApplicationSpringBootPlugin.groovy index f9893b2a..08938445 100644 --- a/src/main/groovy/com/netflix/gradle/plugins/application/OspackageApplicationSpringBootPlugin.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/application/OspackageApplicationSpringBootPlugin.groovy @@ -124,9 +124,10 @@ class OspackageApplicationSpringBootPlugin implements Plugin { // Workaround for https://github.com/gradle/gradle/issues/16371 if (GradleVersion.current().baseVersion >= GradleVersion.version('6.4').baseVersion) { + def mainClass = project.application.mainClass project.tasks.named(ApplicationPlugin.TASK_START_SCRIPTS_NAME).configure { doFirst { - if (!project.application.mainClass.isPresent()) { + if (!mainClass.isPresent()) { throw new GradleException("mainClass should be configured in order to generate a valid start script. i.e. mainClass = 'com.netflix.app.MyApp'") } } diff --git a/src/main/groovy/com/netflix/gradle/plugins/daemon/OspackageDaemonPlugin.groovy b/src/main/groovy/com/netflix/gradle/plugins/daemon/OspackageDaemonPlugin.groovy index ee6d8f74..0a4fe7e0 100644 --- a/src/main/groovy/com/netflix/gradle/plugins/daemon/OspackageDaemonPlugin.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/daemon/OspackageDaemonPlugin.groovy @@ -29,7 +29,6 @@ import org.gradle.api.Project class OspackageDaemonPlugin implements Plugin { public static final String POST_INSTALL_TEMPLATE = "postInstall" - Project project DaemonExtension extension DaemonTemplatesConfigExtension daemonTemplatesConfigExtension DefaultDaemonDefinitionExtension defaultDefinition @@ -54,7 +53,6 @@ class OspackageDaemonPlugin implements Plugin { @Override void apply(Project project) { - this.project = project project.plugins.apply(SystemPackagingBasePlugin) DomainObjectSet daemonsList = WrapUtil.toDomainObjectSet(DaemonDefinition) @@ -105,7 +103,7 @@ class OspackageDaemonPlugin implements Plugin { // Use Property API instead of conventionMapping it.destDir.convention(outputDirProvider) it.templatesFolder.convention(daemonTemplatesConfigExtension.folder ?: DEFAULT_TEMPLATES_FOLDER) - it.context.convention(project.provider { + it.context.convention(project.providers.provider { Map context = toContext(defaults, definition) context.daemonName = daemonName context.isRedhat = isRedhat @@ -123,7 +121,7 @@ class OspackageDaemonPlugin implements Plugin { } def destPathProvider = templateTaskProvider.flatMap { templateTask -> - project.provider { + project.providers.provider { getDestPath(destPathTemplate, templateTask) } } diff --git a/src/main/groovy/com/netflix/gradle/plugins/deb/Deb.groovy b/src/main/groovy/com/netflix/gradle/plugins/deb/Deb.groovy index eea15795..3eaddb48 100755 --- a/src/main/groovy/com/netflix/gradle/plugins/deb/Deb.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/deb/Deb.groovy @@ -21,6 +21,7 @@ import com.netflix.gradle.plugins.packaging.Dependency import com.netflix.gradle.plugins.packaging.SystemPackagingTask import com.netflix.gradle.plugins.utils.DeprecationLoggerUtils import groovy.transform.CompileDynamic +import org.gradle.api.Project import org.gradle.api.file.ProjectLayout import org.gradle.api.provider.Property import org.gradle.api.tasks.Input @@ -60,8 +61,8 @@ abstract class Deb extends SystemPackagingTask { } @Override - protected void applyConventions(org.gradle.api.Project projectRef = project) { - super.applyConventions(projectRef) + protected void applyConventions(Project project) { + super.applyConventions(project) // Apply default conventions FIRST (lowest priority) exten.uid.convention(0) diff --git a/src/main/groovy/com/netflix/gradle/plugins/deb/DebCopyAction.groovy b/src/main/groovy/com/netflix/gradle/plugins/deb/DebCopyAction.groovy index 55481079..652a8140 100755 --- a/src/main/groovy/com/netflix/gradle/plugins/deb/DebCopyAction.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/deb/DebCopyAction.groovy @@ -314,7 +314,7 @@ class DebCopyAction extends AbstractPackagingCopyAction { maker.setDeb(debFile) if (StringUtils.isNotBlank(task.getSigningKeyId()) && StringUtils.isNotBlank(task.getSigningKeyPassphrase()) - && task.getSigningKeyRingFile().exists()) { + && task.getSigningKeyRingFile()?.exists()) { maker.setKey(task.getSigningKeyId()) maker.setPassphrase(task.getSigningKeyPassphrase()) maker.setKeyring(task.getSigningKeyRingFile()) diff --git a/src/main/groovy/com/netflix/gradle/plugins/packaging/SystemPackagingTask.groovy b/src/main/groovy/com/netflix/gradle/plugins/packaging/SystemPackagingTask.groovy index 4e2aac51..fcb7a7ec 100755 --- a/src/main/groovy/com/netflix/gradle/plugins/packaging/SystemPackagingTask.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/packaging/SystemPackagingTask.groovy @@ -494,12 +494,12 @@ abstract class SystemPackagingTask extends OsPackageAbstractArchiveTask { def customField(Map fields) { getExten().customField(fields) } // Apply conventions to task extension properties using modern Property API - protected void applyConventions(Project projectRef = project) { + protected void applyConventions(Project project = project) { // Apply default conventions FIRST (lowest priority) exten.packageName.convention(getArchiveBaseName()) exten.release.convention(getArchiveClassifier()) exten.version.convention(getProviders().provider { - sanitizeVersion(parentExten?.getVersion()?.getOrNull() ?: projectRef.getVersion().toString()) + sanitizeVersion(parentExten?.getVersion()?.getOrNull() ?: project.getVersion().toString()) }) exten.epoch.convention(0) exten.signingKeyId.convention('') @@ -517,7 +517,7 @@ abstract class SystemPackagingTask extends OsPackageAbstractArchiveTask { exten.setgid.convention(false) exten.buildHost.convention(HOST_NAME) exten.summary.convention(exten.packageName) - exten.packageDescription.convention(getProviders().provider { projectRef.getDescription() ?: '' }) + exten.packageDescription.convention(getProviders().provider { project.getDescription() ?: '' }) exten.license.convention('') exten.packager.convention(System.getProperty('user.name', '')) exten.distribution.convention('') diff --git a/src/main/groovy/com/netflix/gradle/plugins/rpm/Rpm.groovy b/src/main/groovy/com/netflix/gradle/plugins/rpm/Rpm.groovy index 08b61741..d397e6e0 100755 --- a/src/main/groovy/com/netflix/gradle/plugins/rpm/Rpm.groovy +++ b/src/main/groovy/com/netflix/gradle/plugins/rpm/Rpm.groovy @@ -20,6 +20,7 @@ import com.netflix.gradle.plugins.packaging.AbstractPackagingCopyAction import com.netflix.gradle.plugins.packaging.SystemPackagingTask import com.netflix.gradle.plugins.utils.DeprecationLoggerUtils import groovy.transform.CompileDynamic +import org.gradle.api.Project import org.gradle.api.file.ProjectLayout import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property @@ -68,8 +69,8 @@ abstract class Rpm extends SystemPackagingTask { } @Override - protected void applyConventions(org.gradle.api.Project projectRef = project) { - super.applyConventions(projectRef) + protected void applyConventions(Project project) { + super.applyConventions(project) // Apply default conventions FIRST (lowest priority) exten.addParentDirs.convention(true) diff --git a/src/test/groovy/com/netflix/gradle/plugins/ConfigurationCacheSpec.groovy b/src/test/groovy/com/netflix/gradle/plugins/ConfigurationCacheSpec.groovy index 70500b95..b5615c63 100644 --- a/src/test/groovy/com/netflix/gradle/plugins/ConfigurationCacheSpec.groovy +++ b/src/test/groovy/com/netflix/gradle/plugins/ConfigurationCacheSpec.groovy @@ -16,6 +16,8 @@ package com.netflix.gradle.plugins +import spock.lang.Ignore + /** * Tests that verify configuration cache support for Debian packaging. * These tests run tasks twice to ensure the configuration cache is stored and reused. @@ -356,4 +358,685 @@ exec multilog t ./main and: 'package is created' new File(projectDir, "build/distributions/simple-test_1.0_all.deb").exists() } + + def 'no Task.getProject() deprecation warnings when using project version description and supplementaryControl'() { + given: + def srcDir = new File(projectDir, 'src/app') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + def controlFile = new File(projectDir, 'src/changelog') + controlFile.text = 'Initial release' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + version = '1.0.0' + description = 'My application' + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'deprecation-test' + supplementaryControl 'src/changelog' + + from('src/app') { + into '/opt/app' + } + } + """.stripIndent() + + when: + def result = runTasks('myDeb', '--warning-mode', 'all') + + then: 'no Task.project at execution time deprecation warning' + !result.output.contains('Task.project at execution time has been deprecated') + !result.output.contains('Invocation of Task.project at execution time') + } + + def 'ospackage parent extension propagates version and user to child tasks with configuration cache'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.ospackage' + } + + ospackage { + packageName = 'parent-ext-test' + version = '3.0.0' + user = 'myuser' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run buildDeb first time' + def result1 = runTasks('buildDeb', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run buildDeb second time' + def result2 = runTasks('buildDeb', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created with values from parent extension' + new File(projectDir, 'build/distributions/parent-ext-test_3.0.0_all.deb').exists() + } + + def 'rpm derives version from project.version with configuration cache'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.rpm' + } + + version = '2.0.0' + + tasks.register('myRpm', com.netflix.gradle.plugins.rpm.Rpm) { + packageName = 'rpm-project-version-test' + release = '1' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run myRpm first time' + def result1 = runTasks('myRpm', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myRpm second time' + def result2 = runTasks('myRpm', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created with version from project' + new File(projectDir, 'build/distributions/rpm-project-version-test-2.0.0-1.noarch.rpm').exists() + } + + def 'packager convention propagates to user maintainer and uploaders with configuration cache'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'packager-test' + version = '1.0.0' + packager = 'Test Packager' + // user, maintainer, uploaders intentionally not set — should default to packager + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run myDeb first time' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myDeb second time' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created' + new File(projectDir, 'build/distributions/packager-test_1.0.0_all.deb').exists() + } + + def 'signing key ring file convention works with configuration cache'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'signing-convention-test' + version = '1.0.0' + // Configure signing key ID and passphrase — ring file uses the lazy convention + // that checks ~/.gnupg/secring.gpg at execution time (not config time) + signingKeyId = 'ABCD1234' + signingKeyPassphrase = 'test-passphrase' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run myDeb first time' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myDeb second time' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created (unsigned since ring file does not exist)' + new File(projectDir, 'build/distributions/signing-convention-test_1.0.0_all.deb').exists() + } + + def 'supplementaryControl files work with configuration cache'() { + given: + def controlFile = new File(projectDir, 'src/changelog') + controlFile.parentFile.mkdirs() + controlFile.text = 'Initial release' + + def srcDir = new File(projectDir, 'src/app') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'supplementary-test' + version = '1.0.0' + supplementaryControl 'src/changelog' + + from('src/app') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run myDeb first time' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myDeb second time' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created' + new File(projectDir, 'build/distributions/supplementary-test_1.0.0_all.deb').exists() + } + + def 'version and description derived from project work with configuration cache'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + version = '2.5.0' + description = 'My application' + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'project-defaults-test' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run myDeb first time' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myDeb second time' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created with version from project' + new File(projectDir, 'build/distributions/project-defaults-test_2.5.0_all.deb').exists() + } + + def 'rpm plugin works with configuration cache'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.rpm' + } + + tasks.register('myRpm', com.netflix.gradle.plugins.rpm.Rpm) { + packageName = 'rpm-cc-test' + version = '1.0.0' + release = '1' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run myRpm first time' + def result1 = runTasks('myRpm', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myRpm second time' + def result2 = runTasks('myRpm', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created' + new File(projectDir, 'build/distributions/rpm-cc-test-1.0.0-1.noarch.rpm').exists() + } + + def 'configuration cache is invalidated and re-stored when build file changes'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'cache-invalidation-test' + version = '1.0.0' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'first run - CC stored' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: + result1.output.contains('Configuration cache entry stored') + + when: 'build version changes' + buildFile.text = buildFile.text.replace("version = '1.0.0'", "version = '2.0.0'") + + and: 're-run with changed build file' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: 'CC is re-stored because the build configuration changed' + result2.output.contains('Configuration cache entry stored') + + and: 'new version artifact is built' + new File(projectDir, 'build/distributions/cache-invalidation-test_2.0.0_all.deb').exists() + } + + def 'task re-executes when source files change while configuration cache is reused'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + def srcFile = new File(srcDir, 'app.txt') + srcFile.text = 'original content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'incremental-cc-test' + version = '1.0.0' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'first run - CC stored, task executes' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: + result1.output.contains('Configuration cache entry stored') + + when: 'second run - CC reused, task is UP-TO-DATE' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: + result2.output.contains('Configuration cache entry reused') + result2.output.contains('UP-TO-DATE') + + when: 'source file is modified' + srcFile.text = 'modified content' + + and: 'third run - CC reused but task re-executes' + def result3 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is still reused (build file unchanged)' + result3.output.contains('Configuration cache entry reused') + + and: 'task executed because its inputs changed' + !result3.output.contains('1 actionable task: 1 up-to-date') + } + + def 'rpm plugin produces no Task.getProject() deprecation warnings'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.rpm' + } + + version = '2.0.0' + + tasks.register('myRpm', com.netflix.gradle.plugins.rpm.Rpm) { + packageName = 'rpm-deprecation-test' + release = '1' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: + def result = runTasks('myRpm', '--warning-mode', 'all') + + then: 'no Task.project at execution time deprecation warnings' + !result.output.contains('Task.project at execution time has been deprecated') + !result.output.contains('Invocation of Task.project at execution time') + } + + def 'packager convention produces no Task.getProject() deprecation warnings'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'packager-deprecation-test' + version = '1.0.0' + packager = 'CI System' + // user, maintainer, uploaders all default to packager via lazy providers + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: + def result = runTasks('myDeb', '--warning-mode', 'all') + + then: 'no Task.project at execution time deprecation warnings' + !result.output.contains('Task.project at execution time has been deprecated') + !result.output.contains('Invocation of Task.project at execution time') + } + + def 'deb packaged from configuration dependency works with configuration cache'() { + given: + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + id 'java' + } + + repositories { + mavenCentral() + } + + configurations { + bundled + } + + dependencies { + bundled 'log4j:log4j:1.2.17' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'config-dep-test' + version = '1.0.0' + + from(configurations.bundled) { + into '/opt/libs' + createDirectoryEntry = true + } + } + """.stripIndent() + + when: 'run myDeb first time' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myDeb second time' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created' + new File(projectDir, 'build/distributions/config-dep-test_1.0.0_all.deb').exists() + } + + def 'deb with file-based install scripts works with configuration cache'() { + given: + def scriptsDir = new File(projectDir, 'scripts') + scriptsDir.mkdirs() + new File(scriptsDir, 'pre.sh').text = '#!/bin/sh\necho "pre-install"' + new File(scriptsDir, 'post.sh').text = '#!/bin/sh\necho "post-install"' + new File(scriptsDir, 'pre-rm.sh').text = '#!/bin/sh\necho "pre-remove"' + new File(scriptsDir, 'post-rm.sh').text = '#!/bin/sh\necho "post-remove"' + + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'scripts-test' + version = '1.0.0' + + preInstall file('scripts/pre.sh') + postInstall file('scripts/post.sh') + preUninstall file('scripts/pre-rm.sh') + postUninstall file('scripts/post-rm.sh') + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run myDeb first time' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myDeb second time' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created' + new File(projectDir, 'build/distributions/scripts-test_1.0.0_all.deb').exists() + } + + def 'buildDeb and buildRpm both work with configuration cache in the same build'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.txt').text = 'app content' + + buildFile << """ + plugins { + id 'com.netflix.nebula.ospackage' + } + + ospackage { + packageName = 'multi-format-test' + version = '1.0.0' + release = '1' + + from('src') { + into '/opt/app' + } + } + """.stripIndent() + + when: 'run both tasks first time' + def result1 = runTasks('buildDeb', 'buildRpm', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run both tasks second time' + def result2 = runTasks('buildDeb', 'buildRpm', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'deb package is created' + new File(projectDir, 'build/distributions/multi-format-test_1.0.0-1_all.deb').exists() + + and: 'rpm package is created' + new File(projectDir, 'build/distributions/multi-format-test-1.0.0-1.noarch.rpm').exists() + } + + def 'deb with explicit file permissions works with configuration cache'() { + given: + def srcDir = new File(projectDir, 'src') + srcDir.mkdirs() + new File(srcDir, 'app.sh').text = '#!/bin/sh\necho "hello"' + new File(srcDir, 'config.txt').text = 'config value' + + buildFile << """ + plugins { + id 'com.netflix.nebula.deb' + } + + tasks.register('myDeb', com.netflix.gradle.plugins.deb.Deb) { + packageName = 'permissions-test' + version = '1.0.0' + + from('src') { + into '/opt/app' + include 'app.sh' + filePermissions { + unix(0755) + } + } + from('src') { + into '/opt/app' + include 'config.txt' + filePermissions { + unix(0644) + } + } + } + """.stripIndent() + + when: 'run myDeb first time' + def result1 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is stored' + result1.output.contains('Configuration cache entry stored') + + when: 'run myDeb second time' + def result2 = runTasks('myDeb', '--configuration-cache') + + then: 'configuration cache is reused' + result2.output.contains('Configuration cache entry reused') + + and: 'package is created' + new File(projectDir, 'build/distributions/permissions-test_1.0.0_all.deb').exists() + } + + /** + * Documents the known configuration cache limitation of OspackageApplicationSpringBootPlugin. + * + * The Spring Boot plugin uses project.distributions.main.contents.exclude { } with a closure + * that captures a Provider derived from project configurations. While the core + * ospackage tasks are CC-compatible (Task.getProject() at execution time is fixed), the Spring + * Boot integration plugin adds configuration-time closures that may not survive CC serialization + * depending on the Gradle and Spring Boot versions in use. + * + * To verify Spring Boot CC support manually: + * 1. Create a project with `org.springframework.boot` and `com.netflix.nebula.ospackage-application-spring-boot` + * 2. Run: ./gradlew buildDeb --configuration-cache + * 3. Check for CC incompatibility problems in the output + */ + @Ignore('Requires a full Spring Boot project setup with the org.springframework.boot plugin') + def 'spring boot ospackage plugin configuration cache compatibility'() { + given: + buildFile << """ + plugins { + id 'org.springframework.boot' version '3.5.2' + id 'com.netflix.nebula.ospackage-application-spring-boot' + } + + application { + mainClass = 'com.example.Main' + } + """.stripIndent() + + when: + def result = runTasks('buildDeb', '--configuration-cache') + + then: + result.output.contains('Configuration cache entry stored') + } }