Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,3 @@ build/
*.iws
.java-version

# Documentation folder - contains drafts, AI-generated content, and other private content not intended for public repo
docs/
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ class DefaultProguardFileParser @Inject constructor(
readFromReader(InputStreamReader(it, Charsets.UTF_8))
}
} catch (e: IOException) {
logger.log(e)
logger.log("Parse mapping file failure", e)
} catch (e: ParseException) {
logger.log(e)
logger.log("Parse mapping file failure", e)
}
}
}
10 changes: 3 additions & 7 deletions app-sizer/src/main/kotlin/com/grab/sizer/utils/Logger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,13 @@ const val DEFAULT_TAG = "AppSize"

interface Logger {
fun log(tag: String, message: String)
fun log(tag: String, e: Exception)
fun logDebug(tag: String, message: String)
fun log(tag: String, message: String, e: Exception)
}

fun Logger.logDebug(message: String) {
logDebug(DEFAULT_TAG, message)
}
fun Logger.log(message: String) {
log(DEFAULT_TAG, message)
}

fun Logger.log(e: Exception) {
log(DEFAULT_TAG, e)
fun Logger.log(message: String, e: Exception) {
log(DEFAULT_TAG, message, e)
}
8 changes: 2 additions & 6 deletions cli/src/main/kotlin/com/grab/sizer/utils/CliLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,8 @@ class CliLogger : Logger {
println("$tag : $message")
}

override fun log(tag: String, e: Exception) {
println("$tag :")
e.printStackTrace()
}

override fun logDebug(tag: String, message: String) {
override fun log(tag: String, message: String, e: Exception) {
println("$tag : $message")
e.printStackTrace()
}
}
72 changes: 72 additions & 0 deletions docs/plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,14 +231,76 @@ appSizer {
<img src="../images/task-graph.png" width="80%">
</p>

## Known Limitations

### Variant Matching and Module Skipping

The App Sizer plugin uses sophisticated variant matching to analyze dependencies across different project types. However, some scenarios may result in modules being skipped during analysis, which can impact the accuracy of the final results.

#### When Modules Are Skipped

The plugin may skip modules in the following scenarios:

1. **Unsupported Project Types**: Projects that don't match any supported type (Android app/library, Java/Kotlin JVM, Kotlin Multiplatform)
2. **Missing Build Variants**: Android library modules that lack variants matching the main app's flavor/buildType configuration
4. **Custom Build Logic**: Modules using non-standard build configurations that the plugin cannot interpret


#### Error Handling Behavior

The plugin uses defensive error handling to maintain build stability while logging informative warnings:

```
AppSize: Skipping project module-name - variant extraction failed: Cannot find matching variant for module-name
AppSize: Skipping dependency project library-name - unsupported type: Project type not supported: library-name
AppSize: Could not find matching variant for Android library project module-name: Cannot find matching variant for module-name
AppSize: Unsupported project type for Android library project module-name: Project type not supported: module-name
```

When debug logging is enabled (`--debug` flag), full stack traces are available for detailed troubleshooting:

```
AppSize: Full stack trace for variant extraction failure:
java.lang.IllegalStateException: Cannot find matching variant for module-name
at com.grab.plugin.sizer.dependencies.DefaultVariantExtractor.extractVariant(VariantExtractor.kt:284)
at com.grab.plugin.sizer.dependencies.DefaultVariantExtractor.defaultFindMatchVariant(VariantExtractor.kt:132)
[... full stack trace ...]
```

#### Impact on Analysis Results

All classes and resources belonging to skipped modules will be automatically attributed to the app module during analysis, which may impact the accuracy of module-wise and team-based size breakdowns.


## Troubleshooting

### Common Variant Matching Issues

1. **"Cannot find matching variant for [module-name]"**
- **Cause**: Library module lacks a variant matching the app's configuration
- **Solution**: Add `matchingFallbacks` to the library module or ensure consistent flavor/buildType naming

2. **"Unsupported project type: [project-name]"**
- **Cause**: Project doesn't use a supported plugin type
- **Solution**: Verify the project applies Android, Java, or Kotlin plugin correctly

3. **"Skipping dependency project [library-name]"**
- **Cause**: External or internal dependency has configuration issues
- **Solution**: Check dependency's build configuration and ensure it's compatible

### Resource Verification Failures
If you encounter issues with the `verifyResourceRelease` task, try these solutions:
- Check that your resource files are properly formatted and located
- Verify that resource names follow Android naming conventions
- Enable the `enableMatchDebugVariant` flag in your configuration

### Missing Analysis Data

If modules appear to be missing from your analysis reports:

1. **Check Warning Logs**: Look for "Skipping project" messages in build output
2. **Enable Debug Mode**: Run with `--debug` to get detailed variant matching information

### Dagger NoSuchMethodError
If you encounter this exception:
```java
Expand All @@ -252,6 +314,16 @@ This error typically occurs due to a version conflict between the Android build
classpath "com.google.dagger:dagger:2.47"
```

### Debug Mode Analysis

Run with debug logging to get detailed information about module processing, variant matching, and potential issues:

```bash
./gradlew appSizeAnalysisRelease --debug 2>&1 | grep -E "(Skipping|variant|extraction)"
```

This will filter the output to show only variant matching and module processing information.

## Resources

- [Bundletool GitHub Repository](https://github.com/google/bundletool)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

package com.grab.plugin.sizer

import com.grab.plugin.sizer.utils.DefaultPluginLogger
import org.gradle.api.Plugin
import org.gradle.api.Project

Expand All @@ -36,6 +37,7 @@ class AppSizerPlugin : Plugin<Project> {
override fun apply(project: Project) =
TaskManager(
project,
project.extensions.create(PLUGIN_EXTENSION, AppSizePluginExtension::class.java)
project.extensions.create(PLUGIN_EXTENSION, AppSizePluginExtension::class.java),
DefaultPluginLogger(project)
).configTasks()
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,7 @@ import com.grab.plugin.sizer.dependencies.*
import com.grab.plugin.sizer.tasks.AppSizeAnalysisTask
import com.grab.plugin.sizer.tasks.GenerateApkTask
import com.grab.plugin.sizer.tasks.GenerateArchivesListTask
import com.grab.plugin.sizer.utils.isAndroidApplication
import com.grab.plugin.sizer.utils.isAndroidLibrary
import com.grab.plugin.sizer.utils.isJava
import com.grab.plugin.sizer.utils.isKotlinJvm
import com.grab.plugin.sizer.utils.isKotlinMultiplatform
import com.grab.plugin.sizer.utils.*
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.ProjectDependency
Expand All @@ -57,7 +53,8 @@ import org.gradle.kotlin.dsl.the
*/
internal class TaskManager(
private val project: Project,
private val pluginExtension: AppSizePluginExtension
private val pluginExtension: AppSizePluginExtension,
private val logger: PluginLogger
) {
fun configTasks() {
project.rootProject.gradle.projectsEvaluated {
Expand Down Expand Up @@ -147,8 +144,12 @@ internal class TaskManager(
if (variant is AndroidAppSizeVariant) {
task.dependsOn(variant.baseVariant.assembleProvider)
}
} catch (e: RuntimeException) {
project.logger.warn("Could not find matching variant for Android library project ${project.name}: ${e.message}")
} catch (e: UnsupportedOperationException) {
logger.warn("Unsupported project type for Android library project ${project.name}: ${e.message}")
logger.debug("Full stack trace for variant extraction failure:", e)
} catch (e: IllegalStateException) {
logger.warn("Could not find matching variant for Android library project ${project.name}: ${e.message}")
logger.debug("Full stack trace for variant extraction failure:", e)
}
}

Expand All @@ -163,10 +164,10 @@ internal class TaskManager(
project.isKotlinMultiplatform -> {
task.dependsOn(project.tasks.named(KMP_JAR_TASK))
}

else -> {
// Skip unsupported project types to avoid variant extraction errors
project.logger.debug("Skipping variant extraction for unsupported project type: ${project.name}")
logger.warn("Skipping variant extraction for unsupported project type: ${project.name}")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,23 @@ import org.gradle.api.Project
import javax.inject.Inject

interface ArchiveExtractor {
/**
* Extracts archive dependency from a project.
*
* @param project The project to extract archive dependency from
* @return ArchiveDependency representing the project's binary output
* @throws UnsupportedOperationException if the project type is unsupported
* @throws IllegalStateException if variant extraction fails
*/
@Throws(UnsupportedOperationException::class, IllegalStateException::class)
fun extract(project: Project): ArchiveDependency
}

@DependenciesScope
internal class DefaultArchiveExtractor @Inject constructor(
private val variantExtractor: VariantExtractor
) : ArchiveExtractor {
@Throws(UnsupportedOperationException::class, IllegalStateException::class)
override fun extract(project: Project): ArchiveDependency {
val matchVariant = variantExtractor.findMatchVariant(project)
return when {
Expand All @@ -61,7 +71,7 @@ internal class DefaultArchiveExtractor @Inject constructor(
pathToArtifact = matchVariant.binaryOutPut.path
)

else -> throw IllegalArgumentException("The ${project.name} is not an Android/Kotlin/Java module")
else -> throw UnsupportedOperationException("Unsupported project type: ${project.name}")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

package com.grab.plugin.sizer.dependencies

import com.grab.plugin.sizer.utils.PluginLogger
import com.grab.plugin.sizer.utils.debug
import com.grab.plugin.sizer.utils.warn
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import javax.inject.Inject
Expand All @@ -37,14 +40,22 @@ interface ConfigurationExtractor {

@DependenciesScope
internal class DefaultConfigurationExtractor @Inject constructor(
private val variantExtractor: VariantExtractor
private val variantExtractor: VariantExtractor,
private val logger: PluginLogger
) : ConfigurationExtractor {
override fun runtimeConfigurations(project: Project): Sequence<Configuration> {
val variant = variantExtractor.findMatchVariant(project)
return project.configurations.asSequence()
.filter {
variant.runtimeConfiguration.hierarchy.contains(it)
}
return try {
val variant = variantExtractor.findMatchVariant(project)
project.configurations.asSequence()
.filter {
variant.runtimeConfiguration.hierarchy.contains(it)
}
} catch (e: RuntimeException) {
logger.warn("Could not find matching variant for project ${project.name}: ${e.message}")
logger.debug("Full stack trace for variant extraction failure:", e)
// Return empty sequence if variant extraction fails
emptySequence()
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

package com.grab.plugin.sizer.dependencies

import com.grab.plugin.sizer.utils.DefaultPluginLogger
import com.grab.plugin.sizer.utils.PluginLogger
import com.grab.sizer.utils.Logger
import dagger.Binds
Expand All @@ -50,8 +51,6 @@ internal interface DependenciesComponent {
fun configurationExtractor(): ConfigurationExtractor
fun variantExtractor(): VariantExtractor

fun logger(): Logger

@Component.Factory
interface Factory {
fun create(
Expand Down Expand Up @@ -79,5 +78,5 @@ internal interface DependenciesModule {
fun bindVariantExtractor(extractor: DefaultVariantExtractor): VariantExtractor

@Binds
fun bindLogger(logger: PluginLogger): Logger
fun bindLogger(logger: DefaultPluginLogger): PluginLogger
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@

package com.grab.plugin.sizer.dependencies

import com.grab.sizer.utils.Logger
import com.grab.sizer.utils.log
import com.grab.plugin.sizer.utils.PluginLogger
import com.grab.plugin.sizer.utils.debug
import com.grab.plugin.sizer.utils.warn
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.ResolveException
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.internal.artifacts.DefaultResolvedDependency
import org.gradle.internal.component.AmbiguousVariantSelectionException
import java.util.*
import javax.inject.Inject
Expand All @@ -53,7 +53,7 @@ class DefaultDependencyExtractor @Inject constructor(
private val appProject: Project,
private val configurationExtractor: ConfigurationExtractor,
private val archiveExtractor: ArchiveExtractor,
private val logger: Logger
private val logger: PluginLogger
) : DependencyExtractor {
override fun extract(): ArchiveDependencyStore {
return ArchiveDependencyStore().apply {
Expand All @@ -62,8 +62,16 @@ class DefaultDependencyExtractor @Inject constructor(

while (queue.isNotEmpty()) {
val project = queue.poll()
val projectArchive = archiveExtractor.extract(project)
add(projectArchive)
try {
val projectArchive = archiveExtractor.extract(project)
add(projectArchive)
} catch (e: UnsupportedOperationException) {
logger.warn("Skipping project ${project.name} - unsupported type: ${e.message}")
logger.debug("Full stack trace for archive extraction failure:", e)
} catch (e: IllegalStateException) {
logger.warn("Skipping project ${project.name} - variant extraction failed: ${e.message}")
logger.debug("Full stack trace for archive extraction failure:", e)
}
fetchInternalDependency(project, this, checkedProjects, queue)
fetchExternalDependency(project, this)
}
Expand All @@ -81,9 +89,17 @@ class DefaultDependencyExtractor @Inject constructor(
.filterIsInstance<ProjectDependency>()
.map { it.dependencyProject }
.forEach { dependencyProject ->
archiveDependencyStore.add(
archiveExtractor.extract(dependencyProject)
)
try {
archiveDependencyStore.add(
archiveExtractor.extract(dependencyProject)
)
} catch (e: UnsupportedOperationException) {
logger.warn("Skipping dependency project ${dependencyProject.name} - unsupported type: ${e.message}")
logger.debug("Full stack trace for dependency archive extraction failure:", e)
} catch (e: IllegalStateException) {
logger.warn("Skipping dependency project ${dependencyProject.name} - variant extraction failed: ${e.message}")
logger.debug("Full stack trace for dependency archive extraction failure:", e)
}
if (!checkedProjects.contains(dependencyProject.path)) {
queue.add(dependencyProject)
checkedProjects.add(dependencyProject.path)
Expand All @@ -102,7 +118,7 @@ class DefaultDependencyExtractor @Inject constructor(
try {
it.firstLevelModuleDependencies
} catch (e: ResolveException) {
logger.log("Fetching firstLevelModuleDependencies having issue with $it for ${project.name}")
logger.warn("Fetching firstLevelModuleDependencies having issue with $it for ${project.name}")
emptySet<ResolvedDependency>()
}
}
Expand All @@ -118,7 +134,7 @@ class DefaultDependencyExtractor @Inject constructor(
archiveDependencyStore.add(artifact.toArchiveDependency())
}
} catch (e: AmbiguousVariantSelectionException) {
logger.log("Fetching allModuleArtifacts having issue with ${resolvedDep.name}")
logger.warn("Fetching allModuleArtifacts having issue with ${resolvedDep.name}")
}
}
}
Expand Down
Loading