xref: /aosp_15_r20/build/make/tools/sbom/gen_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) 2024 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  gen_sbom.py --output_file out/soong/sbom/aosp_cf_x86_64_phone/sbom.spdx \
21*9e94795aSAndroid Build Coastguard Worker              --metadata out/soong/metadata/aosp_cf_x86_64_phone/metadata.db \
22*9e94795aSAndroid Build Coastguard Worker              --product_out out/target/vsoc_x86_64
23*9e94795aSAndroid Build Coastguard Worker              --soong_out out/soong
24*9e94795aSAndroid Build Coastguard Worker              --build_version $(cat out/target/product/vsoc_x86_64/build_fingerprint.txt) \
25*9e94795aSAndroid Build Coastguard Worker              --product_mfr=Google
26*9e94795aSAndroid Build Coastguard Worker"""
27*9e94795aSAndroid Build Coastguard Worker
28*9e94795aSAndroid Build Coastguard Workerimport argparse
29*9e94795aSAndroid Build Coastguard Workerimport compliance_metadata
30*9e94795aSAndroid Build Coastguard Workerimport datetime
31*9e94795aSAndroid Build Coastguard Workerimport google.protobuf.text_format as text_format
32*9e94795aSAndroid Build Coastguard Workerimport hashlib
33*9e94795aSAndroid Build Coastguard Workerimport os
34*9e94795aSAndroid Build Coastguard Workerimport pathlib
35*9e94795aSAndroid Build Coastguard Workerimport queue
36*9e94795aSAndroid Build Coastguard Workerimport metadata_file_pb2
37*9e94795aSAndroid Build Coastguard Workerimport sbom_data
38*9e94795aSAndroid Build Coastguard Workerimport sbom_writers
39*9e94795aSAndroid Build Coastguard Worker
40*9e94795aSAndroid Build Coastguard Worker# Package type
41*9e94795aSAndroid Build Coastguard WorkerPKG_SOURCE = 'SOURCE'
42*9e94795aSAndroid Build Coastguard WorkerPKG_UPSTREAM = 'UPSTREAM'
43*9e94795aSAndroid Build Coastguard WorkerPKG_PREBUILT = 'PREBUILT'
44*9e94795aSAndroid Build Coastguard Worker
45*9e94795aSAndroid Build Coastguard Worker# Security tag
46*9e94795aSAndroid Build Coastguard WorkerNVD_CPE23 = 'NVD-CPE2.3:'
47*9e94795aSAndroid Build Coastguard Worker
48*9e94795aSAndroid Build Coastguard Worker# Report
49*9e94795aSAndroid Build Coastguard WorkerISSUE_NO_METADATA = 'No metadata generated in Make for installed files:'
50*9e94795aSAndroid Build Coastguard WorkerISSUE_NO_METADATA_FILE = 'No METADATA file found for installed file:'
51*9e94795aSAndroid Build Coastguard WorkerISSUE_METADATA_FILE_INCOMPLETE = 'METADATA file incomplete:'
52*9e94795aSAndroid Build Coastguard WorkerISSUE_UNKNOWN_SECURITY_TAG_TYPE = 'Unknown security tag type:'
53*9e94795aSAndroid Build Coastguard WorkerISSUE_INSTALLED_FILE_NOT_EXIST = 'Non-existent installed files:'
54*9e94795aSAndroid Build Coastguard WorkerISSUE_NO_MODULE_FOUND_FOR_STATIC_DEP = 'No module found for static dependency files:'
55*9e94795aSAndroid Build Coastguard WorkerINFO_METADATA_FOUND_FOR_PACKAGE = 'METADATA file found for packages:'
56*9e94795aSAndroid Build Coastguard Worker
57*9e94795aSAndroid Build Coastguard WorkerSOONG_PREBUILT_MODULE_TYPES = [
58*9e94795aSAndroid Build Coastguard Worker    'android_app_import',
59*9e94795aSAndroid Build Coastguard Worker    'android_library_import',
60*9e94795aSAndroid Build Coastguard Worker    'cc_prebuilt_binary',
61*9e94795aSAndroid Build Coastguard Worker    'cc_prebuilt_library',
62*9e94795aSAndroid Build Coastguard Worker    'cc_prebuilt_library_headers',
63*9e94795aSAndroid Build Coastguard Worker    'cc_prebuilt_library_shared',
64*9e94795aSAndroid Build Coastguard Worker    'cc_prebuilt_library_static',
65*9e94795aSAndroid Build Coastguard Worker    'cc_prebuilt_object',
66*9e94795aSAndroid Build Coastguard Worker    'dex_import',
67*9e94795aSAndroid Build Coastguard Worker    'java_import',
68*9e94795aSAndroid Build Coastguard Worker    'java_sdk_library_import',
69*9e94795aSAndroid Build Coastguard Worker    'java_system_modules_import',
70*9e94795aSAndroid Build Coastguard Worker    'libclang_rt_prebuilt_library_static',
71*9e94795aSAndroid Build Coastguard Worker    'libclang_rt_prebuilt_library_shared',
72*9e94795aSAndroid Build Coastguard Worker    'llvm_prebuilt_library_static',
73*9e94795aSAndroid Build Coastguard Worker    'ndk_prebuilt_object',
74*9e94795aSAndroid Build Coastguard Worker    'ndk_prebuilt_shared_stl',
75*9e94795aSAndroid Build Coastguard Worker    'nkd_prebuilt_static_stl',
76*9e94795aSAndroid Build Coastguard Worker    'prebuilt_apex',
77*9e94795aSAndroid Build Coastguard Worker    'prebuilt_bootclasspath_fragment',
78*9e94795aSAndroid Build Coastguard Worker    'prebuilt_dsp',
79*9e94795aSAndroid Build Coastguard Worker    'prebuilt_firmware',
80*9e94795aSAndroid Build Coastguard Worker    'prebuilt_kernel_modules',
81*9e94795aSAndroid Build Coastguard Worker    'prebuilt_rfsa',
82*9e94795aSAndroid Build Coastguard Worker    'prebuilt_root',
83*9e94795aSAndroid Build Coastguard Worker    'rust_prebuilt_dylib',
84*9e94795aSAndroid Build Coastguard Worker    'rust_prebuilt_library',
85*9e94795aSAndroid Build Coastguard Worker    'rust_prebuilt_rlib',
86*9e94795aSAndroid Build Coastguard Worker    'vndk_prebuilt_shared',
87*9e94795aSAndroid Build Coastguard Worker]
88*9e94795aSAndroid Build Coastguard Worker
89*9e94795aSAndroid Build Coastguard WorkerTHIRD_PARTY_IDENTIFIER_TYPES = [
90*9e94795aSAndroid Build Coastguard Worker    # Types defined in metadata_file.proto
91*9e94795aSAndroid Build Coastguard Worker    'Git',
92*9e94795aSAndroid Build Coastguard Worker    'SVN',
93*9e94795aSAndroid Build Coastguard Worker    'Hg',
94*9e94795aSAndroid Build Coastguard Worker    'Darcs',
95*9e94795aSAndroid Build Coastguard Worker    'VCS',
96*9e94795aSAndroid Build Coastguard Worker    'Archive',
97*9e94795aSAndroid Build Coastguard Worker    'PrebuiltByAlphabet',
98*9e94795aSAndroid Build Coastguard Worker    'LocalSource',
99*9e94795aSAndroid Build Coastguard Worker    'Other',
100*9e94795aSAndroid Build Coastguard Worker    # OSV ecosystems defined at https://ossf.github.io/osv-schema/#affectedpackage-field.
101*9e94795aSAndroid Build Coastguard Worker    'Go',
102*9e94795aSAndroid Build Coastguard Worker    'npm',
103*9e94795aSAndroid Build Coastguard Worker    'OSS-Fuzz',
104*9e94795aSAndroid Build Coastguard Worker    'PyPI',
105*9e94795aSAndroid Build Coastguard Worker    'RubyGems',
106*9e94795aSAndroid Build Coastguard Worker    'crates.io',
107*9e94795aSAndroid Build Coastguard Worker    'Hackage',
108*9e94795aSAndroid Build Coastguard Worker    'GHC',
109*9e94795aSAndroid Build Coastguard Worker    'Packagist',
110*9e94795aSAndroid Build Coastguard Worker    'Maven',
111*9e94795aSAndroid Build Coastguard Worker    'NuGet',
112*9e94795aSAndroid Build Coastguard Worker    'Linux',
113*9e94795aSAndroid Build Coastguard Worker    'Debian',
114*9e94795aSAndroid Build Coastguard Worker    'Alpine',
115*9e94795aSAndroid Build Coastguard Worker    'Hex',
116*9e94795aSAndroid Build Coastguard Worker    'Android',
117*9e94795aSAndroid Build Coastguard Worker    'GitHub Actions',
118*9e94795aSAndroid Build Coastguard Worker    'Pub',
119*9e94795aSAndroid Build Coastguard Worker    'ConanCenter',
120*9e94795aSAndroid Build Coastguard Worker    'Rocky Linux',
121*9e94795aSAndroid Build Coastguard Worker    'AlmaLinux',
122*9e94795aSAndroid Build Coastguard Worker    'Bitnami',
123*9e94795aSAndroid Build Coastguard Worker    'Photon OS',
124*9e94795aSAndroid Build Coastguard Worker    'CRAN',
125*9e94795aSAndroid Build Coastguard Worker    'Bioconductor',
126*9e94795aSAndroid Build Coastguard Worker    'SwiftURL'
127*9e94795aSAndroid Build Coastguard Worker]
128*9e94795aSAndroid Build Coastguard Worker
129*9e94795aSAndroid Build Coastguard Worker
130*9e94795aSAndroid Build Coastguard Workerdef get_args():
131*9e94795aSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser()
132*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Print more information.')
133*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug mode')
134*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('--output_file', required=True, help='The generated SBOM file in SPDX format.')
135*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('--metadata', required=True, help='The metadata DB file path.')
136*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('--product_out', required=True, help='The path of PRODUCT_OUT, e.g. out/target/product/vsoc_x86_64.')
137*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('--soong_out', required=True, help='The path of Soong output directory, e.g. out/soong')
138*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('--build_version', required=True, help='The build version.')
139*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('--product_mfr', required=True, help='The product manufacturer.')
140*9e94795aSAndroid Build Coastguard Worker  parser.add_argument('--json', action='store_true', default=False, help='Generated SBOM file in SPDX JSON format')
141*9e94795aSAndroid Build Coastguard Worker
142*9e94795aSAndroid Build Coastguard Worker  return parser.parse_args()
143*9e94795aSAndroid Build Coastguard Worker
144*9e94795aSAndroid Build Coastguard Worker
145*9e94795aSAndroid Build Coastguard Workerdef log(*info):
146*9e94795aSAndroid Build Coastguard Worker  if args.verbose:
147*9e94795aSAndroid Build Coastguard Worker    for i in info:
148*9e94795aSAndroid Build Coastguard Worker      print(i)
149*9e94795aSAndroid Build Coastguard Worker
150*9e94795aSAndroid Build Coastguard Worker
151*9e94795aSAndroid Build Coastguard Workerdef new_package_id(package_name, type):
152*9e94795aSAndroid Build Coastguard Worker  return f'SPDXRef-{type}-{sbom_data.encode_for_spdxid(package_name)}'
153*9e94795aSAndroid Build Coastguard Worker
154*9e94795aSAndroid Build Coastguard Worker
155*9e94795aSAndroid Build Coastguard Workerdef new_file_id(file_path):
156*9e94795aSAndroid Build Coastguard Worker  return f'SPDXRef-{sbom_data.encode_for_spdxid(file_path)}'
157*9e94795aSAndroid Build Coastguard Worker
158*9e94795aSAndroid Build Coastguard Worker
159*9e94795aSAndroid Build Coastguard Workerdef new_license_id(license_name):
160*9e94795aSAndroid Build Coastguard Worker  return f'LicenseRef-{sbom_data.encode_for_spdxid(license_name)}'
161*9e94795aSAndroid Build Coastguard Worker
162*9e94795aSAndroid Build Coastguard Worker
163*9e94795aSAndroid Build Coastguard Workerdef checksum(file_path):
164*9e94795aSAndroid Build Coastguard Worker  h = hashlib.sha1()
165*9e94795aSAndroid Build Coastguard Worker  if os.path.islink(file_path):
166*9e94795aSAndroid Build Coastguard Worker    h.update(os.readlink(file_path).encode('utf-8'))
167*9e94795aSAndroid Build Coastguard Worker  else:
168*9e94795aSAndroid Build Coastguard Worker    with open(file_path, 'rb') as f:
169*9e94795aSAndroid Build Coastguard Worker      h.update(f.read())
170*9e94795aSAndroid Build Coastguard Worker  return f'SHA1: {h.hexdigest()}'
171*9e94795aSAndroid Build Coastguard Worker
172*9e94795aSAndroid Build Coastguard Worker
173*9e94795aSAndroid Build Coastguard Workerdef is_soong_prebuilt_module(file_metadata):
174*9e94795aSAndroid Build Coastguard Worker  return (file_metadata['soong_module_type'] and
175*9e94795aSAndroid Build Coastguard Worker          file_metadata['soong_module_type'] in SOONG_PREBUILT_MODULE_TYPES)
176*9e94795aSAndroid Build Coastguard Worker
177*9e94795aSAndroid Build Coastguard Worker
178*9e94795aSAndroid Build Coastguard Workerdef is_source_package(file_metadata):
179*9e94795aSAndroid Build Coastguard Worker  module_path = file_metadata['module_path']
180*9e94795aSAndroid Build Coastguard Worker  return module_path.startswith('external/') and not is_prebuilt_package(file_metadata)
181*9e94795aSAndroid Build Coastguard Worker
182*9e94795aSAndroid Build Coastguard Worker
183*9e94795aSAndroid Build Coastguard Workerdef is_prebuilt_package(file_metadata):
184*9e94795aSAndroid Build Coastguard Worker  module_path = file_metadata['module_path']
185*9e94795aSAndroid Build Coastguard Worker  if module_path:
186*9e94795aSAndroid Build Coastguard Worker    return (module_path.startswith('prebuilts/') or
187*9e94795aSAndroid Build Coastguard Worker            is_soong_prebuilt_module(file_metadata) or
188*9e94795aSAndroid Build Coastguard Worker            file_metadata['is_prebuilt_make_module'])
189*9e94795aSAndroid Build Coastguard Worker
190*9e94795aSAndroid Build Coastguard Worker  kernel_module_copy_files = file_metadata['kernel_module_copy_files']
191*9e94795aSAndroid Build Coastguard Worker  if kernel_module_copy_files and not kernel_module_copy_files.startswith('ANDROID-GEN:'):
192*9e94795aSAndroid Build Coastguard Worker    return True
193*9e94795aSAndroid Build Coastguard Worker
194*9e94795aSAndroid Build Coastguard Worker  return False
195*9e94795aSAndroid Build Coastguard Worker
196*9e94795aSAndroid Build Coastguard Worker
197*9e94795aSAndroid Build Coastguard Workerdef get_source_package_info(file_metadata, metadata_file_path):
198*9e94795aSAndroid Build Coastguard Worker  """Return source package info exists in its METADATA file, currently including name, security tag
199*9e94795aSAndroid Build Coastguard Worker  and external SBOM reference.
200*9e94795aSAndroid Build Coastguard Worker
201*9e94795aSAndroid Build Coastguard Worker  See go/android-spdx and go/android-sbom-gen for more details.
202*9e94795aSAndroid Build Coastguard Worker  """
203*9e94795aSAndroid Build Coastguard Worker  if not metadata_file_path:
204*9e94795aSAndroid Build Coastguard Worker    return file_metadata['module_path'], []
205*9e94795aSAndroid Build Coastguard Worker
206*9e94795aSAndroid Build Coastguard Worker  metadata_proto = metadata_file_protos[metadata_file_path]
207*9e94795aSAndroid Build Coastguard Worker  external_refs = []
208*9e94795aSAndroid Build Coastguard Worker  for tag in metadata_proto.third_party.security.tag:
209*9e94795aSAndroid Build Coastguard Worker    if tag.lower().startswith((NVD_CPE23 + 'cpe:2.3:').lower()):
210*9e94795aSAndroid Build Coastguard Worker      external_refs.append(
211*9e94795aSAndroid Build Coastguard Worker          sbom_data.PackageExternalRef(category=sbom_data.PackageExternalRefCategory.SECURITY,
212*9e94795aSAndroid Build Coastguard Worker                                       type=sbom_data.PackageExternalRefType.cpe23Type,
213*9e94795aSAndroid Build Coastguard Worker                                       locator=tag.removeprefix(NVD_CPE23)))
214*9e94795aSAndroid Build Coastguard Worker    elif tag.lower().startswith((NVD_CPE23 + 'cpe:/').lower()):
215*9e94795aSAndroid Build Coastguard Worker      external_refs.append(
216*9e94795aSAndroid Build Coastguard Worker          sbom_data.PackageExternalRef(category=sbom_data.PackageExternalRefCategory.SECURITY,
217*9e94795aSAndroid Build Coastguard Worker                                       type=sbom_data.PackageExternalRefType.cpe22Type,
218*9e94795aSAndroid Build Coastguard Worker                                       locator=tag.removeprefix(NVD_CPE23)))
219*9e94795aSAndroid Build Coastguard Worker
220*9e94795aSAndroid Build Coastguard Worker  if metadata_proto.name:
221*9e94795aSAndroid Build Coastguard Worker    return metadata_proto.name, external_refs
222*9e94795aSAndroid Build Coastguard Worker  else:
223*9e94795aSAndroid Build Coastguard Worker    return os.path.basename(metadata_file_path), external_refs  # return the directory name only as package name
224*9e94795aSAndroid Build Coastguard Worker
225*9e94795aSAndroid Build Coastguard Worker
226*9e94795aSAndroid Build Coastguard Workerdef get_prebuilt_package_name(file_metadata, metadata_file_path):
227*9e94795aSAndroid Build Coastguard Worker  """Return name of a prebuilt package, which can be from the METADATA file, metadata file path,
228*9e94795aSAndroid Build Coastguard Worker  module path or kernel module's source path if the installed file is a kernel module.
229*9e94795aSAndroid Build Coastguard Worker
230*9e94795aSAndroid Build Coastguard Worker  See go/android-spdx and go/android-sbom-gen for more details.
231*9e94795aSAndroid Build Coastguard Worker  """
232*9e94795aSAndroid Build Coastguard Worker  name = None
233*9e94795aSAndroid Build Coastguard Worker  if metadata_file_path:
234*9e94795aSAndroid Build Coastguard Worker    metadata_proto = metadata_file_protos[metadata_file_path]
235*9e94795aSAndroid Build Coastguard Worker    if metadata_proto.name:
236*9e94795aSAndroid Build Coastguard Worker      name = metadata_proto.name
237*9e94795aSAndroid Build Coastguard Worker    else:
238*9e94795aSAndroid Build Coastguard Worker      name = metadata_file_path
239*9e94795aSAndroid Build Coastguard Worker  elif file_metadata['module_path']:
240*9e94795aSAndroid Build Coastguard Worker    name = file_metadata['module_path']
241*9e94795aSAndroid Build Coastguard Worker  elif file_metadata['kernel_module_copy_files']:
242*9e94795aSAndroid Build Coastguard Worker    src_path = file_metadata['kernel_module_copy_files'].split(':')[0]
243*9e94795aSAndroid Build Coastguard Worker    name = os.path.dirname(src_path)
244*9e94795aSAndroid Build Coastguard Worker
245*9e94795aSAndroid Build Coastguard Worker  return name.removeprefix('prebuilts/').replace('/', '-')
246*9e94795aSAndroid Build Coastguard Worker
247*9e94795aSAndroid Build Coastguard Worker
248*9e94795aSAndroid Build Coastguard Workerdef get_metadata_file_path(file_metadata):
249*9e94795aSAndroid Build Coastguard Worker  """Search for METADATA file of a package and return its path."""
250*9e94795aSAndroid Build Coastguard Worker  metadata_path = ''
251*9e94795aSAndroid Build Coastguard Worker  if file_metadata['module_path']:
252*9e94795aSAndroid Build Coastguard Worker    metadata_path = file_metadata['module_path']
253*9e94795aSAndroid Build Coastguard Worker  elif file_metadata['kernel_module_copy_files']:
254*9e94795aSAndroid Build Coastguard Worker    metadata_path = os.path.dirname(file_metadata['kernel_module_copy_files'].split(':')[0])
255*9e94795aSAndroid Build Coastguard Worker
256*9e94795aSAndroid Build Coastguard Worker  while metadata_path and not os.path.exists(metadata_path + '/METADATA'):
257*9e94795aSAndroid Build Coastguard Worker    metadata_path = os.path.dirname(metadata_path)
258*9e94795aSAndroid Build Coastguard Worker
259*9e94795aSAndroid Build Coastguard Worker  return metadata_path
260*9e94795aSAndroid Build Coastguard Worker
261*9e94795aSAndroid Build Coastguard Worker
262*9e94795aSAndroid Build Coastguard Workerdef get_package_version(metadata_file_path):
263*9e94795aSAndroid Build Coastguard Worker  """Return a package's version 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  return metadata_proto.third_party.version
268*9e94795aSAndroid Build Coastguard Worker
269*9e94795aSAndroid Build Coastguard Worker
270*9e94795aSAndroid Build Coastguard Workerdef get_package_homepage(metadata_file_path):
271*9e94795aSAndroid Build Coastguard Worker  """Return a package's homepage URL in its METADATA file."""
272*9e94795aSAndroid Build Coastguard Worker  if not metadata_file_path:
273*9e94795aSAndroid Build Coastguard Worker    return None
274*9e94795aSAndroid Build Coastguard Worker  metadata_proto = metadata_file_protos[metadata_file_path]
275*9e94795aSAndroid Build Coastguard Worker  if metadata_proto.third_party.homepage:
276*9e94795aSAndroid Build Coastguard Worker    return metadata_proto.third_party.homepage
277*9e94795aSAndroid Build Coastguard Worker  for url in metadata_proto.third_party.url:
278*9e94795aSAndroid Build Coastguard Worker    if url.type == metadata_file_pb2.URL.Type.HOMEPAGE:
279*9e94795aSAndroid Build Coastguard Worker      return url.value
280*9e94795aSAndroid Build Coastguard Worker
281*9e94795aSAndroid Build Coastguard Worker  return None
282*9e94795aSAndroid Build Coastguard Worker
283*9e94795aSAndroid Build Coastguard Worker
284*9e94795aSAndroid Build Coastguard Workerdef get_package_download_location(metadata_file_path):
285*9e94795aSAndroid Build Coastguard Worker  """Return a package's code repository URL in its METADATA file."""
286*9e94795aSAndroid Build Coastguard Worker  if not metadata_file_path:
287*9e94795aSAndroid Build Coastguard Worker    return None
288*9e94795aSAndroid Build Coastguard Worker  metadata_proto = metadata_file_protos[metadata_file_path]
289*9e94795aSAndroid Build Coastguard Worker  if metadata_proto.third_party.url:
290*9e94795aSAndroid Build Coastguard Worker    urls = sorted(metadata_proto.third_party.url, key=lambda url: url.type)
291*9e94795aSAndroid Build Coastguard Worker    if urls[0].type != metadata_file_pb2.URL.Type.HOMEPAGE:
292*9e94795aSAndroid Build Coastguard Worker      return urls[0].value
293*9e94795aSAndroid Build Coastguard Worker    elif len(urls) > 1:
294*9e94795aSAndroid Build Coastguard Worker      return urls[1].value
295*9e94795aSAndroid Build Coastguard Worker
296*9e94795aSAndroid Build Coastguard Worker  return None
297*9e94795aSAndroid Build Coastguard Worker
298*9e94795aSAndroid Build Coastguard Worker
299*9e94795aSAndroid Build Coastguard Workerdef get_license_text(license_files):
300*9e94795aSAndroid Build Coastguard Worker  license_text = ''
301*9e94795aSAndroid Build Coastguard Worker  for license_file in license_files:
302*9e94795aSAndroid Build Coastguard Worker    if args.debug:
303*9e94795aSAndroid Build Coastguard Worker      license_text += '#### Content from ' + license_file + '\n'
304*9e94795aSAndroid Build Coastguard Worker    else:
305*9e94795aSAndroid Build Coastguard Worker      license_text += pathlib.Path(license_file).read_text(errors='replace') + '\n\n'
306*9e94795aSAndroid Build Coastguard Worker  return license_text
307*9e94795aSAndroid Build Coastguard Worker
308*9e94795aSAndroid Build Coastguard Worker
309*9e94795aSAndroid Build Coastguard Workerdef get_sbom_fragments(installed_file_metadata, metadata_file_path):
310*9e94795aSAndroid Build Coastguard Worker  """Return SPDX fragment of source/prebuilt packages, which usually contains a SOURCE/PREBUILT
311*9e94795aSAndroid Build Coastguard Worker  package, a UPSTREAM package and an external SBOM document reference if sbom_ref defined in its
312*9e94795aSAndroid Build Coastguard Worker  METADATA file.
313*9e94795aSAndroid Build Coastguard Worker
314*9e94795aSAndroid Build Coastguard Worker  See go/android-spdx and go/android-sbom-gen for more details.
315*9e94795aSAndroid Build Coastguard Worker  """
316*9e94795aSAndroid Build Coastguard Worker  external_doc_ref = None
317*9e94795aSAndroid Build Coastguard Worker  packages = []
318*9e94795aSAndroid Build Coastguard Worker  relationships = []
319*9e94795aSAndroid Build Coastguard Worker  licenses = []
320*9e94795aSAndroid Build Coastguard Worker
321*9e94795aSAndroid Build Coastguard Worker  # Info from METADATA file
322*9e94795aSAndroid Build Coastguard Worker  homepage = get_package_homepage(metadata_file_path)
323*9e94795aSAndroid Build Coastguard Worker  version = get_package_version(metadata_file_path)
324*9e94795aSAndroid Build Coastguard Worker  download_location = get_package_download_location(metadata_file_path)
325*9e94795aSAndroid Build Coastguard Worker
326*9e94795aSAndroid Build Coastguard Worker  lics = db.get_package_licenses(installed_file_metadata['module_path'])
327*9e94795aSAndroid Build Coastguard Worker  if not lics:
328*9e94795aSAndroid Build Coastguard Worker    lics = db.get_package_licenses(metadata_file_path)
329*9e94795aSAndroid Build Coastguard Worker
330*9e94795aSAndroid Build Coastguard Worker  if lics:
331*9e94795aSAndroid Build Coastguard Worker    for license_name, license_files in lics.items():
332*9e94795aSAndroid Build Coastguard Worker      if not license_files:
333*9e94795aSAndroid Build Coastguard Worker        continue
334*9e94795aSAndroid Build Coastguard Worker      license_id = new_license_id(license_name)
335*9e94795aSAndroid Build Coastguard Worker      if license_name not in licenses_text:
336*9e94795aSAndroid Build Coastguard Worker        licenses_text[license_name] = get_license_text(license_files.split(' '))
337*9e94795aSAndroid Build Coastguard Worker      licenses.append(sbom_data.License(id=license_id, name=license_name, text=licenses_text[license_name]))
338*9e94795aSAndroid Build Coastguard Worker
339*9e94795aSAndroid Build Coastguard Worker  if is_source_package(installed_file_metadata):
340*9e94795aSAndroid Build Coastguard Worker    # Source fork packages
341*9e94795aSAndroid Build Coastguard Worker    name, external_refs = get_source_package_info(installed_file_metadata, metadata_file_path)
342*9e94795aSAndroid Build Coastguard Worker    source_package_id = new_package_id(name, PKG_SOURCE)
343*9e94795aSAndroid Build Coastguard Worker    source_package = sbom_data.Package(id=source_package_id, name=name, version=args.build_version,
344*9e94795aSAndroid Build Coastguard Worker                                       download_location=sbom_data.VALUE_NONE,
345*9e94795aSAndroid Build Coastguard Worker                                       supplier='Organization: ' + args.product_mfr,
346*9e94795aSAndroid Build Coastguard Worker                                       external_refs=external_refs)
347*9e94795aSAndroid Build Coastguard Worker
348*9e94795aSAndroid Build Coastguard Worker    upstream_package_id = new_package_id(name, PKG_UPSTREAM)
349*9e94795aSAndroid Build Coastguard Worker    upstream_package = sbom_data.Package(id=upstream_package_id, name=name, version=version,
350*9e94795aSAndroid Build Coastguard Worker                                         supplier=(
351*9e94795aSAndroid Build Coastguard Worker                                               'Organization: ' + homepage) if homepage else sbom_data.VALUE_NOASSERTION,
352*9e94795aSAndroid Build Coastguard Worker                                         download_location=download_location)
353*9e94795aSAndroid Build Coastguard Worker    packages += [source_package, upstream_package]
354*9e94795aSAndroid Build Coastguard Worker    relationships.append(sbom_data.Relationship(id1=source_package_id,
355*9e94795aSAndroid Build Coastguard Worker                                                relationship=sbom_data.RelationshipType.VARIANT_OF,
356*9e94795aSAndroid Build Coastguard Worker                                                id2=upstream_package_id))
357*9e94795aSAndroid Build Coastguard Worker
358*9e94795aSAndroid Build Coastguard Worker    for license in licenses:
359*9e94795aSAndroid Build Coastguard Worker      source_package.declared_license_ids.append(license.id)
360*9e94795aSAndroid Build Coastguard Worker      upstream_package.declared_license_ids.append(license.id)
361*9e94795aSAndroid Build Coastguard Worker
362*9e94795aSAndroid Build Coastguard Worker  elif is_prebuilt_package(installed_file_metadata):
363*9e94795aSAndroid Build Coastguard Worker    # Prebuilt fork packages
364*9e94795aSAndroid Build Coastguard Worker    name = get_prebuilt_package_name(installed_file_metadata, metadata_file_path)
365*9e94795aSAndroid Build Coastguard Worker    prebuilt_package_id = new_package_id(name, PKG_PREBUILT)
366*9e94795aSAndroid Build Coastguard Worker    prebuilt_package = sbom_data.Package(id=prebuilt_package_id,
367*9e94795aSAndroid Build Coastguard Worker                                         name=name,
368*9e94795aSAndroid Build Coastguard Worker                                         download_location=sbom_data.VALUE_NONE,
369*9e94795aSAndroid Build Coastguard Worker                                         version=version if version else args.build_version,
370*9e94795aSAndroid Build Coastguard Worker                                         supplier='Organization: ' + args.product_mfr)
371*9e94795aSAndroid Build Coastguard Worker
372*9e94795aSAndroid Build Coastguard Worker    upstream_package_id = new_package_id(name, PKG_UPSTREAM)
373*9e94795aSAndroid Build Coastguard Worker    upstream_package = sbom_data.Package(id=upstream_package_id, name=name, version=version,
374*9e94795aSAndroid Build Coastguard Worker                                         supplier=(
375*9e94795aSAndroid Build Coastguard Worker                                               'Organization: ' + homepage) if homepage else sbom_data.VALUE_NOASSERTION,
376*9e94795aSAndroid Build Coastguard Worker                                         download_location=download_location)
377*9e94795aSAndroid Build Coastguard Worker    packages += [prebuilt_package, upstream_package]
378*9e94795aSAndroid Build Coastguard Worker    relationships.append(sbom_data.Relationship(id1=prebuilt_package_id,
379*9e94795aSAndroid Build Coastguard Worker                                                relationship=sbom_data.RelationshipType.VARIANT_OF,
380*9e94795aSAndroid Build Coastguard Worker                                                id2=upstream_package_id))
381*9e94795aSAndroid Build Coastguard Worker    for license in licenses:
382*9e94795aSAndroid Build Coastguard Worker      prebuilt_package.declared_license_ids.append(license.id)
383*9e94795aSAndroid Build Coastguard Worker      upstream_package.declared_license_ids.append(license.id)
384*9e94795aSAndroid Build Coastguard Worker
385*9e94795aSAndroid Build Coastguard Worker  if metadata_file_path:
386*9e94795aSAndroid Build Coastguard Worker    metadata_proto = metadata_file_protos[metadata_file_path]
387*9e94795aSAndroid Build Coastguard Worker    if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
388*9e94795aSAndroid Build Coastguard Worker      sbom_url = metadata_proto.third_party.sbom_ref.url
389*9e94795aSAndroid Build Coastguard Worker      sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
390*9e94795aSAndroid Build Coastguard Worker      upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
391*9e94795aSAndroid Build Coastguard Worker      if sbom_url and sbom_checksum and upstream_element_id:
392*9e94795aSAndroid Build Coastguard Worker        doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{sbom_data.encode_for_spdxid(name)}'
393*9e94795aSAndroid Build Coastguard Worker        external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
394*9e94795aSAndroid Build Coastguard Worker                                                               uri=sbom_url,
395*9e94795aSAndroid Build Coastguard Worker                                                               checksum=sbom_checksum)
396*9e94795aSAndroid Build Coastguard Worker        relationships.append(
397*9e94795aSAndroid Build Coastguard Worker            sbom_data.Relationship(id1=upstream_package_id,
398*9e94795aSAndroid Build Coastguard Worker                                   relationship=sbom_data.RelationshipType.VARIANT_OF,
399*9e94795aSAndroid Build Coastguard Worker                                   id2=doc_ref_id + ':' + upstream_element_id))
400*9e94795aSAndroid Build Coastguard Worker
401*9e94795aSAndroid Build Coastguard Worker  return external_doc_ref, packages, relationships, licenses
402*9e94795aSAndroid Build Coastguard Worker
403*9e94795aSAndroid Build Coastguard Worker
404*9e94795aSAndroid Build Coastguard Workerdef save_report(report_file_path, report):
405*9e94795aSAndroid Build Coastguard Worker  with open(report_file_path, 'w', encoding='utf-8') as report_file:
406*9e94795aSAndroid Build Coastguard Worker    for type, issues in report.items():
407*9e94795aSAndroid Build Coastguard Worker      report_file.write(type + '\n')
408*9e94795aSAndroid Build Coastguard Worker      for issue in issues:
409*9e94795aSAndroid Build Coastguard Worker        report_file.write('\t' + issue + '\n')
410*9e94795aSAndroid Build Coastguard Worker      report_file.write('\n')
411*9e94795aSAndroid Build Coastguard Worker
412*9e94795aSAndroid Build Coastguard Worker
413*9e94795aSAndroid Build Coastguard Worker# Validate the metadata generated by Make for installed files and report if there is no metadata.
414*9e94795aSAndroid Build Coastguard Workerdef installed_file_has_metadata(installed_file_metadata, report):
415*9e94795aSAndroid Build Coastguard Worker  installed_file = installed_file_metadata['installed_file']
416*9e94795aSAndroid Build Coastguard Worker  module_path = installed_file_metadata['module_path']
417*9e94795aSAndroid Build Coastguard Worker  product_copy_files = installed_file_metadata['product_copy_files']
418*9e94795aSAndroid Build Coastguard Worker  kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files']
419*9e94795aSAndroid Build Coastguard Worker  is_platform_generated = installed_file_metadata['is_platform_generated']
420*9e94795aSAndroid Build Coastguard Worker
421*9e94795aSAndroid Build Coastguard Worker  if (not module_path and
422*9e94795aSAndroid Build Coastguard Worker      not product_copy_files and
423*9e94795aSAndroid Build Coastguard Worker      not kernel_module_copy_files and
424*9e94795aSAndroid Build Coastguard Worker      not is_platform_generated and
425*9e94795aSAndroid Build Coastguard Worker      not installed_file.endswith('.fsv_meta')):
426*9e94795aSAndroid Build Coastguard Worker    report[ISSUE_NO_METADATA].append(installed_file)
427*9e94795aSAndroid Build Coastguard Worker    return False
428*9e94795aSAndroid Build Coastguard Worker
429*9e94795aSAndroid Build Coastguard Worker  return True
430*9e94795aSAndroid Build Coastguard Worker
431*9e94795aSAndroid Build Coastguard Worker
432*9e94795aSAndroid Build Coastguard Worker# Validate identifiers in a package's METADATA.
433*9e94795aSAndroid Build Coastguard Worker# 1) Only known identifier type is allowed
434*9e94795aSAndroid Build Coastguard Worker# 2) Only one identifier's primary_source can be true
435*9e94795aSAndroid Build Coastguard Workerdef validate_package_metadata(metadata_file_path, package_metadata):
436*9e94795aSAndroid Build Coastguard Worker  primary_source_found = False
437*9e94795aSAndroid Build Coastguard Worker  for identifier in package_metadata.third_party.identifier:
438*9e94795aSAndroid Build Coastguard Worker    if identifier.type not in THIRD_PARTY_IDENTIFIER_TYPES:
439*9e94795aSAndroid Build Coastguard Worker      sys.exit(f'Unknown value of third_party.identifier.type in {metadata_file_path}/METADATA: {identifier.type}.')
440*9e94795aSAndroid Build Coastguard Worker    if primary_source_found and identifier.primary_source:
441*9e94795aSAndroid Build Coastguard Worker      sys.exit(
442*9e94795aSAndroid Build Coastguard Worker          f'Field "primary_source" is set to true in multiple third_party.identifier in {metadata_file_path}/METADATA.')
443*9e94795aSAndroid Build Coastguard Worker    primary_source_found = identifier.primary_source
444*9e94795aSAndroid Build Coastguard Worker
445*9e94795aSAndroid Build Coastguard Worker
446*9e94795aSAndroid Build Coastguard Workerdef report_metadata_file(metadata_file_path, installed_file_metadata, report):
447*9e94795aSAndroid Build Coastguard Worker  if metadata_file_path:
448*9e94795aSAndroid Build Coastguard Worker    report[INFO_METADATA_FOUND_FOR_PACKAGE].append(
449*9e94795aSAndroid Build Coastguard Worker        'installed_file: {}, module_path: {}, METADATA file: {}'.format(
450*9e94795aSAndroid Build Coastguard Worker            installed_file_metadata['installed_file'],
451*9e94795aSAndroid Build Coastguard Worker            installed_file_metadata['module_path'],
452*9e94795aSAndroid Build Coastguard Worker            metadata_file_path + '/METADATA'))
453*9e94795aSAndroid Build Coastguard Worker
454*9e94795aSAndroid Build Coastguard Worker    package_metadata = metadata_file_pb2.Metadata()
455*9e94795aSAndroid Build Coastguard Worker    with open(metadata_file_path + '/METADATA', 'rt') as f:
456*9e94795aSAndroid Build Coastguard Worker      text_format.Parse(f.read(), package_metadata)
457*9e94795aSAndroid Build Coastguard Worker
458*9e94795aSAndroid Build Coastguard Worker    validate_package_metadata(metadata_file_path, package_metadata)
459*9e94795aSAndroid Build Coastguard Worker
460*9e94795aSAndroid Build Coastguard Worker    if not metadata_file_path in metadata_file_protos:
461*9e94795aSAndroid Build Coastguard Worker      metadata_file_protos[metadata_file_path] = package_metadata
462*9e94795aSAndroid Build Coastguard Worker      if not package_metadata.name:
463*9e94795aSAndroid Build Coastguard Worker        report[ISSUE_METADATA_FILE_INCOMPLETE].append(f'{metadata_file_path}/METADATA does not has "name"')
464*9e94795aSAndroid Build Coastguard Worker
465*9e94795aSAndroid Build Coastguard Worker      if not package_metadata.third_party.version:
466*9e94795aSAndroid Build Coastguard Worker        report[ISSUE_METADATA_FILE_INCOMPLETE].append(
467*9e94795aSAndroid Build Coastguard Worker            f'{metadata_file_path}/METADATA does not has "third_party.version"')
468*9e94795aSAndroid Build Coastguard Worker
469*9e94795aSAndroid Build Coastguard Worker      for tag in package_metadata.third_party.security.tag:
470*9e94795aSAndroid Build Coastguard Worker        if not tag.startswith(NVD_CPE23):
471*9e94795aSAndroid Build Coastguard Worker          report[ISSUE_UNKNOWN_SECURITY_TAG_TYPE].append(
472*9e94795aSAndroid Build Coastguard Worker              f'Unknown security tag type: {tag} in {metadata_file_path}/METADATA')
473*9e94795aSAndroid Build Coastguard Worker  else:
474*9e94795aSAndroid Build Coastguard Worker    report[ISSUE_NO_METADATA_FILE].append(
475*9e94795aSAndroid Build Coastguard Worker        "installed_file: {}, module_path: {}".format(
476*9e94795aSAndroid Build Coastguard Worker            installed_file_metadata['installed_file'], installed_file_metadata['module_path']))
477*9e94795aSAndroid Build Coastguard Worker
478*9e94795aSAndroid Build Coastguard Worker
479*9e94795aSAndroid Build Coastguard Worker# If a file is from a source fork or prebuilt fork package, add its package information to SBOM
480*9e94795aSAndroid Build Coastguard Workerdef add_package_of_file(file_id, file_metadata, doc, report):
481*9e94795aSAndroid Build Coastguard Worker  metadata_file_path = get_metadata_file_path(file_metadata)
482*9e94795aSAndroid Build Coastguard Worker  report_metadata_file(metadata_file_path, file_metadata, report)
483*9e94795aSAndroid Build Coastguard Worker
484*9e94795aSAndroid Build Coastguard Worker  external_doc_ref, pkgs, rels, licenses = get_sbom_fragments(file_metadata, metadata_file_path)
485*9e94795aSAndroid Build Coastguard Worker  if len(pkgs) > 0:
486*9e94795aSAndroid Build Coastguard Worker    if external_doc_ref:
487*9e94795aSAndroid Build Coastguard Worker      doc.add_external_ref(external_doc_ref)
488*9e94795aSAndroid Build Coastguard Worker    for p in pkgs:
489*9e94795aSAndroid Build Coastguard Worker      doc.add_package(p)
490*9e94795aSAndroid Build Coastguard Worker    for rel in rels:
491*9e94795aSAndroid Build Coastguard Worker      doc.add_relationship(rel)
492*9e94795aSAndroid Build Coastguard Worker    fork_package_id = pkgs[0].id  # The first package should be the source/prebuilt fork package
493*9e94795aSAndroid Build Coastguard Worker    doc.add_relationship(sbom_data.Relationship(id1=file_id,
494*9e94795aSAndroid Build Coastguard Worker                                                relationship=sbom_data.RelationshipType.GENERATED_FROM,
495*9e94795aSAndroid Build Coastguard Worker                                                id2=fork_package_id))
496*9e94795aSAndroid Build Coastguard Worker    for license in licenses:
497*9e94795aSAndroid Build Coastguard Worker      doc.add_license(license)
498*9e94795aSAndroid Build Coastguard Worker
499*9e94795aSAndroid Build Coastguard Worker
500*9e94795aSAndroid Build Coastguard Worker# Add STATIC_LINK relationship for static dependencies of a file
501*9e94795aSAndroid Build Coastguard Workerdef add_static_deps_of_file(file_id, file_metadata, doc):
502*9e94795aSAndroid Build Coastguard Worker  if not file_metadata['static_dep_files'] and not file_metadata['whole_static_dep_files']:
503*9e94795aSAndroid Build Coastguard Worker    return
504*9e94795aSAndroid Build Coastguard Worker  static_dep_files = []
505*9e94795aSAndroid Build Coastguard Worker  if file_metadata['static_dep_files']:
506*9e94795aSAndroid Build Coastguard Worker    static_dep_files += file_metadata['static_dep_files'].split(' ')
507*9e94795aSAndroid Build Coastguard Worker  if file_metadata['whole_static_dep_files']:
508*9e94795aSAndroid Build Coastguard Worker    static_dep_files += file_metadata['whole_static_dep_files'].split(' ')
509*9e94795aSAndroid Build Coastguard Worker
510*9e94795aSAndroid Build Coastguard Worker  for dep_file in static_dep_files:
511*9e94795aSAndroid Build Coastguard Worker    # Static libs are not shipped on devices, so names are derived from .intermediates paths.
512*9e94795aSAndroid Build Coastguard Worker    doc.add_relationship(sbom_data.Relationship(id1=file_id,
513*9e94795aSAndroid Build Coastguard Worker                                                relationship=sbom_data.RelationshipType.STATIC_LINK,
514*9e94795aSAndroid Build Coastguard Worker                                                id2=new_file_id(
515*9e94795aSAndroid Build Coastguard Worker                                                  dep_file.removeprefix(args.soong_out + '/.intermediates/'))))
516*9e94795aSAndroid Build Coastguard Worker
517*9e94795aSAndroid Build Coastguard Worker
518*9e94795aSAndroid Build Coastguard Workerdef add_licenses_of_file(file_id, file_metadata, doc):
519*9e94795aSAndroid Build Coastguard Worker  lics = db.get_module_licenses(file_metadata.get('name', ''), file_metadata['module_path'])
520*9e94795aSAndroid Build Coastguard Worker  if lics:
521*9e94795aSAndroid Build Coastguard Worker    file = next(f for f in doc.files if file_id == f.id)
522*9e94795aSAndroid Build Coastguard Worker    for license_name, license_files in lics.items():
523*9e94795aSAndroid Build Coastguard Worker      if not license_files:
524*9e94795aSAndroid Build Coastguard Worker        continue
525*9e94795aSAndroid Build Coastguard Worker      license_id = new_license_id(license_name)
526*9e94795aSAndroid Build Coastguard Worker      file.concluded_license_ids.append(license_id)
527*9e94795aSAndroid Build Coastguard Worker      if license_name not in licenses_text:
528*9e94795aSAndroid Build Coastguard Worker        license_text = get_license_text(license_files.split(' '))
529*9e94795aSAndroid Build Coastguard Worker        licenses_text[license_name] = license_text
530*9e94795aSAndroid Build Coastguard Worker
531*9e94795aSAndroid Build Coastguard Worker      doc.add_license(sbom_data.License(id=license_id, name=license_name, text=licenses_text[license_name]))
532*9e94795aSAndroid Build Coastguard Worker
533*9e94795aSAndroid Build Coastguard Worker
534*9e94795aSAndroid Build Coastguard Workerdef get_all_transitive_static_dep_files_of_installed_files(installed_files_metadata, db, report):
535*9e94795aSAndroid Build Coastguard Worker  # Find all transitive static dep files of all installed files
536*9e94795aSAndroid Build Coastguard Worker  q = queue.Queue()
537*9e94795aSAndroid Build Coastguard Worker  for installed_file_metadata in installed_files_metadata:
538*9e94795aSAndroid Build Coastguard Worker    if installed_file_metadata['static_dep_files']:
539*9e94795aSAndroid Build Coastguard Worker      for f in installed_file_metadata['static_dep_files'].split(' '):
540*9e94795aSAndroid Build Coastguard Worker        q.put(f)
541*9e94795aSAndroid Build Coastguard Worker    if installed_file_metadata['whole_static_dep_files']:
542*9e94795aSAndroid Build Coastguard Worker      for f in installed_file_metadata['whole_static_dep_files'].split(' '):
543*9e94795aSAndroid Build Coastguard Worker        q.put(f)
544*9e94795aSAndroid Build Coastguard Worker
545*9e94795aSAndroid Build Coastguard Worker  all_static_dep_files = {}
546*9e94795aSAndroid Build Coastguard Worker  while not q.empty():
547*9e94795aSAndroid Build Coastguard Worker    dep_file = q.get()
548*9e94795aSAndroid Build Coastguard Worker    if dep_file in all_static_dep_files:
549*9e94795aSAndroid Build Coastguard Worker      # It has been processed
550*9e94795aSAndroid Build Coastguard Worker      continue
551*9e94795aSAndroid Build Coastguard Worker
552*9e94795aSAndroid Build Coastguard Worker    all_static_dep_files[dep_file] = True
553*9e94795aSAndroid Build Coastguard Worker    soong_module = db.get_soong_module_of_built_file(dep_file)
554*9e94795aSAndroid Build Coastguard Worker    if not soong_module:
555*9e94795aSAndroid Build Coastguard Worker      # This should not happen, add to report[ISSUE_NO_MODULE_FOUND_FOR_STATIC_DEP]
556*9e94795aSAndroid Build Coastguard Worker      report[ISSUE_NO_MODULE_FOUND_FOR_STATIC_DEP].append(f)
557*9e94795aSAndroid Build Coastguard Worker      continue
558*9e94795aSAndroid Build Coastguard Worker
559*9e94795aSAndroid Build Coastguard Worker    if soong_module['static_dep_files']:
560*9e94795aSAndroid Build Coastguard Worker      for f in soong_module['static_dep_files'].split(' '):
561*9e94795aSAndroid Build Coastguard Worker        if f not in all_static_dep_files:
562*9e94795aSAndroid Build Coastguard Worker          q.put(f)
563*9e94795aSAndroid Build Coastguard Worker    if soong_module['whole_static_dep_files']:
564*9e94795aSAndroid Build Coastguard Worker      for f in soong_module['whole_static_dep_files'].split(' '):
565*9e94795aSAndroid Build Coastguard Worker        if f not in all_static_dep_files:
566*9e94795aSAndroid Build Coastguard Worker          q.put(f)
567*9e94795aSAndroid Build Coastguard Worker
568*9e94795aSAndroid Build Coastguard Worker  return sorted(all_static_dep_files.keys())
569*9e94795aSAndroid Build Coastguard Worker
570*9e94795aSAndroid Build Coastguard Worker
571*9e94795aSAndroid Build Coastguard Workerdef main():
572*9e94795aSAndroid Build Coastguard Worker  global args
573*9e94795aSAndroid Build Coastguard Worker  args = get_args()
574*9e94795aSAndroid Build Coastguard Worker  log('Args:', vars(args))
575*9e94795aSAndroid Build Coastguard Worker
576*9e94795aSAndroid Build Coastguard Worker  global db
577*9e94795aSAndroid Build Coastguard Worker  db = compliance_metadata.MetadataDb(args.metadata)
578*9e94795aSAndroid Build Coastguard Worker  if args.debug:
579*9e94795aSAndroid Build Coastguard Worker    db.dump_debug_db(os.path.dirname(args.output_file) + '/compliance-metadata-debug.db')
580*9e94795aSAndroid Build Coastguard Worker
581*9e94795aSAndroid Build Coastguard Worker  global metadata_file_protos
582*9e94795aSAndroid Build Coastguard Worker  metadata_file_protos = {}
583*9e94795aSAndroid Build Coastguard Worker  global licenses_text
584*9e94795aSAndroid Build Coastguard Worker  licenses_text = {}
585*9e94795aSAndroid Build Coastguard Worker
586*9e94795aSAndroid Build Coastguard Worker  product_package_id = sbom_data.SPDXID_PRODUCT
587*9e94795aSAndroid Build Coastguard Worker  product_package_name = sbom_data.PACKAGE_NAME_PRODUCT
588*9e94795aSAndroid Build Coastguard Worker  product_package = sbom_data.Package(id=product_package_id,
589*9e94795aSAndroid Build Coastguard Worker                                      name=product_package_name,
590*9e94795aSAndroid Build Coastguard Worker                                      download_location=sbom_data.VALUE_NONE,
591*9e94795aSAndroid Build Coastguard Worker                                      version=args.build_version,
592*9e94795aSAndroid Build Coastguard Worker                                      supplier='Organization: ' + args.product_mfr,
593*9e94795aSAndroid Build Coastguard Worker                                      files_analyzed=True)
594*9e94795aSAndroid Build Coastguard Worker  doc_name = args.build_version
595*9e94795aSAndroid Build Coastguard Worker  doc = sbom_data.Document(name=doc_name,
596*9e94795aSAndroid Build Coastguard Worker                           namespace=f'https://www.google.com/sbom/spdx/android/{doc_name}',
597*9e94795aSAndroid Build Coastguard Worker                           creators=['Organization: ' + args.product_mfr],
598*9e94795aSAndroid Build Coastguard Worker                           describes=product_package_id)
599*9e94795aSAndroid Build Coastguard Worker
600*9e94795aSAndroid Build Coastguard Worker  doc.packages.append(product_package)
601*9e94795aSAndroid Build Coastguard Worker  doc.packages.append(sbom_data.Package(id=sbom_data.SPDXID_PLATFORM,
602*9e94795aSAndroid Build Coastguard Worker                                        name=sbom_data.PACKAGE_NAME_PLATFORM,
603*9e94795aSAndroid Build Coastguard Worker                                        download_location=sbom_data.VALUE_NONE,
604*9e94795aSAndroid Build Coastguard Worker                                        version=args.build_version,
605*9e94795aSAndroid Build Coastguard Worker                                        supplier='Organization: ' + args.product_mfr,
606*9e94795aSAndroid Build Coastguard Worker                                        declared_license_ids=[sbom_data.SPDXID_LICENSE_APACHE]))
607*9e94795aSAndroid Build Coastguard Worker
608*9e94795aSAndroid Build Coastguard Worker  # Report on some issues and information
609*9e94795aSAndroid Build Coastguard Worker  report = {
610*9e94795aSAndroid Build Coastguard Worker      ISSUE_NO_METADATA: [],
611*9e94795aSAndroid Build Coastguard Worker      ISSUE_NO_METADATA_FILE: [],
612*9e94795aSAndroid Build Coastguard Worker      ISSUE_METADATA_FILE_INCOMPLETE: [],
613*9e94795aSAndroid Build Coastguard Worker      ISSUE_UNKNOWN_SECURITY_TAG_TYPE: [],
614*9e94795aSAndroid Build Coastguard Worker      ISSUE_INSTALLED_FILE_NOT_EXIST: [],
615*9e94795aSAndroid Build Coastguard Worker      ISSUE_NO_MODULE_FOUND_FOR_STATIC_DEP: [],
616*9e94795aSAndroid Build Coastguard Worker      INFO_METADATA_FOUND_FOR_PACKAGE: [],
617*9e94795aSAndroid Build Coastguard Worker  }
618*9e94795aSAndroid Build Coastguard Worker
619*9e94795aSAndroid Build Coastguard Worker  # Get installed files and corresponding make modules' metadata if an installed file is from a make module.
620*9e94795aSAndroid Build Coastguard Worker  installed_files_metadata = db.get_installed_files()
621*9e94795aSAndroid Build Coastguard Worker
622*9e94795aSAndroid Build Coastguard Worker  # Find which Soong module an installed file is from and merge metadata from Make and Soong
623*9e94795aSAndroid Build Coastguard Worker  for installed_file_metadata in installed_files_metadata:
624*9e94795aSAndroid Build Coastguard Worker    soong_module = db.get_soong_module_of_installed_file(installed_file_metadata['installed_file'])
625*9e94795aSAndroid Build Coastguard Worker    if soong_module:
626*9e94795aSAndroid Build Coastguard Worker      # Merge soong metadata to make metadata
627*9e94795aSAndroid Build Coastguard Worker      installed_file_metadata.update(soong_module)
628*9e94795aSAndroid Build Coastguard Worker    else:
629*9e94795aSAndroid Build Coastguard Worker      # For make modules soong_module_type should be empty
630*9e94795aSAndroid Build Coastguard Worker      installed_file_metadata['soong_module_type'] = ''
631*9e94795aSAndroid Build Coastguard Worker      installed_file_metadata['static_dep_files'] = ''
632*9e94795aSAndroid Build Coastguard Worker      installed_file_metadata['whole_static_dep_files'] = ''
633*9e94795aSAndroid Build Coastguard Worker
634*9e94795aSAndroid Build Coastguard Worker  # Scan the metadata and create the corresponding package and file records in SPDX
635*9e94795aSAndroid Build Coastguard Worker  for installed_file_metadata in installed_files_metadata:
636*9e94795aSAndroid Build Coastguard Worker    installed_file = installed_file_metadata['installed_file']
637*9e94795aSAndroid Build Coastguard Worker    module_path = installed_file_metadata['module_path']
638*9e94795aSAndroid Build Coastguard Worker    product_copy_files = installed_file_metadata['product_copy_files']
639*9e94795aSAndroid Build Coastguard Worker    kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files']
640*9e94795aSAndroid Build Coastguard Worker    build_output_path = installed_file
641*9e94795aSAndroid Build Coastguard Worker    installed_file = installed_file.removeprefix(args.product_out)
642*9e94795aSAndroid Build Coastguard Worker
643*9e94795aSAndroid Build Coastguard Worker    if not installed_file_has_metadata(installed_file_metadata, report):
644*9e94795aSAndroid Build Coastguard Worker      continue
645*9e94795aSAndroid Build Coastguard Worker    if not (os.path.islink(build_output_path) or os.path.isfile(build_output_path)):
646*9e94795aSAndroid Build Coastguard Worker      report[ISSUE_INSTALLED_FILE_NOT_EXIST].append(installed_file)
647*9e94795aSAndroid Build Coastguard Worker      continue
648*9e94795aSAndroid Build Coastguard Worker
649*9e94795aSAndroid Build Coastguard Worker    file_id = new_file_id(installed_file)
650*9e94795aSAndroid Build Coastguard Worker    sha1 = checksum(build_output_path)
651*9e94795aSAndroid Build Coastguard Worker    f = sbom_data.File(id=file_id, name=installed_file, checksum=sha1)
652*9e94795aSAndroid Build Coastguard Worker    doc.files.append(f)
653*9e94795aSAndroid Build Coastguard Worker    product_package.file_ids.append(file_id)
654*9e94795aSAndroid Build Coastguard Worker
655*9e94795aSAndroid Build Coastguard Worker    if is_source_package(installed_file_metadata) or is_prebuilt_package(installed_file_metadata):
656*9e94795aSAndroid Build Coastguard Worker      add_package_of_file(file_id, installed_file_metadata, doc, report)
657*9e94795aSAndroid Build Coastguard Worker
658*9e94795aSAndroid Build Coastguard Worker    elif module_path or installed_file_metadata['is_platform_generated']:
659*9e94795aSAndroid Build Coastguard Worker      # File from PLATFORM package
660*9e94795aSAndroid Build Coastguard Worker      doc.add_relationship(sbom_data.Relationship(id1=file_id,
661*9e94795aSAndroid Build Coastguard Worker                                                  relationship=sbom_data.RelationshipType.GENERATED_FROM,
662*9e94795aSAndroid Build Coastguard Worker                                                  id2=sbom_data.SPDXID_PLATFORM))
663*9e94795aSAndroid Build Coastguard Worker      if installed_file_metadata['is_platform_generated']:
664*9e94795aSAndroid Build Coastguard Worker        f.concluded_license_ids = [sbom_data.SPDXID_LICENSE_APACHE]
665*9e94795aSAndroid Build Coastguard Worker
666*9e94795aSAndroid Build Coastguard Worker    elif product_copy_files:
667*9e94795aSAndroid Build Coastguard Worker      # Format of product_copy_files: <source path>:<dest path>
668*9e94795aSAndroid Build Coastguard Worker      src_path = product_copy_files.split(':')[0]
669*9e94795aSAndroid Build Coastguard Worker      # So far product_copy_files are copied from directory system, kernel, hardware, frameworks and device,
670*9e94795aSAndroid Build Coastguard Worker      # so process them as files from PLATFORM package
671*9e94795aSAndroid Build Coastguard Worker      doc.add_relationship(sbom_data.Relationship(id1=file_id,
672*9e94795aSAndroid Build Coastguard Worker                                                  relationship=sbom_data.RelationshipType.GENERATED_FROM,
673*9e94795aSAndroid Build Coastguard Worker                                                  id2=sbom_data.SPDXID_PLATFORM))
674*9e94795aSAndroid Build Coastguard Worker      if installed_file_metadata['license_text']:
675*9e94795aSAndroid Build Coastguard Worker        if installed_file_metadata['license_text'] == 'build/soong/licenses/LICENSE':
676*9e94795aSAndroid Build Coastguard Worker          f.concluded_license_ids = [sbom_data.SPDXID_LICENSE_APACHE]
677*9e94795aSAndroid Build Coastguard Worker
678*9e94795aSAndroid Build Coastguard Worker    elif installed_file.endswith('.fsv_meta'):
679*9e94795aSAndroid Build Coastguard Worker      doc.add_relationship(sbom_data.Relationship(id1=file_id,
680*9e94795aSAndroid Build Coastguard Worker                                                  relationship=sbom_data.RelationshipType.GENERATED_FROM,
681*9e94795aSAndroid Build Coastguard Worker                                                  id2=sbom_data.SPDXID_PLATFORM))
682*9e94795aSAndroid Build Coastguard Worker      f.concluded_license_ids = [sbom_data.SPDXID_LICENSE_APACHE]
683*9e94795aSAndroid Build Coastguard Worker
684*9e94795aSAndroid Build Coastguard Worker    elif kernel_module_copy_files.startswith('ANDROID-GEN'):
685*9e94795aSAndroid Build Coastguard Worker      # For the four files generated for _dlkm, _ramdisk partitions
686*9e94795aSAndroid Build Coastguard Worker      doc.add_relationship(sbom_data.Relationship(id1=file_id,
687*9e94795aSAndroid Build Coastguard Worker                                                  relationship=sbom_data.RelationshipType.GENERATED_FROM,
688*9e94795aSAndroid Build Coastguard Worker                                                  id2=sbom_data.SPDXID_PLATFORM))
689*9e94795aSAndroid Build Coastguard Worker
690*9e94795aSAndroid Build Coastguard Worker    # Process static dependencies of the installed file
691*9e94795aSAndroid Build Coastguard Worker    add_static_deps_of_file(file_id, installed_file_metadata, doc)
692*9e94795aSAndroid Build Coastguard Worker
693*9e94795aSAndroid Build Coastguard Worker    # Add licenses of the installed file
694*9e94795aSAndroid Build Coastguard Worker    add_licenses_of_file(file_id, installed_file_metadata, doc)
695*9e94795aSAndroid Build Coastguard Worker
696*9e94795aSAndroid Build Coastguard Worker  # Add all static library files to SBOM
697*9e94795aSAndroid Build Coastguard Worker  for dep_file in get_all_transitive_static_dep_files_of_installed_files(installed_files_metadata, db, report):
698*9e94795aSAndroid Build Coastguard Worker    filepath = dep_file.removeprefix(args.soong_out + '/.intermediates/')
699*9e94795aSAndroid Build Coastguard Worker    file_id = new_file_id(filepath)
700*9e94795aSAndroid Build Coastguard Worker    # SHA1 of empty string. Sometimes .a files might not be built.
701*9e94795aSAndroid Build Coastguard Worker    sha1 = 'SHA1: da39a3ee5e6b4b0d3255bfef95601890afd80709'
702*9e94795aSAndroid Build Coastguard Worker    if os.path.islink(dep_file) or os.path.isfile(dep_file):
703*9e94795aSAndroid Build Coastguard Worker      sha1 = checksum(dep_file)
704*9e94795aSAndroid Build Coastguard Worker    doc.files.append(sbom_data.File(id=file_id,
705*9e94795aSAndroid Build Coastguard Worker                                    name=filepath,
706*9e94795aSAndroid Build Coastguard Worker                                    checksum=sha1))
707*9e94795aSAndroid Build Coastguard Worker    file_metadata = {
708*9e94795aSAndroid Build Coastguard Worker        'installed_file': dep_file,
709*9e94795aSAndroid Build Coastguard Worker        'is_prebuilt_make_module': False
710*9e94795aSAndroid Build Coastguard Worker    }
711*9e94795aSAndroid Build Coastguard Worker    file_metadata.update(db.get_soong_module_of_built_file(dep_file))
712*9e94795aSAndroid Build Coastguard Worker    add_package_of_file(file_id, file_metadata, doc, report)
713*9e94795aSAndroid Build Coastguard Worker
714*9e94795aSAndroid Build Coastguard Worker    # Add relationships for static deps of static libraries
715*9e94795aSAndroid Build Coastguard Worker    add_static_deps_of_file(file_id, file_metadata, doc)
716*9e94795aSAndroid Build Coastguard Worker
717*9e94795aSAndroid Build Coastguard Worker    # Add licenses of the static lib
718*9e94795aSAndroid Build Coastguard Worker    add_licenses_of_file(file_id, file_metadata, doc)
719*9e94795aSAndroid Build Coastguard Worker
720*9e94795aSAndroid Build Coastguard Worker  # Save SBOM records to output file
721*9e94795aSAndroid Build Coastguard Worker  doc.generate_packages_verification_code()
722*9e94795aSAndroid Build Coastguard Worker  doc.created = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
723*9e94795aSAndroid Build Coastguard Worker  prefix = args.output_file
724*9e94795aSAndroid Build Coastguard Worker  if prefix.endswith('.spdx'):
725*9e94795aSAndroid Build Coastguard Worker    prefix = prefix.removesuffix('.spdx')
726*9e94795aSAndroid Build Coastguard Worker  elif prefix.endswith('.spdx.json'):
727*9e94795aSAndroid Build Coastguard Worker    prefix = prefix.removesuffix('.spdx.json')
728*9e94795aSAndroid Build Coastguard Worker
729*9e94795aSAndroid Build Coastguard Worker  output_file = prefix + '.spdx'
730*9e94795aSAndroid Build Coastguard Worker  with open(output_file, 'w', encoding="utf-8") as file:
731*9e94795aSAndroid Build Coastguard Worker    sbom_writers.TagValueWriter.write(doc, file)
732*9e94795aSAndroid Build Coastguard Worker  if args.json:
733*9e94795aSAndroid Build Coastguard Worker    with open(prefix + '.spdx.json', 'w', encoding="utf-8") as file:
734*9e94795aSAndroid Build Coastguard Worker      sbom_writers.JSONWriter.write(doc, file)
735*9e94795aSAndroid Build Coastguard Worker
736*9e94795aSAndroid Build Coastguard Worker  save_report(prefix + '-gen-report.txt', report)
737*9e94795aSAndroid Build Coastguard Worker
738*9e94795aSAndroid Build Coastguard Worker
739*9e94795aSAndroid Build Coastguard Workerif __name__ == '__main__':
740*9e94795aSAndroid Build Coastguard Worker  main()
741