diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..4a5492f
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# EditorConfig is awesome: https://editorconfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+# 4 space indentation
+[*.java]
+indent_style = space
+indent_size = 2
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
index 34b1256..ea2eccc 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,15 @@
+#
+# https://help.github.com/articles/dealing-with-line-endings/
+#
+# Linux start script should use lf
+/gradlew        text eol=lf
+
+# These are Windows script files and should use crlf
+*.bat           text eol=crlf
+
+# Binary files should be left untouched
+*.jar           binary
+
 .gitattributes export-ignore
 .gitignore export-ignore
 circle.yml export-ignore
diff --git a/README.md b/README.md
index fa3f9aa..cfb3a86 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,12 @@
 
 A Gradle plugin, similar to the groovy and scala plugins for Gradle.
 
-Specifically, this adds compileGosu and compileTestGosu tasks. These tasks are dependent on the compileJava and compileTestJava tasks, respectively.
+Specifically, this adds `compileGosu` and `compileTestGosu` tasks. These tasks are dependent on the `compileJava` and `compileTestJava` tasks, respectively.  A `gosudoc` task is also created.
 
 Java 8 is required, as is Gosu version 1.13.9+ or 1.14.2+. Gosu 1.14 and 1.14.1 are not supported.
 
+As of this release, the plugin supports Gradle's [Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html).  However, **the plugin now requires Gradle 8.8 or higher**.
+
 Build status: [![Circle CI](https://circleci.com/gh/gosu-lang/gradle-gosu-plugin/tree/main.svg?style=svg)](https://circleci.com/gh/gosu-lang/gradle-gosu-plugin/tree/main)
 
 ## Why would I use this?
@@ -36,6 +38,27 @@ The latest release version, and instructions to apply it, is available here: htt
 
 It is now necessary to create a single compile-time dependency on the gosu-core-api JAR. Runtime and other dependencies are automatically inferred and applied by the plugin.
 
+Example minimal project configuration (groovy build script):
+```groovy
+plugins {
+    id 'org.gosu-lang.gosu' version '8.1.3'
+}
+
+java {
+    toolchain {
+        it.languageVersion = JavaLanguageVersion.of 11
+    }
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation 'org.gosu-lang.gosu:gosu-core-api:1.17.8'
+}
+```
+
 Additionally, snapshots are available from https://oss.sonatype.org/content/repositories/snapshots/org/gosu-lang/gosu/gradle-gosu-plugin/
 
 ## Configurable Options
diff --git a/build.gradle b/build.gradle
index b55741a..2136727 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,4 +1,6 @@
 import org.gosulang.gradle.build.VersionWriterTask
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+
 import java.nio.charset.StandardCharsets
 
 plugins {
@@ -92,7 +94,7 @@ dependencies {
 test {
   useJUnitPlatform()
   testLogging {
-      events 'passed', 'failed', 'skipped'
+      events TestLogEvent.PASSED, TestLogEvent.FAILED, TestLogEvent.SKIPPED
   }
   environment('JAVA_TOOL_OPTIONS', '-Duser.language=en')
 }
@@ -170,4 +172,4 @@ publishing {
             }
         }
     }    
-}
\ No newline at end of file
+}
diff --git a/gradle.properties b/gradle.properties
index 5a0acbb..8756a49 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,7 +2,7 @@ group=org.gosu-lang.gosu
 name=gradle-gosu-plugin
 version=8.1.3-SNAPSHOT
 
-gradleVersion=8.6
+gradleVersion=8.8
 org.gradle.caching=false
 org.gradle.daemon=false
 org.gradle.jvmargs=-Xms1g -Xmx2g -Dfile.encoding=UTF-8
@@ -10,7 +10,7 @@ org.gradle.parallel=false
 org.gradle.workers.max=1
 
 gosuVersion=1.17.8
-testedVersions=8.2,8.10.1
+testedVersions=8.10.1
 
 # Gradle plugins
 gradlePublishPluginVersion=1.3.0
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index d64cd49..e644113 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2ea3535..6f7a6eb 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index 1aa94a4..b740cf1 100755
--- a/gradlew
+++ b/gradlew
@@ -55,7 +55,7 @@
 #       Darwin, MinGW, and NonStop.
 #
 #   (3) This script is generated from the Groovy template
-#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 #       within the Gradle project.
 #
 #       You can find Gradle at https://github.com/gradle/gradle/.
diff --git a/gradlew.bat b/gradlew.bat
index 7101f8e..25da30d 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,92 +1,92 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem      https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%"=="" @echo off
-@rem ##########################################################################
-@rem
-@rem  Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%"=="" set DIRNAME=.
-@rem This is normally unused
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if %ERRORLEVEL% equ 0 goto execute
-
-echo. 1>&2
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo. 1>&2
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/org/gosulang/gradle/GosuBasePlugin.java b/src/main/java/org/gosulang/gradle/GosuBasePlugin.java
index 65e56c6..9e62868 100644
--- a/src/main/java/org/gosulang/gradle/GosuBasePlugin.java
+++ b/src/main/java/org/gosulang/gradle/GosuBasePlugin.java
@@ -1,18 +1,18 @@
 package org.gosulang.gradle;
 
 
-import org.codehaus.groovy.runtime.InvokerHelper;
 import org.gosulang.gradle.tasks.DefaultGosuSourceSet;
 import org.gosulang.gradle.tasks.GosuRuntime;
-import org.gosulang.gradle.tasks.GosuSourceSet;
+import org.gosulang.gradle.tasks.GosuSourceDirectorySet;
 import org.gosulang.gradle.tasks.compile.GosuCompile;
 import org.gosulang.gradle.tasks.gosudoc.GosuDoc;
 import org.gradle.api.Plugin;
 import org.gradle.api.Project;
+import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.tasks.DefaultSourceSetOutput;
 import org.gradle.api.model.ObjectFactory;
-import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.JavaBasePlugin;
 import org.gradle.api.reporting.ReportingExtension;
 import org.gradle.api.tasks.SourceSet;
@@ -21,84 +21,92 @@
 import org.gradle.internal.Cast;
 
 import javax.inject.Inject;
-import java.io.File;
+import java.util.concurrent.Callable;
 
 import static org.gosulang.gradle.tasks.Util.javaPluginExtension;
+import static org.gradle.api.internal.lambdas.SerializableLambdas.spec;
 
