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 _do_report(out, licenses): 35 """Produce a report showing the set of licenses being used. 36 37 Args: 38 out: file object to write to 39 licenses: list of LicenseInfo objects 40 41 Returns: 42 0 for no restricted licenses. 43 """ 44 45 for lic in licenses: 46 print("lic:", lic) 47 rule = lic['rule'] 48 for kind in lic['license_kinds']: 49 out.write('= %s\n kind: %s\n' % (rule, kind['target'])) 50 out.write(' conditions: %s\n' % kind['conditions']) 51 52 53def _check_conditions(out, licenses, allowed_conditions): 54 """Check that the application does not use any disallowed licenses. 55 56 Args: 57 out: file object to write to 58 licenses: list of LicenseInfo objects 59 allowed_conditions: list of allowed condition names 60 61 Returns: 62 0 for no licenses from outside allowed_conditions. 63 """ 64 err = 0 65 for lic in licenses: # using strange name lic because license is built-in 66 rule = lic['rule'] 67 for kind in lic['license_kinds']: 68 disallowed = [] 69 for condition in kind['conditions']: 70 if condition not in allowed_conditions: 71 disallowed.append(condition) 72 if disallowed: 73 out.write('ERROR: %s\n' % rule) 74 out.write(' kind: %s\n' % kind['target']) 75 out.write(' conditions: %s\n' % kind['conditions']) 76 out.write(' disallowed condition: %s\n' % ','.join(disallowed)) 77 err += 1 78 return err 79 80 81def _do_copyright_notices(out, licenses): 82 for l in licenses: 83 name = l.get('package_name') or '<unknown>' 84 if l.get('package_version'): 85 name = name + "/" + l['package_version'] 86 # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one. 87 out.write('package(%s), copyright(%s)\n' % (name, l['copyright_notice'])) 88 89 90def _do_licenses(out, licenses): 91 for lic in licenses: 92 path = lic['license_text'] 93 with codecs.open(path, encoding='utf-8') as license_file: 94 out.write('= %s\n' % path) 95 out.write(license_file.read()) 96 97 98def main(): 99 parser = argparse.ArgumentParser( 100 description='Demonstraton license compliance checker') 101 102 parser.add_argument('--licenses_info', 103 help='path to JSON file containing all license data') 104 parser.add_argument('--report', default='report', help='Summary report') 105 parser.add_argument('--copyright_notices', 106 help='output file of all copyright notices') 107 parser.add_argument('--license_texts', help='output file of all license files') 108 parser.add_argument('--check_conditions', action='store_true', 109 help='check that the dep only includes allowed license conditions') 110 args = parser.parse_args() 111 112 license_data = _load_license_data(args.licenses_info) 113 target = license_data[0] # we assume only one target for the demo 114 115 top_level_target = target['top_level_target'] 116 dependencies = target['dependencies'] 117 licenses = target['licenses'] 118 119 err = 0 120 with codecs.open(args.report, mode='w', encoding='utf-8') as rpt: 121 _do_report(rpt, licenses) 122 if args.check_conditions: 123 # TODO(aiuto): Read conditions from a file of allowed conditions for 124 # a specified application deployment environment. 125 err = _check_conditions(rpt, licenses, _ALWAYS_ALLOWED_CONDITIONS) 126 if args.copyright_notices: 127 with codecs.open( 128 args.copyright_notices, mode='w', encoding='utf-8') as out: 129 _do_copyright_notices(out, licenses) 130 if args.license_texts: 131 with codecs.open(args.license_texts, mode='w', encoding='utf-8') as out: 132 _do_licenses(out, licenses) 133 return err 134 135 136if __name__ == '__main__': 137 main() 138