From c18bbd408b541f09fc82e227b88e57260d1c6138 Mon Sep 17 00:00:00 2001 From: John Burns Date: Wed, 15 Jul 2026 10:49:41 -0500 Subject: [PATCH] replace OSS dep locks plugin replace OSS dep locks plugin with a local convention plugin that is more opinionated and is much faster at configuration time --- build.gradle.kts | 12 +++ .../plugin/plugin/NebulaLibraryPlugin.groovy | 1 - .../plugin/plugin/NebulaPluginPlugin.groovy | 20 +++- .../nebula/plugin/plugin/NebulaProblems.java | 17 ++++ .../convention/DependencyLockingPlugin.kt | 17 ++++ .../oss/settings/NebulaSettingsPlugin.kt | 1 + .../convention/DependencyLockingPluginTest.kt | 92 +++++++++++++++++++ .../oss/settings/NebulaSettingsPluginTest.kt | 41 +++++++++ .../plugin/plugin/NebulaPluginPluginTest.kt | 58 ++++++++++-- .../kotlin/nebula/plugin/plugin/SampleCode.kt | 21 ++++- 10 files changed, 270 insertions(+), 10 deletions(-) create mode 100644 src/main/java/nebula/plugin/plugin/NebulaProblems.java create mode 100644 src/main/kotlin/com/netflix/nebula/convention/DependencyLockingPlugin.kt create mode 100644 src/test/kotlin/com/netflix/nebula/convention/DependencyLockingPluginTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5ea7e3a..5325576 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -116,6 +116,18 @@ gradlePlugin { } } } + create("com.netflix.nebula.locks") { + id = "com.netflix.nebula.locks" + displayName = "Nebula Dependency Locks Plugin" + description = "opinionated dependency locking for nebula projects" + implementationClass = "com.netflix.nebula.convention.DependencyLockingPlugin" + tags.set(listOf("nebula")) + compatibility { + features { + configurationCache = true + } + } + } } } diff --git a/src/main/groovy/nebula/plugin/plugin/NebulaLibraryPlugin.groovy b/src/main/groovy/nebula/plugin/plugin/NebulaLibraryPlugin.groovy index 66ed0a3..c6c08f2 100644 --- a/src/main/groovy/nebula/plugin/plugin/NebulaLibraryPlugin.groovy +++ b/src/main/groovy/nebula/plugin/plugin/NebulaLibraryPlugin.groovy @@ -37,7 +37,6 @@ import javax.inject.Inject @NullMarked class NebulaLibraryPlugin implements Plugin { static final NEBULA_PLUGIN_IDS = ['com.netflix.nebula.contacts', - 'com.netflix.nebula.dependency-lock', 'com.netflix.nebula.info', 'com.netflix.nebula.javadoc-jar', 'com.netflix.nebula.maven-apache-license', diff --git a/src/main/groovy/nebula/plugin/plugin/NebulaPluginPlugin.groovy b/src/main/groovy/nebula/plugin/plugin/NebulaPluginPlugin.groovy index b80f18c..d70fbe7 100644 --- a/src/main/groovy/nebula/plugin/plugin/NebulaPluginPlugin.groovy +++ b/src/main/groovy/nebula/plugin/plugin/NebulaPluginPlugin.groovy @@ -20,11 +20,16 @@ import nebula.plugin.publishing.NebulaOssPublishingExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.problems.Problem +import org.gradle.api.problems.ProblemId +import org.gradle.api.problems.Problems +import org.gradle.api.problems.Severity import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.publish.maven.tasks.PublishToMavenRepository import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.testing.Test +import org.gradle.internal.deprecation.DeprecationLogger import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.jvm.toolchain.JavaToolchainService import org.gradle.jvm.toolchain.JavaToolchainSpec @@ -46,7 +51,6 @@ class NebulaPluginPlugin implements Plugin { 'java-gradle-plugin'] static final NEBULA_PLUGIN_IDS = ['com.netflix.nebula.contacts', - 'com.netflix.nebula.dependency-lock', 'com.netflix.nebula.info', 'com.netflix.nebula.javadoc-jar', 'com.netflix.nebula.maven-apache-license', @@ -61,17 +65,29 @@ class NebulaPluginPlugin implements Plugin { static final PLUGIN_IDS = GRADLE_PLUGIN_IDS + NEBULA_PLUGIN_IDS private final ProviderFactory providers + private final Problems problems private boolean isPluginPublishingValidation @Inject - NebulaPluginPlugin(ProviderFactory providerFactory) { + NebulaPluginPlugin(ProviderFactory providerFactory,Problems problems) { this.providers = providerFactory + this.problems = problems } @Override void apply(Project project) { project.group = 'com.netflix.nebula' + project.afterEvaluate { + if(!project.pluginManager.hasPlugin("com.netflix.nebula.locks")){ + Problem problem = problems.reporter.create(NebulaProblems.OSS_SETTINGS) { + it.details("locking convention is not found. using com.netflix.nebula.plugin-plugin without the oss.settings plugin is deprecated") + it.solution("apply the 'com.netflix.nebula.oss.settings' plugin in settings.gradle.kts") + } + problems.reporter.report(problem) + project.pluginManager.apply("com.netflix.nebula.dependency-lock") + } + } this.isPluginPublishingValidation = project.gradle.startParameter.taskNames.contains('--validate-only') project.plugins.withId("com.netflix.nebula.oss-publishing") { NebulaOssPublishingExtension ossPublishingExt = project.rootProject.extensions.findByType(NebulaOssPublishingExtension) diff --git a/src/main/java/nebula/plugin/plugin/NebulaProblems.java b/src/main/java/nebula/plugin/plugin/NebulaProblems.java new file mode 100644 index 0000000..70186cf --- /dev/null +++ b/src/main/java/nebula/plugin/plugin/NebulaProblems.java @@ -0,0 +1,17 @@ +package nebula.plugin.plugin; + +import org.gradle.api.problems.ProblemGroup; +import org.gradle.api.problems.ProblemId; + +/** + * Constants for Problem reporting + */ +public class NebulaProblems { + public static final ProblemGroup NEBULA_GROUP = + ProblemGroup.create("com.netflix.nebula", "Nebula Plugins"); + + public static final ProblemId OSS_SETTINGS = ProblemId.create( + "com.netflix.nebula.oss.settings not found", + "using a nebula convention plugin without com.netflix.nebula.oss.settings is deprecated", + NEBULA_GROUP); +} diff --git a/src/main/kotlin/com/netflix/nebula/convention/DependencyLockingPlugin.kt b/src/main/kotlin/com/netflix/nebula/convention/DependencyLockingPlugin.kt new file mode 100644 index 0000000..94d6afd --- /dev/null +++ b/src/main/kotlin/com/netflix/nebula/convention/DependencyLockingPlugin.kt @@ -0,0 +1,17 @@ +package com.netflix.nebula.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * Highly opinionated lockign setup for nebula projects. + * Used over the "com.netflix.nebula.dependency-lock" plugin since we don't need its extra options, + * and that plugin adds significant configuration time + */ +class DependencyLockingPlugin : Plugin { + override fun apply(project: Project) { + project.dependencyLocking { + lockAllConfigurations() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/netflix/nebula/oss/settings/NebulaSettingsPlugin.kt b/src/main/kotlin/com/netflix/nebula/oss/settings/NebulaSettingsPlugin.kt index af6b955..5275055 100644 --- a/src/main/kotlin/com/netflix/nebula/oss/settings/NebulaSettingsPlugin.kt +++ b/src/main/kotlin/com/netflix/nebula/oss/settings/NebulaSettingsPlugin.kt @@ -28,6 +28,7 @@ class NebulaSettingsPlugin : Plugin { } settings.gradle.allprojects { pluginManager.apply("com.netflix.nebula.resolve") + pluginManager.apply("com.netflix.nebula.locks") } } } \ No newline at end of file diff --git a/src/test/kotlin/com/netflix/nebula/convention/DependencyLockingPluginTest.kt b/src/test/kotlin/com/netflix/nebula/convention/DependencyLockingPluginTest.kt new file mode 100644 index 0000000..1373cf6 --- /dev/null +++ b/src/test/kotlin/com/netflix/nebula/convention/DependencyLockingPluginTest.kt @@ -0,0 +1,92 @@ +package com.netflix.nebula.convention + +import com.netflix.nebula.SupportedGradleVersion +import nebula.test.dsl.* +import nebula.test.dsl.TestKitAssertions.assertThat +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import java.io.File + +class DependencyLockingPluginTest { + @TempDir + lateinit var projectDir: File + + @ParameterizedTest + @EnumSource(SupportedGradleVersion::class) + fun `test multiproject no root`(gradle: SupportedGradleVersion) { + val runner = testProject(projectDir) { + properties { + buildCache(true) + configurationCache(true) + } + subProject("sub1") { + plugins { + java() + id("com.netflix.nebula.locks") + } + repositories{ + mavenCentral() + } + dependencies("""implementation("org.slf4j:slf4j-api:2.0.18")""") + } + } + val result = runner.run(":sub1:dependencies", "--write-locks"){ + withGradle(gradle.version) + } + assertThat(result) + .hasNoProblemsReport() + .hasNoMutableStateWarnings() + .hasNoDeprecationWarnings() + val lockFile = projectDir.resolve("sub1/gradle.lockfile") + assertThat(lockFile) + .exists() + .content() + .contains("org.slf4j:slf4j-api") + .contains("compileClasspath") + .contains("runtimeClasspath") + .contains("testCompileClasspath") + .contains("testRuntimeClasspath") + .contains("empty=annotationProcessor,testAnnotationProcessor") + } + + @Test + fun `test archrules integration`() { + val runner = testProject(projectDir) { + properties { + buildCache(true) + configurationCache(true) + } + subProject("sub1") { + plugins { + java() + id("com.netflix.nebula.archrules.runner") + id("com.netflix.nebula.locks") + } + repositories{ + mavenCentral() + } + dependencies( + """implementation("org.slf4j:slf4j-api:2.0.18")""", + """archRules("com.netflix.nebula:archrules-deprecation:1.0.2")""" + ) + } + } + val result = runner.run(":sub1:dependencies", "--write-locks") + assertThat(result) + .hasNoProblemsReport() + .hasNoMutableStateWarnings() + .hasNoDeprecationWarnings() + val lockFile = projectDir.resolve("sub1/gradle.lockfile") + assertThat(lockFile) + .exists() + .content() + .contains("org.slf4j:slf4j-api") + .contains("mainArchRulesRuntime") + .contains("testArchRulesRuntime") + .contains("com.netflix.nebula:archrules-deprecation:1.0.2=archRules,mainArchRulesRuntime,testArchRulesRuntime") + .contains("empty=annotationProcessor,testAnnotationProcessor") + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/netflix/nebula/oss/settings/NebulaSettingsPluginTest.kt b/src/test/kotlin/com/netflix/nebula/oss/settings/NebulaSettingsPluginTest.kt index 6824733..c2f4916 100644 --- a/src/test/kotlin/com/netflix/nebula/oss/settings/NebulaSettingsPluginTest.kt +++ b/src/test/kotlin/com/netflix/nebula/oss/settings/NebulaSettingsPluginTest.kt @@ -135,4 +135,45 @@ class NebulaSettingsPluginTest { assertThat(result.task(":sub2:dependencies")).hasOutcome(TaskOutcome.SUCCESS) assertThat(result.task(":resolve")).hasOutcome(TaskOutcome.SUCCESS) } + + @ParameterizedTest + @EnumSource(SupportedGradleVersion::class) + fun `test locking multiproject`(gradle: SupportedGradleVersion) { + val runner = testProject(projectDir) { + properties { + buildCache(true) + configurationCache(true) + } + settings { + plugins { + id("com.netflix.nebula.oss.settings") + } + } + subProject("sub1"){ + plugins { + java() + } + } + subProject("sub2"){ + plugins { + java() + } + } + } + val result = runner.run("resolve", "--write-locks") { + withGradle(gradle.version) + } + assertThat(result) + .hasNoProblemsReport() + .hasNoMutableStateWarnings() + .hasNoDeprecationWarnings() + assertThat(projectDir.resolve("sub1/gradle.lockfile")) + .exists() + .content() + .contains("empty=annotationProcessor,compileClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath") + assertThat(projectDir.resolve("sub2/gradle.lockfile")) + .exists() + .content() + .contains("empty=annotationProcessor,compileClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath") + } } \ No newline at end of file diff --git a/src/test/kotlin/nebula/plugin/plugin/NebulaPluginPluginTest.kt b/src/test/kotlin/nebula/plugin/plugin/NebulaPluginPluginTest.kt index 6a013fe..2d7dd1f 100644 --- a/src/test/kotlin/nebula/plugin/plugin/NebulaPluginPluginTest.kt +++ b/src/test/kotlin/nebula/plugin/plugin/NebulaPluginPluginTest.kt @@ -39,7 +39,13 @@ internal class NebulaPluginPluginTest { artifactory.stop() } - private fun TestProjectBuilder.sampleSinglePluginSetup() { + private fun TestProjectBuilder.sampleSinglePluginSetup(config: ProjectBuilder.() -> Unit = { + src { + main { + java("example/MyPlugin.java", SAMPLE_JAVA_PLUGIN) + } + } + }) { properties { configurationCache(true) buildCache(true) @@ -73,11 +79,7 @@ gradlePlugin { } """ ) - src { - main { - java("example/MyPlugin.java", SAMPLE_JAVA_PLUGIN) - } - } + config() } } @@ -93,6 +95,29 @@ gradlePlugin { .`as`("integTest facet enabled by default") .hasOutcome(TaskOutcome.NO_SOURCE) + assertThat(result) + .hasNoDeprecationWarnings() + .hasNoMutableStateWarnings() + + assertThat(result.output) + .contains("Problem found: using a nebula convention plugin without com.netflix.nebula.oss.settings is deprecated") + .contains("Problems report is available at:") + } + + @Test + fun `test archrules integration`() { + val runner = testProject(projectDir) { + sampleSinglePluginSetup{ + src { + main { + java("example/MyPlugin.java", SAMPLE_JAVA_PLUGIN_WITH_ARCHRULES_FAILURE) + } + } + } + } + + val result = runner.run("check", "--stacktrace") + assertThat(result) .hasNoDeprecationWarnings() .hasNoMutableStateWarnings() @@ -102,6 +127,27 @@ gradlePlugin { .hasOutcome(TaskOutcome.SUCCESS) assertThat(result.output) .contains("Rule: ") + assertThat(result.output) + .contains("Problem found:") + .contains("Problems report is available at:") + } + + @Test + fun `test setup with settings plugin`() { + val runner = testProject(projectDir) { + settings{ + plugins { + id("com.netflix.nebula.oss.settings") + } + } + sampleSinglePluginSetup() + } + + val result = runner.run("check", "--stacktrace") +assertThat(result) + .hasNoProblemsReport() + .hasNoDeprecationWarnings() + .hasNoMutableStateWarnings() } @Test diff --git a/src/test/kotlin/nebula/plugin/plugin/SampleCode.kt b/src/test/kotlin/nebula/plugin/plugin/SampleCode.kt index 2fb8e29..24de522 100644 --- a/src/test/kotlin/nebula/plugin/plugin/SampleCode.kt +++ b/src/test/kotlin/nebula/plugin/plugin/SampleCode.kt @@ -1,7 +1,7 @@ package nebula.plugin.plugin //language=java -const val SAMPLE_JAVA_PLUGIN: String = """ +const val SAMPLE_JAVA_PLUGIN_WITH_ARCHRULES_FAILURE: String = """ package example; import org.gradle.api.Plugin; @@ -18,6 +18,25 @@ public class MyPlugin implements Plugin<@NonNull Project> { } """ +//language=java +const val SAMPLE_JAVA_PLUGIN: String = """ +package example; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.jspecify.annotations.NullMarked; + +/** +* +*/ +@NullMarked +public class MyPlugin implements Plugin { + @Override + public void apply(Project project) { + } +} +""" + //language=java const val SAMPLE_JAVA_MAIN_CLASS: String = """ package example;