diff --git a/apps/RNApp/.gitignore b/apps/RNApp/.gitignore index 60141bd4..f6ca3efb 100644 --- a/apps/RNApp/.gitignore +++ b/apps/RNApp/.gitignore @@ -77,3 +77,7 @@ yarn-error.log # Brownfield android/BrownfieldLib/libsDebug/ android/BrownfieldLib/libsRelease/ + +# Benchmarks +android/gradle-user-home/ +android/profile-out/ diff --git a/apps/RNApp/android/gradle-profiler/README.md b/apps/RNApp/android/gradle-profiler/README.md new file mode 100644 index 00000000..3e97ce57 --- /dev/null +++ b/apps/RNApp/android/gradle-profiler/README.md @@ -0,0 +1,25 @@ +### Gradle Profiler + +Repo: https://github.com/gradle/gradle-profiler +Android docs: https://developer.android.com/build/profile-your-build + +### Pre-Requisites +- Ensure gradle-profiler is installed, (instructions)[https://github.com/gradle/gradle-profiler#installing] +- Change your working directory to `RNApp/android/gradle-profiler` + +### Steps: +- Checkout to main or baseline branch +- Adjust the scenarios to suit your use-case OR create a new scenarios file +- Run the following command: +```bash +gradle-profiler --benchmark --project-dir ../ --scenario-file ./scenarios.txt +``` +- Once the run finishes, copy the items in `profile-out/benchmark.csv` to `benchmarks/old.txt` OR create a new file +- Checkout to the current branch +- Use the same scenarios from above and run the benchmark command +- Once the run finishes, copy the items in `profile-out/benchmark.csv` to `benchmarks/new.txt` OR create a new file + +> [NOTE] +> Clear the `profile-out` folder before a next run, otherwise a new folder `profile-out2` will be created. +> The changes to scenarios.txt or new file creation, similarly under benchmarks folder, will required to be +> tracked to version control. diff --git a/apps/RNApp/android/gradle-profiler/benchmarks/new.txt b/apps/RNApp/android/gradle-profiler/benchmarks/new.txt new file mode 100644 index 00000000..861af971 --- /dev/null +++ b/apps/RNApp/android/gradle-profiler/benchmarks/new.txt @@ -0,0 +1,5 @@ +measured build #1,77705.66 +measured build #2,73437.54 +measured build #3,82706.11 +measured build #4,75284.93 +measured build #5,75479.56 diff --git a/apps/RNApp/android/gradle-profiler/benchmarks/old.txt b/apps/RNApp/android/gradle-profiler/benchmarks/old.txt new file mode 100644 index 00000000..e7d8dfad --- /dev/null +++ b/apps/RNApp/android/gradle-profiler/benchmarks/old.txt @@ -0,0 +1,11 @@ +scenario,on_clean_build +version,Gradle 9.3.1 +tasks,:brownfield:assembleRelease +value,total execution time +warm-up build #1,162130.94 +warm-up build #2,94060.00 +measured build #1,104519.90 +measured build #2,104545.84 +measured build #3,90355.22 +measured build #4,90874.80 +measured build #5,104207.60 diff --git a/apps/RNApp/android/gradle-profiler/print.sh b/apps/RNApp/android/gradle-profiler/print.sh new file mode 100644 index 00000000..ad2c65e0 --- /dev/null +++ b/apps/RNApp/android/gradle-profiler/print.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# --- Colors for Output --- +RED='\033[0;31m' +GREEN='\033[0;32m' +CYAN='\033[0;36m' +YELLOW='\033[1;33m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +# --- Configuration & Args --- +OLD_FILE=${1:-"benchmarks/old.txt"} +NEW_FILE=${2:-"benchmarks/new.txt"} + +# Validation +if [[ ! -f "$OLD_FILE" || ! -f "$NEW_FILE" ]]; then + echo "${RED}${BOLD}Error:${NC} Files not found." + echo "Usage: $0 [old_file] [new_file]" + exit 1 +fi + +# --- Extraction Logic --- +# Extract metadata from the first file (assuming they are for the same scenario) +SCENARIO=$(grep "^scenario," "$OLD_FILE" | cut -d',' -f2) +TASKS=$(grep "^tasks," "$OLD_FILE" | cut -d',' -f2) + +# Calculation Function via AWK +# Returns: avg|count +process_data() { + awk -F',' '/^measured build/ { sum += $2; count++ } END { if (count > 0) print sum/count "|" count; else print "0|0" }' "$1" +} + +OLD_DATA=$(process_data "$OLD_FILE") +NEW_DATA=$(process_data "$NEW_FILE") + +OLD_AVG=$(echo "$OLD_DATA" | cut -d'|' -f1) +OLD_CNT=$(echo "$OLD_DATA" | cut -d'|' -f2) +NEW_AVG=$(echo "$NEW_DATA" | cut -d'|' -f1) +NEW_CNT=$(echo "$NEW_DATA" | cut -d'|' -f2) + +# --- Verbose Pretty Print --- +echo "${CYAN}${BOLD}==============================================================" +echo " GRADLE BENCHMARK COMPARISON" +echo "==============================================================${NC}" +printf "${BOLD}%-12s${NC} %s\n" "Scenario:" "$SCENARIO" +printf "${BOLD}%-12s${NC} %s\n" "Tasks:" "$TASKS" +echo "--------------------------------------------------------------" + +# Table Header +printf "${BOLD}%-15s | %-12s | %-15s | %-10s${NC}\n" "Target" "Builds" "Average (ms)" "Minutes" +echo "----------------|--------------|-----------------|------------" + +# Row function for reuse +print_row() { + local label=$1 + local cnt=$2 + local avg=$3 + # Calculate minutes/seconds inside AWK for the row + local min_fmt=$(awk -v ms="$avg" 'BEGIN { printf "%dm %05.2fs", int(ms/60000), (ms%60000)/1000 }') + printf "%-15s | %-12s | %-15.2f | %-10s\n" "$label" "$cnt" "$avg" "$min_fmt" +} + +print_row "Main" "$OLD_CNT" "$OLD_AVG" +print_row "Optimized" "$NEW_CNT" "$NEW_AVG" + +echo "----------------|--------------|-----------------|------------" + +# Final Comparison Logic +awk -v old="$OLD_AVG" -v new="$NEW_AVG" \ + -v red="$RED" -v grn="$GREEN" -v yel="$YELLOW" -v bld="$BOLD" -v nc="$NC" ' +BEGIN { + diff = new - old + pct = (old > 0) ? (diff / old) * 100 : 0 + abs_diff = (diff < 0) ? -diff : diff + + # Format diff to minutes + diff_min = sprintf("%dm %05.2fs", int(abs_diff/60000), (abs_diff%60000)/1000) + + if (diff < -1) { + printf "\n%sRESULT: IMPROVEMENT%s\n\n", grn bld, nc + printf "The new build is %s%.2f ms (%s) faster%s\n", grn, abs_diff, diff_min, nc + printf "Speedup: %s%.2f%%%s\n", grn, -pct, nc + } else if (diff > 1) { + printf "\n%sRESULT: REGRESSION%s\n", red bld, nc + printf "The new build is %s%.2f ms (%s) slower%s\n", red, diff, diff_min, nc + printf "Slowdown: %s+%.2f%%%s\n", red, pct, nc + } else { + printf "\n%sRESULT: NEGLIGIBLE CHANGE%s\n", yel, nc + } + print "" +}' \ No newline at end of file diff --git a/apps/RNApp/android/gradle-profiler/scenarios.txt b/apps/RNApp/android/gradle-profiler/scenarios.txt new file mode 100644 index 00000000..0631127d --- /dev/null +++ b/apps/RNApp/android/gradle-profiler/scenarios.txt @@ -0,0 +1,8 @@ +on_clean_build { + tasks = [":brownfield:assembleRelease"] // Replace with your task + cleanup-tasks = ["clean"] + # Disables caching to ensure an "absolute" clean run + gradle-args = ["--no-build-cache", "--no-configuration-cache"] + warm-ups = 2 + iterations = 5 +} \ No newline at end of file diff --git a/gradle-plugins/react/brownfield/build.gradle.kts b/gradle-plugins/react/brownfield/build.gradle.kts index 5f0a263b..31b64903 100644 --- a/gradle-plugins/react/brownfield/build.gradle.kts +++ b/gradle-plugins/react/brownfield/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(libs.plugins.detekt) `maven-publish` signing + kotlin("plugin.serialization") version "1.9.24" } ktlint { @@ -69,18 +70,6 @@ publishing { distribution.set("repo") } } - developers { - developer { - id.set("callstack") - name.set("Callstack Team") - email.set("it-admin@callstack.com") - } - } - scm { - connection.set(property("SCM_CONNECTION").toString()) - developerConnection.set(property("SCM_DEV_CONNECTION").toString()) - url.set(property("GITHUB_URL").toString()) - } } } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/artifacts/ArtifactsResolver.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/artifacts/ArtifactsResolver.kt index 2b9b5dcb..76841de1 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/artifacts/ArtifactsResolver.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/artifacts/ArtifactsResolver.kt @@ -1,64 +1,34 @@ -@file:Suppress("DEPRECATION") - -/** - * Suppressing because of LibraryVariant. - * We can't use the new `com.android.build.gradle.api.LibraryVariant` - * as of now. - * - * We may want to re-visit this in future. - */ - package com.callstack.react.brownfield.artifacts -import com.android.build.gradle.LibraryExtension -import com.android.build.gradle.api.LibraryVariant -import com.callstack.react.brownfield.plugin.ProjectConfigurations.Companion.CONFIG_NAME -import com.callstack.react.brownfield.plugin.ProjectConfigurations.Companion.CONFIG_SUFFIX -import com.callstack.react.brownfield.processors.VariantHelper -import com.callstack.react.brownfield.processors.VariantProcessor -import com.callstack.react.brownfield.processors.VariantTaskProvider -import com.callstack.react.brownfield.shared.BaseProject -import com.callstack.react.brownfield.shared.GradleProps +import com.callstack.react.brownfield.expo.utils.ExpoGradleProjectProjection +import com.callstack.react.brownfield.expo.utils.LocalMavenUtils +import com.callstack.react.brownfield.shared.UnresolvedArtifactInfo import com.callstack.react.brownfield.utils.Extension import com.callstack.react.brownfield.utils.Utils -import org.gradle.api.ProjectConfigurationException -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ResolvedArtifact +import org.gradle.api.Project import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency +import java.io.File +import kotlin.collections.mutableListOf class ArtifactsResolver( - private val configurations: MutableCollection, - private val baseProject: BaseProject, - private val extension: Extension, + private val project: Project, private val hasExpo: Boolean, -) : - GradleProps() { +) { companion object { - const val ARTIFACT_TYPE_AAR = "aar" - const val ARTIFACT_TYPE_JAR = "jar" - } - - fun processArtifacts() { - embedDefaultDependencies("implementation") - setTransitiveToConfigurations() - generateArtifacts() + const val CONFIG_NAME = "implementation" } - private fun setTransitiveToConfigurations() { - configurations.forEach { - if (baseProject.project.extensions.getByType(Extension::class.java).transitive) { - it.isTransitive = true - } - } + fun processDefaultDependencies(expoProjects: List): List { + return embedDefaultDependencies(expoProjects) } - private fun embedExpoDependencies() { + private fun embedExpoDependencies(expoProjects: List): List { /** * expo project does not exist in example-android-library so doing an * early exit. */ - if (Utils.isExampleLibrary(baseProject.project.name)) { - return + if (Utils.isExampleLibrary(project.name)) { + return listOf() } /** @@ -70,125 +40,102 @@ class ArtifactsResolver( * We get those dependencies of `expo` project and add those to the consumer * library project. */ - val expoProject = baseProject.project.rootProject.project("expo") + val expoProject = project.rootProject.project("expo") val expoConfig = expoProject.configurations.findByName("api") - expoConfig?.dependencies?.forEach { - if (extension.resolveLocalDependencies) { - if (it is DefaultProjectDependency) { - val projectDependency = - expoProject.dependencies.project(mapOf("path" to ":${it.name}")) - baseProject.project.dependencies.add( - CONFIG_NAME, - projectDependency, - ) - } else { - baseProject.project.dependencies.add( - CONFIG_NAME, - it, - ) - } - } - } - } + val unresolvedArtifactInfo = mutableListOf() - private fun embedDefaultDependencies(configName: String) { - if (this.hasExpo) { - embedExpoDependencies() + val expoPublishedProjects = mutableMapOf() + expoProjects.filter { it.usePublication }.forEach { + val artifactId = it.publication?.artifactId + if (artifactId != null) { + expoPublishedProjects[artifactId] = it + } } - val config = baseProject.project.configurations.findByName(configName) - val defaultDependencies = config?.dependencies?.filterIsInstance() - defaultDependencies?.forEach { dependency -> - if (extension.resolveLocalDependencies) { - val projectDependency = - baseProject.project.dependencies.project(mapOf("path" to ":${dependency.name}")) - baseProject.project.dependencies.add( + expoConfig?.dependencies?.forEach { + if (it is DefaultProjectDependency) { + addProjectDependencyFromDefault( + expoProject, + it, + unresolvedArtifactInfo, + ) + } else { + project.dependencies.add( CONFIG_NAME, - projectDependency, + it, ) + + val projectDependency = expoPublishedProjects[it.name] + val sourceDir = projectDependency?.sourceDir ?: return@forEach + unresolvedArtifactInfo.add(getExpoUnresolvedArtifactInfo(projectDependency, sourceDir)) } } + + return unresolvedArtifactInfo } - private fun generateArtifacts() { - baseProject.project.extensions.getByType(LibraryExtension::class.java).libraryVariants.all { variant -> - val artifacts: MutableCollection = ArrayList() - - configurations.forEach { configuration -> - if (isEmbedConfig(configuration, variant)) { - val resolvedArtifacts = resolveArtifacts(configuration) - artifacts.addAll(resolvedArtifacts) - artifacts.addAll( - handleUnResolvedArtifacts( - configuration, - variant, - resolvedArtifacts, - ), - ) - } - } + private fun embedDefaultDependencies(expoProjects: List): List { + val unresolvedArtifactInfo = + if (hasExpo) embedExpoDependencies(expoProjects).toMutableList() else mutableListOf() - if (artifacts.isNotEmpty()) { - val processor = VariantProcessor(variant) - processor.project = baseProject.project - processor.processVariant(artifacts) - } + val projectExt = project.extensions.getByType(Extension::class.java) + val appProject = project.rootProject.project(projectExt.appProjectName) + + val config = project.rootProject.project(appProject.path).configurations.findByName(CONFIG_NAME) + val defaultDependencies = config?.dependencies?.filterIsInstance() + defaultDependencies?.forEach { dependency -> + addProjectDependencyFromDefault( + project, + dependency, + unresolvedArtifactInfo, + ) } - } - private fun isEmbedConfig( - configuration: Configuration, - variant: LibraryVariant, - ): Boolean { - return configuration.name == CONFIG_NAME || configuration.name == variant.buildType.name + CONFIG_SUFFIX || - configuration.name == variant.flavorName + CONFIG_SUFFIX || - configuration.name == variant.name + CONFIG_SUFFIX + return unresolvedArtifactInfo } - private fun resolveArtifacts(configuration: Configuration): Collection { - val artifacts = ArrayList() - configuration.resolvedConfiguration.resolvedArtifacts.forEach { artifact -> - if (artifact.type != ARTIFACT_TYPE_AAR && artifact.type != ARTIFACT_TYPE_JAR) { - throw ProjectConfigurationException( - "Unsupported dependency. Please provide either Aar or Jar dependency", - listOf(), - ) - } - artifacts.add(artifact) - } - return artifacts + /** + * Resolves a [DefaultProjectDependency] relative to [ownerProject], adds it to this consumer's + * [CONFIG_NAME], and records a non-publish [UnresolvedArtifactInfo]. + */ + private fun addProjectDependencyFromDefault( + ownerProject: Project, + defaultProjectDependency: DefaultProjectDependency, + unresolvedArtifactInfo: MutableList, + ) { + val projectDependency = + ownerProject.dependencies.project(mapOf("path" to ":${defaultProjectDependency.name}")) + project.dependencies.add(CONFIG_NAME, projectDependency) + unresolvedArtifactInfo.add( + UnresolvedArtifactInfo( + projectDependency.group.toString(), + projectDependency.name, + projectDependency.version.toString(), + null, + isExpoPublishDependency = false, + ), + ) } - private fun handleUnResolvedArtifacts( - configuration: Configuration, - variant: LibraryVariant, - artifacts: Collection, - ): Collection { - val artifactList = ArrayList() - val unMatchedArtifacts = - configuration.resolvedConfiguration.firstLevelModuleDependencies.filter { - !artifacts.any { artifact -> - it.moduleName == artifact.moduleVersion.id.name - } - } - - val variantHelper = VariantHelper(variant) - variantHelper.project = baseProject.project - val variantTaskProvider = VariantTaskProvider(variantHelper) - variantTaskProvider.project = baseProject.project - val flavorArtifact = FlavorArtifact(variant, variantTaskProvider) - flavorArtifact.project = baseProject.project - - unMatchedArtifacts.forEach { dependency -> - val resolvedArtifact = - flavorArtifact.createFlavorArtifact( - dependency, - calculatedValueContainerFactory, - fileResolver, - taskDependencyFactory, - ) - artifactList.add(resolvedArtifact) - } - return artifactList + private fun getExpoUnresolvedArtifactInfo( + projectDependency: ExpoGradleProjectProjection, + sourceDir: String, + ): UnresolvedArtifactInfo { + val projectName = projectDependency.name + val projectDir = File(sourceDir) + val expoLocalMavenRepo = projectDir.parentFile.resolve("local-maven-repo") + val publication = + LocalMavenUtils.getPublishingInfo(projectDependency, project) + ?: error(LocalMavenUtils.publishingNotFound(projectName)) + + val artifactFile = LocalMavenUtils.getAarFile(expoLocalMavenRepo, publication) + + return UnresolvedArtifactInfo( + publication.groupId, + projectName, + publication.version, + artifactFile.absolutePath, + isExpoPublishDependency = true, + ) } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/artifacts/FlavorArtifact.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/artifacts/FlavorArtifact.kt deleted file mode 100644 index db91a447..00000000 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/artifacts/FlavorArtifact.kt +++ /dev/null @@ -1,125 +0,0 @@ -@file:Suppress("DEPRECATION") - -/** - * Suppressing because of LibraryVariant. - * We can't use the new `com.android.build.gradle.api.LibraryVariant` - * as of now. - * - * We may want to re-visit this in future. - */ - -package com.callstack.react.brownfield.artifacts - -import com.android.build.gradle.LibraryExtension -import com.android.build.gradle.api.LibraryVariant -import com.android.build.gradle.internal.tasks.factory.dependsOn -import com.callstack.react.brownfield.processors.VariantTaskProvider -import com.callstack.react.brownfield.shared.BaseProject -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.artifacts.ResolvedArtifact -import org.gradle.api.artifacts.ResolvedDependency -import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier -import org.gradle.api.internal.artifacts.DefaultResolvedArtifact -import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact -import org.gradle.api.internal.file.FileResolver -import org.gradle.api.internal.tasks.TaskDependencyFactory -import org.gradle.api.tasks.TaskProvider -import org.gradle.api.tasks.bundling.Zip -import org.gradle.internal.Describables -import org.gradle.internal.component.local.model.PublishArtifactLocalArtifactMetadata -import org.gradle.internal.component.model.DefaultIvyArtifactName -import org.gradle.internal.model.CalculatedValueContainerFactory -import java.io.File - -class FlavorArtifact(private val variant: LibraryVariant, private val variantTaskProvider: VariantTaskProvider) : BaseProject() { - fun createFlavorArtifact( - unResolvedArtifact: ResolvedDependency, - calculatedValueContainerFactory: CalculatedValueContainerFactory, - fileResolver: FileResolver, - taskDependencyFactory: TaskDependencyFactory, - ): ResolvedArtifact { - val artifactProject = getArtifactProject(unResolvedArtifact) - val bundleProvider: TaskProvider? = - artifactProject?.let { getBundleTaskProvider(it, variant) } - - project.tasks.named("preBuild").dependsOn("${artifactProject?.path}:${bundleProvider?.name}") - - val identifier = - DefaultModuleVersionIdentifier.newId( - unResolvedArtifact.moduleGroup, - unResolvedArtifact.moduleName, - unResolvedArtifact.moduleVersion, - ) - val artifactFile = createArtifactFile(bundleProvider?.get() as Task) - val artifactName = DefaultIvyArtifactName(artifactFile.name, "aar", "") - - return DefaultResolvedArtifact( - PublishArtifactLocalArtifactMetadata( - { artifactName.name }, - LazyPublishArtifact(bundleProvider, fileResolver, taskDependencyFactory), - ), - calculatedValueContainerFactory.create(Describables.of(artifactFile.name), artifactFile), - identifier, - artifactName, - ) - } - - private fun createArtifactFile(bundle: Task): File { - val packageLibraryProvider = bundle as Zip - return File(packageLibraryProvider.destinationDirectory.get().asFile, packageLibraryProvider.archiveFileName.get()) - } - - private fun getBundleTaskProvider( - project: Project, - variant: LibraryVariant, - ): TaskProvider? { - var bundleTaskProvider: TaskProvider? = null - val androidExtension = project.extensions.getByType(LibraryExtension::class.java) - - androidExtension.libraryVariants.find { - if (it.name == variant.name || it.name == variant.buildType.name) { - bundleTaskProvider = variantTaskProvider.bundleTaskProvider(project, it.name) - } - - if (bundleTaskProvider == null) { - val flavor = if (variant.productFlavors.isEmpty()) variant.mergedFlavor else variant.productFlavors.first() - try { - val missingDimensionStrategies = androidExtension.productFlavors.getByName(flavor.name).missingDimensionStrategies - - missingDimensionStrategies.entries.find { entry -> - val toDimension = entry.key - val requestedValues = listOf(entry.value.requested) - val toFlavors = requestedValues + entry.value.fallbacks - val subFlavor = - if (it.productFlavors.isEmpty()) { - it.mergedFlavor - } else { - it.productFlavors.first() - } - toFlavors.firstOrNull { toFlavor -> - val isDimensionEqual = toDimension == subFlavor.dimension - val isFlavorEqual = toFlavor == subFlavor.name - val isBuildTypeEqual = variant.buildType.name == it.buildType.name - if (isDimensionEqual && isFlavorEqual && isBuildTypeEqual) { - bundleTaskProvider = variantTaskProvider.bundleTaskProvider(project, it.name) - } - false - } != null - } != null - } catch (ignore: Exception) { - } - } - - bundleTaskProvider != null - } - - return bundleTaskProvider - } - - private fun getArtifactProject(unResolvedArtifact: ResolvedDependency): Project? { - return project.rootProject.allprojects.find { p -> - unResolvedArtifact.moduleName == p.name && unResolvedArtifact.moduleGroup == p.group.toString() - } - } -} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/ExpoPublishingHelper.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/ExpoPublishingHelper.kt index 1b3cbd2f..72c4288b 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/ExpoPublishingHelper.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/ExpoPublishingHelper.kt @@ -1,10 +1,9 @@ package com.callstack.react.brownfield.expo -import com.android.build.gradle.LibraryExtension import com.android.utils.forEach -import com.callstack.react.brownfield.expo.utils.BrownfieldPublishingInfo import com.callstack.react.brownfield.expo.utils.DependencyInfo import com.callstack.react.brownfield.expo.utils.ExpoGradleProjectProjection +import com.callstack.react.brownfield.expo.utils.LocalMavenUtils import com.callstack.react.brownfield.expo.utils.VersionMediatingDependencySet import com.callstack.react.brownfield.expo.utils.asExpoGradleProjectProjection import com.callstack.react.brownfield.shared.Constants @@ -30,7 +29,7 @@ fun Node.getChildNodeByName(nodeName: String): Node? { } open class ExpoPublishingHelper(val brownfieldAppProject: Project) { - fun afterEvaluate() { + fun configure(): List { val discoverableExpoProjects = getDiscoverableExpoProjects() Logging.log( @@ -58,6 +57,8 @@ open class ExpoPublishingHelper(val brownfieldAppProject: Project) { reconfigurePOM(expoTransitiveDependencies) reconfigureGradleModuleJSON(expoTransitiveDependencies) + + return discoverableExpoProjects } protected fun shouldExcludeDependency( @@ -273,11 +274,8 @@ open class ExpoPublishingHelper(val brownfieldAppProject: Project) { fun discoverExpoTransitiveDependenciesForPublication(expoGPProjection: ExpoGradleProjectProjection): VersionMediatingDependencySet? { val publication = - getPublishingInfo(expoGPProjection) - ?: error( - "Cannot configure publishing for Expo project " + - "${expoGPProjection.name} - could not determine publishing info", - ) + LocalMavenUtils.getPublishingInfo(expoGPProjection, brownfieldAppProject) + ?: error(LocalMavenUtils.publishingNotFound(expoGPProjection.name)) val pkgProjectDir = File(expoGPProjection.sourceDir) val pkgProject = @@ -285,18 +283,7 @@ open class ExpoPublishingHelper(val brownfieldAppProject: Project) { it.projectDir.canonicalFile == pkgProjectDir.canonicalFile } val expoPkgLocalMavenRepo = pkgProjectDir.parentFile.resolve("local-maven-repo") - - val pomFile = - expoPkgLocalMavenRepo - .resolve( - "${ - publication.groupId.replace( - '.', - '/', - ) - }/${publication.artifactId}/${publication.version}/" + - "${publication.artifactId}-${publication.version}.pom", - ) + val pomFile = LocalMavenUtils.getPomFile(expoPkgLocalMavenRepo, publication) val dependencies = VersionMediatingDependencySet() var depsDiscoverySource: String @@ -405,7 +392,7 @@ open class ExpoPublishingHelper(val brownfieldAppProject: Project) { * Discovers Expo projects in the current brownfield app project that are marked for publication. * @return List of ExpoGradleProjectProjection representing the discoverable Expo projects. */ - protected fun getDiscoverableExpoProjects(): List { + fun getDiscoverableExpoProjects(): List { val expoExtension = brownfieldAppProject.rootProject.gradle.extensions.findByType(Class.forName("expo.modules.plugin.ExpoGradleExtension")) ?: error("Expo Gradle extension not found. This should never happen in an Expo project.") @@ -442,42 +429,4 @@ open class ExpoPublishingHelper(val brownfieldAppProject: Project) { ) } } - - fun getPublishingInfo(expoGPProjection: ExpoGradleProjectProjection): BrownfieldPublishingInfo? { - return expoGPProjection.publication?.let { - BrownfieldPublishingInfo( - groupId = it.groupId, - artifactId = it.artifactId, - version = it.version, - ) - } ?: run { - val targetProject = - brownfieldAppProject.rootProject.allprojects.firstOrNull { - it.projectDir.absoluteFile.path == expoGPProjection.sourceDir - } - - if (targetProject == null) { - return null - } - - val targetProjectAndroidLibExt = - targetProject.extensions.getByType(LibraryExtension::class.java) - - val packagePieces = targetProjectAndroidLibExt.namespace!!.split(".") - val artifactId = packagePieces.last() - // below: remove the trailing artifactId component -> leaves only the groupId components - val groupId = packagePieces.dropLast(1).joinToString(".") - - ( - BrownfieldPublishingInfo( - groupId = groupId, - artifactId = artifactId, - version = ( - targetProjectAndroidLibExt.defaultConfig.versionName - ?: targetProject.version.toString() - ), - ) - ) - } - } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/utils/LocalMavenUtils.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/utils/LocalMavenUtils.kt new file mode 100644 index 00000000..fc2c124f --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/utils/LocalMavenUtils.kt @@ -0,0 +1,83 @@ +package com.callstack.react.brownfield.expo.utils + +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Project +import java.io.File + +object LocalMavenUtils { + fun getAarFile( + localMavenDir: File, + publication: BrownfieldPublishingInfo, + ): File { + return getFile(localMavenDir, publication, "aar") + } + + fun getPomFile( + localMavenDir: File, + publication: BrownfieldPublishingInfo, + ): File { + return getFile(localMavenDir, publication, "pom") + } + + private fun getFile( + localMavenDir: File, + publication: BrownfieldPublishingInfo, + extension: String, + ): File { + return localMavenDir + .resolve( + "${ + publication.groupId.replace( + '.', + '/', + ) + }/${publication.artifactId}/${publication.version}/" + + "${publication.artifactId}-${publication.version}.$extension", + ) + } + + fun getPublishingInfo( + expoGPProjection: ExpoGradleProjectProjection, + brownfieldAppProject: Project, + ): BrownfieldPublishingInfo? { + return expoGPProjection.publication?.let { + BrownfieldPublishingInfo( + groupId = it.groupId, + artifactId = it.artifactId, + version = it.version, + ) + } ?: run { + val targetProject = + brownfieldAppProject.rootProject.allprojects.firstOrNull { + it.projectDir.absoluteFile.path == expoGPProjection.sourceDir + } + + if (targetProject == null) { + return null + } + + val targetProjectAndroidLibExt = + targetProject.extensions.getByType(LibraryExtension::class.java) + + val packagePieces = targetProjectAndroidLibExt.namespace!!.split(".") + val artifactId = packagePieces.last() + // below: remove the trailing artifactId component -> leaves only the groupId components + val groupId = packagePieces.dropLast(1).joinToString(".") + + ( + BrownfieldPublishingInfo( + groupId = groupId, + artifactId = artifactId, + version = ( + targetProjectAndroidLibExt.defaultConfig.versionName + ?: targetProject.version.toString() + ), + ) + ) + } + } + + fun publishingNotFound(projectName: String): String { + return "Cannot configure publishing for Expo project $projectName - could not determine publishing info" + } +} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/CustomDependencyResolver.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/CustomDependencyResolver.kt index faf66b07..6f6162e3 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/CustomDependencyResolver.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/CustomDependencyResolver.kt @@ -19,7 +19,7 @@ class CustomDependencyResolver( private fun getCompileOnlyConfigName(configurationName: String): String { val configSuffix = ProjectConfigurations.CONFIG_SUFFIX if (configurationName.endsWith(configSuffix)) { - val configName = configurationName.substring(0, configurationName.length - configSuffix.length) + val configName = configurationName.dropLast(configSuffix.length) return "${configName}CompileOnly" } else { return "compileOnly" diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ManifestMerger.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ManifestMerger.kt deleted file mode 100644 index 2ce64127..00000000 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ManifestMerger.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.callstack.react.brownfield.plugin - -import com.android.build.gradle.internal.LoggerWrapper -import com.android.manifmerger.ManifestMerger2 -import com.android.manifmerger.ManifestProvider -import com.android.manifmerger.MergingReport -import com.android.utils.ILogger -import com.callstack.react.brownfield.shared.Logging -import org.apache.tools.ant.BuildException -import org.gradle.api.DefaultTask -import java.io.BufferedWriter -import java.io.File -import java.io.FileOutputStream -import java.io.OutputStreamWriter - -open class ManifestMerger : DefaultTask() { - private var mGradlePluginVersion: String? = null - private var mGradleVersion: String? = null - private var mMainManifestFile: File? = null - private var mSecondaryManifestFiles: List? = null - private var mOutputFile: File? = null - - fun setGradlePluginVersion(gradlePluginVersion: String) { - mGradlePluginVersion = gradlePluginVersion - } - - fun setGradleVersion(gradleVersion: String) { - mGradleVersion = gradleVersion - } - - fun setMainManifestFile(mainManifestFile: File) { - mMainManifestFile = mainManifestFile - } - - fun setSecondaryManifestFiles(sm: List) { - mSecondaryManifestFiles = sm - } - - fun setOutputFile(outputFile: File) { - mOutputFile = outputFile - } - - open fun doTaskAction() { - try { - doFullTaskAction() - } catch (e: IllegalStateException) { - Logging.info("Gradle Plugin Version: $mGradlePluginVersion") - Logging.info("Gradle Version: $mGradleVersion") - Logging.log(e.stackTraceToString()) - } - } - - private fun doFullTaskAction() { - val iLogger: ILogger = LoggerWrapper(logger) - val mergerInvoker = ManifestMerger2.newMerger(mMainManifestFile, iLogger, ManifestMerger2.MergeType.LIBRARY) - - val secondaryManifestFiles = mSecondaryManifestFiles - val manifestProviders = mutableListOf() - - val filteredSecondaryManifests = secondaryManifestFiles?.filter { it.exists() } - filteredSecondaryManifests?.forEach { file -> - manifestProviders.add( - object : ManifestProvider { - override fun getManifest(): File = file.absoluteFile - - override fun getName(): String = file.name - }, - ) - } - - mergerInvoker.addManifestProviders(manifestProviders) - val mergingReport: MergingReport = mergerInvoker.merge() - - if (mergingReport.result.isError) { - logger.error(mergingReport.reportString) - mergingReport.log(iLogger) - throw BuildException(mergingReport.reportString) - } - - BufferedWriter(OutputStreamWriter(FileOutputStream(mOutputFile!!), "UTF-8")).use { writer -> - writer.append(mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED)) - writer.flush() - } - } -} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ProjectConfigurations.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ProjectConfigurations.kt index 07f7d5df..510b9515 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ProjectConfigurations.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ProjectConfigurations.kt @@ -1,10 +1,13 @@ package com.callstack.react.brownfield.plugin +import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.android.build.gradle.LibraryExtension import com.callstack.react.brownfield.shared.Logging import com.callstack.react.brownfield.utils.capitalized import org.gradle.api.Project import org.gradle.api.artifacts.Configuration +import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.AttributeContainer class ProjectConfigurations(private val project: Project) { private val configurations: MutableCollection = mutableListOf() @@ -27,10 +30,9 @@ class ProjectConfigurations(private val project: Project) { * 3. creates configs for flavours * 4. creates configs for flavours with buildTypes */ - fun setup() { + fun configure() { // create main configuration createConfiguration(CONFIG_NAME) - val androidExtension = project.extensions.getByName("android") as LibraryExtension createBuildTypesConfiguration(androidExtension) createFlavorConfigurations(androidExtension) @@ -64,13 +66,39 @@ class ProjectConfigurations(private val project: Project) { * creates configuration based on `configName`. Also attaches a resolution listener. */ private fun createConfiguration(configName: String) { - Logging.log("creating configuration $configName") + Logging.log("creating configuration $configName ⌛️") val configuration = project.configurations.create(configName) + + configuration.extendsFrom(project.configurations.getByName("implementation")) configuration.isVisible = false configuration.isTransitive = false + + val androidComponents = project.extensions.getByType(LibraryAndroidComponentsExtension::class.java) + androidComponents.onVariants { variant -> + + val runtimeAttributes = variant.runtimeConfiguration.attributes + runtimeAttributes.keySet().forEach { key -> + copyAttribute( + key, + runtimeAttributes, + configuration.attributes, + ) + } + } + project.gradle.addListener(CustomDependencyResolver(project, configuration)) configurations.add(configuration) - Logging.log("created configuration $configName") + Logging.log("created configuration $configName ✅") + } + + private fun copyAttribute( + key: Attribute, + from: AttributeContainer, + into: AttributeContainer, + ) { + from.getAttribute(key)?.let { + into.attribute(key, it) + } } private fun getConfigName(prefix: String): String { diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RClassTransformer.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RClassTransformer.kt index c9f483ed..6f098c63 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RClassTransformer.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RClassTransformer.kt @@ -29,6 +29,7 @@ object RClassTransformer : BaseProject() { fun registerASMTransformation() { val components = project.extensions.getByType(AndroidComponentsExtension::class.java) val variantPackagesProperty = VariantPackagesProperty.getVariantPackagesProperty() + components.onVariants(components.selector().all()) { variant -> variant.instrumentation.transformClassesWith( RClassAsmTransformerFactory::class.java, @@ -37,9 +38,10 @@ object RClassTransformer : BaseProject() { params.namespace.set(variant.namespace) params.libraryNamespaces.set( variantPackagesProperty.getting(variant.name) - .map { list -> list.map { it.getPackageName() }.toList() }, + .map { list -> list.toList() }, ) } + variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES) } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNBrownfieldPlugin.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNBrownfieldPlugin.kt index ac899c44..d4f8a3c3 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNBrownfieldPlugin.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNBrownfieldPlugin.kt @@ -1,113 +1,172 @@ package com.callstack.react.brownfield.plugin +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.api.LibraryVariant import com.callstack.react.brownfield.artifacts.ArtifactsResolver import com.callstack.react.brownfield.expo.ExpoPublishingHelper +import com.callstack.react.brownfield.expo.utils.ExpoGradleProjectProjection +import com.callstack.react.brownfield.processors.AssetTaskProcessor +import com.callstack.react.brownfield.processors.ExplodeTaskProvider +import com.callstack.react.brownfield.processors.JNILibsProcessor +import com.callstack.react.brownfield.processors.ManifestTaskProcessor +import com.callstack.react.brownfield.processors.ProguardProcessor +import com.callstack.react.brownfield.processors.ResourceTaskProcessor import com.callstack.react.brownfield.processors.VariantPackagesProperty +import com.callstack.react.brownfield.processors.VariantTaskProvider import com.callstack.react.brownfield.shared.BaseProject import com.callstack.react.brownfield.shared.Constants.PROJECT_ID import com.callstack.react.brownfield.shared.Logging +import com.callstack.react.brownfield.shared.UnresolvedArtifactInfo +import com.callstack.react.brownfield.utils.AndroidArchiveLibrary import com.callstack.react.brownfield.utils.DirectoryManager import com.callstack.react.brownfield.utils.Extension +import com.callstack.react.brownfield.utils.Utils import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.ProjectConfigurationException -import org.gradle.api.internal.file.FileResolver -import org.gradle.api.internal.tasks.TaskDependencyFactory -import org.gradle.internal.model.CalculatedValueContainerFactory -import javax.inject.Inject - -class RNBrownfieldPlugin - @Inject - constructor( - private val calculatedValueContainerFactory: CalculatedValueContainerFactory, - private val taskDependencyFactory: TaskDependencyFactory, - private val fileResolver: FileResolver, - ) : Plugin { - private lateinit var project: Project - private lateinit var extension: Extension - private lateinit var projectConfigurations: ProjectConfigurations - private var maybeExpoProject: Project? = null - - private val isExpoProject: Boolean - get() = maybeExpoProject != null - - override fun apply(project: Project) { - verifyAndroidPluginApplied(project) - initializers(project) - - /** - * Make sure that expo project is evaluated before the android library. - * This ensures that the expo modules are available to link with the - * android library, when it is evaluated. - */ - if (this.isExpoProject) { - Logging.log("Expo project detected.") - project.evaluationDependsOn(EXPO_PROJECT_LOCATOR) - } - - projectConfigurations.setup() - registerRClassTransformer() - - project.afterEvaluate { - RNSourceSets.configure(project, extension) - afterEvaluate() - - if (this.isExpoProject) { - ExpoPublishingHelper( - brownfieldAppProject = project, - ).afterEvaluate() - } - } - } - private fun initializers(project: Project) { - this.project = project - Logging.project = project - DirectoryManager.project = project - RClassTransformer.project = project - this.extension = project.extensions.create(Extension.NAME, Extension::class.java) - projectConfigurations = ProjectConfigurations(project) - VariantPackagesProperty.setVariantPackagesProperty(project) +class RNBrownfieldPlugin : Plugin { + private lateinit var extension: Extension + private lateinit var project: Project + + private var maybeExpoProject: Project? = null + private val isExpoProject: Boolean + get() = maybeExpoProject != null + + override fun apply(project: Project) { + verifyAndroidPluginApplied(project) + + this.project = project + initializers() + + val projectConfigurations = ProjectConfigurations(project) + projectConfigurations.configure() + RNSourceSets.configure(project, extension) + RClassTransformer.registerASMTransformation() - this.maybeExpoProject = project.findProject(EXPO_PROJECT_LOCATOR) + if (Utils.isExampleLibrary(project.name)) { + return } /** - * Verifies and throws error if `com.android.library` plugin is not applied + * Must run before processDefaultDependencies: ArtifactsResolver reads :expo's api configuration, + * which is only populated after the expo project is evaluated. */ - private fun verifyAndroidPluginApplied(project: Project) { - if (!project.plugins.hasPlugin("com.android.library")) { - throw ProjectConfigurationException( - "$PROJECT_ID must be applied to an android library project", - Throwable("Apply $PROJECT_ID"), - ) - } + if (this.isExpoProject) { + project.evaluationDependsOn(EXPO_PROJECT_LOCATOR) + } + + var expoProjects = listOf() + if (this.isExpoProject) { + val expoPublishingHelper = ExpoPublishingHelper(brownfieldAppProject = project) + expoProjects = expoPublishingHelper.configure() } /** - * Transforms RClass + * curates a list of artifacts that we need to bundle with the Aar + */ + val artifactsResolver = ArtifactsResolver(project, isExpoProject) + val artifacts = artifactsResolver.processDefaultDependencies(expoProjects) + + val variantTaskProvider = VariantTaskProvider(project) + + /** + * Configure Tasks */ - private fun registerRClassTransformer() { - RClassTransformer.registerASMTransformation() + project.extensions.getByType(LibraryExtension::class.java).libraryVariants.all { variant -> + configureTasks(variant, artifacts, variantTaskProvider) } + } - private fun afterEvaluate() { - val baseProject = BaseProject() - baseProject.project = project - val artifactsResolver = - ArtifactsResolver( - projectConfigurations.getConfigurations(), - baseProject, - extension, - this.isExpoProject, - ) - artifactsResolver.calculatedValueContainerFactory = calculatedValueContainerFactory - artifactsResolver.taskDependencyFactory = taskDependencyFactory - artifactsResolver.fileResolver = fileResolver - artifactsResolver.processArtifacts() + companion object { + const val EXPO_PROJECT_LOCATOR = ":expo" + } + + private fun initializers() { + RClassTransformer.project = project + Logging.project = project + val baseProject = BaseProject() + baseProject.project = project + DirectoryManager.project = project + + this.extension = project.extensions.create(Extension.NAME, Extension::class.java) + VariantPackagesProperty.setVariantPackagesProperty(project) + this.maybeExpoProject = project.findProject(EXPO_PROJECT_LOCATOR) + } + + /** + * Verifies and throws error if `com.android.library` plugin is not applied + */ + private fun verifyAndroidPluginApplied(project: Project) { + if (!project.plugins.hasPlugin("com.android.library")) { + throw ProjectConfigurationException( + "$PROJECT_ID must be applied to an android library project", + Throwable("Apply $PROJECT_ID"), + ) } + } - companion object { - const val EXPO_PROJECT_LOCATOR = ":expo" + private fun getAarLibraries( + artifacts: List, + variantName: String, + ): List { + val aarLibraries = mutableListOf() + artifacts.forEach { art -> + val archiveLibrary = + AndroidArchiveLibrary( + this.project, + art, + variantName, + ) + aarLibraries.add(archiveLibrary) } + + return aarLibraries + } + + private fun configureTasks( + variant: LibraryVariant, + artifacts: List, + variantTaskProvider: VariantTaskProvider, + ) { + val variantName = variant.name + val capitalizedVariantName = variantName.replaceFirstChar(Char::titlecase) + + /** ======= EXPLODE AAR =========*/ + val explodeTask = ExplodeTaskProvider.getTask(variant, project, artifacts) + + /** ======= PreBuild =========*/ + variantTaskProvider.preBuildTaskByVariant(capitalizedVariantName, explodeTask) + + val aarLibraries = getAarLibraries(artifacts, variantName) + + /** + * Flat IDs to be put into the variant property, required for RClass Transformer + */ + val packageIDs = aarLibraries.map { it.getPackageName() } + VariantPackagesProperty.getVariantPackagesProperty().put(variantName, packageIDs) + + /** ======= MANIFEST MERGER =========*/ + ManifestTaskProcessor.process(variant, project, aarLibraries) + + /** ======= GENERATE RESOURCES =========*/ + ResourceTaskProcessor.process(variant, project, aarLibraries) + + /** ======= GENERATE ASSETS ========= */ + AssetTaskProcessor.process(variant, project, aarLibraries) + + /** ===== jniLibsProcessor ===== */ + val jniLibsProcessor = JNILibsProcessor(project) + jniLibsProcessor.processJniLibs(aarLibraries, variantName) + + /** ===== proguardProcessor ===== */ + val proguardProcessor = ProguardProcessor(project) + val proguardRules = aarLibraries.map { it.getProguardRules() } + proguardProcessor.processConsumerFiles(proguardRules, capitalizedVariantName) + proguardProcessor.processGeneratedFiles(proguardRules, capitalizedVariantName) + + /** ===== processDataBinding ===== */ + val bundleTask = variantTaskProvider.bundleTaskProvider(project, variantName) + variantTaskProvider.processDataBinding(bundleTask, aarLibraries, variantName) } +} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt index dd956b3f..6084bbfa 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt @@ -1,5 +1,6 @@ package com.callstack.react.brownfield.plugin +import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.android.build.gradle.LibraryExtension import com.callstack.react.brownfield.exceptions.NameSpaceNotFound import com.callstack.react.brownfield.utils.Extension @@ -35,41 +36,50 @@ object RNSourceSets { this.project = project this.extension = extension - androidExtension = RNSourceSets.project.extensions.getByName("android") as LibraryExtension - appProject = RNSourceSets.project.rootProject.project(RNSourceSets.extension.appProjectName) + androidExtension = this.project.extensions.getByType(LibraryExtension::class.java) + appProject = this.project.rootProject.project(RNSourceSets.extension.appProjectName) appBuildDir = appProject.layout.buildDirectory.get() - moduleBuildDir = RNSourceSets.project.layout.buildDirectory.get() + moduleBuildDir = this.project.layout.buildDirectory.get() configureSourceSets() configureTasks() } private fun configureSourceSets() { - project.extensions.getByType(LibraryExtension::class.java).libraryVariants.all { variant -> - val capitalizedVariantName = variant.name.capitalized() + // 1. Get the 'androidComponents' extension for the new Variant API + val componentsExtension = project.extensions.getByType(LibraryAndroidComponentsExtension::class.java) - androidExtension.sourceSets.getByName("main") { sourceSet -> - sourceSet.java.srcDirs("$moduleBuildDir/generated/autolinking/src/main/java") - } + // Move the non-variant-specific configuration out of the loop + androidExtension.sourceSets.named("main") { sourceSet -> + // This path is not variant-specific, so it's added once here. + sourceSet.java.srcDir("$moduleBuildDir/generated/autolinking/src/main/java") + } - androidExtension.sourceSets.getByName(variant.name) { sourceSet -> - for (bundlePathSegment in listOf( - // outputs for RN <= 0.81 - "createBundle${capitalizedVariantName}JsAndAssets", - // outputs for RN >= 0.82 - "react/${variant.name}", - )) { - sourceSet.assets.srcDirs("$appBuildDir/generated/assets/$bundlePathSegment") - sourceSet.res.srcDirs("$appBuildDir/generated/res/$bundlePathSegment") - } + // 2. Use the onVariants block to configure each variant + componentsExtension.onVariants { variant -> + // 3. Lazily configure the 'main' source set using .named() + androidExtension.sourceSets.named(variant.name) { sourceSet -> + // Paths are collected and added, similar to your improved version + val bundlePathSegments = + listOf( + // outputs for RN <= 0.81 + "createBundle${variant.name.capitalized()}JsAndAssets", + // outputs for RN >= 0.82 + "react/${variant.name}", + ) + + // Add the variant-specific generated asset and resource directories + sourceSet.assets.srcDirs(bundlePathSegments.map { "$appBuildDir/generated/assets/$it" }) + sourceSet.res.srcDirs(bundlePathSegments.map { "$appBuildDir/generated/res/$it" }) } } - androidExtension.sourceSets.getByName("release") { + // These remain the same, but using .named() is the modern, lazy approach + androidExtension.sourceSets.named("release") { it.jniLibs.srcDirs("libsRelease") } - androidExtension.sourceSets.getByName("debug") { + androidExtension.sourceSets.named("debug") { it.jniLibs.srcDirs("libsDebug") } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/AssetTaskProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/AssetTaskProcessor.kt new file mode 100644 index 00000000..65c90ddd --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/AssetTaskProcessor.kt @@ -0,0 +1,29 @@ +package com.callstack.react.brownfield.processors + +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.api.LibraryVariant +import com.callstack.react.brownfield.utils.AndroidArchiveLibrary +import org.gradle.api.Project + +object AssetTaskProcessor { + fun process( + variant: LibraryVariant, + project: Project, + aarLibraries: List, + ) { + val assetsTask = variant.mergeAssetsProvider.get() + + val androidExtension = project.extensions.getByName("android") as LibraryExtension + assetsTask.doFirst { + val filteredSourceSets = + androidExtension.sourceSets.filter { it.name == variant.name } + + filteredSourceSets.forEach { sourceSet -> + val filteredAarLibs = aarLibraries.filter { it.getAssetsDir().exists() } + if (!filteredAarLibs.isEmpty()) { + sourceSet.assets.srcDirs(filteredAarLibs.map { it.getAssetsDir() }) + } + } + } + } +} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ExplodeTaskProvider.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ExplodeTaskProvider.kt new file mode 100644 index 00000000..c4acf9c5 --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ExplodeTaskProvider.kt @@ -0,0 +1,63 @@ +package com.callstack.react.brownfield.processors + +import com.android.build.gradle.api.LibraryVariant +import com.callstack.react.brownfield.shared.BundleTaskProvider +import com.callstack.react.brownfield.shared.ExplodeAarTask +import com.callstack.react.brownfield.shared.UnresolvedArtifactInfo +import com.callstack.react.brownfield.utils.capitalized +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.TaskProvider +import org.gradle.api.tasks.bundling.Zip +import java.io.File + +object ExplodeTaskProvider { + fun getTask( + variant: LibraryVariant, + project: Project, + artifacts: List, + ): TaskProvider { + val variantTaskProvider = VariantTaskProvider(project) + val bundleProvider = BundleTaskProvider(variantTaskProvider) + val capitalizedVariantName = variant.name.capitalized() + + return project.tasks.register( + "explode${capitalizedVariantName}Aar", + ExplodeAarTask::class.java, + ) { task -> + task.variantName.set(variant.name) + task.minifyEnabled.set(variant.buildType.isMinifyEnabled) + + val finalArtifacts = mutableListOf() + artifacts.forEach { art -> + var artifactPath = art.file + if (art.isExpoPublishDependency != true) { + val defaultTaskName = "bundle${capitalizedVariantName}Aar" + val dependencyProject = project.project(":${art.moduleName}") + val bundleTaskProvider = bundleProvider.getBundleTask(dependencyProject, variant) + val taskName = bundleTaskProvider?.name ?: defaultTaskName + + dependencyProject.tasks.findByName(taskName)?.let { task.dependsOn(it) } + artifactPath = createArtifactFile(bundleTaskProvider?.get() as Task).absolutePath + } + + finalArtifacts.add( + UnresolvedArtifactInfo( + art.moduleGroup, + art.moduleName, + art.moduleVersion, + artifactPath, + isExpoPublishDependency = art.isExpoPublishDependency, + ), + ) + } + + task.inputArtifacts.set(finalArtifacts) + } + } + + private fun createArtifactFile(bundle: Task): File { + val packageLibraryProvider = bundle as Zip + return File(packageLibraryProvider.destinationDirectory.get().asFile, packageLibraryProvider.archiveFileName.get()) + } +} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/JNILibsProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/JNILibsProcessor.kt index a38e1a43..c0f08b3d 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/JNILibsProcessor.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/JNILibsProcessor.kt @@ -1,35 +1,22 @@ -@file:Suppress("DEPRECATION") - -/** - * Suppressing because of LibraryVariant. - * We can't use the new `com.android.build.gradle.api.LibraryVariant` - * as of now. - * - * We may want to re-visit this in future. - */ - package com.callstack.react.brownfield.processors import com.android.build.gradle.LibraryExtension -import com.android.build.gradle.api.LibraryVariant import com.callstack.react.brownfield.exceptions.TaskNotFound -import com.callstack.react.brownfield.shared.BaseProject import com.callstack.react.brownfield.shared.Logging import com.callstack.react.brownfield.utils.AndroidArchiveLibrary import com.callstack.react.brownfield.utils.Extension import com.callstack.react.brownfield.utils.capitalized -import org.gradle.api.Task +import org.gradle.api.Project import org.gradle.api.tasks.Copy import org.gradle.api.tasks.TaskProvider import java.io.File -class JNILibsProcessor : BaseProject() { +class JNILibsProcessor(val project: Project) { fun processJniLibs( aarLibraries: Collection, - explodeTasks: MutableList, - variant: LibraryVariant, + variantName: String, ) { - val capitalizedVariantName = variant.name.capitalized() + val capitalizedVariantName = variantName.capitalized() val taskName = "merge${capitalizedVariantName}JniLibFolders" val mergeJniLibsTask = project.tasks.named(taskName) @@ -38,10 +25,9 @@ class JNILibsProcessor : BaseProject() { } val androidExtension = project.extensions.getByName("android") as LibraryExtension - val copyTask = copySoLibsTask(variant) + val copyTask = copySoLibsTask(variantName) mergeJniLibsTask.configure { - it.dependsOn(explodeTasks) it.dependsOn(copyTask) it.doFirst { @@ -54,11 +40,11 @@ class JNILibsProcessor : BaseProject() { val jniDir = archiveLibrary.getJniDir() processNestedLibs(jniDir.listFiles(), existingJNILibs) if (projectExt.experimentalUseStrippedSoFiles) { - copyStrippedSoLibs(variant, existingJNILibs) + copyStrippedSoLibs(variantName, existingJNILibs) } else { if (jniDir.exists()) { val filteredSourceSets = - androidExtension.sourceSets.filter { sourceSet -> sourceSet.name == variant.name } + androidExtension.sourceSets.filter { sourceSet -> sourceSet.name == variantName } filteredSourceSets.forEach { sourceSet -> sourceSet.jniLibs.srcDir( jniDir, @@ -71,12 +57,11 @@ class JNILibsProcessor : BaseProject() { } } - private fun getStrippedLibsPath(variant: LibraryVariant): Pair { + private fun getStrippedLibsPath(variantName: String): Pair { val projectExt = project.extensions.getByType(Extension::class.java) val appProject = project.rootProject.project(projectExt.appProjectName) val appBuildDir = appProject.layout.buildDirectory.get() - val variantName = variant.name val capitalizedVariant = variantName.capitalized() val fromDir = @@ -89,15 +74,15 @@ class JNILibsProcessor : BaseProject() { return Pair(fromDir, intoDir) } - private fun copySoLibsTask(variant: LibraryVariant): TaskProvider { - val capitalizedVariant = variant.name.capitalized() + private fun copySoLibsTask(variantName: String): TaskProvider { + val capitalizedVariant = variantName.capitalized() val projectExt = project.extensions.getByType(Extension::class.java) val appProject = project.rootProject.project(projectExt.appProjectName) val stripTask = ":${appProject.name}:strip${capitalizedVariant}DebugSymbols" val codegenTask = ":${project.name}:generateCodegenSchemaFromJavaScript" - val (fromDir, intoDir) = getStrippedLibsPath(variant) + val (fromDir, intoDir) = getStrippedLibsPath(variantName) return project.tasks.register("copy${capitalizedVariant}LibSources", Copy::class.java) { it.dependsOn(stripTask, codegenTask) @@ -110,10 +95,10 @@ class JNILibsProcessor : BaseProject() { } private fun copyStrippedSoLibs( - variant: LibraryVariant, + variantName: String, existingJNILibs: MutableMap>, ) { - val (fromDir, intoDir) = getStrippedLibsPath(variant) + val (fromDir, intoDir) = getStrippedLibsPath(variantName) existingJNILibs.forEach { (arch, libNames) -> copyLibsForArchitecture(fromDir, intoDir, arch, libNames) diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ManifestTaskProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ManifestTaskProcessor.kt new file mode 100644 index 00000000..a6ecea14 --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ManifestTaskProcessor.kt @@ -0,0 +1,66 @@ +package com.callstack.react.brownfield.processors + +import com.android.build.gradle.api.LibraryVariant +import com.android.build.gradle.internal.LoggerWrapper +import com.android.build.gradle.internal.coverage.JacocoReportTask.JacocoReportWorkerAction.Companion.logger +import com.android.manifmerger.ManifestMerger2 +import com.android.manifmerger.ManifestProvider +import com.android.manifmerger.MergingReport +import com.android.utils.ILogger +import com.callstack.react.brownfield.utils.AndroidArchiveLibrary +import com.callstack.react.brownfield.utils.capitalized +import org.gradle.api.Project +import java.io.BufferedWriter +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStreamWriter + +object ManifestTaskProcessor { + fun process( + variant: LibraryVariant, + project: Project, + aarLibraries: List, + ) { + val processManifestTask = variant.outputs.first().processManifestProvider.get() + val variantName = variant.name + processManifestTask.doLast { + val buildDir = project.layout.buildDirectory.get() + val manifestOutput = + project.file( + "$buildDir/intermediates/merged_manifest/$variantName/process${variantName.capitalized()}Manifest/AndroidManifest.xml", + ) + + val inputManifests = aarLibraries.map { it.getManifestFile() } + mergeManifests(manifestOutput, inputManifests, manifestOutput) + } + } + + private fun mergeManifests( + mainManifestFile: File, + secondaryManifestFiles: List, + outputFile: File, + ) { + val iLogger: ILogger = LoggerWrapper(logger) + val mergerInvoker = ManifestMerger2.newMerger(mainManifestFile, iLogger, ManifestMerger2.MergeType.LIBRARY) + val manifestProviders = mutableListOf() + + val filteredSecondaryManifests = secondaryManifestFiles.filter { it.exists() } + filteredSecondaryManifests.forEach { file -> + manifestProviders.add( + object : ManifestProvider { + override fun getManifest(): File = file.absoluteFile + + override fun getName(): String = file.name + }, + ) + } + + mergerInvoker.addManifestProviders(manifestProviders) + val mergingReport: MergingReport = mergerInvoker.merge() + + BufferedWriter(OutputStreamWriter(FileOutputStream(outputFile), "UTF-8")).use { writer -> + writer.append(mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED)) + writer.flush() + } + } +} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/MergeProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/MergeProcessor.kt index 253c8f5a..394b3e8e 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/MergeProcessor.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/MergeProcessor.kt @@ -52,29 +52,4 @@ object MergeProcessor { private fun getFilteredAarLibs(androidLibraries: Collection): Collection { return androidLibraries.filter { it.getExplodedAarRootDir().exists() } } - - fun mergeLibsIntoLibs( - project: Project, - androidLibraries: Collection, - jarFiles: Collection, - folderOut: File, - ) { - val filteredLibs = getFilteredAarLibs(androidLibraries) - filteredLibs.forEach { aarLib -> - if (!aarLib.getLocalJars().isEmpty()) { - project.copy { - it.from(aarLib.getLocalJars()) - it.into(folderOut) - } - } - } - - val filteredJarFiles = jarFiles.filter { it.exists() } - filteredJarFiles.forEach { jarFile -> - project.copy { - it.from(jarFile) - it.into(folderOut) - } - } - } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ProguardProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ProguardProcessor.kt index 7499bb33..c067679c 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ProguardProcessor.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ProguardProcessor.kt @@ -1,33 +1,17 @@ -@file:Suppress("DEPRECATION") - -/** - * Suppressing because of LibraryVariant. - * We can't use the new `com.android.build.gradle.api.LibraryVariant` - * as of now. - * - * We may want to re-visit this in future. - */ - package com.callstack.react.brownfield.processors -import com.android.build.gradle.api.LibraryVariant import com.callstack.react.brownfield.exceptions.TaskNotFound -import com.callstack.react.brownfield.shared.BaseProject import com.callstack.react.brownfield.shared.Logging -import com.callstack.react.brownfield.utils.AndroidArchiveLibrary import com.callstack.react.brownfield.utils.Utils -import com.callstack.react.brownfield.utils.capitalized -import org.gradle.api.Task +import org.gradle.api.Project import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.TaskProvider import java.io.File -class ProguardProcessor(variant: LibraryVariant) : BaseProject() { - private val capitalizedVariantName = variant.name.capitalized() - +class ProguardProcessor(val project: Project) { fun processConsumerFiles( - aarLibs: Collection, - explodeTasks: MutableList, + proguardRules: List, + capitalizedVariantName: String, ) { val mergeTaskName = "merge${capitalizedVariantName}ConsumerProguardFiles" val mergeFileTask = project.tasks.named(mergeTaskName) @@ -36,31 +20,23 @@ class ProguardProcessor(variant: LibraryVariant) : BaseProject() { throw TaskNotFound("Task $mergeTaskName not found") } - mergeFileTask.configure { task -> - task.dependsOn(explodeTasks) - task.doLast { - val files = aarLibs.map { aarLib -> aarLib.getProguardRules() } - val outputFile = it.outputs.files.singleFile - doLast(files, outputFile) - } + mergeFileTask.get().doLast { + val outputFile = it.outputs.files.singleFile + doLast(proguardRules, outputFile) } } fun processGeneratedFiles( - aarLibs: Collection, - explodeTasks: MutableList, + proguardRules: List, + capitalizedVariantName: String, ) { val mergeGenerateProguardTask: TaskProvider<*>? val mergeName = "merge${capitalizedVariantName}GeneratedProguardFiles" mergeGenerateProguardTask = project.tasks.named(mergeName) - mergeGenerateProguardTask?.configure { task -> - task.dependsOn(explodeTasks) - task.doLast { - val files = aarLibs.map { it.getProguardRules() } - val outputFile = it.outputs.files.singleFile - doLast(files, outputFile) - } + mergeGenerateProguardTask.get().doLast { + val outputFile = it.outputs.files.singleFile + doLast(proguardRules, outputFile) } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ResourceTaskProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ResourceTaskProcessor.kt new file mode 100644 index 00000000..f58416bb --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ResourceTaskProcessor.kt @@ -0,0 +1,26 @@ +package com.callstack.react.brownfield.processors + +import com.android.build.gradle.api.LibraryVariant +import com.callstack.react.brownfield.exceptions.TaskNotFound +import com.callstack.react.brownfield.utils.AndroidArchiveLibrary +import com.callstack.react.brownfield.utils.capitalized +import org.gradle.api.Project + +object ResourceTaskProcessor { + fun process( + variant: LibraryVariant, + project: Project, + aarLibraries: List, + ) { + val taskPath = "generate${variant.name.capitalized()}Resources" + val resourceGenTask = project.tasks.named(taskPath) + + if (!resourceGenTask.isPresent) { + throw TaskNotFound("Task $taskPath not found") + } + + variant.registerGeneratedResFolders( + project.files(aarLibraries.map { it.getResDir() }), + ) + } +} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantHelper.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantHelper.kt index 36dc8105..7dd3fcb1 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantHelper.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantHelper.kt @@ -1,83 +1,28 @@ -@file:Suppress("DEPRECATION") - -/** - * Suppressing because of LibraryVariant. - * We can't use the new `com.android.build.gradle.api.LibraryVariant` - * as of now. - * - * We may want to re-visit this in future. - */ - package com.callstack.react.brownfield.processors -import com.android.build.gradle.LibraryExtension -import com.android.build.gradle.api.LibraryVariant -import com.android.build.gradle.tasks.ManifestProcessorTask -import com.callstack.react.brownfield.exceptions.TaskNotFound import com.callstack.react.brownfield.shared.BaseProject import com.callstack.react.brownfield.utils.AndroidArchiveLibrary import com.callstack.react.brownfield.utils.DirectoryManager import com.callstack.react.brownfield.utils.capitalized -import groovy.lang.MissingPropertyException -import org.gradle.api.Task -import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.tasks.compile.JavaCompile import java.io.File import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths -class VariantHelper(private val variant: LibraryVariant) : BaseProject() { - private val capitalizedVariantName = variant.name.capitalized() - - fun getVariant(): LibraryVariant { - return variant - } - - fun getJavaCompileTask(): JavaCompile { - return variant.javaCompileProvider.get() - } - - fun getTaskDependencies(artifact: ResolvedArtifact): Set { - return try { - val publishArtifact = - artifact::class.members.find { it.name == "publishArtifact" }?.call(artifact) - val buildDependencies = - publishArtifact?.javaClass?.getMethod("getBuildDependencies") - ?.invoke(publishArtifact) - @Suppress("UNCHECKED_CAST") - buildDependencies as? Set ?: emptySet() - } catch (ignore: MissingPropertyException) { - emptySet() - } - } - - fun getSyncLibJarsTaskPath(): String { - return "sync${variant.name.capitalized()}LibJars" - } - - private fun getClassPathDirFiles(): ConfigurableFileCollection { +class VariantHelper : BaseProject() { + private fun getClassPathDirFiles(variantName: String): ConfigurableFileCollection { return project.files( - "$buildDir/intermediates/javac/${variant.name}/compile${ - variant.name.capitalized() - }JavaWithJavac/classes", + "$buildDir/intermediates/javac/$variantName/compile${variantName.capitalized()}JavaWithJavac/classes", ) } - fun getLocalJarFiles(aarLibraries: Collection): Collection { - return aarLibraries.flatMap { - it.getLocalJars() - } - } - - fun getClassesJarFiles(aarLibraries: Collection): List { - return aarLibraries.map { it.getClassesJarFile() } - } - - fun classesMergeTaskDoFirst(outputDir: File) { + fun classesMergeTaskDoFirst( + outputDir: File, + variantName: String, + ) { val pathsToDelete = mutableListOf() - val javacDir = getClassPathDirFiles().first() + val javacDir = getClassPathDirFiles(variantName).first() project.fileTree(outputDir).forEach { path -> pathsToDelete.add( Paths.get(outputDir.absolutePath).relativize(Paths.get(path.absolutePath)), @@ -93,12 +38,14 @@ class VariantHelper(private val variant: LibraryVariant) : BaseProject() { outputDir: File, aarLibraries: Collection, jarFiles: MutableList, + variantName: String, + isMinifyEnabled: Boolean, ) { MergeProcessor.mergeClassesJarIntoClasses(project, aarLibraries, outputDir) - if (variant.buildType.isMinifyEnabled) { + if (isMinifyEnabled) { MergeProcessor.mergeLibsIntoClasses(project, aarLibraries, jarFiles, outputDir) } - val javacDir = getClassPathDirFiles().first() + val javacDir = getClassPathDirFiles(variantName).first() project.copy { copyTask -> copyTask.from(outputDir) copyTask.into(javacDir) @@ -107,63 +54,8 @@ class VariantHelper(private val variant: LibraryVariant) : BaseProject() { project.copy { copyTask -> copyTask.from("${outputDir.absolutePath}/META-INF") - copyTask.into(DirectoryManager.getKotlinMetaDirectory(variant)) + copyTask.into(DirectoryManager.getKotlinMetaDirectory(variantName)) copyTask.include("*.kotlin_module") } } - - fun getLibsDirFile(): File { - return project.file( - "$buildDir/intermediates/aar_libs_directory/${variant.name}/sync${ - variant.name.replaceFirstChar( - Char::titlecase, - ) - }LibJars/libs", - ) - } - - fun getProcessManifest(): ManifestProcessorTask { - return variant.outputs.first().processManifestProvider.get() - } - - fun processResources( - aarLibraries: Collection, - explodeTasks: MutableList, - ) { - val taskPath = "generate${capitalizedVariantName}Resources" - val resourceGenTask = project.tasks.named(taskPath) - - if (!resourceGenTask.isPresent) { - throw TaskNotFound("Task $taskPath not found") - } - - resourceGenTask.configure { - it.dependsOn(explodeTasks) - } - - aarLibraries.forEach { - variant.registerGeneratedResFolders( - project.files(it.getResDir()), - ) - } - } - - fun processAssets( - aarLibraries: Collection, - explodeTasks: MutableList, - ) { - val assetsTask = variant.mergeAssetsProvider.get() - - assetsTask.dependsOn(explodeTasks) - val androidExtension = project.extensions.getByName("android") as LibraryExtension - assetsTask.doFirst { - val filteredSourceSets = androidExtension.sourceSets.filter { it.name == variant.name } - filteredSourceSets.forEach { sourceSet -> - val filteredAarLibs = aarLibraries.filter { it.getAssetsDir().exists() } - filteredAarLibs.forEach { - sourceSet.assets.srcDir(it.getAssetsDir()) - } - } - } - } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantPackagesProperty.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantPackagesProperty.kt index 5030ed00..bd18d24b 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantPackagesProperty.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantPackagesProperty.kt @@ -1,13 +1,12 @@ package com.callstack.react.brownfield.processors -import com.callstack.react.brownfield.utils.AndroidArchiveLibrary import org.gradle.api.Project import org.gradle.api.provider.MapProperty object VariantPackagesProperty { - private lateinit var properties: MapProperty> + private lateinit var properties: MapProperty> - fun getVariantPackagesProperty(): MapProperty> { + fun getVariantPackagesProperty(): MapProperty> { return properties } @@ -16,7 +15,7 @@ object VariantPackagesProperty { properties = project.objects.mapProperty( String::class.java, - List::class.java as Class>, + List::class.java as Class>, ) } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantProcessor.kt deleted file mode 100644 index 4dc27a54..00000000 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantProcessor.kt +++ /dev/null @@ -1,176 +0,0 @@ -@file:Suppress("DEPRECATION") - -/** - * Suppressing because of LibraryVariant. - * We can't use the new `com.android.build.gradle.api.LibraryVariant` - * as of now. - * - * We may want to re-visit this in the future. - */ - -package com.callstack.react.brownfield.processors - -import com.android.build.gradle.api.LibraryVariant -import com.android.build.gradle.internal.tasks.factory.dependsOn -import com.callstack.react.brownfield.artifacts.ArtifactsResolver.Companion.ARTIFACT_TYPE_AAR -import com.callstack.react.brownfield.artifacts.ArtifactsResolver.Companion.ARTIFACT_TYPE_JAR -import com.callstack.react.brownfield.exceptions.TaskNotFound -import com.callstack.react.brownfield.shared.BaseProject -import com.callstack.react.brownfield.utils.AndroidArchiveLibrary -import com.callstack.react.brownfield.utils.Extension -import com.callstack.react.brownfield.utils.capitalized -import org.gradle.api.Task -import org.gradle.api.artifacts.ResolvedArtifact -import org.gradle.api.provider.ListProperty -import org.gradle.api.tasks.Copy -import org.gradle.api.tasks.PathSensitivity -import org.gradle.api.tasks.TaskProvider -import java.io.File - -class VariantProcessor(private val variant: LibraryVariant) : BaseProject() { - private val capitalizedVariantName = variant.name.capitalized() - private val variantHelper = VariantHelper(variant) - private val variantTaskProvider = VariantTaskProvider(variantHelper) - private val jniLibsProcessor = JNILibsProcessor() - private val proguardProcessor = ProguardProcessor(variant) - private val explodeTasks = mutableListOf() - private val aarLibraries = mutableListOf() - private lateinit var aarLibrariesProperty: ListProperty - private val jarFiles = mutableListOf() - private var mergeClassTask: TaskProvider? = null - - private fun setup() { - variantHelper.project = project - variantTaskProvider.project = project - jniLibsProcessor.project = project - proguardProcessor.project = project - aarLibrariesProperty = project.objects.listProperty(AndroidArchiveLibrary::class.java) - VariantPackagesProperty.getVariantPackagesProperty().put(variant.name, aarLibrariesProperty) - } - - fun processVariant(artifacts: Collection) { - setup() - val preBuildTaskPath = "pre${capitalizedVariantName}Build" - val prepareTask = project.tasks.named(preBuildTaskPath) - - if (!prepareTask.isPresent) { - throw TaskNotFound("Can not find $preBuildTaskPath task") - } - - if (capitalizedVariantName.contains("Release")) { - val projectExt = project.extensions.getByType(Extension::class.java) - val appProject = project.rootProject.project(projectExt.appProjectName) - prepareTask.dependsOn("${appProject.path}:createBundle${capitalizedVariantName}JsAndAssets") - } - - val bundleTask = variantTaskProvider.bundleTaskProvider(project, variant.name) - explodeArtifactFiles(artifacts, prepareTask, bundleTask) - mergeClassesAndJars(bundleTask) - - if (aarLibraries.isEmpty()) return - - variantTaskProvider.processManifestTask(aarLibraries, explodeTasks) - variantHelper.processResources(aarLibraries, explodeTasks) - variantHelper.processAssets(aarLibraries, explodeTasks) - jniLibsProcessor.processJniLibs(aarLibraries, explodeTasks, variant) - proguardProcessor.processConsumerFiles(aarLibraries, explodeTasks) - proguardProcessor.processGeneratedFiles(aarLibraries, explodeTasks) - variantTaskProvider.processDataBinding(bundleTask, aarLibraries) - variantTaskProvider.processDeepLinkTasks(explodeTasks) - } - - private fun mergeClassesAndJars(bundleTask: TaskProvider) { - val syncLibTask = project.tasks.named(variantHelper.getSyncLibJarsTaskPath()) - val extractAnnotationsTask = - project.tasks.named("extract${capitalizedVariantName}Annotations") - - mergeClassTask = variantTaskProvider.classesMergeTask(aarLibraries, jarFiles, explodeTasks) - syncLibTask.configure { - it.dependsOn(mergeClassTask) - it.inputs.files(aarLibraries.map { aarLib -> aarLib.getLibsDir() }).withPathSensitivity( - PathSensitivity.RELATIVE, - ) - it.inputs.files(jarFiles).withPathSensitivity(PathSensitivity.RELATIVE) - } - - project.tasks.named("transform${capitalizedVariantName}ClassesWithAsm").configure { - it.dependsOn(mergeClassTask) - } - extractAnnotationsTask.configure { - it.mustRunAfter(mergeClassTask) - } - - if (!variant.buildType.isMinifyEnabled) { - val mergeJars = - variantTaskProvider.jarMergeTask(syncLibTask, aarLibraries, jarFiles, explodeTasks) - project.tasks.named("bundle${capitalizedVariantName}LocalLintAar").configure { - it.dependsOn(mergeJars) - } - bundleTask.configure { - it.dependsOn(mergeJars) - } - } - } - - private fun explodeArtifactFiles( - artifacts: Collection, - prepareTask: TaskProvider, - bundleTask: TaskProvider, - ) { - for (artifact in artifacts) { - when (artifact.type) { - ARTIFACT_TYPE_JAR -> jarFiles.add(artifact.file) - ARTIFACT_TYPE_AAR -> processAar(artifact, prepareTask, bundleTask) - } - } - } - - private fun processAar( - artifact: ResolvedArtifact, - prepareTask: TaskProvider, - bundleTask: TaskProvider, - ) { - val archiveLibrary = - AndroidArchiveLibrary( - project, - artifact, - variant.name, - ) - aarLibraries.add(archiveLibrary) - aarLibrariesProperty.add(archiveLibrary) - - val dependencies = variantHelper.getTaskDependencies(artifact) - val zipFolder = archiveLibrary.getExplodedAarRootDir() - zipFolder.mkdirs() - - val explodeTask = getExplodeTask(zipFolder, artifact) - explodeTask.dependsOn(if (dependencies.isEmpty()) prepareTask else dependencies.first()) - - val javacTask = variantHelper.getJavaCompileTask() - javacTask.dependsOn(explodeTask) - - bundleTask.configure { - it.dependsOn(explodeTask) - } - explodeTasks.add(explodeTask) - } - - private fun getExplodeTask( - zipFolder: File, - artifact: ResolvedArtifact, - ): Copy { - val group = artifact.moduleVersion.id.group.capitalized() - val name = artifact.name.capitalized() - val taskName = "explode$group$name$capitalizedVariantName" - val explodeTask = - project.tasks.create(taskName, Copy::class.java) { - it.from(project.zipTree(artifact.file.absolutePath)) - it.into(zipFolder) - - it.doFirst { - zipFolder.deleteRecursively() - } - } - return explodeTask - } -} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantTaskProvider.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantTaskProvider.kt index 7166ff95..d2e6985f 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantTaskProvider.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantTaskProvider.kt @@ -1,80 +1,19 @@ package com.callstack.react.brownfield.processors +import com.android.build.gradle.internal.tasks.factory.dependsOn import com.callstack.react.brownfield.exceptions.TaskNotFound -import com.callstack.react.brownfield.plugin.ManifestMerger -import com.callstack.react.brownfield.shared.BaseProject +import com.callstack.react.brownfield.shared.ExplodeAarTask import com.callstack.react.brownfield.utils.AndroidArchiveLibrary import com.callstack.react.brownfield.utils.DirectoryManager -import com.callstack.react.brownfield.utils.Utils +import com.callstack.react.brownfield.utils.Extension import com.callstack.react.brownfield.utils.capitalized import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.UnknownTaskException -import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskProvider import java.io.File -class VariantTaskProvider(private val variantHelper: VariantHelper) : BaseProject() { - private val variant = variantHelper.getVariant() - private val capitalizedVariantName = variant.name.capitalized() - - fun classesMergeTask( - aarLibraries: Collection, - jarFiles: MutableList, - explodeTasks: MutableList, - ): TaskProvider { - val mergeClassesTaskName = "mergeClasses$capitalizedVariantName" - val kotlinCompileTaskName = "compile${capitalizedVariantName}Kotlin" - - return project.tasks.register(mergeClassesTaskName) { - it.outputs.upToDateWhen { false } - - it.dependsOn(explodeTasks) - it.dependsOn(variantHelper.getJavaCompileTask()) - - it.dependsOn(project.tasks.named(kotlinCompileTaskName)) - - it.inputs.files(variantHelper.getClassesJarFiles(aarLibraries)) - .withPathSensitivity(PathSensitivity.RELATIVE) - - if (variant.buildType.isMinifyEnabled) { - it.inputs.files(variantHelper.getLocalJarFiles(aarLibraries)) - .withPathSensitivity(PathSensitivity.RELATIVE) - it.inputs.files(jarFiles).withPathSensitivity(PathSensitivity.RELATIVE) - } - - val outputDir = DirectoryManager.getMergeClassDirectory(variant) - it.outputs.dir(outputDir) - - it.doFirst { variantHelper.classesMergeTaskDoFirst(outputDir) } - it.doLast { variantHelper.classesMergeTaskDoLast(outputDir, aarLibraries, jarFiles) } - } - } - - fun jarMergeTask( - syncLibTask: TaskProvider, - aarLibraries: Collection, - jarFiles: MutableList, - explodeTasks: MutableList, - ): TaskProvider { - return project.tasks.register("mergeJars$capitalizedVariantName") { - it.dependsOn(explodeTasks) - it.dependsOn(variantHelper.getJavaCompileTask()) - it.mustRunAfter(syncLibTask) - - it.inputs.files(aarLibraries.map { aarLib -> aarLib.getLibsDir() }).withPathSensitivity( - PathSensitivity.RELATIVE, - ) - it.inputs.files(jarFiles).withPathSensitivity(PathSensitivity.RELATIVE) - val outputDir = variantHelper.getLibsDirFile() - it.outputs.dir(outputDir) - - it.doFirst { - MergeProcessor.mergeLibsIntoLibs(project, aarLibraries, jarFiles, outputDir) - } - } - } - +class VariantTaskProvider(val project: Project) { fun bundleTaskProvider( project: Project, variantName: String, @@ -82,53 +21,23 @@ class VariantTaskProvider(private val variantHelper: VariantHelper) : BaseProjec var bundleTaskPath = "bundle${variantName.capitalized()}" return try { project.tasks.named(bundleTaskPath) - } catch (ignored: UnknownTaskException) { + } catch (_: UnknownTaskException) { bundleTaskPath += "Aar" project.tasks.named(bundleTaskPath) } } - fun processManifestTask( - aarLibraries: Collection, - explodeTasks: MutableList, - ) { - val processManifestTask = variantHelper.getProcessManifest() - val manifestOutput = - project.file( - "$buildDir/intermediates/merged_manifest/${variant.name}/process${capitalizedVariantName}Manifest/AndroidManifest.xml", - ) - - val inputManifests = aarLibraries.map { it.getManifestFile() } - - val manifestsMergeTask = - project.tasks.register( - "merge${capitalizedVariantName}Manifest", - ManifestMerger::class.java, - ) { - it.setGradleVersion(project.gradle.gradleVersion) - it.setGradlePluginVersion(Utils.getAGPVersion()) - it.setMainManifestFile(manifestOutput) - it.setSecondaryManifestFiles(inputManifests) - it.setOutputFile(manifestOutput) - } - - processManifestTask.dependsOn(explodeTasks) - processManifestTask.inputs.files(inputManifests) - processManifestTask.doLast { - manifestsMergeTask.get().doTaskAction() - } - } - fun processDataBinding( bundleTask: TaskProvider, aarLibraries: Collection, + variantName: String, ) { bundleTask.configure { task -> task.doLast { aarLibraries.forEach { val dataBindingFolder = it.getDataBindingFolder() if (dataBindingFolder.exists()) { - val filePath = getReBundleFilePath(dataBindingFolder.name) + val filePath = getReBundleFilePath(dataBindingFolder.name, variantName) File(filePath).mkdirs() project.copy { copyTask -> copyTask.from(dataBindingFolder) @@ -138,7 +47,7 @@ class VariantTaskProvider(private val variantHelper: VariantHelper) : BaseProjec val dataBindingLogFolder = it.getDataBindingLogFolder() if (dataBindingLogFolder.exists()) { - val filePath = getReBundleFilePath(dataBindingLogFolder.name) + val filePath = getReBundleFilePath(dataBindingLogFolder.name, variantName) File(filePath).mkdirs() project.copy { copyTask -> copyTask.from(dataBindingLogFolder) @@ -150,18 +59,29 @@ class VariantTaskProvider(private val variantHelper: VariantHelper) : BaseProjec } } - fun processDeepLinkTasks(explodeTasks: MutableList) { - val taskName = "extractDeepLinksForAar$capitalizedVariantName" - val extractDeepLinks = project.tasks.named(taskName) + private fun getReBundleFilePath( + folderName: String, + variantName: String, + ) = "${DirectoryManager.getReBundleDirectory( + variantName, + ).path}/$folderName" + + fun preBuildTaskByVariant( + capitalizedVariantName: String, + explodeAarTask: TaskProvider, + ) { + val preBuildTaskPath = "pre${capitalizedVariantName}Build" + val preBuildTask = project.tasks.named(preBuildTaskPath) - if (!extractDeepLinks.isPresent) { - throw TaskNotFound("Task $taskName not found") + if (!preBuildTask.isPresent) { + throw TaskNotFound("Can not find $preBuildTaskPath task") } - extractDeepLinks.configure { - it.dependsOn(explodeTasks) + preBuildTask.dependsOn(explodeAarTask) + if (capitalizedVariantName.contains("Release")) { + val projectExt = project.extensions.getByType(Extension::class.java) + val appProject = project.rootProject.project(projectExt.appProjectName) + preBuildTask.dependsOn("${appProject.path}:createBundle${capitalizedVariantName}JsAndAssets") } } - - private fun getReBundleFilePath(folderName: String) = "${DirectoryManager.getReBundleDirectory(variant).path}/$folderName" } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/BundleTaskProvider.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/BundleTaskProvider.kt new file mode 100644 index 00000000..26a0452c --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/BundleTaskProvider.kt @@ -0,0 +1,46 @@ +@file:Suppress("DEPRECATION") + +package com.callstack.react.brownfield.shared + +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.api.LibraryVariant +import com.callstack.react.brownfield.processors.VariantTaskProvider +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.TaskProvider + +class BundleTaskProvider(private val variantTaskProvider: VariantTaskProvider) { + fun getBundleTask( + project: Project, + variant: LibraryVariant, + ): TaskProvider? { + val androidExtension = project.extensions.getByType(LibraryExtension::class.java) + + // Find the first variant in the library that matches our criteria + val matchedVariant = + androidExtension.libraryVariants.find { libraryVariant -> + // 1. Try Simple Match + if (libraryVariant.name == variant.name || libraryVariant.name == variant.buildType.name) { + return@find true + } + + // 2. Try Dimension Strategy Match + val flavor = variant.productFlavors.firstOrNull() ?: variant.mergedFlavor + val strategies = + runCatching { + androidExtension.productFlavors.getByName(flavor.name).missingDimensionStrategies + }.getOrNull() ?: return@find false + + strategies.any { (dimension, strategy) -> + val fallbacks = listOf(strategy.requested) + strategy.fallbacks + val libFlavor = libraryVariant.productFlavors.firstOrNull() ?: libraryVariant.mergedFlavor + + dimension == libFlavor.dimension && + fallbacks.contains(libFlavor.name) && + variant.buildType.name == libraryVariant.buildType.name + } + } + + return matchedVariant?.let { variantTaskProvider.bundleTaskProvider(project, it.name) } + } +} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ExplodeAarTask.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ExplodeAarTask.kt new file mode 100644 index 00000000..47500b85 --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ExplodeAarTask.kt @@ -0,0 +1,65 @@ +package com.callstack.react.brownfield.shared + +import com.callstack.react.brownfield.processors.VariantHelper +import com.callstack.react.brownfield.utils.AndroidArchiveLibrary +import com.callstack.react.brownfield.utils.DirectoryManager +import org.gradle.api.DefaultTask +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction + +abstract class ExplodeAarTask : DefaultTask() { + @get:Internal + abstract val inputArtifacts: ListProperty + + @get:Input + abstract val variantName: Property + + @get:Input + abstract val minifyEnabled: Property + + @TaskAction + fun run() { + val artifacts = inputArtifacts.get() + val resolvedVariantName = variantName.get() + + val variantHelper = VariantHelper() + variantHelper.project = project + + // classes-merge + variantHelper.classesMergeTaskDoFirst( + DirectoryManager.getMergeClassDirectory( + resolvedVariantName, + ), + resolvedVariantName, + ) + + val aarLibraries = mutableListOf() + artifacts.forEach { art -> + val archiveLibrary = + AndroidArchiveLibrary( + this.project, + art, + resolvedVariantName, + ) + + aarLibraries.add(archiveLibrary) + + // explode-aar + val zipFolder = archiveLibrary.getExplodedAarRootDir() + zipFolder.mkdirs() + + project.copy { + zipFolder.deleteRecursively() + it.from(project.zipTree(art.file)) + it.into(zipFolder) + } + } + + // classes-merge + val mergeClassesOutputDir = DirectoryManager.getMergeClassDirectory(resolvedVariantName) + variantHelper.classesMergeTaskDoLast(mergeClassesOutputDir, aarLibraries, mutableListOf(), resolvedVariantName, minifyEnabled.get()) + } +} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/GradleProps.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/GradleProps.kt deleted file mode 100644 index 94f1aeb7..00000000 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/GradleProps.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.callstack.react.brownfield.shared - -import org.gradle.api.internal.file.FileResolver -import org.gradle.api.internal.tasks.TaskDependencyFactory -import org.gradle.internal.model.CalculatedValueContainerFactory - -open class GradleProps { - lateinit var calculatedValueContainerFactory: CalculatedValueContainerFactory - lateinit var taskDependencyFactory: TaskDependencyFactory - lateinit var fileResolver: FileResolver -} diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ModuleInfo.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ModuleInfo.kt new file mode 100644 index 00000000..e13004fd --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ModuleInfo.kt @@ -0,0 +1,22 @@ +package com.callstack.react.brownfield.shared + +interface ModuleInfo { + val moduleGroup: String + val moduleName: String + val moduleVersion: String + + val file: String? + + val type: String + get() = "aar" + + val isExpoPublishDependency: Boolean? +} + +data class UnresolvedArtifactInfo( + override val moduleGroup: String, + override val moduleName: String, + override val moduleVersion: String, + override val file: String?, + override val isExpoPublishDependency: Boolean?, +) : ModuleInfo diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/AndroidArchiveLibrary.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/AndroidArchiveLibrary.kt index c83a669e..b7513ca9 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/AndroidArchiveLibrary.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/AndroidArchiveLibrary.kt @@ -1,50 +1,58 @@ package com.callstack.react.brownfield.utils -import com.callstack.react.brownfield.shared.Logging +import com.callstack.react.brownfield.shared.UnresolvedArtifactInfo import org.gradle.api.Project -import org.gradle.api.artifacts.ResolvedArtifact import java.io.File -import java.io.FileNotFoundException -import javax.xml.parsers.DocumentBuilderFactory class AndroidArchiveLibrary( private val project: Project, - artifact: ResolvedArtifact, + artifact: UnresolvedArtifactInfo, private val variantName: String, ) { private var packageName: String? = null - private val artifact: ResolvedArtifact = + private val artifact: UnresolvedArtifactInfo = requireNotNull(artifact.takeIf { it.type == "aar" }) { "Only Aar is accepted as an artifact" } - private fun getArtifactName() = artifact.moduleVersion.id.name - fun getExplodedAarRootDir(): File { val explodedRootDir = File("${project.layout.buildDirectory.get()}/intermediates/exploded-aar") - val id = artifact.moduleVersion.id - return File(explodedRootDir, "${id.group}/${id.name}/${id.version}/$variantName") + return File(explodedRootDir, "${artifact.moduleGroup}/${artifact.moduleName}/${artifact.moduleVersion}/$variantName") } @Synchronized fun getPackageName(): String { - if (packageName == null) { - val manifestFile = getManifestFile() - if (!manifestFile.exists()) { - throw FileNotFoundException("${getArtifactName()} module's AndroidManifest file not found") - } - - try { - val documentBuilderFactory = DocumentBuilderFactory.newInstance() - val document = documentBuilderFactory.newDocumentBuilder().parse(manifestFile) - packageName = document.documentElement.getAttribute("package") - } catch (e: IllegalStateException) { - Logging.log(e.stackTraceToString()) - } - } + if (packageName != null) return packageName!! + + packageName = getNameSpaceFromBuildGradle() return packageName!! } + private fun getNameSpaceFromBuildGradle(): String { + if (artifact.isExpoPublishDependency == true) { + return artifact.moduleGroup + } + + val subProj = project.rootProject.project(":${artifact.moduleName}") + val buildFile = subProj.buildFile // points to build.gradle or build.gradle.kts + + if (!buildFile.exists()) { + error("build.gradle file does not exist for ${artifact.moduleName}") + } + + val text = buildFile.readText() + + // Regex to match: namespace = "com.example.rnscreens" + val regex = Regex("""namespace\s*=?\s*["']([^"']+)["']""") + val match = regex.find(text) + + val namespace = + match?.groupValues?.get(1) + ?: error("No namespace found in ${buildFile.path}") + + return namespace + } + fun getManifestFile() = File(getExplodedAarRootDir(), "AndroidManifest.xml") fun getAssetsDir(): File = File(getExplodedAarRootDir(), "assets") diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/DirectoryManager.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/DirectoryManager.kt index aa40a0d2..1f9c1e5b 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/DirectoryManager.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/DirectoryManager.kt @@ -1,31 +1,20 @@ -@file:Suppress("DEPRECATION") - -/** - * Suppressing because of LibraryVariant. - * We can't use the new `com.android.build.gradle.api.LibraryVariant` - * as of now. - * - * We may want to re-visit this in future. - */ - package com.callstack.react.brownfield.utils -import com.android.build.gradle.api.LibraryVariant import com.callstack.react.brownfield.shared.BaseProject import com.callstack.react.brownfield.shared.Constants.INTERMEDIATES_TEMP_DIR import com.callstack.react.brownfield.shared.Constants.RE_BUNDLE_FOLDER import java.io.File object DirectoryManager : BaseProject() { - fun getMergeClassDirectory(variant: LibraryVariant): File { - return project.file("$buildDir/intermediates/$INTERMEDIATES_TEMP_DIR/merge_classes/${variant.name}") + fun getMergeClassDirectory(variantName: String): File { + return project.file("$buildDir/intermediates/$INTERMEDIATES_TEMP_DIR/merge_classes/$variantName") } - fun getKotlinMetaDirectory(variant: LibraryVariant): File { - return project.file("$buildDir/tmp/kotlin-classes/${variant.name}/META-INF") + fun getKotlinMetaDirectory(variantName: String): File { + return project.file("$buildDir/tmp/kotlin-classes/$variantName/META-INF") } - fun getReBundleDirectory(variant: LibraryVariant): File { - return project.file("$buildDir/outputs/$RE_BUNDLE_FOLDER/${variant.name}") + fun getReBundleDirectory(variantName: String): File { + return project.file("$buildDir/outputs/$RE_BUNDLE_FOLDER/$variantName") } } diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Extension.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Extension.kt index 12c2686b..074bb68b 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Extension.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Extension.kt @@ -5,23 +5,6 @@ open class Extension { const val NAME = "reactBrownfield" } - /** - * Whether to resolve transitive dependencies. - * If false, only embed dependency - * If true, embed remote library's dependencies and local jar module - * - * Default value is false - */ - var transitive = false - - /** - * Should the plugin embed the local project dependencies. - * For eg, local modules - * - * Default value is true - */ - var resolveLocalDependencies = true - /** * Name of the module using `com.android.application` * For eg, app diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Utils.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Utils.kt index 26d2598c..7b4c78ea 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Utils.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Utils.kt @@ -3,21 +3,6 @@ package com.callstack.react.brownfield.utils import java.io.File object Utils { - fun getAGPVersion(): String { - return try { - extractAGPVersion("com.android.Version") - } catch (ignore: Throwable) { - extractAGPVersion("com.android.builder.model.Version") - } - } - - private fun extractAGPVersion(className: String): String { - val versionField = - Class.forName(className) - .getDeclaredField("ANDROID_GRADLE_PLUGIN_VERSION") - return versionField.get(null) as String - } - fun mergeFiles( inputFiles: Collection?, output: File,