6060 _log = "log" ,
6161)
6262load ("//rules:visibility.bzl" , "PROJECT_VISIBILITY" )
63+ load ("//rules/android_binary:r8.bzl" , "process_feature_splits_r8" )
6364load ("@rules_java//java/common:java_common.bzl" , "java_common" )
6465load (":android_feature_module_rule.bzl" , "get_feature_module_paths" )
6566load (":attrs.bzl" , "ANDROID_APPLICATION_ATTRS" )
@@ -85,7 +86,8 @@ def _process_feature_module(
8586 base_apk = None ,
8687 feature_target = None ,
8788 java_package = None ,
88- application_id = None ):
89+ application_id = None ,
90+ feature_dex = None ):
8991 manifest = _create_feature_manifest (
9092 ctx ,
9193 base_apk ,
@@ -98,14 +100,11 @@ def _process_feature_module(
98100 _common .get_host_javabase (ctx ),
99101 )
100102
101- has_dex = bool (feature_target [AndroidFeatureModuleInfo ].has_dex )
102- if has_dex :
103+ has_code = bool (feature_target [AndroidFeatureModuleInfo ].has_code )
104+ if has_code :
103105 binary = feature_target [AndroidFeatureModuleInfo ].binary [ApkInfo ].unsigned_apk
104106 else :
105- # Remove all dexes from the feature module apk. jvm / resources are not
106- # supported in feature modules. The android_feature_module rule has
107- # already validated that there are no transitive sources / resources, but
108- # we may get dexes via e.g. the legacy dex or the record globals.
107+ # Remove the dex files.
109108 binary = ctx .actions .declare_file (ctx .label .name + "/" + feature_target .label .name + "_filtered.apk" )
110109 _common .filter_zip_exclude (
111110 ctx ,
@@ -119,7 +118,7 @@ def _process_feature_module(
119118
120119 # Create res .proto-apk_, output depending on whether further manipulations
121120 # are required after busybox. This prevents action conflicts.
122- if has_native_libs or is_asset_pack or has_dex :
121+ if has_native_libs or is_asset_pack or has_code :
123122 res_apk = ctx .actions .declare_file (ctx .label .name + "/" + feature_target .label .name + "/res.proto-ap_" )
124123 else :
125124 res_apk = out
@@ -159,7 +158,7 @@ def _process_feature_module(
159158 application_id = application_id ,
160159 )
161160
162- if not is_asset_pack and not has_native_libs and not has_dex :
161+ if not is_asset_pack and not has_native_libs and not has_code :
163162 return
164163
165164 if is_asset_pack :
@@ -178,11 +177,16 @@ def _process_feature_module(
178177 _common .filter_zip_include (ctx , binary , native_libs , ["lib/*" ])
179178 inputs_to_merge .append (native_libs )
180179
181- # Extract dex files from split binary if has_dex is enabled
182- if has_dex :
183- dex_files = ctx .actions .declare_file (ctx .label .name + "/" + feature_target .label .name + "/dex_files.zip" )
184- _common .filter_zip_include (ctx , binary , dex_files , ["*.dex" , "classes*.dex" ])
185- inputs_to_merge .append (dex_files )
180+ # Add dex files if has_code is enabled
181+ if has_code :
182+ if feature_dex :
183+ # Use R8 feature splits dex output
184+ inputs_to_merge .append (feature_dex )
185+ else :
186+ # Extract dex files from split binary
187+ dex_files = ctx .actions .declare_file (ctx .label .name + "/" + feature_target .label .name + "/dex_files.zip" )
188+ _common .filter_zip_include (ctx , binary , dex_files , ["*.dex" , "classes*.dex" ])
189+ inputs_to_merge .append (dex_files )
186190
187191 # Merge into output
188192 # Note: singlejar adds META-INF/MANIFEST.MF, but bundletool_module_builder filters it out
@@ -216,7 +220,7 @@ def _create_feature_manifest(
216220 args .add (info .title_id )
217221 args .add (info .fused )
218222 args .add (aapt2 .executable )
219- args .add (info .has_dex )
223+ args .add (info .has_code )
220224
221225 ctx .actions .run (
222226 executable = feature_manifest_script ,
@@ -235,7 +239,7 @@ def _create_feature_manifest(
235239 # Rule has a manifest (already validated by android_feature_module).
236240 # Generate a priority manifest and then merge the user supplied manifest.
237241 is_asset_pack = feature_target [AndroidFeatureModuleInfo ].is_asset_pack
238- has_dex = feature_target [AndroidFeatureModuleInfo ].has_dex
242+ has_code = feature_target [AndroidFeatureModuleInfo ].has_code
239243 priority_manifest = ctx .actions .declare_file (
240244 ctx .label .name + "/" + feature_target .label .name + "/Priority_AndroidManifest.xml" ,
241245 )
@@ -247,7 +251,7 @@ def _create_feature_manifest(
247251 args .add (aapt2 .executable )
248252 args .add (info .manifest )
249253 args .add (is_asset_pack )
250- args .add (has_dex )
254+ args .add (has_code )
251255
252256 ctx .actions .run (
253257 executable = priority_feature_manifest_script ,
@@ -320,8 +324,28 @@ def _validate_manifest_values(manifest_values):
320324def _impl (ctx ):
321325 _validate_manifest_values (ctx .attr .manifest_values )
322326
323- # Convert base apk to .proto_ap_
324327 base_apk = ctx .attr .base_module [ApkInfo ].unsigned_apk
328+
329+ # Collect feature deploy jars and proguard specs for R8 processing (together with base module's deploy jar and proguard specs)
330+ feature_deploy_jars = {}
331+ all_proguard_specs = list (ctx .files .proguard_specs )
332+ for feature in ctx .attr .feature_modules :
333+ info = feature [AndroidFeatureModuleInfo ]
334+ if info .has_code :
335+ feature_deploy_jars [info .feature_name ] = info .pre_dexed_jar
336+ if info .proguards_specs :
337+ all_proguard_specs .extend (info .proguards_specs )
338+
339+ r8_output = None
340+ if feature_deploy_jars and ctx .files .proguard_specs :
341+ r8_output = process_feature_splits_r8 (
342+ ctx = ctx ,
343+ base_deploy_jar = ctx .attr .base_module [AndroidPreDexJarInfo ].pre_dex_jar ,
344+ feature_deploy_jars = feature_deploy_jars ,
345+ proguard_specs = all_proguard_specs ,
346+ )
347+
348+ # Convert base apk to proto format
325349 base_proto_apk = ctx .actions .declare_file (ctx .label .name + "/modules/base.proto-ap_" )
326350 _aapt .convert (
327351 ctx ,
@@ -332,6 +356,28 @@ def _impl(ctx):
332356 )
333357
334358 modules = []
359+
360+ # Replace base dex files with optimized dex files from process_feature_splits_r8.
361+ if r8_output :
362+ base_no_dex = ctx .actions .declare_file (ctx .label .name + "/modules/base_no_dex.zip" )
363+ _common .filter_zip_exclude (
364+ ctx ,
365+ output = base_no_dex ,
366+ input = base_proto_apk ,
367+ filters = [".*\\ .dex" ],
368+ )
369+
370+ base_proto_apk_with_optimized_dex = ctx .actions .declare_file (ctx .label .name + "/modules/base_optimized.proto-ap_" )
371+ _java .singlejar (
372+ ctx ,
373+ inputs = [base_no_dex , r8_output .base_dex_zip ],
374+ output = base_proto_apk_with_optimized_dex ,
375+ java_toolchain = _common .get_java_toolchain (ctx ),
376+ )
377+ base_proto_apk_for_module = base_proto_apk_with_optimized_dex
378+ else :
379+ base_proto_apk_for_module = base_proto_apk
380+
335381 base_module = ctx .actions .declare_file (
336382 base_proto_apk .basename + ".zip" ,
337383 sibling = base_proto_apk ,
@@ -340,15 +386,20 @@ def _impl(ctx):
340386 _bundletool .proto_apk_to_module (
341387 ctx ,
342388 out = base_module ,
343- proto_apk = base_proto_apk ,
344- # RuntimeEnabledSdkConfig should only be added to the base module.
389+ proto_apk = base_proto_apk_for_module ,
345390 runtime_enabled_sdk_config = _generate_runtime_enabled_sdk_config (ctx , base_proto_apk ),
346391 bundletool_module_builder =
347392 get_android_toolchain (ctx ).bundletool_module_builder .files_to_run ,
348393 )
349394
350- # Convert each feature to module zip.
395+ # Process each feature module
351396 for feature in ctx .attr .feature_modules :
397+ info = feature [AndroidFeatureModuleInfo ]
398+
399+ feature_dex = None
400+ if r8_output and info .feature_name in r8_output .feature_dex_zips :
401+ feature_dex = r8_output .feature_dex_zips [info .feature_name ]
402+
352403 proto_apk = ctx .actions .declare_file (
353404 "%s.proto-ap_" % feature .label .name ,
354405 sibling = base_proto_apk ,
@@ -360,6 +411,7 @@ def _impl(ctx):
360411 feature_target = feature ,
361412 java_package = _java .resolve_package_from_label (ctx .label , ctx .attr .custom_package ),
362413 application_id = ctx .attr .manifest_values .get ("applicationId" ),
414+ feature_dex = feature_dex ,
363415 )
364416 module = ctx .actions .declare_file (
365417 proto_apk .basename + ".zip" ,
@@ -375,7 +427,9 @@ def _impl(ctx):
375427 )
376428
377429 metadata = dict ()
378- if ProguardMappingInfo in ctx .attr .base_module :
430+ if r8_output :
431+ metadata ["com.android.tools.build.obfuscation/proguard.map" ] = r8_output .proguard_mapping
432+ elif ProguardMappingInfo in ctx .attr .base_module :
379433 metadata ["com.android.tools.build.obfuscation/proguard.map" ] = ctx .attr .base_module [ProguardMappingInfo ].proguard_mapping
380434
381435 if ctx .file .device_group_config :
@@ -524,9 +578,18 @@ def android_application_macro(_android_binary, **attrs):
524578 module_targets = get_feature_module_paths (feature_module )
525579 deps = deps + [str (module_targets .title_lib )]
526580
581+ tags = attrs .pop ("tags" , [])
582+ proguard_specs = attrs .pop ("proguard_specs" , [])
583+
584+ # When feature modules exist, don't pass proguard_specs to base binary.
585+ # Unified R8 at android_application level will handle optimization.
586+ base_proguard_specs = [] if feature_modules else proguard_specs
587+
527588 _android_binary (
528589 name = base_split_name ,
529590 deps = deps ,
591+ tags = tags ,
592+ proguard_specs = base_proguard_specs ,
530593 ** attrs
531594 )
532595
@@ -544,6 +607,7 @@ def android_application_macro(_android_binary, **attrs):
544607 sdk_archives = sdk_archives ,
545608 sdk_bundles = sdk_bundles ,
546609 manifest_values = attrs .get ("manifest_values" ),
610+ proguard_specs = proguard_specs ,
547611 visibility = attrs .get ("visibility" , None ),
548- tags = attrs . get ( " tags" , []) ,
612+ tags = tags ,
549613 )
0 commit comments