diff --git a/apps/RNApp/android/build.gradle b/apps/RNApp/android/build.gradle index 675d09cc..09e5bfe0 100644 --- a/apps/RNApp/android/build.gradle +++ b/apps/RNApp/android/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") - classpath("com.callstack.react:brownfield-gradle-plugin:0.6.3") + classpath("com.callstack.react:brownfield-gradle-plugin:0.6.3-local") } } diff --git a/apps/RNApp/package.json b/apps/RNApp/package.json index 2aaeb40b..6899cac5 100644 --- a/apps/RNApp/package.json +++ b/apps/RNApp/package.json @@ -7,6 +7,9 @@ "ios": "react-native run-ios", "build:example:android-rn": "react-native build-android", "build:example:ios-rn": "react-native build-ios", + "build:aar:dryRun": "cd android && ./gradlew :brownfieldlib:assembleRelease --dry-run", + "build:aar": "cd android && ./gradlew :brownfieldlib:processArtifacts :brownfieldlib:assembleRelease", + "build:scan": "cd android && ./gradlew :brownfieldlib:assembleRelease --scan", "lint": "eslint .", "start": "react-native start", "test": "jest" diff --git a/apps/TesterIntegrated/package.json b/apps/TesterIntegrated/package.json index f7cf6891..96ce755f 100644 --- a/apps/TesterIntegrated/package.json +++ b/apps/TesterIntegrated/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "start": "react-native start", - "build:tester-integrated:android": "cd kotlin && ./gradlew assembleDebug", + "build:tester-integrated:android": "cd kotlin && ./gradlew installDebug", "build:tester-integrated:ios": "cd swift && xcodebuild -workspace SwiftExample.xcworkspace -scheme SwiftExample -configuration Release -sdk iphonesimulator build CODE_SIGNING_ALLOWED=NO -derivedDataPath ./build" }, "dependencies": { diff --git a/gradle-plugins/publish-to-maven-local.sh b/gradle-plugins/publish-to-maven-local.sh index 39db8b77..e56b888b 100755 --- a/gradle-plugins/publish-to-maven-local.sh +++ b/gradle-plugins/publish-to-maven-local.sh @@ -2,7 +2,7 @@ set -e -cd ./gradle-plugins/react +cd ./react ./gradlew clean ./gradlew build ./gradlew :brownfield:publishMavenLocalPublicationToMavenLocalRepository \ 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 d250ce05..6736de98 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 { @@ -64,18 +65,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()) - } } } } @@ -85,10 +74,6 @@ publishing { } } -signing { - sign(publishing.publications["mavenLocal"]) -} - repositories { mavenCentral() google() @@ -98,6 +83,7 @@ dependencies { implementation(libs.agp) implementation(libs.common) implementation(libs.asm.commons) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") } tasks.named("detekt").configure { diff --git a/gradle-plugins/react/brownfield/gradle.properties b/gradle-plugins/react/brownfield/gradle.properties index 800ce244..1b6b84ea 100644 --- a/gradle-plugins/react/brownfield/gradle.properties +++ b/gradle-plugins/react/brownfield/gradle.properties @@ -1,6 +1,6 @@ PROJECT_ID=com.callstack.react.brownfield ARTIFACT_ID=brownfield-gradle-plugin -VERSION=0.6.3 +VERSION=0.6.3-local GROUP=com.callstack.react IMPLEMENTATION_CLASS=com.callstack.react.brownfield.plugin.RNBrownfieldPlugin 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 47a10fcb..8f91e929 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 @@ -14,17 +14,23 @@ 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.BundleTaskProvider import com.callstack.react.brownfield.shared.GradleProps +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.Project import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ResolvedArtifact +import org.gradle.api.artifacts.component.ModuleComponentIdentifier +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency +import org.gradle.internal.component.local.model.PublishArtifactLocalArtifactMetadata +import kotlin.collections.mutableListOf + class ArtifactsResolver( private val configurations: MutableCollection, @@ -32,23 +38,12 @@ class ArtifactsResolver( private val extension: Extension, ) : GradleProps() { - companion object { - const val ARTIFACT_TYPE_AAR = "aar" - const val ARTIFACT_TYPE_JAR = "jar" - } - - fun processArtifacts() { + fun processDefaultDependencies() { embedDefaultDependencies("implementation") - setTransitiveToConfigurations() - generateArtifacts() } - private fun setTransitiveToConfigurations() { - configurations.forEach { - if (baseProject.project.extensions.getByType(Extension::class.java).transitive) { - it.isTransitive = true - } - } + fun processArtifacts(): MutableList { + return generateArtifacts() } private fun embedExpoDependencies() { @@ -72,19 +67,19 @@ class ArtifactsResolver( val expoProject = baseProject.project.rootProject.project("expo") val expoConfig = expoProject.configurations.findByName("api") expoConfig?.dependencies?.forEach { - if (extension.resolveLocalDependencies) { - if (it is DefaultProjectDependency) { - baseProject.project.dependencies.add( - CONFIG_NAME, - expoProject.dependencies.project(mapOf("path" to ":${it.name}")), - ) - } else { - baseProject.project.dependencies.add( - CONFIG_NAME, - it, - ) + if (extension.resolveLocalDependencies) { + if (it is DefaultProjectDependency) { + baseProject.project.dependencies.add( + CONFIG_NAME, + expoProject.dependencies.project(mapOf("path" to ":${it.name}")), + ) + } else { + baseProject.project.dependencies.add( + CONFIG_NAME, + it, + ) + } } - } } } @@ -96,33 +91,35 @@ class ArtifactsResolver( val config = baseProject.project.configurations.findByName(configName) val defaultDependencies = config?.dependencies?.filterIsInstance() defaultDependencies?.forEach { dependency -> - if (extension.resolveLocalDependencies) { - baseProject.project.dependencies.add( - CONFIG_NAME, - baseProject.project.dependencies.project(mapOf("path" to ":${dependency.name}")), - ) - } + if (extension.resolveLocalDependencies) { + baseProject.project.dependencies.add( + CONFIG_NAME, + baseProject.project.dependencies.project(mapOf("path" to ":${dependency.name}")), + ) + } } } - private fun generateArtifacts() { + private fun generateArtifacts(): MutableList { + // store the artifacts which can be variant aware including flavors + val artifacts = mutableSetOf() 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)) + val res = handleUnResolvedArtifacts(configuration, variant) + artifacts.addAll(res) } } if (artifacts.isNotEmpty()) { - val processor = VariantProcessor(variant) + val processor = VariantProcessor() processor.project = baseProject.project - processor.processVariant(artifacts) + processor.processVariant(variant.name) } } + + return artifacts.toMutableList() } private fun isEmbedConfig( @@ -134,47 +131,88 @@ class ArtifactsResolver( configuration.name == variant.name + CONFIG_SUFFIX } - 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 - } - 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 + ): List { + val unMatchedArtifacts = mutableListOf() + val firstLevelIds = setOf() + + val resolutionResult = configuration.incoming.resolutionResult + resolutionResult.root.dependencies.forEach { + if (it is ResolvedDependencyResult) { + when (val id = it.selected.id) { + is ModuleComponentIdentifier -> { +// println("===----- module ${id.moduleIdentifier.name} ${id.displayName} ") + if (id.displayName !in firstLevelIds) { +// unMatchedArtifacts.add( +// UnresolvedArtifactInfo( +// id.group, +// id.module, +// id.version, +// ) +// ) + } + } + is ProjectComponentIdentifier -> { + val depProj = baseProject.project.project(id.projectPath) + if (id.displayName !in firstLevelIds) { + unMatchedArtifacts.add(UnresolvedArtifactInfo( + depProj.group.toString(), + depProj.name, + depProj.version.toString(), + baseProject.project.file(id.projectName).absolutePath, + mutableSetOf(), + null, + )) + } + } } } + } - val variantHelper = VariantHelper(variant) - variantHelper.project = baseProject.project - val variantTaskProvider = VariantTaskProvider(variantHelper) + val variantTaskProvider = VariantTaskProvider() variantTaskProvider.project = baseProject.project - val flavorArtifact = FlavorArtifact(variant, variantTaskProvider) + val flavorArtifact = FlavorArtifact(configuration) flavorArtifact.project = baseProject.project - unMatchedArtifacts.forEach { dependency -> + val bundleTaskProvider = BundleTaskProvider(variantTaskProvider) + + val unMatchedArtifacts1 = mutableListOf() + unMatchedArtifacts.forEach { artifact -> + val artifactProject = getArtifactProject(artifact) + val bundleProvider = artifactProject?.let { bundleTaskProvider.getBundleTask(it, variant) } + val resolvedArtifact = flavorArtifact.createFlavorArtifact( - dependency, - calculatedValueContainerFactory, fileResolver, taskDependencyFactory, + bundleProvider, + variant.name ) - artifactList.add(resolvedArtifact) + + when (val asd = resolvedArtifact.id) { + is PublishArtifactLocalArtifactMetadata -> { + val deps = asd.buildDependencies.getDependencies(null) + unMatchedArtifacts1.add(UnresolvedArtifactInfo( + artifact.moduleGroup, + artifact.moduleName, + artifact.moduleVersion, + resolvedArtifact.file.absolutePath, + deps.map { it.path }.toSet(), + bundleProvider?.name, + )) + } + } + } + + return unMatchedArtifacts1 + } + + + private fun getArtifactProject(unResolvedArtifact: UnresolvedArtifactInfo): Project? { + return baseProject.project.rootProject.allprojects.find { p -> + unResolvedArtifact.moduleName == p.name && unResolvedArtifact.moduleGroup == p.group.toString() } - return artifactList } } 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 index db91a447..ddf7c63a 100644 --- 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 @@ -1,67 +1,43 @@ -@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 org.gradle.api.component.Artifact 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.artifacts.Configuration +import org.gradle.api.artifacts.result.ResolvedArtifactResult import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact +import org.gradle.api.internal.artifacts.result.DefaultResolvedArtifactResult 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.external.model.ImmutableCapabilities 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() { +class FlavorArtifact( + private val configuration: Configuration) : 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) + bundleTaskProvider: TaskProvider?, + variantName: String + ): ResolvedArtifactResult { + val artifactFile = createArtifactFile(bundleTaskProvider?.get() as Task) val artifactName = DefaultIvyArtifactName(artifactFile.name, "aar", "") - return DefaultResolvedArtifact( + return DefaultResolvedArtifactResult( PublishArtifactLocalArtifactMetadata( { artifactName.name }, - LazyPublishArtifact(bundleProvider, fileResolver, taskDependencyFactory), + LazyPublishArtifact(bundleTaskProvider, fileResolver, taskDependencyFactory), ), - calculatedValueContainerFactory.create(Describables.of(artifactFile.name), artifactFile), - identifier, - artifactName, + configuration.attributes, + ImmutableCapabilities.EMPTY, + Describables.of(variantName), + Artifact::class.java, + artifactFile ) } @@ -69,57 +45,4 @@ class FlavorArtifact(private val variant: LibraryVariant, private val variantTas 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/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 85613539..8e59175a 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,9 +1,12 @@ 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 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() @@ -26,10 +29,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) @@ -63,13 +65,35 @@ 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..2ad9bbd6 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 b060b2e6..fd79b2c0 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,91 +1,249 @@ package com.callstack.react.brownfield.plugin +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.gradle.LibraryExtension +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.artifacts.ArtifactsResolver +import com.callstack.react.brownfield.exceptions.TaskNotFound +import com.callstack.react.brownfield.processors.JNILibsProcessor +import com.callstack.react.brownfield.processors.ProguardProcessor 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.ExplodeAarTask +import com.callstack.react.brownfield.shared.JsonInstance +import com.callstack.react.brownfield.shared.ProcessArtifactsTask +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 java.io.BufferedWriter +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStreamWriter 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 - - override fun apply(project: Project) { - verifyAndroidPluginApplied(project) - initializers(project) +@Inject +constructor( + private val taskDependencyFactory: TaskDependencyFactory, + private val fileResolver: FileResolver, +) : Plugin { + private lateinit var extension: Extension + private lateinit var projectConfigurations: ProjectConfigurations + private lateinit var artifactsResolver: ArtifactsResolver + + private lateinit var project: Project + + + override fun apply(project: Project) { + verifyAndroidPluginApplied(project) + + this.project = project + initializers() + + // Configure + projectConfigurations.configure() + RNSourceSets.configure(project, extension) + RClassTransformer.registerASMTransformation() + + if (Utils.isExampleLibrary(project.name)) { + return + } + + val baseProject = BaseProject() + baseProject.project = project + DirectoryManager.project = project + artifactsResolver = + ArtifactsResolver(projectConfigurations.getConfigurations(), baseProject, extension) + artifactsResolver.taskDependencyFactory = taskDependencyFactory + artifactsResolver.fileResolver = fileResolver + + artifactsResolver.processDefaultDependencies() + + val processArtifactsTask = + project.tasks.register("processArtifacts", ProcessArtifactsTask::class.java) { + it.artifactsResolver.set(artifactsResolver) + it.outputFile.set(project.layout.buildDirectory.file("artifacts.txt")) + it.artifactOutput.set(project.layout.buildDirectory.file("artifacts-list.jsonl")) + } + + val jniLibsProcessor = JNILibsProcessor() + jniLibsProcessor.project = project + + val proguardProcessor = ProguardProcessor() + proguardProcessor.project = project + + val variantTaskProvider = VariantTaskProvider() + variantTaskProvider.project = project + + // process manifest & merger task + project.extensions.getByType(LibraryExtension::class.java).libraryVariants.all { variant -> + val capitalizedVariantName = variant.name.replaceFirstChar(Char::titlecase) + + project.tasks.register("explode${capitalizedVariantName}Aar", ExplodeAarTask::class.java) { task -> + task.inputArtifactListFile.set(processArtifactsTask.get().artifactOutput) + task.inputTaskList.set(processArtifactsTask.get().outputFile) + + task.variantName.set(variant.name) + task.minifyEnabled.set(variant.buildType.isMinifyEnabled) + + val file = task.inputTaskList.get().asFile + if (file.exists()) { + file.readLines().forEach { line -> + val lineSplits = line.split(",") + + val projectPath = lineSplits[0] + val taskName = lineSplits[1] + val artifactProject = project.rootProject.project(projectPath) + + if (taskName.contains(capitalizedVariantName)) { + artifactProject.tasks.findByName(taskName)?.let { task.dependsOn(it) } + } + } + } + } + + val artifacts = readArtifacts(processArtifactsTask.get().artifactOutput.get().asFile) + val filteredArtifacts = artifacts.filter { it.bundleTaskName?.contains(capitalizedVariantName) == true } + val aarLibraries = mutableListOf() + filteredArtifacts.forEach { art -> + val archiveLibrary = + AndroidArchiveLibrary( + this.project, + art, + variant.name, + ) + aarLibraries.add(archiveLibrary) + } /** - * 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. + * Flat IDs to be put into the variant property, required for RClass Transformer */ - val expoProjectPath = ":expo" - val hasExpoProject = project.findProject(expoProjectPath) != null - if (hasExpoProject) { - project.evaluationDependsOn(expoProjectPath) + val packageIDs = aarLibraries.map { it.getPackageName() } + VariantPackagesProperty.getVariantPackagesProperty().put(variant.name, packageIDs) + + val processManifestTask = variant.outputs.first().processManifestProvider.get() + processManifestTask.doLast { + // manifest-merger + val buildDir = project.layout.buildDirectory.get() + val manifestOutput = + project.file( + "$buildDir/intermediates/merged_manifest/${variant.name}/process${capitalizedVariantName}Manifest/AndroidManifest.xml", + ) + + val inputManifests = aarLibraries.map { it.getManifestFile() } + manifestMerger(manifestOutput, inputManifests, manifestOutput) } - RNSourceSets.configure(project, extension) - projectConfigurations.setup() - registerRClassTransformer() + /** ======= GENERATE RESOURCES =========*/ + + /** + * See if we can move this block to configure phase of prebuild or resourceGenTask + * + * Tried adding to resourceGenTask configure, doFirst and doLast. PreBuild doFirst and doLast + * but it does not take effect. Adding to preBuild.configure, works. + */ + val taskPath = "generate${capitalizedVariantName}Resources" + val resourceGenTask = project.tasks.named(taskPath) - project.afterEvaluate { - afterEvaluate() + if (!resourceGenTask.isPresent) { + throw TaskNotFound("Task $taskPath not found") } - } - 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) - } + variant.registerGeneratedResFolders( + project.files(aarLibraries.map { it.getResDir() }), + ) + + /** ======= GENERATE ASSETS ========= */ + val assetsTask = variant.mergeAssetsProvider.get() + + val androidExtension = project.extensions.getByName("android") as LibraryExtension + assetsTask.doFirst { + val filteredSourceSets = androidExtension.sourceSets.filter { it.name == variant.name } - /** - * 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"), - ) + filteredSourceSets.forEach { sourceSet -> + val filteredAarLibs = aarLibraries.filter { it.getAssetsDir().exists() } + if (!filteredAarLibs.isEmpty()) { + sourceSet.assets.srcDirs(filteredAarLibs.map { it.getAssetsDir() }) + } + } } + + /** ===== jniLibsProcessor ===== */ + jniLibsProcessor.processJniLibs(aarLibraries, variant.name) + + /** ===== proguardProcessor ===== */ + val proguardRules = aarLibraries.map { it.getProguardRules() } + proguardProcessor.processConsumerFiles(proguardRules, capitalizedVariantName) + proguardProcessor.processGeneratedFiles(proguardRules, capitalizedVariantName) + + /** ===== processDataBinding ===== */ + val bundleTask = variantTaskProvider.bundleTaskProvider(project, variant.name) + variantTaskProvider.processDataBinding(bundleTask, aarLibraries, variant.name) } + } + + private fun readArtifacts(file: File): List { + if (!file.exists()) return emptyList() + + return file.readLines() + .filter { it.isNotBlank() } + .map { JsonInstance.json.decodeFromString(it) } + } + + private fun manifestMerger(mainManifestFile: File, secondaryManifestFiles: List, outputFile: File) { + val iLogger: ILogger = LoggerWrapper(logger) + val mergerInvoker = ManifestMerger2.newMerger(mainManifestFile, iLogger, ManifestMerger2.MergeType.LIBRARY) + val manifestProviders = mutableListOf() - /** - * Transforms RClass - */ - private fun registerRClassTransformer() { - RClassTransformer.registerASMTransformation() + 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 + }, + ) } - private fun afterEvaluate() { - val baseProject = BaseProject() - baseProject.project = project - val artifactsResolver = ArtifactsResolver(projectConfigurations.getConfigurations(), baseProject, extension) - artifactsResolver.calculatedValueContainerFactory = calculatedValueContainerFactory - artifactsResolver.taskDependencyFactory = taskDependencyFactory - artifactsResolver.fileResolver = fileResolver - artifactsResolver.processArtifacts() + 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() + } + } + + private fun initializers() { + RClassTransformer.project = project + this.extension = project.extensions.create(Extension.NAME, Extension::class.java) + projectConfigurations = ProjectConfigurations(project) + VariantPackagesProperty.setVariantPackagesProperty(project) + } + + /** + * 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"), + ) } } +} 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 ac1873c1..25b5b850 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 @@ -34,39 +35,52 @@ 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 -> + // 1. Get the 'androidComponents' extension for the new Variant API + val componentsExtension = project.extensions.getByType(LibraryAndroidComponentsExtension::class.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") + } + + // 2. Use the onVariants block to configure each variant + componentsExtension.onVariants { variant -> + // The variant name is directly available val capitalizedVariantName = variant.name.replaceFirstChar(Char::titlecase) - androidExtension.sourceSets.getByName("main") { sourceSet -> - for (bundlePathSegment in listOf( + // 3. Lazily configure the 'main' source set using .named() + androidExtension.sourceSets.named("main") { sourceSet -> + // Paths are collected and added, similar to your improved version + val bundlePathSegments = 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") - } + ) - sourceSet.java.srcDirs("$moduleBuildDir/generated/autolinking/src/main/java") + // 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/JNILibsProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/JNILibsProcessor.kt index 6a30affe..2b404dfa 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,23 +1,11 @@ -@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 org.gradle.api.Task import org.gradle.api.tasks.Copy import org.gradle.api.tasks.TaskProvider import java.io.File @@ -25,10 +13,9 @@ import java.io.File class JNILibsProcessor : BaseProject() { fun processJniLibs( aarLibraries: Collection, - explodeTasks: MutableList, - variant: LibraryVariant, + variantName: String, ) { - val capitalizedVariantName = variant.name.replaceFirstChar(Char::titlecase) + val capitalizedVariantName = variantName.replaceFirstChar(Char::titlecase) val taskName = "merge${capitalizedVariantName}JniLibFolders" val mergeJniLibsTask = project.tasks.named(taskName) @@ -37,10 +24,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 { @@ -53,7 +39,7 @@ class JNILibsProcessor : BaseProject() { val jniDir = archiveLibrary.getJniDir() processNestedLibs(jniDir.listFiles(), existingJNILibs) if (jniDir.exists()) { - val filteredSourceSets = androidExtension.sourceSets.filter { sourceSet -> sourceSet.name == variant.name } + val filteredSourceSets = androidExtension.sourceSets.filter { sourceSet -> sourceSet.name == variantName } filteredSourceSets.forEach { sourceSet -> sourceSet.jniLibs.srcDir(jniDir) } } } @@ -61,8 +47,7 @@ class JNILibsProcessor : BaseProject() { } } - private fun copySoLibsTask(variant: LibraryVariant): TaskProvider { - val variantName = variant.name + private fun copySoLibsTask(variantName: String): TaskProvider { val capitalizedVariant = variantName.replaceFirstChar(Char::titlecase) val projectExt = project.extensions.getByType(Extension::class.java) @@ -81,6 +66,7 @@ class JNILibsProcessor : BaseProject() { project.rootProject .file("${project.name}/libs$capitalizedVariant") + return project.tasks.register("copy${capitalizedVariant}LibSources", Copy::class.java) { it.dependsOn(stripTask, codegenTask) it.from(fromDir) 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 4c326f72..d89119ad 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,32 +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 org.gradle.api.Task 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.replaceFirstChar(Char::titlecase) - +class ProguardProcessor : BaseProject() { fun processConsumerFiles( - aarLibs: Collection, - explodeTasks: MutableList, + proguardRules: List, + capitalizedVariantName: String ) { val mergeTaskName = "merge${capitalizedVariantName}ConsumerProguardFiles" val mergeFileTask = project.tasks.named(mergeTaskName) @@ -35,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/VariantHelper.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantHelper.kt index 7a0bfdf7..14338002 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,76 +1,24 @@ -@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 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.replaceFirstChar(Char::titlecase) - - 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) - buildDependencies as? Set ?: emptySet() - } catch (ignore: MissingPropertyException) { - emptySet() - } - } - - fun getSyncLibJarsTaskPath(): String { - return "sync${variant.name.replaceFirstChar(Char::titlecase)}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.replaceFirstChar(Char::titlecase)}JavaWithJavac/classes", + "$buildDir/intermediates/javac/$variantName/compile${variantName.replaceFirstChar(Char::titlecase)}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))) } @@ -82,14 +30,16 @@ class VariantHelper(private val variant: LibraryVariant) : BaseProject() { fun classesMergeTaskDoLast( outputDir: File, - aarLibraries: Collection, + 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) @@ -98,59 +48,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 bbe80fac..afe6e547 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 @@ -5,13 +5,17 @@ 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 } fun setVariantPackagesProperty(project: Project) { - properties = project.objects.mapProperty(String::class.java, List::class.java as Class>) + properties = + project.objects.mapProperty( + String::class.java, + 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 index 5b57e990..3b56163b 100644 --- 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 @@ -1,53 +1,12 @@ -@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.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 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.replaceFirstChar(Char::titlecase) - 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() +class VariantProcessor : BaseProject() { + fun processVariant(variantName: String) { + val capitalizedVariantName = variantName.replaceFirstChar(Char::titlecase) val preBuildTaskPath = "pre${capitalizedVariantName}Build" val prepareTask = project.tasks.named(preBuildTaskPath) @@ -58,113 +17,5 @@ class VariantProcessor(private val variant: LibraryVariant) : BaseProject() { if (capitalizedVariantName.contains("Release")) { prepareTask.dependsOn(":app: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.replaceFirstChar(Char::titlecase) - val name = artifact.name.replaceFirstChar(Char::titlecase) - 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 891de00f..2665b293 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,77 +1,15 @@ package com.callstack.react.brownfield.processors -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.utils.AndroidArchiveLibrary import com.callstack.react.brownfield.utils.DirectoryManager -import com.callstack.react.brownfield.utils.Utils 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.replaceFirstChar(Char::titlecase) - - 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: BaseProject() { fun bundleTaskProvider( project: Project, variantName: String, @@ -79,53 +17,23 @@ class VariantTaskProvider(private val variantHelper: VariantHelper) : BaseProjec var bundleTaskPath = "bundle${variantName.replaceFirstChar(Char::titlecase)}" 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) @@ -135,7 +43,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) @@ -147,18 +55,5 @@ class VariantTaskProvider(private val variantHelper: VariantHelper) : BaseProjec } } - fun processDeepLinkTasks(explodeTasks: MutableList) { - val taskName = "extractDeepLinksForAar$capitalizedVariantName" - val extractDeepLinks = project.tasks.named(taskName) - - if (!extractDeepLinks.isPresent) { - throw TaskNotFound("Task $taskName not found") - } - - extractDeepLinks.configure { - it.dependsOn(explodeTasks) - } - } - - private fun getReBundleFilePath(folderName: String) = "${DirectoryManager.getReBundleDirectory(variant).path}/$folderName" + private fun getReBundleFilePath(folderName: String, variantName: String) = "${DirectoryManager.getReBundleDirectory(variantName).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..cae0210c --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/BundleTaskProvider.kt @@ -0,0 +1,57 @@ +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? { + 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 (_: Exception) {} + } + + bundleTaskProvider != null + } + + return bundleTaskProvider + } + +} \ No newline at end of file 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..f34163cf --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ExplodeAarTask.kt @@ -0,0 +1,81 @@ +package com.callstack.react.brownfield.shared + +import com.android.build.gradle.LibraryExtension +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.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.TaskAction +import java.io.File + +abstract class ExplodeAarTask: DefaultTask() { + + @get:InputFile + abstract val inputArtifactListFile: RegularFileProperty + + @get:InputFile + abstract val inputTaskList: RegularFileProperty + + @get:Input + abstract val variantName: Property + + @get:Input + abstract val minifyEnabled: Property + + @TaskAction + fun run() { + val file = inputArtifactListFile.get().asFile + val artifacts = readArtifacts(file) + val resolvedVariantName = variantName.get() + + val variantHelper = VariantHelper() + variantHelper.project = project + + // classes-merge + variantHelper.classesMergeTaskDoFirst( + DirectoryManager.getMergeClassDirectory( + resolvedVariantName + ), + resolvedVariantName + ) + + val aarLibraries = mutableListOf() + val filteredArtifacts = artifacts.filter { it.bundleTaskName?.lowercase()?.contains(resolvedVariantName) == true } + filteredArtifacts.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()) + } + + private fun readArtifacts(file: File): List { + if (!file.exists()) return emptyList() + + return file.readLines() + .filter { it.isNotBlank() } + .map { JsonInstance.json.decodeFromString(it) } + } +} \ No newline at end of file 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 index 94f1aeb7..b1243a76 100644 --- 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 @@ -5,7 +5,6 @@ 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/JsonInstance.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/JsonInstance.kt new file mode 100644 index 00000000..fcb3eaa8 --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/JsonInstance.kt @@ -0,0 +1,11 @@ +package com.callstack.react.brownfield.shared + +import kotlinx.serialization.json.Json + +object JsonInstance { + val json = Json { + prettyPrint = false + encodeDefaults = true + ignoreUnknownKeys = true + } +} \ No newline at end of file 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..1275859c --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ModuleInfo.kt @@ -0,0 +1,30 @@ +package com.callstack.react.brownfield.shared + +import org.gradle.api.Task +import java.io.File +import kotlinx.serialization.Serializable + +interface ModuleInfo { + val moduleGroup: String + val moduleName: String + val moduleVersion: String + + val file: String + + val type: String + get() = "aar" + + val dependencies: Set? + val bundleTaskName: String? +} + +@Serializable +data class UnresolvedArtifactInfo( + override val moduleGroup: String, + override val moduleName: String, + override val moduleVersion: String, + override val file: String, + override val dependencies: Set?, + override val bundleTaskName: String?, +) : ModuleInfo + diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ProcessArtifactTask.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ProcessArtifactTask.kt new file mode 100644 index 00000000..56598649 --- /dev/null +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ProcessArtifactTask.kt @@ -0,0 +1,37 @@ +package com.callstack.react.brownfield.shared + +import com.callstack.react.brownfield.artifacts.ArtifactsResolver +import kotlinx.serialization.encodeToString +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction + +abstract class ProcessArtifactsTask: DefaultTask() { + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @get:OutputFile + abstract val artifactOutput: RegularFileProperty + + @TaskAction + fun run() { + val artifacts = artifactsResolver.get().processArtifacts() + val metaList = mutableListOf() + artifacts.forEach { + metaList.add("${it.moduleName},${it.bundleTaskName}") + } + outputFile.get().asFile.writeText(metaList.joinToString("\n")) + artifactOutput.get().asFile.writeText( + artifacts.joinToString("\n") { + JsonInstance.json.encodeToString(it) + } + ) + } + + @get:Internal + abstract val artifactsResolver: Property +} \ No newline at end of file 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..4da459ea 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,53 @@ 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 { + 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/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,