Skip to content

Commit

Permalink
Merge pull request #106 from boxheed/develop
Browse files Browse the repository at this point in the history
Auto-generated PR
  • Loading branch information
boxheed authored Dec 17, 2024
2 parents 9a39acb + bdcf8c8 commit 7153681
Show file tree
Hide file tree
Showing 23 changed files with 413 additions and 384 deletions.
106 changes: 57 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,57 @@
[![CircleCI](https://circleci.com/gh/boxheed/gradle-pater-build-plugin/tree/master.svg?style=shield)](https://circleci.com/gh/boxheed/gradle-pater-build-plugin/tree/master)

# Gradle Pater Plugin
This plugin provides parent build script like capabilities to Gradle. Inspired by a number of blog posts about how to work with/around the current capabilities of Gradle to support importing other build scripts. Current strategies use either `apply from:` in conjunction with a URL or recommend writing your own plugin to set different aspects of a Gradle project programmatically. This plugin combines both of these approaches into a single strategy that allows you to write simple gradle build files and take advantage of dependency management.

# How
By packaging your gradle build script up in a jar file and including in your build script dependencies this plugin will find the build script and apply it to the gradle project as if it was referenced in the `apply from:` notation. What's more by taking advantage of jar library dependency management you can chain a number of build scripts together and have them applied to the project. This plugin uses the naming convention of the build scripts to sort the discovered scripts into a determinate order for applying to the project.

# Simple Usage
Create your java library which includes your build script. the pater-build plugin scans for files in a specific location in the `META-INF/pater-build` and must end in `.gradle` e.g. `META-INF/pater-build/opinion-java.gradle`. Then all you have to do is add your library into the buildscripts classpath along with the pater-build plugin e.g.

```
buildscript {
repositories {
jcenter()
}
dependencies {
//The pater build plugin.
classpath 'com.fizzpod:gradle-pater-build-plugin:1.0.0'
//Your library containing the build file in META-INF/pater-build
classpath 'com.example:opinion-java:1.0.0'
}
}
apply plugin: 'com.fizzpod.pater-build'
```

You can of course include the pater-build plugin as a dependency of your build jar, in which case you would only have your build jar as a dependency, I'll leave this up to you to decide whether you want to manage it that way or not.

# Build script ordering
The plugin orders the build scripts that it finds according to the names of the build scripts following a naming convention.
* The .gradle extension is removed.
* The names are separated out by hyphens e.g. `my-build-script.gradle` will become three tokens `[my][build][script]`.
* The tokens from each build script name are compared with scripts with fewer tokens migrating to the top of the list.

It is therefore necessary to apply a naming convention to the build files included to be certain that they are applied in the correct order.

# Limitations
As the library uses classpath scanning if you have more than one build script with the same name it is indeterminate which one will be selected, but it will be only one of them.

# Extension points
The plugin exposes a number of extension points, these are provided by using the Java ServiceLoader to discover implementations of the extension points.

## Build file resolution
You can customise the build file resolution with your own implementation. The interface is `com.fizzpod.gradle.plugins.pater.GradleBuildFileResolver` with the defualt implentation being `com.fizzpod.gradle.plugins.pater.ClasspathGradleBuildFileResolver`. To apply your own implementation you need to add a file to your `META-INF/services/com.fizzpod.gradle.plugins.pater.GradleBuildFileResolver` the contents of which is the implementing classes. Note that if you still want the default implementation to run along with your custom one then you need to specify the default one in this file. The plugin will use all defined GradleBuildFileResolvers to find the build files.

## Build file sorting
you can customise the sorting of the buil files with your own implementation. The interface is `com.fizzpod.gradle.plugins.pater.GradleBuildFileSorter` with the default implementation being `com.fizzpod.gradle.plugins.pater.FileNameGradleBuildFileSorter`. As with the build file resolution you need to specify the implementation in a `META-INF/services/com.fizzpod.gradle.plugins.pater.GradleBuildFileSorter`. The difference with this extension point is that whilst you can specify multiple GradleBuildFileSorters it will only use the first one discovered.
[![CircleCI](https://circleci.com/gh/boxheed/gradle-pater-build-plugin/tree/master.svg?style=shield)](https://circleci.com/gh/boxheed/gradle-pater-build-plugin/tree/master)

# Gradle Pater Plugin

This plugin provides parent build script like capabilities to Gradle. Inspired by a number of blog posts about how to work with/around the current capabilities of Gradle to support importing other build scripts. Current strategies use either `apply from:` in conjunction with a URL or recommend writing your own plugin to set different aspects of a Gradle project programmatically. This plugin combines both of these approaches into a single strategy that allows you to write simple gradle build files and take advantage of dependency management.

# How

By packaging your gradle build script up in a jar file and including in your build script dependencies this plugin will find the build script and apply it to the gradle project as if it was referenced in the `apply from:` notation. What's more by taking advantage of jar library dependency management you can chain a number of build scripts together and have them applied to the project. This plugin uses the naming convention of the build scripts to sort the discovered scripts into a determinate order for applying to the project.

# Simple Usage

Create your java library which includes your build script. the pater-build plugin scans for files in a specific location in the `META-INF/pater-build` and must end in `.gradle` e.g. `META-INF/pater-build/opinion-java.gradle`. Then all you have to do is add your library into the buildscripts classpath along with the pater-build plugin e.g.

```
buildscript {
repositories {
jcenter()
}
dependencies {
//The pater build plugin.
classpath 'com.fizzpod:gradle-pater-build-plugin:1.0.0'
//Your library containing the build file in META-INF/pater-build
classpath 'com.example:opinion-java:1.0.0'
}
}
apply plugin: 'com.fizzpod.pater-build'
```

You can of course include the pater-build plugin as a dependency of your build jar, in which case you would only have your build jar as a dependency, I'll leave this up to you to decide whether you want to manage it that way or not.

# Build script ordering

The plugin orders the build scripts that it finds according to the names of the build scripts following a naming convention.
* The .gradle extension is removed.
* The names are separated out by hyphens e.g. `my-build-script.gradle` will become three tokens `[my][build][script]`.
* The tokens from each build script name are compared with scripts with fewer tokens migrating to the top of the list.

It is therefore necessary to apply a naming convention to the build files included to be certain that they are applied in the correct order.

# Limitations

As the library uses classpath scanning if you have more than one build script with the same name it is indeterminate which one will be selected, but it will be only one of them.

# Extension points

The plugin exposes a number of extension points, these are provided by using the Java ServiceLoader to discover implementations of the extension points.

## Build file resolution

You can customise the build file resolution with your own implementation. The interface is `com.fizzpod.gradle.plugins.pater.GradleBuildFileResolver` with the defualt implentation being `com.fizzpod.gradle.plugins.pater.ClasspathGradleBuildFileResolver`. To apply your own implementation you need to add a file to your `META-INF/services/com.fizzpod.gradle.plugins.pater.GradleBuildFileResolver` the contents of which is the implementing classes. Note that if you still want the default implementation to run along with your custom one then you need to specify the default one in this file. The plugin will use all defined GradleBuildFileResolvers to find the build files.

## Build file sorting

you can customise the sorting of the buil files with your own implementation. The interface is `com.fizzpod.gradle.plugins.pater.GradleBuildFileSorter` with the default implementation being `com.fizzpod.gradle.plugins.pater.FileNameGradleBuildFileSorter`. As with the build file resolution you need to specify the implementation in a `META-INF/services/com.fizzpod.gradle.plugins.pater.GradleBuildFileSorter`. The difference with this extension point is that whilst you can specify multiple GradleBuildFileSorters it will only use the first one discovered.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
}
}
dependencies {
classpath ('com.fizzpod:gradle-plugin-opinion:20.1.0')
classpath ('com.fizzpod:gradle-plugin-opinion:21.0.0')
}
}

Expand Down
1 change: 1 addition & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ assert_lefthook_installed: true
pre-commit:
follow: true
commands:
spotless: {glob: '*', run: ./gradlew spotlessCheck}
test: {glob: '*.{groovy,java}', run: ./gradlew test}
commit-msg:
follow: true
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ include 'services:webservice'

plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0'
id "com.gradle.develocity" version "3.18.2"
id "com.gradle.develocity" version "3.19"
}

