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

Refactor the plugin, improve the logging for the git provider #71

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
124 changes: 69 additions & 55 deletions src/main/groovy/wooga/gradle/version/VersionPlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,19 @@ import org.gradle.api.Project
import org.gradle.api.provider.Provider
import wooga.gradle.version.internal.DefaultVersionCodeExtension
import wooga.gradle.version.internal.DefaultVersionPluginExtension
import wooga.gradle.version.internal.GitBuildService
import wooga.gradle.version.internal.ToStringProvider
import wooga.gradle.version.internal.VersionCode
import wooga.gradle.version.internal.release.base.ReleaseVersion
import wooga.gradle.version.internal.release.semver.ChangeScope

import java.util.stream.Stream

/**
* A plugin that sets the project version based on selected {@code VersionSchemes}
*/
class VersionPlugin implements Plugin<Project> {

static String EXTENSION_NAME = "versionBuilder"
static String VERSION_CODE_EXTENSION_NAME = "versionCode"

Expand All @@ -43,57 +48,27 @@ class VersionPlugin implements Plugin<Project> {
setProjectVersion(project, extension)
}

static void applyOnCurrentAndSubProjects(Project project, Closure operation) {
operation(project)
project.childProjects.values().each { prj ->
operation(prj)
applyOnCurrentAndSubProjects(prj, operation)
}
project.parent
}

private static void setProjectVersion(Project project, VersionPluginExtension extension) {
def versionProvider = new ToStringProvider(extension.version.map({ it.version }.memoize()))
applyOnCurrentAndSubProjects(project) { Project prj ->
prj.setVersion(versionProvider)
def versionCodeExt = project.extensions.findByName(VERSION_CODE_EXTENSION_NAME) as VersionCodeExtension
if(!versionCodeExt) {
versionCodeExt = DefaultVersionCodeExtension.empty(prj, VERSION_CODE_EXTENSION_NAME)
}
versionCodeExt.convention(extension.versionCode)
}
}

static File findNearestGitFolder(File projectDir, int maxDepth) {
if(maxDepth > 0) {
def maybeDotGit = Stream.of(projectDir.listFiles( { File file ->
file.directory && file.name == ".git"
} as FileFilter)).findFirst()

return maybeDotGit.orElseGet {
findNearestGitFolder(projectDir.parentFile, maxDepth-1)
}
}
return null
}

/**
* Configures the extension used by the plugin with the default conventions
*/
protected static VersionPluginExtension createAndConfigureExtension(Project project) {

def extension = project.extensions.create(VersionPluginExtension, EXTENSION_NAME, DefaultVersionPluginExtension) as DefaultVersionPluginExtension
// Register the git build service
Provider<GitBuildService> gitBuildService = project.gradle.sharedServices.registerIfAbsent("git",
GitBuildService.class, spec -> {
})

// Create the extension
def extension = project.extensions.create(VersionPluginExtension, EXTENSION_NAME, DefaultVersionPluginExtension) as DefaultVersionPluginExtension

Provider<String> gitRoot = VersionPluginConventions.gitRoot.getStringValueProvider(project)
.orElse(VersionPluginConventions.maxGitRootSearchDepth.getIntegerValueProvider(project).map {
findNearestGitFolder(project.projectDir, it)?.absolutePath
})
.orElse(VersionPluginConventions.maxGitRootSearchDepth.getIntegerValueProvider(project).map {
findNearestGitFolder(project.projectDir, it)?.absolutePath
})

extension.git.convention(ProviderExtensions.mapOnce(gitRoot) { String it ->
try {
Grgit git = Grgit.open(dir: it)
project.gradle.buildFinished {
project.logger.info "Closing Git repo: ${git.repository.rootDir}"
git.close()
}
return git
return gitBuildService.get().getRepository(it)
} catch (RepositoryNotFoundException ignore) {
project.logger.warn("Git repository not found at $gitRoot ")
}
Expand All @@ -115,20 +90,20 @@ class VersionPlugin implements Plugin<Project> {
extension.mainBranchPattern.convention(VersionPluginConventions.mainBranchPattern.getStringValueProvider(project))

extension.scope.convention(VersionPluginConventions.scope.getStringValueProvider(project)
.map {it?.trim()?.empty? null: it }
.map { ChangeScope.valueOf(it.toUpperCase()) })
.map {it?.trim()?.empty? null: it }
.map { ChangeScope.valueOf(it.toUpperCase()) })

extension.version.convention(VersionPluginConventions.version.getStringValueProvider(project)
.map {new ReleaseVersion(version: it) }
.orElse(inferVersionIfGitIsPresent(project, extension))
.orElse(createInferredByGitVersionProvider(project, extension))
.orElse(new ReleaseVersion(version: VersionPluginConventions.UNINITIALIZED_VERSION))
)

extension.versionCode.convention(extension.versionCodeScheme.map({ VersionCodeSchemes scheme ->
def version = extension.version.map { it.version }.orNull
return VersionCode.Schemes
.fromExternal(scheme)
.versionCodeFor(version, extension.versionRepo.orNull, extension.versionCodeOffset.getOrElse(0))
.fromExternal(scheme)
.versionCodeFor(version, extension.versionRepo.orNull, extension.versionCodeOffset.getOrElse(0))
}.memoize()))

extension.stage.convention(VersionPluginConventions.stage.getStringValueProvider(project))
Expand All @@ -138,16 +113,55 @@ class VersionPlugin implements Plugin<Project> {
return extension
}

private static Provider<ReleaseVersion> inferVersionIfGitIsPresent(Project project, VersionPluginExtension extension) {
def inferredVersion = ProviderExtensions.mapOnce(extension.git) { Grgit git ->
private static Provider<ReleaseVersion> createInferredByGitVersionProvider(Project project, VersionPluginExtension extension) {

def provider = ProviderExtensions.mapOnce(extension.git) { Grgit git ->
def version = extension.inferVersion(extension.versionScheme.get(), extension.stage, extension.scope)
.orElse(project.provider {
throw new GradleException('No version strategies were selected. Run build with --info for more detail.')
}).get()
.orElse(project.provider {
throw new GradleException('Could not resolve the project version as no version strategies could be selected. Run build with --info for more detail.')
}).get()
project.logger.warn('Inferred project: {}, version: {}', project.name, version.version)
return version
} as Provider<ReleaseVersion>

return inferredVersion.orElse(new ReleaseVersion(version: VersionPluginConventions.UNINITIALIZED_VERSION))
// If git was not found
return provider.orElse(new ReleaseVersion(version: VersionPluginConventions.UNINITIALIZED_VERSION))
}

private static File findNearestGitFolder(File projectDir, int maxDepth) {
if(maxDepth > 0) {
def maybeDotGit = Stream.of(projectDir.listFiles( { File file ->
file.directory && file.name == ".git"
} as FileFilter)).findFirst()

return maybeDotGit.orElseGet {
findNearestGitFolder(projectDir.parentFile, maxDepth-1)
}
}
return null
}

/**
* Sets the project version onto the current project and any of its subprojects
*/
private static void setProjectVersion(Project project, VersionPluginExtension extension) {
def versionProvider = new ToStringProvider(extension.version.map({ it.version }.memoize()))
applyOnCurrentAndSubProjects(project) { Project prj ->
prj.setVersion(versionProvider)
def versionCodeExt = project.extensions.findByName(VERSION_CODE_EXTENSION_NAME) as VersionCodeExtension
if(!versionCodeExt) {
versionCodeExt = DefaultVersionCodeExtension.empty(prj, VERSION_CODE_EXTENSION_NAME)
}
versionCodeExt.convention(extension.versionCode)
}
}

private static void applyOnCurrentAndSubProjects(Project project, Closure operation) {
operation(project)
project.childProjects.values().each { prj ->
operation(prj)
applyOnCurrentAndSubProjects(prj, operation)
}
project.parent
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package wooga.gradle.version.internal

import org.ajoberstar.grgit.Grgit
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters

/**
* Implemented from https://docs.gradle.org/current/userguide/build_services.html#build_services
*/
abstract class GitBuildService implements BuildService<BuildServiceParameters.None>, AutoCloseable {

List<Grgit> instances = []

GitBuildService() {
}

/**
* Opens the repository at the given directory path. When the gradle build has finished,
* will automatically close it
* @param directory The file path to the git repository
* @return A managed git repository
*/
Grgit getRepository(String directory) {
Grgit git = Grgit.open(dir: directory)
instances.add(git)
return git
}

@Override
void close() throws Exception {
for(instance in instances) {
System.out.println("Closing Git repository: ${instance.repository.rootDir}")
instance.close()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,28 +137,31 @@ final class SemVerStrategy implements DefaultVersionStrategy {
* Composes a string detailing the current repository staged/unstaged files
*/
static String composeRepositoryStatus(Status status) {
StringBuilder str = new StringBuilder()
StringBuilder builder = new StringBuilder()

Closure printChangeSet = { label, changeSet ->
str.append("\n> ${label}\n")
str.append(changeSet.added.collect({ "[ADDED] ${it}" }).join("\n"))
str.append(changeSet.modified.collect({ "[MODIFIED] ${it}" }).join("\n"))
str.append(changeSet.removed.collect({ "[REMOVED] ${it}" }).join("\n"))
builder.append("\n> ${label}\n")

List<String> lines = []
lines.addAll(changeSet.added.collect({ "[ADDED] ${it}" }))
lines.addAll(changeSet.modified.collect({ "[MODIFIED] ${it}"}))
lines.addAll(changeSet.removed.collect({ "[REMOVED] ${it}"}))

builder.append(lines.join("\n"))
}

str.append("Repository Status:")
printChangeSet("Staged", status.staged)
printChangeSet("Unstaged", status.unstaged)
builder.append("Git Repository Status:")
printChangeSet("Staged Files", status.staged)
printChangeSet("Unstaged Files", status.unstaged)

str.toString()
builder.toString()
}

/**
* Infers the version to use for this build. Uses the normal, pre-release, and build metadata
* strategies in order to infer the version. If the {@code release.stage} is not set, uses the
* first value in the {@code stages} set (i.e. the one with the lowest precedence). After inferring
* the version precedence will be enforced, if required by this strategy.
*
*/
@Override
ReleaseVersion infer(VersionInferenceParameters params) {
Expand Down