Skip to content

Commit

Permalink
Introduces monorepo support
Browse files Browse the repository at this point in the history
* First quick and dirty attempt

* Introduce TaggedCommits

* Refactor method name: currentPostion(path) -> positionOfLastChangeIn(path)

* Wrapper class around TagsOnCommit, with factory methods

* #291 Initial commit

* #310 #291 Updated JGit & GrGit to exclude dirs when calculating versions.

* #310 #291 Implement requested changes.
  • Loading branch information
john-tipper authored and adamdubiel committed Jan 18, 2020
1 parent 2e86885 commit 7202376
Show file tree
Hide file tree
Showing 29 changed files with 733 additions and 66 deletions.
10 changes: 8 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,21 @@ sourceCompatibility = '1.7'

repositories {
mavenCentral()
jcenter()
}

project.ext.versions = [
jgit: '4.11.0.201803080745-r',
jgit: '5.6.0.201912101111-r',
jsch: '0.1.54',
jschAgent: '0.0.9'
]

sourceSets {
main {
java { srcDirs = [] } // no source dirs for the java compiler
groovy { srcDirs = ["src/main/java", "src/main/groovy"] } // compile everything in src/ with groovy
}

integration {
java.srcDir project.file('src/integration/java')
groovy.srcDir project.file('src/integration/groovy')
Expand Down Expand Up @@ -91,7 +97,7 @@ dependencies {

compile group: 'com.github.zafarkhaja', name: 'java-semver', version: '0.9.0'

testCompile (group: 'org.ajoberstar', name: 'grgit', version: '1.7.2') {
testCompile (group: 'org.ajoberstar.grgit', name: 'grgit-core', version: '4.0.1') {
exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit.ui'
exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit'
}
Expand Down
24 changes: 23 additions & 1 deletion docs/configuration/basic_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ allprojects {
}
```

### Multi-module with multiple versions
### Multi-module project with multiple versions

Sometimes it might be desirable to release each module (or just some
modules) of multi-module project separately. If so, please make sure
Expand Down Expand Up @@ -117,6 +117,28 @@ my-service
my-service-client
```

Use the `projectDirs` configuration parameter within a `monorepos` block to identify submodules
that should be excluded from consideration when calculating whether to increment
the version of the parent project. Typically, you would do this in the top level
project, assuming that submodules are named after the directory they appear in:

```
scmVersion {
monorepos {
projectDirs = project.subprojects.collect({p -> p.name})
}
}
```

Version calculation rules:
1. Changes to files within a submodule increment that submodule's version only.
2. Changes to a submodule do not cause a change to the parent project's version if
the parent is set to ignore that submodule, via `foldersToExclude`.
3. Changes to files in the parent project but which are not in a submodule identified via
`foldersToExclude` will cause the parent project's version to increment but not the
versions of any submodules. If this is desired then consider wiring the `createRelease` or
`release` tasks of the submodules to be dependencies of the tasks of the same name in the parent.

## Releasing

```
Expand Down
3 changes: 3 additions & 0 deletions docs/configuration/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ All `axion-release-plugin` configuration options:
// doc: Version / Sanitization
sanitizeVersion = true // should created version be sanitized, true by default

// doc: Basic usage / Basic configuration
foldersToExclude = ['submodule1', 'submodule2'] // ignore changes in these subdirs when calculating changes to parent

tag { // doc: Version / Parsing
prefix = 'tag-prefix' // prefix to be used, 'release' by default
branchPrefix = [ // set different prefix per branch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package pl.allegro.tech.build.axion.release

import org.gradle.testkit.runner.TaskOutcome

import java.util.stream.Collectors

class MultiModuleProjectIntegrationTest extends BaseIntegrationTest {

File getSubmoduleDir(String projectName) {
return new File(temporaryFolder.root, projectName)
}

void generateSubmoduleBuildFile(String projectName) {
File submoduleDir = getSubmoduleDir(projectName)
submoduleDir.mkdirs()
File buildFile = new File(submoduleDir, "build.gradle")
buildFile << """
plugins {
id 'pl.allegro.tech.build.axion-release'
}
scmVersion {
tag {
prefix = '${projectName}'
}
}
project.version = scmVersion.version
"""
}

void generateSettingsFile(File dir, List<String> submodules) {
String submodulesIncludeString = submodules.stream().map({ m -> "'${m}'" }).collect(Collectors.joining(','))
File settings = new File(dir, "settings.gradle")
settings << """
include ${submodulesIncludeString}
"""
}

void generateGitIgnoreFile(File dir) {
File gitIgnore = new File(dir, ".gitignore")
gitIgnore << """\
.gradle
""".stripIndent()
}

/**
* Configure the multi-module project for testing.
* We start off with v1.0.0 on the parent project and versions 2.0.0 and 3.0.0 for the 2 child projects.
*/
void initialProjectConfiguration() {
List<String> submodules = ["project1", "project2"]
buildFile('''
scmVersion {
monorepos {
projectDirs = project.subprojects.collect({p -> p.name})
}
}
'''
)
generateSettingsFile(temporaryFolder.root, submodules)
generateGitIgnoreFile(temporaryFolder.root)

repository.commit(['.'], "initial commit of top level project")

// create version for main project
runGradle(':createRelease', '-Prelease.version=1.0.0', '-Prelease.disableChecks')

// create submodules
int versionCounter = 2
for (String module : submodules) {
// generate the project files
generateSubmoduleBuildFile(module)
// add to repo
repository.commit(["${module}"], "commit submodule ${module}")
// tag release for that project
runGradle(":${module}:createRelease", "-Prelease.version=${versionCounter}.0.0", '-Prelease.disableChecks')
versionCounter++
}
}

def "plugin can distinguish between submodules versions"() {
given:
initialProjectConfiguration()

when:
def result = runGradle(':currentVersion')
def match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '1.0.0'
result.task(":currentVersion").outcome == TaskOutcome.SUCCESS

when:
result = runGradle(':project1:currentVersion')
match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '2.0.0'
result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS

when:
result = runGradle(':project2:currentVersion')
match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '3.0.0'
result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS

}

def "change to submodule should not change sibling project version"() {
given:
initialProjectConfiguration()
File dummy = new File(getSubmoduleDir("project1"), "dummy.txt")
dummy.createNewFile()
repository.commit(["project1"], "commit submodule project1")
runGradle(":project1:release", "-Prelease.version=4.0.0", '-Prelease.localOnly', '-Prelease.disableChecks')

when:
def result = runGradle(':project1:currentVersion')
def match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '4.0.0'
result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS

when:
result = runGradle(':project2:currentVersion')
match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '3.0.0'
result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS

}

def "change to submodule should not change root project version"() {
given:
initialProjectConfiguration()
File dummy = new File(getSubmoduleDir("project1"), "dummy.txt")
dummy.createNewFile()
repository.commit(["project1"], "commit submodule project1")
runGradle(":project1:release", "-Prelease.version=4.0.0", '-Prelease.localOnly', '-Prelease.disableChecks')

when:
def result = runGradle(':currentVersion')
def match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '1.0.0'
result.task(":currentVersion").outcome == TaskOutcome.SUCCESS
}

def "change to root project should not change child project versions"() {
given:
initialProjectConfiguration()
File dummy = new File(temporaryFolder.root, "dummy.txt")
dummy.createNewFile()
repository.commit(["dummy.txt"], "commit parent project")
runGradle(":release", "-Prelease.version=4.0.0", '-Prelease.localOnly', '-Prelease.disableChecks')

when:
def result = runGradle(':currentVersion')
def match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '4.0.0'
result.task(":currentVersion").outcome == TaskOutcome.SUCCESS

when:
result = runGradle(':project1:currentVersion')
match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '2.0.0'
result.task(":project1:currentVersion").outcome == TaskOutcome.SUCCESS

when:
result = runGradle(':project2:currentVersion')
match = result.output =~ /(?m)^.*Project version: (.*)$/

then:
match[0][1] == '3.0.0'
result.task(":project2:currentVersion").outcome == TaskOutcome.SUCCESS

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pl.allegro.tech.build.axion.release.domain

class MonorepoConfig {
public List<String> projectDirs = []
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class VersionConfig {

HooksConfig hooks = new HooksConfig()

MonorepoConfig monorepoConfig = new MonorepoConfig()

private Context context

private VersionService.DecoratedVersion resolvedVersion = null
Expand Down Expand Up @@ -165,4 +167,8 @@ class VersionConfig {
this.context = GradleAwareContext.create(project, this)
}
}

void monorepos(Closure c) {
project.configure(monorepoConfig, c)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import pl.allegro.tech.build.axion.release.domain.properties.TagProperties
import pl.allegro.tech.build.axion.release.domain.properties.VersionProperties
import pl.allegro.tech.build.axion.release.domain.scm.ScmPosition
import pl.allegro.tech.build.axion.release.domain.scm.ScmRepository
import pl.allegro.tech.build.axion.release.domain.scm.TagsOnCommit
import pl.allegro.tech.build.axion.release.domain.scm.TaggedCommits

import java.util.regex.Pattern

Expand All @@ -24,16 +24,23 @@ class VersionResolver {
private final ScmRepository repository

private final VersionSorter sorter

VersionResolver(ScmRepository repository) {
private ScmPosition latestChangePosition
/**
* This is the path of the project relative to the Git root.
* If this path is not empty then it means that the project is running as a submodule of a parent project.
*/
private String projectRootRelativePath

VersionResolver(ScmRepository repository, String projectRootRelativePath) {
this.repository = repository
this.projectRootRelativePath = projectRootRelativePath
this.sorter = new VersionSorter()
}

VersionContext resolveVersion(VersionProperties versionRules, TagProperties tagProperties, NextVersionProperties nextVersionProperties) {
ScmPosition position = repository.currentPosition()
latestChangePosition = repository.positionOfLastChangeIn(projectRootRelativePath, versionRules.monorepoProperties.dirsToExclude)

VersionFactory versionFactory = new VersionFactory(versionRules, tagProperties, nextVersionProperties, position)
VersionFactory versionFactory = new VersionFactory(versionRules, tagProperties, nextVersionProperties, latestChangePosition)

Map versions
if (versionFactory.versionProperties.useHighestVersion) {
Expand All @@ -51,7 +58,7 @@ class VersionResolver {

Map finalVersion = versionFactory.createFinalVersion(scmState, versions.current)

return new VersionContext(finalVersion.version, finalVersion.snapshot, versions.previous, position)
return new VersionContext(finalVersion.version, finalVersion.snapshot, versions.previous, latestChangePosition)
}

private Map readVersions(VersionFactory versionFactory,
Expand All @@ -64,23 +71,21 @@ class VersionResolver {
boolean forceSnapshot = versionProperties.forceSnapshot

Map currentVersionInfo, previousVersionInfo
TagsOnCommit latestTags = repository.latestTags(releaseTagPattern)
currentVersionInfo = versionFromTaggedCommits([latestTags], false, nextVersionTagPattern,
TaggedCommits latestTaggedCommit = TaggedCommits.fromLatestCommit(repository, releaseTagPattern, latestChangePosition)
currentVersionInfo = versionFromTaggedCommits(latestTaggedCommit, false, nextVersionTagPattern,
versionFactory, forceSnapshot)
boolean onCommitWithLatestChange = currentVersionInfo.commit == latestChangePosition.revision

TagsOnCommit previousTags = latestTags
while (previousTags.hasOnlyMatching(nextVersionTagPattern)) {
previousTags = repository.latestTags(releaseTagPattern, previousTags.commitId)
}
previousVersionInfo = versionFromTaggedCommits([previousTags], true, nextVersionTagPattern,
TaggedCommits previousTaggedCommit = TaggedCommits.fromLatestCommitBeforeNextVersion(repository, releaseTagPattern, nextVersionTagPattern, latestChangePosition)
previousVersionInfo = versionFromTaggedCommits(previousTaggedCommit, true, nextVersionTagPattern,
versionFactory, forceSnapshot)

Version currentVersion = currentVersionInfo.version
Version previousVersion = previousVersionInfo.version
return [
current : currentVersion,
previous : previousVersion,
onReleaseTag : currentVersionInfo.isHead && !currentVersionInfo.isNextVersion,
onReleaseTag : onCommitWithLatestChange && !currentVersionInfo.isNextVersion,
onNextVersionTag: currentVersionInfo.isNextVersion,
noTagsFound : currentVersionInfo.noTagsFound
]
Expand All @@ -96,7 +101,7 @@ class VersionResolver {
boolean forceSnapshot = versionProperties.forceSnapshot

Map currentVersionInfo, previousVersionInfo
List<TagsOnCommit> allTaggedCommits = repository.taggedCommits(releaseTagPattern)
TaggedCommits allTaggedCommits = TaggedCommits.fromAllCommits(repository, releaseTagPattern, latestChangePosition)

currentVersionInfo = versionFromTaggedCommits(allTaggedCommits, false, nextVersionTagPattern,
versionFactory, forceSnapshot)
Expand All @@ -108,13 +113,13 @@ class VersionResolver {
return [
current : currentVersion,
previous : previousVersion,
onReleaseTag : currentVersionInfo.isHead && !currentVersionInfo.isNextVersion,
onReleaseTag : (currentVersionInfo.commit == latestChangePosition.revision) && !currentVersionInfo.isNextVersion,
onNextVersionTag: currentVersionInfo.isNextVersion,
noTagsFound : currentVersionInfo.noTagsFound
]
}

private Map versionFromTaggedCommits(List<TagsOnCommit> taggedCommits,
private Map versionFromTaggedCommits(TaggedCommits taggedCommits,
boolean ignoreNextVersionTags,
Pattern nextVersionTagPattern,
VersionFactory versionFactory,
Expand Down
Loading

0 comments on commit 7202376

Please sign in to comment.