1class KotlinExpoModulesCorePlugin implements Plugin<Project> {
2  void apply(Project project) {
3    // For compatibility reasons the plugin needs to declare that it provides common build.gradle
4    // options for the modules
5    project.rootProject.ext.expoProvidesDefaultConfig = {
6      true
7    }
8
9    project.ext.safeExtGet = { prop, fallback ->
10      project.rootProject.ext.has(prop) ? project.rootProject.ext.get(prop) : fallback
11    }
12
13    project.buildscript {
14      project.ext.kotlinVersion = {
15        project.rootProject.ext.has("kotlinVersion")
16            ? project.rootProject.ext.get("kotlinVersion")
17            : "1.8.10"
18      }
19
20      project.ext.kspVersion = {
21        def kspVersionsMap = [
22          "1.6.10": "1.6.10-1.0.4",
23          "1.6.21": "1.6.21-1.0.6",
24          "1.7.22": "1.7.22-1.0.8",
25          "1.8.0": "1.8.0-1.0.9",
26          "1.8.10": "1.8.10-1.0.9"
27        ]
28
29        project.rootProject.ext.has("kspVersion")
30          ? project.rootProject.ext.get("kspVersion")
31          : kspVersionsMap.containsKey(project.ext.kotlinVersion())
32          ? kspVersionsMap.get(project.ext.kotlinVersion())
33          : "1.8.10-1.0.9"
34      }
35    }
36
37    // Setup build options that are common for all modules
38    if (project.plugins.hasPlugin('kotlin-android')) {
39      project.android {
40        compileSdkVersion project.ext.safeExtGet("compileSdkVersion", 33)
41
42        defaultConfig {
43          minSdkVersion project.ext.safeExtGet("minSdkVersion", 23)
44          targetSdkVersion project.ext.safeExtGet("targetSdkVersion", 33)
45        }
46
47        lintOptions {
48          abortOnError false
49        }
50      }
51    }
52  }
53}
54
55ext.applyKotlinExpoModulesCorePlugin = {
56  apply plugin: KotlinExpoModulesCorePlugin
57}
58
59ext.useExpoPublishing = {
60  afterEvaluate {
61    publishing {
62      publications {
63        release(MavenPublication) {
64          from components.release
65        }
66      }
67      repositories {
68        maven {
69          url = mavenLocal().url
70        }
71      }
72    }
73  }
74
75  android {
76    publishing {
77      singleVariant("release") {
78        withSourcesJar()
79      }
80    }
81  }
82}
83
84ext.useCoreDependencies = {
85  dependencies {
86    // Avoids cyclic dependencies
87    if (!project.project.name.startsWith("expo-modules-core")) {
88      implementation project.project(':expo-modules-core')
89    }
90    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${project.ext.kotlinVersion()}"
91  }
92}
93
94ext.boolish = { value ->
95  return value.toString().toBoolean()
96}
97
98// [BEGIN] Remove when we drop SDK 47
99abstract class ExtractReactNativeAARTask extends DefaultTask {
100  @Input
101  abstract Property<String> getBuildType()
102
103  @Input
104  abstract Property<String> getReactNativeDir()
105
106  @TaskAction
107  def taskAction() {
108    def suffix = buildType.get() == 'Debug' ? '-debug' : '-release'
109    def rnAARs = project.fileTree("${reactNativeDir.get()}/android").matching { include "**/react-native/**/*${suffix}.aar" }
110    if (rnAARs.isEmpty()) {
111      rnAARs = project.fileTree("${reactNativeDir.get()}/android").matching { include "**/react-native/**/*.aar" }
112    }
113    if (rnAARs.any()) {
114      // node_modules/react-native has a .aar, extract headers
115      if (rnAARs.size() > 1) {
116        logger.error("More than one React Native AAR file has been found:")
117        rnAARs.each {println(it) }
118        throw new GradleException("Multiple React Native AARs found:\n${rnAARs.join("\n")}" +
119            "\nRemove the old ones and try again")
120      }
121    }
122    def rnAAR = rnAARs.singleFile
123    def file = rnAAR.absoluteFile
124    def packageName = file.name.tokenize('-')[0]
125    project.copy {
126      from project.zipTree(file)
127      into "${project.buildDir}/${packageName}"
128      include "jni/**/*"
129    }
130  }
131}
132
133class LegacyReactNativeLibsExtractionPlugin implements Plugin<Project> {
134  void nativeBuildDependsOn(project, dependsOnTask, buildTypesIncludes) {
135    def buildTasks = project.tasks.findAll { task ->
136      def taskName = task.name
137      if (taskName.contains("Clean")) { return false }
138      if (taskName.contains("externalNative") || taskName.contains("CMake") || taskName.contains("generateJsonModel")) {
139        if (buildTypesIncludes == null) { return true }
140        for (buildType in buildTypesIncludes) {
141          if (taskName.contains(buildType)) { return true }
142        }
143      }
144      return false
145    }
146    buildTasks.forEach { task -> task.dependsOn(dependsOnTask) }
147  }
148
149  void apply(Project project) {
150    def REACT_NATIVE_BUILD_FROM_SOURCE = project.findProject(":ReactAndroid") != null
151    def REACT_NATIVE_DIR = REACT_NATIVE_BUILD_FROM_SOURCE
152      ? project.findProject(":ReactAndroid").getProjectDir().parent
153      : new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, project.rootDir).text.trim()).parent
154
155    def reactProperties = new Properties()
156    new File("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
157    def FOLLY_VERSION = reactProperties.getProperty("FOLLY_VERSION")
158    def DOUBLE_CONVERSION_VERSION = reactProperties.getProperty("DOUBLE_CONVERSION_VERSION")
159    def REACT_NATIVE_VERSION = System.getenv("REACT_NATIVE_OVERRIDE_VERSION") ?: reactProperties.getProperty("VERSION_NAME")
160    def REACT_NATIVE_TARGET_VERSION = REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
161
162    def isNewArchitectureEnabled = project.findProperty("newArchEnabled") == "true"
163    def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
164    def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : new File("${project.buildDir}/downloads")
165    def thirdPartyNdkDir = new File("${project.buildDir}/third-party-ndk")
166    def reactNativeThirdParty = new File("$REACT_NATIVE_DIR/ReactAndroid/src/main/jni/third-party")
167
168    def createNativeDepsDirectories = project.tasks.findByName('createNativeDepsDirectories') ?: project.tasks.register('createNativeDepsDirectories') {
169      downloadsDir.mkdirs()
170      thirdPartyNdkDir.mkdirs()
171    }
172
173    def extractReactNativeAARRelease = project.tasks.register('extractReactNativeAARRelease', ExtractReactNativeAARTask) {
174      reactNativeDir = REACT_NATIVE_DIR
175      buildType = 'Release'
176    }
177    def extractReactNativeAARDebug = project.tasks.register('extractReactNativeAARDebug', ExtractReactNativeAARTask) {
178      reactNativeDir = REACT_NATIVE_DIR
179      buildType = 'Debug'
180    }
181
182    def packageReactNdkDebugLibs = project.tasks.register("packageReactNdkDebugLibs", Copy) {
183      dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck")
184      from("$REACT_NATIVE_DIR/ReactAndroid/src/main/jni/prebuilt/lib")
185      into("${project.buildDir}/react-ndk/exported")
186    }
187    def packageReactNdkReleaseLibs = project.tasks.register("packageReactNdkReleaseLibs", Copy) {
188      dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck")
189      from("$REACT_NATIVE_DIR/ReactAndroid/src/main/jni/prebuilt/lib")
190      into("${project.buildDir}/react-ndk/exported")
191    }
192
193    // [BEGIN] Extra libs
194   def downloadDoubleConversion = project.tasks.create('downloadDoubleConversion', project.Download) {
195      dependsOn(createNativeDepsDirectories)
196      src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz")
197      onlyIfNewer(true)
198      overwrite(false)
199      dest(new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz"))
200    }
201
202    def prepareDoubleConversion = project.tasks.register('prepareDoubleConversion', Copy) {
203      dependsOn(downloadDoubleConversion)
204      from(project.tarTree(downloadDoubleConversion.dest))
205      from("$reactNativeThirdParty/double-conversion/Android.mk")
206      include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk")
207      filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" })
208      includeEmptyDirs = false
209      into("$thirdPartyNdkDir/double-conversion")
210    }
211
212    def downloadFolly = project.tasks.create('downloadFolly', project.Download) {
213      dependsOn(createNativeDepsDirectories)
214      src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz")
215      onlyIfNewer(true)
216      overwrite(false)
217      dest(new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz"))
218    }
219
220    def prepareFolly = project.tasks.register('prepareFolly', Copy) {
221      dependsOn(downloadFolly)
222      from(project.tarTree(downloadFolly.dest))
223      from("$reactNativeThirdParty/folly/Android.mk")
224      include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk")
225      eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") }
226      // Fixes problem with Folly failing to build on certain systems. See
227      // https://github.com/software-mansion/react-native-reanimated/issues/1024
228      def follyReplaceContent = '''
229      ssize_t r;
230      do {
231        r = open(name, flags, mode);
232      } while (r == -1 && errno == EINTR);
233        return r;
234      '''
235      filter { line -> line.replaceAll("return int\\(wrapNoInt\\(open, name, flags, mode\\)\\);", follyReplaceContent) }
236      includeEmptyDirs = false
237      into("$thirdPartyNdkDir/folly")
238    }
239    // [END] Extra libs
240
241    project.afterEvaluate {
242      if (REACT_NATIVE_BUILD_FROM_SOURCE) {
243        nativeBuildDependsOn(project, ":ReactAndroid:copyReleaseJniLibsProjectOnly", ["Release", "RelWithDebInfo"])
244        nativeBuildDependsOn(project, ":ReactAndroid:copyDebugJniLibsProjectOnly", ["Debug"])
245      } else {
246        nativeBuildDependsOn(project, extractReactNativeAARRelease, ["Release", "RelWithDebInfo"])
247        nativeBuildDependsOn(project, extractReactNativeAARDebug, ["Debug"])
248      }
249
250      def extraLibs = project.extensions.extraProperties.has('extraLegacyReactNativeLibs')
251        ? project.extensions.extraProperties.get('extraLegacyReactNativeLibs')
252        : []
253      extraLibs.each {
254        nativeBuildDependsOn(project, project.tasks.named(it), null)
255      }
256
257      if (isNewArchitectureEnabled) {
258        def preDebugBuild = project.tasks.named('preDebugBuild')
259        def preReleaseBuild = project.tasks.named('preReleaseBuild')
260        preDebugBuild.configure {
261          dependsOn(packageReactNdkDebugLibs)
262        }
263        preReleaseBuild.configure {
264          dependsOn(packageReactNdkReleaseLibs)
265        }
266
267        // Due to a bug inside AGP, we have to explicitly set a dependency
268        // between configureCMake* tasks and the preBuild tasks.
269        // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732
270        project.tasks.named('configureCMakeDebug').configure {
271          dependsOn(preDebugBuild)
272        }
273        project.tasks.named('configureCMakeRelWithDebInfo').configure {
274          dependsOn(preReleaseBuild)
275        }
276        def reactNativeArchitectures = project.getProperties().get("reactNativeArchitectures")?.split(",") ?: ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
277
278        reactNativeArchitectures.each { architecture ->
279          project.tasks.named("configureCMakeDebug[${architecture}]")?.configure {
280            dependsOn("preDebugBuild")
281          }
282          project.tasks.named("configureCMakeRelWithDebInfo[${architecture}]")?.configure {
283            dependsOn("preReleaseBuild")
284          }
285        }
286      }
287    }
288  }
289}
290
291ext.applyLegacyReactNativeLibsExtractionPlugin = {
292  apply plugin: LegacyReactNativeLibsExtractionPlugin
293}
294
295// [END] Remove when we drop SDK 47
296