Expo documentation states it's possible to exclude Expo packages from the Android build by calling the useExpoModules
function in the project's settings.gradle
file.
useExpoModules
seems to be defined in expo-modules-autolinking/scripts/android/autolinking_implementation.gradle
. It accepts a map of options, one of which is exclude
, which is a list of Expo packages to exclude. settings.gradle
has a reference to this function via the apply from
syntax.
Here's an example from the Expo Go app's settings.gradle
file:
// ...
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
// ...
useExpoModules([
searchPaths: [
'../../../packages'
],
exclude : [
'expo-module-template',
'expo-module-template-local',
'react-native-reanimated',
'expo-dev-menu-interface',
'expo-dev-menu',
'expo-dev-launcher',
'expo-dev-client',
'expo-maps',
'expo-network-addons',
'expo-splash-screen',
'@expo/ui',
'expo-mesh-gradient'
]
])
In my Expo project, using SDK version 52, I conditionally exclude certain packages from the Android build. For example, my settings.gradle
would have something like this:
if (/* condition */) {
useExpoModules([
exclude: [/* List of excluded packages */]
])
} else {
useExpoModules()
}
The above works in SDK 52, however SDK 53 (which I am upgrading to) changes how useExpoModules
is defined and called.
The function is defined in the expo-autolinking-settings-plugin
and does not seem to accept any options via parameters but references options via properties on the class it's defined on:
// ...
open class ExpoAutolinkingSettingsExtension(
val settings: Settings,
@Inject val objects: ObjectFactory
) {
// ...
var exclude: List<String>? = null
// ...
fun useExpoModules() {
SettingsManager(
settings,
searchPaths,
ignorePaths,
exclude
).useExpoModules()
}
// ...
}
See the "bare minimum" Expo template for an example of how the plugin is applied and the function called:
// ...
plugins {
// ...
id("expo-autolinking-settings")
}
// ...
expoAutolinking.useExpoModules()
// ...
With this information, how might I (conditionally) exclude Expo package(s) from the Android build in Expo SDK 53?
expoAutolinking.useExpoModules([ exclude: [/* List of excluded packages */] ])
, predictably, does not work, raising the following error:
Could not find method useExpoModules() for arguments [{exclude=[ ... ]}] on extension 'expoAutolinking' of type expo.modules.plugin.ExpoAutolinkingSettingsExtension.
expoAutolinking.exclude
For example:
// ...
if (/* condition */) {
expoAutolinking.exclude = [/* List of excluded packages */]
}
expoAutolinking.useExpoModules()
// ...
This also does not seem to work because the packages which are supposed to be excluded are still included (according to build logs (logged from here) and an error originating from a task from a package which was supposed to be excluded).
I then added the following logs: (listed in call order):
exclude
in ExpoAutolinkingSettingsExtension.useExpoModules
autolinkingOptions.exclude
in SettingsManager.useExpoModules
All logs correctly logged a list of excluded packages. This means Expo is correctly receiving the list of excluded packages, right?
Adding a log within the logic that resolves the auto-linking configuration in SettingsManager.kt
(source) revealed the issue. The logged command was:
[
node,
--no-warnings,
--eval,
require(require.resolve('expo-modules-autolinking', { paths: [require.resolve('expo/package.json')] }))(process.argv.slice(1)),
--,
resolve,
--platform,
android,
--json,
--exclude,
excluded-package-1 excluded-package-2 excluded-package-n
]
The excluded packages were passed as a single argument to the command that resolves the details for each package. As a result, the script was excluding a package by the name of all the excluded packages joined together which doesn't exist so can't be excluded.
The reason why all the excluded packages gets passed as a single argument is because they are not split up:
fun build(): List<String> {
val command = baseCommand +
autolinkingCommand +
platform +
useJson +
optionsMap.map { (key, value) -> listOf("--$key", value) }.flatMap { it } +
searchPaths
return Os.windowsAwareCommandLine(command)
}
ā source
optionsMap.map { (key, value) -> listOf("--$key", value) }.flatMap { it }
should be something like
optionsMap.flatMap { (key, value) ->
value.split(" ").filter { it.isNotEmpty() }.map { subvalue -> listOf("--$key", subvalue) }
}.flatMap { it }