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