Home | History | Annotate | Download | only in build
      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 */
     16 package com.example.android.samples.build
     17 
     18 import freemarker.ext.dom.NodeModel
     19 import groovy.transform.Canonical
     20 import org.gradle.api.GradleException
     21 import org.gradle.api.Project
     22 import org.gradle.api.file.FileTree
     23 
     24 /**
     25  * Gradle extension that holds properties for sample generation.
     26  *
     27  * The sample generator needs a number of properties whose values can be
     28  * inferred by convention from a smaller number of initial properties.
     29  * This class defines fields for the initial properties, and getter
     30  * methods for the inferred properties. It also defines a small number
     31  * of convenience methods for setting up template-generation tasks.
     32  */
     33 @Canonical
     34 class SampleGenProperties {
     35     /**
     36      * The Gradle project that this extension is being applied to.
     37      */
     38     Project project
     39 
     40     /**
     41      *  Directory where the top-level sample project lives
     42      */
     43     def targetProjectPath
     44 
     45     /**
     46      * Relative path to samples/common directory
     47      */
     48     def pathToSamplesCommon
     49 
     50     /**
     51      * Relative path to build directory (platform/developers/build)
     52      */
     53     def pathToBuild
     54 
     55     /**
     56      * Java package name for the root package of this sample.
     57      */
     58     String targetSamplePackage
     59 
     60     /**
     61      *
     62      * @return The path to the sample project (as opposed to the top-level project, which
     63      *         what is that even for anyway?)
     64      */
     65     String targetSamplePath() {
     66         return "${targetProjectPath}/${targetSampleModule()}"
     67     }
     68 
     69 
     70 
     71     /**
     72      *
     73      * @return The path that contains common files -- can be cleaned without harming
     74      *         the sample
     75      */
     76     String targetCommonPath() {
     77         return "${targetSamplePath()}/src/common/java/com/example/android/common"
     78     }
     79 
     80     /**
     81      *
     82      * @return The path that contains template files -- can be cleaned without harming
     83      *         the sample
     84      */
     85     String targetTemplatePath() {
     86         return "${targetSamplePath()}/src/template"
     87     }
     88 
     89     /**
     90      * The name of this sample (and also of the corresponding .iml file)
     91      */
     92     String targetSampleName() {
     93         return project.file(targetProjectPath).getName()
     94     }
     95 
     96     /**
     97      * The name of the main module in the sample project
     98      */
     99     String targetSampleModule() {
    100         return "Application"
    101     }
    102 
    103     /**
    104      * The path to the template parameters file
    105      */
    106     String templateXml() {
    107         return "${targetProjectPath}/template-params.xml"
    108     }
    109 
    110     /**
    111      * Transforms a package name into a java-style OS dependent path
    112      * @param pkg cccc
    113      * @return The java-style path to the package's code
    114      */
    115     String packageAsPath(String pkg) {
    116         return pkg.replaceAll(/\./, File.separator)
    117     }
    118 
    119     /**
    120      * Transforms a path into a java-style package name
    121      * @param path The java-style path to the package's code
    122      * @return Name of the package to transform
    123      */
    124     String pathAsPackage(String path) {
    125         return path.replaceAll(File.separator, /\./)
    126     }
    127 
    128     /**
    129      * Returns the path to the common/build/templates directory
    130      */
    131     String templatesRoot() {
    132         return "${targetProjectPath}/${pathToBuild}/templates"
    133     }
    134 
    135 
    136     /**
    137      * Returns the path to common/src/java
    138      */
    139     String commonSourceRoot() {
    140         return "${targetProjectPath}/${pathToSamplesCommon}/src/java/com/example/android/common"
    141     }
    142 
    143     /**
    144      * Returns the path to the template include directory
    145      */
    146     String templatesInclude() {
    147         return "${templatesRoot()}/include"
    148     }
    149 
    150     /**
    151      * Returns the output file that will be generated for a particular
    152      * input, by replacing generic pathnames with project-specific pathnames
    153      * and dropping the .ftl extension from freemarker files.
    154      *
    155      * @param relativeInputPath Input file as a relative path from the template directory
    156      * @return Relative output file path
    157      */
    158     String getOutputForInput(String relativeInputPath) {
    159         String outputPath = relativeInputPath
    160         outputPath = outputPath.replaceAll('_PROJECT_', targetSampleName())
    161         outputPath = outputPath.replaceAll('_MODULE_', targetSampleModule())
    162         outputPath = outputPath.replaceAll('_PACKAGE_', packageAsPath(targetSamplePackage))
    163 
    164         // This is kind of a hack; IntelliJ picks up any and all subdirectories named .idea, so
    165         // named them ._IDE_ instead. TODO: remove when generating .idea projects is no longer necessary.
    166         outputPath = outputPath.replaceAll('_IDE_', "idea")
    167         outputPath = outputPath.replaceAll(/\.ftl$/, '')
    168 
    169         // Any file beginning with a dot won't get picked up, so rename them as necessary here.
    170         outputPath = outputPath.replaceAll('gitignore', '.gitignore')
    171         return outputPath
    172     }
    173 
    174     /**
    175      * Returns the tree(s) where the templates to be processed live. The template
    176      * input paths that are passed to
    177      * {@link SampleGenProperties#getOutputForInput(java.lang.String) getOutputForInput}
    178      * are relative to the dir element in each tree.
    179      */
    180     FileTree[] templates() {
    181         def result = []
    182         def xmlFile = project.file(templateXml())
    183         if (xmlFile.exists()) {
    184             def xml = new XmlSlurper().parse(xmlFile)
    185             xml.template.each { template ->
    186                 result.add(project.fileTree(dir: "${templatesRoot()}/${template.@src}"))
    187             }
    188         } else {
    189             result.add(project.fileTree(dir: "${templatesRoot()}/create"))
    190         }
    191         return result;
    192     }
    193 
    194     /**
    195      * Path(s) of the common directories to copy over to the sample project.
    196      */
    197     FileTree[] common() {
    198         def result = []
    199         def xmlFile = project.file(templateXml())
    200         if (xmlFile.exists()) {
    201             def xml = new XmlSlurper().parse(xmlFile)
    202             xml.common.each { common ->
    203                 println "Adding common/${common.@src} from ${commonSourceRoot()}"
    204                 result.add(project.fileTree (
    205                         dir: "${commonSourceRoot()}",
    206                         include: "${common.@src}/**/*"
    207                 ))
    208             }
    209         }
    210         return result
    211     }
    212 
    213     /**
    214      * Returns the hash to supply to the freemarker template processor.
    215      * This is loaded from the file specified by {@link SampleGenProperties#templateXml()}
    216      * if such a file exists, or synthesized with some default parameters if it does not.
    217      * In addition, some data about the current project is added to the "meta" key of the
    218      * hash.
    219      *
    220      * @return The hash to supply to freemarker
    221      */
    222     Map templateParams() {
    223         Map result = new HashMap();
    224 
    225         def xmlFile = project.file(templateXml())
    226         if (xmlFile.exists()) {
    227             // Parse the xml into Freemarker's DOM structure
    228             def params = freemarker.ext.dom.NodeModel.parse(xmlFile)
    229 
    230             // Move to the <sample> node and stuff that in our map
    231             def sampleNode = (NodeModel)params.exec(['/sample'])
    232             result.put("sample", sampleNode)
    233         } else {
    234             // Fake data for use on creation
    235             result.put("sample", [
    236                     name:targetSampleName(),
    237                     package:targetSamplePackage,
    238                     minSdk:4
    239             ])
    240         }
    241 
    242         // Extra data that some templates find useful
    243         result.put("meta", [
    244                 root: targetProjectPath,
    245                 module: targetSampleModule(),
    246                 common: pathToSamplesCommon,
    247                 build: pathToBuild,
    248         ])
    249         return result
    250     }
    251 
    252 
    253 
    254     /**
    255      * Generate default values for properties that can be inferred from an existing
    256      * generated project, unless those properties have already been
    257      * explicitly specified.
    258      */
    259     void getRefreshProperties() {
    260         if (!this.targetProjectPath) {
    261             this.targetProjectPath = project.projectDir
    262         }
    263         def xmlFile = project.file(templateXml())
    264         if (xmlFile.exists()) {
    265             println "Template XML: $xmlFile"
    266             def xml = new XmlSlurper().parse(xmlFile)
    267             this.targetSamplePackage = xml.package.toString()
    268             println "Target Package: $targetSamplePackage"
    269         }
    270     }
    271 
    272     /**
    273      * Generate default values for creation properties, unless those properties
    274      * have already been explicitly specified. This method will attempt to get
    275      * these properties interactively from the user if necessary.
    276      */
    277     void getCreationProperties() {
    278         def calledFrom = project.hasProperty('calledFrom') ? new File(project.calledFrom)
    279                 : project.projectDir
    280         calledFrom = calledFrom.getCanonicalPath()
    281         println('\n\n\nReady to create project...')
    282 
    283         if (project.hasProperty('pathToSamplesCommon')) {
    284             this.pathToSamplesCommon = project.pathToSamplesCommon
    285         } else {
    286             throw new GradleException (
    287                     'create task requires project property pathToSamplesCommon')
    288         }
    289 
    290 
    291         if (project.hasProperty('pathToBuild')) {
    292             this.pathToBuild = project.pathToBuild
    293         } else {
    294             throw new GradleException ('create task requires project property pathToBuild')
    295         }
    296 
    297         if (!this.targetProjectPath) {
    298             if (project.hasProperty('out')) {
    299                 this.targetProjectPath = project.out
    300             } else {
    301                 this.targetProjectPath = System.console().readLine(
    302                         "\noutput directory [$calledFrom]:")
    303                 if (this.targetProjectPath.length() <= 0) {
    304                     this.targetProjectPath = calledFrom
    305                 }
    306             }
    307         }
    308 
    309         if (!this.targetSamplePackage) {
    310             def defaultPackage = "com.example.android." +
    311                     this.targetSampleName().toLowerCase()
    312             this.targetSamplePackage = System.console().readLine(
    313                     "\nsample package name[$defaultPackage]:")
    314             if (this.targetSamplePackage.length() <= 0) {
    315                 this.targetSamplePackage = defaultPackage
    316             }
    317         }
    318     }
    319 
    320 }
    321