Skip to content

Commit

Permalink
Merge pull request #354 from ajoberstar/config-cache
Browse files Browse the repository at this point in the history
Introduce the grgit-service plugin
  • Loading branch information
ajoberstar authored Feb 8, 2022
2 parents e8b01fd + 2aba52c commit 6e14884
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 42 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
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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.ajoberstar.grgit.gradle;

import org.ajoberstar.grgit.Grgit;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

/**
* Plugin adding a {@code grgit} property to all projects that searches for a Git repo from the
* project's directory.
*
* @since 2.0.0
*/
public class GrgitPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply(GrgitServicePlugin.class);
var serviceExtension = project.getExtensions().getByType(GrgitServiceExtension.class);
try {
project.getLogger().info("The org.ajoberstar.grgit plugin eagerly opens a Grgit instance. Use org.ajoberstar.grgit-service for better performance.");
project.getExtensions().add(Grgit.class, "grgit", serviceExtension.getService().get().getGrgit());
} catch (Exception e) {
project.getLogger().debug("Failed to open Grgit instance", e);
project.getExtensions().getExtraProperties().set("grgit", null);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.ajoberstar.grgit.gradle;

import javax.inject.Inject;

import org.ajoberstar.grgit.Grgit;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.provider.Property;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;

public abstract class GrgitService implements BuildService<GrgitService.Params>, AutoCloseable {
private static final Logger logger = Logging.getLogger(GrgitService.class);

public interface Params extends BuildServiceParameters {
DirectoryProperty getCurrentDirectory();

DirectoryProperty getDirectory();

Property<Boolean> getInitIfNotExists();
}

private final Grgit grgit;

@Inject
public GrgitService() {
if (getParameters().getCurrentDirectory().isPresent()) {
this.grgit = Grgit.open(op -> {
op.setCurrentDir(getParameters().getCurrentDirectory().get().getAsFile());
});
return;
}

var dir = getParameters().getCurrentDirectory().get().getAsFile();
if (dir.exists()) {
this.grgit = Grgit.open(op -> {
op.setDir(dir);
});
} else if (getParameters().getInitIfNotExists().get()) {
this.grgit = Grgit.init(op -> {
op.setDir(dir);
});
} else {
throw new IllegalStateException("No Git repo exists at " + dir + " and initIfNotExists is false. Cannot proceed.");
}
}

public Grgit getGrgit() {
return grgit;
}

@Override
public void close() throws Exception {
logger.info("Closing Git repo: {}", grgit.getRepository().getRootDir());
grgit.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.ajoberstar.grgit.gradle;

import javax.inject.Inject;

import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;

public class GrgitServiceExtension {
private Property<GrgitService> service;

@Inject
public GrgitServiceExtension(ObjectFactory objectFactory) {
this.service = objectFactory.property(GrgitService.class);
}

public Property<GrgitService> getService() {
return service;
}
}
Loading

0 comments on commit 6e14884

Please sign in to comment.