-public class GosuBasePlugin implements Plugin<Project> {
+public abstract class GosuBasePlugin implements Plugin<Project> {
   public static final String GOSU_RUNTIME_EXTENSION_NAME = "gosuRuntime";
 
-  private final ObjectFactory _objectFactory;
-
-  private Project _project;
-  private GosuRuntime _gosuRuntime;
-
   @Inject
-  GosuBasePlugin(ObjectFactory objectFactory){
-  _objectFactory = objectFactory;
-  }
+  public abstract ObjectFactory getObjectFactory();
 
   @Override
   public void apply(Project project) {
-    _project = project;
-    _project.getPluginManager().apply(JavaBasePlugin.class);
+    project.getPluginManager().apply(JavaBasePlugin.class);
 
-    configureGosuRuntimeExtension();
-    configureCompileDefaults();
-    configureSourceSetDefaults();
-    configureGosuDoc();
-  }
-
-  private void configureGosuRuntimeExtension() {
-    _gosuRuntime = _project.getExtensions().create(GOSU_RUNTIME_EXTENSION_NAME, GosuRuntime.class);
+    GosuRuntime gosuRuntime = project.getExtensions().create(GOSU_RUNTIME_EXTENSION_NAME, GosuRuntime.class);
+    configureCompileDefaults(project, gosuRuntime);
+    configureSourceSetDefaults(project);
+    configureGosuDoc(project, gosuRuntime);
   }
 
   /**
    * Sets the gosuClasspath property for all GosuCompile tasks: compileGosu and compileTestGosu
    */
-  private void configureCompileDefaults() {
-    _project.getTasks().withType(GosuCompile.class, gosuCompile ->
-        gosuCompile.getConventionMapping().map("gosuClasspath", () -> _gosuRuntime.inferGosuClasspath(gosuCompile.getClasspath())));
+  private static void configureCompileDefaults(Project project, GosuRuntime gosuRuntime) {
+    project.getTasks().withType(GosuCompile.class).configureEach(gosuCompile -> {
+        gosuCompile.getGosuClasspath().convention((Callable<FileCollection>) () -> gosuRuntime.inferGosuClasspath(gosuCompile.getClasspath()));
+    });
   }
 
- private void configureSourceSetDefaults() {
-     javaPluginExtension(_project).getSourceSets().all(sourceSet -> {
-      DefaultGosuSourceSet gosuSourceSet = new DefaultGosuSourceSet(sourceSet.getName(), _objectFactory);
-     //have to be revisit to avoid using the covention here
-      Convention sourceSetConvention = (Convention) InvokerHelper.getProperty(sourceSet, "convention");
-      sourceSetConvention.getPlugins().put("gosu", gosuSourceSet);
-  //    sourceSet.getExtensions().add(SourceDirectorySet.class, "gosu", gosuSourceSet.getGosu()); //alternative but it's not working
-      gosuSourceSet.getGosu().srcDir("src/" + sourceSet.getName() + "/gosu");
-      sourceSet.getResources().getFilter().exclude(element -> gosuSourceSet.getGosu().contains(element.getFile()));
-      sourceSet.getAllSource().source(gosuSourceSet.getGosu());
-      configureGosuCompile(sourceSet, gosuSourceSet);
+ private void configureSourceSetDefaults(Project project) {
+     javaPluginExtension(project).getSourceSets().all(sourceSet -> {
+
+         GosuSourceDirectorySet gosuSource = getGosuSourceDirectorySet(sourceSet);
+         sourceSet.getExtensions().add(GosuSourceDirectorySet.class, "gosu", gosuSource);
+         gosuSource.srcDir("src/" + sourceSet.getName() + "/gosu");
+
+         // Explicitly capture only a FileCollection in the lambda below for compatibility with configuration-cache.
+         final FileCollection gosuSourceFiles = gosuSource;
+         sourceSet.getResources().getFilter().exclude(
+             spec(element -> gosuSourceFiles.contains(element.getFile()))
+         );
+         sourceSet.getAllSource().source(gosuSource);
+      configureGosuCompile(project, sourceSet, gosuSource);
     });
   }
 
+  private GosuSourceDirectorySet getGosuSourceDirectorySet(SourceSet sourceSet) {
+      final DefaultGosuSourceSet gosuSourceSet = getObjectFactory().newInstance(DefaultGosuSourceSet.class, sourceSet.getName());
+
+      // TODO remove in Gradle 9.0?
+      new DslObject(sourceSet).getConvention().getPlugins().put("gosu", gosuSourceSet);
+
+      return gosuSourceSet.getGosu();
+  }
+
   /**
    * Create and configure default compileGosu and compileTestGosu tasks
    * Gradle 4.0+: call local equivalent of o.g.a.p.i.SourceSetUtil.configureForSourceSet(sourceSet, gosuSourceSet.getGosu(), gosuCompile, _project)
    * Gradle 2.x, 3.x: call javaPlugin.configureForSourceSet(sourceSet, gosuCompile);
    */
-  private void configureGosuCompile(SourceSet sourceSet, GosuSourceSet gosuSourceSet) {
+  private void configureGosuCompile(Project project, SourceSet sourceSet, GosuSourceDirectorySet gosuSourceSet) {
     String compileTaskName = sourceSet.getCompileTaskName("gosu");
-    TaskProvider<? extends AbstractCompile> gosuCompile = _project.getTasks().register(compileTaskName, GosuCompile.class);
-    configureForSourceSet(sourceSet, gosuSourceSet.getGosu(), gosuCompile, _project);
+    TaskProvider<GosuCompile> gosuCompile = project.getTasks().register(compileTaskName, GosuCompile.class);
+    configureForSourceSet(sourceSet, gosuSourceSet, gosuCompile, project);
     gosuCompile.configure(t -> t.dependsOn(sourceSet.getCompileJavaTaskName()));
-    gosuCompile.configure(t -> t.setSource((Object) gosuSourceSet.getGosu())); // Gradle 4.0 overloads setSource; must upcast to Object for backwards compatibility
-    _project.getTasks().getByName(sourceSet.getClassesTaskName()).dependsOn(compileTaskName);
+    gosuCompile.configure(t -> t.setSource((Object) gosuSourceSet)); // Gradle 4.0 overloads setSource; must upcast to Object for backwards compatibility
+    gosuCompile.configure(t -> {
+        t.getProjectName().set(t.getProject().getName());
+        t.getProjectDir().set(t.getProject().getLayout().getProjectDirectory());
+    });
+    project.getTasks().named(sourceSet.getClassesTaskName()).configure(task -> task.dependsOn(compileTaskName));
   }
 
-  private void configureGosuDoc() {
-    _project.getTasks().withType(GosuDoc.class, gosudoc -> {
-      gosudoc.getConventionMapping().map("gosuClasspath", () -> _gosuRuntime.inferGosuClasspath(gosudoc.getClasspath()));
-      gosudoc.getConventionMapping().map("destinationDir", () -> new File(javaPluginExtension(_project).getDocsDir().get().getAsFile(), "gosudoc"));
-      gosudoc.getConventionMapping().map("title", () -> _project.getExtensions().getByType(ReportingExtension.class).getApiDocTitle());
-      //gosudoc.getConventionMapping().map("windowTitle", (Callable<Object>) () -> _project.getExtensions().getByType(ReportingExtension.class).getApiDocTitle());
+  private static void configureGosuDoc(Project project, GosuRuntime gosuRuntime) {
+    project.getTasks().withType(GosuDoc.class, gosudoc -> {
+      gosudoc.getProjectName().set(project.getName());
+      gosudoc.getProjectDir().set(project.getLayout().getProjectDirectory());
+      gosudoc.getBuildDir().set(project.getLayout().getBuildDirectory());
+      gosudoc.getGosuClasspath().convention((Callable<FileCollection>) () -> gosuRuntime.inferGosuClasspath(gosudoc.getClasspath()));
+      gosudoc.getDestinationDir().convention(javaPluginExtension(project).getDocsDir().dir("gosudoc"));
+      gosudoc.getTitle().convention(project.getExtensions().getByType(ReportingExtension.class).getApiDocTitle());
     });
   }
 
-  private static void configureForSourceSet(final SourceSet sourceSet, final SourceDirectorySet sourceDirectorySet, TaskProvider<? extends AbstractCompile> compile, final Project target) {
+  private static void configureForSourceSet(final SourceSet sourceSet, final GosuSourceDirectorySet sourceDirectorySet, TaskProvider<? extends AbstractCompile> compile, final Project target) {
     compile.configure(t -> {
       t.setDescription("Compiles the " + sourceDirectorySet.getDisplayName() + ".");
       t.setSource(sourceSet.getJava());
diff --git a/src/main/java/org/gosulang/gradle/GosuPlugin.java b/src/main/java/org/gosulang/gradle/GosuPlugin.java
index a9fb857..a5a2bee 100644
--- a/src/main/java/org/gosulang/gradle/GosuPlugin.java
+++ b/src/main/java/org/gosulang/gradle/GosuPlugin.java
@@ -1,20 +1,21 @@
 package org.gosulang.gradle;
 
-import org.codehaus.groovy.runtime.InvokerHelper;
 import org.gosulang.gradle.tasks.GosuRuntime;
-import org.gosulang.gradle.tasks.GosuSourceSet;
+import org.gosulang.gradle.tasks.GosuSourceDirectorySet;
 import org.gosulang.gradle.tasks.gosudoc.GosuDoc;
 import org.gradle.api.Plugin;
 import org.gradle.api.Project;
-import org.gradle.api.plugins.Convention;
+import org.gradle.api.file.SourceDirectorySet;
 import org.gradle.api.plugins.JavaBasePlugin;
 import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.internal.JavaPluginHelper;
+import org.gradle.api.plugins.jvm.internal.JvmFeatureInternal;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.TaskProvider;
 
 import static org.gosulang.gradle.tasks.Util.javaPluginExtension;
 
-public class GosuPlugin implements Plugin<Project> {
+public abstract class GosuPlugin implements Plugin<Project> {
 
   @SuppressWarnings("WeakerAccess")
   public static final String GOSUDOC_TASK_NAME = "gosudoc";
@@ -30,34 +31,30 @@ public void apply(Project project) {
   /**
    * Ensures that the runtime dependency on gosu-core is included the testRuntime's classpath
    */
-  private void refreshTestRuntimeClasspath( final Project project ) {
-
+  private void refreshTestRuntimeClasspath(final Project project) {
     GosuRuntime gosuRuntime = project.getExtensions().getByType(GosuRuntime.class);
 
     SourceSet main = javaPluginExtension(project).getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
     SourceSet test = javaPluginExtension(project).getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME);
 
     test.setRuntimeClasspath(project.files(
-        test.getOutput(),
-        main.getOutput(),
-        project.getConfigurations().getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME),
-        gosuRuntime.inferGosuClasspath(project.getConfigurations().getByName(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME))));
+      test.getOutput(),
+      main.getOutput(),
+      project.getConfigurations().getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME),
+      gosuRuntime.inferGosuClasspath(project.getConfigurations().getByName(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME))));
   }
 
- private void configureGosuDoc( final Project project ) {
-     SourceSet mainSourceSet = javaPluginExtension(project).getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
-     Convention sourceSetConvention = (Convention) InvokerHelper.getProperty(mainSourceSet, "convention");
-     GosuSourceSet gosuSourceSet = sourceSetConvention.getPlugin(GosuSourceSet.class);
-
-     TaskProvider<GosuDoc> gosuDoc = project.getTasks().register(GOSUDOC_TASK_NAME, GosuDoc.class, t -> {
-        t.setDescription("Generates Gosudoc API documentation for the main source code.");
-        t.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
-        // JvmFeatureInternal mainFeature = JavaPluginHelper.getJavaComponent(project).getMainFeature();//alternative approach but needs to be tested
-        // gosuDoc.setClasspath(mainFeature.getSourceSet().getOutput().plus(mainFeature.getSourceSet().getCompileClasspath()));
-        t.setClasspath(mainSourceSet.getOutput().plus(mainSourceSet.getCompileClasspath()));
-        t.setSource(gosuSourceSet.getGosu());
+  private void configureGosuDoc(final Project project) {
+    TaskProvider<GosuDoc> gosuDoc = project.getTasks().register(GOSUDOC_TASK_NAME, GosuDoc.class, t -> {
+      t.setDescription("Generates Gosudoc API documentation for the main source code.");
+      t.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
+
+      JvmFeatureInternal mainFeature = JavaPluginHelper.getJavaComponent(project).getMainFeature(); //alternative approach but needs to be tested
+      t.getClasspath().from(mainFeature.getSourceSet().getOutput().plus(mainFeature.getSourceSet().getCompileClasspath()));
+
+      SourceDirectorySet gosuSourceSet = mainFeature.getSourceSet().getExtensions().getByType(GosuSourceDirectorySet.class);
+      t.setSource(gosuSourceSet);
     });
   }
 
-
 }
diff --git a/src/main/java/org/gosulang/gradle/tasks/DefaultGosuSourceDirectorySet.java b/src/main/java/org/gosulang/gradle/tasks/DefaultGosuSourceDirectorySet.java
new file mode 100644
index 0000000..fe1a125
--- /dev/null
+++ b/src/main/java/org/gosulang/gradle/tasks/DefaultGosuSourceDirectorySet.java
@@ -0,0 +1,15 @@
+package org.gosulang.gradle.tasks;
+
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.tasks.TaskDependencyFactory;
+
+import javax.inject.Inject;
+
+public abstract class DefaultGosuSourceDirectorySet extends DefaultSourceDirectorySet implements GosuSourceDirectorySet {
+
+  @Inject
+  public DefaultGosuSourceDirectorySet(SourceDirectorySet sourceDirectorySet, TaskDependencyFactory taskDependencyFactory) {
+    super(sourceDirectorySet, taskDependencyFactory);
+  }
+}
diff --git a/src/main/java/org/gosulang/gradle/tasks/DefaultGosuSourceSet.java b/src/main/java/org/gosulang/gradle/tasks/DefaultGosuSourceSet.java
index baf8f0e..314eafa 100644
--- a/src/main/java/org/gosulang/gradle/tasks/DefaultGosuSourceSet.java
+++ b/src/main/java/org/gosulang/gradle/tasks/DefaultGosuSourceSet.java
@@ -1,36 +1,41 @@
 package org.gosulang.gradle.tasks;
 
-import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.file.SourceDirectorySet;
 import org.gradle.api.model.ObjectFactory;
-import org.gradle.api.tasks.SourceSet;
-import org.gradle.util.ConfigureUtil;
+import org.gradle.api.reflect.HasPublicType;
+import org.gradle.api.reflect.TypeOf;
 import org.gradle.util.GUtil;
 
+import javax.inject.Inject;
 import java.util.Arrays;
 import java.util.List;
 
-public class DefaultGosuSourceSet implements GosuSourceSet {
+import static org.gradle.api.reflect.TypeOf.typeOf;
 
-  private final SourceDirectorySet _gosu;
+@SuppressWarnings("DeprecatedIsStillUsed")
+@Deprecated
+public abstract class DefaultGosuSourceSet implements GosuSourceSet, HasPublicType {
+
+  private final GosuSourceDirectorySet _gosu;
   private final SourceDirectorySet _allGosu;
 
   private static final List<String> _gosuAndJavaExtensions = Arrays.asList("**/*.java", "**/*.gs", "**/*.gsx", "**/*.gst", "**/*.gsp");
   private static final List<String> _gosuExtensionsOnly = _gosuAndJavaExtensions.subList(1, _gosuAndJavaExtensions.size());
 
   private final String name;
-  private final String baseName;
   private final String displayName;
 
-  public DefaultGosuSourceSet( String name, ObjectFactory objectFacotry ) {
+  @Inject
+  public abstract ObjectFactory getObjectFactory();
 
+  @Inject
+  public DefaultGosuSourceSet(String name) {
     this.name = name;
-    this.baseName = name.equals(SourceSet.MAIN_SOURCE_SET_NAME) ? "" : name.toUpperCase();
     displayName = GUtil.toWords(this.name);
-    _gosu = objectFacotry.sourceDirectorySet("gosu", displayName + " Gosu source");
+    _gosu = getObjectFactory().newInstance(DefaultGosuSourceDirectorySet.class, getObjectFactory().sourceDirectorySet("gosu", displayName + " Gosu source"));
     _gosu.getFilter().include(_gosuAndJavaExtensions);
-    _allGosu = objectFacotry.sourceDirectorySet("gosu", displayName + " Gosu source");
+    _allGosu = getObjectFactory().sourceDirectorySet("gosu", displayName + " Gosu source");
     _allGosu.getFilter().include(_gosuExtensionsOnly);
     _allGosu.source(_gosu);
   }
@@ -49,18 +54,12 @@ public String getDisplayName() {
   }
 
   @Override
-  public SourceDirectorySet getGosu() {
+  public GosuSourceDirectorySet getGosu() {
     return _gosu;
   }
 
   @Override
-  public GosuSourceSet gosu( Closure configureClosure ) {
-    ConfigureUtil.configure(configureClosure, getGosu());
-    return this;
-  }
-
-  @Override
-  public GosuSourceSet gosu( Action<? super SourceDirectorySet> configureAction) {
+  public GosuSourceSet gosu(Action<? super SourceDirectorySet> configureAction) {
     configureAction.execute(getGosu());
     return this;
   }
@@ -69,4 +68,9 @@ public GosuSourceSet gosu( Action<? super SourceDirectorySet> configureAction) {
   public SourceDirectorySet getAllGosu() {
     return _allGosu;
   }
+
+  @Override
+  public TypeOf<?> getPublicType() {
+    return typeOf(org.gosulang.gradle.tasks.GosuSourceSet.class);
+  }
 }
diff --git a/src/main/java/org/gosulang/gradle/tasks/GosuRuntime.java b/src/main/java/org/gosulang/gradle/tasks/GosuRuntime.java
index d4a7fdb..021905d 100644
--- a/src/main/java/org/gosulang/gradle/tasks/GosuRuntime.java
+++ b/src/main/java/org/gosulang/gradle/tasks/GosuRuntime.java
@@ -1,31 +1,35 @@
 package org.gosulang.gradle.tasks;
 
-import groovy.lang.Closure;
 import org.gradle.api.Buildable;
 import org.gradle.api.GradleException;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.file.ConfigurableFileCollection;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.FailingFileCollection;
+import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.plugins.jvm.internal.JvmPluginServices;
 import org.gradle.util.VersionNumber;
 
 import javax.annotation.Nullable;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Callable;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class GosuRuntime {
+public abstract class GosuRuntime {
 
   private static final Pattern GOSU_JAR_PATTERN = Pattern.compile("gosu-(\\w.*?)-(\\d.*).jar");
   private static final String LF = System.lineSeparator();
 
   private final Project _project;
+  private final JvmPluginServices _jvmPluginServices;
 
   public GosuRuntime(Project project) {
     _project = project;
+    _jvmPluginServices = ((ProjectInternal) project).getServices().get(JvmPluginServices.class);
   }
 
   /**
@@ -37,64 +41,66 @@ public GosuRuntime(Project project) {
    * @param classpath a classpath containing a 'gosu-core-api' Jar
    * @return a classpath containing a corresponding 'gosu-doc' Jar and its dependencies
    */
-  public Closure<FileCollection> inferGosuClasspath(final Iterable<File> classpath) {
-    return new Closure<FileCollection>(this, this) {
-      private FileCollection resolved;
-
-      public FileCollection doCall(Object ignore) {
-        ConfigurableFileCollection fileCollection = _project.files((Callable<FileCollection>) () -> {
-          if (resolved == null) {
-            resolved = doInfer(classpath);
-          }
-          return resolved;
-        });
-        if (classpath instanceof Buildable) {
-          fileCollection.builtBy(((Buildable) classpath).getBuildDependencies());
+  public FileCollection inferGosuClasspath(final Iterable<File> classpath) {
+    return new LazilyInitializedFileCollection(((ProjectInternal) _project).getTaskDependencyFactory()) {
+      @Override
+      public String getDisplayName() {
+        return "Gosu runtime classpath";
+      }
+
+      @Override
+      public FileCollection createDelegate() {
+        try {
+          return inferGosuClasspath();
+        } catch (RuntimeException e) {
+          return new FailingFileCollection(getDisplayName(), e);
         }
-        return fileCollection;
       }
-    };
-  }
 
-  private FileCollection doInfer(Iterable<File> classpath) {
-    if (_project.getRepositories().isEmpty()) {
-      throw new GradleException("Cannot infer Gosu classpath because no repository is declared in " + _project);
-    }
+      private Configuration inferGosuClasspath() {
+        File gosuCoreApiJar = findGosuJar(classpath, "core-api");
+        if (gosuCoreApiJar == null) {
+          List<String> classpathAsStrings = new ArrayList<>();
+          classpath.forEach(file -> classpathAsStrings.add(file.getAbsolutePath()));
+          String flattenedClasspath = String.join(":", classpathAsStrings);
+          String errorMsg = String.format("Cannot infer Gosu classpath because the Gosu Core API Jar was not found." + LF +
+                  "Does %s declare dependency to gosu-core-api? Searched classpath: %s.", _project, flattenedClasspath) + LF +
+                  "An example dependencies closure may resemble the following:" + LF +
+                  LF +
+                  "dependencies {" + LF +
+                  "    implementation 'org.gosu-lang.gosu:gosu-core-api:1.14.3' //a newer version may be available" + LF +
+                  "}" + LF;
+          _project.getLogger().quiet(errorMsg);
+          throw new GradleException(errorMsg);
+        }
 
-    File gosuCoreApiJar = findGosuJar(classpath, "core-api");
-
-    if (gosuCoreApiJar == null) {
-      List<String> classpathAsStrings = new ArrayList<>();
-      classpath.forEach(file -> classpathAsStrings.add(file.getAbsolutePath()));
-      String flattenedClasspath = String.join(":", classpathAsStrings);
-      String errorMsg = String.format("Cannot infer Gosu classpath because the Gosu Core API Jar was not found." + LF +
-              "Does %s declare dependency to gosu-core-api? Searched classpath: %s.", _project, flattenedClasspath) + LF +
-              "An example dependencies closure may resemble the following:" + LF +
-              LF +
-              "dependencies {" + LF +
-              "    compile 'org.gosu-lang.gosu:gosu-core-api:1.14.3' //a newer version may be available" + LF +
-              "}" + LF;
-      _project.getLogger().quiet(errorMsg);
-      throw new GradleException(errorMsg);
-    }
+        String gosuCoreApiRawVersion = getGosuVersion(gosuCoreApiJar);
 
-    String gosuCoreApiRawVersion = getGosuVersion(gosuCoreApiJar);
+        if (gosuCoreApiRawVersion == null) {
+          throw new AssertionError(String.format("Unexpectedly failed to parse version of Gosu Jar file: %s in %s", gosuCoreApiJar, _project));
+        }
 
-    if (gosuCoreApiRawVersion == null) {
-      throw new AssertionError(String.format("Unexpectedly failed to parse version of Gosu Jar file: %s in %s", gosuCoreApiJar, _project));
-    }
+        //Use Gradle's VersionNumber construct, which implements Comparable
+        VersionNumber gosuCoreApiVersion = VersionNumber.parse(gosuCoreApiRawVersion);
 
-    //Use Gradle's VersionNumber construct, which implements Comparable
-    VersionNumber gosuCoreApiVersion = VersionNumber.parse(gosuCoreApiRawVersion);
+        //Gosu dist with gosuc executable is required
+        if (!gosuCoreApiRawVersion.endsWith("-SNAPSHOT") && !hasGosuc(gosuCoreApiVersion)) {
+          throw new GradleException(String.format("Please declare a dependency on Gosu version 1.13.9, 1.14.2 or greater. Found: %s", gosuCoreApiRawVersion));
+        }
 
-    //Gosu dist with gosuc executable is required
-    if (!gosuCoreApiRawVersion.endsWith("-SNAPSHOT") && !hasGosuc(gosuCoreApiVersion)) {
-      throw new GradleException(String.format("Please declare a dependency on Gosu version 1.13.9, 1.14.2 or greater. Found: %s", gosuCoreApiRawVersion));
-    }
+        Configuration gosuRuntimeClasspath = _project.getConfigurations().detachedConfiguration();
+        gosuRuntimeClasspath.getDependencies().add(_project.getDependencies().create("org.gosu-lang.gosu:gosu-doc:" + gosuCoreApiRawVersion));
+        _jvmPluginServices.configureAsRuntimeClasspath(gosuRuntimeClasspath);
+        return gosuRuntimeClasspath;
+      }
 
-    Configuration detachedConfiguration = _project.getConfigurations().detachedConfiguration();
-    detachedConfiguration.getDependencies().add(_project.getDependencies().create("org.gosu-lang.gosu:gosu-doc:" + gosuCoreApiRawVersion));
-    return detachedConfiguration;
+      @Override
+      public void visitDependencies(TaskDependencyResolveContext context) {
+        if (classpath instanceof Buildable) {
+          context.add(classpath);
+        }
+      }
+    };
   }
 
   /**
diff --git a/src/main/java/org/gosulang/gradle/tasks/GosuSourceDirectorySet.java b/src/main/java/org/gosulang/gradle/tasks/GosuSourceDirectorySet.java
new file mode 100644
index 0000000..2ce602e
--- /dev/null
+++ b/src/main/java/org/gosulang/gradle/tasks/GosuSourceDirectorySet.java
@@ -0,0 +1,6 @@
+package org.gosulang.gradle.tasks;
+
+import org.gradle.api.file.SourceDirectorySet;
+
+public interface GosuSourceDirectorySet extends SourceDirectorySet {
+}
diff --git a/src/main/java/org/gosulang/gradle/tasks/GosuSourceSet.java b/src/main/java/org/gosulang/gradle/tasks/GosuSourceSet.java
index 00b09ec..c09ee5b 100644
--- a/src/main/java/org/gosulang/gradle/tasks/GosuSourceSet.java
+++ b/src/main/java/org/gosulang/gradle/tasks/GosuSourceSet.java
@@ -1,9 +1,15 @@
 package org.gosulang.gradle.tasks;
 
-import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.file.SourceDirectorySet;
 
+/**
+ * @deprecated Using convention to contribute to source sets is deprecated.
+ * <p>
+ * You can configure the Gosu sources via the {@code GosuSourceDirectorySet} extension
+ * (e.g. {@code sourceSet.getExtensions().getByType(GosuSourceDirectorySet.class).setSrcDirs(...)}).
+ */
+@Deprecated
 public interface GosuSourceSet {
 
   /**
@@ -13,16 +19,6 @@ public interface GosuSourceSet {
    */
   SourceDirectorySet getGosu();
 
-  /**
-   * Configures the Gosu source for this set.
-   *
-   * <p>The given closure is used to configure the {@link SourceDirectorySet} which contains the Gosu source.
-   *
-   * @param configureClosure The closure to use to configure the Gosu source.
-   * @return this
-   */
-  GosuSourceSet gosu(Closure configureClosure);
-
   /**
    * Configures the Gosu source for this set.
    *
@@ -31,7 +27,7 @@ public interface GosuSourceSet {
    * @param configureAction The action to use to configure the Gosu source.
    * @return this
    */
-  GosuSourceSet gosu( Action<? super SourceDirectorySet> configureAction);  
+  GosuSourceSet gosu(Action<? super SourceDirectorySet> configureAction);
   
   /**
    * All Gosu source for this source set.
diff --git a/src/main/java/org/gosulang/gradle/tasks/InfersGosuRuntime.java b/src/main/java/org/gosulang/gradle/tasks/InfersGosuRuntime.java
index 363b5d2..6de4eaf 100644
--- a/src/main/java/org/gosulang/gradle/tasks/InfersGosuRuntime.java
+++ b/src/main/java/org/gosulang/gradle/tasks/InfersGosuRuntime.java
@@ -1,12 +1,11 @@
 package org.gosulang.gradle.tasks;
 
-import groovy.lang.Closure;
 import org.gradle.api.file.FileCollection;
 
 public interface InfersGosuRuntime {
 
-  Closure<FileCollection> getGosuClasspath();
+  FileCollection getGosuClasspath();
 
-  void setGosuClasspath(Closure<FileCollection> gosuClasspathClosure);
+  void setGosuClasspath(FileCollection gosuClasspath);
 
 }
diff --git a/src/main/java/org/gosulang/gradle/tasks/compile/CommandLineGosuCompiler.java b/src/main/java/org/gosulang/gradle/tasks/compile/CommandLineGosuCompiler.java
index c4c7ec9..6b9613d 100644
--- a/src/main/java/org/gosulang/gradle/tasks/compile/CommandLineGosuCompiler.java
+++ b/src/main/java/org/gosulang/gradle/tasks/compile/CommandLineGosuCompiler.java
@@ -2,17 +2,20 @@
 
 import org.apache.tools.ant.taskdefs.condition.Os;
 import org.gosulang.gradle.tasks.Util;
-import org.gradle.api.Project;
+import org.gradle.api.JavaVersion;
+import org.gradle.api.file.Directory;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.api.model.ObjectFactory;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.api.tasks.compile.BaseForkOptions;
+import org.gradle.process.ExecOperations;
 import org.gradle.process.ExecResult;
 import org.gradle.process.JavaExecSpec;
 import org.gradle.util.GUtil;
-import org.gradle.api.JavaVersion;
 
+import javax.inject.Inject;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -21,15 +24,22 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class CommandLineGosuCompiler implements GosuCompiler<GosuCompileSpec> {
+public abstract class CommandLineGosuCompiler implements GosuCompiler<GosuCompileSpec> {
   private static final Logger LOGGER = Logging.getLogger(CommandLineGosuCompiler.class);
-  
-  private final Project _project;
+
+  private final Directory _projectDir; // TODO replace, all we need is workingDir
   private final GosuCompileSpec _spec;
   private final String _projectName;
-  
-  public CommandLineGosuCompiler(Project project, GosuCompileSpec spec, String projectName ) {
-    _project = project;
+
+  @Inject
+  public abstract ObjectFactory getObjectFactory();
+
+  @Inject
+  public abstract ExecOperations getExecOperations();
+
+  @Inject
+  public CommandLineGosuCompiler(Directory projectDir, GosuCompileSpec spec, String projectName ) {
+    _projectDir = projectDir;
     _spec = spec;
     _projectName = projectName;
   }
@@ -56,13 +66,13 @@ public WorkResult execute( GosuCompileSpec spec ) {
     ByteArrayOutputStream stdout = new ByteArrayOutputStream();
     ByteArrayOutputStream stderr = new ByteArrayOutputStream();
 
-    ExecResult result = _project.javaexec(javaExecSpec -> {
-      FileCollection gosuClasspathJars =  spec.getGosuClasspath().call();
+    ExecResult result = getExecOperations().javaexec(javaExecSpec -> {
+      FileCollection gosuClasspathJars = getObjectFactory().fileCollection().from(spec.getGosuClasspath());
       if (!JavaVersion.current().isJava11Compatible()) { //if it is not java 11
-        gosuClasspathJars = gosuClasspathJars.plus(_project.files(Util.findToolsJar()));
+        gosuClasspathJars = gosuClasspathJars.plus(getObjectFactory().fileCollection().from(Util.findToolsJar()));
       }
 
-      javaExecSpec.setWorkingDir((Object) _project.getProjectDir()); // Gradle 4.0 overloads ProcessForkOptions#setWorkingDir; must upcast to Object for backwards compatibility
+      javaExecSpec.setWorkingDir(_projectDir);
       setJvmArgs(javaExecSpec, _spec.getGosuCompileOptions().getForkOptions());
       javaExecSpec.getMainClass().set("gw.lang.gosuc.cli.CommandLineCompiler");
       javaExecSpec.setClasspath(gosuClasspathJars)
diff --git a/src/main/java/org/gosulang/gradle/tasks/compile/DefaultGosuCompileSpec.java b/src/main/java/org/gosulang/gradle/tasks/compile/DefaultGosuCompileSpec.java
index 8f9755c..89ccb7c 100644
--- a/src/main/java/org/gosulang/gradle/tasks/compile/DefaultGosuCompileSpec.java
+++ b/src/main/java/org/gosulang/gradle/tasks/compile/DefaultGosuCompileSpec.java
@@ -1,6 +1,5 @@
 package org.gosulang.gradle.tasks.compile;
 
-import groovy.lang.Closure;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.tasks.compile.CompileOptions;
 
@@ -12,7 +11,7 @@
 public class DefaultGosuCompileSpec implements GosuCompileSpec {
 
   private GosuCompileOptions _gosuCompileOptions;
-  private transient Closure<FileCollection> _gosuClasspath;
+  private FileCollection _gosuClasspath;
   private FileCollection _srcDirSet;
 
   @Override
@@ -26,13 +25,13 @@ public void setSourceRoots( FileCollection srcDirSet ) {
   }
 
   @Override
-  public Closure<FileCollection> getGosuClasspath() {
+  public FileCollection getGosuClasspath() {
     return _gosuClasspath;
   }
 
   @Override
-  public void setGosuClasspath(Closure<FileCollection> _gosuClasspathClosure) {
-    _gosuClasspath = _gosuClasspathClosure;
+  public void setGosuClasspath(FileCollection gosuClasspath) {
+    _gosuClasspath = gosuClasspath;
   }
 
   public GosuCompileOptions getGosuCompileOptions() {
diff --git a/src/main/java/org/gosulang/gradle/tasks/compile/GosuCompile.java b/src/main/java/org/gosulang/gradle/tasks/compile/GosuCompile.java
index a0e7b44..0935ccf 100644
--- a/src/main/java/org/gosulang/gradle/tasks/compile/GosuCompile.java
+++ b/src/main/java/org/gosulang/gradle/tasks/compile/GosuCompile.java
@@ -1,62 +1,70 @@
 package org.gosulang.gradle.tasks.compile;
 
-import groovy.lang.Closure;
-import org.gosulang.gradle.tasks.InfersGosuRuntime;
-import org.gradle.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.DirectoryProperty;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
-import org.gradle.api.file.SourceDirectorySet;
 import org.gradle.api.internal.file.FileTreeInternal;
 import org.gradle.api.internal.tasks.compile.CompilationSourceDirs;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.model.ObjectFactory;
 import org.gradle.api.plugins.JavaPlugin;
-import org.gradle.api.tasks.*;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.CacheableTask;
+import org.gradle.api.tasks.Classpath;
+import org.gradle.api.tasks.CompileClasspath;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Internal;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.PathSensitive;
+import org.gradle.api.tasks.SkipWhenEmpty;
+import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.tasks.compile.AbstractCompile;
 import org.gradle.api.tasks.compile.CompileOptions;
-import org.gradle.util.VersionNumber;
 
 import javax.inject.Inject;
 import java.io.File;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.function.BiFunction;
 
 import static org.gradle.api.tasks.PathSensitivity.NAME_ONLY;
 
 @CacheableTask
-public class GosuCompile extends AbstractCompile implements InfersGosuRuntime {
+public abstract class GosuCompile extends AbstractCompile {
 
   private GosuCompiler<GosuCompileSpec> _compiler;
-  private Closure<FileCollection> _gosuClasspath;
-  private Closure<FileCollection> _orderClasspath;
+  private BiFunction<Project, Configuration, FileCollection> _orderClasspathFunction;
 
   private final CompileOptions _compileOptions;
   private final GosuCompileOptions _gosuCompileOptions = new GosuCompileOptions();
-  private final FileCollection stableSources = getProject().files(new Callable<FileTree>() {
-    @Override
-    public FileTree call() {
-      return getSource();
-    }
-  });
+  private final FileCollection stableSources = getProject().files((Callable<FileTree>) this::getSource);
+
+  @Inject
+  public abstract ObjectFactory getObjectFactory();
 
   @Inject
   public GosuCompile() {
-      _compileOptions = getServices().get(ObjectFactory.class).newInstance(CompileOptions.class);
+      _compileOptions = getObjectFactory().newInstance(CompileOptions.class);
   }
 
   @TaskAction
   protected void compile() {
-    DefaultGosuCompileSpec spec = createSpec();
+    GosuCompileSpec spec = createSpec();
     _compiler = getCompiler(spec);
     _compiler.execute(spec);
   }
 
+  @Internal
+  public abstract DirectoryProperty getProjectDir();
+
+  @Internal
+  public abstract Property<String> getProjectName();
+
   /**
    * {@inheritDoc}
    */
@@ -96,78 +104,30 @@ public FileCollection getClasspath() {
 
   /**
    * @return the classpath to use to load the Gosu compiler.
+   * <p>
+   * This value is set by default in {@link org.gosulang.gradle.GosuBasePlugin} by inferring the gosu-core-api jar from the compile-time classpath
    */
-  @Override
   @Classpath
-  @InputFiles
-  public Closure<FileCollection> getGosuClasspath() {
-    return _gosuClasspath;
-  }
+  public abstract ConfigurableFileCollection getGosuClasspath(); // {
 
-  @Override
-  public void setGosuClasspath(Closure<FileCollection> gosuClasspathClosure) {
-    _gosuClasspath = gosuClasspathClosure;
-  }
-
-  /**
-   * Annotating as @Input or @InputFiles causes errors in Guidewire applications, even when paired with @Optional.
-   * Marking as @Internal instead to skip warning thrown by :validateTaskProperties (org.gradle.plugin.devel.tasks.ValidateTaskProperties)
-   * @return a Closure returning a classpath to be passed to the GosuCompile task
-   */
   @Internal
-  public Closure<FileCollection> getOrderClasspath() {
-    return _orderClasspath;
+  public BiFunction<Project, Configuration, FileCollection> getOrderClasspathFunction() {
+    return _orderClasspathFunction;
   }
 
-  /**
-   * Normally setting this value is not required.
-   * Certain projects relying on depth-first resolution of module dependencies can use this
-   * Closure to reorder the classpath as needed.
-   *
-   * @param orderClasspath a Closure returning a classpath to be passed to the GosuCompile task
-   */
-  public void setOrderClasspath(Closure<FileCollection> orderClasspath) {
-    _orderClasspath = orderClasspath;
+  public void setOrderClasspathFunction(BiFunction<Project, Configuration, FileCollection> orderClasspathFunction) {
+    _orderClasspathFunction = orderClasspathFunction;
   }
 
-/*  @Internal
+  @Internal
   public FileCollection getSourceRoots() {
-    Set<File> returnValues = new HashSet<>();
-    //noinspection Convert2streamapi
-   //  for(Object obj : getSourceReflectively()) {
-    for(Object obj : getSource()) {
-      if(obj instanceof SourceDirectorySet) {
-        returnValues.addAll(((SourceDirectorySet) obj).getSrcDirs());
-      }
-    }
-    return getProject().files(returnValues);
-  }*/
-
-
-@Internal
-public FileCollection getSourceRoots() {
-  FileTreeInternal stableSourcesAsFileTree = (FileTreeInternal) getStableSources().getAsFileTree();
-  List<File> sourceRoots = CompilationSourceDirs.inferSourceRoots(stableSourcesAsFileTree);
-  return getProject().getLayout().files(sourceRoots);
-}
-
-
-
-  //!! todo: find a better way to iterate the FileTree
-  private Iterable getSourceReflectively() {
-    try {
-     // Field field = SourceTask.class.getDeclaredField("source");
-      Field field = SourceTask.class.getDeclaredField("sourceFiles");
-      field.setAccessible(true);
-      return (Iterable)field.get(this);
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
+    FileTreeInternal stableSourcesAsFileTree = (FileTreeInternal) getStableSources().getAsFileTree();
+    List<File> sourceRoots = CompilationSourceDirs.inferSourceRoots(stableSourcesAsFileTree);
+    return getObjectFactory().fileCollection().from(sourceRoots);
   }
 
-  private DefaultGosuCompileSpec createSpec() {
+  private GosuCompileSpec createSpec() {
     DefaultGosuCompileSpec spec = new DefaultGosuCompileSpec();
-    Project project = getProject();
     spec.setSource(getSource());
     spec.setSourceRoots(getSourceRoots());
     spec.setDestinationDir(getDestinationDirectory().get().getAsFile());
@@ -176,17 +136,18 @@ private DefaultGosuCompileSpec createSpec() {
     spec.setCompileOptions(_compileOptions);
     spec.setGosuCompileOptions(_gosuCompileOptions);
 
-    if (_orderClasspath == null) {
+    if (_orderClasspathFunction == null) {
       spec.setClasspath(asList(getClasspath()));
     } else {
-      spec.setClasspath(asList(_orderClasspath.call(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME))));
-      //spec.setClasspath(asList(_orderClasspath.call(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME))));
+      spec.setClasspath(asList(_orderClasspathFunction.apply(getProject(), getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME))));
+      // FIXME getProject() may break configuration cache
     }
 
-    Logger logger = project.getLogger();
+    Logger logger = getLogger();
+    String projectName = getProjectName().get();
 
     if(logger.isInfoEnabled()) {
-      logger.info("Gosu Compiler source roots for {} are:", project.getName());
+      logger.info("Gosu Compiler source roots for {} are:", projectName);
       if(spec.getSourceRoots().isEmpty()) {
         logger.info("<empty>");
       } else {
@@ -195,7 +156,7 @@ private DefaultGosuCompileSpec createSpec() {
         }
       }
 
-      logger.info("Gosu Compiler Spec classpath for {} is:", project.getName());
+      logger.info("Gosu Compiler Spec classpath for {} is:", projectName);
       if(!spec.getClasspath().iterator().hasNext()) {
         logger.info("<empty>");
       } else {
@@ -204,8 +165,8 @@ private DefaultGosuCompileSpec createSpec() {
         }
       }
 
-      logger.info("Gosu Compile Spec gosuClasspath for {} is:", project.getName());
-      FileCollection gosuClasspath = spec.getGosuClasspath().call();
+      logger.info("Gosu Compile Spec gosuClasspath for {} is:", projectName);
+      FileCollection gosuClasspath = getObjectFactory().fileCollection().from(spec.getGosuClasspath());
       if(gosuClasspath.isEmpty()) {
         logger.info("<empty>");
       } else {
@@ -219,13 +180,20 @@ private DefaultGosuCompileSpec createSpec() {
   }
 
   private GosuCompiler<GosuCompileSpec> getCompiler(GosuCompileSpec spec) {
+    assertGosuClasspathIsNotEmpty();
     if(_compiler == null) {
-      GosuCompilerFactory gosuCompilerFactory = new GosuCompilerFactory(getProject(), this.getPath());
+      GosuCompilerFactory gosuCompilerFactory = getServices().get(ObjectFactory.class).newInstance(GosuCompilerFactory.class, getProjectDir().get(), this.getPath()); // FIXME don't call getProject()
       _compiler = gosuCompilerFactory.newCompiler(spec);
     }
     return _compiler;
   }
 
+  protected void assertGosuClasspathIsNotEmpty() {
+    if (getGosuClasspath().isEmpty()) {
+      throw new InvalidUserDataException("Cannot infer Gosu classpath because the Gosu Core API Jar was not found.");
+    }
+  }
+
   private List<File> asList(final FileCollection files) {
     List<File> list = new ArrayList<>();
     files.forEach(list::add);
diff --git a/src/main/java/org/gosulang/gradle/tasks/compile/GosuCompilerFactory.java b/src/main/java/org/gosulang/gradle/tasks/compile/GosuCompilerFactory.java
index e044d87..f4a1925 100644
--- a/src/main/java/org/gosulang/gradle/tasks/compile/GosuCompilerFactory.java
+++ b/src/main/java/org/gosulang/gradle/tasks/compile/GosuCompilerFactory.java
@@ -1,14 +1,21 @@
 package org.gosulang.gradle.tasks.compile;
 
-import org.gradle.api.Project;
+import org.gradle.api.file.Directory;
+import org.gradle.api.model.ObjectFactory;
 
-public class GosuCompilerFactory implements IGosuCompilerFactory<GosuCompileSpec> {
+import javax.inject.Inject;
 
-  private final Project _project;
+public abstract class GosuCompilerFactory implements IGosuCompilerFactory<GosuCompileSpec> {
+
+  private final Directory _projectDir;
   private final String _taskPath;
 
-  public GosuCompilerFactory(Project project, String forTask) {
-    _project = project;
+  @Inject
+  public abstract ObjectFactory getObjectFactory();
+
+  @Inject
+  public GosuCompilerFactory(Directory projectDir, String forTask) {
+    _projectDir = projectDir;
     _taskPath = forTask;
   }
 
@@ -17,7 +24,7 @@ public GosuCompiler<GosuCompileSpec> newCompiler( GosuCompileSpec spec ) {
     GosuCompileOptions gosuOptions = spec.getGosuCompileOptions();
     GosuCompiler<GosuCompileSpec> gosuCompiler;
     if(gosuOptions.isFork()) {
-      gosuCompiler = new CommandLineGosuCompiler(_project, spec, _taskPath);
+      gosuCompiler = getObjectFactory().newInstance(CommandLineGosuCompiler.class, _projectDir, spec, _taskPath);
     } else {
       gosuCompiler = new InProcessGosuCompiler();
     }
diff --git a/src/main/java/org/gosulang/gradle/tasks/gosudoc/CommandLineGosuDoc.java b/src/main/java/org/gosulang/gradle/tasks/gosudoc/CommandLineGosuDoc.java
index 6ce178c..6c1ec60 100644
--- a/src/main/java/org/gosulang/gradle/tasks/gosudoc/CommandLineGosuDoc.java
+++ b/src/main/java/org/gosulang/gradle/tasks/gosudoc/CommandLineGosuDoc.java
@@ -4,15 +4,20 @@
 import org.gosulang.gradle.tasks.Util;
 import org.gradle.api.GradleException;
 import org.gradle.api.JavaVersion;
-import org.gradle.api.Project;
+import org.gradle.api.file.Directory;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileSystemOperations;
+import org.gradle.api.file.RegularFile;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.api.model.ObjectFactory;
 import org.gradle.api.tasks.compile.BaseForkOptions;
+import org.gradle.process.ExecOperations;
 import org.gradle.process.ExecResult;
 import org.gradle.process.JavaExecSpec;
 import org.gradle.tooling.BuildException;
 
+import javax.inject.Inject;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -24,30 +29,43 @@
 import java.util.jar.Manifest;
 import java.util.regex.Pattern;
 
-public class CommandLineGosuDoc {
+public abstract class CommandLineGosuDoc {
   private static final Logger LOGGER = Logging.getLogger(CommandLineGosuDoc.class);
   
   private final FileCollection _source;
-  private final File _targetDir;
+  private final Directory _targetDir;
   private final FileCollection _projectClasspath;
   private final FileCollection _gosuClasspath;
   private final GosuDocOptions _options;
-  private final Project _project;
+  private final String _projectName;
+  private final Directory _projectDir;
+  private final Directory _buildDir;
 
+  @Inject
+  public abstract ObjectFactory getObjectFactory();
 
-  public CommandLineGosuDoc(FileCollection source, File targetDir, FileCollection gosuClasspath, FileCollection projectClasspath, GosuDocOptions options, Project project) {
+  @Inject
+  public abstract ExecOperations getExecOperations();
+
+  @Inject
+  public abstract FileSystemOperations getFs();
+
+  @Inject
+  public CommandLineGosuDoc(FileCollection source, Directory targetDir, FileCollection gosuClasspath, FileCollection projectClasspath, GosuDocOptions options, String projectName, Directory projectDir, Directory buildDir) {
     _source = source;
     _targetDir = targetDir;
     _gosuClasspath = gosuClasspath;
     _projectClasspath = projectClasspath;
     _options = options;
-    _project = project;
+    _projectName = projectName;
+    _projectDir = projectDir;
+    _buildDir = buildDir;
   }
   
   public void execute() {
     String startupMsg = "Initializing gosudoc generator";
-    if(_project.getName().isEmpty()) {
-      startupMsg += " for " + _project.getName();
+    if(!_projectName.isEmpty()) {
+      startupMsg += " for " + _projectName;
     }
     LOGGER.info(startupMsg);
     
@@ -55,17 +73,17 @@ public void execute() {
     // We don't want that, so instead we create a temp directory with the contents of 'source'
     // Copying 'source' to the temp dir should honor its include/exclude patterns
     // Finally, the tmpdir will be the sole inputdir passed to the gosudoc task
-    final File tmpDir = new File(_project.getBuildDir(), "tmp/gosudoc");
-    _project.delete(tmpDir);
-    _project.copy(copySpec -> copySpec.from(_source).into(tmpDir));
+    final RegularFile tmpDir = _buildDir.file("tmp/gosudoc");
+    getFs().delete(files-> files.delete(tmpDir));
+    getFs().copy(copySpec -> copySpec.from(_source).into(tmpDir));
 
     List<String> gosudocArgs = new ArrayList<>();
 
     gosudocArgs.add("-inputDirs");
-    gosudocArgs.add(tmpDir.getAbsolutePath());
+    gosudocArgs.add(tmpDir.getAsFile().getAbsolutePath());
     
     gosudocArgs.add("-output");
-    gosudocArgs.add(_targetDir.getAbsolutePath());
+    gosudocArgs.add(_targetDir.getAsFile().getAbsolutePath());
     
     if(_options.isVerbose()) {
       gosudocArgs.add("-verbose");
@@ -74,9 +92,9 @@ public void execute() {
     ByteArrayOutputStream stdout = new ByteArrayOutputStream();
     ByteArrayOutputStream stderr = new ByteArrayOutputStream();
 
-    FileCollection jointClasspath = _project.files(_gosuClasspath).plus(_projectClasspath);
+    FileCollection jointClasspath = getObjectFactory().fileCollection().from(_gosuClasspath).plus(_projectClasspath);
     if (!JavaVersion.current().isJava11Compatible()) { //if it is not java 11
-      jointClasspath = jointClasspath.plus(_project.files(Util.findToolsJar()));
+      jointClasspath = jointClasspath.plus(getObjectFactory().fileCollection().from(Util.findToolsJar()));
     }
 
     //FileCollection jointClasspath = _project.files(_gosuClasspath).plus(_projectClasspath);
@@ -90,12 +108,12 @@ public void execute() {
     
     LOGGER.info("Created classpathJar at " + classpathJar.getAbsolutePath());
     
-    ExecResult result = _project.javaexec(javaExecSpec -> {
+    ExecResult result = getExecOperations().javaexec(javaExecSpec -> {
 
-      javaExecSpec.setWorkingDir((Object) _project.getProjectDir()); // Gradle 4.0 overloads ProcessForkOptions#setWorkingDir; must upcast to Object for backwards compatibility
+      javaExecSpec.setWorkingDir(_projectDir);
       setJvmArgs(javaExecSpec, _options.getForkOptions());
       javaExecSpec.getMainClass().set("gw.gosudoc.cli.Gosudoc");
-      javaExecSpec.setClasspath(_project.files(classpathJar))
+      javaExecSpec.setClasspath(getObjectFactory().fileCollection().from(classpathJar))
           .setArgs((Iterable<?>) gosudocArgs); // Gradle 4.0 overloads JavaExecSpec#setArgs; must upcast to Iterable<?> for backwards compatibility
       javaExecSpec.setStandardOutput(stdout);
       javaExecSpec.setErrorOutput(stderr);
@@ -119,13 +137,13 @@ public void execute() {
   private File createClasspathJarFromFileCollection(FileCollection classpath) throws IOException {
     File tempFile;
     if (LOGGER.isDebugEnabled()) {
-      tempFile = File.createTempFile(CommandLineGosuDoc.class.getName(), "classpath.jar", new File(_targetDir.getAbsolutePath()));
+      tempFile = File.createTempFile(CommandLineGosuDoc.class.getName(), "classpath.jar", new File(_targetDir.getAsFile().getAbsolutePath()));
     } else {
       tempFile = File.createTempFile(CommandLineGosuDoc.class.getName(), "classpath.jar");
       tempFile.deleteOnExit();
-    }    
-    
-    LOGGER.info("Creating classpath JAR at " + tempFile.getAbsolutePath());
+    }
+
+    LOGGER.info("Creating classpath JAR at {}", tempFile.getAbsolutePath());
     
     Manifest man = new Manifest();
     man.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
diff --git a/src/main/java/org/gosulang/gradle/tasks/gosudoc/GosuDoc.java b/src/main/java/org/gosulang/gradle/tasks/gosudoc/GosuDoc.java
index 8e8ec79..ad0f015 100644
--- a/src/main/java/org/gosulang/gradle/tasks/gosudoc/GosuDoc.java
+++ b/src/main/java/org/gosulang/gradle/tasks/gosudoc/GosuDoc.java
@@ -1,14 +1,15 @@
 package org.gosulang.gradle.tasks.gosudoc;
 
-import groovy.lang.Closure;
-import org.gosulang.gradle.tasks.InfersGosuRuntime;
-import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.DirectoryProperty;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.api.model.ObjectFactory;
+import org.gradle.api.provider.Property;
 import org.gradle.api.tasks.CacheableTask;
 import org.gradle.api.tasks.Classpath;
 import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Internal;
 import org.gradle.api.tasks.Nested;
 import org.gradle.api.tasks.Optional;
 import org.gradle.api.tasks.OutputDirectory;
@@ -17,21 +18,31 @@
 import org.gradle.api.tasks.SourceTask;
 import org.gradle.api.tasks.TaskAction;
 
+import javax.inject.Inject;
 import java.io.File;
 
 @CacheableTask
-public class GosuDoc extends SourceTask implements InfersGosuRuntime {
+public abstract class GosuDoc extends SourceTask /*implements InfersGosuRuntime*/ {
 
-  private FileCollection _classpath;
-  private Closure<FileCollection> _gosuClasspath;
-  private File _destinationDir;
   private GosuDocOptions _gosuDocOptions = new GosuDocOptions();
-  private String _title;
 
+  @Inject
+  public abstract ObjectFactory getObjectFactory();
+
+  @Inject
   public GosuDoc() {
     getLogging().captureStandardOutput(LogLevel.INFO);
   }
 
+  @Internal
+  public abstract Property<String> getProjectName();
+
+  @Internal
+  public abstract DirectoryProperty getProjectDir();
+
+  @Internal
+  public abstract DirectoryProperty getBuildDir();
+
   /**
    * {@inheritDoc}
    */
@@ -46,13 +57,7 @@ public FileTree getSource() {
    * @return the target directory to generate the API documentation.
    */
   @OutputDirectory
-  public File getDestinationDir() {
-    return _destinationDir;
-  }
-
-  public void setDestinationDir( File destinationDir ) {
-    _destinationDir = destinationDir;
-  }
+  public abstract DirectoryProperty getDestinationDir();
 
   /**
    * <p>Returns the classpath to use to locate classes referenced by the documented source.</p>
@@ -60,30 +65,15 @@ public void setDestinationDir( File destinationDir ) {
    * @return The classpath.
    */
   @Classpath
-  @InputFiles
-  public FileCollection getClasspath() {
-    return _classpath;
-  }
-
-  public void setClasspath( FileCollection classpath ) {
-    _classpath = classpath;
-  }
+  public abstract ConfigurableFileCollection getClasspath();
 
   /**
    * Returns the classpath to use to load the gosudoc tool.
+   *
    * @return the classpath to use to load the gosudoc tool.
    */
-  @Override
   @Classpath
-  @InputFiles
-  public Closure<FileCollection> getGosuClasspath() {
-    return _gosuClasspath;
-  }
-
-  @Override
-  public void setGosuClasspath( Closure<FileCollection> gosuClasspathClosure ) {
-    _gosuClasspath = gosuClasspathClosure;
-  }
+  public abstract ConfigurableFileCollection getGosuClasspath();
 
   /**
    * Returns the gosudoc generation options.
@@ -104,20 +94,14 @@ public void setGosuDocOptions(GosuDocOptions gosuDocOptions) {
    */
   @Input
   @Optional
-  public String getTitle() {
-    return _title;
-  }
-
-  public void setTitle( String title ) {
-    this._title = title;
-  }
+  public abstract Property<String> getTitle();
 
   @TaskAction
   protected void generate() {
     GosuDocOptions options = getGosuDocOptions();
     if (options.getTitle() != null && !options.getTitle().isEmpty()) {
-      options.setTitle(getTitle());
+      options.setTitle(getTitle().get());
     }
-    new CommandLineGosuDoc(getSource(), getDestinationDir(), getGosuClasspath().call(), getClasspath(), options, getProject()).execute();
+    getObjectFactory().newInstance(CommandLineGosuDoc.class, getSource(), getDestinationDir().get(), getGosuClasspath(), getClasspath(), options, getProjectName().get(), getProjectDir().get(), getBuildDir().get()).execute();
   }
 }
diff --git a/src/test/groovy/org/gosulang/gradle/functional/ConfigurationCacheClasspathReorderTest.groovy b/src/test/groovy/org/gosulang/gradle/functional/ConfigurationCacheClasspathReorderTest.groovy
new file mode 100644
index 0000000..1859ebe
--- /dev/null
+++ b/src/test/groovy/org/gosulang/gradle/functional/ConfigurationCacheClasspathReorderTest.groovy
@@ -0,0 +1,76 @@
+package org.gosulang.gradle.functional
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+import spock.lang.Ignore
+
+import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
+
+@Ignore('This is known to fail when configuration-cache is enabled')
+class ConfigurationCacheClasspathReorderTest extends AbstractGosuPluginSpecification {
+
+    File srcMainGosu
+    File simplePogo
+
+    @Override
+    def setup() {
+        srcMainGosu = testProjectDir.newFolder('src', 'main', 'gosu')
+    }
+
+    def 'apply gosu plugin and reorder classpath with configuration cache in strict mode'() {
+        given:
+        buildScript << getBasicBuildScriptForTesting()
+        buildScript.text +=
+                '''
+                |tasks.withType(org.gosulang.gradle.tasks.compile.GosuCompile).configureEach { t ->
+                |  // this accepts a BiFunction<Project, Configuration, FileCollection>, which in this case reverses the classpath order
+                |  t.orderClasspathFunction = (Project p, Configuration c) -> { 
+                |    t.logger.quiet("Project name: {}", p.name)
+                |    def originalList = c.getFiles().asList()
+                |    def reversedList = []
+                |    ListIterator li = originalList.listIterator(originalList.size())
+                |    while(li.hasPrevious()) {
+                |      reversedList.add(li.previous())
+                |    }
+                |    t.logger.quiet('Original list: {}', originalList)
+                |    t.logger.quiet('Reversed list: {}', reversedList)
+                |    return p.objects.fileCollection().from(reversedList)
+                |  }
+                |}
+                |
+                '''.stripMargin()
+        testProjectDir.newFile('settings.gradle') << 'enableFeaturePreview "STABLE_CONFIGURATION_CACHE"'
+
+        simplePogo = new File(srcMainGosu, asPath('example', 'gradle', 'SimplePogo.gs'))
+        simplePogo.getParentFile().mkdirs()
+        simplePogo << """
+            package example.gradle
+            
+            class SimplePogo {}"""
+
+        when:
+        GradleRunner runner = GradleRunner.create()
+                .withProjectDir(testProjectDir.root)
+                .withPluginClasspath()
+                .withArguments('compileGosu', '--no-configuration-cache')
+                .withGradleVersion(gradleVersion)
+//                .forwardOutput()
+
+        BuildResult result = runner.build()
+
+        then:
+        result.task(":compileGosu").outcome == SUCCESS
+        result.output.contains('Configuration cache entry stored.')
+
+        when:
+        result = runner.build()
+
+        then:
+        result.task(":compileGosu").outcome == SUCCESS
+        result.output.contains('Reusing configuration cache.')
+        result.output.contains('Configuration cache entry reused.')
+
+        where:
+        gradleVersion << gradleVersionsToTest
+    }
+}
diff --git a/src/test/groovy/org/gosulang/gradle/functional/ConfigurationCacheNoOpTest.groovy b/src/test/groovy/org/gosulang/gradle/functional/ConfigurationCacheNoOpTest.groovy
new file mode 100644
index 0000000..d71af7c
--- /dev/null
+++ b/src/test/groovy/org/gosulang/gradle/functional/ConfigurationCacheNoOpTest.groovy
@@ -0,0 +1,39 @@
+package org.gosulang.gradle.functional
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+
+import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
+
+class ConfigurationCacheNoOpTest extends AbstractGosuPluginSpecification {
+
+    def 'apply gosu plugin and run help with configuration cache in strict mode'() {
+        given:
+        buildScript << getBasicBuildScriptForTesting()
+        testProjectDir.newFile('settings.gradle') << 'enableFeaturePreview "STABLE_CONFIGURATION_CACHE"'
+
+        when:
+        GradleRunner runner = GradleRunner.create()
+                .withProjectDir(testProjectDir.root)
+                .withPluginClasspath()
+                .withArguments('help', '--configuration-cache')
+                .withGradleVersion(gradleVersion)
+
+        BuildResult result = runner.build()
+
+        then:
+        result.task(":help").outcome == SUCCESS
+        result.output.contains('Configuration cache entry stored.')
+
+        when:
+        result = runner.build()
+
+        then:
+        result.task(":help").outcome == SUCCESS
+        result.output.contains('Reusing configuration cache.')
+        result.output.contains('Configuration cache entry reused.')
+
+        where:
+        gradleVersion << gradleVersionsToTest
+    }
+}
diff --git a/src/test/groovy/org/gosulang/gradle/functional/LocalBuildCacheTest.groovy b/src/test/groovy/org/gosulang/gradle/functional/LocalBuildCacheTest.groovy
index 6106442..8062cde 100644
--- a/src/test/groovy/org/gosulang/gradle/functional/LocalBuildCacheTest.groovy
+++ b/src/test/groovy/org/gosulang/gradle/functional/LocalBuildCacheTest.groovy
@@ -37,6 +37,7 @@ class LocalBuildCacheTest extends AbstractGosuPluginSpecification {
     def 'apply gosu plugin and compile [Gradle #gradleVersion]'() {
         given:
         buildScript << getBasicBuildScriptForTesting()
+        testProjectDir.newFile('settings.gradle') << 'enableFeaturePreview "STABLE_CONFIGURATION_CACHE"'
 
         simplePogo = new File(srcMainGosu, asPath('example', 'gradle', 'SimplePogo.gs'))
         simplePogo.getParentFile().mkdirs()
@@ -50,7 +51,9 @@ class LocalBuildCacheTest extends AbstractGosuPluginSpecification {
                 .withProjectDir(testProjectDir.root)
                 .withTestKitDir(testKitDir.root)
                 .withPluginClasspath()
-                .withArguments('gosudoc', '--build-cache')
+                .withArguments('gosudoc', '--build-cache', '--configuration-cache')
+//                .forwardOutput()
+//                .withDebug(true)
 
         BuildResult result = runner.build()
 
@@ -58,25 +61,28 @@ class LocalBuildCacheTest extends AbstractGosuPluginSpecification {
         result.task(":compileGosu").outcome == SUCCESS
         result.task(":gosudoc").outcome == SUCCESS
 
-        if(VersionNumber.parse(gradleVersion) >= VersionNumber.parse('4.0')) {
-            result.output.contains('2 actionable tasks: 2 executed')
-        } else {
-            result.output.contains("""
-5 tasks in build, out of which 3 (60%) were executed
-2  (40%) no-source
-2  (40%) cache miss
-1  (20%) not cacheable
-""")
-        }
+        result.output.contains('Calculating task graph as no cached configuration is available for tasks: gosudoc')
+        result.output.contains('2 actionable tasks: 2 executed')
+        result.output.contains('Configuration cache entry stored.')
 
         assertTaskOutputs()
-        
+
         when:
+        GradleRunner.create()
+            .withProjectDir(testProjectDir.root)
+            .withTestKitDir(testKitDir.root)
+            .withPluginClasspath()
+            .withArguments('clean')
+            .build()
+
+        and:
         runner = GradleRunner.create()
                 .withProjectDir(testProjectDir.root)
                 .withTestKitDir(testKitDir.root)
                 .withPluginClasspath()
-                .withArguments('clean', 'gosudoc', '--build-cache')
+                .withArguments('gosudoc', '--build-cache', '--configuration-cache')
+//                .forwardOutput()
+//                .withDebug(true)
 
         result = runner.build()
 
@@ -84,22 +90,14 @@ class LocalBuildCacheTest extends AbstractGosuPluginSpecification {
         result.task(":compileGosu").outcome == FROM_CACHE
         result.task(":gosudoc").outcome == FROM_CACHE
 
-        if(VersionNumber.parse(gradleVersion) >= VersionNumber.parse('4.0')) {
-            result.output.contains('2 actionable tasks: 0 executed')
-        } else {
-            result.output.contains("""
-6 tasks in build, out of which 1 (17%) were executed
-1  (17%) up-to-date
-2  (33%) no-source
-2  (33%) loaded from cache
-1  (17%) not cacheable
-""")
-        }
+        result.output.contains('Reusing configuration cache.')
+        result.output.contains('2 actionable tasks: 2 from cache')
+        result.output.contains('Configuration cache entry reused.')
 
         assertTaskOutputs()
         
         where:
-        gradleVersion << gradleVersionsToTest.findAll { VersionNumber.parse(it) >= VersionNumber.parse('3.5') } // build caching only available since Gradle 3.5
+        gradleVersion << gradleVersionsToTest
     }
 
     private boolean assertTaskOutputs() {
diff --git a/src/test/groovy/org/gosulang/gradle/unit/DefaultGosuSourceSetTest.groovy b/src/test/groovy/org/gosulang/gradle/unit/DefaultGosuSourceSetTest.groovy
index 2abd0fc..de4974f 100644
--- a/src/test/groovy/org/gosulang/gradle/unit/DefaultGosuSourceSetTest.groovy
+++ b/src/test/groovy/org/gosulang/gradle/unit/DefaultGosuSourceSetTest.groovy
@@ -33,7 +33,7 @@ class DefaultGosuSourceSetTest extends Specification {
     def setup() {
         _testProjectDir.create()
         Project project = createRootProject(_testProjectDir.root)
-        sourceSet = new DefaultGosuSourceSet("<set-display-name>", project.objects);
+        sourceSet = project.objects.newInstance(DefaultGosuSourceSet, "<set-display-name>")
     }
 
     def 'verify_the_default_values'() {
diff --git a/src/test/groovy/org/gosulang/gradle/unit/GosuRuntimeTest.groovy b/src/test/groovy/org/gosulang/gradle/unit/GosuRuntimeTest.groovy
index d09bf10..ea2cdfb 100644
--- a/src/test/groovy/org/gosulang/gradle/unit/GosuRuntimeTest.groovy
+++ b/src/test/groovy/org/gosulang/gradle/unit/GosuRuntimeTest.groovy
@@ -17,12 +17,12 @@ class GosuRuntimeTest extends Specification {
     def 'inference fails if no repository declared'() {
         when:
         def gosuClasspath = project.gosuRuntime.inferGosuClasspath([new File('other.jar'), new File('gosu-core-api-1.8.jar')])
-        gosuClasspath.call().files
+        gosuClasspath.files
 
         then:
         GradleException e = thrown()
         System.out.println(e.message)
-        e.message.equals('Cannot infer Gosu classpath because no repository is declared in ' + project)
+        e.message.equals('Please declare a dependency on Gosu version 1.13.9, 1.14.2 or greater. Found: 1.8')
     }
 
     def 'test to find Gosu Jars on class path'() {