xref: /aosp_15_r20/external/bazelbuild-rules_license/tools/write_sbom.py (revision f578df4fd057ffe2023728444759535685631548)
1*f578df4fSJingwen Chen#!/usr/bin/env python3
2*f578df4fSJingwen Chen# Copyright 2020 Google LLC
3*f578df4fSJingwen Chen#
4*f578df4fSJingwen Chen# Licensed under the Apache License, Version 2.0 (the "License");
5*f578df4fSJingwen Chen# you may not use this file except in compliance with the License.
6*f578df4fSJingwen Chen# You may obtain a copy of the License at
7*f578df4fSJingwen Chen#
8*f578df4fSJingwen Chen# https://www.apache.org/licenses/LICENSE-2.0
9*f578df4fSJingwen Chen#
10*f578df4fSJingwen Chen# Unless required by applicable law or agreed to in writing, software
11*f578df4fSJingwen Chen# distributed under the License is distributed on an "AS IS" BASIS,
12*f578df4fSJingwen Chen# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*f578df4fSJingwen Chen# See the License for the specific language governing permissions and
14*f578df4fSJingwen Chen# limitations under the License.
15*f578df4fSJingwen Chen
16*f578df4fSJingwen Chen"""Proof of concept license checker.
17*f578df4fSJingwen Chen
18*f578df4fSJingwen ChenThis is only a demonstration. It will be replaced with other tools.
19*f578df4fSJingwen Chen"""
20*f578df4fSJingwen Chen
21*f578df4fSJingwen Chenimport argparse
22*f578df4fSJingwen Chenimport codecs
23*f578df4fSJingwen Chenimport datetime
24*f578df4fSJingwen Chenimport json
25*f578df4fSJingwen Chenimport os
26*f578df4fSJingwen Chen
27*f578df4fSJingwen Chen
28*f578df4fSJingwen ChenTOOL = 'https//github.com/bazelbuild/rules_license/tools:write_sbom'
29*f578df4fSJingwen Chen
30*f578df4fSJingwen Chendef _load_package_data(package_info):
31*f578df4fSJingwen Chen  with codecs.open(package_info, encoding='utf-8') as inp:
32*f578df4fSJingwen Chen    return json.loads(inp.read())
33*f578df4fSJingwen Chen
34*f578df4fSJingwen Chendef _write_sbom_header(out, package):
35*f578df4fSJingwen Chen  header = [
36*f578df4fSJingwen Chen    'SPDXVersion: SPDX-2.2',
37*f578df4fSJingwen Chen    'DataLicense: CC0-1.0',
38*f578df4fSJingwen Chen    'SPDXID: SPDXRef-DOCUMENT',
39*f578df4fSJingwen Chen    'DocumentName: %s' % package,
40*f578df4fSJingwen Chen    # TBD
41*f578df4fSJingwen Chen    # 'DocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3
42*f578df4fSJingwen Chen    'Creator: Person: %s' % os.getlogin(),
43*f578df4fSJingwen Chen    'Creator: Tool: %s' % TOOL,
44*f578df4fSJingwen Chen    datetime.datetime.utcnow().strftime('Created: %Y-%m-%d-%H:%M:%SZ'),
45*f578df4fSJingwen Chen    '',
46*f578df4fSJingwen Chen    '##### Package: %s' % package,
47*f578df4fSJingwen Chen  ]
48*f578df4fSJingwen Chen  out.write('\n'.join(header))
49*f578df4fSJingwen Chen
50*f578df4fSJingwen Chen
51*f578df4fSJingwen Chen
52*f578df4fSJingwen Chendef _write_sbom(out, packages):
53*f578df4fSJingwen Chen  """Produce a basic SBOM
54*f578df4fSJingwen Chen
55*f578df4fSJingwen Chen  Args:
56*f578df4fSJingwen Chen    out: file object to write to
57*f578df4fSJingwen Chen    packages: package metadata. A big blob of JSON.
58*f578df4fSJingwen Chen  """
59*f578df4fSJingwen Chen  for p in packages:
60*f578df4fSJingwen Chen    name = p.get('package_name') or '<unknown>'
61*f578df4fSJingwen Chen    out.write('\n')
62*f578df4fSJingwen Chen    out.write('SPDXID: "%s"\n' % name)
63*f578df4fSJingwen Chen    out.write('  name: "%s"\n' % name)
64*f578df4fSJingwen Chen    if p.get('package_version'):
65*f578df4fSJingwen Chen      out.write('  versionInfo: "%s"\n' % p['package_version'])
66*f578df4fSJingwen Chen    # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one.
67*f578df4fSJingwen Chen    cn = p.get('copyright_notice')
68*f578df4fSJingwen Chen    if cn:
69*f578df4fSJingwen Chen      out.write('  copyrightText: "%s"\n' % cn)
70*f578df4fSJingwen Chen    kinds = p.get('license_kinds')
71*f578df4fSJingwen Chen    if kinds:
72*f578df4fSJingwen Chen      out.write('  licenseDeclared: "%s"\n' %
73*f578df4fSJingwen Chen                ','.join([k['name'] for k in kinds]))
74*f578df4fSJingwen Chen    url = p.get('package_url')
75*f578df4fSJingwen Chen    if url:
76*f578df4fSJingwen Chen      out.write('  downloadLocation: %s\n' % url)
77*f578df4fSJingwen Chen
78*f578df4fSJingwen Chen
79*f578df4fSJingwen Chendef main():
80*f578df4fSJingwen Chen  parser = argparse.ArgumentParser(
81*f578df4fSJingwen Chen      description='Demonstraton license compliance checker')
82*f578df4fSJingwen Chen
83*f578df4fSJingwen Chen  parser.add_argument('--licenses_info',
84*f578df4fSJingwen Chen                      help='path to JSON file containing all license data')
85*f578df4fSJingwen Chen  parser.add_argument('--out', default='sbom.out', help='SBOM output')
86*f578df4fSJingwen Chen  args = parser.parse_args()
87*f578df4fSJingwen Chen
88*f578df4fSJingwen Chen  license_data = _load_package_data(args.licenses_info)
89*f578df4fSJingwen Chen  target = license_data[0]  # we assume only one target for the demo
90*f578df4fSJingwen Chen
91*f578df4fSJingwen Chen  top_level_target = target['top_level_target']
92*f578df4fSJingwen Chen  dependencies = target['dependencies']
93*f578df4fSJingwen Chen  # It's not really packages, but this is close proxy for now
94*f578df4fSJingwen Chen  licenses = target['licenses']
95*f578df4fSJingwen Chen  package_infos = target['packages']
96*f578df4fSJingwen Chen
97*f578df4fSJingwen Chen  # These are similar dicts, so merge them by package. This is not
98*f578df4fSJingwen Chen  # strictly true, as different licenese can appear in the same
99*f578df4fSJingwen Chen  # package, but it is good enough for demonstrating the sbom.
100*f578df4fSJingwen Chen
101*f578df4fSJingwen Chen  all = {x['bazel_package']: x for x in licenses}
102*f578df4fSJingwen Chen  for pi in package_infos:
103*f578df4fSJingwen Chen    p = all.get(pi['bazel_package'])
104*f578df4fSJingwen Chen    if p:
105*f578df4fSJingwen Chen      p.update(pi)
106*f578df4fSJingwen Chen    else:
107*f578df4fSJingwen Chen      all[pi['bazel_package']] = pi
108*f578df4fSJingwen Chen
109*f578df4fSJingwen Chen  err = 0
110*f578df4fSJingwen Chen  with codecs.open(args.out, mode='w', encoding='utf-8') as out:
111*f578df4fSJingwen Chen    _write_sbom_header(out, package=top_level_target)
112*f578df4fSJingwen Chen    _write_sbom(out, all.values())
113*f578df4fSJingwen Chen  return err
114*f578df4fSJingwen Chen
115*f578df4fSJingwen Chen
116*f578df4fSJingwen Chenif __name__ == '__main__':
117*f578df4fSJingwen Chen  main()
118