Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes Resources$NotFoundException when accessing app module resource from dynamic feature module #1367

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kevinzheng-ap
Copy link
Collaborator

Related to #1060

Fixes Resources$NotFoundException when accessing app module resource from dynamic feature module.

The cause is in PublishingSpecs.kt, PACKAGED_RES and SYMBOL_LIST_WITH_PACKAGE_NAME are not properly associated with correspondent ArtifactType when publishing as ComponentTypeImpl.BASE_APK from application module. So these artifacts are not available for dynamic feature module during the execution of preparePaparazziDebugResources.

The proposed solution is to replicate the logic in ArtifactPublishingUtil.kt to map above artifacts to their respective ArtifactType like what ComponentTypeImpl.LIBRARY does. However, this approach requires application module to apply PaparazziPlugin, even if it does not have any snapshot tests of its own.

dependencies {
implementation files('libs/external.aar')
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike library module, application module does not generate symbol_list_with_package_name/[variant]/package-aware-r.txt when there is no dependency. So I have to add one dependency in the test.

In reality, application module will always have dependencies, so there is no issue about that. We do not have to check if symbol_list_with_package_name/[variant]/package-aware-r.txt exists

Copy link
Collaborator

@jrodbx jrodbx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so this is an awesome solution given your source digging into AGP internals, but I'm concerned this depends on internals too much and will break every time we bump AGP.

I'm curious if there is a DSL one level up that exposes this to plugin or build script authors. In other words,

Looking at line 105 of ArtifactPublishingUtil in AGP 8.2.2:

fun publishBuildArtifacts(
    creationConfig: ComponentCreationConfig,
    publishInfo: VariantPublishingInfo?
) {
    for (outputSpec in PublishingSpecs.getVariantPublishingSpec(creationConfig.componentType).outputs) {

ultimately, it looks like this approach replaces publishInfo with the additionalSpecs needed to drive this business logic correctly. Is there a way via DSL to configure the variant publishing spec such that:

PublishingSpecs.getVariantPublishingSpec(creationConfig.componentType).outputs

in AGP internal will execute the desired path in your implementation of publishBuildArtifacts?

@@ -65,6 +85,12 @@ public class PaparazziPlugin : Plugin<Project> {

supportedPlugins.forEach { plugin ->
project.plugins.withId(plugin) {
project.extensions.getByType(AndroidComponentsExtension::class.java).onVariants { variant ->
variant.components.filterIsInstance<ApplicationVariantImpl>().forEach {
publishBuildArtifacts(it as ComponentImpl<*>, getApplicationAdditionalSpec())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we move publishBuildArtifacts, etc. to a new file dynamicFeaturesSupport.kt to highlight the reason from this added source?

Comment on lines +467 to +468
OutputSpec(InternalArtifactType.PACKAGED_RES, ArtifactType.ANDROID_RES, RUNTIME_ELEMENTS_ONLY),
OutputSpec(InternalArtifactType.SYMBOL_LIST_WITH_PACKAGE_NAME, ArtifactType.SYMBOL_LIST_WITH_PACKAGE_NAME)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inline

Comment on lines +461 to +464
private val API_AND_RUNTIME_ELEMENTS: List<AndroidArtifacts.PublishedConfigType> =
listOf(API_ELEMENTS, RUNTIME_ELEMENTS)
private val RUNTIME_ELEMENTS_ONLY: List<AndroidArtifacts.PublishedConfigType> =
listOf(RUNTIME_ELEMENTS)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inline

*
* Publish intermediate artifacts in the BuildArtifactsHolder in addition to default PublishingSpecs.
*
* ignore original publishInfo: VariantPublishingInfo? parameter as we are not using publication config
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be passed as null, since the signature of com.android.build.gradle.internal.scope.ArtifactPublishingUtil.kt#publishBuildArtifacts is:

fun publishBuildArtifacts(
    creationConfig: ComponentCreationConfig,
    publishInfo: VariantPublishingInfo?
) {

@jrodbx
Copy link
Collaborator

jrodbx commented May 14, 2024

The proposed solution is to replicate the logic in ArtifactPublishingUtil.kt to map above artifacts to their respective ArtifactType like what ComponentTypeImpl.LIBRARY does.

Why mimic what ComponentTypeImpl.LIBRARY does? Is a dynamic feature considered a LIBRARY component type for the purposes of publishing?

However, this approach requires application module to apply PaparazziPlugin, even if it does not have any snapshot tests of its own.

How come? Any ideas how we can avoid this? Android Studio doesn't require this, so it'd be an odd user experience to require this.

@jrodbx jrodbx requested a review from geoff-powell May 14, 2024 20:17
@jrodbx jrodbx marked this pull request as draft May 14, 2024 20:24
@kevinzheng-ap
Copy link
Collaborator Author

Ok, so this is an awesome solution given your source digging into AGP internals, but I'm concerned this depends on internals too much and will break every time we bump AGP.

I'm curious if there is a DSL one level up that exposes this to plugin or build script authors. In other words,

Looking at line 105 of ArtifactPublishingUtil in AGP 8.2.2:

fun publishBuildArtifacts(
    creationConfig: ComponentCreationConfig,
    publishInfo: VariantPublishingInfo?
) {
    for (outputSpec in PublishingSpecs.getVariantPublishingSpec(creationConfig.componentType).outputs) {

ultimately, it looks like this approach replaces publishInfo with the additionalSpecs needed to drive this business logic correctly. Is there a way via DSL to configure the variant publishing spec such that:

PublishingSpecs.getVariantPublishingSpec(creationConfig.componentType).outputs

in AGP internal will execute the desired path in your implementation of publishBuildArtifacts?

PublishingSpecs.getVariantPublishingSpec is referring to a private static map which is initialized with hardcoded builder. I did not find a way to intercept the initialization and add more artifact type there. And it is immutable after initialization.

@kevinzheng-ap
Copy link
Collaborator Author

kevinzheng-ap commented May 18, 2024

The proposed solution is to replicate the logic in ArtifactPublishingUtil.kt to map above artifacts to their respective ArtifactType like what ComponentTypeImpl.LIBRARY does.

Why mimic what ComponentTypeImpl.LIBRARY does? Is a dynamic feature considered a LIBRARY component type for the purposes of publishing?

However, this approach requires application module to apply PaparazziPlugin, even if it does not have any snapshot tests of its own.

How come? Any ideas how we can avoid this? Android Studio doesn't require this, so it'd be an odd user experience to require this.

The root cause is not dynamic feature itself, but its dependency application module. PACKAGED_RES and SYMBOL_LIST_WITH_PACKAGE_NAME are not provided for application module who is published as ComponentTypeImpl.BASE_APK. Then these artifacts are not available for dynamic feature to consume.

So the propose solution is actually to mimic what ComponentTypeImpl.LIBRARY does for application module. In order to do that, we have to apply PaparazziPlugin to application module to provide these additional artifacts. I understand it is weird user experience to do that approach. I'm open to hearing other options.

How come? Any ideas how we can avoid this? Android Studio doesn't require this

As I have investigated, Android Studio gets application module as an instance of AndroidFacet, so it can access resources from there directly rather than getting resources package names from dependencies' published artifacts types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants