1#!/usr/bin/env python3 2# 3# Copyright (C) 2023 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17""" 18Generate the SBOM of the current target product in SPDX format. 19Usage example: 20 generate-sbom.py --output_file out/target/product/vsoc_x86_64/sbom.spdx \ 21 --metadata out/target/product/vsoc_x86_64/sbom-metadata.csv \ 22 --build_version $(cat out/target/product/vsoc_x86_64/build_fingerprint.txt) \ 23 --product_mfr=Google 24""" 25 26import argparse 27import csv 28import datetime 29import google.protobuf.text_format as text_format 30import hashlib 31import os 32import metadata_file_pb2 33import sbom_data 34import sbom_writers 35 36 37# Package type 38PKG_SOURCE = 'SOURCE' 39PKG_UPSTREAM = 'UPSTREAM' 40PKG_PREBUILT = 'PREBUILT' 41 42# Security tag 43NVD_CPE23 = 'NVD-CPE2.3:' 44 45# Report 46ISSUE_NO_METADATA = 'No metadata generated in Make for installed files:' 47ISSUE_NO_METADATA_FILE = 'No METADATA file found for installed file:' 48ISSUE_METADATA_FILE_INCOMPLETE = 'METADATA file incomplete:' 49ISSUE_UNKNOWN_SECURITY_TAG_TYPE = 'Unknown security tag type:' 50ISSUE_INSTALLED_FILE_NOT_EXIST = 'Non-exist installed files:' 51INFO_METADATA_FOUND_FOR_PACKAGE = 'METADATA file found for packages:' 52 53SOONG_PREBUILT_MODULE_TYPES = [ 54 'android_app_import', 55 'android_library_import', 56 'cc_prebuilt_binary', 57 'cc_prebuilt_library', 58 'cc_prebuilt_library_headers', 59 'cc_prebuilt_library_shared', 60 'cc_prebuilt_library_static', 61 'cc_prebuilt_object', 62 'dex_import', 63 'java_import', 64 'java_sdk_library_import', 65 'java_system_modules_import', 66 'libclang_rt_prebuilt_library_static', 67 'libclang_rt_prebuilt_library_shared', 68 'llvm_prebuilt_library_static', 69 'ndk_prebuilt_object', 70 'ndk_prebuilt_shared_stl', 71 'nkd_prebuilt_static_stl', 72 'prebuilt_apex', 73 'prebuilt_bootclasspath_fragment', 74 'prebuilt_dsp', 75 'prebuilt_firmware', 76 'prebuilt_kernel_modules', 77 'prebuilt_rfsa', 78 'prebuilt_root', 79 'rust_prebuilt_dylib', 80 'rust_prebuilt_library', 81 'rust_prebuilt_rlib', 82 'vndk_prebuilt_shared', 83] 84 85THIRD_PARTY_IDENTIFIER_TYPES = [ 86 # Types defined in metadata_file.proto 87 'Git', 88 'SVN', 89 'Hg', 90 'Darcs', 91 'VCS', 92 'Archive', 93 'PrebuiltByAlphabet', 94 'LocalSource', 95 'Other', 96 # OSV ecosystems defined at https://ossf.github.io/osv-schema/#affectedpackage-field. 97 'Go', 98 'npm', 99 'OSS-Fuzz', 100 'PyPI', 101 'RubyGems', 102 'crates.io', 103 'Hackage', 104 'GHC', 105 'Packagist', 106 'Maven', 107 'NuGet', 108 'Linux', 109 'Debian', 110 'Alpine', 111 'Hex', 112 'Android', 113 'GitHub Actions', 114 'Pub', 115 'ConanCenter', 116 'Rocky Linux', 117 'AlmaLinux', 118 'Bitnami', 119 'Photon OS', 120 'CRAN', 121 'Bioconductor', 122 'SwiftURL' 123] 124 125 126def get_args(): 127 parser = argparse.ArgumentParser() 128 parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Print more information.') 129 parser.add_argument('--output_file', required=True, help='The generated SBOM file in SPDX format.') 130 parser.add_argument('--metadata', required=True, help='The SBOM metadata file path.') 131 parser.add_argument('--build_version', required=True, help='The build version.') 132 parser.add_argument('--product_mfr', required=True, help='The product manufacturer.') 133 parser.add_argument('--module_name', help='The module name. If specified, the generated SBOM is for the module.') 134 parser.add_argument('--json', action='store_true', default=False, help='Generated SBOM file in SPDX JSON format') 135 parser.add_argument('--unbundled_apk', action='store_true', default=False, help='Generate SBOM for unbundled APKs') 136 parser.add_argument('--unbundled_apex', action='store_true', default=False, help='Generate SBOM for unbundled APEXs') 137 138 return parser.parse_args() 139 140 141def log(*info): 142 if args.verbose: 143 for i in info: 144 print(i) 145 146 147def new_package_id(package_name, type): 148 return f'SPDXRef-{type}-{sbom_data.encode_for_spdxid(package_name)}' 149 150 151def new_file_id(file_path): 152 return f'SPDXRef-{sbom_data.encode_for_spdxid(file_path)}' 153 154 155def checksum(file_path): 156 h = hashlib.sha1() 157 if os.path.islink(file_path): 158 h.update(os.readlink(file_path).encode('utf-8')) 159 else: 160 with open(file_path, 'rb') as f: 161 h.update(f.read()) 162 return f'SHA1: {h.hexdigest()}' 163 164 165def is_soong_prebuilt_module(file_metadata): 166 return (file_metadata['soong_module_type'] and 167 file_metadata['soong_module_type'] in SOONG_PREBUILT_MODULE_TYPES) 168 169 170def is_source_package(file_metadata): 171 module_path = file_metadata['module_path'] 172 return module_path.startswith('external/') and not is_prebuilt_package(file_metadata) 173 174 175def is_prebuilt_package(file_metadata): 176 module_path = file_metadata['module_path'] 177 if module_path: 178 return (module_path.startswith('prebuilts/') or 179 is_soong_prebuilt_module(file_metadata) or 180 file_metadata['is_prebuilt_make_module']) 181 182 kernel_module_copy_files = file_metadata['kernel_module_copy_files'] 183 if kernel_module_copy_files and not kernel_module_copy_files.startswith('ANDROID-GEN:'): 184 return True 185 186 return False 187 188 189def get_source_package_info(file_metadata, metadata_file_path): 190 """Return source package info exists in its METADATA file, currently including name, security tag 191 and external SBOM reference. 192 193 See go/android-spdx and go/android-sbom-gen for more details. 194 """ 195 if not metadata_file_path: 196 return file_metadata['module_path'], [] 197 198 metadata_proto = metadata_file_protos[metadata_file_path] 199 external_refs = [] 200 for tag in metadata_proto.third_party.security.tag: 201 if tag.lower().startswith((NVD_CPE23 + 'cpe:2.3:').lower()): 202 external_refs.append( 203 sbom_data.PackageExternalRef(category=sbom_data.PackageExternalRefCategory.SECURITY, 204 type=sbom_data.PackageExternalRefType.cpe23Type, 205 locator=tag.removeprefix(NVD_CPE23))) 206 elif tag.lower().startswith((NVD_CPE23 + 'cpe:/').lower()): 207 external_refs.append( 208 sbom_data.PackageExternalRef(category=sbom_data.PackageExternalRefCategory.SECURITY, 209 type=sbom_data.PackageExternalRefType.cpe22Type, 210 locator=tag.removeprefix(NVD_CPE23))) 211 212 if metadata_proto.name: 213 return metadata_proto.name, external_refs 214 else: 215 return os.path.basename(metadata_file_path), external_refs # return the directory name only as package name 216 217 218def get_prebuilt_package_name(file_metadata, metadata_file_path): 219 """Return name of a prebuilt package, which can be from the METADATA file, metadata file path, 220 module path or kernel module's source path if the installed file is a kernel module. 221 222 See go/android-spdx and go/android-sbom-gen for more details. 223 """ 224 name = None 225 if metadata_file_path: 226 metadata_proto = metadata_file_protos[metadata_file_path] 227 if metadata_proto.name: 228 name = metadata_proto.name 229 else: 230 name = metadata_file_path 231 elif file_metadata['module_path']: 232 name = file_metadata['module_path'] 233 elif file_metadata['kernel_module_copy_files']: 234 src_path = file_metadata['kernel_module_copy_files'].split(':')[0] 235 name = os.path.dirname(src_path) 236 237 return name.removeprefix('prebuilts/').replace('/', '-') 238 239 240def get_metadata_file_path(file_metadata): 241 """Search for METADATA file of a package and return its path.""" 242 metadata_path = '' 243 if file_metadata['module_path']: 244 metadata_path = file_metadata['module_path'] 245 elif file_metadata['kernel_module_copy_files']: 246 metadata_path = os.path.dirname(file_metadata['kernel_module_copy_files'].split(':')[0]) 247 248 while metadata_path and not os.path.exists(metadata_path + '/METADATA'): 249 metadata_path = os.path.dirname(metadata_path) 250 251 return metadata_path 252 253 254def get_package_version(metadata_file_path): 255 """Return a package's version in its METADATA file.""" 256 if not metadata_file_path: 257 return None 258 metadata_proto = metadata_file_protos[metadata_file_path] 259 return metadata_proto.third_party.version 260 261 262def get_package_homepage(metadata_file_path): 263 """Return a package's homepage URL in its METADATA file.""" 264 if not metadata_file_path: 265 return None 266 metadata_proto = metadata_file_protos[metadata_file_path] 267 if metadata_proto.third_party.homepage: 268 return metadata_proto.third_party.homepage 269 for url in metadata_proto.third_party.url: 270 if url.type == metadata_file_pb2.URL.Type.HOMEPAGE: 271 return url.value 272 273 return None 274 275 276def get_package_download_location(metadata_file_path): 277 """Return a package's code repository URL in its METADATA file.""" 278 if not metadata_file_path: 279 return None 280 metadata_proto = metadata_file_protos[metadata_file_path] 281 if metadata_proto.third_party.url: 282 urls = sorted(metadata_proto.third_party.url, key=lambda url: url.type) 283 if urls[0].type != metadata_file_pb2.URL.Type.HOMEPAGE: 284 return urls[0].value 285 elif len(urls) > 1: 286 return urls[1].value 287 288 return None 289 290 291def get_sbom_fragments(installed_file_metadata, metadata_file_path): 292 """Return SPDX fragment of source/prebuilt packages, which usually contains a SOURCE/PREBUILT 293 package, a UPSTREAM package and an external SBOM document reference if sbom_ref defined in its 294 METADATA file. 295 296 See go/android-spdx and go/android-sbom-gen for more details. 297 """ 298 external_doc_ref = None 299 packages = [] 300 relationships = [] 301 302 # Info from METADATA file 303 homepage = get_package_homepage(metadata_file_path) 304 version = get_package_version(metadata_file_path) 305 download_location = get_package_download_location(metadata_file_path) 306 307 if is_source_package(installed_file_metadata): 308 # Source fork packages 309 name, external_refs = get_source_package_info(installed_file_metadata, metadata_file_path) 310 source_package_id = new_package_id(name, PKG_SOURCE) 311 source_package = sbom_data.Package(id=source_package_id, name=name, version=args.build_version, 312 download_location=sbom_data.VALUE_NONE, 313 supplier='Organization: ' + args.product_mfr, 314 external_refs=external_refs) 315 316 upstream_package_id = new_package_id(name, PKG_UPSTREAM) 317 upstream_package = sbom_data.Package(id=upstream_package_id, name=name, version=version, 318 supplier=('Organization: ' + homepage) if homepage else sbom_data.VALUE_NOASSERTION, 319 download_location=download_location) 320 packages += [source_package, upstream_package] 321 relationships.append(sbom_data.Relationship(id1=source_package_id, 322 relationship=sbom_data.RelationshipType.VARIANT_OF, 323 id2=upstream_package_id)) 324 elif is_prebuilt_package(installed_file_metadata): 325 # Prebuilt fork packages 326 name = get_prebuilt_package_name(installed_file_metadata, metadata_file_path) 327 prebuilt_package_id = new_package_id(name, PKG_PREBUILT) 328 prebuilt_package = sbom_data.Package(id=prebuilt_package_id, 329 name=name, 330 download_location=sbom_data.VALUE_NONE, 331 version=version if version else args.build_version, 332 supplier='Organization: ' + args.product_mfr) 333 334 upstream_package_id = new_package_id(name, PKG_UPSTREAM) 335 upstream_package = sbom_data.Package(id=upstream_package_id, name=name, version = version, 336 supplier=('Organization: ' + homepage) if homepage else sbom_data.VALUE_NOASSERTION, 337 download_location=download_location) 338 packages += [prebuilt_package, upstream_package] 339 relationships.append(sbom_data.Relationship(id1=prebuilt_package_id, 340 relationship=sbom_data.RelationshipType.VARIANT_OF, 341 id2=upstream_package_id)) 342 343 if metadata_file_path: 344 metadata_proto = metadata_file_protos[metadata_file_path] 345 if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref': 346 sbom_url = metadata_proto.third_party.sbom_ref.url 347 sbom_checksum = metadata_proto.third_party.sbom_ref.checksum 348 upstream_element_id = metadata_proto.third_party.sbom_ref.element_id 349 if sbom_url and sbom_checksum and upstream_element_id: 350 doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{sbom_data.encode_for_spdxid(name)}' 351 external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id, 352 uri=sbom_url, 353 checksum=sbom_checksum) 354 relationships.append( 355 sbom_data.Relationship(id1=upstream_package_id, 356 relationship=sbom_data.RelationshipType.VARIANT_OF, 357 id2=doc_ref_id + ':' + upstream_element_id)) 358 359 return external_doc_ref, packages, relationships 360 361 362def save_report(report_file_path, report): 363 with open(report_file_path, 'w', encoding='utf-8') as report_file: 364 for type, issues in report.items(): 365 report_file.write(type + '\n') 366 for issue in issues: 367 report_file.write('\t' + issue + '\n') 368 report_file.write('\n') 369 370 371# Validate the metadata generated by Make for installed files and report if there is no metadata. 372def installed_file_has_metadata(installed_file_metadata, report): 373 installed_file = installed_file_metadata['installed_file'] 374 module_path = installed_file_metadata['module_path'] 375 product_copy_files = installed_file_metadata['product_copy_files'] 376 kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files'] 377 is_platform_generated = installed_file_metadata['is_platform_generated'] 378 379 if (not module_path and 380 not product_copy_files and 381 not kernel_module_copy_files and 382 not is_platform_generated and 383 not installed_file.endswith('.fsv_meta')): 384 report[ISSUE_NO_METADATA].append(installed_file) 385 return False 386 387 return True 388 389 390# Validate identifiers in a package's METADATA. 391# 1) Only known identifier type is allowed 392# 2) Only one identifier's primary_source can be true 393def validate_package_metadata(metadata_file_path, package_metadata): 394 primary_source_found = False 395 for identifier in package_metadata.third_party.identifier: 396 if identifier.type not in THIRD_PARTY_IDENTIFIER_TYPES: 397 sys.exit(f'Unknown value of third_party.identifier.type in {metadata_file_path}/METADATA: {identifier.type}.') 398 if primary_source_found and identifier.primary_source: 399 sys.exit( 400 f'Field "primary_source" is set to true in multiple third_party.identifier in {metadata_file_path}/METADATA.') 401 primary_source_found = identifier.primary_source 402 403 404def report_metadata_file(metadata_file_path, installed_file_metadata, report): 405 if metadata_file_path: 406 report[INFO_METADATA_FOUND_FOR_PACKAGE].append( 407 'installed_file: {}, module_path: {}, METADATA file: {}'.format( 408 installed_file_metadata['installed_file'], 409 installed_file_metadata['module_path'], 410 metadata_file_path + '/METADATA')) 411 412 package_metadata = metadata_file_pb2.Metadata() 413 with open(metadata_file_path + '/METADATA', 'rt') as f: 414 text_format.Parse(f.read(), package_metadata) 415 416 validate_package_metadata(metadata_file_path, package_metadata) 417 418 if not metadata_file_path in metadata_file_protos: 419 metadata_file_protos[metadata_file_path] = package_metadata 420 if not package_metadata.name: 421 report[ISSUE_METADATA_FILE_INCOMPLETE].append(f'{metadata_file_path}/METADATA does not has "name"') 422 423 if not package_metadata.third_party.version: 424 report[ISSUE_METADATA_FILE_INCOMPLETE].append( 425 f'{metadata_file_path}/METADATA does not has "third_party.version"') 426 427 for tag in package_metadata.third_party.security.tag: 428 if not tag.startswith(NVD_CPE23): 429 report[ISSUE_UNKNOWN_SECURITY_TAG_TYPE].append( 430 f'Unknown security tag type: {tag} in {metadata_file_path}/METADATA') 431 else: 432 report[ISSUE_NO_METADATA_FILE].append( 433 "installed_file: {}, module_path: {}".format( 434 installed_file_metadata['installed_file'], installed_file_metadata['module_path'])) 435 436 437def generate_sbom_for_unbundled_apk(): 438 with open(args.metadata, newline='') as sbom_metadata_file: 439 reader = csv.DictReader(sbom_metadata_file) 440 doc = sbom_data.Document(name=args.build_version, 441 namespace=f'https://www.google.com/sbom/spdx/android/{args.build_version}', 442 creators=['Organization: ' + args.product_mfr]) 443 for installed_file_metadata in reader: 444 installed_file = installed_file_metadata['installed_file'] 445 if args.output_file != installed_file_metadata['build_output_path'] + '.spdx.json': 446 continue 447 448 module_path = installed_file_metadata['module_path'] 449 package_id = new_package_id(module_path, PKG_PREBUILT) 450 package = sbom_data.Package(id=package_id, 451 name=module_path, 452 version=args.build_version, 453 supplier='Organization: ' + args.product_mfr) 454 file_id = new_file_id(installed_file) 455 file = sbom_data.File(id=file_id, 456 name=installed_file, 457 checksum=checksum(installed_file_metadata['build_output_path'])) 458 relationship = sbom_data.Relationship(id1=file_id, 459 relationship=sbom_data.RelationshipType.GENERATED_FROM, 460 id2=package_id) 461 doc.add_package(package) 462 doc.files.append(file) 463 doc.describes = file_id 464 doc.add_relationship(relationship) 465 doc.created = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') 466 break 467 468 with open(args.output_file, 'w', encoding='utf-8') as file: 469 sbom_writers.JSONWriter.write(doc, file) 470 fragment_file = args.output_file.removesuffix('.spdx.json') + '-fragment.spdx' 471 with open(fragment_file, 'w', encoding='utf-8') as file: 472 sbom_writers.TagValueWriter.write(doc, file, fragment=True) 473 474 475def main(): 476 global args 477 args = get_args() 478 log('Args:', vars(args)) 479 480 if args.unbundled_apk: 481 generate_sbom_for_unbundled_apk() 482 return 483 484 global metadata_file_protos 485 metadata_file_protos = {} 486 487 product_package_id = sbom_data.SPDXID_PRODUCT 488 product_package_name = sbom_data.PACKAGE_NAME_PRODUCT 489 if args.module_name: 490 # Build SBOM of a module so use the module name instead. 491 product_package_id = f'SPDXRef-{sbom_data.encode_for_spdxid(args.module_name)}' 492 product_package_name = args.module_name 493 product_package = sbom_data.Package(id=product_package_id, 494 name=product_package_name, 495 download_location=sbom_data.VALUE_NONE, 496 version=args.build_version, 497 supplier='Organization: ' + args.product_mfr, 498 files_analyzed=True) 499 doc_name = args.build_version 500 if args.module_name: 501 doc_name = f'{args.build_version}/{args.module_name}' 502 doc = sbom_data.Document(name=doc_name, 503 namespace=f'https://www.google.com/sbom/spdx/android/{doc_name}', 504 creators=['Organization: ' + args.product_mfr], 505 describes=product_package_id) 506 if not args.unbundled_apex: 507 doc.packages.append(product_package) 508 509 doc.packages.append(sbom_data.Package(id=sbom_data.SPDXID_PLATFORM, 510 name=sbom_data.PACKAGE_NAME_PLATFORM, 511 download_location=sbom_data.VALUE_NONE, 512 version=args.build_version, 513 supplier='Organization: ' + args.product_mfr)) 514 515 # Report on some issues and information 516 report = { 517 ISSUE_NO_METADATA: [], 518 ISSUE_NO_METADATA_FILE: [], 519 ISSUE_METADATA_FILE_INCOMPLETE: [], 520 ISSUE_UNKNOWN_SECURITY_TAG_TYPE: [], 521 ISSUE_INSTALLED_FILE_NOT_EXIST: [], 522 INFO_METADATA_FOUND_FOR_PACKAGE: [], 523 } 524 525 # Scan the metadata in CSV file and create the corresponding package and file records in SPDX 526 with open(args.metadata, newline='') as sbom_metadata_file: 527 reader = csv.DictReader(sbom_metadata_file) 528 for installed_file_metadata in reader: 529 installed_file = installed_file_metadata['installed_file'] 530 module_path = installed_file_metadata['module_path'] 531 product_copy_files = installed_file_metadata['product_copy_files'] 532 kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files'] 533 build_output_path = installed_file_metadata['build_output_path'] 534 is_static_lib = installed_file_metadata['is_static_lib'] 535 536 if not installed_file_has_metadata(installed_file_metadata, report): 537 continue 538 if not is_static_lib and not (os.path.islink(build_output_path) or os.path.isfile(build_output_path)): 539 # Ignore non-existing static library files for now since they are not shipped on devices. 540 report[ISSUE_INSTALLED_FILE_NOT_EXIST].append(installed_file) 541 continue 542 543 file_id = new_file_id(installed_file) 544 # TODO(b/285453664): Soong should report the information of statically linked libraries to Make. 545 # This happens when a different sanitized version of static libraries is used in linking. 546 # As a workaround, use the following SHA1 checksum for static libraries created by Soong, if .a files could not be 547 # located correctly because Soong doesn't report the information to Make. 548 sha1 = 'SHA1: da39a3ee5e6b4b0d3255bfef95601890afd80709' # SHA1 of empty string 549 if os.path.islink(build_output_path) or os.path.isfile(build_output_path): 550 sha1 = checksum(build_output_path) 551 doc.files.append(sbom_data.File(id=file_id, 552 name=installed_file, 553 checksum=sha1)) 554 555 if not is_static_lib: 556 if not args.unbundled_apex: 557 product_package.file_ids.append(file_id) 558 elif len(doc.files) > 1: 559 doc.add_relationship(sbom_data.Relationship(doc.files[0].id, sbom_data.RelationshipType.CONTAINS, file_id)) 560 561 if is_source_package(installed_file_metadata) or is_prebuilt_package(installed_file_metadata): 562 metadata_file_path = get_metadata_file_path(installed_file_metadata) 563 report_metadata_file(metadata_file_path, installed_file_metadata, report) 564 565 # File from source fork packages or prebuilt fork packages 566 external_doc_ref, pkgs, rels = get_sbom_fragments(installed_file_metadata, metadata_file_path) 567 if len(pkgs) > 0: 568 if external_doc_ref: 569 doc.add_external_ref(external_doc_ref) 570 for p in pkgs: 571 doc.add_package(p) 572 for rel in rels: 573 doc.add_relationship(rel) 574 fork_package_id = pkgs[0].id # The first package should be the source/prebuilt fork package 575 doc.add_relationship(sbom_data.Relationship(id1=file_id, 576 relationship=sbom_data.RelationshipType.GENERATED_FROM, 577 id2=fork_package_id)) 578 elif module_path or installed_file_metadata['is_platform_generated']: 579 # File from PLATFORM package 580 doc.add_relationship(sbom_data.Relationship(id1=file_id, 581 relationship=sbom_data.RelationshipType.GENERATED_FROM, 582 id2=sbom_data.SPDXID_PLATFORM)) 583 elif product_copy_files: 584 # Format of product_copy_files: <source path>:<dest path> 585 src_path = product_copy_files.split(':')[0] 586 # So far product_copy_files are copied from directory system, kernel, hardware, frameworks and device, 587 # so process them as files from PLATFORM package 588 doc.add_relationship(sbom_data.Relationship(id1=file_id, 589 relationship=sbom_data.RelationshipType.GENERATED_FROM, 590 id2=sbom_data.SPDXID_PLATFORM)) 591 elif installed_file.endswith('.fsv_meta'): 592 # See build/make/core/Makefile:2988 593 doc.add_relationship(sbom_data.Relationship(id1=file_id, 594 relationship=sbom_data.RelationshipType.GENERATED_FROM, 595 id2=sbom_data.SPDXID_PLATFORM)) 596 elif kernel_module_copy_files.startswith('ANDROID-GEN'): 597 # For the four files generated for _dlkm, _ramdisk partitions 598 # See build/make/core/Makefile:323 599 doc.add_relationship(sbom_data.Relationship(id1=file_id, 600 relationship=sbom_data.RelationshipType.GENERATED_FROM, 601 id2=sbom_data.SPDXID_PLATFORM)) 602 603 # Process static libraries and whole static libraries the installed file links to 604 static_libs = installed_file_metadata['static_libraries'] 605 whole_static_libs = installed_file_metadata['whole_static_libraries'] 606 all_static_libs = (static_libs + ' ' + whole_static_libs).strip() 607 if all_static_libs: 608 for lib in all_static_libs.split(' '): 609 doc.add_relationship(sbom_data.Relationship(id1=file_id, 610 relationship=sbom_data.RelationshipType.STATIC_LINK, 611 id2=new_file_id(lib + '.a'))) 612 613 if args.unbundled_apex: 614 doc.describes = doc.files[0].id 615 616 # Save SBOM records to output file 617 doc.generate_packages_verification_code() 618 doc.created = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') 619 prefix = args.output_file 620 if prefix.endswith('.spdx'): 621 prefix = prefix.removesuffix('.spdx') 622 elif prefix.endswith('.spdx.json'): 623 prefix = prefix.removesuffix('.spdx.json') 624 625 output_file = prefix + '.spdx' 626 if args.unbundled_apex: 627 output_file = prefix + '-fragment.spdx' 628 with open(output_file, 'w', encoding="utf-8") as file: 629 sbom_writers.TagValueWriter.write(doc, file, fragment=args.unbundled_apex) 630 if args.json: 631 with open(prefix + '.spdx.json', 'w', encoding="utf-8") as file: 632 sbom_writers.JSONWriter.write(doc, file) 633 634 save_report(prefix + '-gen-report.txt', report) 635 636 637if __name__ == '__main__': 638 main() 639