Skip to content

Commit

Permalink
Fix plugin handling of 'root versions' in monorepo
Browse files Browse the repository at this point in the history
  • Loading branch information
serpro69 committed Mar 20, 2024
1 parent 1c82cea commit e2a642a
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import kotlin.io.path.Path
/**
* Monorepo module configuration enables individual versioning of multi-modules in a mono-repo.
*
* @property sources path to track changes for the entire monorepo,
* relative to the repo [GitRepoConfig.directory]; defaults to current dir.
* @property sources path to track changes for the monorepo submodules that are
* not part of the [modules] list, relative to the given submodule directory;
* defaults to current submodule dir.
*
* All [modules] that don't set their own [ModuleConfig.sources] will use this property
* to track changes in the mono-repository.
Expand All @@ -32,7 +33,6 @@ interface MonorepoConfig {
* For example, in case of gradle, it would mean a fully-qualified gradle module path.
* So for `./core` module in the root of a gradle mono-repo, this would be `:core`,
* and for `./foo/bar` module in a gradle mono-repo, this would be `:foo:bar`.
* module directory name (e.g. `core` for `./core` submodule in the root of the mono-repo)
* @property sources path to track changes for the module, relative to the module [path].
* Defaults to module directory.
* @property tag git tag configuration for the module.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ import io.github.serpro69.semverkt.release.Increment.PATCH
import io.github.serpro69.semverkt.release.Increment.PRE_RELEASE
import io.github.serpro69.semverkt.release.SemverRelease
import io.github.serpro69.semverkt.release.configuration.JsonConfiguration
import io.github.serpro69.semverkt.release.configuration.ModuleConfig
import io.github.serpro69.semverkt.release.repo.GitRepository
import io.github.serpro69.semverkt.spec.PreRelease
import io.github.serpro69.semverkt.spec.Semver
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.initialization.Settings
import org.gradle.api.logging.Logging
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.Path
import kotlin.io.path.relativeTo

@Suppress("unused")
class SemverKtPlugin : Plugin<Settings> {
Expand Down Expand Up @@ -76,35 +80,76 @@ class SemverKtPlugin : Plugin<Settings> {
* IF `currentVersion` exists, both `latestVersion` and `nextVersion` will always return as `null`
*/
private fun setVersion(project: Project, config: SemverKtPluginConfig): Triple<Semver?, Semver?, Semver?> {
logger.info("Set version for {}", project.name)
logger.debug("Using configuration: {}", config.jsonString())
logger.lifecycle("Set version for {}", project.name)
logger.lifecycle("Using configuration: {}", config.jsonString())
val propRelease = project.hasProperty("release")
val propPromoteRelease = project.hasProperty("promoteRelease")
val propPreRelease = project.hasProperty("preRelease")
val propIncrement = project.findProperty("increment")
?.let { Increment.getByName(it.toString()) }
?: NONE

val srcPath: (ModuleConfig?) -> Path = { m ->
project.rootDir.toPath()
.resolve(normalize(m?.path ?: project.path))
.resolve(m?.sources ?: config.monorepo.sources)
.relativeTo(project.rootDir.toPath())
.normalize()
.also { p -> logger.lifecycle("Module path: {}", p) }
}

val isRoot = project == project.rootProject
val isMonorepo = config.monorepo.modules.isNotEmpty()
val module = config.monorepo.modules.firstOrNull { it.path == project.path }
logger.debug("Module '{}' config: {}", module?.path, module?.jsonString())
val hasChanges by lazy {
if (project == project.rootProject) true // always apply version to root project
else if (isMonorepo && module != null) GitRepository(config).use { repo ->
val prefix = module.tag?.prefix ?: config.git.tag.prefix
val s = repo.head().name
val e = repo.headVersionTag(prefix)?.name
?: repo.latestVersionTag(prefix)?.name
?: repo.log(tagPrefix = prefix).last().objectId.name
repo.diff(s, e).any {
logger.debug("{} diff: {}", project.name, it)
val srcPath = Path(project.projectDir.relativeTo(project.rootDir).path)
.resolve(module.sources).normalize()
logger.debug("Module path: {}", srcPath)
Path(it.oldPath).startsWith(srcPath) || Path(it.newPath).startsWith(srcPath)
when {
// always apply version to root project in non-monorepo
!isMonorepo && isRoot -> true
isMonorepo -> GitRepository(config).use { repo ->
val prefix = module?.tag?.prefix ?: config.git.tag.prefix
val s = repo.head().name
val e = repo.headVersionTag(prefix)?.name
?: repo.latestVersionTag(prefix)?.name
?: repo.log(tagPrefix = prefix).last().objectId.name
repo.diff(s, e).any {
logger.lifecycle("{} diff: {}", project.name, it)
when {
// root and non-configured modules in monorepo are tracked together via 'monorepo.sources' config prop
// so for root we check that the diff does not match any of the configured modules' sources
isRoot -> config.monorepo.modules.none { m ->
val moduleSrc = srcPath(m)
Paths.get(it.oldPath).startsWith(moduleSrc)
|| Paths.get(it.newPath).startsWith(moduleSrc)
}
// for non-configured module we check that the diff matches own submodule sources
// or does not match any of the configured modules' sources
module == null -> {
val none by lazy {
config.monorepo.modules.none { m ->
val moduleSrc = srcPath(m)
Paths.get(it.oldPath).startsWith(moduleSrc)
|| Paths.get(it.newPath).startsWith(moduleSrc)
}
}
val own by lazy {
val moduleSrc = srcPath(module)
Paths.get(it.oldPath).startsWith(moduleSrc)
|| Paths.get(it.newPath).startsWith(moduleSrc)
}
own || none
}
// for configured modules we check that the diff matches the given module's sources
else -> with(srcPath(module)) {
Paths.get(it.oldPath).startsWith(this)
|| Paths.get(it.newPath).startsWith(this)
}
}
}
}
// not a monorepo
else -> true
}
else true // module not versioned separately
}
logger.debug("Module has changes: {}", hasChanges)

Expand Down Expand Up @@ -220,3 +265,13 @@ class SemverKtPlugin : Plugin<Settings> {
}
}
}

/**
* Takes a gradle project fully-qualified [path] and returns as [Path].
* (Drop first ':' and replace the rest with '/' of the gradle project fully-qualified path)
*
* Examples
* - `normalizePath(":core") == Path("core")`
* - `normalizePath(":foo:bar") == Path("foo/bar")`
*/
private fun normalize(path: String): Path = Path(path.drop(1).replace(":", "/"))
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ class SemverKtPluginMonorepoConfig internal constructor(settings: Settings?, pri
}
}

/**
* @property path a fully-qualified gradle module path.
* For example, for `./core` module in the root of a gradle mono-repo, this would be `:core`,
* and for `./foo/bar` module in a gradle mono-repo, this would be `:foo:bar`.
*/
@PluginConfigDsl
class SemverKtPluginModuleConfig internal constructor(
override val path: String,
Expand Down

0 comments on commit e2a642a

Please sign in to comment.