Repository: expo/expo
Pull request #35789: [autolinking][Android] Introduce `useExpoVersionCatalog` and `reactNativeGradlePlugin`
Author: login: lukmccall
Labels: name: bot: passed checks
description: ExpoBot has nothing to complain about
name: bot: fingerprint changed
Created: 2025-03-31T11:10:44Z | Merged: 2025-03-31T12:18:20Z

# Why

Adds `expoAutolinking.useExpoVersionCatalog` and `expoAutolinking.reactNativeGradlePlugin`.

# How

I've started updating our bare template and I've decided to clean the `setting.gradle` file to reduce configuration.

# Test Plan

- bare-expo ✅ 


Diff:
diff --git a/apps/bare-expo/android/app/build.gradle b/apps/bare-expo/android/app/build.gradle
index 4d51c71ca64f79..a355ac99c45b5e 100644
--- a/apps/bare-expo/android/app/build.gradle
+++ b/apps/bare-expo/android/app/build.gradle
@@ -173,15 +173,15 @@ dependencies {
 
     if (isGifEnabled) {
         // For animated gif support
-        implementation("com.facebook.fresco:animated-gif:${libs.versions.fresco.get()}")
+        implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
     }
 
     if (isWebpEnabled) {
         // For webp support
-        implementation("com.facebook.fresco:webpsupport:${libs.versions.fresco.get()}")
+        implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
         if (isWebpAnimatedEnabled) {
             // Animated webp support
-            implementation("com.facebook.fresco:animated-webp:${libs.versions.fresco.get()}")
+            implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}")
         }
     }
 
