Skip to content

Commit

Permalink
Merge pull request #15 from vierbergenlars/update-provider
Browse files Browse the repository at this point in the history
Add ability to register additional CiInformationProviders outside of the ServiceLoader mechanism
  • Loading branch information
vierbergenlars authored Oct 25, 2020
2 parents 9a5a741 + bfa64c1 commit 0f91e35
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 10 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ CiInformation ci = CIInformation.detect(project); // Gives CI detectors access t

# Extending

You can provide extra CI detectors by creating a provider extending [`be.vbgn.gradle.cidetect.provider.CiInformationProvider`](./src/main/java/be/vbgn/gradle/cidetect/provider/CiInformationProvider.java),
[registering it as a service](https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html) and placing your jar on the classpath.
You can provide extra CI detectors by creating a provider extending [`be.vbgn.gradle.cidetect.provider.CiInformationProvider`](./src/main/java/be/vbgn/gradle/cidetect/provider/CiInformationProvider.java).

There are 2 ways to register extra CI detectors:

* Using the ServiceLoader mechanism: [registering it as a service](https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html) and placing your jar on the classpath.
* Calling `CiInformationProvider#registerProvider()` from a separate Gradle plugin that is applied **before** `be.vbgn.ci-detect`.

For examples of providers, you can have a look at [the builtin providers](./src/main/java/be/vbgn/gradle/cidetect/impl).

Expand Down
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ dependencies {
implementation gradleApi()
implementation localGroovy()

compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'

testImplementation gradleTestKit()
testImplementation group: 'junit', name: 'junit', version: '4.13.1'
}
Expand All @@ -48,6 +51,10 @@ pluginBundle {
}
}

test {
forkEvery = 1
}

