• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2020 Google LLC
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# https://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Proof of concept license checker.
17
18This is only a demonstration. It will be replaced with other tools.
19"""
20
21import argparse
22import codecs
23import json
24
25# Conditions allowed for all applications
26_ALWAYS_ALLOWED_CONDITIONS = frozenset(['notice', 'permissive', 'unencumbered'])
27
28
29def _load_license_data(licenses_info):
30  with codecs.open(licenses_info, encoding='utf-8') as licenses_file:
31    return json.loads(licenses_file.read())
32
33
34def unique_licenses(licenses):
35  for target in licenses:
36    for lic in target.get('licenses') or []:
37      yield lic
38
39def _do_report(out, licenses):
40  """Produce a report showing the set of licenses being used.
41
42  Args:
43    out: file object to write to
44    licenses: list of LicenseInfo objects
45
46  Returns:
47    0 for no restricted licenses.
48  """
49
50  for target in unique_licenses(licenses):
51    for lic in target.get('licenses') or []:
52      print("lic:", lic)
53      rule = lic['rule']
54      for kind in lic['license_kinds']:
55        out.write('= %s\n  kind: %s\n' % (rule, kind['target']))
56        out.write('  conditions: %s\n' % kind['conditions'])
57
58
59def _check_conditions(out, licenses, allowed_conditions):
60  """Check that the application does not use any disallowed licenses.
61
62  Args:
63    out: file object to write to
64    licenses: list of LicenseInfo objects
65    allowed_conditions: list of allowed condition names
66
67  Returns:
68    0 for no licenses from outside allowed_conditions.
69  """
70  err = 0
71  for lic in licenses:  # using strange name lic because license is built-in
72    rule = lic['rule']
73    for kind in lic['license_kinds']:
74      disallowed = []
75      for condition in kind['conditions']:
76        if condition not in allowed_conditions:
77          disallowed.append(condition)
78      if disallowed:
79        out.write('ERROR: %s\n' % rule)
80        out.write('   kind: %s\n' % kind['target'])
81        out.write('   conditions: %s\n' % kind['conditions'])
82        out.write('   disallowed condition: %s\n' % ','.join(disallowed))
83        err += 1
84  return err
85
86
87def _do_copyright_notices(out, licenses):
88  for l in licenses:
89    name = l.get('package_name') or '<unknown>'
90    if l.get('package_version'):
91      name =  name + "/" + l['package_version']
92    # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one.
93    out.write('package(%s), copyright(%s)\n' % (name, l['copyright_notice']))
94
95
96def _do_licenses(out, licenses):
97  for lic in unique_licenses(licenses):
98    path = lic['license_text']
99    with codecs.open(path, encoding='utf-8') as license_file:
100      out.write('= %s\n' % path)
101      out.write(license_file.read())
102
103
104def main():
105  parser = argparse.ArgumentParser(
106      description='Demonstraton license compliance checker')
107
108  parser.add_argument('--licenses_info',
109                      help='path to JSON file containing all license data')
110  parser.add_argument('--report', default='report', help='Summary report')
111  parser.add_argument('--copyright_notices',
112                      help='output file of all copyright notices')
113  parser.add_argument('--license_texts', help='output file of all license files')
114  parser.add_argument('--check_conditions', action='store_true',
115                      help='check that the dep only includes allowed license conditions')
116  args = parser.parse_args()
117
118  license_data = _load_license_data(args.licenses_info)
119  target = license_data[0]  # we assume only one target for the demo
120
121  top_level_target = target['top_level_target']
122  dependencies = target['dependencies']
123  licenses = target['licenses']
124
125  err = 0
126  with codecs.open(args.report, mode='w', encoding='utf-8') as rpt:
127    _do_report(rpt, licenses)
128    if args.check_conditions:
129      # TODO(aiuto): Read conditions from a file of allowed conditions for
130      # a specified application deployment environment.
131      err = _check_conditions(rpt, licenses, _ALWAYS_ALLOWED_CONDITIONS)
132  if args.copyright_notices:
133    with codecs.open(
134        args.copyright_notices, mode='w', encoding='utf-8') as out:
135      _do_copyright_notices(out, licenses)
136  if args.license_texts:
137    with codecs.open(args.license_texts, mode='w', encoding='utf-8') as out:
138      _do_licenses(out, licenses)
139  return err
140
141
142if __name__ == '__main__':
143  main()
144