Skip to content

Commit

Permalink
Introduce the grgit-service plugin
Browse files Browse the repository at this point in the history
The org.ajoberstar.grgit-service is the new underlying behavior for
org.ajoberstar.grgit, building on the work from @runningcode and
@abelom.

The service plugin only registers a GrgitService, but leaves it up to
plugins (or builds) to grab the service and try to use it.

Alternatively, the existing grgit plugin applies the service plugin and
eagerly resolves it to provide the prior grgit extension property.

A breaking change is that any project which wants to access a
pre-initialized grgit instance now must apply the grgit plugin. This
reduces the amount of cross-project logic going on, which Gradle has
discouraged for a while (but can be hard to avoid).

Some plugins may have use cases to register their own GrgitService
instances that are used for their own behavior (the gradle-git-publish
plugin will take advantage of this once it upgrades to use grgit 5).

All projects using grgit-service plugin will share a Grgit instance,
which is also controlled to avoid concurrent access. (That doesn't
affect other plugins/builds registering their own GrgitService
instances).
  • Loading branch information
ajoberstar committed Feb 8, 2022
1 parent 1982af3 commit 2aba52c
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 220 deletions.
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,78 @@ It also provides a Gradle plugin to easily get a Grgit instance for the build's
- [Documentation Site](http://ajoberstar.org/grgit/index.html)
- [Release Notes](https://github.com/ajoberstar/grgit/releases)

## Simple Usage in Gradle

Apply the `org.ajoberstar.grgit` plugin in any project that needs to access a `Grgit` instance.

NOTE: This plugin eagerly opens a Grgit instance, which may not be needed depending on the tasks you want to run. If this is not desired, see the next section.

```
plugins {
id 'org.ajoberstar.grgit' version '<version>'
}
// adds a grgit property to the project (will silently be null if there's no git repo)
tasks.register("describe") {
doFirst {
println grgit.describe()
}
}
```

## More Performant Usage in Gradle

Apply the `org.ajoberstar.grgit-service` plugin instead of `org.ajoberstar.grgit` to avoid eagerly resolving the `Grgit` instance. This works best with custom tasks that accept a `Property<GrgitService>`.

This approach ensures you only open a `Grgit` instance when a task is run that uses it.

```
import org.ajoberstar.grgit.gradle.GrgitService
plugins {
id 'org.ajoberstar.grgit-service' version '<version>'
}
tasks.register("describe", DescribeTask) {
service = grgitService.service
}
class DescribeTask extends DefaultTask {
@Input
final Property<GrgitService> service
@Inject
DoStuffTask(ObjectFactory objectFactory) {
this.service = objectFactory.property(GrgitService.class);
}
@TaskAction
void execute() {
println service.get().grgit.describe()
}
}
```

### Custom Gradle Plugins

If you are writing a custom Gradle plugin, you'll want to use one or both of the following approaches:

- If you need a `Grgit` instance representing the repository the project is in, use `org.ajoberstar.grgit-service` and use the `GrgitServiceExtension` to access the shared `GrgitService`. Wire this into any tasks or whatever needs to use the service via `Property<GrgitService>` for full lazy evaluation benefits.
- If you need a `Grgit` instance that's separate from the project's repository, declare your own `GrgitService` naming it something _not_ prefixed with `grgit*`.

```
Provider<GrgitService> serviceProvider = project.getGradle().getSharedServices().registerIfAbsent("grgit", GrgitService.class, spec -> {
// use getCurrentDirectory() if you need to search upwards from the provided directory
spec.getParameters().getCurrentDirectory().set(project.getLayout().getProjectDirectory());
// or use getDirectory() if you want to specify a specific directory and not search
spec.getParameters().getDirectory().set(project.getLayout().getBuildDirectory().dir("my-custom-repo"));
// generally, this should be false, unless you're using getDirectory() choose to have the repo initialized if the directory does not exist
spec.getParameters().getInitIfNotExists().set(false);
// I recommend setting this to 1 unless you know better, this will avoid multiple parallel tasks editing the repo at the same time
spec.getMaxParallelUsages().set(1);
});
```

## Questions, Bugs, and Features

Please use the repo's [issues](https://github.com/ajoberstar/grgit/issues)
Expand Down
5 changes: 5 additions & 0 deletions grgit-gradle/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ pluginBundle {
displayName = "The Groovy way to use Git"
tags = listOf("git", "groovy")
}
create("grgitServicePlugin") {
id = "org.ajoberstar.grgit-service"
displayName = "The Groovy way to use Git (BuildService edition)"
tags = listOf("git", "groovy")
}
}
mavenCoordinates {
groupId = project.group as String
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package org.ajoberstar.grgit.gradle

import spock.lang.Specification
import spock.lang.Unroll

import org.ajoberstar.grgit.Grgit
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import spock.lang.TempDir

class BaseCompatTest extends Specification {
class GrgitPluginCompatTest extends Specification {
@TempDir File tempDir
File projectDir
File buildFile

def setup() {
projectDir = new File(tempDir, 'project')
buildFile = projectFile('build.gradle')

}

def 'with no repo, plugin sets grgit to null'() {
Expand All @@ -34,7 +32,7 @@ task doStuff {
}
'''
when:
def result = build('doStuff')
def result = build('doStuff', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
}
Expand All @@ -59,7 +57,7 @@ task doStuff {
}
'''
when:
def result = build('doStuff', '--quiet')
def result = build('doStuff', '--quiet', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
result.output.normalize() == '1.0.0\n'
Expand All @@ -85,7 +83,7 @@ task doStuff {
}
'''
when:
def result = build('doStuff', '--info')
def result = build('doStuff', '--info', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
result.output.contains('Closing Git repo')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.ajoberstar.grgit.gradle

import spock.lang.Specification

import org.ajoberstar.grgit.Grgit
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import spock.lang.TempDir

class GrgitServicePluginCompatTest extends Specification {
@TempDir File tempDir
File projectDir
File buildFile

def setup() {
projectDir = new File(tempDir, 'project')
buildFile = projectFile('build.gradle')
buildFile << '''\
import org.ajoberstar.grgit.gradle.GrgitService
plugins {
id 'org.ajoberstar.grgit-service'
}
tasks.register("doStuff", DoStuffTask.class) {
service = grgitService.service
}
class DoStuffTask extends DefaultTask {
@Input
final Property<GrgitService> service
@Inject
DoStuffTask(ObjectFactory objectFactory) {
this.service = objectFactory.property(GrgitService.class);
}
@TaskAction
void execute() {
println service.get().grgit.describe()
}
}
'''
}

def 'with no repo, accessing service fails'() {
given:
// nothing
when:
def result = buildAndFail('doStuff', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.FAILED
}

def 'with repo, plugin opens the repo as grgit'() {
given:
Grgit git = Grgit.init(dir: projectDir)
projectFile('1.txt') << '1'
git.add(patterns: ['1.txt'])
git.commit(message: 'yay')
git.tag.add(name: '1.0.0')
when:
def result = build('doStuff', '--quiet', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
result.output.normalize() == '1.0.0\n'
}

def 'with repo, plugin closes the repo after build is finished'() {
given:
Grgit git = Grgit.init(dir: projectDir)
projectFile('1.txt') << '1'
git.add(patterns: ['1.txt'])
git.commit(message: 'yay')
git.tag.add(name: '1.0.0')
when:
def result = build('doStuff', '--info', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
result.output.contains('Closing Git repo')
}

private BuildResult build(String... args) {
return GradleRunner.create()
.withGradleVersion(System.properties['compat.gradle.version'])
.withPluginClasspath()
.withProjectDir(projectDir)
.forwardOutput()
.withArguments((args + '--stacktrace') as String[])
.build()
}

private BuildResult buildAndFail(String... args) {
return GradleRunner.create()
.withGradleVersion(System.properties['compat.gradle.version'])
.withPluginClasspath()
.withProjectDir(projectDir)
.forwardOutput()
.withArguments((args + '--stacktrace') as String[])
.buildAndFail()
}

private File projectFile(String path) {
File file = new File(projectDir, path)
file.parentFile.mkdirs()
return file
}
}

This file was deleted.

Loading

0 comments on commit 2aba52c

Please sign in to comment.