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