rootProject.name = 'gradle-pater-build-plugin'
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.fizzpod.gradle.plugins.pater;
/* (C) 2024 */
/* SPDX-License-Identifier: Apache-2.0 */
package com.fizzpod.gradle.plugins.pater

import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern

import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.IOUtils
Expand All @@ -15,54 +16,53 @@ import org.slf4j.LoggerFactory


public class ClasspathGradleBuildFileResolver implements GradleBuildFileResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(ClasspathGradleBuildFileResolver.class);

private static final Logger LOGGER = LoggerFactory.getLogger(ClasspathGradleBuildFileResolver.class)

Collection<GradleBuildFile> findBuildFiles(Project project) {
Set<String> buildFiles = scanForBuildFiles();
LOGGER.info("Discovered build files: {}", buildFiles);
return exportBuildFiles(buildFiles);
Set<String> buildFiles = scanForBuildFiles()
LOGGER.info("Discovered build files: {}", buildFiles)
return exportBuildFiles(buildFiles)
}

private Set<String> scanForBuildFiles(Project project) {
Reflections reflections = new Reflections("META-INF.pater-build", new ResourcesScanner());

Reflections reflections = new Reflections("META-INF.pater-build", new ResourcesScanner())

Set<String> buildFiles =
reflections.getResources(Pattern.compile(".*\\.gradle"));
return buildFiles;
reflections.getResources(Pattern.compile(".*\\.gradle"))
return buildFiles
}

