Description
Hey all,
I'd like to share an update on a series of build failures React Native & Expo users have been experiencing when building Android apps starting from November 4th 2022.
We'd like to apologize for the disruption this caused to your developer workflows. The React team is fully committed to delivering a smooth developer experience, and we take this type of issues extremely seriously.
📢 Patches for >= 0.63
We have prepared releases for all the main versions of react-native with an hotfix:
🛳 0.70.5: https://github.com/facebook/react-native/releases/tag/v0.70.5
🛳️ 0.69.7: https://github.com/facebook/react-native/releases/tag/v0.69.7
🛳 0.68.5: https://github.com/facebook/react-native/releases/tag/v0.68.5
🛳️ 0.67.5: https://github.com/facebook/react-native/releases/tag/v0.67.5
🛳️ 0.66.5: https://github.com/facebook/react-native/releases/tag/v0.66.5
🛳️ 0.65.3: https://github.com/facebook/react-native/releases/tag/v0.65.3
🛳️ 0.64.4: https://github.com/facebook/react-native/releases/tag/v0.64.4
🛳️ 0.63.5: https://github.com/facebook/react-native/releases/tag/v0.63.5
By updating to these patch versions, your Android build should start working again.
To do so, in your package.json
change react-native
's version to the relevant new patch (ex. if you are on 0.64.3, change to 0.64.4) and run yarn install
. No other changes should be necessary, but you might want to clean your android artifacts with a cd android && ./gradlew clean
before trying to re-run your Android app.
Fix for older react-native (< 0.63)
The fix above only works on gradle 6.2 and higher. Older react-native used older gradle.
You may determine your gradle version by looking in your /android/gradle/wrapper/gradle-wrapper.properties
file.
If you are on an older version of react-native (for example 0.63 or earlier) that uses gradle version 6.1 or below, you must use a different workaround as gradle 6.1 does not support exclusiveContent
.
Add this in the allprojects
area of your android/buld.gradle
file.
def REACT_NATIVE_VERSION = new File(['node', '--print',"JSON.parse(require('fs').readFileSync(require.resolve('react-native/package.json'), 'utf-8')).version"].execute(null, rootDir).text.trim())
allprojects {
configurations.all {
resolutionStrategy {
// Remove this override in 0.65+, as a proper fix is included in react-native itself.
force "com.facebook.react:react-native:" + REACT_NATIVE_VERSION
}
}
Instead of using exclusiveContent
, the hotfix must force the version of React Native. The recommended hotfix shells out to node
to read your current version of react-native. If you hard code the version of react-native, when you upgrade your project in the future, your build will fail if you forget to remove this hotfix.
Note that this fix is fragile as the location of your package.json could be different if you are in a monorepo, and node might not be available if you use a node package manager like nvm.
Thank you @mikehardy for finding and providing this fix in your comment.
Technical Details
The issue
On November 4th 2022 we published the React Native version 0.71.0-rc0
, the first release candidate for the 71
release series, on several public repositories. Specifically, this version was also published on Maven Central as we're updating our artifacts distribution strategy (technical details are explained below).
This event resulted in build failures for Android on several users as they ended up downloading the wrong React Native version (0.71.0-rc0
instead of the version they were using in their project, say 0.68.0
).
The build error looks something like this:
Error: Command failed: gradlew.bat app:installDebug -PreactNativeDevServerPort=8081
FAILURE: Build failed with an exception.
* What went wrong:
A problem occurred configuring root project 'My Project'.
Could not determine the dependencies of null.
Could not resolve all task dependencies for configuration ':classpath'.
> Could not resolve com.facebook.react:react-native:+.
Required by:
project :
> No matching variant of com.facebook.react:react-native:0.71.0-rc.0 was found.
The consumer was configured to find a runtime of a library compatible with Java 11,
packaged as a jar, and its dependencies declared externally, as well
as attribute 'org.gradle.plugin.api-version' with value '7.3.3' but:
Sadly, due to how older projects of React Native were created in the past, we couldn't simply re-publish older versions, and we're asking users to update their template using the fix suggested below.
Why this happened
Historically, the React Native template provided build.gradle
files that contain a dependency on the React Native Android library as follows: implementation("com.facebook.react:react-native:+")
.
Specifically, the +
part of this dependency declaration is causing Gradle to pick the highest version among all the declared repositories (often referred as Gradle Dynamic versions). Till React Native version 0.70
, we were shipping a Maven Repository inside the NPM package (inside the ./android
folder). Starting from 0.71
, we moved such folder and uploaded it to Maven Central.
Using Gradle Dynamic versions (i.e. a +
dependency) is considered an antipattern (see the various warnings and notes on this page), specifically as it can lead to scenarios like this one and generally exposes users to less-reproducible builds. This is exactly what happened in this scenario, as builds started failing without any changes to user projects.
In 0.71
we cleaned up the new app template and removed all the +
dependencies (see here) and we moved the template to use the React Native Gradle Plugin, which will allow us to prevent scenarios like this from happening in the future.
Sadly, users on older versions, say 0.66.0
, were still using a +
version. This caused their build to query all the repositories for the highest available versions of the React Native. While the node_modules/react-native/android
folder contained a valid 0.66.0
version, Gradle honoured the +
version and fetched version 0.71.0-rc0
from Maven Central as it's higher from the semantic versioning ordering.
The resolutionStrategy
block in the fix is forcing the version of com.facebook.react:react-native
to all the projects to the one provided by the version of React Native you're effectively using.
Who is affected?
All the React Native users on versions till 0.66.x
are affected.
React Native users on versions from 0.67
till 0.70
could be affected, based on which third-party library they're using.
We verified that empty projects created on versions from 0.67
till 0.70
are not affected.
However, some of your dependencies might end up causing a wrong resolution of the React Native Android library, resulting in build failures.
For instance, we verified that a project on React Native 0.68.4
works well with Reanimated 2.12.0
but fails with Reanimated 2.10.0
.
We also worked with Expo to make sure that users on eas build
received the aforementioned fix, so those users should not be affected.
Why React Native Android was published on Maven Central?
As mentioned, this was done as part of the implementation of the RFC #580.
The summary of that RFC is to allow us to overcome the limitation of NPM in terms of package size. Moving to Maven Central will allow us to reduce the build time for our users and distribute more granular artifacts which will end up benefitting the overall developer experience. We'll be able to share information on this in the near future.
In the initial Github Issue, several users suggest to remove the publishing from Maven Central or re-publish older artifacts to overcome the build failure without requesting a fix.
This is sadly not an option as:
- Maven Central is an immutable artifacts storage and doesn't allow deletion.
- The publishing on
0.71.0-rc0
was effectively correct. We believe this episode is essentially a "mis-configuration" in user projects, and we believe the better solution is to distribute this fix to our users.
How could this have been prevented
We were already aware of the risk of having Gradle dynamic dependencies, so since React Native 0.67
we introduced an exclude
rule on the Maven Central dependency inside the default template (see here for more context):
mavenCentral {
content {
excludeGroup("com.facebook.react")
}
}
React Native used to be published on Maven Central till version 0.20.1
(published in 2016). We had users in the past accidentally fetching older versions of React from Maven Central, causing their build to fail.
This exclude
directive assured that you won't ever download React Native from Maven Central. However, third party libraries could declare repositories inside their repositories{}
block which might not contain the exclude
directive, resulting in potentially broken dependency resolution.
React Native version 0.71.0
will ship with an updated template that relies on React Native Gradle Plugin. This new integration will allow us to protect us against future distributed build failures like this one, by allowing to re-distribute updated build logic across the whole ecosystem if needed (this should protect us also against similar outages like the JCenter outage happened last year).
I'd like to thank everyone for your patience and understanding and I'd like to call out some members our our community, specifically @mikehardy, @Titozzz, @brentvatne, @inckie and all the other folks involved in the initial investigation and fix for this issue.
We'll follow up with more updates in the future if needed.
Nicola
On behalf of the React team
Platform: Android Resolution: Fixed RN Team