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