SourceHtmlPlugin.groovy
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2018-2019 Andres Almiray.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     https://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package org.kordamp.gradle.plugin.sourcehtml
019 
020 import com.bmuschko.gradle.java2html.ConvertCodeTask
021 import com.bmuschko.gradle.java2html.GenerateOverviewTask
022 import groovy.transform.CompileDynamic
023 import groovy.transform.CompileStatic
024 import org.gradle.BuildAdapter
025 import org.gradle.api.Action
026 import org.gradle.api.Project
027 import org.gradle.api.Task
028 import org.gradle.api.artifacts.Configuration
029 import org.gradle.api.artifacts.DependencySet
030 import org.gradle.api.artifacts.ResolvableDependencies
031 import org.gradle.api.artifacts.dsl.DependencyHandler
032 import org.gradle.api.file.FileCollection
033 import org.gradle.api.invocation.Gradle
034 import org.gradle.api.tasks.Copy
035 import org.gradle.api.tasks.TaskProvider
036 import org.gradle.api.tasks.bundling.Jar
037 import org.kordamp.gradle.plugin.AbstractKordampPlugin
038 import org.kordamp.gradle.plugin.base.BasePlugin
039 import org.kordamp.gradle.plugin.base.ProjectConfigurationExtension
040 import org.kordamp.gradle.plugin.base.plugins.SourceHtml
041 
042 import static org.kordamp.gradle.PluginUtils.resolveEffectiveConfig
043 import static org.kordamp.gradle.plugin.base.BasePlugin.isRootProject
044 
045 /**
046  @author Andres Almiray
047  @since 0.5.0
048  */
049 @CompileStatic
050 class SourceHtmlPlugin extends AbstractKordampPlugin {
051     static final String AGGREGATE_SOURCE_HTML_TASK_NAME = 'aggregateSourceHtml'
052     static final String AGGREGATE_CONVERT_CODE_TO_HTML_TASK_NAME = 'aggregateConvertCodeToHtml'
053     static final String AGGREGATE_GENERATE_SOURCE_HTML_OVERVIEW_TASK_NAME = 'aggregateGenerateSourceHtmlOverview'
054     static final String SOURCE_HTML_TASK_NAME = 'sourceHtml'
055     static final String CONFIGURATION_NAME = 'java2html'
056 
057     Project project
058 
059     void apply(Project project) {
060         this.project = project
061 
062         if (isRootProject(project)) {
063             project.childProjects.values().each {
064                 configureProject(it)
065             }
066         }
067         configureProject(project)
068     }
069 
070     static void applyIfMissing(Project project) {
071         if (!project.plugins.findPlugin(SourceHtmlPlugin)) {
072             project.pluginManager.apply(SourceHtmlPlugin)
073         }
074     }
075 
076     private void configureProject(Project project) {
077         if (hasBeenVisited(project)) {
078             return
079         }
080         setVisited(project, true)
081 
082         BasePlugin.applyIfMissing(project)
083 
084         Configuration configuration = project.configurations.create(CONFIGURATION_NAME)
085             .setVisible(false)
086             .setTransitive(true)
087             .setDescription('The Java2HTML library to be used for this project.')
088 
089         configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
090             @SuppressWarnings('UnusedMethodParameter')
091             void execute(ResolvableDependencies resolvableDependencies) {
092                 DependencyHandler dependencyHandler = project.dependencies
093                 DependencySet dependencies = configuration.dependencies
094                 dependencies.add(dependencyHandler.create('de.java2html:java2html:5.0'))
095             }
096         })
097         project.pluginManager.withPlugin('java-base') {
098             project.afterEvaluate {
099                 ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
100 
101                 if (effectiveConfig.sourceHtml.enabled) {
102                     if (configureSourceHtmlTask(project, configuration)) {
103                         effectiveConfig.sourceHtml.projects() << project
104                     else {
105                         effectiveConfig.sourceHtml.enabled = false
106                     }
107                 }
108                 setEnabled(effectiveConfig.sourceHtml.enabled)
109             }
110         }
111 
112         if (isRootProject(project&& !project.childProjects.isEmpty()) {
113             TaskProvider<Copy> sourceHtmlTask = project.tasks.register(AGGREGATE_SOURCE_HTML_TASK_NAME, Copy,
114                 new Action<Copy>() {
115                     @Override
116                     void execute(Copy t) {
117                         t.group = 'Documentation'
118                         t.description = 'Generates a HTML report of the source code.'
119                         t.destinationDir = project.file("${project.buildDir}/docs/source-html")
120                         t.enabled = false
121                     }
122                 })
123 
124             TaskProvider<Jar> sourceHtmlJarTask = project.tasks.register(AGGREGATE_SOURCE_HTML_TASK_NAME + 'Jar', Jar,
125                 new Action<Jar>() {
126                     @Override
127                     void execute(Jar t) {
128                         t.dependsOn sourceHtmlTask
129                         t.group = 'Documentation'
130                         t.description = 'An archive of the HTML report the source code.'
131                         t.classifier = 'sources-html'
132                         t.from sourceHtmlTask.get().destinationDir
133                         t.enabled = false
134                     }
135                 })
136 
137             project.gradle.addBuildListener(new BuildAdapter() {
138                 @Override
139                 void projectsEvaluated(Gradle gradle) {
140                     configureAggregateSourceHtmlTask(project, configuration, sourceHtmlTask, sourceHtmlJarTask)
141                 }
142             })
143         }
144     }
145 
146     private boolean configureSourceHtmlTask(Project project, Configuration configuration) {
147         ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
148         if (!effectiveConfig.sourceHtml.enabled) {
149             return false
150         }
151 
152         Task classesTask = project.tasks.findByName('classes')
153         if (!classesTask) {
154             return false
155         }
156 
157         effectiveConfig.sourceHtml.srcDirs = resolveSrcDirs(project, effectiveConfig.sourceHtml.conversion.srcDirs)
158 
159         TaskProvider<ConvertCodeTask> convertCodeTask = project.tasks.register('convertCodeToHtml', ConvertCodeTask,
160             new Action<ConvertCodeTask>() {
161                 @Override
162                 void execute(ConvertCodeTask t) {
163                     t.enabled = !effectiveConfig.sourceHtml.srcDirs.isEmpty()
164                     t.dependsOn classesTask
165                     t.group = 'Documentation'
166                     t.description = 'Converts source code into HTML.'
167                     t.classpath = configuration.asFileTree
168                     t.srcDirs = effectiveConfig.sourceHtml.srcDirs
169                     t.destDir = effectiveConfig.sourceHtml.conversion.destDir
170                     t.includes = effectiveConfig.sourceHtml.conversion.includes
171                     t.outputFormat = effectiveConfig.sourceHtml.conversion.outputFormat
172                     t.tabs = effectiveConfig.sourceHtml.conversion.tabs
173                     t.style = effectiveConfig.sourceHtml.conversion.style
174                     t.showLineNumbers = effectiveConfig.sourceHtml.conversion.showLineNumbers
175                     t.showFileName = effectiveConfig.sourceHtml.conversion.showFileName
176                     t.showDefaultTitle = effectiveConfig.sourceHtml.conversion.showDefaultTitle
177                     t.showTableBorder = effectiveConfig.sourceHtml.conversion.showTableBorder
178                     t.includeDocumentHeader = effectiveConfig.sourceHtml.conversion.includeDocumentHeader
179                     t.includeDocumentFooter = effectiveConfig.sourceHtml.conversion.includeDocumentFooter
180                     t.addLineAnchors = effectiveConfig.sourceHtml.conversion.addLineAnchors
181                     t.lineAnchorPrefix = effectiveConfig.sourceHtml.conversion.lineAnchorPrefix
182                     t.horizontalAlignment = effectiveConfig.sourceHtml.conversion.horizontalAlignment
183                     t.useShortFileName = effectiveConfig.sourceHtml.conversion.useShortFileName
184                     t.overwrite = effectiveConfig.sourceHtml.conversion.overwrite
185                 }
186             })
187 
188         TaskProvider<GenerateOverviewTask> generateOverviewTask = project.tasks.register('generateSourceHtmlOverview', GenerateOverviewTask,
189             new Action<GenerateOverviewTask>() {
190                 @Override
191                 void execute(GenerateOverviewTask t) {
192                     t.enabled = !effectiveConfig.sourceHtml.srcDirs.isEmpty()
193                     t.dependsOn convertCodeTask
194                     t.group = 'Documentation'
195                     t.description = 'Generate an overview of converted source code.'
196                     t.srcDirs = project.files(effectiveConfig.sourceHtml.conversion.destDir)
197                     t.destDir = effectiveConfig.sourceHtml.overview.destDir
198                     t.pattern = effectiveConfig.sourceHtml.overview.pattern
199                     t.windowTitle = effectiveConfig.sourceHtml.overview.windowTitle
200                     t.docTitle = effectiveConfig.sourceHtml.overview.docTitle
201                     t.docDescription = effectiveConfig.sourceHtml.overview.docDescription ?: ''
202                     t.icon = effectiveConfig.sourceHtml.overview.icon
203                     t.stylesheet = effectiveConfig.sourceHtml.overview.stylesheet
204                 }
205             })
206 
207         TaskProvider<Copy> sourceHtmlTask = project.tasks.register(SOURCE_HTML_TASK_NAME, Copy,
208             new Action<Copy>() {
209                 @Override
210                 void execute(Copy t) {
211                     t.enabled = !effectiveConfig.sourceHtml.srcDirs.isEmpty()
212                     t.dependsOn generateOverviewTask
213                     t.group = 'Documentation'
214                     t.description = 'Generates a HTML report of the source code.'
215                     t.destinationDir = project.file("${project.buildDir}/docs/source-html")
216                     t.from convertCodeTask.get().destDir
217                     t.from generateOverviewTask.get().destDir
218                 }
219             })
220 
221         project.tasks.register(SOURCE_HTML_TASK_NAME + 'Jar', Jar,
222             new Action<Jar>() {
223                 @Override
224                 void execute(Jar t) {
225                     t.enabled = !effectiveConfig.sourceHtml.srcDirs.isEmpty()
226                     t.dependsOn sourceHtmlTask
227                     t.group = 'Documentation'
228                     t.description = 'An archive of the HTML report the source code.'
229                     t.classifier = 'sources-html'
230                     t.from sourceHtmlTask.get().destinationDir
231                 }
232             })
233 
234         return !effectiveConfig.sourceHtml.srcDirs.isEmpty()
235     }
236 
237     private void configureAggregateSourceHtmlTask(Project project, Configuration configuration, TaskProvider<Copy> sourceHtmlTask, TaskProvider<Jar> sourceHtmlJarTask) {
238         ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
239 
240         Set<Project> projects = new LinkedHashSet<>()
241         FileCollection srcdirs = project.files()
242         List<Task> sourceHtmlTasks = []
243 
244         project.childProjects.values().each {
245             SourceHtml e = resolveEffectiveConfig(it).sourceHtml
246             if (!e.enabled || effectiveConfig.sourceHtml.excludedProjects().intersect(e.projects())) return
247             projects.addAll(e.projects())
248             srcdirs = project.files(srcdirs, e.srcDirs)
249         }
250 
251         projects.each p ->
252             sourceHtmlTasks << p.tasks.findByName('sourceHtml')
253         }
254 
255         TaskProvider<ConvertCodeTask> convertCodeTask = project.tasks.register(AGGREGATE_CONVERT_CODE_TO_HTML_TASK_NAME, ConvertCodeTask,
256             new Action<ConvertCodeTask>() {
257                 @Override
258                 void execute(ConvertCodeTask t) {
259                     t.dependsOn sourceHtmlTasks
260                     t.group = 'Documentation'
261                     t.description = 'Converts source code into HTML.'
262                     t.classpath = configuration.asFileTree
263                     t.srcDirs = srcdirs
264                     t.destDir = effectiveConfig.sourceHtml.conversion.destDir
265                     t.includes = effectiveConfig.sourceHtml.conversion.includes
266                     t.outputFormat = effectiveConfig.sourceHtml.conversion.outputFormat
267                     t.tabs = effectiveConfig.sourceHtml.conversion.tabs
268                     t.style = effectiveConfig.sourceHtml.conversion.style
269                     t.showLineNumbers = effectiveConfig.sourceHtml.conversion.showLineNumbers
270                     t.showFileName = effectiveConfig.sourceHtml.conversion.showFileName
271                     t.showDefaultTitle = effectiveConfig.sourceHtml.conversion.showDefaultTitle
272                     t.showTableBorder = effectiveConfig.sourceHtml.conversion.showTableBorder
273                     t.includeDocumentHeader = effectiveConfig.sourceHtml.conversion.includeDocumentHeader
274                     t.includeDocumentFooter = effectiveConfig.sourceHtml.conversion.includeDocumentFooter
275                     t.addLineAnchors = effectiveConfig.sourceHtml.conversion.addLineAnchors
276                     t.lineAnchorPrefix = effectiveConfig.sourceHtml.conversion.lineAnchorPrefix
277                     t.horizontalAlignment = effectiveConfig.sourceHtml.conversion.horizontalAlignment
278                     t.useShortFileName = effectiveConfig.sourceHtml.conversion.useShortFileName
279                     t.overwrite = effectiveConfig.sourceHtml.conversion.overwrite
280                 }
281             })
282 
283         TaskProvider<GenerateOverviewTask> generateOverviewTask = project.tasks.register(AGGREGATE_GENERATE_SOURCE_HTML_OVERVIEW_TASK_NAME, GenerateOverviewTask,
284             new Action<GenerateOverviewTask>() {
285                 @Override
286                 void execute(GenerateOverviewTask t) {
287                     t.dependsOn convertCodeTask
288                     t.group = 'Documentation'
289                     t.description = 'Generate an overview of converted source code.'
290                     t.srcDirs = project.files(effectiveConfig.sourceHtml.conversion.destDir)
291                     t.destDir = effectiveConfig.sourceHtml.overview.destDir
292                     t.pattern = effectiveConfig.sourceHtml.overview.pattern
293                     t.windowTitle = effectiveConfig.sourceHtml.overview.windowTitle
294                     t.docTitle = effectiveConfig.sourceHtml.overview.docTitle
295                     t.docDescription = effectiveConfig.sourceHtml.overview.docDescription ?: ''
296                     t.icon = effectiveConfig.sourceHtml.overview.icon
297                     t.stylesheet = effectiveConfig.sourceHtml.overview.stylesheet
298                 }
299             })
300 
301         sourceHtmlTask.configure(new Action<Copy>() {
302             @Override
303             void execute(Copy t) {
304                 t.dependsOn generateOverviewTask
305                 t.from convertCodeTask.get().destDir
306                 t.from generateOverviewTask.get().destDir
307                 t.enabled = true
308             }
309         })
310 
311         sourceHtmlJarTask.configure(new Action<Jar>() {
312             @Override
313             void execute(Jar t) {
314                 t.enabled = true
315             }
316         })
317     }
318 
319     private boolean hasJavaPlugin(Project project) {
320         project.pluginManager.hasPlugin('java-base')
321     }
322 
323     private boolean hasGroovyPlugin(Project project) {
324         project.pluginManager.hasPlugin('groovy-base')
325     }
326 
327     @CompileDynamic
328     private FileCollection resolveSrcDirs(Project project, FileCollection files) {
329         try {
330             if (project.sourceSets.main) {
331                 if (hasGroovyPlugin(project)) {
332                     return project.files(project.files(files,
333                         project.sourceSets.main.groovy.srcDirs,
334                         project.sourceSets.main.java.srcDirs).files.findAll file ->
335                         file.exists()
336                     })
337                 else if (hasJavaPlugin(project)) {
338                     return project.files(
339                         project.files(files,
340                             project.sourceSets.main.java.srcDirs).files.findAll file ->
341                             file.exists()
342                         })
343                 }
344             }
345         catch (Exception ignored) {
346             // ignore this project
347             return project.files()
348         }
349 
350         files
351     }
352 }