Skip to content

About

Dev Publish Plugin :: publish Gradle projects for local testing

Resources

Contributing

Stars

6 stars

Watchers

1 watching

Forks

Repository files navigation

GitHub license Gradle Plugin Portal

Dev Publish Gradle Plugin

Dev Publish is a Gradle plugin that supports functional testing of a published module.

Each subproject is published to a local file-based Maven repository, containing real publications (Gradle Module Metadata, Plugin Marker Artifacts, checksums) that your tests can resolve.

Requirements

  • Gradle: 8.14.5+
  • Gradle JVM: 17+
  • Kotlin: 2.1+, to compile tests that use dev-publish-utils. A Gradle plugin project uses Gradle's embedded Kotlin, so it needs Gradle 9+.
  • Required plugins: maven-publish, in every project that publishes.

Quick Start

Apply maven-publish and Dev Publish to any subproject that publishes Maven artifacts, using the plugin ID dev.adamko.dev-publish and the latest version.

plugins {
  kotlin("jvm")
  `maven-publish`
  id("dev.adamko.dev-publish")
}

group = "com.example"
version = "1.0.0"
description = "A library that does something."
// This project is tested by the `:functional-tests` project.

publishing {
  publications {
    create<MavenPublication>("maven") {
      from(components["java"])
    }
  }
}

See the full code here.

Any project can collect publications from other subprojects into its own dev repo, by declaring a devPublication(project("...")) dependency.

plugins {
  kotlin("jvm")
  id("dev.adamko.dev-publish")
}

dependencies {
  devPublication(project(":lib-extras"))
  devPublication(project(":signed-library"))

  testImplementation(kotlin("test"))

  // Makes the `test` source set depend all projects declared as `devPublication()`.
  testImplementation(devPublish.dependency())
}

See the full code here.

Declaring devPublish.dependency() runs updateDevRepo before the tests, puts the dev Maven repository location on their runtime classpath, and adds dev-publish-utils for reading it.

Important

Dev Publish adds the current project as a default devPublication() dependency. Adding another devPublication() dependency will replace the default, so a project that collects other projects and also wants its own publications must declare itself as well.

For example, in :lib-extras:

// lib-extras/build.gradle.kts
dependencies {
  devPublication(project(":lib-core")) // some other subproject.
  // must re-add this project, if it's required for testing.
  devPublication(project())
}

This lets a project collect other projects while leaving out its own publications, such as a test-only project like :functional-tests above.

Note: Gradle 9.4 or below must use project's own path, because project() is only available on Gradle 9.5+.
// lib-extras/build.gradle.kts
dependencies {
  devPublication(project(":lib-core")) // some other subproject.
  // must re-add this project, if it's required for testing.
  devPublication(project(":lib-extras"))
}

devPublication dependencies are shared with consumers. If :lib-extras declares devPublication(project(":lib-core")), then anyone declaring devPublication(project(":lib-extras")) gets :lib-core's publications too, without naming :lib-core.

That is the default because a dev repo is only useful if it is closed. :lib-extras' published POM and Gradle Module Metadata name :lib-core, so a test resolving :lib-extras from the dev repo fails unless :lib-core is there as well. This applies to implementation dependencies too - they appear in the POM at runtime scope.

To collect a project without its own devPublication dependencies, make the dependency non-transitive:

dependencies {
  devPublication(project(":lib-extras")) { isTransitive = false }
}

This prunes every level at once, so anything else that is still needed must be named explicitly.

See the multi-project-aggregation example.

Important

Every project named in a devPublication dependency must itself apply Dev Publish. Declaring a dependency on a project that does not will fail with a variant-resolution error. subprojects { devPublication(it) } and allprojects { devPublication(it) } is not supported.

Dev Publish will automatically update the dev repository to a local, isolated directory before running gradle test.

Using the dev repo in tests

Once devPublish.dependency() is configured use the dev-publish-utils helper library in your tests.

@Test
fun `dev repo contains requested publications`() {
  val devRepo: Path = devMavenRepo()

  assertFileExists(devRepo.resolve("com/example/lib-extras/1.0.0/lib-extras-1.0.0.jar"))
  assertFileExists(devRepo.resolve("com/example/signed-library/1.0.0/signed-library-1.0.0.jar"))

  // lib-core is only reachable via lib-extras' devPublication dependency
  assertFileExists(devRepo.resolve("com/example/lib-core/1.0.0/lib-core-1.0.0.jar"))
}

See the full code here.

Included builds

For a project in an included build, use module notation rather than project(...):

dependencies {
  devPublication("com.example:data-model")
}

Testing a Gradle plugin with TestKit

Because the dev repo contains the real publication (including the Plugin Marker Artifact) the build under test can apply your plugin by id and version, exactly as a user would, instead of using withPluginClasspath().

devPublish.dependency() also brings in the helpers that write the build under test's settings.gradle.kts:

@Test
fun `plugin can be resolved by id and version`(@TempDir projectDir: Path) {
  // Writes a settings file that resolves the plugin under test from the dev Maven repo.
  projectDir.resolve("settings.gradle.kts").writeText(
    devPublishSettings(rootProjectName = "greeting-consumer")
  )

  // ...
}

See the full code here.

For finer control, devPublishRepository(), devPublishPluginManagement() and devPublishDependencyResolutionManagement() return the individual maven(devPublishRepo()) blocks.

See the gradle-plugin-testkit example.

Signing

The signing plugin attaches signatures to a MavenPublication, not to an individual repository, so signatures cannot be filtered out per-repository. A project that signs its publications therefore needs a GPG key just to run its functional tests.

To avoid that, only require signing when the build is actually publishing somewhere else by using signing { setRequired(...) }. Dev Publish adds a helper property, publishingOutsideDevRepo, to the signing {} block to help:

signing {
  setRequired(publishingOutsideDevRepo)
}

Combine it with your own conditions if you have them:

val signingCredentialsPresent: Property<Boolean> = TODO("check if credentials are present")

signing {
  setRequired { publishingOutsideDevRepo.get() || signingCredentialsPresent.get() }
}

Examples

Example projects are available in the examples/ directory.

About

Dev Publish Plugin :: publish Gradle projects for local testing

Resources

Contributing

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages