xref: /aosp_15_r20/developers/build/buildSrc/src/main/groovy/com/example/android/samples/build/ApplyTemplates.groovy (revision d353a188ca6ec4b5eba25b5fbd7bcb8ce61322fb)
1/*
2* Copyright 2013 The Android Open Source Project
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8*     http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16package com.example.android.samples.build
17
18import freemarker.cache.FileTemplateLoader
19import freemarker.cache.MultiTemplateLoader
20import freemarker.cache.TemplateLoader
21import freemarker.template.Configuration
22import freemarker.template.DefaultObjectWrapper
23import freemarker.template.Template
24import org.gradle.api.GradleException
25import org.gradle.api.file.FileVisitDetails
26import org.gradle.api.tasks.InputDirectory
27import org.gradle.api.tasks.OutputDirectory
28import org.gradle.api.tasks.SourceTask
29import org.gradle.api.tasks.TaskAction
30
31
32class ApplyTemplates extends SourceTask {
33    /**
34     * Freemarker context object
35     */
36    def Configuration cfg = new freemarker.template.Configuration()
37
38    /**
39     * The root directory for output files. All output file paths
40     * are assumed to be relative to this root.
41     */
42    @OutputDirectory
43    public outputDir = project.projectDir
44
45    /**
46     * Include directory. The templates in this directory will not be
47     * processed directly, but will be accessible to other templates
48     * via the <#include> directive.
49     */
50    def include = project.file("$project.projectDir/templates/include")
51
52    /**
53     * List of file extensions that indicate a file to be processed, rather
54     * than simply copied.
55     */
56    def extensionsToProcess = ['ftl']
57
58    /**
59     * List of file extensions that should be completely ignored by this
60     * task. File extensions that appear in neither this list nor the list
61     * specified by {@link #extensionsToProcess} are copied into the destination
62     * without processing.
63     */
64    def extensionsToIgnore = ['ftli']
65
66    /**
67     * A String -> String closure that transforms a (relative) input path into a
68     * (relative) output path. This closure is responsible for any alterations to
69     * the output path, including pathname substitution and extension removal.
70     */
71    Closure<String> filenameTransform
72
73    /**
74     * The hash which will be passed to the freemarker template engine. This hash
75     * is used by the freemarker script as input data.
76     * The hash should contain a key named "meta". The template processor will add
77     * processing data to this key.
78     */
79    def parameters
80
81    /**
82     * The main action for this task. Visits each file in the source directories and
83     * either processes, copies, or ignores it. The action taken for each file depends
84     * on the contents of {@link #extensionsToProcess} and {@link #extensionsToIgnore}.
85     */
86    @TaskAction
87    def applyTemplate() {
88        // Create a list of Freemarker template loaders based on the
89        // source tree(s) of this task. The loader list establishes a virtual
90        // file system for freemarker templates; the template language can
91        // load files, and each load request will have its path resolved
92        // against this set of loaders.
93        println "Gathering template load locations:"
94        def List loaders = []
95        source.asFileTrees.each {
96            src ->
97                println "    ${src.dir}"
98                loaders.add(0, new FileTemplateLoader(project.file(src.dir)))
99        }
100
101        // Add the include path(s) to the list of loaders.
102        println "Gathering template include locations:"
103        include = project.fileTree(include)
104        include.asFileTrees.each {
105            inc ->
106                println "    ${inc.dir}"
107                loaders.add(0, new FileTemplateLoader(project.file(inc.dir)))
108        }
109        // Add the loaders to the freemarker config
110        cfg.setTemplateLoader(new MultiTemplateLoader(loaders.toArray(new TemplateLoader[1])))
111
112        // Set the wrapper that will be used to convert the template parameters hash into
113        // the internal freemarker data model. The default wrapper is capable of handling a
114        // mix of POJOs/POGOs and XML nodes, so we'll use that.
115        cfg.setObjectWrapper(new DefaultObjectWrapper())
116
117        // This is very much like setting the target SDK level in Android.
118        cfg.setIncompatibleEnhancements("2.3.20")
119
120        // Add an implicit <#include 'common.ftl' to the top of every file.
121        // TODO: should probably be a parameter instead of hardcoded like this.
122        cfg.addAutoInclude('common.ftl')
123
124        // Visit every file in the source tree(s)
125        def processTree = source.getAsFileTree()
126        processTree.visit {
127            FileVisitDetails input ->
128                def inputFile = input.getRelativePath().toString()
129                def outputFile = input.getRelativePath().getFile(project.file(outputDir))
130                // Get the input and output files, and make sure the output path exists
131                def renamedOutput = filenameTransform(outputFile.toString())
132                outputFile = project.file(renamedOutput)
133
134                if (input.directory){
135                    // create the output directory. This probably will have already been
136                    // created as part of processing the files *in* the directory, but
137                    // do it here anyway to support empty directories.
138                    outputFile.mkdirs()
139                } else {
140                    // We may or may not see the directory before we see the files
141                    // in that directory, so create it here
142                    outputFile.parentFile.mkdirs()
143
144                    // Check the input file extension against the process/ignore list
145                    def extension = "NONE"
146                    def extensionPattern = ~/.*\.(\w*)$/
147                    def extensionMatch = extensionPattern.matcher(inputFile)
148                    if (extensionMatch.matches()) {
149                        extension = extensionMatch[0][1]
150                    }
151                    // If the extension is in the process list, put the input through freemarker
152                    if (extensionsToProcess.contains(extension)){
153                        print '[freemarker] PROCESS: '
154                        println "$inputFile -> $outputFile"
155
156                        try {
157                            def Template tpl = this.cfg.getTemplate(inputFile)
158                            def FileWriter out = new FileWriter(outputFile)
159
160                            // Add the output file path to parameters.meta so that the freemarker
161                            // script can access it.
162                            parameters.meta.put("outputFile", "${outputFile}")
163                            tpl.process(parameters, out)
164                        } catch (e) {
165                            println e.message
166                            throw new GradleException("Error processing ${inputFile}: ${e.message}")
167                        }
168                    } else if (!extensionsToIgnore.contains(extension)) {
169                        // if it's not processed and not ignored, then it must be copied.
170                        print '[freemarker] COPY: '
171                        println "$inputFile -> $outputFile"
172                        input.copyTo(outputFile);
173                    }
174                }
175        }
176    }
177}
178