xref: /aosp_15_r20/external/bazelbuild-rules_license/admin/refresh_spdx/add_licenses.py (revision f578df4fd057ffe2023728444759535685631548)
1#!/usr/bin/env python3
2"""Refresh the BUILD file of SPDX license_kinds with new ones from licenses.json.
3
4Usage:
5  wget https://github.com/spdx/license-list-data/raw/master/json/licenses.json
6  LC_ALL="en_US.UTF-8" admin/refresh_spdx/add_licenses.py
7  git diff
8  git commit
9"""
10
11import json
12import os
13from string import Template
14import re
15import sys
16
17def load_json(path: str):
18  """Loads a JSON file and returns the data.
19
20  Args:
21    path: (str) a file path.
22  Returns:
23    (dict) the parsed data.
24  """
25  with open(path, 'r') as fp:
26    ret = json.load(fp)
27  return ret
28
29
30def parse_build_file(text: str):
31  """Parse a BUILD file of license declaration.
32
33  Parses a build file and returns all the text that
34  is not licenese_kind declarations and a map of
35  license kind names to the text which declared them
36
37  license_kind(
38    name = "0BSD",
39    conditions = [],
40    url = "https://spdx.org/licenses/0BSD.html",
41  )
42
43  Returns:
44    preamble: str
45    targets: map<str, str>
46  """
47
48  target_start = re.compile(r'^([a-zA-Z0-9_]+)\(')  # ) to fix autoindent
49  name_match = re.compile(r'^ *name *= *"([^"]*)"')
50
51  targets = {}
52  preamble = []
53  collecting_rule = None
54
55  for line in text.split('\n'):
56    if collecting_rule:
57      collecting_rule.append(line)
58      if line.startswith(')'):
59        assert cur_name
60        targets[cur_name] = '\n'.join(collecting_rule)
61        # print(cur_name, "=====", targets[cur_name])
62        collecting_rule = None
63        cur_name = None
64      else:
65        m = name_match.match(line)
66        if m:
67          cur_name = m.group(1)
68      continue
69
70    # not collecting a rule
71    m = target_start.match(line)
72    if m:
73      cur_rule = m.group(1)
74      if cur_rule == 'license_kind':
75        collecting_rule = [line]
76        continue
77
78    if line or preamble[-1]:
79      preamble.append(line)
80
81  return '\n'.join(preamble), targets
82
83
84# Sample JSON formate license declaration.
85"""
86{
87  "licenseListVersion": "3.8-57-gca4f142",
88  "licenses": [
89    {
90      "reference": "./0BSD.html",
91      "isDeprecatedLicenseId": false,
92      "detailsUrl": "http://spdx.org/licenses/0BSD.json",
93      "referenceNumber": "232",
94      "name": "BSD Zero Clause License",
95      "licenseId": "0BSD",
96      "seeAlso": [
97        "http://landley.net/toybox/license.html"
98      ],
99      "isOsiApproved": true
100    },
101"""
102
103
104def insert_new(already_declared, licenses):
105  template = Template((
106      'license_kind(\n'
107      '    name = "${licenseId}",\n'
108      '    conditions = [],\n'
109      '    url = "https://spdx.org/licenses/${licenseId}.html",\n'
110      ')'))
111
112  for license in licenses:
113    id = license['licenseId']
114    old = already_declared.get(id)
115    if not old:
116      print('Adding:', id)
117      already_declared[id] = template.substitute(license)
118
119
120def update_spdx_build():
121  """Update //licenses/spdx/BUILD with new license kinds."""
122
123  license_json = load_json('licenses.json')
124
125  # Find workspace root
126  build_path = 'licenses/spdx/BUILD'
127  found_spdx_build = False
128  for i in range(5):  # Simple regression failure limiter
129    if os.access('WORKSPACE', os.R_OK) and os.access(build_path, os.R_OK):
130      found_spdx_build = True
131      break
132    os.chdir('..')
133  if not found_spdx_build:
134    print('Could not find', build_path)
135    return 1
136
137  with open(build_path, 'r') as fp:
138    current_file_content = fp.read()
139
140  preamble, kinds = parse_build_file(current_file_content)
141  insert_new(kinds, license_json['licenses'])
142  with open(build_path, 'w') as fp:
143    fp.write(preamble)
144    for license in sorted(kinds.keys(), key=lambda x: x.lower()):
145      fp.write('\n')
146      fp.write(kinds[license])
147      fp.write('\n')
148
149
150def main():
151  lc_all = os.environ.get('LC_ALL')
152  if lc_all != 'en_US.UTF-8':
153    print('Your environment settings will reorder the file badly.')
154    print('Please rerun as:')
155    print('  LC_ALL="en_US.UTF-8"', ' '.join(sys.argv))
156    sys.exit(1)
157
158  update_spdx_build()
159
160
161if __name__ == '__main__':
162  main()
163