diff --git a/apps/bare-expo/android/settings.gradle b/apps/bare-expo/android/settings.gradle
index 20c4ff04d0441b..c8f09d4fedf31e 100644
--- a/apps/bare-expo/android/settings.gradle
+++ b/apps/bare-expo/android/settings.gradle
@@ -1,5 +1,11 @@
 pluginManagement {
-  includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile().toString())
+  def reactNativeGradlePlugin = new File(
+    providers.exec {
+      workingDir(rootDir)
+      commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
+    }.standardOutput.asText.get().trim()
+  ).getParentFile().absolutePath
+  includeBuild(reactNativeGradlePlugin)
 
   def expoPluginsPath = new File(
     providers.exec {
@@ -10,6 +16,7 @@ pluginManagement {
   ).absolutePath
   includeBuild(expoPluginsPath)
 }
+
 plugins {
   id("com.facebook.react.settings")
   id("expo-autolinking-settings")
@@ -22,43 +29,10 @@ expoAutolinking.useExpoModules()
 
 rootProject.name = 'BareExpo'
 
-def reactNativePackage = providers.exec {
-  workingDir(rootDir)
-  commandLine("node", "--print", "require.resolve('react-native/package.json')")
-}.standardOutput.asText.get().trim()
-
-dependencyResolutionManagement {
-  versionCatalogs {
-    libs {
-      from(files(new File(reactNativePackage, "../gradle/libs.versions.toml")))
-      if (hasProperty('android.buildToolsVersion')) {
-        version("buildTools", findProperty('android.buildToolsVersion'))
-      }
-      if (hasProperty('android.minSdkVersion')) {
-        version("minSdk", findProperty('android.minSdkVersion'))
-      }
-      if (hasProperty('android.compileSdkVersion')) {
-        version("compileSdk", findProperty('android.compileSdkVersion'))
-      }
-      if (hasProperty('android.targetSdkVersion')) {
-        version("targetSdk", findProperty('android.targetSdkVersion'))
-      }
-      if (hasProperty('android.kotlinVersion')) {
-        version("kotlin", findProperty('android.kotlinVersion'))
-      }
-    }
-  }
-}
+expoAutolinking.useExpoVersionCatalog()
 
 include(":expo-modules-test-core")
 project(":expo-modules-test-core").projectDir = new File("../../../packages/expo-modules-test-core/android")
 
 include ':app'
-includeBuild(
-  file(
-    providers.exec {
-      workingDir(rootDir)
-      commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')")
-    }.standardOutput.asText.get().trim()
-  ).getParentFile()
-)
+includeBuild(expoAutolinking.reactNativeGradlePlugin)
diff --git a/packages/expo-modules-autolinking/CHANGELOG.md b/packages/expo-modules-autolinking/CHANGELOG.md
index 1d26fd96c876ec..59d85f65092e93 100644
--- a/packages/expo-modules-autolinking/CHANGELOG.md
+++ b/packages/expo-modules-autolinking/CHANGELOG.md
@@ -10,6 +10,7 @@
 
 - Added `coreFeatures` field. ([#34015](https://github.com/expo/expo/pull/34015) by [@lukmccall](https://github.com/lukmccall))
 - Add macOS support. ([#35065](https://github.com/expo/expo/pull/35065) by [@gabrieldonadel](https://github.com/gabrieldonadel))
+- [Android] Added `expoAutolinking.useExpoVersionCatalog` and `expoAutolinking.reactNativeGradlePlugin`. ([#35789](https://github.com/expo/expo/pull/35789) by [@lukmccall](https://github.com/lukmccall))
 
 ### 🐛 Bug fixes
 
diff --git a/packages/expo-modules-autolinking/android/expo-gradle-plugin/expo-autolinking-settings-plugin/src/main/kotlin/expo/modules/plugin/ExpoAutolinkingSettingsExtension.kt b/packages/expo-modules-autolinking/android/expo-gradle-plugin/expo-autolinking-settings-plugin/src/main/kotlin/expo/modules/plugin/ExpoAutolinkingSettingsExtension.kt
index 4bbe552a3c61b0..831f0a115bb612 100644
--- a/packages/expo-modules-autolinking/android/expo-gradle-plugin/expo-autolinking-settings-plugin/src/main/kotlin/expo/modules/plugin/ExpoAutolinkingSettingsExtension.kt
+++ b/packages/expo-modules-autolinking/android/expo-gradle-plugin/expo-autolinking-settings-plugin/src/main/kotlin/expo/modules/plugin/ExpoAutolinkingSettingsExtension.kt
@@ -1,8 +1,16 @@
 package expo.modules.plugin
 
+import org.gradle.api.Action
 import org.gradle.api.initialization.Settings
+import org.gradle.api.initialization.dsl.VersionCatalogBuilder
+import org.gradle.api.model.ObjectFactory
+import java.io.File
+import javax.inject.Inject
 
-open class ExpoAutolinkingSettingsExtension(val settings: Settings) {
+open class ExpoAutolinkingSettingsExtension(
+  val settings: Settings,
+  @Inject val objects: ObjectFactory
+) {
   /**
    * Command that should be provided to `react-native` to resolve the configuration.
    */
@@ -27,6 +35,30 @@ open class ExpoAutolinkingSettingsExtension(val settings: Settings) {
    */
   var exclude: List<String>? = null
 
+  /**
+   * The file pointing to the React Native Gradle plugin.
+   */
+  val reactNativeGradlePlugin: File by lazy {
+    File(
+      settings.providers.exec { env ->
+        env.workingDir(settings.rootDir)
+        env.commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
+      }.standardOutput.asText.get().trim(),
+    ).parentFile
+  }
+
+  /**
+   * The file pointing to the React Native root directory.
+   */
+  val reactNative: File by lazy {
+    File(
+      settings.providers.exec { env ->
+        env.workingDir(settings.rootDir)
+        env.commandLine("node", "--print", "require.resolve('react-native/package.json')")
+      }.standardOutput.asText.get().trim(),
+    ).parentFile
+  }
+
   /**
    * Uses Expo modules autolinking.
    */
@@ -38,4 +70,60 @@ open class ExpoAutolinkingSettingsExtension(val settings: Settings) {
       exclude
     ).useExpoModules()
   }
+
+  fun useExpoVersionCatalog() {
+    useExpoVersionCatalog(
+      reactNativeVersionCatalog = null,
+      override = null
+    )
+  }
+
+  fun useExpoVersionCatalog(
+    override: Action<in VersionCatalogBuilder>
+  ) {
+    useExpoVersionCatalog(
+      reactNativeVersionCatalog = null,
+      override = override
+    )
+  }
+
+  fun useExpoVersionCatalog(
+    reactNativeVersionCatalog: String?,
+    override: Action<in VersionCatalogBuilder>?
+  ) {
+    val baseFile = if (reactNativeVersionCatalog != null) {
+      File(reactNativeVersionCatalog)
+    } else {
+      File(
+        reactNative,
+        "gradle/libs.versions.toml"
+      )
+    }
+
+    val catalogFile = objects.fileCollection().from(baseFile)
+
+    val properties = listOf(
+      "android.buildToolsVersion" to "buildTools",
+      "android.minSdkVersion" to "minSdk",
+      "android.compileSdkVersion" to "compileSdk",
+      "android.targetSdkVersion" to "targetSdk",
+      "android.kotlinVersion" to "kotlin"
+    )
+
+    settings.dependencyResolutionManagement {
+      it.versionCatalogs { spec ->
+        spec.create("expoLibs") { catalog ->
+          catalog.from(catalogFile)
+          properties.forEach { (propertyName, name) ->
+            val property = settings.providers.gradleProperty(propertyName)
+            if (property.isPresent) {
+              catalog.version(name, property.get())
+            }
+          }
+
+          override?.execute(catalog)
+        }
+      }
+    }
+  }
 }


Reviews:
user: login: alanjhughes
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-31T11:13:56Z
user: login: Kudo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T11:47:22Z

Review comments:
diff_hunk: @@ -1,5 +1,11 @@
 pluginManagement {
-  includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile().toString())
+  def reactNativeGradlePlugin = new File(
+    providers.exec {
+      workingDir(rootDir)
+      commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')")
path: apps/bare-expo/android/settings.gradle
user: login: Kudo
body: ```suggestion
      commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
```

same as https://github.com/expo/expo/pull/35790#discussion_r2020889673
created_at: 2025-03-31T11:46:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,6 +35,30 @@ open class ExpoAutolinkingSettingsExtension(val settings: Settings) {
    */
   var exclude: List<String>? = null
 
+  /**
+   * The file pointing to the React Native Gradle plugin.
+   */
+  val reactNativeGradlePlugin: File by lazy {
+    File(
+      settings.providers.exec { env ->
+        env.workingDir(settings.rootDir)
+        env.commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')")
path: packages/expo-modules-autolinking/android/expo-gradle-plugin/expo-autolinking-settings-plugin/src/main/kotlin/expo/modules/plugin/ExpoAutolinkingSettingsExtension.kt
user: login: Kudo
body: ```suggestion
        env.commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
```

same as https://github.com/expo/expo/pull/35790#discussion_r2020889673
created_at: 2025-03-31T11:47:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-31T11:11:07Z
body: Subscribed to pull request
| File Patterns | Mentions |
| - | - |
| packages/expo-modules-autolinking/\*\* | @kudo |

Generated by [CodeMention](https://github.com/expo/expo/actions/workflows/codemention.yaml)
<!-- codemention header -->
author_association: CONTRIBUTOR
user: login: expo-bot
created_at: 2025-03-31T11:13:16Z
body: <!-- pr-labeler comment -->
The Pull Request introduced fingerprint changes against the base commit: 301429f8e4e20e7733b4d3daecdc3f6719167ac4
<details><summary>Fingerprint diff</summary>

```json
[
  {
    "op": "changed",
    "beforeSource": {
      "type": "dir",
      "filePath": "android",
      "reasons": [
        "bareNativeDir"
      ],
      "hash": "5aa4ffb0f8e56e847ac8296a6b8b86eeeef69671"
    },
    "afterSource": {
      "type": "dir",
      "filePath": "android",
      "reasons": [
        "bareNativeDir"
      ],
      "hash": "350c01ee921ed21cef540df070682ea4dcea9895"
    }
  }
]
```

</details>

---
*Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*

author_association: COLLABORATOR