diff --git a/api/build.gradle b/api/build.gradle index 1060a0a..01c890a 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -8,7 +8,7 @@ repositories { dependencies { // https://archiva.jessebrault.com/#artifact/com.jessebrault.gst/lib - implementation 'com.jessebrault.gst:lib:0.0.3' + implementation 'com.jessebrault.gst:lib:0.0.5' // https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates implementation 'org.apache.groovy:groovy-templates:4.0.12' diff --git a/api/src/main/groovy/com/jessebrault/ssg/BuildScriptBasedStaticSiteGenerator.groovy b/api/src/main/groovy/com/jessebrault/ssg/BuildScriptBasedStaticSiteGenerator.groovy index 4fde512..a6aa702 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/BuildScriptBasedStaticSiteGenerator.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/BuildScriptBasedStaticSiteGenerator.groovy @@ -2,7 +2,7 @@ package com.jessebrault.ssg import com.jessebrault.ssg.buildscript.Build import com.jessebrault.ssg.buildscript.BuildScriptConfiguratorFactory -import com.jessebrault.ssg.buildscript.BuildScripts +import com.jessebrault.ssg.buildscript.BuildScriptRunner import com.jessebrault.ssg.util.Diagnostic import org.jetbrains.annotations.Nullable import org.slf4j.Logger @@ -18,45 +18,50 @@ final class BuildScriptBasedStaticSiteGenerator implements StaticSiteGenerator { private static final Marker enter = MarkerFactory.getMarker('enter') private static final Marker exit = MarkerFactory.getMarker('exit') - private final GroovyScriptEngine engine - private final Collection configuratorFactories private final @Nullable File buildScript - private final Map scriptArgs private final Collection builds = [] private boolean ranBuildScript = false + private GroovyClassLoader buildScriptClassLoader + /** + * @param buildScriptClassLoaderUrls the urls to pass to the buildScriptRunner's GroovyClassLoader. + * These should include the URL needed to find the given buildScript file, if present, as + * well as any script dependencies (such as the buildSrc dir). + * @param buildScript The buildScript File, may be null. + */ BuildScriptBasedStaticSiteGenerator( - GroovyScriptEngine engine, - Collection configuratorFactories = [], - @Nullable File buildScript = null, - Map scriptArgs = [:] + Collection buildScriptClassLoaderUrls, + @Nullable File buildScript = null ) { - this.engine = engine - this.configuratorFactories = configuratorFactories this.buildScript = buildScript - this.scriptArgs = scriptArgs } - private void runBuildScript() { - logger.trace(enter, '') + private void runBuildScript( + Collection configuratorFactories, + Map buildScriptArgs + ) { + logger.trace(enter, 'configuratorFactories: {}, buildScriptArgs: {}', configuratorFactories, buildScriptArgs) if (this.buildScript == null) { logger.info('no specified build script; using defaults') - def result = BuildScripts.runBuildScript { base -> - this.configuratorFactories.each { + def result = BuildScriptRunner.runClosureScript { base -> + configuratorFactories.each { it.get().accept(base) } } this.builds.addAll(result) } else if (this.buildScript.exists() && this.buildScript.isFile()) { logger.info('running buildScript: {}', this.buildScript) - def result = BuildScripts.runBuildScript( + def buildScriptRunner = new BuildScriptRunner([ + this.buildScript.parentFile.toURI().toURL() + ]) + this.buildScriptClassLoader = buildScriptRunner.getBuildScriptClassLoader() + def result = buildScriptRunner.runBuildScript( this.buildScript.name, - this.engine, - [args: this.scriptArgs] + [args: buildScriptArgs] ) { base -> - this.configuratorFactories.each { it.get().accept(base) } + configuratorFactories.each { it.get().accept(base) } } this.builds.addAll(result) } else { @@ -67,12 +72,27 @@ final class BuildScriptBasedStaticSiteGenerator implements StaticSiteGenerator { logger.trace(exit, '') } + /** + * @return The classLoader used to load the buildScript. + * @throws NullPointerException if the buildScriptRunner was not initialized yet (make sure to call + * {@link #doBuild} first). + */ + GroovyClassLoader getBuildScriptClassLoader() { + Objects.requireNonNull(this.buildScriptClassLoader) + } + + // TODO: cache @Override - boolean doBuild(String buildName, Consumer> diagnosticsConsumer = { }) { + boolean doBuild( + String buildName, + Collection configuratorFactories, + Map buildScriptArgs, + Consumer> diagnosticsConsumer + ) { logger.trace(enter, 'buildName: {}, diagnosticsConsumer: {}', buildName, diagnosticsConsumer) if (!this.ranBuildScript) { - this.runBuildScript() + this.runBuildScript(configuratorFactories, buildScriptArgs) } def build = this.builds.find { it.name == buildName } diff --git a/api/src/main/groovy/com/jessebrault/ssg/StaticSiteGenerator.groovy b/api/src/main/groovy/com/jessebrault/ssg/StaticSiteGenerator.groovy index 43240f9..f9b0e70 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/StaticSiteGenerator.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/StaticSiteGenerator.groovy @@ -1,9 +1,15 @@ package com.jessebrault.ssg +import com.jessebrault.ssg.buildscript.BuildScriptConfiguratorFactory import com.jessebrault.ssg.util.Diagnostic import java.util.function.Consumer interface StaticSiteGenerator { - boolean doBuild(String buildName, Consumer> diagnosticsConsumer) + boolean doBuild( + String buildName, + Collection configuratorFactories, + Map buildScriptArgs, + Consumer> diagnosticsConsumer + ) } \ No newline at end of file diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptRunner.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptRunner.groovy new file mode 100644 index 0000000..e4630af --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptRunner.groovy @@ -0,0 +1,71 @@ +package com.jessebrault.ssg.buildscript + +import com.jessebrault.ssg.util.ExtensionUtil +import groovy.transform.NullCheck +import groovy.transform.stc.ClosureParams +import groovy.transform.stc.SimpleType +import org.codehaus.groovy.control.CompilerConfiguration + +import java.util.function.Consumer + +@NullCheck +final class BuildScriptRunner { + + private static Collection runBase(BuildScriptBase base) { + base.run() + BuildSpecUtil.getBuilds(base.getBuildSpecs()) + } + + static Collection runClosureScript( + @DelegatesTo(value = BuildScriptBase, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.BuildScriptBase') + Closure scriptBody + ) { + def base = new BuildScriptBase() { + + @Override + Object run() { + scriptBody.delegate = this + scriptBody.resolveStrategy = Closure.DELEGATE_FIRST + scriptBody.call(this) + } + + } + runBase(base) + } + + private final GroovyClassLoader buildScriptClassLoader + + BuildScriptRunner(Collection classLoaderUrls) { + this.buildScriptClassLoader = new GroovyClassLoader( + Thread.currentThread().contextClassLoader, + new CompilerConfiguration().tap { + scriptBaseClass = BuildScriptBase.name + } + ) + classLoaderUrls.each(this.buildScriptClassLoader::addURL) + } + + GroovyClassLoader getBuildScriptClassLoader() { + this.buildScriptClassLoader + } + + Collection runBuildScript( + String scriptName, + Map binding, + Consumer configureBuildScript + ) { + Class scriptClass = this.buildScriptClassLoader.loadClass( + ExtensionUtil.stripExtension(scriptName), + true, + false, + false + ) + def scriptObject = scriptClass.getConstructor().newInstance() + assert scriptObject instanceof BuildScriptBase + scriptObject.setBinding(new Binding(binding)) + configureBuildScript.accept(scriptObject) + runBase(scriptObject) + } + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScripts.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScripts.groovy deleted file mode 100644 index 5a217a9..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScripts.groovy +++ /dev/null @@ -1,99 +0,0 @@ -package com.jessebrault.ssg.buildscript - -import groovy.transform.stc.ClosureParams -import groovy.transform.stc.SimpleType -import org.codehaus.groovy.control.CompilerConfiguration - -import java.util.function.Consumer - -final class BuildScripts { - - private static Collection runBase(BuildScriptBase base) { - base.run() - BuildSpecUtil.getBuilds(base.getBuildSpecs()) - } - - static Collection runBuildScript( - @DelegatesTo(value = BuildScriptBase, strategy = Closure.DELEGATE_FIRST) - @ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.BuildScriptBase') - Closure scriptBody - ) { - def base = new BuildScriptBase() { - - @Override - Object run() { - scriptBody.delegate = this - scriptBody.resolveStrategy = Closure.DELEGATE_FIRST - scriptBody.call(this) - } - - } - runBase(base) - } - - static Collection runBuildScript( - String scriptName, - GroovyScriptEngine engine, - Map binding = [:], - Consumer configureBuildScript = { } - ) { - engine.config = new CompilerConfiguration().tap { - scriptBaseClass = 'com.jessebrault.ssg.buildscript.BuildScriptBase' - } - - def base = engine.createScript(scriptName, new Binding(binding)) - assert base instanceof BuildScriptBase - configureBuildScript.accept(base) - runBase(base) - } - - @Deprecated - static Collection runBuildScript( - String scriptName, - URL scriptBaseDirUrl, - Collection otherUrls, - Map binding, - Consumer configureBuildScript - ) { - def engine = new GroovyScriptEngine([scriptBaseDirUrl, *otherUrls] as URL[]) - - engine.config = new CompilerConfiguration().tap { - scriptBaseClass = 'com.jessebrault.ssg.buildscript.BuildScriptBase' - } - - def base = engine.createScript(scriptName, new Binding(binding)) - assert base instanceof BuildScriptBase - configureBuildScript.accept(base) - runBase(base) - } - - @Deprecated - static Collection runBuildScript( - String scriptName, - URL scriptBaseDirUrl, - Collection otherUrls, - Map binding - ) { - runBuildScript(scriptName, scriptBaseDirUrl, otherUrls, binding) { } - } - - @Deprecated - static Collection runBuildScript( - String scriptName, - URL scriptBaseDirUrl, - Collection otherUrls - ) { - runBuildScript(scriptName, scriptBaseDirUrl, otherUrls, [:]) { } - } - - @Deprecated - static Collection runBuildScript( - String scriptName, - URL scriptBaseDirUrl - ) { - runBuildScript(scriptName, scriptBaseDirUrl, [], [:]) { } - } - - private BuildScripts() {} - -} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/DefaultBuildScriptConfiguratorFactory.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/DefaultBuildScriptConfiguratorFactory.groovy index 4abfea8..23cae26 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/DefaultBuildScriptConfiguratorFactory.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/DefaultBuildScriptConfiguratorFactory.groovy @@ -1,5 +1,6 @@ package com.jessebrault.ssg.buildscript +import com.jessebrault.ssg.html.PageToHtmlSpecProviders import com.jessebrault.ssg.html.PageToHtmlTaskFactory import com.jessebrault.ssg.html.TextToHtmlSpecProviders import com.jessebrault.ssg.html.TextToHtmlTaskFactory @@ -16,6 +17,7 @@ import groovy.transform.NullCheck import groovy.transform.TupleConstructor import java.util.function.Consumer +import java.util.function.Supplier @TupleConstructor(includeFields = true, defaults = false) @NullCheck(includeGenerated = true) @@ -23,8 +25,7 @@ import java.util.function.Consumer final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfiguratorFactory { private final File baseDir - private final File tmpDir - private final GroovyScriptEngine engine + private final Supplier classLoaderSupplier @Override Consumer get() { @@ -36,9 +37,9 @@ final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfigur types { textTypes << TextTypes.MARKDOWN - pageTypes << PageTypes.getGsp(['.gsp', '.ssg.gst'], this.tmpDir, this.engine) - templateTypes << TemplateTypes.getGsp(['.gsp', '.ssg.gst'], this.tmpDir, this.engine) - partTypes << PartTypes.getGsp(['.gsp', '.ssg.gst'], this.tmpDir, this.engine) + pageTypes << PageTypes.getGsp(['.gsp', '.ssg.gst'], this.classLoaderSupplier.get()) + templateTypes << TemplateTypes.getGsp(['.gsp', '.ssg.gst'], this.classLoaderSupplier.get()) + partTypes << PartTypes.getGsp(['.gsp', '.ssg.gst'], this.classLoaderSupplier.get()) } sources { base, types -> @@ -50,14 +51,14 @@ final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfigur taskFactories { base, sources -> register('textToHtml', TextToHtmlTaskFactory::new) { - it.specProvider += TextToHtmlSpecProviders.from(sources) + it.specsProvider += TextToHtmlSpecProviders.from(sources) it.allTextsProvider += sources.textsProvider it.allPartsProvider += sources.partsProvider it.allModelsProvider += sources.modelsProvider } register('pageToHtml', PageToHtmlTaskFactory::new) { - it.pagesProvider += sources.pagesProvider + it.specsProvider += PageToHtmlSpecProviders.from(sources.pagesProvider) it.allTextsProvider += sources.textsProvider it.allPartsProvider += sources.partsProvider it.allModelsProvider += sources.modelsProvider diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/SourceProviders.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/SourceProviders.groovy index b3c3f92..9c85e65 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/SourceProviders.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/SourceProviders.groovy @@ -13,8 +13,7 @@ import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import groovy.transform.TupleConstructor -@TupleConstructor(defaults = false) -@NullCheck(includeGenerated = true) +@NullCheck @EqualsAndHashCode final class SourceProviders { @@ -26,22 +25,24 @@ final class SourceProviders { sp0.modelsProvider + sp1.modelsProvider, sp0.pagesProvider + sp1.pagesProvider, sp0.templatesProvider + sp1.templatesProvider, - sp0.partsProvider + sp1.partsProvider + sp0.partsProvider + sp1.partsProvider, + sp0.custom + sp1.custom ) } static SourceProviders get(Map args) { new SourceProviders( - args?.textsProvider as CollectionProvider + args.textsProvider as CollectionProvider ?: CollectionProviders.getEmpty() as CollectionProvider, - args?.modelsProvider as CollectionProvider> + args.modelsProvider as CollectionProvider> ?: CollectionProviders.getEmpty() as CollectionProvider>, - args?.pagesProvider as CollectionProvider + args.pagesProvider as CollectionProvider ?: CollectionProviders.getEmpty() as CollectionProvider, - args?.templatesProvider as CollectionProvider