diff --git a/gradle_test.go b/gradle_test.go index 725c2641d..346e14039 100644 --- a/gradle_test.go +++ b/gradle_test.go @@ -552,6 +552,158 @@ func TestGradleBuildWithFlexPackKotlinDSL(t *testing.T) { cleanGradleTest(t) } +// TestGradleBuildSharedBuildLogicClassic verifies that a dependency declared only in a +// shared convention plugin living in buildSrc (not in the api module's own build.gradle) +// is correctly present in the published build-info, when using Classic mode (the plugin +// is auto-injected into every project via the init script). +func TestGradleBuildSharedBuildLogicClassic(t *testing.T) { + initGradleTest(t) + buildGradlePath := createGradleProject(t, "buildsrcdependency") + configFilePath := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "buildspecs", tests.GradleConfig) + destPath := filepath.Join(filepath.Dir(buildGradlePath), ".jfrog", "projects") + createConfigFile(destPath, configFilePath, t) + oldHomeDir := changeWD(t, filepath.Dir(buildGradlePath)) + defer clientTestUtils.ChangeDirAndAssert(t, oldHomeDir) + + buildName := tests.GradleBuildName + "-shared-build-logic-classic" + buildNumber := "1" + + // Run from within the project directory (already the cwd via changeWD above) instead of + // passing -b/--build-file: newer Gradle versions reject that flag when combined with + // task-specific command-line options. + runJfrogCli(t, "gradle", "clean", "artifactoryPublish", "--build-name="+buildName, "--build-number="+buildNumber) + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + assertSharedConventionDependencyInBuildInfo(t, buildName, buildNumber) + cleanGradleTest(t) +} + +// TestGradleBuildSharedBuildLogicFlexPack is the same check as +// TestGradleBuildSharedBuildLogicClassic, but using FlexPack (native) mode instead. +func TestGradleBuildSharedBuildLogicFlexPack(t *testing.T) { + initGradleTest(t) + buildGradlePath := createGradleProject(t, "buildsrcdependency") + oldHomeDir := changeWD(t, filepath.Dir(buildGradlePath)) + defer clientTestUtils.ChangeDirAndAssert(t, oldHomeDir) + + buildName := tests.GradleBuildName + "-shared-build-logic-flexpack" + buildNumber := "1" + setEnvCallBack := clientTestUtils.SetEnvWithCallbackAndAssert(t, "JFROG_RUN_NATIVE", "true") + defer setEnvCallBack() + + // Defensive: ShouldRunNative refuses native mode if any .jfrog/projects/gradle.yaml is + // found (via an upward directory search from cwd). This fixture directory name is reused + // by TestGradleBuildSharedBuildLogicClassic, and if that test's config file isn't fully + // removed by the shared harness cleanup before this test starts (e.g. a lingering Gradle + // daemon file lock), native mode would silently be skipped. Remove it explicitly so this + // test's outcome never depends on that cleanup having completed. + assert.NoError(t, os.RemoveAll(".jfrog")) + + err := runJfrogCliWithoutAssertion("gradle", "clean", "build", "--build-name="+buildName, "--build-number="+buildNumber) + assert.NoError(t, err) + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + assertSharedConventionDependencyInBuildInfo(t, buildName, buildNumber) + cleanGradleTest(t) +} + +// TestGradleBuildSharedBuildLogicBuildLogicClassic is the same check as +// TestGradleBuildSharedBuildLogicClassic, but the shared convention plugin lives in an +// included build-logic build instead of buildSrc. +func TestGradleBuildSharedBuildLogicBuildLogicClassic(t *testing.T) { + initGradleTest(t) + buildGradlePath := createGradleProject(t, "buildlogicdependency") + configFilePath := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "buildspecs", tests.GradleConfig) + destPath := filepath.Join(filepath.Dir(buildGradlePath), ".jfrog", "projects") + createConfigFile(destPath, configFilePath, t) + oldHomeDir := changeWD(t, filepath.Dir(buildGradlePath)) + defer clientTestUtils.ChangeDirAndAssert(t, oldHomeDir) + + buildName := tests.GradleBuildName + "-shared-build-logic-buildlogic-classic" + buildNumber := "1" + + runJfrogCli(t, "gradle", "clean", "artifactoryPublish", "--build-name="+buildName, "--build-number="+buildNumber) + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + assertSharedConventionDependencyInBuildInfo(t, buildName, buildNumber) + cleanGradleTest(t) +} + +// TestGradleBuildSharedBuildLogicBuildLogicFlexPack is the same check as +// TestGradleBuildSharedBuildLogicFlexPack, but the shared convention plugin lives in an +// included build-logic build instead of buildSrc. +func TestGradleBuildSharedBuildLogicBuildLogicFlexPack(t *testing.T) { + initGradleTest(t) + buildGradlePath := createGradleProject(t, "buildlogicdependency") + oldHomeDir := changeWD(t, filepath.Dir(buildGradlePath)) + defer clientTestUtils.ChangeDirAndAssert(t, oldHomeDir) + + buildName := tests.GradleBuildName + "-shared-build-logic-buildlogic-flexpack" + buildNumber := "1" + setEnvCallBack := clientTestUtils.SetEnvWithCallbackAndAssert(t, "JFROG_RUN_NATIVE", "true") + defer setEnvCallBack() + + // See TestGradleBuildSharedBuildLogicFlexPack for why this is needed. + assert.NoError(t, os.RemoveAll(".jfrog")) + + err := runJfrogCliWithoutAssertion("gradle", "clean", "build", "--build-name="+buildName, "--build-number="+buildNumber) + assert.NoError(t, err) + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + assertSharedConventionDependencyInBuildInfo(t, buildName, buildNumber) + cleanGradleTest(t) +} + +// assertSharedConventionDependencyInBuildInfo asserts the published build-info has: +// - an "api" module (applies the shared java-common-conventions plugin) whose +// dependencies include org.slf4j:slf4j-api — a dependency declared only in the +// shared buildSrc convention plugin, never in api's own build.gradle. +// - a "lib" module (does NOT apply the shared convention plugin) whose dependencies +// do NOT include org.slf4j:slf4j-api, proving the shared dependency only reaches +// subprojects that actually apply the convention plugin. +func assertSharedConventionDependencyInBuildInfo(t *testing.T, buildName, buildNumber string) { + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + if err != nil { + assert.NoError(t, err) + return + } + if !found { + assert.True(t, found, "build info was expected to be found") + return + } + buildInfo := publishedBuildInfo.BuildInfo + + apiModule := findModuleByIdSubstring(buildInfo.Modules, "com.example:api") + if assert.NotNil(t, apiModule, "api module missing from build-info") { + assert.True(t, moduleHasDependency(apiModule, "org.slf4j:slf4j-api"), + "shared dependency (org.slf4j:slf4j-api, declared only in the shared convention plugin) missing from api module's dependencies") + } + + libModule := findModuleByIdSubstring(buildInfo.Modules, "com.example:lib") + if assert.NotNil(t, libModule, "lib module missing from build-info") { + assert.False(t, moduleHasDependency(libModule, "org.slf4j:slf4j-api"), + "lib does not apply the shared convention plugin, so org.slf4j:slf4j-api must not appear in its dependencies") + } +} + +func findModuleByIdSubstring(modules []buildinfo.Module, idSubstring string) *buildinfo.Module { + for i := range modules { + if strings.Contains(modules[i].Id, idSubstring) { + return &modules[i] + } + } + return nil +} + +func moduleHasDependency(module *buildinfo.Module, depIdPrefix string) bool { + for _, dep := range module.Dependencies { + if strings.HasPrefix(dep.Id, depIdPrefix) { + return true + } + } + return false +} + func createGradleProject(t *testing.T, projectName string) string { // Copy the entire project directory including source files projectSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "gradle", projectName) diff --git a/testdata/gradle/buildlogicdependency/api/build.gradle b/testdata/gradle/buildlogicdependency/api/build.gradle new file mode 100644 index 000000000..a82909111 --- /dev/null +++ b/testdata/gradle/buildlogicdependency/api/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'java-common-conventions' + id 'maven-publish' +} + +group = 'com.example' +version = '1.0.0' + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } +} diff --git a/testdata/gradle/buildlogicdependency/api/src/main/java/com/example/api/PersonList.java b/testdata/gradle/buildlogicdependency/api/src/main/java/com/example/api/PersonList.java new file mode 100644 index 000000000..a0642892f --- /dev/null +++ b/testdata/gradle/buildlogicdependency/api/src/main/java/com/example/api/PersonList.java @@ -0,0 +1,12 @@ +package com.example.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PersonList { + private static final Logger logger = LoggerFactory.getLogger(PersonList.class); + + public void log() { + logger.info("PersonList using slf4j-api pulled in only via the shared convention plugin"); + } +} diff --git a/testdata/gradle/buildlogicdependency/build-logic/build.gradle b/testdata/gradle/buildlogicdependency/build-logic/build.gradle new file mode 100644 index 000000000..15bac7130 --- /dev/null +++ b/testdata/gradle/buildlogicdependency/build-logic/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'groovy-gradle-plugin' +} + +repositories { + mavenCentral() +} diff --git a/testdata/gradle/buildlogicdependency/build-logic/settings.gradle b/testdata/gradle/buildlogicdependency/build-logic/settings.gradle new file mode 100644 index 000000000..6ef2ba876 --- /dev/null +++ b/testdata/gradle/buildlogicdependency/build-logic/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'build-logic' diff --git a/testdata/gradle/buildlogicdependency/build-logic/src/main/groovy/java-common-conventions.gradle b/testdata/gradle/buildlogicdependency/build-logic/src/main/groovy/java-common-conventions.gradle new file mode 100644 index 000000000..016340a68 --- /dev/null +++ b/testdata/gradle/buildlogicdependency/build-logic/src/main/groovy/java-common-conventions.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.slf4j:slf4j-api:2.0.9' +} diff --git a/testdata/gradle/buildlogicdependency/build.gradle b/testdata/gradle/buildlogicdependency/build.gradle new file mode 100644 index 000000000..281335e1c --- /dev/null +++ b/testdata/gradle/buildlogicdependency/build.gradle @@ -0,0 +1,3 @@ +// Root build file intentionally left plain. Neither Classic mode (plugin +// auto-injected via init script) nor FlexPack mode requires any plugin +// application here. diff --git a/testdata/gradle/buildlogicdependency/lib/build.gradle b/testdata/gradle/buildlogicdependency/lib/build.gradle new file mode 100644 index 000000000..1ce7cd888 --- /dev/null +++ b/testdata/gradle/buildlogicdependency/lib/build.gradle @@ -0,0 +1,22 @@ +// Intentionally does NOT apply the shared java-common-conventions plugin from +// build-logic, so it must not receive org.slf4j:slf4j-api (or anything else the +// convention plugin declares). +plugins { + id 'java-library' + id 'maven-publish' +} + +repositories { + mavenCentral() +} + +group = 'com.example' +version = '1.0.0' + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } +} diff --git a/testdata/gradle/buildlogicdependency/lib/src/main/java/com/example/lib/Utils.java b/testdata/gradle/buildlogicdependency/lib/src/main/java/com/example/lib/Utils.java new file mode 100644 index 000000000..aef512b41 --- /dev/null +++ b/testdata/gradle/buildlogicdependency/lib/src/main/java/com/example/lib/Utils.java @@ -0,0 +1,7 @@ +package com.example.lib; + +public class Utils { + public static String greet() { + return "hello from lib"; + } +} diff --git a/testdata/gradle/buildlogicdependency/settings.gradle b/testdata/gradle/buildlogicdependency/settings.gradle new file mode 100644 index 000000000..cc6ffd7bd --- /dev/null +++ b/testdata/gradle/buildlogicdependency/settings.gradle @@ -0,0 +1,7 @@ +pluginManagement { + includeBuild 'build-logic' +} + +rootProject.name = 'buildlogicdependency' +include 'api' +include 'lib' diff --git a/testdata/gradle/buildsrcdependency/api/build.gradle b/testdata/gradle/buildsrcdependency/api/build.gradle new file mode 100644 index 000000000..a82909111 --- /dev/null +++ b/testdata/gradle/buildsrcdependency/api/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'java-common-conventions' + id 'maven-publish' +} + +group = 'com.example' +version = '1.0.0' + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } +} diff --git a/testdata/gradle/buildsrcdependency/api/src/main/java/com/example/api/PersonList.java b/testdata/gradle/buildsrcdependency/api/src/main/java/com/example/api/PersonList.java new file mode 100644 index 000000000..a0642892f --- /dev/null +++ b/testdata/gradle/buildsrcdependency/api/src/main/java/com/example/api/PersonList.java @@ -0,0 +1,12 @@ +package com.example.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PersonList { + private static final Logger logger = LoggerFactory.getLogger(PersonList.class); + + public void log() { + logger.info("PersonList using slf4j-api pulled in only via the shared convention plugin"); + } +} diff --git a/testdata/gradle/buildsrcdependency/build.gradle b/testdata/gradle/buildsrcdependency/build.gradle new file mode 100644 index 000000000..281335e1c --- /dev/null +++ b/testdata/gradle/buildsrcdependency/build.gradle @@ -0,0 +1,3 @@ +// Root build file intentionally left plain. Neither Classic mode (plugin +// auto-injected via init script) nor FlexPack mode requires any plugin +// application here. diff --git a/testdata/gradle/buildsrcdependency/buildSrc/build.gradle b/testdata/gradle/buildsrcdependency/buildSrc/build.gradle new file mode 100644 index 000000000..15bac7130 --- /dev/null +++ b/testdata/gradle/buildsrcdependency/buildSrc/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'groovy-gradle-plugin' +} + +repositories { + mavenCentral() +} diff --git a/testdata/gradle/buildsrcdependency/buildSrc/src/main/groovy/java-common-conventions.gradle b/testdata/gradle/buildsrcdependency/buildSrc/src/main/groovy/java-common-conventions.gradle new file mode 100644 index 000000000..016340a68 --- /dev/null +++ b/testdata/gradle/buildsrcdependency/buildSrc/src/main/groovy/java-common-conventions.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.slf4j:slf4j-api:2.0.9' +} diff --git a/testdata/gradle/buildsrcdependency/lib/build.gradle b/testdata/gradle/buildsrcdependency/lib/build.gradle new file mode 100644 index 000000000..8b8e92bbb --- /dev/null +++ b/testdata/gradle/buildsrcdependency/lib/build.gradle @@ -0,0 +1,22 @@ +// Intentionally does NOT apply the shared java-common-conventions plugin from +// buildSrc, so it must not receive org.slf4j:slf4j-api (or anything else the +// convention plugin declares). +plugins { + id 'java-library' + id 'maven-publish' +} + +repositories { + mavenCentral() +} + +group = 'com.example' +version = '1.0.0' + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } +} diff --git a/testdata/gradle/buildsrcdependency/lib/src/main/java/com/example/lib/Utils.java b/testdata/gradle/buildsrcdependency/lib/src/main/java/com/example/lib/Utils.java new file mode 100644 index 000000000..aef512b41 --- /dev/null +++ b/testdata/gradle/buildsrcdependency/lib/src/main/java/com/example/lib/Utils.java @@ -0,0 +1,7 @@ +package com.example.lib; + +public class Utils { + public static String greet() { + return "hello from lib"; + } +} diff --git a/testdata/gradle/buildsrcdependency/settings.gradle b/testdata/gradle/buildsrcdependency/settings.gradle new file mode 100644 index 000000000..b00039095 --- /dev/null +++ b/testdata/gradle/buildsrcdependency/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'buildsrcdependency' +include 'api' +include 'lib'