287 lines
17 KiB
Markdown
287 lines
17 KiB
Markdown
On each step, keep the user informed of the progress by displaying the output.
|
|
|
|
### 1. Requirements
|
|
|
|
- R8 Version: 9.3.7-dev or later
|
|
|
|
### 2. Generate proto
|
|
|
|
The report and files must be generated at `{project_root}/tmp/r8analysis`. If
|
|
the folder is not present, create it. For example:
|
|
|
|
mkdir -p "$PWD/tmp/r8analysis"
|
|
|
|
### 3. Remove existing files
|
|
|
|
To make sure that this invocation doesn't source data from previous runs, remove
|
|
the intermediate files `keepruleradius.json` and `analysis_result.txt` and
|
|
remove the proto files in the `{project_root}/tmp/r8analysis` folder. Example
|
|
bash commands:
|
|
|
|
# Remove the intermediate JSON and the directory containing protobuf files
|
|
rm tmp/r8analysis/keepruleradius.json
|
|
rm tmp/r8analysis/*.pb
|
|
|
|
# Copy the previous result to history before deleting the analysis
|
|
if [ -f tmp/r8analysis/analysis_result.txt ];
|
|
then cat tmp/r8analysis/analysis_result.txt > tmp/r8analysis/history.txt &&
|
|
rm tmp/r8analysis/analysis_result.txt; fi
|
|
|
|
### 4. Generate the Configuration Analyzer report
|
|
|
|
Run the R8 enabled build with the system property
|
|
"-Dcom.android.tools.r8.dumpkeepradiustodirectory=$PWD/tmp/r8analysis" to
|
|
generate Configuration Analyzer report
|
|
|
|
./gradlew assembleRelease \
|
|
-Dcom.android.tools.r8.dumpkeepradiustodirectory=$PWD/tmp/r8analysis
|
|
|
|
### 5. Convert to JSON
|
|
|
|
To convert the generated protobuf files in `{project_root}/tmp/r8analysis` into
|
|
json, run the following script. The json must be generated in
|
|
`{project_root}/tmp/r8analysis`. Ensure `keep_radius_pb2.py` (from Step 10) is
|
|
in the same directory.
|
|
|
|
import sys
|
|
import os
|
|
import glob
|
|
from google.protobuf import json_format
|
|
import keep_radius_pb2
|
|
|
|
def convert_pb_to_json(input_pb_path, output_json_path):
|
|
bundle = keep_radius_pb2.BlastRadiusContainer()
|
|
|
|
try:
|
|
with open(input_pb_path, "rb") as pb_file:
|
|
binary_data = pb_file.read()
|
|
except Exception as e:
|
|
print(f"Error reading file {input_pb_path}: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
try:
|
|
bundle.ParseFromString(binary_data)
|
|
except Exception as e:
|
|
print(f"Error parsing protobuf: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
try:
|
|
json_string = json_format.MessageToJson(
|
|
bundle,
|
|
always_print_fields_with_no_presence=True,
|
|
preserving_proto_field_name=True,
|
|
indent=4
|
|
)
|
|
with open(output_json_path, "w", encoding="utf-8") as json_file:
|
|
json_file.write(json_string)
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error writing JSON: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
if __name__ == "__main__":
|
|
input_pb = sys.argv[1] if len(sys.argv) > 1 else None
|
|
if not input_pb:
|
|
pb_files = glob.glob("tmp/r8analysis/*.pb")
|
|
if not pb_files:
|
|
print("Error: No .pb file found in tmp/r8analysis", file=sys.stderr)
|
|
sys.exit(1)
|
|
input_pb = sorted(pb_files)[-1] # Use the most recent one
|
|
output_json = sys.argv[2] if len(sys.argv) > 2 else "tmp/r8analysis/keepruleradius.json"
|
|
if not convert_pb_to_json(input_pb, output_json):
|
|
sys.exit(1)
|
|
|
|
### 6. Analyze
|
|
|
|
Run the following analysis script on the generated JSON to get the impact of the
|
|
keep rules and sort it.
|
|
|
|
import json, sys
|
|
|
|
def analyze(path):
|
|
try:
|
|
with open(path, 'r') as f:
|
|
d = json.load(f)
|
|
except Exception as e:
|
|
print(f"Error loading JSON: {e}")
|
|
return
|
|
|
|
# Build reference map
|
|
c_map = {c.get('id'): set(c.get('constraints', [])) for c in d.get('keep_constraints_table', [])}
|
|
r_map = {r.get('id'): c_map.get(r.get('constraints_id'), set()) for r in d.get('keep_rule_blast_radius_table', [])}
|
|
|
|
tot_opt = tot_obf = tot_shr = tot_items = 0
|
|
|
|
# Tally constraints across all kept items
|
|
for tbl in ('kept_class_info_table', 'kept_field_info_table', 'kept_method_info_table'):
|
|
for i in d.get(tbl, []):
|
|
tot_items += 1
|
|
kb = i.get('kept_by', [])
|
|
if any('DONT_OPTIMIZE' in r_map.get(r, set()) for r in kb): tot_opt += 1
|
|
if any('DONT_OBFUSCATE' in r_map.get(r, set()) for r in kb): tot_obf += 1
|
|
if any('DONT_SHRINK' in r_map.get(r, set()) for r in kb): tot_shr += 1
|
|
|
|
# Find denominator
|
|
bi = d.get('build_info', {})
|
|
live = sum(int(bi.get(k, 0)) for k in ('live_class_count', 'live_field_count', 'live_method_count'))
|
|
denom = live if live > 0 else tot_items
|
|
|
|
# Check for globals
|
|
globals_src = [g.get('source', '').lower() for g in d.get('global_keep_rule_blast_radius_table', [])]
|
|
def score(cnt, flag):
|
|
if any(flag in src for src in globals_src): return 0.0
|
|
return max(0.0, 100.0 - ((cnt / denom * 100) if denom > 0 else 0))
|
|
|
|
result = [
|
|
f"Optimization Score: {score(tot_opt, '-dontoptimize'):.2f}%",
|
|
f"Obfuscation Score: {score(tot_obf, '-dontobfuscate'):.2f}%",
|
|
f"Shrinking Score: {score(tot_shr, '-dontshrink'):.2f}%"
|
|
]
|
|
for line in result:
|
|
print(line)
|
|
with open("tmp/r8analysis/analysis_result.txt", "w") as f:
|
|
f.write("\n".join(result))
|
|
|
|
if __name__ == "__main__":
|
|
path = sys.argv[1] if len(sys.argv) > 1 else "tmp/r8analysis/keepruleradius.json"
|
|
analyze(path)
|
|
|
|
Outputs `analysis_result.txt` containing scores and rule impacts.
|
|
|
|
### 7. Report impactful rules
|
|
|
|
Identify the keep rules with the highest impact and the subsumed rules using the
|
|
following script.
|
|
|
|
import json, sys
|
|
|
|
def report(path):
|
|
try:
|
|
with open(path, 'r') as f:
|
|
data = json.load(f)
|
|
except Exception as e:
|
|
print(f"Error loading JSON: {e}")
|
|
return
|
|
|
|
# Calculate denominator for percentage
|
|
bi = data.get('build_info', {})
|
|
live = sum(int(bi.get(k, 0)) for k in ('live_class_count', 'live_field_count', 'live_method_count'))
|
|
denom = live if live > 0 else sum(len(data.get(tbl, [])) for tbl in ('kept_class_info_table', 'kept_field_info_table', 'kept_method_info_table'))
|
|
|
|
processed = []
|
|
for r in data.get('keep_rule_blast_radius_table', []):
|
|
br = r.get('blast_radius', {})
|
|
c, f, m = len(br.get('class_blast_radius', [])), len(br.get('field_blast_radius', [])), len(br.get('method_blast_radius', []))
|
|
impact = c + f + m
|
|
if impact == 0:
|
|
continue
|
|
impact_pct = (impact / denom * 100) if denom > 0 else 0.0
|
|
processed.append({
|
|
'id': r.get('id'),
|
|
'source': r.get('source'),
|
|
'impact': impact,
|
|
'impact_pct': f"{impact_pct:.2f}%",
|
|
'classes': c,
|
|
'fields': f,
|
|
'methods': m,
|
|
'subsumed_by': br.get('subsumed_by', [])
|
|
})
|
|
|
|
processed.sort(key=lambda x: x['impact'], reverse=True)
|
|
|
|
# Output JSON for the agent to fetch and process
|
|
print(json.dumps({
|
|
"top_5_impact_keep_rules": [r for r in processed if not r['subsumed_by']][:5],
|
|
"subsumed": [r for r in processed if r['subsumed_by']]
|
|
}, indent=2))
|
|
|
|
if __name__ == "__main__":
|
|
report("tmp/r8analysis/keepruleradius.json")
|
|
|
|
Add this data to the `analysis_result.txt` with the top impactful rules and
|
|
subsumed rules.
|
|
|
|
### 8. Compare with previous report
|
|
|
|
If `{project_root}/tmp/r8analysis/history.txt` exists, use the following script
|
|
to compare the previous run. Use this to compare with the current values
|
|
|
|
### 9. Remove generated files
|
|
|
|
After the final report and analysis results are generated, remove the
|
|
intermediate files `keepruleradius.json` and `analysis_result.txt` and remove
|
|
the proto files in "{project_root}/tmp/r8analysis" folder
|
|
|
|
rm tmp/r8analysis/keepruleradius.json
|
|
rm tmp/r8analysis/*.pb
|
|
|
|
### 10. Protobuf Python bindings
|
|
|
|
The following script `keep_radius_pb2.py` is required by the conversion script
|
|
in Step 5.
|
|
|
|
from google.protobuf import descriptor as _descriptor
|
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
from google.protobuf import runtime_version as _runtime_version
|
|
from google.protobuf import symbol_database as _symbol_database
|
|
from google.protobuf.internal import builder as _builder
|
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
_runtime_version.Domain.PUBLIC,
|
|
6,
|
|
33,
|
|
4,
|
|
'',
|
|
'keep_radius.proto'
|
|
)
|
|
# @@protoc_insertion_point(imports)
|
|
|
|
_sym_db = _symbol_database.Default()
|
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11keep_radius.proto\x12&com.android.tools.r8.blastradius.proto\"\x9f\x02\n\x13KeepRuleBlastRadius\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x16\n\x0e\x63onstraints_id\x18\x03 \x01(\x05\x12\x46\n\x06origin\x18\x04 \x01(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.TextFileOrigin\x12I\n\x0c\x62last_radius\x18\x05 \x01(\x0b\x32\x33.com.android.tools.r8.blastradius.proto.BlastRadius\x12\x41\n\x04tags\x18\x06 \x03(\x0e\x32\x33.com.android.tools.r8.blastradius.proto.KeepRuleTag\"w\n\x0b\x42lastRadius\x12\x13\n\x0bsubsumed_by\x18\x01 \x03(\x05\x12\x1a\n\x12\x63lass_blast_radius\x18\x02 \x03(\x05\x12\x1a\n\x12\x66ield_blast_radius\x18\x03 \x03(\x05\x12\x1b\n\x13method_blast_radius\x18\x04 \x03(\x05\"\x7f\n\x19GlobalKeepRuleBlastRadius\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x46\n\x06origin\x18\x03 \x01(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.TextFileOrigin\"j\n\x0fKeepConstraints\x12\n\n\x02id\x18\x01 \x01(\x05\x12K\n\x0b\x63onstraints\x18\x02 \x03(\x0e\x32\x36.com.android.tools.r8.blastradius.proto.KeepConstraint\"`\n\rKeptClassInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"`\n\rKeptFieldInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x66ield_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"b\n\x0eKeptMethodInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1b\n\x13method_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"a\n\x0e\x46ieldReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x19\n\x11type_reference_id\x18\x03 \x01(\x05\x12\x0c\n\x04name\x18\x04 \x01(\t\"c\n\x0fMethodReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x1a\n\x12proto_reference_id\x18\x03 \x01(\x05\x12\x0c\n\x04name\x18\x04 \x01(\t\"K\n\x0eProtoReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x15\n\rparameters_id\x18\x02 \x01(\x05\x12\x16\n\x0ereturn_type_id\x18\x03 \x01(\x05\"4\n\rTypeReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x17\n\x0fjava_descriptor\x18\x02 \x01(\t\";\n\x11TypeReferenceList\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12type_reference_ids\x18\x02 \x03(\x05\"\x9f\x01\n\nFileOrigin\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x10\n\x08\x66ilename\x18\x02 \x01(\t\x12Q\n\x10maven_coordinate\x18\x03 \x01(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MavenCoordinate\x12 \n\x18provided_by_build_system\x18\x04 \x01(\x08\"I\n\x14\x43lassFileInJarOrigin\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x02 \x01(\x05\x12\r\n\x05\x65ntry\x18\x03 \x01(\t\"T\n\x0eTextFileOrigin\x12\x16\n\x0e\x66ile_origin_id\x18\x01 \x01(\x05\x12\x13\n\x0bline_number\x18\x02 \x01(\x05\x12\x15\n\rcolumn_number\x18\x03 \x01(\x05\"U\n\x0fMavenCoordinate\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x10\n\x08group_id\x18\x02 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\"\x9a\x01\n\tBuildInfo\x12\x13\n\x0b\x63lass_count\x18\x01 \x01(\x05\x12\x13\n\x0b\x66ield_count\x18\x02 \x01(\x05\x12\x14\n\x0cmethod_count\x18\x03 \x01(\x05\x12\x18\n\x10live_class_count\x18\x04 \x01(\x05\x12\x18\n\x10live_field_count\x18\x05 \x01(\x05\x12\x19\n\x11live_method_count\x18\x06 \x01(\x05\"\xd5\n\n\x14\x42lastRadiusContainer\x12M\n\x11\x66ile_origin_table\x18\x01 \x03(\x0b\x32\x32.com.android.tools.r8.blastradius.proto.FileOrigin\x12\x64\n\x1e\x63lass_file_in_jar_origin_table\x18\x02 \x03(\x0b\x32<.com.android.tools.r8.blastradius.proto.ClassFileInJarOrigin\x12W\n\x16maven_coordinate_table\x18\x03 \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MavenCoordinate\x12U\n\x15\x66ield_reference_table\x18\x04 \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.FieldReference\x12W\n\x16method_reference_table\x18\x05 \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MethodReference\x12U\n\x15proto_reference_table\x18\x06 \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.ProtoReference\x12S\n\x14type_reference_table\x18\x07 \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.TypeReference\x12\\\n\x19type_reference_list_table\x18\x08 \x03(\x0b\x32\x39.com.android.tools.r8.blastradius.proto.TypeReferenceList\x12T\n\x15kept_class_info_table\x18\t \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.KeptClassInfo\x12T\n\x15kept_field_info_table\x18\n \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.KeptFieldInfo\x12V\n\x16kept_method_info_table\x18\x0b \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.KeptMethodInfo\x12W\n\x16keep_constraints_table\x18\x0c \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.KeepConstraints\x12\x61\n\x1ckeep_rule_blast_radius_table\x18\r \x03(\x0b\x32;.com.android.tools.r8.blastradius.proto.KeepRuleBlastRadius\x12n\n#global_keep_rule_blast_radius_table\x18\x0e \x03(\x0b\x32\x41.com.android.tools.r8.blastradius.proto.GlobalKeepRuleBlastRadius\x12\x45\n\nbuild_info\x18\x0f \x01(\x0b\x32\x31.com.android.tools.r8.blastradius.proto.BuildInfo*\x1f\n\x0bKeepRuleTag\x12\x10\n\x0cPACKAGE_WIDE\x10\x00*H\n\x0eKeepConstraint\x12\x12\n\x0e\x44ONT_OBFUSCATE\x10\x00\x12\x11\n\rDONT_OPTIMIZE\x10\x01\x12\x0f\n\x0b\x44ONT_SHRINK\x10\x02\x42\x45\n&com.android.tools.r8.blastradius.protoB\x19KeepRuleBlastRadiusProtosP\001\x62\x06proto3')
|
|
|
|
_globals = globals()
|
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'keep_radius_pb2', _globals)
|
|
if not _descriptor._USE_C_DESCRIPTORS:
|
|
_globals['DESCRIPTOR']._loaded_options = None
|
|
_globals['DESCRIPTOR']._serialized_options = b'\n&com.android.tools.r8.blastradius.protoB\031KeepRuleBlastRadiusProtosP\001'
|
|
_globals['_KEEPRULETAG']._serialized_start = 3332
|
|
_globals['_KEEPRULETAG']._serialized_end = 3363
|
|
_globals['_KEEPCONSTRAINT']._serialized_start = 3365
|
|
_globals['_KEEPCONSTRAINT']._serialized_end = 3437
|
|
_globals['_KEEPRULEBLASTRADIUS']._serialized_start = 62
|
|
_globals['_KEEPRULEBLASTRADIUS']._serialized_end = 349
|
|
_globals['_BLASTRADIUS']._serialized_start = 351
|
|
_globals['_BLASTRADIUS']._serialized_end = 470
|
|
_globals['_GLOBALKEEPRULEBLASTRADIUS']._serialized_start = 472
|
|
_globals['_GLOBALKEEPRULEBLASTRADIUS']._serialized_end = 599
|
|
_globals['_KEEPCONSTRAINTS']._serialized_start = 601
|
|
_globals['_KEEPCONSTRAINTS']._serialized_end = 707
|
|
_globals['_KEPTCLASSINFO']._serialized_start = 709
|
|
_globals['_KEPTCLASSINFO']._serialized_end = 805
|
|
_globals['_KEPTFIELDINFO']._serialized_start = 807
|
|
_globals['_KEPTFIELDINFO']._serialized_end = 903
|
|
_globals['_KEPTMETHODINFO']._serialized_start = 905
|
|
_globals['_KEPTMETHODINFO']._serialized_end = 1003
|
|
_globals['_FIELDREFERENCE']._serialized_start = 1005
|
|
_globals['_FIELDREFERENCE']._serialized_end = 1102
|
|
_globals['_METHODREFERENCE']._serialized_start = 1104
|
|
_globals['_METHODREFERENCE']._serialized_end = 1203
|
|
_globals['_PROTOREFERENCE']._serialized_start = 1205
|
|
_globals['_PROTOREFERENCE']._serialized_end = 1280
|
|
_globals['_TYPEREFERENCE']._serialized_start = 1282
|
|
_globals['_TYPEREFERENCE']._serialized_end = 1334
|
|
_globals['_TYPEREFERENCELIST']._serialized_start = 1336
|
|
_globals['_TYPEREFERENCELIST']._serialized_end = 1395
|
|
_globals['_FILEORIGIN']._serialized_start = 1398
|
|
_globals['_FILEORIGIN']._serialized_end = 1557
|
|
_globals['_CLASSFILEINJARORIGIN']._serialized_start = 1559
|
|
_globals['_CLASSFILEINJARORIGIN']._serialized_end = 1632
|
|
_globals['_TEXTFILEORIGIN']._serialized_start = 1634
|
|
_globals['_TEXTFILEORIGIN']._serialized_end = 1718
|
|
_globals['_MAVENCOORDINATE']._serialized_start = 1720
|
|
_globals['_MAVENCOORDINATE']._serialized_end = 1805
|
|
_globals['_BUILDINFO']._serialized_start = 1808
|
|
_globals['_BUILDINFO']._serialized_end = 1962
|
|
_globals['_BLASTRADIUSCONTAINER']._serialized_start = 1965
|
|
_globals['_BLASTRADIUSCONTAINER']._serialized_end = 3330
|
|
# @@protoc_insertion_point(module_scope) |