@@ -1016,6 +1016,7 @@ def pack_ota_payload(self) -> None:
10161016 self ._sync_partition_info_from_stock_avb (profile )
10171017 self ._apply_avb_to_custom_images (current_partitions )
10181018 self ._rebuild_vbmeta_images (current_partitions )
1019+ self ._generate_care_map ()
10191020
10201021 self ._generate_meta_info ()
10211022 self ._copy_build_props ()
@@ -1530,6 +1531,90 @@ def _verify_avb_images(self) -> None:
15301531 self .shell .run (cmd , env = self ._avb_env ())
15311532 self .logger .info ("AVB verification succeeded for vbmeta chain." )
15321533
1534+ def _generate_care_map (self ) -> None :
1535+ """Generate care_map.pb for AVB hashtree-enabled partitions.
1536+
1537+ Identifies partitions with AVB hashtree enabled from the stock AVB profile
1538+ and generates META/care_map.pb using the care_map_generator tool.
1539+ """
1540+ profile = self ._collect_stock_avb_profile ()
1541+ if not profile :
1542+ self .logger .info ("Skipping care_map generation: stock AVB profile unavailable." )
1543+ return
1544+
1545+ care_map_gen = self .ota_tools_dir / "bin" / "care_map_generator"
1546+ if not care_map_gen .exists ():
1547+ self .logger .warning ("care_map_generator not found, skipping care_map.pb generation." )
1548+ return
1549+
1550+ # Partitions that should be included in care_map (hashtree-enabled partitions)
1551+ # These are typically the large dynamic partitions with dm-verity
1552+ hashtree_parts = cast (set [str ], profile .get ("hashtree_parts" , set ()))
1553+ if not hashtree_parts :
1554+ self .logger .info ("No hashtree partitions found, skipping care_map generation." )
1555+ return
1556+
1557+ # Build care_map text content
1558+ # Format:
1559+ # /partition_name
1560+ # extent_start,extent_end
1561+ # Where extents represent the ranges that need to be checked during OTA
1562+ care_map_lines : List [str ] = []
1563+ for part in sorted (hashtree_parts ):
1564+ image = self .images_out / f"{ part } .img"
1565+ if not image .exists ():
1566+ self .logger .debug ("Skipping %s: image not found" , part )
1567+ continue
1568+
1569+ # Add partition entry
1570+ care_map_lines .append (f"/{ part } " )
1571+
1572+ # Calculate image extents (in 4K blocks for care_map)
1573+ # The care_map format uses block numbers (typically 4KB blocks)
1574+ image_size = image .stat ().st_size
1575+ block_size = 4096
1576+ num_blocks = (image_size + block_size - 1 ) // block_size
1577+
1578+ # Add extent covering the entire image
1579+ # Format: start_block,end_block (exclusive)
1580+ care_map_lines .append (f"0,{ num_blocks } " )
1581+
1582+ self .logger .debug ("Added %s to care_map (%d blocks)" , part , num_blocks )
1583+
1584+ if not care_map_lines :
1585+ self .logger .info ("No valid partitions for care_map, skipping generation." )
1586+ return
1587+
1588+ # Write intermediate text file
1589+ care_map_txt = self .meta_out / "care_map.txt"
1590+ self .meta_out .mkdir (parents = True , exist_ok = True )
1591+ care_map_txt .write_text ("\n " .join (care_map_lines ) + "\n " , encoding = "utf-8" )
1592+
1593+ # Generate care_map.pb using care_map_generator
1594+ care_map_pb = self .meta_out / "care_map.pb"
1595+ cmd = [
1596+ str (care_map_gen ),
1597+ str (care_map_txt ),
1598+ str (care_map_pb ),
1599+ ]
1600+
1601+ try :
1602+ self .shell .run (cmd , env = self ._avb_env ())
1603+ self .logger .info (
1604+ "Generated care_map.pb with partitions: %s" ,
1605+ ", " .join (sorted (hashtree_parts ))
1606+ )
1607+ except subprocess .CalledProcessError as e :
1608+ self .logger .warning ("Failed to generate care_map.pb: %s" , e )
1609+ # Don't fail the build if care_map generation fails
1610+ # Clean up the text file if pb generation failed
1611+ if care_map_txt .exists ():
1612+ care_map_txt .unlink ()
1613+ else :
1614+ # Clean up intermediate text file after successful generation
1615+ if care_map_txt .exists ():
1616+ care_map_txt .unlink ()
1617+
15331618 def _build_avb_misc_lines_from_stock (self , partition_list : List [str ]) -> List [str ]:
15341619 """Infer AVB-related misc_info lines from stock images."""
15351620 profile = self ._collect_stock_avb_profile ()
@@ -1737,11 +1822,17 @@ def _copy_build_props(self) -> None:
17371822 "system_ext" : "SYSTEM_EXT" ,
17381823 "vendor" : "VENDOR" ,
17391824 "odm" : "ODM" ,
1825+ "system_dlkm" : "SYSTEM_DLKM" ,
1826+ "vendor_dlkm" : "VENDOR_DLKM" ,
1827+ "odm_dlkm" : "ODM_DLKM" ,
1828+ "product_dlkm" : "PRODUCT_DLKM" ,
17401829 }
17411830 for part_lower , part_upper in mapping .items ():
17421831 src_prop : Optional [Path ] = self .ctx .get_target_prop_file (part_lower )
17431832 if src_prop and src_prop .exists ():
1744- shutil .copy2 (src_prop , self .product_out / part_upper / "build.prop" )
1833+ dest_dir = self .product_out / part_upper
1834+ dest_dir .mkdir (parents = True , exist_ok = True )
1835+ shutil .copy2 (src_prop , dest_dir / "build.prop" )
17451836 else :
17461837 self .logger .warning (f"build.prop for { part_lower } not found." )
17471838
0 commit comments