jacocoTestReport {
dependsOn(test)
reports {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/be/vbgn/gradle/cidetect/CiInformation.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;

final class LoggerHolder {

static final Logger LOGGER = Logging.getLogger(CiInformation.class);

private LoggerHolder() {
}
}

public interface CiInformation {

Expand All @@ -25,15 +35,21 @@ static CiInformation detect() {
*/
@Nonnull
static CiInformation detect(@Nullable Project project) {
LoggerHolder.LOGGER.debug("Performing CI detection for {}", project);
for (CiInformationProvider installedProvider : CiInformationProvider.installedProviders()) {
LoggerHolder.LOGGER.debug("Querying provider {}", installedProvider);
if (installedProvider.isSupported()) {
CiInformation ciInformation = installedProvider.newCiInformation(project);
LoggerHolder.LOGGER.debug("Information from provider {}: {}", installedProvider, ciInformation);
if (ciInformation != null && ciInformation.isCi()) {
LoggerHolder.LOGGER.debug("Using CI information for {}: {}", project, ciInformation);
return ciInformation;
}
}
}

LoggerHolder.LOGGER.debug("No CI information found for {}. Using a null result.", project);

return new NullCiInformation();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ static List<CiInformationProvider> installedProviders() {
return CiInformationProviderImpl.installedProviders();
}

static void registerProvider(Class<? extends CiInformationProvider> clazz) {
CiInformationProviderImpl.registerProvider(clazz);
}

default boolean isSupported() {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import lombok.Synchronized;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;

final class CiInformationProviderImpl {

Expand All @@ -18,24 +23,68 @@ private static final class LazyHolder {
private static final CiInformationProviderImpl INSTANCE = new CiInformationProviderImpl();
}

private final List<CiInformationProvider> installedProviders;
private static final Logger LOGGER = Logging.getLogger(CiInformationProviderImpl.class);

private List<CiInformationProvider> installedProviders;

private CiInformationProviderImpl() {
ServiceLoader<CiInformationProvider> serviceLoader = ServiceLoader.load(CiInformationProvider.class);
}

List<CiInformationProvider> providers = new ArrayList<>();
for (CiInformationProvider ciInformationProvider : serviceLoader) {
providers.add(ciInformationProvider);
}
@Synchronized
private void initializeInstalledProviders() {
if (installedProviders == null) {
LOGGER.debug("Initializing providers for service {}", CiInformationProvider.class);
ServiceLoader<CiInformationProvider> serviceLoader = ServiceLoader.load(CiInformationProvider.class);

List<CiInformationProvider> providers = new ArrayList<>();
for (CiInformationProvider ciInformationProvider : serviceLoader) {
providers.add(ciInformationProvider);
}

providers.sort(Comparator.comparing(CiInformationProvider::getPriority).reversed());
installedProviders = Collections.unmodifiableList(providers);
LOGGER.debug("Found {} providers for service {}", providers.size(), CiInformationProvider.class);

installedProviders = providers;
resortProviders();
}
}

@Synchronized
private List<CiInformationProvider> getInstalledProviders() {
initializeInstalledProviders();
// Prevent additional providers from being installed after installed providers have been read once
LOGGER.debug("Locking providers");
installedProviders = Collections.unmodifiableList(installedProviders);
return installedProviders;
}

private void resortProviders() {
installedProviders.sort(Comparator.comparing(CiInformationProvider::getPriority).reversed());
LOGGER.debug("Providers in query order: {}",
installedProviders.stream().map(CiInformationProvider::getClass).collect(
Collectors.toList()));
}

private void registerAdditionalProvider(Class<? extends CiInformationProvider> clazz) {
initializeInstalledProviders();
LOGGER.debug("Registering additional provider {}", clazz);
if (installedProviders.stream()
.anyMatch(ciInformationProvider -> ciInformationProvider.getClass().equals(clazz))) {
LOGGER.debug("Additional provider {} is already registered, not registering an additional instance", clazz);
return;
}

try {
installedProviders.add(clazz.newInstance());
resortProviders();
LOGGER.debug("Registered additional provider {}", clazz);
} catch (InstantiationException | IllegalAccessException e) {
throw new ServiceConfigurationError("Failed to register a new provider instance", e);
}
}

static void registerProvider(Class<? extends CiInformationProvider> clazz) {
LazyHolder.INSTANCE.registerAdditionalProvider(clazz);
}

static List<CiInformationProvider> installedProviders() {
return LazyHolder.INSTANCE.getInstalledProviders();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package be.vbgn.gradle.cidetect.provider;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import be.vbgn.gradle.cidetect.CiInformation;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.gradle.api.Project;
import org.junit.Test;

public class CiInformationProviderRegisterTest {

static class TestProvider implements CiInformationProvider {

@Override
public int getPriority() {
return 10;
}

@Nullable
@Override
public CiInformation newCiInformation(@Nullable Project project) {
return null;
}
}

@Test
public void testRegisterProvider() {
CiInformationProvider.registerProvider(TestProvider.class);
CiInformationProvider.registerProvider(TestProvider.class);

List<CiInformationProvider> providers = CiInformationProvider.installedProviders();
List<Class<? extends CiInformationProvider>> providerClasses = providers.stream()
.map(CiInformationProvider::getClass).collect(Collectors.toList());

assertSame(TestProvider.class, providerClasses.get(0));

assertEquals("There are no providers registered multiple times", new HashSet<>(providerClasses).size(),
providerClasses.size());
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,33 @@

import static org.junit.Assert.assertTrue;

import be.vbgn.gradle.cidetect.CiInformation;
import javax.annotation.Nullable;
import org.gradle.api.Project;
import org.junit.Test;

public class CiInformationProviderTest {

static class TestProvider implements CiInformationProvider {

@Nullable
@Override
public CiInformation newCiInformation(@Nullable Project project) {
return null;
}
}

@Test
public void testInstalledProviders() {
assertTrue("At least one provider should be available", !CiInformationProvider.installedProviders().isEmpty());
}


@Test(expected = UnsupportedOperationException.class)
public void testRegisterProviderAfterFetching() {
CiInformationProvider.installedProviders();

CiInformationProvider.registerProvider(TestProvider.class);
}

}

0 comments on commit 0f91e35

Please sign in to comment.