mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-11-14 07:42:05 +08:00
feat(templates): add API to generate Java source files
This commit is contained in:
Binary file not shown.
@@ -20,5 +20,5 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.subprojects.javaCompiler)
|
||||
api(projects.subprojects.javaCompiler)
|
||||
}
|
||||
@@ -25,6 +25,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.subprojects.javapoet)
|
||||
|
||||
implementation(projects.common)
|
||||
implementation(projects.logger)
|
||||
implementation(projects.resources)
|
||||
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.templates.base
|
||||
|
||||
import com.squareup.javapoet.ClassName
|
||||
import com.squareup.javapoet.JavaFile
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
|
||||
/**
|
||||
* Utility for building Java source files.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class JavaSourceBuilder {
|
||||
|
||||
/**
|
||||
* The type of Java source. A [SourceType] creates a [TypeSpec.Builder] and optionally
|
||||
* pre-configures it.
|
||||
*/
|
||||
interface SourceType {
|
||||
|
||||
/**
|
||||
* Create the [TypeSpec.Builder] for building this type of Java source file.
|
||||
*/
|
||||
fun builder(klass: ClassName): TypeSpec.Builder
|
||||
}
|
||||
|
||||
/**
|
||||
* A class type.
|
||||
*/
|
||||
class ClassType : SourceType {
|
||||
|
||||
override fun builder(klass: ClassName): TypeSpec.Builder {
|
||||
return TypeSpec.classBuilder(klass)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface type.
|
||||
*/
|
||||
class InterfaceType : SourceType {
|
||||
|
||||
override fun builder(klass: ClassName): TypeSpec.Builder {
|
||||
return TypeSpec.interfaceBuilder(klass)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum type.
|
||||
*/
|
||||
class EnumType : SourceType {
|
||||
|
||||
override fun builder(klass: ClassName): TypeSpec.Builder {
|
||||
return TypeSpec.enumBuilder(klass)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An annotation interface type.
|
||||
*/
|
||||
class AnnotationType : SourceType {
|
||||
|
||||
override fun builder(klass: ClassName): TypeSpec.Builder {
|
||||
return TypeSpec.annotationBuilder(klass)
|
||||
}
|
||||
}
|
||||
|
||||
private val files = hashSetOf<JavaFile>()
|
||||
|
||||
/**
|
||||
* Creates a new Java class.
|
||||
*
|
||||
* @param packageName The package name of the class.
|
||||
* @param className The name of the class.
|
||||
* @param configure Function to configure the [TypeSpec.Builder].
|
||||
*/
|
||||
fun createClass(packageName: String, className: String, configure: TypeSpec.Builder.() -> Unit) {
|
||||
return createJavaFile(packageName, className, ClassType(), configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Java enum class.
|
||||
*
|
||||
* @param packageName The package name of the class.
|
||||
* @param className The name of the class.
|
||||
* @param configure Function to configure the [TypeSpec.Builder].
|
||||
*/
|
||||
fun createEnum(packageName: String, className: String, configure: TypeSpec.Builder.() -> Unit) {
|
||||
return createJavaFile(packageName, className, EnumType(), configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Java interface.
|
||||
*
|
||||
* @param packageName The package name of the interface.
|
||||
* @param className The name of the interface.
|
||||
* @param configure Function to configure the [TypeSpec.Builder].
|
||||
*/
|
||||
fun createInterface(packageName: String, className: String, configure: TypeSpec.Builder.() -> Unit
|
||||
) {
|
||||
return createJavaFile(packageName, className, InterfaceType(), configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Java annotation interface.
|
||||
*
|
||||
* @param packageName The package name of the interface.
|
||||
* @param className The name of the interface.
|
||||
* @param configure Function to configure the [TypeSpec.Builder].
|
||||
*/
|
||||
fun createAnnotation(packageName: String, className: String,
|
||||
configure: TypeSpec.Builder.() -> Unit
|
||||
) {
|
||||
return createJavaFile(packageName, className, AnnotationType(), configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Java class.
|
||||
*
|
||||
* @param packageName The package name of the class.
|
||||
* @param className The name of the class.
|
||||
* @param type The type of the class.
|
||||
* @param configure Function to configure the [TypeSpec.Builder].
|
||||
*/
|
||||
fun createJavaFile(packageName: String, className: String, type: SourceType,
|
||||
configure: TypeSpec.Builder.() -> Unit
|
||||
) {
|
||||
val klass = ClassName.get(packageName, className)
|
||||
val builder = type.builder(klass).apply(configure)
|
||||
val file = JavaFile.builder(packageName, builder.build())
|
||||
file.skipJavaLangImports(true)
|
||||
files.add(file.build())
|
||||
}
|
||||
|
||||
internal fun ModuleTemplateBuilder.write() {
|
||||
for (file in files) {
|
||||
file.writeTo(mainJavaSrc())
|
||||
}
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.templates.base
|
||||
|
||||
import com.itsaky.androidide.templates.ModuleTemplate
|
||||
import com.itsaky.androidide.templates.ModuleTemplateData
|
||||
import com.itsaky.androidide.templates.RecipeExecutor
|
||||
import com.itsaky.androidide.templates.SrcSet
|
||||
import com.itsaky.androidide.templates.TemplateBuilder
|
||||
import com.itsaky.androidide.templates.TemplateRecipe
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Abstract [TemplateBuilder] for building module projects.
|
||||
*
|
||||
* @property name The name of the module (gradle format, e.g. ':app').
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class ModuleTemplateBuilder :
|
||||
ExecutorDataTemplateBuilder<ModuleTemplate, ModuleTemplateData>() {
|
||||
|
||||
protected val dependencies = hashSetOf<Dependency>()
|
||||
protected val javaSourceBuilder = JavaSourceBuilder()
|
||||
internal var _name: String? = null
|
||||
|
||||
val name: String
|
||||
get() = checkNotNull(_name) { "Name not set to module template" }
|
||||
|
||||
protected open fun RecipeExecutor.preConfig() {}
|
||||
protected open fun RecipeExecutor.postConfig() {}
|
||||
|
||||
/**
|
||||
* Get the asset path for base module project template.
|
||||
*
|
||||
* @param path The path for the asset.
|
||||
* @see com.itsaky.androidide.templates.base.baseAsset
|
||||
*/
|
||||
fun baseAsset(path: String) = baseAsset("module", path)
|
||||
|
||||
/**
|
||||
* Get the `build.gradle[.kts]` file for this module.l
|
||||
*/
|
||||
fun buildGradleFile(): File {
|
||||
return data.buildGradleFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `java` sources directory for the [SrcSet.Main] source set.
|
||||
*/
|
||||
fun mainJavaSrc(): File {
|
||||
return javaSrc(SrcSet.Main)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `resources` directory for the [SrcSet.Main] source set.
|
||||
*/
|
||||
fun mainResourcesDir(): File {
|
||||
return javaSrc(SrcSet.Main)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `resources` directory for the given [source set][srcSet].
|
||||
*/
|
||||
fun resourcesDir(srcSet: SrcSet): File {
|
||||
return File(srcFolder(srcSet), "resources").also { it.mkdirs() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `java` source directory for the given [srcSet].
|
||||
*/
|
||||
fun javaSrc(srcSet: SrcSet): File {
|
||||
return File(srcFolder(srcSet), "java").also { it.mkdirs() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source directory for the given [srcSet].
|
||||
*
|
||||
* @param srcSet The type of source directory.
|
||||
*/
|
||||
fun srcFolder(srcSet: SrcSet): File {
|
||||
return data.srcFolder(srcSet)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the Java source files for this module.
|
||||
*
|
||||
* @param configure Function for configuring the Java source files.
|
||||
*/
|
||||
fun java(configure: JavaSourceBuilder.() -> Unit) {
|
||||
javaSourceBuilder.apply(configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common pre-recipe configuration.
|
||||
*
|
||||
* @param moduleData Called after the base configuration is setup and before the [recipe] is executed. Caller can perform its own
|
||||
* pre-recipe configuration here. Returns the [ModuleTemplateData] instance.
|
||||
*/
|
||||
fun commonPreRecipe(extraConfig: ModuleTemplateConfigurator = {},
|
||||
moduleData: RecipeExecutor.() -> ModuleTemplateData
|
||||
): TemplateRecipe = {
|
||||
val data = moduleData()
|
||||
|
||||
this@ModuleTemplateBuilder._data = data
|
||||
this@ModuleTemplateBuilder._name = data.name
|
||||
this@ModuleTemplateBuilder._executor = this
|
||||
|
||||
data.projectDir.mkdirs()
|
||||
|
||||
// Create the main source directory
|
||||
srcFolder(SrcSet.Main)
|
||||
|
||||
preConfig()
|
||||
extraConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Common post-recipe configuration.
|
||||
*
|
||||
* @param extraConfig Called after the [recipe] is executed. Caller can perform its own
|
||||
* post-recipe configuration here.
|
||||
*/
|
||||
fun commonPostRecipe(extraConfig: ModuleTemplateConfigurator = {}): TemplateRecipe = {
|
||||
|
||||
// Write the java source files
|
||||
javaSourceBuilder.apply {
|
||||
write()
|
||||
}
|
||||
|
||||
// Write build.gradle[.kts]
|
||||
buildGradle()
|
||||
|
||||
postConfig()
|
||||
extraConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given dependency to the `build.gradle[.kts]` file for this module.
|
||||
*/
|
||||
fun addDependency(dependency: Dependency) {
|
||||
this.dependencies.add(dependency)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the `build.gradle[.kts]` file for this module.
|
||||
*/
|
||||
abstract fun RecipeExecutor.buildGradle()
|
||||
|
||||
override fun buildInternal(): ModuleTemplate {
|
||||
return ModuleTemplate(name, templateName!!, thumb!!, widgets!!, recipe!!)
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.templates.base
|
||||
|
||||
import com.itsaky.androidide.templates.ModuleTemplate
|
||||
import com.itsaky.androidide.templates.ModuleTemplateData
|
||||
import com.itsaky.androidide.templates.ProjectTemplate
|
||||
import com.itsaky.androidide.templates.ProjectTemplateData
|
||||
import com.itsaky.androidide.templates.base.root.buildGradleSrcGroovy
|
||||
import com.itsaky.androidide.templates.base.root.buildGradleSrcKts
|
||||
import com.itsaky.androidide.templates.base.root.gradleWrapperJar
|
||||
import com.itsaky.androidide.templates.base.root.gradleWrapperProps
|
||||
import com.itsaky.androidide.templates.base.root.settingsGradleSrcGroovy
|
||||
import com.itsaky.androidide.templates.base.root.settingsGradleSrcKts
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Builder for building project templates.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ProjectTemplateBuilder : ExecutorDataTemplateBuilder<ProjectTemplate, ProjectTemplateData>() {
|
||||
|
||||
private var _defModule: ModuleTemplateData? = null
|
||||
private val defModuleTemplate: ModuleTemplate? = null
|
||||
|
||||
internal val modules = mutableListOf<ModuleTemplate>()
|
||||
|
||||
private val defModule: ModuleTemplateData
|
||||
get() = checkNotNull(_defModule) { "Module template data not set" }
|
||||
|
||||
/**
|
||||
* Set the template data that will be used to create the default application module in the project.
|
||||
*
|
||||
* @param data The module template data to use.
|
||||
*/
|
||||
fun setDefaultModuleData(data: ModuleTemplateData) {
|
||||
_defModule = data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the asset path for base root project template.
|
||||
*
|
||||
* @param path The path to the asset.
|
||||
* @see com.itsaky.androidide.templates.base.baseAsset
|
||||
*/
|
||||
fun baseAsset(path: String) = baseAsset("root", path)
|
||||
|
||||
/**
|
||||
* Get the `build.gradle[.kts] file for the project.
|
||||
*/
|
||||
fun buildGradleFile(): File {
|
||||
return data.buildGradleFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the `build.gradle[.kts]` file in the project root directory.
|
||||
*/
|
||||
fun buildGradle() {
|
||||
executor.save(buildGradleSrc(), buildGradleFile())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source for `build.gradle[.kts]` files.
|
||||
*/
|
||||
fun buildGradleSrc(): String {
|
||||
return if (data.useKts) buildGradleSrcKts() else buildGradleSrcGroovy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the `settings.gradle[.kts]` file in the project root directory.
|
||||
*/
|
||||
fun settingsGradle() {
|
||||
executor.save(settingsGradleSrc(), settingsGradleFile())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `settings.gradle[.kts]` file for this project.
|
||||
*/
|
||||
fun settingsGradleFile(): File {
|
||||
return File(data.projectDir, data.optonallyKts("settings.gradle"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source for `settings.gradle[.kts]`.
|
||||
*/
|
||||
fun settingsGradleSrc(): String {
|
||||
return if (data.useKts) settingsGradleSrcKts() else return settingsGradleSrcGroovy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the `gradle.properties` file in the root project.
|
||||
*/
|
||||
fun gradleProps() {
|
||||
val name = "gradle.properties"
|
||||
val gradleProps = File(data.projectDir, name)
|
||||
executor.copyAsset(baseAsset(name), gradleProps)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes/copies the Gradle Wrapper related files in the project directory.
|
||||
*/
|
||||
fun gradleWrapper() {
|
||||
val gradlew = File(data.projectDir, "gradlew")
|
||||
val gradlewBat = File(data.projectDir, "${gradlew.name}.bat")
|
||||
executor.copyAsset(baseAsset(gradlew.name), gradlew)
|
||||
executor.copyAsset(baseAsset(gradlewBat.name), gradlewBat)
|
||||
gradleWrapperJar()
|
||||
gradleWrapperProps()
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the default template for the project.
|
||||
*
|
||||
* @param name The name of the module (gradle format, e.g. ':app').
|
||||
* @param block The module configurator.
|
||||
*/
|
||||
fun defaultModule(name: String = ":app", block: AndroidModuleTemplateConfigurator) {
|
||||
check(defModuleTemplate == null) { "Default module has been already configured" }
|
||||
|
||||
val module = AndroidModuleTemplateBuilder().apply {
|
||||
_name = name
|
||||
templateName = 0
|
||||
thumb = 0
|
||||
|
||||
preRecipe = commonPreRecipe {
|
||||
return@commonPreRecipe defModule
|
||||
}
|
||||
|
||||
postRecipe = commonPostRecipe()
|
||||
|
||||
block()
|
||||
}.build()
|
||||
|
||||
modules.add(module)
|
||||
}
|
||||
|
||||
override fun buildInternal(): ProjectTemplate {
|
||||
return ProjectTemplate(modules, templateName!!, thumb!!, widgets!!, recipe!!)
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,9 @@ package com.itsaky.androidide.templates.base
|
||||
import com.android.SdkConstants.ANDROID_MANIFEST_XML
|
||||
import com.itsaky.androidide.templates.ModuleType.AndroidLibrary
|
||||
import com.itsaky.androidide.templates.RecipeExecutor
|
||||
import com.itsaky.androidide.templates.SrcSet
|
||||
import com.itsaky.androidide.templates.base.modules.android.AndroidManifestBuilder
|
||||
import com.itsaky.androidide.templates.base.modules.android.buildGradleSrc
|
||||
import com.itsaky.androidide.templates.base.modules.buildGradleFile
|
||||
import java.io.File
|
||||
|
||||
class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
|
||||
@@ -33,7 +33,7 @@ class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
|
||||
* Return the file path to `AndroidManifest.xml`.
|
||||
*/
|
||||
fun manifestFile(): File {
|
||||
return File(data.projectDir, "src/main/$ANDROID_MANIFEST_XML").also { it.parentFile!!.mkdirs() }
|
||||
return File(srcFolder(SrcSet.Main), ANDROID_MANIFEST_XML).also { it.parentFile!!.mkdirs() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,217 +33,15 @@ import com.itsaky.androidide.templates.ProjectTemplate
|
||||
import com.itsaky.androidide.templates.ProjectTemplateData
|
||||
import com.itsaky.androidide.templates.ProjectVersionData
|
||||
import com.itsaky.androidide.templates.R
|
||||
import com.itsaky.androidide.templates.RecipeExecutor
|
||||
import com.itsaky.androidide.templates.Sdk
|
||||
import com.itsaky.androidide.templates.SpinnerWidget
|
||||
import com.itsaky.androidide.templates.SrcFolder
|
||||
import com.itsaky.androidide.templates.Template
|
||||
import com.itsaky.androidide.templates.TemplateBuilder
|
||||
import com.itsaky.androidide.templates.TemplateData
|
||||
import com.itsaky.androidide.templates.TemplateRecipe
|
||||
import com.itsaky.androidide.templates.TextFieldWidget
|
||||
import com.itsaky.androidide.templates.base.root.buildGradleSrc
|
||||
import com.itsaky.androidide.templates.base.root.gradleWrapper
|
||||
import com.itsaky.androidide.templates.base.root.settingsGradleFile
|
||||
import com.itsaky.androidide.templates.base.root.settingsGradleSrc
|
||||
import com.itsaky.androidide.templates.enumParameter
|
||||
import com.itsaky.androidide.templates.stringParameter
|
||||
import com.itsaky.androidide.utils.AndroidUtils
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import java.io.File
|
||||
|
||||
sealed class PrePostRecipeTemplateBuilder<T : Template> : TemplateBuilder<T>() {
|
||||
|
||||
internal var preRecipe: TemplateRecipe = {}
|
||||
internal var postRecipe: TemplateRecipe = {}
|
||||
|
||||
override var recipe: TemplateRecipe? = null
|
||||
get() = {
|
||||
preRecipe()
|
||||
field?.let { it() }
|
||||
postRecipe()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @property executor The [RecipeExecutor] instance.
|
||||
* @property data The project template data.
|
||||
*/
|
||||
sealed class ExecutorDataTemplateBuilder<T : Template, D : TemplateData> :
|
||||
PrePostRecipeTemplateBuilder<T>() {
|
||||
|
||||
internal var _executor: RecipeExecutor? = null
|
||||
internal var _data: D? = null
|
||||
|
||||
val executor: RecipeExecutor
|
||||
get() = checkNotNull(_executor)
|
||||
|
||||
val data: D
|
||||
get() = checkNotNull(_data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for building project templates.
|
||||
*/
|
||||
class ProjectTemplateBuilder : ExecutorDataTemplateBuilder<ProjectTemplate, ProjectTemplateData>() {
|
||||
|
||||
private var _defModule: ModuleTemplateData? = null
|
||||
private val defModuleTemplate: ModuleTemplate? = null
|
||||
|
||||
internal val modules = mutableListOf<ModuleTemplate>()
|
||||
|
||||
private val defModule: ModuleTemplateData
|
||||
get() = checkNotNull(_defModule) { "Module template data not set" }
|
||||
|
||||
/**
|
||||
* Set the template data that will be used to create the default application module in the project.
|
||||
*
|
||||
* @param data The module template data to use.
|
||||
*/
|
||||
fun setDefaultModuleData(data: ModuleTemplateData) {
|
||||
_defModule = data
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the `settings.gradle[.kts]` file in the project root directory.
|
||||
*/
|
||||
fun settingsGradle() {
|
||||
executor.save(settingsGradleSrc(), settingsGradleFile())
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the `build.gradle[.kts]` file in the project root directory.
|
||||
*/
|
||||
fun buildGradle() {
|
||||
executor.save(buildGradleSrc(), buildGradleFile())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `build.gradle[.kts] file for the project.l
|
||||
*/
|
||||
fun buildGradleFile(): File {
|
||||
return data.buildGradleFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the `gradle.properties` file in the root project.
|
||||
*/
|
||||
fun gradleProps() {
|
||||
val name = "gradle.properties"
|
||||
val gradleProps = File(data.projectDir, name)
|
||||
executor.copyAsset(baseAsset(name), gradleProps)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the default template for the project.
|
||||
*
|
||||
* @param name The name of the module (gradle format, e.g. ':app').
|
||||
* @param block The module configurator.
|
||||
*/
|
||||
fun defaultModule(name: String = ":app", block: AndroidModuleTemplateConfigurator) {
|
||||
check(defModuleTemplate == null) { "Default module has been already configured" }
|
||||
|
||||
val module = AndroidModuleTemplateBuilder().apply {
|
||||
_name = name
|
||||
templateName = 0
|
||||
thumb = 0
|
||||
|
||||
preRecipe = commonPreRecipe {
|
||||
return@commonPreRecipe defModule
|
||||
}
|
||||
|
||||
postRecipe = commonPostRecipe()
|
||||
|
||||
block()
|
||||
}.build()
|
||||
|
||||
modules.add(module)
|
||||
}
|
||||
|
||||
override fun buildInternal(): ProjectTemplate {
|
||||
return ProjectTemplate(modules, templateName!!, thumb!!, widgets!!, recipe!!)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract [TemplateBuilder] for building module projects.
|
||||
*
|
||||
* @property name The name of the module (gradle format, e.g. ':app').
|
||||
*/
|
||||
abstract class ModuleTemplateBuilder :
|
||||
ExecutorDataTemplateBuilder<ModuleTemplate, ModuleTemplateData>() {
|
||||
|
||||
protected val dependencies = hashSetOf<Dependency>()
|
||||
internal var _name: String? = null
|
||||
|
||||
val name: String
|
||||
get() = checkNotNull(_name) { "Name not set to module template" }
|
||||
|
||||
protected open fun RecipeExecutor.preConfig() {}
|
||||
protected open fun RecipeExecutor.postConfig() {}
|
||||
|
||||
/**
|
||||
* Creates the source directory for the given [type].
|
||||
*
|
||||
* @param type The type of the source directory.
|
||||
* @param block Function to configure the source folder.
|
||||
*/
|
||||
fun srcFolder(type: SrcFolder, block: (File) -> Unit = {}) {
|
||||
data.srcFolder(type).apply(block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common pre-recipe configuration.
|
||||
*
|
||||
* @param moduleData Called after the base configuration is setup and before the [recipe] is executed. Caller can perform its own
|
||||
* pre-recipe configuration here. Returns the [ModuleTemplateData] instance.
|
||||
*/
|
||||
fun commonPreRecipe(extraConfig: ModuleTemplateConfigurator = {},
|
||||
moduleData: RecipeExecutor.() -> ModuleTemplateData
|
||||
): TemplateRecipe = {
|
||||
val data = moduleData()
|
||||
|
||||
this@ModuleTemplateBuilder._data = data
|
||||
this@ModuleTemplateBuilder._name = data.name
|
||||
this@ModuleTemplateBuilder._executor = this
|
||||
|
||||
data.projectDir.mkdirs()
|
||||
|
||||
// Create the main source directory
|
||||
srcFolder(SrcFolder.Main)
|
||||
|
||||
preConfig()
|
||||
extraConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Common post-recipe configuration.
|
||||
*
|
||||
* @param extraConfig Called after the [recipe] is executed. Caller can perform its own
|
||||
* post-recipe configuration here.
|
||||
*/
|
||||
fun commonPostRecipe(extraConfig: ModuleTemplateConfigurator = {}): TemplateRecipe = {
|
||||
|
||||
// Write build.gradle[.kts]
|
||||
buildGradle()
|
||||
|
||||
postConfig()
|
||||
extraConfig()
|
||||
}
|
||||
|
||||
fun addDependency(dependency: Dependency) {
|
||||
this.dependencies.add(dependency)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the `build.gradle[.kts]` file for this module.
|
||||
*/
|
||||
abstract fun RecipeExecutor.buildGradle()
|
||||
|
||||
override fun buildInternal(): ModuleTemplate {
|
||||
return ModuleTemplate(name, templateName!!, thumb!!, widgets!!, recipe!!)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup base files for project templates.
|
||||
@@ -380,40 +178,4 @@ fun baseAndroidModule(block: AndroidModuleTemplateConfigurator): ModuleTemplate
|
||||
|
||||
block()
|
||||
}.build()
|
||||
}
|
||||
|
||||
internal fun ProjectTemplateData.moduleNameToDir(name: String): File {
|
||||
return File(this.projectDir, moduleNameToDirName(name).replace(':', '/').trim { it == '/' })
|
||||
}
|
||||
|
||||
internal fun moduleNameToDirName(name: String): String {
|
||||
val result = StringBuilder()
|
||||
var prev: Char? = null
|
||||
for (i in name.indices) {
|
||||
var c = name[i]
|
||||
if (c == ' ') {
|
||||
c = '-'
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
// the first character must be a letter
|
||||
(i == 0 && !c.isLetter())
|
||||
|
||||
// chars at other indices must be a letter or digit or
|
||||
|| !(c.isDigit() || c.isLetter() || c == '-')
|
||||
|
||||
// must not include consecutive '-'
|
||||
|| (prev == '-' && c == '-')
|
||||
) {
|
||||
|
||||
prev = c
|
||||
continue
|
||||
}
|
||||
|
||||
result.append(c)
|
||||
prev = c
|
||||
}
|
||||
|
||||
return result.trim { it == ' ' || it == '-' }.toString()
|
||||
}
|
||||
@@ -18,24 +18,13 @@
|
||||
package com.itsaky.androidide.templates.base
|
||||
|
||||
import com.itsaky.androidide.templates.BaseTemplateData
|
||||
import com.itsaky.androidide.templates.ProjectTemplateData
|
||||
import java.io.File
|
||||
|
||||
typealias ProjectTemplateConfigurator = ProjectTemplateBuilder.() -> Unit
|
||||
typealias ModuleTemplateConfigurator = ModuleTemplateBuilder.() -> Unit
|
||||
typealias AndroidModuleTemplateConfigurator = AndroidModuleTemplateBuilder.() -> Unit
|
||||
|
||||
/**
|
||||
* Get the asset path for base root project template.
|
||||
*/
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
internal fun ProjectTemplateBuilder.baseAsset(path: String) = baseAsset("root", path)
|
||||
|
||||
/**
|
||||
* Get the asset path for base module project template.
|
||||
*/
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
internal fun ModuleTemplateBuilder.baseAsset(path: String) = baseAsset("module", path)
|
||||
|
||||
/**
|
||||
* Get the asset path for base template.
|
||||
*/
|
||||
@@ -45,4 +34,40 @@ internal fun baseAsset(type: String, path: String): String {
|
||||
|
||||
internal fun BaseTemplateData.optonallyKts(file: String): String {
|
||||
return if (useKts) "${file}.kts" else file
|
||||
}
|
||||
|
||||
internal fun ProjectTemplateData.moduleNameToDir(name: String): File {
|
||||
return File(this.projectDir, moduleNameToDirName(name).replace(':', '/').trim { it == '/' })
|
||||
}
|
||||
|
||||
internal fun moduleNameToDirName(name: String): String {
|
||||
val result = StringBuilder()
|
||||
var prev: Char? = null
|
||||
for (i in name.indices) {
|
||||
var c = name[i]
|
||||
if (c == ' ') {
|
||||
c = '-'
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
// the first character must be a letter
|
||||
(i == 0 && !c.isLetter())
|
||||
|
||||
// chars at other indices must be a letter or digit or
|
||||
|| !(c.isDigit() || c.isLetter() || c == '-')
|
||||
|
||||
// must not include consecutive '-'
|
||||
|| (prev == '-' && c == '-')
|
||||
) {
|
||||
|
||||
prev = c
|
||||
continue
|
||||
}
|
||||
|
||||
result.append(c)
|
||||
prev = c
|
||||
}
|
||||
|
||||
return result.trim { it == ' ' || it == '-' }.toString()
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.templates.base.modules
|
||||
|
||||
import com.itsaky.androidide.templates.base.ModuleTemplateBuilder
|
||||
import com.itsaky.androidide.templates.base.buildGradleFile
|
||||
import java.io.File
|
||||
|
||||
internal fun ModuleTemplateBuilder.buildGradleFile(): File {
|
||||
return data.buildGradleFile()
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.templates.base.modules
|
||||
|
||||
import com.squareup.javapoet.FieldSpec
|
||||
import com.squareup.javapoet.MethodSpec
|
||||
import com.squareup.javapoet.TypeName
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
import jdkx.lang.model.element.Modifier
|
||||
|
||||
/**
|
||||
* Creates a new [FieldSpec] for a static constant and adds it to this [TypeSpec].
|
||||
*
|
||||
* @param name The name of the field.
|
||||
* @param configure A function to configure the field.
|
||||
*/
|
||||
fun TypeSpec.Builder.createConstant(type: TypeName, name: String, isPublic: Boolean,
|
||||
configure: FieldSpec.Builder.() -> Unit
|
||||
) {
|
||||
val accessMod = if (isPublic) Modifier.PUBLIC else Modifier.PRIVATE
|
||||
FieldSpec.builder(type, name, accessMod, Modifier.STATIC, Modifier.FINAL)
|
||||
.apply(configure)
|
||||
.build()
|
||||
.also { addField(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new [FieldSpec] and adds it to this [TypeSpec].
|
||||
*
|
||||
* @param name The name of the field.
|
||||
* @param configure A function to configure the field.
|
||||
*/
|
||||
fun TypeSpec.Builder.createField(type: TypeName, name: String, vararg modifiers: Modifier,
|
||||
configure: FieldSpec.Builder.() -> Unit
|
||||
) {
|
||||
FieldSpec.builder(type, name, *modifiers).apply(configure).build().also { addField(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new [MethodSpec] for a constructor and adds it to this [TypeSpec].
|
||||
*
|
||||
* @param configure A function to configure the method.
|
||||
*/
|
||||
fun TypeSpec.Builder.createConstructor(configure: MethodSpec.Builder.() -> Unit) {
|
||||
MethodSpec.constructorBuilder().apply(configure).build().also { addMethod(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new [MethodSpec] and adds it to this [TypeSpec].
|
||||
*
|
||||
* @param name The name of the method.
|
||||
* @param configure A function to configure the method.
|
||||
*/
|
||||
fun TypeSpec.Builder.createMethod(name: String, configure: MethodSpec.Builder.() -> Unit) {
|
||||
MethodSpec.methodBuilder(name).apply(configure).build().also { addMethod(it) }
|
||||
}
|
||||
+2
-9
@@ -18,16 +18,9 @@
|
||||
package com.itsaky.androidide.templates.base.root
|
||||
|
||||
import com.itsaky.androidide.templates.Language
|
||||
import com.itsaky.androidide.templates.base.ModuleTemplateBuilder
|
||||
import com.itsaky.androidide.templates.base.ProjectTemplateBuilder
|
||||
import com.itsaky.androidide.templates.base.buildGradleFile
|
||||
import java.io.File
|
||||
|
||||
fun ProjectTemplateBuilder.buildGradleSrc(): String {
|
||||
return if (data.useKts) buildGradleSrcKts() else buildGradleSrcGroovy()
|
||||
}
|
||||
|
||||
private fun ProjectTemplateBuilder.buildGradleSrcKts(): String {
|
||||
internal fun ProjectTemplateBuilder.buildGradleSrcKts(): String {
|
||||
return """
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
@@ -42,7 +35,7 @@ private fun ProjectTemplateBuilder.buildGradleSrcKts(): String {
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
private fun ProjectTemplateBuilder.buildGradleSrcGroovy(): String {
|
||||
internal fun ProjectTemplateBuilder.buildGradleSrcGroovy(): String {
|
||||
return """
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
|
||||
+3
-17
@@ -21,21 +21,7 @@ import com.itsaky.androidide.templates.base.ProjectTemplateBuilder
|
||||
import com.itsaky.androidide.templates.base.baseAsset
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Writes/copies the Gradle Wrapper related files in the project directory.
|
||||
*
|
||||
* @param data The projecte template data.
|
||||
*/
|
||||
fun ProjectTemplateBuilder.gradleWrapper() {
|
||||
val gradlew = File(data.projectDir, "gradlew")
|
||||
val gradlewBat = File(data.projectDir, "${gradlew.name}.bat")
|
||||
executor.copyAsset(baseAsset(gradlew.name), gradlew)
|
||||
executor.copyAsset(baseAsset(gradlewBat.name), gradlewBat)
|
||||
gradleWrapperJar()
|
||||
gradleWrapperProps()
|
||||
}
|
||||
|
||||
fun ProjectTemplateBuilder.gradleWrapperJar() {
|
||||
internal fun ProjectTemplateBuilder.gradleWrapperJar() {
|
||||
val name = "gradle-wrapper.jar"
|
||||
val wrapperJar = File(data.projectDir, "gradle/wrapper/${name}")
|
||||
wrapperJar.parentFile!!.mkdirs()
|
||||
@@ -43,7 +29,7 @@ fun ProjectTemplateBuilder.gradleWrapperJar() {
|
||||
executor.copyAsset(baseAsset(name), wrapperJar)
|
||||
}
|
||||
|
||||
fun ProjectTemplateBuilder.gradleWrapperProps() {
|
||||
internal fun ProjectTemplateBuilder.gradleWrapperProps() {
|
||||
val name = "gradle-wrapper.properties"
|
||||
val wrapperProps = File(data.projectDir, "gradle/wrapper/${name}")
|
||||
wrapperProps.parentFile!!.mkdirs()
|
||||
@@ -51,7 +37,7 @@ fun ProjectTemplateBuilder.gradleWrapperProps() {
|
||||
executor.save(gradleWrapperPropsSrc(), wrapperProps)
|
||||
}
|
||||
|
||||
private fun ProjectTemplateBuilder.gradleWrapperPropsSrc(): String {
|
||||
internal fun ProjectTemplateBuilder.gradleWrapperPropsSrc(): String {
|
||||
return """
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
|
||||
@@ -21,14 +21,6 @@ import com.itsaky.androidide.templates.base.ProjectTemplateBuilder
|
||||
import com.itsaky.androidide.templates.base.optonallyKts
|
||||
import java.io.File
|
||||
|
||||
fun ProjectTemplateBuilder.settingsGradleFile(): File {
|
||||
return File(data.projectDir, data.optonallyKts("settings.gradle"))
|
||||
}
|
||||
|
||||
fun ProjectTemplateBuilder.settingsGradleSrc(): String {
|
||||
return if (data.useKts) settingsGradleSrcKts() else return settingsGradleSrcGroovy()
|
||||
}
|
||||
|
||||
internal fun ProjectTemplateBuilder.settingsGradleSrcKts(): String {
|
||||
return """
|
||||
pluginManagement {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.templates.base
|
||||
|
||||
import com.itsaky.androidide.templates.RecipeExecutor
|
||||
import com.itsaky.androidide.templates.Template
|
||||
import com.itsaky.androidide.templates.TemplateBuilder
|
||||
import com.itsaky.androidide.templates.TemplateData
|
||||
import com.itsaky.androidide.templates.TemplateRecipe
|
||||
|
||||
sealed class PrePostRecipeTemplateBuilder<T : Template> : TemplateBuilder<T>() {
|
||||
|
||||
internal var preRecipe: TemplateRecipe = {}
|
||||
internal var postRecipe: TemplateRecipe = {}
|
||||
|
||||
override var recipe: TemplateRecipe? = null
|
||||
get() = {
|
||||
preRecipe()
|
||||
field?.let { it() }
|
||||
postRecipe()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @property executor The [RecipeExecutor] instance.
|
||||
* @property data The project template data.
|
||||
*/
|
||||
sealed class ExecutorDataTemplateBuilder<T : Template, D : TemplateData> :
|
||||
PrePostRecipeTemplateBuilder<T>() {
|
||||
|
||||
internal var _executor: RecipeExecutor? = null
|
||||
internal var _data: D? = null
|
||||
|
||||
val executor: RecipeExecutor
|
||||
get() = checkNotNull(_executor)
|
||||
|
||||
val data: D
|
||||
get() = checkNotNull(_data)
|
||||
}
|
||||
@@ -76,7 +76,7 @@ enum class ModuleType(val typeName: String) {
|
||||
*
|
||||
* @property folder The name of the folder.
|
||||
*/
|
||||
enum class SrcFolder(val folder: String) {
|
||||
enum class SrcSet(val folder: String) {
|
||||
|
||||
/**
|
||||
* `src/main`.
|
||||
@@ -153,10 +153,10 @@ class ModuleTemplateData(name: String, val packageName: String, projectDir: File
|
||||
minSdk: Sdk, val versions: ModuleVersionData = ModuleVersionData(minSdk)
|
||||
) : BaseTemplateData(name, projectDir, language, useKts) {
|
||||
|
||||
private val srcDirs = mutableMapOf<SrcFolder, File>()
|
||||
private val srcDirs = mutableMapOf<SrcSet, File>()
|
||||
|
||||
fun srcFolder(type: SrcFolder): File {
|
||||
return srcDirs.computeIfAbsent(type) { File(projectDir, "src/${it.folder}") }
|
||||
fun srcFolder(srcSet: SrcSet): File {
|
||||
return srcDirs.computeIfAbsent(srcSet) { File(projectDir, "src/${it.folder}") }
|
||||
.also { it.mkdirs() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,13 @@ package com.itsaky.androidide.templates
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.templates.base.modules.android.ManifestActivity
|
||||
import com.itsaky.androidide.templates.base.modules.createMethod
|
||||
import com.itsaky.androidide.xml.permissions.Permission
|
||||
import com.squareup.javapoet.ArrayTypeName
|
||||
import com.squareup.javapoet.ParameterSpec
|
||||
import com.squareup.javapoet.TypeName
|
||||
import jdkx.lang.model.element.Modifier.PUBLIC
|
||||
import jdkx.lang.model.element.Modifier.STATIC
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
@@ -86,8 +92,21 @@ class TemplateBuilderTest {
|
||||
defaultModule {
|
||||
manifest {
|
||||
addPermission(Permission.INTERNET)
|
||||
addActivity(
|
||||
ManifestActivity(name = ".MainActivity", isExported = true, isLauncher = true))
|
||||
|
||||
addActivity(ManifestActivity(name = ".MainActivity", isExported = true, isLauncher = true))
|
||||
java {
|
||||
createClass("com.itsaky", "TestClass") {
|
||||
createMethod("main") {
|
||||
addModifiers(PUBLIC, STATIC)
|
||||
returns(TypeName.VOID)
|
||||
addParameter(
|
||||
ParameterSpec.builder(ArrayTypeName.get(java.lang.String::class.java), "args")
|
||||
.build())
|
||||
addStatement("System.out.println(\"Hello world!\")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user