You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 lines
8.5 KiB

import groovy.json.JsonSlurper
import java.nio.file.Paths
// Object representing a gradle project.
class ExpoModuleGradleProject {
// Name of the Android project
String name
// Path to the folder with Android project
String sourceDir
ExpoModuleGradleProject(Object data) {
this.name = data.name
this.sourceDir = data.sourceDir
}
}
// Object representing a module.
class ExpoModule {
// Name of the JavaScript package
String name
// Version of the package, loaded from `package.json`
String version
// Gradle projects
ExpoModuleGradleProject[] projects
ExpoModule(Object data) {
this.name = data.packageName
this.version = data.packageVersion
this.projects = data.projects.collect { new ExpoModuleGradleProject(it) }
}
}
class ExpoAutolinkingManager {
private File projectDir
private Map options
private Object cachedResolvingResults
static String generatedPackageListNamespace = 'expo.modules'
static String generatedPackageListFilename = 'ExpoModulesPackageList.java'
static String generatedFilesSrcDir = 'generated/expo/src/main/java'
ExpoAutolinkingManager(File projectDir, Map options = [:]) {
this.projectDir = projectDir
this.options = options
}
Object resolve() {
if (cachedResolvingResults) {
return cachedResolvingResults
}
String[] args = convertOptionsToCommandArgs('resolve', this.options)
args += ['--json']
String output = exec(args, projectDir)
Object json = new JsonSlurper().parseText(output)
cachedResolvingResults = json
return json
}
boolean shouldUseAAR() {
return options?.useAAR == true
}
ExpoModule[] getModules() {
Object json = resolve()
return json.modules.collect { new ExpoModule(it) }
}
static void generatePackageList(Project project, Map options) {
String[] args = convertOptionsToCommandArgs('generate-package-list', options)
// Construct absolute path to generated package list.
def generatedFilePath = Paths.get(
project.buildDir.toString(),
generatedFilesSrcDir,
generatedPackageListNamespace.replace('.', '/'),
generatedPackageListFilename
)
args += [
'--namespace',
generatedPackageListNamespace,
'--target',
generatedFilePath.toString()
]
if (options == null) {
// Options are provided only when settings.gradle was configured.
// If not or opted-out from autolinking, the generated list should be empty.
args += '--empty'
}
exec(args, project.rootDir)
}
static String exec(String[] commandArgs, File dir) {
Process proc = commandArgs.execute(null, dir)
StringBuffer outputStream = new StringBuffer()
proc.waitForProcessOutput(outputStream, System.err)
return outputStream.toString()
}
static private String[] convertOptionsToCommandArgs(String command, Map options) {
String[] args = [
'node',
'--eval',
'require(\'expo-modules-autolinking\')(process.argv.slice(1))',
'--',
command,
'--platform',
'android'
]
def searchPaths = options?.get("searchPaths", options?.get("modulesPaths", null))
if (searchPaths) {
args += searchPaths
}
if (options?.ignorePaths) {
args += '--ignore-paths'
args += options.ignorePaths
}
if (options?.exclude) {
args += '--exclude'
args += options.exclude
}
return args
}
}
class Colors {
static final String GREEN = "\u001B[32m"
static final String RESET = "\u001B[0m"
}
// We can't cast a manager that is created in `settings.gradle` to the `ExpoAutolinkingManager`
// because if someone is using `buildSrc`, the `ExpoAutolinkingManager` class
// will be loaded by two different class loader - `settings.gradle` will use a diffrent loader.
// In the JVM, classes are equal only if were loaded by the same loader.
// There is nothing that we can do in that case, but to make our code safer, we check if the class name is the same.
def validateExpoAutolinkingManager(manager) {
assert ExpoAutolinkingManager.name == manager.getClass().name
return manager
}
// Here we split the implementation, depending on Gradle context.
// `rootProject` is a `ProjectDescriptor` if this file is imported in `settings.gradle` context,
// otherwise we can assume it is imported in `build.gradle`.
if (rootProject instanceof ProjectDescriptor) {
// Method to be used in `settings.gradle`. Options passed here will have an effect in `build.gradle` context as well,
// i.e. adding the dependencies and generating the package list.
ext.useExpoModules = { Map options = [:] ->
ExpoAutolinkingManager manager = new ExpoAutolinkingManager(rootProject.projectDir, options)
ExpoModule[] modules = manager.getModules()
for (module in modules) {
for (moduleProject in module.projects) {
include(":${moduleProject.name}")
project(":${moduleProject.name}").projectDir = new File(moduleProject.sourceDir)
}
}
// Save the manager in the shared context, so that we can later use it in `build.gradle`.
gradle.ext.expoAutolinkingManager = manager
}
} else {
def addModule = { DependencyHandler handler, String projectName, Boolean useAAR ->
Project dependency = rootProject.project(":${projectName}")
if (useAAR) {
handler.add('api', "${dependency.group}:${projectName}:${dependency.version}")
} else {
handler.add('api', dependency)
}
}
def addDependencies = { DependencyHandler handler, Project project ->
def manager = validateExpoAutolinkingManager(gradle.ext.expoAutolinkingManager)
def modules = manager.getModules()
if (!modules.length) {
return
}
println ''
println 'Using expo modules'
for (module in modules) {
// Don't link itself
if (module.name == project.name) {
continue
}
// Can remove this once we move all the interfaces into the core.
if (module.name.endsWith('-interface')) {
continue
}
for (moduleProject in module.projects) {
addModule(handler, moduleProject.name, manager.shouldUseAAR())
println " - ${Colors.GREEN}${moduleProject.name}${Colors.RESET} (${module.version})"
}
}
println ''
}
// Adding dependencies
ext.addExpoModulesDependencies = { DependencyHandler handler, Project project ->
// Return early if `useExpoModules` was not called in `settings.gradle`
if (!gradle.ext.has('expoAutolinkingManager')) {
logger.error('Error: Autolinking is not set up in `settings.gradle`: expo modules won\'t be autolinked.')
return
}
def manager = validateExpoAutolinkingManager(gradle.ext.expoAutolinkingManager)
if (rootProject.findProject(':expo-modules-core')) {
// `expo` requires `expo-modules-core` as a dependency, even if autolinking is turned off.
addModule(handler, 'expo-modules-core', manager.shouldUseAAR())
} else {
logger.error('Error: `expo-modules-core` project is not included by autolinking.')
}
// If opted-in not to autolink modules as dependencies
if (manager.options == null) {
return
}
addDependencies(handler, project)
}
// Generating the package list
ext.generatedFilesSrcDir = ExpoAutolinkingManager.generatedFilesSrcDir
ext.generateExpoModulesPackageList = {
// Get options used in `settings.gradle` or null if it wasn't set up.
Map options = gradle.ext.has('expoAutolinkingManager') ? gradle.ext.expoAutolinkingManager.options : null
if (options == null) {
// TODO(@tsapeta): Temporarily muted this error — uncomment it once we start migrating from autolinking v1 to v2
// logger.error('Autolinking is not set up in `settings.gradle`: generated package list with expo modules will be empty.')
}
ExpoAutolinkingManager.generatePackageList(project, options)
}
ext.ensureDependeciesWereEvaluated = { Project project ->
if (!gradle.ext.has('expoAutolinkingManager')) {
return
}
def modules = gradle.ext.expoAutolinkingManager.getModules()
for (module in modules) {
for (moduleProject in module.projects) {
def dependency = project.findProject(":${moduleProject.name}")
if (dependency == null) {
logger.warn("Coudn't find project ${moduleProject.name}. Please, make sure that `useExpoModules` was called in `settings.gradle`.")
continue
}
// Prevent circular dependencies
if (moduleProject.name == project.name) {
continue
}
project.evaluationDependsOn(":${moduleProject.name}")
}
}
}
}