From 1982af3dea6901859a9484a60fd8db8d6d349347 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Wed, 8 Jul 2020 12:21:47 +0200 Subject: [PATCH 1/2] Add configuration caching support to grgit. --- .../grgit/gradle/ConfigCacheTest.groovy | 101 ++++++++++++++++++ .../grgit/gradle/GrgitBuildService.groovy | 41 +++++++ .../grgit/gradle/GrgitExtension.groovy | 17 +++ .../grgit/gradle/GrgitPlugin.groovy | 45 +++++--- 4 files changed, 191 insertions(+), 13 deletions(-) create mode 100644 grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/ConfigCacheTest.groovy create mode 100644 grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitBuildService.groovy create mode 100644 grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitExtension.groovy diff --git a/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/ConfigCacheTest.groovy b/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/ConfigCacheTest.groovy new file mode 100644 index 00000000..0625f98c --- /dev/null +++ b/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/ConfigCacheTest.groovy @@ -0,0 +1,101 @@ +package org.ajoberstar.grgit.gradle + +import org.ajoberstar.grgit.Grgit +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification + +class ConfigCacheTest extends Specification { + @Rule TemporaryFolder tempDir = new TemporaryFolder() + File projectDir + File buildFile + + def setup() { + projectDir = tempDir.newFolder('project') + buildFile = projectFile('build.gradle') + } + + def "grgit build service can be fetched from registered services"() { + given: + buildFile << """ + import org.ajoberstar.grgit.gradle.GrgitBuildService + + plugins { + id 'org.ajoberstar.grgit' + } + + task doStuff { + def injected = project.gradle.sharedServices.registrations.getByName("grgit").getService() + doLast { + assert injected.get().grgit == null + } + } + """ + + when: + runner() + .withArguments('--configuration-cache', 'doStuff') + .build() + + and: + def result = runner() + .withArguments('--configuration-cache', 'doStuff') + .build() + + then: + result.output.contains('Reusing configuration cache.') + } + + + 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') + + buildFile << '''\ +plugins { + id 'org.ajoberstar.grgit' +} + +task doStuff { + def injected = project.grgitExtension + doLast { + println injected.describe() + } +} +''' + when: + runner() + .withArguments('--configuration-cache', 'doStuff') + .build() + + and: + def result = runner() + .withArguments('--configuration-cache', 'doStuff') + .build() + then: + result.task(':doStuff').outcome == TaskOutcome.SUCCESS + result.output.contains('Reusing configuration cache.') + result.output.contains('1.0.0\n') + } + + private GradleRunner runner(String... args) { + return GradleRunner.create() + .withGradleVersion("6.6-milestone-3") + .withPluginClasspath() + .withProjectDir(projectDir) + .forwardOutput() + .withArguments((args + '--stacktrace') as String[]) + } + + private File projectFile(String path) { + File file = new File(projectDir, path) + file.parentFile.mkdirs() + return file + } +} diff --git a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitBuildService.groovy b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitBuildService.groovy new file mode 100644 index 00000000..0cfa6c60 --- /dev/null +++ b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitBuildService.groovy @@ -0,0 +1,41 @@ +package org.ajoberstar.grgit.gradle + +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.services.BuildService +import org.gradle.api.services.BuildServiceParameters + +abstract class GrgitBuildService implements BuildService, AutoCloseable { + + private static final Logger LOGGER = Logging.getLogger(GrgitBuildService.class); + + interface Params extends BuildServiceParameters { + DirectoryProperty getRootDirectory(); + } + + Grgit grgit; + + GrgitBuildService() { + try { + grgit = Grgit.open(currentDir: parameters.rootDirectory.get()) + } catch (Exception e) { + LOGGER.debug("Failed trying to find git repository for ${parameters.rootDirectory.get()}", e) + grgit = null + } + } + + @Delegate + public Grgit lookup() { + return grgit; + } + + @Override + public void close() throws Exception { + if (grgit != null) { + LOGGER.info("Closing Git repo: ${grgit.repository.rootDir}") + grgit.close() + } + } +} diff --git a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitExtension.groovy b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitExtension.groovy new file mode 100644 index 00000000..c844b4e9 --- /dev/null +++ b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitExtension.groovy @@ -0,0 +1,17 @@ +package org.ajoberstar.grgit.gradle + +import org.ajoberstar.grgit.Grgit +import org.gradle.api.provider.Provider + +public class GrgitExtension { + public final Provider grgitBuildServiceProvider; + + public GrgitExtension(Provider grgitBuildServiceProvider) { + this.grgitBuildServiceProvider = grgitBuildServiceProvider + } + + @Delegate + public Grgit lookup() { + return grgitBuildServiceProvider.get().grgit; + } +} diff --git a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitPlugin.groovy b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitPlugin.groovy index aa8a43bb..99acfd7e 100644 --- a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitPlugin.groovy +++ b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitPlugin.groovy @@ -3,6 +3,8 @@ 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 +import org.gradle.util.GradleVersion /** * Plugin adding a {@code grgit} property to all projects @@ -13,24 +15,41 @@ import org.gradle.api.Project class GrgitPlugin implements Plugin { @Override void apply(Project project) { - try { - Grgit grgit = Grgit.open(currentDir: project.rootDir) + if (GradleVersion.current() >= GradleVersion.version("6.1")) { + Provider provider = project.gradle.sharedServices.registerIfAbsent("grgit", GrgitBuildService, { spec -> + spec.parameters.rootDirectory = project.rootDir + }) - // Make sure Git repo is closed when the build is over. Ideally, this avoids issues with the daemon. - project.gradle.buildFinished { - project.logger.info "Closing Git repo: ${grgit.repository.rootDir}" - grgit.close() + if (provider.get().grgit != null) { + project.allprojects { + project.extensions.add(Grgit, 'grgit', provider.get().grgit) + project.extensions.create('grgitExtension', GrgitExtension, provider) + } + } else { + project.allprojects { + project.extensions.add(Grgit, 'grgit', null) + } } + } else { + try { + Grgit grgit = Grgit.open(currentDir: project.rootDir) + + // Make sure Git repo is closed when the build is over. Ideally, this avoids issues with the daemon. + project.gradle.buildFinished { + project.logger.info "Closing Git repo: ${grgit.repository.rootDir}" + grgit.close() + } - project.allprojects { Project prj -> - if (prj.extensions.hasProperty('grgit')) { - prj.logger.warn("Project ${prj.path} already has a grgit property. Remove org.ajoberstar.grgit from either ${prj.path} or ${project.path}.") + project.allprojects { Project prj -> + if (prj.extensions.hasProperty('grgit')) { + prj.logger.warn("Project ${prj.path} already has a grgit property. Remove org.ajoberstar.grgit from either ${prj.path} or ${project.path}.") + } + prj.extensions.add(Grgit, 'grgit', grgit) } - prj.extensions.add(Grgit, 'grgit', grgit) + } catch (Exception e) { + project.logger.debug("Failed trying to find git repository for ${project.path}", e) + project.ext.grgit = null } - } catch (Exception e) { - project.logger.debug("Failed trying to find git repository for ${project.path}", e) - project.ext.grgit = null } } } From 2aba52c87eec9378fbdaf7b8a81bd5191022e8b7 Mon Sep 17 00:00:00 2001 From: Andrew Oberstar Date: Mon, 7 Feb 2022 18:24:32 -0600 Subject: [PATCH 2/2] Introduce the grgit-service plugin 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). --- README.md | 72 ++++++++++++ grgit-gradle/build.gradle.kts | 5 + .../grgit/gradle/ConfigCacheTest.groovy | 101 ---------------- ...st.groovy => GrgitPluginCompatTest.groovy} | 10 +- .../GrgitServicePluginCompatTest.groovy | 109 ++++++++++++++++++ .../grgit/gradle/GrgitBuildService.groovy | 41 ------- .../grgit/gradle/GrgitExtension.groovy | 17 --- .../grgit/gradle/GrgitPlugin.groovy | 55 --------- .../ajoberstar/grgit/gradle/GrgitPlugin.java | 27 +++++ .../ajoberstar/grgit/gradle/GrgitService.java | 58 ++++++++++ .../grgit/gradle/GrgitServiceExtension.java | 19 +++ .../grgit/gradle/GrgitServicePlugin.java | 20 ++++ .../org.ajoberstar.grgit-service.properties | 1 + 13 files changed, 315 insertions(+), 220 deletions(-) delete mode 100644 grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/ConfigCacheTest.groovy rename grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/{BaseCompatTest.groovy => GrgitPluginCompatTest.groovy} (89%) create mode 100644 grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/GrgitServicePluginCompatTest.groovy delete mode 100644 grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitBuildService.groovy delete mode 100644 grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitExtension.groovy delete mode 100644 grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitPlugin.groovy create mode 100644 grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitPlugin.java create mode 100644 grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitService.java create mode 100644 grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitServiceExtension.java create mode 100644 grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitServicePlugin.java create mode 100644 grgit-gradle/src/main/resources/META-INF/gradle-plugins/org.ajoberstar.grgit-service.properties diff --git a/README.md b/README.md index 697afad5..2cf3c714 100644 --- a/README.md +++ b/README.md @@ -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 '' +} + +// 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`. + +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 '' +} + +tasks.register("describe", DescribeTask) { + service = grgitService.service +} + +class DescribeTask extends DefaultTask { + @Input + final Property 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` 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 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) diff --git a/grgit-gradle/build.gradle.kts b/grgit-gradle/build.gradle.kts index c199c781..46dc2fc4 100644 --- a/grgit-gradle/build.gradle.kts +++ b/grgit-gradle/build.gradle.kts @@ -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 diff --git a/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/ConfigCacheTest.groovy b/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/ConfigCacheTest.groovy deleted file mode 100644 index 0625f98c..00000000 --- a/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/ConfigCacheTest.groovy +++ /dev/null @@ -1,101 +0,0 @@ -package org.ajoberstar.grgit.gradle - -import org.ajoberstar.grgit.Grgit -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome -import org.junit.Rule -import org.junit.rules.TemporaryFolder -import spock.lang.Specification - -class ConfigCacheTest extends Specification { - @Rule TemporaryFolder tempDir = new TemporaryFolder() - File projectDir - File buildFile - - def setup() { - projectDir = tempDir.newFolder('project') - buildFile = projectFile('build.gradle') - } - - def "grgit build service can be fetched from registered services"() { - given: - buildFile << """ - import org.ajoberstar.grgit.gradle.GrgitBuildService - - plugins { - id 'org.ajoberstar.grgit' - } - - task doStuff { - def injected = project.gradle.sharedServices.registrations.getByName("grgit").getService() - doLast { - assert injected.get().grgit == null - } - } - """ - - when: - runner() - .withArguments('--configuration-cache', 'doStuff') - .build() - - and: - def result = runner() - .withArguments('--configuration-cache', 'doStuff') - .build() - - then: - result.output.contains('Reusing configuration cache.') - } - - - 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') - - buildFile << '''\ -plugins { - id 'org.ajoberstar.grgit' -} - -task doStuff { - def injected = project.grgitExtension - doLast { - println injected.describe() - } -} -''' - when: - runner() - .withArguments('--configuration-cache', 'doStuff') - .build() - - and: - def result = runner() - .withArguments('--configuration-cache', 'doStuff') - .build() - then: - result.task(':doStuff').outcome == TaskOutcome.SUCCESS - result.output.contains('Reusing configuration cache.') - result.output.contains('1.0.0\n') - } - - private GradleRunner runner(String... args) { - return GradleRunner.create() - .withGradleVersion("6.6-milestone-3") - .withPluginClasspath() - .withProjectDir(projectDir) - .forwardOutput() - .withArguments((args + '--stacktrace') as String[]) - } - - private File projectFile(String path) { - File file = new File(projectDir, path) - file.parentFile.mkdirs() - return file - } -} diff --git a/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/BaseCompatTest.groovy b/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/GrgitPluginCompatTest.groovy similarity index 89% rename from grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/BaseCompatTest.groovy rename to grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/GrgitPluginCompatTest.groovy index 6a631751..82b14e59 100644 --- a/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/BaseCompatTest.groovy +++ b/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/GrgitPluginCompatTest.groovy @@ -1,7 +1,6 @@ package org.ajoberstar.grgit.gradle import spock.lang.Specification -import spock.lang.Unroll import org.ajoberstar.grgit.Grgit import org.gradle.testkit.runner.GradleRunner @@ -9,7 +8,7 @@ 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 @@ -17,7 +16,6 @@ class BaseCompatTest extends Specification { def setup() { projectDir = new File(tempDir, 'project') buildFile = projectFile('build.gradle') - } def 'with no repo, plugin sets grgit to null'() { @@ -34,7 +32,7 @@ task doStuff { } ''' when: - def result = build('doStuff') + def result = build('doStuff', '--configuration-cache') then: result.task(':doStuff').outcome == TaskOutcome.SUCCESS } @@ -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' @@ -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') diff --git a/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/GrgitServicePluginCompatTest.groovy b/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/GrgitServicePluginCompatTest.groovy new file mode 100644 index 00000000..b776dc0c --- /dev/null +++ b/grgit-gradle/src/compatTest/groovy/org/ajoberstar/grgit/gradle/GrgitServicePluginCompatTest.groovy @@ -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 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 + } +} diff --git a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitBuildService.groovy b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitBuildService.groovy deleted file mode 100644 index 0cfa6c60..00000000 --- a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitBuildService.groovy +++ /dev/null @@ -1,41 +0,0 @@ -package org.ajoberstar.grgit.gradle - -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.services.BuildService -import org.gradle.api.services.BuildServiceParameters - -abstract class GrgitBuildService implements BuildService, AutoCloseable { - - private static final Logger LOGGER = Logging.getLogger(GrgitBuildService.class); - - interface Params extends BuildServiceParameters { - DirectoryProperty getRootDirectory(); - } - - Grgit grgit; - - GrgitBuildService() { - try { - grgit = Grgit.open(currentDir: parameters.rootDirectory.get()) - } catch (Exception e) { - LOGGER.debug("Failed trying to find git repository for ${parameters.rootDirectory.get()}", e) - grgit = null - } - } - - @Delegate - public Grgit lookup() { - return grgit; - } - - @Override - public void close() throws Exception { - if (grgit != null) { - LOGGER.info("Closing Git repo: ${grgit.repository.rootDir}") - grgit.close() - } - } -} diff --git a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitExtension.groovy b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitExtension.groovy deleted file mode 100644 index c844b4e9..00000000 --- a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitExtension.groovy +++ /dev/null @@ -1,17 +0,0 @@ -package org.ajoberstar.grgit.gradle - -import org.ajoberstar.grgit.Grgit -import org.gradle.api.provider.Provider - -public class GrgitExtension { - public final Provider grgitBuildServiceProvider; - - public GrgitExtension(Provider grgitBuildServiceProvider) { - this.grgitBuildServiceProvider = grgitBuildServiceProvider - } - - @Delegate - public Grgit lookup() { - return grgitBuildServiceProvider.get().grgit; - } -} diff --git a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitPlugin.groovy b/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitPlugin.groovy deleted file mode 100644 index 99acfd7e..00000000 --- a/grgit-gradle/src/main/groovy/org/ajoberstar/grgit/gradle/GrgitPlugin.groovy +++ /dev/null @@ -1,55 +0,0 @@ -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 -import org.gradle.util.GradleVersion - -/** - * Plugin adding a {@code grgit} property to all projects - * that searches for a Git repo from the project's - * directory. - * @since 2.0.0 - */ -class GrgitPlugin implements Plugin { - @Override - void apply(Project project) { - if (GradleVersion.current() >= GradleVersion.version("6.1")) { - Provider provider = project.gradle.sharedServices.registerIfAbsent("grgit", GrgitBuildService, { spec -> - spec.parameters.rootDirectory = project.rootDir - }) - - if (provider.get().grgit != null) { - project.allprojects { - project.extensions.add(Grgit, 'grgit', provider.get().grgit) - project.extensions.create('grgitExtension', GrgitExtension, provider) - } - } else { - project.allprojects { - project.extensions.add(Grgit, 'grgit', null) - } - } - } else { - try { - Grgit grgit = Grgit.open(currentDir: project.rootDir) - - // Make sure Git repo is closed when the build is over. Ideally, this avoids issues with the daemon. - project.gradle.buildFinished { - project.logger.info "Closing Git repo: ${grgit.repository.rootDir}" - grgit.close() - } - - project.allprojects { Project prj -> - if (prj.extensions.hasProperty('grgit')) { - prj.logger.warn("Project ${prj.path} already has a grgit property. Remove org.ajoberstar.grgit from either ${prj.path} or ${project.path}.") - } - prj.extensions.add(Grgit, 'grgit', grgit) - } - } catch (Exception e) { - project.logger.debug("Failed trying to find git repository for ${project.path}", e) - project.ext.grgit = null - } - } - } -} diff --git a/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitPlugin.java b/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitPlugin.java new file mode 100644 index 00000000..bafe5b40 --- /dev/null +++ b/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitPlugin.java @@ -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 { + @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); + } + } +} diff --git a/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitService.java b/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitService.java new file mode 100644 index 00000000..c10e07d9 --- /dev/null +++ b/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitService.java @@ -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, AutoCloseable { + private static final Logger logger = Logging.getLogger(GrgitService.class); + + public interface Params extends BuildServiceParameters { + DirectoryProperty getCurrentDirectory(); + + DirectoryProperty getDirectory(); + + Property 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(); + } +} diff --git a/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitServiceExtension.java b/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitServiceExtension.java new file mode 100644 index 00000000..315bd89d --- /dev/null +++ b/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitServiceExtension.java @@ -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 service; + + @Inject + public GrgitServiceExtension(ObjectFactory objectFactory) { + this.service = objectFactory.property(GrgitService.class); + } + + public Property getService() { + return service; + } +} diff --git a/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitServicePlugin.java b/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitServicePlugin.java new file mode 100644 index 00000000..ce4c8617 --- /dev/null +++ b/grgit-gradle/src/main/java/org/ajoberstar/grgit/gradle/GrgitServicePlugin.java @@ -0,0 +1,20 @@ +package org.ajoberstar.grgit.gradle; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.provider.Provider; + +public class GrgitServicePlugin implements Plugin { + @Override + public void apply(Project project) { + GrgitServiceExtension extension = project.getExtensions().create("grgitService", GrgitServiceExtension.class); + + Provider serviceProvider = project.getGradle().getSharedServices().registerIfAbsent("grgit", GrgitService.class, spec -> { + spec.getParameters().getCurrentDirectory().set(project.getLayout().getProjectDirectory()); + spec.getParameters().getInitIfNotExists().set(false); + spec.getMaxParallelUsages().set(1); + }); + + extension.getService().set(serviceProvider); + } +} diff --git a/grgit-gradle/src/main/resources/META-INF/gradle-plugins/org.ajoberstar.grgit-service.properties b/grgit-gradle/src/main/resources/META-INF/gradle-plugins/org.ajoberstar.grgit-service.properties new file mode 100644 index 00000000..2497a0ee --- /dev/null +++ b/grgit-gradle/src/main/resources/META-INF/gradle-plugins/org.ajoberstar.grgit-service.properties @@ -0,0 +1 @@ +implementation-class=org.ajoberstar.grgit.gradle.GrgitServicePlugin