first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
id("java-gradle-plugin")
}
repositories {
google()
mavenCentral()
}
dependencies {
implementation(project(":expo-autolinking-plugin-shared"))
implementation(gradleApi())
compileOnly("com.android.tools.build:gradle:8.5.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks.withType<KotlinCompile> {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
group = "expo.modules"
gradlePlugin {
plugins {
create("expoAutolinkingPlugin") {
id = "expo-autolinking"
implementationClass = "expo.modules.plugin.ExpoAutolinkingPlugin"
}
create("expoRootProjectPlugin") {
id = "expo-root-project"
implementationClass = "expo.modules.plugin.ExpoRootProjectPlugin"
}
}
}

View File

@@ -0,0 +1,165 @@
package expo.modules.plugin
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.tasks.factory.dependsOn
import expo.modules.plugin.configuration.ExpoModule
import expo.modules.plugin.text.Colors
import expo.modules.plugin.text.withColor
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import java.nio.file.Paths
const val generatedPackageListNamespace = "expo.modules"
const val generatedPackageListFilename = "ExpoModulesPackageList.kt"
const val generatedFilesSrcDir = "generated/expo/src/main/java"
open class ExpoAutolinkingPlugin : Plugin<Project> {
override fun apply(project: Project) {
val gradleExtension = project.gradle.extensions.findByType(ExpoGradleExtension::class.java)
?: throw IllegalStateException("`ExpoGradleExtension` not found. Please, make sure that `useExpoModules` was called in `settings.gradle`.")
val config = gradleExtension.config
project.logger.quiet("")
project.logger.quiet("Using expo modules")
val appProject = findAppProject(project.rootProject)
appProject?.let { copyAppDimensionsAndFlavorsToProject(project, it) }
val (prebuiltProjects, projects) = config.allProjects.partition { project ->
project.usePublication
}
project.withSubprojects(projects) { subproject ->
// Ensures that dependencies are resolved before the project is evaluated.
project.evaluationDependsOn(subproject.path)
// Adds the subproject as a dependency to the current project (expo package).
project.dependencies.add("api", subproject)
project.logger.quiet(" - ${subproject.name.withColor(Colors.GREEN)} (${subproject.version})")
}
prebuiltProjects.forEach { prebuiltProject ->
val publication = requireNotNull(prebuiltProject.publication)
project.dependencies.add("api", "${publication.groupId}:${publication.artifactId}:${publication.version}")
project.logger.quiet(" - ${"[\uD83D\uDCE6]".withColor(Colors.YELLOW)} ${prebuiltProject.name.withColor(Colors.GREEN)} (${publication.version})")
}
project.logger.quiet("")
// Creates a task that generates a list of expo modules.
val generatePackagesList = createGeneratePackagesListTask(project, gradleExtension.config.modules, gradleExtension.hash)
// Ensures that the task is executed before the build.
project.tasks
.named("preBuild", Task::class.java)
.dependsOn(generatePackagesList)
// Adds the generated file to the source set.
project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
ext
.sourceSets
.getByName("main")
.java
.srcDir(getPackageListDir(project))
}
}
fun getPackageListDir(project: Project): Provider<Directory> {
return project.layout.buildDirectory.dir(generatedFilesSrcDir)
}
fun getPackageListFile(project: Project): Provider<RegularFile> {
val packageListRelativePath = Paths.get(
generatedFilesSrcDir,
generatedPackageListNamespace.replace('.', '/'),
generatedPackageListFilename
).toString()
return project.layout.buildDirectory.file(packageListRelativePath)
}
fun createGeneratePackagesListTask(project: Project, modules: List<ExpoModule>, hash: String): TaskProvider<GeneratePackagesListTask> {
return project.tasks.register("generatePackagesList", GeneratePackagesListTask::class.java) {
it.hash.set(hash)
it.namespace.set(generatedPackageListNamespace)
it.outputFile.set(getPackageListFile(project))
it.modules = modules
}
}
private fun findAppProject(root: Project): Project? {
return root.allprojects.firstOrNull { it.plugins.hasPlugin("com.android.application") }
}
private fun copyAppDimensionsAndFlavorsToProject(
project: Project,
appProject: Project
) {
val appAndroid = appProject.extensions.findByName("android") as? BaseExtension ?: run {
return
}
val consumerAndroid = project.extensions.findByName("android") as? BaseExtension ?: run {
return
}
val appDimensions = syncFlavorDimensions(project, consumerAndroid, appAndroid)
copyMissingProductFlavors(project, consumerAndroid, appAndroid, appDimensions)
}
private fun syncFlavorDimensions(
project: Project,
consumerAndroid: BaseExtension,
appAndroid: BaseExtension
): List<String> {
val appDimensions = appAndroid
.flavorDimensionList
.takeIf { it.isNotEmpty() }
?: return emptyList()
val consumerDimensions = (consumerAndroid.flavorDimensionList).toMutableList()
val dimensionsAdded = appDimensions.any { dimension ->
if (dimension !in consumerDimensions) {
consumerDimensions.add(dimension)
true
} else {
false
}
}
if (dimensionsAdded) {
consumerAndroid.flavorDimensions(*consumerDimensions.toTypedArray())
project.logger.quiet(" -> Copied/merged flavorDimensions: ${consumerDimensions.joinToString()}")
}
return appDimensions
}
private fun copyMissingProductFlavors(
project: Project,
consumerAndroid: BaseExtension,
appAndroid: BaseExtension,
appDimensions: List<String>
) {
val appFlavors = appAndroid.productFlavors
val consumerFlavors = consumerAndroid.productFlavors
val existingFlavorNames = consumerFlavors.map { it.name }.toSet()
appFlavors.forEach { appFlavor ->
if (appFlavor.name !in existingFlavorNames) {
val dimension = appFlavor.dimension ?: appDimensions.singleOrNull()
consumerFlavors.create(appFlavor.name).apply {
this.dimension = dimension
}
project.logger.quiet(" -> Created flavor '${appFlavor.name}' (dimension='$dimension') in :${project.path}")
}
}
}
}

View File

@@ -0,0 +1,74 @@
package expo.modules.plugin
import expo.modules.plugin.text.Colors
import expo.modules.plugin.text.withColor
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.internal.extensions.core.extra
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
class ExpoRootProjectPlugin : Plugin<Project> {
override fun apply(rootProject: Project) {
val versionCatalogs = rootProject.extensions.getByType(VersionCatalogsExtension::class.java)
val libs = versionCatalogs.find("expoLibs")
with(rootProject) {
defineDefaultProperties(libs)
}
}
}
fun Project.defineDefaultProperties(versionCatalogs: Optional<VersionCatalog>) {
// Android related
val buildTools = extra.setIfNotExist("buildToolsVersion") { versionCatalogs.getVersionOrDefault("buildTools", "35.0.0") }
val minSdk = extra.setIfNotExist("minSdkVersion") { Integer.parseInt(versionCatalogs.getVersionOrDefault("minSdk", "24")) }
val compileSdk = extra.setIfNotExist("compileSdkVersion") { Integer.parseInt(versionCatalogs.getVersionOrDefault("compileSdk", "35")) }
val targetSdk = extra.setIfNotExist("targetSdkVersion") { Integer.parseInt(versionCatalogs.getVersionOrDefault("targetSdk", "35")) }
val ndk = extra.setIfNotExist("ndkVersion") { versionCatalogs.getVersionOrDefault("ndkVersion", "27.1.12297006") }
// Kotlin related
val kotlin = extra.setIfNotExist("kotlinVersion") { versionCatalogs.getVersionOrDefault("kotlin", "2.0.21") }
val ksp = extra.setIfNotExist("kspVersion") {
versionCatalogs.getVersionOrDefault("ksp") {
try {
return@getVersionOrDefault KSPLookup.getValue(extra.get("kotlinVersion") as String)
} catch (e: Throwable) {
throw IllegalStateException(
"Can't find KSP version for Kotlin version '${extra.get("kotlinVersion")}'. You're probably using an unsupported version of Kotlin. Supported versions are: '${KSPLookup.keys.joinToString(", ")}'",
e
)
}
}
}
project.logger.quiet("""
${"[ExpoRootProject]".withColor(Colors.GREEN)} Using the following versions:
- buildTools: ${buildTools.withColor(Colors.GREEN)}
- minSdk: ${minSdk.withColor(Colors.GREEN)}
- compileSdk: ${compileSdk.withColor(Colors.GREEN)}
- targetSdk: ${targetSdk.withColor(Colors.GREEN)}
- ndk: ${ndk.withColor(Colors.GREEN)}
- kotlin: ${kotlin.withColor(Colors.GREEN)}
- ksp: ${ksp.withColor(Colors.GREEN)}
""".trimIndent())
}
inline fun ExtraPropertiesExtension.setIfNotExist(name: String, value: () -> Any): Any? {
if (!has(name)) {
set(name, value())
}
return get(name)
}
fun Optional<VersionCatalog>.getVersionOrDefault(name: String, default: String): String {
return getOrNull()?.findVersion(name)?.getOrNull()?.requiredVersion ?: default
}
fun Optional<VersionCatalog>.getVersionOrDefault(name: String, default: () -> String): String {
return getOrNull()?.findVersion(name)?.getOrNull()?.requiredVersion ?: default.invoke()
}

View File

@@ -0,0 +1,117 @@
package expo.modules.plugin
import expo.modules.plugin.configuration.ExpoModule
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
/**
* Task that generates a list of packages that should be included in your app's runtime.
*/
abstract class GeneratePackagesListTask : DefaultTask() {
init {
group = "expo"
}
/**
* Hash of the current configuration.
* Used to invalidate the task when the configuration changes.
*/
@get:Input
abstract val hash: Property<String>
/**
* Java package name under which the package list should be placed.
*/
@get:Input
abstract val namespace: Property<String>
/**
* List of modules.
*/
@get:Internal
lateinit var modules: List<ExpoModule>
/**
* The output file where the package list should be written.
*/
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun generatePackagesList() {
val target = outputFile.get().asFile
val content = generatePackageListFileContent()
target.writeText(content)
}
private fun generatePackageListFileContent(): String {
return """package ${namespace.get()};
import expo.modules.core.interfaces.Package;
import expo.modules.kotlin.modules.Module;
import expo.modules.kotlin.ModulesProvider;
class ExpoModulesPackageList : ModulesProvider {
companion object {
val packagesList: List<Package> = listOf(
${
modules
.filterNot { it.packageName == "expo" }
.flatMap { module ->
module.projects.flatMap { project ->
project.packages.map { " ${it}()" }
}
}
.joinToString(",\n")
}
)
val modulesMap: Map<Class<out Module>, String?> = mapOf(
${
modules
.flatMap { module ->
module.projects.flatMap { project ->
project.modules.map { (classifier, name) ->
" ${classifier}::class.java to ${name?.let { "\"${it}\"" }}"
}
}
}
.joinToString(",\n")
}
)
@JvmStatic
fun getPackageList(): List<Package> {
return packagesList
}
}
override fun getModulesMap(): Map<Class<out Module>, String?> {
return modulesMap
}
override fun getServices(): List<Class<out expo.modules.kotlin.services.Service>> {
return listOf<Class<out expo.modules.kotlin.services.Service>>(
${
modules
.flatMap { module ->
module.projects.flatMap { project ->
project.services.map { " ${it}::class.java" }
}
}
.joinToString(",\n")
}
)
}
}
""".trimIndent()
}
}

View File

@@ -0,0 +1,20 @@
// Copyright 2015-present 650 Industries. All rights reserved.
// Generated using './scripts/generateKSPLookUp.js'
package expo.modules.plugin
val KSPLookup = mapOf(
"2.2.21" to "2.2.21-2.0.5",
"2.3.1" to "2.3.1",
"2.3.0" to "2.3.0",
"2.2.20" to "2.2.20-2.0.4",
"2.2.10" to "2.2.10-2.0.2",
"2.2.0" to "2.2.0-2.0.2",
"2.1.21" to "2.1.21-2.0.2",
"2.1.20" to "2.1.20-2.0.1",
"2.1.10" to "2.1.10-1.0.31",
"2.1.0" to "2.1.0-1.0.29",
"2.0.21" to "2.0.21-1.0.28",
"2.0.20" to "2.0.20-1.0.25",
"2.0.10" to "2.0.10-1.0.24",
"2.0.0" to "2.0.0-1.0.24"
)

View File

@@ -0,0 +1,26 @@
package expo.modules.plugin
import expo.modules.plugin.configuration.GradleProject
import org.gradle.api.Project
internal fun Project.withSubproject(subprojectConfig: GradleProject, action: (subproject: Project) -> Unit) {
val subprojectPath = ":${subprojectConfig.name}"
val subproject = findProject(subprojectPath)
if (subproject == null) {
logger.warn("Couldn't find project ${subprojectConfig.name}. Please, make sure that `expo-autolinking-settings` plugin was applied in `settings.gradle`.")
return
}
// Prevent circular dependencies
if (subproject == this) {
return
}
action(subproject)
}
internal fun Project.withSubprojects(subprojectsConfig: List<GradleProject>, action: (subproject: Project) -> Unit) {
subprojectsConfig.forEach { subprojectConfig ->
withSubproject(subprojectConfig, action)
}
}