Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gradle plugin: Duplicated tag: 'dependencyManagement' #198

Open
skjolber opened this issue Jan 4, 2019 · 11 comments
Open

Gradle plugin: Duplicated tag: 'dependencyManagement' #198

skjolber opened this issue Jan 4, 2019 · 11 comments

Comments

@skjolber
Copy link

skjolber commented Jan 4, 2019

For build.gradle, when using

    dependencies {
        constraints {
            compile("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}") {
                because 'previous versions have security issues'
            }
        }
    }

in combination with

apply plugin: 'io.spring.dependency-management'

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

an invalid POM file is generated with two dependencyManagement elements. Deployment (artifactoryPublish) is thus refused by the artifactory server.

@lauraliparulo
Copy link

I had the same problem.

You don´t need to/cannot use the 'io.spring.dependency-management' plugin in the project where you use your bom. so no "dependencymanagement in the build gradle too!

All you need is:

dependencies {

implementation platform('your-bom:version')

//your deps

}

@piacenti
Copy link

piacenti commented Apr 8, 2020

Had the same problem and just implemented a hack that combines the content of the two dependency management blocks.

task fixPom {
    doLast {
        File file = new File("$buildDir/publications/mavenJava/pom-default.xml")
        def text = file.text
        def pattern = "(?s)(<dependencyManagement>.+?<dependencies>)(.+?)(</dependencies>.+?</dependencyManagement>)"
        Matcher matcher = text =~ pattern
        if (matcher.find()) {
            text = text.replaceFirst(pattern, "")
            def firstDeps = matcher.group(2)
            text = text.replaceFirst(pattern, '$1$2' + firstDeps + '$3')
        }
        file.write(text)
    }
}
generatePomFileForMavenJavaPublication.finalizedBy fixPom

RobiNino pushed a commit to RobiNino/build-info that referenced this issue Oct 7, 2020
@piyush8098
Copy link

Hi @piacenti ,

Thanks for sharing the hack. I am also facing the same issue.
I am getting an error using the hack suggest by you:

Caused by: groovy.lang.MissingPropertyException: Could not get unknown property 'generatePomFileForCoreJarPublication' for root project 'project-name' of type org.gradle.api.Project.

Any idea what's wrong here?

@piyush8098
Copy link

piyush8098 commented Mar 9, 2021

Hi @lauraliparulo ,

explicitly defining the dependency does add the dependency.

implementation platform('org.springframework.boot:spring-boot-dependencies:2.4.2')
implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.952')

However I still get an extra dependencyManagement element in the end for spring-boot-dependencies.

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.4.2</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-bom</artifactId>
        <version>1.11.952</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk-s3</artifactId>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.4.2</version>
        <scope>import</scope>
        <type>pom</type>
      </dependency>
    </dependencies>
  </dependencyManagement>

@micheljung
Copy link

Kotlin variant of the fix:

// FIXME workaround for https://github.com/jfrog/build-info/issues/198
val fixPom = tasks.register("fixPom") {
  doLast {
    val file = Path.of("$buildDir/publications/maven/pom-default.xml")
    var content = Files.readString(file)
    val pattern = Pattern.compile(
      "(<dependencyManagement>.+?<dependencies>)(.+?)(</dependencies>.+?</dependencyManagement>)",
      Pattern.DOTALL
    )
    var matcher = pattern.matcher(content)

    if (matcher.find()) {
      val firstDependencies = matcher.group(2)
      content = matcher.replaceFirst("")

      matcher = pattern.matcher(content)
      if (!matcher.find()) {
        throw GradleException("Didn't find second <dependencyManagement> tag, maybe https://github.com/jfrog/build-info/issues/198 has been fixed?")
      }
      content = matcher.replaceFirst("$1$2$firstDependencies$3")
    }

    Files.writeString(file, content)
  }
}
tasks.findByName("generatePomFileForMavenPublication")?.finalizedBy(fixPom)

Note that this only works if dependencyManagement is used. Otherwise, the empty tag <dependencies/> is generated, causing the pattern above to fail.

@mpeterka
Copy link

@micheljung Thank you for your workaround fix and idea.
But it did not work in our environment, there were a few differences (file path, task name..)

I wrote another workaround implementation. I may help somebody.

// FIXME workaround for https://github.com/jfrog/build-info/issues/198
val fixPom = tasks.register("fixPom") {
    doLast {
        val file = Path.of("$buildDir/publications/mavenJava/pom-default.xml")
        val content = Files.readString(file)

        val tag = "dependencyManagement"
        val findAll = "<$tag>".toRegex().findAll(content)
        if (findAll.count() == 2) {
            val tagEnd = "</$tag>"
            val start = findAll.toList()[1].range.first
            val end = content.indexOf(tagEnd, start) +tagEnd.length
            val fixedContent = content.removeRange(start, end)
            Files.writeString(file, fixedContent)
        } else {
            println("Is https://github.com/jfrog/build-info/issues/198 fixed already?")
        }
    }
}
tasks.findByName("generatePomFileForMavenJavaPublication")?.finalizedBy(fixPom)
    ?: throw NullPointerException("Task 'generatePomFileForMavenJavaPublication' was not found in " + tasks.names)

@MaksymPavlenko
Copy link

@micheljung @mpeterka thank you guys for your effort.

