mirror of
https://github.com/HighCapable/Gropify.git
synced 2025-12-11 07:43:52 +08:00
Initial commit
This commit is contained in:
65
gropify-gradle-plugin/build.gradle.kts
Normal file
65
gropify-gradle-plugin/build.gradle.kts
Normal file
@@ -0,0 +1,65 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.maven.publish)
|
||||
}
|
||||
|
||||
group = gropify.project.groupName
|
||||
version = gropify.project.version
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
|
||||
sourceSets.all { languageSettings { languageVersion = "2.0" } }
|
||||
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.jackson.module.kotlin)
|
||||
implementation(libs.kavaref.core)
|
||||
implementation(libs.kavaref.extension)
|
||||
implementation(libs.kotlinpoet)
|
||||
implementation(libs.javapoet)
|
||||
implementation(libs.zip4j)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create(gropify.project.moduleName) {
|
||||
id = gropify.project.groupName
|
||||
implementationClass = gropify.gradle.plugin.implementationClass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
configure<PublishingExtension> {
|
||||
repositories {
|
||||
val repositoryDir = gradle.gradleUserHomeDir
|
||||
.resolve("highcapable-maven-repository")
|
||||
.resolve("repository")
|
||||
|
||||
maven {
|
||||
name = "HighCapableMavenReleases"
|
||||
url = repositoryDir.resolve("releases").toURI()
|
||||
}
|
||||
maven {
|
||||
name = "HighCapableMavenSnapShots"
|
||||
url = repositoryDir.resolve("snapshots").toURI()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/22.
|
||||
*/
|
||||
package com.highcapable.gropify.gradle.api
|
||||
|
||||
import org.gradle.api.initialization.Settings
|
||||
import org.gradle.api.invocation.Gradle
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Gradle description implementation.
|
||||
*/
|
||||
internal object GradleDescriptor {
|
||||
|
||||
private var gradle by Delegates.notNull<Gradle>()
|
||||
|
||||
/** Current Gradle version. */
|
||||
val version get() = gradle.gradleVersion
|
||||
|
||||
/**
|
||||
* Initialize Gradle instance.
|
||||
* @param settings the current Gradle settings instance.
|
||||
*/
|
||||
fun init(settings: Settings) {
|
||||
gradle = settings.gradle
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.gradle.api.entity
|
||||
|
||||
/**
|
||||
* Dependency entity.
|
||||
* @param groupId the group ID.
|
||||
* @param artifactId the artifact ID.
|
||||
* @param version the current version.
|
||||
*/
|
||||
internal data class Dependency(
|
||||
val groupId: String,
|
||||
val artifactId: String,
|
||||
val version: String
|
||||
) {
|
||||
|
||||
/**
|
||||
* Get [Dependency] relative path.
|
||||
* @return [String]
|
||||
*/
|
||||
val relativePath get() = "${groupId.toPathName()}/$artifactId/$version"
|
||||
|
||||
private fun String.toPathName() = trim()
|
||||
.replace(".", "/")
|
||||
.replace("_", "/")
|
||||
.replace(":", "/")
|
||||
.replace("-", "/")
|
||||
|
||||
override fun toString() = "$groupId:$artifactId:$version"
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* SweetProperty - An easy get project properties anywhere Gradle plugin.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/SweetProperty
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2023/8/28.
|
||||
*/
|
||||
package com.highcapable.gropify.gradle.api.entity
|
||||
|
||||
import com.highcapable.gropify.gradle.api.extension.getFullName
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.initialization.Settings
|
||||
import java.io.File
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Project description implementation.
|
||||
*/
|
||||
internal class ProjectDescriptor private constructor() {
|
||||
|
||||
internal companion object {
|
||||
|
||||
/**
|
||||
* Create [ProjectDescriptor] from [Settings].
|
||||
* @param settings the current settings.
|
||||
* @param name the project name, leave it blank to get root project.
|
||||
* @return [ProjectDescriptor]
|
||||
*/
|
||||
fun create(settings: Settings, name: String = "") = ProjectDescriptor().also {
|
||||
val isRootProject = name.isBlank() || name.lowercase() == settings.rootProject.name.lowercase()
|
||||
val subProjectNotice = "if this is a sub-project, please set it like \":$name\""
|
||||
|
||||
it.type = Type.Settings
|
||||
it.name = name.ifBlank { null } ?: settings.rootProject.name
|
||||
it.currentDir = (if (isRootProject) settings.rootProject else settings.findProject(name))?.projectDir
|
||||
?: Gropify.error("Project '$name' not found${if (!name.startsWith(":")) ", $subProjectNotice." else "."}")
|
||||
it.rootDir = settings.rootDir
|
||||
it.homeDir = settings.gradle.gradleUserHomeDir
|
||||
}
|
||||
|
||||
/**
|
||||
* Create [ProjectDescriptor] from [Project].
|
||||
* @param project the current project.
|
||||
* @return [ProjectDescriptor]
|
||||
*/
|
||||
fun create(project: Project) = ProjectDescriptor().also {
|
||||
it.type = Type.Project
|
||||
it.name = project.getFullName()
|
||||
it.currentDir = project.projectDir
|
||||
it.rootDir = project.rootDir
|
||||
it.homeDir = project.gradle.gradleUserHomeDir
|
||||
}
|
||||
}
|
||||
|
||||
/** The buildscript type. */
|
||||
var type by Delegates.notNull<Type>()
|
||||
|
||||
/** The project name. */
|
||||
var name = ""
|
||||
|
||||
/** The current project directory. */
|
||||
var currentDir by Delegates.notNull<File>()
|
||||
|
||||
/** The root project directory. */
|
||||
var rootDir by Delegates.notNull<File>()
|
||||
|
||||
/** The Gradle home directory. */
|
||||
var homeDir by Delegates.notNull<File>()
|
||||
|
||||
/**
|
||||
* Project type definition.
|
||||
*/
|
||||
enum class Type {
|
||||
Settings,
|
||||
Project
|
||||
}
|
||||
|
||||
override fun toString() = "ProjectDescriptor(type=$type, name=$name)"
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.gradle.api.extension
|
||||
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.utils.extension.camelcase
|
||||
import com.highcapable.kavaref.extension.classOf
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
|
||||
/**
|
||||
* Create or get extension.
|
||||
* @receiver [ExtensionAware]
|
||||
* @param name the name, auto called by [toSafeExtName].
|
||||
* @param target the target class.
|
||||
* @param args the constructor arguments.
|
||||
* @return [ExtensionAware]
|
||||
*/
|
||||
internal fun ExtensionAware.getOrCreate(name: String, target: Class<*>, vararg args: Any) = name.toSafeExtName().let { sName ->
|
||||
runCatching { extensions.create(sName, target, *args).asExtension() }.getOrElse {
|
||||
if (!(it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name.") == true)) throw it
|
||||
runCatching { extensions.getByName(sName).asExtension() }.getOrNull() ?: Gropify.error("Create or get extension failed with name \"$sName\".")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or get extension [T].
|
||||
* @receiver [ExtensionAware]
|
||||
* @param name the name, auto called by [toSafeExtName].
|
||||
* @param args the constructor arguments.
|
||||
* @return [ExtensionAware]
|
||||
*/
|
||||
internal inline fun <reified T : Any> ExtensionAware.getOrCreate(name: String, vararg args: Any) = name.toSafeExtName().let { sName ->
|
||||
runCatching { extensions.create(sName, classOf<T>(), *args) }.getOrElse {
|
||||
if (!(it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name.") == true)) throw it
|
||||
runCatching { extensions.getByName(sName) as? T? }.getOrNull() ?: Gropify.error("Create or get extension failed with name \"$sName\".")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extension.
|
||||
* @receiver [ExtensionAware]
|
||||
* @param name the name.
|
||||
* @return [ExtensionAware]
|
||||
*/
|
||||
internal fun ExtensionAware.get(name: String) = runCatching {
|
||||
extensions.getByName(name).asExtension()
|
||||
}.getOrNull() ?: Gropify.error("Could not get extension with name \"$name\".")
|
||||
|
||||
/**
|
||||
* Get extension or null if not exists.
|
||||
* @receiver [ExtensionAware]
|
||||
* @param name the name.
|
||||
* @return [ExtensionAware] or null.
|
||||
*/
|
||||
internal fun ExtensionAware.getOrNull(name: String) = runCatching {
|
||||
extensions.getByName(name).asExtension()
|
||||
}.getOrNull()
|
||||
|
||||
/**
|
||||
* Get extension, target [T].
|
||||
* @receiver [ExtensionAware]
|
||||
* @param name the name.
|
||||
* @return [T]
|
||||
*/
|
||||
internal inline fun <reified T> ExtensionAware.get(name: String) = runCatching {
|
||||
extensions.getByName(name) as T
|
||||
}.getOrNull() ?: Gropify.error("Could not get extension with name \"$name\".")
|
||||
|
||||
/**
|
||||
* Get extension, target [T].
|
||||
* @receiver [ExtensionAware]
|
||||
* @return [T]
|
||||
*/
|
||||
internal inline fun <reified T : Any> ExtensionAware.get() = runCatching {
|
||||
extensions.getByType(classOf<T>())
|
||||
}.getOrNull() ?: Gropify.error("Could not get extension with type ${classOf<T>()}.")
|
||||
|
||||
/**
|
||||
* Configure extension, target [T].
|
||||
* @receiver [ExtensionAware]
|
||||
* @param name the name.
|
||||
* @param configure the configure action.
|
||||
*/
|
||||
internal inline fun <reified T : Any> ExtensionAware.configure(name: String, configure: Action<T>) = extensions.configure(name, configure)
|
||||
|
||||
/**
|
||||
* Detect whether the extension exists.
|
||||
* @receiver [ExtensionAware]
|
||||
* @param name the name.
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun ExtensionAware.hasExtension(name: String) = runCatching { extensions.getByName(name); true }.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* Convert to [ExtensionAware].
|
||||
* @receiver [Any]
|
||||
* @return [ExtensionAware]
|
||||
* @throws IllegalStateException when the instance is not a valid [ExtensionAware].
|
||||
*/
|
||||
internal fun Any.asExtension() = this as? ExtensionAware? ?: Gropify.error("This instance \"$this\" is not a valid ExtensionAware.")
|
||||
|
||||
/**
|
||||
* Since Gradle has an [ExtensionAware] extension,
|
||||
* this function is used to detect if the current string is a keyword name used by Gradle.
|
||||
* @receiver [String]
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun String.isUnSafeExtName() = camelcase().let { it == "ext" || it == "extra" || it == "extraProperties" || it == "extensions" }
|
||||
|
||||
/**
|
||||
* Since Gradle has an [ExtensionAware] extension,
|
||||
* this function is used to convert non-conforming strings to "{string}s"
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.toSafeExtName() = if (isUnSafeExtName()) "${this}s" else this
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.gradle.api.extension
|
||||
|
||||
import com.highcapable.gropify.gradle.api.entity.Dependency
|
||||
import com.highcapable.gropify.utils.extension.toFile
|
||||
import com.highcapable.kavaref.extension.toClassOrNull
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.buildscript
|
||||
import org.gradle.kotlin.dsl.repositories
|
||||
|
||||
/**
|
||||
* Get the full name of the specified project.
|
||||
* @receiver [Project]
|
||||
* @param useColon whether to use a colon before sub-items, default is true.
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun Project.getFullName(useColon: Boolean = true): String {
|
||||
val isRoot = this == rootProject
|
||||
val baseNames = mutableListOf<String>()
|
||||
|
||||
fun fetchChild(project: Project) {
|
||||
project.parent?.also { if (it != it.rootProject) fetchChild(it) }
|
||||
baseNames.add(project.name)
|
||||
}
|
||||
|
||||
fetchChild(project = this)
|
||||
|
||||
return buildString {
|
||||
baseNames.onEach { append(":$it") }.clear()
|
||||
}.let { if (useColon && !isRoot) it else it.drop(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom dependency to the buildscript.
|
||||
* @receiver [Project]
|
||||
* @param repositoryPath the repository path.
|
||||
* @param dependency the dependency entity.
|
||||
*/
|
||||
internal fun Project.addDependencyToBuildscript(repositoryPath: String, dependency: Dependency) {
|
||||
buildscript {
|
||||
repositories {
|
||||
maven {
|
||||
url = repositoryPath.toFile().toURI()
|
||||
mavenContent { includeGroup(dependency.groupId) }
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath(dependency.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to class or null by project buildscript classloader.
|
||||
* @receiver [String]
|
||||
* @param project the target [Project].
|
||||
* @return [Class] or null.
|
||||
*/
|
||||
internal fun String.toClassOrNull(project: Project) = toClassOrNull(project.buildscript.classLoader)
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.gradle.api.plugin
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.initialization.Settings
|
||||
|
||||
/**
|
||||
* Gradle plugin lifecycle interface.
|
||||
*/
|
||||
internal interface PluginLifecycle {
|
||||
|
||||
/**
|
||||
* Callback when Gradle starts loading.
|
||||
*/
|
||||
fun onCreate(settings: Settings)
|
||||
|
||||
/**
|
||||
* Callback when Gradle settings evaluation is complete.
|
||||
*/
|
||||
fun onSettingsEvaluated(settings: Settings)
|
||||
|
||||
/**
|
||||
* Callback when Gradle root project starts evaluation.
|
||||
*/
|
||||
fun beforeProjectEvaluate(rootProject: Project)
|
||||
|
||||
/**
|
||||
* Callback when Gradle root project evaluation is complete.
|
||||
*/
|
||||
fun afterProjectEvaluate(rootProject: Project)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/16.
|
||||
*/
|
||||
@file:Suppress("UnusedReceiverParameter")
|
||||
|
||||
package com.highcapable.gropify.internal
|
||||
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* Gropify exception.
|
||||
*/
|
||||
internal class GropifyException(message: String) : IllegalStateException("Gropify was ran into an error: $message")
|
||||
|
||||
/**
|
||||
* Throws a [GropifyException] with the specified [message].
|
||||
*/
|
||||
internal fun Gropify.error(message: String): Nothing = throw GropifyException(message)
|
||||
|
||||
/**
|
||||
* Requires that a condition is true. If it is not, throws a [GropifyException] with the result of
|
||||
* calling the specified [lazyMessage] function.
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
internal fun Gropify.require(value: Boolean, lazyMessage: () -> Any) {
|
||||
contract {
|
||||
returns() implies value
|
||||
}
|
||||
if (!value) {
|
||||
val message = lazyMessage()
|
||||
throw GropifyException(message.toString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/16.
|
||||
*/
|
||||
@file:Suppress("unused", "LoggingStringTemplateAsArgument")
|
||||
|
||||
package com.highcapable.gropify.internal
|
||||
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.logging.Logger
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Gropify logger.
|
||||
*/
|
||||
internal object Logger {
|
||||
|
||||
private var logger by Delegates.notNull<Logger>()
|
||||
|
||||
/**
|
||||
* Initialize logger with project.
|
||||
* @param project the project.
|
||||
* @return [Logger]
|
||||
*/
|
||||
fun init(project: Project) = apply {
|
||||
logger = project.logger
|
||||
}
|
||||
|
||||
internal fun debug(msg: Any) = logger.debug("[${Gropify.TAG}] $msg")
|
||||
internal fun info(msg: Any) = logger.info("[${Gropify.TAG}] $msg")
|
||||
internal fun warn(msg: Any) = logger.warn("[${Gropify.TAG}] $msg")
|
||||
internal fun error(msg: Any) = logger.error("[${Gropify.TAG}] $msg")
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/12.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin
|
||||
|
||||
import com.highcapable.gropify.gradle.api.entity.ProjectDescriptor
|
||||
import com.highcapable.gropify.internal.require
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.config.type.GropifyLocation
|
||||
import com.highcapable.gropify.plugin.deployer.BuildscriptDeployer
|
||||
import com.highcapable.gropify.plugin.deployer.SourceCodeDeployer
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyMap
|
||||
import com.highcapable.gropify.utils.extension.hasInterpolation
|
||||
import com.highcapable.gropify.utils.extension.removeSurroundingQuotes
|
||||
import com.highcapable.gropify.utils.extension.replaceInterpolation
|
||||
import com.highcapable.gropify.utils.extension.toStringMap
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.initialization.Settings
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.util.*
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.set
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Default properties' key-values deployer.
|
||||
*/
|
||||
internal object DefaultDeployer {
|
||||
|
||||
private var config by Delegates.notNull<GropifyConfig>()
|
||||
|
||||
private var lastModifiedHashCode = 0
|
||||
private var configModified = true
|
||||
|
||||
private val deployers by lazy {
|
||||
listOf(
|
||||
BuildscriptDeployer { config },
|
||||
SourceCodeDeployer { config }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize deployers.
|
||||
* @param settings the current Gradle settings.
|
||||
* @param config the gropify configuration.
|
||||
*/
|
||||
fun init(settings: Settings, config: GropifyConfig) {
|
||||
DefaultDeployer.config = config
|
||||
if (!config.isEnabled) return
|
||||
|
||||
checkingConfigModified(settings)
|
||||
|
||||
deployers.forEach { it.init(settings, configModified) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve deployers before project evaluation.
|
||||
* @param rootProject the current root project.
|
||||
*/
|
||||
fun resolve(rootProject: Project) {
|
||||
if (!config.isEnabled) return
|
||||
|
||||
deployers.forEach { it.resolve(rootProject, configModified) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy deployers after project evaluation.
|
||||
* @param rootProject the current root project.
|
||||
*/
|
||||
fun deploy(rootProject: Project) {
|
||||
if (!config.isEnabled) return
|
||||
|
||||
deployers.forEach { it.deploy(rootProject, configModified) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate properties' key-values map.
|
||||
* @param config the generate configuration.
|
||||
* @param descriptor the project descriptor.
|
||||
* @return [PropertyMap]
|
||||
*/
|
||||
fun generateMap(config: GropifyConfig.CommonGenerateConfig, descriptor: ProjectDescriptor): PropertyMap {
|
||||
val properties = mutableMapOf<String, Any>()
|
||||
val resolveProperties = mutableMapOf<Any?, Any?>()
|
||||
|
||||
config.permanentKeyValues.forEach { (key, value) -> properties[key] = value }
|
||||
config.locations.forEach { location ->
|
||||
when (location) {
|
||||
GropifyLocation.CurrentProject -> createProperties(config, descriptor.currentDir).forEach { resolveProperties.putAll(it) }
|
||||
GropifyLocation.RootProject -> createProperties(config, descriptor.rootDir).forEach { resolveProperties.putAll(it) }
|
||||
GropifyLocation.Global -> createProperties(config, descriptor.homeDir).forEach { resolveProperties.putAll(it) }
|
||||
GropifyLocation.System -> resolveProperties.putAll(System.getProperties())
|
||||
GropifyLocation.SystemEnv -> resolveProperties.putAll(System.getenv())
|
||||
}
|
||||
}
|
||||
|
||||
resolveProperties.filter { (key, value) ->
|
||||
if (config.excludeNonStringValue)
|
||||
key is CharSequence && key.isNotBlank() && value is CharSequence
|
||||
else key.toString().isNotBlank() && value != null
|
||||
}.toStringMap().filter { (key, _) ->
|
||||
config.includeKeys.ifEmpty { null }?.any { content ->
|
||||
when (content) {
|
||||
is Regex -> content.matches(key)
|
||||
else -> content.toString() == key
|
||||
}
|
||||
} ?: true
|
||||
}.filter { (key, _) ->
|
||||
config.excludeKeys.ifEmpty { null }?.none { content ->
|
||||
when (content) {
|
||||
is Regex -> content.matches(key)
|
||||
else -> content.toString() == key
|
||||
}
|
||||
} ?: true
|
||||
}.toMutableMap().also { resolveKeyValues ->
|
||||
resolveKeyValues.onEach { (key, value) ->
|
||||
val resolveKeys = mutableListOf<String>()
|
||||
|
||||
fun String.resolveValue(): String = replaceInterpolation { matchKey ->
|
||||
Gropify.require(resolveKeys.size <= 5) {
|
||||
"Key \"$key\" has been called recursively multiple times of those $resolveKeys."
|
||||
}
|
||||
|
||||
resolveKeys.add(matchKey)
|
||||
var resolveValue = if (config.useValueInterpolation)
|
||||
resolveKeyValues[matchKey] ?: ""
|
||||
else matchKey
|
||||
resolveValue = resolveValue.removeSurroundingQuotes()
|
||||
|
||||
if (resolveValue.hasInterpolation()) resolveValue.resolveValue()
|
||||
else resolveValue
|
||||
}
|
||||
|
||||
if (value.hasInterpolation()) resolveKeyValues[key] = value.resolveValue()
|
||||
}.takeIf { config.keyValuesRules.isNotEmpty() }?.forEach { (key, value) ->
|
||||
config.keyValuesRules[key]?.also { resolveKeyValues[key] = it(value) }
|
||||
}
|
||||
|
||||
properties.putAll(resolveKeyValues)
|
||||
}
|
||||
|
||||
// Replace all key-values if exists.
|
||||
config.replacementKeyValues.forEach { (key, value) -> properties[key] = value }
|
||||
|
||||
return properties
|
||||
}
|
||||
|
||||
private fun createProperties(config: GropifyConfig.CommonGenerateConfig, dir: File?) = runCatching {
|
||||
mutableListOf<Properties>().apply {
|
||||
config.existsPropertyFiles.forEach {
|
||||
val propertiesFile = dir?.resolve(it)
|
||||
if (propertiesFile?.exists() == true)
|
||||
add(Properties().apply { load(FileReader(propertiesFile.absolutePath)) })
|
||||
}
|
||||
}
|
||||
}.getOrNull() ?: mutableListOf()
|
||||
|
||||
private fun checkingConfigModified(settings: Settings) {
|
||||
settings.settingsDir.also { dir ->
|
||||
val groovyHashCode = dir.resolve("settings.gradle").takeIf { it.exists() }?.readText()?.hashCode()
|
||||
val kotlinHashCode = dir.resolve("settings.gradle.kts").takeIf { it.exists() }?.readText()?.hashCode()
|
||||
val gradleHashCode = groovyHashCode ?: kotlinHashCode ?: -1
|
||||
|
||||
configModified = gradleHashCode == -1 || lastModifiedHashCode != gradleHashCode
|
||||
lastModifiedHashCode = gradleHashCode
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/13.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin
|
||||
|
||||
import com.highcapable.gropify.generated.GropifyProperties
|
||||
|
||||
/**
|
||||
* Here is Gropify!
|
||||
*/
|
||||
object Gropify {
|
||||
|
||||
internal const val GROUP_NAME = GropifyProperties.PROJECT_GROUP_NAME
|
||||
|
||||
const val TAG = GropifyProperties.PROJECT_NAME
|
||||
const val VERSION = GropifyProperties.PROJECT_VERSION
|
||||
const val PROJECT_URL = GropifyProperties.PROJECT_URL
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin
|
||||
|
||||
import com.highcapable.gropify.gradle.api.GradleDescriptor
|
||||
import com.highcapable.gropify.gradle.api.extension.getOrCreate
|
||||
import com.highcapable.gropify.gradle.api.plugin.PluginLifecycle
|
||||
import com.highcapable.gropify.internal.Logger
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.initialization.Settings
|
||||
|
||||
/**
|
||||
* Lifecycle for Gropify.
|
||||
*/
|
||||
internal class GropifyLifecycle : PluginLifecycle {
|
||||
|
||||
private var configure: GropifyConfigureExtension? = null
|
||||
|
||||
override fun onCreate(settings: Settings) {
|
||||
GradleDescriptor.init(settings)
|
||||
|
||||
configure = settings.getOrCreate<GropifyConfigureExtension>(GropifyConfigureExtension.NAME)
|
||||
}
|
||||
|
||||
override fun onSettingsEvaluated(settings: Settings) {
|
||||
val config = configure?.build(settings) ?: Gropify.error("Extension \"${GropifyConfigureExtension.NAME}\" create failed.")
|
||||
|
||||
DefaultDeployer.init(settings, config)
|
||||
}
|
||||
|
||||
override fun beforeProjectEvaluate(rootProject: Project) {
|
||||
Logger.init(rootProject)
|
||||
DefaultDeployer.resolve(rootProject)
|
||||
}
|
||||
|
||||
override fun afterProjectEvaluate(rootProject: Project) {
|
||||
DefaultDeployer.deploy(rootProject)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/8.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.gropify.plugin
|
||||
|
||||
import com.highcapable.gropify.internal.Logger
|
||||
import com.highcapable.gropify.internal.error
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.initialization.Settings
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
|
||||
/**
|
||||
* Entry class for Gropify plugin.
|
||||
*/
|
||||
class GropifyPlugin<T : ExtensionAware> internal constructor() : Plugin<T> {
|
||||
|
||||
private val lifecycle = GropifyLifecycle()
|
||||
|
||||
override fun apply(target: T) = when (target) {
|
||||
is Settings -> {
|
||||
val lifecycle = this.lifecycle
|
||||
|
||||
lifecycle.onCreate(target)
|
||||
|
||||
target.gradle.settingsEvaluated {
|
||||
lifecycle.onSettingsEvaluated(target)
|
||||
}
|
||||
|
||||
target.gradle.projectsLoaded {
|
||||
rootProject.beforeEvaluate {
|
||||
lifecycle.beforeProjectEvaluate(rootProject = this)
|
||||
}
|
||||
rootProject.afterEvaluate {
|
||||
lifecycle.afterProjectEvaluate(rootProject = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Project -> Logger.init(target).error(
|
||||
"Gropify can only applied in settings.gradle or settings.gradle.kts, but current is $target, stop loading.",
|
||||
)
|
||||
else -> Gropify.error("Gropify applied to an unknown target: $target, stop loading.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/8.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.compiler
|
||||
|
||||
import com.highcapable.gropify.gradle.api.entity.Dependency
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.internal.require
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.utils.extension.deleteEmptyRecursively
|
||||
import com.highcapable.gropify.utils.extension.toFile
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import net.lingala.zip4j.model.ZipParameters
|
||||
import java.io.File
|
||||
import javax.tools.DiagnosticCollector
|
||||
import javax.tools.JavaFileObject
|
||||
import javax.tools.StandardLocation
|
||||
import javax.tools.ToolProvider
|
||||
|
||||
/**
|
||||
* Java code compiler.
|
||||
*/
|
||||
internal object CodeCompiler {
|
||||
|
||||
private const val MAVEN_MODEL_VERSION = "4.0.0"
|
||||
|
||||
/**
|
||||
* Compile [JavaFileObject] as a dependency.
|
||||
* @param dependency the dependency entity.
|
||||
* @param outputDirPath the compile output directory path.
|
||||
* @param files the compile [JavaFileObject] array.
|
||||
* @param compileOnlyFiles the compile only [JavaFileObject] array.
|
||||
* @throws IllegalStateException if compilation fails.
|
||||
*/
|
||||
fun compile(
|
||||
dependency: Dependency,
|
||||
outputDirPath: String,
|
||||
files: List<JavaFileObject>,
|
||||
compileOnlyFiles: List<JavaFileObject> = mutableListOf()
|
||||
) {
|
||||
val outputDir = outputDirPath.toFile()
|
||||
|
||||
if (files.isEmpty()) {
|
||||
if (outputDir.exists()) outputDir.deleteRecursively()
|
||||
|
||||
return
|
||||
} else outputDir.also { if (!it.exists()) it.mkdirs() }
|
||||
|
||||
val outputBuildDir = "$outputDirPath/build".toFile().also {
|
||||
if (it.exists()) it.deleteRecursively()
|
||||
it.mkdirs()
|
||||
}
|
||||
|
||||
val outputClassesDir = "${outputBuildDir.absolutePath}/classes".toFile().apply { mkdirs() }
|
||||
val outputSourcesDir = "${outputBuildDir.absolutePath}/sources".toFile().apply { mkdirs() }
|
||||
|
||||
val compiler = ToolProvider.getSystemJavaCompiler()
|
||||
val diagnostics = DiagnosticCollector<JavaFileObject>()
|
||||
|
||||
val fileManager = compiler.getStandardFileManager(diagnostics, null, null)
|
||||
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, listOf(outputClassesDir))
|
||||
|
||||
val task = compiler.getTask(null, fileManager, diagnostics, null, null, compileOnlyFiles + files)
|
||||
|
||||
val result = task.call()
|
||||
var diagnosticsMessage = ""
|
||||
|
||||
diagnostics.diagnostics?.forEach { diagnostic ->
|
||||
diagnosticsMessage += " > Error on line ${diagnostic.lineNumber} in ${diagnostic.source?.toUri()}\n"
|
||||
diagnosticsMessage += " ${diagnostic.getMessage(null)}\n"
|
||||
}
|
||||
|
||||
runCatching { fileManager.close() }
|
||||
|
||||
compileOnlyFiles.forEach {
|
||||
"${outputClassesDir.absolutePath}/${it.name}"
|
||||
.replace(".java", ".class")
|
||||
.toFile()
|
||||
.delete()
|
||||
}
|
||||
|
||||
files.forEach {
|
||||
it.toFiles(outputSourcesDir).also { (sourceDir, sourceFile) ->
|
||||
sourceDir.mkdirs()
|
||||
sourceFile.writeText(it.getCharContent(true).toString())
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
outputClassesDir.deleteEmptyRecursively()
|
||||
|
||||
writeMetaInf(outputClassesDir)
|
||||
writeMetaInf(outputSourcesDir)
|
||||
|
||||
createJar(dependency, outputDir, outputBuildDir, outputClassesDir, outputSourcesDir)
|
||||
} else Gropify.error("Failed to compile java files into path: $outputDirPath\n$diagnosticsMessage")
|
||||
}
|
||||
|
||||
private fun createJar(dependency: Dependency, outputDir: File, buildDir: File, classesDir: File, sourcesDir: File) {
|
||||
val dependencyDir = outputDir.resolve(dependency.relativePath).also { if (!it.exists()) it.mkdirs() }
|
||||
|
||||
packageJar(classesDir, dependencyDir, dependency, sourcesJar = false)
|
||||
packageJar(sourcesDir, dependencyDir, dependency, sourcesJar = true)
|
||||
writeDependency(dependencyDir, dependency)
|
||||
|
||||
buildDir.deleteRecursively()
|
||||
}
|
||||
|
||||
private fun writeMetaInf(dir: File) {
|
||||
val metaInfDir = dir.resolve("META-INF").apply { mkdirs() }
|
||||
metaInfDir.resolve("MANIFEST.MF").writeText("Manifest-Version: 1.0")
|
||||
}
|
||||
|
||||
private fun writeDependency(dir: File, dependency: Dependency) {
|
||||
dir.resolve("${dependency.artifactId}-${dependency.version}.pom").writeText(
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project
|
||||
xmlns="http://maven.apache.org/POM/$MAVEN_MODEL_VERSION"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/$MAVEN_MODEL_VERSION https://maven.apache.org/xsd/maven-$MAVEN_MODEL_VERSION.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>$MAVEN_MODEL_VERSION</modelVersion>
|
||||
<groupId>${dependency.groupId}</groupId>
|
||||
<artifactId>${dependency.artifactId}</artifactId>
|
||||
<version>${dependency.version}</version>
|
||||
</project>
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
private fun JavaFileObject.toFiles(outputDir: File): Pair<File, File> {
|
||||
val outputDirPath = outputDir.absolutePath
|
||||
val separator = if (name.contains("/")) "/" else "\\"
|
||||
val names = name.split(separator)
|
||||
|
||||
val fileName = names[names.lastIndex]
|
||||
val folderName = name.replace(fileName, "")
|
||||
|
||||
return "$outputDirPath/$folderName".toFile() to "$outputDirPath/$name".toFile()
|
||||
}
|
||||
|
||||
private fun packageJar(buildDir: File, outputDir: File, dependency: Dependency, sourcesJar: Boolean) {
|
||||
Gropify.require(buildDir.exists()) {
|
||||
"Build directory not found: ${buildDir.absolutePath}."
|
||||
}
|
||||
|
||||
val jarFile = outputDir.resolve("${dependency.artifactId}-${dependency.version}${if (sourcesJar) "-sources" else ""}.jar")
|
||||
if (jarFile.exists()) jarFile.delete()
|
||||
|
||||
ZipFile(jarFile).addFolder(buildDir, ZipParameters().apply { isIncludeRootFolder = false })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/8.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.gropify.plugin.compiler.extension
|
||||
|
||||
import com.highcapable.gropify.gradle.api.entity.Dependency
|
||||
import com.highcapable.gropify.plugin.compiler.CodeCompiler
|
||||
import com.palantir.javapoet.JavaFile
|
||||
import javax.tools.JavaFileObject
|
||||
|
||||
/**
|
||||
* Compile [JavaFile] as a dependency.
|
||||
* @receiver [JavaFile]
|
||||
* @param dependency the dependency entity.
|
||||
* @param outputDirPath the compile output directory path.
|
||||
* @param compileOnlyFiles the compile only [JavaFile] array.
|
||||
* @throws IllegalStateException if compilation fails.
|
||||
*/
|
||||
@JvmName("compileWithJavaFile")
|
||||
internal fun JavaFile.compile(
|
||||
dependency: Dependency,
|
||||
outputDirPath: String,
|
||||
compileOnlyFiles: List<JavaFile> = mutableListOf()
|
||||
) = CodeCompiler.compile(
|
||||
dependency = dependency,
|
||||
outputDirPath = outputDirPath,
|
||||
files = listOf(toJavaFileObject()),
|
||||
compileOnlyFiles = mutableListOf<JavaFileObject>().also {
|
||||
compileOnlyFiles.forEach { file ->
|
||||
it.add(file.toJavaFileObject())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Compile [List]<[JavaFile]> as a dependency.
|
||||
* @receiver [List]<[JavaFile]>
|
||||
* @param dependency the dependency entity.
|
||||
* @param outputDirPath the compile output directory path.
|
||||
* @param compileOnlyFiles the compile only [JavaFile] array.
|
||||
* @throws IllegalStateException if compilation fails.
|
||||
*/
|
||||
@JvmName("compileWithJavaFile")
|
||||
internal fun List<JavaFile>.compile(
|
||||
dependency: Dependency,
|
||||
outputDirPath: String,
|
||||
compileOnlyFiles: List<JavaFile> = mutableListOf()
|
||||
) = CodeCompiler.compile(
|
||||
dependency = dependency,
|
||||
outputDirPath = outputDirPath,
|
||||
files = mutableListOf<JavaFileObject>().also {
|
||||
forEach { file ->
|
||||
it.add(file.toJavaFileObject())
|
||||
}
|
||||
},
|
||||
compileOnlyFiles = mutableListOf<JavaFileObject>().also {
|
||||
compileOnlyFiles.forEach { file ->
|
||||
it.add(file.toJavaFileObject())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Compile [JavaFileObject] as a dependency.
|
||||
* @receiver [JavaFileObject]
|
||||
* @param dependency the dependency entity.
|
||||
* @param outputDirPath the compile output directory path.
|
||||
* @param compileOnlyFiles the compile only [JavaFileObject] array.
|
||||
* @throws IllegalStateException if compilation fails.
|
||||
*/
|
||||
@JvmName("compileWithJavaFileObject")
|
||||
internal fun JavaFileObject.compile(
|
||||
dependency: Dependency,
|
||||
outputDirPath: String,
|
||||
compileOnlyFiles: List<JavaFileObject> = mutableListOf()
|
||||
) = CodeCompiler.compile(dependency, outputDirPath, listOf(this), compileOnlyFiles)
|
||||
|
||||
/**
|
||||
* Compile [List]<[JavaFileObject]> as a dependency.
|
||||
* @receiver [List]<[JavaFileObject]>
|
||||
* @param dependency the dependency entity.
|
||||
* @param outputDirPath the compile output directory path.
|
||||
* @param compileOnlyFiles the compile only [JavaFileObject] array.
|
||||
* @throws IllegalStateException if compilation fails.
|
||||
*/
|
||||
@JvmName("compileWithJavaFileObject")
|
||||
internal fun List<JavaFileObject>.compile(
|
||||
dependency: Dependency,
|
||||
outputDirPath: String,
|
||||
compileOnlyFiles: List<JavaFileObject> = mutableListOf()
|
||||
) = CodeCompiler.compile(dependency, outputDirPath, files = this, compileOnlyFiles)
|
||||
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.config
|
||||
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyValueRule
|
||||
|
||||
/**
|
||||
* Default configuration for Gropify.
|
||||
*/
|
||||
internal object DefaultConfig {
|
||||
|
||||
fun createGenerateConfig(name: String, common: GropifyConfigureExtension.CommonGenerateConfigureScope? = null) =
|
||||
object : GropifyConfig.GenerateConfig {
|
||||
override val buildscript get() = createBuildscriptGenerateConfig(name, common)
|
||||
override val android get() = createAndroidGenerateConfig(name, common)
|
||||
override val jvm get() = createJvmGenerateConfig(name, common)
|
||||
override val kmp get() = createKmpGenerateConfig(name, common)
|
||||
}
|
||||
|
||||
fun createBuildscriptGenerateConfig(
|
||||
name: String,
|
||||
selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null,
|
||||
globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null
|
||||
) = object : GropifyConfig.BuildscriptGenerateConfig {
|
||||
|
||||
override val name get() = name
|
||||
|
||||
override val extensionName get() = GropifyConfig.DEFAULT_EXTENSION_NAME
|
||||
|
||||
override val isEnabled
|
||||
get() = selfCommon?.isEnabled
|
||||
?: globalCommon?.isEnabled
|
||||
?: createCommonGenerateConfig(name).isEnabled
|
||||
|
||||
override val existsPropertyFiles
|
||||
get() = selfCommon?.existsPropertyFiles
|
||||
?: globalCommon?.existsPropertyFiles
|
||||
?: createCommonGenerateConfig(name).existsPropertyFiles
|
||||
|
||||
override val permanentKeyValues
|
||||
get() = selfCommon?.permanentKeyValues
|
||||
?: globalCommon?.permanentKeyValues
|
||||
?: createCommonGenerateConfig(name).permanentKeyValues
|
||||
|
||||
override val replacementKeyValues
|
||||
get() = selfCommon?.permanentKeyValues
|
||||
?: globalCommon?.permanentKeyValues
|
||||
?: createCommonGenerateConfig(name).permanentKeyValues
|
||||
|
||||
override val excludeKeys
|
||||
get() = selfCommon?.excludeKeys
|
||||
?: globalCommon?.excludeKeys
|
||||
?: createCommonGenerateConfig(name).excludeKeys
|
||||
|
||||
override val includeKeys
|
||||
get() = selfCommon?.includeKeys
|
||||
?: globalCommon?.includeKeys
|
||||
?: createCommonGenerateConfig(name).includeKeys
|
||||
|
||||
override val keyValuesRules
|
||||
get() = selfCommon?.keyValuesRules
|
||||
?: globalCommon?.keyValuesRules
|
||||
?: createCommonGenerateConfig(name).keyValuesRules
|
||||
|
||||
override val excludeNonStringValue
|
||||
get() = selfCommon?.excludeNonStringValue
|
||||
?: globalCommon?.excludeNonStringValue
|
||||
?: createCommonGenerateConfig(name).excludeNonStringValue
|
||||
|
||||
override val useTypeAutoConversion
|
||||
get() = selfCommon?.useTypeAutoConversion
|
||||
?: globalCommon?.useTypeAutoConversion
|
||||
?: createCommonGenerateConfig(name).useTypeAutoConversion
|
||||
|
||||
override val useValueInterpolation
|
||||
get() = selfCommon?.useValueInterpolation
|
||||
?: globalCommon?.useValueInterpolation
|
||||
?: createCommonGenerateConfig(name).useValueInterpolation
|
||||
|
||||
override val locations
|
||||
get() = selfCommon?.locations
|
||||
?: globalCommon?.locations
|
||||
?: createCommonGenerateConfig(name).locations
|
||||
}
|
||||
|
||||
fun createAndroidGenerateConfig(
|
||||
name: String,
|
||||
selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null,
|
||||
globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null
|
||||
) = object : GropifyConfig.AndroidGenerateConfig {
|
||||
|
||||
override val name get() = name
|
||||
|
||||
override val generateDirPath get() = GropifyConfig.DEFAULT_COMMON_CODE_GENERATE_DIR_PATH
|
||||
|
||||
override val sourceSetName get() = GropifyConfig.DEFAULT_ANDROID_JVM_SOURCE_SET_NAME
|
||||
|
||||
override val useKotlin get() = true
|
||||
|
||||
override val packageName get() = ""
|
||||
|
||||
override val className get() = ""
|
||||
|
||||
override val isRestrictedAccessEnabled get() = false
|
||||
|
||||
override val isIsolationEnabled get() = true
|
||||
|
||||
override val isEnabled
|
||||
get() = selfCommon?.isEnabled
|
||||
?: globalCommon?.isEnabled
|
||||
?: createCommonGenerateConfig(name).isEnabled
|
||||
|
||||
override val existsPropertyFiles
|
||||
get() = selfCommon?.existsPropertyFiles
|
||||
?: globalCommon?.existsPropertyFiles
|
||||
?: createCommonGenerateConfig(name).existsPropertyFiles
|
||||
|
||||
override val permanentKeyValues
|
||||
get() = selfCommon?.permanentKeyValues
|
||||
?: globalCommon?.permanentKeyValues
|
||||
?: createCommonGenerateConfig(name).permanentKeyValues
|
||||
|
||||
override val replacementKeyValues
|
||||
get() = selfCommon?.permanentKeyValues
|
||||
?: globalCommon?.permanentKeyValues
|
||||
?: createCommonGenerateConfig(name).permanentKeyValues
|
||||
|
||||
override val excludeKeys
|
||||
get() = selfCommon?.excludeKeys
|
||||
?: globalCommon?.excludeKeys
|
||||
?: createCommonGenerateConfig(name).excludeKeys
|
||||
|
||||
override val includeKeys
|
||||
get() = selfCommon?.includeKeys
|
||||
?: globalCommon?.includeKeys
|
||||
?: createCommonGenerateConfig(name).includeKeys
|
||||
|
||||
override val keyValuesRules
|
||||
get() = selfCommon?.keyValuesRules
|
||||
?: globalCommon?.keyValuesRules
|
||||
?: createCommonGenerateConfig(name).keyValuesRules
|
||||
|
||||
override val excludeNonStringValue
|
||||
get() = selfCommon?.excludeNonStringValue
|
||||
?: globalCommon?.excludeNonStringValue
|
||||
?: createCommonGenerateConfig(name).excludeNonStringValue
|
||||
|
||||
override val useTypeAutoConversion
|
||||
get() = selfCommon?.useTypeAutoConversion
|
||||
?: globalCommon?.useTypeAutoConversion
|
||||
?: createCommonGenerateConfig(name).useTypeAutoConversion
|
||||
|
||||
override val useValueInterpolation
|
||||
get() = selfCommon?.useValueInterpolation
|
||||
?: globalCommon?.useValueInterpolation
|
||||
?: createCommonGenerateConfig(name).useValueInterpolation
|
||||
|
||||
override val locations
|
||||
get() = selfCommon?.locations
|
||||
?: globalCommon?.locations
|
||||
?: createCommonGenerateConfig(name).locations
|
||||
}
|
||||
|
||||
fun createJvmGenerateConfig(
|
||||
name: String,
|
||||
selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null,
|
||||
globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null
|
||||
) = object : GropifyConfig.JvmGenerateConfig {
|
||||
|
||||
override val name get() = name
|
||||
|
||||
override val generateDirPath get() = GropifyConfig.DEFAULT_COMMON_CODE_GENERATE_DIR_PATH
|
||||
|
||||
override val sourceSetName get() = GropifyConfig.DEFAULT_ANDROID_JVM_SOURCE_SET_NAME
|
||||
|
||||
override val useKotlin get() = true
|
||||
|
||||
override val packageName get() = ""
|
||||
|
||||
override val className get() = ""
|
||||
|
||||
override val isRestrictedAccessEnabled get() = false
|
||||
|
||||
override val isIsolationEnabled get() = true
|
||||
|
||||
override val isEnabled
|
||||
get() = selfCommon?.isEnabled
|
||||
?: globalCommon?.isEnabled
|
||||
?: createCommonGenerateConfig(name).isEnabled
|
||||
|
||||
override val existsPropertyFiles
|
||||
get() = selfCommon?.existsPropertyFiles
|
||||
?: globalCommon?.existsPropertyFiles
|
||||
?: createCommonGenerateConfig(name).existsPropertyFiles
|
||||
|
||||
override val permanentKeyValues
|
||||
get() = selfCommon?.permanentKeyValues
|
||||
?: globalCommon?.permanentKeyValues
|
||||
?: createCommonGenerateConfig(name).permanentKeyValues
|
||||
|
||||
override val replacementKeyValues
|
||||
get() = selfCommon?.permanentKeyValues
|
||||
?: globalCommon?.permanentKeyValues
|
||||
?: createCommonGenerateConfig(name).permanentKeyValues
|
||||
|
||||
override val excludeKeys
|
||||
get() = selfCommon?.excludeKeys
|
||||
?: globalCommon?.excludeKeys
|
||||
?: createCommonGenerateConfig(name).excludeKeys
|
||||
|
||||
override val includeKeys
|
||||
get() = selfCommon?.includeKeys
|
||||
?: globalCommon?.includeKeys
|
||||
?: createCommonGenerateConfig(name).includeKeys
|
||||
|
||||
override val keyValuesRules
|
||||
get() = selfCommon?.keyValuesRules
|
||||
?: globalCommon?.keyValuesRules
|
||||
?: createCommonGenerateConfig(name).keyValuesRules
|
||||
|
||||
override val excludeNonStringValue
|
||||
get() = selfCommon?.excludeNonStringValue
|
||||
?: globalCommon?.excludeNonStringValue
|
||||
?: createCommonGenerateConfig(name).excludeNonStringValue
|
||||
|
||||
override val useTypeAutoConversion
|
||||
get() = selfCommon?.useTypeAutoConversion
|
||||
?: globalCommon?.useTypeAutoConversion
|
||||
?: createCommonGenerateConfig(name).useTypeAutoConversion
|
||||
|
||||
override val useValueInterpolation
|
||||
get() = selfCommon?.useValueInterpolation
|
||||
?: globalCommon?.useValueInterpolation
|
||||
?: createCommonGenerateConfig(name).useValueInterpolation
|
||||
|
||||
override val locations
|
||||
get() = selfCommon?.locations
|
||||
?: globalCommon?.locations
|
||||
?: createCommonGenerateConfig(name).locations
|
||||
}
|
||||
|
||||
fun createKmpGenerateConfig(
|
||||
name: String,
|
||||
selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null,
|
||||
globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null
|
||||
) = object : GropifyConfig.KmpGenerateConfig {
|
||||
|
||||
override val name get() = name
|
||||
|
||||
override val generateDirPath get() = GropifyConfig.DEFAULT_COMMON_CODE_GENERATE_DIR_PATH
|
||||
|
||||
override val sourceSetName get() = GropifyConfig.DEFAULT_KMP_COMMON_SOURCE_SET_NAME
|
||||
|
||||
override val packageName get() = ""
|
||||
|
||||
override val className get() = ""
|
||||
|
||||
override val isRestrictedAccessEnabled get() = false
|
||||
|
||||
override val isIsolationEnabled get() = true
|
||||
|
||||
override val isEnabled
|
||||
get() = selfCommon?.isEnabled
|
||||
?: globalCommon?.isEnabled
|
||||
?: createCommonGenerateConfig(name).isEnabled
|
||||
|
||||
override val existsPropertyFiles
|
||||
get() = selfCommon?.existsPropertyFiles
|
||||
?: globalCommon?.existsPropertyFiles
|
||||
?: createCommonGenerateConfig(name).existsPropertyFiles
|
||||
|
||||
override val permanentKeyValues
|
||||
get() = selfCommon?.permanentKeyValues
|
||||
?: globalCommon?.permanentKeyValues
|
||||
?: createCommonGenerateConfig(name).permanentKeyValues
|
||||
|
||||
override val replacementKeyValues
|
||||
get() = selfCommon?.permanentKeyValues
|
||||
?: globalCommon?.permanentKeyValues
|
||||
?: createCommonGenerateConfig(name).permanentKeyValues
|
||||
|
||||
override val excludeKeys
|
||||
get() = selfCommon?.excludeKeys
|
||||
?: globalCommon?.excludeKeys
|
||||
?: createCommonGenerateConfig(name).excludeKeys
|
||||
|
||||
override val includeKeys
|
||||
get() = selfCommon?.includeKeys
|
||||
?: globalCommon?.includeKeys
|
||||
?: createCommonGenerateConfig(name).includeKeys
|
||||
|
||||
override val keyValuesRules
|
||||
get() = selfCommon?.keyValuesRules
|
||||
?: globalCommon?.keyValuesRules
|
||||
?: createCommonGenerateConfig(name).keyValuesRules
|
||||
|
||||
override val excludeNonStringValue
|
||||
get() = selfCommon?.excludeNonStringValue
|
||||
?: globalCommon?.excludeNonStringValue
|
||||
?: createCommonGenerateConfig(name).excludeNonStringValue
|
||||
|
||||
override val useTypeAutoConversion
|
||||
get() = selfCommon?.useTypeAutoConversion
|
||||
?: globalCommon?.useTypeAutoConversion
|
||||
?: createCommonGenerateConfig(name).useTypeAutoConversion
|
||||
|
||||
override val useValueInterpolation
|
||||
get() = selfCommon?.useValueInterpolation
|
||||
?: globalCommon?.useValueInterpolation
|
||||
?: createCommonGenerateConfig(name).useValueInterpolation
|
||||
|
||||
override val locations
|
||||
get() = selfCommon?.locations
|
||||
?: globalCommon?.locations
|
||||
?: createCommonGenerateConfig(name).locations
|
||||
}
|
||||
|
||||
private fun createCommonGenerateConfig(name: String) = object : GropifyConfig.CommonGenerateConfig {
|
||||
override val name get() = name
|
||||
override val isEnabled get() = true
|
||||
override val existsPropertyFiles get() = mutableListOf(GropifyConfig.DEFAULT_EXISTS_PROPERTY_FILE)
|
||||
override val permanentKeyValues get() = mutableMapOf<String, Any>()
|
||||
override val replacementKeyValues get() = mutableMapOf<String, Any>()
|
||||
override val excludeKeys get() = mutableListOf<Any>()
|
||||
override val includeKeys get() = mutableListOf<Any>()
|
||||
override val keyValuesRules get() = mutableMapOf<String, PropertyValueRule>()
|
||||
override val excludeNonStringValue get() = true
|
||||
override val useTypeAutoConversion get() = true
|
||||
override val useValueInterpolation get() = true
|
||||
override val locations get() = GropifyConfig.defaultLocations
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.config.extension
|
||||
|
||||
import com.highcapable.gropify.gradle.api.extension.getFullName
|
||||
import com.highcapable.gropify.plugin.config.DefaultConfig
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension
|
||||
import org.gradle.api.Project
|
||||
|
||||
internal fun GropifyConfig.from(project: Project) = projects[project.getFullName()] ?: global
|
||||
|
||||
internal fun GropifyConfigureExtension.GenerateConfigureScope.create(
|
||||
name: String = "Global",
|
||||
global: GropifyConfigureExtension.GenerateConfigureScope = this
|
||||
) = object : GropifyConfig.GenerateConfig {
|
||||
|
||||
override val buildscript
|
||||
get() = this@create.buildscriptConfigure?.create(name, global.buildscriptConfigure, this@create.commonConfigure, global.commonConfigure)
|
||||
?: global.buildscriptConfigure?.create(name, this@create.buildscriptConfigure ?: global.buildscriptConfigure)
|
||||
?: DefaultConfig.createGenerateConfig(name, this@create.commonConfigure ?: global.commonConfigure).buildscript
|
||||
|
||||
override val android
|
||||
get() = this@create.androidConfigure?.create(name, global.androidConfigure, this@create.commonConfigure, global.commonConfigure)
|
||||
?: global.androidConfigure?.create(name, this@create.androidConfigure ?: global.androidConfigure)
|
||||
?: DefaultConfig.createGenerateConfig(name, this@create.commonConfigure ?: global.commonConfigure).android
|
||||
|
||||
override val jvm
|
||||
get() = this@create.jvmConfigure?.create(name, global.jvmConfigure, this@create.commonConfigure, global.commonConfigure)
|
||||
?: global.jvmConfigure?.create(name, this@create.jvmConfigure ?: global.jvmConfigure)
|
||||
?: DefaultConfig.createGenerateConfig(name, this@create.commonConfigure ?: global.commonConfigure).jvm
|
||||
|
||||
override val kmp
|
||||
get() = this@create.kmpConfigure?.create(name, global.kmpConfigure, this@create.commonConfigure, global.commonConfigure)
|
||||
?: global.kmpConfigure?.create(name, this@create.kmpConfigure ?: global.kmpConfigure)
|
||||
?: DefaultConfig.createGenerateConfig(name, this@create.commonConfigure ?: global.commonConfigure).kmp
|
||||
}
|
||||
|
||||
private fun GropifyConfigureExtension.BuildscriptGenerateConfigureScope.create(
|
||||
name: String,
|
||||
global: GropifyConfigureExtension.BuildscriptGenerateConfigureScope? = null,
|
||||
selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null,
|
||||
globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null
|
||||
) = object : GropifyConfig.BuildscriptGenerateConfig {
|
||||
|
||||
override val name get() = name
|
||||
|
||||
override val extensionName
|
||||
get() = this@create.extensionName.ifBlank { null }
|
||||
?: global?.extensionName?.ifBlank { null }
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).extensionName
|
||||
|
||||
override val isEnabled
|
||||
get() = this@create.isEnabled
|
||||
?: selfCommon?.isEnabled
|
||||
?: global?.isEnabled
|
||||
?: globalCommon?.isEnabled
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).isEnabled
|
||||
|
||||
override val existsPropertyFiles
|
||||
get() = this@create.existsPropertyFiles
|
||||
?: global?.existsPropertyFiles
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).existsPropertyFiles
|
||||
|
||||
override val permanentKeyValues
|
||||
get() = this@create.permanentKeyValues
|
||||
?: global?.permanentKeyValues
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).permanentKeyValues
|
||||
|
||||
override val replacementKeyValues
|
||||
get() = this@create.replacementKeyValues
|
||||
?: global?.replacementKeyValues
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).replacementKeyValues
|
||||
|
||||
override val excludeKeys
|
||||
get() = this@create.excludeKeys
|
||||
?: global?.excludeKeys
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).excludeKeys
|
||||
|
||||
override val includeKeys
|
||||
get() = this@create.includeKeys
|
||||
?: global?.includeKeys
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).includeKeys
|
||||
|
||||
override val keyValuesRules
|
||||
get() = this@create.keyValuesRules
|
||||
?: global?.keyValuesRules
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).keyValuesRules
|
||||
|
||||
override val excludeNonStringValue
|
||||
get() = this@create.excludeNonStringValue
|
||||
?: selfCommon?.excludeNonStringValue
|
||||
?: global?.excludeNonStringValue
|
||||
?: globalCommon?.excludeNonStringValue
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).excludeNonStringValue
|
||||
|
||||
override val useTypeAutoConversion
|
||||
get() = this@create.useTypeAutoConversion
|
||||
?: selfCommon?.useTypeAutoConversion
|
||||
?: global?.useTypeAutoConversion
|
||||
?: globalCommon?.useTypeAutoConversion
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).useTypeAutoConversion
|
||||
|
||||
override val useValueInterpolation
|
||||
get() = this@create.useValueInterpolation
|
||||
?: selfCommon?.useValueInterpolation
|
||||
?: global?.useValueInterpolation
|
||||
?: globalCommon?.useValueInterpolation
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).useValueInterpolation
|
||||
|
||||
override val locations
|
||||
get() = this@create.locations
|
||||
?: selfCommon?.locations
|
||||
?: global?.locations
|
||||
?: globalCommon?.locations
|
||||
?: DefaultConfig.createBuildscriptGenerateConfig(name, selfCommon, globalCommon).locations
|
||||
}
|
||||
|
||||
private fun GropifyConfigureExtension.AndroidGenerateConfigureScope.create(
|
||||
name: String,
|
||||
global: GropifyConfigureExtension.AndroidGenerateConfigureScope? = null,
|
||||
selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null,
|
||||
globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null
|
||||
) = object : GropifyConfig.AndroidGenerateConfig {
|
||||
|
||||
override val name get() = name
|
||||
|
||||
override val isEnabled
|
||||
get() = this@create.isEnabled
|
||||
?: selfCommon?.isEnabled
|
||||
?: global?.isEnabled
|
||||
?: globalCommon?.isEnabled
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).isEnabled
|
||||
|
||||
override val generateDirPath
|
||||
get() = this@create.generateDirPath.ifBlank { null }
|
||||
?: global?.generateDirPath?.ifBlank { null }
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).generateDirPath
|
||||
|
||||
override val sourceSetName
|
||||
get() = this@create.sourceSetName.ifBlank { null }
|
||||
?: global?.sourceSetName?.ifBlank { null }
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).sourceSetName
|
||||
|
||||
override val useKotlin
|
||||
get() = this@create.useKotlin
|
||||
?: global?.useKotlin
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).useKotlin
|
||||
|
||||
override val packageName
|
||||
get() = this@create.packageName.ifBlank { null }
|
||||
?: global?.packageName?.ifBlank { null }
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).packageName
|
||||
|
||||
override val className
|
||||
get() = this@create.className.ifBlank { null }
|
||||
?: global?.className?.ifBlank { null }
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).className
|
||||
|
||||
override val isRestrictedAccessEnabled
|
||||
get() = this@create.isRestrictedAccessEnabled
|
||||
?: global?.isRestrictedAccessEnabled
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).isRestrictedAccessEnabled
|
||||
|
||||
override val isIsolationEnabled
|
||||
get() = this@create.isIsolationEnabled
|
||||
?: global?.isIsolationEnabled
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).isIsolationEnabled
|
||||
|
||||
override val existsPropertyFiles
|
||||
get() = this@create.existsPropertyFiles
|
||||
?: global?.existsPropertyFiles
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).existsPropertyFiles
|
||||
|
||||
override val permanentKeyValues
|
||||
get() = this@create.permanentKeyValues
|
||||
?: global?.permanentKeyValues
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).permanentKeyValues
|
||||
|
||||
override val replacementKeyValues
|
||||
get() = this@create.replacementKeyValues
|
||||
?: global?.replacementKeyValues
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).replacementKeyValues
|
||||
|
||||
override val excludeKeys
|
||||
get() = this@create.excludeKeys
|
||||
?: global?.excludeKeys
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).excludeKeys
|
||||
|
||||
override val includeKeys
|
||||
get() = this@create.includeKeys
|
||||
?: global?.includeKeys
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).includeKeys
|
||||
|
||||
override val keyValuesRules
|
||||
get() = this@create.keyValuesRules
|
||||
?: global?.keyValuesRules
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).keyValuesRules
|
||||
|
||||
override val excludeNonStringValue
|
||||
get() = this@create.excludeNonStringValue
|
||||
?: selfCommon?.excludeNonStringValue
|
||||
?: global?.excludeNonStringValue
|
||||
?: globalCommon?.excludeNonStringValue
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).excludeNonStringValue
|
||||
|
||||
override val useTypeAutoConversion
|
||||
get() = this@create.useTypeAutoConversion
|
||||
?: selfCommon?.useTypeAutoConversion
|
||||
?: global?.useTypeAutoConversion
|
||||
?: globalCommon?.useTypeAutoConversion
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).useTypeAutoConversion
|
||||
|
||||
override val useValueInterpolation
|
||||
get() = this@create.useValueInterpolation
|
||||
?: selfCommon?.useValueInterpolation
|
||||
?: global?.useValueInterpolation
|
||||
?: globalCommon?.useValueInterpolation
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).useValueInterpolation
|
||||
|
||||
override val locations
|
||||
get() = this@create.locations
|
||||
?: selfCommon?.locations
|
||||
?: global?.locations
|
||||
?: globalCommon?.locations
|
||||
?: DefaultConfig.createAndroidGenerateConfig(name, selfCommon, globalCommon).locations
|
||||
}
|
||||
|
||||
private fun GropifyConfigureExtension.JvmGenerateConfigureScope.create(
|
||||
name: String,
|
||||
global: GropifyConfigureExtension.JvmGenerateConfigureScope? = null,
|
||||
selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null,
|
||||
globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null
|
||||
) = object : GropifyConfig.JvmGenerateConfig {
|
||||
|
||||
override val name get() = name
|
||||
|
||||
override val isEnabled
|
||||
get() = this@create.isEnabled
|
||||
?: selfCommon?.isEnabled
|
||||
?: global?.isEnabled
|
||||
?: globalCommon?.isEnabled
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).isEnabled
|
||||
|
||||
override val generateDirPath
|
||||
get() = this@create.generateDirPath.ifBlank { null }
|
||||
?: global?.generateDirPath?.ifBlank { null }
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).generateDirPath
|
||||
|
||||
override val sourceSetName
|
||||
get() = this@create.sourceSetName.ifBlank { null }
|
||||
?: global?.sourceSetName?.ifBlank { null }
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).sourceSetName
|
||||
|
||||
override val useKotlin
|
||||
get() = this@create.useKotlin
|
||||
?: global?.useKotlin
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).useKotlin
|
||||
|
||||
override val packageName
|
||||
get() = this@create.packageName.ifBlank { null }
|
||||
?: global?.packageName?.ifBlank { null }
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).packageName
|
||||
|
||||
override val className
|
||||
get() = this@create.className.ifBlank { null }
|
||||
?: global?.className?.ifBlank { null }
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).className
|
||||
|
||||
override val isRestrictedAccessEnabled
|
||||
get() = this@create.isRestrictedAccessEnabled
|
||||
?: global?.isRestrictedAccessEnabled
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).isRestrictedAccessEnabled
|
||||
|
||||
override val isIsolationEnabled
|
||||
get() = this@create.isIsolationEnabled
|
||||
?: global?.isIsolationEnabled
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).isIsolationEnabled
|
||||
|
||||
override val existsPropertyFiles
|
||||
get() = this@create.existsPropertyFiles
|
||||
?: global?.existsPropertyFiles
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).existsPropertyFiles
|
||||
|
||||
override val permanentKeyValues
|
||||
get() = this@create.permanentKeyValues
|
||||
?: global?.permanentKeyValues
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).permanentKeyValues
|
||||
|
||||
override val replacementKeyValues
|
||||
get() = this@create.replacementKeyValues
|
||||
?: global?.replacementKeyValues
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).replacementKeyValues
|
||||
|
||||
override val excludeKeys
|
||||
get() = this@create.excludeKeys
|
||||
?: global?.excludeKeys
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).excludeKeys
|
||||
|
||||
override val includeKeys
|
||||
get() = this@create.includeKeys
|
||||
?: global?.includeKeys
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).includeKeys
|
||||
|
||||
override val keyValuesRules
|
||||
get() = this@create.keyValuesRules
|
||||
?: global?.keyValuesRules
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).keyValuesRules
|
||||
|
||||
override val excludeNonStringValue
|
||||
get() = this@create.excludeNonStringValue
|
||||
?: selfCommon?.excludeNonStringValue
|
||||
?: global?.excludeNonStringValue
|
||||
?: globalCommon?.excludeNonStringValue
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).excludeNonStringValue
|
||||
|
||||
override val useTypeAutoConversion
|
||||
get() = this@create.useTypeAutoConversion
|
||||
?: selfCommon?.useTypeAutoConversion
|
||||
?: global?.useTypeAutoConversion
|
||||
?: globalCommon?.useTypeAutoConversion
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).useTypeAutoConversion
|
||||
|
||||
override val useValueInterpolation
|
||||
get() = this@create.useValueInterpolation
|
||||
?: selfCommon?.useValueInterpolation
|
||||
?: global?.useValueInterpolation
|
||||
?: globalCommon?.useValueInterpolation
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).useValueInterpolation
|
||||
|
||||
override val locations
|
||||
get() = this@create.locations
|
||||
?: selfCommon?.locations
|
||||
?: global?.locations
|
||||
?: globalCommon?.locations
|
||||
?: DefaultConfig.createJvmGenerateConfig(name, selfCommon, globalCommon).locations
|
||||
}
|
||||
|
||||
private fun GropifyConfigureExtension.KmpGenerateConfigureScope.create(
|
||||
name: String,
|
||||
global: GropifyConfigureExtension.KmpGenerateConfigureScope? = null,
|
||||
selfCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null,
|
||||
globalCommon: GropifyConfigureExtension.CommonGenerateConfigureScope? = null
|
||||
) = object : GropifyConfig.KmpGenerateConfig {
|
||||
|
||||
override val name get() = name
|
||||
|
||||
override val isEnabled
|
||||
get() = this@create.isEnabled
|
||||
?: selfCommon?.isEnabled
|
||||
?: global?.isEnabled
|
||||
?: globalCommon?.isEnabled
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).isEnabled
|
||||
|
||||
override val generateDirPath
|
||||
get() = this@create.generateDirPath.ifBlank { null }
|
||||
?: global?.generateDirPath?.ifBlank { null }
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).generateDirPath
|
||||
|
||||
override val sourceSetName
|
||||
get() = this@create.sourceSetName.ifBlank { null }
|
||||
?: global?.sourceSetName?.ifBlank { null }
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).sourceSetName
|
||||
|
||||
override val packageName
|
||||
get() = this@create.packageName.ifBlank { null }
|
||||
?: global?.packageName?.ifBlank { null }
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).packageName
|
||||
|
||||
override val className
|
||||
get() = this@create.className.ifBlank { null }
|
||||
?: global?.className?.ifBlank { null }
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).className
|
||||
|
||||
override val isRestrictedAccessEnabled
|
||||
get() = this@create.isRestrictedAccessEnabled
|
||||
?: global?.isRestrictedAccessEnabled
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).isRestrictedAccessEnabled
|
||||
|
||||
override val isIsolationEnabled
|
||||
get() = this@create.isIsolationEnabled
|
||||
?: global?.isIsolationEnabled
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).isIsolationEnabled
|
||||
|
||||
override val existsPropertyFiles
|
||||
get() = this@create.existsPropertyFiles
|
||||
?: global?.existsPropertyFiles
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).existsPropertyFiles
|
||||
|
||||
override val permanentKeyValues
|
||||
get() = this@create.permanentKeyValues
|
||||
?: global?.permanentKeyValues
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).permanentKeyValues
|
||||
|
||||
override val replacementKeyValues
|
||||
get() = this@create.replacementKeyValues
|
||||
?: global?.replacementKeyValues
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).replacementKeyValues
|
||||
|
||||
override val excludeKeys
|
||||
get() = this@create.excludeKeys
|
||||
?: global?.excludeKeys
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).excludeKeys
|
||||
|
||||
override val includeKeys
|
||||
get() = this@create.includeKeys
|
||||
?: global?.includeKeys
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).includeKeys
|
||||
|
||||
override val keyValuesRules
|
||||
get() = this@create.keyValuesRules
|
||||
?: global?.keyValuesRules
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).keyValuesRules
|
||||
|
||||
override val excludeNonStringValue
|
||||
get() = this@create.excludeNonStringValue
|
||||
?: selfCommon?.excludeNonStringValue
|
||||
?: global?.excludeNonStringValue
|
||||
?: globalCommon?.excludeNonStringValue
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).excludeNonStringValue
|
||||
|
||||
override val useTypeAutoConversion
|
||||
get() = this@create.useTypeAutoConversion
|
||||
?: selfCommon?.useTypeAutoConversion
|
||||
?: global?.useTypeAutoConversion
|
||||
?: globalCommon?.useTypeAutoConversion
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).useTypeAutoConversion
|
||||
|
||||
override val useValueInterpolation
|
||||
get() = this@create.useValueInterpolation
|
||||
?: selfCommon?.useValueInterpolation
|
||||
?: global?.useValueInterpolation
|
||||
?: globalCommon?.useValueInterpolation
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).useValueInterpolation
|
||||
|
||||
override val locations
|
||||
get() = this@create.locations
|
||||
?: selfCommon?.locations
|
||||
?: global?.locations
|
||||
?: globalCommon?.locations
|
||||
?: DefaultConfig.createKmpGenerateConfig(name, selfCommon, globalCommon).locations
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.config.proxy
|
||||
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.plugin.config.type.GropifyLocation
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyValueRule
|
||||
|
||||
/**
|
||||
* Configuration interface for Gropify.
|
||||
*/
|
||||
internal interface GropifyConfig {
|
||||
|
||||
companion object {
|
||||
|
||||
internal const val ARTIFACTS_NAME = "artifacts"
|
||||
internal const val ACCESSORS_NAME = "gropify-accessors"
|
||||
|
||||
internal const val DEFAULT_PACKAGE_NAME = "${Gropify.GROUP_NAME}.properties.default"
|
||||
|
||||
internal const val DEFAULT_COMMON_CODE_GENERATE_DIR_PATH = "build/generated/gropify"
|
||||
internal const val DEFAULT_ANDROID_JVM_SOURCE_SET_NAME = "main"
|
||||
internal const val DEFAULT_KMP_COMMON_SOURCE_SET_NAME = "commonMain"
|
||||
|
||||
internal const val DEFAULT_EXISTS_PROPERTY_FILE = "gradle.properties"
|
||||
internal const val DEFAULT_EXTENSION_NAME = "gropify"
|
||||
|
||||
internal val defaultLocations = listOf(GropifyLocation.CurrentProject, GropifyLocation.RootProject)
|
||||
}
|
||||
|
||||
/** Whether to enable this plugin. */
|
||||
val isEnabled: Boolean
|
||||
|
||||
/** Whether to enable debug mode. */
|
||||
val debugMode: Boolean
|
||||
|
||||
/** Configure global. */
|
||||
val global: GenerateConfig
|
||||
|
||||
/** Configure projects. */
|
||||
val projects: MutableMap<String, GenerateConfig>
|
||||
|
||||
/**
|
||||
* Generate configuration interface.
|
||||
*/
|
||||
interface GenerateConfig {
|
||||
|
||||
val buildscript: BuildscriptGenerateConfig
|
||||
|
||||
val android: AndroidGenerateConfig
|
||||
|
||||
val jvm: JvmGenerateConfig
|
||||
|
||||
val kmp: KmpGenerateConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Buildscript generation code configuration interface.
|
||||
*/
|
||||
interface BuildscriptGenerateConfig : CommonGenerateConfig {
|
||||
|
||||
/** Custom buildscript extension name. */
|
||||
val extensionName: String
|
||||
}
|
||||
|
||||
/**
|
||||
* Android project generate configuration interface.
|
||||
*/
|
||||
interface AndroidGenerateConfig : JvmGenerateConfig
|
||||
|
||||
/**
|
||||
* Jvm project generate configuration interface.
|
||||
*/
|
||||
interface JvmGenerateConfig : CommonCodeGenerateConfig {
|
||||
|
||||
/** Whether to use Kotlin language generation. */
|
||||
val useKotlin: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Kotlin Multiplatform project generate configuration interface.
|
||||
*/
|
||||
interface KmpGenerateConfig : CommonCodeGenerateConfig
|
||||
|
||||
/**
|
||||
* Project common code generate configuration interface.
|
||||
*/
|
||||
interface CommonCodeGenerateConfig : SourceCodeGenerateConfig {
|
||||
|
||||
/** Custom deployment `sourceSet` name. */
|
||||
val sourceSetName: String
|
||||
|
||||
/** Custom generated package name. */
|
||||
val packageName: String
|
||||
|
||||
/** Custom generated class name. */
|
||||
val className: String
|
||||
|
||||
/** Whether to enable restricted access. */
|
||||
val isRestrictedAccessEnabled: Boolean
|
||||
|
||||
/** Whether to enable code isolation. */
|
||||
val isIsolationEnabled: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Project code generate configuration interface.
|
||||
*/
|
||||
interface SourceCodeGenerateConfig : CommonGenerateConfig {
|
||||
|
||||
/** Custom generated directory path. */
|
||||
val generateDirPath: String
|
||||
}
|
||||
|
||||
/**
|
||||
* Common generate configuration interface.
|
||||
*/
|
||||
interface CommonGenerateConfig {
|
||||
|
||||
/** The config name (project name). */
|
||||
val name: String
|
||||
|
||||
/** Whether to generate code. */
|
||||
val isEnabled: Boolean
|
||||
|
||||
/** Exists property files. */
|
||||
val existsPropertyFiles: MutableList<String>
|
||||
|
||||
/** Permanent list of properties' key-values. */
|
||||
val permanentKeyValues: MutableMap<String, Any>
|
||||
|
||||
/** Replacement list of properties' key-values. */
|
||||
val replacementKeyValues: MutableMap<String, Any>
|
||||
|
||||
/** Key list of properties' key-values name that need to be excluded. */
|
||||
val excludeKeys: MutableList<Any>
|
||||
|
||||
/** Key list of properties' key-values name that need to be included. */
|
||||
val includeKeys: MutableList<Any>
|
||||
|
||||
/** Properties' key-values rules. */
|
||||
val keyValuesRules: MutableMap<String, PropertyValueRule>
|
||||
|
||||
/** Whether to exclude the non-string type key-values content. */
|
||||
val excludeNonStringValue: Boolean
|
||||
|
||||
/** Whether to use automatic type conversion. */
|
||||
val useTypeAutoConversion: Boolean
|
||||
|
||||
/** Whether to use key-values content interpolation. */
|
||||
val useValueInterpolation: Boolean
|
||||
|
||||
/** Locations. */
|
||||
val locations: List<GropifyLocation>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.gropify.plugin.config.type
|
||||
|
||||
/**
|
||||
* Properties' key-values location.
|
||||
*/
|
||||
enum class GropifyLocation {
|
||||
/** The current project. */
|
||||
CurrentProject,
|
||||
|
||||
/** The root project. */
|
||||
RootProject,
|
||||
|
||||
/** The Gradle home directory. */
|
||||
Global,
|
||||
|
||||
/** The system properties. */
|
||||
System,
|
||||
|
||||
/** The system environment variables. */
|
||||
SystemEnv
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/17.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.deployer
|
||||
|
||||
import com.highcapable.gropify.gradle.api.entity.Dependency
|
||||
import com.highcapable.gropify.gradle.api.entity.ProjectDescriptor
|
||||
import com.highcapable.gropify.gradle.api.extension.addDependencyToBuildscript
|
||||
import com.highcapable.gropify.gradle.api.extension.getOrCreate
|
||||
import com.highcapable.gropify.gradle.api.extension.toClassOrNull
|
||||
import com.highcapable.gropify.plugin.DefaultDeployer
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.plugin.compiler.extension.compile
|
||||
import com.highcapable.gropify.plugin.config.extension.from
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.deployer.proxy.Deployer
|
||||
import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension
|
||||
import com.highcapable.gropify.plugin.generator.BuildscriptGenerator
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyMap
|
||||
import com.highcapable.gropify.utils.extension.camelcase
|
||||
import com.highcapable.gropify.utils.extension.isEmpty
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.initialization.Settings
|
||||
import java.io.File
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Buildscript deployer.
|
||||
*/
|
||||
internal class BuildscriptDeployer(private val _config: () -> GropifyConfig) : Deployer {
|
||||
|
||||
private val config get() = _config()
|
||||
|
||||
private val buildscriptGenerator = BuildscriptGenerator()
|
||||
|
||||
private var buildscriptAccessorsDir by Delegates.notNull<File>()
|
||||
private val buildscriptAccessorsDependency = Dependency(Gropify.GROUP_NAME, GropifyConfig.ACCESSORS_NAME, Gropify.VERSION)
|
||||
|
||||
private var cachedSettingsProperties = mutableListOf<PropertyMap>()
|
||||
|
||||
override fun init(settings: Settings, configModified: Boolean) {
|
||||
buildscriptAccessorsDir = generatedBuildscriptAccessorsDir(settings)
|
||||
|
||||
val allConfig = mutableListOf<GropifyConfig.BuildscriptGenerateConfig>()
|
||||
val allProperties = mutableListOf<PropertyMap>()
|
||||
|
||||
if (config.global.buildscript.isEnabled) {
|
||||
val map = DefaultDeployer.generateMap(config.global.buildscript, ProjectDescriptor.create(settings))
|
||||
allProperties.add(map)
|
||||
allConfig.add(config.global.buildscript)
|
||||
}
|
||||
|
||||
config.projects.forEach { (name, subConfig) ->
|
||||
if (!subConfig.buildscript.isEnabled) return@forEach
|
||||
|
||||
val map = DefaultDeployer.generateMap(subConfig.buildscript, ProjectDescriptor.create(settings, name))
|
||||
allProperties.add(map)
|
||||
allConfig.add(subConfig.buildscript)
|
||||
}
|
||||
|
||||
if (!configModified &&
|
||||
allProperties == cachedSettingsProperties &&
|
||||
!buildscriptAccessorsDir.resolve(buildscriptAccessorsDependency.relativePath).isEmpty()
|
||||
) return
|
||||
|
||||
cachedSettingsProperties = allProperties
|
||||
buildscriptGenerator.build(allConfig, allProperties).compile(
|
||||
buildscriptAccessorsDependency,
|
||||
buildscriptAccessorsDir.absolutePath,
|
||||
buildscriptGenerator.compileStubFiles
|
||||
)
|
||||
}
|
||||
|
||||
override fun resolve(rootProject: Project, configModified: Boolean) {
|
||||
if (!buildscriptAccessorsDir.resolve(buildscriptAccessorsDependency.relativePath).isEmpty())
|
||||
rootProject.addDependencyToBuildscript(buildscriptAccessorsDir.absolutePath, buildscriptAccessorsDependency)
|
||||
}
|
||||
|
||||
override fun deploy(rootProject: Project, configModified: Boolean) {
|
||||
fun Project.deploy() {
|
||||
val config = config.from(this).buildscript
|
||||
if (!config.isEnabled) return
|
||||
|
||||
val className = buildscriptGenerator.propertiesClass(config.name)
|
||||
val accessorsClass = className.toClassOrNull(this) ?: throw RuntimeException(
|
||||
"""
|
||||
Generated class "$className" not found, stop loading $this.
|
||||
Please check whether the initialization process is interrupted and re-run Gradle sync.
|
||||
If this doesn't work, please manually delete the entire "${buildscriptAccessorsDir.absolutePath}" directory.
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
getOrCreate(config.extensionName.camelcase(), accessorsClass)
|
||||
}
|
||||
|
||||
rootProject.deploy()
|
||||
rootProject.subprojects.forEach { it.deploy() }
|
||||
}
|
||||
|
||||
private fun generatedBuildscriptAccessorsDir(settings: Settings) =
|
||||
settings.rootDir.resolve(".gradle")
|
||||
.resolve(GropifyConfigureExtension.NAME)
|
||||
.resolve(GropifyConfig.ARTIFACTS_NAME)
|
||||
.apply { mkdirs() }
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/17.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.deployer
|
||||
|
||||
import com.highcapable.gropify.gradle.api.entity.ProjectDescriptor
|
||||
import com.highcapable.gropify.gradle.api.extension.getFullName
|
||||
import com.highcapable.gropify.gradle.api.extension.getOrNull
|
||||
import com.highcapable.gropify.internal.Logger
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.plugin.DefaultDeployer.generateMap
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.plugin.config.extension.from
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.deployer.extension.ExtensionName
|
||||
import com.highcapable.gropify.plugin.deployer.extension.ProjectType
|
||||
import com.highcapable.gropify.plugin.deployer.extension.resolveType
|
||||
import com.highcapable.gropify.plugin.deployer.proxy.Deployer
|
||||
import com.highcapable.gropify.plugin.generator.SourceCodeGenerator
|
||||
import com.highcapable.gropify.plugin.generator.config.GenerateConfig
|
||||
import com.highcapable.gropify.plugin.generator.config.SourceCodeSpec
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyMap
|
||||
import com.highcapable.gropify.plugin.helper.AndroidProjectHelper
|
||||
import com.highcapable.gropify.utils.extension.flatted
|
||||
import com.highcapable.gropify.utils.extension.isEmpty
|
||||
import com.highcapable.gropify.utils.extension.upperCamelcase
|
||||
import com.highcapable.kavaref.KavaRef.Companion.asResolver
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.initialization.Settings
|
||||
import java.io.File
|
||||
import kotlin.collections.set
|
||||
|
||||
/**
|
||||
* Source code deployer.
|
||||
*/
|
||||
internal class SourceCodeDeployer(private val _config: () -> GropifyConfig) : Deployer {
|
||||
|
||||
private val config get() = _config()
|
||||
|
||||
private val debugMode get() = config.debugMode
|
||||
|
||||
private val sourceCodeGenerator = SourceCodeGenerator()
|
||||
|
||||
private var cachedProjectProperties = mutableMapOf<String, PropertyMap>()
|
||||
|
||||
override fun init(settings: Settings, configModified: Boolean) {
|
||||
// No initialization required.
|
||||
}
|
||||
|
||||
override fun resolve(rootProject: Project, configModified: Boolean) {
|
||||
// No resolution required.
|
||||
}
|
||||
|
||||
override fun deploy(rootProject: Project, configModified: Boolean) {
|
||||
fun Project.generate() {
|
||||
val projectType = resolveType()
|
||||
|
||||
val config = config.from(this).let {
|
||||
when (projectType) {
|
||||
ProjectType.Android -> it.android
|
||||
ProjectType.Kotlin, ProjectType.Java -> it.jvm
|
||||
ProjectType.KMP -> it.kmp
|
||||
else -> null
|
||||
} ?: return
|
||||
}
|
||||
|
||||
val sourceCodeType = decideSourceCodeType(config, projectType)
|
||||
val generateDirPath = resolveGenerateDirPath(config)
|
||||
|
||||
if (!config.isEnabled) return
|
||||
|
||||
val outputDir = file(generateDirPath)
|
||||
val properties = generateMap(config, ProjectDescriptor.create(project = this))
|
||||
|
||||
if (!configModified && properties == cachedProjectProperties[getFullName()] && !outputDir.isEmpty()) {
|
||||
if (config.isEnabled) configureSourceSets(project = this)
|
||||
return
|
||||
}
|
||||
|
||||
outputDir.apply { if (exists()) deleteRecursively() }
|
||||
|
||||
// The directory will be re-created every time.
|
||||
outputDir.mkdirs()
|
||||
|
||||
cachedProjectProperties[getFullName()] = properties
|
||||
|
||||
val packageName = generatedPackageName(config)
|
||||
val className = generatedClassName(config)
|
||||
|
||||
val generateConfig = GenerateConfig(packageName, className)
|
||||
|
||||
sourceCodeGenerator.build(config, generateConfig, properties).let { generator ->
|
||||
generator.first { it.type == sourceCodeType }
|
||||
}.writeTo(outputDir)
|
||||
configureSourceSets(project = this)
|
||||
}
|
||||
|
||||
rootProject.generate()
|
||||
rootProject.subprojects.forEach {
|
||||
it.afterEvaluate {
|
||||
generate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureSourceSets(project: Project) {
|
||||
val projectType = project.resolveType()
|
||||
|
||||
val config = config.from(project).let {
|
||||
when (projectType) {
|
||||
ProjectType.Android -> it.android
|
||||
ProjectType.Kotlin, ProjectType.Java -> it.jvm
|
||||
ProjectType.KMP -> it.kmp
|
||||
else -> null
|
||||
} ?: return
|
||||
}
|
||||
|
||||
val sourceCodeType = decideSourceCodeType(config, projectType)
|
||||
val resolveSourceCodeType = if (projectType == ProjectType.Java) SourceCodeSpec.Type.Java else sourceCodeType
|
||||
val generateDirPath = resolveGenerateDirPath(config)
|
||||
|
||||
val androidExtension = project.getOrNull(ExtensionName.ANDROID)
|
||||
val kotlinExtension = project.getOrNull(ExtensionName.KOTLIN)
|
||||
val javaExtension = project.getOrNull(ExtensionName.JAVA)
|
||||
|
||||
val extension = when (projectType) {
|
||||
ProjectType.Android -> androidExtension
|
||||
ProjectType.Kotlin -> if (sourceCodeType == SourceCodeSpec.Type.Kotlin) kotlinExtension else javaExtension
|
||||
ProjectType.Java -> javaExtension
|
||||
ProjectType.KMP -> kotlinExtension
|
||||
else -> return
|
||||
} ?: return
|
||||
|
||||
val collection = extension.asResolver().optional(!debugMode).firstMethodOrNull {
|
||||
name = "getSourceSets"
|
||||
}?.invokeQuietly<Iterable<*>>()
|
||||
|
||||
val sourceSet = collection?.firstOrNull {
|
||||
it?.asResolver()?.optional(!debugMode)?.firstMethodOrNull {
|
||||
name = "getName"
|
||||
}?.invokeQuietly<String>() == config.sourceSetName
|
||||
} ?: return Logger.warn(
|
||||
"Could not found source sets \"${config.sourceSetName}\" in project '${project.getFullName()}' ($projectType)."
|
||||
)
|
||||
|
||||
val directorySet = sourceSet.asResolver().optional(!debugMode).firstMethodOrNull {
|
||||
name = when (resolveSourceCodeType) {
|
||||
SourceCodeSpec.Type.Java -> "getJava"
|
||||
SourceCodeSpec.Type.Kotlin -> "getKotlin"
|
||||
}
|
||||
superclass()
|
||||
}?.invokeQuietly()
|
||||
val srcDirs = directorySet?.asResolver()?.optional(!debugMode)?.firstMethodOrNull {
|
||||
name = "getSrcDirs"
|
||||
}?.invokeQuietly<Set<*>>()
|
||||
|
||||
val alreadyAdded = srcDirs?.any { it is File && it.canonicalPath.endsWith(generateDirPath) } == true
|
||||
if (!alreadyAdded) {
|
||||
val resolver = directorySet?.asResolver()?.optional(!debugMode)?.firstMethodOrNull {
|
||||
name = "srcDir"
|
||||
parameters(Any::class)
|
||||
superclass()
|
||||
}
|
||||
resolver?.invokeQuietly(generateDirPath) ?: Logger.error(
|
||||
"Project '${project.getFullName()}' source sets deployed failed, method \"srcDir\" maybe failed during the processing."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun decideSourceCodeType(config: GropifyConfig.CommonCodeGenerateConfig, type: ProjectType) = when (type) {
|
||||
ProjectType.Android, ProjectType.Kotlin ->
|
||||
if (config is GropifyConfig.JvmGenerateConfig && !config.useKotlin)
|
||||
SourceCodeSpec.Type.Java
|
||||
else SourceCodeSpec.Type.Kotlin
|
||||
ProjectType.Java -> SourceCodeSpec.Type.Java
|
||||
ProjectType.KMP -> SourceCodeSpec.Type.Kotlin
|
||||
else -> Gropify.error("Unsupported project type for source code generation.")
|
||||
}
|
||||
|
||||
private fun resolveGenerateDirPath(config: GropifyConfig.CommonCodeGenerateConfig) = "${config.generateDirPath}/src/main"
|
||||
|
||||
private fun Project.generatedPackageName(config: GropifyConfig.CommonCodeGenerateConfig): String {
|
||||
val packageName = config.packageName.ifBlank { null }
|
||||
?: AndroidProjectHelper.getNamespace(this)
|
||||
?: group.toString().ifBlank { null }
|
||||
?: "${GropifyConfig.DEFAULT_PACKAGE_NAME}.${getFullName(useColon = false).replace(":", "").flatted()}"
|
||||
|
||||
return if (config.isIsolationEnabled) "$packageName.generated" else packageName
|
||||
}
|
||||
|
||||
private fun Project.generatedClassName(config: GropifyConfig.CommonCodeGenerateConfig): String {
|
||||
val className = config.className.ifBlank { null }
|
||||
?: getFullName(useColon = false).replace(":", "_").upperCamelcase().ifBlank { null }
|
||||
?: "Undefined"
|
||||
|
||||
return "${className.upperCamelcase()}Properties"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/17.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.deployer.extension
|
||||
|
||||
import com.highcapable.gropify.gradle.api.extension.get
|
||||
import com.highcapable.gropify.gradle.api.extension.hasExtension
|
||||
import com.highcapable.gropify.gradle.api.extension.toClassOrNull
|
||||
import com.highcapable.kavaref.extension.isSubclassOf
|
||||
import org.gradle.api.Project
|
||||
|
||||
/**
|
||||
* Resolve current project type.
|
||||
* @receiver [Project]
|
||||
* @return [ProjectType]
|
||||
*/
|
||||
internal fun Project.resolveType() = when {
|
||||
hasKmpPlugin() -> ProjectType.KMP
|
||||
hasExtension(ExtensionName.ANDROID) -> ProjectType.Android
|
||||
hasExtension(ExtensionName.KOTLIN) -> ProjectType.Kotlin
|
||||
hasExtension(ExtensionName.JAVA) -> ProjectType.Java
|
||||
else -> ProjectType.Unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Project extension names.
|
||||
*/
|
||||
internal object ExtensionName {
|
||||
|
||||
const val ANDROID = "android"
|
||||
const val JAVA = "java"
|
||||
const val KOTLIN = "kotlin"
|
||||
|
||||
const val KMP_EXTENSION_CLASS = "org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension"
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current project has Kotlin Multiplatform plugin applied.
|
||||
* @receiver [Project]
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun Project.hasKmpPlugin(): Boolean {
|
||||
// The extension names of Kotlin and KMP are the same.
|
||||
val hasKotlin = hasExtension(ExtensionName.KOTLIN)
|
||||
// The KMP extensions must inherit from [KMP_EXTENSION_CLASS].
|
||||
val kmpClass = ExtensionName.KMP_EXTENSION_CLASS.toClassOrNull(this)
|
||||
val hasKmpExtension = if (hasKotlin && kmpClass != null)
|
||||
get(ExtensionName.KOTLIN).javaClass isSubclassOf kmpClass
|
||||
else false
|
||||
|
||||
return hasKmpExtension
|
||||
}
|
||||
|
||||
internal enum class ProjectType {
|
||||
Android,
|
||||
Kotlin,
|
||||
Java,
|
||||
KMP, // Kotlin Multiplatform
|
||||
Unknown
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/17.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.deployer.proxy
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.initialization.Settings
|
||||
|
||||
/**
|
||||
* Deployer interface for Gropify.
|
||||
*/
|
||||
internal interface Deployer {
|
||||
|
||||
/**
|
||||
* Initialize with settings.
|
||||
*/
|
||||
fun init(settings: Settings, configModified: Boolean)
|
||||
|
||||
/**
|
||||
* Resolve for root project.
|
||||
*/
|
||||
fun resolve(rootProject: Project, configModified: Boolean)
|
||||
|
||||
/**
|
||||
* Deploy to root project.
|
||||
*/
|
||||
fun deploy(rootProject: Project, configModified: Boolean)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/11.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.extension.accessors.proxy
|
||||
|
||||
/**
|
||||
* Extension accessible [Class] defines spatial interface.
|
||||
*/
|
||||
internal interface ExtensionAccessors
|
||||
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "PropertyName", "DeprecatedCallableAddReplaceWith", "FunctionName")
|
||||
|
||||
package com.highcapable.gropify.plugin.extension.dsl.configure
|
||||
|
||||
import com.highcapable.gropify.gradle.api.extension.isUnSafeExtName
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.internal.require
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.plugin.config.extension.create
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.config.type.GropifyLocation
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyValueRule
|
||||
import com.highcapable.gropify.utils.KeywordsDetector
|
||||
import com.highcapable.gropify.utils.extension.isStartsWithLetter
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.initialization.Settings
|
||||
|
||||
/**
|
||||
* Configure extension for Gropify.
|
||||
*/
|
||||
open class GropifyConfigureExtension internal constructor() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** Extension name. */
|
||||
const val NAME = "gropify"
|
||||
|
||||
private const val ROOT_PROJECT_TAG = "<ROOT_PROJECT>"
|
||||
}
|
||||
|
||||
private val globalConfigure = GenerateConfigureScope()
|
||||
private val projectConfigures = mutableMapOf<String, GenerateConfigureScope>()
|
||||
|
||||
/**
|
||||
* Whether to enable this plugin.
|
||||
*
|
||||
* If you want to disable this plugin, just set it here.
|
||||
*/
|
||||
var isEnabled = true
|
||||
@JvmName("enabled") set
|
||||
|
||||
/**
|
||||
* Whether to enable debug mode.
|
||||
*
|
||||
* You can help us identify the problem by enabling this option
|
||||
* to print more debugging information in the logs.
|
||||
*/
|
||||
var debugMode = false
|
||||
@JvmName("debugMode") set
|
||||
|
||||
/**
|
||||
* Configure global.
|
||||
*/
|
||||
fun global(action: Action<GenerateConfigureScope>) = action.execute(globalConfigure)
|
||||
|
||||
/**
|
||||
* Configure root project.
|
||||
*/
|
||||
fun rootProject(action: Action<GenerateConfigureScope>) = configureProject(ROOT_PROJECT_TAG, action)
|
||||
|
||||
/**
|
||||
* Configure each project.
|
||||
* @param names the project names.
|
||||
*/
|
||||
fun projects(vararg names: String, action: Action<GenerateConfigureScope>) = names.forEach { configureProject(it, action) }
|
||||
|
||||
private fun configureProject(name: String, action: Action<GenerateConfigureScope>) =
|
||||
action.execute(GenerateConfigureScope().also { projectConfigures[name] = it })
|
||||
|
||||
open inner class GenerateConfigureScope internal constructor(
|
||||
internal var commonConfigure: CommonGenerateConfigureScope? = null,
|
||||
internal var buildscriptConfigure: BuildscriptGenerateConfigureScope? = null,
|
||||
internal var androidConfigure: AndroidGenerateConfigureScope? = null,
|
||||
internal var jvmConfigure: JvmGenerateConfigureScope? = null,
|
||||
internal var kmpConfigure: KmpGenerateConfigureScope? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* Please use [common], [buildscript], [android], [jvm], [kmp] to configure it.
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@Deprecated(
|
||||
message = "Please use `common`, `buildscript`, `android`, `jvm`, `kmp` to configure it.",
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
val isEnabled: Boolean get() = Gropify.error("No getter available.")
|
||||
|
||||
/**
|
||||
* Configure common.
|
||||
*
|
||||
* The configuration here will be applied downward to [buildscript], [android], [jvm], [kmp].
|
||||
*/
|
||||
fun common(action: Action<CommonGenerateConfigureScope>) {
|
||||
commonConfigure = CommonGenerateConfigureScope().also { action.execute(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure buildscript.
|
||||
*/
|
||||
fun buildscript(action: Action<BuildscriptGenerateConfigureScope>) {
|
||||
buildscriptConfigure = BuildscriptGenerateConfigureScope().also { action.execute(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if this is an Android project.
|
||||
*/
|
||||
fun android(action: Action<AndroidGenerateConfigureScope>) {
|
||||
androidConfigure = AndroidGenerateConfigureScope().also { action.execute(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if this is a Java or Kotlin project.
|
||||
*/
|
||||
fun jvm(action: Action<JvmGenerateConfigureScope>) {
|
||||
jvmConfigure = JvmGenerateConfigureScope().also { action.execute(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if this is a Kotlin Multiplatform project.
|
||||
*/
|
||||
fun kmp(action: Action<KmpGenerateConfigureScope>) {
|
||||
kmpConfigure = KmpGenerateConfigureScope().also { action.execute(it) }
|
||||
}
|
||||
}
|
||||
|
||||
open inner class BuildscriptGenerateConfigureScope internal constructor() : CommonGenerateConfigureScope() {
|
||||
|
||||
/**
|
||||
* Custom buildscript extension name.
|
||||
*
|
||||
* Default is "gropify".
|
||||
*/
|
||||
var extensionName = ""
|
||||
@JvmName("extensionName") set
|
||||
}
|
||||
|
||||
open inner class AndroidGenerateConfigureScope internal constructor() : JvmGenerateConfigureScope()
|
||||
|
||||
open inner class JvmGenerateConfigureScope internal constructor() : CommonCodeGenerateConfigureScope() {
|
||||
|
||||
/**
|
||||
* Whether to use Kotlin language generation.
|
||||
*
|
||||
* Enabled by default, when enabled will generate Kotlin code, disabled will generate Java code.
|
||||
*
|
||||
* - Note: This option will be disabled when this project is a pure Java project.
|
||||
*/
|
||||
var useKotlin: Boolean? = null
|
||||
@JvmName("useKotlin") set
|
||||
}
|
||||
|
||||
open inner class KmpGenerateConfigureScope internal constructor() : CommonCodeGenerateConfigureScope()
|
||||
|
||||
abstract inner class CommonCodeGenerateConfigureScope internal constructor() : SourceCodeGenerateConfigureExtension() {
|
||||
|
||||
/**
|
||||
* Custom deployment `sourceSet` name.
|
||||
*
|
||||
* If your project source code deployment name is not default, you can customize it here.
|
||||
*
|
||||
* Default is "main", if this project is a Kotlin Multiplatform project, the default is "commonMain".
|
||||
*/
|
||||
var sourceSetName = ""
|
||||
@JvmName("sourceSetName") set
|
||||
|
||||
/**
|
||||
* Custom generated package name.
|
||||
*
|
||||
* Android projects use the `namespace` in the `android` configuration method block by default.
|
||||
*
|
||||
* Java, Kotlin or Kotlin Multiplatform projects use the `project.group` of the project settings by default.
|
||||
*
|
||||
* In a Kotlin Multiplatform project, if the AGP plugin is also applied,
|
||||
* the `namespace` will still be used as the package name by default.
|
||||
*
|
||||
* The "generated" is a fixed suffix that avoids conflicts with your own namespaces,
|
||||
* if you don't want this suffix, you can refer to [isIsolationEnabled].
|
||||
*/
|
||||
var packageName = ""
|
||||
@JvmName("packageName") set
|
||||
|
||||
/**
|
||||
* Custom generated class name.
|
||||
*
|
||||
* Default is use the name of the current project.
|
||||
*
|
||||
* The "Properties" is a fixed suffix to distinguish it from your own class names.
|
||||
*/
|
||||
var className = ""
|
||||
@JvmName("className") set
|
||||
|
||||
/**
|
||||
* Whether to enable restricted access.
|
||||
*
|
||||
* Disabled by default, when enabled will add the `internal` modifier to generated Kotlin classes or
|
||||
* remove the `public` modifier to generated Java classes.
|
||||
*/
|
||||
var isRestrictedAccessEnabled: Boolean? = null
|
||||
@JvmName("restrictedAccessEnabled") set
|
||||
|
||||
/**
|
||||
* Whether to enable code isolation.
|
||||
*
|
||||
* Enabled by default, when enabled will generate code in an isolated package suffix "generated"
|
||||
* to avoid conflicts with other projects that also use or not only Gropify to generate code.
|
||||
*
|
||||
* - Note: If you disable this option, please make sure that there are no other projects
|
||||
* that also use or not only Gropify to generate code to avoid conflicts.
|
||||
*/
|
||||
var isIsolationEnabled: Boolean? = null
|
||||
@JvmName("isolationEnabled") set
|
||||
}
|
||||
|
||||
abstract inner class SourceCodeGenerateConfigureExtension internal constructor() : CommonGenerateConfigureScope() {
|
||||
|
||||
/**
|
||||
* Custom generated directory path.
|
||||
*
|
||||
* You can fill in the path relative to the current project.
|
||||
*
|
||||
* Format example: "path/to/your/src/main", the "src/main" is a fixed suffix.
|
||||
*
|
||||
* The `android`, `jvm` and `kmp` default is "build/generated/gropify/src/main".
|
||||
*
|
||||
* We recommend that you set the generated path under the "build" directory,
|
||||
* which is ignored by version control systems by default.
|
||||
*/
|
||||
var generateDirPath = ""
|
||||
@JvmName("generateDirPath") set
|
||||
}
|
||||
|
||||
open inner class CommonGenerateConfigureScope internal constructor(
|
||||
internal var existsPropertyFiles: MutableList<String>? = null,
|
||||
internal var permanentKeyValues: MutableMap<String, Any>? = null,
|
||||
internal var replacementKeyValues: MutableMap<String, Any>? = null,
|
||||
internal var excludeKeys: MutableList<Any>? = null,
|
||||
internal var includeKeys: MutableList<Any>? = null,
|
||||
internal var keyValuesRules: MutableMap<String, PropertyValueRule>? = null,
|
||||
internal var locations: List<GropifyLocation>? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* Whether to generate code.
|
||||
*
|
||||
* Enabled by default.
|
||||
*/
|
||||
var isEnabled: Boolean? = null
|
||||
@JvmName("enabled") set
|
||||
|
||||
/**
|
||||
* Whether to exclude the non-string type key-values content.
|
||||
*
|
||||
* Enabled by default, when enabled, key-values and content that are not string types will be excluded from properties' key-values.
|
||||
*/
|
||||
var excludeNonStringValue: Boolean? = null
|
||||
@JvmName("excludeNonStringValue") set
|
||||
|
||||
/**
|
||||
* Whether to use type auto conversion.
|
||||
*
|
||||
* Enabled by default, when enabled, the type in the properties' key-values will be
|
||||
* automatically identified and converted to the corresponding type.
|
||||
*
|
||||
* After enabling, if you want to force the content of a key-values to be a string type,
|
||||
* you can use single quotes or double quotes to wrap the entire string.
|
||||
*
|
||||
* - Note: After disabled this function, the functions mentioned above will also be invalid.
|
||||
*/
|
||||
var useTypeAutoConversion: Boolean? = null
|
||||
@JvmName("useTypeAutoConversion") set
|
||||
|
||||
/**
|
||||
* Whether to use key-values content interpolation.
|
||||
*
|
||||
* Enabled by default, after enabling it will automatically identify
|
||||
* the `${...}` content in the properties' key-values content and replace it.
|
||||
*
|
||||
* Note: The interpolated content will only be looked up from the
|
||||
* current (current configuration file) properties' key-values list.
|
||||
*/
|
||||
var useValueInterpolation: Boolean? = null
|
||||
@JvmName("useValueInterpolation") set
|
||||
|
||||
/**
|
||||
* Set exists property files.
|
||||
*
|
||||
* The property files will be automatically obtained from the root directory
|
||||
* of the current root project, subproject and user directory according to the file name you set.
|
||||
*
|
||||
* By default, will add "gradle.properties" if [addDefault] is `true`.
|
||||
*
|
||||
* You can add multiple sets of property files name, they will be read in order.
|
||||
*
|
||||
* - Note: Generally there is no need to modify this setting,
|
||||
* an incorrect file name will result in obtaining empty key-values content.
|
||||
* @param fileNames the file names.
|
||||
* @param addDefault whether to add a default property file name, default is true.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun existsPropertyFiles(vararg fileNames: String, addDefault: Boolean = true) {
|
||||
Gropify.require(fileNames.isNotEmpty() && fileNames.all { it.isNotBlank() }) {
|
||||
"Property file names must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
existsPropertyFiles = fileNames.distinct().toMutableList()
|
||||
if (addDefault) existsPropertyFiles?.add(0, GropifyConfig.DEFAULT_EXISTS_PROPERTY_FILE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a permanent list of properties' key-values.
|
||||
*
|
||||
* Here you can set some key-values that must exist, these key-values will be generated regardless of
|
||||
* whether they can be obtained from the properties' key-values.
|
||||
*
|
||||
* These keys use the content of the properties' key if it exists, use the content set here if it does not exist.
|
||||
*
|
||||
* - Note: Special symbols and spaces cannot exist in properties' key names, otherwise the generation may fail.
|
||||
* @param pairs the key-values array.
|
||||
*/
|
||||
@JvmName("`kotlin-dsl-only-permanentKeyValues`")
|
||||
fun permanentKeyValues(vararg pairs: Pair<String, Any>) {
|
||||
Gropify.require(pairs.isNotEmpty() && pairs.all { it.first.isNotBlank() }) {
|
||||
"Permanent key-values must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
permanentKeyValues = mutableMapOf(*pairs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a permanent list of properties' key-values. (Groovy compatible function)
|
||||
*
|
||||
* Here you can set some key-values that must exist, these key-values will be generated regardless of
|
||||
* whether they can be obtained from the properties' key-values.
|
||||
*
|
||||
* These keys use the content of the properties' key if it exists, use the content set here if it does not exist.
|
||||
*
|
||||
* - Note: Special symbols and spaces cannot exist in properties' key names, otherwise the generation may fail.
|
||||
* @param keyValues the key-value array.
|
||||
*/
|
||||
fun permanentKeyValues(keyValues: Map<String, Any>) {
|
||||
Gropify.require(keyValues.isNotEmpty() && keyValues.all { it.key.isNotBlank() }) {
|
||||
"Permanent key-values must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
permanentKeyValues = keyValues.toMutableMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a replacement list of properties' key-values.
|
||||
*
|
||||
* Here you can set some key-values that need to be replaced, these key-values will be replaced
|
||||
* the existing properties' key-values, if not exist, they will be ignored.
|
||||
*
|
||||
* The key-values set here will also overwrite the key-values set in [permanentKeyValues].
|
||||
* @param pairs the key-values array.
|
||||
*/
|
||||
@JvmName("`kotlin-dsl-only-replacementKeyValues`")
|
||||
fun replacementKeyValues(vararg pairs: Pair<String, Any>) {
|
||||
Gropify.require(pairs.isNotEmpty() && pairs.all { it.first.isNotBlank() }) {
|
||||
"Replacement key-values must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
replacementKeyValues = mutableMapOf(*pairs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a replacement list of properties' key-values. (Groovy compatible function)
|
||||
*
|
||||
* Here you can set some key-values that need to be replaced, these key-values will be replaced
|
||||
* the existing properties' key-values, if not exist, they will be ignored.
|
||||
*
|
||||
* The key-values set here will also overwrite the key-values set in [permanentKeyValues].
|
||||
* @param keyValues the key-value array.
|
||||
*/
|
||||
fun replacementKeyValues(keyValues: Map<String, Any>) {
|
||||
Gropify.require(keyValues.isNotEmpty() && keyValues.all { it.key.isNotBlank() }) {
|
||||
"Replacement key-values must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
replacementKeyValues = keyValues.toMutableMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key list of properties' key-values name that need to be excluded.
|
||||
*
|
||||
* Here you can set some key names that you want to exclude from known properties' key-values.
|
||||
*
|
||||
* These keys are excluded if they are present in the properties' key, will not appear in the generated code.
|
||||
*
|
||||
* - Note: If you exclude key-values set in [permanentKeyValues], then they will only change to the
|
||||
* initial key-values content you set and continue to exist.
|
||||
* @param keys the key names, you can pass in [Regex] or use [String.toRegex] to use regex functionality.
|
||||
*/
|
||||
fun excludeKeys(vararg keys: Any) {
|
||||
Gropify.require(keys.isNotEmpty() && keys.all { it.toString().isNotBlank() }) {
|
||||
"Excluded keys must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
excludeKeys = keys.distinct().toMutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key list of properties' key-values name that need to be included.
|
||||
*
|
||||
* Here you can set some key value names that you want to include from known properties' key-values.
|
||||
*
|
||||
* These keys are included if the properties' key exists, unincluded keys will not appear in the generated code.
|
||||
* @param keys the key names, you can pass in [Regex] or use [String.toRegex] to use regex functionality.
|
||||
*/
|
||||
fun includeKeys(vararg keys: Any) {
|
||||
Gropify.require(keys.isNotEmpty() && keys.all { it.toString().isNotBlank() }) {
|
||||
"Included keys must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
includeKeys = keys.distinct().toMutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set properties' key-values rules.
|
||||
*
|
||||
* You can set up a set of key-values rules,
|
||||
* use [ValueRule] to create new rule for parsing the obtained key-values content.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```kotlin
|
||||
* keyValuesRules(
|
||||
* "some.key1" to createValueRule { if (it.contains("_")) it.replace("_", "-") else it },
|
||||
* "some.key2" to createValueRule { "$it-value" }
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* These key-values rules are applied when properties' keys exist.
|
||||
* @param pairs the key-values array.
|
||||
*/
|
||||
@JvmName("`kotlin-dsl-only-keyValuesRules`")
|
||||
fun keyValuesRules(vararg pairs: Pair<String, PropertyValueRule>) {
|
||||
Gropify.require(pairs.isNotEmpty() && pairs.all { it.first.isNotBlank() }) {
|
||||
"Key-values rules must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
keyValuesRules = mutableMapOf(*pairs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set properties' key-values rules. (Groovy compatible function)
|
||||
*
|
||||
* You can set up a set of key-values rules,
|
||||
* use [ValueRule] to create new rule for parsing the obtained key-values content.
|
||||
*
|
||||
* These key-values rules are applied when properties' keys exist.
|
||||
* @param rules the key-values array.
|
||||
*/
|
||||
fun keyValuesRules(rules: Map<String, PropertyValueRule>) {
|
||||
Gropify.require(rules.isNotEmpty() && rules.all { it.key.isNotBlank() }) {
|
||||
"Key-values rules must not be empty or have blank contents."
|
||||
}
|
||||
|
||||
keyValuesRules = rules.toMutableMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new properties' values rule.
|
||||
* @param rule callback current rule.
|
||||
* @return [PropertyValueRule]
|
||||
*/
|
||||
fun ValueRule(rule: PropertyValueRule) = rule
|
||||
|
||||
/**
|
||||
* Set where to find properties' key-values.
|
||||
*
|
||||
* Defaults are [GropifyLocation.CurrentProject], [GropifyLocation.RootProject].
|
||||
*
|
||||
* You can set this up using the following types.
|
||||
*
|
||||
* - [GropifyLocation.CurrentProject]
|
||||
* - [GropifyLocation.RootProject]
|
||||
* - [GropifyLocation.Global]
|
||||
* - [GropifyLocation.System]
|
||||
* - [GropifyLocation.SystemEnv]
|
||||
*
|
||||
* We will generate properties' key-values in sequence from the locations you set,
|
||||
* the order of the generation locations follows the order you set.
|
||||
*
|
||||
* - Risk warning: [GropifyLocation.Global], [GropifyLocation.System],
|
||||
* [GropifyLocation.SystemEnv] may have keys and certificates,
|
||||
* please manage the generated code carefully.
|
||||
* @param types the location type array.
|
||||
*/
|
||||
fun locations(vararg types: GropifyLocation) {
|
||||
locations = types.toList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build [GropifyConfig] from current extension.
|
||||
* @param settings the Gradle [Settings] instance.
|
||||
* @return [GropifyConfig]
|
||||
*/
|
||||
internal fun build(settings: Settings): GropifyConfig {
|
||||
fun String.checkingStartWithLetter(description: String) {
|
||||
Gropify.require(isStartsWithLetter()) {
|
||||
"$description name \"$this\" must start with a letter."
|
||||
}
|
||||
}
|
||||
|
||||
fun String.checkingPackageName() {
|
||||
Gropify.require(isBlank() || KeywordsDetector.verifyPackage(this)) {
|
||||
"Illegal package name \"$this\"."
|
||||
}
|
||||
}
|
||||
|
||||
fun String.checkingClassName() {
|
||||
Gropify.require(isBlank() || KeywordsDetector.verifyClass(this)) {
|
||||
"Illegal class name \"$this\"."
|
||||
}
|
||||
}
|
||||
|
||||
fun String.checkingValidExtensionName() {
|
||||
checkingStartWithLetter(description = "Extension")
|
||||
if (isNotBlank() && isUnSafeExtName()) Gropify.error("This name \"$this\" is a Gradle built-in extension。")
|
||||
}
|
||||
|
||||
fun GenerateConfigureScope.checkingNames() {
|
||||
buildscriptConfigure?.extensionName?.checkingValidExtensionName()
|
||||
|
||||
androidConfigure?.packageName?.checkingPackageName()
|
||||
androidConfigure?.className?.checkingClassName()
|
||||
|
||||
jvmConfigure?.packageName?.checkingPackageName()
|
||||
jvmConfigure?.className?.checkingClassName()
|
||||
|
||||
kmpConfigure?.packageName?.checkingPackageName()
|
||||
kmpConfigure?.className?.checkingClassName()
|
||||
}
|
||||
|
||||
val currentEnabled = isEnabled
|
||||
val currentDebugMode = debugMode
|
||||
val currentGlobal = globalConfigure.create()
|
||||
val currentProjects = mutableMapOf<String, GropifyConfig.GenerateConfig>()
|
||||
val rootName = settings.rootProject.name
|
||||
|
||||
globalConfigure.checkingNames()
|
||||
|
||||
Gropify.require(projectConfigures.none { (name, _) -> name.lowercase() == rootName.lowercase() }) {
|
||||
"This project name '$rootName' is a root project, please use `rootProject` function to configure it, not `projects(\"$rootName\")`."
|
||||
}
|
||||
|
||||
if (projectConfigures.containsKey(ROOT_PROJECT_TAG)) {
|
||||
projectConfigures[rootName] = projectConfigures[ROOT_PROJECT_TAG]!!
|
||||
projectConfigures.remove(ROOT_PROJECT_TAG)
|
||||
}
|
||||
|
||||
projectConfigures.forEach { (name, subConfigure) ->
|
||||
name.replaceFirst(":", "").checkingStartWithLetter(description = "Project")
|
||||
subConfigure.checkingNames()
|
||||
|
||||
currentProjects[name] = subConfigure.create(name, globalConfigure)
|
||||
}
|
||||
|
||||
return object : GropifyConfig {
|
||||
override val isEnabled get() = currentEnabled
|
||||
override val debugMode get() = currentDebugMode
|
||||
override val global get() = currentGlobal
|
||||
override val projects get() = currentProjects
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/12.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.generator
|
||||
|
||||
import com.highcapable.gropify.gradle.api.GradleDescriptor
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.internal.require
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.extension.accessors.proxy.ExtensionAccessors
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyMap
|
||||
import com.highcapable.gropify.plugin.generator.extension.createTypedValue
|
||||
import com.highcapable.gropify.plugin.generator.extension.toOptimize
|
||||
import com.highcapable.gropify.plugin.generator.extension.toPoetNoEscape
|
||||
import com.highcapable.gropify.utils.extension.capitalize
|
||||
import com.highcapable.gropify.utils.extension.firstNumberToLetter
|
||||
import com.highcapable.gropify.utils.extension.uncapitalize
|
||||
import com.highcapable.gropify.utils.extension.upperCamelcase
|
||||
import com.highcapable.kavaref.extension.classOf
|
||||
import com.palantir.javapoet.ClassName
|
||||
import com.palantir.javapoet.FieldSpec
|
||||
import com.palantir.javapoet.JavaFile
|
||||
import com.palantir.javapoet.MethodSpec
|
||||
import com.palantir.javapoet.TypeSpec
|
||||
import javax.lang.model.element.Modifier
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Generator for buildscript accessors classes.
|
||||
*/
|
||||
internal class BuildscriptGenerator {
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val ACCESSORS_PACKAGE_NAME = "${Gropify.GROUP_NAME}.plugin.extension.accessors.generated"
|
||||
|
||||
private const val CLASS_SUFFIX_NAME = "Accessors"
|
||||
|
||||
private const val TOP_CLASS_SUFFIX_NAME = "Properties$CLASS_SUFFIX_NAME"
|
||||
private const val TOP_SUCCESSIVE_NAME = "_top_successive_name"
|
||||
|
||||
private val NonNullApiClass = ClassName.get("org.gradle.api", "NonNullApi")
|
||||
private val NullMarkedClass = ClassName.get("org.jspecify.annotations", "NullMarked")
|
||||
|
||||
private val NonNullClass get() = when {
|
||||
// At least Gradle 9.x/10.x, use `@NullMarked` annotation.
|
||||
GradleDescriptor.version.let { it.startsWith("9.") || it.startsWith("10.") } -> NullMarkedClass
|
||||
// Below Gradle 9.x, use `@NonNullApi` annotation.
|
||||
else -> NonNullApiClass
|
||||
}
|
||||
}
|
||||
|
||||
private var config by Delegates.notNull<GropifyConfig.BuildscriptGenerateConfig>()
|
||||
|
||||
private val classSpecs = mutableMapOf<String, TypeSpec.Builder>()
|
||||
private val constructorSpecs = mutableMapOf<String, MethodSpec.Builder>()
|
||||
private val preAddConstructorSpecNames = mutableListOf<Pair<String, String>>()
|
||||
private val memoryExtensionClasses = mutableMapOf<String, String>()
|
||||
private val grandSuccessiveNames = mutableListOf<String>()
|
||||
private val grandSuccessiveDuplicateIndexes = mutableMapOf<String, Int>()
|
||||
private val usedSuccessiveMethods = mutableMapOf<String, MutableList<String>>()
|
||||
private val usedSuccessiveTags = mutableSetOf<String>()
|
||||
|
||||
private inline fun noRepeated(vararg tags: String, block: () -> Unit) {
|
||||
val allTag = tags.joinToString("-")
|
||||
|
||||
if (!usedSuccessiveTags.contains(allTag)) block()
|
||||
usedSuccessiveTags.add(allTag)
|
||||
}
|
||||
|
||||
private fun String.capitalized() = "${capitalize()}$CLASS_SUFFIX_NAME"
|
||||
private fun String.uncapitalized() = "${uncapitalize()}$CLASS_SUFFIX_NAME"
|
||||
|
||||
private fun String.asClassType(packageName: String = "") = ClassName.get(packageName, this)
|
||||
|
||||
private fun TypeSpec.createJavaFile(packageName: String) = JavaFile.builder(packageName, this).build()
|
||||
|
||||
private fun createClassSpec(name: String, accessorsName: String = "", isInner: Boolean = true) =
|
||||
TypeSpec.classBuilder(if (isInner) name.capitalized() else name).apply {
|
||||
if (isInner) {
|
||||
addJavadoc("The \"$accessorsName\" accessors.")
|
||||
|
||||
addSuperinterface(classOf<ExtensionAccessors>())
|
||||
addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
} else {
|
||||
addJavadoc(
|
||||
"""
|
||||
This class is auto generated by Gropify.
|
||||
<br/>
|
||||
You can visit <a href="${Gropify.PROJECT_URL}">here</a> for more help.
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
addAnnotation(NonNullClass)
|
||||
addModifiers(Modifier.PUBLIC)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createConstructorSpec() = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC)
|
||||
|
||||
private fun TypeSpec.Builder.addSuccessiveField(accessorsName: String, className: String) = addField(
|
||||
FieldSpec.builder(className.capitalized().asClassType(), className.uncapitalized(), Modifier.PRIVATE, Modifier.FINAL).apply {
|
||||
addJavadoc("Create the \"$accessorsName\" accessors.")
|
||||
}.build()
|
||||
)
|
||||
|
||||
private fun TypeSpec.Builder.addSuccessiveMethod(accessorsName: String, methodName: String, className: String) =
|
||||
addMethod(
|
||||
MethodSpec.methodBuilder("get${getOrCreateUsedSuccessiveMethodName(methodName, className).capitalize()}").apply {
|
||||
addJavadoc("Resolve the \"$accessorsName\" accessors.")
|
||||
|
||||
addModifiers(Modifier.PUBLIC, Modifier.FINAL)
|
||||
returns(className.capitalized().asClassType())
|
||||
addStatement("return ${className.uncapitalized()}")
|
||||
}.build()
|
||||
)
|
||||
|
||||
private fun TypeSpec.Builder.addFinalValueMethod(accessorsName: String, methodName: String, className: String, value: Any) =
|
||||
addMethod(
|
||||
MethodSpec.methodBuilder("get${getOrCreateUsedSuccessiveMethodName(methodName, className).capitalize()}").apply {
|
||||
val typedValue = value.createTypedValue(config.useTypeAutoConversion)
|
||||
val safeValueForJavadoc = typedValue.second.replace("$", "$$")
|
||||
|
||||
addJavadoc("Resolve the \"$accessorsName\" value \"${value.toString().toPoetNoEscape()}\".")
|
||||
|
||||
addModifiers(Modifier.PUBLIC, Modifier.FINAL)
|
||||
returns(typedValue.first.java)
|
||||
addStatement("return $safeValueForJavadoc")
|
||||
}.build()
|
||||
)
|
||||
|
||||
private fun MethodSpec.Builder.addSuccessiveStatement(className: String) =
|
||||
addStatement("${className.uncapitalized()} = new ${className.capitalized()}()")
|
||||
|
||||
private fun getOrCreateUsedSuccessiveMethodName(methodName: String, className: String): String {
|
||||
if (usedSuccessiveMethods[className] == null) usedSuccessiveMethods[className] = mutableListOf()
|
||||
|
||||
val methods = usedSuccessiveMethods[className]!!
|
||||
val finalName = if (methods.contains(methodName)) "$methodName${methods.filter { it == methodName }.size + 1}" else methodName
|
||||
methods.add(methodName)
|
||||
|
||||
return finalName
|
||||
}
|
||||
|
||||
private fun getOrCreateClassSpec(name: String, accessorsName: String = "") =
|
||||
classSpecs[name] ?: createClassSpec(name, accessorsName).also { classSpecs[name] = it }
|
||||
|
||||
private fun getOrCreateConstructorSpec(name: String) = constructorSpecs[name]
|
||||
?: createConstructorSpec().also { constructorSpecs[name] = it }
|
||||
|
||||
/**
|
||||
* Parse and generate builders for all classes (core function).
|
||||
*
|
||||
* Before starting the parsing, we need to ensure that [createTopClassSpec]
|
||||
* has been called and [clearGeneratedData] has been called once to prevent data confusion.
|
||||
*
|
||||
* After the parsing is completed, we need to call [releaseParseTypeSpec] to complete the parsing.
|
||||
*/
|
||||
private fun parseTypeSpec(successiveName: String, key: String, value: Any) {
|
||||
fun String.duplicateGrandSuccessiveIndex() = lowercase().let { name ->
|
||||
if (grandSuccessiveDuplicateIndexes.contains(name)) {
|
||||
grandSuccessiveDuplicateIndexes[name] = (grandSuccessiveDuplicateIndexes[name] ?: 1) + 1
|
||||
grandSuccessiveDuplicateIndexes[name] ?: 2
|
||||
} else 2.also { grandSuccessiveDuplicateIndexes[name] = it }
|
||||
}
|
||||
|
||||
fun String.withoutJavaKeywords() = if (lowercase() == "class") "clazz" else this
|
||||
|
||||
/**
|
||||
* Parse (split) names into arrays.
|
||||
*
|
||||
* Likes "com.foobar" → "ComFoobar" → "foobar".
|
||||
*/
|
||||
fun String.parseSuccessiveNames(): List<Triple<String, String, String>> {
|
||||
var grandAccessorsName = ""
|
||||
var grandSuccessiveName = ""
|
||||
val successiveNames = mutableListOf<Triple<String, String, String>>()
|
||||
|
||||
// Like "com_foobar" or "com__foobar" will be split to ["com", "foobar"].
|
||||
val splitNames = split("_").filter { it.isNotBlank() }.ifEmpty { listOf(this) }
|
||||
|
||||
splitNames.forEach { eachName ->
|
||||
val name = eachName.capitalize().withoutJavaKeywords().firstNumberToLetter()
|
||||
|
||||
grandAccessorsName += if (grandAccessorsName.isNotBlank()) ".$eachName" else eachName
|
||||
grandSuccessiveName += name
|
||||
|
||||
if (grandSuccessiveNames.any { it != grandSuccessiveName && it.lowercase() == grandSuccessiveName.lowercase() })
|
||||
grandSuccessiveName += duplicateGrandSuccessiveIndex().toString()
|
||||
grandSuccessiveNames.add(grandSuccessiveName)
|
||||
|
||||
successiveNames.add(Triple(grandAccessorsName, grandSuccessiveName, name))
|
||||
}
|
||||
|
||||
return successiveNames.distinct()
|
||||
}
|
||||
|
||||
val successiveNames = successiveName.parseSuccessiveNames()
|
||||
successiveNames.forEachIndexed { index, (accessorsName, className, methodName) ->
|
||||
val nextItem = successiveNames.getOrNull(index + 1)
|
||||
val lastItem = successiveNames.getOrNull(successiveNames.lastIndex)
|
||||
|
||||
val nextAccessorsName = nextItem?.first ?: ""
|
||||
val nextClassName = nextItem?.second ?: ""
|
||||
val nextMethodName = nextItem?.third ?: ""
|
||||
|
||||
val lastClassName = lastItem?.second ?: ""
|
||||
val lastMethodName = lastItem?.third ?: ""
|
||||
|
||||
val isPreLastIndex = index == successiveNames.lastIndex - 1
|
||||
|
||||
if (successiveNames.size == 1) getOrCreateClassSpec(TOP_SUCCESSIVE_NAME).apply {
|
||||
addFinalValueMethod(key, methodName, className, value)
|
||||
}
|
||||
if (index == successiveNames.lastIndex) return@forEachIndexed
|
||||
|
||||
if (index == 0) noRepeated(TOP_SUCCESSIVE_NAME, methodName, className) {
|
||||
getOrCreateClassSpec(TOP_SUCCESSIVE_NAME, accessorsName).apply {
|
||||
addSuccessiveField(accessorsName, className)
|
||||
addSuccessiveMethod(accessorsName, methodName, className)
|
||||
}
|
||||
getOrCreateConstructorSpec(TOP_SUCCESSIVE_NAME).addSuccessiveStatement(className)
|
||||
}
|
||||
|
||||
noRepeated(className, nextMethodName, nextClassName) {
|
||||
getOrCreateClassSpec(className, accessorsName).apply {
|
||||
if (!isPreLastIndex) {
|
||||
addSuccessiveField(nextAccessorsName, nextClassName)
|
||||
addSuccessiveMethod(nextAccessorsName, nextMethodName, nextClassName)
|
||||
} else addFinalValueMethod(key, lastMethodName, lastClassName, value)
|
||||
}
|
||||
|
||||
if (!isPreLastIndex) preAddConstructorSpecNames.add(className to nextClassName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseParseTypeSpec() =
|
||||
preAddConstructorSpecNames.onEach { (topClassName, innerClassName) ->
|
||||
getOrCreateConstructorSpec(topClassName)?.addSuccessiveStatement(innerClassName)
|
||||
}.clear()
|
||||
|
||||
private fun buildTypeSpec(): TypeSpec {
|
||||
classSpecs.forEach { (name, typeSpec) ->
|
||||
constructorSpecs[name]?.build()?.let { typeSpec.addMethod(it) }
|
||||
if (name != TOP_SUCCESSIVE_NAME) classSpecs[TOP_SUCCESSIVE_NAME]?.addType(typeSpec.build())
|
||||
}
|
||||
|
||||
return classSpecs[TOP_SUCCESSIVE_NAME]?.build() ?: Gropify.error("Merging accessors class failed.")
|
||||
}
|
||||
|
||||
private fun createTopClassSpec(config: GropifyConfig.BuildscriptGenerateConfig) {
|
||||
Gropify.require(config.name.isNotBlank()) {
|
||||
"Class name cannot be empty or blank."
|
||||
}
|
||||
|
||||
this.config = config
|
||||
|
||||
val topClassName = "${config.name.replace(":", "_").upperCamelcase()}$TOP_CLASS_SUFFIX_NAME"
|
||||
|
||||
memoryExtensionClasses[config.name] = "$ACCESSORS_PACKAGE_NAME.$topClassName"
|
||||
classSpecs[TOP_SUCCESSIVE_NAME] = createClassSpec(topClassName, isInner = false)
|
||||
constructorSpecs[TOP_SUCCESSIVE_NAME] = createConstructorSpec()
|
||||
}
|
||||
|
||||
private fun clearGeneratedData(clearAll: Boolean = false) {
|
||||
classSpecs.clear()
|
||||
constructorSpecs.clear()
|
||||
preAddConstructorSpecNames.clear()
|
||||
grandSuccessiveNames.clear()
|
||||
grandSuccessiveDuplicateIndexes.clear()
|
||||
usedSuccessiveMethods.clear()
|
||||
usedSuccessiveTags.clear()
|
||||
if (clearAll) memoryExtensionClasses.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate [JavaFile] array.
|
||||
*
|
||||
* - Note: [allConfig] and [allKeyValues] must be equal in number.
|
||||
*/
|
||||
fun build(
|
||||
allConfig: MutableList<GropifyConfig.BuildscriptGenerateConfig>,
|
||||
allKeyValues: MutableList<PropertyMap>
|
||||
) = runCatching {
|
||||
Gropify.require(allConfig.size == allKeyValues.size) {
|
||||
"The number of configurations must be equal to the number of key-value pairs."
|
||||
}
|
||||
|
||||
val files = mutableListOf<JavaFile>()
|
||||
if (allConfig.isEmpty()) return@runCatching files
|
||||
|
||||
clearGeneratedData(clearAll = true)
|
||||
allConfig.forEachIndexed { index, configs ->
|
||||
val keyValues = allKeyValues[index]
|
||||
|
||||
clearGeneratedData()
|
||||
createTopClassSpec(configs)
|
||||
|
||||
keyValues.toOptimize().forEach { (key, value) ->
|
||||
parseTypeSpec(key, value.first, value.second)
|
||||
releaseParseTypeSpec()
|
||||
}
|
||||
|
||||
files.add(buildTypeSpec().createJavaFile(ACCESSORS_PACKAGE_NAME))
|
||||
}
|
||||
|
||||
files
|
||||
}.getOrElse { Gropify.error("Failed to generated accessors classes.\n$it") }
|
||||
|
||||
/**
|
||||
* Generate compile only stub files for buildscript accessors.
|
||||
* @return [List]<[JavaFile]>
|
||||
*/
|
||||
val compileStubFiles get(): List<JavaFile> {
|
||||
val stubFiles = mutableListOf<JavaFile>()
|
||||
|
||||
val nonNullFile =
|
||||
TypeSpec.annotationBuilder(NonNullClass.simpleName())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build().createJavaFile(NonNullClass.packageName())
|
||||
val extensionAccessorsFile =
|
||||
TypeSpec.interfaceBuilder(classOf<ExtensionAccessors>().simpleName)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build().createJavaFile(classOf<ExtensionAccessors>().packageName)
|
||||
|
||||
stubFiles.add(nonNullFile)
|
||||
stubFiles.add(extensionAccessorsFile)
|
||||
|
||||
return stubFiles
|
||||
}
|
||||
|
||||
/**
|
||||
* Get generated buildscript extension class full name by extension name.
|
||||
* @param name the extension name.
|
||||
* @return [String]
|
||||
* @throws IllegalStateException if the class is not found.
|
||||
*/
|
||||
fun propertiesClass(name: String) = memoryExtensionClasses[name] ?: Gropify.error("Could not found class \"$name\".")
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/11.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.generator
|
||||
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.generator.config.GenerateConfig
|
||||
import com.highcapable.gropify.plugin.generator.config.SourceCodeSpec
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyMap
|
||||
import com.highcapable.gropify.plugin.generator.extension.createTypedValue
|
||||
import com.highcapable.gropify.plugin.generator.extension.toOptimize
|
||||
import com.highcapable.gropify.plugin.generator.extension.toPoetNoEscape
|
||||
import com.highcapable.gropify.plugin.generator.extension.toPoetSpace
|
||||
import com.highcapable.gropify.plugin.generator.extension.toUnderscores
|
||||
import com.highcapable.gropify.utils.extension.firstNumberToLetter
|
||||
import com.palantir.javapoet.ClassName
|
||||
import com.palantir.javapoet.FieldSpec
|
||||
import com.palantir.javapoet.JavaFile
|
||||
import com.palantir.javapoet.TypeSpec
|
||||
import javax.lang.model.element.Modifier
|
||||
|
||||
/**
|
||||
* Generator for Java code.
|
||||
*/
|
||||
internal class JavaCodeGenerator {
|
||||
|
||||
/**
|
||||
* Build Java source code.
|
||||
* @return [SourceCodeSpec]
|
||||
*/
|
||||
fun build(config: GropifyConfig.CommonCodeGenerateConfig, generateConfig: GenerateConfig, keyValues: PropertyMap) = runCatching {
|
||||
val className = ClassName.get(generateConfig.packageName, generateConfig.className)
|
||||
|
||||
val typeSpec = TypeSpec.classBuilder(className).apply {
|
||||
addJavadoc(
|
||||
"""
|
||||
This class is auto generated by Gropify.
|
||||
<br/>
|
||||
You can visit <a href="${Gropify.PROJECT_URL}">here</a> for more help.
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
if (!config.isRestrictedAccessEnabled) addModifiers(Modifier.PUBLIC)
|
||||
keyValues.toOptimize().toUnderscores().forEach { (key, value) ->
|
||||
val typedValue = value.second.createTypedValue(config.useTypeAutoConversion)
|
||||
|
||||
addField(
|
||||
FieldSpec.builder(typedValue.first.java, key.firstNumberToLetter()).apply {
|
||||
addJavadoc("Resolve the \"${value.first.toPoetNoEscape()}\" value \"${value.second.toString().toPoetNoEscape()}\".")
|
||||
|
||||
if (!config.isRestrictedAccessEnabled)
|
||||
addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
else addModifiers(Modifier.STATIC, Modifier.FINAL)
|
||||
|
||||
initializer(typedValue.second.toPoetNoEscape().toPoetSpace())
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
}.build()
|
||||
|
||||
val javaFile = JavaFile.builder(generateConfig.packageName, typeSpec).apply {
|
||||
addFileComment(
|
||||
"""
|
||||
This file is auto generated by Gropify.
|
||||
You can visit ${Gropify.PROJECT_URL} for more help.
|
||||
**DO NOT EDIT THIS FILE MANUALLY**
|
||||
""".trimIndent()
|
||||
)
|
||||
}.build()
|
||||
SourceCodeSpec(SourceCodeSpec.Type.Java, javaFile)
|
||||
}.getOrElse { Gropify.error("Failed to generated Java file.\n$it") }
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/11.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.generator
|
||||
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.generator.config.GenerateConfig
|
||||
import com.highcapable.gropify.plugin.generator.config.SourceCodeSpec
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyMap
|
||||
import com.highcapable.gropify.plugin.generator.extension.createTypedValue
|
||||
import com.highcapable.gropify.plugin.generator.extension.toOptimize
|
||||
import com.highcapable.gropify.plugin.generator.extension.toPoetNoEscape
|
||||
import com.highcapable.gropify.plugin.generator.extension.toPoetSpace
|
||||
import com.highcapable.gropify.plugin.generator.extension.toUnderscores
|
||||
import com.highcapable.gropify.utils.extension.firstNumberToLetter
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.PropertySpec
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
|
||||
/**
|
||||
* Generator for Kotlin code.
|
||||
*/
|
||||
internal class KotlinCodeGenerator {
|
||||
|
||||
/**
|
||||
* Build Kotlin source code.
|
||||
* @return [SourceCodeSpec]
|
||||
*/
|
||||
fun build(config: GropifyConfig.CommonCodeGenerateConfig, generateConfig: GenerateConfig, keyValues: PropertyMap) = runCatching {
|
||||
val fileSpec = FileSpec.builder(generateConfig.packageName, generateConfig.className).apply {
|
||||
addType(TypeSpec.objectBuilder(generateConfig.className).apply {
|
||||
addFileComment(
|
||||
"""
|
||||
This file is auto generated by Gropify.
|
||||
You can visit ${Gropify.PROJECT_URL} for more help.
|
||||
**DO NOT EDIT THIS FILE MANUALLY**
|
||||
""".trimIndent()
|
||||
)
|
||||
addKdoc(
|
||||
"""
|
||||
This class is auto generated by Gropify.
|
||||
You can visit [here](${Gropify.PROJECT_URL}) for more help.
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
if (config.isRestrictedAccessEnabled) addModifiers(KModifier.INTERNAL)
|
||||
keyValues.toOptimize().toUnderscores().forEach { (key, value) ->
|
||||
val typedValue = value.second.createTypedValue(config.useTypeAutoConversion)
|
||||
|
||||
addProperty(PropertySpec.builder(key.firstNumberToLetter(), typedValue.first).apply {
|
||||
addKdoc("Resolve the \"${value.first.toPoetNoEscape()}\" value \"${value.second.toString().toPoetNoEscape()}\".")
|
||||
|
||||
if (config.isRestrictedAccessEnabled) addModifiers(KModifier.INTERNAL)
|
||||
addModifiers(KModifier.CONST)
|
||||
initializer(typedValue.second.toPoetNoEscape().toPoetSpace())
|
||||
}.build())
|
||||
}
|
||||
}.build())
|
||||
}.build()
|
||||
|
||||
SourceCodeSpec(SourceCodeSpec.Type.Kotlin, fileSpec)
|
||||
}.getOrElse { Gropify.error("Failed to generated Kotlin file.\n$it") }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/11.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.generator
|
||||
|
||||
import com.highcapable.gropify.internal.require
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.highcapable.gropify.plugin.config.proxy.GropifyConfig
|
||||
import com.highcapable.gropify.plugin.generator.config.GenerateConfig
|
||||
import com.highcapable.gropify.plugin.generator.config.SourceCodeSpec
|
||||
import com.highcapable.gropify.plugin.generator.extension.PropertyMap
|
||||
|
||||
/**
|
||||
* Source code generator.
|
||||
*/
|
||||
internal class SourceCodeGenerator {
|
||||
|
||||
private val java = JavaCodeGenerator()
|
||||
private val kotlin = KotlinCodeGenerator()
|
||||
|
||||
/**
|
||||
* Build source code specs.
|
||||
* @param config the current generate config.
|
||||
* @param generateConfig the generate config.
|
||||
* @param keyValues the properties' key-values map.
|
||||
* @return [List]<[SourceCodeSpec]>
|
||||
*/
|
||||
fun build(config: GropifyConfig.SourceCodeGenerateConfig, generateConfig: GenerateConfig, keyValues: PropertyMap): List<SourceCodeSpec> {
|
||||
Gropify.require(config is GropifyConfig.CommonCodeGenerateConfig) {
|
||||
"Only Android, Jvm, Kotlin Multiplatform project is supported for now."
|
||||
}
|
||||
|
||||
return listOf(
|
||||
java.build(config, generateConfig, keyValues),
|
||||
kotlin.build(config, generateConfig, keyValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/13.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.generator.config
|
||||
|
||||
/**
|
||||
* Configuration for source code generation.
|
||||
*/
|
||||
internal data class GenerateConfig(
|
||||
val packageName: String,
|
||||
val className: String
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/13.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.generator.config
|
||||
|
||||
import com.highcapable.gropify.internal.error
|
||||
import com.highcapable.gropify.plugin.Gropify
|
||||
import com.palantir.javapoet.JavaFile
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Source code specification.
|
||||
*/
|
||||
internal class SourceCodeSpec(val type: Type, private val codeSpec: Any) {
|
||||
|
||||
/**
|
||||
* Source code type.
|
||||
*/
|
||||
enum class Type {
|
||||
Java,
|
||||
Kotlin
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the source code to the specified directory.
|
||||
* @param directory the target directory.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun writeTo(directory: File) {
|
||||
when (codeSpec) {
|
||||
is JavaFile -> codeSpec.writeTo(directory)
|
||||
is FileSpec -> codeSpec.writeTo(directory)
|
||||
else -> Gropify.error("Unsupported code specification type: ${codeSpec.javaClass.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.generator.extension
|
||||
|
||||
import com.highcapable.gropify.utils.extension.isNumeric
|
||||
import com.highcapable.gropify.utils.extension.underscore
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal typealias PropertyMap = MutableMap<String, Any>
|
||||
internal typealias PropertyOptimizeMap = MutableMap<String, Pair<String, Any>>
|
||||
internal typealias PropertyValueRule = (value: String) -> String
|
||||
|
||||
/**
|
||||
* Create typed value from [Any] value.
|
||||
* @param autoConversion whether to enable auto conversion.
|
||||
* @return [Pair]<[KClass], [String]>
|
||||
*/
|
||||
internal fun Any.createTypedValue(autoConversion: Boolean): Pair<KClass<*>, String> {
|
||||
var isStringType = false
|
||||
val valueString = toString()
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r")
|
||||
.replace("\\", "\\\\")
|
||||
.let {
|
||||
if (autoConversion && (it.startsWith("\"") && it.endsWith("\"") || it.startsWith("'") && it.endsWith("'"))) {
|
||||
isStringType = true
|
||||
it.drop(1).dropLast(1)
|
||||
} else it.replace("\"", "\\\"")
|
||||
}
|
||||
|
||||
if (!autoConversion) return String::class to "\"$valueString\""
|
||||
|
||||
val trimmed = valueString.trim()
|
||||
val typeSpec = when {
|
||||
isStringType -> String::class
|
||||
trimmed.toBooleanStrictOrNull() != null -> Boolean::class
|
||||
trimmed.isNumeric() ->
|
||||
if (!trimmed.contains(".")) {
|
||||
val longValue = trimmed.toLongOrNull()
|
||||
when {
|
||||
longValue == null -> String::class
|
||||
longValue > Int.MAX_VALUE -> Long::class
|
||||
else -> Int::class
|
||||
}
|
||||
} else Double::class
|
||||
else -> String::class
|
||||
}
|
||||
val finalValue = when (typeSpec) {
|
||||
String::class -> "\"$valueString\""
|
||||
Long::class -> if (trimmed.endsWith("L")) trimmed else "${trimmed}L"
|
||||
else -> trimmed
|
||||
}
|
||||
|
||||
return typeSpec to finalValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize property keys for code generation.
|
||||
* @receiver [PropertyMap]
|
||||
* @return [PropertyOptimizeMap]
|
||||
*/
|
||||
internal fun PropertyMap.toOptimize(): PropertyOptimizeMap {
|
||||
val newMap: PropertyOptimizeMap = mutableMapOf()
|
||||
var uniqueNumber = 1
|
||||
|
||||
forEach { (key, value) ->
|
||||
var newKey = key.replace("\\W".toRegex(), "_")
|
||||
while (newMap.containsKey(newKey))
|
||||
newKey = "$newKey${++uniqueNumber}"
|
||||
|
||||
newMap[newKey] = key to value
|
||||
}
|
||||
|
||||
return newMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize property keys to underscores for code generation.
|
||||
* @receiver [PropertyOptimizeMap]
|
||||
* @return [PropertyOptimizeMap]
|
||||
*/
|
||||
internal fun PropertyOptimizeMap.toUnderscores(): PropertyOptimizeMap {
|
||||
val newMap: PropertyOptimizeMap = mutableMapOf()
|
||||
var uniqueNumber = 1
|
||||
|
||||
forEach { (key, value) ->
|
||||
var newKey = key.underscore()
|
||||
while (newMap.containsKey(newKey))
|
||||
newKey = "$newKey${++uniqueNumber}"
|
||||
|
||||
newMap[newKey] = value.first to value.second
|
||||
}
|
||||
|
||||
return newMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace spaces to middle dot for code generation.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.toPoetSpace() = replace(" ", "·")
|
||||
|
||||
/**
|
||||
* Escape percentage signs for code generation.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.toPoetNoEscape() = replace("%", "%%")
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/23.
|
||||
*/
|
||||
package com.highcapable.gropify.plugin.helper
|
||||
|
||||
import com.highcapable.gropify.gradle.api.extension.getFullName
|
||||
import com.highcapable.gropify.gradle.api.extension.getOrNull
|
||||
import com.highcapable.gropify.gradle.api.extension.hasExtension
|
||||
import com.highcapable.gropify.internal.Logger
|
||||
import com.highcapable.gropify.plugin.deployer.extension.ExtensionName
|
||||
import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension
|
||||
import com.highcapable.kavaref.KavaRef.Companion.asResolver
|
||||
import org.gradle.api.Project
|
||||
import tools.jackson.module.kotlin.jacksonObjectMapper
|
||||
import tools.jackson.module.kotlin.readValue
|
||||
|
||||
/**
|
||||
* Android (AGP) project helper.
|
||||
*
|
||||
* Why we need this? Because the namespace provided by AGP may become unstable
|
||||
* after the Gradle daemon is restarted, we manually maintain a file-level cache.
|
||||
*/
|
||||
internal object AndroidProjectHelper {
|
||||
|
||||
private const val ANDROID_PROJECT_NAMESPACES_FILE = "android-project-namespaces.json"
|
||||
|
||||
private val jsonMapper = jacksonObjectMapper()
|
||||
|
||||
/**
|
||||
* Get Android project namespace.
|
||||
* @receiver [Project]
|
||||
* @param project the current project.
|
||||
* @return [String] or null.
|
||||
*/
|
||||
fun getNamespace(project: Project): String? {
|
||||
// If not an Android project, return null directly.
|
||||
if (!project.hasExtension(ExtensionName.ANDROID)) return null
|
||||
|
||||
return project.getConfigNamespace()
|
||||
}
|
||||
|
||||
private fun Project.resolveNamespacesFile() =
|
||||
rootProject.file(".gradle")
|
||||
.resolve(GropifyConfigureExtension.NAME)
|
||||
.resolve(ANDROID_PROJECT_NAMESPACES_FILE)
|
||||
|
||||
private fun Project.getConfigNamespace(): String? {
|
||||
val namespacesFile = resolveNamespacesFile()
|
||||
|
||||
if (namespacesFile.parentFile?.exists() == false) namespacesFile.parentFile.mkdirs()
|
||||
if (!namespacesFile.exists()) namespacesFile.writeText("{}")
|
||||
|
||||
val namespaces = runCatching {
|
||||
jsonMapper.readValue<HashMap<String, String>>(namespacesFile)
|
||||
}.onFailure {
|
||||
// If file broken, reset it.
|
||||
namespacesFile.writeText("{}")
|
||||
Logger.warn("Android project namespaces file was broken and has been reset.")
|
||||
}.getOrDefault(hashMapOf())
|
||||
|
||||
val namespace = getExtensionNamespace()
|
||||
if (namespace != null) {
|
||||
namespaces[getFullName()] = namespace
|
||||
jsonMapper.writeValue(namespacesFile, namespaces)
|
||||
}
|
||||
|
||||
return namespace ?: namespaces[getFullName()]
|
||||
}
|
||||
|
||||
private fun Project.getExtensionNamespace(): String? {
|
||||
val extension = getOrNull(ExtensionName.ANDROID)
|
||||
|
||||
return extension?.asResolver()?.optional(silent = true)?.firstMethodOrNull {
|
||||
name = "getNamespace"
|
||||
}?.invokeQuietly<String>()?.ifBlank { null }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/20.
|
||||
*/
|
||||
package com.highcapable.gropify.utils
|
||||
|
||||
/**
|
||||
* Java keywords detector.
|
||||
*/
|
||||
internal object KeywordsDetector {
|
||||
|
||||
private val javaKeywords = setOf(
|
||||
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class",
|
||||
"const", "continue", "default", "do", "double", "else", "enum", "extends", "final",
|
||||
"finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int",
|
||||
"interface", "long", "native", "new", "package", "private", "protected", "public",
|
||||
"return", "short", "static", "strictfp", "super", "switch", "synchronized", "this",
|
||||
"throw", "throws", "transient", "try", "void", "volatile", "while"
|
||||
)
|
||||
|
||||
private val invalidPattern = "^(\\d.*|.*[^A-Za-z0-9_\$].*)$".toRegex()
|
||||
|
||||
/**
|
||||
* Verify if the name is a valid Java package name.
|
||||
* @param name the name to verify.
|
||||
* @return [Boolean] `true` if valid, `false` otherwise.
|
||||
*/
|
||||
fun verifyPackage(name: String) = name !in javaKeywords && !invalidPattern.matches(name) ||
|
||||
(name.contains(".") && !name.contains("..") &&
|
||||
name.split(".").all { it !in javaKeywords && !invalidPattern.matches(it) })
|
||||
|
||||
/**
|
||||
* Verify if the name is a valid Java class name.
|
||||
* @param name the name to verify.
|
||||
* @return [Boolean] `true` if valid, `false` otherwise.
|
||||
*/
|
||||
fun verifyClass(name: String) = name !in javaKeywords && !invalidPattern.matches(name)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.highcapable.gropify.utils.extension
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Convert string path to file.
|
||||
*
|
||||
* Auto called by [parseFileSeparator].
|
||||
* @receiver [String]
|
||||
* @return [File]
|
||||
*/
|
||||
internal fun String.toFile() = File(parseFileSeparator())
|
||||
|
||||
/**
|
||||
* Format to the file delimiter of the current operating system.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.parseFileSeparator() = replace("/", File.separator).replace("\\", File.separator)
|
||||
|
||||
/**
|
||||
* File delimiters formatted to Unix operating systems.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.parseUnixFileSeparator() = replace("\\", "/")
|
||||
|
||||
/**
|
||||
* Check if directory is empty.
|
||||
*
|
||||
* If not a directory (possibly a file), returns true.
|
||||
*
|
||||
* If the file does not exist, returns true.
|
||||
* @receiver [File]
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun File.isEmpty() = !exists() || !isDirectory || listFiles().isNullOrEmpty()
|
||||
|
||||
/**
|
||||
* Delete empty subdirectories under a directory.
|
||||
* @receiver [File]
|
||||
*/
|
||||
internal fun File.deleteEmptyRecursively() {
|
||||
listFiles { file -> file.isDirectory }?.forEach { subDir ->
|
||||
subDir.deleteEmptyRecursively()
|
||||
if (subDir.listFiles()?.isEmpty() == true) subDir.delete()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
package com.highcapable.gropify.utils.extension
|
||||
|
||||
private val numericRegex = """^-?(\d+(\.\d+)?|\.\d+)$""".toRegex()
|
||||
|
||||
/**
|
||||
* Convert the current [Map] key value to string type.
|
||||
* @receiver [Map]<[K], [V]>
|
||||
* @return [Map]<[String], [String]>
|
||||
*/
|
||||
internal inline fun <reified K, V> Map<K, V>.toStringMap() = mapKeys { e -> e.key.toString() }.mapValues { e -> e.value.toString() }
|
||||
|
||||
/**
|
||||
* Remove surrounding single and double quotes from a string.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.removeSurroundingQuotes() = removeSurrounding("\"").removeSurrounding("'")
|
||||
|
||||
/**
|
||||
* Flatten string processing.
|
||||
*
|
||||
* Remove all spaces and convert to lowercase letters.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.flatted() = replace(" ", "").lowercase()
|
||||
|
||||
/**
|
||||
* Camelcase, "-", "." are converted to uppercase and underline names.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.underscore() = replace(".", "_")
|
||||
.replace("-", "_")
|
||||
.replace(" ", "_")
|
||||
.replace("([a-z])([A-Z]+)".toRegex(), "$1_$2")
|
||||
.uppercase()
|
||||
|
||||
/**
|
||||
* Convert underline, separator, dot, and space named strings to camelcase named strings.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.camelcase() = runCatching {
|
||||
split("_", ".", "-", " ").map { it.replaceFirstChar { e -> e.titlecase() } }.let { words ->
|
||||
words.first().replaceFirstChar { it.lowercase() } + words.drop(1).joinToString("")
|
||||
}
|
||||
}.getOrNull() ?: this
|
||||
|
||||
/**
|
||||
* Convert underline, separator, dot, and space named strings to camelCase named string.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.upperCamelcase() = camelcase().capitalize()
|
||||
|
||||
/**
|
||||
* Capitalize the first letter of a string.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.capitalize() = replaceFirstChar { it.uppercaseChar() }
|
||||
|
||||
/**
|
||||
* Lowercase the first letter of string.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.uncapitalize() = replaceFirstChar { it.lowercaseChar() }
|
||||
|
||||
/**
|
||||
* Converts the first digit of a string to approximately uppercase letters.
|
||||
* @receiver [String]
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.firstNumberToLetter() =
|
||||
if (isNotBlank()) (mapOf(
|
||||
'0' to 'O', '1' to 'I',
|
||||
'2' to 'Z', '3' to 'E',
|
||||
'4' to 'A', '5' to 'S',
|
||||
'6' to 'G', '7' to 'T',
|
||||
'8' to 'B', '9' to 'P'
|
||||
)[first()] ?: first()) + substring(1)
|
||||
else this
|
||||
|
||||
/**
|
||||
* Whether the string is a numeric type.
|
||||
* @receiver [String]
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun String.isNumeric() = numericRegex.matches(this)
|
||||
|
||||
/**
|
||||
* Whether the first character of the string is a letter.
|
||||
* @receiver [String]
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun String.isStartsWithLetter() = firstOrNull()?.let {
|
||||
it in 'A'..'Z' || it in 'a'..'z'
|
||||
} == true
|
||||
|
||||
/**
|
||||
* Whether the interpolation symbol `${...}` exists in the string.
|
||||
* @receiver [String]
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun String.hasInterpolation() = contains("\${") && contains("}")
|
||||
|
||||
/**
|
||||
* Replace interpolation symbols `${...}` in a string.
|
||||
* @receiver [String]
|
||||
* @param result call the result.
|
||||
* @return [String]
|
||||
*/
|
||||
internal fun String.replaceInterpolation(result: (groupValue: String) -> CharSequence) =
|
||||
"\\$\\{(.+?)}".toRegex().replace(this) { result(it.groupValues[1]) }
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/9.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package org.gradle.kotlin.dsl
|
||||
|
||||
import com.highcapable.gropify.gradle.api.extension.configure
|
||||
import com.highcapable.gropify.gradle.api.extension.get
|
||||
import com.highcapable.gropify.plugin.extension.dsl.configure.GropifyConfigureExtension
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.initialization.Settings
|
||||
|
||||
// WORKAROUND: for some reason a type-safe accessor is not generated for the extension,
|
||||
// even though it is present in the extension container where the plugin is applied.
|
||||
// This seems to work fine, and the extension methods are only available when the plugin
|
||||
// is actually applied.
|
||||
//
|
||||
// See related link [here](https://stackoverflow.com/questions/72627792/gradle-settings-plugin-extension)
|
||||
|
||||
/**
|
||||
* Retrieves the [GropifyConfigureExtension] extension.
|
||||
* @return [GropifyConfigureExtension]
|
||||
*/
|
||||
val Settings.gropify get() = get<GropifyConfigureExtension>(GropifyConfigureExtension.NAME)
|
||||
|
||||
/**
|
||||
* Configures the [GropifyConfigureExtension] extension.
|
||||
* @param configure
|
||||
*/
|
||||
fun Settings.gropify(configure: Action<GropifyConfigureExtension>) = configure(GropifyConfigureExtension.NAME, configure)
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Gropify - A type-safe and modern properties plugin for Gradle.
|
||||
* Copyright (C) 2019 HighCapable
|
||||
* https://github.com/HighCapable/Gropify
|
||||
*
|
||||
* Apache License Version 2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file is created by fankes on 2025/10/18.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package org.gradle.kotlin.dsl
|
||||
|
||||
/**
|
||||
* Typealias for [com.highcapable.gropify.plugin.config.type.GropifyLocation].
|
||||
*/
|
||||
typealias GropifyLocation = com.highcapable.gropify.plugin.config.type.GropifyLocation
|
||||
Reference in New Issue
Block a user