private Collection<GradleBuildFile> exportBuildFiles(Collection<String> classpathBuildFiles) {
List<GradleBuildFile> buildFiles = new LinkedList<>();
List<GradleBuildFile> buildFiles = new LinkedList<>()
for(String classpathBuildFile: classpathBuildFiles) {
File buildFile = exportBuildFile(classpathBuildFile);
File buildFile = exportBuildFile(classpathBuildFile)
if(buildFile != null && buildFile.exists()) {
GradleBuildFile gradleBuildFile = new UriGradleBuildFile(buildFile.toPath().toUri());
buildFiles.add(gradleBuildFile);
GradleBuildFile gradleBuildFile = new UriGradleBuildFile(buildFile.toPath().toUri())
buildFiles.add(gradleBuildFile)
} else {
LOGGER.warn("Could not export build file {}", classpathBuildFile);
LOGGER.warn("Could not export build file {}", classpathBuildFile)
}
}
return buildFiles;
return buildFiles
}

private File exportBuildFile(String classpathBuildFile) {
File tempDirectoryFile = Files.createTempDirectory("pater-build").toFile();
FileUtils.forceDeleteOnExit(tempDirectoryFile);
File buildFile = new File(tempDirectoryFile, FilenameUtils.getName(classpathBuildFile));
InputStream inputStream = null;
OutputStream outputStream = null;
File tempDirectoryFile = Files.createTempDirectory("pater-build").toFile()
FileUtils.forceDeleteOnExit(tempDirectoryFile)
File buildFile = new File(tempDirectoryFile, FilenameUtils.getName(classpathBuildFile))
InputStream inputStream = null
OutputStream outputStream = null
try {
inputStream = this.getClass().getClassLoader().getResourceAsStream(classpathBuildFile);
outputStream = new FileOutputStream(buildFile);
IOUtils.copy(inputStream, outputStream);
FileUtils.forceDeleteOnExit(buildFile);
inputStream = this.getClass().getClassLoader().getResourceAsStream(classpathBuildFile)
outputStream = new FileOutputStream(buildFile)
IOUtils.copy(inputStream, outputStream)
FileUtils.forceDeleteOnExit(buildFile)
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
IOUtils.closeQuietly(inputStream)
IOUtils.closeQuietly(outputStream)
}
return buildFile;
return buildFile
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.fizzpod.gradle.plugins.pater;
/* (C) 2024 */
/* SPDX-License-Identifier: Apache-2.0 */
package com.fizzpod.gradle.plugins.pater

import java.net.URI;
import java.util.Collection;
import java.net.URI
import java.util.Collection
import java.util.regex.Pattern

import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Project
Expand All @@ -14,40 +15,40 @@ import org.slf4j.LoggerFactory

public class FileNameGradleBuildFileSorter implements GradleBuildFileSorter {

private static final Logger LOGGER = LoggerFactory.getLogger(FileNameGradleBuildFileSorter.class);

@Override
public Collection<GradleBuildFile> sortBuildFiles(Project project, Collection<GradleBuildFile> buildFiles) {
List<GradleBuildFile> buildFileList = new LinkedList<>(buildFiles);
Collections.sort(buildFileList, new NameSplittingComaparator());
return buildFileList;
}

private static final class NameSplittingComaparator implements Comparator<GradleBuildFile> {

@Override
public int compare(GradleBuildFile buildFile1, GradleBuildFile buildFile2) {
int comparison = 0;
String[] parts1 = getNameParts(buildFile1.getName());
String[] parts2 = getNameParts(buildFile2.getName());
if(parts1.length == parts2.length) {
comparison = compareParts(parts1, parts2);
} else {
comparison = parts1.length - parts2.length;
}
return comparison;
}

private String[] getNameParts(String name) {
return name.split("-");
}

private int compareParts(String[] parts1, String[] parts2) {
int comparison = 0;
for(int i = 0; i < parts1.length && comparison == 0; i++) {
comparison = parts1[i].toLowerCase().compareTo(parts2[i].toLowerCase());
}
return comparison;
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(FileNameGradleBuildFileSorter.class)

@Override
public Collection<GradleBuildFile> sortBuildFiles(Project project, Collection<GradleBuildFile> buildFiles) {
List<GradleBuildFile> buildFileList = new LinkedList<>(buildFiles)
Collections.sort(buildFileList, new NameSplittingComaparator())
return buildFileList
}

private static final class NameSplittingComaparator implements Comparator<GradleBuildFile> {

@Override
public int compare(GradleBuildFile buildFile1, GradleBuildFile buildFile2) {
int comparison = 0
String[] parts1 = getNameParts(buildFile1.getName())
String[] parts2 = getNameParts(buildFile2.getName())
if(parts1.length == parts2.length) {
comparison = compareParts(parts1, parts2)
} else {
comparison = parts1.length - parts2.length
}
return comparison
}

private String[] getNameParts(String name) {
return name.split("-")
}

private int compareParts(String[] parts1, String[] parts2) {
int comparison = 0
for(int i = 0; i < parts1.length && comparison == 0; i++) {
comparison = parts1[i].toLowerCase().compareTo(parts2[i].toLowerCase())
}
return comparison
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.fizzpod.gradle.plugins.pater;
/* (C) 2024 */
/* SPDX-License-Identifier: Apache-2.0 */
package com.fizzpod.gradle.plugins.pater

import org.gradle.api.Project

public interface GradleBuildFile {

String getName();

void apply(Project project);

String getName()

void apply(Project project)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.fizzpod.gradle.plugins.pater;
/* (C) 2024 */
/* SPDX-License-Identifier: Apache-2.0 */
package com.fizzpod.gradle.plugins.pater

import org.gradle.api.Project

public interface GradleBuildFileResolver {

Collection<GradleBuildFile> findBuildFiles(Project project);

Collection<GradleBuildFile> findBuildFiles(Project project)
}
Loading

0 comments on commit 7153681

Please sign in to comment.