We've encountered the same issue after adding a transitive dependency constraint for a security reason.
Hence, it's crucial for us to keep the content of multiple tags.

The following solution merges <dependencyManagement> tags to keep all the managed dependencies data in the POM.
Also, it doesn't break the build if there are no tags or less than 2, but it will log a message.
The solution is pretty verbose though.

I didn't use the exceptions but nested if blocks, since I'm not sure about the way Gradle handles them, hence if you know it - feel free to optimize it further.

import java.nio.file.Files
import java.util.regex.Pattern
import java.nio.file.Path as FilePath

/**
 * Task to merge multiple <dependencyManagement> tags created in the POM file to publish.
 * Caused by: https://github.com/jfrog/build-info/issues/198 (io.spring.dependency-management + dependency constraints)
 */
val fixPom = tasks.register("fixPom") {
    doLast {
        val pomFilePath = FilePath.of("$buildDir/publications/mavenJava/pom-default.xml")
        val pomContent = Files.readString(pomFilePath)

        if ("<dependencyManagement>" in pomContent) {
            val (pomContentWithoutDependencyManagement, dependencyManagementTagContents) =
                extractDependencyManagementTags(pomContent)

            fixPomFileIfMultipleDependencyManagementTagsArePresent(
                pomContent,
                pomContentWithoutDependencyManagement,
                dependencyManagementTagContents,
                pomFilePath
            )
        } else
            logger.warn("Didn't find any <dependencyManagement> tags, maybe this project doesn't need this fix task")
    }
}

fun extractDependencyManagementTags(pomContent: String): Pair<String, List<String>> {
    val pattern = Pattern.compile(
        """(<dependencyManagement>.*?<dependencies>)(.+?)(</dependencies>.*?</dependencyManagement>)""",
        Pattern.DOTALL
    )
    var pomContentWithoutDependencyManagement = pomContent
    var matcher = pattern.matcher(pomContentWithoutDependencyManagement)
    val dependencyManagementTagContents = mutableListOf<String>()

    while (matcher.find()) {
        dependencyManagementTagContents.add(matcher.group(2))
        pomContentWithoutDependencyManagement = matcher.replaceFirst("")
        matcher = pattern.matcher(pomContentWithoutDependencyManagement)
    }

    return Pair(pomContentWithoutDependencyManagement, dependencyManagementTagContents)
}

fun fixPomFileIfMultipleDependencyManagementTagsArePresent(
    pomContent: String,
    pomContentWithoutDependencyManagement: String,
    dependencyManagementTagContents: List<String>,
    pomFilePath: FilePath?
) {
    if (dependencyManagementTagContents.size > 1) {
        val indexToInsertDependencyManagementTag = pomContent.indexOf("<dependencyManagement>")
        val fixedPomContent = getFixedPomContent(
            pomContentWithoutDependencyManagement,
            indexToInsertDependencyManagementTag,
            dependencyManagementTagContents
        )
        Files.writeString(pomFilePath, fixedPomContent)
    } else
        logger.warn(
            "Didn't find multiple <dependencyManagement> tag, " +
                    "maybe https://github.com/jfrog/build-info/issues/198 has been fixed " +
                    "or this project doesn't need this fix task anymore"
        )
}

fun getFixedPomContent(
    pomContent: String,
    indexToInsertDependencyManagementTag: Int,
    managedDependencies: List<String>
): String =
    pomContent.substring(0, indexToInsertDependencyManagementTag) +
            buildDependencyManagementTag(managedDependencies) +
            pomContent.substring(indexToInsertDependencyManagementTag)

fun buildDependencyManagementTag(managedDependencies: List<String>) =
    managedDependencies.joinToString(
        separator = "",
        prefix = "<dependencyManagement><dependencies>",
        postfix = "</dependencies></dependencyManagement>"
    )

tasks.findByName("generatePomFileForMavenJavaPublication")?.finalizedBy(fixPom)
    ?: logger.warn("Task 'generatePomFileForMavenJavaPublication' was not found in {}", tasks.names)

@derekwwest
Copy link

Had the same problem and just implemented a hack that combines the content of the two dependency management blocks.

task fixPom {
    doLast {
        File file = new File("$buildDir/publications/mavenJava/pom-default.xml")
        def text = file.text
        def pattern = "(?s)(<dependencyManagement>.+?<dependencies>)(.+?)(</dependencies>.+?</dependencyManagement>)"
        Matcher matcher = text =~ pattern
        if (matcher.find()) {
            text = text.replaceFirst(pattern, "")
            def firstDeps = matcher.group(2)
            text = text.replaceFirst(pattern, '$1$2' + firstDeps + '$3')
        }
        file.write(text)
    }
}
generatePomFileForMavenJavaPublication.finalizedBy fixPom

How are you pulling in the dependencies? Which version of 'io.spring.dependency-management' are you using to run this? Seems like some versions do not have that method.

@piacenti
Copy link

@DerekWWestAwesomeDeveloper Which method?

@artemptushkin
Copy link

The workarounds for updating the pom file with custom tasks are ugly, the issue is simple:

if you use dependencyManagement you can not combine it with implementation platform(..)

The right solution would be to use one, for instance, to migrate all the BOMs usages from

implementation platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")

to

dependencyManagement {
  imports {
      mavenBom("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")

@micheljung
Copy link

I'd rather get rid of dependencyManagement, which is obsolete since Gradle introduced the platform plugin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants